diff options
author | Michael Sevakis <jethead71@rockbox.org> | 2011-06-29 06:37:04 +0000 |
---|---|---|
committer | Michael Sevakis <jethead71@rockbox.org> | 2011-06-29 06:37:04 +0000 |
commit | a2b6703a369f6cdbfec1f150c408dadc877631fb (patch) | |
tree | 3145a8c1372c44711d38feefeba39c7d4098f139 /apps | |
parent | 8411614b8a068a4f274c3841aa55aab1df1bc246 (diff) | |
download | rockbox-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
Diffstat (limited to 'apps')
-rw-r--r-- | apps/SOURCES | 3 | ||||
-rw-r--r-- | apps/action.c | 2 | ||||
-rw-r--r-- | apps/beep.c | 142 | ||||
-rw-r--r-- | apps/gui/wps.c | 16 | ||||
-rw-r--r-- | apps/misc.c | 5 | ||||
-rw-r--r-- | apps/misc.h | 3 | ||||
-rw-r--r-- | apps/pcmbuf.c | 319 | ||||
-rw-r--r-- | apps/pcmbuf.h | 3 | ||||
-rw-r--r-- | apps/playback.c | 20 | ||||
-rw-r--r-- | apps/plugin.c | 5 | ||||
-rw-r--r-- | apps/plugin.h | 10 | ||||
-rw-r--r-- | apps/plugins/fft/fft.c | 13 | ||||
-rw-r--r-- | apps/recorder/keyboard.c | 2 | ||||
-rw-r--r-- | apps/voice_thread.c | 279 | ||||
-rw-r--r-- | apps/voice_thread.h | 5 |
15 files changed, 468 insertions, 359 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 | |||
170 | playback.c | 170 | playback.c |
171 | codecs.c | 171 | codecs.c |
172 | dsp.c | 172 | dsp.c |
173 | #ifndef HAVE_HARDWARE_BEEP | ||
174 | beep.c | ||
175 | #endif | ||
173 | #ifdef HAVE_PITCHSCREEN | 176 | #ifdef HAVE_PITCHSCREEN |
174 | tdspeed.c | 177 | tdspeed.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 | |||
29 | static int32_t beep_phase; /* Phase of square wave generator */ | ||
30 | static uint32_t beep_step; /* Step of square wave generator on each sample */ | ||
31 | static uint32_t beep_amplitude; /* Amplitude of square wave generator */ | ||
32 | static 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) | ||
36 | static uint32_t beep_buf[BEEP_BUF_COUNT] IBSS_ATTR; | ||
37 | |||
38 | /* Actually output samples into beep_buf */ | ||
39 | #if defined(CPU_ARM) | ||
40 | static 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) | ||
57 | static 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 | ||
77 | static 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 */ | ||
97 | static void __attribute__((noinline)) ICODE_ATTR | ||
98 | beep_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 */ | ||
114 | void 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 | ||
122 | void pause_action(bool may_fade, bool updatewps) | 122 | void 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 | ||
141 | void unpause_action(bool may_fade, bool updatewps) | 145 | void 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 | ||
149 | void fade(bool fade_in, bool updatewps) | 157 | void 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 | ||
208 | static bool update_onvol_change(enum screen_type screen) | 217 | static 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 | ||
103 | void beep_play(unsigned int frequency, unsigned int duration, | ||
104 | unsigned int amplitude); | ||
105 | |||
103 | enum current_activity { | 106 | enum 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 */ |
92 | static bool track_transition IDATA_ATTR; | 90 | static bool track_transition IDATA_ATTR; |
93 | 91 | ||
92 | /* Fade effect */ | ||
93 | static unsigned int fade_vol = MIX_AMP_UNITY; | ||
94 | |||
95 | /* Voice */ | ||
96 | static bool soft_mode = false; | ||
97 | |||
94 | #ifdef HAVE_CROSSFADE | 98 | #ifdef HAVE_CROSSFADE |
95 | /* Crossfade buffer */ | 99 | /* Crossfade buffer */ |
96 | static char *fadebuf IDATA_ATTR; | 100 | static char *fadebuf IDATA_ATTR; |
@@ -121,11 +125,6 @@ static size_t last_chunksize IDATA_ATTR; | |||
121 | static size_t pcmbuf_unplayed_bytes IDATA_ATTR; | 125 | static size_t pcmbuf_unplayed_bytes IDATA_ATTR; |
122 | static size_t pcmbuf_watermark IDATA_ATTR; | 126 | static size_t pcmbuf_watermark IDATA_ATTR; |
123 | 127 | ||
124 | /* Voice */ | ||
125 | static char *voicebuf IDATA_ATTR; | ||
126 | static struct chunkdesc *mix_chunk IDATA_ATTR; | ||
127 | static size_t pcmbuf_mix_sample IDATA_ATTR; | ||
128 | |||
129 | static bool low_latency_mode = false; | 128 | static bool low_latency_mode = false; |
130 | static bool flush_pcmbuf = false; | 129 | static 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. */ |
318 | static bool prepare_insert(size_t length) | 317 | static 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|... */ |
470 | size_t pcmbuf_init(unsigned char *bufend) | 471 | size_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 */ |
697 | void pcmbuf_play_start(void) | 699 | void 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 | ||
709 | void pcmbuf_play_stop(void) | 712 | void 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) | |||
737 | void pcmbuf_pause(bool pause) | 739 | void 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). */ | ||
1037 | static int pcmbuf_usage(void) | ||
1038 | { | ||
1039 | return pcmbuf_unplayed_bytes * 100 / pcmbuf_size; | ||
1040 | } | ||
1041 | |||
1042 | static 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 | |||
1056 | void *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 | |||
1083 | void 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 */ | ||
1087 | static 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 */ | ||
1098 | void pcmbuf_soft_mode(bool shhh) | ||
1099 | { | ||
1100 | soft_mode = shhh; | ||
1101 | pcmbuf_update_volume(); | ||
1102 | } | ||
1103 | |||
1104 | /* Fade channel in or out */ | ||
1105 | void 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 | ||
1179 | bool pcmbuf_is_lowdata(void) | 1151 | bool pcmbuf_is_lowdata(void) |
@@ -1201,107 +1173,6 @@ void pcmbuf_set_low_latency(bool state) | |||
1201 | 1173 | ||
1202 | unsigned long pcmbuf_get_latency(void) | 1174 | unsigned 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. */ | ||
1213 | void 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 */ |
67 | void pcmbuf_fade(bool fade, bool in); | ||
68 | void pcmbuf_soft_mode(bool shhh); | ||
67 | bool pcmbuf_is_lowdata(void); | 69 | bool pcmbuf_is_lowdata(void); |
68 | void pcmbuf_set_low_latency(bool state); | 70 | void pcmbuf_set_low_latency(bool state); |
69 | unsigned long pcmbuf_get_latency(void); | 71 | unsigned long pcmbuf_get_latency(void); |
70 | void 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? */ |
3171 | bool audio_pcmbuf_may_play(void) | 3185 | bool 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 | ||
783 | int plugin_load(const char* plugin, const void* parameter) | 786 | int 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 ********************************/ |
1140 | static 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 **/ |
1142 | static inline bool fft_init_fft_lib(void) | 1146 | static inline bool fft_init_fft_lib(void) |
@@ -1156,7 +1160,8 @@ static inline bool fft_init_fft_lib(void) | |||
1156 | static inline bool fft_get_fft(void) | 1160 | static 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 */ |
1297 | static bool fft_have_fft(void) | 1302 | static 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 | ||
1302 | static inline void fft_free_fft_output(void) | 1307 | static 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 */ |
61 | static unsigned int voice_thread_id = 0; | 67 | static unsigned int voice_thread_id = 0; |
62 | static 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 | ||
74 | static long voice_stack[(DEFAULT_STACK_SIZE + VOICE_STACK_EXTRA)/sizeof(long)] | ||
75 | IBSS_ATTR_VOICE_STACK; | ||
63 | static const char voice_thread_name[] = "voice"; | 76 | static const char voice_thread_name[] = "voice"; |
64 | 77 | ||
65 | /* Voice thread synchronization objects */ | 78 | /* Voice thread synchronization objects */ |
66 | static struct event_queue voice_queue SHAREDBSS_ATTR; | 79 | static struct event_queue voice_queue SHAREDBSS_ATTR; |
67 | static struct mutex voice_mutex SHAREDBSS_ATTR; | ||
68 | static struct queue_sender_list voice_queue_sender_list SHAREDBSS_ATTR; | 80 | static struct queue_sender_list voice_queue_sender_list SHAREDBSS_ATTR; |
69 | static bool voice_done SHAREDDATA_ATTR = true; | 81 | static bool voice_done SHAREDDATA_ATTR = true; |
70 | 82 | ||
71 | /* Buffer for decoded samples */ | 83 | /* Buffer for decoded samples */ |
72 | static spx_int16_t voice_output_buf[VOICE_FRAME_SIZE] CACHEALIGN_ATTR; | 84 | static 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 */ | ||
95 | static size_t voicebuf_sizes[VOICE_FRAMES]; | ||
96 | static uint32_t (* voicebuf)[VOICE_PCM_FRAME_COUNT]; | ||
97 | static 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 | |||
74 | enum voice_thread_states | 102 | enum 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 */ |
99 | struct voice_thread_data | 126 | struct 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 */ |
113 | static inline bool playback_is_playing(void) | 141 | static 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 */ | ||
147 | static 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 */ | ||
159 | static 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 */ | ||
170 | static 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 */ | ||
177 | static 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 */ | ||
190 | static 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 */ |
119 | void mp3_play_data(const unsigned char* start, int size, | 196 | void 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 | ||
153 | void mp3_play_pause(bool play) | 223 | void 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 */ |
160 | bool mp3_is_playing(void) | 230 | bool 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 */ |
170 | void voice_stop(void) | 237 | void 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. */ |
191 | void voice_wait(void) | 244 | void 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 */ |
300 | static void voice_thread(void) | 364 | static 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 */ |
435 | void voice_thread_init(void) | 469 | void 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 */ |
458 | void voice_thread_set_priority(int priority) | 491 | void 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 */ | ||
501 | size_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 | ||
30 | void voice_wait(void); | 30 | void voice_wait(void); |
31 | void voice_stop(void); | 31 | void voice_stop(void); |
32 | |||
32 | void voice_thread_init(void); | 33 | void voice_thread_init(void); |
33 | void voice_thread_resume(void); | 34 | void voice_thread_resume(void); |
35 | #ifdef HAVE_PRIORITY_SCHEDULING | ||
34 | void voice_thread_set_priority(int priority); | 36 | void voice_thread_set_priority(int priority); |
37 | #endif | ||
38 | |||
39 | size_t voicebuf_init(unsigned char *bufend); | ||
35 | 40 | ||
36 | #endif /* VOICE_THREAD_H */ | 41 | #endif /* VOICE_THREAD_H */ |