From 6c837394ca6d7a988bfbdc9e97630e69f0bc2025 Mon Sep 17 00:00:00 2001 From: Michael Sevakis Date: Wed, 28 Dec 2016 00:06:39 -0500 Subject: Playback: Fix problems with crossfade on short tracks. Addresses issues brought up in this thread: http://forums.rockbox.org/index.php/topic,51605.0.html While we're at it, improve the quality with a sample-level fader. Change-Id: I73dde60d6858a1c9042812e26d490739e3906a1e --- apps/pcmbuf.c | 646 ++++++++++++++++++++++++++++++++-------------------------- apps/pcmbuf.h | 3 - 2 files changed, 359 insertions(+), 290 deletions(-) diff --git a/apps/pcmbuf.c b/apps/pcmbuf.c index ff9b3e16a2..8c1ca06c2b 100644 --- a/apps/pcmbuf.c +++ b/apps/pcmbuf.c @@ -41,6 +41,9 @@ #include "audio.h" #include "voice_thread.h" +/* 2 channels * 2 bytes/sample, interleaved */ +#define PCMBUF_SAMPLE_SIZE (2 * 2) + /* 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 */ @@ -67,9 +70,8 @@ /* Return data level in 1/4-second increments */ #define DATA_LEVEL(quarter_secs) (pcmbuf_sampr * (quarter_secs)) -/* Number of bytes played per second: - (sample rate * 2 channels * 2 bytes/sample) */ -#define BYTERATE (pcmbuf_sampr * 2 * 2) +/* Number of bytes played per second */ +#define BYTERATE (pcmbuf_sampr * PCMBUF_SAMPLE_SIZE) #if MEMORYSIZE > 2 /* Keep watermark high for large memory target - at least (2s) */ @@ -88,12 +90,15 @@ 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) */ + uint16_t pos_key; /* Who put the position info in + (undefined: 0, valid: 1..POSITION_KEY_MAX) */ unsigned long elapsed; /* Elapsed time to use */ off_t offset; /* Offset to use */ }; +#define POSITION_KEY_MAX UINT16_MAX + + /* General PCM buffer data */ #define INVALID_BUF_INDEX ((size_t)0 - (size_t)1) @@ -110,6 +115,7 @@ static size_t chunk_widx; static size_t pcmbuf_bytes_waiting; static struct chunkdesc *current_desc; +static size_t chunk_transidx; static size_t pcmbuf_watermark = 0; @@ -131,28 +137,36 @@ static bool fade_out_complete = false; static bool soft_mode = false; #ifdef HAVE_CROSSFADE -/* Crossfade buffer */ -static void *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_INACTIVE = 0, /* Crossfade is OFF */ + CROSSFADE_ACTIVE, /* Crossfade is fading in */ + CROSSFADE_START, /* New crossfade is starting */ + CROSSFADE_CONTINUE, /* Next track continues fade */ } crossfade_status = CROSSFADE_INACTIVE; -/* Track the current location for processing crossfade */ -static size_t crossfade_index; +static bool crossfade_mixmode; +static bool crossfade_auto_skip; +static size_t crossfade_widx; +static size_t crossfade_bufidx; -/* Counters for fading in new data */ -static size_t crossfade_fade_in_total; -static size_t crossfade_fade_in_rem; +struct mixfader +{ + int32_t factor; /* Current volume factor to use */ + int32_t endfac; /* Saturating end factor */ + int32_t nsamp2; /* Twice the number of samples */ + int32_t dfact2; /* Twice the range of factors */ + int32_t ferr; /* Current error accumulator */ + int32_t dfquo; /* Quotient of fade range / sample range */ + int32_t dfrem; /* Remainder of fade range / sample range */ + int32_t dfinc; /* Base increment (-1 or +1) */ + bool alloc; /* Allocate blocks if needed else abort at EOB */ +} crossfade_infader; /* Defines for operations on position info when mixing/fading - passed in offset parameter */ @@ -163,10 +177,16 @@ enum /* Positive values cause stamping/restamping */ }; +#define MIXFADE_UNITY_BITS 16 +#define MIXFADE_UNITY (1 << MIXFADE_UNITY_BITS) + +static void crossfade_cancel(void); 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); +#else +#define crossfade_cancel() do {} while(0) #endif /* HAVE_CROSSFADE */ /* Thread */ @@ -232,9 +252,9 @@ 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' +/* Return the first byte of a chunk for a byte index in the buffer, offset by 'offset' chunks */ -static struct chunkdesc * index_chunkdesc_offs(size_t index, int offset) +static size_t index_chunk_offs(size_t index, int offset) { int i = index / PCMBUF_CHUNK_SIZE; @@ -247,7 +267,53 @@ static struct chunkdesc * index_chunkdesc_offs(size_t index, int offset) i += pcmbuf_desc_count; } - return &pcmbuf_descriptors[i]; + return i * PCMBUF_CHUNK_SIZE; +} + +/* Test if a buffer index lies within the committed data region */ +static bool index_committed(size_t index) +{ + if (index == INVALID_BUF_INDEX) + return false; + + size_t ridx = chunk_ridx; + size_t widx = chunk_widx; + + if (widx < ridx) + { + widx += pcmbuf_size; + + if (index < ridx) + index += pcmbuf_size; + } + + return index >= ridx && index < widx; +} + +/* Snip the tail of buffer at chunk of specified index plus chunk offset */ +void snip_buffer_tail(size_t index, int offset) +{ + /* Call with PCM lockout */ + if (index == INVALID_BUF_INDEX) + return; + + index = index_chunk_offs(index, offset); + + if (!index_committed(index) && index != chunk_widx) + return; + + chunk_widx = index; + pcmbuf_bytes_waiting = 0; + index_chunkdesc(index)->pos_key = 0; + +#ifdef HAVE_CROSSFADE + /* Kill crossfade if it would now be operating in the void */ + if (crossfade_status != CROSSFADE_INACTIVE && + !index_committed(crossfade_widx) && crossfade_widx != chunk_widx) + { + crossfade_cancel(); + } +#endif /* HAVE_CROSSFADE */ } @@ -280,7 +346,6 @@ static void commit_chunks(size_t threshold) desc = index_chunkdesc(index); /* Reset it before using it */ - desc->is_end = 0; desc->pos_key = 0; } while (pcmbuf_bytes_waiting >= threshold); @@ -363,37 +428,32 @@ static void * get_write_buffer(size_t *size) 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) +/* Commit outstanding data leaving less than a chunk size remaining */ +static void commit_write_buffer(size_t size) { - 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; + size_t size = *count * PCMBUF_SAMPLE_SIZE; #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) + if (crossfade_status > CROSSFADE_ACTIVE) crossfade_start(); - /* If crossfade has begun, put the new track samples in crossfade_buffer */ + /* If crossfade has begun, put the new track samples in the crossfade + buffer area */ if (crossfade_status != CROSSFADE_INACTIVE && size > CROSSFADE_BUFSIZE) size = CROSSFADE_BUFSIZE; else #endif /* HAVE_CROSSFADE */ - if (size > PCMBUF_MAX_BUFFER) - size = PCMBUF_MAX_BUFFER; /* constrain */ + size = PCMBUF_MAX_BUFFER; /* constrain request */ enum channel_status status = mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK); size_t remaining = pcmbuf_unplayed_bytes(); @@ -425,12 +485,6 @@ void * pcmbuf_request_buffer(int *count) 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 */ { @@ -447,7 +501,8 @@ void * pcmbuf_request_buffer(int *count) #ifdef HAVE_CROSSFADE if (crossfade_status != CROSSFADE_INACTIVE) { - buf = crossfade_buffer; /* always CROSSFADE_BUFSIZE */ + crossfade_bufidx = index_chunk_offs(chunk_ridx, -1); + buf = index_buffer(crossfade_bufidx); /* always CROSSFADE_BUFSIZE */ } else #endif @@ -459,14 +514,14 @@ void * pcmbuf_request_buffer(int *count) buf = get_write_buffer(&size); } - *count = size / 4; + *count = size / PCMBUF_SAMPLE_SIZE; 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; + size_t size = count * PCMBUF_SAMPLE_SIZE; #ifdef HAVE_CROSSFADE if (crossfade_status != CROSSFADE_INACTIVE) @@ -476,7 +531,8 @@ void pcmbuf_write_complete(int count, unsigned long elapsed, off_t offset) else #endif { - commit_write_buffer(size, elapsed, offset); + stamp_chunk(index_chunkdesc(chunk_widx), elapsed, offset); + commit_write_buffer(size); } /* Revert to position updates by PCM */ @@ -510,13 +566,15 @@ static void init_buffer_state(void) pcmbuf_bytes_waiting = 0; /* Reset first descriptor */ - struct chunkdesc *desc = pcmbuf_descriptors; - desc->is_end = 0; - desc->pos_key = 0; + if (pcmbuf_descriptors) + pcmbuf_descriptors->pos_key = 0; + + /* Clear change notification */ + chunk_transidx = INVALID_BUF_INDEX; } /* Initialize the PCM buffer. The structure looks like this: - * ...[|FADEBUF]|---------PCMBUF---------|GUARDBUF|DESCS| */ + * ...|---------PCMBUF---------|GUARDBUF|DESCS| */ size_t pcmbuf_init(void *bufend) { void *bufstart; @@ -536,13 +594,6 @@ size_t pcmbuf_init(void *bufend) 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 pcmbuf_watermark = PCMBUF_WATERMARK; @@ -560,33 +611,43 @@ size_t pcmbuf_init(void *bufend) /* 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) +static void pcmbuf_monitor_track_change_ex(size_t index) { + /* Call with PCM lockout */ 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); + /* If monitoring, set flag for one previous to specified chunk */ + index = index_chunk_offs(index, -1); + + /* Ensure PCM playback hasn't already played this out */ + if (index_committed(index)) + { + chunk_transidx = index; + return; + } } + + /* Post now if buffer is no longer coming up */ + chunk_transidx = INVALID_BUF_INDEX; + 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) { + /* Call with PCM lockout */ + snip_buffer_tail(chunk_transidx, 1); + + chunk_transidx = INVALID_BUF_INDEX; + + if (!position) + return; + size_t index = chunk_ridx; while (1) { - struct chunkdesc *desc = index_chunkdesc(index); - - desc->is_end = 0; - - if (position) - desc->pos_key = 0; + index_chunkdesc(index)->pos_key = 0; if (index == chunk_widx) break; @@ -602,7 +663,7 @@ void pcmbuf_monitor_track_change(bool monitor) pcm_play_lock(); if (monitor) - pcmbuf_monitor_track_change_ex(chunk_widx, -1); + pcmbuf_monitor_track_change_ex(chunk_widx); else pcmbuf_cancel_track_change(false); @@ -627,44 +688,45 @@ void pcmbuf_start_track_change(enum pcm_track_change_type type) 2) Buffers stamped with the outgoing track's positions are restamped to the incoming track's positions when crossfading */ - if (++position_key > UINT8_MAX) + if (++position_key > POSITION_KEY_MAX) position_key = 1; if (type == TRACK_CHANGE_END_OF_DATA) { + crossfade_cancel(); + /* 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) + else if (crossfade_setting != CROSSFADE_ENABLE_OFF) { - switch (crossfade_setting) + if (crossfade_status == CROSSFADE_INACTIVE && + pcmbuf_unplayed_bytes() >= DATA_LEVEL(2) && + !low_latency_mode) { - 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; + 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) { @@ -676,33 +738,26 @@ void pcmbuf_start_track_change(enum pcm_track_change_type type) crossfade_auto_skip = auto_skip; - crossfade_status = CROSSFADE_TRACK_CHANGE_STARTED; + crossfade_status = CROSSFADE_START; - trigger_cpu_boost(); + pcmbuf_monitor_track_change(auto_skip); - /* 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(); + trigger_cpu_boost(); } 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(); - } + if (crossfade_status == CROSSFADE_ACTIVE) + crossfade_status = CROSSFADE_CONTINUE; #endif + pcmbuf_monitor_track_change(true); } else @@ -726,8 +781,11 @@ static void pcmbuf_pcm_callback(const void **start, size_t *size) if (desc) { /* If last chunk in the track, notify of track change */ - if (desc->is_end != 0) + if (index == chunk_transidx) + { + chunk_transidx = INVALID_BUF_INDEX; audio_pcmbuf_track_change(true); + } /* Free it for reuse */ chunk_ridx = index = index_next(index); @@ -778,9 +836,8 @@ void pcmbuf_play_stop(void) /* Revert to position updates by PCM */ pcmbuf_sync_position = false; -#ifdef HAVE_CROSSFADE - crossfade_status = CROSSFADE_INACTIVE; -#endif + /* Fader OFF */ + crossfade_cancel(); /* Can unboost the codec thread here no matter who's calling, * pretend full pcm buffer to unboost */ @@ -801,12 +858,72 @@ void pcmbuf_pause(bool pause) /** Crossfade */ #ifdef HAVE_CROSSFADE + +/* Initialize a fader */ +static void mixfader_init(struct mixfader *faderp, int32_t start_factor, + int32_t end_factor, size_t size, bool alloc) +{ + /* Linear fade */ + faderp->endfac = end_factor; + faderp->nsamp2 = size / PCMBUF_SAMPLE_SIZE * 2; + faderp->alloc = alloc; + + if (faderp->nsamp2 == 0) + { + /* No data; set up as if fader finished the fade */ + faderp->factor = end_factor; + return; + } + + int32_t dfact2 = 2*abs(end_factor - start_factor); + faderp->factor = start_factor; + faderp->ferr = dfact2 / 2; + faderp->dfquo = dfact2 / faderp->nsamp2; + faderp->dfrem = dfact2 - faderp->dfquo*faderp->nsamp2; + faderp->dfinc = end_factor < start_factor ? -1 : +1; + faderp->dfquo *= faderp->dfinc; +} + +/* Query if the fader has finished its envelope */ +static inline bool mixfader_finished(const struct mixfader *faderp) +{ + return faderp->factor == faderp->endfac; +} + +/* Step fader by one sample */ +static inline void mixfader_step(struct mixfader *faderp) +{ + if (mixfader_finished(faderp)) + return; + + faderp->factor += faderp->dfquo; + faderp->ferr += faderp->dfrem; + + if (faderp->ferr >= faderp->nsamp2) + { + faderp->factor += faderp->dfinc; + faderp->ferr -= faderp->nsamp2; + } +} + +static FORCE_INLINE int32_t mixfade_sample(const struct mixfader *faderp, int32_t s) +{ + return (faderp->factor * s + MIXFADE_UNITY/2) >> MIXFADE_UNITY_BITS; +} + +/* Cancel crossfade operation */ +static void crossfade_cancel(void) +{ + crossfade_status = CROSSFADE_INACTIVE; + crossfade_widx = INVALID_BUF_INDEX; +} + /* 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_t i = index_chunk_offs(index, 0); size += index - i; while (i != chunk_widx) @@ -814,26 +931,30 @@ static size_t crossfade_find_index(size_t index, size_t size) size_t desc_size = index_chunkdesc(i)->size; if (size < desc_size) - return i + size; + { + index = i + size; + break; + } size -= desc_size; i = index_next(i); } } - return INVALID_BUF_INDEX; + return 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) +static size_t crossfade_find_buftail(bool auto_skip, size_t buffer_rem, + size_t buffer_need, size_t *buffer_rem_outp) { - crossfade_index = chunk_ridx; + size_t index = chunk_ridx; if (buffer_rem > buffer_need) { size_t distance; - if (crossfade_auto_skip) + if (auto_skip) { /* Automatic track changes only modify the last part of the buffer, * so find the right chunk and sample to start the crossfade */ @@ -843,35 +964,41 @@ static size_t crossfade_find_buftail(size_t buffer_rem, size_t buffer_need) else { /* Manual skips occur immediately, but give 1/5s to process */ - distance = BYTERATE / 5; - buffer_rem -= BYTERATE / 5; + distance = MIN(BYTERATE / 5, buffer_rem); + buffer_rem -= distance; } - crossfade_index = crossfade_find_index(crossfade_index, distance); + index = crossfade_find_index(index, distance); } - return buffer_rem; + if (buffer_rem_outp) + *buffer_rem_outp = buffer_rem; + + return index; } -/* Returns the number of bytes _NOT_ mixed/faded */ -static size_t crossfade_mix_fade(int factor, size_t size, void *buf, - size_t *out_index, unsigned long elapsed, - off_t offset) +/* Run a fader on some buffers */ +static void crossfade_mix_fade(struct mixfader *faderp, size_t size, + void *input_buf, size_t *out_index, + unsigned long elapsed, off_t offset) { if (size == 0) - return 0; + return; size_t index = *out_index; if (index == INVALID_BUF_INDEX) - return size; + return; + + int16_t *inbuf = input_buf; - const int16_t *input_buf = buf; - int16_t *output_buf = index_buffer(index); + bool alloced = inbuf && faderp->alloc && + index_chunk_offs(index, 0) == chunk_widx; while (size) { struct chunkdesc *desc = index_chunkdesc(index); + int16_t *outbuf = index_buffer(index); switch (offset) { @@ -887,60 +1014,77 @@ static size_t crossfade_mix_fade(int factor, size_t size, void *buf, stamp_chunk(desc, elapsed, offset); } - size_t rem = desc->size - (index % PCMBUF_CHUNK_SIZE); - int16_t *chunk_end = SKIPBYTES(output_buf, rem); + size_t amount = (alloced ? PCMBUF_CHUNK_SIZE : desc->size) + - (index % PCMBUF_CHUNK_SIZE); + int16_t *chunkend = SKIPBYTES(outbuf, amount); - if (size < rem) - rem = size; + if (size < amount) + amount = size; - size -= rem; + size -= amount; - do + if (alloced) { - /* fade left and right channel at once to keep buffer alignment */ - int32_t left = output_buf[0]; - int32_t right = output_buf[1]; + /* Fade the input buffer into the new destination chunk */ + for (size_t s = amount; s != 0; s -= PCMBUF_SAMPLE_SIZE) + { + *outbuf++ = mixfade_sample(faderp, *inbuf++); + *outbuf++ = mixfade_sample(faderp, *inbuf++); + mixfader_step(faderp); + } - if (input_buf) + commit_write_buffer(amount); + } + else if (inbuf) + { + /* Fade the input buffer and mix into the destination chunk */ + for (size_t s = amount; s != 0; s -= PCMBUF_SAMPLE_SIZE) { - /* 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); + int32_t left = outbuf[0]; + int32_t right = outbuf[1]; + left += mixfade_sample(faderp, *inbuf++); + right += mixfade_sample(faderp, *inbuf++); + *outbuf++ = clip_sample_16(left); + *outbuf++ = clip_sample_16(right); + mixfader_step(faderp); } - else + } + else + { + /* Fade the chunk in place */ + for (size_t s = amount; s != 0; s -= PCMBUF_SAMPLE_SIZE) { - /* fade the chunk only */ - left = left * factor >> 8; - right = right * factor >> 8; + int32_t left = outbuf[0]; + int32_t right = outbuf[1]; + *outbuf++ = mixfade_sample(faderp, left); + *outbuf++ = mixfade_sample(faderp, right); + mixfader_step(faderp); } - - *output_buf++ = left; - *output_buf++ = right; - - rem -= 4; } - while (rem); - /* move to next chunk as needed */ - if (output_buf >= chunk_end) + if (outbuf < chunkend) { - index = index_next(index); + index += amount; + continue; + } + + /* Move destination to next chunk as needed */ + index = index_next(index); - if (index == chunk_widx) + if (index == chunk_widx) + { + /* End of existing data */ + if (!inbuf || !faderp->alloc) { - /* End of existing data */ - *out_index = INVALID_BUF_INDEX; - return size; + index = INVALID_BUF_INDEX; + break; } - output_buf = index_buffer(index); + alloced = true; } } - *out_index = buffer_index(output_buf); - return 0; + *out_index = index; } /* Initializes crossfader, calculates all necessary parameters and performs @@ -951,6 +1095,19 @@ static void crossfade_start(void) pcm_play_lock(); + if (crossfade_status == CROSSFADE_CONTINUE) + { + logf("fade-in continuing"); + + crossfade_status = CROSSFADE_ACTIVE; + + if (crossfade_auto_skip) + pcmbuf_monitor_track_change_ex(crossfade_widx); + + pcm_play_unlock(); + return; + } + /* 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(); @@ -959,12 +1116,7 @@ static void crossfade_start(void) if (unplayed < DATA_LEVEL(2)) { logf("crossfade rejected"); - - crossfade_status = CROSSFADE_INACTIVE; - - if (crossfade_auto_skip) - pcmbuf_monitor_track_change(true); - + crossfade_cancel(); pcm_play_unlock(); return; } @@ -982,11 +1134,7 @@ static void crossfade_start(void) { /* 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_out_delay -= MIN(fade_out_delay, fade_in_delay); fade_in_delay = 0; } @@ -994,7 +1142,10 @@ static void crossfade_start(void) if (!crossfade_mixmode) { - size_t buffer_rem = crossfade_find_buftail(unplayed, fade_out_need); + /* Completely process the crossfade fade-out effect with current PCM buffer */ + size_t buffer_rem; + size_t index = crossfade_find_buftail(crossfade_auto_skip, unplayed, + fade_out_need, &buffer_rem); pcm_play_unlock(); @@ -1003,59 +1154,52 @@ static void crossfade_start(void) /* Existing buffers are short */ size_t fade_out_short = fade_out_need - buffer_rem; - if (fade_out_rem >= fade_out_short) + if (fade_out_delay >= fade_out_short) { - /* Truncate fade-out duration */ - fade_out_rem -= fade_out_short; + /* Truncate fade-out delay */ + fade_out_delay -= fade_out_short; } else { - /* Truncate fade-out and fade-out delay */ - fade_out_delay = fade_out_rem; - fade_out_rem = 0; + /* Truncate fade-out and eliminate fade-out delay */ + fade_out_rem = buffer_rem; + fade_out_delay = 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; + fade_out_need = fade_out_delay + 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); + index = crossfade_find_index(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 the specified amount of the already processed audio */ + struct mixfader outfader; - fade_out_rem -= block_rem; + mixfader_init(&outfader, MIXFADE_UNITY, 0, fade_out_rem, false); + crossfade_mix_fade(&outfader, fade_out_rem, NULL, &index, 0, + MIXFADE_KEEP_POS); - 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); + /* Zero-out the rest of the buffer */ + crossfade_mix_fade(&outfader, pcmbuf_size, NULL, &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; + mixfader_init(&crossfade_infader, 0, MIXFADE_UNITY, fade_in_duration, true); /* 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); + crossfade_widx = crossfade_find_buftail(crossfade_auto_skip, unplayed, + fade_out_need, NULL); + /* Move track transistion to chunk before the first one of incoming track */ if (crossfade_auto_skip) - pcmbuf_monitor_track_change_ex(crossfade_index, 0); + pcmbuf_monitor_track_change_ex(crossfade_widx); pcm_play_unlock(); @@ -1065,83 +1209,16 @@ static void crossfade_start(void) /* Perform fade-in of new track */ static void write_to_crossfade(size_t size, unsigned long elapsed, off_t offset) { - void *buf = crossfade_buffer; + /* Mix the data */ + crossfade_mix_fade(&crossfade_infader, size, index_buffer(crossfade_bufidx), + &crossfade_widx, elapsed, offset); - if (crossfade_fade_in_rem) + /* If no more fading-in to do, stop the crossfade */ + if (mixfader_finished(&crossfade_infader) && + index_chunk_offs(crossfade_widx, 0) == chunk_widx) { - /* 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 = 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; - } + crossfade_cancel(); } - - 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; - void *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) @@ -1156,11 +1233,6 @@ static void pcmbuf_finish_crossfade_enable(void) 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 */ @@ -1322,7 +1394,7 @@ bool pcmbuf_is_lowdata(void) { enum channel_status status = mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK); - if (status != CHANNEL_PLAYING || pcmbuf_is_crossfade_active()) + if (status != CHANNEL_PLAYING || crossfade_status != CROSSFADE_INACTIVE) return false; return pcmbuf_data_critical(); diff --git a/apps/pcmbuf.h b/apps/pcmbuf.h index 008872be59..e16f86174c 100644 --- a/apps/pcmbuf.h +++ b/apps/pcmbuf.h @@ -50,13 +50,10 @@ void pcmbuf_start_track_change(enum pcm_track_change_type type); /* Crossfade */ #ifdef HAVE_CROSSFADE -bool pcmbuf_is_crossfade_active(void); void pcmbuf_request_crossfade_enable(int setting); bool pcmbuf_is_same_size(void); #else /* Dummy functions with sensible returns */ -static FORCE_INLINE bool pcmbuf_is_crossfade_active(void) - { return false; } static FORCE_INLINE void pcmbuf_request_crossfade_enable(bool on_off) { return; (void)on_off; } static FORCE_INLINE bool pcmbuf_is_same_size(void) -- cgit