/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2005 by Miika Pekkarinen * Copyright (C) 2011 by Michael Sevakis * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include #include "config.h" #include "system.h" #include "debug.h" #include #include "pcm.h" #include "pcm_mixer.h" #include "pcmbuf.h" #include "playback.h" #include "codec_thread.h" /* Define LOGF_ENABLE to enable logf output in this file */ /*#define LOGF_ENABLE*/ #include "logf.h" #if (CONFIG_PLATFORM & PLATFORM_NATIVE) #include "cpu.h" #endif #include "settings.h" #include "audio.h" #include "voice_thread.h" #include "dsp.h" /* This is the target fill size of chunks on the pcm buffer Can be any number of samples but power of two sizes make for faster and smaller math - must be < 65536 bytes */ #define PCMBUF_CHUNK_SIZE 8192u #define PCMBUF_GUARD_SIZE 1024u /* Mnemonics for common data commit thresholds */ #define COMMIT_CHUNKS PCMBUF_CHUNK_SIZE #define COMMIT_ALL_DATA 1u /* Size of the crossfade buffer where codec data is written to be faded on commit */ #define CROSSFADE_BUFSIZE 8192u /* Number of bytes played per second: (sample rate * 2 channels * 2 bytes/sample) */ #define BYTERATE (NATIVE_FREQUENCY * 4) #if MEMORYSIZE > 2 /* Keep watermark high for large memory target - at least (2s) */ #define PCMBUF_WATERMARK (BYTERATE * 2) #define MIN_BUFFER_SIZE (BYTERATE * 3) #else #define PCMBUF_WATERMARK (BYTERATE / 4) /* 0.25 seconds */ #define MIN_BUFFER_SIZE (BYTERATE * 1) #endif /* Describes each audio packet - keep it small since there are many of them */ struct chunkdesc { uint16_t size; /* Actual size (0 < size <= PCMBUF_CHUNK_SIZE) */ uint8_t is_end; /* Flag indicating end of track */ uint8_t pos_key; /* Who put the position info in (0 = undefined) */ unsigned long elapsed; /* Elapsed time to use */ off_t offset; /* Offset to use */ }; /* General PCM buffer data */ #define INVALID_BUF_INDEX ((size_t)0 - (size_t)1) static unsigned char *pcmbuf_buffer; static unsigned char *pcmbuf_guardbuf; static size_t pcmbuf_size; static struct chunkdesc *pcmbuf_descriptors; static unsigned int pcmbuf_desc_count; static unsigned int position_key = 1; static size_t chunk_ridx; static size_t chunk_widx; static size_t pcmbuf_bytes_waiting; static size_t pcmbuf_watermark; static struct chunkdesc *current_desc; static bool low_latency_mode = false; static bool pcmbuf_sync_position = false; /* Fade effect */ static unsigned int fade_vol = MIX_AMP_UNITY; static enum { PCM_NOT_FADING = 0, PCM_FADING_IN, PCM_FADING_OUT, } fade_state = PCM_NOT_FADING; static bool fade_out_complete = false; /* Voice */ static bool soft_mode = false; #ifdef HAVE_CROSSFADE /* Crossfade buffer */ static unsigned char *crossfade_buffer; /* Crossfade related state */ static int crossfade_setting; static int crossfade_enable_request; static bool crossfade_mixmode; static bool crossfade_auto_skip; static enum { CROSSFADE_INACTIVE = 0, CROSSFADE_TRACK_CHANGE_STARTED, CROSSFADE_ACTIVE, } crossfade_status = CROSSFADE_INACTIVE; /* Track the current location for processing crossfade */ static size_t crossfade_index; /* Counters for fading in new data */ static size_t crossfade_fade_in_total; static size_t crossfade_fade_in_rem; /* Defines for operations on position info when mixing/fading - passed in offset parameter */ enum { MIXFADE_KEEP_POS = -1, /* Keep position info in chunk */ MIXFADE_NULLIFY_POS = -2, /* Ignore position info in chunk */ /* Positive values cause stamping/restamping */ }; static void crossfade_start(void); static void write_to_crossfade(size_t size, unsigned long elapsed, off_t offset); static void pcmbuf_finish_crossfade_enable(void); #endif /* HAVE_CROSSFADE */ /* Thread */ #ifdef HAVE_PRIORITY_SCHEDULING static int codec_thread_priority = PRIORITY_PLAYBACK; #endif /* Helpful macros for use in conditionals this assumes some of the above * static variable names */ #define DATA_LEVEL(quarter_secs) (NATIVE_FREQUENCY * (quarter_secs)) /* Callbacks into playback.c */ extern void audio_pcmbuf_position_callback(unsigned long elapsed, off_t offset, unsigned int key); extern void audio_pcmbuf_track_change(bool pcmbuf); extern bool audio_pcmbuf_may_play(void); extern void audio_pcmbuf_sync_position(void); /**************************************/ /* Return number of commited bytes in buffer (committed chunks count as a full chunk even if only partially filled) */ static size_t pcmbuf_unplayed_bytes(void) { size_t ridx = chunk_ridx; size_t widx = chunk_widx; if (ridx > widx) widx += pcmbuf_size; return widx - ridx; } /* Return the next PCM chunk in the PCM buffer given a byte index into it */ static size_t index_next(size_t index) { index = ALIGN_DOWN(index + PCMBUF_CHUNK_SIZE, PCMBUF_CHUNK_SIZE); if (index >= pcmbuf_size) index -= pcmbuf_size; return index; } /* Convert a byte offset in the PCM buffer into a pointer in the buffer */ static FORCE_INLINE void * index_buffer(size_t index) { return pcmbuf_buffer + index; } /* Convert a pointer in the buffer into an index offset */ static FORCE_INLINE size_t buffer_index(void *p) { return (uintptr_t)p - (uintptr_t)pcmbuf_buffer; } /* Return a chunk descriptor for a byte index in the buffer */ static struct chunkdesc * index_chunkdesc(size_t index) { return &pcmbuf_descriptors[index / PCMBUF_CHUNK_SIZE]; } /* Return a chunk descriptor for a byte index in the buffer, offset by 'offset' chunks */ static struct chunkdesc * index_chunkdesc_offs(size_t index, int offset) { int i = index / PCMBUF_CHUNK_SIZE; if (offset != 0) { i = (i + offset) % pcmbuf_desc_count; /* remainder => modulus */ if (i < 0) i += pcmbuf_desc_count; } return &pcmbuf_descriptors[i]; } /** Accept new PCM data */ /* Split the uncommitted data as needed into chunks, stopping when uncommitted data is below the threshold */ static void commit_chunks(size_t threshold) { size_t index = chunk_widx; size_t end_index = index + pcmbuf_bytes_waiting; /* Copy to the beginning of the buffer all data that must wrap */ if (end_index > pcmbuf_size) memcpy(pcmbuf_buffer, pcmbuf_guardbuf, end_index - pcmbuf_size); struct chunkdesc *desc = index_chunkdesc(index); do { size_t size = MIN(pcmbuf_bytes_waiting, PCMBUF_CHUNK_SIZE); pcmbuf_bytes_waiting -= size; /* Fill in the values in the new buffer chunk */ desc->size = (uint16_t)size; /* Advance the current write chunk and make it available to the PCM callback */ chunk_widx = index = index_next(index); desc = index_chunkdesc(index); /* Reset it before using it */ desc->is_end = 0; desc->pos_key = 0; } while (pcmbuf_bytes_waiting >= threshold); } /* If uncommitted data count is above or equal to the threshold, commit it */ static FORCE_INLINE void commit_if_needed(size_t threshold) { if (pcmbuf_bytes_waiting >= threshold) commit_chunks(threshold); } /* Place positioning information in the chunk */ static void stamp_chunk(struct chunkdesc *desc, unsigned long elapsed, off_t offset) { /* One-time stamping of a given chunk by the same track - new track may overwrite */ unsigned int key = position_key; if (desc->pos_key != key) { desc->pos_key = key; desc->elapsed = elapsed; desc->offset = offset; } } /* Set priority of the codec thread */ #ifdef HAVE_PRIORITY_SCHEDULING /* * expects pcm_fill_state in tenth-% units (e.g. full pcm buffer is 10) */ static void boost_codec_thread(int pcm_fill_state) { static const int8_t prios[11] = { PRIORITY_PLAYBACK_MAX, /* 0 - 10% */ PRIORITY_PLAYBACK_MAX+1, /* 10 - 20% */ PRIORITY_PLAYBACK_MAX+3, /* 20 - 30% */ PRIORITY_PLAYBACK_MAX+5, /* 30 - 40% */ PRIORITY_PLAYBACK_MAX+7, /* 40 - 50% */ PRIORITY_PLAYBACK_MAX+8, /* 50 - 60% */ PRIORITY_PLAYBACK_MAX+9, /* 60 - 70% */ /* raising priority above 70% shouldn't be needed */ PRIORITY_PLAYBACK, /* 70 - 80% */ PRIORITY_PLAYBACK, /* 80 - 90% */ PRIORITY_PLAYBACK, /* 90 -100% */ PRIORITY_PLAYBACK, /* 100% */ }; int new_prio = prios[pcm_fill_state]; /* Keep voice and codec threads at the same priority or else voice * will starve if the codec thread's priority is boosted. */ if (new_prio != codec_thread_priority) { codec_thread_set_priority(new_prio); voice_thread_set_priority(new_prio); codec_thread_priority = new_prio; } } #else #define boost_codec_thread(pcm_fill_state) do{}while(0) #endif /* HAVE_PRIORITY_SCHEDULING */ /* Get the next available buffer and size - assumes adequate space exists */ static void * get_write_buffer(size_t *size) { /* Obtain current chunk fill address */ size_t index = chunk_widx + pcmbuf_bytes_waiting; size_t index_end = pcmbuf_size + PCMBUF_GUARD_SIZE; /* Get count to the end of the buffer where a wrap will happen + the guard */ size_t endsize = index_end - index; /* Return available unwrapped space */ *size = MIN(*size, endsize); return index_buffer(index); } /* Commit outstanding data leaving less than a chunk size remaining and write position info to the first chunk */ static void commit_write_buffer(size_t size, unsigned long elapsed, off_t offset) { struct chunkdesc *desc = index_chunkdesc(chunk_widx); stamp_chunk(desc, elapsed, offset); /* Add this data and commit if one or more chunks are ready */ pcmbuf_bytes_waiting += size; commit_if_needed(COMMIT_CHUNKS); } /* Request space in the buffer for writing output samples */ void * pcmbuf_request_buffer(int *count) { size_t size = *count * 4; #ifdef HAVE_CROSSFADE /* We're going to crossfade to a new track, which is now on its way */ if (crossfade_status == CROSSFADE_TRACK_CHANGE_STARTED) crossfade_start(); /* If crossfade has begun, put the new track samples in crossfade_buffer */ if (crossfade_status != CROSSFADE_INACTIVE && size > CROSSFADE_BUFSIZE) size = CROSSFADE_BUFSIZE; #endif enum channel_status status = mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK); size_t remaining = pcmbuf_unplayed_bytes(); /* Need to have length bytes to prevent wrapping overwriting - leave one descriptor free to guard so that 0 != full in ring buffer */ size_t freespace = pcmbuf_free(); if (pcmbuf_sync_position) audio_pcmbuf_sync_position(); if (freespace < size + PCMBUF_CHUNK_SIZE) return NULL; /* Maintain the buffer level above the watermark */ if (status != CHANNEL_STOPPED) { if (low_latency_mode) { /* 1/4s latency. */ if (remaining > DATA_LEVEL(1)) return NULL; } /* Boost CPU if necessary */ size_t realrem = pcmbuf_size - freespace; if (realrem < pcmbuf_watermark) trigger_cpu_boost(); boost_codec_thread(realrem*10 / pcmbuf_size); #ifdef HAVE_CROSSFADE /* Disable crossfade if < .5s of audio */ if (remaining < DATA_LEVEL(2)) crossfade_status = CROSSFADE_INACTIVE; #endif } else /* !playing */ { /* Boost CPU for pre-buffer */ trigger_cpu_boost(); /* If pre-buffered to the watermark, start playback */ #if MEMORYSIZE > 2 if (remaining > DATA_LEVEL(4)) #else if (remaining > pcmbuf_watermark) #endif { if (audio_pcmbuf_may_play()) pcmbuf_play_start(); } } void *buf = #ifdef HAVE_CROSSFADE crossfade_status != CROSSFADE_INACTIVE ? crossfade_buffer : #endif get_write_buffer(&size); *count = size / 4; return buf; } /* Handle new samples to the buffer */ void pcmbuf_write_complete(int count, unsigned long elapsed, off_t offset) { size_t size = count * 4; #ifdef HAVE_CROSSFADE if (crossfade_status != CROSSFADE_INACTIVE) { write_to_crossfade(size, elapsed, offset); } else #endif { commit_write_buffer(size, elapsed, offset); } /* Revert to position updates by PCM */ pcmbuf_sync_position = false; } /** Init */ static unsigned int get_next_required_pcmbuf_chunks(void) { size_t size = MIN_BUFFER_SIZE; #ifdef HAVE_CROSSFADE if (crossfade_enable_request != CROSSFADE_ENABLE_OFF) { size_t seconds = global_settings.crossfade_fade_out_delay + global_settings.crossfade_fade_out_duration; size += seconds * BYTERATE; } #endif logf("pcmbuf len: %lu", (unsigned long)(size / BYTERATE)); return size / PCMBUF_CHUNK_SIZE; } /* Initialize the ringbuffer state */ static void init_buffer_state(void) { /* Reset counters */ chunk_ridx = chunk_widx = 0; pcmbuf_bytes_waiting = 0; /* Reset first descriptor */ struct chunkdesc *desc = pcmbuf_descriptors; desc->is_end = 0; desc->pos_key = 0; } /* Initialize the PCM buffer. The structure looks like this: * ...[|FADEBUF]|---------PCMBUF---------|GUARDBUF|DESCS| */ size_t pcmbuf_init(unsigned char *bufend) { unsigned char *bufstart; /* Set up the buffers */ pcmbuf_desc_count = get_next_required_pcmbuf_chunks(); pcmbuf_size = pcmbuf_desc_count * PCMBUF_CHUNK_SIZE; pcmbuf_descriptors = (struct chunkdesc *)bufend - pcmbuf_desc_count; pcmbuf_buffer = (void *)pcmbuf_descriptors - pcmbuf_size - PCMBUF_GUARD_SIZE; /* Mem-align buffer chunks for more efficient handling in lower layers */ pcmbuf_buffer = ALIGN_DOWN(pcmbuf_buffer, (uintptr_t)MEM_ALIGN_SIZE); pcmbuf_guardbuf = pcmbuf_buffer + pcmbuf_size; bufstart = pcmbuf_buffer; #ifdef HAVE_CROSSFADE /* Allocate FADEBUF if it will be needed */ if (crossfade_enable_request != CROSSFADE_ENABLE_OFF) { bufstart -= CROSSFADE_BUFSIZE; crossfade_buffer = bufstart; } pcmbuf_finish_crossfade_enable(); #else /* !HAVE_CROSSFADE */ pcmbuf_watermark = PCMBUF_WATERMARK; #endif /* HAVE_CROSSFADE */ init_buffer_state(); pcmbuf_soft_mode(false); return bufend - bufstart; } /** Track change */ /* Place a track change notification in a specific descriptor or post it immediately if the buffer is empty or the index is invalid */ static void pcmbuf_monitor_track_change_ex(size_t index, int offset) { if (chunk_ridx != chunk_widx && index != INVALID_BUF_INDEX) { /* If monitoring, set flag in specified chunk */ index_chunkdesc_offs(index, offset)->is_end = 1; } else { /* Post now if no outstanding buffers exist */ audio_pcmbuf_track_change(false); } } /* Clear end of track and optionally the positioning info for all data */ static void pcmbuf_cancel_track_change(bool position) { size_t index = chunk_ridx; while (1) { struct chunkdesc *desc = index_chunkdesc(index); desc->is_end = 0; if (position) desc->pos_key = 0; if (index == chunk_widx) break; index = index_next(index); } } /* Place a track change notification at the end of the buffer or post it immediately if the buffer is empty */ void pcmbuf_monitor_track_change(bool monitor) { pcm_play_lock(); if (monitor) pcmbuf_monitor_track_change_ex(chunk_widx, -1); else pcmbuf_cancel_track_change(false); pcm_play_unlock(); } void pcmbuf_start_track_change(enum pcm_track_change_type type) { #ifdef HAVE_CROSSFADE bool crossfade = false; #endif bool auto_skip = type != TRACK_CHANGE_MANUAL; /* Commit all outstanding data before starting next track - tracks don't comingle inside a single buffer chunk */ commit_if_needed(COMMIT_ALL_DATA); /* Update position key so that: 1) Positions are keyed to the track to which they belong for sync purposes 2) Buffers stamped with the outgoing track's positions are restamped to the incoming track's positions when crossfading */ if (++position_key > UINT8_MAX) position_key = 1; if (type == TRACK_CHANGE_END_OF_DATA) { /* If end of all data, force playback */ if (audio_pcmbuf_may_play()) pcmbuf_play_start(); } #ifdef HAVE_CROSSFADE /* Determine whether this track change needs to crossfaded and how */ else if (crossfade_setting != CROSSFADE_ENABLE_OFF && !pcmbuf_is_crossfade_active() && pcmbuf_unplayed_bytes() >= DATA_LEVEL(2) && !low_latency_mode) { switch (crossfade_setting) { case CROSSFADE_ENABLE_AUTOSKIP: crossfade = auto_skip; break; case CROSSFADE_ENABLE_MANSKIP: crossfade = !auto_skip; break; case CROSSFADE_ENABLE_SHUFFLE: crossfade = global_settings.playlist_shuffle; break; case CROSSFADE_ENABLE_SHUFFLE_OR_MANSKIP: crossfade = global_settings.playlist_shuffle || !auto_skip; break; case CROSSFADE_ENABLE_ALWAYS: crossfade = true; break; } } /* else crossfade is off, crossfade is already active, not enough data, * pcm is off now (implying low data), not crossfading or low latency mode */ if (crossfade) { logf("crossfade track change"); /* Don't enable mix mode when skipping tracks manually */ crossfade_mixmode = auto_skip && global_settings.crossfade_fade_out_mixmode; crossfade_auto_skip = auto_skip; crossfade_status = CROSSFADE_TRACK_CHANGE_STARTED; trigger_cpu_boost(); /* Cancel any pending automatic gapless transition and if a manual skip, stop position updates */ pcm_play_lock(); pcmbuf_cancel_track_change(!auto_skip); pcm_play_unlock(); } else #endif /* HAVE_CROSSFADE */ if (auto_skip) { /* The codec is moving on to the next track, but the current track will * continue to play, so mark the last write chunk as the last one in * the track */ logf("gapless track change"); #ifdef HAVE_CROSSFADE if (crossfade_status != CROSSFADE_INACTIVE) { /* Crossfade is still active but crossfade is not happening - for * now, chicken-out and clear out the buffer (just like before) to * avoid fade pile-up on short tracks fading-in over long ones */ pcmbuf_play_stop(); } #endif pcmbuf_monitor_track_change(true); } else { /* Discard old data; caller needs no transition notification */ logf("manual track change"); pcmbuf_play_stop(); } } /** Playback */ /* PCM driver callback */ static void pcmbuf_pcm_callback(unsigned char **start, size_t *size) { /*- Process the chunk that just finished -*/ size_t index = chunk_ridx; struct chunkdesc *desc = current_desc; if (desc) { /* If last chunk in the track, notify of track change */ if (desc->is_end != 0) audio_pcmbuf_track_change(true); /* Free it for reuse */ chunk_ridx = index = index_next(index); } /*- Process the new one -*/ if (index != chunk_widx && !fade_out_complete) { current_desc = desc = index_chunkdesc(index); *start = index_buffer(index); *size = desc->size; if (desc->pos_key != 0) { /* Positioning chunk - notify playback */ audio_pcmbuf_position_callback(desc->elapsed, desc->offset, desc->pos_key); } } } /* Force playback */ void pcmbuf_play_start(void) { logf("pcmbuf_play_start"); if (mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK) == CHANNEL_STOPPED && chunk_widx != chunk_ridx) { current_desc = NULL; mixer_channel_play_data(PCM_MIXER_CHAN_PLAYBACK, pcmbuf_pcm_callback, NULL, 0); } } /* Stop channel, empty and reset buffer */ void pcmbuf_play_stop(void) { logf("pcmbuf_play_stop"); /* Reset channel */ mixer_channel_stop(PCM_MIXER_CHAN_PLAYBACK); /* Reset buffer */ init_buffer_state(); /* Revert to position updates by PCM */ pcmbuf_sync_position = false; #ifdef HAVE_CROSSFADE crossfade_status = CROSSFADE_INACTIVE; #endif /* Can unboost the codec thread here no matter who's calling, * pretend full pcm buffer to unboost */ boost_codec_thread(10); } void pcmbuf_pause(bool pause) { logf("pcmbuf_pause: %s", pause?"pause":"play"); 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(); } /** Crossfade */ #ifdef HAVE_CROSSFADE /* Find the buffer index that's 'size' bytes away from 'index' */ static size_t crossfade_find_index(size_t index, size_t size) { if (index != INVALID_BUF_INDEX) { size_t i = ALIGN_DOWN(index, PCMBUF_CHUNK_SIZE); size += index - i; while (i != chunk_widx) { size_t desc_size = index_chunkdesc(i)->size; if (size < desc_size) return i + size; size -= desc_size; i = index_next(i); } } return INVALID_BUF_INDEX; } /* Align the needed buffer area up to the end of existing data */ static size_t crossfade_find_buftail(size_t buffer_rem, size_t buffer_need) { crossfade_index = chunk_ridx; if (buffer_rem > buffer_need) { size_t distance; if (crossfade_auto_skip) { /* Automatic track changes only modify the last part of the buffer, * so find the right chunk and sample to start the crossfade */ distance = buffer_rem - buffer_need; buffer_rem = buffer_need; } else { /* Manual skips occur immediately, but give 1/5s to process */ distance = BYTERATE / 5; buffer_rem -= BYTERATE / 5; } crossfade_index = crossfade_find_index(crossfade_index, distance); } return buffer_rem; } /* Clip sample to signed 16 bit range */ static FORCE_INLINE int32_t clip_sample_16(int32_t sample) { if ((int16_t)sample != sample) sample = 0x7fff ^ (sample >> 31); return sample; } /* Returns the number of bytes _NOT_ mixed/faded */ static int crossfade_mix_fade(int factor, size_t size, void *buf, size_t *out_index, unsigned long elapsed, off_t offset) { if (size == 0) return 0; size_t index = *out_index; if (index == INVALID_BUF_INDEX) return size; const int16_t *input_buf = buf; int16_t *output_buf = (int16_t *)index_buffer(index); while (size) { struct chunkdesc *desc = index_chunkdesc(index); switch (offset) { case MIXFADE_NULLIFY_POS: /* Stop position updates for the chunk */ desc->pos_key = 0; break; case MIXFADE_KEEP_POS: /* Keep position info as it is */ break; default: /* Replace position info */ stamp_chunk(desc, elapsed, offset); } size_t rem = desc->size - (index % PCMBUF_CHUNK_SIZE); int16_t *chunk_end = SKIPBYTES(output_buf, rem); if (size < rem) rem = size; size -= rem; do { /* fade left and right channel at once to keep buffer alignment */ int32_t left = output_buf[0]; int32_t right = output_buf[1]; if (input_buf) { /* fade the input buffer and mix into the chunk */ left += *input_buf++ * factor >> 8; right += *input_buf++ * factor >> 8; left = clip_sample_16(left); right = clip_sample_16(right); } else { /* fade the chunk only */ left = left * factor >> 8; right = right * factor >> 8; } *output_buf++ = left; *output_buf++ = right; rem -= 4; } while (rem); /* move to next chunk as needed */ if (output_buf >= chunk_end) { index = index_next(index); if (index == chunk_widx) { /* End of existing data */ *out_index = INVALID_BUF_INDEX; return size; } output_buf = (int16_t *)index_buffer(index); } } *out_index = buffer_index(output_buf); return 0; } /* Initializes crossfader, calculates all necessary parameters and performs * fade-out with the PCM buffer. */ static void crossfade_start(void) { logf("crossfade_start"); pcm_play_lock(); /* Initialize the crossfade buffer size to all of the buffered data that * has not yet been sent to the DMA */ size_t unplayed = pcmbuf_unplayed_bytes(); /* Reject crossfade if less than .5s of data */ if (unplayed < DATA_LEVEL(2)) { logf("crossfade rejected"); crossfade_status = CROSSFADE_INACTIVE; if (crossfade_auto_skip) pcmbuf_monitor_track_change(true); pcm_play_unlock(); return; } /* Fading will happen */ crossfade_status = CROSSFADE_ACTIVE; /* Get fade info from settings. */ size_t fade_out_delay = global_settings.crossfade_fade_out_delay * BYTERATE; size_t fade_out_rem = global_settings.crossfade_fade_out_duration * BYTERATE; size_t fade_in_delay = global_settings.crossfade_fade_in_delay * BYTERATE; size_t fade_in_duration = global_settings.crossfade_fade_in_duration * BYTERATE; if (!crossfade_auto_skip) { /* Forego fade-in delay on manual skip - do the best to preserve auto skip relationship */ if (fade_out_delay > fade_in_delay) fade_out_delay -= fade_in_delay; else fade_out_delay = 0; fade_in_delay = 0; } size_t fade_out_need = fade_out_delay + fade_out_rem; if (!crossfade_mixmode) { size_t buffer_rem = crossfade_find_buftail(unplayed, fade_out_need); pcm_play_unlock(); if (buffer_rem < fade_out_need) { /* Existing buffers are short */ size_t fade_out_short = fade_out_need - buffer_rem; if (fade_out_rem >= fade_out_short) { /* Truncate fade-out duration */ fade_out_rem -= fade_out_short; } else { /* Truncate fade-out and fade-out delay */ fade_out_delay = fade_out_rem; fade_out_rem = 0; } } /* Completely process the crossfade fade-out effect with current PCM buffer */ /* Fade out the specified amount of the already processed audio */ size_t fade_out_total = fade_out_rem; /* Find the right chunk and sample to start fading out */ size_t fade_out_index = crossfade_find_index(crossfade_index, fade_out_delay); while (fade_out_rem > 0) { /* Each 1/20 second of audio will have the same fade applied */ size_t block_rem = MIN(BYTERATE / 20, fade_out_rem); int factor = (fade_out_rem << 8) / fade_out_total; fade_out_rem -= block_rem; crossfade_mix_fade(factor, block_rem, NULL, &fade_out_index, 0, MIXFADE_KEEP_POS); } /* zero out the rest of the buffer */ crossfade_mix_fade(0, pcmbuf_size, NULL, &fade_out_index, 0, MIXFADE_NULLIFY_POS); pcm_play_lock(); } /* Initialize fade-in counters */ crossfade_fade_in_total = fade_in_duration; crossfade_fade_in_rem = fade_in_duration; /* Find the right chunk and sample to start fading in - redo from read chunk in case original position were/was overrun in callback - the track change event _must not_ ever fail to happen */ unplayed = pcmbuf_unplayed_bytes() + fade_in_delay; crossfade_find_buftail(unplayed, fade_out_need); if (crossfade_auto_skip) pcmbuf_monitor_track_change_ex(crossfade_index, 0); pcm_play_unlock(); logf("crossfade_start done!"); } /* Perform fade-in of new track */ static void write_to_crossfade(size_t size, unsigned long elapsed, off_t offset) { unsigned char *buf = crossfade_buffer; if (crossfade_fade_in_rem) { /* Fade factor for this packet */ int factor = ((crossfade_fade_in_total - crossfade_fade_in_rem) << 8) / crossfade_fade_in_total; /* Bytes to fade */ size_t fade_rem = MIN(size, crossfade_fade_in_rem); /* We _will_ fade this many bytes */ crossfade_fade_in_rem -= fade_rem; if (crossfade_index != INVALID_BUF_INDEX) { /* Mix the data */ size_t fade_total = fade_rem; fade_rem = crossfade_mix_fade(factor, fade_rem, buf, &crossfade_index, elapsed, offset); fade_total -= fade_rem; size -= fade_total; buf += fade_total; if (!size) return; } /* Fade remaining samples in place */ int samples = fade_rem / 4; int16_t *input_buf = (int16_t *)buf; while (samples--) { int32_t left = input_buf[0]; int32_t right = input_buf[1]; *input_buf++ = left * factor >> 8; *input_buf++ = right * factor >> 8; } } if (crossfade_index != INVALID_BUF_INDEX) { /* Mix the data */ size_t mix_total = size; /* A factor of 256 means mix only, no fading */ size = crossfade_mix_fade(256, size, buf, &crossfade_index, elapsed, offset); buf += mix_total - size; if (!size) return; } /* Data might remain in the fade buffer yet the fade-in has run its course - finish it off as normal chunks */ while (size > 0) { size_t copy_n = size; unsigned char *outbuf = get_write_buffer(©_n); memcpy(outbuf, buf, copy_n); commit_write_buffer(copy_n, elapsed, offset); buf += copy_n; size -= copy_n; } /* if no more fading-in to do, stop the crossfade */ #if 0 /* This way (the previous way) can cause a sudden volume jump if mixable data is used up before the fade-in completes and that just sounds wrong -- jethead71 */ if (!crossfade_fade_in_rem || crossfade_index == INVALID_BUF_INDEX) #endif /* Let fade-in complete even if not fully overlapping the existing data */ if (!crossfade_fade_in_rem) crossfade_status = CROSSFADE_INACTIVE; } static void pcmbuf_finish_crossfade_enable(void) { /* Copy the pending setting over now */ crossfade_setting = crossfade_enable_request; pcmbuf_watermark = (crossfade_setting != CROSSFADE_ENABLE_OFF && pcmbuf_size) ? /* If crossfading, try to keep the buffer full other than 1 second */ (pcmbuf_size - BYTERATE) : /* Otherwise, just use the default */ PCMBUF_WATERMARK; } bool pcmbuf_is_crossfade_active(void) { return crossfade_status != CROSSFADE_INACTIVE; } void pcmbuf_request_crossfade_enable(int setting) { /* Next setting to be used, not applied now */ crossfade_enable_request = setting; } bool pcmbuf_is_same_size(void) { /* if pcmbuf_buffer is NULL, then not set up yet even once so always */ bool same_size = pcmbuf_buffer ? (get_next_required_pcmbuf_chunks() == pcmbuf_desc_count) : true; /* no buffer change needed, so finish crossfade setup now */ if (same_size) pcmbuf_finish_crossfade_enable(); return same_size; } #endif /* HAVE_CROSSFADE */ /** Debug menu, other metrics */ /* Amount of bytes left in the buffer, accounting for uncommitted bytes */ size_t pcmbuf_free(void) { return pcmbuf_size - pcmbuf_unplayed_bytes() - pcmbuf_bytes_waiting; } /* Data bytes allocated for buffer */ size_t pcmbuf_get_bufsize(void) { return pcmbuf_size; } /* Number of committed descriptors */ int pcmbuf_used_descs(void) { return pcmbuf_unplayed_bytes() / PCMBUF_CHUNK_SIZE; } /* Total number of descriptors allocated */ int pcmbuf_descs(void) { return pcmbuf_desc_count; } /** 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); } /* Tick that does the fade for the playback channel */ static void pcmbuf_fade_tick(void) { /* ~1/3 second for full range fade */ const unsigned int fade_step = MIX_AMP_UNITY / (HZ / 3); if (fade_state == PCM_FADING_IN) fade_vol += MIN(fade_step, MIX_AMP_UNITY - fade_vol); else if (fade_state == PCM_FADING_OUT) fade_vol -= MIN(fade_step, fade_vol - MIX_AMP_MUTE); pcmbuf_update_volume(); if (fade_vol == MIX_AMP_MUTE || fade_vol == MIX_AMP_UNITY) { /* Fade is complete */ tick_remove_task(pcmbuf_fade_tick); if (fade_state == PCM_FADING_OUT) { /* Tell PCM to stop at its earliest convenience */ fade_out_complete = true; } fade_state = PCM_NOT_FADING; } } /* Fade channel in or out in the background */ void pcmbuf_fade(bool fade, bool in) { /* Must pause any active fade */ pcm_play_lock(); if (fade_state != PCM_NOT_FADING) tick_remove_task(pcmbuf_fade_tick); fade_out_complete = false; pcm_play_unlock(); if (!fade) { /* Simply set the level */ fade_state = PCM_NOT_FADING; fade_vol = in ? MIX_AMP_UNITY : MIX_AMP_MUTE; pcmbuf_update_volume(); } else { /* Set direction and resume fade from current point */ fade_state = in ? PCM_FADING_IN : PCM_FADING_OUT; tick_add_task(pcmbuf_fade_tick); } } /* Return 'true' if fade is in progress */ bool pcmbuf_fading(void) { return fade_state != PCM_NOT_FADING; } /* Quiet-down the channel if 'shhh' is true or else play at normal level */ void pcmbuf_soft_mode(bool shhh) { /* Have to block the tick or improper order could leave volume in soft mode if fading reads the old value first but updates after us. */ int res = fade_state != PCM_NOT_FADING ? tick_remove_task(pcmbuf_fade_tick) : -1; soft_mode = shhh; pcmbuf_update_volume(); if (res == 0) tick_add_task(pcmbuf_fade_tick); } /** Misc */ bool pcmbuf_is_lowdata(void) { enum channel_status status = mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK); if (status != CHANNEL_PLAYING || pcmbuf_is_crossfade_active()) return false; #if MEMORYSIZE > 2 /* 1 seconds of buffer is low data */ return pcmbuf_unplayed_bytes() < DATA_LEVEL(4); #else /* under watermark is low data */ return pcmbuf_unplayed_bytes() < pcmbuf_watermark; #endif } void pcmbuf_set_low_latency(bool state) { low_latency_mode = state; } /* Return the current position key value */ unsigned int pcmbuf_get_position_key(void) { return position_key; } /* Set position updates to be synchronous and immediate in addition to during PCM frames - cancelled upon first codec insert or upon stopping */ void pcmbuf_sync_position_update(void) { pcmbuf_sync_position = true; }