From 0e1726c4575a82515d80b41df1707e449420496e Mon Sep 17 00:00:00 2001 From: kub Date: Tue, 23 Jan 2024 21:57:27 +0100 Subject: [PATCH] core, improve pico save/load (ADPCM state, page, etc) --- pico/pico.h | 7 +-- pico/pico/pico.c | 1 + pico/pico/xpcm.c | 155 ++++++++++++++++++++++++++++++----------------- pico/pico_int.h | 2 + pico/state.c | 49 ++++++++++----- 5 files changed, 136 insertions(+), 78 deletions(-) diff --git a/pico/pico.h b/pico/pico.h index f28e170d2..2db502d5b 100644 --- a/pico/pico.h +++ b/pico/pico.h @@ -150,17 +150,14 @@ void PicoGetInternal(pint_t which, pint_ret_t *ret); struct PicoEState; // pico.c -#define XPCM_BUFFER_SIZE (320+160) +#define XPCM_BUFFER_SIZE 64 typedef struct { int pen_pos[2]; int page; - // internal int fifo_bytes; // bytes in FIFO - int fifo_bytes_prev; - int fifo_line_bytes; // float part, << 16 - int line_counter; unsigned short r1, r12; + unsigned int reserved[3]; unsigned char xpcm_buffer[XPCM_BUFFER_SIZE+4]; unsigned char *xpcm_ptr; } picohw_state; diff --git a/pico/pico/pico.c b/pico/pico/pico.c index 81d535abe..d190a9402 100644 --- a/pico/pico/pico.c +++ b/pico/pico/pico.c @@ -18,6 +18,7 @@ picohw_state PicoPicohw; PICO_INTERNAL void PicoReratePico(void) { PicoPicoPCMRerate(); + PicoPicohw.xpcm_ptr = PicoPicohw.xpcm_buffer + PicoPicohw.fifo_bytes; } static void PicoLinePico(void) diff --git a/pico/pico/xpcm.c b/pico/pico/xpcm.c index ef0f8701e..91749bacd 100644 --- a/pico/pico/xpcm.c +++ b/pico/pico/xpcm.c @@ -42,14 +42,23 @@ static const int step_deltas[16][16] = 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; +s32 stepsamples = (44100LL<<16)/ADPCM_CLOCK; -static int startpin, irqenable; -static enum { RESET, START, HDR, COUNT } portstate = RESET; -static int rate, silence, nibbles, highlow, cache; +static struct xpcm_state { + s32 samplepos; // leftover duration for current sample wrt sndrate, Q16 + int sample; // current sample + short state; // ADPCM engine state + short samplegain; // programmable gain + + char startpin; // value on the !START pin + char irqenable; // IRQ enabled? + + char portstate; // data stream state + short silence; // silence blocks still to be played + short rate, nibbles; // ADPCM nibbles still to be played + unsigned char highlow, cache; // nibble selector and cache +} xpcm; +enum { RESET, START, HDR, COUNT }; // portstate // SEGA Pico specific filtering @@ -58,8 +67,8 @@ static int rate, silence, nibbles, highlow, cache; #define FP(f) (int)((f)*(1<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]; @@ -99,21 +109,21 @@ static int PicoPicoFilterApply(struct iir2 *iir, int sample) PICO_INTERNAL void PicoPicoPCMResetN(int pin) { if (!pin) { - portstate = RESET; - sample = samplepos = state = 0; - portstate = nibbles = silence = 0; - } else if (portstate == RESET) - portstate = START; + xpcm.portstate = RESET; + xpcm.sample = xpcm.samplepos = xpcm.state = 0; + xpcm.nibbles = xpcm.silence = 0; + } else if (xpcm.portstate == RESET) + xpcm.portstate = START; } PICO_INTERNAL void PicoPicoPCMStartN(int pin) { - startpin = pin; + xpcm.startpin = pin; } PICO_INTERNAL int PicoPicoPCMBusyN(void) { - return (portstate <= START); + return (xpcm.portstate <= START); } @@ -125,14 +135,14 @@ PICO_INTERNAL void PicoPicoPCMRerate(void) stepsamples = ((u64)PicoIn.sndRate<<16)/ADPCM_CLOCK; // 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 + PicoPicoFilterCoeff(&filters[1], 6000/2, PicoIn.sndRate); // 5-6 KHz + PicoPicoFilterCoeff(&filters[2], 9000/2, PicoIn.sndRate); // 8-12 KHz + PicoPicoFilterCoeff(&filters[3], 15000/2, PicoIn.sndRate); // 14-16 KHz } PICO_INTERNAL void PicoPicoPCMGain(int gain) { - samplegain = gain*4; + xpcm.samplegain = gain*4; } PICO_INTERNAL void PicoPicoPCMFilter(int index) @@ -144,13 +154,14 @@ PICO_INTERNAL void PicoPicoPCMFilter(int index) PICO_INTERNAL void PicoPicoPCMIrqEn(int enable) { - irqenable = (enable ? 3 : 0); + xpcm.irqenable = (enable ? 3 : 0); } // TODO need an interupt pending mask? PICO_INTERNAL int PicoPicoIrqAck(int level) { - return (PicoPicohw.fifo_bytes < FIFO_IRQ_THRESHOLD && level != irqenable ? irqenable : 0); + return (PicoPicohw.fifo_bytes < FIFO_IRQ_THRESHOLD && level != xpcm.irqenable + ? xpcm.irqenable : 0); } @@ -158,18 +169,20 @@ PICO_INTERNAL int PicoPicoIrqAck(int level) #define apply_filter(v) PicoPicoFilterApply(filter, v) +// compute next ADPCM sample #define do_sample(nibble) \ { \ - sample += step_deltas[state][nibble]; \ - state += state_deltas[nibble]; \ - state = (state < 0 ? 0 : state > 15 ? 15 : state); \ + xpcm.sample += step_deltas[xpcm.state][nibble]; \ + xpcm.state += state_deltas[nibble]; \ + xpcm.state = (xpcm.state < 0 ? 0 : xpcm.state > 15 ? 15 : xpcm.state); \ } +// writes samples with sndRate, nearest neighbour resampling, filtering #define write_sample(buffer, length, stereo) \ { \ - while (samplepos > 0 && length > 0) { \ - int val = Limit(samplegain*sample, 16383, -16384); \ - samplepos -= 1<<16; \ + while (xpcm.samplepos > 0 && length > 0) { \ + int val = Limit(xpcm.samplegain*xpcm.sample, 16383, -16384); \ + xpcm.samplepos -= 1<<16; \ length --; \ if (buffer) { \ int out = apply_filter(val); \ @@ -191,56 +204,56 @@ PICO_INTERNAL void PicoPicoPCMUpdate(short *buffer, int length, int stereo) // loop over FIFO data, generating ADPCM samples while (length > 0 && src < lim) { - if (silence > 0) { - silence --; - sample = 0; - samplepos += stepsamples*256; + if (xpcm.silence > 0) { + xpcm.silence --; + xpcm.sample = 0; + xpcm.samplepos += stepsamples*256; - } else if (nibbles > 0) { - nibbles --; + } else if (xpcm.nibbles > 0) { + xpcm.nibbles --; - if (highlow) - cache = *src++; + if (xpcm.highlow) + xpcm.cache = *src++; else - cache <<= 4; - highlow = !highlow; + xpcm.cache <<= 4; + xpcm.highlow = !xpcm.highlow; - do_sample((cache & 0xf0) >> 4); - samplepos += stepsamples*rate; + do_sample((xpcm.cache & 0xf0) >> 4); + xpcm.samplepos += stepsamples*xpcm.rate; - } else switch (portstate) { + } else switch (xpcm.portstate) { case RESET: - sample = 0; - samplepos += length<<16; + xpcm.sample = 0; + xpcm.samplepos += length<<16; break; case START: - if (startpin) { + if (xpcm.startpin) { if (*src) - portstate ++; + xpcm.portstate ++; else // kill 0x00 bytes at stream start src ++; } else { - sample = 0; - samplepos += length<<16; + xpcm.sample = 0; + xpcm.samplepos += length<<16; } break; case HDR: srcval = *src++; - nibbles = silence = rate = 0; - highlow = 1; + xpcm.nibbles = xpcm.silence = xpcm.rate = 0; + xpcm.highlow = 1; if (srcval == 0) { // terminator // HACK, kill leftover odd byte to avoid restart (Minna de Odorou) if (lim-src == 1) src++; - portstate = START; + xpcm.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 0: xpcm.silence = (srcval & 0x3f) + 1; break; + case 1: xpcm.rate = (srcval & 0x3f) + 1; xpcm.nibbles = 256; break; + case 2: xpcm.rate = (srcval & 0x3f) + 1; xpcm.portstate = COUNT; break; case 3: break; } break; case COUNT: - nibbles = *src++ + 1; portstate = HDR; + xpcm.nibbles = *src++ + 1; xpcm.portstate = HDR; break; } @@ -255,14 +268,14 @@ PICO_INTERNAL void PicoPicoPCMUpdate(short *buffer, int length, int stereo) elprintf(EL_PICOHW, "xpcm update: over %i", di); if (!irq && di < FIFO_IRQ_THRESHOLD) - irq = irqenable; + irq = xpcm.irqenable; PicoPicohw.fifo_bytes = di; } else if (src == lim && src != PicoPicohw.xpcm_buffer) { PicoPicohw.xpcm_ptr = PicoPicohw.xpcm_buffer; elprintf(EL_PICOHW, "xpcm update: under %i", length); if (!irq) - irq = irqenable; + irq = xpcm.irqenable; PicoPicohw.fifo_bytes = 0; } @@ -275,7 +288,7 @@ PICO_INTERNAL void PicoPicoPCMUpdate(short *buffer, int length, int stereo) if (buffer && length) { // for underflow, use last sample to avoid clicks - int val = Limit(samplegain*sample, 16383, -16384); + int val = Limit(xpcm.samplegain*xpcm.sample, 16383, -16384); while (length--) { int out = apply_filter(val); *buffer++ += out; @@ -283,3 +296,31 @@ PICO_INTERNAL void PicoPicoPCMUpdate(short *buffer, int length, int stereo) } } } + +PICO_INTERNAL int PicoPicoPCMSave(void *buffer, int length) +{ + u8 *bp = buffer; + + if (length < sizeof(xpcm) + sizeof(filters)) { + elprintf(EL_ANOMALY, "save buffer too small?"); + return 0; + } + + memcpy(bp, &xpcm, sizeof(xpcm)); + bp += sizeof(xpcm); + memcpy(bp, filters, sizeof(filters)); + bp += sizeof(filters); + return (bp - (u8*)buffer); +} + +PICO_INTERNAL void PicoPicoPCMLoad(void *buffer, int length) +{ + u8 *bp = buffer; + + if (length >= sizeof(xpcm)) + memcpy(&xpcm, bp, sizeof(xpcm)); + bp += sizeof(xpcm); + if (length >= sizeof(xpcm) + sizeof(filters)) + memcpy(filters, bp, sizeof(filters)); + bp += sizeof(filters); +} diff --git a/pico/pico_int.h b/pico/pico_int.h index e9437c137..86259cd75 100644 --- a/pico/pico_int.h +++ b/pico/pico_int.h @@ -853,6 +853,8 @@ PICO_INTERNAL void PicoPicoPCMGain(int gain); PICO_INTERNAL void PicoPicoPCMFilter(int index); PICO_INTERNAL void PicoPicoPCMIrqEn(int enable); PICO_INTERNAL void PicoPicoPCMRerate(void); +PICO_INTERNAL int PicoPicoPCMSave(void *buffer, int length); +PICO_INTERNAL void PicoPicoPCMLoad(void *buffer, int length); // sek.c PICO_INTERNAL void SekInit(void); diff --git a/pico/state.c b/pico/state.c index 9f91cd154..3b93765fa 100644 --- a/pico/state.c +++ b/pico/state.c @@ -133,6 +133,8 @@ typedef enum { CHUNK_CD_CDC, CHUNK_CD_CDD, CHUNK_YM2413, + CHUNK_PICO_PCM, + CHUNK_PICO, // CHUNK_DEFAULT_COUNT, CHUNK_CARTHW_ = CHUNK_CARTHW, // 64 (defined in PicoInt) @@ -181,9 +183,8 @@ static const char * const chunk_names[CHUNK_DEFAULT_COUNT] = { "SSH2 BIOS", // 35 "SDRAM", "DRAM", - "PAL", - "events", - "YM2413", //40 + "32X palette", + "32X events", }; static int write_chunk(chunk_name_e name, int len, void *data, void *file) @@ -230,6 +231,10 @@ static int state_save(void *file) int retval = -1; int len; + buf2 = malloc(CHUNK_LIMIT_W); + if (buf2 == NULL) + return -1; + areaWrite("PicoSEXT", 1, 8, file); areaWrite(&ver, 1, 4, file); @@ -243,9 +248,15 @@ static int state_save(void *file) CHECKED_WRITE_BUFF(CHUNK_RAM, PicoMem.ram); CHECKED_WRITE_BUFF(CHUNK_VSRAM, PicoMem.vsram); CHECKED_WRITE_BUFF(CHUNK_IOPORTS, PicoMem.ioports); - ym2612_pack_state(); - ym_regs = YM2612GetRegs(); - CHECKED_WRITE(CHUNK_FM, 0x200+4, ym_regs); + if (PicoIn.AHW & PAHW_PICO) { + len = PicoPicoPCMSave(buf2, CHUNK_LIMIT_W); + CHECKED_WRITE(CHUNK_PICO_PCM, len, buf2); + CHECKED_WRITE(CHUNK_PICO, sizeof(PicoPicohw), &PicoPicohw); + } else { + ym2612_pack_state(); + ym_regs = YM2612GetRegs(); + CHECKED_WRITE(CHUNK_FM, 0x200+4, ym_regs); + } if (!(PicoIn.opt & POPT_DIS_IDLE_DET)) SekInitIdleDet(); @@ -255,25 +266,23 @@ static int state_save(void *file) ym_regs = YM2413GetRegs(); CHECKED_WRITE(CHUNK_YM2413, 0x40+4, ym_regs); } + CHECKED_WRITE(CHUNK_PSG, 28*4, sn76496_regs); + + if (!(PicoIn.AHW & PAHW_PICO)) { + z80_pack(buff_z80); + CHECKED_WRITE_BUFF(CHUNK_Z80, buff_z80); + CHECKED_WRITE_BUFF(CHUNK_ZRAM, PicoMem.zram); + } CHECKED_WRITE_BUFF(CHUNK_VRAM, PicoMem.vram); - CHECKED_WRITE_BUFF(CHUNK_ZRAM, PicoMem.zram); CHECKED_WRITE_BUFF(CHUNK_CRAM, PicoMem.cram); - CHECKED_WRITE_BUFF(CHUNK_MISC, Pico.m); + CHECKED_WRITE_BUFF(CHUNK_MISC, Pico.m); PicoVideoSave(); CHECKED_WRITE_BUFF(CHUNK_VIDEO, Pico.video); - z80_pack(buff_z80); - CHECKED_WRITE_BUFF(CHUNK_Z80, buff_z80); - CHECKED_WRITE(CHUNK_PSG, 28*4, sn76496_regs); - if (PicoIn.AHW & PAHW_MCD) { - buf2 = malloc(CHUNK_LIMIT_W); - if (buf2 == NULL) - return -1; - memset(buff, 0, sizeof(buff)); SekPackCpu(buff, 1); if (Pico_mcd->s68k_regs[3] & 4) // 1M mode? @@ -464,6 +473,14 @@ static int state_load(void *file) ym2612_unpack_state(); break; + case CHUNK_PICO_PCM: + CHECKED_READ(len, buf); + PicoPicoPCMLoad(buf, len); + break; + case CHUNK_PICO: + CHECKED_READ_BUFF(PicoPicohw); + break; + case CHUNK_SMS: CHECKED_READ_BUFF(Pico.ms); break;