aboutsummaryrefslogtreecommitdiffstats
path: root/src/crossfade.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/crossfade.c')
-rw-r--r--src/crossfade.c150
1 files changed, 95 insertions, 55 deletions
diff --git a/src/crossfade.c b/src/crossfade.c
index 01552bf65..cdfd82879 100644
--- a/src/crossfade.c
+++ b/src/crossfade.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2003-2009 The Music Player Daemon Project
+ * Copyright (C) 2003-2010 The Music Player Daemon Project
* http://www.musicpd.org
*
* This program is free software; you can redistribute it and/or modify
@@ -17,6 +17,7 @@
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
+#include "config.h"
#include "crossfade.h"
#include "pcm_mix.h"
#include "chunk.h"
@@ -25,73 +26,112 @@
#include <assert.h>
#include <string.h>
+#include <stdlib.h>
+#include <glib.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "crossfade"
+
+#ifdef G_OS_WIN32
+static char *
+strtok_r(char *str, const char *delim, G_GNUC_UNUSED char **saveptr)
+{
+ return strtok(str, delim);
+}
+#endif
+
+static float mixramp_interpolate(char *ramp_list, float required_db)
+{
+ float db, secs, last_db = nan(""), last_secs = 0;
+ char *ramp_str, *save_str = NULL;
+
+ /* ramp_list is a string of pairs of dBs and seconds that describe the
+ * volume profile. Delimiters are semi-colons between pairs and spaces
+ * between the dB and seconds of a pair.
+ * The dB values must be monotonically increasing for this to work. */
+
+ while (1) {
+ /* Parse the dB tokens out of the input string. */
+ ramp_str = strtok_r(ramp_list, " ", &save_str);
+
+ /* Tell strtok to continue next time round. */
+ ramp_list = NULL;
+
+ /* Parse the dB value. */
+ if (NULL == ramp_str) {
+ return nan("");
+ }
+ db = (float)atof(ramp_str);
+
+ /* Parse the time. */
+ ramp_str = strtok_r(NULL, ";", &save_str);
+ if (NULL == ramp_str) {
+ return nan("");
+ }
+ secs = (float)atof(ramp_str);
+
+ /* Check for exact match. */
+ if (db == required_db) {
+ return secs;
+ }
+
+ /* Save if too quiet. */
+ if (db < required_db) {
+ last_db = db;
+ last_secs = secs;
+ continue;
+ }
+
+ /* If required db < any stored value, use the least. */
+ if (isnan(last_db)) {
+ return secs;
+ }
+
+ /* Finally, interpolate linearly. */
+ secs = last_secs + (required_db - last_db) * (secs - last_secs) / (db - last_db);
+ return secs;
+ }
+}
unsigned cross_fade_calc(float duration, float total_time,
+ float mixramp_db, float mixramp_delay,
+ float replay_gain_db, float replay_gain_prev_db,
+ char *mixramp_start, char *mixramp_prev_end,
const struct audio_format *af,
const struct audio_format *old_format,
unsigned max_chunks)
{
- unsigned int chunks;
+ unsigned int chunks = 0;
+ float chunks_f;
+ float mixramp_overlap;
- if (duration <= 0 || duration >= total_time ||
+ if (duration < 0 || duration >= total_time ||
/* we can't crossfade when the audio formats are different */
!audio_format_equals(af, old_format))
return 0;
- assert(duration > 0);
- assert(af->bits > 0);
- assert(af->channels > 0);
- assert(af->sample_rate > 0);
-
- chunks = audio_format_time_to_size(af) / CHUNK_SIZE;
- chunks = (chunks * duration + 0.5);
+ assert(duration >= 0);
+ assert(audio_format_valid(af));
+
+ chunks_f = (float)audio_format_time_to_size(af) / (float)CHUNK_SIZE;
+
+ if (isnan(mixramp_delay) || !(mixramp_start) || !(mixramp_prev_end)) {
+ chunks = (chunks_f * duration + 0.5);
+ } else {
+ /* Calculate mixramp overlap. */
+ mixramp_overlap = mixramp_interpolate(mixramp_start, mixramp_db - replay_gain_db)
+ + mixramp_interpolate(mixramp_prev_end, mixramp_db - replay_gain_prev_db);
+ if (!isnan(mixramp_overlap) && (mixramp_delay <= mixramp_overlap)) {
+ chunks = (chunks_f * (mixramp_overlap - mixramp_delay));
+ g_debug("will overlap %d chunks, %fs", chunks,
+ mixramp_overlap - mixramp_delay);
+ }
+ }
- if (chunks > max_chunks)
+ if (chunks > max_chunks) {
chunks = max_chunks;
+ g_warning("audio_buffer_size too small for computed MixRamp overlap");
+ }
return chunks;
}
-
-void cross_fade_apply(struct music_chunk *a, const struct music_chunk *b,
- const struct audio_format *format,
- unsigned int current_chunk, unsigned int num_chunks)
-{
- size_t size;
-
- assert(a != NULL);
- assert(b != NULL);
- assert(a->length == 0 || b->length == 0 ||
- audio_format_equals(&a->audio_format, &b->audio_format));
- assert(current_chunk <= num_chunks);
-
- if (a->tag == NULL && b->tag != NULL)
- /* merge the tag into the destination chunk */
- a->tag = tag_dup(b->tag);
-
- size = b->length > a->length
- ? a->length
- : b->length;
-
- pcm_mix(a->data,
- b->data,
- size,
- format,
- ((float)current_chunk) / num_chunks);
-
- if (b->length > a->length) {
- /* the second buffer is larger than the first one:
- there is unmixed rest at the end. Copy it over.
- The output buffer API guarantees that there is
- enough room in a->data. */
-
-#ifndef NDEBUG
- if (a->length == 0)
- a->audio_format = b->audio_format;
-#endif
-
- memcpy(a->data + a->length,
- b->data + a->length,
- b->length - a->length);
- a->length = b->length;
- }
-}