Beruflich Dokumente
Kultur Dokumente
* File: OPL3.java
* Software implementation of the Yamaha YMF262 sound generator.
* Copyright (C) 2008 Robson Cozendey <robson@cozendey.com>
*
*
* Version 1.0.6
*
*/
package com.cozendey.opl3;
static int nts, dam, dvb, ryt, bd, sd, tom, tc, hh, _new, connectionsel;
static int vibratoIndex, tremoloIndex;
// If _new = 0, use OPL2 mode with 9 channels. If _new = 1, use OPL3 18 channels;
for(int array=0; array < (_new + 1); array++)
for(int channelNumber=0; channelNumber < 9; channelNumber++) {
// Reads output from each OPL3 channel, and accumulates it in the output buffer:
channelOutput = channels[array][channelNumber].getChannelOutput();
for(int outputChannelNumber=0; outputChannelNumber<4; outputChannelNumber++)
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(int outputChannelNumber=0; outputChannelNumber<4; outputChannelNumber++)
output[outputChannelNumber] =
(short)(outputBuffer[outputChannelNumber] / 18 * 0x7FFF);
return output;
}
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)
update_2_CONNECTIONSEL6();
else if(address==0x05)
update_7_NEW1();
}
else if(address==0x08) update_1_NTS1_6();
break;
case 0xA0:
// 0xBD is a control register for the entire OPL3:
if(address==0xBD) {
if(array==0)
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.
channels[array][address&0x0F].update_2_KON1_BLOCK3_FNUMH2();
break;
}
// 0xA0...0xA8 keeps fnum(l) for each channel.
if( (address&0xF0) == 0xA0 && address <= 0xA8)
channels[array][address&0x0F].update_FNUML8();
break;
// 0xC0...0xC8 keeps cha,chb,chc,chd,fb,cnt for each channel:
case 0xC0:
if(address <= 0xC8)
channels[array][address&0x0F].update_CHD1_CHC1_CHB1_CHA1_FB3_CNT1();
break;
public OPL3() {
nts = dam = dvb = ryt = bd = sd = tom = tc = hh = _new = connectionsel = 0;
vibratoIndex = tremoloIndex = 0;
channels = new Channel[2][9];
initOperators();
initChannels2op();
initChannels4op();
initRhythmChannels();
initChannels();
}
// 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(int array=0; array<2; array++)
for(int i=0; i<3; i++) {
if(_new == 1) {
int shift = array*3 + i;
int connectionBit = (connectionsel >> shift) & 0x01;
if(connectionBit == 1) {
channels[array][i] = channels4op[array][i];
channels[array][i+3] = disabledChannel;
channels[array][i].updateChannel();
continue;
}
}
channels[array][i] = channels2op[array][i];
channels[array][i+3] = channels2op[array][i+3];
channels[array][i].updateChannel();
channels[array][i+3].updateChannel();
}
}
//
// Channels
//
double[] feedback;
int fnuml, fnumh, kon, block, cha, chb, chc, chd, fb, cnt;
void update_2_KON1_BLOCK3_FNUMH2() {
// Frequency Number (hi-register) and Block. These two registers, together with fnuml,
// sets the Channel´s base frequency;
block = (_2_kon1_block3_fnumh2 & 0x1C) >> 2;
fnumh = _2_kon1_block3_fnumh2 & 0x03;
updateOperators();
void update_FNUML8() {
int fnuml8 = OPL3.registers[channelBaseAddress+ChannelData.FNUML8_Offset];
// Frequency Number, low register.
fnuml = fnuml8&0xFF;
updateOperators();
}
void update_CHD1_CHC1_CHB1_CHA1_FB3_CNT1() {
int chd1_chc1_chb1_cha1_fb3_cnt1 =
OPL3.registers[channelBaseAddress+ChannelData.CHD1_CHC1_CHB1_CHA1_FB3_CNT1_Offset];
chd = (chd1_chc1_chb1_cha1_fb3_cnt1 & 0x80) >> 7;
chc = (chd1_chc1_chb1_cha1_fb3_cnt1 & 0x40) >> 6;
chb = (chd1_chc1_chb1_cha1_fb3_cnt1 & 0x20) >> 5;
cha = (chd1_chc1_chb1_cha1_fb3_cnt1 & 0x10) >> 4;
fb = (chd1_chc1_chb1_cha1_fb3_cnt1 & 0x0E) >> 1;
cnt = chd1_chc1_chb1_cha1_fb3_cnt1 & 0x01;
updateOperators();
}
void updateChannel() {
update_2_KON1_BLOCK3_FNUMH2();
update_FNUML8();
update_CHD1_CHC1_CHB1_CHA1_FB3_CNT1();
}
if( OPL3._new==0)
output[0] = output[1] = output[2] = output[3] = channelOutput;
else {
output[0] = (cha==1) ? channelOutput : 0;
output[1] = (chb==1) ? channelOutput : 0;
output[2] = (chc==1) ? channelOutput : 0;
output[3] = (chd==1) ? channelOutput : 0;
}
return output;
}
double[] getChannelOutput() {
double channelOutput = 0, op1Output = 0, op2Output = 0;
double[] output;
// The feedback uses the last two outputs from
// the first operator, instead of just the last one.
double feedbackOutput = (feedback[0] + feedback[1]) / 2;
switch(cnt) {
// CNT = 0, the operators are in series, with the first in feedback.
case 0:
if(op2.envelopeGenerator.stage==EnvelopeGenerator.Stage.OFF)
return getInFourChannels(0);
op1Output = op1.getOperatorOutput(feedbackOutput);
channelOutput = op2.getOperatorOutput(op1Output*toPhase);
break;
// CNT = 1, the operators are in parallel, with the first in feedback.
case 1:
if(op1.envelopeGenerator.stage==EnvelopeGenerator.Stage.OFF &&
op2.envelopeGenerator.stage==EnvelopeGenerator.Stage.OFF)
return getInFourChannels(0);
op1Output = op1.getOperatorOutput(feedbackOutput);
op2Output = op2.getOperatorOutput(Operator.noModulator);
channelOutput = (op1Output + op2Output) / 2;
}
feedback[0] = feedback[1];
feedback[1] = (op1Output * ChannelData.feedback[fb])%1;
output = getInFourChannels(channelOutput);
return output;
}
@Override
public String toString() {
StringBuffer str = new StringBuffer();
return str.toString();
}
}
class Channel4op extends Channel {
Operator op1, op2, op3, op4;
Channel4op (int baseAddress, Operator o1, Operator o2, Operator o3, Operator o4) {
super(baseAddress);
op1 = o1;
op2 = o2;
op3 = o3;
op4 = o4;
}
double[] getChannelOutput() {
double channelOutput = 0,
op1Output = 0, op2Output = 0, op3Output = 0, op4Output = 0;
double[] output;
switch(cnt4op) {
case 0:
if(op4.envelopeGenerator.stage==EnvelopeGenerator.Stage.OFF)
return getInFourChannels(0);
op1Output = op1.getOperatorOutput(feedbackOutput);
op2Output = op2.getOperatorOutput(op1Output*toPhase);
op3Output = op3.getOperatorOutput(op2Output*toPhase);
channelOutput = op4.getOperatorOutput(op3Output*toPhase);
break;
case 1:
if(op2.envelopeGenerator.stage==EnvelopeGenerator.Stage.OFF &&
op4.envelopeGenerator.stage==EnvelopeGenerator.Stage.OFF)
return getInFourChannels(0);
op1Output = op1.getOperatorOutput(feedbackOutput);
op2Output = op2.getOperatorOutput(op1Output*toPhase);
op3Output = op3.getOperatorOutput(Operator.noModulator);
op4Output = op4.getOperatorOutput(op3Output*toPhase);
op1Output = op1.getOperatorOutput(feedbackOutput);
op2Output = op2.getOperatorOutput(Operator.noModulator);
op3Output = op3.getOperatorOutput(op2Output*toPhase);
op4Output = op4.getOperatorOutput(op3Output*toPhase);
op1Output = op1.getOperatorOutput(feedbackOutput);
op2Output = op2.getOperatorOutput(Operator.noModulator);
op3Output = op3.getOperatorOutput(op2Output*toPhase);
op4Output = op4.getOperatorOutput(Operator.noModulator);
feedback[0] = feedback[1];
feedback[1] = (op1Output * ChannelData.feedback[fb])%1;
output = getInFourChannels(channelOutput);
return output;
}
@Override
public String toString() {
StringBuffer str = new StringBuffer();
return str.toString();
}
}
// There's just one instance of this class, that fills the eventual gaps in the Channel array;
class DisabledChannel extends Channel {
DisabledChannel() {
super(0);
}
double[] getChannelOutput() { return getInFourChannels(0); }
protected void keyOn() { }
protected void keyOff() { }
protected void updateOperators() { }
}
//
// Operators
//
class Operator {
PhaseGenerator phaseGenerator;
EnvelopeGenerator envelopeGenerator;
int operatorBaseAddress;
int am, vib, ksr, egt, mult, ksl, tl, ar, dr, sl, rr, ws;
int keyScaleNumber, f_number, block;
Operator(int baseAddress) {
operatorBaseAddress = baseAddress;
phaseGenerator = new PhaseGenerator();
envelopeGenerator = new EnvelopeGenerator();
envelope = 0;
am = vib = ksr = egt = mult = ksl = tl = ar = dr = sl = rr = ws = 0;
keyScaleNumber = f_number = block = 0;
}
void update_AM1_VIB1_EGT1_KSR1_MULT4() {
void update_KSL2_TL6() {
// Key Scale Level. Sets the attenuation in accordance with the octave.
ksl = (ksl2_tl6 & 0xC0) >> 6;
// Total Level. Sets the overall damping for the envelope.
tl = ksl2_tl6 & 0x3F;
void update_AR4_DR4() {
// Attack Rate.
ar = (ar4_dr4 & 0xF0) >> 4;
// Decay Rate.
dr = ar4_dr4 & 0x0F;
void update_SL4_RR4() {
// Sustain Level.
sl = (sl4_rr4 & 0xF0) >> 4;
// Release Rate.
rr = sl4_rr4 & 0x0F;
envelopeGenerator.setActualSustainLevel(sl);
envelopeGenerator.setActualReleaseRate(rr, ksr, keyScaleNumber);
}
void update_5_WS3() {
int _5_ws3 = OPL3.registers[operatorBaseAddress+OperatorData._5_WS3_Offset];
ws = _5_ws3 & 0x07;
}
phase = phaseGenerator.getPhase(vib);
@Override
public String toString() {
StringBuffer str = new StringBuffer();
return str.toString();
}
//
// Envelope Generator
//
class EnvelopeGenerator {
final static double[] INFINITY = null;
enum Stage {ATTACK,DECAY,SUSTAIN,RELEASE,OFF};
Stage stage;
int actualAttackRate, actualDecayRate, actualReleaseRate;
double xAttackIncrement, xMinimumInAttack;
double dBdecayIncrement;
double dBreleaseIncrement;
double attenuation, totalLevel, sustainLevel;
double x, envelope;
EnvelopeGenerator() {
stage = Stage.OFF;
actualAttackRate = actualDecayRate = actualReleaseRate = 0;
xAttackIncrement = xMinimumInAttack = 0;
dBdecayIncrement = 0;
dBreleaseIncrement = 0;
attenuation = totalLevel = sustainLevel = 0;
x = dBtoX(-96);
envelope = -96;
}
// 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.
double outputEnvelope;
//
// Envelope Generation
//
switch(stage) {
case 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(envelope<-envelopeResolution && xAttackIncrement != -EnvelopeGeneratorData.INFINITY) {
// The attack is exponential.
envelope = -Math.pow(2,x);
x += 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.
envelope = 0;
stage = Stage.DECAY;
}
case DECAY:
// The decay and release are linear.
if(envelope>envelopeSustainLevel) {
envelope -= dBdecayIncrement;
break;
}
else
stage = Stage.SUSTAIN;
case 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(envelope > envelopeMinimum)
envelope -= dBreleaseIncrement;
else stage = Stage.OFF;
}
break;
case 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(envelope > envelopeMinimum)
envelope -= dBreleaseIncrement;
else stage = Stage.OFF;
}
//Tremolo
if(am == 1) outputEnvelope += envelopeTremolo;
//Attenuation
outputEnvelope += envelopeAttenuation;
//Total Level
outputEnvelope += envelopeTotalLevel;
return outputEnvelope;
}
void keyOn() {
// 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); ->
double xCurrent = OperatorData.log2(-envelope);
x = xCurrent < xMinimumInAttack ? xCurrent : xMinimumInAttack;
stage = Stage.ATTACK;
}
void keyOff() {
if(stage != Stage.OFF) stage = Stage.RELEASE;
}
@Override
public String toString() {
StringBuffer str = new StringBuffer();
str.append("Envelope Generator: \n");
double attackPeriodInSeconds = EnvelopeGeneratorData.attackTimeValuesTable[actualAttackRate][0]/1000d;
str.append(String.format("\tATTACK %f s, rate %d. \n", attackPeriodInSeconds, actualAttackRate));
double decayPeriodInSeconds = EnvelopeGeneratorData.decayAndReleaseTimeValuesTable[actualDecayRate][0]/1000d;
str.append(String.format("\tDECAY %f s, rate %d. \n",decayPeriodInSeconds, actualDecayRate));
str.append(String.format("\tSL %f dB. \n", sustainLevel));
double releasePeriodInSeconds = EnvelopeGeneratorData.decayAndReleaseTimeValuesTable[actualReleaseRate][0]/1000d;
str.append(String.format("\tRELEASE %f s, rate %d. \n", releasePeriodInSeconds,actualReleaseRate));
str.append("\n");
return str.toString();
}
}
//
// Phase Generator
//
class PhaseGenerator {
double phase, phaseIncrement;
PhaseGenerator() {
phase = phaseIncrement = 0;
}
void keyOn() {
phase = 0;
}
@Override
public String toString() {
return String.format("Operator frequency: %f Hz.\n", OPL3Data.sampleRate*phaseIncrement);
}
}
//
// Rhythm
//
@Override
double[] getChannelOutput() {
double channelOutput = 0, op1Output = 0, op2Output = 0;
double[] output;
output = getInFourChannels(channelOutput);
return output;
};
TomTomTopCymbalChannel() {
super(tomTomTopCymbalChannelBaseAddress,
OPL3.tomTomOperator,
OPL3.topCymbalOperator);
}
}
TopCymbalOperator(int baseAddress) {
super(baseAddress);
}
TopCymbalOperator() {
this(topCymbalOperatorBaseAddress);
}
@Override
double getOperatorOutput(double modulator) {
double highHatOperatorPhase =
OPL3.highHatOperator.phase * OperatorData.multTable[OPL3.highHatOperator.mult];
// The Top Cymbal operator uses his own phase together with the High Hat phase.
return getOperatorOutput(modulator, highHatOperatorPhase);
}
phase = phaseGenerator.getPhase(vib);
int cycles = 4;
if( (carrierPhase*cycles)%cycles > 0.1) carrierOutput = 0;
return carrierOutput*2;
}
}
HighHatOperator() {
super(highHatOperatorBaseAddress);
}
@Override
double getOperatorOutput(double modulator) {
double topCymbalOperatorPhase =
OPL3.topCymbalOperator.phase * OperatorData.multTable[OPL3.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.
double operatorOutput = super.getOperatorOutput(modulator, topCymbalOperatorPhase);
if(operatorOutput == 0) operatorOutput = Math.random()*envelope;
return operatorOutput;
}
SnareDrumOperator() {
super(snareDrumOperatorBaseAddress);
}
@Override
double getOperatorOutput(double modulator) {
if(envelopeGenerator.stage == EnvelopeGenerator.Stage.OFF) return 0;
phase = OPL3.highHatOperator.phase * 2;
return operatorOutput*2;
}
}
BassDrumChannel () {
super(bassDrumChannelBaseAddress, new Operator(op1BaseAddress), new Operator(op2BaseAddress));
}
@Override
double[] getChannelOutput() {
// Bass Drum ignores first operator, when it is in series.
if(cnt == 1) op1.ar=0;
return super.getChannelOutput();
}
//
// OPl3 Data
//
class OPL3Data {
static {
loadVibratoTable();
loadTremoloTable();
}
// 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.
// The first array is used when DVB=0 and the second array is used when DVB=1.
vibratoTable = new double[2][8192];
final double semitone = Math.pow(2,1/12d);
// A cent is 1/100 of a semitone:
final double cent = Math.pow(semitone, 1/100d);
}
//
// Channel Data
//
class ChannelData {
//
// Operator Data
//
class OperatorData {
{0,0,0,-3,-6,-9,-12,-15},
{0,0, -1.125, -4.125, -7.125, -10.125, -13.125, -16.125},
{0,0, -1.875, -4.875, -7.875, -10.875, -13.875, -16.875},
{0,0, -2.625, -5.625, -8.625, -11.625, -14.625, -17.625},
{0,0,-3,-6,-9,-12,-15,-18},
{0, -0.750, -3.750, -6.750, -9.750, -12.750, -15.750, -18.750},
{0, -1.125, -4.125, -7.125, -10.125, -13.125, -16.125, -19.125},
{0, -1.500, -4.500, -7.500, -10.500, -13.500, -16.500, -19.500},
static {
OperatorData.loadWaveforms();
}
int i;
// 1st waveform: sinusoid.
double theta = 0, thetaIncrement = 2*Math.PI / 1024;
//
// Envelope Generator Data
//
class EnvelopeGeneratorData {
// These decay and release periods in miliseconds were taken from the YMF278B manual.
// The rate index range from 0 to 63, with different data for
// 0%-100% and for 10%-90%:
final static double[][] decayAndReleaseTimeValuesTable = {
{INFINITY,INFINITY}, {INFINITY,INFINITY}, {INFINITY,INFINITY}, {INFINITY,INFINITY},
{39280.64,8212.48}, {31416.32,6574.08}, {26173.44,5509.12}, {22446.08,4730.88},
{19640.32,4106.24}, {15708.16,3287.04}, {13086.72,2754.56}, {11223.04,2365.44},
{9820.16,2053.12}, {7854.08,1643.52}, {6543.36,1377.28}, {5611.52,1182.72},