sound, improve ym2612 timers implementation
authorkub <derkub@gmail.com>
Fri, 30 Jun 2023 19:12:53 +0000 (19:12 +0000)
committerkub <derkub@gmail.com>
Fri, 30 Jun 2023 19:12:53 +0000 (19:12 +0000)
pico/memory.c
pico/pico_int.h

index a9398b8..2f373cd 100644 (file)
@@ -1041,7 +1041,7 @@ static int get_scanline(int is_from_z80)
   if (is_from_z80) {\r
     // ugh... compute by dividing cycles since frame start by cycles per line\r
     // need some fractional resolution here, else there may be an extra line\r
-    int cycles_line = cycles_68k_to_z80(488 << 8); // cycles per line, as Q8\r
+    int cycles_line = cycles_68k_to_z80(488 << 8)+1; // cycles per line, as Q8\r
     int cycles_z80 = (z80_cyclesLeft<0 ? Pico.t.z80c_aim:z80_cyclesDone())<<8;\r
     int cycles = cycles_line * Pico.t.z80_scanline;\r
     // approximation by multiplying with inverse\r
@@ -1063,45 +1063,65 @@ static int get_scanline(int is_from_z80)
   return Pico.m.scanline;\r
 }\r
 \r
+#define ym2612_update_status(xcycles) \\r
+  if (xcycles >= Pico.t.timer_a_next_oflow) \\r
+    ym2612.OPN.ST.status |= (ym2612.OPN.ST.mode >> 2) & 1; \\r
+  if (xcycles >= Pico.t.timer_b_next_oflow) \\r
+    ym2612.OPN.ST.status |= (ym2612.OPN.ST.mode >> 2) & 2\r
+\r
 /* probably should not be in this file, but it's near related code here */\r
 void ym2612_sync_timers(int z80_cycles, int mode_old, int mode_new)\r
 {\r
   int xcycles = z80_cycles << 8;\r
 \r
-  /* check for overflows */\r
-  if ((mode_old & 4) && xcycles >= Pico.t.timer_a_next_oflow)\r
-    ym2612.OPN.ST.status |= 1;\r
-\r
-  if ((mode_old & 8) && xcycles >= Pico.t.timer_b_next_oflow)\r
-    ym2612.OPN.ST.status |= 2;\r
+  // update timer status\r
+  ym2612_update_status(xcycles);\r
 \r
-  /* update timer a */\r
+  // update timer a\r
   if (mode_old & 1)\r
-    while (xcycles > Pico.t.timer_a_next_oflow)\r
+    while (xcycles >= Pico.t.timer_a_next_oflow)\r
       Pico.t.timer_a_next_oflow += Pico.t.timer_a_step;\r
 \r
-  if ((mode_old ^ mode_new) & 1) // turning on/off\r
+  // turning on/off\r
+  if ((mode_old ^ mode_new) & 1)\r
   {\r
     if (mode_old & 1)\r
       Pico.t.timer_a_next_oflow = TIMER_NO_OFLOW;\r
-    else\r
-      Pico.t.timer_a_next_oflow = xcycles + Pico.t.timer_a_step;\r
+    else {\r
+      /* The internal tick of the YM2612 takes 144 clock cycles (with clock\r
+       * being OSC/7), or 67.2 z80 cycles. Timers are run once each tick.\r
+       * Starting a timer takes place at the next tick, so xcycles needs to be\r
+       * rounded up to that: t = next tick# = (xcycles / TICK_ZCYCLES) + 1\r
+       */\r
+      unsigned t = ((xcycles * (((1<<20)/TIMER_A_TICK_ZCYCLES)+1))>>20) + 1;\r
+      Pico.t.timer_a_next_oflow = t*TIMER_A_TICK_ZCYCLES + Pico.t.timer_a_step;\r
+    }\r
   }\r
+\r
   if (mode_new & 1)\r
     elprintf(EL_YMTIMER, "timer a upd to %i @ %i", Pico.t.timer_a_next_oflow>>8, z80_cycles);\r
 \r
-  /* update timer b */\r
+  // update timer b\r
   if (mode_old & 2)\r
-    while (xcycles > Pico.t.timer_b_next_oflow)\r
+    while (xcycles >= Pico.t.timer_b_next_oflow)\r
       Pico.t.timer_b_next_oflow += Pico.t.timer_b_step;\r
 \r
+  // turning on/off\r
   if ((mode_old ^ mode_new) & 2)\r
   {\r
     if (mode_old & 2)\r
       Pico.t.timer_b_next_oflow = TIMER_NO_OFLOW;\r
-    else\r
-      Pico.t.timer_b_next_oflow = xcycles + Pico.t.timer_b_step;\r
+    else {\r
+      /* timer b has a divider of 16 which runs in its own counter. It is not\r
+       * reset by loading timer b. The first run of timer b after loading is\r
+       * therefore shorter by up to 15 ticks.\r
+       */\r
+      unsigned t = ((xcycles * (((1<<20)/TIMER_A_TICK_ZCYCLES)+1))>>20) + 1;\r
+      int step = Pico.t.timer_b_step - TIMER_A_TICK_ZCYCLES*(t&15);\r
+      Pico.t.timer_b_next_oflow = t*TIMER_A_TICK_ZCYCLES + step;\r
+    }\r
   }\r
+\r
   if (mode_new & 2)\r
     elprintf(EL_YMTIMER, "timer b upd to %i @ %i", Pico.t.timer_b_next_oflow>>8, z80_cycles);\r
 }\r
