UNPKG

vgm-conv

Version:
259 lines (258 loc) 9.29 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.SN76489ToAY8910Converter = void 0; const vgm_converter_1 = require("./vgm-converter"); const vgm_parser_1 = require("vgm-parser"); const vgm_write_data_buffer_1 = __importDefault(require("./vgm-write-data-buffer")); const voltbl = [15, 14, 14, 13, 12, 12, 11, 10, 10, 9, 8, 8, 7, 6, 6, 0]; class SN76489ToAY8910Converter extends vgm_converter_1.VGMConverter { _buf = new vgm_write_data_buffer_1.default(256, 1); _freq = new Uint16Array(4); _ch = 0; // channel number latched _type = 0; // register type latched _atts = [15, 15, 15, 15]; // channel attenuations _mixChannel = 2; // the index of the channel which shared with tone and noise. _mixResolver = "mix"; _periodicNoiseAssignment = "tone"; _periodicNoisePitchShift = 4; _periodicNoiseAttenuation = 0; _whiteNoiseAttenuation = 0; _periodic = false; _noiseFreq = 0; _noisePitchMap = [7, 15, 31]; _channelAttenuationMap = [0, 0, 0, 0]; constructor(from, to, opts) { super(from, { chip: "ay8910", index: from.index, clock: 1 / 2, relativeClock: true }); if (opts.mixChannel != null) { if (opts.mixChannel == "none") { this._mixChannel = -1; } else { this._mixChannel = Math.min(opts.mixChannel, 2); } } this._mixResolver = opts.mixResolver || this._mixResolver; if (opts.periodicNoiseAssignment != null) { this._periodicNoiseAssignment = opts.periodicNoiseAssignment; } if (opts.periodicNoisePitchShift != null) { this._periodicNoisePitchShift = opts.periodicNoisePitchShift; } if (opts.noisePitchMap != null) { this._noisePitchMap = opts.noisePitchMap; } if (opts.channelAttenuationMap != null) { this._channelAttenuationMap = opts.channelAttenuationMap; } if (opts.whiteNoiseAttenuation != null) { this._whiteNoiseAttenuation = opts.whiteNoiseAttenuation; } if (opts.periodicNoiseAttenuation != null) { this._periodicNoiseAttenuation = opts.periodicNoiseAttenuation; } } _y(a, d, optimize = true) { const index = this.from.index; const addr = index === 0 ? a : a | 0x80; this._buf.push(new vgm_parser_1.VGMWriteDataCommand({ target: vgm_parser_1.VGMWriteDataTargetId.ay8910, index, port: 0, addr, data: d }), optimize); } getInitialCommands() { this._y(7, 0x38); switch (this._periodicNoiseAssignment) { case "env.saw": this._y(13, 8); break; case "env.tri": this._y(13, 14); break; } return this._buf.commit(); } _updateSharedChannel() { let noiseChannel = this._mixChannel; let enableTone = this._atts[noiseChannel] != 0xf; let enableNoise = this._atts[3] != 0xf; let att; if (enableTone && enableNoise) { switch (this._mixResolver) { case "noise": enableTone = false; break; case "tone": enableNoise = false; att = this._atts[noiseChannel]; break; default: break; } } if (enableTone && enableNoise) { att = Math.min(this._atts[noiseChannel], this._atts[3]); } else if (enableNoise) { att = this._atts[3]; } else { att = this._atts[noiseChannel]; } let env = false; if (this._periodic) { if (this._noiseFreq == 3) { if (enableNoise) { att = Math.max(0, Math.min(15, att + this._periodicNoiseAttenuation)); switch (this._periodicNoiseAssignment) { case "none": enableNoise = false; break; case "noise": break; case "mix": enableTone = true; break; case "env.tri": case "env.saw": env = true; enableTone = enableNoise = false; break; case "tone": default: enableTone = true; enableNoise = false; break; } } } } else { if (enableNoise) { att = Math.max(0, Math.min(15, att + this._whiteNoiseAttenuation)); } } const toneMask = enableTone ? 0 : (1 << noiseChannel); const noiseMask = enableNoise ? (7 & ~(1 << noiseChannel)) : 7; this._y(7, noiseMask << 3 | toneMask); this._y(8 + noiseChannel, env ? 16 : voltbl[att]); } _updateAttenuation(ch, rawAtt) { const att = rawAtt == 0xf ? 0xf : Math.max(0, Math.min(15, rawAtt + this._channelAttenuationMap[ch])); this._atts[ch] = att; if (0 <= this._mixChannel) { if (ch === this._mixChannel || ch == 3) { this._updateSharedChannel(); } else { this._y(8 + ch, voltbl[att]); } } else { if (ch < 3) { this._y(8 + ch, voltbl[att]); } } } _updateNoise(data) { const periodic = (data & 4) ? false : true; const noiseFreq = (data & 3); if (this._periodic != periodic || this._noiseFreq != noiseFreq) { this._periodic = periodic; this._noiseFreq = noiseFreq; this._updateSharedChannel(); } if ((data & 3) == 3) { this._y(6, this._freq[2] & 31); } else { this._y(6, this._noisePitchMap[data & 3]); } } _updateFreq(ch, optimize = true) { if (ch < 3) { let freq = this._freq[ch]; if (ch == this._mixChannel && this._noiseFreq == 3) { if (this._atts[3] != 0xf && (this._atts[ch] == 0x0f || this._mixResolver == "noise")) { freq = Math.min(0xfff, this._freq[2] << this._periodicNoisePitchShift); let mfreq = null; switch (this._periodicNoiseAssignment) { case "env.tri": mfreq = (freq >> 5); break; case "env.saw": { mfreq = (freq >> 4); break; } } if (mfreq != null) { this._y(11, mfreq & 0xff, optimize); this._y(12, mfreq >> 8, optimize); } } } this._y(ch * 2, freq & 0xff, optimize); this._y(ch * 2 + 1, freq >> 8, optimize); } if (ch != this._mixChannel) { this._updateFreq(this._mixChannel, optimize); } } _convert(cmd) { const { data } = cmd; if (data & 0x80) { const ch = (data >> 5) & 3; const type = (data >> 4) & 1; this._ch = ch; this._type = type; if (type) { this._updateAttenuation(ch, data & 0xf); } else { if (ch < 3) { this._freq[ch] = (this._freq[ch] & 0x3f0) | (data & 0xf); } else { this._updateNoise(data); } this._updateFreq(ch); } } else { const ch = this._ch; const type = this._type; if (type) { this._updateAttenuation(ch, data & 0xf); } else { if (ch < 3) { this._freq[ch] = ((data & 0x3f) << 4) | (this._freq[ch] & 0xf); } else { this._updateNoise(data); } this._updateFreq(ch); } } return this._buf.commit(); } convertCommand(cmd) { if (cmd instanceof vgm_parser_1.VGMWriteDataCommand && cmd.chip === "sn76489" && cmd.index == this.from.index) { return this._convert(cmd); } return [cmd]; } getLoopCommands() { // Update all frequency registers on loop to avoid wrong optimization. for (let ch = 0; ch < 3; ch++) { this._updateFreq(ch, false); } return this._buf.commit(); } } exports.SN76489ToAY8910Converter = SN76489ToAY8910Converter;