platform ps2, handle audio similar to psp
[picodrive.git] / pico / pico / xpcm.c
CommitLineData
ef4eb506 1/*
cff531af 2 * PicoDrive
3 * (C) notaz, 2008
f1b425e3 4 * (C) irixxxx, 2024
cff531af 5 *
6 * This work is licensed under the terms of MAME license.
7 * See COPYING file in the top-level directory.
8 *
f1b425e3 9 * The following ADPCM algorithm was derived from MAME upd7759 driver.
492c47c4 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
ef4eb506 21 */
22
f1b425e3 23#include <math.h>
efcba75f 24#include "../pico_int.h"
ef4eb506 25
ef4eb506 26/* limitter */
f1b425e3 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
ef4eb506 33
f1b425e3 34static const int step_deltas[16][16] =
ef4eb506 35{
f1b425e3 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 },
ef4eb506 52};
53
f1b425e3 54static const int state_deltas[16] = { -1, -1, 0, 0, 1, 2, 2, 3, -1, -1, 0, 0, 1, 2, 2, 3 };
55
492c47c4 56static s32 stepsamples; // ratio as Q16, host sound rate / chip sample rate
f1b425e3 57
fa4e0531 58static struct xpcm_state {
59 s32 samplepos; // leftover duration for current sample wrt sndrate, Q16
60 int sample; // current sample
492c47c4 61 short state; // ADPCM decoder state
fa4e0531 62 short samplegain; // programmable gain
63
64 char startpin; // value on the !START pin
65 char irqenable; // IRQ enabled?
66
492c47c4 67 char portstate; // ADPCM stream state
fa4e0531 68 short silence; // silence blocks still to be played
69 short rate, nibbles; // ADPCM nibbles still to be played
492c47c4 70 unsigned char highlow, cache; // nibble selector and cache
71
72 char filter; // filter selector
73 s32 x[3], y[3]; // filter history
fa4e0531 74} xpcm;
75enum { RESET, START, HDR, COUNT }; // portstate
f1b425e3 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
492c47c4 83static struct iir2 { // 2nd order Butterworth IIR coefficients
fa4e0531 84 s32 a[2], gain; // coefficients
f1b425e3 85} filters[4];
492c47c4 86static struct iir2 *filter; // currently selected filter
f1b425e3 87
88
89static void PicoPicoFilterCoeff(struct iir2 *iir, int cutoff, int rate)
90{
492c47c4 91 // no filter if the cutoff is above the Nyquist frequency
f1b425e3 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
106static int PicoPicoFilterApply(struct iir2 *iir, int sample)
107{
108 if (!iir)
109 return sample;
110
fa4e0531 111 // NB Butterworth specific!
492c47c4 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];
f1b425e3 118}
119
120
121// pin functions, N designating a negated pin
122
123PICO_INTERNAL void PicoPicoPCMResetN(int pin)
124{
125 if (!pin) {
fa4e0531 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;
f1b425e3 131}
132
133PICO_INTERNAL void PicoPicoPCMStartN(int pin)
134{
fa4e0531 135 xpcm.startpin = pin;
f1b425e3 136}
137
138PICO_INTERNAL int PicoPicoPCMBusyN(void)
139{
fa4e0531 140 return (xpcm.portstate <= START);
f1b425e3 141}
142
143
144// configuration functions
145
146PICO_INTERNAL void PicoPicoPCMRerate(void)
147{
492c47c4 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
f1b425e3 156 // output samples per chip clock
492c47c4 157 stepsamples = nextstep;
ed367a3f 158
f1b425e3 159 // compute filter coefficients, cutoff at half the ADPCM sample rate
fa4e0531 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
492c47c4 163
164 PicoPicoPCMFilter(xpcm.filter);
f1b425e3 165}
ef4eb506 166
f1b425e3 167PICO_INTERNAL void PicoPicoPCMGain(int gain)
168{
fa4e0531 169 xpcm.samplegain = gain*4;
f1b425e3 170}
171
172PICO_INTERNAL void PicoPicoPCMFilter(int index)
173{
492c47c4 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;
f1b425e3 181 filter = filters+index;
182 if (filter->a[0] == 0)
183 filter = NULL;
184}
ef4eb506 185
f1b425e3 186PICO_INTERNAL void PicoPicoPCMIrqEn(int enable)
ef4eb506 187{
fa4e0531 188 xpcm.irqenable = (enable ? 3 : 0);
ef4eb506 189}
190
f1b425e3 191// TODO need an interupt pending mask?
192PICO_INTERNAL int PicoPicoIrqAck(int level)
ed367a3f 193{
fa4e0531 194 return (PicoPicohw.fifo_bytes < FIFO_IRQ_THRESHOLD && level != xpcm.irqenable
195 ? xpcm.irqenable : 0);
ed367a3f 196}
197
ef4eb506 198
f1b425e3 199// adpcm operation
200
201#define apply_filter(v) PicoPicoFilterApply(filter, v)
202
fa4e0531 203// compute next ADPCM sample
f1b425e3 204#define do_sample(nibble) \
ef4eb506 205{ \
fa4e0531 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); \
f1b425e3 209}
210
fa4e0531 211// writes samples with sndRate, nearest neighbour resampling, filtering
f1b425e3 212#define write_sample(buffer, length, stereo) \
213{ \
fa4e0531 214 while (xpcm.samplepos > 0 && length > 0) { \
215 int val = Limit(xpcm.samplegain*xpcm.sample, 16383, -16384); \
216 xpcm.samplepos -= 1<<16; \
f1b425e3 217 length --; \
218 if (buffer) { \
219 int out = apply_filter(val); \
220 *buffer++ += out; \
221 if (stereo) *buffer++ += out; \
222 } \
223 } \
ef4eb506 224}
225
226PICO_INTERNAL void PicoPicoPCMUpdate(short *buffer, int length, int stereo)
227{
228 unsigned char *src = PicoPicohw.xpcm_buffer;
229 unsigned char *lim = PicoPicohw.xpcm_ptr;
f1b425e3 230 int srcval, irq = 0;
ef4eb506 231
f1b425e3 232 // leftover partial sample from last run
233 write_sample(buffer, length, stereo);
ef4eb506 234
f1b425e3 235 // loop over FIFO data, generating ADPCM samples
236 while (length > 0 && src < lim)
ef4eb506 237 {
492c47c4 238 // ADPCM state engine
239 if (xpcm.silence > 0) { // generate silence
fa4e0531 240 xpcm.silence --;
241 xpcm.sample = 0;
242 xpcm.samplepos += stepsamples*256;
ef4eb506 243
492c47c4 244 } else if (xpcm.nibbles > 0) { // produce samples
fa4e0531 245 xpcm.nibbles --;
f1b425e3 246
fa4e0531 247 if (xpcm.highlow)
248 xpcm.cache = *src++;
f1b425e3 249 else
fa4e0531 250 xpcm.cache <<= 4;
251 xpcm.highlow = !xpcm.highlow;
ef4eb506 252
fa4e0531 253 do_sample((xpcm.cache & 0xf0) >> 4);
254 xpcm.samplepos += stepsamples*xpcm.rate;
ef4eb506 255
492c47c4 256 } else switch (xpcm.portstate) { // handle stream headers
f1b425e3 257 case RESET:
fa4e0531 258 xpcm.sample = 0;
259 xpcm.samplepos += length<<16;
f1b425e3 260 break;
261 case START:
fa4e0531 262 if (xpcm.startpin) {
f1b425e3 263 if (*src)
fa4e0531 264 xpcm.portstate ++;
f1b425e3 265 else // kill 0x00 bytes at stream start
266 src ++;
267 } else {
fa4e0531 268 xpcm.sample = 0;
269 xpcm.samplepos += length<<16;
f1b425e3 270 }
271 break;
272 case HDR:
273 srcval = *src++;
fa4e0531 274 xpcm.nibbles = xpcm.silence = xpcm.rate = 0;
275 xpcm.highlow = 1;
f1b425e3 276 if (srcval == 0) { // terminator
277 // HACK, kill leftover odd byte to avoid restart (Minna de Odorou)
278 if (lim-src == 1) src++;
fa4e0531 279 xpcm.portstate = START;
f1b425e3 280 } else switch (srcval >> 6) {
fa4e0531 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;
f1b425e3 284 case 3: break;
285 }
286 break;
287 case COUNT:
fa4e0531 288 xpcm.nibbles = *src++ + 1; xpcm.portstate = HDR;
f1b425e3 289 break;
e44c606f 290 }
ed367a3f 291
f1b425e3 292 write_sample(buffer, length, stereo);
ef4eb506 293 }
294
f1b425e3 295 // buffer cleanup, generate irq if lowwater reached
296 if (src < lim && src != PicoPicohw.xpcm_buffer) {
ef4eb506 297 int di = lim - src;
298 memmove(PicoPicohw.xpcm_buffer, src, di);
299 PicoPicohw.xpcm_ptr = PicoPicohw.xpcm_buffer + di;
fa22af4c 300 elprintf(EL_PICOHW, "xpcm update: over %i", di);
f1b425e3 301
302 if (!irq && di < FIFO_IRQ_THRESHOLD)
fa4e0531 303 irq = xpcm.irqenable;
ed367a3f 304 PicoPicohw.fifo_bytes = di;
f1b425e3 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);
ed367a3f 308
f1b425e3 309 if (!irq)
fa4e0531 310 irq = xpcm.irqenable;
f1b425e3 311 PicoPicohw.fifo_bytes = 0;
312 }
ed367a3f 313
f1b425e3 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 }
ed367a3f 320
f1b425e3 321 if (buffer && length) {
322 // for underflow, use last sample to avoid clicks
fa4e0531 323 int val = Limit(xpcm.samplegain*xpcm.sample, 16383, -16384);
f1b425e3 324 while (length--) {
325 int out = apply_filter(val);
326 *buffer++ += out;
327 if (stereo) *buffer++ += out;
328 }
329 }
ef4eb506 330}
fa4e0531 331
332PICO_INTERNAL int PicoPicoPCMSave(void *buffer, int length)
333{
334 u8 *bp = buffer;
335
492c47c4 336 if (length < sizeof(xpcm)) {
fa4e0531 337 elprintf(EL_ANOMALY, "save buffer too small?");
338 return 0;
339 }
340
341 memcpy(bp, &xpcm, sizeof(xpcm));
342 bp += sizeof(xpcm);
fa4e0531 343 return (bp - (u8*)buffer);
344}
345
346PICO_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);
fa4e0531 353}