diff --git a/BizHawk.Emulation/BizHawk.Emulation.csproj b/BizHawk.Emulation/BizHawk.Emulation.csproj
index e08c7f161c..b926856155 100644
--- a/BizHawk.Emulation/BizHawk.Emulation.csproj
+++ b/BizHawk.Emulation/BizHawk.Emulation.csproj
@@ -354,9 +354,6 @@
     <Compile Include="Sound\Utilities\DualSound.cs" />
     <Compile Include="Sound\Utilities\Equalizer.cs" />
     <Compile Include="Sound\VRC6.cs" />
-    <Compile Include="Sound\YM2612.IO.cs" />
-    <Compile Include="Sound\YM2612.Channel.cs" />
-    <Compile Include="Sound\YM2612.Operator.cs" />
     <Compile Include="Sound\Utilities\BufferedAsync.cs" />
     <Compile Include="Sound\Utilities\Metaspu.cs" />
     <Compile Include="Interfaces\IController.cs" />
@@ -390,7 +387,6 @@
     <Compile Include="Consoles\Sega\SMS\SMS.cs" />
     <Compile Include="Consoles\Sega\SMS\VDP.cs" />
     <Compile Include="Sound\YM2413.cs" />
-    <Compile Include="Sound\YM2612.Timers.cs" />
     <Compile Include="Util.cs" />
     <Compile Include="Sound\Utilities\Waves.cs" />
   </ItemGroup>
