diff --git a/src/BizHawk.Emulation.Cores/Sound/YM2612.cs b/src/BizHawk.Emulation.Cores/Sound/YM2612.cs deleted file mode 100644 index 7f2f9f4d8b..0000000000 --- a/src/BizHawk.Emulation.Cores/Sound/YM2612.cs +++ /dev/null @@ -1,1290 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; - -namespace BizHawk.Emulation.Cores.Components -{ - // ====================================================================== - // Yamaha YM2612 Emulation Core - // Primarily sourced from Nemesis's documentation on Sprite's Mind forums: - // http://gendev.spritesmind.net/forum/viewtopic.php?t=386 - // - // Notes: - // - In order to facilitate asynchronous sound generation, timer commands - // and reads are emulated immediately, while all other commands are - // queued together with a timestamp and processed at the end of the frame. - // - Commands are stretched in time to match the number of samples requested - // for the frame. For accurate, synchronous sound, simply request the correct - // number of samples for each frame. - // - Output is emulated at native output rate and downsampled (badly) to 44100hz. - // ====================================================================== - - // TODO: Finish testing Envelope generator - // TODO: maybe add guards when changing envelope parameters to immediately change envelope state (ie don't wait for EG cycle?) - // TODO: Detune - // TODO: LFO - // TODO: Switch from Perfect Operator to Accurate Operator. - // TODO: Operator1 Self-Feedback - // 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 : IMixedSoundProvider - { - public readonly Channel[] Channels = { new Channel(), new Channel(), new Channel(), new Channel(), new Channel(), new Channel() }; - - public YM2612() - { - InitTimers(); - MaxVolume = short.MaxValue; - } - - // ==================================================================================== - - int frameStartClock; - int frameEndClock; - - public void BeginFrame(int clock) - { - frameStartClock = clock; - while (commands.Count > 0) - { - var cmd = commands.Dequeue(); - WriteCommand(cmd); - } - } - - public void EndFrame(int clock) - { - frameEndClock = clock; - } - - // ==================================================================================== - // YM2612 I/O - // ==================================================================================== - - public class QueuedCommand - { - public byte Part; - public byte Register; - public byte Data; - public int Clock; - } - - byte PartSelect; - byte RegisterSelect; - bool DacEnable; - byte DacValue; - - readonly Queue commands = new Queue(); - - const int Slot1 = 0; - const int Slot2 = 2; - const int Slot3 = 1; - const int Slot4 = 3; - - public byte ReadStatus(int clock) - { - UpdateTimers(clock); - - byte retval = 0; - if (TimerATripped) retval |= 1; - if (TimerBTripped) retval |= 2; - return retval; - } - - public void Write(int addr, byte value, int clock) - { - UpdateTimers(clock); - - if (addr == 0) - { - PartSelect = 1; - RegisterSelect = value; - return; - } - else if (addr == 2) - { - PartSelect = 2; - RegisterSelect = value; - return; - } - - if (PartSelect == 1) - { - if (RegisterSelect == 0x24) { WriteTimerA_MSB_24(value, clock); return; } - if (RegisterSelect == 0x25) { WriteTimerA_LSB_25(value, clock); return; } - if (RegisterSelect == 0x26) { WriteTimerB_26(value, clock); return; } - if (RegisterSelect == 0x27) { WriteTimerControl_27(value, clock); } // we process immediately AND enqueue command for port $27. Allows accurate tracking of CH3 special modes. - } - - // If its not timer related just queue the command write - var cmd = new QueuedCommand { Part = PartSelect, Register = RegisterSelect, Data = value, Clock = clock - frameStartClock }; - commands.Enqueue(cmd); - } - - void WriteCommand(QueuedCommand cmd) - { - if (cmd.Part == 1) - Part1_WriteRegister(cmd.Register, cmd.Data); - else - Part2_WriteRegister(cmd.Register, cmd.Data); - } - - static void GetChanOpP1(byte value, out int channel, out int oper) - { - 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 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 13: channel = 1; oper = 3; 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; - - default: channel = -1; oper = -1; return; - } - } - - static void GetChanOpP2(byte value, out int channel, out int oper) - { - 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 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 13: channel = 4; oper = 3; 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; - - default: channel = -1; oper = -1; return; - } - } - - void Part1_WriteRegister(byte register, byte value) - { - GetChanOpP1(register, out var chan, out var oper); - - switch (register & 0xF0) - { - case 0x20: WriteLowBlock(register, value); break; - case 0x30: Write_MUL_DT(chan, oper, value); break; - case 0x40: Write_TL(chan, oper, value); break; - case 0x50: Write_AR_KS(chan, oper, value); break; - case 0x60: Write_DR_AM(chan, oper, value); break; - case 0x70: Write_SR(chan, oper, value); break; - case 0x80: Write_RR_SL(chan, oper, value); break; - case 0x90: Write_SSGEG(chan, oper, value); break; - case 0xA0: - case 0xB0: WriteHighBlockP1(register, value); break; - } - } - - void Part2_WriteRegister(byte register, byte value) - { - GetChanOpP2(register, out var chan, out var oper); - - switch (register & 0xF0) - { - case 0x30: Write_MUL_DT(chan, oper, value); break; - case 0x40: Write_TL(chan, oper, value); break; - case 0x50: Write_AR_KS(chan, oper, value); break; - case 0x60: Write_DR_AM(chan, oper, value); break; - case 0x70: Write_SR(chan, oper, value); break; - case 0x80: Write_RR_SL(chan, oper, value); break; - case 0x90: Write_SSGEG(chan, oper, value); break; - case 0xA0: - case 0xB0: WriteHighBlockP2(register, value); break; - } - } - - void WriteLowBlock(byte register, byte value) - { - switch (register) - { - //case 0x22: Console.WriteLine("LFO Control {0:X2}", value); break; - case 0x24: break; // Timer A MSB, handled immediately - case 0x25: break; // Timer A LSB, handled immediately - case 0x26: break; // Timer B, handled immediately - //case 0x27: Console.WriteLine("$27: Ch3 Mode / Timer Control {0:X2}", value); break; // determines if CH3 has 1 frequency or 4 frequencies. - case 0x28: KeyOnOff(value); break; - case 0x2A: DacValue = value; break; - case 0x2B: DacEnable = (value & 0x80) != 0; break; - case 0x2C: throw new Exception("something wrote to ym2612 port $2C!"); //http://forums.sonicretro.org/index.php?showtopic=28589 - } - } - - void WriteHighBlockP1(byte register, byte value) - { - switch (register) - { - case 0xA0: WriteFrequencyLow(Channels[0], value); break; - case 0xA1: WriteFrequencyLow(Channels[1], value); break; - case 0xA2: WriteFrequencyLow(Channels[2], value); break; - - case 0xA4: WriteFrequencyHigh(Channels[0], value); break; - case 0xA5: WriteFrequencyHigh(Channels[1], value); break; - case 0xA6: WriteFrequencyHigh(Channels[2], value); break; - - case 0xB0: Write_Feedback_Algorithm(Channels[0], value); break; - case 0xB1: Write_Feedback_Algorithm(Channels[1], value); break; - case 0xB2: Write_Feedback_Algorithm(Channels[2], value); break; - - case 0xB4: Write_Stereo_LfoSensitivy(Channels[0], value); break; - case 0xB5: Write_Stereo_LfoSensitivy(Channels[1], value); break; - case 0xB6: Write_Stereo_LfoSensitivy(Channels[2], value); break; - } - } - - void WriteHighBlockP2(byte register, byte value) - { - switch (register) - { - case 0xA0: WriteFrequencyLow(Channels[3], value); break; - case 0xA1: WriteFrequencyLow(Channels[4], value); break; - case 0xA2: WriteFrequencyLow(Channels[5], value); break; - - case 0xA4: WriteFrequencyHigh(Channels[3], value); break; - case 0xA5: WriteFrequencyHigh(Channels[4], value); break; - case 0xA6: WriteFrequencyHigh(Channels[5], value); break; - - case 0xB0: Write_Feedback_Algorithm(Channels[3], value); break; - case 0xB1: Write_Feedback_Algorithm(Channels[4], value); break; - case 0xB2: Write_Feedback_Algorithm(Channels[5], value); break; - - case 0xB4: Write_Stereo_LfoSensitivy(Channels[3], value); break; - case 0xB5: Write_Stereo_LfoSensitivy(Channels[4], value); break; - case 0xB6: Write_Stereo_LfoSensitivy(Channels[5], value); break; - } - } - - void KeyOnOff(byte value) - { - int channel = value & 3; - if (channel == 3) return; // illegal channel number, abort - if ((value & 4) != 0) channel += 3; // select part 2 - - var chan = Channels[channel]; - - //Console.WriteLine("KeyOnOff for channel {0}", channel); - - if ((value & 0x10) != 0) KeyOn(chan.Operators[Slot1]); else KeyOff(chan.Operators[Slot1]); - if ((value & 0x20) != 0) KeyOn(chan.Operators[Slot2]); else KeyOff(chan.Operators[Slot2]); - if ((value & 0x40) != 0) KeyOn(chan.Operators[Slot3]); else KeyOff(chan.Operators[Slot3]); - if ((value & 0x80) != 0) KeyOn(chan.Operators[Slot4]); else KeyOff(chan.Operators[Slot4]); - } - - static void WriteFrequencyLow(Channel channel, byte value) - { - channel.FrequencyNumber &= 0x700; - channel.FrequencyNumber |= value; - - // TODO maybe its 4-frequency mode - // TODO is this right, only reflect change when writing LSB? - - 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) - { - channel.FrequencyNumber &= 0x0FF; - channel.FrequencyNumber |= (value & 15) << 8; - 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; - channel.Feedback = (value >> 3) & 7; - } - - static void Write_Stereo_LfoSensitivy(Channel channel, byte value) - { - channel.FMS_FrequencyModulationSensitivity = value & 3; - channel.AMS_AmplitudeModulationSensitivity = (value >> 3) & 7; - channel.RightOutput = (value & 0x40) != 0; - channel.LeftOutput = (value & 0x80) != 0; - } - - void Write_MUL_DT(int chan, int op, byte value) - { - if (chan < 0) return; - var oper = Channels[chan].Operators[op]; - oper.MUL_Multiple = value & 15; - oper.DT_Detune = (value >> 4) & 7; - } - - public void Write_TL(int chan, int op, byte value) - { - if (chan < 0) return; - var oper = Channels[chan].Operators[op]; - oper.TL_TotalLevel = value & 127; - } - - public void Write_AR_KS(int chan, int op, byte value) - { - if (chan < 0) return; - 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) - { - if (chan < 0) return; - var oper = Channels[chan].Operators[op]; - oper.DR_DecayRate = value & 31; - oper.AM_AmplitudeModulation = (value & 128) != 0; - } - - public void Write_SR(int chan, int op, byte value) - { - if (chan < 0) return; - var oper = Channels[chan].Operators[op]; - oper.SR_SustainRate = value & 31; - } - - public void Write_RR_SL(int chan, int op, byte value) - { - if (chan < 0) return; - var oper = Channels[chan].Operators[op]; - oper.RR_ReleaseRate = value & 15; - oper.SL_SustainLevel = value >> 4; - } - - public void Write_SSGEG(int chan, int op, byte value) - { - if (chan < 0) return; - var oper = Channels[chan].Operators[op]; - oper.SSG_EG = value & 15; - } - - // ==================================================================================== - // Timers - // ==================================================================================== - - // Assuming this is connected to a Genesis/MegaDrive: - - // The master clock on the Genesis is 53,693,175 MCLK / sec (NTSC) - // 53,203,424 MCLK / sec (PAL) - // 7,670,454 68K cycles / sec (7 MCLK divisor) (NTSC) - // 3,579,545 Z80 cycles / sec (15 MCLK divisor) (NTSC) - - // YM2612 is fed by 68000 Clock: 7,670,454 ECLK / sec (NTSC) - // 7,600,489 ECLK / sec (PAL) - - // YM2612 has /6 divisor on the EXT CLOCK. - // YM2612 takes 24 cycles to generate a sample. 6*24 = 144. This is where the /144 divisor comes from. - // YM2612 native output rate is 7670454 / 144 = 53267 hz (NTSC), 52781 hz (PAL) - - // Timer A ticks at the native output rate (53267 times per second for NTSC). - // Timer B ticks down with a /16 divisor. (3329 times per second for NTSC). - - // Ergo, Timer A ticks every 67.2 Z80 cycles. Timer B ticks every 1075.2 Z80 cycles. - - // TODO: make this not hardcoded to Genesis timing. - - const float timerAZ80Factor = 67.2f; - const float timerBZ80Factor = 1075.2f; - - const int ntscOutputRate = 53267; - const int palOutputRate = 52781; - - const float ntsc44100Factor = 1.20786848f; - const float pal44100Factor = 1.19684807f; - - int TimerAPeriod, TimerBPeriod; - bool TimerATripped, TimerBTripped; - int TimerAResetClock, TimerBResetClock; - int TimerALastReset, TimerBLastReset; - - byte TimerControl27; - bool TimerALoad => (TimerControl27 & 1) != 0; - bool TimerBLoad => (TimerControl27 & 2) != 0; - bool TimerAEnable => (TimerControl27 & 4) != 0; - bool TimerBEnable => (TimerControl27 & 8) != 0; - bool TimerAReset => (TimerControl27 & 16) != 0; - bool TimerBReset => (TimerControl27 & 32) != 0; - - void InitTimers() - { - TimerAResetClock = 68812; - TimerBResetClock = 275200; - } - - void UpdateTimers(int clock) - { - int elapsedCyclesSinceLastTimerAReset = clock - TimerALastReset; - if (elapsedCyclesSinceLastTimerAReset > TimerAResetClock) - { - if (TimerAEnable) - TimerATripped = true; - - int numTimesTripped = elapsedCyclesSinceLastTimerAReset / TimerAResetClock; - TimerALastReset += (TimerAResetClock * numTimesTripped); - } - - int elapsedCyclesSinceLastTimerBReset = clock - TimerBLastReset; - if (elapsedCyclesSinceLastTimerBReset > TimerBResetClock) - { - if (TimerBEnable) - TimerBTripped = true; - - int numTimesTripped = elapsedCyclesSinceLastTimerBReset / TimerBResetClock; - TimerBLastReset += (TimerBResetClock * numTimesTripped); - } - } - - void WriteTimerA_MSB_24(byte value, int clock) - { - TimerAPeriod = (value << 2) | (TimerAPeriod & 3); - TimerAResetClock = (int)((1024 - TimerAPeriod) * timerAZ80Factor); - } - - void WriteTimerA_LSB_25(byte value, int clock) - { - TimerAPeriod = (TimerAPeriod & 0x3FC) | (value & 3); - TimerAResetClock = (int)((1024 - TimerAPeriod) * timerAZ80Factor); - } - - void WriteTimerB_26(byte value, int clock) - { - TimerBPeriod = value; - TimerBResetClock = (int)((256 - TimerBPeriod) * timerBZ80Factor); - } - - void WriteTimerControl_27(byte value, int clock) - { - bool lagALoad = TimerALoad; - bool lagBLoad = TimerBLoad; - - TimerControl27 = value; - - if (!lagALoad && TimerALoad) - TimerALastReset = clock; - - if (!lagBLoad && TimerBLoad) - TimerBLastReset = clock; - - if (TimerAReset) TimerATripped = false; - if (TimerBReset) TimerBTripped = false; - } - - // ==================================================================================== - // Support Tables - // ==================================================================================== - - #region tables - static readonly byte[] egRateCounterShiftValues = - { - 11, 11, 11, 11, // Rates 0-3 - 10, 10, 10, 10, // Rates 4-7 - 9, 9, 9, 9, // Rates 8-11 - 8, 8, 8, 8, // Rates 12-15 - 7, 7, 7, 7, // Rates 16-19 - 6, 6, 6, 6, // Rates 20-23 - 5, 5, 5, 5, // Rates 24-27 - 4, 4, 4, 4, // Rates 28-31 - 3, 3, 3, 3, // Rates 32-35 - 2, 2, 2, 2, // Rates 36-39 - 1, 1, 1, 1, // Rates 40-43 - 0, 0, 0, 0, // Rates 44-47 - 0, 0, 0, 0, // Rates 48-51 - 0, 0, 0, 0, // Rates 52-55 - 0, 0, 0, 0, // Rates 56-59 - 0, 0, 0, 0 // Rates 60-63 - }; - - static readonly byte[] egRateIncrementValues = - { - 0,0,0,0,0,0,0,0, // Rate 0 - 0,0,0,0,0,0,0,0, // Rate 1 - 0,1,0,1,0,1,0,1, // Rate 2 - 0,1,0,1,0,1,0,1, // Rate 3 - 0,1,0,1,0,1,0,1, // Rate 4 - 0,1,0,1,0,1,0,1, // Rate 5 - 0,1,1,1,0,1,1,1, // Rate 6 - 0,1,1,1,0,1,1,1, // Rate 7 - 0,1,0,1,0,1,0,1, // Rate 8 - 0,1,0,1,1,1,0,1, // Rate 9 - 0,1,1,1,0,1,1,1, // Rate 10 - 0,1,1,1,1,1,1,1, // Rate 11 - 0,1,0,1,0,1,0,1, // Rate 12 - 0,1,0,1,1,1,0,1, // Rate 13 - 0,1,1,1,0,1,1,1, // Rate 14 - 0,1,1,1,1,1,1,1, // Rate 15 - 0,1,0,1,0,1,0,1, // Rate 16 - 0,1,0,1,1,1,0,1, // Rate 17 - 0,1,1,1,0,1,1,1, // Rate 18 - 0,1,1,1,1,1,1,1, // Rate 19 - 0,1,0,1,0,1,0,1, // Rate 20 - 0,1,0,1,1,1,0,1, // Rate 21 - 0,1,1,1,0,1,1,1, // Rate 22 - 0,1,1,1,1,1,1,1, // Rate 23 - 0,1,0,1,0,1,0,1, // Rate 24 - 0,1,0,1,1,1,0,1, // Rate 25 - 0,1,1,1,0,1,1,1, // Rate 26 - 0,1,1,1,1,1,1,1, // Rate 27 - 0,1,0,1,0,1,0,1, // Rate 28 - 0,1,0,1,1,1,0,1, // Rate 29 - 0,1,1,1,0,1,1,1, // Rate 30 - 0,1,1,1,1,1,1,1, // Rate 31 - 0,1,0,1,0,1,0,1, // Rate 32 - 0,1,0,1,1,1,0,1, // Rate 33 - 0,1,1,1,0,1,1,1, // Rate 34 - 0,1,1,1,1,1,1,1, // Rate 35 - 0,1,0,1,0,1,0,1, // Rate 36 - 0,1,0,1,1,1,0,1, // Rate 37 - 0,1,1,1,0,1,1,1, // Rate 38 - 0,1,1,1,1,1,1,1, // Rate 39 - 0,1,0,1,0,1,0,1, // Rate 40 - 0,1,0,1,1,1,0,1, // Rate 41 - 0,1,1,1,0,1,1,1, // Rate 42 - 0,1,1,1,1,1,1,1, // Rate 43 - 0,1,0,1,0,1,0,1, // Rate 44 - 0,1,0,1,1,1,0,1, // Rate 45 - 0,1,1,1,0,1,1,1, // Rate 46 - 0,1,1,1,1,1,1,1, // Rate 47 - 1,1,1,1,1,1,1,1, // Rate 48 - 1,1,1,2,1,1,1,2, // Rate 49 - 1,2,1,2,1,2,1,2, // Rate 50 - 1,2,2,2,1,2,2,2, // Rate 51 - 2,2,2,2,2,2,2,2, // Rate 52 - 2,2,2,4,2,2,2,4, // Rate 53 - 2,4,2,4,2,4,2,4, // Rate 54 - 2,4,4,4,2,4,4,4, // Rate 55 - 4,4,4,4,4,4,4,4, // Rate 56 - 4,4,4,8,4,4,4,8, // Rate 57 - 4,8,4,8,4,8,4,8, // Rate 58 - 4,8,8,8,4,8,8,8, // Rate 59 - 8,8,8,8,8,8,8,8, // Rate 60 - 8,8,8,8,8,8,8,8, // Rate 61 - 8,8,8,8,8,8,8,8, // Rate 62 - 8,8,8,8,8,8,8,8 // Rate 63 - }; - - static readonly int[] slTable = // translates a 4-bit SL value into a 10-bit attenuation value - { - 0x000, 0x020, 0x040, 0x060, 0x080, 0x0A0, 0x0C0, 0x0E0, - 0x100, 0x120, 0x140, 0x160, 0x180, 0x1A0, 0x1C0, 0x3FF - }; - - static readonly int[] detuneTable = - { - 0, 0, 1, 2, // Key-Code 0 - 0, 0, 1, 2, // Key-Code 1 - 0, 0, 1, 2, // Key-Code 2 - 0, 0, 1, 2, // Key-Code 3 - 0, 1, 2, 2, // Key-Code 4 - 0, 1, 2, 3, // Key-Code 5 - 0, 1, 2, 3, // Key-Code 6 - 0, 1, 2, 3, // Key-Code 7 - 0, 1, 2, 4, // Key-Code 8 - 0, 1, 3, 4, // Key-Code 9 - 0, 1, 3, 4, // Key-Code 10 - 0, 1, 3, 5, // Key-Code 11 - 0, 2, 4, 5, // Key-Code 12 - 0, 2, 4, 6, // Key-Code 13 - 0, 2, 4, 6, // Key-Code 14 - 0, 2, 5, 7, // Key-Code 15 - 0, 2, 5, 8, // Key-Code 16 - 0, 3, 6, 8, // Key-Code 17 - 0, 3, 6, 9, // Key-Code 18 - 0, 3, 7, 10, // Key-Code 19 - 0, 4, 8, 11, // Key-Code 20 - 0, 4, 8, 12, // Key-Code 21 - 0, 4, 9, 13, // Key-Code 22 - 0, 5, 10, 14, // Key-Code 23 - 0, 5, 11, 16, // Key-Code 24 - 0, 6, 12, 17, // Key-Code 25 - 0, 6, 13, 19, // Key-Code 26 - 0, 7, 14, 20, // Key-Code 27 - 0, 8, 16, 22, // Key-Code 28 - 0, 8, 16, 22, // Key-Code 29 - 0, 8, 16, 22, // Key-Code 30 - 0, 8, 16, 22 // Key-Code 31 - }; - #endregion - - // ==================================================================================== - // Envelope Generator - // ==================================================================================== - - int egDivisorCounter; // This provides the /3 divisor to run the envelope generator once for every 3 FM sample output ticks. - int egCycleCounter; // This provides a rolling counter of the envelope generator update ticks. (/3 divisor already applied) - - const int MaxAttenuation = 1023; - - void MaybeRunEnvelopeGenerator() - { - if (egDivisorCounter == 0) - { - for (int c = 0; c < 6; c++) - for (int o = 0; o < 4; o++) - EnvelopeGeneratorTick(Channels[c].Operators[o]); - - egCycleCounter++; - } - - egDivisorCounter++; - if (egDivisorCounter == 3) - egDivisorCounter = 0; - } - - void EnvelopeGeneratorTick(Operator op) - { - // First, let's handle envelope generator phase transitions. - - if (op.EnvelopeState == EnvelopeState.Off) - return; - - if (op.EnvelopeState != EnvelopeState.Attack && op.EgAttenuation == MaxAttenuation) - { - op.EnvelopeState = EnvelopeState.Off; - return; - } - - if (op.EnvelopeState == EnvelopeState.Attack && op.EgAttenuation == 0) - { - 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.EnvelopeState == EnvelopeState.Decay && op.EgAttenuation >= op.Normalized10BitSL) - { - op.EnvelopeState = EnvelopeState.Sustain; - } - - // At this point, we've determined what envelope phase we're in. Lets do the update. - // Start by calculating Rate. - - int rate = 0; - switch (op.EnvelopeState) - { - 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 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 - int updateCycleOffset = (egCycleCounter >> shiftValue) & 7; // gives the offset within the 8-step cycle - int attenuationAdjustment = egRateIncrementValues[(rate * 8) + updateCycleOffset]; - - if (op.EnvelopeState == EnvelopeState.Attack) - op.EgAttenuation += (~op.EgAttenuation * attenuationAdjustment) >> 4; - else // One of the decay phases - op.EgAttenuation += attenuationAdjustment; - } - } - - static void KeyOn(Operator op) - { - op.PhaseCounter = 0; // Reset Phase Generator - - 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 - op.EnvelopeState = EnvelopeState.Attack; - - } - } - - static void KeyOff(Operator op) - { - op.EnvelopeState = EnvelopeState.Release; - } - - // ==================================================================================== - // Operator Unit - // ==================================================================================== - - int GetOperatorOutput(Operator op, int phaseModulationInput14) - { - if (op.EgAttenuation == MaxAttenuation) - return 0; - - RunPhaseGenerator(op); - int phase10 = op.PhaseCounter >> 10; - - // operators return a 14-bit output, but take a 10-bit input. What 4 bits are discarded? not the obvious ones... - // the input is shifted right by one; the least significant bit and the 3 most significant bits are discarded. - int phaseModulationInput10 = (phaseModulationInput14 >> 1) & 0x3FF; - - phase10 += phaseModulationInput10; - phase10 &= 0x3FF; - - return OperatorCalc(phase10, op.AdjustedEGOutput); - } - - static void RunPhaseGenerator(Operator op) - { - // Take the Frequency Number & shift based on Block - int phaseIncrement = op.FrequencyNumber; - switch (op.Block) - { - case 0: phaseIncrement >>= 1; break; - case 1: break; - default: phaseIncrement <<= op.Block - 1; break; - } - - // Apply Detune - int detuneAdjustment = detuneTable[(op.KeyCode * 4) + (op.DT_Detune & 3)]; - if ((op.DT_Detune & 4) != 0) - detuneAdjustment = -detuneAdjustment; - phaseIncrement += detuneAdjustment; - phaseIncrement &= 0x1FFFF; // mask to 17-bits, which is the current size of the register at this point in the calculation. This allows proper detune overflow. - - // Apply MUL - switch (op.MUL_Multiple) - { - case 0: phaseIncrement /= 2; break; - default: phaseIncrement *= op.MUL_Multiple; break; - } - - op.PhaseCounter += phaseIncrement; - op.PhaseCounter &= 0xFFFFF; - } - - static int OperatorCalc(int phase10, int attenuation) - { - // calculate sin - double phaseNormalized = (phase10 / 1023d); - double sinResult = Math.Sin(phaseNormalized * Math.PI * 2); - - // convert attenuation into linear power representation - const double attenuationIndividualBitWeighting = 48.0 / 1024.0; - double attenuationInBels = (((double)attenuation * attenuationIndividualBitWeighting) / 10.0); - double powerLinear = Math.Pow(10.0, -attenuationInBels); - - // attenuate result - double resultNormalized = sinResult * powerLinear; - - // calculate 14-bit operator output - const int maxOperatorOutput = 8191; - return (int)(resultNormalized * maxOperatorOutput); - } - - // ==================================================================================== - // Channel Unit - // ==================================================================================== - - const int max14bitValue = 0x1FFF; // maximum signed value - - int GetChannelOutput(Channel channel, int maxVolume) - { - int outc = 0; - - switch (channel.Algorithm) - { - case 0: - { - int out1; - out1 = GetOperatorOutput(channel.Operators[0], 0); - out1 = GetOperatorOutput(channel.Operators[1], out1); - out1 = GetOperatorOutput(channel.Operators[2], out1); - outc = GetOperatorOutput(channel.Operators[3], out1); - break; - } - - case 1: - { - int out1, out2; - out1 = GetOperatorOutput(channel.Operators[0], 0); - out2 = GetOperatorOutput(channel.Operators[1], 0); - outc = GetOperatorOutput(channel.Operators[2], Limit(out1 + out2, -8191, 8191)); // TODO test whether these Limit calls are actually correct. technically I expect it to be overflowing in a 10-bit space. - outc = GetOperatorOutput(channel.Operators[3], outc); - break; - } - - case 2: - { - int out1, out2; - out1 = GetOperatorOutput(channel.Operators[0], 0); - out2 = GetOperatorOutput(channel.Operators[1], 0); - out2 = GetOperatorOutput(channel.Operators[2], out2); - outc = GetOperatorOutput(channel.Operators[3], Limit(out1 + out2, -8191, 8191)); - break; - } - - case 3: - { - int out1, out2; - out1 = GetOperatorOutput(channel.Operators[0], 0); - out1 = GetOperatorOutput(channel.Operators[1], out1); - out2 = GetOperatorOutput(channel.Operators[2], 0); - outc = GetOperatorOutput(channel.Operators[3], Limit(out1 + out2, -8191, 8191)); - break; - } - - case 4: - { - int out1, out2; - out1 = GetOperatorOutput(channel.Operators[0], 0); - out1 = GetOperatorOutput(channel.Operators[1], out1); - out2 = GetOperatorOutput(channel.Operators[2], 0); - out2 = GetOperatorOutput(channel.Operators[3], out2); - outc = Limit(out1 + out2, -8191, 8191); - break; - } - - case 5: - { - int out1, out2, out3, out4; - out1 = GetOperatorOutput(channel.Operators[0], 0); - out2 = GetOperatorOutput(channel.Operators[1], out1); - out3 = GetOperatorOutput(channel.Operators[2], out1); - out4 = GetOperatorOutput(channel.Operators[3], out1); - outc = Limit(out2 + out3 + out4, -8191, 8191); - break; - } - - case 6: - { - int out1, out2, out3, out4; - out1 = GetOperatorOutput(channel.Operators[0], 0); - out2 = GetOperatorOutput(channel.Operators[1], out1); - out3 = GetOperatorOutput(channel.Operators[2], out1); - out4 = GetOperatorOutput(channel.Operators[3], out1); - outc = Limit(out2 + out3 + out4, -8191, 8191); - break; - } - - case 7: - { - int out1, out2, out3, out4; - out1 = GetOperatorOutput(channel.Operators[0], 0); - out2 = GetOperatorOutput(channel.Operators[1], out1); - out3 = GetOperatorOutput(channel.Operators[2], 0); - out4 = GetOperatorOutput(channel.Operators[3], 0); - outc = Limit(out2 + out3 + out4, -8191, 8191); - break; - } - } - - return outc * maxVolume / max14bitValue; - } - - static int Limit(int value, int min, int max) - { - if (value < min) return min; - if (value > max) return max; - return value; - } - - // ==================================================================================== - // Support Classes/Structs/Enums - // ==================================================================================== - - public enum EnvelopeState - { - Attack, - Decay, - Sustain, - Release, - Off - } - - public sealed class Operator - { - // External Settings - - public int TL_TotalLevel; // 7 bits - public int SL_SustainLevel; // 4 bits - public int AR_AttackRate; // 5 bits - public int DR_DecayRate; // 5 bits - public int SR_SustainRate; // 5 bits - public int RR_ReleaseRate; // 4 bits - public int KS_KeyScale; // 2 bits - public int SSG_EG; // 4 bits - - public int DT_Detune; // 3 bits - public int MUL_Multiple; // 4 bits - - public bool AM_AmplitudeModulation; // 1 bit - - 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 - - public int PhaseCounter; // 20 bits, where the 10 most significant bits are output to the operator. - - public EnvelopeState EnvelopeState = EnvelopeState.Off; - - private int egAttenuation = MaxAttenuation; // 10-bit attenuation value output from envelope generator - public int EgAttenuation - { - get => egAttenuation; - set - { - egAttenuation = value; - if (egAttenuation < 0) egAttenuation = 0; - if (egAttenuation > 1023) egAttenuation = 1023; - } - } - - public int Normalized10BitSL => slTable[SL_SustainLevel]; - public int Normalized10BitTL => TL_TotalLevel << 3; - public int AdjustedEGOutput => Math.Min(egAttenuation + Normalized10BitTL, 1023); - } - - public sealed class Channel - { - public readonly Operator[] Operators = { new Operator(), new Operator(), new Operator(), new Operator() }; - - public int FrequencyNumber; // 11 bits - public int Block; // 3 bits - public int Feedback; // 3 bits - public int Algorithm; // 3 bits (algorithms 0 - 7) - - public bool SpecialMode; // TODO, there are 2 special modes, a bool is not going to do the trick. - public bool LeftOutput = true; // These apparently need to be initialized on. - public bool RightOutput = true; // These apparently need to be initialized on. - - public int AMS_AmplitudeModulationSensitivity; // 3 bits - public int FMS_FrequencyModulationSensitivity; // 2 bits - } - - // ==================================================================================== - // ISoundProvider - // ==================================================================================== - - public void GetSamples(short[] samples) - { - // Generate raw samples at native sampling rate (~53hz) - int numStereoSamples = samples.Length / 2; - int nativeStereoSamples = (int)(numStereoSamples * ntsc44100Factor) * 2; - short[] nativeSamples = new short[nativeStereoSamples]; - GetSamplesNative(nativeSamples); - - // downsample from native output rate to 44100. - //CrappyNaiveResampler(nativeSamples, samples); - MaybeBetterDownsampler(nativeSamples, samples); - //LinearDownsampler(nativeSamples, samples); - } - - static void CrappyNaiveResampler(short[] input, short[] output) - { - // this is not good resampling code. - int numStereoSamples = output.Length / 2; - - int offset = 0; - for (int i = 0; i < numStereoSamples; i++) - { - int nativeOffset = ((i * ntscOutputRate) / 44100) * 2; - output[offset++] += input[nativeOffset++]; // left - output[offset++] += input[nativeOffset]; // right - } - } - - static double Fraction(double value) - { - return value - Math.Floor(value); - } - - static void LinearDownsampler(short[] input, short[] output) - { - double samplefactor = input.Length / (double)output.Length; - - for (int i = 0; i < output.Length / 2; i++) - { - // exact position on input stream - double inpos = i * samplefactor; - // selected interpolation points and weights - int pt0 = (int)inpos; // pt1 = pt0 + 1 - double wt1 = inpos - pt0; // wt0 = 1 - wt1 - double wt0 = 1.0 - wt1; - - output[i * 2 + 0] = (short)(input[pt0 * 2 + 0] * wt0 + input[pt0 * 2 + 2] * wt1); - output[i * 2 + 1] = (short)(input[pt0 * 2 + 1] * wt0 + input[pt0 * 2 + 3] * wt1); - } - } - - - static void MaybeBetterDownsampler(short[] input, short[] output) - { - // This is still not a good resampler. But it's better than the other one. Unsure how much difference it makes. - // The difference with this one is that all source samples will be sampled and weighted, none skipped over. - double nativeSamplesPerOutputSample = (double)input.Length / (double)output.Length; - int outputStereoSamples = output.Length / 2; - int inputStereoSamples = input.Length / 2; - - int offset = 0; - for (int i = 0; i < outputStereoSamples; i++) - { - - double startSample = nativeSamplesPerOutputSample * i; - double endSample = nativeSamplesPerOutputSample * (i + 1); - - int iStartSample = (int)Math.Floor(startSample); - int iEndSample = (int)Math.Floor(endSample); - double leftSample = 0; - double rightSample = 0; - for (int j = iStartSample; j <= iEndSample; j++) - { - if (j == inputStereoSamples) - break; - - double weight = 1.0; - - if (j == iStartSample) - weight = 1.0 - Fraction(startSample); - else if (j == iEndSample) - weight = Fraction(endSample); - - leftSample += ((double)input[(j * 2) + 0] * weight); - rightSample += ((double)input[(j * 2) + 1] * weight); - } - output[offset++] = (short)leftSample; - output[offset++] = (short)rightSample; - } - } - - void GetSamplesNative(short[] samples) - { - int elapsedCycles = frameEndClock - frameStartClock; - int start = 0; - while (commands.Count > 0) - { - var cmd = commands.Dequeue(); - int pos = ((cmd.Clock * samples.Length) / elapsedCycles) & ~1; - GetSamplesImmediate(samples, start, pos - start); - start = pos; - WriteCommand(cmd); - } - GetSamplesImmediate(samples, start, samples.Length - start); - } - - void GetSamplesImmediate(short[] samples, int pos, int length) - { - int channelVolume = MaxVolume / 6; - - for (int i = 0; i < length / 2; i++) - { - MaybeRunEnvelopeGenerator(); - - // Generate FM output - for (int ch = 0; ch < 6; ch++) - { - short sample = (short)GetChannelOutput(Channels[ch], channelVolume); - - if (ch < 5 || DacEnable == false) - { - if (Channels[ch].LeftOutput) samples[pos] += sample; - if (Channels[ch].RightOutput) samples[pos + 1] += sample; - } - else - { - short dacValue = (short)(((DacValue - 80) * channelVolume) / 80); - if (Channels[5].LeftOutput) samples[pos] += dacValue; - if (Channels[5].RightOutput) samples[pos + 1] += dacValue; - } - } - pos += 2; - } - } - - public void DiscardSamples() { } - public int MaxVolume { get; set; } - - // ==================================================================================== - // Save States - // ==================================================================================== - - public void SaveStateBinary(BinaryWriter writer) - { - writer.Write(TimerAPeriod); - writer.Write(TimerBPeriod); - writer.Write(TimerATripped); - writer.Write(TimerBTripped); - writer.Write(TimerAResetClock); - writer.Write(TimerBResetClock); - writer.Write(TimerALastReset); - writer.Write(TimerBLastReset); - writer.Write(TimerControl27); - writer.Write(egDivisorCounter); - writer.Write(egCycleCounter); - - writer.Write(PartSelect); - writer.Write(RegisterSelect); - writer.Write(DacEnable); - writer.Write(DacValue); - - for (int i = 0; i < 6; i++) - ChannelSaveStateBinary(writer, Channels[i]); - } - - public void LoadStateBinary(BinaryReader reader) - { - TimerAPeriod = reader.ReadInt32(); - TimerBPeriod = reader.ReadInt32(); - TimerATripped = reader.ReadBoolean(); - TimerBTripped = reader.ReadBoolean(); - TimerAResetClock = reader.ReadInt32(); - TimerBResetClock = reader.ReadInt32(); - TimerALastReset = reader.ReadInt32(); - TimerBLastReset = reader.ReadInt32(); - TimerControl27 = reader.ReadByte(); - egDivisorCounter = reader.ReadInt32(); - egCycleCounter = reader.ReadInt32(); - - PartSelect = reader.ReadByte(); - RegisterSelect = reader.ReadByte(); - DacEnable = reader.ReadBoolean(); - DacValue = reader.ReadByte(); - - for (int i = 0; i < 6; i++) - ChannelLoadStateBinary(reader, Channels[i]); - } - - void ChannelSaveStateBinary(BinaryWriter writer, Channel c) - { - // TODO reduce size of state via casting - writer.Write(c.FrequencyNumber); - writer.Write(c.Block); - writer.Write(c.Feedback); - writer.Write(c.Algorithm); - writer.Write(c.SpecialMode); - writer.Write(c.LeftOutput); - writer.Write(c.RightOutput); - writer.Write(c.AMS_AmplitudeModulationSensitivity); - writer.Write(c.FMS_FrequencyModulationSensitivity); - - for (int i = 0; i < 4; i++) - OperatorSaveStateBinary(writer, c.Operators[i]); - } - - void ChannelLoadStateBinary(BinaryReader reader, Channel c) - { - c.FrequencyNumber = reader.ReadInt32(); - c.Block = reader.ReadInt32(); - c.Feedback = reader.ReadInt32(); - c.Algorithm = reader.ReadInt32(); - c.SpecialMode = reader.ReadBoolean(); - c.LeftOutput = reader.ReadBoolean(); - c.RightOutput = reader.ReadBoolean(); - c.AMS_AmplitudeModulationSensitivity = reader.ReadInt32(); - c.FMS_FrequencyModulationSensitivity = reader.ReadInt32(); - - for (int i = 0; i < 4; i++) - OperatorLoadStateBinary(reader, c.Operators[i]); - } - - void OperatorSaveStateBinary(BinaryWriter writer, Operator op) - { - // TODO, size of states could be shrunken by using casts. - writer.Write(op.TL_TotalLevel); - writer.Write(op.SL_SustainLevel); - writer.Write(op.AR_AttackRate); - writer.Write(op.DR_DecayRate); - writer.Write(op.SR_SustainRate); - writer.Write(op.RR_ReleaseRate); - writer.Write(op.KS_KeyScale); - writer.Write(op.SSG_EG); - writer.Write(op.DT_Detune); - writer.Write(op.MUL_Multiple); - writer.Write(op.AM_AmplitudeModulation); - writer.Write(op.FrequencyNumber); - writer.Write(op.Block); - writer.Write(op.KeyCode); - writer.Write(op.Rks); - writer.Write(op.PhaseCounter); - writer.Write((byte)op.EnvelopeState); - writer.Write(op.EgAttenuation); - } - - void OperatorLoadStateBinary(BinaryReader reader, Operator op) - { - op.TL_TotalLevel = reader.ReadInt32(); - op.SL_SustainLevel = reader.ReadInt32(); - op.AR_AttackRate = reader.ReadInt32(); - op.DR_DecayRate = reader.ReadInt32(); - op.SR_SustainRate = reader.ReadInt32(); - op.RR_ReleaseRate = reader.ReadInt32(); - op.KS_KeyScale = reader.ReadInt32(); - op.SSG_EG = reader.ReadInt32(); - op.DT_Detune = reader.ReadInt32(); - op.MUL_Multiple = reader.ReadInt32(); - op.AM_AmplitudeModulation = reader.ReadBoolean(); - - op.FrequencyNumber = reader.ReadInt32(); - op.Block = reader.ReadInt32(); - op.KeyCode = reader.ReadInt32(); - op.Rks = reader.ReadInt32(); - op.PhaseCounter = reader.ReadInt32(); - op.EnvelopeState = (EnvelopeState)Enum.ToObject(typeof(EnvelopeState), reader.ReadByte()); - op.EgAttenuation = reader.ReadInt32(); - } - } -} \ No newline at end of file