pandora: fix readme and pxml version
[picodrive.git] / pico / pico / xpcm.c
... / ...
CommitLineData
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
34static 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
54static const int state_deltas[16] = { -1, -1, 0, 0, 1, 2, 2, 3, -1, -1, 0, 0, 1, 2, 2, 3 };
55
56static s32 stepsamples; // ratio as Q16, host sound rate / chip sample rate
57
58static 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;
75enum { 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
83static struct iir2 { // 2nd order Butterworth IIR coefficients
84 s32 a[2], gain; // coefficients
85} filters[4];
86static struct iir2 *filter; // currently selected filter
87
88
89static 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
106static 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
123PICO_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
133PICO_INTERNAL void PicoPicoPCMStartN(int pin)
134{
135 xpcm.startpin = pin;
136}
137
138PICO_INTERNAL int PicoPicoPCMBusyN(void)
139{
140 return (xpcm.portstate <= START);
141}
142
143
144// configuration functions
145
146PICO_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
167PICO_INTERNAL void PicoPicoPCMGain(int gain)
168{
169 xpcm.samplegain = gain*4;
170}
171
172PICO_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
186PICO_INTERNAL void PicoPicoPCMIrqEn(int enable)
187{
188 xpcm.irqenable = (enable ? 3 : 0);
189}
190
191// TODO need an interupt pending mask?
192PICO_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
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;
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
332PICO_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
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);
353}