diff --git a/BizHawk.Emulation/Sound/YM2612.Channel.cs b/BizHawk.Emulation/Sound/YM2612.Channel.cs
deleted file mode 100644
index 4f66d2d38c..0000000000
--- a/BizHawk.Emulation/Sound/YM2612.Channel.cs
+++ /dev/null
@@ -1,80 +0,0 @@
-using System;
-
-namespace BizHawk.Emulation.Sound
-{
-    public partial class YM2612
-    {
-        public sealed class Channel
-        {
-            public readonly Operator[] Operators;
-
-            public int FrequencyNumber;
-            public int Block;
-            public int Feedback;
-            public int Algorithm;
-
-            public bool SpecialMode; // Enables separate frequency for each operator, available on CH3 and CH6 only
-            // TODO. CSM.   Pg6 details CSM mode.
-            public bool LeftOutput;
-            public bool RightOutput;
-
-            public int AMS_AmplitudeModulationSensitivity;
-            public int FMS_FrequencyModulationSensitivity;
-
-            public Channel()
-            {
-                Operators = new Operator[4];
-                Operators[0] = new Operator();
-                Operators[1] = new Operator();
-                Operators[2] = new Operator();
-                Operators[3] = new Operator();
-
-                LeftOutput = true; // Revenge of Shinobi does not output DAC if these arent initialized ??
-                RightOutput = true;
-            }
-
-            public void WriteFrequencyLow(byte value)
-            {
-                FrequencyNumber &= 0x700;
-                FrequencyNumber |= value;
-
-                // TODO maybe its 4-frequency mode
-                // TODO is this right, only reflect change when writing LSB?
-                Operators[0].FrequencyNumber = FrequencyNumber;
-                Operators[1].FrequencyNumber = FrequencyNumber;
-                Operators[2].FrequencyNumber = FrequencyNumber;
-                Operators[3].FrequencyNumber = FrequencyNumber;
-            }
-
-            public void WriteFrequencyHigh(byte value)
-            {
-                FrequencyNumber &= 0x0FF;
-                FrequencyNumber |= (value & 15) << 8;
-                Block = (value >> 3) & 7;
-            }
-
-            public void Write_Feedback_Algorithm(byte value)
-            {
-                Algorithm = value & 7;
-                Feedback = (value >> 3) & 7;
-            }
-
-            public void Write_Stereo_LfoSensitivy(byte value)
-            {
-                FMS_FrequencyModulationSensitivity = value & 3;
-                AMS_AmplitudeModulationSensitivity = (value >> 3) & 7;
-                RightOutput = (value & 0x40) != 0;
-                LeftOutput = (value & 0x80) != 0;
-            }
-
-            //---------------------- 
-            //|Mode| Behaviour     | 
-            //|----|---------------| 
-            //| 00 | Normal        | 
-            //| 01 | Special       | 
-            //| 10 | Special + CSM | 
-            //| 11 | Special       | 
-            //----------------------
-        }
-    }
-}
\ No newline at end of file
diff --git a/BizHawk.Emulation/Sound/YM2612.IO.cs b/BizHawk.Emulation/Sound/YM2612.IO.cs
deleted file mode 100644
index 8260bb23ec..0000000000
--- a/BizHawk.Emulation/Sound/YM2612.IO.cs
+++ /dev/null
@@ -1,231 +0,0 @@
-using System;
-using System.Collections.Generic;
-
-namespace BizHawk.Emulation.Sound
-{
-    // We process TIMER writes immediately when writes come in.
-    // All other writes are queued up with a timestamp, so that we  
-    // can sift through them when we're rendering audio for the frame.
-    
-    public partial class YM2612
-    {
-        byte PartSelect;
-        byte RegisterSelect;
-        bool DacEnable;
-        byte DacValue;
-
-        Queue<QueuedCommand> commands = new Queue<QueuedCommand>();
-
-        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); } // don't return on this one; we process immediately AND enqueue command for port $27.
-            }
-
-            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)
-        {
-            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: Console.WriteLine("Operator Key On/Off Ctrl {0:X2}", 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
-
-                default:
-                    int chan, oper;
-                    GetChanOpP1(register, out chan, out oper);
-                    if (chan < 0) break; // abort if invalid port number
-                    switch (register & 0xF0)
-                    {
-                        case 0x30: Channels[chan].Operators[oper].Write_MUL_DT1(value); break;
-                        case 0x40: Channels[chan].Operators[oper].Write_TL(value); break;
-                        case 0x50: Channels[chan].Operators[oper].Write_AR_KS(value); break;
-                        case 0x60: Channels[chan].Operators[oper].Write_DR_AM(value); break;
-                        case 0x70: Channels[chan].Operators[oper].Write_SR(value); break;
-                        case 0x80: Channels[chan].Operators[oper].Write_RR_SL(value); break;
-                        case 0x90: Channels[chan].Operators[oper].Write_SSGEG(value); break;
-                        case 0xA0:
-                        case 0xB0: WriteHighBlockP1(register, value); break;
-                    }
-                    break;
-
-                // "In MAME OPN emulation code register pairs for multi-frequency mode are A6A2, ACA8, AEAA, ADA9".
-
-                //D7 - operator, which frequency defined by A6A2 
-                //D6 - .. ACA8 
-                //D5 - .. AEAA 
-                //D4 - .. ADA9
-                //Where D7=op4, D6=op3, D5=op2, and D4=op1. That matches the YM2608 document. At least that's confirmed then. 
-
-                // PG4 has some info on frquency calculations
-            }
-        }
-
-        void Part2_WriteRegister(byte register, byte value)
-        {
-            // NOTE. Only first bank has multi-frequency CSM/Special mode. This mode can't work on CH6.
-
-            int chan, oper;
-            GetChanOpP2(register, out chan, out oper);
-            if (chan < 0) return; // abort if invalid port number
-            switch (register & 0xF0)
-            {
-                case 0x30: Channels[chan].Operators[oper].Write_MUL_DT1(value); break;
-                case 0x40: Channels[chan].Operators[oper].Write_TL(value); break;
-                case 0x50: Channels[chan].Operators[oper].Write_AR_KS(value); break;
-                case 0x60: Channels[chan].Operators[oper].Write_DR_AM(value); break;
-                case 0x70: Channels[chan].Operators[oper].Write_SR(value); break;
-                case 0x80: Channels[chan].Operators[oper].Write_RR_SL(value); break;
-                case 0x90: Channels[chan].Operators[oper].Write_SSGEG(value); break;
-                case 0xA0:
-                case 0xB0: WriteHighBlockP2(register, value); break;
-            }
-        }
-
-        void WriteHighBlockP1(byte register, byte value)
-        {
-            switch (register)
-            {
-                case 0xA0: Channels[0].WriteFrequencyLow(value); break;
-                case 0xA1: Channels[1].WriteFrequencyLow(value); break;
-                case 0xA2: Channels[2].WriteFrequencyLow(value); break;
-
-                case 0xA4: Channels[0].WriteFrequencyHigh(value); break;
-                case 0xA5: Channels[1].WriteFrequencyHigh(value); break;
-                case 0xA6: Channels[2].WriteFrequencyHigh(value); break;
-
-                case 0xB0: Channels[0].Write_Feedback_Algorithm(value); break;
-                case 0xB1: Channels[1].Write_Feedback_Algorithm(value); break;
-                case 0xB2: Channels[2].Write_Feedback_Algorithm(value); break;
-
-                case 0xB4: Channels[0].Write_Stereo_LfoSensitivy(value); break;
-                case 0xB5: Channels[1].Write_Stereo_LfoSensitivy(value); break;
-                case 0xB6: Channels[2].Write_Stereo_LfoSensitivy(value); break;
-            }
-        }
-
-        void WriteHighBlockP2(byte register, byte value)
-        {
-            switch (register)
-            {
-                case 0xA0: Channels[3].WriteFrequencyLow(value); break;
-                case 0xA1: Channels[4].WriteFrequencyLow(value); break;
-                case 0xA2: Channels[5].WriteFrequencyLow(value); break;
-
-                case 0xA4: Channels[3].WriteFrequencyHigh(value); break;
-                case 0xA5: Channels[4].WriteFrequencyHigh(value); break;
-                case 0xA6: Channels[5].WriteFrequencyHigh(value); break;
-
-                case 0xB0: Channels[3].Write_Feedback_Algorithm(value); break;
-                case 0xB1: Channels[4].Write_Feedback_Algorithm(value); break;
-                case 0xB2: Channels[5].Write_Feedback_Algorithm(value); break;
-
-                case 0xB4: Channels[3].Write_Stereo_LfoSensitivy(value); break;
-                case 0xB5: Channels[4].Write_Stereo_LfoSensitivy(value); break;
-                case 0xB6: Channels[5].Write_Stereo_LfoSensitivy(value); break;
-            }
-        }
-
-        public class QueuedCommand
-        {
-            public byte Part;
-            public byte Register;
-            public byte Data;
-            public int Clock;
-        }
-    }
-}
\ No newline at end of file
diff --git a/BizHawk.Emulation/Sound/YM2612.Operator.cs b/BizHawk.Emulation/Sound/YM2612.Operator.cs
deleted file mode 100644
index 53f94d0f2e..0000000000
--- a/BizHawk.Emulation/Sound/YM2612.Operator.cs
+++ /dev/null
@@ -1,90 +0,0 @@
-using System;
-
-namespace BizHawk.Emulation.Sound
-{
-    public partial class YM2612
-    {
-        public sealed class Operator
-        {
-            // External Settings
-            public int TL_TotalLevel;
-            public int SL_SustainLevel;
-            public int AR_AttackRate;
-            public int DR_DecayRate;
-            public int SR_SustainRate;
-            public int RR_ReleaseRate;
-            public int KS_KeyScale;
-            public int SSG_EG;
-
-            public int DT1_Detune;
-            public int MUL_Multiple;
-
-            public bool AM_AmplitudeModulation;
-
-            public int FrequencyNumber;
-            public int Block;
-            
-            // Internal State
-            public int PhaseCounter;
-
-            // I/O
-            public void Write_MUL_DT1(byte value)
-            {
-                MUL_Multiple = value & 15;
-                DT1_Detune = (value >> 4) & 7;
-            }
-
-            public void Write_TL(byte value)
-            {
-                TL_TotalLevel = value & 127;
-            }
-
-            public void Write_AR_KS(byte value)
-            {
-                AR_AttackRate = value & 31;
-                KS_KeyScale = value >> 6;
-            }
-
-            public void Write_DR_AM(byte value)
-            {
-                DR_DecayRate = value & 31;
-                AM_AmplitudeModulation = (value & 128) != 0;
-            }
-
-            public void Write_SR(byte value)
-            {
-                SR_SustainRate = value & 31;
-            }
-
-            public void Write_RR_SL(byte value)
-            {
-                RR_ReleaseRate = value & 15;
-                SL_SustainLevel = value >> 4;
-            }
-
-            public void Write_SSGEG(byte value)
-            {
-                SSG_EG = value & 15;
-            }
-
-            public void UpdateFrequency(int frequencyNumber, int block)
-            {
-                FrequencyNumber = frequencyNumber;
-                Block = block;
-            }
-        }
-    }
-    //TODO "the shape of the waves of the envelope changes in a exponential when attacking it, and it changes in the straight line at other rates."
-    
-    // pg 8, read it
-    // pg 11, detailed overview of how operator works.
-    // pg 12, detailed description of phase generator.
-
-    //TL      Total Level     7 bits 
-    //SL      Sustain Level   4 bits 
-    //AR      Attack Rate     5 bits 
-    //DR      Decay Rate      5 bits 
-    //SR      Sustain Rate    5 bits 
-    //RR      Release Rate    4 bits 
-    //SSG-EG  SSG-EG Mode     4 bits
-}
\ No newline at end of file
diff --git a/BizHawk.Emulation/Sound/YM2612.Timers.cs b/BizHawk.Emulation/Sound/YM2612.Timers.cs
deleted file mode 100644
index 2de1cd1b39..0000000000
--- a/BizHawk.Emulation/Sound/YM2612.Timers.cs
+++ /dev/null
@@ -1,104 +0,0 @@
-using System;
-
-namespace BizHawk.Emulation.Sound
-{
-    // 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)
-    //                                     3,579,545 Z80 cycles / sec (15 MCLK divisor)
-
-    // YM2612 is fed by EXT CLOCK:         7,670,454 ECLK / sec (NTSC)
-    //  (Same clock on 68000)              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.
-
-    public partial class YM2612
-    {
-        const float timerAZ80Factor = 67.2f;
-        const float timerBZ80Factor = 1075.2f;
-
-        int  TimerAPeriod,     TimerBPeriod;
-        bool TimerATripped,    TimerBTripped;
-        int  TimerAResetClock, TimerBResetClock;
-        int  TimerALastReset,  TimerBLastReset;
-
-        byte TimerControl27;
-        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; } }
-
-        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;
-        }
-    }
-}
\ No newline at end of file
diff --git a/BizHawk.Emulation/Sound/YM2612.cs b/BizHawk.Emulation/Sound/YM2612.cs
index 021c59acc4..5bf9d071e8 100644
--- a/BizHawk.Emulation/Sound/YM2612.cs
+++ b/BizHawk.Emulation/Sound/YM2612.cs
@@ -1,32 +1,48 @@
 using System;
 using System.Diagnostics;
