diff options
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 */ |