| 1 | /* |
| 2 | * PicoDrive |
| 3 | * (C) notaz, 2008 |
| 4 | * (C) irixxxx, 2024 |
| 5 | * |
| 6 | * This work is licensed under the terms of MAME license. |
| 7 | * See COPYING file in the top-level directory. |
| 8 | * |
| 9 | * The following ADPCM algorithm was derived from MAME upd7759 driver. |
| 10 | * |
| 11 | * The Pico is using this chip in slave mode. In this mode there are no ROM |
| 12 | * headers, but the first byte sent to the chip is used to start the ADPCM |
| 13 | * engine. This byte is discarded, i.e. not processed by the engine. |
| 14 | * |
| 15 | * Data is fed into the chip through a FIFO. An Interrupt is created if the |
| 16 | * FIFO has been drained below the low water mark. |
| 17 | * |
| 18 | * The Pico has 2 extensions to the standard upd7759 chip: |
| 19 | * - gain control, used to control the volume of the ADPCM output |
| 20 | * - filtering, used to remove (some of) the ADPCM compression artifacts |
| 21 | */ |
| 22 | |
| 23 | #include <math.h> |
| 24 | #include "../pico_int.h" |
| 25 | |
| 26 | /* limitter */ |
| 27 | #define Limit(val, max, min) \ |
| 28 | (val > max ? max : val < min ? min : val) |
| 29 | |
| 30 | #define ADPCM_CLOCK (1280000/4) |
| 31 | |
| 32 | #define FIFO_IRQ_THRESHOLD 16 |
| 33 | |
| 34 | static const int step_deltas[16][16] = |
| 35 | { |
| 36 | { 0, 0, 1, 2, 3, 5, 7, 10, 0, 0, -1, -2, -3, -5, -7, -10 }, |
| 37 | { 0, 1, 2, 3, 4, 6, 8, 13, 0, -1, -2, -3, -4, -6, -8, -13 }, |
| 38 | { 0, 1, 2, 4, 5, 7, 10, 15, 0, -1, -2, -4, -5, -7, -10, -15 }, |
| 39 | { 0, 1, 3, 4, 6, 9, 13, 19, 0, -1, -3, -4, -6, -9, -13, -19 }, |
| 40 | { 0, 2, 3, 5, 8, 11, 15, 23, 0, -2, -3, -5, -8, -11, -15, -23 }, |
| 41 | { 0, 2, 4, 7, 10, 14, 19, 29, 0, -2, -4, -7, -10, -14, -19, -29 }, |
| 42 | { 0, 3, 5, 8, 12, 16, 22, 33, 0, -3, -5, -8, -12, -16, -22, -33 }, |
| 43 | { 1, 4, 7, 10, 15, 20, 29, 43, -1, -4, -7, -10, -15, -20, -29, -43 }, |
| 44 | { 1, 4, 8, 13, 18, 25, 35, 53, -1, -4, -8, -13, -18, -25, -35, -53 }, |
| 45 | { 1, 6, 10, 16, 22, 31, 43, 64, -1, -6, -10, -16, -22, -31, -43, -64 }, |
| 46 | { 2, 7, 12, 19, 27, 37, 51, 76, -2, -7, -12, -19, -27, -37, -51, -76 }, |
| 47 | { 2, 9, 16, 24, 34, 46, 64, 96, -2, -9, -16, -24, -34, -46, -64, -96 }, |
| 48 | { 3, 11, 19, 29, 41, 57, 79, 117, -3, -11, -19, -29, -41, -57, -79, -117 }, |
| 49 | { 4, 13, 24, 36, 50, 69, 96, 143, -4, -13, -24, -36, -50, -69, -96, -143 }, |
| 50 | { 4, 16, 29, 44, 62, 85, 118, 175, -4, -16, -29, -44, -62, -85, -118, -175 }, |
| 51 | { 6, 20, 36, 54, 76, 104, 144, 214, -6, -20, -36, -54, -76, -104, -144, -214 }, |
| 52 | }; |
| 53 | |
| 54 | static const int state_deltas[16] = { -1, -1, 0, 0, 1, 2, 2, 3, -1, -1, 0, 0, 1, 2, 2, 3 }; |
| 55 | |
| 56 | static s32 stepsamples; // ratio as Q16, host sound rate / chip sample rate |
| 57 | |
| 58 | static struct xpcm_state { |
| 59 | s32 samplepos; // leftover duration for current sample wrt sndrate, Q16 |
| 60 | int sample; // current sample |
| 61 | short state; // ADPCM decoder state |
| 62 | short samplegain; // programmable gain |
| 63 | |
| 64 | char startpin; // value on the !START pin |
| 65 | char irqenable; // IRQ enabled? |
| 66 | |
| 67 | char portstate; // ADPCM stream state |
| 68 | short silence; // silence blocks still to be played |
| 69 | short rate, nibbles; // ADPCM nibbles still to be played |
| 70 | unsigned char highlow, cache; // nibble selector and cache |
| 71 | |
| 72 | char filter; // filter selector |
| 73 | s32 x[3], y[3]; // filter history |
| 74 | } xpcm; |
| 75 | enum { RESET, START, HDR, COUNT }; // portstate |
| 76 | |
| 77 | |
| 78 | // SEGA Pico specific filtering |
| 79 | |
| 80 | #define QB 16 // mantissa bits |
| 81 | #define FP(f) (int)((f)*(1<<QB)) // convert to fixpoint |
| 82 | |
| 83 | static struct iir2 { // 2nd order Butterworth IIR coefficients |
| 84 | s32 a[2], gain; // coefficients |
| 85 | } filters[4]; |
| 86 | static struct iir2 *filter; // currently selected filter |
| 87 | |
| 88 | |
| 89 | static void PicoPicoFilterCoeff(struct iir2 *iir, int cutoff, int rate) |
| 90 | { |
| 91 | // no filter if the cutoff is above the Nyquist frequency |
| 92 | if (cutoff >= rate/2) { |
| 93 | memset(iir, 0, sizeof(*iir)); |
| 94 | return; |
| 95 | } |
| 96 | |
| 97 | // compute 2nd order butterworth filter coefficients |
| 98 | double a = 1 / tan(M_PI * cutoff / rate); |
| 99 | double axa = a*a; |
| 100 | double gain = 1/(1 + M_SQRT2*a + axa); |
| 101 | iir->gain = FP(gain); |
| 102 | iir->a[0] = FP(2 * (axa-1) * gain); |
| 103 | iir->a[1] = FP(-(1 - M_SQRT2*a + axa) * gain); |
| 104 | } |
| 105 | |
| 106 | static int PicoPicoFilterApply(struct iir2 *iir, int sample) |
| 107 | { |
| 108 | if (!iir) |
| 109 | return sample; |
| 110 | |
| 111 | // NB Butterworth specific! |
| 112 | xpcm.x[0] = xpcm.x[1]; xpcm.x[1] = xpcm.x[2]; |
| 113 | xpcm.x[2] = sample * iir->gain; // Qb |
| 114 | xpcm.y[0] = xpcm.y[1]; xpcm.y[1] = xpcm.y[2]; |
| 115 | xpcm.y[2] = (xpcm.x[0] + 2*xpcm.x[1] + xpcm.x[2] |
| 116 | + xpcm.y[0]*iir->a[1] + xpcm.y[1]*iir->a[0]) >> QB; |
| 117 | return xpcm.y[2]; |
| 118 | } |
| 119 | |
| 120 | |
| 121 | // pin functions, N designating a negated pin |
| 122 | |
| 123 | PICO_INTERNAL void PicoPicoPCMResetN(int pin) |
| 124 | { |
| 125 | if (!pin) { |
| 126 | xpcm.portstate = RESET; |
| 127 | xpcm.sample = xpcm.samplepos = xpcm.state = 0; |
| 128 | xpcm.nibbles = xpcm.silence = 0; |
| 129 | } else if (xpcm.portstate == RESET) |
| 130 | xpcm.portstate = START; |
| 131 | } |
| 132 | |
| 133 | PICO_INTERNAL void PicoPicoPCMStartN(int pin) |
| 134 | { |
| 135 | xpcm.startpin = pin; |
| 136 | } |
| 137 | |
| 138 | PICO_INTERNAL int PicoPicoPCMBusyN(void) |
| 139 | { |
| 140 | return (xpcm.portstate <= START); |
| 141 | } |
| 142 | |
| 143 | |
| 144 | // configuration functions |
| 145 | |
| 146 | PICO_INTERNAL void PicoPicoPCMRerate(void) |
| 147 | { |
| 148 | s32 nextstep = ((u64)PicoIn.sndRate<<16)/ADPCM_CLOCK; |
| 149 | |
| 150 | // if the sound rate changes, erase filter history to avoid freak behaviour |
| 151 | if (stepsamples != nextstep) { |
| 152 | memset(xpcm.x, 0, sizeof(xpcm.x)); |
| 153 | memset(xpcm.y, 0, sizeof(xpcm.y)); |
| 154 | } |
| 155 | |
| 156 | // output samples per chip clock |
| 157 | stepsamples = nextstep; |
| 158 | |
| 159 | // compute filter coefficients, cutoff at half the ADPCM sample rate |
| 160 | PicoPicoFilterCoeff(&filters[1], 6000/2, PicoIn.sndRate); // 5-6 KHz |
| 161 | PicoPicoFilterCoeff(&filters[2], 9000/2, PicoIn.sndRate); // 8-12 KHz |
| 162 | PicoPicoFilterCoeff(&filters[3], 15000/2, PicoIn.sndRate); // 14-16 KHz |
| 163 | |
| 164 | PicoPicoPCMFilter(xpcm.filter); |
| 165 | } |
| 166 | |
| 167 | PICO_INTERNAL void PicoPicoPCMGain(int gain) |
| 168 | { |
| 169 | xpcm.samplegain = gain*4; |
| 170 | } |
| 171 | |
| 172 | PICO_INTERNAL void PicoPicoPCMFilter(int index) |
| 173 | { |
| 174 | // if the filter changes, erase the history to avoid freak behaviour |
| 175 | if (index != xpcm.filter) { |
| 176 | memset(xpcm.x, 0, sizeof(xpcm.x)); |
| 177 | memset(xpcm.y, 0, sizeof(xpcm.y)); |
| 178 | } |
| 179 | |
| 180 | xpcm.filter = index; |
| 181 | filter = filters+index; |
| 182 | if (filter->a[0] == 0) |
| 183 | filter = NULL; |
| 184 | } |
| 185 | |
| 186 | PICO_INTERNAL void PicoPicoPCMIrqEn(int enable) |
| 187 | { |
| 188 | xpcm.irqenable = (enable ? 3 : 0); |
| 189 | } |
| 190 | |
| 191 | // TODO need an interupt pending mask? |
| 192 | PICO_INTERNAL int PicoPicoIrqAck(int level) |
| 193 | { |
| 194 | return (PicoPicohw.fifo_bytes < FIFO_IRQ_THRESHOLD && level != xpcm.irqenable |
| 195 | ? xpcm.irqenable : 0); |
| 196 | } |
| 197 | |
| 198 | |
| 199 | // adpcm operation |
| 200 | |
| 201 | #define apply_filter(v) PicoPicoFilterApply(filter, v) |
| 202 | |
| 203 | // compute next ADPCM sample |
| 204 | #define do_sample(nibble) \ |
| 205 | { \ |
| 206 | xpcm.sample += step_deltas[xpcm.state][nibble]; \ |
| 207 | xpcm.state += state_deltas[nibble]; \ |
| 208 | xpcm.state = (xpcm.state < 0 ? 0 : xpcm.state > 15 ? 15 : xpcm.state); \ |
| 209 | } |
| 210 | |
| 211 | // writes samples with sndRate, nearest neighbour resampling, filtering |
| 212 | #define write_sample(buffer, length, stereo) \ |
| 213 | { \ |
| 214 | while (xpcm.samplepos > 0 && length > 0) { \ |
| 215 | int val = Limit(xpcm.samplegain*xpcm.sample, 16383, -16384); \ |
| 216 | xpcm.samplepos -= 1<<16; \ |
| 217 | length --; \ |
| 218 | if (buffer) { \ |
| 219 | int out = apply_filter(val); \ |
| 220 | *buffer++ += out; \ |
| 221 | if (stereo) *buffer++ += out; \ |
| 222 | } \ |
| 223 | } \ |
| 224 | } |
| 225 | |
| 226 | PICO_INTERNAL void PicoPicoPCMUpdate(short *buffer, int length, int stereo) |
| 227 | { |
| 228 | unsigned char *src = PicoPicohw.xpcm_buffer; |
| 229 | unsigned char *lim = PicoPicohw.xpcm_ptr; |
| 230 | int srcval, irq = 0; |
| 231 | |
| 232 | // leftover partial sample from last run |
| 233 | write_sample(buffer, length, stereo); |
| 234 | |
| 235 | // loop over FIFO data, generating ADPCM samples |
| 236 | while (length > 0 && src < lim) |
| 237 | { |
| 238 | // ADPCM state engine |
| 239 | if (xpcm.silence > 0) { // generate silence |
| 240 | xpcm.silence --; |
| 241 | xpcm.sample = 0; |
| 242 | xpcm.samplepos += stepsamples*256; |
| 243 | |
| 244 | } else if (xpcm.nibbles > 0) { // produce samples |
| 245 | xpcm.nibbles --; |
| 246 | |
| 247 | if (xpcm.highlow) |
| 248 | xpcm.cache = *src++; |
| 249 | else |
| 250 | xpcm.cache <<= 4; |
| 251 | xpcm.highlow = !xpcm.highlow; |
| 252 | |
| 253 | do_sample((xpcm.cache & 0xf0) >> 4); |
| 254 | xpcm.samplepos += stepsamples*xpcm.rate; |
| 255 | |
| 256 | } else switch (xpcm.portstate) { // handle stream headers |
| 257 | case RESET: |
| 258 | xpcm.sample = 0; |
| 259 | xpcm.samplepos += length<<16; |
| 260 | break; |
| 261 | case START: |
| 262 | if (xpcm.startpin) { |
| 263 | if (*src) |
| 264 | xpcm.portstate ++; |
| 265 | else // kill 0x00 bytes at stream start |
| 266 | src ++; |
| 267 | } else { |
| 268 | xpcm.sample = 0; |
| 269 | xpcm.samplepos += length<<16; |
| 270 | } |
| 271 | break; |
| 272 | case HDR: |
| 273 | srcval = *src++; |
| 274 | xpcm.nibbles = xpcm.silence = xpcm.rate = 0; |
| 275 | xpcm.highlow = 1; |
| 276 | if (srcval == 0) { // terminator |
| 277 | // HACK, kill leftover odd byte to avoid restart (Minna de Odorou) |
| 278 | if (lim-src == 1) src++; |
| 279 | xpcm.portstate = START; |
| 280 | } else switch (srcval >> 6) { |
| 281 | case 0: xpcm.silence = (srcval & 0x3f) + 1; break; |
| 282 | case 1: xpcm.rate = (srcval & 0x3f) + 1; xpcm.nibbles = 256; break; |
| 283 | case 2: xpcm.rate = (srcval & 0x3f) + 1; xpcm.portstate = COUNT; break; |
| 284 | case 3: break; |
| 285 | } |
| 286 | break; |
| 287 | case COUNT: |
| 288 | xpcm.nibbles = *src++ + 1; xpcm.portstate = HDR; |
| 289 | break; |
| 290 | } |
| 291 | |
| 292 | write_sample(buffer, length, stereo); |
| 293 | } |
| 294 | |
| 295 | // buffer cleanup, generate irq if lowwater reached |
| 296 | if (src < lim && src != PicoPicohw.xpcm_buffer) { |
| 297 | int di = lim - src; |
| 298 | memmove(PicoPicohw.xpcm_buffer, src, di); |
| 299 | PicoPicohw.xpcm_ptr = PicoPicohw.xpcm_buffer + di; |
| 300 | elprintf(EL_PICOHW, "xpcm update: over %i", di); |
| 301 | |
| 302 | if (!irq && di < FIFO_IRQ_THRESHOLD) |
| 303 | irq = xpcm.irqenable; |
| 304 | PicoPicohw.fifo_bytes = di; |
| 305 | } else if (src == lim && src != PicoPicohw.xpcm_buffer) { |
| 306 | PicoPicohw.xpcm_ptr = PicoPicohw.xpcm_buffer; |
| 307 | elprintf(EL_PICOHW, "xpcm update: under %i", length); |
| 308 | |
| 309 | if (!irq) |
| 310 | irq = xpcm.irqenable; |
| 311 | PicoPicohw.fifo_bytes = 0; |
| 312 | } |
| 313 | |
| 314 | // TODO need an IRQ mask somewhere to avoid loosing one in cases of HINT/VINT |
| 315 | if (irq && SekIrqLevel != irq) { |
| 316 | elprintf(EL_PICOHW, "irq%d", irq); |
| 317 | if (SekIrqLevel < irq) |
| 318 | SekInterrupt(irq); |
| 319 | } |
| 320 | |
| 321 | if (buffer && length) { |
| 322 | // for underflow, use last sample to avoid clicks |
| 323 | int val = Limit(xpcm.samplegain*xpcm.sample, 16383, -16384); |
| 324 | while (length--) { |
| 325 | int out = apply_filter(val); |
| 326 | *buffer++ += out; |
| 327 | if (stereo) *buffer++ += out; |
| 328 | } |
| 329 | } |
| 330 | } |
| 331 | |
| 332 | PICO_INTERNAL int PicoPicoPCMSave(void *buffer, int length) |
| 333 | { |
| 334 | u8 *bp = buffer; |
| 335 | |
| 336 | if (length < sizeof(xpcm)) { |
| 337 | elprintf(EL_ANOMALY, "save buffer too small?"); |
| 338 | return 0; |
| 339 | } |
| 340 | |
| 341 | memcpy(bp, &xpcm, sizeof(xpcm)); |
| 342 | bp += sizeof(xpcm); |
| 343 | return (bp - (u8*)buffer); |
| 344 | } |
| 345 | |
| 346 | PICO_INTERNAL void PicoPicoPCMLoad(void *buffer, int length) |
| 347 | { |
| 348 | u8 *bp = buffer; |
| 349 | |
| 350 | if (length >= sizeof(xpcm)) |
| 351 | memcpy(&xpcm, bp, sizeof(xpcm)); |
| 352 | bp += sizeof(xpcm); |
| 353 | } |