ym2612: fix EG Rate calculation. Fix Attack Rate exponential formulation.

This commit is contained in:
beirich 2012-08-10 04:46:29 +00:00
parent 945c12eb13
commit f42b96ba7d
1 changed files with 72 additions and 55 deletions

View File

@ -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);