aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Phipps <mpd@phipps-hutton.freeserve.co.uk>2010-03-21 18:21:47 +0100
committerMax Kellermann <max@duempel.org>2010-03-21 18:21:47 +0100
commite7a515c8b11c643332406d60a13ab1fe06d2b226 (patch)
treeaa7179b453b6fe7b163d1b4b807157359cb436cf
parente9b75d462c4d0ffee3b3b26582800ec4f657a333 (diff)
Add support for MixRamp tags
Adds mixrampdb and mixrampdelay commands. Reads MIXRAP_START and MIXRAMP_END tags from FLAC files and overlaps instead of crossfading.
-rw-r--r--NEWS1
-rw-r--r--doc/protocol.xml38
-rw-r--r--src/command.c49
-rw-r--r--src/crossfade.c94
-rw-r--r--src/crossfade.h8
-rw-r--r--src/decoder/_flac_common.c11
-rw-r--r--src/decoder/flac_decoder_plugin.c7
-rw-r--r--src/decoder/flac_metadata.c43
-rw-r--r--src/decoder/flac_metadata.h4
-rw-r--r--src/decoder/mad_decoder_plugin.c47
-rw-r--r--src/decoder_api.c12
-rw-r--r--src/decoder_api.h11
-rw-r--r--src/decoder_control.c50
-rw-r--r--src/decoder_control.h13
-rw-r--r--src/decoder_thread.c11
-rw-r--r--src/pcm_mix.c11
-rw-r--r--src/player_control.c31
-rw-r--r--src/player_control.h14
-rw-r--r--src/player_thread.c16
-rw-r--r--src/playlist_state.c10
-rw-r--r--test/read_tags.c8
-rw-r--r--test/run_decoder.c8
22 files changed, 478 insertions, 19 deletions
diff --git a/NEWS b/NEWS
index be041afd..31c699e9 100644
--- a/NEWS
+++ b/NEWS
@@ -88,6 +88,7 @@ ver 0.16 (20??/??/??)
* pcm_volume, pcm_mix: implemented 32 bit support
* support packed 24 bit samples
* CUE sheet support
+* support for MixRamp tags
* obey $(sysconfdir) for default mpd.conf location
* build with large file support by default
* added test suite ("make check")
diff --git a/doc/protocol.xml b/doc/protocol.xml
index 4ed1878c..e327bf66 100644
--- a/doc/protocol.xml
+++ b/doc/protocol.xml
@@ -340,6 +340,18 @@
</listitem>
<listitem>
<para>
+ <varname>mixrampdb</varname>:
+ <returnvalue>mixramp threshold in dB</returnvalue>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ <varname>mixrampdelay</varname>:
+ <returnvalue>mixrampdelay in seconds</returnvalue>
+ </para>
+ </listitem>
+ <listitem>
+ <para>
<varname>audio</varname>:
<returnvalue>sampleRate:bits:channels</returnvalue>
</para>
@@ -442,6 +454,32 @@
</para>
</listitem>
</varlistentry>
+ <varlistentry id="command_mixrampdb">
+ <term>
+ <cmdsynopsis>
+ <command>mixrampdb</command>
+ <arg choice="req"><replaceable>deciBels</replaceable></arg>
+ </cmdsynopsis>
+ </term>
+ <listitem>
+ <para>
+ Sets the threshold at which songs will be overlapped. Like crossfading but doesn't fade the track volume, just overlaps. The songs need to have MixRamp tags added by an external tool. 0dB is the normalized maximum volume so use negative values, I prefer -17dB. In the absence of mixramp tags crossfading will be used. See http://sourceforge.net/projects/mixramp
+ </para>
+ </listitem>
+ </varlistentry>
+ <varlistentry id="command_mixrampdelay">
+ <term>
+ <cmdsynopsis>
+ <command>mixrampdelay</command>
+ <arg choice="req"><replaceable>SECONDS</replaceable></arg>
+ </cmdsynopsis>
+ </term>
+ <listitem>
+ <para>
+ Additional time subtracted from the overlap calculated by mixrampdb. A value of "nan" disables MixRamp overlapping and falls back to crossfading.
+ </para>
+ </listitem>
+ </varlistentry>
<varlistentry id="command_random">
<term>
<cmdsynopsis>
diff --git a/src/command.c b/src/command.c
index a7fccbed..d514d069 100644
--- a/src/command.c
+++ b/src/command.c
@@ -76,6 +76,8 @@
#define COMMAND_STATUS_BITRATE "bitrate"
#define COMMAND_STATUS_ERROR "error"
#define COMMAND_STATUS_CROSSFADE "xfade"
+#define COMMAND_STATUS_MIXRAMPDB "mixrampdb"
+#define COMMAND_STATUS_MIXRAMPDELAY "mixrampdelay"
#define COMMAND_STATUS_AUDIO "audio"
#define COMMAND_STATUS_UPDATING_DB "updating_db"
@@ -294,6 +296,23 @@ check_bool(struct client *client, bool *value_r, const char *s)
return true;
}
+static bool
+check_float(struct client *client, float *value_r, const char *s)
+{
+ float value;
+ char *endptr;
+
+ value = strtof(s, &endptr);
+ if (*endptr != 0 && endptr == s) {
+ command_error(client, ACK_ERROR_ARG,
+ "Float expected: %s", s);
+ return false;
+ }
+
+ *value_r = value;
+ return true;
+}
+
static enum command_return
print_playlist_result(struct client *client,
enum playlist_result result)
@@ -495,6 +514,8 @@ handle_status(struct client *client,
COMMAND_STATUS_PLAYLIST ": %li\n"
COMMAND_STATUS_PLAYLIST_LENGTH ": %i\n"
COMMAND_STATUS_CROSSFADE ": %i\n"
+ COMMAND_STATUS_MIXRAMPDB ": %f\n"
+ COMMAND_STATUS_MIXRAMPDELAY ": %f\n"
COMMAND_STATUS_STATE ": %s\n",
volume_level_get(),
playlist_get_repeat(&g_playlist),
@@ -504,6 +525,8 @@ handle_status(struct client *client,
playlist_get_version(&g_playlist),
playlist_get_length(&g_playlist),
(int)(pc_get_cross_fade() + 0.5),
+ pc_get_mixramp_db(),
+ pc_get_mixramp_delay(),
state);
song = playlist_get_current_song(&g_playlist);
@@ -1451,6 +1474,30 @@ handle_crossfade(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
}
static enum command_return
+handle_mixrampdb(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ float db;
+
+ if (!check_float(client, &db, argv[1]))
+ return COMMAND_RETURN_ERROR;
+ pc_set_mixramp_db(db);
+
+ return COMMAND_RETURN_OK;
+}
+
+static enum command_return
+handle_mixrampdelay(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
+{
+ float delay_secs;
+
+ if (!check_float(client, &delay_secs, argv[1]))
+ return COMMAND_RETURN_ERROR;
+ pc_set_mixramp_delay(delay_secs);
+
+ return COMMAND_RETURN_OK;
+}
+
+static enum command_return
handle_enableoutput(struct client *client, G_GNUC_UNUSED int argc, char *argv[])
{
unsigned device;
@@ -1805,6 +1852,8 @@ static const struct command commands[] = {
{ "listplaylists", PERMISSION_READ, 0, 0, handle_listplaylists },
{ "load", PERMISSION_ADD, 1, 1, handle_load },
{ "lsinfo", PERMISSION_READ, 0, 1, handle_lsinfo },
+ { "mixrampdb", PERMISSION_CONTROL, 1, 1, handle_mixrampdb },
+ { "mixrampdelay", PERMISSION_CONTROL, 1, 1, handle_mixrampdelay },
{ "move", PERMISSION_CONTROL, 2, 2, handle_move },
{ "moveid", PERMISSION_CONTROL, 2, 2, handle_moveid },
{ "next", PERMISSION_CONTROL, 0, 0, handle_next },
diff --git a/src/crossfade.c b/src/crossfade.c
index 57a6c0d1..50a35f69 100644
--- a/src/crossfade.c
+++ b/src/crossfade.c
@@ -26,34 +26,111 @@
#include <assert.h>
#include <string.h>
+#include <stdlib.h>
+#include <glib.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "crossfade"
+
+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,
+ 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(duration >= 0);
assert(audio_format_valid(af));
- chunks = audio_format_time_to_size(af) / CHUNK_SIZE;
- chunks = (chunks * duration + 0.5);
+ 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.
+ * FIXME factor in ReplayGain for both songs. */
+ mixramp_overlap = mixramp_interpolate(mixramp_start, mixramp_db)
+ + mixramp_interpolate(mixramp_prev_end, mixramp_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)
+ float mix_ratio)
{
size_t size;
@@ -61,7 +138,6 @@ void cross_fade_apply(struct music_chunk *a, const struct music_chunk *b,
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 */
@@ -75,7 +151,7 @@ void cross_fade_apply(struct music_chunk *a, const struct music_chunk *b,
b->data,
size,
format,
- ((float)current_chunk) / num_chunks);
+ mix_ratio);
if (b->length > a->length) {
/* the second buffer is larger than the first one:
diff --git a/src/crossfade.h b/src/crossfade.h
index 81ba9141..d313b473 100644
--- a/src/crossfade.h
+++ b/src/crossfade.h
@@ -28,6 +28,10 @@ struct music_chunk;
*
* @param duration the requested crossfade duration
* @param total_time total_time the duration of the new song
+ * @param mixramp_db the current mixramp_db setting
+ * @param mixramp_delay the current mixramp_delay setting
+ * @param mixramp_start the next songs mixramp_start tag
+ * @param mixramp_prev_end the last songs mixramp_end setting
* @param af the audio format of the new song
* @param old_format the audio format of the current song
* @param max_chunks the maximum number of chunks
@@ -35,6 +39,8 @@ struct music_chunk;
* should be disabled for this song change
*/
unsigned cross_fade_calc(float duration, float total_time,
+ float mixramp_db, float mixramp_delay,
+ char *mixramp_start, char *mixramp_prev_end,
const struct audio_format *af,
const struct audio_format *old_format,
unsigned max_chunks);
@@ -51,6 +57,6 @@ unsigned cross_fade_calc(float duration, float total_time,
*/
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);
+ float mix_ratio);
#endif
diff --git a/src/decoder/_flac_common.c b/src/decoder/_flac_common.c
index b642121b..6f6d33f0 100644
--- a/src/decoder/_flac_common.c
+++ b/src/decoder/_flac_common.c
@@ -102,11 +102,6 @@ flac_got_stream_info(struct flac_data *data,
if (data->total_frames == 0)
data->total_frames = stream_info->total_samples;
- decoder_initialized(data->decoder, &data->audio_format,
- data->input_stream->seekable,
- (float)data->total_frames /
- (float)data->audio_format.sample_rate);
-
data->initialized = true;
}
@@ -117,6 +112,8 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
return;
struct replay_gain_info rgi;
+ char *mixramp_start;
+ char *mixramp_end;
switch (block->type) {
case FLAC__METADATA_TYPE_STREAMINFO:
@@ -126,6 +123,10 @@ void flac_metadata_common_cb(const FLAC__StreamMetadata * block,
case FLAC__METADATA_TYPE_VORBIS_COMMENT:
if (flac_parse_replay_gain(&rgi, block))
decoder_replay_gain(data->decoder, &rgi);
+ if (flac_parse_mixramp(&mixramp_start, &mixramp_end, block)) {
+ g_debug("setting mixramp_tags");
+ decoder_mixramp(data->decoder, mixramp_start, mixramp_end);
+ }
if (data->tag != NULL)
flac_vorbis_comments_to_tag(data->tag, NULL,
diff --git a/src/decoder/flac_decoder_plugin.c b/src/decoder/flac_decoder_plugin.c
index 022ee704..e89e2ea1 100644
--- a/src/decoder/flac_decoder_plugin.c
+++ b/src/decoder/flac_decoder_plugin.c
@@ -247,9 +247,14 @@ flac_decoder_initialize(struct flac_data *data, FLAC__StreamDecoder *sd,
return false;
}
- if (data->initialized)
+ if (data->initialized) {
/* done */
+ decoder_initialized(data->decoder, &data->audio_format,
+ data->input_stream->seekable,
+ (float)data->total_frames /
+ (float)data->audio_format.sample_rate);
return true;
+ }
if (data->input_stream->seekable)
/* allow the workaround below only for nonseekable
diff --git a/src/decoder/flac_metadata.c b/src/decoder/flac_metadata.c
index 926cd3af..68d15f6d 100644
--- a/src/decoder/flac_metadata.c
+++ b/src/decoder/flac_metadata.c
@@ -80,6 +80,49 @@ flac_parse_replay_gain(struct replay_gain_info *rgi,
return found;
}
+static bool
+flac_find_string_comment(const FLAC__StreamMetadata *block,
+ const char *cmnt, char **str)
+{
+ int offset;
+ size_t pos;
+ int len;
+ unsigned char tmp, *p;
+
+ *str = NULL;
+ offset = FLAC__metadata_object_vorbiscomment_find_entry_from(block, 0,
+ cmnt);
+ if (offset < 0)
+ return false;
+
+ pos = strlen(cmnt) + 1; /* 1 is for '=' */
+ len = block->data.vorbis_comment.comments[offset].length - pos;
+ if (len <= 0)
+ return false;
+
+ p = &block->data.vorbis_comment.comments[offset].entry[pos];
+ tmp = p[len];
+ p[len] = '\0';
+ *str = strdup((char *)p);
+ p[len] = tmp;
+
+ return true;
+}
+
+bool
+flac_parse_mixramp(char **mixramp_start, char **mixramp_end,
+ const FLAC__StreamMetadata *block)
+{
+ bool found = false;
+
+ if (flac_find_string_comment(block, "mixramp_start", mixramp_start))
+ found = true;
+ if (flac_find_string_comment(block, "mixramp_end", mixramp_end))
+ found = true;
+
+ return found;
+}
+
/**
* Checks if the specified name matches the entry's name, and if yes,
* returns the comment value (not null-temrinated).
diff --git a/src/decoder/flac_metadata.h b/src/decoder/flac_metadata.h
index 3cc33361..06e691d1 100644
--- a/src/decoder/flac_metadata.h
+++ b/src/decoder/flac_metadata.h
@@ -37,6 +37,10 @@ bool
flac_parse_replay_gain(struct replay_gain_info *rgi,
const FLAC__StreamMetadata *block);
+bool
+flac_parse_mixramp(char **mixramp_start, char **mixramp_end,
+ const FLAC__StreamMetadata *block);
+
void
flac_vorbis_comments_to_tag(struct tag *tag, const char *char_tnum,
const FLAC__StreamMetadata_VorbisComment *comment);
diff --git a/src/decoder/mad_decoder_plugin.c b/src/decoder/mad_decoder_plugin.c
index 379cb9b8..6f6ee8fa 100644
--- a/src/decoder/mad_decoder_plugin.c
+++ b/src/decoder/mad_decoder_plugin.c
@@ -347,6 +347,47 @@ parse_id3_replay_gain_info(struct replay_gain_info *replay_gain_info,
}
#endif
+#ifdef HAVE_ID3TAG
+static bool
+parse_id3_mixramp(char **mixramp_start, char **mixramp_end,
+ struct id3_tag *tag)
+{
+ int i;
+ char *key;
+ char *value;
+ struct id3_frame *frame;
+ bool found = false;
+
+ *mixramp_start = NULL;
+ *mixramp_end = NULL;
+
+ for (i = 0; (frame = id3_tag_findframe(tag, "TXXX", i)); i++) {
+ if (frame->nfields < 3)
+ continue;
+
+ key = (char *)
+ id3_ucs4_latin1duplicate(id3_field_getstring
+ (&frame->fields[1]));
+ value = (char *)
+ id3_ucs4_latin1duplicate(id3_field_getstring
+ (&frame->fields[2]));
+
+ if (g_ascii_strcasecmp(key, "mixramp_start") == 0) {
+ *mixramp_start = strdup(value);
+ found = true;
+ } else if (g_ascii_strcasecmp(key, "mixramp_end") == 0) {
+ *mixramp_end = strdup(value);
+ found = true;
+ }
+
+ free(key);
+ free(value);
+ }
+
+ return found;
+}
+#endif
+
static void mp3_parse_id3(struct mp3_data *data, size_t tagsize,
struct tag **mpd_tag)
{
@@ -403,10 +444,16 @@ static void mp3_parse_id3(struct mp3_data *data, size_t tagsize,
if (data->decoder != NULL) {
struct replay_gain_info rgi;
+ char *mixramp_start;
+ char *mixramp_end;
if (parse_id3_replay_gain_info(&rgi, id3_tag)) {
decoder_replay_gain(data->decoder, &rgi);
data->found_replay_gain = true;
}
+ if (parse_id3_mixramp(&mixramp_start, &mixramp_end, id3_tag)) {
+ g_debug("setting mixramp_tags");
+ decoder_mixramp(data->decoder, mixramp_start, mixramp_end);
+ }
}
id3_tag_delete(id3_tag);
diff --git a/src/decoder_api.c b/src/decoder_api.c
index fc7ed390..948ccb56 100644
--- a/src/decoder_api.c
+++ b/src/decoder_api.c
@@ -427,3 +427,15 @@ decoder_replay_gain(struct decoder *decoder,
} else
decoder->replay_gain_serial = 0;
}
+
+void
+decoder_mixramp(struct decoder *decoder,
+ char *mixramp_start, char *mixramp_end)
+{
+ assert(decoder != NULL);
+ struct decoder_control *dc = decoder->dc;
+ assert(dc != NULL);
+
+ dc_mixramp_start(dc, mixramp_start);
+ dc_mixramp_end(dc, mixramp_end);
+}
diff --git a/src/decoder_api.h b/src/decoder_api.h
index 8c713325..e2b645f6 100644
--- a/src/decoder_api.h
+++ b/src/decoder_api.h
@@ -157,4 +157,15 @@ void
decoder_replay_gain(struct decoder *decoder,
const struct replay_gain_info *replay_gain_info);
+/**
+ * Store MixRamp tags.
+ *
+ * @param decoder the decoder object
+ * @param mixramp_start the mixramp_start tag; may be NULL to invalidate
+ * @param mixramp_end the mixramp_end tag; may be NULL to invalidate
+ */
+void
+decoder_mixramp(struct decoder *decoder,
+ char *mixramp_start, char *mixramp_end);
+
#endif
diff --git a/src/decoder_control.c b/src/decoder_control.c
index eeb4670a..7388d307 100644
--- a/src/decoder_control.c
+++ b/src/decoder_control.c
@@ -22,6 +22,10 @@
#include "player_control.h"
#include <assert.h>
+#include <malloc.h>
+
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "decoder_control"
void
dc_init(struct decoder_control *dc)
@@ -33,6 +37,10 @@ dc_init(struct decoder_control *dc)
dc->state = DECODE_STATE_STOP;
dc->command = DECODE_COMMAND_NONE;
+
+ dc->mixramp_start = NULL;
+ dc->mixramp_end = NULL;
+ dc->mixramp_prev_end = NULL;
}
void
@@ -40,6 +48,15 @@ dc_deinit(struct decoder_control *dc)
{
g_cond_free(dc->cond);
g_mutex_free(dc->mutex);
+ if (dc->mixramp_start)
+ free(dc->mixramp_start);
+ if (dc->mixramp_end)
+ free(dc->mixramp_end);
+ if (dc->mixramp_prev_end)
+ free(dc->mixramp_prev_end);
+ dc->mixramp_start = NULL;
+ dc->mixramp_end = NULL;
+ dc->mixramp_prev_end = NULL;
}
static void
@@ -147,3 +164,36 @@ dc_quit(struct decoder_control *dc)
g_thread_join(dc->thread);
dc->thread = NULL;
}
+
+void
+dc_mixramp_start(struct decoder_control *dc, char *mixramp_start)
+{
+ assert(dc != NULL);
+
+ if (dc->mixramp_start)
+ free(dc->mixramp_start);
+ dc->mixramp_start = mixramp_start;
+ g_debug("mixramp_start = %s", mixramp_start ? mixramp_start : "NULL");
+}
+
+void
+dc_mixramp_end(struct decoder_control *dc, char *mixramp_end)
+{
+ assert(dc != NULL);
+
+ if (dc->mixramp_end)
+ free(dc->mixramp_end);
+ dc->mixramp_end = mixramp_end;
+ g_debug("mixramp_end = %s", mixramp_end ? mixramp_end : "NULL");
+}
+
+void
+dc_mixramp_prev_end(struct decoder_control *dc, char *mixramp_prev_end)
+{
+ assert(dc != NULL);
+
+ if (dc->mixramp_prev_end)
+ free(dc->mixramp_prev_end);
+ dc->mixramp_prev_end = mixramp_prev_end;
+ g_debug("mixramp_prev_end = %s", mixramp_prev_end ? mixramp_prev_end : "NULL");
+}
diff --git a/src/decoder_control.h b/src/decoder_control.h
index 9c6f6e88..7794258c 100644
--- a/src/decoder_control.h
+++ b/src/decoder_control.h
@@ -89,6 +89,10 @@ struct decoder_control {
* owns this object, and is responsible for freeing it.
*/
struct music_pipe *pipe;
+
+ char *mixramp_start;
+ char *mixramp_end;
+ char *mixramp_prev_end;
};
void
@@ -235,4 +239,13 @@ dc_seek(struct decoder_control *dc, double where);
void
dc_quit(struct decoder_control *dc);
+void
+dc_mixramp_start(struct decoder_control *dc, char *mixramp_start);
+
+void
+dc_mixramp_end(struct decoder_control *dc, char *mixramp_end);
+
+void
+dc_mixramp_prev_end(struct decoder_control *dc, char *mixramp_prev_end);
+
#endif
diff --git a/src/decoder_thread.c b/src/decoder_thread.c
index 99fa2c7e..3eab6129 100644
--- a/src/decoder_thread.c
+++ b/src/decoder_thread.c
@@ -23,6 +23,7 @@
#include "decoder_internal.h"
#include "decoder_list.h"
#include "decoder_plugin.h"
+#include "decoder_api.h"
#include "input_stream.h"
#include "player_control.h"
#include "pipe.h"
@@ -36,6 +37,9 @@
#include <unistd.h>
+#undef G_LOG_DOMAIN
+#define G_LOG_DOMAIN "decoder_thread"
+
static enum decoder_command
decoder_lock_get_command(struct decoder_control *dc)
{
@@ -430,6 +434,13 @@ decoder_task(gpointer arg)
switch (dc->command) {
case DECODE_COMMAND_START:
+ g_debug("clearing mixramp tags");
+ dc_mixramp_start(dc, NULL);
+ dc_mixramp_prev_end(dc, dc->mixramp_end);
+ dc->mixramp_end = NULL; /* Don't free, it's copied above. */
+
+ /* fall through */
+
case DECODE_COMMAND_SEEK:
decoder_run(dc);
diff --git a/src/pcm_mix.c b/src/pcm_mix.c
index 9a8aaeac..28129608 100644
--- a/src/pcm_mix.c
+++ b/src/pcm_mix.c
@@ -137,7 +137,16 @@ pcm_mix(void *buffer1, const void *buffer2, size_t size,
const struct audio_format *format, float portion1)
{
int vol1;
- float s = sin(M_PI_2 * portion1);
+ float s;
+
+ /* portion1 is between 0.0 and 1.0 for crossfading, MixRamp uses NaN
+ * to signal mixing rather than fading */
+ if (isnan(portion1)) {
+ pcm_add(buffer1, buffer2, size, PCM_VOLUME_1, PCM_VOLUME_1, format);
+ return;
+ }
+
+ s = sin(M_PI_2 * portion1);
s *= s;
vol1 = s * PCM_VOLUME_1 + 0.5;
diff --git a/src/player_control.c b/src/player_control.c
index d8aed14b..30a530a6 100644
--- a/src/player_control.c
+++ b/src/player_control.c
@@ -30,6 +30,7 @@
#include <assert.h>
#include <stdio.h>
+#include <math.h>
struct player_control pc;
@@ -45,6 +46,8 @@ void pc_init(unsigned buffer_chunks, unsigned int buffered_before_play)
pc.error = PLAYER_ERROR_NOERROR;
pc.state = PLAYER_STATE_STOP;
pc.cross_fade_seconds = 0;
+ pc.mixramp_db = 0;
+ pc.mixramp_delay_seconds = nanf("");
}
void pc_deinit(void)
@@ -305,6 +308,34 @@ pc_set_cross_fade(float cross_fade_seconds)
idle_add(IDLE_OPTIONS);
}
+float
+pc_get_mixramp_db(void)
+{
+ return pc.mixramp_db;
+}
+
+void
+pc_set_mixramp_db(float mixramp_db)
+{
+ pc.mixramp_db = mixramp_db;
+
+ idle_add(IDLE_OPTIONS);
+}
+
+float
+pc_get_mixramp_delay(void)
+{
+ return pc.mixramp_delay_seconds;
+}
+
+void
+pc_set_mixramp_delay(float mixramp_delay_seconds)
+{
+ pc.mixramp_delay_seconds = mixramp_delay_seconds;
+
+ idle_add(IDLE_OPTIONS);
+}
+
double
pc_get_total_play_time(void)
{
diff --git a/src/player_control.h b/src/player_control.h
index 9735b01f..76c47609 100644
--- a/src/player_control.h
+++ b/src/player_control.h
@@ -111,6 +111,8 @@ struct player_control {
const struct song *errored_song;
double seek_where;
float cross_fade_seconds;
+ float mixramp_db;
+ float mixramp_delay_seconds;
double total_play_time;
};
@@ -250,6 +252,18 @@ pc_set_cross_fade(float cross_fade_seconds);
float
pc_get_cross_fade(void);
+void
+pc_set_mixramp_db(float mixramp_db);
+
+float
+pc_get_mixramp_db(void);
+
+void
+pc_set_mixramp_delay(float mixramp_delay_seconds);
+
+float
+pc_get_mixramp_delay(void);
+
double
pc_get_total_play_time(void);
diff --git a/src/player_thread.c b/src/player_thread.c
index 2496f0cd..d3f5d7cc 100644
--- a/src/player_thread.c
+++ b/src/player_thread.c
@@ -644,13 +644,21 @@ play_next_chunk(struct player *player)
}
if (other_chunk != NULL) {
+ float mix_ratio;
+
chunk = music_pipe_shift(player->pipe);
assert(chunk != NULL);
+ if (isnan(pc.mixramp_delay_seconds)) {
+ mix_ratio = ((float)cross_fade_position)
+ / player->cross_fade_chunks;
+ } else {
+ mix_ratio = nan("");
+ }
+
cross_fade_apply(chunk, other_chunk,
&dc->out_audio_format,
- cross_fade_position,
- player->cross_fade_chunks);
+ mix_ratio);
music_buffer_return(player_buffer, other_chunk);
} else {
/* there are not enough decoded chunks yet */
@@ -865,6 +873,10 @@ static void do_play(struct decoder_control *dc)
for it */
player.cross_fade_chunks =
cross_fade_calc(pc.cross_fade_seconds, dc->total_time,
+ pc.mixramp_db,
+ pc.mixramp_delay_seconds,
+ dc->mixramp_start,
+ dc->mixramp_prev_end,
&dc->out_audio_format,
&player.play_audio_format,
music_buffer_size(player_buffer) -
diff --git a/src/playlist_state.c b/src/playlist_state.c
index 93e64ccb..9f057332 100644
--- a/src/playlist_state.c
+++ b/src/playlist_state.c
@@ -40,6 +40,8 @@
#define PLAYLIST_STATE_FILE_CURRENT "current: "
#define PLAYLIST_STATE_FILE_TIME "time: "
#define PLAYLIST_STATE_FILE_CROSSFADE "crossfade: "
+#define PLAYLIST_STATE_FILE_MIXRAMPDB "mixrampdb: "
+#define PLAYLIST_STATE_FILE_MIXRAMPDELAY "mixrampdelay: "
#define PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "playlist_begin"
#define PLAYLIST_STATE_FILE_PLAYLIST_END "playlist_end"
@@ -90,6 +92,10 @@ playlist_state_save(FILE *fp, const struct playlist *playlist)
playlist->queue.consume);
fprintf(fp, "%s%i\n", PLAYLIST_STATE_FILE_CROSSFADE,
(int)(pc_get_cross_fade()));
+ fprintf(fp, "%s%f\n", PLAYLIST_STATE_FILE_MIXRAMPDB,
+ pc_get_mixramp_db());
+ fprintf(fp, "%s%f\n", PLAYLIST_STATE_FILE_MIXRAMPDELAY,
+ pc_get_mixramp_delay());
fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_BEGIN);
queue_save(fp, &playlist->queue);
fprintf(fp, "%s\n", PLAYLIST_STATE_FILE_PLAYLIST_END);
@@ -168,6 +174,10 @@ playlist_state_restore(const char *line, FILE *fp, struct playlist *playlist)
playlist_set_consume(playlist, false);
} else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_CROSSFADE)) {
pc_set_cross_fade(atoi(buffer + strlen(PLAYLIST_STATE_FILE_CROSSFADE)));
+ } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_MIXRAMPDB)) {
+ pc_set_mixramp_db(atof(buffer + strlen(PLAYLIST_STATE_FILE_MIXRAMPDB)));
+ } else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_MIXRAMPDELAY)) {
+ pc_set_mixramp_delay(atof(buffer + strlen(PLAYLIST_STATE_FILE_MIXRAMPDELAY)));
} else if (g_str_has_prefix(buffer, PLAYLIST_STATE_FILE_RANDOM)) {
random_mode =
strcmp(buffer + strlen(PLAYLIST_STATE_FILE_RANDOM),
diff --git a/test/read_tags.c b/test/read_tags.c
index 4f4b3d99..19e1a4eb 100644
--- a/test/read_tags.c
+++ b/test/read_tags.c
@@ -121,6 +121,14 @@ decoder_replay_gain(G_GNUC_UNUSED struct decoder *decoder,
{
}
+void
+decoder_mixramp(G_GNUC_UNUSED struct decoder *decoder,
+ char *mixramp_start, char *mixramp_end)
+{
+ g_free(mixramp_start);
+ g_free(mixramp_end);
+}
+
static void
print_tag(const struct tag *tag)
{
diff --git a/test/run_decoder.c b/test/run_decoder.c
index 35543dee..d85cf10f 100644
--- a/test/run_decoder.c
+++ b/test/run_decoder.c
@@ -142,6 +142,14 @@ decoder_replay_gain(G_GNUC_UNUSED struct decoder *decoder,
{
}
+void
+decoder_mixramp(G_GNUC_UNUSED struct decoder *decoder,
+ char *mixramp_start, char *mixramp_end)
+{
+ g_free(mixramp_start);
+ g_free(mixramp_end);
+}
+
int main(int argc, char **argv)
{
GError *error = NULL;