From f1b425e380dde381d4e7e0684b3f6a387866ccbf Mon Sep 17 00:00:00 2001 From: kub Date: Sat, 20 Jan 2024 17:48:16 +0100 Subject: [PATCH] core, complete rewrite of Pico adpcm driver --- pico/pico/memory.c | 73 ++++++----- pico/pico/pico.c | 86 ++++--------- pico/pico/xpcm.c | 303 ++++++++++++++++++++++++++++++++++----------- pico/pico_int.h | 10 +- pico/sek.c | 2 +- 5 files changed, 303 insertions(+), 171 deletions(-) diff --git a/pico/pico/memory.c b/pico/pico/memory.c index 14bf5d43..f294b037 100644 --- a/pico/pico/memory.c +++ b/pico/pico/memory.c @@ -1,6 +1,7 @@ /* * PicoDrive * (C) notaz, 2008 + * (C) irixxxx, 2024 * * This work is licensed under the terms of MAME license. * See COPYING file in the top-level directory. @@ -24,49 +25,43 @@ void dump(u16 w) } */ -static u32 PicoRead8_pico(u32 a) +static u32 PicoRead16_pico(u32 a) { u32 d = 0; - if ((a & 0xffffe0) == 0x800000) // Pico I/O + switch (a & 0x1e) { - switch (a & 0x1f) - { - case 0x01: d = PicoPicohw.r1; break; - case 0x03: - d = PicoIn.pad[0]&0x1f; // d-pad - d |= (PicoIn.pad[0]&0x20) << 2; // pen push -> C - d = ~d; - break; - - case 0x05: d = (PicoPicohw.pen_pos[0] >> 8); break; // what is MS bit for? Games read it.. - case 0x07: d = PicoPicohw.pen_pos[0] & 0xff; break; - case 0x09: d = (PicoPicohw.pen_pos[1] >> 8); break; - case 0x0b: d = PicoPicohw.pen_pos[1] & 0xff; break; - case 0x0d: d = (1 << (PicoPicohw.page & 7)) - 1; break; - case 0x12: d = PicoPicohw.fifo_bytes == 0 ? 0x80 : 0; break; // guess - default: - goto unhandled; - } - return d; + case 0x00: d = PicoPicohw.r1; break; + case 0x02: d = PicoIn.pad[0]&0x1f; // d-pad + d |= (PicoIn.pad[0]&0x20) << 2; // pen push -> C + d = ~d; + break; + case 0x04: d = (PicoPicohw.pen_pos[0] >> 8); break; // what is MS bit for? Games read it.. + case 0x06: d = PicoPicohw.pen_pos[0] & 0xff; break; + case 0x08: d = (PicoPicohw.pen_pos[1] >> 8); break; + case 0x0a: d = PicoPicohw.pen_pos[1] & 0xff; break; + case 0x0c: d = (1 << (PicoPicohw.page & 7)) - 1; break; + case 0x10: d = (PicoPicohw.fifo_bytes > 0x3f) ? 0 : (0x3f - PicoPicohw.fifo_bytes); break; + case 0x12: d = (PicoPicohw.fifo_bytes | !PicoPicoPCMBusyN()) ? 0 : 0x8000; + d |= PicoPicohw.r12 & 0x7fff; + break; + default: elprintf(EL_UIO, "m68k unmapped r16 [%06x] @%06x", a, SekPc); break; } - -unhandled: - elprintf(EL_UIO, "m68k unmapped r8 [%06x] @%06x", a, SekPc); return d; } -static u32 PicoRead16_pico(u32 a) +static u32 PicoRead8_pico(u32 a) { u32 d = 0; - if (a == 0x800010) - d = (PicoPicohw.fifo_bytes > 0x3f) ? 0 : (0x3f - PicoPicohw.fifo_bytes); - else if (a == 0x800012) - d = PicoPicohw.fifo_bytes == 0 ? 0x8000 : 0; // guess - else - elprintf(EL_UIO, "m68k unmapped r16 [%06x] @%06x", a, SekPc); + if ((a & 0xffffe0) == 0x800000) // Pico I/O + { + d = PicoRead16_pico(a); + if (!(a & 1)) d >>= 8; + return d & 0xff; + } + elprintf(EL_UIO, "m68k unmapped r8 [%06x] @%06x", a, SekPc); return d; } @@ -97,10 +92,20 @@ static void PicoWrite16_pico(u32 a, u32 d) } } else if (a == 0x800012) { - int r12_old = PicoPicohw.r12; PicoPicohw.r12 = d; - if (r12_old != d) - PicoReratePico(); + + PicoPicoPCMGain(8 - (d & 0x0007)); // volume + PicoPicoPCMFilter((d & 0x00c0) >> 6); // low pass filter + PicoPicoPCMIrqEn(d & 0x4000); // PCM IRQ enable + + if (d & 0x8000) { // PCM reset if 1 is written (dalmatians)? + PsndDoPCM(cycles_68k_to_z80(SekCyclesDone() - Pico.t.m68c_frame_start)); + PicoPicoPCMResetN(0); + PicoPicohw.xpcm_ptr = PicoPicohw.xpcm_buffer; + PicoPicohw.fifo_bytes = 0; + PicoPicoPCMResetN(1); + } + // other bits used in software: 0x3f00. } else elprintf(EL_UIO, "m68k unmapped w16 [%06x] %04x @%06x", a, d & 0xffff, SekPc); diff --git a/pico/pico/pico.c b/pico/pico/pico.c index 1266ce87..81d535ab 100644 --- a/pico/pico/pico.c +++ b/pico/pico/pico.c @@ -1,6 +1,7 @@ /* * PicoDrive * (C) notaz, 2008 + * (C) irixxxx, 2024 * * This work is licensed under the terms of MAME license. * See COPYING file in the top-level directory. @@ -12,72 +13,40 @@ // 0x2f8 - 0x3f3 picohw_state PicoPicohw; -static int prev_line_cnt_irq3 = 0, prev_line_cnt_irq5 = 0; -static int fifo_bytes_line = (16000<<16)/60/262/2; -static const int guessed_rates[] = { 8000, 14000, 12000, 14000, 16000, 18000, 16000, 16000 }; // ? - -#define PICOHW_FIFO_IRQ_THRESHOLD 12 PICO_INTERNAL void PicoReratePico(void) { - int rate = guessed_rates[PicoPicohw.r12 & 7]; - if (Pico.m.pal) - fifo_bytes_line = (rate<<16)/50/313/2; - else fifo_bytes_line = (rate<<16)/60/262/2; - PicoPicoPCMRerate(rate); + PicoPicoPCMRerate(); } static void PicoLinePico(void) { - PicoPicohw.line_counter++; - -#if 1 - if ((PicoPicohw.r12 & 0x4003) && PicoPicohw.line_counter - prev_line_cnt_irq3 > 200) { - prev_line_cnt_irq3 = PicoPicohw.line_counter; - // just a guess/hack, allows 101 Dalmantians to boot - elprintf(EL_PICOHW, "irq3"); - SekInterrupt(3); - return; - } -#endif - - if (PicoPicohw.fifo_bytes > 0) - { - PicoPicohw.fifo_line_bytes += fifo_bytes_line; - if (PicoPicohw.fifo_line_bytes >= (1<<16)) { - PicoPicohw.fifo_bytes -= PicoPicohw.fifo_line_bytes >> 16; - PicoPicohw.fifo_line_bytes &= 0xffff; - if (PicoPicohw.fifo_bytes < 0) - PicoPicohw.fifo_bytes = 0; - } - } - else - PicoPicohw.fifo_line_bytes = 0; - -#if 1 - if (PicoPicohw.fifo_bytes_prev >= PICOHW_FIFO_IRQ_THRESHOLD && - PicoPicohw.fifo_bytes < PICOHW_FIFO_IRQ_THRESHOLD) { - prev_line_cnt_irq3 = PicoPicohw.line_counter; // ? - elprintf(EL_PICOHW, "irq3, fb=%i", PicoPicohw.fifo_bytes); - SekInterrupt(3); - } - PicoPicohw.fifo_bytes_prev = PicoPicohw.fifo_bytes; -#endif - -#if 0 - if (PicoPicohw.line_counter - prev_line_cnt_irq5 > 512) { - prev_line_cnt_irq5 = PicoPicohw.line_counter; - elprintf(EL_PICOHW, "irq5"); - SekInterrupt(5); - } -#endif + // update sound so that irq for FIFO refill is generated + if ((PicoPicohw.fifo_bytes | !PicoPicoPCMBusyN()) && (Pico.m.scanline & 7) == 7) + PsndDoPCM(cycles_68k_to_z80(SekCyclesDone() - Pico.t.m68c_frame_start)); } static void PicoResetPico(void) { - PicoPicoPCMReset(); + PicoPicoPCMResetN(1); + PicoPicoPCMStartN(1); PicoPicohw.xpcm_ptr = PicoPicohw.xpcm_buffer; + PicoPicohw.fifo_bytes = 0; + PicoPicohw.r12 = 0; + + PicoPicoPCMIrqEn(0); + PicoPicoPCMFilter(0); + PicoPicoPCMGain(8); + + // map version register + PicoDetectRegion(); + switch (Pico.m.hardware >> 6) { + case 0: PicoPicohw.r1 = 0x40; break; // JP NTSC + case 1: PicoPicohw.r1 = 0x00; break; // JP PAL + case 2: PicoPicohw.r1 = 0x60; break; // US + case 3: PicoPicohw.r1 = 0x20; break; // EU + } } PICO_INTERNAL void PicoInitPico(void) @@ -90,15 +59,4 @@ PICO_INTERNAL void PicoInitPico(void) memset(&PicoPicohw, 0, sizeof(PicoPicohw)); PicoPicohw.pen_pos[0] = 0x03c + 320/2; PicoPicohw.pen_pos[1] = 0x200 + 240/2; - prev_line_cnt_irq3 = prev_line_cnt_irq5 = 0; - - // map version register - PicoDetectRegion(); - switch (Pico.m.hardware >> 6) { - case 0: PicoPicohw.r1 = 0x40; break; - case 1: PicoPicohw.r1 = 0x00; break; - case 2: PicoPicohw.r1 = 0x60; break; - case 3: PicoPicohw.r1 = 0x20; break; - } } - diff --git a/pico/pico/xpcm.c b/pico/pico/xpcm.c index 5d13a4b7..ef0f8701 100644 --- a/pico/pico/xpcm.c +++ b/pico/pico/xpcm.c @@ -1,122 +1,285 @@ /* * PicoDrive * (C) notaz, 2008 + * (C) irixxxx, 2024 * * This work is licensed under the terms of MAME license. * See COPYING file in the top-level directory. * - * The following ADPCM algorithm was stolen from MAME aica driver. - * I'm quite sure it's not the right one, but it's the - * best sounding of the ones that I tried. + * The following ADPCM algorithm was derived from MAME upd7759 driver. */ +#include #include "../pico_int.h" -#define ADPCMSHIFT 8 -#define ADFIX(f) (int) ((double)f * (double)(1< max ) val = max; \ - else if ( val < min ) val = min; \ -} +#define Limit(val, max, min) \ + (val > max ? max : val < min ? min : val) + +#define ADPCM_CLOCK (1280000/4) + +#define FIFO_IRQ_THRESHOLD 16 -static const int TableQuant[8] = +static const int step_deltas[16][16] = { - ADFIX(0.8984375), - ADFIX(0.8984375), - ADFIX(0.8984375), - ADFIX(0.8984375), - ADFIX(1.19921875), - ADFIX(1.59765625), - ADFIX(2.0), - ADFIX(2.3984375) + { 0, 0, 1, 2, 3, 5, 7, 10, 0, 0, -1, -2, -3, -5, -7, -10 }, + { 0, 1, 2, 3, 4, 6, 8, 13, 0, -1, -2, -3, -4, -6, -8, -13 }, + { 0, 1, 2, 4, 5, 7, 10, 15, 0, -1, -2, -4, -5, -7, -10, -15 }, + { 0, 1, 3, 4, 6, 9, 13, 19, 0, -1, -3, -4, -6, -9, -13, -19 }, + { 0, 2, 3, 5, 8, 11, 15, 23, 0, -2, -3, -5, -8, -11, -15, -23 }, + { 0, 2, 4, 7, 10, 14, 19, 29, 0, -2, -4, -7, -10, -14, -19, -29 }, + { 0, 3, 5, 8, 12, 16, 22, 33, 0, -3, -5, -8, -12, -16, -22, -33 }, + { 1, 4, 7, 10, 15, 20, 29, 43, -1, -4, -7, -10, -15, -20, -29, -43 }, + { 1, 4, 8, 13, 18, 25, 35, 53, -1, -4, -8, -13, -18, -25, -35, -53 }, + { 1, 6, 10, 16, 22, 31, 43, 64, -1, -6, -10, -16, -22, -31, -43, -64 }, + { 2, 7, 12, 19, 27, 37, 51, 76, -2, -7, -12, -19, -27, -37, -51, -76 }, + { 2, 9, 16, 24, 34, 46, 64, 96, -2, -9, -16, -24, -34, -46, -64, -96 }, + { 3, 11, 19, 29, 41, 57, 79, 117, -3, -11, -19, -29, -41, -57, -79, -117 }, + { 4, 13, 24, 36, 50, 69, 96, 143, -4, -13, -24, -36, -50, -69, -96, -143 }, + { 4, 16, 29, 44, 62, 85, 118, 175, -4, -16, -29, -44, -62, -85, -118, -175 }, + { 6, 20, 36, 54, 76, 104, 144, 214, -6, -20, -36, -54, -76, -104, -144, -214 }, }; -// changed using trial and error.. -//static const int quant_mul[16] = { 1, 3, 5, 7, 9, 11, 13, 15, -1, -3, -5, -7, -9, -11, -13, -15 }; -static const int quant_mul[16] = { 1, 3, 5, 7, 9, 11, 13, -1, -1, -3, -5, -7, -9, -11, -13, -15 }; +static const int state_deltas[16] = { -1, -1, 0, 0, 1, 2, 2, 3, -1, -1, 0, 0, 1, 2, 2, 3 }; + +static int sample = 0, state = 0; +static s32 stepsamples = (44100LL<<16)/ADPCM_CLOCK; +static s32 samplepos; +static int samplegain; + +static int startpin, irqenable; +static enum { RESET, START, HDR, COUNT } portstate = RESET; +static int rate, silence, nibbles, highlow, cache; + + +// SEGA Pico specific filtering + +#define QB 16 // mantissa bits +#define FP(f) (int)((f)*(1<= rate/2) { + memset(iir, 0, sizeof(*iir)); + return; + } + + // compute 2nd order butterworth filter coefficients + double a = 1 / tan(M_PI * cutoff / rate); + double axa = a*a; + double gain = 1/(1 + M_SQRT2*a + axa); + iir->gain = FP(gain); + iir->a[0] = FP(2 * (axa-1) * gain); + iir->a[1] = FP(-(1 - M_SQRT2*a + axa) * gain); +} + +static int PicoPicoFilterApply(struct iir2 *iir, int sample) +{ + if (!iir) + return sample; + + iir->x[0] = iir->x[1]; iir->x[1] = iir->x[2]; + iir->x[2] = sample * iir->gain; // Qb + iir->y[0] = iir->y[1]; iir->y[1] = iir->y[2]; + iir->y[2] = (iir->x[0] + 2*iir->x[1] + iir->x[2] + + iir->y[0]*iir->a[1] + iir->y[1]*iir->a[0]) >> QB; + return iir->y[2]; +} + + +// pin functions, N designating a negated pin + +PICO_INTERNAL void PicoPicoPCMResetN(int pin) +{ + if (!pin) { + portstate = RESET; + sample = samplepos = state = 0; + portstate = nibbles = silence = 0; + } else if (portstate == RESET) + portstate = START; +} + +PICO_INTERNAL void PicoPicoPCMStartN(int pin) +{ + startpin = pin; +} + +PICO_INTERNAL int PicoPicoPCMBusyN(void) +{ + return (portstate <= START); +} + + +// configuration functions + +PICO_INTERNAL void PicoPicoPCMRerate(void) +{ + // output samples per chip clock + stepsamples = ((u64)PicoIn.sndRate<<16)/ADPCM_CLOCK; -static int sample = 0, quant = 0, sgn = 0; -static int stepsamples = (44100<<10)/16000; + // compute filter coefficients, cutoff at half the ADPCM sample rate + PicoPicoFilterCoeff(&filters[1], 5000/2, PicoIn.sndRate); // 5-6 KHz + PicoPicoFilterCoeff(&filters[2], 8000/2, PicoIn.sndRate); // 8-12 KHz + PicoPicoFilterCoeff(&filters[3], 14000/2, PicoIn.sndRate); // 14-16 KHz +} +PICO_INTERNAL void PicoPicoPCMGain(int gain) +{ + samplegain = gain*4; +} + +PICO_INTERNAL void PicoPicoPCMFilter(int index) +{ + filter = filters+index; + if (filter->a[0] == 0) + filter = NULL; +} -PICO_INTERNAL void PicoPicoPCMReset(void) +PICO_INTERNAL void PicoPicoPCMIrqEn(int enable) { - sample = sgn = 0; - quant = 0x7f; - memset(PicoPicohw.xpcm_buffer, 0, sizeof(PicoPicohw.xpcm_buffer)); + irqenable = (enable ? 3 : 0); } -PICO_INTERNAL void PicoPicoPCMRerate(int xpcm_rate) +// TODO need an interupt pending mask? +PICO_INTERNAL int PicoPicoIrqAck(int level) { - stepsamples = (PicoIn.sndRate<<10)/xpcm_rate; + return (PicoPicohw.fifo_bytes < FIFO_IRQ_THRESHOLD && level != irqenable ? irqenable : 0); } -#define XSHIFT 6 -#define do_sample() \ +// adpcm operation + +#define apply_filter(v) PicoPicoFilterApply(filter, v) + +#define do_sample(nibble) \ { \ - int delta = quant * quant_mul[srcval] >> XSHIFT; \ - sample += delta - (delta >> 2); /* 3/4 */ \ - quant = (quant * TableQuant[srcval&7]) >> ADPCMSHIFT; \ - Limit(quant, 0x6000, 0x7f); \ - Limit(sample, 32767*3/4, -32768*3/4); \ + sample += step_deltas[state][nibble]; \ + state += state_deltas[nibble]; \ + state = (state < 0 ? 0 : state > 15 ? 15 : state); \ +} + +#define write_sample(buffer, length, stereo) \ +{ \ + while (samplepos > 0 && length > 0) { \ + int val = Limit(samplegain*sample, 16383, -16384); \ + samplepos -= 1<<16; \ + length --; \ + if (buffer) { \ + int out = apply_filter(val); \ + *buffer++ += out; \ + if (stereo) *buffer++ += out; \ + } \ + } \ } PICO_INTERNAL void PicoPicoPCMUpdate(short *buffer, int length, int stereo) { unsigned char *src = PicoPicohw.xpcm_buffer; unsigned char *lim = PicoPicohw.xpcm_ptr; - int srcval, needsamples = 0; + int srcval, irq = 0; - if (src == lim) goto end; + // leftover partial sample from last run + write_sample(buffer, length, stereo); - for (; length > 0 && src < lim; src++) + // loop over FIFO data, generating ADPCM samples + while (length > 0 && src < lim) { - srcval = *src >> 4; - do_sample(); + if (silence > 0) { + silence --; + sample = 0; + samplepos += stepsamples*256; - if (buffer) - for (needsamples += stepsamples; needsamples > (1<<10) && length > 0; needsamples -= (1<<10), length--) { - *buffer++ += sample; - if (stereo) { buffer[0] = buffer[-1]; buffer++; } - } + } else if (nibbles > 0) { + nibbles --; + + if (highlow) + cache = *src++; + else + cache <<= 4; + highlow = !highlow; - srcval = *src & 0xf; - do_sample(); + do_sample((cache & 0xf0) >> 4); + samplepos += stepsamples*rate; - if (buffer) - for (needsamples += stepsamples; needsamples > (1<<10) && length > 0; needsamples -= (1<<10), length--) { - *buffer++ += sample; - if (stereo) { buffer[0] = buffer[-1]; buffer++; } + } else switch (portstate) { + case RESET: + sample = 0; + samplepos += length<<16; + break; + case START: + if (startpin) { + if (*src) + portstate ++; + else // kill 0x00 bytes at stream start + src ++; + } else { + sample = 0; + samplepos += length<<16; + } + break; + case HDR: + srcval = *src++; + nibbles = silence = rate = 0; + highlow = 1; + if (srcval == 0) { // terminator + // HACK, kill leftover odd byte to avoid restart (Minna de Odorou) + if (lim-src == 1) src++; + portstate = START; + } else switch (srcval >> 6) { + case 0: silence = (srcval & 0x3f) + 1; break; + case 1: rate = (srcval & 0x3f) + 1; nibbles = 256; break; + case 2: rate = (srcval & 0x3f) + 1; portstate = COUNT; break; + case 3: break; + } + break; + case COUNT: + nibbles = *src++ + 1; portstate = HDR; + break; } - // lame normalization stuff, needed due to wrong adpcm algo - sgn += (sample < 0) ? -1 : 1; - if (sgn < -16 || sgn > 16) sample -= sample >> 5; + write_sample(buffer, length, stereo); } - if (src < lim) { + // buffer cleanup, generate irq if lowwater reached + if (src < lim && src != PicoPicohw.xpcm_buffer) { int di = lim - src; memmove(PicoPicohw.xpcm_buffer, src, di); PicoPicohw.xpcm_ptr = PicoPicohw.xpcm_buffer + di; elprintf(EL_PICOHW, "xpcm update: over %i", di); - // adjust fifo + + if (!irq && di < FIFO_IRQ_THRESHOLD) + irq = irqenable; PicoPicohw.fifo_bytes = di; - return; - } + } else if (src == lim && src != PicoPicohw.xpcm_buffer) { + PicoPicohw.xpcm_ptr = PicoPicohw.xpcm_buffer; + elprintf(EL_PICOHW, "xpcm update: under %i", length); - elprintf(EL_PICOHW, "xpcm update: under %i", length); - PicoPicohw.xpcm_ptr = PicoPicohw.xpcm_buffer; + if (!irq) + irq = irqenable; + PicoPicohw.fifo_bytes = 0; + } -end: - if (buffer && stereo) - // still must expand SN76496 to stereo - for (; length > 0; buffer+=2, length--) - buffer[1] = buffer[0]; + // TODO need an IRQ mask somewhere to avoid loosing one in cases of HINT/VINT + if (irq && SekIrqLevel != irq) { + elprintf(EL_PICOHW, "irq%d", irq); + if (SekIrqLevel < irq) + SekInterrupt(irq); + } - sample = sgn = 0; - quant = 0x7f; + if (buffer && length) { + // for underflow, use last sample to avoid clicks + int val = Limit(samplegain*sample, 16383, -16384); + while (length--) { + int out = apply_filter(val); + *buffer++ += out; + if (stereo) *buffer++ += out; + } + } } - diff --git a/pico/pico_int.h b/pico/pico_int.h index 52734c62..e9437c13 100644 --- a/pico/pico_int.h +++ b/pico/pico_int.h @@ -842,11 +842,17 @@ unsigned int pcd_pcm_read(unsigned int a); // pico/pico.c PICO_INTERNAL void PicoInitPico(void); PICO_INTERNAL void PicoReratePico(void); +PICO_INTERNAL int PicoPicoIrqAck(int level); // pico/xpcm.c PICO_INTERNAL void PicoPicoPCMUpdate(short *buffer, int length, int stereo); -PICO_INTERNAL void PicoPicoPCMReset(void); -PICO_INTERNAL void PicoPicoPCMRerate(int xpcm_rate); +PICO_INTERNAL void PicoPicoPCMResetN(int pin); +PICO_INTERNAL void PicoPicoPCMStartN(int pin); +PICO_INTERNAL int PicoPicoPCMBusyN(void); +PICO_INTERNAL void PicoPicoPCMGain(int gain); +PICO_INTERNAL void PicoPicoPCMFilter(int index); +PICO_INTERNAL void PicoPicoPCMIrqEn(int enable); +PICO_INTERNAL void PicoPicoPCMRerate(void); // sek.c PICO_INTERNAL void SekInit(void); diff --git a/pico/sek.c b/pico/sek.c index 236d03fe..68aeb173 100644 --- a/pico/sek.c +++ b/pico/sek.c @@ -41,7 +41,7 @@ static int do_ack(int level) else if (pv->pending_ints & pv->reg[0] & 0x10) pv->pending_ints &= ~0x10; - return 0; + return (PicoIn.AHW & PAHW_PICO ? PicoPicoIrqAck(level) : 0); } /* callbacks */ -- 2.39.2