opll2opl
Version:
VGM converter from OPLL to OPLx
267 lines (233 loc) • 7.17 kB
text/typescript
import VGM from "./vgm";
import { InputBuffer, OutputBuffer } from "./buffer";
import PSG2OPL from "./psg2opl";
import OPLL2OPL from "./opll2opl";
import OPLType from "./opl-type";
function toOPLType(type: string): OPLType | null {
switch (type) {
case "opl2":
case "ym3812":
return "ym3812";
case "opl":
case "ym3526":
return "ym3526";
case "y8950":
return "y8950";
case "opl3":
case "ymf262":
return "ymf262";
default:
return null;
}
}
export default class Converter {
private _vgm: VGM;
private _data: InputBuffer;
private _output: OutputBuffer;
private _orgLoopOffset: number; // original loop offset from top of the data.
private _newLoopOffset: number; // new loop offset from top of the data.
private _psg2opl?: PSG2OPL;
private _opll2opl?: OPLL2OPL;
private _opllTo: string;
private _psgTo: string;
private _oplClock: number;
constructor(vgm: VGM, opllTo: string, psgTo: string) {
this._vgm = vgm;
this._orgLoopOffset =
this._vgm.header.offsets.loop - this._vgm.header.offsets.data;
this._newLoopOffset = 0;
this._data = new InputBuffer(vgm.data.buffer);
this._output = new OutputBuffer(new ArrayBuffer(8));
this._opllTo = opllTo;
this._psgTo = psgTo;
this._oplClock = vgm.header.chips.ym2413
? vgm.header.chips.ym2413.clock
: 3579545;
const psgToType = toOPLType(this._psgTo);
if (psgToType) {
this._psg2opl = new PSG2OPL(
psgToType,
vgm.header.chips.ay8910 ? vgm.header.chips.ay8910.clock : 3579545 / 2,
this._oplClock
);
}
const opllToType = toOPLType(this._opllTo);
if (opllToType) {
this._opll2opl = new OPLL2OPL(
opllToType,
vgm.header.chips.ym2413 ? vgm.header.chips.ym2413.clock : 3579545,
this._oplClock
);
}
}
private _processGameGearPsg() {
const d = this._data.readByte();
this._output.writeByte(d);
}
private _processSn76489() {
const d = this._data.readByte();
this._output.writeByte(d);
}
private _processYm2413() {
if (!this._opll2opl) return;
this._initializeOPL3();
const a = this._data.readByte();
const d = this._data.readByte();
const data = this._opll2opl.interpret(a, d);
for (let i = 0; i < data.length; i++) {
const { a, d } = data[i];
this._output.writeByte(this._opll2opl.command);
this._output.writeByte(a);
this._output.writeByte(d);
}
}
_opl3initialized = false;
private _initializeOPL3() {
if (this._opllTo === "ymf262") {
if (!this._opl3initialized) {
this._output.writeByte(0x5f);
this._output.writeByte(0x05);
this._output.writeByte(0x01);
this._opl3initialized = true;
}
}
}
private _processAY8910() {
if (!this._psg2opl) return;
this._initializeOPL3();
const a = this._data.readByte();
const d = this._data.readByte();
const data = this._psg2opl.interpret(a, d);
for (let i = 0; i < data.length; i++) {
const { a, d } = data[i];
this._output.writeByte(this._psg2opl.command);
this._output.writeByte(a);
this._output.writeByte(d);
}
}
private _processCommon() {
const a = this._data.readByte();
const d = this._data.readByte();
this._output.writeByte(a);
this._output.writeByte(d);
}
private _processCommon2() {
const p = this._data.readByte();
const a = this._data.readByte();
const d = this._data.readByte();
this._output.writeByte(p);
this._output.writeByte(a);
this._output.writeByte(d);
}
private _processSeekPcmDataBank() {
this._output.writeDword(this._data.readDword());
}
private _processDataBlock() {
if (this._data.readByte() != 0x66) {
throw new Error();
}
this._output.writeByte(0x66);
const type = this._data.readByte();
const size = this._data.readDword();
this._output.writeByte(type);
this._output.writeDword(size);
for (let i = 0; i < size; i++) {
this._output.writeByte(this._data.readByte());
}
}
private _detectNewLoopOffset() {
if (0 <= this._orgLoopOffset && this._newLoopOffset === 0) {
if (this._orgLoopOffset <= this._data.readOffset) {
this._newLoopOffset = this._output.writeOffset;
}
}
}
private _buildVGM() {
const dataLength = this._output.writeOffset;
const vgm = new VGM(this._vgm.header, this._output.buffer);
vgm.header.version = 0x171;
vgm.header.offsets.data = 0x100;
if (this._newLoopOffset) {
vgm.header.offsets.loop = vgm.header.offsets.data + this._newLoopOffset;
} else {
vgm.header.offsets.loop = 0;
}
vgm.header.offsets.eof = vgm.header.offsets.data + dataLength;
if (this._opllTo === "none") {
vgm.header.chips.ym2413 = undefined;
} else if (this._opll2opl) {
vgm.header.chips[this._opll2opl.type] = {
clock: this._opll2opl.clock,
};
vgm.header.chips.ym2413 = undefined;
}
if (this._psgTo === "none") {
vgm.header.chips.ay8910 = undefined;
} else if (this._psg2opl) {
vgm.header.chips[this._psg2opl.type] = {
clock: this._psg2opl.clock,
};
vgm.header.chips.ay8910 = undefined;
}
return vgm.build();
}
convert() {
while (!this._data.eod) {
const d = this._data.readByte();
if (d == 0x67) {
this._output.writeByte(d);
this._processDataBlock();
} else if (d == 0x61) {
this._output.writeByte(d);
this._output.writeWord(this._data.readWord());
} else if (d == 0x62) {
this._output.writeByte(d);
} else if (d == 0x63) {
this._output.writeByte(d);
} else if (d == 0x4f) {
this._output.writeByte(d);
this._processGameGearPsg();
} else if (d == 0x50) {
this._output.writeByte(d);
this._processSn76489();
} else if (d == 0x51) {
if (this._opll2opl) {
this._processYm2413();
} else {
this._output.writeByte(d);
this._processCommon();
}
} else if (0x52 <= d && d <= 0x5f) {
this._output.writeByte(d);
this._processCommon();
} else if (d == 0xa0) {
if (this._psg2opl) {
this._processAY8910();
} else {
this._output.writeByte(d);
this._processCommon();
}
} else if (0xb0 <= d && d <= 0xbf) {
this._output.writeByte(d);
this._processCommon();
} else if (0xd0 <= d && d <= 0xd6) {
this._output.writeByte(d);
this._processCommon2();
} else if (d == 0xe0) {
this._output.writeByte(d);
this._processSeekPcmDataBank();
} else if (0x70 <= d && d <= 0x7f) {
this._output.writeByte(d);
} else if (0x80 <= d && d <= 0x8f) {
this._output.writeByte(d);
} else if (d == 0x66) {
this._output.writeByte(d);
break;
} else {
throw new Error("Unsupported command: 0x" + d.toString(16));
}
this._detectNewLoopOffset();
}
return this._buildVGM();
}
}