From f42b96ba7ddab61f4241a00acae165c0d03796db Mon Sep 17 00:00:00 2001 From: beirich Date: Fri, 10 Aug 2012 04:46:29 +0000 Subject: [PATCH] ym2612: fix EG Rate calculation. Fix Attack Rate exponential formulation. --- BizHawk.Emulation/Sound/YM2612.cs | 127 +++++++++++++++++------------- 1 file changed, 72 insertions(+), 55 deletions(-) diff --git a/BizHawk.Emulation/Sound/YM2612.cs b/BizHawk.Emulation/Sound/YM2612.cs index 5bf9d071e8..0d8a3ccb2c 100644 --- a/BizHawk.Emulation/Sound/YM2612.cs +++ b/BizHawk.Emulation/Sound/YM2612.cs @@ -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);