mirror of
https://github.com/Rockbox/rockbox.git
synced 2025-11-10 05:32:40 -05:00
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
This commit is contained in:
parent
dbee727664
commit
6c837394ca
2 changed files with 363 additions and 294 deletions
604
apps/pcmbuf.c
604
apps/pcmbuf.c
|
|
@ -41,6 +41,9 @@
|
||||||
#include "audio.h"
|
#include "audio.h"
|
||||||
#include "voice_thread.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
|
/* 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
|
Can be any number of samples but power of two sizes make for faster and
|
||||||
smaller math - must be < 65536 bytes */
|
smaller math - must be < 65536 bytes */
|
||||||
|
|
@ -67,9 +70,8 @@
|
||||||
/* Return data level in 1/4-second increments */
|
/* Return data level in 1/4-second increments */
|
||||||
#define DATA_LEVEL(quarter_secs) (pcmbuf_sampr * (quarter_secs))
|
#define DATA_LEVEL(quarter_secs) (pcmbuf_sampr * (quarter_secs))
|
||||||
|
|
||||||
/* Number of bytes played per second:
|
/* Number of bytes played per second */
|
||||||
(sample rate * 2 channels * 2 bytes/sample) */
|
#define BYTERATE (pcmbuf_sampr * PCMBUF_SAMPLE_SIZE)
|
||||||
#define BYTERATE (pcmbuf_sampr * 2 * 2)
|
|
||||||
|
|
||||||
#if MEMORYSIZE > 2
|
#if MEMORYSIZE > 2
|
||||||
/* Keep watermark high for large memory target - at least (2s) */
|
/* Keep watermark high for large memory target - at least (2s) */
|
||||||
|
|
@ -88,12 +90,15 @@
|
||||||
struct chunkdesc
|
struct chunkdesc
|
||||||
{
|
{
|
||||||
uint16_t size; /* Actual size (0 < size <= PCMBUF_CHUNK_SIZE) */
|
uint16_t size; /* Actual size (0 < size <= PCMBUF_CHUNK_SIZE) */
|
||||||
uint8_t is_end; /* Flag indicating end of track */
|
uint16_t pos_key; /* Who put the position info in
|
||||||
uint8_t pos_key; /* Who put the position info in (0 = undefined) */
|
(undefined: 0, valid: 1..POSITION_KEY_MAX) */
|
||||||
unsigned long elapsed; /* Elapsed time to use */
|
unsigned long elapsed; /* Elapsed time to use */
|
||||||
off_t offset; /* Offset to use */
|
off_t offset; /* Offset to use */
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#define POSITION_KEY_MAX UINT16_MAX
|
||||||
|
|
||||||
|
|
||||||
/* General PCM buffer data */
|
/* General PCM buffer data */
|
||||||
#define INVALID_BUF_INDEX ((size_t)0 - (size_t)1)
|
#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 size_t pcmbuf_bytes_waiting;
|
||||||
static struct chunkdesc *current_desc;
|
static struct chunkdesc *current_desc;
|
||||||
|
static size_t chunk_transidx;
|
||||||
|
|
||||||
static size_t pcmbuf_watermark = 0;
|
static size_t pcmbuf_watermark = 0;
|
||||||
|
|
||||||
|
|
@ -131,28 +137,36 @@ static bool fade_out_complete = false;
|
||||||
static bool soft_mode = false;
|
static bool soft_mode = false;
|
||||||
|
|
||||||
#ifdef HAVE_CROSSFADE
|
#ifdef HAVE_CROSSFADE
|
||||||
/* Crossfade buffer */
|
|
||||||
static void *crossfade_buffer;
|
|
||||||
|
|
||||||
/* Crossfade related state */
|
/* Crossfade related state */
|
||||||
|
|
||||||
static int crossfade_setting;
|
static int crossfade_setting;
|
||||||
static int crossfade_enable_request;
|
static int crossfade_enable_request;
|
||||||
static bool crossfade_mixmode;
|
|
||||||
static bool crossfade_auto_skip;
|
|
||||||
|
|
||||||
static enum
|
static enum
|
||||||
{
|
{
|
||||||
CROSSFADE_INACTIVE = 0,
|
CROSSFADE_INACTIVE = 0, /* Crossfade is OFF */
|
||||||
CROSSFADE_TRACK_CHANGE_STARTED,
|
CROSSFADE_ACTIVE, /* Crossfade is fading in */
|
||||||
CROSSFADE_ACTIVE,
|
CROSSFADE_START, /* New crossfade is starting */
|
||||||
|
CROSSFADE_CONTINUE, /* Next track continues fade */
|
||||||
} crossfade_status = CROSSFADE_INACTIVE;
|
} crossfade_status = CROSSFADE_INACTIVE;
|
||||||
|
|
||||||
/* Track the current location for processing crossfade */
|
static bool crossfade_mixmode;
|
||||||
static size_t crossfade_index;
|
static bool crossfade_auto_skip;
|
||||||
|
static size_t crossfade_widx;
|
||||||
|
static size_t crossfade_bufidx;
|
||||||
|
|
||||||
/* Counters for fading in new data */
|
struct mixfader
|
||||||
static size_t crossfade_fade_in_total;
|
{
|
||||||
static size_t crossfade_fade_in_rem;
|
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 -
|
/* Defines for operations on position info when mixing/fading -
|
||||||
passed in offset parameter */
|
passed in offset parameter */
|
||||||
|
|
@ -163,10 +177,16 @@ enum
|
||||||
/* Positive values cause stamping/restamping */
|
/* 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 crossfade_start(void);
|
||||||
static void write_to_crossfade(size_t size, unsigned long elapsed,
|
static void write_to_crossfade(size_t size, unsigned long elapsed,
|
||||||
off_t offset);
|
off_t offset);
|
||||||
static void pcmbuf_finish_crossfade_enable(void);
|
static void pcmbuf_finish_crossfade_enable(void);
|
||||||
|
#else
|
||||||
|
#define crossfade_cancel() do {} while(0)
|
||||||
#endif /* HAVE_CROSSFADE */
|
#endif /* HAVE_CROSSFADE */
|
||||||
|
|
||||||
/* Thread */
|
/* Thread */
|
||||||
|
|
@ -232,9 +252,9 @@ static struct chunkdesc * index_chunkdesc(size_t index)
|
||||||
return &pcmbuf_descriptors[index / PCMBUF_CHUNK_SIZE];
|
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 */
|
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;
|
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;
|
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);
|
desc = index_chunkdesc(index);
|
||||||
|
|
||||||
/* Reset it before using it */
|
/* Reset it before using it */
|
||||||
desc->is_end = 0;
|
|
||||||
desc->pos_key = 0;
|
desc->pos_key = 0;
|
||||||
}
|
}
|
||||||
while (pcmbuf_bytes_waiting >= threshold);
|
while (pcmbuf_bytes_waiting >= threshold);
|
||||||
|
|
@ -363,37 +428,32 @@ static void * get_write_buffer(size_t *size)
|
||||||
return index_buffer(index);
|
return index_buffer(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Commit outstanding data leaving less than a chunk size remaining and
|
/* Commit outstanding data leaving less than a chunk size remaining */
|
||||||
write position info to the first chunk */
|
static void commit_write_buffer(size_t size)
|
||||||
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 */
|
/* Add this data and commit if one or more chunks are ready */
|
||||||
pcmbuf_bytes_waiting += size;
|
pcmbuf_bytes_waiting += size;
|
||||||
|
|
||||||
commit_if_needed(COMMIT_CHUNKS);
|
commit_if_needed(COMMIT_CHUNKS);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Request space in the buffer for writing output samples */
|
/* Request space in the buffer for writing output samples */
|
||||||
void * pcmbuf_request_buffer(int *count)
|
void * pcmbuf_request_buffer(int *count)
|
||||||
{
|
{
|
||||||
size_t size = *count * 4;
|
size_t size = *count * PCMBUF_SAMPLE_SIZE;
|
||||||
|
|
||||||
#ifdef HAVE_CROSSFADE
|
#ifdef HAVE_CROSSFADE
|
||||||
/* We're going to crossfade to a new track, which is now on its way */
|
/* 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();
|
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)
|
if (crossfade_status != CROSSFADE_INACTIVE && size > CROSSFADE_BUFSIZE)
|
||||||
size = CROSSFADE_BUFSIZE;
|
size = CROSSFADE_BUFSIZE;
|
||||||
else
|
else
|
||||||
#endif /* HAVE_CROSSFADE */
|
#endif /* HAVE_CROSSFADE */
|
||||||
|
|
||||||
if (size > PCMBUF_MAX_BUFFER)
|
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);
|
enum channel_status status = mixer_channel_status(PCM_MIXER_CHAN_PLAYBACK);
|
||||||
size_t remaining = pcmbuf_unplayed_bytes();
|
size_t remaining = pcmbuf_unplayed_bytes();
|
||||||
|
|
@ -425,12 +485,6 @@ void * pcmbuf_request_buffer(int *count)
|
||||||
trigger_cpu_boost();
|
trigger_cpu_boost();
|
||||||
|
|
||||||
boost_codec_thread(realrem*10 / pcmbuf_size);
|
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 */
|
else /* !playing */
|
||||||
{
|
{
|
||||||
|
|
@ -447,7 +501,8 @@ void * pcmbuf_request_buffer(int *count)
|
||||||
#ifdef HAVE_CROSSFADE
|
#ifdef HAVE_CROSSFADE
|
||||||
if (crossfade_status != CROSSFADE_INACTIVE)
|
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
|
else
|
||||||
#endif
|
#endif
|
||||||
|
|
@ -459,14 +514,14 @@ void * pcmbuf_request_buffer(int *count)
|
||||||
buf = get_write_buffer(&size);
|
buf = get_write_buffer(&size);
|
||||||
}
|
}
|
||||||
|
|
||||||
*count = size / 4;
|
*count = size / PCMBUF_SAMPLE_SIZE;
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Handle new samples to the buffer */
|
/* Handle new samples to the buffer */
|
||||||
void pcmbuf_write_complete(int count, unsigned long elapsed, off_t offset)
|
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
|
#ifdef HAVE_CROSSFADE
|
||||||
if (crossfade_status != CROSSFADE_INACTIVE)
|
if (crossfade_status != CROSSFADE_INACTIVE)
|
||||||
|
|
@ -476,7 +531,8 @@ void pcmbuf_write_complete(int count, unsigned long elapsed, off_t offset)
|
||||||
else
|
else
|
||||||
#endif
|
#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 */
|
/* Revert to position updates by PCM */
|
||||||
|
|
@ -510,13 +566,15 @@ static void init_buffer_state(void)
|
||||||
pcmbuf_bytes_waiting = 0;
|
pcmbuf_bytes_waiting = 0;
|
||||||
|
|
||||||
/* Reset first descriptor */
|
/* Reset first descriptor */
|
||||||
struct chunkdesc *desc = pcmbuf_descriptors;
|
if (pcmbuf_descriptors)
|
||||||
desc->is_end = 0;
|
pcmbuf_descriptors->pos_key = 0;
|
||||||
desc->pos_key = 0;
|
|
||||||
|
/* Clear change notification */
|
||||||
|
chunk_transidx = INVALID_BUF_INDEX;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Initialize the PCM buffer. The structure looks like this:
|
/* Initialize the PCM buffer. The structure looks like this:
|
||||||
* ...[|FADEBUF]|---------PCMBUF---------|GUARDBUF|DESCS| */
|
* ...|---------PCMBUF---------|GUARDBUF|DESCS| */
|
||||||
size_t pcmbuf_init(void *bufend)
|
size_t pcmbuf_init(void *bufend)
|
||||||
{
|
{
|
||||||
void *bufstart;
|
void *bufstart;
|
||||||
|
|
@ -536,13 +594,6 @@ size_t pcmbuf_init(void *bufend)
|
||||||
bufstart = pcmbuf_buffer;
|
bufstart = pcmbuf_buffer;
|
||||||
|
|
||||||
#ifdef HAVE_CROSSFADE
|
#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();
|
pcmbuf_finish_crossfade_enable();
|
||||||
#else
|
#else
|
||||||
pcmbuf_watermark = PCMBUF_WATERMARK;
|
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
|
/* Place a track change notification in a specific descriptor or post it
|
||||||
immediately if the buffer is empty or the index is invalid */
|
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 (chunk_ridx != chunk_widx && index != INVALID_BUF_INDEX)
|
||||||
{
|
{
|
||||||
/* If monitoring, set flag in specified chunk */
|
/* If monitoring, set flag for one previous to specified chunk */
|
||||||
index_chunkdesc_offs(index, offset)->is_end = 1;
|
index = index_chunk_offs(index, -1);
|
||||||
}
|
|
||||||
else
|
/* Ensure PCM playback hasn't already played this out */
|
||||||
|
if (index_committed(index))
|
||||||
{
|
{
|
||||||
/* Post now if no outstanding buffers exist */
|
chunk_transidx = index;
|
||||||
audio_pcmbuf_track_change(false);
|
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 */
|
/* Clear end of track and optionally the positioning info for all data */
|
||||||
static void pcmbuf_cancel_track_change(bool position)
|
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;
|
size_t index = chunk_ridx;
|
||||||
|
|
||||||
while (1)
|
while (1)
|
||||||
{
|
{
|
||||||
struct chunkdesc *desc = index_chunkdesc(index);
|
index_chunkdesc(index)->pos_key = 0;
|
||||||
|
|
||||||
desc->is_end = 0;
|
|
||||||
|
|
||||||
if (position)
|
|
||||||
desc->pos_key = 0;
|
|
||||||
|
|
||||||
if (index == chunk_widx)
|
if (index == chunk_widx)
|
||||||
break;
|
break;
|
||||||
|
|
@ -602,7 +663,7 @@ void pcmbuf_monitor_track_change(bool monitor)
|
||||||
pcm_play_lock();
|
pcm_play_lock();
|
||||||
|
|
||||||
if (monitor)
|
if (monitor)
|
||||||
pcmbuf_monitor_track_change_ex(chunk_widx, -1);
|
pcmbuf_monitor_track_change_ex(chunk_widx);
|
||||||
else
|
else
|
||||||
pcmbuf_cancel_track_change(false);
|
pcmbuf_cancel_track_change(false);
|
||||||
|
|
||||||
|
|
@ -627,19 +688,22 @@ void pcmbuf_start_track_change(enum pcm_track_change_type type)
|
||||||
2) Buffers stamped with the outgoing track's positions are restamped
|
2) Buffers stamped with the outgoing track's positions are restamped
|
||||||
to the incoming track's positions when crossfading
|
to the incoming track's positions when crossfading
|
||||||
*/
|
*/
|
||||||
if (++position_key > UINT8_MAX)
|
if (++position_key > POSITION_KEY_MAX)
|
||||||
position_key = 1;
|
position_key = 1;
|
||||||
|
|
||||||
if (type == TRACK_CHANGE_END_OF_DATA)
|
if (type == TRACK_CHANGE_END_OF_DATA)
|
||||||
{
|
{
|
||||||
|
crossfade_cancel();
|
||||||
|
|
||||||
/* If end of all data, force playback */
|
/* If end of all data, force playback */
|
||||||
if (audio_pcmbuf_may_play())
|
if (audio_pcmbuf_may_play())
|
||||||
pcmbuf_play_start();
|
pcmbuf_play_start();
|
||||||
}
|
}
|
||||||
#ifdef HAVE_CROSSFADE
|
#ifdef HAVE_CROSSFADE
|
||||||
/* Determine whether this track change needs to crossfaded and how */
|
/* Determine whether this track change needs to crossfaded and how */
|
||||||
else if (crossfade_setting != CROSSFADE_ENABLE_OFF &&
|
else if (crossfade_setting != CROSSFADE_ENABLE_OFF)
|
||||||
!pcmbuf_is_crossfade_active() &&
|
{
|
||||||
|
if (crossfade_status == CROSSFADE_INACTIVE &&
|
||||||
pcmbuf_unplayed_bytes() >= DATA_LEVEL(2) &&
|
pcmbuf_unplayed_bytes() >= DATA_LEVEL(2) &&
|
||||||
!low_latency_mode)
|
!low_latency_mode)
|
||||||
{
|
{
|
||||||
|
|
@ -662,9 +726,7 @@ void pcmbuf_start_track_change(enum pcm_track_change_type type)
|
||||||
break;
|
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)
|
if (crossfade)
|
||||||
{
|
{
|
||||||
|
|
@ -676,15 +738,11 @@ void pcmbuf_start_track_change(enum pcm_track_change_type type)
|
||||||
|
|
||||||
crossfade_auto_skip = auto_skip;
|
crossfade_auto_skip = auto_skip;
|
||||||
|
|
||||||
crossfade_status = CROSSFADE_TRACK_CHANGE_STARTED;
|
crossfade_status = CROSSFADE_START;
|
||||||
|
|
||||||
|
pcmbuf_monitor_track_change(auto_skip);
|
||||||
|
|
||||||
trigger_cpu_boost();
|
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
|
else
|
||||||
#endif /* HAVE_CROSSFADE */
|
#endif /* HAVE_CROSSFADE */
|
||||||
|
|
@ -694,15 +752,12 @@ void pcmbuf_start_track_change(enum pcm_track_change_type type)
|
||||||
* continue to play, so mark the last write chunk as the last one in
|
* continue to play, so mark the last write chunk as the last one in
|
||||||
* the track */
|
* the track */
|
||||||
logf("gapless track change");
|
logf("gapless track change");
|
||||||
|
|
||||||
#ifdef HAVE_CROSSFADE
|
#ifdef HAVE_CROSSFADE
|
||||||
if (crossfade_status != CROSSFADE_INACTIVE)
|
if (crossfade_status == CROSSFADE_ACTIVE)
|
||||||
{
|
crossfade_status = CROSSFADE_CONTINUE;
|
||||||
/* 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
|
#endif
|
||||||
|
|
||||||
pcmbuf_monitor_track_change(true);
|
pcmbuf_monitor_track_change(true);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -726,8 +781,11 @@ static void pcmbuf_pcm_callback(const void **start, size_t *size)
|
||||||
if (desc)
|
if (desc)
|
||||||
{
|
{
|
||||||
/* If last chunk in the track, notify of track change */
|
/* 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);
|
audio_pcmbuf_track_change(true);
|
||||||
|
}
|
||||||
|
|
||||||
/* Free it for reuse */
|
/* Free it for reuse */
|
||||||
chunk_ridx = index = index_next(index);
|
chunk_ridx = index = index_next(index);
|
||||||
|
|
@ -778,9 +836,8 @@ void pcmbuf_play_stop(void)
|
||||||
/* Revert to position updates by PCM */
|
/* Revert to position updates by PCM */
|
||||||
pcmbuf_sync_position = false;
|
pcmbuf_sync_position = false;
|
||||||
|
|
||||||
#ifdef HAVE_CROSSFADE
|
/* Fader OFF */
|
||||||
crossfade_status = CROSSFADE_INACTIVE;
|
crossfade_cancel();
|
||||||
#endif
|
|
||||||
|
|
||||||
/* Can unboost the codec thread here no matter who's calling,
|
/* Can unboost the codec thread here no matter who's calling,
|
||||||
* pretend full pcm buffer to unboost */
|
* pretend full pcm buffer to unboost */
|
||||||
|
|
@ -801,12 +858,72 @@ void pcmbuf_pause(bool pause)
|
||||||
/** Crossfade */
|
/** Crossfade */
|
||||||
|
|
||||||
#ifdef HAVE_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' */
|
/* Find the buffer index that's 'size' bytes away from 'index' */
|
||||||
static size_t crossfade_find_index(size_t index, size_t size)
|
static size_t crossfade_find_index(size_t index, size_t size)
|
||||||
{
|
{
|
||||||
if (index != INVALID_BUF_INDEX)
|
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;
|
size += index - i;
|
||||||
|
|
||||||
while (i != chunk_widx)
|
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;
|
size_t desc_size = index_chunkdesc(i)->size;
|
||||||
|
|
||||||
if (size < desc_size)
|
if (size < desc_size)
|
||||||
return i + size;
|
{
|
||||||
|
index = i + size;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
size -= desc_size;
|
size -= desc_size;
|
||||||
i = index_next(i);
|
i = index_next(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return INVALID_BUF_INDEX;
|
return index;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Align the needed buffer area up to the end of existing data */
|
/* 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)
|
if (buffer_rem > buffer_need)
|
||||||
{
|
{
|
||||||
size_t distance;
|
size_t distance;
|
||||||
|
|
||||||
if (crossfade_auto_skip)
|
if (auto_skip)
|
||||||
{
|
{
|
||||||
/* Automatic track changes only modify the last part of the buffer,
|
/* Automatic track changes only modify the last part of the buffer,
|
||||||
* so find the right chunk and sample to start the crossfade */
|
* 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
|
else
|
||||||
{
|
{
|
||||||
/* Manual skips occur immediately, but give 1/5s to process */
|
/* Manual skips occur immediately, but give 1/5s to process */
|
||||||
distance = BYTERATE / 5;
|
distance = MIN(BYTERATE / 5, buffer_rem);
|
||||||
buffer_rem -= BYTERATE / 5;
|
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 */
|
/* Run a fader on some buffers */
|
||||||
static size_t crossfade_mix_fade(int factor, size_t size, void *buf,
|
static void crossfade_mix_fade(struct mixfader *faderp, size_t size,
|
||||||
size_t *out_index, unsigned long elapsed,
|
void *input_buf, size_t *out_index,
|
||||||
off_t offset)
|
unsigned long elapsed, off_t offset)
|
||||||
{
|
{
|
||||||
if (size == 0)
|
if (size == 0)
|
||||||
return 0;
|
return;
|
||||||
|
|
||||||
size_t index = *out_index;
|
size_t index = *out_index;
|
||||||
|
|
||||||
if (index == INVALID_BUF_INDEX)
|
if (index == INVALID_BUF_INDEX)
|
||||||
return size;
|
return;
|
||||||
|
|
||||||
const int16_t *input_buf = buf;
|
int16_t *inbuf = input_buf;
|
||||||
int16_t *output_buf = index_buffer(index);
|
|
||||||
|
bool alloced = inbuf && faderp->alloc &&
|
||||||
|
index_chunk_offs(index, 0) == chunk_widx;
|
||||||
|
|
||||||
while (size)
|
while (size)
|
||||||
{
|
{
|
||||||
struct chunkdesc *desc = index_chunkdesc(index);
|
struct chunkdesc *desc = index_chunkdesc(index);
|
||||||
|
int16_t *outbuf = index_buffer(index);
|
||||||
|
|
||||||
switch (offset)
|
switch (offset)
|
||||||
{
|
{
|
||||||
|
|
@ -887,60 +1014,77 @@ static size_t crossfade_mix_fade(int factor, size_t size, void *buf,
|
||||||
stamp_chunk(desc, elapsed, offset);
|
stamp_chunk(desc, elapsed, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t rem = desc->size - (index % PCMBUF_CHUNK_SIZE);
|
size_t amount = (alloced ? PCMBUF_CHUNK_SIZE : desc->size)
|
||||||
int16_t *chunk_end = SKIPBYTES(output_buf, rem);
|
- (index % PCMBUF_CHUNK_SIZE);
|
||||||
|
int16_t *chunkend = SKIPBYTES(outbuf, amount);
|
||||||
|
|
||||||
if (size < rem)
|
if (size < amount)
|
||||||
rem = size;
|
amount = size;
|
||||||
|
|
||||||
size -= rem;
|
size -= amount;
|
||||||
|
|
||||||
do
|
if (alloced)
|
||||||
{
|
{
|
||||||
/* fade left and right channel at once to keep buffer alignment */
|
/* Fade the input buffer into the new destination chunk */
|
||||||
int32_t left = output_buf[0];
|
for (size_t s = amount; s != 0; s -= PCMBUF_SAMPLE_SIZE)
|
||||||
int32_t right = output_buf[1];
|
|
||||||
|
|
||||||
if (input_buf)
|
|
||||||
{
|
{
|
||||||
/* fade the input buffer and mix into the chunk */
|
*outbuf++ = mixfade_sample(faderp, *inbuf++);
|
||||||
left += *input_buf++ * factor >> 8;
|
*outbuf++ = mixfade_sample(faderp, *inbuf++);
|
||||||
right += *input_buf++ * factor >> 8;
|
mixfader_step(faderp);
|
||||||
left = clip_sample_16(left);
|
}
|
||||||
right = clip_sample_16(right);
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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 only */
|
/* Fade the chunk in place */
|
||||||
left = left * factor >> 8;
|
for (size_t s = amount; s != 0; s -= PCMBUF_SAMPLE_SIZE)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
|
int32_t left = outbuf[0];
|
||||||
|
int32_t right = outbuf[1];
|
||||||
|
*outbuf++ = mixfade_sample(faderp, left);
|
||||||
|
*outbuf++ = mixfade_sample(faderp, right);
|
||||||
|
mixfader_step(faderp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outbuf < chunkend)
|
||||||
|
{
|
||||||
|
index += amount;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Move destination to next chunk as needed */
|
||||||
index = index_next(index);
|
index = index_next(index);
|
||||||
|
|
||||||
if (index == chunk_widx)
|
if (index == chunk_widx)
|
||||||
{
|
{
|
||||||
/* End of existing data */
|
/* End of existing data */
|
||||||
*out_index = INVALID_BUF_INDEX;
|
if (!inbuf || !faderp->alloc)
|
||||||
return size;
|
{
|
||||||
|
index = INVALID_BUF_INDEX;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
output_buf = index_buffer(index);
|
alloced = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
*out_index = buffer_index(output_buf);
|
*out_index = index;
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Initializes crossfader, calculates all necessary parameters and performs
|
/* Initializes crossfader, calculates all necessary parameters and performs
|
||||||
|
|
@ -951,6 +1095,19 @@ static void crossfade_start(void)
|
||||||
|
|
||||||
pcm_play_lock();
|
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
|
/* Initialize the crossfade buffer size to all of the buffered data that
|
||||||
* has not yet been sent to the DMA */
|
* has not yet been sent to the DMA */
|
||||||
size_t unplayed = pcmbuf_unplayed_bytes();
|
size_t unplayed = pcmbuf_unplayed_bytes();
|
||||||
|
|
@ -959,12 +1116,7 @@ static void crossfade_start(void)
|
||||||
if (unplayed < DATA_LEVEL(2))
|
if (unplayed < DATA_LEVEL(2))
|
||||||
{
|
{
|
||||||
logf("crossfade rejected");
|
logf("crossfade rejected");
|
||||||
|
crossfade_cancel();
|
||||||
crossfade_status = CROSSFADE_INACTIVE;
|
|
||||||
|
|
||||||
if (crossfade_auto_skip)
|
|
||||||
pcmbuf_monitor_track_change(true);
|
|
||||||
|
|
||||||
pcm_play_unlock();
|
pcm_play_unlock();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -982,11 +1134,7 @@ static void crossfade_start(void)
|
||||||
{
|
{
|
||||||
/* Forego fade-in delay on manual skip - do the best to preserve auto skip
|
/* Forego fade-in delay on manual skip - do the best to preserve auto skip
|
||||||
relationship */
|
relationship */
|
||||||
if (fade_out_delay > fade_in_delay)
|
fade_out_delay -= MIN(fade_out_delay, fade_in_delay);
|
||||||
fade_out_delay -= fade_in_delay;
|
|
||||||
else
|
|
||||||
fade_out_delay = 0;
|
|
||||||
|
|
||||||
fade_in_delay = 0;
|
fade_in_delay = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -994,7 +1142,10 @@ static void crossfade_start(void)
|
||||||
|
|
||||||
if (!crossfade_mixmode)
|
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();
|
pcm_play_unlock();
|
||||||
|
|
||||||
|
|
@ -1003,59 +1154,52 @@ static void crossfade_start(void)
|
||||||
/* Existing buffers are short */
|
/* Existing buffers are short */
|
||||||
size_t fade_out_short = fade_out_need - buffer_rem;
|
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 */
|
/* Truncate fade-out delay */
|
||||||
fade_out_rem -= fade_out_short;
|
fade_out_delay -= fade_out_short;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* Truncate fade-out and fade-out delay */
|
/* Truncate fade-out and eliminate fade-out delay */
|
||||||
fade_out_delay = fade_out_rem;
|
fade_out_rem = buffer_rem;
|
||||||
fade_out_rem = 0;
|
fade_out_delay = 0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Completely process the crossfade fade-out effect with current PCM buffer */
|
fade_out_need = fade_out_delay + fade_out_rem;
|
||||||
|
}
|
||||||
/* 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 */
|
/* 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)
|
/* Fade out the specified amount of the already processed audio */
|
||||||
{
|
struct mixfader outfader;
|
||||||
/* 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;
|
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,
|
/* Zero-out the rest of the buffer */
|
||||||
0, MIXFADE_KEEP_POS);
|
crossfade_mix_fade(&outfader, pcmbuf_size, NULL, &index, 0,
|
||||||
}
|
MIXFADE_NULLIFY_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();
|
pcm_play_lock();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Initialize fade-in counters */
|
/* Initialize fade-in counters */
|
||||||
crossfade_fade_in_total = fade_in_duration;
|
mixfader_init(&crossfade_infader, 0, MIXFADE_UNITY, fade_in_duration, true);
|
||||||
crossfade_fade_in_rem = fade_in_duration;
|
|
||||||
|
|
||||||
/* Find the right chunk and sample to start fading in - redo from read
|
/* Find the right chunk and sample to start fading in - redo from read
|
||||||
chunk in case original position were/was overrun in callback - the
|
chunk in case original position were/was overrun in callback - the
|
||||||
track change event _must not_ ever fail to happen */
|
track change event _must not_ ever fail to happen */
|
||||||
unplayed = pcmbuf_unplayed_bytes() + fade_in_delay;
|
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)
|
if (crossfade_auto_skip)
|
||||||
pcmbuf_monitor_track_change_ex(crossfade_index, 0);
|
pcmbuf_monitor_track_change_ex(crossfade_widx);
|
||||||
|
|
||||||
pcm_play_unlock();
|
pcm_play_unlock();
|
||||||
|
|
||||||
|
|
@ -1064,86 +1208,19 @@ static void crossfade_start(void)
|
||||||
|
|
||||||
/* Perform fade-in of new track */
|
/* Perform fade-in of new track */
|
||||||
static void write_to_crossfade(size_t size, unsigned long elapsed, off_t offset)
|
static void write_to_crossfade(size_t size, unsigned long elapsed, off_t offset)
|
||||||
{
|
|
||||||
void *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 */
|
/* Mix the data */
|
||||||
size_t fade_total = fade_rem;
|
crossfade_mix_fade(&crossfade_infader, size, index_buffer(crossfade_bufidx),
|
||||||
fade_rem = crossfade_mix_fade(factor, fade_rem, buf, &crossfade_index,
|
&crossfade_widx, elapsed, offset);
|
||||||
elapsed, offset);
|
|
||||||
fade_total -= fade_rem;
|
|
||||||
size -= fade_total;
|
|
||||||
buf += fade_total;
|
|
||||||
|
|
||||||
if (!size)
|
/* If no more fading-in to do, stop the crossfade */
|
||||||
return;
|
if (mixfader_finished(&crossfade_infader) &&
|
||||||
}
|
index_chunk_offs(crossfade_widx, 0) == chunk_widx)
|
||||||
|
|
||||||
/* Fade remaining samples in place */
|
|
||||||
int samples = fade_rem / 4;
|
|
||||||
int16_t *input_buf = buf;
|
|
||||||
|
|
||||||
while (samples--)
|
|
||||||
{
|
{
|
||||||
int32_t left = input_buf[0];
|
crossfade_cancel();
|
||||||
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;
|
|
||||||
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)
|
static void pcmbuf_finish_crossfade_enable(void)
|
||||||
{
|
{
|
||||||
/* Copy the pending setting over now */
|
/* Copy the pending setting over now */
|
||||||
|
|
@ -1156,11 +1233,6 @@ static void pcmbuf_finish_crossfade_enable(void)
|
||||||
PCMBUF_WATERMARK;
|
PCMBUF_WATERMARK;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool pcmbuf_is_crossfade_active(void)
|
|
||||||
{
|
|
||||||
return crossfade_status != CROSSFADE_INACTIVE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void pcmbuf_request_crossfade_enable(int setting)
|
void pcmbuf_request_crossfade_enable(int setting)
|
||||||
{
|
{
|
||||||
/* Next setting to be used, not applied now */
|
/* 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);
|
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 false;
|
||||||
|
|
||||||
return pcmbuf_data_critical();
|
return pcmbuf_data_critical();
|
||||||
|
|
|
||||||
|
|
@ -50,13 +50,10 @@ void pcmbuf_start_track_change(enum pcm_track_change_type type);
|
||||||
|
|
||||||
/* Crossfade */
|
/* Crossfade */
|
||||||
#ifdef HAVE_CROSSFADE
|
#ifdef HAVE_CROSSFADE
|
||||||
bool pcmbuf_is_crossfade_active(void);
|
|
||||||
void pcmbuf_request_crossfade_enable(int setting);
|
void pcmbuf_request_crossfade_enable(int setting);
|
||||||
bool pcmbuf_is_same_size(void);
|
bool pcmbuf_is_same_size(void);
|
||||||
#else
|
#else
|
||||||
/* Dummy functions with sensible returns */
|
/* 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)
|
static FORCE_INLINE void pcmbuf_request_crossfade_enable(bool on_off)
|
||||||
{ return; (void)on_off; }
|
{ return; (void)on_off; }
|
||||||
static FORCE_INLINE bool pcmbuf_is_same_size(void)
|
static FORCE_INLINE bool pcmbuf_is_same_size(void)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue