UNPKG

opl3

Version:
1,148 lines (1,019 loc) 59.5 kB
var util = require('util'); var extend = require('extend'); function OPL3(){ this.nts = 0; this.dam = 0; this.dvb = 0; this.ryt = 0; this.bd = 0; this.sd = 0; this.tom = 0; this.tc = 0; this.hh = 0; this._new = 0; this.connectionsel = 0; this.vibratoIndex = 0; this.tremoloIndex = 0; this.registers = new Int32Array(0x200); this.channels = [new Array(9), new Array(9)]; this.initOperators(); this.initChannels2op(); this.initChannels4op(); this.initRhythmChannels(); this.initChannels(); this.output = new Int16Array(2); this.outputBuffer = new Float64Array(4); this.outputChannelNumber = 2; } module.exports = OPL3; extend(OPL3.prototype, { read: function(output, seek){ var offset = seek || 0; output = output || this.output; var converterScale = output instanceof Float32Array ? 32768 : 1; do{ var channelOutput, outputChannelNumber; for (outputChannelNumber = 0; outputChannelNumber < 4; outputChannelNumber++){ this.outputBuffer[outputChannelNumber] = 0; } // If _new = 0, use OPL2 mode with 9 channels. If _new = 1, use OPL3 18 channels; for (var array = 0; array < (this._new + 1); array++){ for (var channelNumber = 0; channelNumber < 9; channelNumber++){ // Reads output from each OPL3 channel, and accumulates it in the output buffer: channelOutput = this.channels[array][channelNumber].getChannelOutput(); for (outputChannelNumber = 0; outputChannelNumber < 4; outputChannelNumber++){ this.outputBuffer[outputChannelNumber] += channelOutput[outputChannelNumber]; } } } // Normalizes the output buffer after all channels have been added, // with a maximum of 18 channels, // and multiplies it to get the 16 bit signed output. for (outputChannelNumber = 0; outputChannelNumber < this.outputChannelNumber; outputChannelNumber++){ output[offset + outputChannelNumber] = ((this.outputBuffer[outputChannelNumber] / 18) * 0x7FFF) / converterScale; } // Advances the OPL3-wide vibrato index, which is used by // PhaseGenerator.getPhase() in each Operator. this.vibratoIndex++; if (this.vibratoIndex >= OPL3Data.vibratoTable[this.dvb].length) this.vibratoIndex = 0; // Advances the OPL3-wide tremolo index, which is used by // EnvelopeGenerator.getEnvelope() in each Operator. this.tremoloIndex++; if (this.tremoloIndex >= OPL3Data.tremoloTable[this.dam].length) this.tremoloIndex = 0; offset += this.outputChannelNumber; }while (offset < output.length); return output; }, write: function(array, address, data){ // The OPL3 has two registers arrays, each with adresses ranging // from 0x00 to 0xF5. // This emulator uses one array, with the two original register arrays // starting at 0x00 and at 0x100. var registerAddress = (array << 8) | address; // If the address is out of the OPL3 memory map, returns. if (registerAddress < 0 || registerAddress >= 0x200) return; this.registers[registerAddress] = data; switch (address & 0xe0){ // The first 3 bits masking gives the type of the register by using its base address: // 0x00, 0x20, 0x40, 0x60, 0x80, 0xA0, 0xC0, 0xE0 // When it is needed, we further separate the register type inside each base address, // which is the case of 0x00 and 0xA0. // Through out this emulator we will use the same name convention to // reference a byte with several bit registers. // The name of each bit register will be followed by the number of bits // it occupies inside the byte. // Numbers without accompanying names are unused bits. case 0x00: // Unique registers for the entire OPL3: if (array == 1){ if (address == 0x04) this.update_2_CONNECTIONSEL6(); else if (address == 0x05){ //console.log(array, address, data); this.update_7_NEW1(); } }else if (address == 0x08) this.update_1_NTS1_6(); break; case 0xA0: // 0xBD is a control register for the entire OPL3: if (address == 0xBD){ if (array == 0) this.update_DAM1_DVB1_RYT1_BD1_SD1_TOM1_TC1_HH1(); break; } // Registers for each channel are in A0-A8, B0-B8, C0-C8, in both register arrays. // 0xB0...0xB8 keeps kon,block,fnum(h) for each channel. if ((address & 0xF0) == 0xB0 && address <= 0xB8){ // If the address is in the second register array, adds 9 to the channel number. // The channel number is given by the last four bits, like in A0,...,A8. this.channels[array][address & 0x0F].update_2_KON1_BLOCK3_FNUMH2(); break; } // 0xA0...0xA8 keeps fnum(l) for each channel. if ((address & 0xF0) == 0xA0 && address <= 0xA8) this.channels[array][address&0x0F].update_FNUML8(); break; // 0xC0...0xC8 keeps cha,chb,chc,chd,fb,cnt for each channel: case 0xC0: if (address <= 0xC8) this.channels[array][address & 0x0F].update_CHD1_CHC1_CHB1_CHA1_FB3_CNT1(); break; // Registers for each of the 36 Operators: default: var operatorOffset = address & 0x1F; if (!this.operators[array][operatorOffset]) break; switch (address & 0xE0){ // 0x20...0x35 keeps am,vib,egt,ksr,mult for each operator: case 0x20: this.operators[array][operatorOffset].update_AM1_VIB1_EGT1_KSR1_MULT4(); break; // 0x40...0x55 keeps ksl,tl for each operator: case 0x40: this.operators[array][operatorOffset].update_KSL2_TL6(); break; // 0x60...0x75 keeps ar,dr for each operator: case 0x60: this.operators[array][operatorOffset].update_AR4_DR4(); break; // 0x80...0x95 keeps sl,rr for each operator: case 0x80: this.operators[array][operatorOffset].update_SL4_RR4(); break; // 0xE0...0xF5 keeps ws for each operator: case 0xE0: this.operators[array][operatorOffset].update_5_WS3(); } } }, initOperators: function(){ // The YMF262 has 36 operators: this.operators = [[], []]; for (var array = 0; array < 2; array++){ for (var group = 0; group <= 0x10; group += 8){ for (var offset = 0; offset < 6; offset++){ var baseAddress = (array << 8) | (group + offset); this.operators[array][group + offset] = new Operator(baseAddress, this); } } } // Create specific operators to switch when in rhythm mode: this.highHatOperator = new HighHatOperator(this); this.snareDrumOperator = new SnareDrumOperator(this); this.tomTomOperator = new TomTomOperator(this); this.topCymbalOperator = new TopCymbalOperator(this); // Save operators when they are in non-rhythm mode: // Channel 7: this.highHatOperatorInNonRhythmMode = this.operators[0][0x11]; this.snareDrumOperatorInNonRhythmMode = this.operators[0][0x14]; // Channel 8: this.tomTomOperatorInNonRhythmMode = this.operators[0][0x12]; this.topCymbalOperatorInNonRhythmMode = this.operators[0][0x15]; }, initChannels2op: function(){ // The YMF262 has 18 2-op channels. // Each 2-op channel can be at a serial or parallel operator configuration: this.channels2op = [[], []]; for (var array = 0; array < 2; array++){ for (var channelNumber = 0; channelNumber < 3; channelNumber++){ var baseAddress = (array << 8) | channelNumber; // Channels 1, 2, 3 -> Operator offsets 0x0,0x3; 0x1,0x4; 0x2,0x5 this.channels2op[array][channelNumber] = new Channel2op(baseAddress, this.operators[array][channelNumber], this.operators[array][channelNumber + 0x3], this); // Channels 4, 5, 6 -> Operator offsets 0x8,0xB; 0x9,0xC; 0xA,0xD this.channels2op[array][channelNumber + 3] = new Channel2op(baseAddress + 3, this.operators[array][channelNumber + 0x8], this.operators[array][channelNumber + 0xb], this); // Channels 7, 8, 9 -> Operators 0x10,0x13; 0x11,0x14; 0x12,0x15 this.channels2op[array][channelNumber + 6] = new Channel2op(baseAddress + 6, this.operators[array][channelNumber + 0x10], this.operators[array][channelNumber + 0x13], this); } } }, initChannels4op: function(){ // The YMF262 has 3 4-op channels in each array: this.channels4op = [[], []]; for (var array = 0; array < 2; array++){ for (var channelNumber = 0; channelNumber < 3; channelNumber++){ var baseAddress = (array << 8) | channelNumber; // Channels 1, 2, 3 -> Operators 0x0,0x3,0x8,0xB; 0x1,0x4,0x9,0xC; 0x2,0x5,0xA,0xD; this.channels4op[array][channelNumber] = new Channel4op( baseAddress, this.operators[array][channelNumber], this.operators[array][channelNumber + 0x3], this.operators[array][channelNumber + 0x8], this.operators[array][channelNumber + 0xb], this ); } } }, initRhythmChannels: function(){ this.bassDrumChannel = new BassDrumChannel(this); this.highHatSnareDrumChannel = new HighHatSnareDrumChannel(this); this.tomTomTopCymbalChannel = new TomTomTopCymbalChannel(this); }, initChannels: function(){ // Channel is an abstract class that can be a 2-op, 4-op, rhythm or disabled channel, // depending on the OPL3 configuration at the time. // channels[] inits as a 2-op serial channel array: for (var array = 0; array < 2; array++){ for (var i = 0; i < 9; i++) this.channels[array][i] = this.channels2op[array][i]; } // Unique instance to fill future gaps in the Channel array, // when there will be switches between 2op and 4op mode. this.disabledChannel = new DisabledChannel(this); }, update_1_NTS1_6: function(){ var _1_nts1_6 = this.registers[OPL3Data._1_NTS1_6_Offset]; // Note Selection. This register is used in Channel.updateOperators() implementations, // to calculate the channel´s Key Scale Number. // The value of the actual envelope rate follows the value of // OPL3.nts,Operator.keyScaleNumber and Operator.ksr this.nts = (_1_nts1_6 & 0x40) >> 6; }, update_DAM1_DVB1_RYT1_BD1_SD1_TOM1_TC1_HH1: function(){ var dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 = this.registers[OPL3Data.DAM1_DVB1_RYT1_BD1_SD1_TOM1_TC1_HH1_Offset]; // Depth of amplitude. This register is used in EnvelopeGenerator.getEnvelope(); this.dam = (dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 & 0x80) >> 7; // Depth of vibrato. This register is used in PhaseGenerator.getPhase(); this.dvb = (dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 & 0x40) >> 6; var new_ryt = (dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 & 0x20) >> 5; if (new_ryt != this.ryt){ this.ryt = new_ryt; this.setRhythmMode(); } var new_bd = (dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 & 0x10) >> 4; if (new_bd != this.bd){ this.bd = new_bd; if (this.bd == 1){ this.bassDrumChannel.op1.keyOn(); this.bassDrumChannel.op2.keyOn(); } } var new_sd = (dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 & 0x08) >> 3; if (new_sd != this.sd){ this.sd = new_sd; if (this.sd == 1) this.snareDrumOperator.keyOn(); } var new_tom = (dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 & 0x04) >> 2; if (new_tom != this.tom) { this.tom = new_tom; if (this.tom == 1) this.tomTomOperator.keyOn(); } var new_tc = (dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 & 0x02) >> 1; if (new_tc != this.tc) { this.tc = new_tc; if (this.tc == 1) this.topCymbalOperator.keyOn(); } var new_hh = dam1_dvb1_ryt1_bd1_sd1_tom1_tc1_hh1 & 0x01; if (new_hh != this.hh) { this.hh = new_hh; if (this.hh == 1) this.highHatOperator.keyOn(); } }, update_7_NEW1: function(){ var _7_new1 = this.registers[OPL3Data._7_NEW1_Offset]; // OPL2/OPL3 mode selection. This register is used in // OPL3.read(), OPL3.write() and Operator.getOperatorOutput(); this._new = (_7_new1 & 0x01); if (this._new == 1) this.setEnabledChannels(); this.set4opConnections(); }, setEnabledChannels: function(){ for (var array = 0; array < 2; array++){ for (var i = 0; i < 9; i++){ var baseAddress = this.channels[array][i].channelBaseAddress; this.registers[baseAddress + ChannelData.CHD1_CHC1_CHB1_CHA1_FB3_CNT1_Offset] |= 0xf0; this.channels[array][i].update_CHD1_CHC1_CHB1_CHA1_FB3_CNT1(); } } }, update_2_CONNECTIONSEL6: function(){ // This method is called only if _new is set. var _2_connectionsel6 = this.registers[OPL3Data._2_CONNECTIONSEL6_Offset]; // 2-op/4-op channel selection. This register is used here to configure the OPL3.channels[] array. this.connectionsel = (_2_connectionsel6 & 0x3f); this.set4opConnections(); }, set4opConnections: function(){ // bits 0, 1, 2 sets respectively 2-op channels (1,4), (2,5), (3,6) to 4-op operation. // bits 3, 4, 5 sets respectively 2-op channels (10,13), (11,14), (12,15) to 4-op operation. for (var array = 0; array < 2; ++array){ for (var i = 0; i < 3; ++i){ if (this._new == 1){ var shift = array * 3 + i; var connectionBit = (this.connectionsel >> shift) & 0x01; if (connectionBit == 1){ this.channels[array][i] = this.channels4op[array][i]; this.channels[array][i + 3] = this.disabledChannel; this.channels[array][i].updateChannel(); continue; } } this.channels[array][i] = this.channels2op[array][i]; this.channels[array][i + 3] = this.channels2op[array][i + 3]; this.channels[array][i].updateChannel(); this.channels[array][i + 3].updateChannel(); } } }, setRhythmMode: function(){ var i; if (this.ryt == 1){ this.channels[0][6] = this.bassDrumChannel; this.channels[0][7] = this.highHatSnareDrumChannel; this.channels[0][8] = this.tomTomTopCymbalChannel; this.operators[0][0x11] = this.highHatOperator; this.operators[0][0x14] = this.snareDrumOperator; this.operators[0][0x12] = this.tomTomOperator; this.operators[0][0x15] = this.topCymbalOperator; }else{ for (i = 6; i <= 8; i++) this.channels[0][i] = this.channels2op[0][i]; this.operators[0][0x11] = this.highHatOperatorInNonRhythmMode; this.operators[0][0x14] = this.snareDrumOperatorInNonRhythmMode; this.operators[0][0x12] = this.tomTomOperatorInNonRhythmMode; this.operators[0][0x15] = this.topCymbalOperatorInNonRhythmMode; } for (i = 6; i <= 8; i++) this.channels[0][i].updateChannel(); } }); function Channel(baseAddress, opl){ this.opl = opl; this.channelBaseAddress = baseAddress; this.fnuml = 0; this.fnumh = 0; this.kon = 0; this.block = 0; this.cha = 0; this.chb = 0; this.chc = 0; this.chd = 0; this.fb = 0; this.cnt = 0; this.feedback = [0, 0]; this.toPhase = 4; this.output = new Float64Array(4); } extend(Channel.prototype, { update_2_KON1_BLOCK3_FNUMH2: function(){ var _2_kon1_block3_fnumh2 = this.opl.registers[this.channelBaseAddress + ChannelData._2_KON1_BLOCK3_FNUMH2_Offset]; // Frequency Number (hi-register) and Block. These two registers, together with fnuml, // sets the Channel´s base frequency; this.block = (_2_kon1_block3_fnumh2 & 0x1c) >> 2; this.fnumh = _2_kon1_block3_fnumh2 & 0x03; this.updateOperators(); // Key On. If changed, calls Channel.keyOn() / keyOff(). var newKon = (_2_kon1_block3_fnumh2 & 0x20) >> 5; if (newKon != this.kon){ if (newKon == 1) this.keyOn(); else this.keyOff(); this.kon = newKon; } }, update_FNUML8: function(){ var fnuml8 = this.opl.registers[this.channelBaseAddress + ChannelData.FNUML8_Offset]; // Frequency Number, low register. this.fnuml = fnuml8 & 0xff; this.updateOperators(); }, update_CHD1_CHC1_CHB1_CHA1_FB3_CNT1: function(){ var chd1_chc1_chb1_cha1_fb3_cnt1 = this.opl.registers[this.channelBaseAddress + ChannelData.CHD1_CHC1_CHB1_CHA1_FB3_CNT1_Offset]; this.chd = (chd1_chc1_chb1_cha1_fb3_cnt1 & 0x80) >> 7; this.chc = (chd1_chc1_chb1_cha1_fb3_cnt1 & 0x40) >> 6; this.chb = (chd1_chc1_chb1_cha1_fb3_cnt1 & 0x20) >> 5; this.cha = (chd1_chc1_chb1_cha1_fb3_cnt1 & 0x10) >> 4; this.fb = (chd1_chc1_chb1_cha1_fb3_cnt1 & 0x0e) >> 1; this.cnt = chd1_chc1_chb1_cha1_fb3_cnt1 & 0x01; this.updateOperators(); }, updateChannel: function(){ this.update_2_KON1_BLOCK3_FNUMH2(); this.update_FNUML8(); this.update_CHD1_CHC1_CHB1_CHA1_FB3_CNT1(); }, getInFourChannels: function(channelOutput){ if (this.opl._new == 0){ this.output[0] = this.output[1] = this.output[2] = this.output[3] = channelOutput; }else{ this.output[0] = (this.cha == 1) ? channelOutput : 0; this.output[1] = (this.chb == 1) ? channelOutput : 0; this.output[2] = (this.chc == 1) ? channelOutput : 0; this.output[3] = (this.chd == 1) ? channelOutput : 0; } return this.output; } }); function Channel2op(baseAddress, o1, o2, opl){ Channel.call(this, baseAddress, opl); this.op1 = o1; this.op2 = o2; } util.inherits(Channel2op, Channel); Channel2op.prototype.getChannelOutput = function(){ var channelOutput = 0, op1Output = 0, op2Output = 0; // The feedback uses the last two outputs from // the first operator, instead of just the last one. var feedbackOutput = (this.feedback[0] + this.feedback[1]) / 2; if (this.cnt == 0){ // CNT = 0, the operators are in series, with the first in feedback. if (this.op2.envelopeGenerator.stage == EnvelopeGenerator.Stage.OFF) return this.getInFourChannels(0); op1Output = this.op1.getOperatorOutput(feedbackOutput); channelOutput = this.op2.getOperatorOutput(op1Output * this.toPhase); }else{ // CNT = 1, the operators are in parallel, with the first in feedback. if (this.op1.envelopeGenerator.stage == EnvelopeGenerator.Stage.OFF && this.op2.envelopeGenerator.stage == EnvelopeGenerator.Stage.OFF) return this.getInFourChannels(0); op1Output = this.op1.getOperatorOutput(feedbackOutput); op2Output = this.op2.getOperatorOutput(Operator.noModulator); channelOutput = (op1Output + op2Output) / 2; } this.feedback[0] = this.feedback[1]; this.feedback[1] = (op1Output * ChannelData.feedback[this.fb]) % 1; return this.getInFourChannels(channelOutput); }; Channel2op.prototype.keyOn = function(){ this.op1.keyOn(); this.op2.keyOn(); this.feedback[0] = this.feedback[1] = 0; }; Channel2op.prototype.keyOff = function(){ this.op1.keyOff(); this.op2.keyOff(); }; Channel2op.prototype.updateOperators = function(){ // Key Scale Number, used in EnvelopeGenerator.setActualRates(). var keyScaleNumber = this.block * 2 + ((this.fnumh >> this.opl.nts) & 0x01); var f_number = (this.fnumh << 8) | this.fnuml; this.op1.updateOperator(keyScaleNumber, f_number, this.block); this.op2.updateOperator(keyScaleNumber, f_number, this.block); }; function Channel4op(baseAddress, o1, o2, o3, o4, opl){ Channel.call(this, baseAddress, opl); this.op1 = o1; this.op2 = o2; this.op3 = o3; this.op4 = o4; } util.inherits(Channel4op, Channel); Channel4op.prototype.getChannelOutput = function(){ var channelOutput = 0, op1Output = 0, op2Output = 0, op3Output = 0, op4Output = 0; var secondChannelBaseAddress = this.channelBaseAddress + 3; var secondCnt = this.opl.registers[secondChannelBaseAddress + ChannelData.CHD1_CHC1_CHB1_CHA1_FB3_CNT1_Offset] & 1; var cnt4op = (this.cnt << 1) | secondCnt; var feedbackOutput = (this.feedback[0] + this.feedback[1]) / 2; switch (cnt4op) { case 0: if(this.op4.envelopeGenerator.stage == EnvelopeGenerator.Stage.OFF) return this.getInFourChannels(0); op1Output = this.op1.getOperatorOutput(feedbackOutput); op2Output = this.op2.getOperatorOutput(op1Output * this.toPhase); op3Output = this.op3.getOperatorOutput(op2Output * this.toPhase); channelOutput = this.op4.getOperatorOutput(op3Output * this.toPhase); break; case 1: if (this.op2.envelopeGenerator.stage == EnvelopeGenerator.Stage.OFF && this.op4.envelopeGenerator.stage == EnvelopeGenerator.Stage.OFF) return this.getInFourChannels(0); op1Output = this.op1.getOperatorOutput(feedbackOutput); op2Output = this.op2.getOperatorOutput(op1Output * this.toPhase); op3Output = this.op3.getOperatorOutput(Operator.noModulator); op4Output = this.op4.getOperatorOutput(op3Output * this.toPhase); channelOutput = (op2Output + op4Output) / 2; break; case 2: if (this.op1.envelopeGenerator.stage == EnvelopeGenerator.Stage.OFF && this.op4.envelopeGenerator.stage == EnvelopeGenerator.Stage.OFF) return this.getInFourChannels(0); op1Output = this.op1.getOperatorOutput(feedbackOutput); op2Output = this.op2.getOperatorOutput(Operator.noModulator); op3Output = this.op3.getOperatorOutput(op2Output * this.toPhase); op4Output = this.op4.getOperatorOutput(op3Output * this.toPhase); channelOutput = (op1Output + op4Output) / 2; break; case 3: if (this.op1.envelopeGenerator.stage==EnvelopeGenerator.Stage.OFF && this.op3.envelopeGenerator.stage==EnvelopeGenerator.Stage.OFF && this.op4.envelopeGenerator.stage==EnvelopeGenerator.Stage.OFF) return this.getInFourChannels(0); op1Output = this.op1.getOperatorOutput(feedbackOutput); op2Output = this.op2.getOperatorOutput(Operator.noModulator); op3Output = this.op3.getOperatorOutput(op2Output * this.toPhase); op4Output = this.op4.getOperatorOutput(Operator.noModulator); channelOutput = (op1Output + op3Output + op4Output) / 3; break; } this.feedback[0] = this.feedback[1]; this.feedback[1] = (op1Output * ChannelData.feedback[this.fb]) % 1; return this.getInFourChannels(channelOutput); }; Channel4op.prototype.keyOn = function(){ this.op1.keyOn(); this.op2.keyOn(); this.op3.keyOn(); this.op4.keyOn(); this.feedback[0] = this.feedback[1] = 0; }; Channel4op.prototype.keyOff = function(){ this.op1.keyOff(); this.op2.keyOff(); this.op3.keyOff(); this.op4.keyOff(); }; Channel4op.prototype.updateOperators = function(){ // Key Scale Number, used in EnvelopeGenerator.setActualRates(). var keyScaleNumber = this.block * 2 + ((this.fnumh >> this.opl.nts) & 0x01); var f_number = (this.fnumh << 8) | this.fnuml; this.op1.updateOperator(keyScaleNumber, f_number, this.block); this.op2.updateOperator(keyScaleNumber, f_number, this.block); this.op3.updateOperator(keyScaleNumber, f_number, this.block); this.op4.updateOperator(keyScaleNumber, f_number, this.block); }; function DisabledChannel(opl){ Channel.call(this, 0, opl); this.opl = opl; } util.inherits(DisabledChannel, Channel); DisabledChannel.prototype.getChannelOutput = function(){ return this.getInFourChannels(0); }; DisabledChannel.prototype.keyOn = function(){ }; DisabledChannel.prototype.keyOff = function(){ }; DisabledChannel.prototype.updateOperators = function(){ }; function Operator(baseAddress, opl){ this.opl = opl; this.operatorBaseAddress = baseAddress; this.phaseGenerator = new PhaseGenerator(opl); this.envelopeGenerator = new EnvelopeGenerator(opl); this.envelope = 0; this.am = 0; this.vib = 0; this.ksr = 0; this.egt = 0; this.mult = 0; this.ksl = 0; this.tl = 0; this.ar = 0; this.dr = 0; this.sl = 0; this.rr = 0; this.ws = 0; this.keyScaleNumber = 0; this.f_number = 0; this.block = 0; } Operator.noModulator = 0; extend(Operator.prototype, { update_AM1_VIB1_EGT1_KSR1_MULT4: function(){ var am1_vib1_egt1_ksr1_mult4 = this.opl.registers[this.operatorBaseAddress + OperatorData.AM1_VIB1_EGT1_KSR1_MULT4_Offset]; // Amplitude Modulation. This register is used int EnvelopeGenerator.getEnvelope(); this.am = (am1_vib1_egt1_ksr1_mult4 & 0x80) >> 7; // Vibrato. This register is used in PhaseGenerator.getPhase(); this.vib = (am1_vib1_egt1_ksr1_mult4 & 0x40) >> 6; // Envelope Generator Type. This register is used in EnvelopeGenerator.getEnvelope(); this.egt = (am1_vib1_egt1_ksr1_mult4 & 0x20) >> 5; // Key Scale Rate. Sets the actual envelope rate together with rate and keyScaleNumber. // This register os used in EnvelopeGenerator.setActualAttackRate(). this.ksr = (am1_vib1_egt1_ksr1_mult4 & 0x10) >> 4; // Multiple. Multiplies the Channel.baseFrequency to get the Operator.operatorFrequency. // This register is used in PhaseGenerator.setFrequency(). this.mult = am1_vib1_egt1_ksr1_mult4 & 0x0f; this.phaseGenerator.setFrequency(this.f_number, this.block, this.mult); this.envelopeGenerator.setActualAttackRate(this.ar, this.ksr, this.keyScaleNumber); this.envelopeGenerator.setActualDecayRate(this.dr, this.ksr, this.keyScaleNumber); this.envelopeGenerator.setActualReleaseRate(this.rr, this.ksr, this.keyScaleNumber); }, update_KSL2_TL6: function(){ var ksl2_tl6 = this.opl.registers[this.operatorBaseAddress + OperatorData.KSL2_TL6_Offset]; // Key Scale Level. Sets the attenuation in accordance with the octave. this.ksl = (ksl2_tl6 & 0xc0) >> 6; // Total Level. Sets the overall damping for the envelope. this.tl = ksl2_tl6 & 0x3f; this.envelopeGenerator.setAtennuation(this.f_number, this.block, this.ksl); this.envelopeGenerator.setTotalLevel(this.tl); }, update_AR4_DR4: function(){ var ar4_dr4 = this.opl.registers[this.operatorBaseAddress + OperatorData.AR4_DR4_Offset]; // Attack Rate. this.ar = (ar4_dr4 & 0xf0) >> 4; // Decay Rate. this.dr = ar4_dr4 & 0x0f; this.envelopeGenerator.setActualAttackRate(this.ar, this.ksr, this.keyScaleNumber); this.envelopeGenerator.setActualDecayRate(this.dr, this.ksr, this.keyScaleNumber); }, update_SL4_RR4: function(){ var sl4_rr4 = this.opl.registers[this.operatorBaseAddress + OperatorData.SL4_RR4_Offset]; // Sustain Level. this.sl = (sl4_rr4 & 0xf0) >> 4; // Release Rate. this.rr = sl4_rr4 & 0x0f; this.envelopeGenerator.setActualSustainLevel(this.sl); this.envelopeGenerator.setActualReleaseRate(this.rr, this.ksr, this.keyScaleNumber); }, update_5_WS3: function(){ var _5_ws3 = this.opl.registers[this.operatorBaseAddress + OperatorData._5_WS3_Offset]; this.ws = _5_ws3 & 0x07; }, getOperatorOutput: function(modulator){ if (this.envelopeGenerator.stage == EnvelopeGenerator.Stage.OFF) return 0; var envelopeInDB = this.envelopeGenerator.getEnvelope(this.egt, this.am); this.envelope = Math.pow(10, envelopeInDB / 10); // If it is in OPL2 mode, use first four waveforms only: this.ws = this.ws & ((this.opl._new << 2) + 3); var waveform = OperatorData.waveforms[this.ws]; this.phase = this.phaseGenerator.getPhase(this.vib); return this.getOutput(modulator, this.phase, waveform); }, getOutput: function(modulator, outputPhase, waveform) { outputPhase = (outputPhase + modulator) % 1; if (outputPhase < 0){ outputPhase++; // If the double could not afford to be less than 1: outputPhase %= 1; } var sampleIndex = (outputPhase * OperatorData.waveLength) | 0; return waveform[sampleIndex] * this.envelope; }, keyOn: function(){ if (this.ar > 0){ this.envelopeGenerator.keyOn(); this.phaseGenerator.keyOn(); }else this.envelopeGenerator.stage = EnvelopeGenerator.Stage.OFF; }, keyOff: function(){ this.envelopeGenerator.keyOff(); }, updateOperator: function(ksn, f_num, blk) { this.keyScaleNumber = ksn; this.f_number = f_num; this.block = blk; this.update_AM1_VIB1_EGT1_KSR1_MULT4(); this.update_KSL2_TL6(); this.update_AR4_DR4(); this.update_SL4_RR4(); this.update_5_WS3(); } }); function EnvelopeGenerator(opl){ this.opl = opl; this.stage = EnvelopeGenerator.Stage.OFF; this.actualAttackRate = 0; this.actualDecayRate = 0; this.actualReleaseRate = 0; this.xAttackIncrement = 0; this.xMinimumInAttack = 0; this.dBdecayIncrement = 0; this.dBreleaseIncrement = 0; this.attenuation = 0; this.totalLevel = 0; this.sustainLevel = 0; this.x = this.dBtoX(-96); this.resolutionMaximum = this.dBtoX(-0.1875); this.percentage10 = this.percentageToX(0.1); this.percentage90 = this.percentageToX(0.9); this.envelope = -96; } EnvelopeGenerator.Stage = { ATTACK: 'ATTACK', DECAY: 'DECAY', SUSTAIN: 'SUSTAIN', RELEASE: 'RELEASE', OFF: 'OFF' }; extend(EnvelopeGenerator.prototype, { setActualSustainLevel: function(sl){ // If all SL bits are 1, sustain level is set to -93 dB: if (sl == 0x0f){ this.sustainLevel = -93; return; } // The datasheet states that the SL formula is // sustainLevel = -24*d7 -12*d6 -6*d5 -3*d4, // translated as: this.sustainLevel = -3 * sl; }, setTotalLevel: function(tl) { // The datasheet states that the TL formula is // TL = -(24*d5 + 12*d4 + 6*d3 + 3*d2 + 1.5*d1 + 0.75*d0), // translated as: this.totalLevel = tl * -0.75; }, setAtennuation: function(f_number, block, ksl){ var hi4bits = (f_number >> 6) & 0x0f; switch (ksl){ case 0: this.attenuation = 0; break; case 1: // ~3 dB/Octave this.attenuation = OperatorData.ksl3dBtable[hi4bits][block]; break; case 2: // ~1.5 dB/Octave this.attenuation = OperatorData.ksl3dBtable[hi4bits][block] / 2; break; case 3: // ~6 dB/Octave this.attenuation = OperatorData.ksl3dBtable[hi4bits][block] * 2; } }, setActualAttackRate: function(attackRate, ksr, keyScaleNumber) { // According to the YMF278B manual's OPL3 section, the attack curve is exponential, // with a dynamic range from -96 dB to 0 dB and a resolution of 0.1875 dB // per level. // // This method sets an attack increment and attack minimum value // that creates a exponential dB curve with 'period0to100' seconds in length // and 'period10to90' seconds between 10% and 90% of the curve total level. this.actualAttackRate = this.calculateActualRate(attackRate, ksr, keyScaleNumber) | 0; var period0to100inSeconds = EnvelopeGeneratorData.attackTimeValuesTable[this.actualAttackRate][0] / 1000; var period0to100inSamples = (period0to100inSeconds * OPL3Data.sampleRate) | 0; var period10to90inSeconds = EnvelopeGeneratorData.attackTimeValuesTable[this.actualAttackRate][1] / 1000; var period10to90inSamples = (period10to90inSeconds * OPL3Data.sampleRate) | 0; // The x increment is dictated by the period between 10% and 90%: this.xAttackIncrement = OPL3Data.calculateIncrement(this.percentage10, this.percentage90, period10to90inSeconds); // Discover how many samples are still from the top. // It cannot reach 0 dB, since x is a logarithmic parameter and would be // negative infinity. So we will use -0.1875 dB as the resolution // maximum. // // percentageToX(0.9) + samplesToTheTop*xAttackIncrement = dBToX(-0.1875); -> // samplesToTheTop = (dBtoX(-0.1875) - percentageToX(0.9)) / xAttackIncrement); -> // period10to100InSamples = period10to90InSamples + samplesToTheTop; -> var period10to100inSamples = (period10to90inSamples + (this.resolutionMaximum - this.percentage90) / this.xAttackIncrement) | 0; // Discover the minimum x that, through the attackIncrement value, keeps // the 10%-90% period, and reaches 0 dB at the total period: this.xMinimumInAttack = this.percentage10 - (period0to100inSamples - period10to100inSamples) * this.xAttackIncrement; }, setActualDecayRate: function(decayRate, ksr, keyScaleNumber){ this.actualDecayRate = this.calculateActualRate(decayRate, ksr, keyScaleNumber) | 0; var period10to90inSeconds = EnvelopeGeneratorData.decayAndReleaseTimeValuesTable[this.actualDecayRate][1] / 1000; // Differently from the attack curve, the decay/release curve is linear. // The dB increment is dictated by the period between 10% and 90%: this.dBdecayIncrement = OPL3Data.calculateIncrement(this.percentageToDB(0.1), this.percentageToDB(0.9), period10to90inSeconds); }, setActualReleaseRate: function(releaseRate, ksr, keyScaleNumber){ this.actualReleaseRate = this.calculateActualRate(releaseRate, ksr, keyScaleNumber) | 0; var period10to90inSeconds = EnvelopeGeneratorData.decayAndReleaseTimeValuesTable[this.actualReleaseRate][1] / 1000; this.dBreleaseIncrement = OPL3Data.calculateIncrement(this.percentageToDB(0.1), this.percentageToDB(0.9), period10to90inSeconds); }, calculateActualRate: function(rate, ksr, keyScaleNumber){ var rof = EnvelopeGeneratorData.rateOffset[ksr][keyScaleNumber]; var actualRate = rate * 4 + rof; // If, as an example at the maximum, rate is 15 and the rate offset is 15, // the value would // be 75, but the maximum allowed is 63: if (actualRate > 63) actualRate = 63; return actualRate; }, getEnvelope: function(egt, am){ // The datasheets attenuation values // must be halved to match the real OPL3 output. var envelopeSustainLevel = this.sustainLevel / 2; var envelopeTremolo = OPL3Data.tremoloTable[this.opl.dam][this.opl.tremoloIndex] / 2; var envelopeAttenuation = this.attenuation / 2; var envelopeTotalLevel = this.totalLevel / 2; var envelopeMinimum = -96; var envelopeResolution = 0.1875; var outputEnvelope; // // Envelope Generation // switch (this.stage){ case EnvelopeGenerator.Stage.ATTACK: // Since the attack is exponential, it will never reach 0 dB, so // we´ll work with the next to maximum in the envelope resolution. if (this.envelope < -envelopeResolution && this.xAttackIncrement != -Infinity){ // The attack is exponential. this.envelope = -Math.pow(2, this.x); this.x += this.xAttackIncrement; break; }else{ // It is needed here to explicitly set envelope = 0, since // only the attack can have a period of // 0 seconds and produce an infinity envelope increment. this.envelope = 0; this.stage = EnvelopeGenerator.Stage.DECAY; } case EnvelopeGenerator.Stage.DECAY: // The decay and release are linear. if (this.envelope > envelopeSustainLevel){ this.envelope -= this.dBdecayIncrement; break; }else this.stage = EnvelopeGenerator.Stage.SUSTAIN; case EnvelopeGenerator.Stage.SUSTAIN: // The Sustain stage is mantained all the time of the Key ON, // even if we are in non-sustaining mode. // This is necessary because, if the key is still pressed, we can // change back and forth the state of EGT, and it will release and // hold again accordingly. if (egt == 1) break; else{ if (this.envelope > envelopeMinimum) this.envelope -= this.dBreleaseIncrement; else this.stage = EnvelopeGenerator.Stage.OFF; } break; case EnvelopeGenerator.Stage.RELEASE: // If we have Key OFF, only here we are in the Release stage. // Now, we can turn EGT back and forth and it will have no effect,i.e., // it will release inexorably to the Off stage. if (this.envelope > envelopeMinimum) this.envelope -= this.dBreleaseIncrement; else this.stage = EnvelopeGenerator.Stage.OFF; } // Ongoing original envelope outputEnvelope = this.envelope; //Tremolo if (am == 1) outputEnvelope += envelopeTremolo; //Attenuation outputEnvelope += envelopeAttenuation; //Total Level outputEnvelope += envelopeTotalLevel; return outputEnvelope; }, keyOn: function(){ // If we are taking it in the middle of a previous envelope, // start to rise from the current level: // envelope = - (2 ^ x); -> // 2 ^ x = -envelope -> // x = log2(-envelope); -> var xCurrent = Math.log2(-this.envelope); this.x = xCurrent < this.xMinimumInAttack ? xCurrent : this.xMinimumInAttack; this.stage = EnvelopeGenerator.Stage.ATTACK; }, keyOff: function(){ if (this.stage != EnvelopeGenerator.Stage.OFF) this.stage = EnvelopeGenerator.Stage.RELEASE; }, dBtoX: function(dB){ return Math.log2(-dB); }, percentageToDB: function(percentage){ return Math.log10(percentage) * 10; }, percentageToX: function(percentage){ return this.dBtoX(this.percentageToDB(percentage)); } }); function PhaseGenerator(opl){ this.opl = opl; this.phase = 0; this.phaseIncrement = 0; } extend(PhaseGenerator.prototype, { setFrequency: function(f_number, block, mult){ // This frequency formula is derived from the following equation: // f_number = baseFrequency * pow(2,19) / sampleRate / pow(2,block-1); var baseFrequency = f_number * Math.pow(2, block - 1) * OPL3Data.sampleRate / Math.pow(2, 19); var operatorFrequency = baseFrequency * OperatorData.multTable[mult]; // phase goes from 0 to 1 at // period = (1/frequency) seconds -> // Samples in each period is (1/frequency)*sampleRate = // = sampleRate/frequency -> // So the increment in each sample, to go from 0 to 1, is: // increment = (1-0) / samples in the period -> // increment = 1 / (OPL3Data.sampleRate/operatorFrequency) -> this.phaseIncrement = operatorFrequency / OPL3Data.sampleRate; }, getPhase: function(vib){ if (vib == 1){ // phaseIncrement = (operatorFrequency * vibrato) / sampleRate this.phase += this.phaseIncrement * OPL3Data.vibratoTable[this.opl.dvb][this.opl.vibratoIndex]; }else{ // phaseIncrement = operatorFrequency / sampleRate this.phase += this.phaseIncrement; } this.phase %= 1; return this.phase; }, keyOn: function(){ this.phase = 0; } }); function RhythmChannel(baseAddress, o1, o2, opl){ Channel2op.call(this, baseAddress, o1, o2, opl); } util.inherits(RhythmChannel, Channel2op); RhythmChannel.prototype.getChannelOutput = function(){ var channelOutput = 0, op1Output = 0, op2Output = 0; // Note that, different from the common channel, // we do not check to see if the Operator's envelopes are Off. // Instead, we always do the calculations, // to update the publicly available phase. op1Output = this.op1.getOperatorOutput(Operator.noModulator); op2Output = this.op2.getOperatorOutput(Operator.noModulator); channelOutput = (op1Output + op2Output) / 2; return this.getInFourChannels(channelOutput); }; RhythmChannel.prototype.keyOn = function(){ }; RhythmChannel.prototype.keyOff = function(){ }; function HighHatSnareDrumChannel(opl){ RhythmChannel.call(this, 7, opl.highHatOperator, opl.snareDrumOperator, opl); } util.inherits(HighHatSnareDrumChannel, RhythmChannel); function TomTomTopCymbalChannel(opl){ RhythmChannel.call(this, 8, opl.tomTomOperator, opl.topCymbalOperator, opl); } util.inherits(TomTomTopCymbalChannel, RhythmChannel); function TopCymbalOperator(baseAddress, opl){ if (arguments.length == 1){ opl = baseAddress; baseAddress = 0x15; } Operator.call(this, baseAddress, opl); } util.inherits(TopCymbalOperator, Operator); TopCymbalOperator.prototype.getOperatorOutput = function(modulator, externalPhase){ // The Top Cymbal operator uses his own phase together with the High Hat phase. if (typeof externalPhase == 'undefined') externalPhase = this.opl.highHatOperator.phase * OperatorData.multTable[this.opl.highHatOperator.mult]; var envelopeInDB = this.envelopeGenerator.getEnvelope(this.egt, this.am); this.envelope = Math.pow(10, envelopeInDB / 10); this.phase = this.phaseGenerator.getPhase(this.vib); var waveIndex = (this.ws & ((this.opl._new << 2) + 3)) | 0; var waveform = OperatorData.waveforms[waveIndex]; // Empirically tested multiplied phase for the Top Cymbal: var carrierPhase = (8 * this.phase) % 1; var modulatorPhase = externalPhase; var modulatorOutput = this.getOutput(Operator.noModulator, modulatorPhase, waveform); var carrierOutput = this.getOutput(modulatorOutput, carrierPhase, waveform); var cycles = 4; if ((carrierPhase * cycles) % cycles > 0.1) carrierOutput = 0; return carrierOutput * 2; }; function HighHatOperator(opl){ TopCymbalOperator.call(this, 0x11, opl); } util.inherits(HighHatOperator, TopCymbalOperator); HighHatOperator.prototype.getOperatorOutput = function(modulator){ var topCymbalOperatorPhase = this.opl.topCymbalOperator.phase * OperatorData.multTable[this.opl.topCymbalOperator.mult]; // The sound output from the High Hat resembles the one from // Top Cymbal, so we use the parent method and modifies his output // accordingly afterwards. var operatorOutput = TopCymbalOperator.prototype.getOperatorOutput.call(this, modulator, topCymbalOperatorPhase); if (operatorOutput == 0) operatorOutput = Math.random() * this.envelope; return operatorOutput; }; function SnareDrumOperator(opl){ Operator.call(this, 0x14, opl); } util.inherits(SnareDrumOperator, Operator); SnareDrumOperator.prototype.getOperatorOutput = function(modulator){ if (this.envelopeGenerator.stage == EnvelopeGenerator.Stage.OFF) return 0; var envelopeInDB = this.envelopeGenerator.getEnvelope(this.egt, this.am); this.envelope = Math.pow(10, envelopeInDB / 10); // If it is in OPL2 mode, use first four waveforms only: var waveIndex = (this.ws & ((this.opl._new << 2) + 3)) | 0; var waveform = OperatorData.waveforms[waveIndex]; this.phase = this.opl.highHatOperator.phase * 2; var operatorOutput = this.getOutput(modulator, this.phase, waveform); var noise = Math.random() * this.envelope; if (operatorOutput / this.envelope != 1 && operatorOutput / this.envelope != -1){ if (operatorOutput > 0) operatorOutput = noise; else if (operatorOutput < 0) operatorOutput = -noise; else operatorOutput = 0; } return operatorOutput * 2; }; function TomTomOperator(opl){ Operator.call(this, 0x12, opl); } util.inherits(TomTomOperator, Operator); function BassDrumChannel(opl){ Channel2op.call(this, 6, new Operator(0x10, opl), new Operator(0x13, opl), opl); } util.inherits(BassDrumChannel, Channel2op); BassDrumChannel.prototype.getChannelOutput = function(){ // Bass Drum ignores first operator, when it is in series. if (this.cnt == 1) this.op1.ar = 0; return Channel2op.prototype.getChannelOutput.call(this); }; BassDrumChannel.prototype.keyOn = function(){ }; BassDrumChannel.prototype.keyOff = function(){ }; var OPL3Data = { // OPL3-wide registers offsets: _1_NTS1_6_Offset: 0x08, DAM1_DVB1_RYT1_BD1_SD1_TOM1_TC1_HH1_Offset: 0xbd, _7_NEW1_Offset: 0x105, _2_CONNECTIONSEL6_Offset: 0x104, sampleRate: 49700, // The first array is used when DVB=0 and the second array is used when DVB=1. vibratoTable: [new Float64Array(8192), new Float64Array(8192)], // First array used when AM = 0 and second array used when AM = 1. tremoloTable: [new Float64Array(13432), new Float64Array(13432)], loadVibratoTable: function(vibratoTable){ // According to the YMF262 datasheet, the OPL3 vibrato repetition rate is 6.1 Hz. // According to the YMF278B manual, it is 6.0 Hz. // The information that the vibrato table has 8 levels standing 1024 samples each // was taken from the emulator by Jarek Burczynski and Tatsuyuki Satoh, // with a frequency of 6,06689453125 Hz, what makes sense with the difference // in the information on the datasheets. var semitone = Math.pow(2, 1 / 12); var cent = Math.pow(semitone, 1 / 100); // When dvb=0, the depth is 7 cents, when it is 1, the depth is 14 cents. var DVB0 = Math.pow(cent, 7); var DVB1 = Math.pow(cent, 14); var i; for (i = 0; i < 1024; i++){ vibratoTable[0][i] = vibratoTable[1][i] = 1; } for (; i < 2048; i++){ vibratoTable[0][i] = Math.sqrt(DVB0); vibratoTable[1][i] = Math.sqrt(DVB1); } for (; i < 3072; i++){ vibratoTable[0][i] = DVB0; vibratoTable[1][i] = DVB1; } for (; i < 4096; i++){ vibratoTable[0][i] = Math.sqrt(DVB0); vibratoTable[1][i] = Math.sqrt(DVB1); } for (; i < 5120; i++){ vibratoTable[0][i] = vibratoTable[1][i] = 1; } for (; i < 6144; i++){ vibratoTable[0][