summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Sevakis <jethead71@rockbox.org>2011-06-29 06:37:04 +0000
committerMichael Sevakis <jethead71@rockbox.org>2011-06-29 06:37:04 +0000
commita2b6703a369f6cdbfec1f150c408dadc877631fb (patch)
tree3145a8c1372c44711d38feefeba39c7d4098f139
parent8411614b8a068a4f274c3841aa55aab1df1bc246 (diff)
downloadrockbox-a2b6703a369f6cdbfec1f150c408dadc877631fb.tar.gz
rockbox-a2b6703a369f6cdbfec1f150c408dadc877631fb.zip
Commit FS#12150 - Fully-functional audio mixer - and finally whip old limitations about playback of voice and other sounds when paused. Channels are independent in state and amplitude. Fade on stop/pause is handled by the channel's volume control rather than global volume which means it now works from anywhere. Opens up the possibility of plugin sounds during music playback by merely adding an additional channel enum. If any PCM drivers were not properly modified, see one of the last comments in the task for a description of the simple change that is expected. Some params are tunable in firmware/export/pcm-mixer.h as well.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@30097 a1c6a512-1295-4272-9138-f99709370657
-rw-r--r--apps/SOURCES3
-rw-r--r--apps/action.c2
-rw-r--r--apps/beep.c142
-rw-r--r--apps/gui/wps.c16
-rw-r--r--apps/misc.c5
-rw-r--r--apps/misc.h3
-rw-r--r--apps/pcmbuf.c319
-rw-r--r--apps/pcmbuf.h3
-rw-r--r--apps/playback.c20
-rw-r--r--apps/plugin.c5
-rw-r--r--apps/plugin.h10
-rw-r--r--apps/plugins/fft/fft.c13
-rw-r--r--apps/recorder/keyboard.c2
-rw-r--r--apps/voice_thread.c279
-rw-r--r--apps/voice_thread.h5
-rw-r--r--firmware/SOURCES1
-rw-r--r--firmware/export/config.h5
-rw-r--r--firmware/export/pcm-internal.h81
-rw-r--r--firmware/export/pcm.h44
-rw-r--r--firmware/export/pcm_mixer.h102
-rw-r--r--firmware/pcm.c18
-rw-r--r--firmware/pcm_mixer.c501
-rw-r--r--firmware/target/arm/as3525/pcm-as3525.c9
-rw-r--r--firmware/target/arm/imx31/gigabeat-s/pcm-gigabeat-s.c3
-rw-r--r--firmware/target/arm/pcm-mixer-armv4.c182
-rw-r--r--firmware/target/arm/pcm-mixer-armv5.c106
-rw-r--r--firmware/target/arm/pcm-mixer-armv6.c118
-rw-r--r--firmware/target/arm/pcm-pp.c43
-rw-r--r--firmware/target/arm/pcm-telechips.c26
-rw-r--r--firmware/target/arm/pnx0101/pcm-pnx0101.c13
-rw-r--r--firmware/target/arm/s3c2440/gigabeat-fx/pcm-meg-fx.c3
-rw-r--r--firmware/target/arm/s3c2440/gigabeat-fx/wmcodec-meg-fx.c5
-rw-r--r--firmware/target/arm/s3c2440/mini2440/pcm-mini2440.c3
-rw-r--r--firmware/target/arm/s5l8700/pcm-s5l8700.c17
-rw-r--r--firmware/target/arm/s5l8702/pcm-s5l8702.c3
-rw-r--r--firmware/target/arm/tms320dm320/creative-zvm/pcm-creativezvm.c1
-rw-r--r--firmware/target/arm/tms320dm320/mrobe-500/pcm-mr500.c3
-rw-r--r--firmware/target/coldfire/pcm-coldfire.c4
-rw-r--r--firmware/target/coldfire/pcm-mixer-coldfire.c134
-rw-r--r--firmware/target/hosted/android/pcm-android.c54
-rw-r--r--firmware/target/hosted/maemo/pcm-gstreamer.c3
-rw-r--r--firmware/target/hosted/sdl/pcm-sdl.c47
-rw-r--r--firmware/target/mips/ingenic_jz47xx/pcm-jz4740.c2
43 files changed, 1930 insertions, 428 deletions
diff --git a/apps/SOURCES b/apps/SOURCES
index c122427900..075ca9a563 100644
--- a/apps/SOURCES
+++ b/apps/SOURCES
@@ -170,6 +170,9 @@ codec_thread.c
170playback.c 170playback.c
171codecs.c 171codecs.c
172dsp.c 172dsp.c
173#ifndef HAVE_HARDWARE_BEEP
174beep.c
175#endif
173#ifdef HAVE_PITCHSCREEN 176#ifdef HAVE_PITCHSCREEN
174tdspeed.c 177tdspeed.c
175#endif 178#endif
diff --git a/apps/action.c b/apps/action.c
index eb5950bb70..aa19403703 100644
--- a/apps/action.c
+++ b/apps/action.c
@@ -205,7 +205,7 @@ static int get_action_worker(int context, int timeout,
205 /* Produce keyclick */ 205 /* Produce keyclick */
206 if (global_settings.keyclick && !(button & BUTTON_REL)) 206 if (global_settings.keyclick && !(button & BUTTON_REL))
207 if (!(button & BUTTON_REPEAT) || global_settings.keyclick_repeats) 207 if (!(button & BUTTON_REPEAT) || global_settings.keyclick_repeats)
208 pcmbuf_beep(4000, KEYCLICK_DURATION, 2500*global_settings.keyclick); 208 beep_play(4000, KEYCLICK_DURATION, 2500*global_settings.keyclick);
209#endif 209#endif
210 210
211 if ((context != last_context) && ((last_button & BUTTON_REL) == 0) 211 if ((context != last_context) && ((last_button & BUTTON_REL) == 0)
diff --git a/apps/beep.c b/apps/beep.c
new file mode 100644
index 0000000000..716847263e
--- /dev/null
+++ b/apps/beep.c
@@ -0,0 +1,142 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (c) 2011 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 "settings.h"
24#include "dsp.h"
25#include "pcm.h"
26#include "pcm_mixer.h"
27#include "misc.h"
28
29static int32_t beep_phase; /* Phase of square wave generator */
30static uint32_t beep_step; /* Step of square wave generator on each sample */
31static uint32_t beep_amplitude; /* Amplitude of square wave generator */
32static int beep_count; /* Number of samples remaining to generate */
33
34/* Reserve enough static space for keyclick to fit */
35#define BEEP_BUF_COUNT (NATIVE_FREQUENCY / 1000 * KEYCLICK_DURATION)
36static uint32_t beep_buf[BEEP_BUF_COUNT] IBSS_ATTR;
37
38/* Actually output samples into beep_buf */
39#if defined(CPU_ARM)
40static FORCE_INLINE void beep_generate(int count)
41{
42 uint32_t *out = beep_buf;
43 uint32_t s;
44
45 asm volatile (
46 "1: \n"
47 "eor %3, %5, %1, asr #31 \n"
48 "subs %2, %2, #1 \n"
49 "str %3, [%0], #4 \n"
50 "add %1, %1, %4 \n"
51 "bgt 1b \n"
52 : "+r"(out), "+r"(beep_phase), "+r"(count),
53 "=&r"(s)
54 : "r"(beep_step), "r"(beep_amplitude));
55}
56#elif defined (CPU_COLDFIRE)
57static FORCE_INLINE void beep_generate(int count)
58{
59 uint32_t *out = beep_buf;
60 uint32_t s;
61
62 asm volatile (
63 "1: \n"
64 "move.l %1, %3 \n"
65 "add.l %4, %1 \n"
66 "add.l %3, %3 \n"
67 "subx.l %3, %3 \n"
68 "eor.l %5, %3 \n"
69 "move.l %3, (%0)+ \n"
70 "subq.l #1, %2 \n"
71 "bgt.b 1b \n"
72 : "+a"(out), "+d"(beep_phase), "+d"(count),
73 "=&d"(s)
74 : "r"(beep_step), "d"(beep_amplitude));
75}
76#else
77static FORCE_INLINE void beep_generate(int count)
78{
79 uint32_t *out = beep_buf;
80 uint32_t amplitude = beep_amplitude;
81 uint32_t step = beep_step;
82 int32_t phase = beep_phase;
83
84 do
85 {
86 *out++ = (phase >> 31) ^ amplitude;
87 phase += step;
88 }
89 while (--count > 0);
90
91 beep_phase = phase;
92}
93#endif
94
95/* Callback to generate the beep frames - also don't want inlining of
96 call below in beep_play */
97static void __attribute__((noinline)) ICODE_ATTR
98beep_get_more(unsigned char **start, size_t *size)
99{
100 int count = beep_count;
101
102 if (count > 0)
103 {
104 count = MIN(count, BEEP_BUF_COUNT);
105 beep_count -= count;
106 *start = (unsigned char *)beep_buf;
107 *size = count * sizeof(uint32_t);
108 beep_generate(count);
109 }
110}
111
112/* Generates a constant square wave sound with a given frequency in Hertz for
113 a duration in milliseconds */
114void beep_play(unsigned int frequency, unsigned int duration,
115 unsigned int amplitude)
116{
117 mixer_channel_stop(PCM_MIXER_CHAN_BEEP);
118
119 if (frequency == 0 || duration == 0 || amplitude == 0)
120 return;
121
122 if (amplitude > INT16_MAX)
123 amplitude = INT16_MAX;
124
125 /* Setup the parameters for the square wave generator */
126 beep_phase = 0;
127 beep_step = 0xffffffffu / NATIVE_FREQUENCY * frequency;
128 beep_count = NATIVE_FREQUENCY / 1000 * duration;
129 beep_amplitude = amplitude | (amplitude << 16); /* Word:|AMP16|AMP16| */
130
131 /* If it fits - avoid cb overhead */
132 unsigned char *start;
133 size_t size;
134
135 /* Generate first frame here */
136 beep_get_more(&start, &size);
137
138 mixer_channel_set_amplitude(PCM_MIXER_CHAN_BEEP, MIX_AMP_UNITY);
139 mixer_channel_play_data(PCM_MIXER_CHAN_BEEP,
140 beep_count ? beep_get_more : NULL,
141 start, size);
142}
diff --git a/apps/gui/wps.c b/apps/gui/wps.c
index e686fcc533..cbf003adbd 100644
--- a/apps/gui/wps.c
+++ b/apps/gui/wps.c
@@ -121,9 +121,11 @@ char* wps_default_skin(enum screen_type screen)
121 121
122void pause_action(bool may_fade, bool updatewps) 122void pause_action(bool may_fade, bool updatewps)
123{ 123{
124#if CONFIG_CODEC != SWCODEC
124 if (may_fade && global_settings.fade_on_stop) 125 if (may_fade && global_settings.fade_on_stop)
125 fade(false, updatewps); 126 fade(false, updatewps);
126 else 127 else
128#endif
127 audio_pause(); 129 audio_pause();
128 130
129 if (global_settings.pause_rewind) { 131 if (global_settings.pause_rewind) {
@@ -136,16 +138,22 @@ void pause_action(bool may_fade, bool updatewps)
136 - global_settings.pause_rewind * 1000; 138 - global_settings.pause_rewind * 1000;
137 audio_ff_rewind(newpos > 0 ? newpos : 0); 139 audio_ff_rewind(newpos > 0 ? newpos : 0);
138 } 140 }
141
142 (void)may_fade; (void)updatewps;
139} 143}
140 144
141void unpause_action(bool may_fade, bool updatewps) 145void unpause_action(bool may_fade, bool updatewps)
142{ 146{
147#if CONFIG_CODEC != SWCODEC
143 if (may_fade && global_settings.fade_on_stop) 148 if (may_fade && global_settings.fade_on_stop)
144 fade(true, updatewps); 149 fade(true, updatewps);
145 else 150 else
151#endif
146 audio_resume(); 152 audio_resume();
153 (void)may_fade; (void)updatewps;
147} 154}
148 155
156#if CONFIG_CODEC != SWCODEC
149void fade(bool fade_in, bool updatewps) 157void fade(bool fade_in, bool updatewps)
150{ 158{
151 int fp_global_vol = global_settings.volume << 8; 159 int fp_global_vol = global_settings.volume << 8;
@@ -204,6 +212,7 @@ void fade(bool fade_in, bool updatewps)
204 sound_set_volume(global_settings.volume); 212 sound_set_volume(global_settings.volume);
205 } 213 }
206} 214}
215#endif /* SWCODEC */
207 216
208static bool update_onvol_change(enum screen_type screen) 217static bool update_onvol_change(enum screen_type screen)
209{ 218{
@@ -569,7 +578,7 @@ static void play_hop(int direction)
569 { 578 {
570#if CONFIG_CODEC == SWCODEC 579#if CONFIG_CODEC == SWCODEC
571 if(global_settings.beep) 580 if(global_settings.beep)
572 pcmbuf_beep(1000, 150, 1500*global_settings.beep); 581 beep_play(1000, 150, 1500*global_settings.beep);
573#endif 582#endif
574 return; 583 return;
575 } 584 }
@@ -1127,9 +1136,12 @@ long gui_wps_show(void)
1127 status_set_record(false); 1136 status_set_record(false);
1128 status_set_audio(false); 1137 status_set_audio(false);
1129#endif 1138#endif
1139#if CONFIG_CODEC != SWCODEC
1130 if (global_settings.fade_on_stop) 1140 if (global_settings.fade_on_stop)
1131 fade(false, true); 1141 fade(false, true);
1132 1142#else
1143 audio_pause();
1144#endif
1133 if (bookmark) 1145 if (bookmark)
1134 bookmark_autobookmark(true); 1146 bookmark_autobookmark(true);
1135 audio_stop(); 1147 audio_stop();
diff --git a/apps/misc.c b/apps/misc.c
index 1f945c5431..a0817d7e27 100644
--- a/apps/misc.c
+++ b/apps/misc.c
@@ -297,12 +297,13 @@ static bool clean_shutdown(void (*callback)(void *), void *parameter)
297 splashf(0, "%s %s", str(LANG_WARNING_BATTERY_EMPTY), 297 splashf(0, "%s %s", str(LANG_WARNING_BATTERY_EMPTY),
298 str(LANG_SHUTTINGDOWN)); 298 str(LANG_SHUTTINGDOWN));
299 } 299 }
300 300#if CONFIG_CODEC != SWCODEC
301 if (global_settings.fade_on_stop 301 if (global_settings.fade_on_stop
302 && (audio_stat & AUDIO_STATUS_PLAY)) 302 && (audio_stat & AUDIO_STATUS_PLAY))
303 { 303 {
304 fade(false, false); 304 fade(false, false);
305 } 305 }
306#endif
306 307
307 if (batt_safe) /* do not save on critical battery */ 308 if (batt_safe) /* do not save on critical battery */
308 { 309 {
@@ -380,8 +381,10 @@ bool list_stop_handler(void)
380 { 381 {
381 if (!global_settings.party_mode) 382 if (!global_settings.party_mode)
382 { 383 {
384#if CONFIG_CODEC != SWCODEC
383 if (global_settings.fade_on_stop) 385 if (global_settings.fade_on_stop)
384 fade(false, false); 386 fade(false, false);
387#endif
385 bookmark_autobookmark(true); 388 bookmark_autobookmark(true);
386 audio_stop(); 389 audio_stop();
387 ret = true; /* bookmarking can make a refresh necessary */ 390 ret = true; /* bookmarking can make a refresh necessary */
diff --git a/apps/misc.h b/apps/misc.h
index 0b155db1ec..c3c52d13e0 100644
--- a/apps/misc.h
+++ b/apps/misc.h
@@ -100,6 +100,9 @@ int clamp_value_wrap(int value, int max, int min);
100#endif 100#endif
101#endif 101#endif
102 102
103void beep_play(unsigned int frequency, unsigned int duration,
104 unsigned int amplitude);
105
103enum current_activity { 106enum current_activity {
104 ACTIVITY_UNKNOWN = 0, 107 ACTIVITY_UNKNOWN = 0,
105 ACTIVITY_MAINMENU, 108 ACTIVITY_MAINMENU,
diff --git a/apps/pcmbuf.c b/apps/pcmbuf.c
index 7201d3981a..f2f94e3bc9 100644
--- a/apps/pcmbuf.c
+++ b/apps/pcmbuf.c
@@ -23,8 +23,9 @@
23#include "system.h" 23#include "system.h"
24#include "debug.h" 24#include "debug.h"
25#include <kernel.h> 25#include <kernel.h>
26#include "pcmbuf.h"
27#include "pcm.h" 26#include "pcm.h"
27#include "pcm_mixer.h"
28#include "pcmbuf.h"
28#include "playback.h" 29#include "playback.h"
29#include "codec_thread.h" 30#include "codec_thread.h"
30 31
@@ -49,9 +50,6 @@
49#define PCMBUF_MIN_CHUNK 4096 /* We try to never feed a chunk smaller than 50#define PCMBUF_MIN_CHUNK 4096 /* We try to never feed a chunk smaller than
50 this to the DMA */ 51 this to the DMA */
51#define CROSSFADE_BUFSIZE 8192 /* Size of the crossfade buffer */ 52#define CROSSFADE_BUFSIZE 8192 /* Size of the crossfade buffer */
52#define AUX_BUFSIZE 512 /* Size of the aux buffer; can be 512 if no
53 resampling or timestretching is allowed in
54 the aux channel, must be 2048 otherwise */
55 53
56/* number of bytes played per second (sample rate * 2 channels * 2 bytes/sample) */ 54/* number of bytes played per second (sample rate * 2 channels * 2 bytes/sample) */
57#define BYTERATE (NATIVE_FREQUENCY * 4) 55#define BYTERATE (NATIVE_FREQUENCY * 4)
@@ -91,6 +89,12 @@ static struct chunkdesc *first_desc;
91/* Gapless playback */ 89/* Gapless playback */
92static bool track_transition IDATA_ATTR; 90static bool track_transition IDATA_ATTR;
93 91
92/* Fade effect */
93static unsigned int fade_vol = MIX_AMP_UNITY;
94
95/* Voice */
96static bool soft_mode = false;
97
94#ifdef HAVE_CROSSFADE 98#ifdef HAVE_CROSSFADE
95/* Crossfade buffer */ 99/* Crossfade buffer */
96static char *fadebuf IDATA_ATTR; 100static char *fadebuf IDATA_ATTR;
@@ -121,11 +125,6 @@ static size_t last_chunksize IDATA_ATTR;
121static size_t pcmbuf_unplayed_bytes IDATA_ATTR; 125static size_t pcmbuf_unplayed_bytes IDATA_ATTR;
122static size_t pcmbuf_watermark IDATA_ATTR; 126static size_t pcmbuf_watermark IDATA_ATTR;
123 127
124/* Voice */
125static char *voicebuf IDATA_ATTR;
126static struct chunkdesc *mix_chunk IDATA_ATTR;
127static size_t pcmbuf_mix_sample IDATA_ATTR;
128
129static bool low_latency_mode = false; 128static bool low_latency_mode = false;
130static bool flush_pcmbuf = false; 129static bool flush_pcmbuf = false;
131 130
@@ -317,10 +316,12 @@ static void boost_codec_thread(int pcm_fill_state)
317 * Also maintain buffer level above the watermark. */ 316 * Also maintain buffer level above the watermark. */
318static bool prepare_insert(size_t length) 317static bool prepare_insert(size_t length)
319{ 318{
319 bool playing = mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK) != CHANNEL_STOPPED;
320
320 if (low_latency_mode) 321 if (low_latency_mode)
321 { 322 {
322 /* 1/4s latency. */ 323 /* 1/4s latency. */
323 if (!LOW_DATA(1) && pcm_is_playing()) 324 if (!LOW_DATA(1) && playing)
324 return false; 325 return false;
325 } 326 }
326 327
@@ -329,7 +330,7 @@ static bool prepare_insert(size_t length)
329 return false; 330 return false;
330 331
331 /* Maintain the buffer level above the watermark */ 332 /* Maintain the buffer level above the watermark */
332 if (pcm_is_playing()) 333 if (playing)
333 { 334 {
334 /* Only codec thread initiates boost - voice boosts the cpu when playing 335 /* Only codec thread initiates boost - voice boosts the cpu when playing
335 a clip */ 336 a clip */
@@ -351,7 +352,7 @@ static bool prepare_insert(size_t length)
351 } 352 }
352#endif 353#endif
353 } 354 }
354 else /* pcm_is_playing */ 355 else /* !playing */
355 { 356 {
356 /* Boost CPU for pre-buffer */ 357 /* Boost CPU for pre-buffer */
357 trigger_cpu_boost(); 358 trigger_cpu_boost();
@@ -469,11 +470,14 @@ static size_t get_next_required_pcmbuf_size(void)
469 * ...|---------PCMBUF---------|FADEBUF|VOICEBUF|DESCS|... */ 470 * ...|---------PCMBUF---------|FADEBUF|VOICEBUF|DESCS|... */
470size_t pcmbuf_init(unsigned char *bufend) 471size_t pcmbuf_init(unsigned char *bufend)
471{ 472{
473 unsigned char *voicebuf;
474
472 pcmbuf_bufend = bufend; 475 pcmbuf_bufend = bufend;
473 pcmbuf_size = get_next_required_pcmbuf_size(); 476 pcmbuf_size = get_next_required_pcmbuf_size();
474 write_chunk = (struct chunkdesc *)pcmbuf_bufend - 477 write_chunk = (struct chunkdesc *)pcmbuf_bufend -
475 NUM_CHUNK_DESCS(pcmbuf_size); 478 NUM_CHUNK_DESCS(pcmbuf_size);
476 voicebuf = (char *)write_chunk - AUX_BUFSIZE; 479 voicebuf = (unsigned char *)write_chunk -
480 voicebuf_init((unsigned char *)write_chunk);
477#ifdef HAVE_CROSSFADE 481#ifdef HAVE_CROSSFADE
478 fadebuf = voicebuf - CROSSFADE_BUFSIZE; 482 fadebuf = voicebuf - CROSSFADE_BUFSIZE;
479 pcmbuffer = fadebuf - pcmbuf_size; 483 pcmbuffer = fadebuf - pcmbuf_size;
@@ -491,6 +495,8 @@ size_t pcmbuf_init(unsigned char *bufend)
491 495
492 pcmbuf_play_stop(); 496 pcmbuf_play_stop();
493 497
498 pcmbuf_soft_mode(false);
499
494 return pcmbuf_bufend - pcmbuffer; 500 return pcmbuf_bufend - pcmbuffer;
495} 501}
496 502
@@ -572,7 +578,7 @@ bool pcmbuf_start_track_change(bool auto_skip)
572#ifdef HAVE_CROSSFADE 578#ifdef HAVE_CROSSFADE
573 pcmbuf_is_crossfade_active() || 579 pcmbuf_is_crossfade_active() ||
574#endif 580#endif
575 !pcm_is_playing()) 581 mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK) == CHANNEL_STOPPED)
576 { 582 {
577 pcmbuf_play_stop(); 583 pcmbuf_play_stop();
578 pcm_play_unlock(); 584 pcm_play_unlock();
@@ -652,10 +658,6 @@ static void pcmbuf_pcm_callback(unsigned char** start, size_t* size)
652 write_end_chunk->link = pcmbuf_current; 658 write_end_chunk->link = pcmbuf_current;
653 write_end_chunk = pcmbuf_current; 659 write_end_chunk = pcmbuf_current;
654 660
655 /* If we've read over the mix chunk while it's still mixing there */
656 if (pcmbuf_current == mix_chunk)
657 mix_chunk = NULL;
658
659#ifdef HAVE_CROSSFADE 661#ifdef HAVE_CROSSFADE
660 /* If we've read over the crossfade chunk while it's still fading */ 662 /* If we've read over the crossfade chunk while it's still fading */
661 if (pcmbuf_current == crossfade_chunk) 663 if (pcmbuf_current == crossfade_chunk)
@@ -696,23 +698,23 @@ static void pcmbuf_pcm_callback(unsigned char** start, size_t* size)
696/* Force playback */ 698/* Force playback */
697void pcmbuf_play_start(void) 699void pcmbuf_play_start(void)
698{ 700{
699 if (!pcm_is_playing() && pcmbuf_unplayed_bytes && read_chunk != NULL) 701 if (mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK) == CHANNEL_STOPPED &&
702 pcmbuf_unplayed_bytes && read_chunk != NULL)
700 { 703 {
701 logf("pcmbuf_play_start"); 704 logf("pcmbuf_play_start");
702 last_chunksize = read_chunk->size; 705 last_chunksize = read_chunk->size;
703 pcmbuf_unplayed_bytes -= last_chunksize; 706 pcmbuf_unplayed_bytes -= last_chunksize;
704 pcm_play_data(pcmbuf_pcm_callback, 707 mixer_channel_play_data(PCM_MIXER_CHAN_PLAYBACK,
705 read_chunk->addr, last_chunksize); 708 pcmbuf_pcm_callback, NULL, 0);
706 } 709 }
707} 710}
708 711
709void pcmbuf_play_stop(void) 712void pcmbuf_play_stop(void)
710{ 713{
711 logf("pcmbuf_play_stop"); 714 logf("pcmbuf_play_stop");
712 pcm_play_stop(); 715 mixer_channel_stop(PCM_MIXER_CHAN_PLAYBACK);
713 716
714 pcmbuf_unplayed_bytes = 0; 717 pcmbuf_unplayed_bytes = 0;
715 mix_chunk = NULL;
716 if (read_chunk) { 718 if (read_chunk) {
717 write_end_chunk->link = read_chunk; 719 write_end_chunk->link = read_chunk;
718 write_end_chunk = read_end_chunk; 720 write_end_chunk = read_end_chunk;
@@ -737,8 +739,9 @@ void pcmbuf_play_stop(void)
737void pcmbuf_pause(bool pause) 739void pcmbuf_pause(bool pause)
738{ 740{
739 logf("pcmbuf_pause: %s", pause?"pause":"play"); 741 logf("pcmbuf_pause: %s", pause?"pause":"play");
740 if (pcm_is_playing()) 742
741 pcm_play_pause(!pause); 743 if (mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK) != CHANNEL_STOPPED)
744 mixer_channel_play_pause(PCM_MIXER_CHAN_PLAYBACK, !pause);
742 else if (!pause) 745 else if (!pause)
743 pcmbuf_play_start(); 746 pcmbuf_play_start();
744} 747}
@@ -1031,102 +1034,6 @@ bool pcmbuf_is_same_size(void)
1031#endif /* HAVE_CROSSFADE */ 1034#endif /* HAVE_CROSSFADE */
1032 1035
1033 1036
1034/** Voice */
1035
1036/* Returns pcm buffer usage in percents (0 to 100). */
1037static int pcmbuf_usage(void)
1038{
1039 return pcmbuf_unplayed_bytes * 100 / pcmbuf_size;
1040}
1041
1042static int pcmbuf_mix_free(void)
1043{
1044 if (mix_chunk)
1045 {
1046 size_t my_mix_end =
1047 (size_t)&((int16_t *)mix_chunk->addr)[pcmbuf_mix_sample];
1048 size_t my_write_pos = (size_t)&pcmbuffer[pcmbuffer_pos];
1049 if (my_write_pos < my_mix_end)
1050 my_write_pos += pcmbuf_size;
1051 return (my_write_pos - my_mix_end) * 100 / pcmbuf_unplayed_bytes;
1052 }
1053 return 100;
1054}
1055
1056void *pcmbuf_request_voice_buffer(int *count)
1057{
1058 /* A get-it-to-work-for-now hack (audio status could change by
1059 completion) */
1060 if (audio_status() & AUDIO_STATUS_PLAY)
1061 {
1062 if (read_chunk == NULL)
1063 {
1064 return NULL;
1065 }
1066 else if (pcmbuf_usage() >= 10 && pcmbuf_mix_free() >= 30 &&
1067 (mix_chunk || read_chunk->link))
1068 {
1069 *count = MIN(*count, AUX_BUFSIZE/4);
1070 return voicebuf;
1071 }
1072 else
1073 {
1074 return NULL;
1075 }
1076 }
1077 else
1078 {
1079 return pcmbuf_request_buffer(count);
1080 }
1081}
1082
1083void pcmbuf_write_voice_complete(int count)
1084{
1085 /* A get-it-to-work-for-now hack (audio status could have changed) */
1086 if (!(audio_status() & AUDIO_STATUS_PLAY))
1087 {
1088 pcmbuf_write_complete(count);
1089 return;
1090 }
1091
1092 int16_t *ibuf = (int16_t *)voicebuf;
1093 int16_t *obuf;
1094 size_t chunk_samples;
1095
1096 if (mix_chunk == NULL && read_chunk != NULL)
1097 {
1098 mix_chunk = read_chunk->link;
1099 /* Start 1/8s into the next chunk */
1100 pcmbuf_mix_sample = BYTERATE / 16;
1101 }
1102
1103 if (!mix_chunk)
1104 return;
1105
1106 obuf = (int16_t *)mix_chunk->addr;
1107 chunk_samples = mix_chunk->size / sizeof (int16_t);
1108
1109 count <<= 1;
1110
1111 while (count-- > 0)
1112 {
1113 int32_t sample = *ibuf++;
1114
1115 if (pcmbuf_mix_sample >= chunk_samples)
1116 {
1117 mix_chunk = mix_chunk->link;
1118 if (!mix_chunk)
1119 return;
1120 pcmbuf_mix_sample = 0;
1121 obuf = (int16_t *)mix_chunk->addr;
1122 chunk_samples = mix_chunk->size / 2;
1123 }
1124 sample += obuf[pcmbuf_mix_sample] >> 2;
1125 obuf[pcmbuf_mix_sample++] = clip_sample_16(sample);
1126 }
1127}
1128
1129
1130/** Debug menu, other metrics */ 1037/** Debug menu, other metrics */
1131 1038
1132/* Amount of bytes left in the buffer. */ 1039/* Amount of bytes left in the buffer. */
@@ -1174,6 +1081,71 @@ unsigned char *pcmbuf_get_meminfo(size_t *length)
1174#endif 1081#endif
1175 1082
1176 1083
1084/** Fading and channel volume control */
1085
1086/* Sync the channel amplitude to all states */
1087static void pcmbuf_update_volume(void)
1088{
1089 unsigned int vol = fade_vol;
1090
1091 if (soft_mode)
1092 vol >>= 2;
1093
1094 mixer_channel_set_amplitude(PCM_MIXER_CHAN_PLAYBACK, vol);
1095}
1096
1097/* Quiet-down the channel if 'shhh' is true or else play at normal level */
1098void pcmbuf_soft_mode(bool shhh)
1099{
1100 soft_mode = shhh;
1101 pcmbuf_update_volume();
1102}
1103
1104/* Fade channel in or out */
1105void pcmbuf_fade(bool fade, bool in)
1106{
1107 if (!fade)
1108 {
1109 /* Simply set the level */
1110 fade_vol = in ? MIX_AMP_UNITY : MIX_AMP_MUTE;
1111 }
1112 else
1113 {
1114 /* Start from the opposing end */
1115 fade_vol = in ? MIX_AMP_MUTE : MIX_AMP_UNITY;
1116 }
1117
1118 pcmbuf_update_volume();
1119
1120 if (fade)
1121 {
1122 /* Do this on thread for now */
1123 int old_prio = thread_set_priority(thread_self(), PRIORITY_REALTIME);
1124
1125 while (1)
1126 {
1127 /* Linear fade actually sounds better */
1128 if (in)
1129 fade_vol += MIN(MIX_AMP_UNITY/16, MIX_AMP_UNITY - fade_vol);
1130 else
1131 fade_vol -= MIN(MIX_AMP_UNITY/16, fade_vol - MIX_AMP_MUTE);
1132
1133 pcmbuf_update_volume();
1134
1135 if (fade_vol > MIX_AMP_MUTE && fade_vol < MIX_AMP_UNITY)
1136 {
1137 sleep(0);
1138 continue;
1139 }
1140
1141 break;
1142 }
1143
1144 thread_set_priority(thread_self(), old_prio);
1145 }
1146}
1147
1148
1177/** Misc */ 1149/** Misc */
1178 1150
1179bool pcmbuf_is_lowdata(void) 1151bool pcmbuf_is_lowdata(void)
@@ -1201,107 +1173,6 @@ void pcmbuf_set_low_latency(bool state)
1201 1173
1202unsigned long pcmbuf_get_latency(void) 1174unsigned long pcmbuf_get_latency(void)
1203{ 1175{
1204 return (pcmbuf_unplayed_bytes + pcm_get_bytes_waiting()) * 1000 / BYTERATE; 1176 return (pcmbuf_unplayed_bytes +
1205} 1177 mixer_channel_get_bytes_waiting(PCM_MIXER_CHAN_PLAYBACK)) * 1000 / BYTERATE;
1206
1207#ifndef HAVE_HARDWARE_BEEP
1208#define MINIBUF_SAMPLES (NATIVE_FREQUENCY / 1000 * KEYCLICK_DURATION)
1209#define MINIBUF_SIZE (MINIBUF_SAMPLES*4)
1210
1211/* Generates a constant square wave sound with a given frequency
1212 in Hertz for a duration in milliseconds. */
1213void pcmbuf_beep(unsigned int frequency, size_t duration, int amplitude)
1214{
1215 unsigned int step = 0xffffffffu / NATIVE_FREQUENCY * frequency;
1216 int32_t phase = 0;
1217 int16_t *bufptr, *bufstart, *bufend;
1218 int32_t sample;
1219 int nsamples = NATIVE_FREQUENCY / 1000 * duration;
1220 bool mix = read_chunk != NULL && read_chunk->link != NULL;
1221 int i;
1222
1223 bufend = SKIPBYTES((int16_t *)pcmbuffer, pcmbuf_size);
1224
1225 /* Find the insertion point and set bufstart to the start of it */
1226 if (mix)
1227 {
1228 /* Get the currently playing chunk at the current position. */
1229 bufstart = (int16_t *)pcm_play_dma_get_peak_buffer(&i);
1230
1231 /* If above isn't implemented or pcm is stopped, no beepeth. */
1232 if (!bufstart || !pcm_is_playing())
1233 return;
1234
1235 /* Give 5ms clearance. */
1236 bufstart += BYTERATE / 200;
1237
1238#ifdef HAVE_PCM_DMA_ADDRESS
1239 /* Returned peak addresses are DMA addresses */
1240 bufend = pcm_dma_addr(bufend);
1241#endif
1242
1243 /* Wrapped above? */
1244 if (bufstart >= bufend)
1245 bufstart -= pcmbuf_size;
1246
1247 /* NOTE: On some targets using hardware DMA, cache range flushing may
1248 * be required or the writes may not be picked up by the controller.
1249 * An incremental flush should be done periodically during the mixdown. */
1250 }
1251 else if (nsamples <= MINIBUF_SAMPLES)
1252 {
1253 static int16_t minibuf[MINIBUF_SAMPLES*2] __attribute__((aligned(4)));
1254 /* Use mini buffer */
1255 bufstart = minibuf;
1256 bufend = SKIPBYTES(bufstart, MINIBUF_SIZE);
1257 }
1258 else if (!audio_buffer_state_trashed())
1259 {
1260 /* Use pcmbuffer */
1261 bufstart = (int16_t *)pcmbuffer;
1262 }
1263 else
1264 {
1265 /* No place */
1266 return;
1267 }
1268
1269 bufptr = bufstart;
1270
1271 /* Mix square wave into buffer */
1272 for (i = 0; i < nsamples; ++i)
1273 {
1274 int32_t amp = (phase >> 31) ^ (int32_t)amplitude;
1275 sample = mix ? *bufptr : 0;
1276 *bufptr++ = clip_sample_16(sample + amp);
1277 if (bufptr >= bufend)
1278 bufptr = (int16_t *)pcmbuffer;
1279 sample = mix ? *bufptr : 0;
1280 *bufptr++ = clip_sample_16(sample + amp);
1281 if (bufptr >= bufend)
1282 bufptr = (int16_t *)pcmbuffer;
1283
1284 phase += step;
1285 }
1286
1287 pcm_play_lock();
1288#ifdef HAVE_RECORDING
1289 pcm_rec_lock();
1290#endif
1291
1292 /* Kick off playback if required and it won't interfere */
1293 if (!pcm_is_playing()
1294#ifdef HAVE_RECORDING
1295 && !pcm_is_recording()
1296#endif
1297 )
1298 {
1299 pcm_play_data(NULL, (unsigned char *)bufstart, nsamples * 4);
1300 }
1301
1302 pcm_play_unlock();
1303#ifdef HAVE_RECORDING
1304 pcm_rec_unlock();
1305#endif
1306} 1178}
1307#endif /* HAVE_HARDWARE_BEEP */
diff --git a/apps/pcmbuf.h b/apps/pcmbuf.h
index b7bf8c2b16..b7f5a3c2d6 100644
--- a/apps/pcmbuf.h
+++ b/apps/pcmbuf.h
@@ -64,9 +64,10 @@ unsigned char *pcmbuf_get_meminfo(size_t *length);
64#endif 64#endif
65 65
66/* Misc */ 66/* Misc */
67void pcmbuf_fade(bool fade, bool in);
68void pcmbuf_soft_mode(bool shhh);
67bool pcmbuf_is_lowdata(void); 69bool pcmbuf_is_lowdata(void);
68void pcmbuf_set_low_latency(bool state); 70void pcmbuf_set_low_latency(bool state);
69unsigned long pcmbuf_get_latency(void); 71unsigned long pcmbuf_get_latency(void);
70void pcmbuf_beep(unsigned int frequency, size_t duration, int amplitude);
71 72
72#endif 73#endif
diff --git a/apps/playback.c b/apps/playback.c
index 2775e8a95b..cbb94a9d22 100644
--- a/apps/playback.c
+++ b/apps/playback.c
@@ -39,6 +39,7 @@
39#include "abrepeat.h" 39#include "abrepeat.h"
40#include "pcmbuf.h" 40#include "pcmbuf.h"
41#include "playback.h" 41#include "playback.h"
42#include "misc.h"
42 43
43#ifdef HAVE_TAGCACHE 44#ifdef HAVE_TAGCACHE
44#include "tagcache.h" 45#include "tagcache.h"
@@ -2360,6 +2361,9 @@ static void audio_start_playback(size_t offset, unsigned int flags)
2360#ifndef PLATFORM_HAS_VOLUME_CHANGE 2361#ifndef PLATFORM_HAS_VOLUME_CHANGE
2361 sound_set_volume(global_settings.volume); 2362 sound_set_volume(global_settings.volume);
2362#endif 2363#endif
2364 /* Be sure channel is audible */
2365 pcmbuf_fade(false, true);
2366
2363 /* Update our state */ 2367 /* Update our state */
2364 play_status = PLAY_PLAYING; 2368 play_status = PLAY_PLAYING;
2365 } 2369 }
@@ -2413,6 +2417,8 @@ static void audio_stop_playback(void)
2413 if (play_status == PLAY_STOPPED) 2417 if (play_status == PLAY_STOPPED)
2414 return; 2418 return;
2415 2419
2420 pcmbuf_fade(global_settings.fade_on_stop, false);
2421
2416 /* Stop the codec and unload it */ 2422 /* Stop the codec and unload it */
2417 halt_decoding_track(true); 2423 halt_decoding_track(true);
2418 pcmbuf_play_stop(); 2424 pcmbuf_play_stop();
@@ -2452,6 +2458,11 @@ static void audio_on_pause(bool pause)
2452 if (play_status == PLAY_STOPPED || pause == (play_status == PLAY_PAUSED)) 2458 if (play_status == PLAY_STOPPED || pause == (play_status == PLAY_PAUSED))
2453 return; 2459 return;
2454 2460
2461 bool const do_fade = global_settings.fade_on_stop;
2462
2463 if (pause)
2464 pcmbuf_fade(do_fade, false);
2465
2455 if (!ff_rw_mode) 2466 if (!ff_rw_mode)
2456 { 2467 {
2457 /* Not in ff/rw mode - may set the state (otherwise this could make 2468 /* Not in ff/rw mode - may set the state (otherwise this could make
@@ -2459,6 +2470,9 @@ static void audio_on_pause(bool pause)
2459 pcmbuf_pause(pause); 2470 pcmbuf_pause(pause);
2460 } 2471 }
2461 2472
2473 if (!pause)
2474 pcmbuf_fade(do_fade, true);
2475
2462 play_status = pause ? PLAY_PAUSED : PLAY_PLAYING; 2476 play_status = pause ? PLAY_PAUSED : PLAY_PLAYING;
2463 2477
2464 if (!pause && codec_skip_pending) 2478 if (!pause && codec_skip_pending)
@@ -3170,7 +3184,7 @@ void audio_pcmbuf_track_change(bool pcmbuf)
3170/* May pcmbuf start PCM playback when the buffer is full enough? */ 3184/* May pcmbuf start PCM playback when the buffer is full enough? */
3171bool audio_pcmbuf_may_play(void) 3185bool audio_pcmbuf_may_play(void)
3172{ 3186{
3173 return play_status != PLAY_PAUSED && !ff_rw_mode; 3187 return play_status == PLAY_PLAYING && !ff_rw_mode;
3174} 3188}
3175 3189
3176 3190
@@ -3339,7 +3353,7 @@ void audio_skip(int offset)
3339 skip_offset = accum; 3353 skip_offset = accum;
3340 3354
3341 if (global_settings.beep) 3355 if (global_settings.beep)
3342 pcmbuf_beep(2000, 100, 2500*global_settings.beep); 3356 beep_play(2000, 100, 2500*global_settings.beep);
3343 3357
3344 LOGFQUEUE("audio > audio Q_AUDIO_SKIP %d", offset); 3358 LOGFQUEUE("audio > audio Q_AUDIO_SKIP %d", offset);
3345 3359
@@ -3360,7 +3374,7 @@ void audio_skip(int offset)
3360 { 3374 {
3361 /* No more tracks */ 3375 /* No more tracks */
3362 if (global_settings.beep) 3376 if (global_settings.beep)
3363 pcmbuf_beep(1000, 100, 1500*global_settings.beep); 3377 beep_play(1000, 100, 1500*global_settings.beep);
3364 } 3378 }
3365 3379
3366 id3_mutex_unlock(); 3380 id3_mutex_unlock();
diff --git a/apps/plugin.c b/apps/plugin.c
index d9f7c4e24c..10cb9263a4 100644
--- a/apps/plugin.c
+++ b/apps/plugin.c
@@ -551,7 +551,7 @@ static const struct plugin_api rockbox_api = {
551 pcm_get_peak_buffer, 551 pcm_get_peak_buffer,
552 pcm_play_lock, 552 pcm_play_lock,
553 pcm_play_unlock, 553 pcm_play_unlock,
554 pcmbuf_beep, 554 beep_play,
555#ifdef HAVE_RECORDING 555#ifdef HAVE_RECORDING
556 &rec_freq_sampr[0], 556 &rec_freq_sampr[0],
557 pcm_init_recording, 557 pcm_init_recording,
@@ -778,6 +778,9 @@ static const struct plugin_api rockbox_api = {
778 778
779 /* new stuff at the end, sort into place next time 779 /* new stuff at the end, sort into place next time
780 the API gets incompatible */ 780 the API gets incompatible */
781
782 mixer_channel_status,
783 mixer_channel_get_buffer,
781}; 784};
782 785
783int plugin_load(const char* plugin, const void* parameter) 786int plugin_load(const char* plugin, const void* parameter)
diff --git a/apps/plugin.h b/apps/plugin.h
index f15c626667..113296c19a 100644
--- a/apps/plugin.h
+++ b/apps/plugin.h
@@ -65,6 +65,7 @@ void* plugin_get_buffer(size_t *buffer_size);
65#include "misc.h" 65#include "misc.h"
66#include "filefuncs.h" 66#include "filefuncs.h"
67#if (CONFIG_CODEC == SWCODEC) 67#if (CONFIG_CODEC == SWCODEC)
68#include "pcm_mixer.h"
68#include "dsp.h" 69#include "dsp.h"
69#include "codecs.h" 70#include "codecs.h"
70#include "playback.h" 71#include "playback.h"
@@ -145,7 +146,7 @@ void* plugin_get_buffer(size_t *buffer_size);
145#define PLUGIN_MAGIC 0x526F634B /* RocK */ 146#define PLUGIN_MAGIC 0x526F634B /* RocK */
146 147
147/* increase this every time the api struct changes */ 148/* increase this every time the api struct changes */
148#define PLUGIN_API_VERSION 205 149#define PLUGIN_API_VERSION 206
149 150
150/* update this to latest version if a change to the api struct breaks 151/* update this to latest version if a change to the api struct breaks
151 backwards compatibility (and please take the opportunity to sort in any 152 backwards compatibility (and please take the opportunity to sort in any
@@ -635,9 +636,8 @@ struct plugin_api {
635 const void* (*pcm_get_peak_buffer)(int *count); 636 const void* (*pcm_get_peak_buffer)(int *count);
636 void (*pcm_play_lock)(void); 637 void (*pcm_play_lock)(void);
637 void (*pcm_play_unlock)(void); 638 void (*pcm_play_unlock)(void);
638 void (*pcmbuf_beep)(unsigned int frequency, 639 void (*beep_play)(unsigned int frequency, unsigned int duration,
639 size_t duration, 640 unsigned int amplitude);
640 int amplitude);
641#ifdef HAVE_RECORDING 641#ifdef HAVE_RECORDING
642 const unsigned long *rec_freq_sampr; 642 const unsigned long *rec_freq_sampr;
643 void (*pcm_init_recording)(void); 643 void (*pcm_init_recording)(void);
@@ -908,6 +908,8 @@ struct plugin_api {
908 908
909 /* new stuff at the end, sort into place next time 909 /* new stuff at the end, sort into place next time
910 the API gets incompatible */ 910 the API gets incompatible */
911 enum channel_status (*mixer_channel_status)(enum pcm_mixer_channel channel);
912 void * (*mixer_channel_get_buffer)(enum pcm_mixer_channel channel, int *count);
911}; 913};
912 914
913/* plugin header */ 915/* plugin header */
diff --git a/apps/plugins/fft/fft.c b/apps/plugins/fft/fft.c
index b6b1e2fead..a920f8c7f1 100644
--- a/apps/plugins/fft/fft.c
+++ b/apps/plugins/fft/fft.c
@@ -1137,6 +1137,10 @@ static void draw_spectrogram_horizontal(void)
1137/********************* End of plotting functions (modes) *********************/ 1137/********************* End of plotting functions (modes) *********************/
1138 1138
1139/****************************** FFT functions ********************************/ 1139/****************************** FFT functions ********************************/
1140static bool is_playing(void)
1141{
1142 return rb->mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK) == CHANNEL_PLAYING;
1143}
1140 1144
1141/** functions use in single/multi configuration **/ 1145/** functions use in single/multi configuration **/
1142static inline bool fft_init_fft_lib(void) 1146static inline bool fft_init_fft_lib(void)
@@ -1156,7 +1160,8 @@ static inline bool fft_init_fft_lib(void)
1156static inline bool fft_get_fft(void) 1160static inline bool fft_get_fft(void)
1157{ 1161{
1158 int count; 1162 int count;
1159 int16_t *value = (int16_t *) rb->pcm_get_peak_buffer(&count); 1163 int16_t *value =
1164 (int16_t *) rb->mixer_channel_get_buffer(PCM_MIXER_CHAN_PLAYBACK, &count);
1160 /* This block can introduce discontinuities in our data. Meaning, the 1165 /* This block can introduce discontinuities in our data. Meaning, the
1161 * FFT will not be done a continuous segment of the signal. Which can 1166 * FFT will not be done a continuous segment of the signal. Which can
1162 * be bad. Or not. 1167 * be bad. Or not.
@@ -1214,7 +1219,7 @@ static void fft_thread_entry(void)
1214 1219
1215 while(fft_thread_run) 1220 while(fft_thread_run)
1216 { 1221 {
1217 if (!rb->pcm_is_playing()) 1222 if (!is_playing())
1218 { 1223 {
1219 rb->sleep(HZ/5); 1224 rb->sleep(HZ/5);
1220 continue; 1225 continue;
@@ -1296,7 +1301,7 @@ static void fft_close_fft(void)
1296 * target uses IRAM */ 1301 * target uses IRAM */
1297static bool fft_have_fft(void) 1302static bool fft_have_fft(void)
1298{ 1303{
1299 return rb->pcm_is_playing() && fft_get_fft(); 1304 return is_playing() && fft_get_fft();
1300} 1305}
1301 1306
1302static inline void fft_free_fft_output(void) 1307static inline void fft_free_fft_output(void)
@@ -1366,7 +1371,7 @@ enum plugin_status plugin_start(const void* parameter)
1366 { 1371 {
1367 int timeout; 1372 int timeout;
1368 1373
1369 if(!rb->pcm_is_playing()) 1374 if(!is_playing())
1370 { 1375 {
1371 showing_warning = true; 1376 showing_warning = true;
1372 mylcd_clear_display(); 1377 mylcd_clear_display();
diff --git a/apps/recorder/keyboard.c b/apps/recorder/keyboard.c
index 1b2d76eab2..5f2a32c367 100644
--- a/apps/recorder/keyboard.c
+++ b/apps/recorder/keyboard.c
@@ -1231,7 +1231,7 @@ static void kbd_move_cursor(struct edit_state *state, int dir)
1231 state->editpos -= dir; 1231 state->editpos -= dir;
1232#if CONFIG_CODEC == SWCODEC 1232#if CONFIG_CODEC == SWCODEC
1233 if (global_settings.talk_menu) 1233 if (global_settings.talk_menu)
1234 pcmbuf_beep(1000, 150, 1500); 1234 beep_play(1000, 150, 1500);
1235#endif 1235#endif
1236 } 1236 }
1237} 1237}
diff --git a/apps/voice_thread.c b/apps/voice_thread.c
index 6683fcc067..3318bbecb3 100644
--- a/apps/voice_thread.c
+++ b/apps/voice_thread.c
@@ -27,6 +27,8 @@
27#include "audio.h" 27#include "audio.h"
28#include "playback.h" 28#include "playback.h"
29#include "pcmbuf.h" 29#include "pcmbuf.h"
30#include "pcm.h"
31#include "pcm_mixer.h"
30#include "codecs/libspeex/speex/speex.h" 32#include "codecs/libspeex/speex/speex.h"
31 33
32/* Define any of these as "1" and uncomment the LOGF_ENABLE line to log 34/* Define any of these as "1" and uncomment the LOGF_ENABLE line to log
@@ -53,24 +55,50 @@
53#define IBSS_ATTR_VOICE_STACK IBSS_ATTR 55#define IBSS_ATTR_VOICE_STACK IBSS_ATTR
54#endif 56#endif
55 57
58/* Minimum priority needs to be a bit elevated since voice has fairly low
59 latency */
60#define PRIORITY_VOICE (PRIORITY_PLAYBACK-4)
61
56#define VOICE_FRAME_SIZE 320 /* Samples / frame */ 62#define VOICE_FRAME_SIZE 320 /* Samples / frame */
57#define VOICE_SAMPLE_RATE 16000 /* Sample rate in HZ */ 63#define VOICE_SAMPLE_RATE 16000 /* Sample rate in HZ */
58#define VOICE_SAMPLE_DEPTH 16 /* Sample depth in bits */ 64#define VOICE_SAMPLE_DEPTH 16 /* Sample depth in bits */
59 65
60/* Voice thread variables */ 66/* Voice thread variables */
61static unsigned int voice_thread_id = 0; 67static unsigned int voice_thread_id = 0;
62static long voice_stack[(DEFAULT_STACK_SIZE + 0x3C0)/sizeof(long)] IBSS_ATTR_VOICE_STACK; 68#ifdef CPU_COLDFIRE
69/* ISR uses any available stack - need a bit more room */
70#define VOICE_STACK_EXTRA 0x400
71#else
72#define VOICE_STACK_EXTRA 0x3c0
73#endif
74static long voice_stack[(DEFAULT_STACK_SIZE + VOICE_STACK_EXTRA)/sizeof(long)]
75 IBSS_ATTR_VOICE_STACK;
63static const char voice_thread_name[] = "voice"; 76static const char voice_thread_name[] = "voice";
64 77
65/* Voice thread synchronization objects */ 78/* Voice thread synchronization objects */
66static struct event_queue voice_queue SHAREDBSS_ATTR; 79static struct event_queue voice_queue SHAREDBSS_ATTR;
67static struct mutex voice_mutex SHAREDBSS_ATTR;
68static struct queue_sender_list voice_queue_sender_list SHAREDBSS_ATTR; 80static struct queue_sender_list voice_queue_sender_list SHAREDBSS_ATTR;
69static bool voice_done SHAREDDATA_ATTR = true; 81static bool voice_done SHAREDDATA_ATTR = true;
70 82
71/* Buffer for decoded samples */ 83/* Buffer for decoded samples */
72static spx_int16_t voice_output_buf[VOICE_FRAME_SIZE] CACHEALIGN_ATTR; 84static spx_int16_t voice_output_buf[VOICE_FRAME_SIZE] CACHEALIGN_ATTR;
73 85
86#define VOICE_PCM_FRAME_COUNT ((NATIVE_FREQUENCY*VOICE_FRAME_SIZE + \
87 VOICE_SAMPLE_RATE) / VOICE_SAMPLE_RATE)
88#define VOICE_PCM_FRAME_SIZE (VOICE_PCM_FRAME_COUNT*4)
89
90/* Default number of native-frequency PCM frames to queue - adjust as
91 necessary per-target */
92#define VOICE_FRAMES 3
93
94/* Might have lookahead and be skipping samples, so size is needed */
95static size_t voicebuf_sizes[VOICE_FRAMES];
96static uint32_t (* voicebuf)[VOICE_PCM_FRAME_COUNT];
97static unsigned int cur_buf_in, cur_buf_out;
98
99/* A delay to not bring audio back to normal level too soon */
100#define QUIET_COUNT 3
101
74enum voice_thread_states 102enum voice_thread_states
75{ 103{
76 TSTATE_STOPPED = 0, /* Voice thread is stopped and awaiting commands */ 104 TSTATE_STOPPED = 0, /* Voice thread is stopped and awaiting commands */
@@ -83,7 +111,6 @@ enum voice_thread_messages
83 Q_VOICE_NULL = 0, /* A message for thread sync - no effect on state */ 111 Q_VOICE_NULL = 0, /* A message for thread sync - no effect on state */
84 Q_VOICE_PLAY, /* Play a clip */ 112 Q_VOICE_PLAY, /* Play a clip */
85 Q_VOICE_STOP, /* Stop current clip */ 113 Q_VOICE_STOP, /* Stop current clip */
86 Q_VOICE_STATE, /* Query playing state */
87}; 114};
88 115
89/* Structure to store clip data callback info */ 116/* Structure to store clip data callback info */
@@ -98,7 +125,7 @@ struct voice_info
98 * internal functions */ 125 * internal functions */
99struct voice_thread_data 126struct voice_thread_data
100{ 127{
101 int state; /* Thread state (TSTATE_*) */ 128 volatile int state; /* Thread state (TSTATE_*) */
102 struct queue_event ev; /* Last queue event pulled from queue */ 129 struct queue_event ev; /* Last queue event pulled from queue */
103 void *st; /* Decoder instance */ 130 void *st; /* Decoder instance */
104 SpeexBits bits; /* Bit cursor */ 131 SpeexBits bits; /* Bit cursor */
@@ -107,33 +134,79 @@ struct voice_thread_data
107 const char *src[2]; /* Current output buffer pointers */ 134 const char *src[2]; /* Current output buffer pointers */
108 int lookahead; /* Number of samples to drop at start of clip */ 135 int lookahead; /* Number of samples to drop at start of clip */
109 int count; /* Count of samples remaining to send to PCM */ 136 int count; /* Count of samples remaining to send to PCM */
137 int quiet_counter; /* Countdown until audio goes back to normal */
110}; 138};
111 139
112/* Audio playback is in a playing state? */ 140/* Number of frames in queue */
113static inline bool playback_is_playing(void) 141static inline int voice_unplayed_frames(void)
114{ 142{
115 return (audio_status() & AUDIO_STATUS_PLAY) != 0; 143 return cur_buf_in - cur_buf_out;
144}
145
146/* Mixer channel callback */
147static void voice_pcm_callback(unsigned char **start, size_t *size)
148{
149 if (voice_unplayed_frames() == 0)
150 return; /* Done! */
151
152 unsigned int i = ++cur_buf_out % VOICE_FRAMES;
153
154 *start = (unsigned char *)voicebuf[i];
155 *size = voicebuf_sizes[i];
156}
157
158/* Start playback of voice channel if not already playing */
159static void voice_start_playback(void)
160{
161 if (mixer_channel_status(PCM_MIXER_CHAN_VOICE) != CHANNEL_STOPPED)
162 return;
163
164 unsigned int i = cur_buf_out % VOICE_FRAMES;
165 mixer_channel_play_data(PCM_MIXER_CHAN_VOICE, voice_pcm_callback,
166 (unsigned char *)voicebuf[i], voicebuf_sizes[i]);
167}
168
169/* Stop the voice channel */
170static void voice_stop_playback(void)
171{
172 mixer_channel_stop(PCM_MIXER_CHAN_VOICE);
173 cur_buf_in = cur_buf_out = 0;
174}
175
176/* Grab a free PCM frame */
177static uint32_t * voice_buf_get(void)
178{
179 if (voice_unplayed_frames() >= VOICE_FRAMES)
180 {
181 /* Full */
182 voice_start_playback();
183 return NULL;
184 }
185
186 return voicebuf[cur_buf_in % VOICE_FRAMES];
187}
188
189/* Commit a frame returned by voice_buf_get and set the actual size */
190static void voice_buf_commit(size_t size)
191{
192 voicebuf_sizes[cur_buf_in++ % VOICE_FRAMES] = size;
116} 193}
117 194
118/* Stop any current clip and start playing a new one */ 195/* Stop any current clip and start playing a new one */
119void mp3_play_data(const unsigned char* start, int size, 196void mp3_play_data(const unsigned char* start, int size,
120 pcm_play_callback_type get_more) 197 pcm_play_callback_type get_more)
121{ 198{
122 /* Shared struct to get data to the thread - once it replies, it has
123 * safely cached it in its own private data */
124 static struct voice_info voice_clip SHAREDBSS_ATTR;
125
126 if (get_more != NULL && start != NULL && (ssize_t)size > 0) 199 if (get_more != NULL && start != NULL && (ssize_t)size > 0)
127 { 200 {
128 mutex_lock(&voice_mutex); 201 struct voice_info voice_clip =
202 {
203 .get_more = get_more,
204 .start = (unsigned char *)start,
205 .size = size,
206 };
129 207
130 voice_clip.get_more = get_more;
131 voice_clip.start = (unsigned char *)start;
132 voice_clip.size = size;
133 LOGFQUEUE("mp3 >| voice Q_VOICE_PLAY"); 208 LOGFQUEUE("mp3 >| voice Q_VOICE_PLAY");
134 queue_send(&voice_queue, Q_VOICE_PLAY, (intptr_t)&voice_clip); 209 queue_send(&voice_queue, Q_VOICE_PLAY, (intptr_t)&voice_clip);
135
136 mutex_unlock(&voice_mutex);
137 } 210 }
138} 211}
139 212
@@ -143,11 +216,8 @@ void mp3_play_stop(void)
143 if(!audio_is_thread_ready()) 216 if(!audio_is_thread_ready())
144 return; 217 return;
145 218
146 mutex_lock(&voice_mutex); /* Sync against voice_stop */ 219 LOGFQUEUE("mp3 >| voice Q_VOICE_STOP");
147 LOGFQUEUE("mp3 >| voice Q_VOICE_STOP: 1"); 220 queue_send(&voice_queue, Q_VOICE_STOP, 0);
148 queue_send(&voice_queue, Q_VOICE_STOP, 1);
149
150 mutex_unlock(&voice_mutex);
151} 221}
152 222
153void mp3_play_pause(bool play) 223void mp3_play_pause(bool play)
@@ -156,36 +226,19 @@ void mp3_play_pause(bool play)
156 (void)play; 226 (void)play;
157} 227}
158 228
159/* Tell is voice is still in a playing state */ 229/* Tell if voice is still in a playing state */
160bool mp3_is_playing(void) 230bool mp3_is_playing(void)
161{ 231{
162 /* TODO: Implement a timeout or state query function for event objects */ 232 return !voice_done;
163 LOGFQUEUE("mp3 >| voice Q_VOICE_STATE");
164 int state = queue_send(&voice_queue, Q_VOICE_STATE, 0);
165 return state != TSTATE_STOPPED;
166} 233}
167 234
168/* This function is meant to be used by the buffer request functions to 235/* This function is meant to be used by the buffer request functions to
169 ensure the codec is no longer active */ 236 ensure the codec is no longer active */
170void voice_stop(void) 237void voice_stop(void)
171{ 238{
172 mutex_lock(&voice_mutex);
173
174 /* Stop the output and current clip */
175 mp3_play_stop();
176
177 /* Careful if using sync objects in talk.c - make sure locking order is
178 * observed with one or the other always granted first */
179
180 /* Unqueue all future clips */ 239 /* Unqueue all future clips */
181 talk_force_shutup(); 240 talk_force_shutup();
182 241}
183 /* Wait for any final queue_post to be processed */
184 LOGFQUEUE("mp3 >| voice Q_VOICE_NULL");
185 queue_send(&voice_queue, Q_VOICE_NULL, 0);
186
187 mutex_unlock(&voice_mutex);
188} /* voice_stop */
189 242
190/* Wait for voice to finish speaking. */ 243/* Wait for voice to finish speaking. */
191void voice_wait(void) 244void voice_wait(void)
@@ -194,8 +247,7 @@ void voice_wait(void)
194 * new clip by the time we wait. This should be resolvable if conditions 247 * new clip by the time we wait. This should be resolvable if conditions
195 * ever require knowing the very clip you requested has finished. */ 248 * ever require knowing the very clip you requested has finished. */
196 249
197 /* Wait for PCM buffer to be exhausted. Works only if not playing. */ 250 while (!voice_done)
198 while(!voice_done || (!playback_is_playing() && pcm_is_playing()))
199 sleep(1); 251 sleep(1);
200} 252}
201 253
@@ -211,6 +263,9 @@ static void voice_data_init(struct voice_thread_data *td)
211 dsp_configure(td->dsp, DSP_SET_FREQUENCY, VOICE_SAMPLE_RATE); 263 dsp_configure(td->dsp, DSP_SET_FREQUENCY, VOICE_SAMPLE_RATE);
212 dsp_configure(td->dsp, DSP_SET_SAMPLE_DEPTH, VOICE_SAMPLE_DEPTH); 264 dsp_configure(td->dsp, DSP_SET_SAMPLE_DEPTH, VOICE_SAMPLE_DEPTH);
213 dsp_configure(td->dsp, DSP_SET_STEREO_MODE, STEREO_MONO); 265 dsp_configure(td->dsp, DSP_SET_STEREO_MODE, STEREO_MONO);
266
267 mixer_channel_set_amplitude(PCM_MIXER_CHAN_VOICE, MIX_AMP_UNITY);
268 td->quiet_counter = 0;
214} 269}
215 270
216/* Voice thread message processing */ 271/* Voice thread message processing */
@@ -222,7 +277,6 @@ static void voice_message(struct voice_thread_data *td)
222 { 277 {
223 case Q_VOICE_PLAY: 278 case Q_VOICE_PLAY:
224 LOGFQUEUE("voice < Q_VOICE_PLAY"); 279 LOGFQUEUE("voice < Q_VOICE_PLAY");
225 /* Put up a block for completion signal */
226 voice_done = false; 280 voice_done = false;
227 281
228 /* Copy the clip info */ 282 /* Copy the clip info */
@@ -239,12 +293,17 @@ static void voice_message(struct voice_thread_data *td)
239 /* Boost CPU now */ 293 /* Boost CPU now */
240 trigger_cpu_boost(); 294 trigger_cpu_boost();
241 } 295 }
242 else if (!playback_is_playing()) 296 else
243 { 297 {
244 /* Just voice, stop any clip still playing */ 298 /* Stop any clip still playing */
245 pcmbuf_play_stop(); 299 voice_stop_playback();
246 } 300 }
247 301
302 /* Make audio play more softly and set delay to return to normal
303 playback level */
304 pcmbuf_soft_mode(true);
305 td->quiet_counter = QUIET_COUNT;
306
248 /* Clean-start the decoder */ 307 /* Clean-start the decoder */
249 td->st = speex_decoder_init(&speex_wb_mode); 308 td->st = speex_decoder_init(&speex_wb_mode);
250 309
@@ -255,30 +314,32 @@ static void voice_message(struct voice_thread_data *td)
255 td->state = TSTATE_DECODE; 314 td->state = TSTATE_DECODE;
256 return; 315 return;
257 316
258 case Q_VOICE_STOP: 317 case SYS_TIMEOUT:
259 LOGFQUEUE("voice < Q_VOICE_STOP: %ld", (long)td->ev.data); 318 if (voice_unplayed_frames())
319 {
320 /* Waiting for PCM to finish */
321 break;
322 }
260 323
261 if (td->ev.data != 0 && !playback_is_playing()) 324 /* Drop through and stop the first time after clip runs out */
325 if (td->quiet_counter-- != QUIET_COUNT)
262 { 326 {
263 /* If not playing, it's just voice so stop pcm playback */ 327 if (td->quiet_counter <= 0)
264 pcmbuf_play_stop(); 328 pcmbuf_soft_mode(false);
329
330 break;
265 } 331 }
266 332
267 /* Cancel boost */ 333 /* Fall-through */
268 cancel_cpu_boost(); 334 case Q_VOICE_STOP:
335 LOGFQUEUE("voice < Q_VOICE_STOP");
269 336
270 td->state = TSTATE_STOPPED; 337 td->state = TSTATE_STOPPED;
271 voice_done = true; 338 voice_done = true;
272 break;
273
274 case Q_VOICE_STATE:
275 LOGFQUEUE("voice < Q_VOICE_STATE");
276 queue_reply(&voice_queue, td->state);
277
278 if (td->state == TSTATE_STOPPED)
279 break; /* Not in a playback state */
280 339
281 return; 340 cancel_cpu_boost();
341 voice_stop_playback();
342 break;
282 343
283 default: 344 default:
284 /* Default messages get a reply and thread continues with no 345 /* Default messages get a reply and thread continues with no
@@ -286,20 +347,24 @@ static void voice_message(struct voice_thread_data *td)
286 LOGFQUEUE("voice < default"); 347 LOGFQUEUE("voice < default");
287 348
288 if (td->state == TSTATE_STOPPED) 349 if (td->state == TSTATE_STOPPED)
289 break; /* Not in playback state */ 350 break; /* Not in (active) playback state */
290 351
291 queue_reply(&voice_queue, 0); 352 queue_reply(&voice_queue, 0);
292 return; 353 return;
293 } 354 }
294 355
295 queue_wait(&voice_queue, &td->ev); 356 if (td->quiet_counter > 0)
357 queue_wait_w_tmo(&voice_queue, &td->ev, HZ/10);
358 else
359 queue_wait(&voice_queue, &td->ev);
296 } 360 }
297} 361}
298 362
299/* Voice thread entrypoint */ 363/* Voice thread entrypoint */
300static void voice_thread(void) 364static void NORETURN_ATTR voice_thread(void)
301{ 365{
302 struct voice_thread_data td; 366 struct voice_thread_data td;
367 char *dest;
303 368
304 voice_data_init(&td); 369 voice_data_init(&td);
305 370
@@ -361,19 +426,10 @@ static void voice_thread(void)
361 } 426 }
362 427
363 /* If all clips are done and not playing, force pcm playback. */ 428 /* If all clips are done and not playing, force pcm playback. */
364 if (!pcm_is_playing()) 429 voice_start_playback();
365 pcmbuf_play_start(); 430
366 431 td.state = TSTATE_STOPPED;
367 /* Synthesize a stop request */ 432 td.ev.id = SYS_TIMEOUT;
368 /* NOTE: We have no way to know when the pcm data placed in the
369 * buffer is actually consumed and playback has reached the end
370 * so until the info is available or inferred somehow, this will
371 * not be accurate and the stopped signal will come too soon.
372 * ie. You may not hear the "Shutting Down" splash even though
373 * it waits for voice to stop. */
374 td.ev.id = Q_VOICE_STOP;
375 td.ev.data = 0; /* Let PCM drain by itself */
376 yield();
377 goto message_process; 433 goto message_process;
378 } 434 }
379 435
@@ -385,62 +441,39 @@ static void voice_thread(void)
385 td.src[1] = NULL; 441 td.src[1] = NULL;
386 td.lookahead -= MIN(VOICE_FRAME_SIZE, td.lookahead); 442 td.lookahead -= MIN(VOICE_FRAME_SIZE, td.lookahead);
387 443
444 if (td.count <= 0)
445 continue;
446
447 td.state = TSTATE_BUFFER_INSERT;
448
388 buffer_insert: 449 buffer_insert:
389 /* Process the PCM samples in the DSP and send out for mixing */ 450 /* Process the PCM samples in the DSP and send out for mixing */
390 td.state = TSTATE_BUFFER_INSERT;
391 451
392 while (td.count > 0) 452 while (1)
393 { 453 {
394 int out_count = dsp_output_count(td.dsp, td.count); 454 if (!queue_empty(&voice_queue))
395 int inp_count; 455 goto message_wait;
396 char *dest;
397 456
398 while (1) 457 if ((dest = (char *)voice_buf_get()) != NULL)
399 {
400 if (!queue_empty(&voice_queue))
401 goto message_wait;
402
403 if ((dest = pcmbuf_request_voice_buffer(&out_count)) != NULL)
404 break;
405
406 yield();
407 }
408
409 /* Get the real input_size for output_size bytes, guarding
410 * against resampling buffer overflows. */
411 inp_count = dsp_input_count(td.dsp, out_count);
412
413 if (inp_count <= 0)
414 break;
415
416 /* Input size has grown, no error, just don't write more than
417 * length */
418 if (inp_count > td.count)
419 inp_count = td.count;
420
421 out_count = dsp_process(td.dsp, dest, td.src, inp_count);
422
423 if (out_count <= 0)
424 break; 458 break;
425 459
426 pcmbuf_write_voice_complete(out_count); 460 yield();
427 td.count -= inp_count;
428 } 461 }
429 462
430 yield(); 463 voice_buf_commit(dsp_process(td.dsp, dest, td.src, td.count)
464 * sizeof (int32_t));
431 } /* end while */ 465 } /* end while */
432} /* voice_thread */ 466}
433 467
434/* Initialize all synchronization objects create the thread */ 468/* Initialize all synchronization objects create the thread */
435void voice_thread_init(void) 469void voice_thread_init(void)
436{ 470{
437 logf("Starting voice thread"); 471 logf("Starting voice thread");
438 queue_init(&voice_queue, false); 472 queue_init(&voice_queue, false);
439 mutex_init(&voice_mutex);
440 473
441 voice_thread_id = create_thread(voice_thread, voice_stack, 474 voice_thread_id = create_thread(voice_thread, voice_stack,
442 sizeof(voice_stack), CREATE_THREAD_FROZEN, 475 sizeof(voice_stack), CREATE_THREAD_FROZEN,
443 voice_thread_name IF_PRIO(, PRIORITY_PLAYBACK) IF_COP(, CPU)); 476 voice_thread_name IF_PRIO(, PRIORITY_VOICE) IF_COP(, CPU));
444 477
445 queue_enable_queue_send(&voice_queue, &voice_queue_sender_list, 478 queue_enable_queue_send(&voice_queue, &voice_queue_sender_list,
446 voice_thread_id); 479 voice_thread_id);
@@ -457,6 +490,18 @@ void voice_thread_resume(void)
457/* Set the voice thread priority */ 490/* Set the voice thread priority */
458void voice_thread_set_priority(int priority) 491void voice_thread_set_priority(int priority)
459{ 492{
493 if (priority > PRIORITY_VOICE)
494 priority = PRIORITY_VOICE;
495
460 thread_set_priority(voice_thread_id, priority); 496 thread_set_priority(voice_thread_id, priority);
461} 497}
462#endif 498#endif
499
500/* Initialize voice PCM buffer and return size, allocated from the end */
501size_t voicebuf_init(unsigned char *bufend)
502{
503 size_t size = VOICE_FRAMES * VOICE_PCM_FRAME_SIZE;
504 cur_buf_out = cur_buf_in = 0;
505 voicebuf = (uint32_t (*)[VOICE_PCM_FRAME_COUNT])(bufend - size);
506 return size;
507}
diff --git a/apps/voice_thread.h b/apps/voice_thread.h
index 4359825dd7..1529f7efe8 100644
--- a/apps/voice_thread.h
+++ b/apps/voice_thread.h
@@ -29,8 +29,13 @@ bool mp3_is_playing(void);
29 29
30void voice_wait(void); 30void voice_wait(void);
31void voice_stop(void); 31void voice_stop(void);
32
32void voice_thread_init(void); 33void voice_thread_init(void);
33void voice_thread_resume(void); 34void voice_thread_resume(void);
35#ifdef HAVE_PRIORITY_SCHEDULING
34void voice_thread_set_priority(int priority); 36void voice_thread_set_priority(int priority);
37#endif
38
39size_t voicebuf_init(unsigned char *bufend);
35 40
36#endif /* VOICE_THREAD_H */ 41#endif /* VOICE_THREAD_H */
diff --git a/firmware/SOURCES b/firmware/SOURCES
index 7c3a909f22..85d9a28f1b 100644
--- a/firmware/SOURCES
+++ b/firmware/SOURCES
@@ -311,6 +311,7 @@ sound.c
311#ifndef BOOTLOADER 311#ifndef BOOTLOADER
312pcm_sampr.c 312pcm_sampr.c
313pcm.c 313pcm.c
314pcm_mixer.c
314#ifdef HAVE_RECORDING 315#ifdef HAVE_RECORDING
315enc_base.c 316enc_base.c
316#endif /* HAVE_RECORDING */ 317#endif /* HAVE_RECORDING */
diff --git a/firmware/export/config.h b/firmware/export/config.h
index 2c7c6e89db..70047ff866 100644
--- a/firmware/export/config.h
+++ b/firmware/export/config.h
@@ -1048,4 +1048,9 @@ Lyre prototype 1 */
1048#define HAVE_IO_PRIORITY 1048#define HAVE_IO_PRIORITY
1049#endif 1049#endif
1050 1050
1051#if defined(CPU_COLDIRE) || CONFIG_CPU == IMX31L
1052/* Can record and play simultaneously */
1053#define HAVE_PCM_FULL_DUPLEX
1054#endif
1055
1051#endif /* __CONFIG_H__ */ 1056#endif /* __CONFIG_H__ */
diff --git a/firmware/export/pcm-internal.h b/firmware/export/pcm-internal.h
new file mode 100644
index 0000000000..d69138f534
--- /dev/null
+++ b/firmware/export/pcm-internal.h
@@ -0,0 +1,81 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2005 by Linus Nielsen Feltzing
11 * Copyright (C) 2011 by Michael Sevakis
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ****************************************************************************/
22#ifndef PCM_INTERNAL_H
23#define PCM_INTERNAL_H
24
25/** The following are for internal use between pcm.c and target-
26 specific portion **/
27
28/* Called by the bottom layer ISR when more data is needed. Returns non-
29 * zero size if more data is to be played. Setting start to NULL
30 * forces stop. */
31void pcm_play_get_more_callback(void **start, size_t *size);
32
33/* Called by the bottom layer ISR after next transfer has begun in order
34 to fill more data for next "get more" callback to implement double-buffered
35 callbacks - except for a couple ASM handlers, help drivers to implement
36 this functionality with minimal overhead */
37static FORCE_INLINE void pcm_play_dma_started_callback(void)
38{
39 extern void (* pcm_play_dma_started)(void);
40 void (* callback)(void) = pcm_play_dma_started;
41 if (callback)
42 callback();
43}
44
45extern unsigned long pcm_curr_sampr;
46extern unsigned long pcm_sampr;
47extern int pcm_fsel;
48
49#ifdef HAVE_PCM_DMA_ADDRESS
50void * pcm_dma_addr(void *addr);
51#endif
52
53extern volatile bool pcm_playing;
54extern volatile bool pcm_paused;
55
56void pcm_play_dma_lock(void);
57void pcm_play_dma_unlock(void);
58void pcm_play_dma_init(void) INIT_ATTR;
59void pcm_play_dma_start(const void *addr, size_t size);
60void pcm_play_dma_stop(void);
61void pcm_play_dma_pause(bool pause);
62const void * pcm_play_dma_get_peak_buffer(int *count);
63
64void pcm_dma_apply_settings(void);
65
66#ifdef HAVE_RECORDING
67
68/* DMA transfer in is currently active */
69extern volatile bool pcm_recording;
70
71/* APIs implemented in the target-specific portion */
72void pcm_rec_dma_init(void);
73void pcm_rec_dma_close(void);
74void pcm_rec_dma_start(void *addr, size_t size);
75void pcm_rec_dma_record_more(void *start, size_t size);
76void pcm_rec_dma_stop(void);
77const void * pcm_rec_dma_get_peak_buffer(void);
78
79#endif /* HAVE_RECORDING */
80
81#endif /* PCM_INTERNAL_H */
diff --git a/firmware/export/pcm.h b/firmware/export/pcm.h
index 80b5b09a79..22c5ef350e 100644
--- a/firmware/export/pcm.h
+++ b/firmware/export/pcm.h
@@ -49,7 +49,7 @@
49 49
50/** RAW PCM routines used with playback and recording **/ 50/** RAW PCM routines used with playback and recording **/
51 51
52/* Typedef for registered callback */ 52/* Typedef for registered callbacks */
53typedef void (*pcm_play_callback_type)(unsigned char **start, 53typedef void (*pcm_play_callback_type)(unsigned char **start,
54 size_t *size); 54 size_t *size);
55typedef void (*pcm_rec_callback_type)(int status, void **start, size_t *size); 55typedef void (*pcm_rec_callback_type)(int status, void **start, size_t *size);
@@ -90,34 +90,7 @@ void pcm_play_pause(bool play);
90bool pcm_is_paused(void); 90bool pcm_is_paused(void);
91bool pcm_is_playing(void); 91bool pcm_is_playing(void);
92 92
93/** The following are for internal use between pcm.c and target- 93void pcm_play_set_dma_started_callback(void (* callback)(void));
94 specific portion **/
95
96/* Called by the bottom layer ISR when more data is needed. Returns non-
97 * zero size if more data is to be played. Setting start to NULL
98 * forces stop. */
99void pcm_play_get_more_callback(void **start, size_t *size);
100
101extern unsigned long pcm_curr_sampr;
102extern unsigned long pcm_sampr;
103extern int pcm_fsel;
104
105#ifdef HAVE_PCM_DMA_ADDRESS
106void * pcm_dma_addr(void *addr);
107#endif
108
109extern volatile bool pcm_playing;
110extern volatile bool pcm_paused;
111
112void pcm_play_dma_lock(void);
113void pcm_play_dma_unlock(void);
114void pcm_play_dma_init(void) INIT_ATTR;
115void pcm_play_dma_start(const void *addr, size_t size);
116void pcm_play_dma_stop(void);
117void pcm_play_dma_pause(bool pause);
118const void * pcm_play_dma_get_peak_buffer(int *count);
119
120void pcm_dma_apply_settings(void);
121 94
122#ifdef HAVE_RECORDING 95#ifdef HAVE_RECORDING
123 96
@@ -148,19 +121,6 @@ void pcm_rec_more_ready_callback(int status, void **start, size_t *size);
148 121
149void pcm_calculate_rec_peaks(int *left, int *right); 122void pcm_calculate_rec_peaks(int *left, int *right);
150 123
151/** The following are for internal use between pcm.c and target-
152 specific portion **/
153/* DMA transfer in is currently active */
154extern volatile bool pcm_recording;
155
156/* APIs implemented in the target-specific portion */
157void pcm_rec_dma_init(void);
158void pcm_rec_dma_close(void);
159void pcm_rec_dma_start(void *addr, size_t size);
160void pcm_rec_dma_record_more(void *start, size_t size);
161void pcm_rec_dma_stop(void);
162const void * pcm_rec_dma_get_peak_buffer(void);
163
164#endif /* HAVE_RECORDING */ 124#endif /* HAVE_RECORDING */
165 125
166#endif /* PCM_PLAYBACK_H */ 126#endif /* PCM_PLAYBACK_H */
diff --git a/firmware/export/pcm_mixer.h b/firmware/export/pcm_mixer.h
new file mode 100644
index 0000000000..3b420e1320
--- /dev/null
+++ b/firmware/export/pcm_mixer.h
@@ -0,0 +1,102 @@
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
22#ifndef PCM_MIXER_H
23#define PCM_MIXER_H
24
25/** Simple config **/
26
27/* Length of PCM frames (always) */
28#if CONFIG_CPU == PP5002
29/* There's far less time to do mixing because HW FIFOs are short */
30#define MIX_FRAME_SAMPLES 64
31#else
32/* Assume HW DMA engine is available or sufficient latency exists in the
33 PCM pathway */
34#define MIX_FRAME_SAMPLES 256
35#endif
36
37#if defined(CPU_COLDFIRE) || defined(CPU_PP)
38/* For Coldfire, it's just faster
39 For PortalPlayer, this also avoids more expensive cache coherency */
40#define DOWNMIX_BUF_IBSS IBSS_ATTR
41#else
42/* Otherwise can't DMA from IRAM, IRAM is pointless or worse */
43#define DOWNMIX_BUF_IBSS
44#endif
45
46
47/** Definitions **/
48
49/* Channels are preassigned for simplicity */
50enum pcm_mixer_channel
51{
52 PCM_MIXER_CHAN_PLAYBACK = 0,
53 PCM_MIXER_CHAN_VOICE,
54#ifndef HAVE_HARDWARE_BEEP
55 PCM_MIXER_CHAN_BEEP,
56#endif
57 /* Add new channel indexes above this line */
58 PCM_MIXER_NUM_CHANNELS,
59};
60
61/* Channel playback states */
62enum channel_status
63{
64 CHANNEL_STOPPED = 0,
65 CHANNEL_PLAYING,
66 CHANNEL_PAUSED,
67};
68
69#define MIX_AMP_UNITY 0x00010000
70#define MIX_AMP_MUTE 0x00000000
71
72
73/** Public interfaces **/
74
75/* Start playback on a channel */
76void mixer_channel_play_data(enum pcm_mixer_channel channel,
77 pcm_play_callback_type get_more,
78 unsigned char *start, size_t size);
79
80/* Pause or resume a channel (when started) */
81void mixer_channel_play_pause(enum pcm_mixer_channel channel, bool play);
82
83/* Stop playback on a channel */
84void mixer_channel_stop(enum pcm_mixer_channel channel);
85
86/* Set channel's amplitude factor */
87void mixer_channel_set_amplitude(enum pcm_mixer_channel channel,
88 unsigned int amplitude);
89
90/* Return channel's playback status */
91enum channel_status mixer_channel_status(enum pcm_mixer_channel channel);
92
93/* Returns amount data remaining in channel before next callback */
94size_t mixer_channel_get_bytes_waiting(enum pcm_mixer_channel channel);
95
96/* Return pointer to channel's playing audio data and the size remaining */
97void * mixer_channel_get_buffer(enum pcm_mixer_channel channel, int *count);
98
99/* Stop ALL channels and PCM and reset state */
100void mixer_reset(void);
101
102#endif /* PCM_MIXER_H */
diff --git a/firmware/pcm.c b/firmware/pcm.c
index d15c129015..b7415f329b 100644
--- a/firmware/pcm.c
+++ b/firmware/pcm.c
@@ -28,6 +28,8 @@
28#include "audio.h" 28#include "audio.h"
29#include "sound.h" 29#include "sound.h"
30#include "general.h" 30#include "general.h"
31#include "pcm-internal.h"
32#include "pcm_mixer.h"
31 33
32/** 34/**
33 * Aspects implemented in the target-specific portion: 35 * Aspects implemented in the target-specific portion:
@@ -78,8 +80,8 @@
78 */ 80 */
79 81
80/* the registered callback function to ask for more mp3 data */ 82/* the registered callback function to ask for more mp3 data */
81static volatile pcm_play_callback_type pcm_callback_for_more 83static pcm_play_callback_type pcm_callback_for_more SHAREDBSS_ATTR = NULL;
82 SHAREDBSS_ATTR = NULL; 84void (* pcm_play_dma_started)(void) SHAREDBSS_ATTR = NULL;
83/* PCM playback state */ 85/* PCM playback state */
84volatile bool pcm_playing SHAREDBSS_ATTR = false; 86volatile bool pcm_playing SHAREDBSS_ATTR = false;
85/* PCM paused state. paused implies playing */ 87/* PCM paused state. paused implies playing */
@@ -95,6 +97,7 @@ int pcm_fsel SHAREDBSS_ATTR = HW_FREQ_DEFAULT;
95static void pcm_play_stopped(void) 97static void pcm_play_stopped(void)
96{ 98{
97 pcm_callback_for_more = NULL; 99 pcm_callback_for_more = NULL;
100 pcm_play_dma_started = NULL;
98 pcm_paused = false; 101 pcm_paused = false;
99 pcm_playing = false; 102 pcm_playing = false;
100} 103}
@@ -404,6 +407,12 @@ void pcm_apply_settings(void)
404 } 407 }
405} 408}
406 409
410/* register callback to buffer more data */
411void pcm_play_set_dma_started_callback(void (* callback)(void))
412{
413 pcm_play_dma_started = callback;
414}
415
407#ifdef HAVE_RECORDING 416#ifdef HAVE_RECORDING
408/** Low level pcm recording apis **/ 417/** Low level pcm recording apis **/
409 418
@@ -475,6 +484,11 @@ void pcm_init_recording(void)
475{ 484{
476 logf("pcm_init_recording"); 485 logf("pcm_init_recording");
477 486
487#ifndef HAVE_PCM_FULL_DUPLEX
488 /* Stop the beasty before attempting recording */
489 mixer_reset();
490#endif
491
478 /* Recording init is locked unlike general pcm init since this is not 492 /* Recording init is locked unlike general pcm init since this is not
479 * just a one-time event at startup and it should and must be safe by 493 * just a one-time event at startup and it should and must be safe by
480 * now. */ 494 * now. */
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 */
39struct 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 ;-) ) */
55static uint32_t downmix_buf[2][MIX_FRAME_SAMPLES] DOWNMIX_BUF_IBSS MEM_ALIGN_ATTR;
56static int downmix_index = 0; /* Which downmix_buf? */
57static size_t next_size = 0; /* Size of buffer to play next time */
58
59/* Descriptors for all available channels */
60static struct mixer_channel channels[PCM_MIXER_NUM_CHANNELS] IBSS_ATTR;
61
62/* Packed pointer array of all playing (active) channels in "channels" array */
63static 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)
67static 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 */
92static 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 */
100static 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 */
155static 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 */
183static 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 */
195static 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 */
201static 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 */
210static 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() */
218static 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 */
229fill_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 */
338static 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 */
361static 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 */
405void 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) */
418void 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 */
444void 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 */
454void 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 */
461enum 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 */
467size_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 */
473void * 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 */
493void mixer_reset(void)
494{
495 pcm_play_stop();
496
497 while (*active_channels)
498 channel_stopped(*active_channels);
499
500 idle_counter = 0;
501}
diff --git a/firmware/target/arm/as3525/pcm-as3525.c b/firmware/target/arm/as3525/pcm-as3525.c
index 469833b05c..1b22d48f7f 100644
--- a/firmware/target/arm/as3525/pcm-as3525.c
+++ b/firmware/target/arm/as3525/pcm-as3525.c
@@ -29,6 +29,7 @@
29#include "as3514.h" 29#include "as3514.h"
30#include "audiohw.h" 30#include "audiohw.h"
31#include "mmu-arm.h" 31#include "mmu-arm.h"
32#include "pcm-internal.h"
32 33
33#define MAX_TRANSFER (4*((1<<11)-1)) /* maximum data we can transfer via DMA 34#define MAX_TRANSFER (4*((1<<11)-1)) /* maximum data we can transfer via DMA
34 * i.e. 32 bits at once (size of I2SO_DATA) 35 * i.e. 32 bits at once (size of I2SO_DATA)
@@ -104,9 +105,13 @@ static void dma_callback(void)
104 105
105 /* force writeback */ 106 /* force writeback */
106 clean_dcache_range(dma_start_addr, dma_start_size); 107 clean_dcache_range(dma_start_addr, dma_start_size);
108 play_start_pcm();
109 pcm_play_dma_started_callback();
110 }
111 else
112 {
113 play_start_pcm();
107 } 114 }
108
109 play_start_pcm();
110} 115}
111 116
112void pcm_play_dma_start(const void *addr, size_t size) 117void pcm_play_dma_start(const void *addr, size_t size)
diff --git a/firmware/target/arm/imx31/gigabeat-s/pcm-gigabeat-s.c b/firmware/target/arm/imx31/gigabeat-s/pcm-gigabeat-s.c
index c8c1283d12..1f6eef435a 100644
--- a/firmware/target/arm/imx31/gigabeat-s/pcm-gigabeat-s.c
+++ b/firmware/target/arm/imx31/gigabeat-s/pcm-gigabeat-s.c
@@ -26,6 +26,7 @@
26#include "ccm-imx31.h" 26#include "ccm-imx31.h"
27#include "sdma-imx31.h" 27#include "sdma-imx31.h"
28#include "mmu-imx31.h" 28#include "mmu-imx31.h"
29#include "pcm-internal.h"
29 30
30#define DMA_PLAY_CH_NUM 2 31#define DMA_PLAY_CH_NUM 2
31#define DMA_REC_CH_NUM 1 32#define DMA_REC_CH_NUM 1
@@ -105,6 +106,8 @@ static void play_dma_callback(void)
105 dma_play_bd.mode.command = TRANSFER_16BIT; 106 dma_play_bd.mode.command = TRANSFER_16BIT;
106 dma_play_bd.mode.status = BD_DONE | BD_WRAP | BD_INTR; 107 dma_play_bd.mode.status = BD_DONE | BD_WRAP | BD_INTR;
107 sdma_channel_run(DMA_PLAY_CH_NUM); 108 sdma_channel_run(DMA_PLAY_CH_NUM);
109
110 pcm_play_dma_started_callback();
108} 111}
109 112
110void pcm_play_lock(void) 113void pcm_play_lock(void)
diff --git a/firmware/target/arm/pcm-mixer-armv4.c b/firmware/target/arm/pcm-mixer-armv4.c
new file mode 100644
index 0000000000..4818544d7b
--- /dev/null
+++ b/firmware/target/arm/pcm-mixer-armv4.c
@@ -0,0 +1,182 @@
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
22#define MIXER_OPTIMIZED_WRITE_SAMPLES
23#define MIXER_OPTIMIZED_MIX_SAMPLES
24
25/* Mix channels' samples and apply gain factors */
26static FORCE_INLINE void mix_samples(void *out,
27 void *src0,
28 int32_t src0_amp,
29 void *src1,
30 int32_t src1_amp,
31 size_t size)
32{
33 if (src0_amp == MIX_AMP_UNITY && src1_amp == MIX_AMP_UNITY)
34 {
35 /* Both are unity amplitude */
36 int32_t l0, l1, h0, h1;
37 asm volatile (
38 "1: \n"
39 "ldrsh %4, [%1], #2 \n"
40 "ldrsh %5, [%2], #2 \n"
41 "ldrsh %6, [%1], #2 \n"
42 "ldrsh %7, [%2], #2 \n"
43 "add %4, %4, %5 \n"
44 "add %6, %6, %7 \n"
45 "mov %5, %4, asr #15 \n"
46 "teq %5, %5, asr #31 \n"
47 "eorne %4, %8, %4, asr #31 \n"
48 "mov %7, %6, asr #15 \n"
49 "teq %7, %7, asr #31 \n"
50 "eorne %6, %8, %6, asr #31 \n"
51 "subs %3, %3, #4 \n"
52 "and %4, %4, %8, lsr #16 \n"
53 "orr %6, %4, %6, lsl #16 \n"
54 "str %6, [%0], #4 \n"
55 "bhi 1b \n"
56 : "+r"(out), "+r"(src0), "+r"(src1), "+r"(size),
57 "=&r"(l0), "=&r"(l1), "=&r"(h0), "=&r"(h1)
58 : "r"(0xffff7fff));
59 }
60 else if (src0_amp != MIX_AMP_UNITY && src1_amp != MIX_AMP_UNITY)
61 {
62 /* Neither are unity amplitude */
63 int32_t l0, l1, h0, h1;
64 asm volatile (
65 "1: \n"
66 "ldrsh %4, [%1], #2 \n"
67 "ldrsh %5, [%2], #2 \n"
68 "ldrsh %6, [%1], #2 \n"
69 "ldrsh %7, [%2], #2 \n"
70 "mul %4, %8, %4 \n"
71 "mul %5, %9, %5 \n"
72 "mul %6, %8, %6 \n"
73 "mul %7, %9, %7 \n"
74 "mov %4, %4, asr #16 \n"
75 "add %4, %4, %5, asr #16 \n"
76 "mov %6, %6, asr #16 \n"
77 "add %6, %6, %7, asr #16 \n"
78 "mov %5, %4, asr #15 \n"
79 "teq %5, %5, asr #31 \n"
80 "eorne %4, %10, %4, asr #31 \n"
81 "mov %7, %6, asr #15 \n"
82 "teq %7, %7, asr #31 \n"
83 "eorne %6, %10, %6, asr #31 \n"
84 "subs %3, %3, #4 \n"
85 "and %4, %4, %10, lsr #16 \n"
86 "orr %6, %4, %6, lsl #16 \n"
87 "str %6, [%0], #4 \n"
88 "bhi 1b \n"
89 : "+r"(out), "+r"(src0), "+r"(src1), "+r"(size),
90 "=&r"(l0), "=&r"(l1), "=&r"(h0), "=&r"(h1)
91 : "r"(src0_amp), "r"(src1_amp), "r"(0xffff7fff));
92 }
93 else
94 {
95 /* One is unity amplitude */
96 if (src0_amp != MIX_AMP_UNITY)
97 {
98 /* Keep unity in src0, amp0 */
99 int16_t *src_tmp = src0;
100 src0 = src1;
101 src1 = src_tmp;
102 src1_amp = src0_amp;
103 src0_amp = MIX_AMP_UNITY;
104 }
105
106 int32_t l0, l1, h0, h1;
107 asm volatile (
108 "1: \n"
109 "ldrsh %4, [%1], #2 \n"
110 "ldrsh %5, [%2], #2 \n"
111 "ldrsh %6, [%1], #2 \n"
112 "ldrsh %7, [%2], #2 \n"
113 "mul %5, %8, %5 \n"
114 "mul %7, %8, %7 \n"
115 "add %4, %4, %5, asr #16 \n"
116 "add %6, %6, %7, asr #16 \n"
117 "mov %5, %4, asr #15 \n"
118 "teq %5, %5, asr #31 \n"
119 "eorne %4, %9, %4, asr #31 \n"
120 "mov %7, %6, asr #15 \n"
121 "teq %7, %7, asr #31 \n"
122 "eorne %6, %9, %6, asr #31 \n"
123 "subs %3, %3, #4 \n"
124 "and %4, %4, %9, lsr #16 \n"
125 "orr %6, %4, %6, lsl #16 \n"
126 "str %6, [%0], #4 \n"
127 "bhi 1b \n"
128 : "+r"(out), "+r"(src0), "+r"(src1), "+r"(size),
129 "=&r"(l0), "=&r"(l1), "=&r"(h0), "=&r"(h1)
130 : "r"(src1_amp), "r"(0xffff7fff));
131 }
132}
133
134/* Write channel's samples and apply gain factor */
135static FORCE_INLINE void write_samples(void *out,
136 void *src,
137 int32_t amp,
138 size_t size)
139{
140 if (LIKELY(amp == MIX_AMP_UNITY))
141 {
142 /* Channel is unity amplitude */
143 asm volatile (
144 "ands r1, %2, #0x1f \n"
145 "beq 2f \n"
146 "1: \n"
147 "ldr r0, [%1], #4 \n"
148 "subs r1, r1, #4 \n"
149 "str r0, [%0], #4 \n"
150 "bne 1b \n"
151 "bics %2, %2, #0x1f \n"
152 "beq 3f \n"
153 "2: \n"
154 "ldmia %1!, { r0-r7 } \n"
155 "subs %2, %2, #32 \n"
156 "stmia %0!, { r0-r7 } \n"
157 "bhi 2b \n"
158 "3: \n"
159 : "+r"(out), "+r"(src), "+r"(size)
160 :
161 : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7");
162 }
163 else
164 {
165 /* Channel needs amplitude cut */
166 uint32_t l, h;
167 asm volatile (
168 "1: \n"
169 "ldrsh %3, [%1], #2 \n"
170 "ldrsh %4, [%1], #2 \n"
171 "subs %2, %2, #4 \n"
172 "mul %3, %5, %3 \n"
173 "mul %4, %5, %4 \n"
174 "and %4, %4, %6, lsl #16 \n"
175 "orr %4, %4, %3, lsr #16 \n"
176 "str %4, [%0], #4 \n"
177 "bhi 1b \n"
178 : "+r"(out), "+r"(src), "+r"(size),
179 "=&r"(l), "=&r"(h)
180 : "r"(amp), "r"(0xffffffffu));
181 }
182}
diff --git a/firmware/target/arm/pcm-mixer-armv5.c b/firmware/target/arm/pcm-mixer-armv5.c
new file mode 100644
index 0000000000..64f2c86f52
--- /dev/null
+++ b/firmware/target/arm/pcm-mixer-armv5.c
@@ -0,0 +1,106 @@
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
22#define MIXER_OPTIMIZED_WRITE_SAMPLES
23#define MIXER_OPTIMIZED_MIX_SAMPLES
24
25/* Mix channels' samples and apply gain factors */
26static FORCE_INLINE void mix_samples(void *out,
27 void *src0,
28 int32_t src0_amp,
29 void *src1,
30 int32_t src1_amp,
31 size_t size)
32{
33 int32_t s0, s1, tmp;
34 asm volatile (
35 "1: \n"
36 "ldr %4, [%1], #4 \n"
37 "ldr %5, [%2], #4 \n"
38 "smulwb %6, %7, %4 \n"
39 "smulwt %4, %7, %4 \n"
40 "smlawb %6, %8, %5, %6 \n"
41 "smlawt %4, %8, %5, %4 \n"
42 "mov %5, %6, asr #15 \n"
43 "teq %5, %5, asr #31 \n"
44 "eorne %6, %9, %6, asr #31 \n"
45 "mov %5, %4, asr #15 \n"
46 "teq %5, %5, asr #31 \n"
47 "eorne %4, %9, %4, asr #31 \n"
48 "subs %3, %3, #4 \n"
49 "and %6, %6, %9, lsr #16 \n"
50 "orr %6, %6, %4, lsl #16 \n"
51 "str %6, [%0], #4 \n"
52 "bhi 1b \n"
53 : "+r"(out), "+r"(src0), "+r"(src1), "+r"(size),
54 "=&r"(s0), "=&r"(s1), "=&r"(tmp)
55 : "r"(src0_amp), "r"(src1_amp), "r"(0xffff7fff));
56}
57
58/* Write channel's samples and apply gain factor */
59static FORCE_INLINE void write_samples(void *out,
60 void *src,
61 int32_t amp,
62 size_t size)
63{
64 if (LIKELY(amp == MIX_AMP_UNITY))
65 {
66 /* Channel is unity amplitude */
67 asm volatile (
68 "ands r1, %2, #0x1f \n"
69 "beq 2f \n"
70 "1: \n"
71 "ldr r0, [%1], #4 \n"
72 "subs r1, r1, #4 \n"
73 "str r0, [%0], #4 \n"
74 "bne 1b \n"
75 "bics %2, %2, #0x1f \n"
76 "beq 3f \n"
77 "2: \n"
78 "ldmia %1!, { r0-r7 } \n"
79 "subs %2, %2, #32 \n"
80 "stmia %0!, { r0-r7 } \n"
81 "bhi 2b \n"
82 "3: \n"
83 : "+r"(out), "+r"(src), "+r"(size)
84 :
85 : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7");
86 }
87 else
88 {
89 /* Channel needs amplitude cut */
90 uint32_t l, h;
91 asm volatile (
92 "1: \n"
93 "ldr %3, [%1], #4 \n"
94 "subs %2, %2, #4 \n"
95 "smulwt %4, %5, %3 \n"
96 "smulwb %3, %5, %3 \n"
97 "mov %4, %4, lsl #16 \n"
98 "mov %3, %3, lsl #16 \n"
99 "orr %4, %4, %3, lsr #16 \n"
100 "str %4, [%0], #4 \n"
101 "bhi 1b \n"
102 : "+r"(out), "+r"(src), "+r"(size),
103 "=&r"(l), "=&r"(h)
104 : "r"(amp));
105 }
106}
diff --git a/firmware/target/arm/pcm-mixer-armv6.c b/firmware/target/arm/pcm-mixer-armv6.c
new file mode 100644
index 0000000000..94eecd0f90
--- /dev/null
+++ b/firmware/target/arm/pcm-mixer-armv6.c
@@ -0,0 +1,118 @@
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#define MIXER_OPTIMIZED_MIX_SAMPLES
22#define MIXER_OPTIMIZED_WRITE_SAMPLES
23
24/* Mix channels' samples and apply gain factors */
25static FORCE_INLINE void mix_samples(void *out,
26 void *src0,
27 int32_t src0_amp,
28 void *src1,
29 int32_t src1_amp,
30 size_t size)
31{
32 uint32_t s0, s1;
33
34 if (src0_amp == MIX_AMP_UNITY && src1_amp == MIX_AMP_UNITY)
35 {
36 /* Both are unity amplitude */
37 asm volatile (
38 "1: \n"
39 "ldr %4, [%1], #4 \n"
40 "ldr %5, [%2], #4 \n"
41 "subs %3, %3, #4 \n"
42 "qadd16 %5, %5, %4 \n"
43 "str %5, [%0], #4 \n"
44 "bhi 1b \n"
45 : "+r"(out), "+r"(src0), "+r"(src1), "+r"(size),
46 "=&r"(s0), "=&r"(s1));
47 }
48 else
49 {
50 /* One or neither are unity amplitude */
51 uint32_t tmp;
52 asm volatile (
53 "1: \n"
54 "ldr %4, [%1], #4 \n"
55 "ldr %5, [%2], #4 \n"
56 "subs %3, %3, #4 \n"
57 "smulwb %6, %7, %4 \n"
58 "smulwt %4, %7, %4 \n"
59 "smlawb %6, %8, %5, %6 \n"
60 "smlawt %4, %8, %5, %4 \n"
61 "ssat %6, #16, %6 \n"
62 "ssat %4, #16, %4 \n"
63 "pkhbt %6, %6, %4, asl #16 \n"
64 "str %6, [%0], #4 \n"
65 "bhi 1b \n"
66 : "+r"(out), "+r"(src0), "+r"(src1), "+r"(size),
67 "=&r"(s0), "=&r"(s1), "=&r"(tmp)
68 : "r"(src0_amp), "r"(src1_amp));
69 }
70}
71
72/* Write channel's samples and apply gain factor */
73static FORCE_INLINE void write_samples(void *out,
74 void *src,
75 int32_t amp,
76 size_t size)
77{
78 if (LIKELY(amp == MIX_AMP_UNITY))
79 {
80 /* Channel is unity amplitude */
81 asm volatile (
82 "ands r1, %2, #0x1f \n"
83 "beq 2f \n"
84 "1: \n"
85 "ldr r0, [%1], #4 \n"
86 "subs r1, r1, #4 \n"
87 "str r0, [%0], #4 \n"
88 "bne 1b \n"
89 "bics %2, %2, #0x1f \n"
90 "beq 3f \n"
91 "2: \n"
92 "ldmia %1!, { r0-r7 } \n"
93 "subs %2, %2, #32 \n"
94 "stmia %0!, { r0-r7 } \n"
95 "bhi 2b \n"
96 "3: \n"
97 : "+r"(out), "+r"(src), "+r"(size)
98 :
99 : "r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7");
100 }
101 else
102 {
103 /* Channel needs amplitude cut */
104 uint32_t s, tmp;
105 asm volatile(
106 "1: \n"
107 "ldr %3, [%1], #4 \n"
108 "subs %2, %2, #4 \n"
109 "smulwt %4, %5, %3 \n"
110 "smulwb %3, %5, %3 \n"
111 "pkhbt %4, %3, %4, asl #16 \n"
112 "str %4, [%0], #4 \n"
113 "bhi 1b \n"
114 : "+r"(out), "+r"(src), "+r"(size),
115 "=&r"(s), "=&r"(tmp)
116 : "r"(amp));
117 }
118}
diff --git a/firmware/target/arm/pcm-pp.c b/firmware/target/arm/pcm-pp.c
index c446f98fcf..704296d407 100644
--- a/firmware/target/arm/pcm-pp.c
+++ b/firmware/target/arm/pcm-pp.c
@@ -26,6 +26,7 @@
26#include "sound.h" 26#include "sound.h"
27#include "pcm.h" 27#include "pcm.h"
28#include "pcm_sampr.h" 28#include "pcm_sampr.h"
29#include "pcm-internal.h"
29 30
30/** DMA **/ 31/** DMA **/
31 32
@@ -115,6 +116,7 @@ void pcm_dma_apply_settings(void)
115/* NOTE: direct stack use forbidden by GCC stack handling bug for FIQ */ 116/* NOTE: direct stack use forbidden by GCC stack handling bug for FIQ */
116void ICODE_ATTR __attribute__((interrupt("FIQ"))) fiq_playback(void) 117void ICODE_ATTR __attribute__((interrupt("FIQ"))) fiq_playback(void)
117{ 118{
119 bool new_buffer = false;
118 register size_t size; 120 register size_t size;
119 121
120 DMA0_STATUS; /* Clear any pending interrupt */ 122 DMA0_STATUS; /* Clear any pending interrupt */
@@ -136,9 +138,14 @@ void ICODE_ATTR __attribute__((interrupt("FIQ"))) fiq_playback(void)
136 /* Set the new DMA values and activate channel */ 138 /* Set the new DMA values and activate channel */
137 DMA0_RAM_ADDR = dma_play_data.addr; 139 DMA0_RAM_ADDR = dma_play_data.addr;
138 DMA0_CMD = DMA_PLAY_CONFIG | (size - 4) | DMA_CMD_START; 140 DMA0_CMD = DMA_PLAY_CONFIG | (size - 4) | DMA_CMD_START;
141
142 if (new_buffer)
143 pcm_play_dma_started_callback();
139 return; 144 return;
140 } 145 }
141 146
147 new_buffer = true;
148
142 /* Buffer empty. Try to get more. */ 149 /* Buffer empty. Try to get more. */
143 pcm_play_get_more_callback((void **)&dma_play_data.addr, 150 pcm_play_get_more_callback((void **)&dma_play_data.addr,
144 &dma_play_data.size); 151 &dma_play_data.size);
@@ -181,8 +188,9 @@ void fiq_playback(void)
181 * r0-r3 and r12 is a working register. 188 * r0-r3 and r12 is a working register.
182 */ 189 */
183 asm volatile ( 190 asm volatile (
184 "stmfd sp!, { r0-r3, lr } \n" /* stack scratch regs and lr */ 191 "stmfd sp!, { r0-r4, lr } \n" /* stack scratch regs and lr */
185 192
193 "mov r4, #0 \n" /* Was the callback called? */
186#if CONFIG_CPU == PP5002 194#if CONFIG_CPU == PP5002
187 "ldr r12, =0xcf001040 \n" /* Some magic from iPodLinux */ 195 "ldr r12, =0xcf001040 \n" /* Some magic from iPodLinux */
188 "ldr r12, [r12] \n" 196 "ldr r12, [r12] \n"
@@ -212,16 +220,13 @@ void fiq_playback(void)
212 "tst r1, #1 \n" /* two samples (one word) left? */ 220 "tst r1, #1 \n" /* two samples (one word) left? */
213 "ldrne r12, [r8], #4 \n" /* load two samples */ 221 "ldrne r12, [r8], #4 \n" /* load two samples */
214 "strne r12, [r10, %[wr]] \n" /* write sample 0-1 to IISFIFO_WR */ 222 "strne r12, [r10, %[wr]] \n" /* write sample 0-1 to IISFIFO_WR */
215
216 "cmp r9, #0 \n" /* either FIFO is full or source buffer is empty */
217 "bgt .exit \n" /* if source buffer is not empty, FIFO must be full */
218#elif SAMPLE_SIZE == 32 223#elif SAMPLE_SIZE == 32
219 ".check_fifo: \n" 224 ".check_fifo: \n"
220 "ldr r0, [r10, %[cfg]] \n" /* read IISFIFO_CFG to check FIFO status */ 225 "ldr r0, [r10, %[cfg]] \n" /* read IISFIFO_CFG to check FIFO status */
221 "and r0, r0, %[mask] \n" /* r0 = IIS_TX_FREE_COUNT << 23 (PP5002) */ 226 "and r0, r0, %[mask] \n" /* r0 = IIS_TX_FREE_COUNT << 23 (PP5002) */
222 227
223 "movs r1, r0, lsr #24 \n" /* number of free pairs of FIFO slots */ 228 "movs r1, r0, lsr #24 \n" /* number of free pairs of FIFO slots */
224 "beq .exit \n" /* no complete pair? -> exit */ 229 "beq .fifo_fill_complete \n" /* no complete pair? -> exit */
225 "cmp r1, r9, lsr #2 \n" /* number of words from source */ 230 "cmp r1, r9, lsr #2 \n" /* number of words from source */
226 "movgt r1, r9, lsr #2 \n" /* r1 = amount of allowed loops */ 231 "movgt r1, r9, lsr #2 \n" /* r1 = amount of allowed loops */
227 "sub r9, r9, r1, lsl #2 \n" /* r1 words will be written in following loop */ 232 "sub r9, r9, r1, lsl #2 \n" /* r1 words will be written in following loop */
@@ -234,11 +239,23 @@ void fiq_playback(void)
234 "subs r1, r1, #1 \n" /* one more loop? */ 239 "subs r1, r1, #1 \n" /* one more loop? */
235 "bgt .fifo_loop \n" /* yes, continue */ 240 "bgt .fifo_loop \n" /* yes, continue */
236 241
242 ".fifo_fill_complete: \n"
243#endif
244 "cmp r4, #0 \n" /* If fill came after get_more... */
245 "beq .still_old_buffer \n"
246 "mov r4, #0 \n"
247 "ldr r2, =pcm_play_dma_started \n"
248 "ldrne r2, [r2] \n"
249 "cmp r2, #0 \n"
250 "movne lr, pc \n"
251 "bxne r2 \n"
252
253 ".still_old_buffer: \n"
237 "cmp r9, #0 \n" /* either FIFO is full or source buffer is empty */ 254 "cmp r9, #0 \n" /* either FIFO is full or source buffer is empty */
238 "bgt .exit \n" /* if source buffer is not empty, FIFO must be full */ 255 "bgt .exit \n" /* if source buffer is not empty, FIFO must be full */
239#endif
240 256
241 ".more_data: \n" 257 ".more_data: \n"
258 "mov r4, #1 \n" /* Remember we did this */
242 "ldr r2, =pcm_play_get_more_callback \n" 259 "ldr r2, =pcm_play_get_more_callback \n"
243 "mov r0, r11 \n" /* r0 = &p */ 260 "mov r0, r11 \n" /* r0 = &p */
244 "add r1, r11, #4 \n" /* r1 = &size */ 261 "add r1, r11, #4 \n" /* r1 = &size */
@@ -250,7 +267,7 @@ void fiq_playback(void)
250 267
251 ".exit: \n" /* (r9=0 if stopping, look above) */ 268 ".exit: \n" /* (r9=0 if stopping, look above) */
252 "stmia r11, { r8-r9 } \n" /* save p and size */ 269 "stmia r11, { r8-r9 } \n" /* save p and size */
253 "ldmfd sp!, { r0-r3, lr } \n" 270 "ldmfd sp!, { r0-r4, lr } \n"
254 "subs pc, lr, #4 \n" /* FIQ specific return sequence */ 271 "subs pc, lr, #4 \n" /* FIQ specific return sequence */
255 ".ltorg \n" 272 ".ltorg \n"
256 : /* These must only be integers! No regs */ 273 : /* These must only be integers! No regs */
@@ -264,6 +281,8 @@ void fiq_playback(void) __attribute__((interrupt ("FIQ"))) ICODE_ATTR;
264/* NOTE: direct stack use forbidden by GCC stack handling bug for FIQ */ 281/* NOTE: direct stack use forbidden by GCC stack handling bug for FIQ */
265void fiq_playback(void) 282void fiq_playback(void)
266{ 283{
284 bool new_buffer = false;
285
267#if CONFIG_CPU == PP5002 286#if CONFIG_CPU == PP5002
268 inl(0xcf001040); 287 inl(0xcf001040);
269#endif 288#endif
@@ -271,6 +290,10 @@ void fiq_playback(void)
271 do { 290 do {
272 while (dma_play_data.size > 0) { 291 while (dma_play_data.size > 0) {
273 if (IIS_TX_FREE_COUNT < 2) { 292 if (IIS_TX_FREE_COUNT < 2) {
293 if (new_buffer) {
294 new_buffer = false;
295 pcm_play_dma_started_callback();
296 }
274 return; 297 return;
275 } 298 }
276#if SAMPLE_SIZE == 16 299#if SAMPLE_SIZE == 16
@@ -282,9 +305,15 @@ void fiq_playback(void)
282 dma_play_data.size -= 4; 305 dma_play_data.size -= 4;
283 } 306 }
284 307
308 if (new_buffer) {
309 new_buffer = false;
310 pcm_play_dma_started_callback();
311 }
312
285 /* p is empty, get some more data */ 313 /* p is empty, get some more data */
286 pcm_play_get_more_callback((void **)&dma_play_data.addr, 314 pcm_play_get_more_callback((void **)&dma_play_data.addr,
287 &dma_play_data.size); 315 &dma_play_data.size);
316 new_buffer = true;
288 } while (dma_play_data.size); 317 } while (dma_play_data.size);
289 318
290 /* No more data */ 319 /* No more data */
diff --git a/firmware/target/arm/pcm-telechips.c b/firmware/target/arm/pcm-telechips.c
index 851ebee7de..aff43171f6 100644
--- a/firmware/target/arm/pcm-telechips.c
+++ b/firmware/target/arm/pcm-telechips.c
@@ -27,6 +27,7 @@
27#include "sound.h" 27#include "sound.h"
28#include "i2s.h" 28#include "i2s.h"
29#include "pcm.h" 29#include "pcm.h"
30#include "pcm-internal.h"
30 31
31struct dma_data 32struct dma_data
32{ 33{
@@ -247,6 +248,8 @@ void fiq_handler(void)
247 * r0-r3 and r12 is a working register. 248 * r0-r3 and r12 is a working register.
248 */ 249 */
249 asm volatile ( 250 asm volatile (
251 "stmfd sp!, { r0-r4, lr } \n" /* stack scratch regs and lr */
252 "mov r4, #0 \n" /* Was the callback called? */
250#if defined(CPU_TCC780X) 253#if defined(CPU_TCC780X)
251 "mov r8, #0xc000 \n" /* DAI_TX_IRQ_MASK | DAI_RX_IRQ_MASK */ 254 "mov r8, #0xc000 \n" /* DAI_TX_IRQ_MASK | DAI_RX_IRQ_MASK */
252 "ldr r9, =0xf3001004 \n" /* CREQ */ 255 "ldr r9, =0xf3001004 \n" /* CREQ */
@@ -279,33 +282,41 @@ void fiq_handler(void)
279 "sub r9, r9, #0x10 \n" /* 4 words written */ 282 "sub r9, r9, #0x10 \n" /* 4 words written */
280 "stmia r11, { r8-r9 } \n" /* save p and size */ 283 "stmia r11, { r8-r9 } \n" /* save p and size */
281 284
285 "cmp r4, #0 \n" /* Callback called? */
286 "beq .exit \n"
287 /* "mov r4, #0 \n" If get_more could be called multiple times! */
288 "ldr r2, =pcm_play_dma_started\n"
289 "ldr r2, [r2] \n"
290 "cmp r2, #0 \n"
291 "blxne r2 \n"
292
282 ".exit: \n" 293 ".exit: \n"
294 "ldmfd sp!, { r0-r4, lr } \n"
283 "subs pc, lr, #4 \n" /* FIQ specific return sequence */ 295 "subs pc, lr, #4 \n" /* FIQ specific return sequence */
284 296
285 ".more_data: \n" 297 ".more_data: \n"
286 "stmfd sp!, { r0-r3, lr } \n" /* stack scratch regs and lr */ 298 "mov r4, #1 \n" /* Remember we got more data in this FIQ */
287 "ldr r2, =pcm_play_get_more_callback \n" 299 "ldr r2, =pcm_play_get_more_callback \n"
288 "mov r0, r11 \n" /* r0 = &p */ 300 "mov r0, r11 \n" /* r0 = &p */
289 "add r1, r11, #4 \n" /* r1 = &size */ 301 "add r1, r11, #4 \n" /* r1 = &size */
290 "blx r2 \n" /* call pcm_play_get_more_callback */ 302 "blx r2 \n" /* call pcm_play_get_more_callback */
291 "ldmia r11, { r8-r9 } \n" /* load new p and size */ 303 "ldmia r11, { r8-r9 } \n" /* load new p and size */
292 "cmp r9, #0x10 \n" /* did we actually get enough data? */ 304 "cmp r9, #0x10 \n" /* did we actually get enough data? */
293 "ldmfd sp!, { r0-r3, lr } \n"
294 "bpl .fill_fifo \n" /* not stop and enough? refill */ 305 "bpl .fill_fifo \n" /* not stop and enough? refill */
295 "b .exit \n" 306 "b .exit \n"
296 ".ltorg \n" 307 ".ltorg \n"
297 ); 308 );
298} 309}
299#else /* C version for reference */ 310#else /* C version for reference */
300void fiq_handler(void) ICODE_ATTR __attribute__((naked)); 311void fiq_handler(void) ICODE_ATTR;
301void fiq_handler(void) 312void fiq_handler(void)
302{ 313{
303 asm volatile( "stmfd sp!, {r0-r7, ip, lr} \n" /* Store context */ 314 register bool new_buffer = false;
304 "sub sp, sp, #8 \n"); /* Reserve stack */
305 315
306 if (dma_play_data.size < 16) 316 if (dma_play_data.size < 16)
307 { 317 {
308 /* p is empty, get some more data */ 318 /* p is empty, get some more data */
319 new_buffer = true;
309 pcm_play_get_more_callback((void**)&dma_play_data.p, 320 pcm_play_get_more_callback((void**)&dma_play_data.p,
310 &dma_play_data.size); 321 &dma_play_data.size);
311 } 322 }
@@ -327,9 +338,8 @@ void fiq_handler(void)
327 /* Clear FIQ status */ 338 /* Clear FIQ status */
328 CREQ = DAI_TX_IRQ_MASK | DAI_RX_IRQ_MASK; 339 CREQ = DAI_TX_IRQ_MASK | DAI_RX_IRQ_MASK;
329 340
330 asm volatile( "add sp, sp, #8 \n" /* Cleanup stack */ 341 if (new_buffer)
331 "ldmfd sp!, {r0-r7, ip, lr} \n" /* Restore context */ 342 pcm_play_dma_started_callback();
332 "subs pc, lr, #4 \n"); /* Return from FIQ */
333} 343}
334#endif 344#endif
335 345
diff --git a/firmware/target/arm/pnx0101/pcm-pnx0101.c b/firmware/target/arm/pnx0101/pcm-pnx0101.c
index 9d4ffbd773..d4c17454ed 100644
--- a/firmware/target/arm/pnx0101/pcm-pnx0101.c
+++ b/firmware/target/arm/pnx0101/pcm-pnx0101.c
@@ -21,6 +21,7 @@
21#include "system.h" 21#include "system.h"
22#include "audio.h" 22#include "audio.h"
23#include "string.h" 23#include "string.h"
24#include "pcm-internal.h"
24 25
25#define DMA_BUF_SAMPLES 0x100 26#define DMA_BUF_SAMPLES 0x100
26 27
@@ -63,6 +64,8 @@ static inline void fill_dma_buf(int offset)
63 64
64 if (pcm_playing && !pcm_paused) 65 if (pcm_playing && !pcm_paused)
65 { 66 {
67 bool new_buffer =false;
68
66 do 69 do
67 { 70 {
68 int count; 71 int count;
@@ -102,10 +105,20 @@ static inline void fill_dma_buf(int offset)
102 count--; 105 count--;
103 } 106 }
104 p = tmp_p; 107 p = tmp_p;
108
109 if (new_buffer)
110 {
111 new_buffer = false;
112 pcm_play_dma_started_callback();
113 }
114
105 if (l >= lend) 115 if (l >= lend)
106 return; 116 return;
107 117
108 pcm_play_get_more_callback((void**)&p, &p_size); 118 pcm_play_get_more_callback((void**)&p, &p_size);
119
120 if (p_size)
121 new_buffer = true;
109 } 122 }
110 while (p_size); 123 while (p_size);
111 } 124 }
diff --git a/firmware/target/arm/s3c2440/gigabeat-fx/pcm-meg-fx.c b/firmware/target/arm/s3c2440/gigabeat-fx/pcm-meg-fx.c
index c1c9017fbb..33194ae5d9 100644
--- a/firmware/target/arm/s3c2440/gigabeat-fx/pcm-meg-fx.c
+++ b/firmware/target/arm/s3c2440/gigabeat-fx/pcm-meg-fx.c
@@ -25,6 +25,7 @@
25#include "audio.h" 25#include "audio.h"
26#include "sound.h" 26#include "sound.h"
27#include "file.h" 27#include "file.h"
28#include "pcm-internal.h"
28 29
29/* PCM interrupt routine lockout */ 30/* PCM interrupt routine lockout */
30static struct 31static struct
@@ -235,6 +236,8 @@ void fiq_handler(void)
235 236
236 /* Re-Activate the channel */ 237 /* Re-Activate the channel */
237 DMASKTRIG2 = 0x2; 238 DMASKTRIG2 = 0x2;
239
240 pcm_play_dma_started_callback();
238} 241}
239 242
240size_t pcm_get_bytes_waiting(void) 243size_t pcm_get_bytes_waiting(void)
diff --git a/firmware/target/arm/s3c2440/gigabeat-fx/wmcodec-meg-fx.c b/firmware/target/arm/s3c2440/gigabeat-fx/wmcodec-meg-fx.c
index 01b177da6c..eea4c58e4b 100644
--- a/firmware/target/arm/s3c2440/gigabeat-fx/wmcodec-meg-fx.c
+++ b/firmware/target/arm/s3c2440/gigabeat-fx/wmcodec-meg-fx.c
@@ -99,14 +99,15 @@ void TIMER3(void)
99 INTPND = TIMER3_MASK; 99 INTPND = TIMER3_MASK;
100} 100}
101 101
102void pcmbuf_beep(unsigned int frequency, size_t duration, int amplitude) 102void beep_play(unsigned int frequency, unsigned int duration,
103 unsigned int amplitude)
103{ 104{
104 #define TIMER3_TICK_SEC (TIMER_FREQ / TIMER234_PRESCALE) 105 #define TIMER3_TICK_SEC (TIMER_FREQ / TIMER234_PRESCALE)
105 106
106 unsigned long tcnt, tcmp; 107 unsigned long tcnt, tcmp;
107 int oldstatus; 108 int oldstatus;
108 109
109 if (amplitude <= 0) 110 if (frequency == 0 || duration == 0 || amplitude == 0)
110 { 111 {
111 beep_stop(); /* won't hear it anyway */ 112 beep_stop(); /* won't hear it anyway */
112 return; 113 return;
diff --git a/firmware/target/arm/s3c2440/mini2440/pcm-mini2440.c b/firmware/target/arm/s3c2440/mini2440/pcm-mini2440.c
index 8a6b62f31f..0c69c1e6d3 100644
--- a/firmware/target/arm/s3c2440/mini2440/pcm-mini2440.c
+++ b/firmware/target/arm/s3c2440/mini2440/pcm-mini2440.c
@@ -26,6 +26,7 @@
26#include "audio.h" 26#include "audio.h"
27#include "sound.h" 27#include "sound.h"
28#include "file.h" 28#include "file.h"
29#include "pcm-internal.h"
29 30
30/* PCM interrupt routine lockout */ 31/* PCM interrupt routine lockout */
31static struct 32static struct
@@ -275,6 +276,8 @@ void fiq_handler(void)
275 276
276 /* Re-Activate the channel */ 277 /* Re-Activate the channel */
277 DMASKTRIG2 = 0x2; 278 DMASKTRIG2 = 0x2;
279
280 pcm_play_dma_started_callback();
278} 281}
279 282
280size_t pcm_get_bytes_waiting(void) 283size_t pcm_get_bytes_waiting(void)
diff --git a/firmware/target/arm/s5l8700/pcm-s5l8700.c b/firmware/target/arm/s5l8700/pcm-s5l8700.c
index 08086c37d8..14c515ec47 100644
--- a/firmware/target/arm/s5l8700/pcm-s5l8700.c
+++ b/firmware/target/arm/s5l8700/pcm-s5l8700.c
@@ -27,6 +27,7 @@
27#include "panic.h" 27#include "panic.h"
28#include "audiohw.h" 28#include "audiohw.h"
29#include "pcm.h" 29#include "pcm.h"
30#include "pcm-internal.h"
30#include "pcm_sampr.h" 31#include "pcm_sampr.h"
31#include "dma-target.h" 32#include "dma-target.h"
32#include "mmu-arm.h" 33#include "mmu-arm.h"
@@ -100,6 +101,7 @@ void pcm_play_unlock(void)
100void INT_DMA(void) ICODE_ATTR; 101void INT_DMA(void) ICODE_ATTR;
101void INT_DMA(void) 102void INT_DMA(void)
102{ 103{
104 bool new_buffer = false;
103 DMACOM0 = 7; 105 DMACOM0 = 7;
104 while (!(DMACON0 & (1 << 18))) 106 while (!(DMACON0 & (1 << 18)))
105 { 107 {
@@ -112,8 +114,12 @@ void INT_DMA(void)
112 } 114 }
113 else 115 else
114 { 116 {
115 if (!nextsize) pcm_play_get_more_callback((void**)&nextbuf, &nextsize); 117 if (!nextsize)
116 if (!nextsize) break; 118 {
119 pcm_play_get_more_callback((void**)&nextbuf, &nextsize);
120 if (!nextsize) break;
121 new_buffer = true;
122 }
117 queuedsize = MIN(sizeof(dblbuf), nextsize / 2); 123 queuedsize = MIN(sizeof(dblbuf), nextsize / 2);
118 nextsize -= queuedsize; 124 nextsize -= queuedsize;
119 queuedbuf = nextbuf + nextsize; 125 queuedbuf = nextbuf + nextsize;
@@ -124,7 +130,14 @@ void INT_DMA(void)
124 clean_dcache(); 130 clean_dcache();
125 DMACOM0 = 4; 131 DMACOM0 = 4;
126 DMACOM0 = 7; 132 DMACOM0 = 7;
133
134 if (new_buffer)
135 {
136 pcm_play_dma_started_callback();
137 new_buffer = false;
138 }
127 } 139 }
140
128} 141}
129 142
130void pcm_play_dma_start(const void* addr, size_t size) 143void pcm_play_dma_start(const void* addr, size_t size)
diff --git a/firmware/target/arm/s5l8702/pcm-s5l8702.c b/firmware/target/arm/s5l8702/pcm-s5l8702.c
index c0498a9ce2..dbadf3bac0 100644
--- a/firmware/target/arm/s5l8702/pcm-s5l8702.c
+++ b/firmware/target/arm/s5l8702/pcm-s5l8702.c
@@ -27,6 +27,7 @@
27#include "panic.h" 27#include "panic.h"
28#include "audiohw.h" 28#include "audiohw.h"
29#include "pcm.h" 29#include "pcm.h"
30#include "pcm-internal.h"
30#include "pcm_sampr.h" 31#include "pcm_sampr.h"
31#include "mmu-arm.h" 32#include "mmu-arm.h"
32#include "pcm-target.h" 33#include "pcm-target.h"
@@ -113,6 +114,8 @@ void INT_DMAC0C0(void)
113 DMAC0C0CONFIG = 0x8a81; 114 DMAC0C0CONFIG = 0x8a81;
114 } 115 }
115 else DMAC0C0NEXTLLI = pcm_lli; 116 else DMAC0C0NEXTLLI = pcm_lli;
117
118 pcm_play_dma_started_callback();
116} 119}
117 120
118void pcm_play_dma_start(const void* addr, size_t size) 121void pcm_play_dma_start(const void* addr, size_t size)
diff --git a/firmware/target/arm/tms320dm320/creative-zvm/pcm-creativezvm.c b/firmware/target/arm/tms320dm320/creative-zvm/pcm-creativezvm.c
index 3c54ce81fb..5ec62cf876 100644
--- a/firmware/target/arm/tms320dm320/creative-zvm/pcm-creativezvm.c
+++ b/firmware/target/arm/tms320dm320/creative-zvm/pcm-creativezvm.c
@@ -27,6 +27,7 @@
27#include "dm320.h" 27#include "dm320.h"
28#include "audiohw.h" 28#include "audiohw.h"
29#include "dsp-target.h" 29#include "dsp-target.h"
30#include "pcm-internal.h"
30 31
31void pcm_play_dma_init(void) 32void pcm_play_dma_init(void)
32{ 33{
diff --git a/firmware/target/arm/tms320dm320/mrobe-500/pcm-mr500.c b/firmware/target/arm/tms320dm320/mrobe-500/pcm-mr500.c
index fb94adae71..90c342e868 100644
--- a/firmware/target/arm/tms320dm320/mrobe-500/pcm-mr500.c
+++ b/firmware/target/arm/tms320dm320/mrobe-500/pcm-mr500.c
@@ -28,6 +28,7 @@
28#include "dsp-target.h" 28#include "dsp-target.h"
29#include "dsp/ipc.h" 29#include "dsp/ipc.h"
30#include "mmu-arm.h" 30#include "mmu-arm.h"
31#include "pcm-internal.h"
31 32
32/* This is global to save some latency when pcm_play_dma_get_peak_buffer is 33/* This is global to save some latency when pcm_play_dma_get_peak_buffer is
33 * called. 34 * called.
@@ -178,6 +179,8 @@ void DSPHINT(void)
178 179
179 DEBUGF("pcm_sdram at 0x%08lx, sdem_addr 0x%08lx", 180 DEBUGF("pcm_sdram at 0x%08lx, sdem_addr 0x%08lx",
180 (unsigned long)start, (unsigned long)sdem_addr); 181 (unsigned long)start, (unsigned long)sdem_addr);
182
183 pcm_play_dma_started_callback();
181 } 184 }
182 185
183 break; 186 break;
diff --git a/firmware/target/coldfire/pcm-coldfire.c b/firmware/target/coldfire/pcm-coldfire.c
index a06542c31f..85eeaec815 100644
--- a/firmware/target/coldfire/pcm-coldfire.c
+++ b/firmware/target/coldfire/pcm-coldfire.c
@@ -28,6 +28,7 @@
28#if defined(HAVE_SPDIF_REC) || defined(HAVE_SPDIF_OUT) 28#if defined(HAVE_SPDIF_REC) || defined(HAVE_SPDIF_OUT)
29#include "spdif.h" 29#include "spdif.h"
30#endif 30#endif
31#include "pcm-internal.h"
31 32
32#define IIS_PLAY_DEFPARM ( (freq_ent[FPARM_CLOCKSEL] << 12) | \ 33#define IIS_PLAY_DEFPARM ( (freq_ent[FPARM_CLOCKSEL] << 12) | \
33 (IIS_PLAY & (7 << 8)) | \ 34 (IIS_PLAY & (7 << 8)) | \
@@ -318,6 +319,9 @@ void DMA0(void)
318 SAR0 = (unsigned long)start; /* Source address */ 319 SAR0 = (unsigned long)start; /* Source address */
319 BCR0 = size; /* Bytes to transfer */ 320 BCR0 = size; /* Bytes to transfer */
320 or_l(DMA_EEXT | DMA_INT, &DCR0); /* per request and int ON */ 321 or_l(DMA_EEXT | DMA_INT, &DCR0); /* per request and int ON */
322
323 /* Call buffer callback */
324 pcm_play_dma_started_callback();
321 } 325 }
322 /* else inished playing */ 326 /* else inished playing */
323} /* DMA0 */ 327} /* DMA0 */
diff --git a/firmware/target/coldfire/pcm-mixer-coldfire.c b/firmware/target/coldfire/pcm-mixer-coldfire.c
new file mode 100644
index 0000000000..d8318fffaf
--- /dev/null
+++ b/firmware/target/coldfire/pcm-mixer-coldfire.c
@@ -0,0 +1,134 @@
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
22#define MIXER_OPTIMIZED_MIX_SAMPLES
23#define MIXER_OPTIMIZED_WRITE_SAMPLES
24static struct emac_context
25{
26 unsigned long r[4];
27} emac_context IBSS_ATTR;
28
29/* Save emac context affected in ISR */
30static FORCE_INLINE void save_emac_context(void)
31{
32 asm volatile (
33 "move.l %%macsr, %%d0 \n"
34 "move.l %%accext01, %%d1 \n"
35 "movclr.l %%acc0, %%a0 \n"
36 "movclr.l %%acc1, %%a1 \n"
37 "movem.l %%d0-%%d1/%%a0-%%a1, (%0) \n"
38 :
39 : "a"(&emac_context)
40 : "d0", "d1", "a0", "a1");
41}
42
43/* Restore emac context affected in ISR */
44static FORCE_INLINE void restore_emac_context(void)
45{
46 asm volatile (
47 "movem.l (%0), %%d0-%%d1/%%a0-%%a1 \n"
48 "move.l %%a1, %%acc1 \n"
49 "move.l %%a0, %%acc0 \n"
50 "move.l %%d1, %%accext01 \n"
51 "move.l %%d0, %%macsr \n"
52 :
53 : "a"(&emac_context)
54 : "d0", "d1", "a0", "a1");
55}
56
57/* Mix channels' samples and apply gain factors */
58static FORCE_INLINE void mix_samples(void *out,
59 void *src0,
60 int32_t src0_amp,
61 void *src1,
62 int32_t src1_amp,
63 size_t size)
64{
65 uint32_t s0, s1, s2, s3;
66 save_emac_context();
67 coldfire_set_macsr(EMAC_ROUND | EMAC_SATURATE);
68
69 asm volatile (
70 "move.l (%1)+, %5 \n"
71 "1: \n"
72 "movea.w %5, %4 \n"
73 "asr.l %10, %5 \n"
74 "mac.l %4, %8, %%acc0 \n"
75 "mac.l %5, %8, (%2)+, %5, %%acc1 \n"
76 "movea.w %5, %4 \n"
77 "asr.l %10, %5 \n"
78 "mac.l %4, %9, %%acc0 \n"
79 "mac.l %5, %9, (%1)+, %5, %%acc1 \n"
80 "movclr.l %%acc0, %6 \n"
81 "movclr.l %%acc1, %7 \n"
82 "swap.w %6 \n"
83 "move.w %6, %7 \n"
84 "move.l %7, (%0)+ \n"
85 "subq.l #4, %3 \n"
86 "bhi.b 1b \n"
87 : "+a"(out), "+a"(src0), "+a"(src1), "+d"(size),
88 "=&a"(s0), "=&d"(s1), "=&d"(s2), "=&d"(s3)
89 : "r"(src0_amp), "r"(src1_amp), "d"(16)
90 );
91
92 restore_emac_context();
93}
94
95/* Write channel's samples and apply gain factor */
96static FORCE_INLINE void write_samples(void *out,
97 void *src,
98 int32_t amp,
99 size_t size)
100{
101 if (LIKELY(amp == MIX_AMP_UNITY))
102 {
103 /* Channel is unity amplitude */
104 memcpy(out, src, size);
105 }
106 else
107 {
108 /* Channel needs amplitude cut */
109 uint32_t s0, s1, s2, s3;
110 save_emac_context();
111 coldfire_set_macsr(EMAC_ROUND | EMAC_SATURATE);
112
113 asm volatile (
114 "move.l (%1)+, %4 \n"
115 "1: \n"
116 "movea.w %4, %3 \n"
117 "asr.l %8, %4 \n"
118 "mac.l %3, %7, %%acc0 \n"
119 "mac.l %4, %7, (%1)+, %4, %%acc1 \n"
120 "movclr.l %%acc0, %5 \n"
121 "movclr.l %%acc1, %6 \n"
122 "swap.w %5 \n"
123 "move.w %5, %6 \n"
124 "move.l %6, (%0)+ \n"
125 "subq.l #4, %2 \n"
126 "bhi.b 1b \n"
127 : "+a"(out), "+a"(src), "+d"(size),
128 "=&a"(s0), "=&d"(s1), "=&d"(s2), "=&d"(s3)
129 : "r"(amp), "d"(16)
130 );
131
132 restore_emac_context();
133 }
134}
diff --git a/firmware/target/hosted/android/pcm-android.c b/firmware/target/hosted/android/pcm-android.c
index 88792cd76f..cbd6cb3228 100644
--- a/firmware/target/hosted/android/pcm-android.c
+++ b/firmware/target/hosted/android/pcm-android.c
@@ -23,14 +23,18 @@
23#include <stdbool.h> 23#include <stdbool.h>
24#define _SYSTEM_WITH_JNI /* for getJavaEnvironment */ 24#define _SYSTEM_WITH_JNI /* for getJavaEnvironment */
25#include <system.h> 25#include <system.h>
26#include <pthread.h>
26#include "debug.h" 27#include "debug.h"
27#include "pcm.h" 28#include "pcm.h"
29#include "pcm-internal.h"
28 30
29extern JNIEnv *env_ptr; 31extern JNIEnv *env_ptr;
30 32
31/* infos about our pcm chunks */ 33/* infos about our pcm chunks */
32static size_t pcm_data_size; 34static size_t pcm_data_size;
33static char *pcm_data_start; 35static char *pcm_data_start;
36static int audio_locked = 0;
37static pthread_mutex_t audio_lock_mutex = PTHREAD_MUTEX_INITIALIZER;
34 38
35/* cache frequently called methods */ 39/* cache frequently called methods */
36static jmethodID play_pause_method; 40static jmethodID play_pause_method;
@@ -42,6 +46,20 @@ static jobject RockboxPCM_instance;
42 46
43 47
44/* 48/*
49 * mutex lock/unlock wrappers neatness' sake
50 */
51static inline void lock_audio(void)
52{
53 pthread_mutex_lock(&audio_lock_mutex);
54}
55
56static inline void unlock_audio(void)
57{
58 pthread_mutex_unlock(&audio_lock_mutex);
59}
60
61
62/*
45 * write pcm samples to the hardware. Calls AudioTrack.write directly (which 63 * write pcm samples to the hardware. Calls AudioTrack.write directly (which
46 * is usually a blocking call) 64 * is usually a blocking call)
47 * 65 *
@@ -54,10 +72,17 @@ JNIEXPORT jint JNICALL
54Java_org_rockbox_RockboxPCM_nativeWrite(JNIEnv *env, jobject this, 72Java_org_rockbox_RockboxPCM_nativeWrite(JNIEnv *env, jobject this,
55 jbyteArray temp_array, jint max_size) 73 jbyteArray temp_array, jint max_size)
56{ 74{
75 bool new_buffer = false;
76
77 lock_audio();
78
57 jint left = max_size; 79 jint left = max_size;
58 80
59 if (!pcm_data_size) /* get some initial data */ 81 if (!pcm_data_size) /* get some initial data */
82 {
83 new_buffer = true;
60 pcm_play_get_more_callback((void**) &pcm_data_start, &pcm_data_size); 84 pcm_play_get_more_callback((void**) &pcm_data_start, &pcm_data_size);
85 }
61 86
62 while(left > 0 && pcm_data_size) 87 while(left > 0 && pcm_data_size)
63 { 88 {
@@ -70,23 +95,49 @@ Java_org_rockbox_RockboxPCM_nativeWrite(JNIEnv *env, jobject this,
70 95
71 ret = (*env)->CallIntMethod(env, this, write_method, 96 ret = (*env)->CallIntMethod(env, this, write_method,
72 temp_array, 0, transfer_size); 97 temp_array, 0, transfer_size);
98
99 if (new_buffer)
100 {
101 new_buffer = false;
102 pcm_play_dma_started_callback();
103
104 /* NOTE: might need to release the mutex and sleep here if the
105 buffer is shorter than the required buffer (like pcm-sdl.c) to
106 have the mixer clocked at a regular interval */
107 }
108
73 if (ret < 0) 109 if (ret < 0)
110 {
111 unlock_audio();
74 return ret; 112 return ret;
113 }
75 114
76 if (pcm_data_size == 0) /* need new data */ 115 if (pcm_data_size == 0) /* need new data */
116 {
117 new_buffer = true;
77 pcm_play_get_more_callback((void**)&pcm_data_start, &pcm_data_size); 118 pcm_play_get_more_callback((void**)&pcm_data_start, &pcm_data_size);
119 }
78 else /* increment data pointer and feed more */ 120 else /* increment data pointer and feed more */
79 pcm_data_start += transfer_size; 121 pcm_data_start += transfer_size;
80 } 122 }
123
124 if (new_buffer && pcm_data_size)
125 pcm_play_dma_started_callback();
126
127 unlock_audio();
81 return max_size - left; 128 return max_size - left;
82} 129}
83 130
84void pcm_play_lock(void) 131void pcm_play_lock(void)
85{ 132{
133 if (++audio_locked == 1)
134 lock_audio();
86} 135}
87 136
88void pcm_play_unlock(void) 137void pcm_play_unlock(void)
89{ 138{
139 if (--audio_locked == 0)
140 unlock_audio();
90} 141}
91 142
92void pcm_dma_apply_settings(void) 143void pcm_dma_apply_settings(void)
@@ -153,8 +204,6 @@ void pcm_play_dma_init(void)
153 set_volume_method = e->GetMethodID(env_ptr, RockboxPCM_class, "set_volume", "(I)V"); 204 set_volume_method = e->GetMethodID(env_ptr, RockboxPCM_class, "set_volume", "(I)V");
154 stop_method = e->GetMethodID(env_ptr, RockboxPCM_class, "stop", "()V"); 205 stop_method = e->GetMethodID(env_ptr, RockboxPCM_class, "stop", "()V");
155 write_method = e->GetMethodID(env_ptr, RockboxPCM_class, "write", "([BII)I"); 206 write_method = e->GetMethodID(env_ptr, RockboxPCM_class, "write", "([BII)I");
156 /* get initial pcm data, if any */
157 pcm_play_get_more_callback((void*)&pcm_data_start, &pcm_data_size);
158} 207}
159 208
160void pcm_postinit(void) 209void pcm_postinit(void)
@@ -173,6 +222,7 @@ void pcm_shutdown(void)
173 JNIEnv e = *env_ptr; 222 JNIEnv e = *env_ptr;
174 jmethodID release = e->GetMethodID(env_ptr, RockboxPCM_class, "release", "()V"); 223 jmethodID release = e->GetMethodID(env_ptr, RockboxPCM_class, "release", "()V");
175 e->CallVoidMethod(env_ptr, RockboxPCM_instance, release); 224 e->CallVoidMethod(env_ptr, RockboxPCM_instance, release);
225 pthread_mutex_destroy(&audio_lock_mutex);
176} 226}
177 227
178/* Due to limitations of default_event_handler(), parameters gets swallowed when 228/* Due to limitations of default_event_handler(), parameters gets swallowed when
diff --git a/firmware/target/hosted/maemo/pcm-gstreamer.c b/firmware/target/hosted/maemo/pcm-gstreamer.c
index e3e40f0619..6069801fba 100644
--- a/firmware/target/hosted/maemo/pcm-gstreamer.c
+++ b/firmware/target/hosted/maemo/pcm-gstreamer.c
@@ -54,6 +54,7 @@
54#endif 54#endif
55 55
56#include "pcm.h" 56#include "pcm.h"
57#include "pcm-internal.h"
57#include "pcm_sampr.h" 58#include "pcm_sampr.h"
58 59
59/*#define LOGF_ENABLE*/ 60/*#define LOGF_ENABLE*/
@@ -182,6 +183,8 @@ static void feed_data(GstElement * appsrc, guint size_hint, void *unused)
182 183
183 if (ret != 0) 184 if (ret != 0)
184 DEBUGF("push-buffer error result: %d\n", ret); 185 DEBUGF("push-buffer error result: %d\n", ret);
186
187 pcm_play_dma_started_callback();
185 } else 188 } else
186 { 189 {
187 DEBUGF("feed_data: No Data.\n"); 190 DEBUGF("feed_data: No Data.\n");
diff --git a/firmware/target/hosted/sdl/pcm-sdl.c b/firmware/target/hosted/sdl/pcm-sdl.c
index 7780083b99..dfdd90f29b 100644
--- a/firmware/target/hosted/sdl/pcm-sdl.c
+++ b/firmware/target/hosted/sdl/pcm-sdl.c
@@ -30,6 +30,7 @@
30#include "sound.h" 30#include "sound.h"
31#include "audiohw.h" 31#include "audiohw.h"
32#include "system.h" 32#include "system.h"
33#include "panic.h"
33 34
34#ifdef HAVE_RECORDING 35#ifdef HAVE_RECORDING
35#include "audiohw.h" 36#include "audiohw.h"
@@ -39,6 +40,7 @@
39#endif 40#endif
40 41
41#include "pcm.h" 42#include "pcm.h"
43#include "pcm-internal.h"
42#include "pcm_sampr.h" 44#include "pcm_sampr.h"
43 45
44/*#define LOGF_ENABLE*/ 46/*#define LOGF_ENABLE*/
@@ -71,15 +73,19 @@ static struct pcm_udata
71 73
72static SDL_AudioSpec obtained; 74static SDL_AudioSpec obtained;
73static SDL_AudioCVT cvt; 75static SDL_AudioCVT cvt;
76static int audio_locked = 0;
77static SDL_mutex *audio_lock;
74 78
75void pcm_play_lock(void) 79void pcm_play_lock(void)
76{ 80{
77 SDL_LockAudio(); 81 if (++audio_locked == 1)
82 SDL_LockMutex(audio_lock);
78} 83}
79 84
80void pcm_play_unlock(void) 85void pcm_play_unlock(void)
81{ 86{
82 SDL_UnlockAudio(); 87 if (--audio_locked == 0)
88 SDL_UnlockMutex(audio_lock);
83} 89}
84 90
85static void pcm_dma_apply_settings_nolock(void) 91static void pcm_dma_apply_settings_nolock(void)
@@ -227,14 +233,19 @@ static void write_to_soundcard(struct pcm_udata *udata)
227static void sdl_audio_callback(struct pcm_udata *udata, Uint8 *stream, int len) 233static void sdl_audio_callback(struct pcm_udata *udata, Uint8 *stream, int len)
228{ 234{
229 logf("sdl_audio_callback: len %d, pcm %d\n", len, pcm_data_size); 235 logf("sdl_audio_callback: len %d, pcm %d\n", len, pcm_data_size);
236
237 bool new_buffer = false;
230 udata->stream = stream; 238 udata->stream = stream;
231 239
240 SDL_LockMutex(audio_lock);
241
232 /* Write what we have in the PCM buffer */ 242 /* Write what we have in the PCM buffer */
233 if (pcm_data_size > 0) 243 if (pcm_data_size > 0)
234 goto start; 244 goto start;
235 245
236 /* Audio card wants more? Get some more then. */ 246 /* Audio card wants more? Get some more then. */
237 while (len > 0) { 247 while (len > 0) {
248 new_buffer = true;
238 pcm_play_get_more_callback((void **)&pcm_data, &pcm_data_size); 249 pcm_play_get_more_callback((void **)&pcm_data, &pcm_data_size);
239 start: 250 start:
240 if (pcm_data_size != 0) { 251 if (pcm_data_size != 0) {
@@ -246,6 +257,28 @@ static void sdl_audio_callback(struct pcm_udata *udata, Uint8 *stream, int len)
246 udata->num_in *= pcm_sample_bytes; 257 udata->num_in *= pcm_sample_bytes;
247 udata->num_out *= pcm_sample_bytes; 258 udata->num_out *= pcm_sample_bytes;
248 259
260
261 if (new_buffer)
262 {
263 new_buffer = false;
264 pcm_play_dma_started_callback();
265
266 if ((size_t)len > udata->num_out)
267 {
268 int delay = pcm_data_size*250 / pcm_sampr - 1;
269
270 if (delay > 0)
271 {
272 SDL_UnlockMutex(audio_lock);
273 SDL_Delay(delay);
274 SDL_LockMutex(audio_lock);
275
276 if (!pcm_is_playing())
277 break;
278 }
279 }
280 }
281
249 pcm_data += udata->num_in; 282 pcm_data += udata->num_in;
250 pcm_data_size -= udata->num_in; 283 pcm_data_size -= udata->num_in;
251 udata->stream += udata->num_out; 284 udata->stream += udata->num_out;
@@ -255,6 +288,8 @@ static void sdl_audio_callback(struct pcm_udata *udata, Uint8 *stream, int len)
255 break; 288 break;
256 } 289 }
257 } 290 }
291
292 SDL_UnlockMutex(audio_lock);
258} 293}
259 294
260const void * pcm_play_dma_get_peak_buffer(int *count) 295const void * pcm_play_dma_get_peak_buffer(int *count)
@@ -320,6 +355,14 @@ void pcm_play_dma_init(void)
320 return; 355 return;
321 } 356 }
322 357
358 audio_lock = SDL_CreateMutex();
359
360 if (!audio_lock)
361 {
362 panicf("Could not create audio_lock\n");
363 return;
364 }
365
323 SDL_AudioSpec wanted_spec; 366 SDL_AudioSpec wanted_spec;
324#ifdef DEBUG 367#ifdef DEBUG
325 udata.debug = NULL; 368 udata.debug = NULL;
diff --git a/firmware/target/mips/ingenic_jz47xx/pcm-jz4740.c b/firmware/target/mips/ingenic_jz47xx/pcm-jz4740.c
index 5cd9c33e18..cfc3c9ef8e 100644
--- a/firmware/target/mips/ingenic_jz47xx/pcm-jz4740.c
+++ b/firmware/target/mips/ingenic_jz47xx/pcm-jz4740.c
@@ -25,6 +25,7 @@
25#include "audio.h" 25#include "audio.h"
26#include "sound.h" 26#include "sound.h"
27#include "pcm.h" 27#include "pcm.h"
28#include "pcm-internal.h"
28#include "jz4740.h" 29#include "jz4740.h"
29 30
30 31
@@ -109,6 +110,7 @@ static inline void play_dma_callback(void)
109 { 110 {
110 set_dma(start, size); 111 set_dma(start, size);
111 REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) |= DMAC_DCCSR_EN; 112 REG_DMAC_DCCSR(DMA_AIC_TX_CHANNEL) |= DMAC_DCCSR_EN;
113 pcm_play_dma_started_callback();
112 } 114 }
113} 115}
114 116