UNPKG

opll2opl

Version:
188 lines (163 loc) 4.66 kB
import OPLType from "./opl-type"; function getModOffset(ch: number) { return 8 * Math.floor(ch / 3) + (ch % 3); } function type2cmd(type: OPLType) { switch (type) { case "ym3526": return 0x5b; case "y8950": return 0x5c; case "ymf262": return 0x5f; case "ym3812": default: return 0x5a; } } export default class PSGToOPL { _regs = new Uint8Array(256).fill(0); _oplRegs = new Uint8Array(256).fill(0); _type: OPLType; _psgClock: number; _oplClock: number; _command: number; constructor(type: OPLType, psgClock: number, oplClock: number) { this._type = type; this._psgClock = psgClock; this._oplClock = oplClock; this._command = type2cmd(this._type); } get type() { return this._type; } get clock() { return this._type === "ymf262" ? this._oplClock * 4 : this._oplClock; } get command() { return this._command; } _updateFreq(ch: number, freq: number) { let fnum = Math.floor((freq << 19) / (this._oplClock / 72)); let blk = 1; while (fnum > 1023) { fnum >>= 1; blk++; } if (blk > 7) blk = 7; return [ { a: 0xb0 + ch, d: 0x20 | ((blk & 7) << 2) | ((fnum >> 8) & 3) }, { a: 0xa0 + ch, d: fnum & 0xff }, ]; } _updateNoiseFreq(np: number) { const fnum = 1024 / (np + 1) - 1; const blk = 7; const res = []; for (let ch = 3; ch < 6; ch++) { res.push({ a: 0xb0 + ch, d: 0x20 | ((blk & 7) << 2) | ((fnum >> 8) & 3), }); res.push({ a: 0xa0 + ch, d: fnum & 0xff }); } return res; } _initialized = false; _setupVoice() { let res: { a: number; d: number }[] = []; // TONE for (let ch = 0; ch < 3; ch++) { const moff = getModOffset(ch); const coff = moff + 3; res = res.concat([ { a: 0x20 + moff, d: 0x02 }, // ML(mod) { a: 0x20 + coff, d: 0x01 }, // ML(car) { a: 0x40 + moff, d: 0x1b }, // TL(mod) { a: 0x40 + coff, d: 0x3f }, // TL(car) { a: 0x60 + moff, d: 0xf0 }, { a: 0x60 + coff, d: 0xf0 }, { a: 0x80 + moff, d: 0x00 }, { a: 0x80 + coff, d: 0x00 }, { a: 0xc0 + ch, d: this._type === "ymf262" ? 0xfe : 0x0e }, { a: 0xe0 + ch, d: 0x00 }, ]); } // NOISE for (let ch = 3; ch < 6; ch++) { const moff = getModOffset(ch); const coff = moff + 3; res = res.concat([ { a: 0x20 + moff, d: 0x0f }, // ML(mod) { a: 0x20 + coff, d: 0x0f }, // ML(car) { a: 0x40 + moff, d: 0x04 }, // TL(mod) { a: 0x40 + coff, d: 0x3f }, // TL(car) { a: 0x60 + moff, d: 0xf0 }, { a: 0x60 + coff, d: 0xf0 }, { a: 0x80 + moff, d: 0x00 }, { a: 0x80 + coff, d: 0x00 }, { a: 0xc0 + ch, d: this._type === "ymf262" ? 0xfe : 0x0e }, { a: 0xe0 + ch, d: 0x00 }, ]); } return res; } _updateTone(ch: number) { let res: { a: number; d: number }[] = []; if (!this._initialized) { res = this._setupVoice(); this._initialized = true; } const t = ((1 << ch) & this._regs[0x7]) === 0; const n = ((8 << ch) & this._regs[0x7]) === 0; const v = this._regs[0x08 + ch]; const vol = v & 0x10 ? 0 : v & 0xf; const tl = [63, 62, 56, 52, 46, 42, 36, 32, 28, 24, 20, 16, 12, 8, 4, 0][ vol & 0xf ]; const coff = getModOffset(ch) + 3; if (t) { res.push({ a: 0x40 + coff, d: tl }); } else { res.push({ a: 0x40 + coff, d: 0x3f }); } const coff2 = getModOffset(ch + 3) + 3; if (n) { res.push({ a: 0x40 + coff2, d: tl }); } else { res.push({ a: 0x40 + coff2, d: 0x3f }); } return res; } _interpret(a: number, d: number): { a: number; d: number }[] { this._regs[a & 0xff] = d & 0xff; if (a <= 0x05) { const ch = a >> 1; const tp = (this._regs[ch * 2 + 1] << 8) | this._regs[ch * 2]; const freq = this._psgClock / (16 * tp); return this._updateFreq(ch, freq); } if (0x08 <= a && a <= 0x0a) { return this._updateTone(a - 0x08); } if (a === 0x06) { const np = this._regs[0x06] & 0x1f; return this._updateNoiseFreq(np); } if (a === 0x07) { return this._updateTone(0) .concat(this._updateTone(1)) .concat(this._updateTone(2)); } return []; } interpret(a: number, d: number) { let res: { a: number; d: number }[] = []; res = res.concat(this._interpret(a, d)); res = res.filter(({ a, d }) => this._oplRegs[a] !== d); res.forEach(({ a, d }) => { this._oplRegs[a] = d; }); return res; } }