diff --git a/ProjectPSX.OpenTK/AudioPlayer.cs b/ProjectPSX.OpenTK/AudioPlayer.cs
index 7022da2..e1bd897 100644
--- a/ProjectPSX.OpenTK/AudioPlayer.cs
+++ b/ProjectPSX.OpenTK/AudioPlayer.cs
@@ -52,7 +52,7 @@ public unsafe void UpdateAudio(byte[] samples) {
                 queueLength++;
             }
             
-            if (AL.GetSourceState(audioSource) != ALSourceState.Playing)
+            if (GetSourceState(audioSource) != ALSourceState.Playing)
                 AL.SourcePlay(audioSource);
 
         }
@@ -62,5 +62,11 @@ public unsafe void UpdateAudio(byte[] samples) {
             ALC.DestroyContext(audioContext);
             ALC.CloseDevice(audioDevice);
         }
+
+        public static ALSourceState GetSourceState(int sid)
+        {
+            AL.GetSource(sid, ALGetSourcei.SourceState, out var value);
+            return (ALSourceState)value;
+        }
     }
 }
diff --git a/ProjectPSX.OpenTK/Program.cs b/ProjectPSX.OpenTK/Program.cs
index a7bfb6e..86a0f96 100644
--- a/ProjectPSX.OpenTK/Program.cs
+++ b/ProjectPSX.OpenTK/Program.cs
@@ -11,7 +11,6 @@ static class Program {
         [STAThread]
         static void Main() {
             GameWindowSettings settings = new GameWindowSettings();
-            settings.RenderFrequency = 60;
             settings.UpdateFrequency = 60;
             NativeWindowSettings nativeWindow = new NativeWindowSettings();
             nativeWindow.API = ContextAPI.OpenGL;
diff --git a/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj b/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj
index b163b1c..610a82b 100644
--- a/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj
+++ b/ProjectPSX.OpenTK/ProjectPSX.OpenTK.csproj
@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <OutputType>Exe</OutputType>
-    <TargetFramework>net5.0</TargetFramework>
+    <TargetFramework>net9.0</TargetFramework>
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
@@ -15,7 +15,7 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="OpenTK" Version="4.6.7" />
+    <PackageReference Include="OpenTK" Version="4.8.1" />
   </ItemGroup>
 
   <ItemGroup>
diff --git a/ProjectPSX.WinForms/.editorconfig b/ProjectPSX.WinForms/.editorconfig
index 89f1f31..ce299b4 100644
--- a/ProjectPSX.WinForms/.editorconfig
+++ b/ProjectPSX.WinForms/.editorconfig
@@ -91,9 +91,9 @@ csharp_style_inlined_variable_declaration = true:suggestion
 ###############################
 # New line preferences
 csharp_new_line_before_open_brace = none
-csharp_new_line_before_else = none
-csharp_new_line_before_catch = none
-csharp_new_line_before_finally = none
+csharp_new_line_before_else = false
+csharp_new_line_before_catch =false
+csharp_new_line_before_finally =false
 csharp_new_line_before_members_in_object_initializers = none
 csharp_new_line_before_members_in_anonymous_types = none
 csharp_new_line_between_query_expression_clauses = none
diff --git a/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj b/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj
index 5276ddd..c5115d3 100644
--- a/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj
+++ b/ProjectPSX.WinForms/ProjectPSX.WinForms.csproj
@@ -2,9 +2,10 @@
 
   <PropertyGroup>
     <OutputType>Exe</OutputType>
-    <TargetFramework>net5.0-windows</TargetFramework>
+    <TargetFramework>net9.0-windows7.0</TargetFramework>
     <UseWindowsForms>true</UseWindowsForms>
     <DisableWinExeOutputInference>true</DisableWinExeOutputInference>
+    <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
@@ -12,7 +13,7 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="NAudio.WinMM" Version="2.0.1" />
+    <PackageReference Include="NAudio.WinMM" Version="2.2.1" />
   </ItemGroup>
 
   <ItemGroup>
diff --git a/ProjectPSX.WinForms/UI/Window.cs b/ProjectPSX.WinForms/UI/Window.cs
index be20845..73ce2f2 100644
--- a/ProjectPSX.WinForms/UI/Window.cs
+++ b/ProjectPSX.WinForms/UI/Window.cs
@@ -68,6 +68,8 @@ public Window() {
             screen.MouseDoubleClick += new MouseEventHandler(toggleDebug);
 
             Controls.Add(screen);
+            // Debug will crash if not:
+            CheckForIllegalCrossThreadCalls = false;
 
             KeyDown += new KeyEventHandler(handleJoyPadDown);
             KeyUp += new KeyEventHandler(handleJoyPadUp);
@@ -169,16 +171,20 @@ public void Render(int[] vram) {
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private void blit24bpp(int[] vramBits) {
+        private unsafe void blit24bpp(int[] vramBits) {
             int yRangeOffset = (240 - (displayY2 - displayY1)) >> (verticalRes == 480 ? 0 : 1);
             if (yRangeOffset < 0) yRangeOffset = 0;
 
+            var display = new Span<int>(this.display.BitmapData.ToPointer(), 0x80000);
+            Span<int> scanLine = stackalloc int[horizontalRes];
+
             for (int y = yRangeOffset; y < verticalRes - yRangeOffset; y++) {
                 int offset = 0;
+                var startXYPosition = displayVRAMXStart + ((y - yRangeOffset + displayVRAMYStart) * 1024);
                 for (int x = 0; x < horizontalRes; x += 2) {
-                    int p0rgb = vramBits[(offset++ + displayVRAMXStart) + ((y - yRangeOffset  + displayVRAMYStart) * 1024)];
-                    int p1rgb = vramBits[(offset++ + displayVRAMXStart) + ((y - yRangeOffset  + displayVRAMYStart) * 1024)];
-                    int p2rgb = vramBits[(offset++ + displayVRAMXStart) + ((y - yRangeOffset  + displayVRAMYStart) * 1024)];
+                    int p0rgb = vramBits[startXYPosition + offset++];
+                    int p1rgb = vramBits[startXYPosition + offset++];
+                    int p2rgb = vramBits[startXYPosition + offset++];
 
                     ushort p0bgr555 = GetPixelBGR555(p0rgb);
                     ushort p1bgr555 = GetPixelBGR555(p1rgb);
@@ -197,9 +203,10 @@ private void blit24bpp(int[] vramBits) {
                     int p0rgb24bpp = p0R << 16 | p0G << 8 | p0B;
                     int p1rgb24bpp = p1R << 16 | p1G << 8 | p1B;
 
-                    display.DrawPixel(x, y, p0rgb24bpp);
-                    display.DrawPixel(x + 1, y, p1rgb24bpp);
+                    scanLine[x] = p0rgb24bpp;
+                    scanLine[x + 1] = p1rgb24bpp;
                 }
+                scanLine.CopyTo(display.Slice(y * 1024));
             }
 
         }
diff --git a/ProjectPSX/Core/BUS.cs b/ProjectPSX/Core/BUS.cs
index a851143..d21abe6 100644
--- a/ProjectPSX/Core/BUS.cs
+++ b/ProjectPSX/Core/BUS.cs
@@ -1,16 +1,12 @@
 using ProjectPSX.Devices;
+using ProjectPSX.Devices.Expansion;
 using System;
 using System.IO;
 using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
-using System.Threading.Tasks;
 
 namespace ProjectPSX {
-    //TODO:
-    //Do loadX and WriteX simple functions that return a value based on a generic load and write giant switch pointer return
-    // WIP: Already got rid of the multiple loadX writeX variants need to adress the giant switches but then how to handle the individual
-    // components? they expect allways an uint and they transform to variables. (uint)(object) is out probably because it kills perf and
-    // Unsafe.As is still noticeable so... some rework needed on the interaction between components and bus.
+
     public class BUS {
 
         //Memory
@@ -33,12 +29,13 @@ public class BUS {
         private JOYPAD joypad;
         private MDEC mdec;
         private SPU spu;
+        private Exp2 exp2;
 
         //temporary hardcoded bios/ex1
-        private static string bios = "./SCPH1001.BIN";
+        private static string bios = "./SCPH1001.BIN"; //SCPH1001 //openbios
         private static string ex1 = "./caetlaEXP.BIN";
 
-        public BUS(GPU gpu, CDROM cdrom, SPU spu, JOYPAD joypad, TIMERS timers, MDEC mdec, InterruptController interruptController) {
+        public BUS(GPU gpu, CDROM cdrom, SPU spu, JOYPAD joypad, TIMERS timers, MDEC mdec, InterruptController interruptController, Exp2 exp2) {
             dma = new DMA(this);
             this.gpu = gpu;
             this.cdrom = cdrom;
@@ -47,6 +44,7 @@ public BUS(GPU gpu, CDROM cdrom, SPU spu, JOYPAD joypad, TIMERS timers, MDEC mde
             this.spu = spu;
             this.joypad = joypad;
             this.interruptController = interruptController;
+            this.exp2 = exp2;
         }
 
         public unsafe uint load32(uint address) {
@@ -63,11 +61,13 @@ public unsafe uint load32(uint address) {
             } else if (addr < 0x1F80_1050) {
                 return joypad.load(addr);
             } else if (addr < 0x1F80_1060) {
+                //HardCode SIO_STAT
+                if (addr == 0x1F80_1054) return 0x0000_0805;
                 Console.WriteLine($"[BUS] Read Unsupported to SIO address: {addr:x8}");
                 return load<uint>(addr & 0xF, sio);
             } else if (addr < 0x1F80_1070) {
                 return load<uint>(addr & 0xF, memoryControl2);
-            } else if (addr < 0x1F801080) {
+            } else if (addr < 0x1F80_1080) {
                 return interruptController.load(addr);
             } else if (addr < 0x1F80_1100) {
                 return dma.load(addr);
@@ -83,8 +83,10 @@ public unsafe uint load32(uint address) {
                 return mdec.readMDEC0_Data();
             } else if (addr == 0x1F80_1824) {
                 return mdec.readMDEC1_Status();
-            } else if (addr < 0x1F802000) {
+            } else if (addr < 0x1F80_2000) {
                 return spu.load(addr);
+            } else if (addr < 0x1F80_4000) {
+                return exp2.load(addr);
             } else if (addr < 0x1FC8_0000) {
                 return load<uint>(addr & 0x7_FFFF, biosPtr);
             } else if (addr == 0xFFFE0130) {
@@ -113,7 +115,7 @@ public unsafe void write32(uint address, uint value) {
                 write(addr & 0xF, value, sio);
             } else if (addr < 0x1F80_1070) {
                 write(addr & 0xF, value, memoryControl2);
-            } else if (addr < 0x1F801080) {
+            } else if (addr < 0x1F80_1080) {
                 interruptController.write(addr, value);
             } else if (addr < 0x1F80_1100) {
                 dma.write(addr, value);
@@ -125,9 +127,11 @@ public unsafe void write32(uint address, uint value) {
                 gpu.write(addr, value);
             } else if (addr < 0x1F80_1830) {
                 mdec.write(addr, value);
-            } else if (addr < 0x1F802000) {
+            } else if (addr < 0x1F80_2000) {
                 spu.write(addr, (ushort)value);
-            } else if (addr == 0xFFFE0130) {
+            } else if (addr < 0x1F80_4000) {
+                exp2.write(addr, value);
+            } else if (addr == 0xFFFE_0130) {
                 memoryCache = value;
             } else {
                 Console.WriteLine($"[BUS] Write32 Unsupported: {addr:x8}");
@@ -141,7 +145,7 @@ public unsafe void write16(uint address, ushort value) {
                 write(addr & 0x1F_FFFF, value, ramPtr);
             } else if (addr < 0x1F80_0000) {
                 write(addr & 0x7_FFFF, value, ex1Ptr);
-            } else if (addr < 0x1f80_0400) {
+            } else if (addr < 0x1F80_0400) {
                 write(addr & 0x3FF, value, scrathpadPtr);
             } else if (addr < 0x1F80_1040) {
                 write(addr & 0x3F, value, memoryControl1);
@@ -152,7 +156,7 @@ public unsafe void write16(uint address, ushort value) {
                 write(addr & 0xF, value, sio);
             } else if (addr < 0x1F80_1070) {
                 write(addr & 0xF, value, memoryControl2);
-            } else if (addr < 0x1F801080) {
+            } else if (addr < 0x1F80_1080) {
                 interruptController.write(addr, value);
             } else if (addr < 0x1F80_1100) {
                 dma.write(addr, value);
@@ -164,9 +168,11 @@ public unsafe void write16(uint address, ushort value) {
                 gpu.write(addr, value);
             } else if (addr < 0x1F80_1830) {
                 mdec.write(addr, value);
-            } else if (addr < 0x1F802000) {
-                spu.write(addr, (ushort)value);
-            } else if (addr == 0xFFFE0130) {
+            } else if (addr < 0x1F80_2000) {
+                spu.write(addr, value);
+            } else if (addr < 0x1F80_4000) {
+                exp2.write(addr, value);
+            } else if (addr == 0xFFFE_0130) {
                 memoryCache = value;
             } else {
                 Console.WriteLine($"[BUS] Write16 Unsupported: {addr:x8}");
@@ -191,7 +197,7 @@ public unsafe void write8(uint address, byte value) {
                 write(addr & 0xF, value, sio);
             } else if (addr < 0x1F80_1070) {
                 write(addr & 0xF, value, memoryControl2);
-            } else if (addr < 0x1F801080) {
+            } else if (addr < 0x1F80_1080) {
                 interruptController.write(addr, value);
             } else if (addr < 0x1F80_1100) {
                 dma.write(addr, value);
@@ -203,20 +209,17 @@ public unsafe void write8(uint address, byte value) {
                 gpu.write(addr, value);
             } else if (addr < 0x1F80_1830) {
                 mdec.write(addr, value);
-            } else if (addr < 0x1F802000) {
-                spu.write(addr, (ushort)value);
-            } else if (addr == 0xFFFE0130) {
+            } else if (addr < 0x1F80_2000) {
+                spu.write(addr, value);
+            } else if (addr < 0x1F80_4000) {
+                exp2.write(addr, value);
+            } else if (addr == 0xFFFE_0130) {
                 memoryCache = value;
             } else {
                 Console.WriteLine($"[BUS] Write8 Unsupported: {addr:x8}");
             }
         }
 
-        private uint maskAddr(uint addr) {
-            uint i = addr >> 29;
-            return addr & RegionMask[i];
-        }
-
         internal unsafe void loadBios() {
             try {
                 byte[] rom = File.ReadAllBytes(bios);
@@ -278,7 +281,7 @@ public unsafe void loadEXE(String fileName) {
             write(0x6FF0 +  8, 0x3C1C0000 | R28 >> 16, biosPtr);
             write(0x6FF0 + 12, 0x379C0000 | R28 & 0xFFFF, biosPtr);
 
-            if(R29 != 0) {
+            if (R29 != 0) {
                 write(0x6FF0 + 16, 0x3C1D0000 | R29 >> 16, biosPtr);
                 write(0x6FF0 + 20, 0x37BD0000 | R29 & 0xFFFF, biosPtr);
 
@@ -410,19 +413,19 @@ public unsafe void DmaOTC(uint baseAddress, int size) {
             //var dest = new Span<uint>(ramPtr + (destAddress & 0x1F_FFFC), size);
             //dma.CopyTo(dest);
 
-            for(int i = 0; i < size - 1; i++) {
+            for (int i = 0; i < size - 1; i++) {
                 DmaToRam(baseAddress, baseAddress - 4);
                 baseAddress -= 4;
             }
-            
+
             DmaToRam(baseAddress, 0xFF_FFFF);
         }
 
         private static uint[] RegionMask = {
-        0xFFFF_FFFF, 0xFFFF_FFFF, 0xFFFF_FFFF, 0xFFFF_FFFF, // KUSEG: 2048MB
-        0x7FFF_FFFF,                                        // KSEG0:  512MB
-        0x1FFF_FFFF,                                        // KSEG1:  512MB
-        0xFFFF_FFFF, 0xFFFF_FFFF,                           // KSEG2: 1024MB
+            0xFFFF_FFFF, 0xFFFF_FFFF, 0xFFFF_FFFF, 0xFFFF_FFFF, // KUSEG: 2048MB
+            0x7FFF_FFFF,                                        // KSEG0:  512MB
+            0x1FFF_FFFF,                                        // KSEG1:  512MB
+            0xFFFF_FFFF, 0xFFFF_FFFF,                           // KSEG2: 1024MB
         };
     }
 }
diff --git a/ProjectPSX/Core/CPU.cs b/ProjectPSX/Core/CPU.cs
index 380c4c3..ff143fd 100644
--- a/ProjectPSX/Core/CPU.cs
+++ b/ProjectPSX/Core/CPU.cs
@@ -1,5 +1,7 @@
-using System;
+//#define CPU_EXCEPTIONS
+using System;
 using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
 using ProjectPSX.Disassembler;
 
 namespace ProjectPSX {
@@ -50,10 +52,10 @@ public struct Instr {
             public uint opcode => value >> 26;     //Instr opcode
 
             //I-Type
-            public uint rs => (value >> 21) & 0x1F;//Register Source
-            public uint rt => (value >> 16) & 0x1F;//Register Target
-            public uint imm => value & 0xFFFF;     //Immediate value
-            public uint imm_s => (uint)(short)imm; //Immediate value sign extended
+            public uint rs => (value >> 21) & 0x1F;  //Register Source
+            public uint rt => (value >> 16) & 0x1F;  //Register Target
+            public uint imm => (ushort)value;        //Immediate value
+            public uint imm_s => (uint)(short)value; //Immediate value sign extended
 
             //R-Type
             public uint rd => (value >> 11) & 0x1F;
@@ -69,7 +71,6 @@ public struct Instr {
         private Instr instr;
 
         //Debug
-        private long cycle; //current CPU cycle counter for debug
         public bool debug = false;
 
         public CPU(BUS bus) {
@@ -79,50 +80,9 @@ public CPU(BUS bus) {
             gte = new GTE();
 
             COP0_GPR[15] = 0x2; //PRID Processor ID
+        }
 
-            initOpCodeTable();
-        }
-
-        private static delegate*<CPU, void>[] opcodeMainTable;
-        private static delegate*<CPU, void>[] opcodeSpecialTable;
-
-        private void initOpCodeTable() {
-            static void SPECIAL(CPU cpu) => cpu.SPECIAL();
-            static void BCOND(CPU cpu) => cpu.BCOND();
-            static void J(CPU cpu) => cpu.J();
-            static void JAL(CPU cpu) => cpu.JAL();
-            static void BEQ(CPU cpu) => cpu.BEQ();
-            static void BNE(CPU cpu) => cpu.BNE();
-            static void BLEZ(CPU cpu) => cpu.BLEZ();
-            static void BGTZ(CPU cpu) => cpu.BGTZ();
-            static void ADDI(CPU cpu) => cpu.ADDI();
-            static void ADDIU(CPU cpu) => cpu.ADDIU();
-            static void SLTI(CPU cpu) => cpu.SLTI();
-            static void SLTIU(CPU cpu) => cpu.SLTIU();
-            static void ANDI(CPU cpu) => cpu.ANDI();
-            static void ORI(CPU cpu) => cpu.ORI();
-            static void XORI(CPU cpu) => cpu.XORI();
-            static void LUI(CPU cpu) => cpu.LUI();
-            static void COP0(CPU cpu) => cpu.COP0();
-            static void NOP(CPU cpu) => cpu.NOP();
-            static void COP2(CPU cpu) => cpu.COP2();
-            static void NA(CPU cpu) => cpu.NA();
-            static void LB(CPU cpu) => cpu.LB();
-            static void LH(CPU cpu) => cpu.LH();
-            static void LWL(CPU cpu) => cpu.LWL();
-            static void LW(CPU cpu) => cpu.LW();
-            static void LBU(CPU cpu) => cpu.LBU();
-            static void LHU(CPU cpu) => cpu.LHU();
-            static void LWR(CPU cpu) => cpu.LWR();
-            static void SB(CPU cpu) => cpu.SB();
-            static void SH(CPU cpu) => cpu.SH();
-            static void SWL(CPU cpu) => cpu.SWL();
-            static void SW(CPU cpu) => cpu.SW();
-            static void SWR(CPU cpu) => cpu.SWR();
-            static void LWC2(CPU cpu) => cpu.LWC2();
-            static void SWC2(CPU cpu) => cpu.SWC2();
-
-            opcodeMainTable = new delegate*<CPU, void>[] {
+        private static delegate*<CPU, void>[] opcodeMainTable = new delegate*<CPU, void>[] {
                 &SPECIAL,  &BCOND,  &J,      &JAL,    &BEQ,    &BNE,    &BLEZ,   &BGTZ,
                 &ADDI,     &ADDIU,  &SLTI,   &SLTIU,  &ANDI,   &ORI,    &XORI,   &LUI,
                 &COP0,     &NOP,    &COP2,   &NOP,    &NA,     &NA,     &NA,     &NA,
@@ -133,36 +93,7 @@ private void initOpCodeTable() {
                 &NOP,      &NOP,    &SWC2,   &NOP,    &NA,     &NA,     &NA,     &NA,
             };
 
-            static void SLL(CPU cpu) => cpu.SLL();
-            static void SRL(CPU cpu) => cpu.SRL();
-            static void SRA(CPU cpu) => cpu.SRA();
-            static void SLLV(CPU cpu) => cpu.SLLV();
-            static void SRLV(CPU cpu) => cpu.SRLV();
-            static void SRAV(CPU cpu) => cpu.SRAV();
-            static void JR(CPU cpu) => cpu.JR();
-            static void SYSCALL(CPU cpu) => cpu.SYSCALL();
-            static void BREAK(CPU cpu) => cpu.BREAK();
-            static void JALR(CPU cpu) => cpu.JALR();
-            static void MFHI(CPU cpu) => cpu.MFHI();
-            static void MTHI(CPU cpu) => cpu.MTHI();
-            static void MFLO(CPU cpu) => cpu.MFLO();
-            static void MTLO(CPU cpu) => cpu.MTLO();
-            static void MULT(CPU cpu) => cpu.MULT();
-            static void MULTU(CPU cpu) => cpu.MULTU();
-            static void DIV(CPU cpu) => cpu.DIV();
-            static void DIVU(CPU cpu) => cpu.DIVU();
-            static void ADD(CPU cpu) => cpu.ADD();
-            static void ADDU(CPU cpu) => cpu.ADDU();
-            static void SUB(CPU cpu) => cpu.SUB();
-            static void SUBU(CPU cpu) => cpu.SUBU();
-            static void AND(CPU cpu) => cpu.AND();
-            static void OR(CPU cpu) => cpu.OR();
-            static void XOR(CPU cpu) => cpu.XOR();
-            static void NOR(CPU cpu) => cpu.NOR();
-            static void SLT(CPU cpu) => cpu.SLT();
-            static void SLTU(CPU cpu) => cpu.SLTU();
-
-            opcodeSpecialTable = new delegate*<CPU, void>[] {
+        private static delegate*<CPU, void>[] opcodeSpecialTable = new delegate*<CPU, void>[] {
                 &SLL,   &NA,    &SRL,   &SRA,   &SLLV,    &NA,     &SRLV, &SRAV,
                 &JR,    &JALR,  &NA,    &NA,    &SYSCALL, &BREAK,  &NA,   &NA,
                 &MFHI,  &MTHI,  &MFLO,  &MTLO,  &NA,      &NA,     &NA,   &NA,
@@ -172,11 +103,10 @@ private void initOpCodeTable() {
                 &NA,    &NA,    &NA,    &NA,    &NA,      &NA,     &NA,   &NA,
                 &NA,    &NA,    &NA,    &NA,    &NA,      &NA,     &NA,   &NA,
             };
-        }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public void Run() {
-            fetchDecode();
+        public int Run() {
+            int ticks = fetchDecode();
             if (instr.value != 0) { //Skip Nops
                 opcodeMainTable[instr.opcode](this); //Execute
             }
@@ -190,6 +120,8 @@ public void Run() {
 
             //TTY();
             //bios.verbose(PC_Now, GPR);
+
+            return ticks;
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -219,24 +151,18 @@ public void handleInterrupts() {
             }
 
             bool IEC = (COP0_GPR[SR] & 0x1) == 1;
-            uint IM = (COP0_GPR[SR] >> 8) & 0xFF;
-            uint IP = (COP0_GPR[CAUSE] >> 8) & 0xFF;
+            byte IM = (byte)((COP0_GPR[SR] >> 8) & 0xFF);
+            byte IP = (byte)((COP0_GPR[CAUSE] >> 8) & 0xFF);
 
             if (IEC && (IM & IP) > 0) {
-                EXCEPTION(EX.INTERRUPT);
+                EXCEPTION(this, EX.INTERRUPT);
             }
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private void fetchDecode() {
+        private int fetchDecode() {
             //Executable address space is limited to ram and bios on psx
             uint maskedPC = PC & 0x1FFF_FFFF;
-            uint load;
-            if (maskedPC < 0x1F00_0000) {
-                load = bus.LoadFromRam(maskedPC);
-            } else {
-                load = bus.LoadFromBios(maskedPC);
-            }
 
             PC_Now = PC;
             PC = PC_Predictor;
@@ -247,20 +173,28 @@ private void fetchDecode() {
             opcodeIsBranch = false;
             opcodeTookBranch = false;
 
+#if CPU_EXCEPTIONS
             if ((PC_Now & 0x3) != 0) {
                 COP0_GPR[BADA] = PC_Now;
-                EXCEPTION(EX.LOAD_ADRESS_ERROR);
-                return;
+                EXCEPTION(this, EX.LOAD_ADRESS_ERROR);
+                return 1;
             }
+#endif
 
-            instr.value = load;
-            //cycle++;
+            if (maskedPC < 0x1F00_0000) {
+                instr.value = bus.LoadFromRam(maskedPC);
+                return 1;
+            } else {
+                instr.value = bus.LoadFromBios(maskedPC);
+                return 20;
+            }
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private void MemAccess() {
             if (delayedMemoryLoad.register != memoryLoad.register) { //if loadDelay on same reg it is lost/overwritten (amidog tests)
-                GPR[memoryLoad.register] = memoryLoad.value;
+                ref uint r0 = ref MemoryMarshal.GetArrayDataReference(GPR);
+                Unsafe.Add(ref r0, (nint)memoryLoad.register) = memoryLoad.value;
             }
             memoryLoad = delayedMemoryLoad;
             delayedMemoryLoad.register = 0;
@@ -268,539 +202,572 @@ private void MemAccess() {
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private void WriteBack() {
-            GPR[writeBack.register] = writeBack.value;
+            ref uint r0 = ref MemoryMarshal.GetArrayDataReference(GPR);
+            Unsafe.Add(ref r0, (nint)writeBack.register) = writeBack.value;
             writeBack.register = 0;
-            GPR[0] = 0;
+            r0 = 0;
         }
 
         // Non Implemented by the CPU Opcodes
-        private void NOP() { /*nop*/ }
+        private static void NOP(CPU cpu) { /*nop*/ }
 
-        private void NA() => EXCEPTION(EX.ILLEGAL_INSTR, instr.id);
+        private static void NA(CPU cpu) => EXCEPTION(cpu, EX.ILLEGAL_INSTR, cpu.instr.id);
 
 
         // Main Table Opcodes
-        private void SPECIAL() => opcodeSpecialTable[instr.function](this);
+        private static void SPECIAL(CPU cpu) => opcodeSpecialTable[cpu.instr.function](cpu);
 
-        private void BCOND() {
-            opcodeIsBranch = true;
-            uint op = instr.rt;
+        private static void BCOND(CPU cpu) {
+            cpu.opcodeIsBranch = true;
+            uint op = cpu.instr.rt;
 
             bool should_link = (op & 0x1E) == 0x10;
-            bool should_branch = (int)(GPR[instr.rs] ^ (op << 31)) < 0;
+            bool should_branch = (int)(cpu.GPR[cpu.instr.rs] ^ (op << 31)) < 0;
 
-            if (should_link) GPR[31] = PC_Predictor;
-            if (should_branch) BRANCH();
+            if (should_link) cpu.GPR[31] = cpu.PC_Predictor;
+            if (should_branch) BRANCH(cpu);
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private void J() {
-            opcodeIsBranch = true;
-            opcodeTookBranch = true;
-            PC_Predictor = (PC_Predictor & 0xF000_0000) | (instr.addr << 2);
+        private static void J(CPU cpu) {
+            cpu.opcodeIsBranch = true;
+            cpu.opcodeTookBranch = true;
+            cpu.PC_Predictor = (cpu.PC_Predictor & 0xF000_0000) | (cpu.instr.addr << 2);
         }
 
-        private void JAL() {
-            setGPR(31, PC_Predictor);
-            J();
+        private static void JAL(CPU cpu) {
+            cpu.setGPR(31, cpu.PC_Predictor);
+            J(cpu);
         }
 
-        private void BEQ() {
-            opcodeIsBranch = true;
-            if (GPR[instr.rs] == GPR[instr.rt]) {
-                BRANCH();
+        private static void BEQ(CPU cpu) {
+            cpu.opcodeIsBranch = true;
+            if (cpu.GPR[cpu.instr.rs] == cpu.GPR[cpu.instr.rt]) {
+                BRANCH(cpu);
             }
         }
 
-        private void BNE() {
-            opcodeIsBranch = true;
-            if (GPR[instr.rs] != GPR[instr.rt]) {
-                BRANCH();
+        private static void BNE(CPU cpu) {
+            cpu.opcodeIsBranch = true;
+            if (cpu.GPR[cpu.instr.rs] != cpu.GPR[cpu.instr.rt]) {
+                BRANCH(cpu);
             }
         }
 
-        private void BLEZ() {
-            opcodeIsBranch = true;
-            if (((int)GPR[instr.rs]) <= 0) {
-                BRANCH();
+        private static void BLEZ(CPU cpu) {
+            cpu.opcodeIsBranch = true;
+            if (((int)cpu.GPR[cpu.instr.rs]) <= 0) {
+                BRANCH(cpu);
             }
         }
 
-        private void BGTZ() {
-            opcodeIsBranch = true;
-            if (((int)GPR[instr.rs]) > 0) {
-                BRANCH();
+        private static void BGTZ(CPU cpu) {
+            cpu.opcodeIsBranch = true;
+            if (((int)cpu.GPR[cpu.instr.rs]) > 0) {
+                BRANCH(cpu);
             }
         }
 
-        private void ADDI() {
-            uint rs = GPR[instr.rs];
-            uint imm_s = instr.imm_s;
+        private static void ADDI(CPU cpu) {
+            uint rs = cpu.GPR[cpu.instr.rs];
+            uint imm_s = cpu.instr.imm_s;
             uint result = rs + imm_s;
+
+#if CPU_EXCEPTIONS
             if(checkOverflow(rs, imm_s, result)) {
-                EXCEPTION(EX.OVERFLOW, instr.id);
+                EXCEPTION(cpu, EX.OVERFLOW, cpu.instr.id);
             } else {
-                setGPR(instr.rt, result);
+                cpu.setGPR(cpu.instr.rt, result);
             }
+#else
+            cpu.setGPR(cpu.instr.rt, result);
+#endif
         }
 
-        private void ADDIU() => setGPR(instr.rt, GPR[instr.rs] + instr.imm_s);
+        private static void ADDIU(CPU cpu) => cpu.setGPR(cpu.instr.rt, cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s);
 
-        private void SLTI() {
-            bool condition = (int)GPR[instr.rs] < (int)instr.imm_s;
-            setGPR(instr.rt, Unsafe.As<bool, uint>(ref condition));
+        private static void SLTI(CPU cpu) {
+            bool condition = (int)cpu.GPR[cpu.instr.rs] < (int)cpu.instr.imm_s;
+            cpu.setGPR(cpu.instr.rt, Unsafe.As<bool, uint>(ref condition));
         }
 
-        private void SLTIU() {
-            bool condition = GPR[instr.rs] < instr.imm_s;
-            setGPR(instr.rt, Unsafe.As<bool, uint>(ref condition));
+        private static void SLTIU(CPU cpu) {
+            bool condition = cpu.GPR[cpu.instr.rs] < cpu.instr.imm_s;
+            cpu.setGPR(cpu.instr.rt, Unsafe.As<bool, uint>(ref condition));
         }
 
-        private void ANDI() => setGPR(instr.rt, GPR[instr.rs] & instr.imm);
+        private static void ANDI(CPU cpu) => cpu.setGPR(cpu.instr.rt, cpu.GPR[cpu.instr.rs] & cpu.instr.imm);
 
-        private void ORI() => setGPR(instr.rt, GPR[instr.rs] | instr.imm);
+        private static void ORI(CPU cpu) => cpu.setGPR(cpu.instr.rt, cpu.GPR[cpu.instr.rs] | cpu.instr.imm);
 
-        private void XORI() => setGPR(instr.rt, GPR[instr.rs] ^ instr.imm);
+        private static void XORI(CPU cpu) => cpu.setGPR(cpu.instr.rt, cpu.GPR[cpu.instr.rs] ^ cpu.instr.imm);
 
-        private void LUI() => setGPR(instr.rt, instr.imm << 16);
+        private static void LUI(CPU cpu) => cpu.setGPR(cpu.instr.rt, cpu.instr.imm << 16);
 
-        private void COP0() {
-            if (instr.rs == 0b0_0000) MFC0();
-            else if (instr.rs == 0b0_0100) MTC0();
-            else if (instr.rs == 0b1_0000) RFE();
-            else EXCEPTION(EX.ILLEGAL_INSTR, instr.id);
+        private static void COP0(CPU cpu) {
+            if (cpu.instr.rs == 0b0_0000) MFC0(cpu);
+            else if (cpu.instr.rs == 0b0_0100) MTC0(cpu);
+            else if (cpu.instr.rs == 0b1_0000) RFE(cpu);
+            else EXCEPTION(cpu, EX.ILLEGAL_INSTR, cpu.instr.id);
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private void MFC0() {
-            uint mfc = instr.rd;
+        private static void MFC0(CPU cpu) {
+            uint mfc = cpu.instr.rd;
             if (mfc == 3 || mfc >= 5 && mfc <= 9 || mfc >= 11 && mfc <= 15) {
-                delayedLoad(instr.rt, COP0_GPR[mfc]);
+                delayedLoad(cpu, cpu.instr.rt, cpu.COP0_GPR[mfc]);
             } else {
-                EXCEPTION(EX.ILLEGAL_INSTR, instr.id);
+                EXCEPTION(cpu, EX.ILLEGAL_INSTR, cpu.instr.id);
             }
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private void MTC0() {
-            uint value = GPR[instr.rt];
-            uint register = instr.rd;
+        private static void MTC0(CPU cpu) {
+            uint value = cpu.GPR[cpu.instr.rt];
+            uint register = cpu.instr.rd;
 
             if (register == CAUSE) { //only bits 8 and 9 are writable
-                COP0_GPR[CAUSE] &= ~(uint)0x300;
-                COP0_GPR[CAUSE] |= value & 0x300;
+                cpu.COP0_GPR[CAUSE] &= ~(uint)0x300;
+                cpu.COP0_GPR[CAUSE] |= value & 0x300;
             } else if (register == SR) {
                 //This can trigger soft interrupts
-                dontIsolateCache = (value & 0x10000) == 0;
-                bool prevIEC = (COP0_GPR[SR] & 0x1) == 1;
+                cpu.dontIsolateCache = (value & 0x10000) == 0;
+                bool prevIEC = (cpu.COP0_GPR[SR] & 0x1) == 1;
                 bool currentIEC = (value & 0x1) == 1;
 
-                COP0_GPR[SR] = value;
+                cpu.COP0_GPR[SR] = value;
 
                 uint IM = (value >> 8) & 0x3;
-                uint IP = (COP0_GPR[CAUSE] >> 8) & 0x3;
+                uint IP = (cpu.COP0_GPR[CAUSE] >> 8) & 0x3;
 
                 if (!prevIEC && currentIEC && (IM & IP) > 0) {
-                    PC = PC_Predictor;
-                    EXCEPTION(EX.INTERRUPT, instr.id);
+                    cpu.PC = cpu.PC_Predictor;
+                    EXCEPTION(cpu, EX.INTERRUPT, cpu.instr.id);
                 }
 
             } else {
-                COP0_GPR[register] = value;
+                cpu.COP0_GPR[register] = value;
             }
 
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private void RFE() {
-            uint mode = COP0_GPR[SR] & 0x3F;
-            COP0_GPR[SR] &= ~(uint)0xF;
-            COP0_GPR[SR] |= mode >> 2;
+        private static void RFE(CPU cpu) {
+            uint mode = cpu.COP0_GPR[SR] & 0x3F;
+            cpu.COP0_GPR[SR] &= ~(uint)0xF;
+            cpu.COP0_GPR[SR] |= mode >> 2;
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private void EXCEPTION(EX cause, uint coprocessor = 0) {
-            uint mode = COP0_GPR[SR] & 0x3F;
-            COP0_GPR[SR] &= ~(uint)0x3F;
-            COP0_GPR[SR] |= (mode << 2) & 0x3F;
+        private static void EXCEPTION(CPU cpu, EX cause, uint coprocessor = 0) {
+            uint mode = cpu.COP0_GPR[SR] & 0x3F;
+            cpu.COP0_GPR[SR] &= ~(uint)0x3F;
+            cpu.COP0_GPR[SR] |= (mode << 2) & 0x3F;
 
-            uint OldCause = COP0_GPR[CAUSE] & 0xff00;
-            COP0_GPR[CAUSE] = (uint)cause << 2;
-            COP0_GPR[CAUSE] |= OldCause;
-            COP0_GPR[CAUSE] |= coprocessor << 28;
+            uint OldCause = cpu.COP0_GPR[CAUSE] & 0xff00;
+            cpu.COP0_GPR[CAUSE] = (uint)cause << 2;
+            cpu.COP0_GPR[CAUSE] |= OldCause;
+            cpu.COP0_GPR[CAUSE] |= coprocessor << 28;
 
             if (cause == EX.INTERRUPT) {
-                COP0_GPR[EPC] = PC;
+                cpu.COP0_GPR[EPC] = cpu.PC;
                 //hack: related to the delay of the ex interrupt
-                opcodeIsDelaySlot = opcodeIsBranch;
-                opcodeInDelaySlotTookBranch = opcodeTookBranch;
+                cpu.opcodeIsDelaySlot = cpu.opcodeIsBranch;
+                cpu.opcodeInDelaySlotTookBranch = cpu.opcodeTookBranch;
             } else {
-                COP0_GPR[EPC] = PC_Now;
+                cpu.COP0_GPR[EPC] = cpu.PC_Now;
             }
 
-            if (opcodeIsDelaySlot) {
-                COP0_GPR[EPC] -= 4;
-                COP0_GPR[CAUSE] |= (uint)1 << 31;
-                COP0_GPR[JUMPDEST] = PC;
+            if (cpu.opcodeIsDelaySlot) {
+                cpu.COP0_GPR[EPC] -= 4;
+                cpu.COP0_GPR[CAUSE] |= (uint)1 << 31;
+                cpu.COP0_GPR[JUMPDEST] = cpu.PC;
 
-                if (opcodeInDelaySlotTookBranch) {
-                    COP0_GPR[CAUSE] |= (1 << 30);
+                if (cpu.opcodeInDelaySlotTookBranch) {
+                    cpu.COP0_GPR[CAUSE] |= (1 << 30);
                 }
             }
 
-            PC = ExceptionAdress[COP0_GPR[SR] & 0x400000 >> 22];
-            PC_Predictor = PC + 4;
+            cpu.PC = ExceptionAdress[cpu.COP0_GPR[SR] & 0x400000 >> 22];
+            cpu.PC_Predictor = cpu.PC + 4;
         }
 
-        private void COP2() {
-            if ((instr.rs & 0x10) == 0) {
-                switch (instr.rs) {
-                    case 0b0_0000: MFC2(); break;
-                    case 0b0_0010: CFC2(); break;
-                    case 0b0_0100: MTC2(); break;
-                    case 0b0_0110: CTC2(); break;
-                    default: EXCEPTION(EX.ILLEGAL_INSTR, instr.id); break;
+        private static void COP2(CPU cpu) {
+            if ((cpu.instr.rs & 0x10) == 0) {
+                switch (cpu.instr.rs) {
+                    case 0b0_0000: MFC2(cpu); break;
+                    case 0b0_0010: CFC2(cpu); break;
+                    case 0b0_0100: MTC2(cpu); break;
+                    case 0b0_0110: CTC2(cpu); break;
+                    default: EXCEPTION(cpu, EX.ILLEGAL_INSTR, cpu.instr.id); break;
                 }
             } else {
-                gte.execute(instr.value);
+                cpu.gte.execute(cpu.instr.value);
             }
         }
 
-        private void MFC2() => delayedLoad(instr.rt, gte.loadData(instr.rd));
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private static void MFC2(CPU cpu) => delayedLoad(cpu, cpu.instr.rt, cpu.gte.loadData(cpu.instr.rd));
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private void CFC2() => delayedLoad(instr.rt, gte.loadControl(instr.rd));
+        private static void CFC2(CPU cpu) => delayedLoad(cpu, cpu.instr.rt, cpu.gte.loadControl(cpu.instr.rd));
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private void MTC2() => gte.writeData(instr.rd, GPR[instr.rt]);
+        private static void MTC2(CPU cpu) => cpu.gte.writeData(cpu.instr.rd, cpu.GPR[cpu.instr.rt]);
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private void CTC2() => gte.writeControl(instr.rd, GPR[instr.rt]);
+        private static void CTC2(CPU cpu) => cpu.gte.writeControl(cpu.instr.rd, cpu.GPR[cpu.instr.rt]);
 
-        private void LWC2() { //TODO WARNING THIS SHOULD HAVE DELAY?
-            uint addr = GPR[instr.rs] + instr.imm_s;
+        private static void LWC2(CPU cpu) {
+            uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s;
 
+#if CPU_EXCEPTIONS
             if ((addr & 0x3) == 0) {
-                uint value = bus.load32(addr);
-                gte.writeData(instr.rt, value);
+                uint value = cpu.bus.load32(addr);
+                cpu.gte.writeData(cpu.instr.rt, value);
             } else {
-                COP0_GPR[BADA] = addr;
-                EXCEPTION(EX.LOAD_ADRESS_ERROR, instr.id);
+                cpu.COP0_GPR[BADA] = addr;
+                EXCEPTION(cpu, EX.LOAD_ADRESS_ERROR, cpu.instr.id);
             }
+#else
+            uint value = cpu.bus.load32(addr);
+            cpu.gte.writeData(cpu.instr.rt, value);
+#endif
         }
 
-        private void SWC2() { //TODO WARNING THIS SHOULD HAVE DELAY?
-            uint addr = GPR[instr.rs] + instr.imm_s;
+        private static void SWC2(CPU cpu) {
+            uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s;
 
+#if CPU_EXCEPTIONS
             if ((addr & 0x3) == 0) {
-                bus.write32(addr, gte.loadData(instr.rt));
+                cpu.bus.write32(addr, cpu.gte.loadData(cpu.instr.rt));
             } else {
-                COP0_GPR[BADA] = addr;
-                EXCEPTION(EX.LOAD_ADRESS_ERROR, instr.id);
+                cpu.COP0_GPR[BADA] = addr;
+                EXCEPTION(cpu, EX.LOAD_ADRESS_ERROR, cpu.instr.id);
             }
+#else
+            cpu.bus.write32(addr, cpu.gte.loadData(cpu.instr.rt));
+#endif
         }
 
-        private void LB() { //todo redo this as it unnecesary load32
-            if (dontIsolateCache) {
-                uint value = (uint)(sbyte)bus.load32(GPR[instr.rs] + instr.imm_s);
-                delayedLoad(instr.rt, value);
+        private static void LB(CPU cpu) { //todo redo this as it unnecesary load32
+            if (cpu.dontIsolateCache) {
+                uint value = (uint)(sbyte)cpu.bus.load32(cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s);
+                delayedLoad(cpu, cpu.instr.rt, value);
             } //else Console.WriteLine("IsolatedCache: Ignoring Load");
         }
 
-        private void LBU() {
-            if (dontIsolateCache) {
-                uint value = (byte)bus.load32(GPR[instr.rs] + instr.imm_s);
-                delayedLoad(instr.rt, value);
+        private static void LBU(CPU cpu) {
+            if (cpu.dontIsolateCache) {
+                uint value = (byte)cpu.bus.load32(cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s);
+                delayedLoad(cpu, cpu.instr.rt, value);
             } //else Console.WriteLine("IsolatedCache: Ignoring Load");
         }
 
-        private void LH() {
-            if (dontIsolateCache) {
-                uint addr = GPR[instr.rs] + instr.imm_s;
+        private static void LH(CPU cpu) {
+            if (cpu.dontIsolateCache) {
+                uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s;
 
+#if CPU_EXCEPTIONS
                 if ((addr & 0x1) == 0) {
-                    uint value = (uint)(short)bus.load32(addr);
-                    delayedLoad(instr.rt, value);
+                    uint value = (uint)(short)cpu.bus.load32(addr);
+                    delayedLoad(cpu, cpu.instr.rt, value);
                 } else {
-                    COP0_GPR[BADA] = addr;
-                    EXCEPTION(EX.LOAD_ADRESS_ERROR, instr.id);
+                    cpu.COP0_GPR[BADA] = addr;
+                    EXCEPTION(cpu, EX.LOAD_ADRESS_ERROR, cpu.instr.id);
                 }
+#else
+                uint value = (uint)(short)cpu.bus.load32(addr);
+                delayedLoad(cpu, cpu.instr.rt, value);
+#endif
 
             } //else Console.WriteLine("IsolatedCache: Ignoring Load");
         }
 
-        private void LHU() {
-            if (dontIsolateCache) {
-                uint addr = GPR[instr.rs] + instr.imm_s;
+        private static void LHU(CPU cpu) {
+            if (cpu.dontIsolateCache) {
+                uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s;
 
+#if CPU_EXCEPTIONS
                 if ((addr & 0x1) == 0) {
-                    uint value = (ushort)bus.load32(addr);
-                    delayedLoad(instr.rt, value);
+                    uint value = (ushort)cpu.bus.load32(addr);
+                    delayedLoad(cpu, cpu.instr.rt, value);
                 } else {
-                    COP0_GPR[BADA] = addr;
-                    EXCEPTION(EX.LOAD_ADRESS_ERROR, instr.id);
+                    cpu.COP0_GPR[BADA] = addr;
+                    EXCEPTION(cpu, EX.LOAD_ADRESS_ERROR, cpu.instr.id);
                 }
+#else
+                uint value = (ushort)cpu.bus.load32(addr);
+                delayedLoad(cpu, cpu.instr.rt, value);
+#endif
 
             } //else Console.WriteLine("IsolatedCache: Ignoring Load");
         }
 
-        private void LW() {
-            if (dontIsolateCache) {
-                uint addr = GPR[instr.rs] + instr.imm_s;
+        private static void LW(CPU cpu) {
+            if (cpu.dontIsolateCache) {
+                uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s;
 
+#if CPU_EXCEPTIONS
                 if ((addr & 0x3) == 0) {
-                    uint value = bus.load32(addr);
-                    delayedLoad(instr.rt, value);
+                    uint value = cpu.bus.load32(addr);
+                    delayedLoad(cpu, cpu.instr.rt, value);
                 } else {
-                    COP0_GPR[BADA] = addr;
-                    EXCEPTION(EX.LOAD_ADRESS_ERROR, instr.id);
+                    cpu.COP0_GPR[BADA] = addr;
+                    EXCEPTION(cpu, EX.LOAD_ADRESS_ERROR, cpu.instr.id);
                 }
+#else
+                uint value = cpu.bus.load32(addr);
+                delayedLoad(cpu, cpu.instr.rt, value);
+#endif
 
             } //else Console.WriteLine("IsolatedCache: Ignoring Load");
         }
 
-        private void LWL() {
-            uint addr = GPR[instr.rs] + instr.imm_s;
+        private static void LWL(CPU cpu) {
+            uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s;
             uint aligned_addr = addr & 0xFFFF_FFFC;
-            uint aligned_load = bus.load32(aligned_addr);
+            uint aligned_load = cpu.bus.load32(aligned_addr);
 
-            uint value = 0;
-            uint LRValue = GPR[instr.rt];
+            uint LRValue = cpu.GPR[cpu.instr.rt];
 
-            if (instr.rt == memoryLoad.register) {
-                LRValue = memoryLoad.value;
+            if (cpu.instr.rt == cpu.memoryLoad.register) {
+                LRValue = cpu.memoryLoad.value;
             }
 
-            switch (addr & 0b11) {
-                case 0: value = (LRValue & 0x00FF_FFFF) | (aligned_load << 24); break;
-                case 1: value = (LRValue & 0x0000_FFFF) | (aligned_load << 16); break;
-                case 2: value = (LRValue & 0x0000_00FF) | (aligned_load << 8); break;
-                case 3: value = aligned_load; break;
-            }
+            int shift = (int)((addr & 0x3) << 3);
+            uint mask = (uint)0x00FF_FFFF >> shift;
+            uint value = (LRValue & mask) | (aligned_load << (24 - shift)); 
 
-            delayedLoad(instr.rt, value);
+            delayedLoad(cpu, cpu.instr.rt, value);
         }
 
-        private void LWR() {
-            uint addr = GPR[instr.rs] + instr.imm_s;
+        private static void LWR(CPU cpu) {
+            uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s;
             uint aligned_addr = addr & 0xFFFF_FFFC;
-            uint aligned_load = bus.load32(aligned_addr);
+            uint aligned_load = cpu.bus.load32(aligned_addr);
 
-            uint value = 0;
-            uint LRValue = GPR[instr.rt];
+            uint LRValue = cpu.GPR[cpu.instr.rt];
 
-            if (instr.rt == memoryLoad.register) {
-                LRValue = memoryLoad.value;
+            if (cpu.instr.rt == cpu.memoryLoad.register) {
+                LRValue = cpu.memoryLoad.value;
             }
 
-            switch (addr & 0b11) {
-                case 0: value = aligned_load; break;
-                case 1: value = (LRValue & 0xFF00_0000) | (aligned_load >> 8); break;
-                case 2: value = (LRValue & 0xFFFF_0000) | (aligned_load >> 16); break;
-                case 3: value = (LRValue & 0xFFFF_FF00) | (aligned_load >> 24); break;
-            }
+            int shift = (int)((addr & 0x3) << 3);
+            uint mask = 0xFFFF_FF00 << (24 - shift);
+            uint value = (LRValue & mask) | (aligned_load >> shift);
 
-            delayedLoad(instr.rt, value);
+            delayedLoad(cpu, cpu.instr.rt, value);
         }
 
-        private void SB() {
-            if (dontIsolateCache)
-                bus.write8(GPR[instr.rs] + instr.imm_s, (byte)GPR[instr.rt]);
+        private static void SB(CPU cpu) {
+            if (cpu.dontIsolateCache)
+                cpu.bus.write8(cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s, (byte)cpu.GPR[cpu.instr.rt]);
             //else Console.WriteLine("IsolatedCache: Ignoring Write");
         }
 
-        private void SH() {
-            if (dontIsolateCache) {
-                uint addr = GPR[instr.rs] + instr.imm_s;
+        private static void SH(CPU cpu) {
+            if (cpu.dontIsolateCache) {
+                uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s;
 
+#if CPU_EXCEPTIONS
                 if ((addr & 0x1) == 0) {
-                    bus.write16(addr, (ushort)GPR[instr.rt]);
+                    cpu.bus.write16(addr, (ushort)cpu.GPR[cpu.instr.rt]);
                 } else {
-                    COP0_GPR[BADA] = addr;
-                    EXCEPTION(EX.STORE_ADRESS_ERROR, instr.id);
+                    cpu.COP0_GPR[BADA] = addr;
+                    EXCEPTION(cpu, EX.STORE_ADRESS_ERROR, cpu.instr.id);
                 }
+#else
+                cpu.bus.write16(addr, (ushort)cpu.GPR[cpu.instr.rt]);
+#endif
             } //else Console.WriteLine("IsolatedCache: Ignoring Write");
         }
 
-        private void SW() {
-            if (dontIsolateCache) {
-                uint addr = GPR[instr.rs] + instr.imm_s;
+        private static void SW(CPU cpu) {
+            if (cpu.dontIsolateCache) {
+                uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s;
 
+#if CPU_EXCEPTIONS
                 if ((addr & 0x3) == 0) {
-                    bus.write32(addr, GPR[instr.rt]);
+                    cpu.bus.write32(addr, cpu.GPR[cpu.instr.rt]);
                 } else {
-                    COP0_GPR[BADA] = addr;
-                    EXCEPTION(EX.STORE_ADRESS_ERROR, instr.id);
+                    cpu.COP0_GPR[BADA] = addr;
+                    EXCEPTION(cpu, EX.STORE_ADRESS_ERROR, cpu.instr.id);
                 }
+#else
+                cpu.bus.write32(addr, cpu.GPR[cpu.instr.rt]);
+#endif
             } //else Console.WriteLine("IsolatedCache: Ignoring Write");
         }
 
-        private void SWR() {
-            uint addr = GPR[instr.rs] + instr.imm_s;
+        private static void SWR(CPU cpu) {
+            uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s;
             uint aligned_addr = addr & 0xFFFF_FFFC;
-            uint aligned_load = bus.load32(aligned_addr);
-
-            uint value = 0;
-            switch (addr & 0b11) {
-                case 0: value = GPR[instr.rt]; break;
-                case 1: value = (aligned_load & 0x0000_00FF) | (GPR[instr.rt] << 8); break;
-                case 2: value = (aligned_load & 0x0000_FFFF) | (GPR[instr.rt] << 16); break;
-                case 3: value = (aligned_load & 0x00FF_FFFF) | (GPR[instr.rt] << 24); break;
-            }
+            uint aligned_load = cpu.bus.load32(aligned_addr);
 
-            bus.write32(aligned_addr, value);
+            int shift = (int)((addr & 0x3) << 3);
+            uint mask = (uint)0x00FF_FFFF >> (24 - shift);
+            uint value = (aligned_load & mask) | (cpu.GPR[cpu.instr.rt] << shift);
+
+            cpu.bus.write32(aligned_addr, value);
         }
 
-        private void SWL() {
-            uint addr = GPR[instr.rs] + instr.imm_s;
+        private static void SWL(CPU cpu) {
+            uint addr = cpu.GPR[cpu.instr.rs] + cpu.instr.imm_s;
             uint aligned_addr = addr & 0xFFFF_FFFC;
-            uint aligned_load = bus.load32(aligned_addr);
-
-            uint value = 0;
-            switch (addr & 0b11) {
-                case 0: value = (aligned_load & 0xFFFF_FF00) | (GPR[instr.rt] >> 24); break;
-                case 1: value = (aligned_load & 0xFFFF_0000) | (GPR[instr.rt] >> 16); break;
-                case 2: value = (aligned_load & 0xFF00_0000) | (GPR[instr.rt] >> 8); break;
-                case 3: value = GPR[instr.rt]; break;
-            }
+            uint aligned_load = cpu.bus.load32(aligned_addr);
+
+            int shift = (int)((addr & 0x3) << 3);
+            uint mask = 0xFFFF_FF00 << shift;
+            uint value = (aligned_load & mask) | (cpu.GPR[cpu.instr.rt] >> (24 - shift));
 
-            bus.write32(aligned_addr, value);
+            cpu.bus.write32(aligned_addr, value);
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private void BRANCH() {
-            opcodeTookBranch = true;
-            PC_Predictor = PC + (instr.imm_s << 2);
+        private static void BRANCH(CPU cpu) {
+            cpu.opcodeTookBranch = true;
+            cpu.PC_Predictor = cpu.PC + (cpu.instr.imm_s << 2);
         }
 
 
         // Special Table Opcodes (Nested on Opcode 0x00 with additional function param)
 
-        private void SLL() => setGPR(instr.rd, GPR[instr.rt] << (int)instr.sa);
+        private static void SLL(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rt] << (int)cpu.instr.sa);
 
-        private void SRL() => setGPR(instr.rd, GPR[instr.rt] >> (int)instr.sa);
+        private static void SRL(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rt] >> (int)cpu.instr.sa);
 
-        private void SRA() => setGPR(instr.rd, (uint)((int)GPR[instr.rt] >> (int)instr.sa));
+        private static void SRA(CPU cpu) => cpu.setGPR(cpu.instr.rd, (uint)((int)cpu.GPR[cpu.instr.rt] >> (int)cpu.instr.sa));
 
-        private void SLLV() => setGPR(instr.rd, GPR[instr.rt] << (int)(GPR[instr.rs] & 0x1F));
+        private static void SLLV(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rt] << (int)(cpu.GPR[cpu.instr.rs] & 0x1F));
 
-        private void SRLV() => setGPR(instr.rd, GPR[instr.rt] >> (int)(GPR[instr.rs] & 0x1F));
+        private static void SRLV(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rt] >> (int)(cpu.GPR[cpu.instr.rs] & 0x1F));
 
-        private void SRAV() => setGPR(instr.rd, (uint)((int)GPR[instr.rt] >> (int)(GPR[instr.rs] & 0x1F)));
+        private static void SRAV(CPU cpu) => cpu.setGPR(cpu.instr.rd, (uint)((int)cpu.GPR[cpu.instr.rt] >> (int)(cpu.GPR[cpu.instr.rs] & 0x1F)));
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private void JR() {
-            opcodeIsBranch = true;
-            opcodeTookBranch = true;
-            PC_Predictor = GPR[instr.rs];
+        private static void JR(CPU cpu) {
+            cpu.opcodeIsBranch = true;
+            cpu.opcodeTookBranch = true;
+            cpu.PC_Predictor = cpu.GPR[cpu.instr.rs];
         }
 
-        private void SYSCALL() => EXCEPTION(EX.SYSCALL, instr.id);
+        private static void SYSCALL(CPU cpu) => EXCEPTION(cpu, EX.SYSCALL, cpu.instr.id);
 
-        private void BREAK() => EXCEPTION(EX.BREAK);
+        private static void BREAK(CPU cpu) => EXCEPTION(cpu, EX.BREAK);
 
-        private void JALR() {
-            setGPR(instr.rd, PC_Predictor);
-            JR();
+        private static void JALR(CPU cpu) {
+            cpu.setGPR(cpu.instr.rd, cpu.PC_Predictor);
+            JR(cpu);
         }
 
-        private void MFHI() => setGPR(instr.rd, HI);
+        private static void MFHI(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.HI);
 
-        private void MTHI() => HI = GPR[instr.rs];
+        private static void MTHI(CPU cpu) => cpu.HI = cpu.GPR[cpu.instr.rs];
 
-        private void MFLO() => setGPR(instr.rd, LO);
+        private static void MFLO(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.LO);
 
-        private void MTLO() => LO = GPR[instr.rs];
+        private static void MTLO(CPU cpu) => cpu.LO = cpu.GPR[cpu.instr.rs];
 
-        private void MULT() {
-            long value = (long)(int)GPR[instr.rs] * (long)(int)GPR[instr.rt]; //sign extend to pass amidog cpu test
+        private static void MULT(CPU cpu) {
+            long value = (long)(int)cpu.GPR[cpu.instr.rs] * (long)(int)cpu.GPR[cpu.instr.rt]; //sign extend to pass amidog cpu test
 
-            HI = (uint)(value >> 32);
-            LO = (uint)value;
+            cpu.HI = (uint)(value >> 32);
+            cpu.LO = (uint)value;
         }
 
-        private void MULTU() {
-            ulong value = (ulong)GPR[instr.rs] * (ulong)GPR[instr.rt]; //sign extend to pass amidog cpu test
+        private static void MULTU(CPU cpu) {
+            ulong value = (ulong)cpu.GPR[cpu.instr.rs] * (ulong)cpu.GPR[cpu.instr.rt]; //sign extend to pass amidog cpu test
 
-            HI = (uint)(value >> 32);
-            LO = (uint)value;
+            cpu.HI = (uint)(value >> 32);
+            cpu.LO = (uint)value;
         }
 
-        private void DIV() {
-            int n = (int)GPR[instr.rs];
-            int d = (int)GPR[instr.rt];
+        private static void DIV(CPU cpu) {
+            int n = (int)cpu.GPR[cpu.instr.rs];
+            int d = (int)cpu.GPR[cpu.instr.rt];
 
             if (d == 0) {
-                HI = (uint)n;
+                cpu.HI = (uint)n;
                 if (n >= 0) {
-                    LO = 0xFFFF_FFFF;
+                    cpu.LO = 0xFFFF_FFFF;
                 } else {
-                    LO = 1;
+                    cpu.LO = 1;
                 }
             } else if ((uint)n == 0x8000_0000 && d == -1) {
-                HI = 0;
-                LO = 0x8000_0000;
+                cpu.HI = 0;
+                cpu.LO = 0x8000_0000;
             } else {
-                HI = (uint)(n % d);
-                LO = (uint)(n / d);
+                cpu.HI = (uint)(n % d);
+                cpu.LO = (uint)(n / d);
             }
         }
 
-        private void DIVU() {
-            uint n = GPR[instr.rs];
-            uint d = GPR[instr.rt];
+        private static void DIVU(CPU cpu) {
+            uint n = cpu.GPR[cpu.instr.rs];
+            uint d = cpu.GPR[cpu.instr.rt];
 
             if (d == 0) {
-                HI = n;
-                LO = 0xFFFF_FFFF;
+                cpu.HI = n;
+                cpu.LO = 0xFFFF_FFFF;
             } else {
-                HI = n % d;
-                LO = n / d;
+                cpu.HI = n % d;
+                cpu.LO = n / d;
             }
         }
 
-        private void ADD() {
-            uint rs = GPR[instr.rs];
-            uint rt = GPR[instr.rt];
+        private static void ADD(CPU cpu) {
+            uint rs = cpu.GPR[cpu.instr.rs];
+            uint rt = cpu.GPR[cpu.instr.rt];
             uint result = rs + rt;
+
+#if CPU_EXCEPTIONS
             if (checkOverflow(rs, rt, result)) {
-                EXCEPTION(EX.OVERFLOW, instr.id);
+                EXCEPTION(cpu, EX.OVERFLOW, cpu.instr.id);
             } else {
-                setGPR(instr.rd, result);
+                cpu.setGPR(cpu.instr.rd, result);
             }
+#else
+            cpu.setGPR(cpu.instr.rd, result);
+#endif
         }
 
-        private void ADDU() => setGPR(instr.rd, GPR[instr.rs] + GPR[instr.rt]);
+        private static void ADDU(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rs] + cpu.GPR[cpu.instr.rt]);
 
-        private void SUB() {
-            uint rs = GPR[instr.rs];
-            uint rt = GPR[instr.rt];
+        private static void SUB(CPU cpu) {
+            uint rs = cpu.GPR[cpu.instr.rs];
+            uint rt = cpu.GPR[cpu.instr.rt];
             uint result = rs - rt;
+
+#if CPU_EXCEPTIONS
             if (checkUnderflow(rs, rt, result)) {
-                EXCEPTION(EX.OVERFLOW, instr.id);
+                EXCEPTION(cpu, EX.OVERFLOW, cpu.instr.id);
             } else {
-                setGPR(instr.rd, result);
+                cpu.setGPR(cpu.instr.rd, result);
             }
+#else
+            cpu.setGPR(cpu.instr.rd, result);
+#endif
         }
 
-        private void SUBU() => setGPR(instr.rd, GPR[instr.rs] - GPR[instr.rt]);
+        private static void SUBU(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rs] - cpu.GPR[cpu.instr.rt]);
 
-        private void AND() => setGPR(instr.rd, GPR[instr.rs] & GPR[instr.rt]);
+        private static void AND(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rs] & cpu.GPR[cpu.instr.rt]);
 
-        private void OR() => setGPR(instr.rd, GPR[instr.rs] | GPR[instr.rt]);
+        private static void OR(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rs] | cpu.GPR[cpu.instr.rt]);
 
-        private void XOR() => setGPR(instr.rd, GPR[instr.rs] ^ GPR[instr.rt]);
+        private static void XOR(CPU cpu) => cpu.setGPR(cpu.instr.rd, cpu.GPR[cpu.instr.rs] ^ cpu.GPR[cpu.instr.rt]);
 
-        private void NOR() => setGPR(instr.rd, ~(GPR[instr.rs] | GPR[instr.rt]));
+        private static void NOR(CPU cpu) => cpu.setGPR(cpu.instr.rd, ~(cpu.GPR[cpu.instr.rs] | cpu.GPR[cpu.instr.rt]));
 
-        private void SLT() {
-            bool condition = (int)GPR[instr.rs] < (int)GPR[instr.rt];
-            setGPR(instr.rd, Unsafe.As<bool, uint>(ref condition));
+        private static void SLT(CPU cpu) {
+            bool condition = (int)cpu.GPR[cpu.instr.rs] < (int)cpu.GPR[cpu.instr.rt];
+            cpu.setGPR(cpu.instr.rd, Unsafe.As<bool, uint>(ref condition));
         }
 
-        private void SLTU() {
-            bool condition = GPR[instr.rs] < GPR[instr.rt];
-            setGPR(instr.rd, Unsafe.As<bool, uint>(ref condition));
+        private static void SLTU(CPU cpu) {
+            bool condition = cpu.GPR[cpu.instr.rs] < cpu.GPR[cpu.instr.rt];
+            cpu.setGPR(cpu.instr.rd, Unsafe.As<bool, uint>(ref condition));
         }
 
 
@@ -819,9 +786,9 @@ private void setGPR(uint regN, uint value) {
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private void delayedLoad(uint regN, uint value) {
-            delayedMemoryLoad.register = regN;
-            delayedMemoryLoad.value = value;
+        private static void delayedLoad(CPU cpu, uint regN, uint value) {
+            cpu.delayedMemoryLoad.register = regN;
+            cpu.delayedMemoryLoad.value = value;
         }
 
         private void TTY() {
diff --git a/ProjectPSX/Core/ProjectPSX.cs b/ProjectPSX/Core/ProjectPSX.cs
index abbe1c2..22e9d1d 100644
--- a/ProjectPSX/Core/ProjectPSX.cs
+++ b/ProjectPSX/Core/ProjectPSX.cs
@@ -1,5 +1,6 @@
 using ProjectPSX.Devices;
 using ProjectPSX.Devices.CdRom;
+using ProjectPSX.Devices.Expansion;
 using ProjectPSX.Devices.Input;
 
 namespace ProjectPSX {
@@ -22,6 +23,7 @@ public class ProjectPSX {
         private MemoryCard memoryCard;
         private CD cd;
         private InterruptController interruptController;
+        private Exp2 exp2;
 
         public ProjectPSX(IHostWindow window, string diskFilename) {
             controller = new DigitalController();
@@ -36,7 +38,8 @@ public ProjectPSX(IHostWindow window, string diskFilename) {
             joypad = new JOYPAD(controller, memoryCard);
             timers = new TIMERS();
             mdec = new MDEC();
-            bus = new BUS(gpu, cdrom, spu, joypad, timers, mdec, interruptController);
+            exp2 = new Exp2();
+            bus = new BUS(gpu, cdrom, spu, joypad, timers, mdec, interruptController, exp2);
             cpu = new CPU(bus);
 
             bus.loadBios();
@@ -47,12 +50,14 @@ public ProjectPSX(IHostWindow window, string diskFilename) {
 
         public void RunFrame() {
             //A lame mainloop with a workaround to be able to underclock.
+            int sync = 0;
             for (int i = 0; i < SYNC_LOOPS; i++) {
-                for (int j = 0; j < SYNC_CYCLES; j++) {
-                    cpu.Run();
+                while (sync < SYNC_CYCLES) {
+                    sync += cpu.Run();
                     //cpu.handleInterrupts();
                 }
-                bus.tick(SYNC_CYCLES * MIPS_UNDERCLOCK);
+                sync -= SYNC_CYCLES;
+                bus.tick(SYNC_CYCLES * MIPS_UNDERCLOCK + 1);
                 cpu.handleInterrupts();
             }
         }
@@ -66,5 +71,9 @@ public void toggleDebug() {
             gpu.debug = !gpu.debug;
         }
 
+        public void toggleCdRomLid() {
+            cdrom.toggleLid();
+        }
+
     }
 }
diff --git a/ProjectPSX/Devices/CDROM/CD.cs b/ProjectPSX/Devices/CDROM/CD.cs
index b131bd9..26a3361 100644
--- a/ProjectPSX/Devices/CDROM/CD.cs
+++ b/ProjectPSX/Devices/CDROM/CD.cs
@@ -14,6 +14,8 @@ public class CD {
 
         public List<Track> tracks;
 
+        public bool isTrackChange;
+
         public CD(string diskFilename) {
             string ext = Path.GetExtension(diskFilename);
 
@@ -53,8 +55,9 @@ public byte[] Read(int loc) {
 
         public Track getTrackFromLoc(int loc) {
             foreach (Track track in tracks) {
-                //Console.WriteLine(loc + " " + track.file + track.lbaEnd);
-                if (track.lbaEnd > loc) return track;
+                isTrackChange = loc == track.lbaEnd;
+                //Console.WriteLine(loc + " " + track.number + " " + track.lbaEnd + " " + isTrackChange);
+                if (track.lbaEnd >= loc) return track;
             }
             Console.WriteLine("[CD] WARNING: LBA beyond tracks!");
             return tracks[0]; //and explode ¯\_(ツ)_/¯ 
@@ -70,5 +73,9 @@ public int getLBA() {
             return lba;
         }
 
+        public bool isAudioCD() {
+            return tracks[0].isAudio;
+        }
+
     }
 }
diff --git a/ProjectPSX/Devices/CDROM/CDROM.cs b/ProjectPSX/Devices/CDROM/CDROM.cs
index c20b2c2..d0f77f0 100644
--- a/ProjectPSX/Devices/CDROM/CDROM.cs
+++ b/ProjectPSX/Devices/CDROM/CDROM.cs
@@ -68,6 +68,7 @@ public class CDROM {
         private byte volumeRtoR = 0xFF;
 
         private bool cdDebug = false;
+        private bool isLidOpen = false;
 
         private struct SectorHeader {
             public byte mm;
@@ -104,7 +105,37 @@ private enum Mode {
         private Mode mode = Mode.Idle;
 
         private int counter;
-        private Queue<byte> interruptQueue = new Queue<byte>();
+        private Queue<DelayedInterrupt> interruptQueue = new Queue<DelayedInterrupt>();
+
+        public class DelayedInterrupt {
+            public int delay;
+            public byte interrupt;
+
+            public DelayedInterrupt(int delay, byte interrupt) {
+                this.delay = delay;
+                this.interrupt = interrupt;
+            }
+        }
+
+        //INT0 No response received(no interrupt request)
+        //INT1 Received SECOND(or further) response to ReadS/ReadN(and Play+Report)
+        //INT2 Received SECOND response(to various commands)
+        //INT3 Received FIRST response(to any command)
+        //INT4 DataEnd(when Play/Forward reaches end of disk) (maybe also for Read?)
+        //INT5 Received error-code(in FIRST or SECOND response)
+        //INT5 also occurs on SECOND GetID response, on unlicensed disks
+        //INT5 also occurs when opening the drive door(even if no command
+        //   was sent, ie.even if no read-command or other command is active)
+        // INT6 N/A
+        //INT7   N/A
+        public class Interrupt {
+            public const byte INT0_NO_RESPONSE = 0;
+            public const byte INT1_SECOND_RESPONSE_READ_PLAY = 1;
+            public const byte INT2_SECOND_RESPONSE = 2;
+            public const byte INT3_FIRST_RESPONSE = 3;
+            public const byte INT4_DATA_END = 4;
+            public const byte INT5_ERROR = 5;
+        }
 
         private CD cd;
         private SPU spu;
@@ -116,12 +147,51 @@ public CDROM(CD cd, SPU spu) {
 
         private bool edgeTrigger;
 
+        private Dictionary<uint, string> commands = new Dictionary<uint, string> {
+            [0x01] = "Cmd_01_GetStat",
+            [0x02] = "Cmd_02_SetLoc",
+            [0x03] = "Cmd_03_Play",
+            [0x04] = "Cmd_04_Forward",
+            [0x05] = "Cmd_05_Backward",
+            [0x06] = "Cmd_06_ReadN",
+            [0x07] = "Cmd_07_MotorOn",
+            [0x08] = "Cmd_08_Stop",
+            [0x09] = "Cmd_09_Pause",
+            [0x0A] = "Cmd_0A_Init",
+            [0x0B] = "Cmd_0B_Mute",
+            [0x0C] = "Cmd_0C_Demute",
+            [0x0D] = "Cmd_0D_SetFilter",
+            [0x0E] = "Cmd_0E_SetMode",
+            [0x0F] = "Cmd_0F_GetParam",
+            [0x10] = "Cmd_10_GetLocL",
+            [0x11] = "Cmd_11_GetLocP",
+            [0x12] = "Cmd_12_SetSession",
+            [0x13] = "Cmd_13_GetTN",
+            [0x14] = "Cmd_14_GetTD",
+            [0x15] = "Cmd_15_SeekL",
+            [0x16] = "Cmd_16_SeekP",
+            [0x17] = "--- [Unimplemented]",
+            [0x18] = "--- [Unimplemented]",
+            [0x19] = "Cmd_19_Test",
+            [0x1A] = "Cmd_1A_GetID",
+            [0x1B] = "Cmd_1B_ReadS",
+            [0x1C] = "Cmd_1C_Reset [Unimplemented]",
+            [0x1D] = "Cmd_1D_GetQ [Unimplemented]",
+            [0x1E] = "Cmd_1E_ReadTOC",
+            [0x1F] = "Cmd_1F_VideoCD",
+        };
+
         public bool tick(int cycles) {
             counter += cycles;
 
-            if (interruptQueue.Count != 0 && IF == 0) {
+            if (interruptQueue.Count != 0) {
+                var delayedInterrupt = interruptQueue.Peek();
+                delayedInterrupt.delay -= cycles;
+            }
+
+            if (interruptQueue.Count != 0 && IF == 0 && interruptQueue.Peek().delay <= 0) {
                 if (cdDebug) Console.WriteLine($"[CDROM] Interrupt Queue is size: {interruptQueue.Count} dequeue to IF next Interrupt: {interruptQueue.Peek()}");
-                IF |= interruptQueue.Dequeue();
+                IF |= interruptQueue.Dequeue().interrupt;
                 //edgeTrigger = true;
             }
 
@@ -134,8 +204,8 @@ public bool tick(int cycles) {
 
             switch (mode) {
                 case Mode.Idle:
-                        counter = 0;
-                        return false;
+                    counter = 0;
+                    return false;
 
                 case Mode.Seek: //Hardcoded seek time...
                     if (counter < 33868800 / 3 || interruptQueue.Count != 0) {
@@ -146,11 +216,11 @@ public bool tick(int cycles) {
                     STAT = (byte)(STAT & (~0x40));
 
                     responseBuffer.Enqueue(STAT);
-                    interruptQueue.Enqueue(0x2);
+                    interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT2_SECOND_RESPONSE);
                     break;
 
                 case Mode.Read:
-                case Mode.Play:  
+                case Mode.Play:
                     if (counter < (33868800 / (isDoubleSpeed ? 150 : 75)) || interruptQueue.Count != 0) {
                         return false;
                     }
@@ -171,7 +241,43 @@ public bool tick(int cycles) {
                             spu.pushCdBufferSamples(readSector);
                         }
 
-                        return false; //CDDA isn't delivered to CPU and doesn't raise interrupt
+                        if (isAutoPause && cd.isTrackChange) {
+                            responseBuffer.Enqueue(STAT);
+                            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT4_DATA_END, 1);
+
+                            STAT = 0x2;
+                            mode = Mode.Idle;
+                        }
+
+                        if (isReport) {
+                            (byte amm, byte ass, byte aff) = getMMSSFFfromLBA(readLoc);
+                            Track track = cd.getTrackFromLoc(readLoc);
+                            bool inPregap = readLoc < track.lbaStart;
+                            byte index = inPregap ? (byte)0 : (byte)1;
+
+                            responseBuffer.Enqueue(STAT);
+                            responseBuffer.Enqueue(track.number);
+                            responseBuffer.Enqueue(index);
+
+                            if ((aff & 0x10) != 0) {
+                                (byte mm, byte ss, byte ff) = getMMSSFFfromLBA(Math.Abs(readLoc - track.lbaStart));
+
+                                responseBuffer.Enqueue(DecToBcd(mm));
+                                responseBuffer.Enqueue((byte)(DecToBcd(ss) | 0x80));
+                                responseBuffer.Enqueue(DecToBcd(ff));
+                            } else {
+                                responseBuffer.Enqueue(DecToBcd(amm));
+                                responseBuffer.Enqueue(DecToBcd(ass));
+                                responseBuffer.Enqueue(DecToBcd(aff));
+                        }
+
+                            responseBuffer.Enqueue(0x80); // peekLo
+                            responseBuffer.Enqueue(0x80); // peekHi
+
+                            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT1_SECOND_RESPONSE_READ_PLAY, 1);
+                    }
+
+                        return false; //CDDA without report isn't delivered to CPU and doesn't raise interrupt
                     }
 
                     //Handle Mode.Read:
@@ -207,7 +313,7 @@ public bool tick(int cycles) {
 
                             if (cdDebug) Console.WriteLine("[CDROM] XA ON: Realtime + Audio"); //todo flag to pass to SPU?
 
-                            if(!mutedAudio && !mutedXAADPCM) {
+                            if (!mutedAudio && !mutedXAADPCM) {
                                 byte[] decodedXaAdpcm = XaAdpcm.Decode(readSector, sectorSubHeader.codingInfo);
                                 applyVolume(decodedXaAdpcm);
                                 spu.pushCdBufferSamples(decodedXaAdpcm);
@@ -227,7 +333,7 @@ public bool tick(int cycles) {
                     }
 
                     responseBuffer.Enqueue(STAT);
-                    interruptQueue.Enqueue(0x1);
+                    interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT1_SECOND_RESPONSE_READ_PLAY);
 
                     break;
 
@@ -237,7 +343,7 @@ public bool tick(int cycles) {
                     }
                     mode = Mode.Idle;
                     responseBuffer.Enqueue(STAT);
-                    interruptQueue.Enqueue(0x2);
+                    interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT2_SECOND_RESPONSE);
                     counter = 0;
                     break;
             }
@@ -252,12 +358,15 @@ public uint load(uint addr) {
                     return STATUS();
 
                 case 0x1F801801:
-                    if (cdDebug) Console.WriteLine("[CDROM] [L01] RESPONSE " /*+ responseBuffer.Peek().ToString("x8")*/);
                     //Console.ReadLine();
                     //if (w == Width.HALF || w == Width.WORD) Console.WriteLine("WARNING RESPONSE BUFFER LOAD " + w);
 
-                    if (responseBuffer.Count > 0)
+                    if (responseBuffer.Count > 0) {
+                        if (cdDebug) Console.WriteLine("[CDROM] [L01] RESPONSE " + responseBuffer.Peek().ToString("x8"));
                         return responseBuffer.Dequeue();
+                    }
+
+                    if (cdDebug) Console.WriteLine("[CDROM] [L01] RESPONSE 0xFF");
 
                     return 0xFF;
 
@@ -295,8 +404,8 @@ public void write(uint addr, uint value) {
                 case 0x1F801801:
                     if (INDEX == 0) {
                         if (cdDebug) {
-                            Console.BackgroundColor = ConsoleColor.Yellow;
-                            Console.WriteLine($"[CDROM] [W01.0] [COMMAND] >>> {value:x2}");
+                            Console.ForegroundColor = ConsoleColor.Yellow;
+                            Console.WriteLine($"[CDROM] [W01.0] [COMMAND] {value:x2} {commands.GetValueOrDefault(value, "---")}");
                             Console.ResetColor();
                         }
                         ExecuteCommand(value);
@@ -357,8 +466,8 @@ public void write(uint addr, uint value) {
                         case 1:
                             IF &= (byte)~(value & 0x1F);
                             if (cdDebug) Console.WriteLine($"[CDROM] [W03.1] Set IF: {value:x8} -> IF = {IF:x8}");
-                            if (interruptQueue.Count > 0) {
-                                IF |= interruptQueue.Dequeue();
+                            if (interruptQueue.Count > 0 && interruptQueue.Peek().delay <= 0) {
+                                IF |= interruptQueue.Dequeue().interrupt;
                             }
 
                             if ((value & 0x40) == 0x40) {
@@ -378,8 +487,8 @@ public void write(uint addr, uint value) {
                             bool applyVolume = (value & 0x20) != 0;
                             if (applyVolume) {
                                 volumeLtoL = pendingVolumeLtoL;
-                                volumeLtoR =  pendingVolumeLtoR;
-                                volumeRtoL =  pendingVolumeRtoL;
+                                volumeLtoR = pendingVolumeLtoR;
+                                volumeRtoL = pendingVolumeRtoL;
                                 volumeRtoR = pendingVolumeRtoR;
                             }
                             break;
@@ -397,258 +506,203 @@ public void write(uint addr, uint value) {
 
         private void ExecuteCommand(uint value) {
             if (cdDebug) Console.WriteLine($"[CDROM] Command {value:x4}");
+            //Console.WriteLine($"PRE STAT {STAT:x2}");
             interruptQueue.Clear();
             responseBuffer.Clear();
             isBusy = true;
             switch (value) {
-                case 0x01: getStat(); break;
-                case 0x02: setLoc(); break;
-                case 0x03: play(); break;
-                case 0x06: readN(); break;
-                case 0x07: motorOn(); break;
-                case 0x08: stop(); break;
-                case 0x09: pause(); break;
-                case 0x0A: init(); break;
-                case 0x0B: mute(); break;
-                case 0x0C: demute(); break;
-                case 0x0D: setFilter(); break;
-                case 0x0E: setMode(); break;
-                case 0x10: getLocL(); break;
-                case 0x11: getLocP(); break;
-                case 0x12: setSession(); break;
-                case 0x13: getTN(); break;
-                case 0x14: getTD(); break;
-                case 0x15: seekL(); break;
-                case 0x16: seekP(); break;
-                case 0x19: test(); break;
-                case 0x1A: getID(); break;
-                case 0x1B: readS(); break;
-                case 0x1E: readTOC(); break;
-                case 0x1F: videoCD(); break;
-                case uint _ when value >= 0x50 && value <= 0x57: lockUnlock(); break;
+                case 0x01: Cmd_01_GetStat(); break;
+                case 0x02: Cmd_02_SetLoc(); break;
+                case 0x03: Cmd_03_Play(); break;
+                //case 0x04: Cmd_04_Forward(); break; //todo
+                //case 0x05: Cmd_05_Backward(); break; //todo
+                case 0x06: Cmd_06_ReadN(); break;
+                case 0x07: Cmd_07_MotorOn(); break;
+                case 0x08: Cmd_08_Stop(); break;
+                case 0x09: Cmd_09_Pause(); break;
+                case 0x0A: Cmd_0A_Init(); break;
+                case 0x0B: Cmd_0B_Mute(); break;
+                case 0x0C: Cmd_0C_Demute(); break;
+                case 0x0D: Cmd_0D_SetFilter(); break;
+                case 0x0E: Cmd_0E_SetMode(); break;
+                //case 0x0F: Cmd_0F_GetParam(); break; //todo
+                case 0x10: Cmd_10_GetLocL(); break;
+                case 0x11: Cmd_11_GetLocP(); break;
+                case 0x12: Cmd_12_SetSession(); break;
+                case 0x13: Cmd_13_GetTN(); break;
+                case 0x14: Cmd_14_GetTD(); break;
+                case 0x15: Cmd_15_SeekL(); break;
+                case 0x16: Cmd_16_SeekP(); break;
+                case 0x19: Cmd_19_Test(); break;
+                case 0x1A: Cmd_1A_GetID(); break;
+                case 0x1B: Cmd_1B_ReadS(); break;
+                case 0x1E: Cmd_1E_ReadTOC(); break;
+                case 0x1F: Cmd_1F_VideoCD(); break;
+                case uint _ when value >= 0x50 && value <= 0x57: Cmd_5x_lockUnlock(); break;
                 default: UnimplementedCDCommand(value); break;
             }
+            //Console.WriteLine($"POST STAT {STAT:x2}");
         }
 
-        private void mute() {
-            mutedAudio = true;
-
-            responseBuffer.Enqueue(STAT);
-            interruptQueue.Enqueue(0x3);
-        }
-
-        private void readTOC() {
-            mode = Mode.TOC;
-            responseBuffer.Enqueue(STAT);
-            interruptQueue.Enqueue(0x3);
-        }
-
-        private void motorOn() {
-            STAT = 0x2;
-
-            responseBuffer.Enqueue(STAT);
-            interruptQueue.Enqueue(0x3);
+        private void Cmd_01_GetStat() {
+            if (!isLidOpen) {
+                STAT = (byte)(STAT & (~0x18));
+                STAT |= 0x2;
+            }
 
             responseBuffer.Enqueue(STAT);
-            interruptQueue.Enqueue(0x2);
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
         }
 
-        private void getLocL() {
-            if (cdDebug) {
-                Console.WriteLine($"mm: {sectorHeader.mm} ss: {sectorHeader.ss} ff: {sectorHeader.ff} mode: {sectorHeader.mode}" +
-                    $" file: {sectorSubHeader.file} channel: {sectorSubHeader.channel} subMode: {sectorSubHeader.subMode} codingInfo: {sectorSubHeader.codingInfo}");
+        private void Cmd_02_SetLoc() {
+            if (isLidOpen) {
+                responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 });
+                interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR);
+                return;
             }
 
-            Span<byte> response = stackalloc byte[] {
-                sectorHeader.mm,
-                sectorHeader.ss,
-                sectorHeader.ff,
-                sectorHeader.mode,
-                sectorSubHeader.file,
-                sectorSubHeader.channel,
-                sectorSubHeader.subMode,
-                sectorSubHeader.codingInfo
-            };
+            byte mm = parameterBuffer.Dequeue();
+            byte ss = parameterBuffer.Dequeue();
+            byte ff = parameterBuffer.Dequeue();
 
-            responseBuffer.EnqueueRange(response);
+            //Console.WriteLine($"[CDROM] setLoc BCD {mm:x2}:{ss:x2}:{ff:x2}");
 
-            interruptQueue.Enqueue(0x3);
-        }
 
-        private void getLocP() { //SubQ missing...
+            int minute = BcdToDec(mm);
+            int second = BcdToDec(ss);
+            int sector = BcdToDec(ff);
 
-            Track track = cd.getTrackFromLoc(readLoc);
-            (byte mm, byte ss, byte ff) = getMMSSFFfromLBA(readLoc - track.lbaStart);
-            (byte amm, byte ass, byte aff) = getMMSSFFfromLBA(readLoc);
+            //There are 75 sectors on a second
+            seekLoc = sector + (second * 75) + (minute * 60 * 75);
+
+            if (seekLoc < 0) {
+                Console.WriteLine($"[CDROM] WARNING NEGATIVE setLOC {seekLoc:x8}");
+                seekLoc = 0;
+            }
 
             if (cdDebug) {
-                Console.ForegroundColor = ConsoleColor.Red;
-                Console.WriteLine($"track: {track.number} index: {1} mm: {mm} ss: {ss}" +
-                    $" ff: {ff} amm: {amm} ass: {ass} aff: {aff}");
-                Console.WriteLine($"track: {track.number} index: {1} mm: {DecToBcd(mm)} ss: {DecToBcd(ss)}" +
-                    $" ff: {DecToBcd(ff)} amm: {DecToBcd(amm)} ass: {DecToBcd(ass)} aff: {DecToBcd(aff)}");
+                Console.ForegroundColor = ConsoleColor.DarkGreen;
+                Console.WriteLine($"[CDROM] setLoc {mm:x2}:{ss:x2}:{ff:x2} Loc: {seekLoc:x8}");
                 Console.ResetColor();
             }
 
-            Span<byte> response = stackalloc byte[] { track.number, 1, DecToBcd(mm), DecToBcd(ss), DecToBcd(ff), DecToBcd(amm), DecToBcd(ass), DecToBcd(aff) };
-            responseBuffer.EnqueueRange(response);
-
-            interruptQueue.Enqueue(0x3);
-        }
-
-        private void lockUnlock() {
-            interruptQueue.Enqueue(0x5);
-        }
-
-        private void videoCD() { //INT5(11h,40h)  ;-Unused/invalid
-            responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x40 });
-
-            interruptQueue.Enqueue(0x5);
+            responseBuffer.Enqueue(STAT);
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
         }
 
-        private void setSession() { //broken
-            parameterBuffer.Clear();
+        private void Cmd_03_Play() {
+            if (isLidOpen) {
+                responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 });
+                interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR);
+                return;
+            }
+            //If theres a trackN param it seeks and plays from the start location of it
+            int track = 0;
+            if (parameterBuffer.Count > 0 && parameterBuffer.Peek() != 0) {
+                track = BcdToDec(parameterBuffer.Dequeue());
+                if (cd.isAudioCD()) {
+                    readLoc = seekLoc = cd.tracks[track - 1].lbaStart;
+                } else {
+                    readLoc = seekLoc = cd.tracks[track].lbaStart;
+                }
+                //else it plays from the previously seekLoc and seeks if not done (actually not checking if already seeked)
+            } else {
+                readLoc = seekLoc;
+            }
 
-            STAT = 0x42;
+            Console.WriteLine($"[CDROM] CDDA Play Triggered Track: {track} readLoc: {readLoc}");
 
-            responseBuffer.Enqueue(STAT);
-            interruptQueue.Enqueue(0x3);
+            STAT = 0x82;
+            mode = Mode.Play;
 
             responseBuffer.Enqueue(STAT);
-            interruptQueue.Enqueue(0x2);
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
         }
 
-        private void setFilter() {
-            filterFile = parameterBuffer.Dequeue();
-            filterChannel = parameterBuffer.Dequeue();
-
-            responseBuffer.Enqueue(STAT);
-            interruptQueue.Enqueue(0x3);
-        }
+        private void Cmd_06_ReadN() {
+            if (isLidOpen) {
+                responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 });
+                interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR);
+                return;
+            }
 
-        private void readS() {
             readLoc = seekLoc;
 
             STAT = 0x2;
             STAT |= 0x20;
 
             responseBuffer.Enqueue(STAT);
-            interruptQueue.Enqueue(0x3);
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
 
             mode = Mode.Read;
         }
 
-        private void seekP() {
-            readLoc = seekLoc;
-            STAT = 0x42; // seek
-
-            mode = Mode.Seek;
+        private void Cmd_07_MotorOn() {
+            STAT = 0x2;
 
             responseBuffer.Enqueue(STAT);
-            interruptQueue.Enqueue(0x3);
-        }
-
-
-        private void play() {
-            //If theres a trackN param it seeks and plays from the start location of it
-            int track = 0;
-            if (parameterBuffer.Count > 0) {
-                track = BcdToDec(parameterBuffer.Dequeue());
-                readLoc = seekLoc = cd.tracks[track].lbaStart;
-                //else it plays from the previously seekLoc and seeks if not done (actually not checking if already seeked)
-            } else {
-                readLoc = seekLoc;
-            }
-
-            Console.WriteLine($"[CDROM] CDDA Play Triggered Track: {track} readLoc: {readLoc}");
-
-            STAT = 0x82;
-            mode = Mode.Play;
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
 
             responseBuffer.Enqueue(STAT);
-            interruptQueue.Enqueue(0x3);
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT2_SECOND_RESPONSE);
         }
 
-        private void stop() {
-            STAT = 0;
-
+        private void Cmd_08_Stop() {
+            STAT = 0x2;
             responseBuffer.Enqueue(STAT);
-            interruptQueue.Enqueue(0x3);
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
 
+            STAT = 0;
             responseBuffer.Enqueue(STAT);
-            interruptQueue.Enqueue(0x2);
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT2_SECOND_RESPONSE);
 
             mode = Mode.Idle;
         }
 
-        private void getTD() {
-            int track = BcdToDec(parameterBuffer.Dequeue());
-
-            if (track == 0) { //returns CD LBA / End of last track
-                (byte mm, byte ss, byte ff) = getMMSSFFfromLBA(cd.getLBA());
-                responseBuffer.EnqueueRange(stackalloc byte[] { STAT, DecToBcd(mm), DecToBcd(ss) });
-                //if (cdDebug)
-                Console.WriteLine($"[CDROM] getTD Track: {track} STAT: {STAT:x2} {mm}:{ss}");
-            } else { //returns Track Start
-                (byte mm, byte ss, byte ff) = getMMSSFFfromLBA(cd.tracks[track - 1].lbaStart);
-                responseBuffer.EnqueueRange(stackalloc byte[] { STAT, DecToBcd(mm), DecToBcd(ss) });
-                //if (cdDebug)
-                Console.WriteLine($"[CDROM] getTD Track: {track} STAT: {STAT:x2} {mm}:{ss}");
-            }
-
-            //Console.ReadLine();
-            interruptQueue.Enqueue(0x3);
-        }
-
-        private void getTN() {
-            //if (cdDebug)
-            Console.WriteLine($"[CDROM] getTN First Track: 1 (Hardcoded) - Last Track: {cd.tracks.Count}");
-            //Console.ReadLine();
-            responseBuffer.EnqueueRange(stackalloc byte[] { STAT, 1, DecToBcd((byte)cd.tracks.Count) });
-            interruptQueue.Enqueue(0x3);
-        }
+        private void Cmd_09_Pause() {
+            responseBuffer.Enqueue(STAT);
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
 
-        private void demute() {
-            mutedAudio = false;
+            STAT = 0x2;
+            mode = Mode.Idle;
 
             responseBuffer.Enqueue(STAT);
-            interruptQueue.Enqueue(0x3);
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT2_SECOND_RESPONSE);
         }
 
-        private void init() {
+        private void Cmd_0A_Init() {
             STAT = 0x2;
 
             responseBuffer.Enqueue(STAT);
-            interruptQueue.Enqueue(0x3);
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
 
             responseBuffer.Enqueue(STAT);
-            interruptQueue.Enqueue(0x2);
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT2_SECOND_RESPONSE);
         }
 
-        private void pause() {
+        private void Cmd_0B_Mute() {
+            mutedAudio = true;
+
             responseBuffer.Enqueue(STAT);
-            interruptQueue.Enqueue(0x3);
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
+        }
 
-            STAT = 0x2;
-            mode = Mode.Idle;
+        private void Cmd_0C_Demute() {
+            mutedAudio = false;
 
             responseBuffer.Enqueue(STAT);
-            interruptQueue.Enqueue(0x2);
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
         }
 
-        private void readN() {
-            readLoc = seekLoc;
-
-            STAT = 0x2;
-            STAT |= 0x20;
+        private void Cmd_0D_SetFilter() {
+            filterFile = parameterBuffer.Dequeue();
+            filterChannel = parameterBuffer.Dequeue();
 
             responseBuffer.Enqueue(STAT);
-            interruptQueue.Enqueue(0x3);
-
-            mode = Mode.Read;
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
         }
 
-        private void setMode() {
+        private void Cmd_0E_SetMode() {
             //7   Speed(0 = Normal speed, 1 = Double speed)
             //6   XA - ADPCM(0 = Off, 1 = Send XA - ADPCM sectors to SPU Audio Input)
             //5   Sector Size(0 = 800h = DataOnly, 1 = 924h = WholeSectorExceptSyncBytes)
@@ -671,85 +725,152 @@ private void setMode() {
             isCDDA = (mode & 0x1) == 1;
 
             responseBuffer.Enqueue(STAT);
-            interruptQueue.Enqueue(0x3);
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
         }
 
-        private void seekL() {
-            readLoc = seekLoc;
-            STAT = 0x42; // seek
+        private void Cmd_10_GetLocL() {
+            if (cdDebug) {
+                Console.WriteLine($"mm: {sectorHeader.mm} ss: {sectorHeader.ss} ff: {sectorHeader.ff} mode: {sectorHeader.mode}" +
+                    $" file: {sectorSubHeader.file} channel: {sectorSubHeader.channel} subMode: {sectorSubHeader.subMode} codingInfo: {sectorSubHeader.codingInfo}");
+            }
 
-            mode = Mode.Seek;
+            if (isLidOpen) {
+                responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 });
+                interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR);
+                return;
+            }
 
-            responseBuffer.Enqueue(STAT);
-            interruptQueue.Enqueue(0x3);
+            Span<byte> response = stackalloc byte[] {
+                sectorHeader.mm,
+                sectorHeader.ss,
+                sectorHeader.ff,
+                sectorHeader.mode,
+                sectorSubHeader.file,
+                sectorSubHeader.channel,
+                sectorSubHeader.subMode,
+                sectorSubHeader.codingInfo
+            };
+
+            responseBuffer.EnqueueRange(response);
+
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
         }
 
-        private void setLoc() {
-            byte mm = parameterBuffer.Dequeue();
-            byte ss = parameterBuffer.Dequeue();
-            byte ff = parameterBuffer.Dequeue();
+        private void Cmd_11_GetLocP() { //SubQ missing...
+            if (isLidOpen) {
+                responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 });
+                interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR);
+                return;
+            }
 
-            //Console.WriteLine($"[CDROM] setLoc BCD {mm:x2}:{ss:x2}:{ff:x2}");
+            Track track = cd.getTrackFromLoc(readLoc);
+            (byte mm, byte ss, byte ff) = getMMSSFFfromLBA(readLoc - track.lbaStart);
+            (byte amm, byte ass, byte aff) = getMMSSFFfromLBA(readLoc);
+
+            if (cdDebug) {
+                Console.ForegroundColor = ConsoleColor.Red;
+                Console.WriteLine($"track: {track.number} index: {1} mm: {mm} ss: {ss}" +
+                    $" ff: {ff} amm: {amm} ass: {ass} aff: {aff}");
+                Console.WriteLine($"track: {track.number} index: {1} mm: {DecToBcd(mm)} ss: {DecToBcd(ss)}" +
+                    $" ff: {DecToBcd(ff)} amm: {DecToBcd(amm)} ass: {DecToBcd(ass)} aff: {DecToBcd(aff)}");
+                Console.ResetColor();
+            }
 
+            Span<byte> response = stackalloc byte[] { track.number, 1, DecToBcd(mm), DecToBcd(ss), DecToBcd(ff), DecToBcd(amm), DecToBcd(ass), DecToBcd(aff) };
+            responseBuffer.EnqueueRange(response);
 
-            int minute = BcdToDec(mm);
-            int second = BcdToDec(ss);
-            int sector = BcdToDec(ff);
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
+        }
 
-            //There are 75 sectors on a second
-            seekLoc = sector + (second * 75) + (minute * 60 * 75);
+        private void Cmd_12_SetSession() { //broken
+            parameterBuffer.Clear();
 
-            if (seekLoc < 0) {
-                Console.WriteLine($"[CDROM] WARNING NEGATIVE setLOC {seekLoc:x8}");
-                seekLoc = 0;
+            if (isLidOpen) {
+                responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 });
+                interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR);
+                return;
             }
 
-            if (cdDebug) {
-                Console.ForegroundColor = ConsoleColor.DarkGreen;
-                Console.WriteLine($"[CDROM] setLoc {mm:x2}:{ss:x2}:{ff:x2} Loc: {seekLoc:x8}");
-                Console.ResetColor();
-            }
+            STAT = 0x42;
+
+            responseBuffer.Enqueue(STAT);
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
 
             responseBuffer.Enqueue(STAT);
-            interruptQueue.Enqueue(0x3);
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT2_SECOND_RESPONSE);
         }
 
-        private void getID() {
-            //No Disk                INT3(stat)     INT5(08h, 40h, 00h, 00h, 00h, 00h, 00h, 00h)
-            //STAT = 0x2; //0x40 seek
-            //responseBuffer.Enqueue(STAT);
-            //interruptQueue.Enqueue(0x3);
-            //
-            //responseBuffer.EnqueueRange(stackalloc byte[] { 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
-            //interruptQueue.Enqueue(0x5);
+        private void Cmd_13_GetTN() {
+            if (isLidOpen) {
+                responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 });
+                interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR);
+                return;
+            }
+            //if (cdDebug)
+            Console.WriteLine($"[CDROM] getTN First Track: 1 (Hardcoded) - Last Track: {cd.tracks.Count}");
+            //Console.ReadLine();
+            responseBuffer.EnqueueRange(stackalloc byte[] { STAT, 1, DecToBcd((byte)cd.tracks.Count) });
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
+        }
 
-            //Door Open              INT5(11h,80h)  N/A
-            //STAT = 0x10; //Shell Open
-            //responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80, 0x00, 0x00 });
-            //interruptQueue.Enqueue(0x5);
+        private void Cmd_14_GetTD() {
+            if (isLidOpen) {
+                responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 });
+                interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR);
+                return;
+            }
 
-            //Licensed: Mode2 INT3(stat)     INT2(02h, 00h, 20h, 00h, 53h, 43h, 45h, 4xh)
-            STAT = 0x40; //0x40 seek
-            STAT |= 0x2;
-            responseBuffer.Enqueue(STAT);
-            interruptQueue.Enqueue(0x3);
+            int track = BcdToDec(parameterBuffer.Dequeue());
 
-            Span<byte> response = stackalloc byte[] { 0x02, 0x00, 0x20, 0x00, 0x53, 0x43, 0x45, 0x41 }; //SCE | //A 0x41 (America) - I 0x49 (Japan) - E 0x45 (Europe)
-            responseBuffer.EnqueueRange(response);
-            interruptQueue.Enqueue(0x2);
+            if (track == 0) { //returns CD LBA / End of last track
+                (byte mm, byte ss, byte ff) = getMMSSFFfromLBA(cd.getLBA());
+                responseBuffer.EnqueueRange(stackalloc byte[] { STAT, DecToBcd(mm), DecToBcd(ss) });
+                //if (cdDebug)
+                Console.WriteLine($"[CDROM] getTD Track: {track} STAT: {STAT:x2} {mm}:{ss}");
+            } else { //returns Track Start
+                (byte mm, byte ss, byte ff) = getMMSSFFfromLBA(cd.tracks[track - 1].lbaStart);
+                responseBuffer.EnqueueRange(stackalloc byte[] { STAT, DecToBcd(mm), DecToBcd(ss) });
+                //if (cdDebug)
+                Console.WriteLine($"[CDROM] getTD Track: {track} STAT: {STAT:x2} {mm}:{ss}");
+            }
+
+            //Console.ReadLine();
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
         }
 
-        private void getStat() {
+        private void Cmd_15_SeekL() {
+            if (isLidOpen) {
+                responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 });
+                interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR);
+                return;
+            }
+
+            readLoc = seekLoc;
+            STAT = 0x42; // seek
+
+            mode = Mode.Seek;
+
             responseBuffer.Enqueue(STAT);
-            interruptQueue.Enqueue(0x3);
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
         }
 
-        private void UnimplementedCDCommand(uint value) {
-            Console.WriteLine($"[CDROM] Unimplemented CD Command {value}");
-            //Console.ReadLine();
+        private void Cmd_16_SeekP() {
+            if (isLidOpen) {
+                responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 });
+                interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR);
+                return;
+            }
+
+            readLoc = seekLoc;
+            STAT = 0x42; // seek
+
+            mode = Mode.Seek;
+
+            responseBuffer.Enqueue(STAT);
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
         }
 
-        private void test() {
+        private void Cmd_19_Test() {
             uint command = parameterBuffer.Dequeue();
             responseBuffer.Clear(); //we need to clear the delay on response to get the actual 0 0 to bypass antimodchip protection
             switch (command) {
@@ -757,24 +878,24 @@ private void test() {
                     Console.WriteLine("[CDROM] Command 19 04 ResetSCExInfo Anti Mod Chip Meassures");
                     STAT = 0x2;
                     responseBuffer.Enqueue(STAT);
-                    interruptQueue.Enqueue(0x3);
+                    interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
                     break;
                 case 0x05:// 05h      -   INT3(total,success);Stop SCEx reading and get counters
                     Console.WriteLine("[CDROM] Command 19 05 GetSCExInfo Hack 0 0 Bypass Response");
                     responseBuffer.EnqueueRange(stackalloc byte[] { 0, 0 });
-                    interruptQueue.Enqueue(0x3);
+                    interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
                     break;
                 case 0x20: //INT3(yy,mm,dd,ver) ;Get cdrom BIOS date/version (yy,mm,dd,ver) http://www.psxdev.net/forum/viewtopic.php?f=70&t=557
                     responseBuffer.EnqueueRange(stackalloc byte[] { 0x94, 0x09, 0x19, 0xC0 });
-                    interruptQueue.Enqueue(0x3);
+                    interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
                     break;
                 case 0x22: //INT3("for US/AEP") --> Region-free debug version --> accepts unlicensed CDRs
                     responseBuffer.EnqueueRange(stackalloc byte[] { 0x66, 0x6F, 0x72, 0x20, 0x55, 0x53, 0x2F, 0x41, 0x45, 0x50 });
-                    interruptQueue.Enqueue(0x3);
+                    interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
                     break;
                 case 0x60://  60h      lo,hi     INT3(databyte)   ;HC05 SUB-CPU read RAM and I/O ports
                     responseBuffer.Enqueue(0);
-                    interruptQueue.Enqueue(0x3);
+                    interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
                     break;
                 default:
                     Console.WriteLine($"[CDROM] Unimplemented 0x19 Test Command {command:x8}");
@@ -782,6 +903,87 @@ private void test() {
             }
         }
 
+        private void Cmd_1A_GetID() {
+            //Door Open              INT5(11h,80h)  N/A
+            if (isLidOpen) {
+                responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 });
+                interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR);
+                return;
+            }
+
+            //No Disk                INT3(stat)     INT5(08h, 40h, 00h, 00h, 00h, 00h, 00h, 00h)
+            //STAT = 0x2; //0x40 seek
+            //responseBuffer.Enqueue(STAT);
+            //interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_RECEIVED_FIRST_RESPONSE);
+            //
+            //responseBuffer.EnqueueRange(stackalloc byte[] { 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 });
+            //interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR);
+
+            //Licensed: Mode2 INT3(stat)     INT2(02h, 00h, 20h, 00h, 53h, 43h, 45h, 4xh)
+            STAT = 0x40; //0x40 seek
+            STAT |= 0x2;
+            responseBuffer.Enqueue(STAT);
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
+
+            // Audio Disk INT3(stat) INT5(0Ah,90h, 00h,00h, 00h,00h,00h,00h)
+            if (cd.isAudioCD()) {
+                Span<byte> audioCdResponse = stackalloc byte[] { 0x0A, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
+                responseBuffer.EnqueueRange(audioCdResponse);
+                interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR);
+                return;
+            }
+
+            // Licensed: Mode2 INT3(stat)     INT2(02h, 00h, 20h, 00h, 53h, 43h, 45h, 4xh)
+            Span<byte> gameResponse = stackalloc byte[] { 0x02, 0x00, 0x20, 0x00, 0x53, 0x43, 0x45, 0x41 }; //SCE | //A 0x41 (America) - I 0x49 (Japan) - E 0x45 (Europe)
+            responseBuffer.EnqueueRange(gameResponse);
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT2_SECOND_RESPONSE);
+        }
+
+        private void Cmd_1B_ReadS() {
+            if (isLidOpen) {
+                responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 });
+                interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR);
+                return;
+            }
+
+            readLoc = seekLoc;
+
+            STAT = 0x2;
+            STAT |= 0x20;
+
+            responseBuffer.Enqueue(STAT);
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
+
+            mode = Mode.Read;
+        }
+
+        private void Cmd_1E_ReadTOC() {
+            if (isLidOpen) {
+                responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x80 });
+                interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR);
+                return;
+            }
+
+            mode = Mode.TOC;
+            responseBuffer.Enqueue(STAT);
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT3_FIRST_RESPONSE);
+        }
+
+        private void Cmd_1F_VideoCD() { //INT5(11h,40h)  ;-Unused/invalid
+            responseBuffer.EnqueueRange(stackalloc byte[] { 0x11, 0x40 });
+
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR);
+        }
+
+        private void Cmd_5x_lockUnlock() {
+            interruptQueue.EnqueueDelayedInterrupt(Interrupt.INT5_ERROR);
+        }
+
+        private void UnimplementedCDCommand(uint value) {
+            Console.WriteLine($"[CDROM] Unimplemented CD Command {value}");
+            Console.ReadLine();
+        }
+
         private byte STATUS() {
             //1F801800h - Index/Status Register (Bit0-1 R/W) (Bit2-7 Read Only)
             //0 - 1 Index Port 1F801801h - 1F801803h index(0..3 = Index0..Index3)   (R / W)
@@ -842,7 +1044,7 @@ private static (byte mm, byte ss, byte ff) getMMSSFFfromLBA(int lba) {
         private void applyVolume(byte[] rawSector) {
             var samples = MemoryMarshal.Cast<byte, short>(rawSector);
 
-            for(int i = 0; i < samples.Length; i+=2) {
+            for (int i = 0; i < samples.Length; i += 2) {
                 short l = samples[i];
                 short r = samples[i + 1];
 
@@ -859,5 +1061,19 @@ public Span<uint> processDmaLoad(int size) {
             return currentSector.read(size);
         }
 
+        internal void toggleLid() {
+            isLidOpen = !isLidOpen;
+            if (isLidOpen) {
+                STAT = 0x18;
+                mode = Mode.Idle;
+                interruptQueue.Clear();
+                responseBuffer.Clear();
+            } else {
+                //todo handle the Cd load and not this hardcoded test:
+                //cd = new CD(@"cd_change_path");
+            }
+            Console.WriteLine($"[CDROM] Shell is Open: {isLidOpen} STAT: {STAT}");
+        }
+
     }
 }
diff --git a/ProjectPSX/Devices/CDROM/Sector.cs b/ProjectPSX/Devices/CDROM/Sector.cs
index cc5f9bf..843a242 100644
--- a/ProjectPSX/Devices/CDROM/Sector.cs
+++ b/ProjectPSX/Devices/CDROM/Sector.cs
@@ -8,9 +8,10 @@ class Sector {
         // Standard size for a raw sector / CDDA
         public const int RAW_BUFFER = 2352;
 
-        // This sector data is already pre decoded and resampled so we need a bigger buffer (RAW_BUFFER * 4)
-        // and on the case of mono even a bigger one, as samples are mirrored to L/R as our output is allways stereo
-        public const int XA_BUFFER = RAW_BUFFER * 8;
+        // Only for the SPU: It represents a sector of data already pre decoded AND resampled so we need a bigger buffer (RAW_BUFFER * 4)
+        // and on the case of mono even a bigger one, as samples are mirrored to L/R as our output is allways stereo (that would be * 8)
+        // but on the special case of 18900 resampling we need even a bigger one... so go * 16
+        public const int XA_BUFFER = RAW_BUFFER * 16;
 
         private byte[] sectorBuffer;
 
diff --git a/ProjectPSX/Devices/CDROM/TrackBuilder.cs b/ProjectPSX/Devices/CDROM/TrackBuilder.cs
index 5146205..9ee1539 100644
--- a/ProjectPSX/Devices/CDROM/TrackBuilder.cs
+++ b/ProjectPSX/Devices/CDROM/TrackBuilder.cs
@@ -15,14 +15,16 @@ public class Track {
             public int lba { get; private set; }
             public int lbaStart { get; private set; }
             public int lbaEnd { get; private set; }
+            public bool isAudio { get; private set; }
 
-            public Track(String file, long size, byte number, int lba, int lbaStart, int lbaEnd) {
+            public Track(String file, long size, byte number, int lba, int lbaStart, int lbaEnd, bool isAudio) {
                 this.file = file;
                 this.size = size;
                 this.number = number;
                 this.lba = lba;
                 this.lbaStart = lbaStart;
                 this.lbaEnd = lbaEnd;
+                this.isAudio = isAudio;
             }
         }
 
@@ -52,9 +54,12 @@ public static List<Track> fromCue(String cue) {
 
                     lbaCounter += lba;
 
-                    tracks.Add(new Track(file, size, number, lba, lbaStart, lbaEnd));  ;
+                    string trackTypeLine = cueFile.ReadLine();
+                    bool isAudio = trackTypeLine.Contains("AUDIO");
 
-                    Console.WriteLine($"File: {file} Size: {size} Number: {number} LbaStart: {lbaStart} LbaEnd: {lbaEnd}");
+                    tracks.Add(new Track(file, size, number, lba, lbaStart, lbaEnd, isAudio));
+
+                    Console.WriteLine($"File: {file} Size: {size} Number: {number} LbaStart: {lbaStart} LbaEnd: {lbaEnd} isAudio {isAudio}");
                 }
             }
 
@@ -71,10 +76,11 @@ public static List<Track> fromBin(string file) {
             int lbaStart = 150; // 150 frames (2 seconds) offset from track 1
             int lbaEnd = lba;
             byte number = 1;
+            bool isAudio = false;
 
-            tracks.Add(new Track(file, size, number, lba, lbaStart, lbaEnd));
+            tracks.Add(new Track(file, size, number, lba, lbaStart, lbaEnd, isAudio));
 
-            Console.WriteLine($"File: {file} Size: {size} Number: {number} LbaStart: {lbaStart} LbaEnd: {lbaEnd}");
+            Console.WriteLine($"File: {file} Size: {size} Number: {number} LbaStart: {lbaStart} LbaEnd: {lbaEnd} isAudio {isAudio}");
 
             return tracks;
         }
diff --git a/ProjectPSX/Devices/CDROM/XaAdpcm.cs b/ProjectPSX/Devices/CDROM/XaAdpcm.cs
index cffefb2..8a4f8ab 100644
--- a/ProjectPSX/Devices/CDROM/XaAdpcm.cs
+++ b/ProjectPSX/Devices/CDROM/XaAdpcm.cs
@@ -122,7 +122,6 @@ private static List<short> resampleTo44100Hz(List<short> samples, bool isStereo,
             List<short> resamples = isStereo ? stereoResamples : monoResamples;
 
             resamples.Clear();
-            //todo handle 18900hz
 
             for (int i = 0; i < samples.Count; i++) {
                 resampleRingBuffer[channel][resamplePointer++ & 0x1F] = samples[i];
@@ -131,7 +130,11 @@ private static List<short> resampleTo44100Hz(List<short> samples, bool isStereo,
                 if (sixStep == 0) {
                     sixStep = 6;
                     for (int table = 0; table < 7; table++) {
-                        resamples.Add(zigZagInterpolate(resamplePointer, table, channel));
+                        short sample = zigZagInterpolate(resamplePointer, table, channel);
+                        resamples.Add(sample);
+                        if(is18900hz) {
+                            resamples.Add(sample);
+                        }
                     }
                 }
             }
@@ -149,7 +152,9 @@ private static short zigZagInterpolate(int resamplePointer, int table, int chann
         }
 
         public static short[] decodeNibbles(byte[] xaapdcm, int position, int blk, int nibble, ref short old, ref short older) {
-            int shift = 12 - (xaapdcm[position + 4 + blk * 2 + nibble] & 0x0F);
+            int headerShift = xaapdcm[position + 4 + blk * 2 + nibble] & 0x0F;
+            if (headerShift > 12) headerShift = 9;
+            int shift = 12 - headerShift;
             int filter = (xaapdcm[position + 4 + blk * 2 + nibble] & 0x30) >> 4;
 
             int f0 = positiveXaAdpcmTable[filter];
diff --git a/ProjectPSX/Devices/DMA.cs b/ProjectPSX/Devices/DMA.cs
deleted file mode 100644
index a0a8974..0000000
--- a/ProjectPSX/Devices/DMA.cs
+++ /dev/null
@@ -1,278 +0,0 @@
-
-using System;
-using System.Collections.Generic;
-
-namespace ProjectPSX.Devices {
-    public class DMA {
-
-        public abstract class AChannel {
-            public abstract void write(uint register, uint value);
-            public abstract uint load(uint regiter);
-        }
-
-        private class InterruptChannel : AChannel {
-
-            private uint control;
-
-            private bool forceIRQ;
-            private uint irqEnable;
-            private bool masterEnable;
-            private uint irqFlag;
-            private bool masterFlag;
-
-            private bool edgeInterruptTrigger;
-
-            public InterruptChannel() {
-                control = 0x07654321;
-            }
-
-            public override uint load(uint register) {
-                switch (register) {
-                    case 0: return control;
-                    case 4: return loadInterrupt();
-                    case 6: return loadInterrupt() >> 16; //castlevania symphony of the night and dino crisis 2 ask for this
-                    default: Console.WriteLine("Unhandled register on interruptChannel DMA load " + register); return 0xFFFF_FFFF;
-                }
-            }
-
-            private uint loadInterrupt() {
-                uint interruptRegister = 0;
-
-                interruptRegister |= (forceIRQ ? 1u : 0) << 15;
-                interruptRegister |= irqEnable << 16;
-                interruptRegister |= (masterEnable ? 1u : 0) << 23;
-                interruptRegister |= irqFlag << 24;
-                interruptRegister |= (masterFlag ? 1u : 0) << 31;
-
-                return interruptRegister;
-            }
-
-            public override void write(uint register, uint value) {
-                //Console.WriteLine("irqflag pre: " + irqFlag.ToString("x8"));
-                switch (register) {
-                    case 0: control = value; break;
-                    case 4: writeInterrupt(value); break;
-                    case 6: writeInterrupt(value << 16 | (forceIRQ ? 1u : 0) << 15); break;
-                    default: Console.WriteLine($"Unhandled write on DMA Interrupt register {register}"); break;
-                }
-                //Console.WriteLine("irqflag post: " + irqFlag.ToString("x8"));
-            }
-
-            private void writeInterrupt(uint value) {
-                forceIRQ = ((value >> 15) & 0x1) != 0;
-                irqEnable = (value >> 16) & 0x7F;
-                masterEnable = ((value >> 23) & 0x1) != 0;
-                irqFlag &= ~((value >> 24) & 0x7F);
-
-                masterFlag = updateMasterFlag();
-            }
-
-            public void handleInterrupt(int channel) {
-                //IRQ flags in Bit(24 + n) are set upon DMAn completion - but caution - they are set ONLY if enabled in Bit(16 + n).
-                if ((irqEnable & (1 << channel)) != 0) {
-                    irqFlag |= (uint)(1 << channel);
-                }
-
-                //Console.WriteLine($"MasterFlag: {masterFlag} irqEnable16: {irqEnable:x8} irqFlag24: {irqFlag:x8} {forceIRQ} {masterEnable} {((irqEnable & irqFlag) > 0)}");
-                masterFlag = updateMasterFlag();
-
-                if (masterFlag) {
-                    edgeInterruptTrigger = true;
-                }
-            }
-
-            private bool updateMasterFlag() {
-                //Bit31 is a simple readonly flag that follows the following rules:
-                //IF b15 = 1 OR(b23 = 1 AND(b16 - 22 AND b24 - 30) > 0) THEN b31 = 1 ELSE b31 = 0
-                return forceIRQ || (masterEnable && ((irqEnable & irqFlag) > 0));
-            }
-
-            public bool tick() {
-                if (edgeInterruptTrigger) {
-                    edgeInterruptTrigger = false;
-                    //Console.WriteLine("[IRQ] Triggering DMA");
-                    return true;
-                }
-                return false;
-            }
-        }
-
-        private class Channel : AChannel {
-
-            private uint baseAddress;
-            private uint blockSize;
-            private uint blockCount;
-
-            private uint transferDirection;
-            private uint memoryStep;
-            private uint choppingEnable;
-            private uint syncMode;
-            private uint choppingDMAWindowSize;
-            private uint choppingCPUWindowSize;
-            private bool enable;
-            private bool trigger;
-
-            private BUS bus;
-            private InterruptChannel interrupt;
-            private int channelNumber;
-
-            public Channel(int channelNumber, InterruptChannel interrupt, BUS bus) {
-                this.channelNumber = channelNumber;
-                this.interrupt = interrupt;
-                this.bus = bus;
-            }
-
-            public override uint load(uint register) {
-                switch (register) {
-                    case 0: return baseAddress;
-                    case 4: return blockCount << 16 | blockSize;
-                    case 8: return loadChannelControl();
-                    default: return 0;
-                }
-            }
-
-            private uint loadChannelControl() {
-                uint channelControl = 0;
-
-                channelControl |= transferDirection;
-                channelControl |= (memoryStep == 4 ? 0 : 1u) << 1;
-                channelControl |= choppingEnable << 8;
-                channelControl |= syncMode << 9;
-                channelControl |= choppingDMAWindowSize << 16;
-                channelControl |= choppingCPUWindowSize << 20;
-                channelControl |= (enable ? 1u : 0) << 24;
-                channelControl |= (trigger ? 1u : 0) << 28;
-
-                return channelControl;
-            }
-
-            public override void write(uint register, uint value) {
-                switch (register) {
-                    case 0: baseAddress = value & 0xFFFFFF; break;
-                    case 4: blockCount = value >> 16; blockSize = value & 0xFFFF; break;
-                    case 8: writeChannelControl(value); break;
-                    default: Console.WriteLine($"Unhandled Write on DMA Channel: {channelNumber} register: {register} value: {value}"); break;
-                }
-            }
-
-            private void writeChannelControl(uint value) {
-                transferDirection = value & 0x1;
-                memoryStep = (uint)(((value >> 1) & 0x1) == 0 ? 4 : -4);
-                choppingEnable = (value >> 8) & 0x1;
-                syncMode = (value >> 9) & 0x3;
-                choppingDMAWindowSize = (value >> 16) & 0x7;
-                choppingCPUWindowSize = (value >> 20) & 0x7;
-                enable = ((value >> 24) & 0x1) != 0;
-                trigger = ((value >> 28) & 0x1) != 0;
-
-                handleDMA();
-            }
-
-            private void handleDMA() {
-                if (!isActive()) return;
-                if (syncMode == 0) {
-                    blockCopy(blockSize);
-                } else if (syncMode == 1) {
-                    blockCopy(blockSize * blockCount);
-                } else if (syncMode == 2) {
-                    linkedList();
-                }
-
-                //disable channel
-                enable = false;
-                trigger = false;
-
-                interrupt.handleInterrupt(channelNumber);
-            }
-
-
-            private void blockCopy(uint size) {
-                if (transferDirection == 0) { //To Ram
-
-                    switch (channelNumber) {
-                        case 1: bus.DmaFromMdecOut(baseAddress, (int)size); break;
-                        case 2: bus.DmaFromGpu(baseAddress, (int)size); break;
-                        case 3: bus.DmaFromCD(baseAddress, (int)size); break;
-                        case 4: bus.DmaFromSpu(baseAddress, (int)size); break;
-                        case 6: bus.DmaOTC(baseAddress, (int)size); break;
-                        default: Console.WriteLine($"[DMA] [BLOCK COPY] Unsupported Channel (to Ram) {channelNumber}"); break;
-                    }
-
-                    baseAddress += memoryStep * size;
-
-                } else { //From Ram
-
-                    Span<uint> dma = bus.DmaFromRam(baseAddress & 0x1F_FFFC, size);
-
-                    switch(channelNumber) {
-                        case 0: bus.DmaToMdecIn(dma); break;
-                        case 2: bus.DmaToGpu(dma); break;
-                        case 4: bus.DmaToSpu(dma); break;
-                        default: Console.WriteLine($"[DMA] [BLOCK COPY] Unsupported Channel (from Ram) {channelNumber}"); break;
-                    }
-
-                    baseAddress += memoryStep * size;
-                }
-
-            }
-
-            private void linkedList() {
-                uint header = 0;
-                uint linkedListHardStop = 0xFFFF; //an arbitrary value to avoid infinity linked lists as we don't run the cpu in between blocks
-
-                while ((header & 0x800000) == 0 && linkedListHardStop-- != 0) {
-                    header = bus.LoadFromRam(baseAddress);
-                    uint size = header >> 24;
-                    //Console.WriteLine($"[DMA] [LinkedList] Header: {baseAddress:x8} size: {size}");
-
-                    if (size > 0) {
-                        baseAddress = (baseAddress + 4) & 0x1ffffc;
-                        Span<uint> load = bus.DmaFromRam(baseAddress, size);
-                        //Console.WriteLine($"[DMA] [LinkedList] DMAtoGPU size: {load.Length}");
-                        bus.DmaToGpu(load);
-                    }
-
-                    if (baseAddress == (header & 0x1ffffc)) break; //Tekken2 hangs here if not handling this posible forever loop
-                    baseAddress = header & 0x1ffffc;
-                }
-
-            }
-
-            //0  Start immediately and transfer all at once (used for CDROM, OTC) needs TRIGGER
-            private bool isActive() => syncMode == 0 ? enable && trigger : enable;
-
-        }
-
-        AChannel[] channels = new AChannel[8];
-
-        public DMA(BUS bus) {
-            InterruptChannel interrupt = new InterruptChannel();
-            channels[0] = new Channel(0, interrupt, bus);
-            channels[1] = new Channel(1, interrupt, bus);
-            channels[2] = new Channel(2, interrupt, bus);
-            channels[3] = new Channel(3, interrupt, bus);
-            channels[4] = new Channel(4, interrupt, bus);
-            channels[5] = new Channel(5, interrupt, bus);
-            channels[6] = new Channel(6, interrupt, bus);
-            channels[7] = interrupt;
-        }
-
-        public uint load(uint addr) {
-            uint channel = (addr & 0x70) >> 4;
-            uint register = addr & 0xF;
-            //Console.WriteLine("DMA load " + channel + " " + register  + ":" + channels[channel].load(register).ToString("x8"));
-            return channels[channel].load(register);
-        }
-
-        public void write(uint addr, uint value) {
-            uint channel = (addr & 0x70) >> 4;
-            uint register = addr & 0xF;
-            //Console.WriteLine("DMA write " + channel + " " + register + ":" + value.ToString("x8"));
-
-            channels[channel].write(register, value);
-        }
-
-        public bool tick() => ((InterruptChannel)channels[7]).tick();
-
-    }
-}
diff --git a/ProjectPSX/Devices/DMA/Channel.cs b/ProjectPSX/Devices/DMA/Channel.cs
new file mode 100644
index 0000000..ea34d55
--- /dev/null
+++ b/ProjectPSX/Devices/DMA/Channel.cs
@@ -0,0 +1,5 @@
+namespace ProjectPSX.Devices; 
+public abstract class Channel {
+    public abstract void write(uint register, uint value);
+    public abstract uint load(uint regiter);
+}
diff --git a/ProjectPSX/Devices/DMA/DMA.cs b/ProjectPSX/Devices/DMA/DMA.cs
new file mode 100644
index 0000000..d619e56
--- /dev/null
+++ b/ProjectPSX/Devices/DMA/DMA.cs
@@ -0,0 +1,42 @@
+using System;
+
+namespace ProjectPSX.Devices; 
+public class DMA {
+
+    Channel[] channels = new Channel[8];
+
+    public DMA(BUS bus) {
+        var interrupt = new InterruptChannel();
+        channels[0] = new DmaChannel(0, interrupt, bus);
+        channels[1] = new DmaChannel(1, interrupt, bus);
+        channels[2] = new DmaChannel(2, interrupt, bus);
+        channels[3] = new DmaChannel(3, interrupt, bus);
+        channels[4] = new DmaChannel(4, interrupt, bus);
+        channels[5] = new DmaChannel(5, interrupt, bus);
+        channels[6] = new DmaChannel(6, interrupt, bus);
+        channels[7] = interrupt;
+    }
+
+    public uint load(uint addr) {
+        var channel = (addr & 0x70) >> 4;
+        var register = addr & 0xF;
+        //Console.WriteLine("DMA load " + channel + " " + register  + ":" + channels[channel].load(register).ToString("x8"));
+        return channels[channel].load(register);
+    }
+
+    public void write(uint addr, uint value) {
+        var channel = (addr & 0x70) >> 4;
+        var register = addr & 0xF;
+        //Console.WriteLine("DMA write " + channel + " " + register + ":" + value.ToString("x8"));
+
+        channels[channel].write(register, value);
+    }
+
+    public bool tick() {
+        for (var i = 0; i < 7; i++) {
+            ((DmaChannel)channels[i]).transferBlockIfPending();
+        }
+        return ((InterruptChannel)channels[7]).tick();
+    }
+
+}
diff --git a/ProjectPSX/Devices/DMA/DmaChannel.cs b/ProjectPSX/Devices/DMA/DmaChannel.cs
new file mode 100644
index 0000000..63ea45e
--- /dev/null
+++ b/ProjectPSX/Devices/DMA/DmaChannel.cs
@@ -0,0 +1,193 @@
+using System;
+
+namespace ProjectPSX.Devices;
+public sealed class DmaChannel : Channel {
+
+    private uint baseAddress;
+    private uint blockSize;
+    private uint blockCount;
+
+    private uint transferDirection;
+    private uint memoryStep;
+    private uint choppingEnable;
+    private uint syncMode;
+    private uint choppingDMAWindowSize;
+    private uint choppingCPUWindowSize;
+    private bool enable;
+    private bool trigger;
+
+    private uint unknownBit29;
+    private uint unknownBit30;
+
+    private BUS bus;
+    private InterruptChannel interrupt;
+    private int channelNumber;
+
+    private uint pendingBlocks;
+
+    public DmaChannel(int channelNumber, InterruptChannel interrupt, BUS bus) {
+        this.channelNumber = channelNumber;
+        this.interrupt = interrupt;
+        this.bus = bus;
+    }
+
+    public override uint load(uint register) {
+        switch (register) {
+            case 0: return baseAddress;
+            case 4: return blockCount << 16 | blockSize;
+            case 8: return loadChannelControl();
+            default: return 0;
+        }
+    }
+
+    private uint loadChannelControl() {
+        uint channelControl = 0;
+
+        channelControl |= transferDirection;
+        channelControl |= (memoryStep == 4 ? 0 : 1u) << 1;
+        channelControl |= choppingEnable << 8;
+        channelControl |= syncMode << 9;
+        channelControl |= choppingDMAWindowSize << 16;
+        channelControl |= choppingCPUWindowSize << 20;
+        channelControl |= (enable ? 1u : 0) << 24;
+        channelControl |= (trigger ? 1u : 0) << 28;
+        channelControl |= unknownBit29 << 29;
+        channelControl |= unknownBit30 << 30;
+
+        if (channelNumber == 6) {
+            return channelControl & 0x5000_0002 | 0x2;
+        }
+
+        return channelControl;
+    }
+
+    public override void write(uint register, uint value) {
+        switch (register) {
+            case 0: baseAddress = value & 0xFFFFFF; break;
+            case 4: blockCount = value >> 16; blockSize = value & 0xFFFF; break;
+            case 8: writeChannelControl(value); break;
+            default: Console.WriteLine($"Unhandled Write on DMA Channel: {channelNumber} register: {register} value: {value}"); break;
+        }
+    }
+
+    private void writeChannelControl(uint value) {
+        transferDirection = value & 0x1;
+        memoryStep = (uint)((value >> 1 & 0x1) == 0 ? 4 : -4);
+        choppingEnable = value >> 8 & 0x1;
+        syncMode = value >> 9 & 0x3;
+        choppingDMAWindowSize = value >> 16 & 0x7;
+        choppingCPUWindowSize = value >> 20 & 0x7;
+        enable = (value >> 24 & 0x1) != 0;
+        trigger = (value >> 28 & 0x1) != 0;
+        unknownBit29 = value >> 29 & 0x1;
+        unknownBit30 = value >> 30 & 0x1;
+
+        if (!enable) pendingBlocks = 0;
+
+        handleDMA();
+    }
+
+    private void handleDMA() {
+        if (!isActive() || !interrupt.isDMAControlMasterEnabled(channelNumber)) return;
+        if (syncMode == 0) {
+            //if (choppingEnable == 1) {
+            //    Console.WriteLine($"[DMA] Chopping Syncmode 0 not supported. DmaWindow: {choppingDMAWindowSize} CpuWindow: {choppingCPUWindowSize}");
+            //}
+
+            blockCopy(blockSize == 0 ? 0x10_000 : blockSize);
+            finishDMA();
+
+        } else if (syncMode == 1) {
+            // HACK:
+            // GPUIn: Bypass blocks to elude mdec/gpu desync as MDEC is actually too fast decoding blocks
+            // MdecIn: GranTurismo produces some artifacts that still needs to be checked otherwise it's ok on other games i've checked
+            if (channelNumber == 2 && transferDirection == 1 || channelNumber == 0) {
+                blockCopy(blockSize * blockCount);
+                finishDMA();
+                return;
+            }
+
+            trigger = false;
+            pendingBlocks = blockCount;
+            transferBlockIfPending();
+        } else if (syncMode == 2) {
+            linkedList();
+            finishDMA();
+        }
+    }
+
+    private void finishDMA() {
+        enable = false;
+        trigger = false;
+
+        interrupt.handleInterrupt(channelNumber);
+    }
+
+    private void blockCopy(uint size) {
+        if (transferDirection == 0) { //To Ram
+
+            switch (channelNumber) {
+                case 1: bus.DmaFromMdecOut(baseAddress, (int)size); break;
+                case 2: bus.DmaFromGpu(baseAddress, (int)size); break;
+                case 3: bus.DmaFromCD(baseAddress, (int)size); break;
+                case 4: bus.DmaFromSpu(baseAddress, (int)size); break;
+                case 6: bus.DmaOTC(baseAddress, (int)size); break;
+                default: Console.WriteLine($"[DMA] [BLOCK COPY] Unsupported Channel (to Ram) {channelNumber}"); break;
+            }
+
+            baseAddress += memoryStep * size;
+
+        } else { //From Ram
+
+            var dma = bus.DmaFromRam(baseAddress & 0x1F_FFFC, size);
+
+            switch (channelNumber) {
+                case 0: bus.DmaToMdecIn(dma); break;
+                case 2: bus.DmaToGpu(dma); break;
+                case 4: bus.DmaToSpu(dma); break;
+                default: Console.WriteLine($"[DMA] [BLOCK COPY] Unsupported Channel (from Ram) {channelNumber}"); break;
+            }
+
+            baseAddress += memoryStep * size;
+        }
+
+    }
+
+    private void linkedList() {
+        uint header = 0;
+        uint linkedListHardStop = 0xFFFF; //an arbitrary value to avoid infinity linked lists as we don't run the cpu in between blocks
+
+        while ((header & 0x800000) == 0 && linkedListHardStop-- != 0) {
+            header = bus.LoadFromRam(baseAddress);
+            var size = header >> 24;
+            //Console.WriteLine($"[DMA] [LinkedList] Header: {baseAddress:x8} size: {size}");
+
+            if (size > 0) {
+                baseAddress = baseAddress + 4 & 0x1ffffc;
+                var load = bus.DmaFromRam(baseAddress, size);
+                //Console.WriteLine($"[DMA] [LinkedList] DMAtoGPU size: {load.Length}");
+                bus.DmaToGpu(load);
+            }
+
+            if (baseAddress == (header & 0x1ffffc)) break; //Tekken2 hangs here if not handling this posible forever loop
+            baseAddress = header & 0x1ffffc;
+        }
+
+    }
+
+    //0  Start immediately and transfer all at once (used for CDROM, OTC) needs TRIGGER
+    private bool isActive() => syncMode == 0 ? enable && trigger : enable;
+
+    public void transferBlockIfPending() {
+        //TODO: check if device can actually transfer. Here we assume devices are always
+        // capable of processing the dmas and never busy.
+        if (pendingBlocks > 0) {
+            pendingBlocks--;
+            blockCopy(blockSize);
+
+            if (pendingBlocks == 0) {
+                finishDMA();
+            }
+        }
+    }
+}
diff --git a/ProjectPSX/Devices/DMA/InterruptChannel.cs b/ProjectPSX/Devices/DMA/InterruptChannel.cs
new file mode 100644
index 0000000..7e45e27
--- /dev/null
+++ b/ProjectPSX/Devices/DMA/InterruptChannel.cs
@@ -0,0 +1,92 @@
+using System;
+
+namespace ProjectPSX.Devices;
+public sealed class InterruptChannel : Channel {
+
+    // 1F8010F0h DPCR - DMA Control register
+    private uint control;
+
+    // 1F8010F4h DICR - DMA Interrupt register
+    private bool forceIRQ;
+    private uint irqEnable;
+    private bool masterEnable;
+    private uint irqFlag;
+    private bool masterFlag;
+
+    private bool edgeInterruptTrigger;
+
+    public InterruptChannel() {
+        control = 0x07654321;
+    }
+
+    public override uint load(uint register) {
+        switch (register) {
+            case 0: return control;
+            case 4: return loadInterrupt();
+            case 6: return loadInterrupt() >> 16; //castlevania symphony of the night and dino crisis 2 ask for this
+            default: Console.WriteLine("Unhandled register on interruptChannel DMA load " + register); return 0xFFFF_FFFF;
+        }
+    }
+
+    private uint loadInterrupt() {
+        uint interruptRegister = 0;
+
+        interruptRegister |= (forceIRQ ? 1u : 0) << 15;
+        interruptRegister |= irqEnable << 16;
+        interruptRegister |= (masterEnable ? 1u : 0) << 23;
+        interruptRegister |= irqFlag << 24;
+        interruptRegister |= (masterFlag ? 1u : 0) << 31;
+
+        return interruptRegister;
+    }
+
+    public override void write(uint register, uint value) {
+        //Console.WriteLine("irqflag pre: " + irqFlag.ToString("x8"));
+        switch (register) {
+            case 0: control = value; break;
+            case 4: writeInterrupt(value); break;
+            case 6: writeInterrupt(value << 16 | (forceIRQ ? 1u : 0) << 15); break;
+            default: Console.WriteLine($"Unhandled write on DMA Interrupt register {register}"); break;
+        }
+        //Console.WriteLine("irqflag post: " + irqFlag.ToString("x8"));
+    }
+
+    private void writeInterrupt(uint value) {
+        forceIRQ = (value >> 15 & 0x1) != 0;
+        irqEnable = value >> 16 & 0x7F;
+        masterEnable = (value >> 23 & 0x1) != 0;
+        irqFlag &= ~(value >> 24 & 0x7F);
+
+        masterFlag = updateMasterFlag();
+    }
+
+    public void handleInterrupt(int channel) {
+        //IRQ flags in Bit(24 + n) are set upon DMAn completion - but caution - they are set ONLY if enabled in Bit(16 + n).
+        if ((irqEnable & 1 << channel) != 0) {
+            irqFlag |= (uint)(1 << channel);
+        }
+
+        //Console.WriteLine($"MasterFlag: {masterFlag} irqEnable16: {irqEnable:x8} irqFlag24: {irqFlag:x8} {forceIRQ} {masterEnable} {((irqEnable & irqFlag) > 0)}");
+        masterFlag = updateMasterFlag();
+        edgeInterruptTrigger |= masterFlag;
+    }
+
+    public bool isDMAControlMasterEnabled(int channelNumber) {
+        return (control >> 3 >> 4 * channelNumber & 0x1) != 0;
+    }
+
+    private bool updateMasterFlag() {
+        //Bit31 is a simple readonly flag that follows the following rules:
+        //IF b15 = 1 OR(b23 = 1 AND(b16 - 22 AND b24 - 30) > 0) THEN b31 = 1 ELSE b31 = 0
+        return forceIRQ || masterEnable && (irqEnable & irqFlag) > 0;
+    }
+
+    public bool tick() {
+        if (edgeInterruptTrigger) {
+            edgeInterruptTrigger = false;
+            //Console.WriteLine("[IRQ] Triggering DMA");
+            return true;
+        }
+        return false;
+    }
+}
diff --git a/ProjectPSX/Devices/Expansion/Exp2.cs b/ProjectPSX/Devices/Expansion/Exp2.cs
new file mode 100644
index 0000000..7e274a0
--- /dev/null
+++ b/ProjectPSX/Devices/Expansion/Exp2.cs
@@ -0,0 +1,29 @@
+using System;
+
+namespace ProjectPSX.Devices.Expansion;
+public class Exp2 {
+
+    public uint load(uint addr) {
+        //Console.WriteLine($"[BUS] Read Unsupported to EXP2 address: {addr:x8}");
+        return 0xFF;
+    }
+
+    public void write(uint addr, uint value) {
+        switch (addr) {
+            case 0x1F802041:
+                Console.ForegroundColor = ConsoleColor.Cyan;
+                Console.WriteLine($"[EXP2] PSX: POST [{value:x1}]");
+                Console.ResetColor();
+                break;
+            case 0x1F802023:
+            case 0x1F802080:
+                Console.ForegroundColor = ConsoleColor.Cyan;
+                Console.Write((char)value);
+                Console.ResetColor();
+                break;
+            default:
+                Console.WriteLine($"[BUS] Write Unsupported to EXP2: {addr:x8} Value: {value:x8}");
+                break;
+        }
+    }
+}
diff --git a/ProjectPSX/Devices/GPU/GPU.cs b/ProjectPSX/Devices/GPU/GPU.cs
index 6f9acda..6676915 100644
--- a/ProjectPSX/Devices/GPU/GPU.cs
+++ b/ProjectPSX/Devices/GPU/GPU.cs
@@ -20,8 +20,10 @@ public class GPU {
 
         private IHostWindow window;
 
-        private VRAM vram = new VRAM(1024, 512); //Vram is 8888 and we transform everything to it
-        private VRAM1555 vram1555 = new VRAM1555(1024, 512); //an un transformed 1555 to 8888 vram so we can fetch clut indexes without reverting to 1555
+        private VRAM vram = new VRAM(); //Vram is 8888 and we transform everything to it
+        private VRAM1555 vram1555 = new VRAM1555(); //an un transformed 1555 to 8888 vram so we can fetch clut indexes without reverting to 1555
+
+        private int[] color1555to8888LUT;
 
         public bool debug;
 
@@ -31,7 +33,7 @@ private enum Mode {
         }
         private Mode mode;
 
-        private ref struct Primitive {
+        private struct Primitive {
             public bool isShaded;
             public bool isTextured;
             public bool isSemiTransparent;
@@ -54,12 +56,9 @@ private struct VramTransfer {
 
         [StructLayout(LayoutKind.Explicit)]
         private struct Point2D {
-            [FieldOffset(0)] public uint val;
             [FieldOffset(0)] public short x;
             [FieldOffset(2)] public short y;
         }
-
-        private Point2D[] v = new Point2D[4];
         private Point2D min = new Point2D();
         private Point2D max = new Point2D();
 
@@ -69,7 +68,7 @@ private struct TextureData {
             [FieldOffset(0)] public byte x;
             [FieldOffset(1)] public byte y;
         }
-        private TextureData[] t = new TextureData[4];
+        TextureData textureData = new TextureData();
 
         [StructLayout(LayoutKind.Explicit)]
         private struct Color {
@@ -107,9 +106,9 @@ private struct Color {
         private bool isInterruptRequested;
         private bool isDmaRequest;
 
-        private bool isReadyToReceiveCommand;
-        private bool isReadyToSendVRAMToCPU;
-        private bool isReadyToReceiveDMABlock;
+        private bool isReadyToReceiveCommand = true; //todo
+        private bool isReadyToSendVRAMToCPU; 
+        private bool isReadyToReceiveDMABlock = true; //todo
 
         private byte dmaDirection;
         private bool isOddLine;
@@ -117,6 +116,11 @@ private struct Color {
         private bool isTexturedRectangleXFlipped;
         private bool isTexturedRectangleYFlipped;
 
+        private uint drawModeBits = 0xFFFF_FFFF;
+        private uint displayModeBits = 0xFFFF_FFFF;
+        uint displayVerticalRange = 0xFFFF_FFFF;
+        uint displayHorizontalRange = 0xFFFF_FFFF;
+
         private uint textureWindowBits = 0xFFFF_FFFF;
         private int preMaskX;
         private int preMaskY;
@@ -144,9 +148,23 @@ private struct Color {
         public GPU(IHostWindow window) {
             this.window = window;
             mode = Mode.COMMAND;
+            initColorTable();
             GP1_00_ResetGPU();
         }
 
+        public void initColorTable() {
+            color1555to8888LUT = new int[ushort.MaxValue + 1];
+            for (int m = 0; m < 2; m++) {
+                for (int r = 0; r < 32; r++) {
+                    for (int g = 0; g < 32; g++) {
+                        for (int b = 0; b < 32; b++) {
+                            color1555to8888LUT[m << 15 | b << 10 | g << 5 | r] = m << 24 | r << 16 + 3 | g << 8 + 3| b << 3;
+                        }
+                    }
+                }
+            }
+        }
+
         public bool tick(int cycles) {
             //Video clock is the cpu clock multiplied by 11/7.
             videoCycles += cycles * 11 / 7;
@@ -165,6 +183,7 @@ public bool tick(int cycles) {
 
                     if (isVerticalInterlace && isVerticalResolution480) {
                         isOddLine = !isOddLine;
+                        isInterlaceField = !isOddLine;
                     }
 
                     window.Render(vram.Bits);
@@ -185,12 +204,7 @@ public bool tick(int cycles) {
         public uint loadGPUSTAT() {
             uint GPUSTAT = 0;
 
-            GPUSTAT |= textureXBase;
-            GPUSTAT |= (uint)textureYBase << 4;
-            GPUSTAT |= (uint)transparencyMode << 5;
-            GPUSTAT |= (uint)textureDepth << 7;
-            GPUSTAT |= (uint)(isDithered ? 1 : 0) << 9;
-            GPUSTAT |= (uint)(isDrawingToDisplayAllowed ? 1 : 0) << 10;
+            GPUSTAT |= drawModeBits & 0x7FF;
             GPUSTAT |= (uint)maskWhileDrawing << 11;
             GPUSTAT |= (uint)(checkMaskBeforeDraw ? 1 : 0) << 12;
             GPUSTAT |= (uint)(isInterlaceField ? 1 : 0) << 13;
@@ -198,7 +212,7 @@ public uint loadGPUSTAT() {
             GPUSTAT |= (uint)(isTextureDisabled ? 1 : 0) << 15;
             GPUSTAT |= (uint)horizontalResolution2 << 16;
             GPUSTAT |= (uint)horizontalResolution1 << 17;
-            GPUSTAT |= (uint)(isVerticalResolution480 ? 1 : 0);
+            GPUSTAT |= (uint)(isVerticalResolution480 ? 1 : 0) << 19;
             GPUSTAT |= (uint)(isPal ? 1 : 0) << 20;
             GPUSTAT |= (uint)(is24BitDepth ? 1 : 0) << 21;
             GPUSTAT |= (uint)(isVerticalInterlace ? 1 : 0) << 22;
@@ -206,9 +220,9 @@ public uint loadGPUSTAT() {
             GPUSTAT |= (uint)(isInterruptRequested ? 1 : 0) << 24;
             GPUSTAT |= (uint)(isDmaRequest ? 1 : 0) << 25;
 
-            GPUSTAT |= (uint)/*(isReadyToReceiveCommand ? 1 : 0)*/1 << 26;
-            GPUSTAT |= (uint)/*(isReadyToSendVRAMToCPU ? 1 : 0)*/1 << 27;
-            GPUSTAT |= (uint)/*(isReadyToReceiveDMABlock ? 1 : 0)*/1 << 28;
+            GPUSTAT |= (uint)(isReadyToReceiveCommand ? 1 : 0) << 26;
+            GPUSTAT |= (uint)(isReadyToSendVRAMToCPU ? 1 : 0) << 27;
+            GPUSTAT |= (uint)(isReadyToReceiveDMABlock ? 1 : 0) << 28;
 
             GPUSTAT |= (uint)dmaDirection << 29;
             GPUSTAT |= (uint)(isOddLine ? 1 : 0) << 31;
@@ -254,7 +268,7 @@ public void writeGP0(uint value) {
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         internal void processDma(Span<uint> dma) {
             if (mode == Mode.COMMAND) {
-                DecodeGP0Command(dma);
+                    DecodeGP0Command(dma);
             } else {
                 for (int i = 0; i < dma.Length; i++) {
                     WriteToVRAM(dma[i]);
@@ -287,38 +301,39 @@ private void WriteToVRAM(uint value) {
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private uint readFromVRAM() {
-            ushort pixel0 = vram.GetPixelBGR555(vramTransfer.x++ & 0x3FF, vramTransfer.y & 0x1FF);
-            ushort pixel1 = vram.GetPixelBGR555(vramTransfer.x++ & 0x3FF, vramTransfer.y & 0x1FF);
-            if (vramTransfer.x == vramTransfer.origin_x + vramTransfer.w) {
-                vramTransfer.x -= vramTransfer.w;
-                vramTransfer.y++;
-            }
-            vramTransfer.halfWords -= 2;
-            return (uint)(pixel1 << 16 | pixel0);
-        }
+            ushort pixel0 = vram.GetPixelBGR555(vramTransfer.x & 0x3FF, vramTransfer.y & 0x1FF);
+            stepVramTransfer();
+            ushort pixel1 = vram.GetPixelBGR555(vramTransfer.x & 0x3FF, vramTransfer.y & 0x1FF);
+            stepVramTransfer();
 
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private void drawVRAMPixel(ushort val) {
-            if (checkMaskBeforeDraw) {
-                int bg = vram.GetPixelRGB888(vramTransfer.x, vramTransfer.y);
+            vramTransfer.halfWords -= 2;
 
-                if (bg >> 24 == 0) {
-                    vram.SetPixel(vramTransfer.x & 0x3FF, vramTransfer.y & 0x1FF, color1555to8888(val));
-                    vram1555.SetPixel(vramTransfer.x & 0x3FF, vramTransfer.y & 0x1FF, val);
-                }
-            } else {
-                vram.SetPixel(vramTransfer.x & 0x3FF, vramTransfer.y & 0x1FF, color1555to8888(val));
-                vram1555.SetPixel(vramTransfer.x & 0x3FF, vramTransfer.y & 0x1FF, val);
+            if (vramTransfer.halfWords == 0) {
+                isReadyToSendVRAMToCPU = false;
+                isReadyToReceiveDMABlock = true;
             }
 
-            vramTransfer.x++;
+            return (uint)(pixel1 << 16 | pixel0);
+        }
 
-            if (vramTransfer.x == vramTransfer.origin_x + vramTransfer.w) {
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private void stepVramTransfer() {
+            if (++vramTransfer.x == vramTransfer.origin_x + vramTransfer.w) {
                 vramTransfer.x -= vramTransfer.w;
                 vramTransfer.y++;
             }
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private void drawVRAMPixel(ushort color1555) {
+            if (!checkMaskBeforeDraw || vram.GetPixelRGB888(vramTransfer.x, vramTransfer.y) >> 24 == 0) {
+                vram.SetPixel(vramTransfer.x & 0x3FF, vramTransfer.y & 0x1FF, color1555to8888LUT[color1555]);
+                vram1555.SetPixel(vramTransfer.x & 0x3FF, vramTransfer.y & 0x1FF, color1555);
+            }
+
+            stepVramTransfer();
+        }
+
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private void DecodeGP0Command(uint value) {
             if (pointer == 0) {
@@ -407,11 +422,18 @@ private void GP0_02_FillRectVRAM(Span<uint> buffer) {
             ushort w = (ushort)(((hw & 0x3FF) + 0xF) & ~0xF);
             ushort h = (ushort)((hw >> 16) & 0x1FF);
 
-            int color = (color0.r << 16 | color0.g << 8 | color0.b);
+            int color = color0.r << 16 | color0.g << 8 | color0.b;
 
-            for (int yPos = y; yPos < h + y; yPos++) {
-                for (int xPos = x; xPos < w + x; xPos++) {
-                    vram.SetPixel(xPos & 0x3FF, yPos & 0x1FF, color);
+            if(x + w <= 0x3FF && y + h <= 0x1FF) {
+                var vramSpan = new Span<int>(vram.Bits);
+                for (int yPos = y; yPos < h + y; yPos++) {
+                    vramSpan.Slice(x + (yPos * 1024), w).Fill(color);
+                }
+            } else {
+                for (int yPos = y; yPos < h + y; yPos++) {
+                    for (int xPos = x; xPos < w + x; xPos++) {
+                        vram.SetPixel(xPos & 0x3FF, yPos & 0x1FF, color);
+                    }
                 }
             }
         }
@@ -441,11 +463,14 @@ public void GP0_RenderPolygon(Span<uint> buffer) {
 
             int vertexN = isQuad ? 4 : 3;
             Span<uint> c = stackalloc uint[vertexN];
+            Span<Point2D> v = stackalloc Point2D[vertexN];
+            Span<TextureData> t = stackalloc TextureData[vertexN];
 
             if (!isShaded) {
                 uint color = buffer[pointer++];
-                c[0] = color; //triangle 1 opaque color
-                c[1] = color; //triangle 2 opaque color
+                uint rgbColor = (uint)GetRgbColor(color);
+                c[0] = rgbColor; //triangle 1 opaque color
+                c[1] = rgbColor; //triangle 2 opaque color
             }
 
             primitive.semiTransparencyMode = transparencyMode;
@@ -453,9 +478,9 @@ public void GP0_RenderPolygon(Span<uint> buffer) {
             for (int i = 0; i < vertexN; i++) {
                 if (isShaded) c[i] = buffer[pointer++];
 
-                v[i].val = buffer[pointer++];
-                v[i].x = (short)(signed11bit((uint)v[i].x) + drawingXOffset);
-                v[i].y = (short)(signed11bit((uint)v[i].y) + drawingYOffset);
+                uint xy = buffer[pointer++];
+                v[i].x = (short)(signed11bit(xy & 0xFFFF) + drawingXOffset);
+                v[i].y = (short)(signed11bit(xy >> 16) + drawingYOffset);
 
                 if (isTextured) {
                     uint textureData = buffer[pointer++];
@@ -468,12 +493,13 @@ public void GP0_RenderPolygon(Span<uint> buffer) {
                     } else if (i == 1) {
                         uint texpage = textureData >> 16;
 
-                        primitive.depth = (int)(texpage >> 7) & 0x3;
-                        primitive.textureBase.x = (short)((texpage & 0xF) << 6);
-                        primitive.textureBase.y = (short)(((texpage >> 4) & 0x1) << 8);
-                        primitive.semiTransparencyMode = (int)((texpage >> 5) & 0x3);
+                        //SET GLOBAL GPU E1
+                        GP0_E1_SetDrawMode(texpage);
 
-                        forceSetE1(texpage);
+                        primitive.depth = textureDepth;
+                        primitive.textureBase.x = (short)(textureXBase << 6);
+                        primitive.textureBase.y = (short)(textureYBase << 8);
+                        primitive.semiTransparencyMode = transparencyMode;
                     }
                 }
             }
@@ -506,8 +532,8 @@ private void rasterizeTri(Point2D v0, Point2D v1, Point2D v2, TextureData t0, Te
             /*clip*/
             min.x = (short)Math.Max(minX, drawingAreaLeft);
             min.y = (short)Math.Max(minY, drawingAreaTop);
-            max.x = (short)Math.Min(maxX, drawingAreaRight);
-            max.y = (short)Math.Min(maxY, drawingAreaBottom);
+            max.x = (short)Math.Min(maxX, drawingAreaRight + 1);
+            max.y = (short)Math.Min(maxY, drawingAreaBottom + 1);
 
             int A01 = v0.y - v1.y, B01 = v1.x - v0.x;
             int A12 = v1.y - v2.y, B12 = v2.x - v1.x;
@@ -517,11 +543,9 @@ private void rasterizeTri(Point2D v0, Point2D v1, Point2D v2, TextureData t0, Te
             int bias1 = isTopLeft(v2, v0) ? 0 : -1;
             int bias2 = isTopLeft(v0, v1) ? 0 : -1;
 
-            int w0_row = orient2d(v1, v2, min);
-            int w1_row = orient2d(v2, v0, min);
-            int w2_row = orient2d(v0, v1, min);
-
-            int baseColor = GetRgbColor(c0);
+            int w0_row = orient2d(v1, v2, min) + bias0;
+            int w1_row = orient2d(v2, v0, min) + bias1;
+            int w2_row = orient2d(v0, v1, min) + bias2;
 
             // Rasterize
             for (int y = min.y; y < max.y; y++) {
@@ -532,7 +556,7 @@ private void rasterizeTri(Point2D v0, Point2D v1, Point2D v2, TextureData t0, Te
 
                 for (int x = min.x; x < max.x; x++) {
                     // If p is on or inside all edges, render pixel.
-                    if ((w0 + bias0 | w1 + bias1 | w2 + bias2) >= 0) {
+                    if ((w0 | w1 | w2) >= 0) {
                         //Adjustements per triangle instead of per pixel can be done at area level
                         //but it still does some little by 1 error apreciable on some textured quads
                         //I assume it could be handled recalculating AXX and BXX offsets but those maths are beyond my scope
@@ -549,13 +573,22 @@ private void rasterizeTri(Point2D v0, Point2D v1, Point2D v2, TextureData t0, Te
                         }
 
                         // reset default color of the triangle calculated outside the for as it gets overwriten as follows...
-                        int color = baseColor;
+                        int color = (int)c0;
 
-                        if (primitive.isShaded) color = getShadedColor(w0, w1, w2, c0, c1, c2, area);
+                        if (primitive.isShaded) {
+                            color0.val = c0;
+                            color1.val = c1;
+                            color2.val = c2;
+
+                            int r = interpolate(w0 - bias0, w1 - bias1, w2 - bias2, color0.r, color1.r, color2.r, area);
+                            int g = interpolate(w0 - bias0, w1 - bias1, w2 - bias2, color0.g, color1.g, color2.g, area);
+                            int b = interpolate(w0 - bias0, w1 - bias1, w2 - bias2, color0.b, color1.b, color2.b, area);
+                            color = r << 16 | g << 8 | b;
+                        }
 
                         if (primitive.isTextured) {
-                            int texelX = interpolateCoords(w0, w1, w2, t0.x, t1.x, t2.x, area);
-                            int texelY = interpolateCoords(w0, w1, w2, t0.y, t1.y, t2.y, area);
+                            int texelX = interpolate(w0 - bias0, w1 - bias1, w2 - bias2, t0.x, t1.x, t2.x, area);
+                            int texelY = interpolate(w0 - bias0, w1 - bias1, w2 - bias2, t0.y, t1.y, t2.y, area);
                             int texel = getTexel(maskTexelAxis(texelX, preMaskX, postMaskX), maskTexelAxis(texelY, preMaskY, postMaskY), primitive.clut, primitive.textureBase, primitive.depth);
                             if (texel == 0) {
                                 w0 += A12;
@@ -734,74 +767,61 @@ private void GP0_RenderRectangle(Span<uint> buffer) {
             short xo = (short)(vertex & 0xFFFF);
             short yo = (short)(vertex >> 16);
 
-            ushort palette = 0;
-            byte textureX = 0;
-            byte textureY = 0;
             if (isTextured) {
                 uint texture = buffer[pointer++];
-                palette = (ushort)((texture >> 16) & 0xFFFF);
-                textureX = (byte)(texture & 0xFF);
-                textureY = (byte)((texture >> 8) & 0xFF);
-            }
-
-            short width = 0;
-            short heigth = 0;
+                textureData.val = (ushort)texture;
 
-            switch ((opcode & 0x18) >> 3) {
-                case 0x0:
-                    uint hw = buffer[pointer++];
-                    width = (short)(hw & 0xFFFF);
-                    heigth = (short)(hw >> 16);
-                    break;
-                case 0x1:
-                    width = 1; heigth = 1;
-                    break;
-                case 0x2:
-                    width = 8; heigth = 8;
-                    break;
-                case 0x3:
-                    width = 16; heigth = 16;
-                    break;
+                ushort palette = (ushort)((texture >> 16) & 0xFFFF);
+                primitive.clut.x = (short)((palette & 0x3f) << 4);
+                primitive.clut.y = (short)((palette >> 6) & 0x1FF);
             }
 
-            int y = signed11bit((uint)(yo + drawingYOffset));
-            int x = signed11bit((uint)(xo + drawingXOffset));
-
-            v[0].x = (short)x;
-            v[0].y = (short)y;
+            primitive.depth = textureDepth;
+            primitive.textureBase.x = (short)(textureXBase << 6);
+            primitive.textureBase.y = (short)(textureYBase << 8);
+            primitive.semiTransparencyMode = transparencyMode;
 
-            v[3].x = (short)(x + width);
-            v[3].y = (short)(y + heigth);
+            short width;
+            short heigth;
 
-            t[0].x = textureX;
-            t[0].y = textureY;
+            uint type = (opcode & 0x18) >> 3;
 
-            uint texpage = getTexpageFromGPU();
+            if (type == 0) {
+                uint hw = buffer[pointer++];
+                width = (short)(hw & 0xFFFF);
+                heigth = (short)(hw >> 16);
+            } else if (type == 1) {
+                width = 1; heigth = 1;
+            } else if (type == 2) {
+                width = 8; heigth = 8;
+            } else {
+                width = 16; heigth = 16;
+            }
 
-            rasterizeRect(v, t[0], color, palette, texpage, primitive);
-        }
+            short y = signed11bit((uint)(yo + drawingYOffset));
+            short x = signed11bit((uint)(xo + drawingXOffset));
 
-        private void rasterizeRect(Point2D[] vec, TextureData t, uint c, ushort palette, uint texpage, Primitive primitive) {
-            int xOrigin = Math.Max(vec[0].x, drawingAreaLeft);
-            int yOrigin = Math.Max(vec[0].y, drawingAreaTop);
-            int width = Math.Min(vec[3].x, drawingAreaRight);
-            int height = Math.Min(vec[3].y, drawingAreaBottom);
+            Point2D origin;
+            origin.x = x;
+            origin.y = y;
 
-            int depth = (int)(texpage >> 7) & 0x3;
-            int semiTransparencyMode = (int)((texpage >> 5) & 0x3);
+            Point2D size;
+            size.x = (short)(x + width);
+            size.y = (short)(y + heigth);
 
-            Point2D clut = new Point2D();
-            clut.x = (short)((palette & 0x3f) << 4);
-            clut.y = (short)((palette >> 6) & 0x1FF);
+            rasterizeRect(origin, size, textureData, color, primitive);
+        }
 
-            Point2D textureBase = new Point2D();
-            textureBase.x = (short)((texpage & 0xF) << 6);
-            textureBase.y = (short)(((texpage >> 4) & 0x1) << 8);
+        private void rasterizeRect(Point2D origin, Point2D size, TextureData texture, uint bgrColor, Primitive primitive) {
+            int xOrigin = Math.Max(origin.x, drawingAreaLeft);
+            int yOrigin = Math.Max(origin.y, drawingAreaTop);
+            int width = Math.Min(size.x, drawingAreaRight + 1);
+            int height = Math.Min(size.y, drawingAreaBottom + 1);
 
-            int uOrigin = t.x + (xOrigin - vec[0].x);
-            int vOrigin = t.y + (yOrigin - vec[0].y);
+            int uOrigin = texture.x + (xOrigin - origin.x);
+            int vOrigin = texture.y + (yOrigin - origin.y);
 
-            int baseColor = GetRgbColor(c);
+            int baseColor = GetRgbColor(bgrColor);
 
             for (int y = yOrigin, v = vOrigin; y < height; y++, v++) {
                 for (int x = xOrigin, u = uOrigin; x < width; x++, u++) {
@@ -815,7 +835,7 @@ private void rasterizeRect(Point2D[] vec, TextureData t, uint c, ushort palette,
 
                     if (primitive.isTextured) {
                         //int texel = getTexel(u, v, clut, textureBase, depth);
-                        int texel = getTexel(maskTexelAxis(u, preMaskX, postMaskX), maskTexelAxis(v, preMaskY, postMaskY), clut, textureBase, depth);
+                        int texel = getTexel(maskTexelAxis(u, preMaskX, postMaskX),maskTexelAxis(v, preMaskY, postMaskY),primitive.clut, primitive.textureBase, primitive.depth);
                         if (texel == 0) {
                             continue;
                         }
@@ -834,7 +854,7 @@ private void rasterizeRect(Point2D[] vec, TextureData t, uint c, ushort palette,
                     }
 
                     if (primitive.isSemiTransparent && (!primitive.isTextured || (color & 0xFF00_0000) != 0)) {
-                        color = handleSemiTransp(x, y, color, semiTransparencyMode);
+                        color = handleSemiTransp(x, y, color, primitive.semiTransparencyMode);
                     }
 
                     color |= maskWhileDrawing << 24;
@@ -916,25 +936,9 @@ private void GP0_MemCopyRectVRAMtoCPU(Span<uint> buffer) {
             vramTransfer.origin_x = x;
             vramTransfer.origin_y = y;
             vramTransfer.halfWords = w * h;
-        }
-
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private int getShadedColor(int w0, int w1, int w2, uint c0, uint c1, uint c2, int area) {
-            color0.val = c0;
-            color1.val = c1;
-            color2.val = c2;
-
-            int r = (color0.r * w0 + color1.r * w1 + color2.r * w2) / area;
-            int g = (color0.g * w0 + color1.g * w1 + color2.g * w2) / area;
-            int b = (color0.b * w0 + color1.b * w1 + color2.b * w2) / area;
-
-            return (r << 16 | g << 8 | b);
-        }
 
-        [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private static int interpolateCoords(int w0, int w1, int w2, int t0, int t1, int t2, int area) {
-            //https://codeplea.com/triangular-interpolation
-            return (t0 * w0 + t1 * w1 + t2 * w2) / area;
+            isReadyToSendVRAMToCPU = true;
+            isReadyToReceiveDMABlock = false;
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -977,17 +981,13 @@ private static int orient2d(Point2D a, Point2D b, Point2D c) {
             return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x);
         }
 
-        private void forceSetE1(uint texpage) {
-            textureXBase = (byte)(texpage & 0xF);
-            textureYBase = (byte)((texpage >> 4) & 0x1);
-            transparencyMode = (byte)((texpage >> 5) & 0x3);
-            textureDepth = (byte)((texpage >> 7) & 0x3);
-            isTextureDisabled = isTextureDisabledAllowed && ((texpage >> 11) & 0x1) != 0;
+        private void GP0_E1_SetDrawMode(uint val) {
+            uint bits = val & 0xFF_FFFF;
 
-            //Console.WriteLine("[GPU] [GP0] Force DrawMode ");
-        }
+            if (bits == drawModeBits) return;
+
+            drawModeBits = bits;
 
-        private void GP0_E1_SetDrawMode(uint val) {
             textureXBase = (byte)(val & 0xF);
             textureYBase = (byte)((val >> 4) & 0x1);
             transparencyMode = (byte)((val >> 5) & 0x3);
@@ -998,7 +998,7 @@ private void GP0_E1_SetDrawMode(uint val) {
             isTexturedRectangleXFlipped = ((val >> 12) & 0x1) != 0;
             isTexturedRectangleYFlipped = ((val >> 13) & 0x1) != 0;
 
-            //Console.WriteLine("[GPU] [GP0] DrawMode ");
+            //Console.WriteLine("[GPU] [GP0] DrawMode " + val.ToString("x8"));
         }
 
         private void GP0_E2_SetTextureWindow(uint val) {
@@ -1083,7 +1083,18 @@ private void GP1_00_ResetGPU() {
 
         private void GP1_03_DisplayEnable(uint value) => isDisplayDisabled = (value & 1) != 0;
 
-        private void GP1_04_DMADirection(uint value) => dmaDirection = (byte)(value & 0x3);
+        private void GP1_04_DMADirection(uint value) {
+            dmaDirection = (byte)(value & 0x3);
+
+            isDmaRequest = dmaDirection switch {
+                0 => false,
+                1 => isReadyToReceiveDMABlock,
+                2 => isReadyToReceiveDMABlock,
+                3 => isReadyToSendVRAMToCPU,
+                _ => false,
+            };
+        }
+
 
         private void GP1_05_DisplayVRAMStart(uint value) {
             displayVRAMXStart = (ushort)(value & 0x3FE);
@@ -1093,6 +1104,12 @@ private void GP1_05_DisplayVRAMStart(uint value) {
         }
 
         private void GP1_06_DisplayHorizontalRange(uint value) {
+            uint bits = value & 0xFF_FFFF;
+
+            if (bits == displayHorizontalRange) return;
+
+            displayHorizontalRange = bits;
+
             displayX1 = (ushort)(value & 0xFFF);
             displayX2 = (ushort)((value >> 12) & 0xFFF);
 
@@ -1100,6 +1117,12 @@ private void GP1_06_DisplayHorizontalRange(uint value) {
         }
 
         private void GP1_07_DisplayVerticalRange(uint value) {
+            uint bits = value & 0xFF_FFFF;
+
+            if (bits == displayVerticalRange) return;
+
+            displayVerticalRange = bits;
+
             displayY1 = (ushort)(value & 0x3FF);
             displayY2 = (ushort)((value >> 10) & 0x3FF);
 
@@ -1107,6 +1130,12 @@ private void GP1_07_DisplayVerticalRange(uint value) {
         }
 
         private void GP1_08_DisplayMode(uint value) {
+            uint bits = value & 0xFF_FFFF;
+
+            if (bits == displayModeBits) return;
+
+            displayModeBits = bits;
+
             horizontalResolution1 = (byte)(value & 0x3);
             isVerticalResolution480 = (value & 0x4) != 0;
             isPal = (value & 0x8) != 0;
@@ -1141,22 +1170,6 @@ private void GP1_GPUInfo(uint value) {
             }
         }
 
-        private uint getTexpageFromGPU() {
-            uint texpage = 0;
-
-            texpage |= (isTexturedRectangleYFlipped ? 1u : 0) << 13;
-            texpage |= (isTexturedRectangleXFlipped ? 1u : 0) << 12;
-            texpage |= (isTextureDisabled ? 1u : 0) << 11;
-            texpage |= (isDrawingToDisplayAllowed ? 1u : 0) << 10;
-            texpage |= (isDithered ? 1u : 0) << 9;
-            texpage |= (uint)(textureDepth << 7);
-            texpage |= (uint)(transparencyMode << 5);
-            texpage |= (uint)(textureYBase << 4);
-            texpage |= textureXBase;
-
-            return texpage;
-        }
-
         private int handleSemiTransp(int x, int y, int color, int semiTranspMode) {
             color0.val = (uint)vram.GetPixelRGB888(x, y); //back
             color1.val = (uint)color; //front
@@ -1219,19 +1232,14 @@ private int interpolate(uint c1, uint c2, float ratio) {
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private static short signed11bit(uint n) {
-            return (short)(((int)n << 21) >> 21);
+        private static int interpolate(int w0, int w1, int w2, int t0, int t1, int t2, int area) {
+            //https://codeplea.com/triangular-interpolation
+            return (t0 * w0 + t1 * w1 + t2 * w2) / area;
         }
 
-        //This needs to go away once a BGR bitmap is achieved
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        private static int color1555to8888(ushort val) {
-            byte m = (byte)(val >> 15);
-            byte r = (byte)((val & 0x1F) << 3);
-            byte g = (byte)(((val >> 5) & 0x1F) << 3);
-            byte b = (byte)(((val >> 10) & 0x1F) << 3);
-
-            return (m << 24 | r << 16 | g << 8 | b);
+        private static short signed11bit(uint n) {
+            return (short)(((int)n << 21) >> 21);
         }
 
         //This is only needed for the Direct GP0 commands as the command number needs to be
diff --git a/ProjectPSX/Devices/GPU/VRAM.cs b/ProjectPSX/Devices/GPU/VRAM.cs
index 6c22e1a..5ec4ebb 100644
--- a/ProjectPSX/Devices/GPU/VRAM.cs
+++ b/ProjectPSX/Devices/GPU/VRAM.cs
@@ -3,15 +3,13 @@
 
 namespace ProjectPSX {
     public class VRAM {
-        public int[] Bits { get; private set; }
-        public int Height;
-        public int Width;
+        public int[] Bits;
+        public const int Height = 512;
+        public const int Width = 1024;
 
-        protected GCHandle BitsHandle { get; private set; }
+        protected GCHandle BitsHandle;
 
-        public VRAM(int width, int height) {
-            Height = height;
-            Width = width;
+        public VRAM() {
             Bits = new int[Width * Height];
             BitsHandle = GCHandle.Alloc(Bits, GCHandleType.Pinned);
         }
@@ -19,19 +17,27 @@ public VRAM(int width, int height) {
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public void SetPixel(int x, int y, int color) {
             int index = x + (y * Width);
-            Bits[index] = color;
+
+            ref int r0 = ref MemoryMarshal.GetArrayDataReference(Bits);
+            Unsafe.Add(ref r0, (nint)index) = color;
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public int GetPixelRGB888(int x, int y) {
+        public ref int GetPixelRGB888(int x, int y) {
             int index = x + (y * Width);
-            return Bits[index];
+
+            ref int r0 = ref MemoryMarshal.GetArrayDataReference(Bits);
+            ref int ri = ref Unsafe.Add(ref r0, (nint)index);
+
+            return ref ri;
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public ushort GetPixelBGR555(int x, int y) {
             int index = x + (y * Width);
-            int color = Bits[index];
+
+            ref int r0 = ref MemoryMarshal.GetArrayDataReference(Bits);
+            ref int color = ref Unsafe.Add(ref r0, (nint)index);
 
             byte m = (byte)((color & 0xFF000000) >> 24);
             byte r = (byte)((color & 0x00FF0000) >> 16 + 3);
diff --git a/ProjectPSX/Devices/GPU/VRAM1555.cs b/ProjectPSX/Devices/GPU/VRAM1555.cs
index 4c28f2f..17f6c3e 100644
--- a/ProjectPSX/Devices/GPU/VRAM1555.cs
+++ b/ProjectPSX/Devices/GPU/VRAM1555.cs
@@ -3,15 +3,13 @@
 
 namespace ProjectPSX {
     public class VRAM1555 {
-        public ushort[] Bits { get; private set; }
-        public int Height;
-        public int Width;
+        public ushort[] Bits;
+        public const int Height = 512;
+        public const int Width = 1024;
 
-        protected GCHandle BitsHandle { get; private set; }
+        protected GCHandle BitsHandle;
 
-        public VRAM1555(int width, int height) {
-            Height = height;
-            Width = width;
+        public VRAM1555() {
             Bits = new ushort[Width * Height];
             BitsHandle = GCHandle.Alloc(Bits, GCHandleType.Pinned);
         }
@@ -19,13 +17,19 @@ public VRAM1555(int width, int height) {
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
         public void SetPixel(int x, int y, ushort color) {
             int index = x + (y * Width);
-            Bits[index] = color;
+
+            ref ushort r0 = ref MemoryMarshal.GetArrayDataReference(Bits);
+            Unsafe.Add(ref r0, (nint)index) = color;
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]
-        public ushort GetPixel(int x, int y) {
+        public ref ushort GetPixel(int x, int y) {
             int index = x + (y * Width);
-            return Bits[index];
+
+            ref ushort r0 = ref MemoryMarshal.GetArrayDataReference(Bits);
+            ref ushort ri = ref Unsafe.Add(ref r0, (nint)index);
+
+            return ref ri;
         }
 
     }
diff --git a/ProjectPSX/Devices/Input/Controller.cs b/ProjectPSX/Devices/Input/Controller.cs
index 8094873..4b979d0 100644
--- a/ProjectPSX/Devices/Input/Controller.cs
+++ b/ProjectPSX/Devices/Input/Controller.cs
@@ -3,7 +3,6 @@
 
 namespace ProjectPSX {
     public abstract class Controller {
-        protected IHostWindow window;
 
         protected Queue<byte> transferDataFifo = new Queue<byte>();
         protected ushort buttons = 0xFFFF;
diff --git a/ProjectPSX/Devices/Input/DigitalController.cs b/ProjectPSX/Devices/Input/DigitalController.cs
index 3387aad..a2182f2 100644
--- a/ProjectPSX/Devices/Input/DigitalController.cs
+++ b/ProjectPSX/Devices/Input/DigitalController.cs
@@ -1,5 +1,5 @@
 namespace ProjectPSX {
-    public class DigitalController : Controller {
+    public sealed class DigitalController : Controller {
 
         private const ushort CONTROLLER_TYPE = 0x5A41; //digital
 
diff --git a/ProjectPSX/Devices/MDEC.cs b/ProjectPSX/Devices/MDEC.cs
index e684a6f..6fc74ee 100644
--- a/ProjectPSX/Devices/MDEC.cs
+++ b/ProjectPSX/Devices/MDEC.cs
@@ -13,8 +13,8 @@ public class MDEC {
         private const int MACRO_BLOCK_DECODED_BYTES = 256 * 3;
 
         //Status Register
-        //private bool isDataOutFifoEmpty;
         private bool isDataInFifoFull;
+        private bool isDataOutFifoEmpty;
         private bool isCommandBusy;
         private bool isDataInRequested;
         private bool isDataOutRequested;
@@ -41,6 +41,8 @@ public class MDEC {
         private IMemoryOwner<byte> outBuffer = MemoryPool<byte>.Shared.Rent(0x30000); //wild guess while resumable dmas come...
         private int outBufferPos = 0;
 
+        private int pendingBytesToTransfer;
+
         public void write(uint addr, uint value) {
             uint register = addr & 0xF;
             if (register == 0) {
@@ -53,7 +55,8 @@ public void write(uint addr, uint value) {
         }
 
         public void writeMDEC0_Command(uint value) { //1F801820h - MDEC0 - MDEC Command/Parameter Register (W)
-            //Console.WriteLine("[MDEC] Write " + value.ToString("x8"));
+            isCommandBusy = true;
+
             if (remainingDataWords == 0) {
                 decodeCommand(value);
             } else {
@@ -61,18 +64,15 @@ public void writeMDEC0_Command(uint value) { //1F801820h - MDEC0 - MDEC Command/
                 inBuffer.Enqueue((ushort)(value >> 16));
 
                 remainingDataWords--;
-                //Console.WriteLine("[MDEC] remaining " + remainingDataWords);
                 if (command == decodeMacroBlocks) {
                     command();
                 }
             }
 
             if (remainingDataWords == 0) {
-                isCommandBusy = true;
-                yuvToRgbBlockPos = 0;
-                outBufferPos = 0;
                 if (command != decodeMacroBlocks) {
                     command();
+                    isCommandBusy = false;
                 }
             }
 
@@ -107,17 +107,19 @@ private void decodeMacroBlocks() {
 
                 currentBlock = 0;
 
+                blockPointer = 64;
+                q_scale = 0;
+                val = 0;
+                n = 0;
+
                 yuv_to_rgb(block[2], 0, 0);
                 yuv_to_rgb(block[3], 8, 0);
                 yuv_to_rgb(block[4], 0, 8);
                 yuv_to_rgb(block[5], 8, 8);
 
+                isDataOutFifoEmpty = false;
                 yuvToRgbBlockPos += MACRO_BLOCK_DECODED_BYTES;
-
-                blockPointer = 64;
-                q_scale = 0;
-                val = 0;
-                n = 0;
+                pendingBytesToTransfer += MACRO_BLOCK_DECODED_BYTES;
             }
         }
 
@@ -158,7 +160,7 @@ private void yuv_to_rgb(short[] Yblk, int xx, int yy) {
         private int val;
         private ushort n;
         public bool rl_decode_block(short[] blk, byte[] qt) {
-            if (blockPointer >= 64) { //Start of new block
+            if (blockPointer >= 63) { //Start of new block
                 for (int i = 0; i < blk.Length; i++) {
                     blk[i] = 0;
                 }
@@ -177,7 +179,7 @@ public bool rl_decode_block(short[] blk, byte[] qt) {
             }
 
 
-            while (blockPointer < blk.Length) {
+            while (blockPointer < 63) {
                 if (q_scale == 0) {
                     val = signed10bit(n & 0x3FF) * 2;
                 }
@@ -253,6 +255,11 @@ public void writeMDEC1_Control(uint value) { //1F801824h - MDEC1 - MDEC Control/
                 outBufferPos = 0;
                 currentBlock = 0;
                 remainingDataWords = 0;
+                pendingBytesToTransfer = 0;
+                yuvToRgbBlockPos = 0;
+
+                inBuffer.Clear();
+                //outBuffer.Memory.Span.Clear();
 
                 blockPointer = 64;
                 q_scale = 0;
@@ -262,20 +269,28 @@ public void writeMDEC1_Control(uint value) { //1F801824h - MDEC1 - MDEC Control/
                 command = null;
             }
 
-            isDataInRequested = ((value >> 30) & 0x1) == 1; //todo enable dma
+            isDataInRequested = ((value >> 30) & 0x1) == 1;
             isDataOutRequested = ((value >> 29) & 0x1) == 1;
-
-            //Console.WriteLine("[MDEC] dataInRequest " + isDataInRequested + " dataOutRequested " + isDataOutRequested);
         }
 
 
         public uint readMDEC0_Data() { //1F801820h.Read - MDEC Data/Response Register (R)
             if (dataOutputDepth == 2) { //2 24b
-                var span = outBuffer.Memory.Span.Slice(outBufferPos++ * 4, 4);
+                int size = 4;
+                var span = outBuffer.Memory.Span.Slice(outBufferPos, size);
+                outBufferPos += size;
+                pendingBytesToTransfer -= size;
+
+                handlePossibleDataOutEnd();
 
                 return Unsafe.ReadUnaligned<uint>(ref MemoryMarshal.GetReference(span));
             } else if (dataOutputDepth == 3) { //3 15b
-                var span = outBuffer.Memory.Span.Slice(outBufferPos++ * 6, 6);
+                int size = 6; // 6 bytes for 2 packed 15b
+                var span = outBuffer.Memory.Span.Slice(outBufferPos, size);
+                outBufferPos += size;
+                pendingBytesToTransfer -= size;
+
+                handlePossibleDataOutEnd();
 
                 ushort lo = (ushort)(bit15 << 15 | convert24to15bpp(span[0], span[1], span[2]));
                 ushort hi = (ushort)(bit15 << 15 | convert24to15bpp(span[3], span[4], span[5]));
@@ -287,13 +302,23 @@ public uint readMDEC0_Data() { //1F801820h.Read - MDEC Data/Response Register (R
 
         }
 
-        public Span<uint> processDmaLoad(int size) {
+        public Span<uint> processDmaLoad(int dmaSize) {
             if (dataOutputDepth == 2) { //2 24b
-                var byteSpan = outBuffer.Memory.Span.Slice(outBufferPos++ * 4 * size, 4 * size); //4 = RGBR, GBRG, BRGB...
+                int size = dmaSize * 4; // DMA Size in words to bytes extracting 4 bytes of packed RGB data
+                var byteSpan = outBuffer.Memory.Span.Slice(outBufferPos, size); //4 = RGBR, GBRG, BRGB...
+                outBufferPos += size;
+                pendingBytesToTransfer -= size;
+
+                handlePossibleDataOutEnd();
 
                 return MemoryMarshal.Cast<byte, uint>(byteSpan);
             } else if (dataOutputDepth == 3) { //3 15b
-                var byteSpan = outBuffer.Memory.Span.Slice(outBufferPos++ * 6 * size, 6 * size); //6 = RGB * 2 => b15|b15
+                int size = dmaSize * 6; // DMA Size in words to bytes as packed 15b data (RGB * 2 => b15|b15)
+                var byteSpan = outBuffer.Memory.Span.Slice(outBufferPos, size);
+                outBufferPos += size;
+                pendingBytesToTransfer -= size;
+
+                handlePossibleDataOutEnd();
 
                 for (int b24 = 0, b15 = 0; b24 < byteSpan.Length; b24 += 3, b15 += 2) {
                     var r = byteSpan[b24 + 0] >> 3;
@@ -308,11 +333,11 @@ public Span<uint> processDmaLoad(int size) {
                     byteSpan[b15 + 1] = hi;
                 }
 
-                var dma = MemoryMarshal.Cast<byte, uint>(byteSpan.Slice(0, 4 * size));
+                var dma = MemoryMarshal.Cast<byte, uint>(byteSpan.Slice(0, 4 * dmaSize));
 
                 return dma;
             } else { //unsupported mode allocate and return garbage so can be seen
-                uint[] garbage = new uint[size];
+                uint[] garbage = new uint[dmaSize];
                 var dma = new Span<uint>(garbage);
                 dma.Fill(0xFF00FF00);
                 return dma;
@@ -330,25 +355,29 @@ public ushort convert24to15bpp(byte sr, byte sg, byte sb) {
         public uint readMDEC1_Status() {//1F801824h - MDEC1 - MDEC Status Register (R)
             uint status = 0;
 
-            status |= (isDataOutFifoEmpty() ? 1u : 0) << 31;
+            status |= (isDataOutFifoEmpty ? 1u : 0) << 31;
             status |= (isDataInFifoFull ? 1u : 0) << 30;
             status |= (isCommandBusy ? 1u : 0) << 29;
-            status |= (isDataInRequested ? 1u : 0) << 28;
-            status |= (isDataOutRequested ? 1u : 0) << 27;
+            status |= (isDataInRequested ? 1u : 0) << 28; // this should be && "enough space in the inQueue"
+            status |= ((isDataOutRequested && pendingBytesToTransfer != 0) ? 1u : 0) << 27;
             status |= dataOutputDepth << 25;
             status |= (isSigned ? 1u : 0) << 24;
             status |= bit15 << 23;
             status |= (currentBlock + 4) % NUM_BLOCKS << 16;
             status |= (ushort)(remainingDataWords - 1);
             //Console.WriteLine("[MDEC] Load Status " + status.ToString("x8"));
-            //Console.ReadLine();
-
-            isCommandBusy = false;
 
             return status;
         }
 
-        private bool isDataOutFifoEmpty() => outBufferPos == 0; //Todo compare to inbufferpos when we handle full dma in
+        private void handlePossibleDataOutEnd() {
+            if (pendingBytesToTransfer <= 0 && remainingDataWords == 0) {
+                outBufferPos = 0;
+                yuvToRgbBlockPos = 0;
+                isCommandBusy = false;
+                isDataOutFifoEmpty = true;
+            }
+        }
 
         private static ReadOnlySpan<byte> zagzig => new byte[] {
              0,  1,  8, 16,  9,  2,  3, 10,
diff --git a/ProjectPSX/Devices/SPU/SPU.cs b/ProjectPSX/Devices/SPU/SPU.cs
index e26337e..2023912 100644
--- a/ProjectPSX/Devices/SPU/SPU.cs
+++ b/ProjectPSX/Devices/SPU/SPU.cs
@@ -1,5 +1,5 @@
 using System;
-using System.Collections.Generic;
+using System.Runtime.CompilerServices;
 using System.Runtime.InteropServices;
 using ProjectPSX.Devices.CdRom;
 using ProjectPSX.Devices.Spu;
@@ -8,11 +8,8 @@ namespace ProjectPSX.Devices {
     public class SPU {
 
         // Todo:
-        // Spu Enable/Disable (koff voices? Ints?)
         // lr sweep envelope
-        // reverb
         // clean up queue/list dequeues enqueues and casts
-        // ...
 
         private static short[] gaussTable = new short[] {
                 -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001, -0x001,
@@ -86,13 +83,13 @@ public class SPU {
 
         private Sector cdBuffer = new Sector(Sector.XA_BUFFER);
 
-        private byte[] ram = new byte[512 * 1024];
+        private unsafe byte* ram = (byte*)Marshal.AllocHGlobal(512 * 1024);
         private Voice[] voices = new Voice[24];
 
-        private ushort mainVolumeLeft;
-        private ushort mainVolumeRight;
-        private ushort reverbOutputLeft;
-        private ushort reverbOutputRight;
+        private short mainVolumeLeft;
+        private short mainVolumeRight;
+        private short reverbOutputVolumeLeft;
+        private short reverbOutputVolumeRight;
 
         private uint keyOn;
         private uint keyOff;
@@ -103,7 +100,8 @@ public class SPU {
 
         private ushort unknownA0;
 
-        private ushort ramReverbStartAddress;
+        private uint ramReverbStartAddress;
+        private uint ramReverbInternalAddress;
         private ushort ramIrqAddress;
         private ushort ramDataTransferAddress;
         private uint ramDataTransferAddressInternal;
@@ -122,6 +120,40 @@ public class SPU {
 
         private int captureBufferPos;
 
+        //Reverb Area
+        private uint dAPF1;   // Reverb APF Offset 1
+        private uint dAPF2;   // Reverb APF Offset 2
+        private short vIIR;   // Reverb Reflection Volume 1
+        private short vCOMB1; // Reverb Comb Volume 1
+        private short vCOMB2; // Reverb Comb Volume 2
+        private short vCOMB3; // Reverb Comb Volume 3
+        private short vCOMB4; // Reverb Comb Volume 4
+        private short vWALL;  // Reverb Reflection Volume 2
+        private short vAPF1;  // Reverb APF Volume 1
+        private short vAPF2;  // Reverb APF Volume 2
+        private uint mLSAME;  // Reverb Same Side Reflection Address 1 Left
+        private uint mRSAME;  // Reverb Same Side Reflection Address 1 Right
+        private uint mLCOMB1; // Reverb Comb Address 1 Left
+        private uint mLCOMB2; // Reverb Comb Address 2 Left
+        private uint mRCOMB1; // Reverb Comb Address 1 Right
+        private uint mRCOMB2; // Reverb Comb Address 2 Right
+        private uint dLSAME;  // Reverb Same Side Reflection Address 2 Left
+        private uint dRSAME;  // Reverb Same Side Reflection Address 2 Right
+        private uint mLDIFF;  // Reverb Different Side Reflection Address 1 Left
+        private uint mRDIFF;  // Reverb Different Side Reflection Address 1 Right
+        private uint mLCOMB3; // Reverb Comb Address 3 Left
+        private uint mRCOMB3; // Reverb Comb Address 3 Right
+        private uint mLCOMB4; // Reverb Comb Address 4 Left
+        private uint mRCOMB4; // Reverb Comb Address 4 Right
+        private uint dLDIFF;  // Reverb Different Side Reflection Address 2 Left
+        private uint dRDIFF;  // Reverb Different Side Reflection Address 2 Right
+        private uint mLAPF1;  // Reverb APF Address 1 Left
+        private uint mRAPF1;  // Reverb APF Address 1 Right
+        private uint mLAPF2;  // Reverb APF Address 2 Left
+        private uint mRAPF2;  // Reverb APF Address 2 Right
+        private short vLIN;   // Reverb Input Volume Left
+        private short vRIN;   // Reverb Input Volume Right
+
         private struct Control {
             public ushort register;
             public bool spuEnabled => ((register >> 15) & 0x1) != 0;
@@ -144,11 +176,12 @@ private struct Status {
             public bool dataTransferBusyFlag => ((register >> 10) & 0x1) != 0;
             public bool dataTransferDmaReadRequest => ((register >> 9) & 0x1) != 0;
             public bool dataTransferDmaWriteRequest => ((register >> 8) & 0x1) != 0;
-            //  7     Data Transfer DMA Read/Write Request ;seems to be same as SPUCNT.Bit5 todo
+            //  7     Data Transfer DMA Read/Write Request ;seems to be same as SPUCNT.Bit5
             public bool irq9Flag {
                 get { return ((register >> 6) & 0x1) != 0; }
                 set { register = value ? (ushort)(register | (1 << 6)) : (ushort)(register & ~(1 << 6)); }
             }
+            //  0..5     Mode same as SPUCNT 0..5
         }
         Status status;
 
@@ -183,19 +216,19 @@ internal void write(uint addr, ushort value) {
                     break;
 
                 case 0x1F801D80:
-                    mainVolumeLeft = value;
+                    mainVolumeLeft = (short)value;
                     break;
 
                 case 0x1F801D82:
-                    mainVolumeRight = value;
+                    mainVolumeRight = (short)value;
                     break;
 
                 case 0x1F801D84:
-                    reverbOutputLeft = value;
+                    reverbOutputVolumeLeft = (short)value;
                     break;
 
                 case 0x1F801D86:
-                    reverbOutputRight = value;
+                    reverbOutputVolumeRight = (short)value;
                     break;
 
                 case 0x1F801D88:
@@ -252,7 +285,8 @@ internal void write(uint addr, ushort value) {
                     break;
 
                 case 0x1F801DA2:
-                    ramReverbStartAddress = value;
+                    ramReverbStartAddress = (uint)(value << 3);
+                    ramReverbInternalAddress = (uint)(value << 3);
                     break;
 
                 case 0x1F801DA4:
@@ -267,20 +301,27 @@ internal void write(uint addr, ushort value) {
                 case 0x1F801DA8:
                     //Console.WriteLine($"[SPU] Manual DMA Write {ramDataTransferAddressInternal:x8} {value:x4}");
                     ramDataTransferFifo = value;
-                    ram[ramDataTransferAddressInternal++] = (byte)value;
-                    ram[ramDataTransferAddressInternal++] = (byte)(value >> 8);
+                    writeRam(ramDataTransferAddressInternal, value);
+                    ramDataTransferAddressInternal += 2;
                     break;
 
                 case 0x1F801DAA:
                     control.register = value;
 
+                    if (!control.spuEnabled) {
+                        foreach (Voice v in voices) {
+                            v.adsrPhase = Voice.Phase.Off;
+                            v.adsrVolume = 0;
+                        }
+                    }
+
                     //Irq Flag is reseted on ack
                     if (!control.irq9Enabled)
                         status.irq9Flag = false;
                     
-                    //Status lower 5 bits are the same as control
-                    status.register &= 0xFFE0;
-                    status.register |= (ushort)(value & 0x1F);
+                    //Status 0..5 bits are the same as control
+                    status.register &= 0xFFC0;
+                    status.register |= (ushort)(value & 0x3F);
                     break;
 
                 case 0x1F801DAC:
@@ -322,6 +363,140 @@ internal void write(uint addr, ushort value) {
                 case 0x1F801DBE:
                     unknownBC = (unknownBC & 0xFFFF) | (uint)(value << 16);
                     break;
+
+                // SPU Reverb Configuration Area
+                case 0x1F801DC0:
+                    dAPF1 = (uint)(value << 3);
+                    break;
+
+                case 0x1F801DC2:
+                    dAPF2 = (uint)(value << 3);
+                    break;
+
+                case 0x1F801DC4:
+                    vIIR = (short)value;
+                    break;
+
+                case 0x1F801DC6:
+                    vCOMB1 = (short)value;
+                    break;
+
+                case 0x1F801DC8:
+                    vCOMB2 = (short)value;
+                    break;
+
+                case 0x1F801DCA:
+                    vCOMB3 = (short)value;
+                    break;
+
+                case 0x1F801DCC:
+                    vCOMB4 = (short)value;
+                    break;
+
+                case 0x1F801DCE:
+                    vWALL = (short)value;
+                    break;
+
+                case 0x1F801DD0:
+                    vAPF1 = (short)value;
+                    break;
+
+                case 0x1F801DD2:
+                    vAPF2 = (short)value;
+                    break;
+
+                case 0x1F801DD4:
+                    mLSAME = (uint)(value << 3);
+                    break;
+
+                case 0x1F801DD6:
+                    mRSAME = (uint)(value << 3);
+                    break;
+
+                case 0x1F801DD8:
+                    mLCOMB1 = (uint)(value << 3);
+                    break;
+
+                case 0x1F801DDA:
+                    mRCOMB1 = (uint)(value << 3);
+                    break;
+
+                case 0x1F801DDC:
+                    mLCOMB2 = (uint)(value << 3);
+                    break;
+
+                case 0x1F801DDE:
+                    mRCOMB2 = (uint)(value << 3);
+                    break;
+
+                case 0x1F801DE0:
+                    dLSAME = (uint)(value << 3);
+                    break;
+
+                case 0x1F801DE2:
+                    dRSAME = (uint)(value << 3);
+                    break;
+
+                case 0x1F801DE4:
+                    mLDIFF = (uint)(value << 3);
+                    break;
+
+                case 0x1F801DE6:
+                    mRDIFF = (uint)(value << 3);
+                    break;
+
+                case 0x1F801DE8:
+                    mLCOMB3 = (uint)(value << 3);
+                    break;
+
+                case 0x1F801DEA:
+                    mRCOMB3 = (uint)(value << 3);
+                    break;
+
+                case 0x1F801DEC:
+                    mLCOMB4 = (uint)(value << 3);
+                    break;
+
+                case 0x1F801DEE:
+                    mRCOMB4 = (uint)(value << 3);
+                    break;
+
+                case 0x1F801DF0:
+                    dLDIFF = (uint)(value << 3);
+                    break;
+
+                case 0x1F801DF2:
+                    dRDIFF = (uint)(value << 3);
+                    break;
+
+                case 0x1F801DF4:
+                    mLAPF1 = (uint)(value << 3);
+                    break;
+
+                case 0x1F801DF6:
+                    mRAPF1 = (uint)(value << 3);
+                    break;
+
+                case 0x1F801DF8:
+                    mLAPF2 = (uint)(value << 3);
+                    break;
+
+                case 0x1F801DFA:
+                    mRAPF2 = (uint)(value << 3);
+                    break;
+
+                case 0x1F801DFC:
+                    vLIN = (short)value;
+                    break;
+
+                case 0x1F801DFE:
+                    vRIN = (short)value;
+                    break;
+
+                default:
+                    Console.WriteLine($"[SPU] Warning write:{addr:x8} value:{value:x8}");
+                    writeRam(addr, value);
+                    break;
             }
 
         }
@@ -332,29 +507,29 @@ internal ushort load(uint addr) {
 
                     uint index = ((addr & 0xFF0) >> 4) - 0xC0;
 
-                    switch (addr & 0xF) {
-                        case 0x0: return voices[index].volumeLeft.register;
-                        case 0x2: return voices[index].volumeRight.register;
-                        case 0x4: return voices[index].pitch;
-                        case 0x6: return voices[index].startAddress;
-                        case 0x8: return voices[index].adsr.lo;
-                        case 0xA: return voices[index].adsr.hi;
-                        case 0xC: return voices[index].adsrVolume;
-                        case 0xE: return voices[index].adpcmRepeatAddress;
-                    }
-                    return 0xFFFF;
+                    return (addr & 0xF) switch {
+                        0x0 => voices[index].volumeLeft.register,
+                        0x2 => voices[index].volumeRight.register,
+                        0x4 => voices[index].pitch,
+                        0x6 => voices[index].startAddress,
+                        0x8 => voices[index].adsr.lo,
+                        0xA => voices[index].adsr.hi,
+                        0xC => voices[index].adsrVolume,
+                        0xE => voices[index].adpcmRepeatAddress,
+                        _ => 0xFFFF,
+                    };
 
                 case 0x1F801D80:
-                    return mainVolumeLeft;
+                    return (ushort)mainVolumeLeft;
 
                 case 0x1F801D82:
-                    return mainVolumeRight;
+                    return (ushort)mainVolumeRight;
 
                 case 0x1F801D84:
-                    return reverbOutputLeft;
+                    return (ushort)reverbOutputVolumeLeft;
 
                 case 0x1F801D86:
-                    return reverbOutputRight;
+                    return (ushort)reverbOutputVolumeRight;
 
                 case 0x1F801D88:
                     return (ushort)keyOn;
@@ -396,7 +571,7 @@ internal ushort load(uint addr) {
                     return unknownA0;
 
                 case 0x1F801DA2:
-                    return ramReverbStartAddress;
+                    return (ushort)(ramReverbStartAddress >> 3);
 
                 case 0x1F801DA4:
                     return ramIrqAddress;
@@ -440,8 +615,105 @@ internal ushort load(uint addr) {
                 case 0x1F801DBE:
                     return (ushort)(unknownBC >> 16);
 
+                // SPU Reverb Configuration Area
+                case 0x1F801DC0:
+                    return (ushort)(dAPF1 >> 3);
+
+                case 0x1F801DC2:
+                    return (ushort)(dAPF2 >> 3);
+
+                case 0x1F801DC4:
+                    return (ushort)vIIR;
+
+                case 0x1F801DC6:
+                    return (ushort)vCOMB1;
+
+                case 0x1F801DC8:
+                    return (ushort)vCOMB2;
+
+                case 0x1F801DCA:
+                    return (ushort)vCOMB3;
+
+                case 0x1F801DCC:
+                    return (ushort)vCOMB4;
+
+                case 0x1F801DCE:
+                    return (ushort)vWALL;
+
+                case 0x1F801DD0:
+                    return (ushort)vAPF1;
+
+                case 0x1F801DD2:
+                    return (ushort)vAPF2;
+
+                case 0x1F801DD4:
+                    return (ushort)(mLSAME >> 3);
+
+                case 0x1F801DD6:
+                    return (ushort)(mRSAME >> 3);
+
+                case 0x1F801DD8:
+                    return (ushort)(mLCOMB1 >> 3);
+
+                case 0x1F801DDA:
+                    return (ushort)(mRCOMB1 >> 3);
+
+                case 0x1F801DDC:
+                    return (ushort)(mLCOMB2 >> 3);
+
+                case 0x1F801DDE:
+                    return (ushort)(mRCOMB2 >> 3);
+
+                case 0x1F801DE0:
+                    return (ushort)(dLSAME >> 3);
+
+                case 0x1F801DE2:
+                    return (ushort)(dRSAME >> 3);
+
+                case 0x1F801DE4:
+                    return (ushort)(mLDIFF >> 3);
+
+                case 0x1F801DE6:
+                    return (ushort)(mRDIFF >> 3);
+
+                case 0x1F801DE8:
+                    return (ushort)(mLCOMB3 >> 3);
+
+                case 0x1F801DEA:
+                    return (ushort)(mRCOMB3 >> 3);
+
+                case 0x1F801DEC:
+                    return (ushort)(mLCOMB4 >> 3);
+
+                case 0x1F801DEE:
+                    return (ushort)(mRCOMB4 >> 3);
+
+                case 0x1F801DF0:
+                    return (ushort)(dLDIFF >> 3);
+
+                case 0x1F801DF2:
+                    return (ushort)(dRDIFF >> 3);
+
+                case 0x1F801DF4:
+                    return (ushort)(mLAPF1 >> 3);
+
+                case 0x1F801DF6:
+                    return (ushort)(mRAPF1 >> 3);
+
+                case 0x1F801DF8:
+                    return (ushort)(mLAPF2 >> 3);
+
+                case 0x1F801DFA:
+                    return (ushort)(mRAPF2 >> 3);
+
+                case 0x1F801DFC:
+                    return (ushort)vLIN;
+
+                case 0x1F801DFE:
+                    return (ushort)vRIN;
+
                 default:
-                    return 0xFFFF;
+                    return (ushort)loadRam(addr);
             }
         }
 
@@ -450,7 +722,8 @@ internal void pushCdBufferSamples(byte[] decodedXaAdpcm) {
         }
 
         private int counter = 0;
-        private int CYCLES_PER_SAMPLE = 0x300; //33868800 / 44100hz
+        private const int CYCLES_PER_SAMPLE = 0x300; //33868800 / 44100hz
+        private int reverbCounter = 0;
         public bool tick(int cycles) {
             bool edgeTrigger = false;
             counter += cycles;
@@ -463,6 +736,9 @@ public bool tick(int cycles) {
             int sumLeft = 0;
             int sumRight = 0;
 
+            int sumLeftReverb = 0;
+            int sumRightReverb = 0;
+
             uint edgeKeyOn = keyOn;
             uint edgeKeyOff = keyOff;
             keyOn = 0;
@@ -509,6 +785,11 @@ public bool tick(int cycles) {
                 //Sum each voice sample
                 sumLeft += (sample * v.processVolume(v.volumeLeft)) >> 15;
                 sumRight += (sample * v.processVolume(v.volumeRight)) >> 15;
+
+                if((channelReverbMode & (0x1 << i)) != 0) {
+                    sumLeftReverb += (sample * v.processVolume(v.volumeLeft)) >> 15;
+                    sumRightReverb += (sample * v.processVolume(v.volumeRight)) >> 15;
+                }
             }
 
             if (!control.spuUnmuted) { //todo merge this on the for voice loop
@@ -520,19 +801,38 @@ public bool tick(int cycles) {
             //Merge in CD audio (CDDA or XA)
             short cdL = 0;
             short cdR = 0;
-            if(control.cdAudioEnabled && cdBuffer.hasSamples()) { //Be sure theres something on the queue...
-                //todo refactor the byte/short queues and casts
+
+            // CD Audio samples are always "consumed" even if cdAudio is disabled and will end
+            // on the capture buffer area of ram, this is needed for VibRibbon to be playable
+            if(cdBuffer.hasSamples()) {
                 cdL = cdBuffer.readShort();
                 cdR = cdBuffer.readShort();
+            }
 
+            if(control.cdAudioEnabled) { //Be sure theres something on the queue...
                 //Apply Spu Cd In (CDDA/XA) Volume
                 cdL = (short)((cdL * cdVolumeLeft) >> 15);
                 cdR = (short)((cdR * cdVolumeRight) >> 15);
 
                 sumLeft += cdL;
                 sumRight += cdR;
+
+                if(control.cdAudioReverb) {
+                    sumLeftReverb += cdL;
+                    sumRightReverb += cdR;
+                }
+            }
+
+            if (reverbCounter == 0) {
+                var (reverbL, reverbR) = processReverb(sumLeftReverb, sumRightReverb);
+
+                sumLeft += reverbL;
+                sumRight += reverbR;
             }
 
+            // reverb is on a 22050hz clock
+            reverbCounter = (reverbCounter + 1) & 0x1;
+
             //Write to capture buffers and check ram irq
             edgeTrigger |= handleCaptureBuffer(0 * 1024 + captureBufferPos, cdL);
             edgeTrigger |= handleCaptureBuffer(1 * 1024 + captureBufferPos, cdR);
@@ -541,8 +841,8 @@ public bool tick(int cycles) {
             captureBufferPos = (captureBufferPos + 2) & 0x3FF;
 
             //Clamp sum
-            sumLeft = (Math.Clamp(sumLeft, -0x8000, 0x7FFF) * mainVolumeLeft) >> 15;
-            sumRight = (Math.Clamp(sumRight, -0x8000, 0x7FFF) * mainVolumeRight) >> 15;
+            sumLeft = (Math.Clamp(sumLeft, -0x8000, 0x7FFF) * (mainVolumeLeft << 1)) >> 15;
+            sumRight = (Math.Clamp(sumRight, -0x8000, 0x7FFF) * (mainVolumeRight << 1)) >> 15;
 
             //Add to samples bytes to output array
             spuOutput[spuOutputPointer++] = (byte)sumLeft;
@@ -555,16 +855,14 @@ public bool tick(int cycles) {
                 spuOutputPointer = 0;
             }
 
-            if (control.spuEnabled && control.irq9Enabled && edgeTrigger) {
+            if (control.irq9Enabled && edgeTrigger) {
                 status.irq9Flag = true;
             }
-            return control.spuEnabled && control.irq9Enabled && edgeTrigger; //todo move spuEnabled outside
+            return control.irq9Enabled && edgeTrigger;
         }
 
         private bool handleCaptureBuffer(int address, short sample) {
-            ram[address] = (byte)(sample & 0xFF);
-            ram[address + 1] = (byte)((sample >> 8) & 0xFF);
-
+            writeRam((uint)address, sample);
             return address >> 3 == ramIrqAddress;
         }
 
@@ -587,7 +885,7 @@ private void tickNoiseGenerator() {
             if (noiseTimer < 0) noiseTimer += 0x20000 >> noiseShift;
         }
 
-        public short sampleVoice(int v) {
+        public unsafe short sampleVoice(int v) {
             Voice voice = voices[v];
 
             //Decode samples if its empty / next block
@@ -651,40 +949,114 @@ public short sampleVoice(int v) {
             return (short)interpolated;
         }
 
+        public (short, short) processReverb(int lInput, int rInput) {
+            // Input from mixer
+            int Lin = (vLIN * lInput) >> 15;
+            int Rin = (vRIN * rInput) >> 15;
+
+            // Same side reflection LtoL and RtoR
+            short mlSame = saturateSample(Lin + ((loadReverb(dLSAME) * vWALL) >> 15) - ((loadReverb(mLSAME - 2) * vIIR) >> 15) + loadReverb(mLSAME - 2));
+            short mrSame = saturateSample(Rin + ((loadReverb(dRSAME) * vWALL) >> 15) - ((loadReverb(mRSAME - 2) * vIIR) >> 15) + loadReverb(mRSAME - 2));
+            writeReverb(mLSAME, mlSame);
+            writeReverb(mRSAME, mrSame);
+
+            // Different side reflection LtoR and RtoL
+            short mldiff = saturateSample(Lin + ((loadReverb(dRDIFF) * vWALL) >> 15) - ((loadReverb(mLDIFF - 2) * vIIR) >> 15) + loadReverb(mLDIFF - 2));
+            short mrdiff = saturateSample(Rin + ((loadReverb(dLDIFF) * vWALL) >> 15) - ((loadReverb(mRDIFF - 2) * vIIR) >> 15) + loadReverb(mRDIFF - 2));
+            writeReverb(mLDIFF, mldiff);
+            writeReverb(mRDIFF, mrdiff);
+
+            // Early echo (comb filter with input from buffer)
+            short l = saturateSample((vCOMB1 * loadReverb(mLCOMB1) >> 15) + (vCOMB2 * loadReverb(mLCOMB2) >> 15) + (vCOMB3 * loadReverb(mLCOMB3) >> 15) + (vCOMB4 * loadReverb(mLCOMB4) >> 15));
+            short r = saturateSample((vCOMB1 * loadReverb(mRCOMB1) >> 15) + (vCOMB2 * loadReverb(mRCOMB2) >> 15) + (vCOMB3 * loadReverb(mRCOMB3) >> 15) + (vCOMB4 * loadReverb(mRCOMB4) >> 15));
+
+            // Late reverb APF1 (All pass filter 1 with input from COMB)
+            l = saturateSample(l - saturateSample((vAPF1 * loadReverb(mLAPF1 - dAPF1)) >> 15));
+            r = saturateSample(r - saturateSample((vAPF1 * loadReverb(mRAPF1 - dAPF1)) >> 15));
+
+            writeReverb(mLAPF1, l);
+            writeReverb(mRAPF1, r);
+            
+            l = saturateSample((l * vAPF1 >> 15) + loadReverb(mLAPF1 - dAPF1));
+            r = saturateSample((r * vAPF1 >> 15) + loadReverb(mRAPF1 - dAPF1));
+
+            // Late reverb APF2 (All pass filter 2 with input from APF1)
+            l = saturateSample(l - saturateSample((vAPF2 * loadReverb(mLAPF2 - dAPF2)) >> 15));
+            r = saturateSample(r - saturateSample((vAPF2 * loadReverb(mRAPF2 - dAPF2)) >> 15));
+            
+            writeReverb(mLAPF2, l);
+            writeReverb(mRAPF2, r);
+
+            l = saturateSample((l * vAPF2 >> 15) + loadReverb(mLAPF2 - dAPF2));
+            r = saturateSample((r * vAPF2 >> 15) + loadReverb(mRAPF2 - dAPF2));
+
+            // Output to mixer (output volume multiplied with input from APF2)
+            l = saturateSample(l * reverbOutputVolumeLeft >> 15);
+            r = saturateSample(r * reverbOutputVolumeRight >> 15);
+
+            // Saturate address
+            ramReverbInternalAddress = Math.Max(ramReverbStartAddress, (ramReverbInternalAddress + 2) & 0x7_FFFE);
+
+            return (l, r);
+        }
+
+        public short saturateSample(int sample) {
+            if(sample < -0x8000) {
+                return -0x8000;
+            }
+
+            if(sample > 0x7FFF) {
+                return 0x7FFF;
+            }
+
+            return (short)sample;
+        }
 
-        public Span<uint> processDmaLoad(int size) { //todo trigger interrupt
-            Span<byte> dma = ram.AsSpan().Slice((int)ramDataTransferAddressInternal, size);
 
-            //ramDataTransferAddressInternal and ramIrqAddress already are >> 3
+        public unsafe Span<uint> processDmaLoad(int size) { //todo trigger interrupt
+            Span<byte> dma = new Span<byte>(ram, 1024*512).Slice((int)ramDataTransferAddressInternal, size * 4);
+
+            //ramDataTransferAddressInternal already is >> 3 while ramIrqAddress is set as ushort
             //so check if it's in the size range and trigger int
-            if (ramIrqAddress > ramDataTransferAddressInternal && ramIrqAddress < ramDataTransferAddressInternal + size) {
-                interruptController.set(Interrupt.SPU);
+            uint ramIrqAddress32 = (uint)ramIrqAddress << 3;
+            if (ramIrqAddress32 > ramDataTransferAddressInternal && ramIrqAddress32 < (ramDataTransferAddressInternal + (size * 4))) {
+                if(control.irq9Enabled) {
+                    status.irq9Flag = true;
+                    interruptController.set(Interrupt.SPU);
+                }
             }
 
-            ramDataTransferAddressInternal = (uint)(ramDataTransferAddressInternal + size * 4);
+            ramDataTransferAddressInternal += (uint)(size * 4);
+            ramDataTransferAddressInternal &= 0x7FFFF;
 
             return MemoryMarshal.Cast<byte, uint>(dma);
         }
 
-        public void processDmaWrite(Span<uint> dma) { //todo trigger interrupt
+        public unsafe void processDmaWrite(Span<uint> dma) { //todo trigger interrupt
             //Tekken 3 and FF8 overflows SPU Ram
             int size = dma.Length * 4;
             int destAddress = (int)ramDataTransferAddressInternal + size - 1;
 
             Span<byte> dmaSpan = MemoryMarshal.Cast<uint, byte>(dma);
 
-            Span<byte> ramStartSpan = ram.AsSpan();
+            Span<byte> ramStartSpan = new Span<byte>(ram, 1024 * 512);
             Span<byte> ramDestSpan = ramStartSpan.Slice((int)ramDataTransferAddressInternal);
 
-            if (ramIrqAddress > ramDataTransferAddressInternal && ramIrqAddress < ramDataTransferAddressInternal + size) {
-                interruptController.set(Interrupt.SPU);
+            //ramDataTransferAddressInternal already is >> 3 while ramIrqAddress is set as ushort
+            //so check if it's in the size range and trigger int
+            uint ramIrqAddress32 = (uint)ramIrqAddress << 3;
+            if (ramIrqAddress32 > ramDataTransferAddressInternal && ramIrqAddress32 < (ramDataTransferAddressInternal + size)) {
+                if (control.irq9Enabled) {
+                    status.irq9Flag = true;
+                    interruptController.set(Interrupt.SPU);
+                }
             }
 
             if (destAddress <= 0x7FFFF) {
                 dmaSpan.CopyTo(ramDestSpan);
             } else {
                 int overflow = destAddress - 0x7FFFF;
-        
+
                 Span<byte> firstSlice = dmaSpan.Slice(0, dmaSpan.Length - overflow);
                 Span<byte> overflowSpan = dmaSpan.Slice(dmaSpan.Length - overflow);
         
@@ -692,7 +1064,33 @@ public void processDmaWrite(Span<uint> dma) { //todo trigger interrupt
                 overflowSpan.CopyTo(ramStartSpan);
             }
         
-            ramDataTransferAddressInternal = (uint)(ramDataTransferAddressInternal + size);
+            ramDataTransferAddressInternal = (uint)((ramDataTransferAddressInternal + size) & 0x7FFFF);
+        }
+
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
+        private unsafe void writeRam<T>(uint addr, T value) where T : unmanaged {
+            *(T*)(ram + addr) = value;
+        }
+
+        private unsafe short loadRam(uint addr) {
+            //Console.WriteLine($"loadSPURam from addr {(addr & 0x3_FFFF):x8}");
+            return *(short*)(ram + (addr & 0x7_FFFF));
+        }
+
+        private unsafe void writeReverb(uint addr, short value) {
+            if (!control.reverbMasterEnabled) return;
+
+            uint relative = (addr + ramReverbInternalAddress - ramReverbStartAddress) % (0x8_0000 - ramReverbStartAddress);
+            uint wrapped = (ramReverbStartAddress + relative) & 0x7_FFFE;
+
+            *(short*)(ram + wrapped) = value;
+        }
+
+        private unsafe short loadReverb(uint addr) {
+            uint relative = (addr + ramReverbInternalAddress - ramReverbStartAddress) % (0x8_0000 - ramReverbStartAddress);
+            uint wrapped = (ramReverbStartAddress + relative) & 0x7_FFFE;
+
+            return *(short*)(ram + wrapped);
         }
 
     }
diff --git a/ProjectPSX/Devices/SPU/Voice.cs b/ProjectPSX/Devices/SPU/Voice.cs
index 7d5107d..e07bef2 100644
--- a/ProjectPSX/Devices/SPU/Voice.cs
+++ b/ProjectPSX/Devices/SPU/Voice.cs
@@ -98,19 +98,22 @@ public enum Phase {
 
         public byte[] spuAdpcm = new byte[16];
         public short[] decodedSamples = new short[31]; //28 samples from current block + 3 to make room for interpolation
-        internal void decodeSamples(byte[] ram, ushort ramIrqAddress) {
+        internal unsafe void decodeSamples(byte* ram, ushort ramIrqAddress) {
             //save the last 3 samples from the last decoded block
             //this are needed for interpolation in case the voice.counter.currentSampleIndex is 0 1 or 2
             decodedSamples[2] = decodedSamples[decodedSamples.Length - 1];
             decodedSamples[1] = decodedSamples[decodedSamples.Length - 2];
             decodedSamples[0] = decodedSamples[decodedSamples.Length - 3];
 
-            Array.Copy(ram, currentAddress * 8, spuAdpcm, 0, 16);
+            new Span<byte>(ram, 1024 * 512).Slice(currentAddress * 8, 16).CopyTo(spuAdpcm);
 
             //ramIrqAddress is >> 8 so we only need to check for currentAddress and + 1
-            readRamIrq |= currentAddress == ramIrqAddress || currentAddress + 1 == ramIrqAddress;           
+            readRamIrq |= currentAddress == ramIrqAddress || currentAddress + 1 == ramIrqAddress;
+
+            int headerShift = spuAdpcm[0] & 0x0F;
+            if (headerShift > 12) headerShift = 9;
+            int shift = 12 - headerShift;
 
-            int shift = 12 - (spuAdpcm[0] & 0x0F);
             int filter = (spuAdpcm[0] & 0x70) >> 4; //filter on SPU adpcm is 0-4 vs XA wich is 0-3
             if (filter > 4) filter = 4; //Crash Bandicoot sets this to 7 at the end of the first level and overflows the filter
 
@@ -144,7 +147,7 @@ internal short processVolume(Volume volume) {
             if (!volume.isSweepMode) {
                 return volume.fixedVolume;
             } else {
-                return 0; //todo handle sweep mode volume envelope
+                return 0x7FFF; //todo handle sweep mode volume envelope
             }
         }
 
diff --git a/ProjectPSX/Devices/TIMERS.cs b/ProjectPSX/Devices/TIMERS.cs
index dd7d583..9d4b96f 100644
--- a/ProjectPSX/Devices/TIMERS.cs
+++ b/ProjectPSX/Devices/TIMERS.cs
@@ -137,8 +137,7 @@ public bool tick(int cyclesTicked) { //todo this needs rework
                             counterValue += (ushort)cycles;
                             cycles = 0;
                         } else {
-                            counterValue += (ushort)(cycles / 2160);
-                            cycles %= 2160;
+                            if (!prevHblank && hblank) counterValue += 1;
                         }
 
                         return handleIrq();
diff --git a/ProjectPSX/ProjectPSX.csproj b/ProjectPSX/ProjectPSX.csproj
index ad133fa..c7918ea 100644
--- a/ProjectPSX/ProjectPSX.csproj
+++ b/ProjectPSX/ProjectPSX.csproj
@@ -2,7 +2,7 @@
 
   <PropertyGroup>
     <OutputType>Library</OutputType>
-    <TargetFramework>net5.0</TargetFramework>
+    <TargetFramework>net9.0</TargetFramework>
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
@@ -10,7 +10,7 @@
   </PropertyGroup>
 
   <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
-    <DefineConstants>TRACE</DefineConstants>
+      <DefineConstants>TRACE</DefineConstants>
     <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
   </PropertyGroup>
 
diff --git a/ProjectPSX/Util/Extension.cs b/ProjectPSX/Util/Extension.cs
index 723feca..8008b4c 100644
--- a/ProjectPSX/Util/Extension.cs
+++ b/ProjectPSX/Util/Extension.cs
@@ -1,5 +1,6 @@
 using System;
 using System.Collections.Generic;
+using static ProjectPSX.Devices.CDROM;
 
 namespace ProjectPSX.Devices {
     static class Extension {
@@ -7,5 +8,9 @@ public static void EnqueueRange<T>(this Queue<T> queue, Span<T> parameters) {
             foreach (T parameter in parameters)
                 queue.Enqueue(parameter);
         }
+
+        public static void EnqueueDelayedInterrupt(this Queue<DelayedInterrupt> queue, byte interrupt, int delay = 50_000) {
+            queue.Enqueue(new DelayedInterrupt(delay, interrupt));
+        }
     }
 }