diff options
-rw-r--r-- | NEWS | 1 | ||||
-rw-r--r-- | doc/protocol.xml | 38 | ||||
-rw-r--r-- | src/command.c | 49 | ||||
-rw-r--r-- | src/crossfade.c | 94 | ||||
-rw-r--r-- | src/crossfade.h | 8 | ||||
-rw-r--r-- | src/decoder/_flac_common.c | 11 | ||||
-rw-r--r-- | src/decoder/flac_decoder_plugin.c | 7 | ||||
-rw-r--r-- | src/decoder/flac_metadata.c | 43 | ||||
-rw-r--r-- | src/decoder/flac_metadata.h | 4 | ||||
-rw-r--r-- | src/decoder/mad_decoder_plugin.c | 47 | ||||
-rw-r--r-- | src/decoder_api.c | 12 | ||||
-rw-r--r-- | src/decoder_api.h | 11 | ||||
-rw-r--r-- | src/decoder_control.c | 50 | ||||
-rw-r--r-- | src/decoder_control.h | 13 | ||||
-rw-r--r-- | src/decoder_thread.c | 11 | ||||
-rw-r--r-- | src/pcm_mix.c | 11 | ||||
-rw-r--r-- | src/player_control.c | 31 | ||||
-rw-r--r-- | src/player_control.h | 14 | ||||
-rw-r--r-- | src/player_thread.c | 16 | ||||
-rw-r--r-- | src/playlist_state.c | 10 | ||||
-rw-r--r-- | test/read_tags.c | 8 | ||||
-rw-r--r-- | test/run_decoder.c | 8 |
22 files changed, 478 insertions, 19 deletions
@@ -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 4ed1878c8..e327bf66d 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 a7fccbed4..d514d0698 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 57a6c0d13..50a35f690 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 81ba91418..d313b4738 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 b642121ba..6f6d33f05 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 022ee7045..e89e2ea11 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 926cd3af7..68d15f6d4 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 3cc333617..06e691d1d 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 379cb9b8a..6f6ee8fa4 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 fc7ed3900..948ccb567 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 8c7133251..e2b645f6d 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 eeb4670a0..7388d307f 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 9c6f6e88c..7794258c9 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 99fa2c7e2..3eab61295 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 9a8aaeaca..28129608b 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 d8aed14ba..30a530a6a 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 9735b01fd..76c47609a 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 2496f0cdb..d3f5d7ccb 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 93e64ccba..9f057332d 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 4f4b3d997..19e1a4eb7 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 35543dee4..d85cf10fe 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; |