@@ -1130,6 +1150,7 @@ static int ym2612_write_local(u32 a, u32 d, int is_from_z80)
 \r
       switch (addr)\r
       {\r
+        // NB, OD2 A/V sync HACK: lower timer step by 1/4 z80 cycle (=64 in Q8)\r
         case 0x24: // timer A High 8\r
         case 0x25: { // timer A Low 2\r
           int TAnew = (addr == 0x24) ? ((ym2612.OPN.ST.TA & 0x03)|(((int)d)<<2))\r
@@ -1142,7 +1163,7 @@ static int ym2612_write_local(u32 a, u32 d, int is_from_z80)
             ym2612.OPN.ST.TA = TAnew;\r
             //ym2612.OPN.ST.TAC = (1024-TAnew)*18;\r
             //ym2612.OPN.ST.TAT = 0;\r
-            Pico.t.timer_a_step = TIMER_A_TICK_ZCYCLES * (1024 - TAnew);\r
+            Pico.t.timer_a_step = TIMER_A_TICK_ZCYCLES * (1024 - TAnew) - 64;\r
             elprintf(EL_YMTIMER, "timer a set to %i, %i", 1024 - TAnew, Pico.t.timer_a_next_oflow>>8);\r
           }\r
           return 0;\r
@@ -1155,7 +1176,7 @@ static int ym2612_write_local(u32 a, u32 d, int is_from_z80)
             ym2612.OPN.ST.TB = d;\r
             //ym2612.OPN.ST.TBC = (256-d) * 288;\r
             //ym2612.OPN.ST.TBT  = 0;\r
-            Pico.t.timer_b_step = TIMER_B_TICK_ZCYCLES * (256 - d); // 262800\r
+            Pico.t.timer_b_step = TIMER_B_TICK_ZCYCLES * (256 - d) - 64;\r
             elprintf(EL_YMTIMER, "timer b set to %i, %i", 256 - d, Pico.t.timer_b_next_oflow>>8);\r
           }\r
           return 0;\r
@@ -1163,11 +1184,11 @@ static int ym2612_write_local(u32 a, u32 d, int is_from_z80)
           int old_mode = ym2612.OPN.ST.mode;\r
           int cycles = is_from_z80 ? z80_cyclesDone() : z80_cycles_from_68k();\r
 \r
-          ym2612.OPN.ST.mode = d;\r
-\r
           elprintf(EL_YMTIMER, "st mode %02x", d);\r
           ym2612_sync_timers(cycles, old_mode, d);\r
 \r
+          ym2612.OPN.ST.mode = d;\r
+\r
           /* reset Timer a flag */\r
           if (d & 0x10)\r
             ym2612.OPN.ST.status &= ~1;\r
@@ -1213,17 +1234,11 @@ static int ym2612_write_local(u32 a, u32 d, int is_from_z80)
 }\r
 \r
 \r
-#define ym2612_read_local() \\r
-  if (xcycles >= Pico.t.timer_a_next_oflow) \\r
-    ym2612.OPN.ST.status |= (ym2612.OPN.ST.mode >> 2) & 1; \\r
-  if (xcycles >= Pico.t.timer_b_next_oflow) \\r
-    ym2612.OPN.ST.status |= (ym2612.OPN.ST.mode >> 2) & 2\r
-\r
 static u32 ym2612_read_local_z80(void)\r
 {\r
   int xcycles = z80_cyclesDone() << 8;\r
 \r
-  ym2612_read_local();\r
+  ym2612_update_status(xcycles);\r
 \r
   elprintf(EL_YMTIMER, "timer z80 read %i, sched %i, %i @ %i|%i",\r
     ym2612.OPN.ST.status, Pico.t.timer_a_next_oflow >> 8,\r
@@ -1235,7 +1250,7 @@ static u32 ym2612_read_local_68k(void)
 {\r
   int xcycles = z80_cycles_from_68k() << 8;\r
 \r
-  ym2612_read_local();\r
+  ym2612_update_status(xcycles);\r
 \r
   elprintf(EL_YMTIMER, "timer 68k read %i, sched %i, %i @ %i|%i",\r
     ym2612.OPN.ST.status, Pico.t.timer_a_next_oflow >> 8,\r
index d8b7160..4944bb7 100644 (file)
@@ -873,16 +873,15 @@ void ym2612_unpack_state(void);
 \r
 #define TIMER_NO_OFLOW 0x70000000\r
 \r
-// NB ~0.2% timers speed up (1/8(A), 2(B) z80 cycles), HACK for A/V sync in OD2\r
 // tA =    72 * (1024 - TA) / M, with M = mclock/2\r
-#define TIMER_A_TICK_ZCYCLES (cycles_68k_to_z80(256LL*   72*2)-32) // Q8\r
+#define TIMER_A_TICK_ZCYCLES cycles_68k_to_z80(256LL*   72*2) // Q8\r
 // tB = 16*72 * ( 256 - TB) / M\r
-#define TIMER_B_TICK_ZCYCLES (cycles_68k_to_z80(256LL*16*72*2)-32*16) // Q8\r
+#define TIMER_B_TICK_ZCYCLES cycles_68k_to_z80(256LL*16*72*2) // Q8\r
 \r
 #define timers_cycle(ticks) \\r
-  if (Pico.t.timer_a_next_oflow > 0 && Pico.t.timer_a_next_oflow < TIMER_NO_OFLOW) \\r
+  if (Pico.t.timer_a_next_oflow < TIMER_NO_OFLOW) \\r
     Pico.t.timer_a_next_oflow -= ticks << 8; \\r
-  if (Pico.t.timer_b_next_oflow > 0 && Pico.t.timer_b_next_oflow < TIMER_NO_OFLOW) \\r
+  if (Pico.t.timer_b_next_oflow < TIMER_NO_OFLOW) \\r
     Pico.t.timer_b_next_oflow -= ticks << 8; \\r
   ym2612_sync_timers(0, ym2612.OPN.ST.mode, ym2612.OPN.ST.mode);\r
 \r