diff options
Diffstat (limited to 'firmware/pcm_mixer.c')
-rw-r--r-- | firmware/pcm_mixer.c | 501 |
1 files changed, 501 insertions, 0 deletions
diff --git a/firmware/pcm_mixer.c b/firmware/pcm_mixer.c new file mode 100644 index 0000000000..cddd3f0f86 --- /dev/null +++ b/firmware/pcm_mixer.c | |||
@@ -0,0 +1,501 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * $Id$ | ||
9 | * | ||
10 | * Copyright (C) 2011 by Michael Sevakis | ||
11 | * | ||
12 | * This program is free software; you can redistribute it and/or | ||
13 | * modify it under the terms of the GNU General Public License | ||
14 | * as published by the Free Software Foundation; either version 2 | ||
15 | * of the License, or (at your option) any later version. | ||
16 | * | ||
17 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
18 | * KIND, either express or implied. | ||
19 | * | ||
20 | ****************************************************************************/ | ||
21 | #include "config.h" | ||
22 | #include "system.h" | ||
23 | #include "general.h" | ||
24 | #include "kernel.h" | ||
25 | #include "pcm.h" | ||
26 | #include "pcm_mixer.h" | ||
27 | #include "dsp.h" | ||
28 | |||
29 | /* Channels use standard-style PCM callback interface but a latency of one | ||
30 | frame by double-buffering is introduced in order to facilitate mixing and | ||
31 | keep the hardware fed. There must be sufficient time to perform operations | ||
32 | before the last samples are sent to the codec and so things are done in | ||
33 | parallel (as much as possible) with sending-out data. */ | ||
34 | |||
35 | /* Define this to nonzero to add a marker pulse at each frame start */ | ||
36 | #define FRAME_BOUNDARY_MARKERS 0 | ||
37 | |||
38 | /* Descriptor for each channel */ | ||
39 | struct mixer_channel | ||
40 | { | ||
41 | unsigned char *start; /* Buffer pointer */ | ||
42 | size_t size; /* Bytes remaining */ | ||
43 | size_t last_size; /* Size of consumed data in prev. cycle */ | ||
44 | pcm_play_callback_type get_more; /* Registered callback */ | ||
45 | enum channel_status status; /* Playback status */ | ||
46 | uint32_t amplitude; /* Amp. factor: 0x0000 = mute, 0x10000 = unity */ | ||
47 | }; | ||
48 | |||
49 | /* Forget about boost here for the moment */ | ||
50 | #define MIX_FRAME_SIZE (MIX_FRAME_SAMPLES*4) | ||
51 | |||
52 | /* Because of the double-buffering, playback is always from here, otherwise a | ||
53 | mechanism for the channel callbacks not to free buffers too early would be | ||
54 | needed (if we _really_ want it and it's worth it, we _can_ do that ;-) ) */ | ||
55 | static uint32_t downmix_buf[2][MIX_FRAME_SAMPLES] DOWNMIX_BUF_IBSS MEM_ALIGN_ATTR; | ||
56 | static int downmix_index = 0; /* Which downmix_buf? */ | ||
57 | static size_t next_size = 0; /* Size of buffer to play next time */ | ||
58 | |||
59 | /* Descriptors for all available channels */ | ||
60 | static struct mixer_channel channels[PCM_MIXER_NUM_CHANNELS] IBSS_ATTR; | ||
61 | |||
62 | /* Packed pointer array of all playing (active) channels in "channels" array */ | ||
63 | static struct mixer_channel * active_channels[PCM_MIXER_NUM_CHANNELS+1] IBSS_ATTR; | ||
64 | |||
65 | /* Number of silence frames to play after all data has played */ | ||
66 | #define MAX_IDLE_FRAMES (NATIVE_FREQUENCY*3 / MIX_FRAME_SAMPLES) | ||
67 | static unsigned int idle_counter = 0; | ||
68 | |||
69 | /* Cheapo buffer align macro to align to the 16-16 PCM size */ | ||
70 | #define ALIGN_CHANNEL(start, size) \ | ||
71 | ({ start = (void *)(((uintptr_t)start + 3) & ~3); \ | ||
72 | size &= ~3; }) | ||
73 | |||
74 | /* Include any implemented CPU-optimized mixdown routines */ | ||
75 | #if defined(CPU_ARM) | ||
76 | #if ARM_ARCH >= 6 | ||
77 | #include "pcm-mixer-armv6.c" | ||
78 | #elif ARM_ARCH >= 5 | ||
79 | #include "pcm-mixer-armv5.c" | ||
80 | #else | ||
81 | #include "pcm-mixer-armv4.c" | ||
82 | #endif /* ARM_ARCH */ | ||
83 | #elif defined (CPU_COLDFIRE) | ||
84 | #include "pcm-mixer-coldfire.c" | ||
85 | #endif /* CPU_* */ | ||
86 | |||
87 | |||
88 | /** Generic mixing routines **/ | ||
89 | |||
90 | #ifndef MIXER_OPTIMIZED_MIX_SAMPLES | ||
91 | /* Clip sample to signed 16 bit range */ | ||
92 | static FORCE_INLINE int32_t clip_sample_16(int32_t sample) | ||
93 | { | ||
94 | if ((int16_t)sample != sample) | ||
95 | sample = 0x7fff ^ (sample >> 31); | ||
96 | return sample; | ||
97 | } | ||
98 | |||
99 | /* Mix channels' samples and apply gain factors */ | ||
100 | static FORCE_INLINE void mix_samples(uint32_t *out, | ||
101 | int16_t *src0, | ||
102 | int32_t src0_amp, | ||
103 | int16_t *src1, | ||
104 | int32_t src1_amp, | ||
105 | size_t size) | ||
106 | { | ||
107 | if (src0_amp == MIX_AMP_UNITY && src1_amp == MIX_AMP_UNITY) | ||
108 | { | ||
109 | /* Both are unity amplitude */ | ||
110 | do | ||
111 | { | ||
112 | int32_t l = *src0++ + *src1++; | ||
113 | int32_t h = *src0++ + *src1++; | ||
114 | *out++ = (uint16_t)clip_sample_16(l) | (clip_sample_16(h) << 16); | ||
115 | } | ||
116 | while ((size -= 4) > 0); | ||
117 | } | ||
118 | else if (src0_amp != MIX_AMP_UNITY && src1_amp != MIX_AMP_UNITY) | ||
119 | { | ||
120 | /* Neither are unity amplitude */ | ||
121 | do | ||
122 | { | ||
123 | int32_t l = (*src0++ * src0_amp >> 16) + (*src1++ * src1_amp >> 16); | ||
124 | int32_t h = (*src0++ * src0_amp >> 16) + (*src1++ * src1_amp >> 16); | ||
125 | *out++ = (uint16_t)clip_sample_16(l) | (clip_sample_16(h) << 16); | ||
126 | } | ||
127 | while ((size -= 4) > 0); | ||
128 | } | ||
129 | else | ||
130 | { | ||
131 | /* One is unity amplitude */ | ||
132 | if (src0_amp != MIX_AMP_UNITY) | ||
133 | { | ||
134 | /* Keep unity in src0, amp0 */ | ||
135 | int16_t *src_tmp = src0; | ||
136 | src0 = src1; | ||
137 | src1 = src_tmp; | ||
138 | src1_amp = src0_amp; | ||
139 | src0_amp = MIX_AMP_UNITY; | ||
140 | } | ||
141 | |||
142 | do | ||
143 | { | ||
144 | int32_t l = *src0++ + (*src1++ * src1_amp >> 16); | ||
145 | int32_t h = *src0++ + (*src1++ * src1_amp >> 16); | ||
146 | *out++ = (uint16_t)clip_sample_16(l) | (clip_sample_16(h) << 16); | ||
147 | } | ||
148 | while ((size -= 4) > 0); | ||
149 | } | ||
150 | } | ||
151 | #endif /* MIXER_OPTIMIZED_MIX_SAMPLES */ | ||
152 | |||
153 | #ifndef MIXER_OPTIMIZED_WRITE_SAMPLES | ||
154 | /* Write channel's samples and apply gain factor */ | ||
155 | static FORCE_INLINE void write_samples(uint32_t *out, | ||
156 | int16_t *src, | ||
157 | int32_t amp, | ||
158 | size_t size) | ||
159 | { | ||
160 | if (LIKELY(amp == MIX_AMP_UNITY)) | ||
161 | { | ||
162 | /* Channel is unity amplitude */ | ||
163 | memcpy(out, src, size); | ||
164 | } | ||
165 | else | ||
166 | { | ||
167 | /* Channel needs amplitude cut */ | ||
168 | do | ||
169 | { | ||
170 | int32_t l = *src++ * amp >> 16; | ||
171 | int32_t h = *src++ * amp & 0xffff0000; | ||
172 | *out++ = (uint16_t)l | h; | ||
173 | } | ||
174 | while ((size -= 4) > 0); | ||
175 | } | ||
176 | } | ||
177 | #endif /* MIXER_OPTIMIZED_WRITE_SAMPLES */ | ||
178 | |||
179 | |||
180 | /** Private generic routines **/ | ||
181 | |||
182 | /* Mark channel active to mix its data */ | ||
183 | static void mixer_activate_channel(struct mixer_channel *chan) | ||
184 | { | ||
185 | void **elem = find_array_ptr((void **)active_channels, chan); | ||
186 | |||
187 | if (!*elem) | ||
188 | { | ||
189 | idle_counter = 0; | ||
190 | *elem = chan; | ||
191 | } | ||
192 | } | ||
193 | |||
194 | /* Stop channel from mixing */ | ||
195 | static void mixer_deactivate_channel(struct mixer_channel *chan) | ||
196 | { | ||
197 | remove_array_ptr((void **)active_channels, chan); | ||
198 | } | ||
199 | |||
200 | /* Deactivate channel and change it to stopped state */ | ||
201 | static void channel_stopped(struct mixer_channel *chan) | ||
202 | { | ||
203 | mixer_deactivate_channel(chan); | ||
204 | chan->size = 0; | ||
205 | chan->start = NULL; | ||
206 | chan->status = CHANNEL_STOPPED; | ||
207 | } | ||
208 | |||
209 | /* Main PCM callback - sends the current prepared frame to play */ | ||
210 | static void mixer_pcm_callback(unsigned char **start, size_t *size) | ||
211 | { | ||
212 | *start = (unsigned char *)downmix_buf[downmix_index]; | ||
213 | *size = next_size; | ||
214 | } | ||
215 | |||
216 | /* Buffering callback - calls sub-callbacks and mixes the data for next | ||
217 | buffer to be sent from mixer_pcm_callback() */ | ||
218 | static void ICODE_ATTR mixer_buffer_callback(void) | ||
219 | { | ||
220 | downmix_index ^= 1; /* Next buffer */ | ||
221 | |||
222 | void *mixptr = downmix_buf[downmix_index]; | ||
223 | size_t mixsize = MIX_FRAME_SIZE; | ||
224 | struct mixer_channel **chan_p; | ||
225 | |||
226 | next_size = 0; | ||
227 | |||
228 | /* "Loop" back here if one round wasn't enough to fill a frame */ | ||
229 | fill_frame: | ||
230 | chan_p = active_channels; | ||
231 | |||
232 | while (*chan_p) | ||
233 | { | ||
234 | /* Find the active channel with the least data remaining and call any | ||
235 | callbacks for channels that ran out - stopping whichever report | ||
236 | "no more" */ | ||
237 | struct mixer_channel *chan = *chan_p; | ||
238 | chan->start += chan->last_size; | ||
239 | chan->size -= chan->last_size; | ||
240 | |||
241 | if (chan->size == 0) | ||
242 | { | ||
243 | if (chan->get_more) | ||
244 | { | ||
245 | chan->get_more(&chan->start, &chan->size); | ||
246 | ALIGN_CHANNEL(chan->start, chan->size); | ||
247 | } | ||
248 | |||
249 | if (!(chan->start && chan->size)) | ||
250 | { | ||
251 | /* Channel is stopping */ | ||
252 | channel_stopped(chan); | ||
253 | continue; | ||
254 | } | ||
255 | } | ||
256 | |||
257 | /* Channel will play for at least part of this frame */ | ||
258 | |||
259 | /* Channel with least amount of data remaining determines the downmix | ||
260 | size */ | ||
261 | if (chan->size < mixsize) | ||
262 | mixsize = chan->size; | ||
263 | |||
264 | chan_p++; | ||
265 | } | ||
266 | |||
267 | /* Add all still-active channels to the downmix */ | ||
268 | chan_p = active_channels; | ||
269 | |||
270 | if (LIKELY(*chan_p)) | ||
271 | { | ||
272 | struct mixer_channel *chan = *chan_p++; | ||
273 | |||
274 | if (LIKELY(!*chan_p)) | ||
275 | { | ||
276 | write_samples(mixptr, (void *)chan->start, | ||
277 | chan->amplitude, mixsize); | ||
278 | } | ||
279 | else | ||
280 | { | ||
281 | void *src0, *src1; | ||
282 | unsigned int amp0, amp1; | ||
283 | |||
284 | /* Mix first two channels with each other as the downmix */ | ||
285 | src0 = chan->start; | ||
286 | amp0 = chan->amplitude; | ||
287 | chan->last_size = mixsize; | ||
288 | |||
289 | chan = *chan_p++; | ||
290 | src1 = chan->start; | ||
291 | amp1 = chan->amplitude; | ||
292 | |||
293 | while (1) | ||
294 | { | ||
295 | mix_samples(mixptr, src0, amp0, src1, amp1, mixsize); | ||
296 | |||
297 | if (!*chan_p) | ||
298 | break; | ||
299 | |||
300 | /* More channels to mix - mix each with existing downmix */ | ||
301 | chan->last_size = mixsize; | ||
302 | chan = *chan_p++; | ||
303 | src0 = mixptr; | ||
304 | amp0 = MIX_AMP_UNITY; | ||
305 | src1 = chan->start; | ||
306 | amp1 = chan->amplitude; | ||
307 | } | ||
308 | } | ||
309 | |||
310 | chan->last_size = mixsize; | ||
311 | next_size += mixsize; | ||
312 | |||
313 | if (next_size < MIX_FRAME_SIZE) | ||
314 | { | ||
315 | /* There is still space remaining in this frame */ | ||
316 | mixptr += mixsize; | ||
317 | mixsize = MIX_FRAME_SIZE - next_size; | ||
318 | goto fill_frame; | ||
319 | } | ||
320 | } | ||
321 | else if (idle_counter++ < MAX_IDLE_FRAMES) | ||
322 | { | ||
323 | /* Pad incomplete frames with silence */ | ||
324 | if (idle_counter <= 3) | ||
325 | memset(mixptr, 0, MIX_FRAME_SIZE - next_size); | ||
326 | |||
327 | next_size = MIX_FRAME_SIZE; | ||
328 | } | ||
329 | /* else silence period ran out - go to sleep */ | ||
330 | |||
331 | #if FRAME_BOUNDARY_MARKERS != 0 | ||
332 | if (next_size) | ||
333 | *downmix_buf[downmix_index] = downmix_index ? 0x7fff7fff : 0x80008000; | ||
334 | #endif | ||
335 | } | ||
336 | |||
337 | /* Start PCM driver if it's not currently playing */ | ||
338 | static void mixer_start_pcm(void) | ||
339 | { | ||
340 | if (pcm_is_playing()) | ||
341 | return; | ||
342 | |||
343 | #if defined(HAVE_RECORDING) && !defined(HAVE_PCM_FULL_DUPLEX) | ||
344 | if (pcm_is_recording()) | ||
345 | return; | ||
346 | #endif | ||
347 | |||
348 | /* Prepare initial frames and set up the double buffer */ | ||
349 | mixer_buffer_callback(); | ||
350 | |||
351 | /* Save the previous call's output */ | ||
352 | void *start = downmix_buf[downmix_index]; | ||
353 | |||
354 | mixer_buffer_callback(); | ||
355 | |||
356 | pcm_play_set_dma_started_callback(mixer_buffer_callback); | ||
357 | pcm_play_data(mixer_pcm_callback, start, MIX_FRAME_SIZE); | ||
358 | } | ||
359 | |||
360 | /* Initialize the channel and start it if it has data */ | ||
361 | static void mixer_channel_play_start(struct mixer_channel *chan, | ||
362 | pcm_play_callback_type get_more, | ||
363 | unsigned char *start, size_t size) | ||
364 | { | ||
365 | pcm_play_unlock(); /* Allow playback while doing any callback */ | ||
366 | |||
367 | ALIGN_CHANNEL(start, size); | ||
368 | |||
369 | if (!(start && size)) | ||
370 | { | ||
371 | /* Initial buffer not passed - call the callback now */ | ||
372 | size = 0; | ||
373 | if (get_more) | ||
374 | { | ||
375 | get_more(&start, &size); | ||
376 | ALIGN_CHANNEL(start, size); | ||
377 | } | ||
378 | } | ||
379 | |||
380 | if (start && size) | ||
381 | { | ||
382 | /* We have data - start the channel */ | ||
383 | chan->status = CHANNEL_PLAYING; | ||
384 | chan->start = start; | ||
385 | chan->size = size; | ||
386 | chan->last_size = 0; | ||
387 | chan->get_more = get_more; | ||
388 | |||
389 | pcm_play_lock(); | ||
390 | mixer_activate_channel(chan); | ||
391 | mixer_start_pcm(); | ||
392 | } | ||
393 | else | ||
394 | { | ||
395 | /* Never had anything - stop it now */ | ||
396 | pcm_play_lock(); | ||
397 | channel_stopped(chan); | ||
398 | } | ||
399 | } | ||
400 | |||
401 | |||
402 | /** Public interfaces **/ | ||
403 | |||
404 | /* Start playback on a channel */ | ||
405 | void mixer_channel_play_data(enum pcm_mixer_channel channel, | ||
406 | pcm_play_callback_type get_more, | ||
407 | unsigned char *start, size_t size) | ||
408 | { | ||
409 | struct mixer_channel *chan = &channels[channel]; | ||
410 | |||
411 | pcm_play_lock(); | ||
412 | mixer_deactivate_channel(chan); | ||
413 | mixer_channel_play_start(chan, get_more, start, size); | ||
414 | pcm_play_unlock(); | ||
415 | } | ||
416 | |||
417 | /* Pause or resume a channel (when started) */ | ||
418 | void mixer_channel_play_pause(enum pcm_mixer_channel channel, bool play) | ||
419 | { | ||
420 | struct mixer_channel *chan = &channels[channel]; | ||
421 | |||
422 | pcm_play_lock(); | ||
423 | |||
424 | if (play == (chan->status == CHANNEL_PAUSED) && | ||
425 | chan->status != CHANNEL_STOPPED) | ||
426 | { | ||
427 | if (play) | ||
428 | { | ||
429 | chan->status = CHANNEL_PLAYING; | ||
430 | mixer_activate_channel(chan); | ||
431 | mixer_start_pcm(); | ||
432 | } | ||
433 | else | ||
434 | { | ||
435 | mixer_deactivate_channel(chan); | ||
436 | chan->status = CHANNEL_PAUSED; | ||
437 | } | ||
438 | } | ||
439 | |||
440 | pcm_play_unlock(); | ||
441 | } | ||
442 | |||
443 | /* Stop playback on a channel */ | ||
444 | void mixer_channel_stop(enum pcm_mixer_channel channel) | ||
445 | { | ||
446 | struct mixer_channel *chan = &channels[channel]; | ||
447 | |||
448 | pcm_play_lock(); | ||
449 | channel_stopped(chan); | ||
450 | pcm_play_unlock(); | ||
451 | } | ||
452 | |||
453 | /* Set channel's amplitude factor */ | ||
454 | void mixer_channel_set_amplitude(enum pcm_mixer_channel channel, | ||
455 | unsigned int amplitude) | ||
456 | { | ||
457 | channels[channel].amplitude = MIN(amplitude, MIX_AMP_UNITY); | ||
458 | } | ||
459 | |||
460 | /* Return channel's playback status */ | ||
461 | enum channel_status mixer_channel_status(enum pcm_mixer_channel channel) | ||
462 | { | ||
463 | return channels[channel].status; | ||
464 | } | ||
465 | |||
466 | /* Returns amount data remaining in channel before next callback */ | ||
467 | size_t mixer_channel_get_bytes_waiting(enum pcm_mixer_channel channel) | ||
468 | { | ||
469 | return channels[channel].size; | ||
470 | } | ||
471 | |||
472 | /* Return pointer to channel's playing audio data and the size remaining */ | ||
473 | void * mixer_channel_get_buffer(enum pcm_mixer_channel channel, int *count) | ||
474 | { | ||
475 | struct mixer_channel *chan = &channels[channel]; | ||
476 | void * buf = *(unsigned char * volatile *)&chan->start; | ||
477 | size_t size = *(size_t volatile *)&chan->size; | ||
478 | void * buf2 = *(unsigned char * volatile *)&chan->start; | ||
479 | |||
480 | /* Still same buffer? */ | ||
481 | if (buf == buf2) | ||
482 | { | ||
483 | *count = size >> 2; | ||
484 | return buf; | ||
485 | } | ||
486 | /* else can't be sure buf and size are related */ | ||
487 | |||
488 | *count = 0; | ||
489 | return NULL; | ||
490 | } | ||
491 | |||
492 | /* Stop ALL channels and PCM and reset state */ | ||
493 | void mixer_reset(void) | ||
494 | { | ||
495 | pcm_play_stop(); | ||
496 | |||
497 | while (*active_channels) | ||
498 | channel_stopped(*active_channels); | ||
499 | |||
500 | idle_counter = 0; | ||
501 | } | ||