+using System.Collections.Generic;
 
 namespace BizHawk.Emulation.Sound
 {
-    public sealed partial class YM2612 : ISoundProvider
+    // ======================================================================
+    //  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: 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
+
+    public sealed class YM2612 : ISoundProvider
     {
-        public readonly Channel[] Channels;
-        
-        int frameStartClock;
-        int frameEndClock;
+        public readonly Channel[] Channels = { new Channel(), new Channel(), new Channel(), new Channel(), new Channel(), new Channel() };
 
         public YM2612()
         {
-            Channels = new Channel[6];
-            Channels[0] = new Channel();
-            Channels[1] = new Channel();
-            Channels[2] = new Channel();
-            Channels[3] = new Channel();
-            Channels[4] = new Channel();
-            Channels[5] = new Channel();
-
             InitTimers();
+            MaxVolume = short.MaxValue;
+            //Channels[2].Operators[3].Debug = true;
         }
 
-        public void Reset()
-        {
-            throw new Exception("something is resetting the ym2612");
-        }
+        // ====================================================================================
+
+        int frameStartClock;
+        int frameEndClock;
 
         public void BeginFrame(int clock)
         {
@@ -43,10 +59,926 @@ namespace BizHawk.Emulation.Sound
             frameEndClock = clock;
         }
 
-        public void DiscardSamples() { }        
-        public int MaxVolume { get; set; }
+        // ====================================================================================
+        //                                      YM2612 I/O
+        // ====================================================================================
 
-        public void GetSamples(short[] samples) 
+        public class QueuedCommand
+        {
+            public byte Part;
+            public byte Register;
+            public byte Data;
+            public int Clock;
+        }
+
+        byte PartSelect;
+        byte RegisterSelect;
+        bool DacEnable;
+        byte DacValue;
+
+        Queue<QueuedCommand> commands = new Queue<QueuedCommand>();
+
+        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)
+        {
+            int chan, oper;
+            GetChanOpP1(register, out chan, out oper);
+
+            switch (register & 0xF0)
+            {
+                case 0x20: WriteLowBlock(register, value); break;
+                case 0x30: Write_MUL_DT1(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)
+        {
+            int chan, oper;
+            GetChanOpP2(register, out chan, out oper);
+
+            switch (register & 0xF0)
+            {
+                case 0x30: Write_MUL_DT1(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?
+
+            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;
+        }
+
+        static void WriteFrequencyHigh(Channel channel, byte value)
+        {
+            channel.FrequencyNumber &= 0x0FF;
+            channel.FrequencyNumber |= (value & 15) << 8;
+            channel.Block = (value >> 3) & 7;
+        }
+
+        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_DT1(int chan, int op, byte value)
+        {
+            if (chan < 0) return;
+            var oper = Channels[chan].Operators[op];
+            oper.MUL_Multiple = value & 15;
+            oper.DT1_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;
+        }
+
+        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 { 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; } }
+
+        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;
+        }
+
+        // ====================================================================================
+        //                                     Envelope Generator
+        // ====================================================================================
+
+        #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
+            };
+        #endregion
+
+        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.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.
+            // 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 KeyScale.
+                rate = Math.Min((rate * 2) + op.KS_KeyScale, 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 -= attenuationAdjustment * (((op.EgAttenuation) / 16) + 1);
+                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
+            {
+
+                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;
+                // 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;
+        }
+
+        // ====================================================================================
+        //                                     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;
+
+
+            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;
+        }
+
+        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;
+            }
+
+            // TODO: Detune
+
+            // 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 DT1_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
+
+            // 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 { return egAttenuation; }
+                set
+                {
+                    egAttenuation = value;
+                    if (egAttenuation < 0) egAttenuation = 0;
+                    if (egAttenuation > 1023) egAttenuation = 1023;
+                }
+            }
+
+            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 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.
+            // TODO this is not good resampling code. I'm not rightly sure it's even correct as linear interpolation goes.
+            int offset = 0;
+            for (int i = 0; i < numStereoSamples; i++)
+            {
+                int nativeOffset = ((i * ntscOutputRate) / 44100) * 2;
+                samples[offset++] += nativeSamples[nativeOffset++]; // left
+                samples[offset++] += nativeSamples[nativeOffset]; // right
+            }
+        }
+
+        void GetSamplesNative(short[] samples)
         {
             int elapsedCycles = frameEndClock - frameStartClock;
             int start = 0;
@@ -65,38 +997,31 @@ namespace BizHawk.Emulation.Sound
         {
             int channelVolume = MaxVolume / 6;
 
-            for (int i=0; i<length/2; i++)
+            for (int i = 0; i < length / 2; i++)
             {
-                // TODO: channels 1-5
-                // TODO, non-DAC
+                MaybeRunEnvelopeGenerator();
 
-                if (DacEnable)
+                for (int ch = 0; ch < 6; ch++)
                 {
-                    short dacValue = (short)(((DacValue-80) * channelVolume) / 80);
-                    if (Channels[5].LeftOutput) samples[pos] += dacValue;
-                    if (Channels[5].RightOutput) samples[pos + 1] += dacValue;
+                    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;
             }
-
         }
 
-        // Pg 8 major post w/ info on clock rates, envelope generator, loudness
-        // Note: part about EG clock is wrong, see pg 12. :|
-        // pg 27 contains further sets of corrections to previous pages. Jesus. Including data on SSG-EG.
-        // pg 32 contains some details about the LFO
-        // pg 33 paul jensens contains frequency conversion. gmaniac corrected. 
-        // pg 33 blargg comments on DAC size - depends on if I WANT to emulate the inaccuracies of the DAC or a hypotheticaly perfect YM2612. more on DAC on page 37 - contradicting blargg.
-
-      /*  
-Chilly Willy wrote:
-Hmm - does the FM channel still update even if it is set to PCM?
-
-Yes, it does. FM Channel 6 and Reg $2A are independent of each other. "DAC mode" (Reg $2B) only chose output of one of them. If it is in "FM Mode" it outputs Channel 6 output. If it is in "DAC Mode", it outputs value stored in Reg $2A and normalized to scale of DAC (-512 to + 512). Normalization formula is 
-(v - $80) * 4 
-where v = ($2A). 
-
-I tested in on HW.
-    */
+        public void DiscardSamples() { }
+        public int MaxVolume { get; set; }
     }
 }
\ No newline at end of file