vgm-conv
Version:
Chip-type and clock converter for VGM.
259 lines (258 loc) • 9.29 kB
JavaScript
"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;