sound, faster resampler for SMS FM
authorkub <derkub@gmail.com>
Thu, 23 Nov 2023 19:50:25 +0000 (20:50 +0100)
committerkub <derkub@gmail.com>
Thu, 23 Nov 2023 20:08:13 +0000 (21:08 +0100)
pico/pico_int.h
pico/sms.c
pico/sound/sound.c

index b8e788e..e74a4ac 100644 (file)
@@ -346,6 +346,7 @@ struct PicoMisc
 #define PMS_HW_LCD     0x2   // GG LCD\r
 #define PMS_HW_JAP     0x4   // japanese system\r
 #define PMS_HW_FM      0x8   // FM sound\r
+#define PMS_HW_FMUSED  0x10  // FM sound accessed\r
 \r
 #define PMS_MAP_AUTO   0\r
 #define PMS_MAP_SEGA   1\r
index 52a36b3..398a4da 100644 (file)
@@ -221,6 +221,7 @@ static void z80_sms_out(unsigned short a, unsigned char d)
       {
         case 0xf0:
           // FM reg port
+          Pico.m.hardware |= PMS_HW_FMUSED;
           YM2413_regWrite(d);
           break;
         case 0xf1:
index 9c7a928..c31c2cf 100644 (file)
@@ -27,8 +27,11 @@ s16 cdda_out_buffer[2*1152];
 // sn76496\r
 extern int *sn76496_regs;\r
 \r
+// FM resampling polyphase FIR\r
+static resampler_t *fmresampler;\r
+static int (*PsndFMUpdate)(s32 *buffer, int length, int stereo, int is_buf_empty);\r
+\r
 // ym2413\r
-#define YM2413_CLK 3579545\r
 static OPLL *opll = NULL;\r
 static OPLL old_opll;\r
 static struct {\r
@@ -36,6 +39,14 @@ static struct {
   uint8_t reg[sizeof(opll->reg)];\r
 } opll_buf;\r
 \r
+\r
+void YM2413_regWrite(unsigned data){\r
+  OPLL_writeIO(opll,0,data);\r
+}\r
+void YM2413_dataWrite(unsigned data){\r
+  OPLL_writeIO(opll,1,data);\r
+}\r
+\r
 PICO_INTERNAL void *YM2413GetRegs(void)\r
 {\r
   memcpy(opll_buf.reg, opll->reg, sizeof(opll->reg));\r
@@ -54,12 +65,9 @@ PICO_INTERNAL void YM2413UnpackState(void)
   opll->adr = opll_buf.adr;\r
 }\r
 \r
-static resampler_t *fmresampler;\r
-static int (*PsndFMUpdate)(s32 *buffer, int length, int stereo, int is_buf_empty);\r
-\r
 PICO_INTERNAL void PsndInit(void)\r
 {\r
-  opll = OPLL_new(YM2413_CLK, PicoIn.sndRate);\r
+  opll = OPLL_new(OSC_NTSC/15, OSC_NTSC/15/72);\r
   OPLL_setChipType(opll,0);\r
   OPLL_reset(opll);\r
 }\r
@@ -95,7 +103,25 @@ static int YM2612UpdateFIR(s32 *buffer, int length, int stereo, int is_buf_empty
   return ymchans;\r
 }\r
 \r
-static void YM2612_setup_FIR(int inrate, int outrate, int stereo)\r
+// resample SMS FM from its native 49716Hz/49262Hz with polyphase FIR filter\r
+static void YM2413Update(s32 *buffer, int length, int stereo)\r
+{\r
+  while (length-- > 0) {\r
+    int16_t getdata = OPLL_calc(opll) * 3;\r
+    *buffer++ = getdata;\r
+    buffer += stereo; // only left for stereo, to be mixed to right later\r
+  }\r
+}\r
+\r
+static int YM2413UpdateFIR(s32 *buffer, int length, int stereo, int is_buf_empty)\r
+{\r
+  if (!is_buf_empty) memset(buffer, 0, (length << stereo) * sizeof(*buffer));\r
+  resampler_update(fmresampler, buffer, length, YM2413Update);\r
+  return 0;\r
+}\r
+\r
+// FIR setup, looks for a close enough rational number matching the ratio\r
+static void YMFM_setup_FIR(int inrate, int outrate, int stereo)\r
 {\r
   int mindiff = 999;\r
   int diff, mul, div;\r
@@ -132,21 +158,31 @@ void PsndRerate(int preserve_state)
   void *state = NULL;\r
   int target_fps = Pico.m.pal ? 50 : 60;\r
   int target_lines = Pico.m.pal ? 313 : 262;\r
+  int sms_clock = Pico.m.pal ? OSC_PAL/15 : OSC_NTSC/15;\r
   int ym2612_clock = Pico.m.pal ? OSC_PAL/7 : OSC_NTSC/7;\r
   int ym2612_rate = YM2612_NATIVE_RATE();\r
+  int ym2413_rate = (sms_clock + 36) / 72;\r
 \r
   if (preserve_state) {\r
     state = malloc(0x204);\r
     if (state == NULL) return;\r
     ym2612_pack_state();\r
     memcpy(state, YM2612GetRegs(), 0x204);\r
+\r
+    if (opll != NULL)\r
+       memcpy(&old_opll, opll, sizeof(OPLL)); // remember old state\r
   }\r
-  if ((PicoIn.opt & POPT_EN_FM_FILTER) && ym2612_rate != PicoIn.sndRate) {\r
+  if (PicoIn.AHW & PAHW_SMS) {\r
+    OPLL_setRate(opll, ym2413_rate);\r
+    OPLL_reset(opll);\r
+    YMFM_setup_FIR(ym2413_rate, PicoIn.sndRate, 0);\r
+    PsndFMUpdate = YM2413UpdateFIR;\r
+  } else if ((PicoIn.opt & POPT_EN_FM_FILTER) && ym2612_rate != PicoIn.sndRate) {\r
     // polyphase FIR resampler, resampling directly from native to output rate\r
     YM2612Init(ym2612_clock, ym2612_rate,\r
         ((PicoIn.opt&POPT_DIS_FM_SSGEG) ? 0 : ST_SSG) |\r
         ((PicoIn.opt&POPT_EN_FM_DAC)    ? ST_DAC : 0));\r
-    YM2612_setup_FIR(ym2612_rate, PicoIn.sndRate, PicoIn.opt & POPT_EN_STEREO);\r
+    YMFM_setup_FIR(ym2612_rate, PicoIn.sndRate, PicoIn.opt & POPT_EN_STEREO);\r
     PsndFMUpdate = YM2612UpdateFIR;\r
   } else {\r
     YM2612Init(ym2612_clock, PicoIn.sndRate,\r
@@ -158,20 +194,17 @@ void PsndRerate(int preserve_state)
     // feed it back it's own registers, just like after loading state\r
     memcpy(YM2612GetRegs(), state, 0x204);\r
     ym2612_unpack_state();\r
+\r
+    if (opll != NULL) {\r
+      memcpy(&opll->adr, &old_opll.adr, sizeof(OPLL)-20); // restore old state\r
+      OPLL_forceRefresh(opll);\r
+    }\r
   }\r
 \r
   if (preserve_state) memcpy(state, sn76496_regs, 28*4); // remember old state\r
   SN76496_init(Pico.m.pal ? OSC_PAL/15 : OSC_NTSC/15, PicoIn.sndRate);\r
   if (preserve_state) memcpy(sn76496_regs, state, 28*4); // restore old state\r
 \r
-  if(opll != NULL){\r
-    if (preserve_state) memcpy(&old_opll, opll, sizeof(OPLL)); // remember old state\r
-    OPLL_setRate(opll, PicoIn.sndRate);\r
-    OPLL_reset(opll);\r
-    if (preserve_state) memcpy(&opll->adr, &old_opll.adr, sizeof(OPLL)-20); // restore old state\r
-    OPLL_forceRefresh(opll);\r
-  }\r
-\r
   if (state)\r
     free(state);\r
 \r
@@ -284,12 +317,12 @@ PICO_INTERNAL void PsndDoPSG(int cyc_to)
   SN76496Update(PicoIn.sndOut + pos, len, stereo);\r
 }\r
 \r
-#if 0\r
-PICO_INTERNAL void PsndDoYM2413(int cyc_to)\r
+PICO_INTERNAL void PsndDoSMSFM(int cyc_to)\r
 {\r
   int pos, len;\r
   int stereo = 0;\r
-  s16 *buf;\r
+  s32 *buf32 = PsndBuffer;\r
+  s16 *buf = PicoIn.sndOut;\r
 \r
   // nothing to do if sound is off\r
   if (!PicoIn.sndOut) return;\r
@@ -312,22 +345,15 @@ PICO_INTERNAL void PsndDoYM2413(int cyc_to)
     pos <<= 1;\r
   }\r
 \r
-  buf = PicoIn.sndOut + pos;\r
-  while (len-- > 0) {\r
-    int16_t getdata = OPLL_calc(opll) * 3;\r
-    *buf++ += getdata;\r
-    buf += stereo; // only left for stereo, to be mixed to right later\r
+  if (Pico.m.hardware & PMS_HW_FMUSED) {\r
+    buf += pos;\r
+    PsndFMUpdate(buf32, len, 0, 0);\r
+    while (len--) {\r
+      *buf++ += *buf32++;\r
+      buf += stereo;\r
+    }\r
   }\r
 }\r
-#endif\r
-\r
-void YM2413_regWrite(unsigned data){\r
-  OPLL_writeIO(opll,0,data);\r
-}\r
-void YM2413_dataWrite(unsigned data){\r
-  OPLL_writeIO(opll,1,data);\r
-}\r
-\r
 \r
 PICO_INTERNAL void PsndDoFM(int cyc_to)\r
 {\r
@@ -515,6 +541,7 @@ PICO_INTERNAL void PsndGetSamples(int y)
 \r
 static int PsndRenderMS(int offset, int length)\r
 {\r
+  s32 *buf32 = PsndBuffer;\r
   int stereo = (PicoIn.opt & 8) >> 3;\r
   int psglen = ((Pico.snd.psg_pos+0x80000) >> 20);\r
   int ym2413len = ((Pico.snd.ym2413_pos+0x80000) >> 20);\r
@@ -536,11 +563,11 @@ static int PsndRenderMS(int offset, int length)
     s16 *ym2413buf = PicoIn.sndOut + (ym2413len << stereo);\r
     Pico.snd.ym2413_pos += (length-ym2413len) << 20;\r
     int len = (length-ym2413len);\r
-    if (PicoIn.opt & POPT_EN_YM2413){\r
-      while (len-- > 0) {\r
-        int16_t getdata = OPLL_calc(opll) * 3;\r
-        *ym2413buf += getdata;\r
-        ym2413buf += 1<<stereo;\r
+    if (Pico.m.hardware & PMS_HW_FMUSED) {\r
+      PsndFMUpdate(buf32, len, 0, 0);\r
+      while (len--) {\r
+        *ym2413buf++ += *buf32++;\r
+        ym2413buf += stereo;\r
       }\r
     }\r
   }\r