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 |
34 | static 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 |
54 | static const int state_deltas[16] = { -1, -1, 0, 0, 1, 2, 2, 3, -1, -1, 0, 0, 1, 2, 2, 3 }; |
55 | |
492c47c4 |
56 | static s32 stepsamples; // ratio as Q16, host sound rate / chip sample rate |
f1b425e3 |
57 | |
fa4e0531 |
58 | static 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; |
75 | enum { 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 |
83 | static struct iir2 { // 2nd order Butterworth IIR coefficients |
fa4e0531 |
84 | s32 a[2], gain; // coefficients |
f1b425e3 |
85 | } filters[4]; |
492c47c4 |
86 | static struct iir2 *filter; // currently selected filter |
f1b425e3 |
87 | |
88 | |
89 | static 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 | |
106 | static 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 | |
123 | PICO_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 | |
133 | PICO_INTERNAL void PicoPicoPCMStartN(int pin) |
134 | { |
fa4e0531 |
135 | xpcm.startpin = pin; |
f1b425e3 |
136 | } |
137 | |
138 | PICO_INTERNAL int PicoPicoPCMBusyN(void) |
139 | { |
fa4e0531 |
140 | return (xpcm.portstate <= START); |
f1b425e3 |
141 | } |
142 | |
143 | |
144 | // configuration functions |
145 | |
146 | PICO_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 |
167 | PICO_INTERNAL void PicoPicoPCMGain(int gain) |
168 | { |
fa4e0531 |
169 | xpcm.samplegain = gain*4; |
f1b425e3 |
170 | } |
171 | |
172 | PICO_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 |
186 | PICO_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? |
192 | PICO_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 | |
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; |
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 | |
332 | PICO_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 | |
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); |
fa4e0531 |
353 | } |