Mercurial > repos > blastem
comparison pico_pcm.c @ 2431:61c0bfe10887
Somewhat busted support for Pico ADPCM
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Tue, 06 Feb 2024 21:47:11 -0800 |
parents | |
children | 18816c5c11d4 |
comparison
equal
deleted
inserted
replaced
2430:fb8d6ebf9d5f | 2431:61c0bfe10887 |
---|---|
1 #include "pico_pcm.h" | |
2 #include "backend.h" | |
3 | |
4 #define PCM_RESET 0x8000 | |
5 #define PCM_INT_EN 0x4000 | |
6 #define PCM_ENABLED 0x0800 | |
7 #define PCM_FILTER 0x00C0 | |
8 #define PCM_VOLUME 0x0007 | |
9 | |
10 void pico_pcm_reset(pico_pcm *pcm) | |
11 { | |
12 pcm->fifo_read = sizeof(pcm->fifo); | |
13 pcm->fifo_write = 0; | |
14 pcm->adpcm_state = 0; | |
15 pcm->output = 0; | |
16 pcm->nibble_store = 0; | |
17 pcm->counter = 0; | |
18 pcm->samples = 0; | |
19 pcm->rate = 0; | |
20 pcm->ctrl &= 0x7FFF; | |
21 } | |
22 | |
23 void pico_pcm_init(pico_pcm *pcm, uint32_t master_clock, uint32_t divider) | |
24 { | |
25 pcm->audio = render_audio_source("PICO ADPCM", master_clock, divider * 4, 1); | |
26 pcm->scope = NULL; | |
27 pcm->scope_channel = 0; | |
28 pcm->clock_inc = divider * 4; | |
29 pico_pcm_reset(pcm); | |
30 } | |
31 | |
32 void pico_pcm_free(pico_pcm *pcm) | |
33 { | |
34 render_free_source(pcm->audio); | |
35 } | |
36 | |
37 void pico_pcm_enable_scope(pico_pcm *pcm, oscilloscope *scope, uint32_t master_clock) | |
38 { | |
39 #ifndef IS_LIB | |
40 pcm->scope = scope; | |
41 pcm->scope_channel = scope_add_channel(scope, "PICO ADPCM", master_clock / pcm->clock_inc); | |
42 #endif | |
43 } | |
44 | |
45 static uint8_t pcm_fifo_read(pico_pcm *pcm) | |
46 { | |
47 if (pcm->fifo_read == sizeof(pcm->fifo)) { | |
48 return 0; | |
49 } | |
50 uint8_t ret = pcm->fifo[pcm->fifo_read++]; | |
51 pcm->fifo_read &= sizeof(pcm->fifo) - 1; | |
52 if (pcm->fifo_read == pcm->fifo_write) { | |
53 pcm->fifo_read = sizeof(pcm->fifo); | |
54 } | |
55 return ret; | |
56 } | |
57 | |
58 int16_t upd7755_calc_sample(uint8_t sample, uint8_t *state) | |
59 { | |
60 //Tables from MAME | |
61 static const int16_t sample_delta[256] = { | |
62 0, 0, 1, 2, 3, 5, 7, 10, 0, 0, -1, -2, -3, -5, -7, -10, | |
63 0, 1, 2, 3, 4, 6, 8, 13, 0, -1, -2, -3, -4, -6, -8, -13, | |
64 0, 1, 2, 4, 5, 7, 10, 15, 0, -1, -2, -4, -5, -7, -10, -15, | |
65 0, 1, 3, 4, 6, 9, 13, 19, 0, -1, -3, -4, -6, -9, -13, -19, | |
66 0, 2, 3, 5, 8, 11, 15, 23, 0, -2, -3, -5, -8, -11, -15, -23, | |
67 0, 2, 4, 7, 10, 14, 19, 29, 0, -2, -4, -7, -10, -14, -19, -29, | |
68 0, 3, 5, 8, 12, 16, 22, 33, 0, -3, -5, -8, -12, -16, -22, -33, | |
69 1, 4, 7, 10, 15, 20, 29, 43, -1, -4, -7, -10, -15, -20, -29, -43, | |
70 1, 4, 8, 13, 18, 25, 35, 53, -1, -4, -8, -13, -18, -25, -35, -53, | |
71 1, 6, 10, 16, 22, 31, 43, 64, -1, -6, -10, -16, -22, -31, -43, -64, | |
72 2, 7, 12, 19, 27, 37, 51, 76, -2, -7, -12, -19, -27, -37, -51, -76, | |
73 2, 9, 16, 24, 34, 46, 64, 96, -2, -9, -16, -24, -34, -46, -64, -96, | |
74 3, 11, 19, 29, 41, 57, 79, 117, -3, -11, -19, -29, -41, -57, -79, -117, | |
75 4, 13, 24, 36, 50, 69, 96, 143, -4, -13, -24, -36, -50, -69, -96, -143, | |
76 4, 16, 29, 44, 62, 85, 118, 175, -4, -16, -29, -44, -62, -85, -118, -175, | |
77 6, 20, 36, 54, 76, 104, 144, 214, -6, -20, -36, -54, -76, -104, -144, -214 | |
78 }; | |
79 static const int state_delta[16] = {-1, -1, 0, 0, 1, 2, 2, 3, -1, -1, 0, 0, 1, 2, 2, 3}; | |
80 int16_t ret = sample_delta[(*state << 4) + sample]; | |
81 int diff = state_delta[*state]; | |
82 if (diff >= 0 || *state > 0) { | |
83 *state += diff; | |
84 if (*state > 15) { | |
85 *state = 15; | |
86 } | |
87 } | |
88 return ret; | |
89 } | |
90 | |
91 void pico_pcm_run(pico_pcm *pcm, uint32_t cycle) | |
92 { | |
93 while (pcm->cycle < cycle) | |
94 { | |
95 pcm->cycle += pcm->clock_inc; | |
96 //TODO: Figure out actual attenuation | |
97 int16_t shift = pcm->ctrl & PCM_VOLUME; | |
98 #ifndef IS_LIB | |
99 if (pcm->scope) { | |
100 scope_add_sample(pcm->scope, pcm->scope_channel, (pcm->output >> shift) * 128, 0); | |
101 } | |
102 #endif | |
103 render_put_mono_sample(pcm->audio, (pcm->output >> shift) * 128); | |
104 if (!(pcm->ctrl & PCM_ENABLED)) { | |
105 continue; | |
106 } | |
107 if (pcm->counter) { | |
108 pcm->counter--; | |
109 } else if (pcm->samples) { | |
110 pcm->samples--; | |
111 uint8_t sample; | |
112 if (pcm->nibble_store) { | |
113 sample = pcm->nibble_store & 0xF; | |
114 pcm->nibble_store = 0; | |
115 } else { | |
116 uint8_t byte = pcm_fifo_read(pcm); | |
117 sample = byte >> 4; | |
118 pcm->nibble_store = 0x80 | (byte & 0xF); | |
119 } | |
120 uint8_t old_state = pcm->adpcm_state; | |
121 pcm->output += upd7755_calc_sample(sample, &pcm->adpcm_state); | |
122 if (pcm->output > 255) { | |
123 pcm->output = 255; | |
124 } else if (pcm->output < -256) { | |
125 pcm->output = -256; | |
126 } | |
127 //printf("Sample %d, old_state %d, new_state %d, output %d\n", sample, old_state, pcm->adpcm_state, pcm->output); | |
128 pcm->counter = pcm->rate; | |
129 } else { | |
130 uint8_t cmd = pcm_fifo_read(pcm); | |
131 if (cmd) { | |
132 pcm->ctrl |= 0x8000; | |
133 } else { | |
134 pcm->ctrl &= 0x7FFF; | |
135 } | |
136 switch (cmd & 0xC0) | |
137 { | |
138 case 0: | |
139 pcm->output = 0; | |
140 pcm->adpcm_state = 0; | |
141 pcm->counter = (cmd & 0x3F) * 160; | |
142 break; | |
143 case 0x40: | |
144 pcm->rate = (cmd & 0x3F); | |
145 pcm->samples = 256; | |
146 break; | |
147 case 0x80: | |
148 pcm->rate = (cmd & 0x3F); | |
149 //FIXME: this probably does not happen instantly | |
150 pcm->samples = pcm_fifo_read(pcm) + 1; | |
151 break; | |
152 case 0xC0: | |
153 //FIXME: this probably does not happen instantly | |
154 //TODO: Does repeat mode even work on this chip? | |
155 // Does it work on a uPD7759 in slave mode? | |
156 // Is this correct behavior if it does work? | |
157 pcm->counter = pcm->rate = pcm_fifo_read(pcm) & 0x3F; | |
158 pcm->samples = (pcm_fifo_read(pcm) + 1) * ((cmd & 7) + 1); | |
159 break; | |
160 } | |
161 } | |
162 } | |
163 } | |
164 | |
165 // RI??E???FF???VVV | |
166 // R: 1 = Reset request, 0 = normal operation | |
167 // I: 1 = interrupts enabled, 0 = disabled | |
168 // E: 1 = Enabled? Sega code always sets this to 1 outside of reset | |
169 // F: Low-pass Filter 1 = 6 kHz, 2 = 12 kHz 3 = 16 kHz | |
170 // V: volume, probably attenuation value since converter defaults to "0" | |
171 void pico_pcm_ctrl_write(pico_pcm *pcm, uint16_t value) | |
172 { | |
173 if (value & PCM_RESET) { | |
174 pico_pcm_reset(pcm); | |
175 } | |
176 pcm->ctrl &= 0x8000; | |
177 pcm->ctrl |= value & ~PCM_RESET; | |
178 //TODO: update low-pass filter | |
179 } | |
180 | |
181 void pico_pcm_data_write(pico_pcm *pcm, uint16_t value) | |
182 { | |
183 if (pcm->fifo_read == sizeof(pcm->fifo)) { | |
184 pcm->fifo_read = pcm->fifo_write; | |
185 } | |
186 pcm->fifo[pcm->fifo_write++] = value >> 8; | |
187 pcm->fifo_write &= sizeof(pcm->fifo)-1; | |
188 pcm->fifo[pcm->fifo_write++] = value; | |
189 pcm->fifo_write &= sizeof(pcm->fifo)-1; | |
190 } | |
191 | |
192 uint16_t pico_pcm_ctrl_read(pico_pcm *pcm) | |
193 { | |
194 return pcm->ctrl; | |
195 } | |
196 | |
197 uint16_t pico_pcm_data_read(pico_pcm *pcm) | |
198 { | |
199 if (pcm->fifo_read == sizeof(pcm->fifo)) { | |
200 return sizeof(pcm->fifo) - 1; | |
201 } | |
202 return (pcm->fifo_read - pcm->fifo_write) & (sizeof(pcm->fifo)-1); | |
203 } | |
204 | |
205 #define FIFO_THRESHOLD 48 | |
206 uint32_t pico_pcm_next_int(pico_pcm *pcm) | |
207 { | |
208 if (!(pcm->ctrl & PCM_INT_EN)) { | |
209 return CYCLE_NEVER; | |
210 } | |
211 uint32_t fifo_bytes; | |
212 if (pcm->fifo_read == sizeof(pcm->fifo)) { | |
213 fifo_bytes = 0; | |
214 } else if (pcm->fifo_read == pcm->fifo_write) { | |
215 fifo_bytes = sizeof(pcm->fifo); | |
216 } else { | |
217 fifo_bytes = (pcm->fifo_write - pcm->fifo_read) & (sizeof(pcm->fifo) - 1); | |
218 } | |
219 if (fifo_bytes < FIFO_THRESHOLD) { | |
220 return pcm->cycle; | |
221 } | |
222 uint32_t cycles_to_threshold = pcm->counter + 1; | |
223 if (pcm->samples) { | |
224 uint16_t samples = pcm->samples; | |
225 if (pcm->nibble_store) { | |
226 cycles_to_threshold += pcm->rate + 1; | |
227 samples--; | |
228 } | |
229 uint16_t bytes = (samples >> 1) + (samples & 1); | |
230 if (bytes > (fifo_bytes - FIFO_THRESHOLD)) { | |
231 cycles_to_threshold += (fifo_bytes - FIFO_THRESHOLD + 1) * (pcm->rate + 1) * 2; | |
232 fifo_bytes = 0; | |
233 } else { | |
234 cycles_to_threshold += bytes * (pcm->rate + 1) * 2; | |
235 fifo_bytes -= bytes; | |
236 } | |
237 } | |
238 uint8_t fifo_read = pcm->fifo_read; | |
239 uint8_t cmd = 0; | |
240 while (fifo_bytes >= FIFO_THRESHOLD) | |
241 { | |
242 if (cmd) { | |
243 switch(cmd & 0xC0) | |
244 { | |
245 case 0: | |
246 cycles_to_threshold += 640 * (cmd & 0x3F) + 1; | |
247 break; | |
248 case 0x40: | |
249 cycles_to_threshold += (fifo_bytes - FIFO_THRESHOLD + 1) * ((cmd & 0x3F) + 1) * 2; | |
250 fifo_bytes = 0; | |
251 break; | |
252 case 0x80: { | |
253 uint32_t samples = pcm->fifo[fifo_read++]; | |
254 fifo_bytes--; | |
255 fifo_read &= sizeof(pcm->fifo) - 1; | |
256 if (fifo_bytes < FIFO_THRESHOLD) { | |
257 break; | |
258 } | |
259 uint32_t bytes = (samples +1) >> 1; | |
260 if (bytes > (fifo_bytes - FIFO_THRESHOLD)) { | |
261 cycles_to_threshold += (fifo_bytes - FIFO_THRESHOLD + 1) * ((cmd & 0x3F) + 1) * 2; | |
262 fifo_bytes = 0; | |
263 } | |
264 break; } | |
265 case 0xC0: { | |
266 uint32_t rate = pcm->fifo[fifo_read++] & 0x3F; | |
267 fifo_bytes--; | |
268 fifo_read &= sizeof(pcm->fifo) - 1; | |
269 uint32_t samples = pcm->fifo[fifo_read++] & 0x3F; | |
270 fifo_bytes--; | |
271 fifo_read &= sizeof(pcm->fifo) - 1; | |
272 if (fifo_bytes < FIFO_THRESHOLD) { | |
273 break; | |
274 } | |
275 samples++; | |
276 samples *= (cmd & 7) + 1; | |
277 uint32_t bytes = (samples + 1) >> 1; | |
278 if (bytes > (fifo_bytes - FIFO_THRESHOLD)) { | |
279 cycles_to_threshold += (fifo_bytes - FIFO_THRESHOLD + 1) * ((cmd & 0x3F) + 1) * 2; | |
280 fifo_bytes = 0; | |
281 } | |
282 break; } | |
283 } | |
284 cmd = 0; | |
285 } else { | |
286 cycles_to_threshold++; | |
287 cmd = pcm->fifo[fifo_read++]; | |
288 fifo_bytes--; | |
289 fifo_read &= sizeof(pcm->fifo) - 1; | |
290 } | |
291 } | |
292 | |
293 return pcm->cycle + cycles_to_threshold * pcm->clock_inc; | |
294 } |