diff options
Diffstat (limited to 'apps/pcmbuf.c')
| -rw-r--r-- | apps/pcmbuf.c | 319 |
1 files changed, 95 insertions, 224 deletions
diff --git a/apps/pcmbuf.c b/apps/pcmbuf.c index 7201d39..f2f94e3 100644 --- a/apps/pcmbuf.c +++ b/apps/pcmbuf.c @@ -23,8 +23,9 @@ #include "system.h" #include "debug.h" #include <kernel.h> -#include "pcmbuf.h" #include "pcm.h" +#include "pcm_mixer.h" +#include "pcmbuf.h" #include "playback.h" #include "codec_thread.h" @@ -49,9 +50,6 @@ #define PCMBUF_MIN_CHUNK 4096 /* We try to never feed a chunk smaller than this to the DMA */ #define CROSSFADE_BUFSIZE 8192 /* Size of the crossfade buffer */ -#define AUX_BUFSIZE 512 /* Size of the aux buffer; can be 512 if no - resampling or timestretching is allowed in - the aux channel, must be 2048 otherwise */ /* number of bytes played per second (sample rate * 2 channels * 2 bytes/sample) */ #define BYTERATE (NATIVE_FREQUENCY * 4) @@ -91,6 +89,12 @@ static struct chunkdesc *first_desc; /* Gapless playback */ static bool track_transition IDATA_ATTR; +/* Fade effect */ +static unsigned int fade_vol = MIX_AMP_UNITY; + +/* Voice */ +static bool soft_mode = false; + #ifdef HAVE_CROSSFADE /* Crossfade buffer */ static char *fadebuf IDATA_ATTR; @@ -121,11 +125,6 @@ static size_t last_chunksize IDATA_ATTR; static size_t pcmbuf_unplayed_bytes IDATA_ATTR; static size_t pcmbuf_watermark IDATA_ATTR; -/* Voice */ -static char *voicebuf IDATA_ATTR; -static struct chunkdesc *mix_chunk IDATA_ATTR; -static size_t pcmbuf_mix_sample IDATA_ATTR; - static bool low_latency_mode = false; static bool flush_pcmbuf = false; @@ -317,10 +316,12 @@ static void boost_codec_thread(int pcm_fill_state) * Also maintain buffer level above the watermark. */ static bool prepare_insert(size_t length) { + bool playing = mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK) != CHANNEL_STOPPED; + if (low_latency_mode) { /* 1/4s latency. */ - if (!LOW_DATA(1) && pcm_is_playing()) + if (!LOW_DATA(1) && playing) return false; } @@ -329,7 +330,7 @@ static bool prepare_insert(size_t length) return false; /* Maintain the buffer level above the watermark */ - if (pcm_is_playing()) + if (playing) { /* Only codec thread initiates boost - voice boosts the cpu when playing a clip */ @@ -351,7 +352,7 @@ static bool prepare_insert(size_t length) } #endif } - else /* pcm_is_playing */ + else /* !playing */ { /* Boost CPU for pre-buffer */ trigger_cpu_boost(); @@ -469,11 +470,14 @@ static size_t get_next_required_pcmbuf_size(void) * ...|---------PCMBUF---------|FADEBUF|VOICEBUF|DESCS|... */ size_t pcmbuf_init(unsigned char *bufend) { + unsigned char *voicebuf; + pcmbuf_bufend = bufend; pcmbuf_size = get_next_required_pcmbuf_size(); write_chunk = (struct chunkdesc *)pcmbuf_bufend - NUM_CHUNK_DESCS(pcmbuf_size); - voicebuf = (char *)write_chunk - AUX_BUFSIZE; + voicebuf = (unsigned char *)write_chunk - + voicebuf_init((unsigned char *)write_chunk); #ifdef HAVE_CROSSFADE fadebuf = voicebuf - CROSSFADE_BUFSIZE; pcmbuffer = fadebuf - pcmbuf_size; @@ -491,6 +495,8 @@ size_t pcmbuf_init(unsigned char *bufend) pcmbuf_play_stop(); + pcmbuf_soft_mode(false); + return pcmbuf_bufend - pcmbuffer; } @@ -572,7 +578,7 @@ bool pcmbuf_start_track_change(bool auto_skip) #ifdef HAVE_CROSSFADE pcmbuf_is_crossfade_active() || #endif - !pcm_is_playing()) + mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK) == CHANNEL_STOPPED) { pcmbuf_play_stop(); pcm_play_unlock(); @@ -652,10 +658,6 @@ static void pcmbuf_pcm_callback(unsigned char** start, size_t* size) write_end_chunk->link = pcmbuf_current; write_end_chunk = pcmbuf_current; - /* If we've read over the mix chunk while it's still mixing there */ - if (pcmbuf_current == mix_chunk) - mix_chunk = NULL; - #ifdef HAVE_CROSSFADE /* If we've read over the crossfade chunk while it's still fading */ if (pcmbuf_current == crossfade_chunk) @@ -696,23 +698,23 @@ static void pcmbuf_pcm_callback(unsigned char** start, size_t* size) /* Force playback */ void pcmbuf_play_start(void) { - if (!pcm_is_playing() && pcmbuf_unplayed_bytes && read_chunk != NULL) + if (mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK) == CHANNEL_STOPPED && + pcmbuf_unplayed_bytes && read_chunk != NULL) { logf("pcmbuf_play_start"); last_chunksize = read_chunk->size; pcmbuf_unplayed_bytes -= last_chunksize; - pcm_play_data(pcmbuf_pcm_callback, - read_chunk->addr, last_chunksize); + mixer_channel_play_data(PCM_MIXER_CHAN_PLAYBACK, + pcmbuf_pcm_callback, NULL, 0); } } void pcmbuf_play_stop(void) { logf("pcmbuf_play_stop"); - pcm_play_stop(); + mixer_channel_stop(PCM_MIXER_CHAN_PLAYBACK); pcmbuf_unplayed_bytes = 0; - mix_chunk = NULL; if (read_chunk) { write_end_chunk->link = read_chunk; write_end_chunk = read_end_chunk; @@ -737,8 +739,9 @@ void pcmbuf_play_stop(void) void pcmbuf_pause(bool pause) { logf("pcmbuf_pause: %s", pause?"pause":"play"); - if (pcm_is_playing()) - pcm_play_pause(!pause); + + if (mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK) != CHANNEL_STOPPED) + mixer_channel_play_pause(PCM_MIXER_CHAN_PLAYBACK, !pause); else if (!pause) pcmbuf_play_start(); } @@ -1031,102 +1034,6 @@ bool pcmbuf_is_same_size(void) #endif /* HAVE_CROSSFADE */ -/** Voice */ - -/* Returns pcm buffer usage in percents (0 to 100). */ -static int pcmbuf_usage(void) -{ - return pcmbuf_unplayed_bytes * 100 / pcmbuf_size; -} - -static int pcmbuf_mix_free(void) -{ - if (mix_chunk) - { - size_t my_mix_end = - (size_t)&((int16_t *)mix_chunk->addr)[pcmbuf_mix_sample]; - size_t my_write_pos = (size_t)&pcmbuffer[pcmbuffer_pos]; - if (my_write_pos < my_mix_end) - my_write_pos += pcmbuf_size; - return (my_write_pos - my_mix_end) * 100 / pcmbuf_unplayed_bytes; - } - return 100; -} - -void *pcmbuf_request_voice_buffer(int *count) -{ - /* A get-it-to-work-for-now hack (audio status could change by - completion) */ - if (audio_status() & AUDIO_STATUS_PLAY) - { - if (read_chunk == NULL) - { - return NULL; - } - else if (pcmbuf_usage() >= 10 && pcmbuf_mix_free() >= 30 && - (mix_chunk || read_chunk->link)) - { - *count = MIN(*count, AUX_BUFSIZE/4); - return voicebuf; - } - else - { - return NULL; - } - } - else - { - return pcmbuf_request_buffer(count); - } -} - -void pcmbuf_write_voice_complete(int count) -{ - /* A get-it-to-work-for-now hack (audio status could have changed) */ - if (!(audio_status() & AUDIO_STATUS_PLAY)) - { - pcmbuf_write_complete(count); - return; - } - - int16_t *ibuf = (int16_t *)voicebuf; - int16_t *obuf; - size_t chunk_samples; - - if (mix_chunk == NULL && read_chunk != NULL) - { - mix_chunk = read_chunk->link; - /* Start 1/8s into the next chunk */ - pcmbuf_mix_sample = BYTERATE / 16; - } - - if (!mix_chunk) - return; - - obuf = (int16_t *)mix_chunk->addr; - chunk_samples = mix_chunk->size / sizeof (int16_t); - - count <<= 1; - - while (count-- > 0) - { - int32_t sample = *ibuf++; - - if (pcmbuf_mix_sample >= chunk_samples) - { - mix_chunk = mix_chunk->link; - if (!mix_chunk) - return; - pcmbuf_mix_sample = 0; - obuf = (int16_t *)mix_chunk->addr; - chunk_samples = mix_chunk->size / 2; - } - sample += obuf[pcmbuf_mix_sample] >> 2; - obuf[pcmbuf_mix_sample++] = clip_sample_16(sample); - } -} - - /** Debug menu, other metrics */ /* Amount of bytes left in the buffer. */ @@ -1174,6 +1081,71 @@ unsigned char *pcmbuf_get_meminfo(size_t *length) #endif +/** Fading and channel volume control */ + +/* Sync the channel amplitude to all states */ +static void pcmbuf_update_volume(void) +{ + unsigned int vol = fade_vol; + + if (soft_mode) + vol >>= 2; + + mixer_channel_set_amplitude(PCM_MIXER_CHAN_PLAYBACK, vol); +} + +/* Quiet-down the channel if 'shhh' is true or else play at normal level */ +void pcmbuf_soft_mode(bool shhh) +{ + soft_mode = shhh; + pcmbuf_update_volume(); +} + +/* Fade channel in or out */ +void pcmbuf_fade(bool fade, bool in) +{ + if (!fade) + { + /* Simply set the level */ + fade_vol = in ? MIX_AMP_UNITY : MIX_AMP_MUTE; + } + else + { + /* Start from the opposing end */ + fade_vol = in ? MIX_AMP_MUTE : MIX_AMP_UNITY; + } + + pcmbuf_update_volume(); + + if (fade) + { + /* Do this on thread for now */ + int old_prio = thread_set_priority(thread_self(), PRIORITY_REALTIME); + + while (1) + { + /* Linear fade actually sounds better */ + if (in) + fade_vol += MIN(MIX_AMP_UNITY/16, MIX_AMP_UNITY - fade_vol); + else + fade_vol -= MIN(MIX_AMP_UNITY/16, fade_vol - MIX_AMP_MUTE); + + pcmbuf_update_volume(); + + if (fade_vol > MIX_AMP_MUTE && fade_vol < MIX_AMP_UNITY) + { + sleep(0); + continue; + } + + break; + } + + thread_set_priority(thread_self(), old_prio); + } +} + + /** Misc */ bool pcmbuf_is_lowdata(void) @@ -1201,107 +1173,6 @@ void pcmbuf_set_low_latency(bool state) unsigned long pcmbuf_get_latency(void) { - return (pcmbuf_unplayed_bytes + pcm_get_bytes_waiting()) * 1000 / BYTERATE; -} - -#ifndef HAVE_HARDWARE_BEEP -#define MINIBUF_SAMPLES (NATIVE_FREQUENCY / 1000 * KEYCLICK_DURATION) -#define MINIBUF_SIZE (MINIBUF_SAMPLES*4) - -/* Generates a constant square wave sound with a given frequency - in Hertz for a duration in milliseconds. */ -void pcmbuf_beep(unsigned int frequency, size_t duration, int amplitude) -{ - unsigned int step = 0xffffffffu / NATIVE_FREQUENCY * frequency; - int32_t phase = 0; - int16_t *bufptr, *bufstart, *bufend; - int32_t sample; - int nsamples = NATIVE_FREQUENCY / 1000 * duration; - bool mix = read_chunk != NULL && read_chunk->link != NULL; - int i; - - bufend = SKIPBYTES((int16_t *)pcmbuffer, pcmbuf_size); - - /* Find the insertion point and set bufstart to the start of it */ - if (mix) - { - /* Get the currently playing chunk at the current position. */ - bufstart = (int16_t *)pcm_play_dma_get_peak_buffer(&i); - - /* If above isn't implemented or pcm is stopped, no beepeth. */ - if (!bufstart || !pcm_is_playing()) - return; - - /* Give 5ms clearance. */ - bufstart += BYTERATE / 200; - -#ifdef HAVE_PCM_DMA_ADDRESS - /* Returned peak addresses are DMA addresses */ - bufend = pcm_dma_addr(bufend); -#endif - - /* Wrapped above? */ - if (bufstart >= bufend) - bufstart -= pcmbuf_size; - - /* NOTE: On some targets using hardware DMA, cache range flushing may - * be required or the writes may not be picked up by the controller. - * An incremental flush should be done periodically during the mixdown. */ - } - else if (nsamples <= MINIBUF_SAMPLES) - { - static int16_t minibuf[MINIBUF_SAMPLES*2] __attribute__((aligned(4))); - /* Use mini buffer */ - bufstart = minibuf; - bufend = SKIPBYTES(bufstart, MINIBUF_SIZE); - } - else if (!audio_buffer_state_trashed()) - { - /* Use pcmbuffer */ - bufstart = (int16_t *)pcmbuffer; - } - else - { - /* No place */ - return; - } - - bufptr = bufstart; - - /* Mix square wave into buffer */ - for (i = 0; i < nsamples; ++i) - { - int32_t amp = (phase >> 31) ^ (int32_t)amplitude; - sample = mix ? *bufptr : 0; - *bufptr++ = clip_sample_16(sample + amp); - if (bufptr >= bufend) - bufptr = (int16_t *)pcmbuffer; - sample = mix ? *bufptr : 0; - *bufptr++ = clip_sample_16(sample + amp); - if (bufptr >= bufend) - bufptr = (int16_t *)pcmbuffer; - - phase += step; - } - - pcm_play_lock(); -#ifdef HAVE_RECORDING - pcm_rec_lock(); -#endif - - /* Kick off playback if required and it won't interfere */ - if (!pcm_is_playing() -#ifdef HAVE_RECORDING - && !pcm_is_recording() -#endif - ) - { - pcm_play_data(NULL, (unsigned char *)bufstart, nsamples * 4); - } - - pcm_play_unlock(); -#ifdef HAVE_RECORDING - pcm_rec_unlock(); -#endif + return (pcmbuf_unplayed_bytes + + mixer_channel_get_bytes_waiting(PCM_MIXER_CHAN_PLAYBACK)) * 1000 / BYTERATE; } -#endif /* HAVE_HARDWARE_BEEP */ |