ym2612: fix EG Rate calculation. Fix Attack Rate exponential formulation.
This commit is contained in:
parent
945c12eb13
commit
f42b96ba7d
|
@ -20,6 +20,7 @@ namespace BizHawk.Emulation.Sound
|
|||
// ======================================================================
|
||||
|
||||
// TODO: Finish testing Envelope generator
|
||||
// TODO: maybe add guards when changing envelope parameters to immediately change envelope state (ie dont wait for EG cycle?)
|
||||
// TODO: Detune
|
||||
// TODO: LFO
|
||||
// TODO: Switch from Perfect Operator to Accurate Operator.
|
||||
|
@ -27,6 +28,8 @@ namespace BizHawk.Emulation.Sound
|
|||
// TODO: MEM delayed samples
|
||||
// TODO: CSM mode
|
||||
// TODO: SSG-EG
|
||||
// TODO: Seriously, I think we need better resampling code.
|
||||
// TODO: Experiment with low-pass filters, etc.
|
||||
|
||||
public sealed class YM2612 : ISoundProvider
|
||||
{
|
||||
|
@ -36,7 +39,6 @@ namespace BizHawk.Emulation.Sound
|
|||
{
|
||||
InitTimers();
|
||||
MaxVolume = short.MaxValue;
|
||||
//Channels[2].Operators[3].Debug = true;
|
||||
}
|
||||
|
||||
// ====================================================================================
|
||||
|
@ -136,18 +138,18 @@ namespace BizHawk.Emulation.Sound
|
|||
value &= 15;
|
||||
switch (value)
|
||||
{
|
||||
case 0: channel = 0; oper = 0; return;
|
||||
case 4: channel = 0; oper = 2; return;
|
||||
case 8: channel = 0; oper = 1; return;
|
||||
case 0: channel = 0; oper = 0; return;
|
||||
case 4: channel = 0; oper = 2; return;
|
||||
case 8: channel = 0; oper = 1; return;
|
||||
case 12: channel = 0; oper = 3; return;
|
||||
|
||||
case 1: channel = 1; oper = 0; return;
|
||||
case 5: channel = 1; oper = 2; return;
|
||||
case 9: channel = 1; oper = 1; return;
|
||||
case 1: channel = 1; oper = 0; return;
|
||||
case 5: channel = 1; oper = 2; return;
|
||||
case 9: channel = 1; oper = 1; return;
|
||||
case 13: channel = 1; oper = 3; return;
|
||||
|
||||
case 2: channel = 2; oper = 0; return;
|
||||
case 6: channel = 2; oper = 2; return;
|
||||
case 2: channel = 2; oper = 0; return;
|
||||
case 6: channel = 2; oper = 2; return;
|
||||
case 10: channel = 2; oper = 1; return;
|
||||
case 14: channel = 2; oper = 3; return;
|
||||
|
||||
|
@ -160,18 +162,18 @@ namespace BizHawk.Emulation.Sound
|
|||
value &= 15;
|
||||
switch (value)
|
||||
{
|
||||
case 0: channel = 3; oper = 0; return;
|
||||
case 4: channel = 3; oper = 2; return;
|
||||
case 8: channel = 3; oper = 1; return;
|
||||
case 0: channel = 3; oper = 0; return;
|
||||
case 4: channel = 3; oper = 2; return;
|
||||
case 8: channel = 3; oper = 1; return;
|
||||
case 12: channel = 3; oper = 3; return;
|
||||
|
||||
case 1: channel = 4; oper = 0; return;
|
||||
case 5: channel = 4; oper = 2; return;
|
||||
case 9: channel = 4; oper = 1; return;
|
||||
case 1: channel = 4; oper = 0; return;
|
||||
case 5: channel = 4; oper = 2; return;
|
||||
case 9: channel = 4; oper = 1; return;
|
||||
case 13: channel = 4; oper = 3; return;
|
||||
|
||||
case 2: channel = 5; oper = 0; return;
|
||||
case 6: channel = 5; oper = 2; return;
|
||||
case 2: channel = 5; oper = 0; return;
|
||||
case 6: channel = 5; oper = 2; return;
|
||||
case 10: channel = 5; oper = 1; return;
|
||||
case 14: channel = 5; oper = 3; return;
|
||||
|
||||
|
@ -302,10 +304,11 @@ namespace BizHawk.Emulation.Sound
|
|||
// TODO maybe its 4-frequency mode
|
||||
// TODO is this right, only reflect change when writing LSB?
|
||||
|
||||
channel.Operators[0].FrequencyNumber = channel.FrequencyNumber; channel.Operators[0].Block = channel.Block;
|
||||
channel.Operators[1].FrequencyNumber = channel.FrequencyNumber; channel.Operators[1].Block = channel.Block;
|
||||
channel.Operators[2].FrequencyNumber = channel.FrequencyNumber; channel.Operators[2].Block = channel.Block;
|
||||
channel.Operators[3].FrequencyNumber = channel.FrequencyNumber; channel.Operators[3].Block = channel.Block;
|
||||
Operator op;
|
||||
op = channel.Operators[0]; op.FrequencyNumber = channel.FrequencyNumber; op.Block = channel.Block; CalcKeyCode(op); CalcRks(op);
|
||||
op = channel.Operators[1]; op.FrequencyNumber = channel.FrequencyNumber; op.Block = channel.Block; CalcKeyCode(op); CalcRks(op);
|
||||
op = channel.Operators[2]; op.FrequencyNumber = channel.FrequencyNumber; op.Block = channel.Block; CalcKeyCode(op); CalcRks(op);
|
||||
op = channel.Operators[3]; op.FrequencyNumber = channel.FrequencyNumber; op.Block = channel.Block; CalcKeyCode(op); CalcRks(op);
|
||||
}
|
||||
|
||||
static void WriteFrequencyHigh(Channel channel, byte value)
|
||||
|
@ -315,6 +318,27 @@ namespace BizHawk.Emulation.Sound
|
|||
channel.Block = (value >> 3) & 7;
|
||||
}
|
||||
|
||||
static void CalcKeyCode(Operator op)
|
||||
{
|
||||
int freq = op.FrequencyNumber;
|
||||
bool f11 = ((freq >> 10) & 1) != 0;
|
||||
bool f10 = ((freq >> 9) & 1) != 0;
|
||||
bool f09 = ((freq >> 8) & 1) != 0;
|
||||
bool f08 = ((freq >> 7) & 1) != 0;
|
||||
|
||||
bool n3a = f11 & (f10 | f09 | f08);
|
||||
bool n3b = !f11 & f10 & f09 & f08;
|
||||
bool n3 = n3a | n3b;
|
||||
|
||||
op.KeyCode = (op.Block << 2) | (f11 ? 2 : 0) | (n3 ? 1 :0);
|
||||
}
|
||||
|
||||
static void CalcRks(Operator op)
|
||||
{
|
||||
int shiftVal = 3 - op.KS_KeyScale;
|
||||
op.Rks = op.KeyCode >> shiftVal;
|
||||
}
|
||||
|
||||
static void Write_Feedback_Algorithm(Channel channel, byte value)
|
||||
{
|
||||
channel.Algorithm = value & 7;
|
||||
|
@ -326,7 +350,7 @@ namespace BizHawk.Emulation.Sound
|
|||
channel.FMS_FrequencyModulationSensitivity = value & 3;
|
||||
channel.AMS_AmplitudeModulationSensitivity = (value >> 3) & 7;
|
||||
channel.RightOutput = (value & 0x40) != 0;
|
||||
channel.LeftOutput = (value & 0x80) != 0;
|
||||
channel.LeftOutput = (value & 0x80) != 0;
|
||||
}
|
||||
|
||||
void Write_MUL_DT1(int chan, int op, byte value)
|
||||
|
@ -350,6 +374,7 @@ namespace BizHawk.Emulation.Sound
|
|||
var oper = Channels[chan].Operators[op];
|
||||
oper.AR_AttackRate = value & 31;
|
||||
oper.KS_KeyScale = value >> 6;
|
||||
CalcRks(oper);
|
||||
}
|
||||
|
||||
public void Write_DR_AM(int chan, int op, byte value)
|
||||
|
@ -422,12 +447,12 @@ namespace BizHawk.Emulation.Sound
|
|||
int TimerALastReset, TimerBLastReset;
|
||||
|
||||
byte TimerControl27;
|
||||
bool TimerALoad { get { return (TimerControl27 & 1) != 0; } }
|
||||
bool TimerBLoad { get { return (TimerControl27 & 2) != 0; } }
|
||||
bool TimerALoad { get { return (TimerControl27 & 1) != 0; } }
|
||||
bool TimerBLoad { get { return (TimerControl27 & 2) != 0; } }
|
||||
bool TimerAEnable { get { return (TimerControl27 & 4) != 0; } }
|
||||
bool TimerBEnable { get { return (TimerControl27 & 8) != 0; } }
|
||||
bool TimerAReset { get { return (TimerControl27 & 16) != 0; } }
|
||||
bool TimerBReset { get { return (TimerControl27 & 32) != 0; } }
|
||||
bool TimerAReset { get { return (TimerControl27 & 16) != 0; } }
|
||||
bool TimerBReset { get { return (TimerControl27 & 32) != 0; } }
|
||||
|
||||
void InitTimers()
|
||||
{
|
||||
|
@ -632,14 +657,11 @@ namespace BizHawk.Emulation.Sound
|
|||
op.EnvelopeState = EnvelopeState.Decay;
|
||||
if (op.SL_SustainLevel == 0) // If Sustain Level is 0, we skip Decay and go straight to Sustain phase.
|
||||
op.EnvelopeState = EnvelopeState.Sustain;
|
||||
if (op.Debug) Console.WriteLine("Switch to " + op.EnvelopeState);
|
||||
}
|
||||
|
||||
if (op.EnvelopeState == EnvelopeState.Decay && op.EgAttenuation >= op.Normalized10BitSL)
|
||||
{
|
||||
// Switch to Sustain phase
|
||||
op.EnvelopeState = EnvelopeState.Sustain;
|
||||
if (op.Debug) Console.WriteLine("Switch to Sustain");
|
||||
}
|
||||
|
||||
// At this point, we've determined what envelope phase we're in. Lets do the update.
|
||||
|
@ -648,17 +670,18 @@ namespace BizHawk.Emulation.Sound
|
|||
int rate = 0;
|
||||
switch (op.EnvelopeState)
|
||||
{
|
||||
case EnvelopeState.Attack: rate = op.AR_AttackRate; break;
|
||||
case EnvelopeState.Decay: rate = op.DR_DecayRate; break;
|
||||
case EnvelopeState.Attack: rate = op.AR_AttackRate; break;
|
||||
case EnvelopeState.Decay: rate = op.DR_DecayRate; break;
|
||||
case EnvelopeState.Sustain: rate = op.SR_SustainRate; break;
|
||||
case EnvelopeState.Release: rate = (op.RR_ReleaseRate << 1) + 1; break;
|
||||
}
|
||||
|
||||
if (rate != 0) // rate=0 is 0 no matter the value of KeyScale.
|
||||
rate = Math.Min((rate * 2) + op.KS_KeyScale, 63);
|
||||
if (rate != 0) // rate=0 is 0 no matter the value of Rks.
|
||||
rate = Math.Min((rate * 2) + op.Rks, 63);
|
||||
|
||||
// Now we have rate. figure out shift value and cycle offset
|
||||
int shiftValue = egRateCounterShiftValues[rate];
|
||||
|
||||
if (egCycleCounter % (1 << shiftValue) == 0)
|
||||
{
|
||||
// Update attenuation value this tick
|
||||
|
@ -666,40 +689,36 @@ namespace BizHawk.Emulation.Sound
|
|||
int attenuationAdjustment = egRateIncrementValues[(rate * 8) + updateCycleOffset];
|
||||
|
||||
if (op.EnvelopeState == EnvelopeState.Attack)
|
||||
op.EgAttenuation -= attenuationAdjustment * (((op.EgAttenuation) / 16) + 1);
|
||||
op.EgAttenuation += (~op.EgAttenuation * attenuationAdjustment) >> 4;
|
||||
else // One of the decay phases
|
||||
op.EgAttenuation += attenuationAdjustment;
|
||||
|
||||
//if (op.Debug) Console.WriteLine("Attn {0} Adj {1} Cycle {2} Rate {3} Shift {4} Offset {5}", op.EgAttenuation, op.AdjustedEGOutput, egCycleCounter, rate, shiftValue, updateCycleOffset);
|
||||
}
|
||||
}
|
||||
|
||||
static void KeyOn(Operator op)
|
||||
{
|
||||
op.PhaseCounter = 0; // Reset Phase Generator
|
||||
//Console.WriteLine("key on");
|
||||
if (op.AR_AttackRate >= 30) // AR of 30 or 31 skips attack phase
|
||||
|
||||
if (op.AR_AttackRate >= 30)
|
||||
{
|
||||
|
||||
|
||||
// AR of 30 or 31 skips attack phase
|
||||
op.EgAttenuation = 0; // Force minimum attenuation
|
||||
|
||||
op.EnvelopeState = EnvelopeState.Decay;
|
||||
if (op.SL_SustainLevel == 0) // If Sustain Level is 0, we skip Decay and go straight to Sustain phase.
|
||||
op.EnvelopeState = EnvelopeState.Sustain;
|
||||
|
||||
}
|
||||
else
|
||||
{ // Regular Key-On
|
||||
} else {
|
||||
|
||||
// Regular Key-On
|
||||
op.EnvelopeState = EnvelopeState.Attack;
|
||||
// It's notable, though unsurprising, that we do not reset attenuation to max attenuation during a standard KeyOn.
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static void KeyOff(Operator op)
|
||||
{
|
||||
// TODO I feel like it should do more than this. But maybe it doesn't.
|
||||
op.EnvelopeState = EnvelopeState.Release;
|
||||
}
|
||||
|
||||
|
@ -721,12 +740,8 @@ namespace BizHawk.Emulation.Sound
|
|||
|
||||
phase10 += phaseModulationInput10;
|
||||
phase10 &= 0x3FF;
|
||||
|
||||
|
||||
int operator_output = OperatorCalc(phase10, op.AdjustedEGOutput);
|
||||
//if (op.Debug) Log.Error("YM2612","SLOT4 eg_out {0} state _ TL {1} volume {2} SL {3} op_out {4} phase {5}", op.AdjustedEGOutput, op.Normalized10BitTL, op.EgAttenuation, op.Normalized10BitSL, operator_output, phase10);
|
||||
|
||||
return operator_output;
|
||||
|
||||
return OperatorCalc(phase10, op.AdjustedEGOutput);
|
||||
}
|
||||
|
||||
static void RunPhaseGenerator(Operator op)
|
||||
|
@ -745,7 +760,7 @@ namespace BizHawk.Emulation.Sound
|
|||
// Apply MUL
|
||||
switch (op.MUL_Multiple)
|
||||
{
|
||||
case 0: phaseIncrement /= 2; break;
|
||||
case 0: phaseIncrement /= 2; break;
|
||||
default: phaseIncrement *= op.MUL_Multiple; break;
|
||||
}
|
||||
|
||||
|
@ -912,6 +927,9 @@ namespace BizHawk.Emulation.Sound
|
|||
|
||||
public int FrequencyNumber; // 11 bits
|
||||
public int Block; // 3 bits
|
||||
|
||||
public int KeyCode; // 5 bits (described on pg 25 of YM2608 docs)
|
||||
public int Rks; // 5 bits (described on pg 29 of YM2608 docs)
|
||||
|
||||
// Internal State
|
||||
|
||||
|
@ -932,10 +950,8 @@ namespace BizHawk.Emulation.Sound
|
|||
}
|
||||
|
||||
public int Normalized10BitSL { get { return slTable[SL_SustainLevel]; } }
|
||||
public int Normalized10BitTL { get { return TL_TotalLevel << 3; } } // TODO no idea if this is correct, it's probably not.
|
||||
public int AdjustedEGOutput { get { return Math.Min(egAttenuation + Normalized10BitTL, 1023); } }
|
||||
|
||||
public bool Debug = false;
|
||||
public int Normalized10BitTL { get { return TL_TotalLevel << 3; } }
|
||||
public int AdjustedEGOutput { get { return Math.Min(egAttenuation + Normalized10BitTL, 1023); } }
|
||||
}
|
||||
|
||||
public sealed class Channel
|
||||
|
@ -1001,6 +1017,7 @@ namespace BizHawk.Emulation.Sound
|
|||
{
|
||||
MaybeRunEnvelopeGenerator();
|
||||
|
||||
// Generate FM output
|
||||
for (int ch = 0; ch < 6; ch++)
|
||||
{
|
||||
short sample = (short)GetChannelOutput(Channels[ch], channelVolume);
|
||||
|
|
Loading…
Reference in New Issue