From e7a515c8b11c643332406d60a13ab1fe06d2b226 Mon Sep 17 00:00:00 2001 From: Tim Phipps Date: Sun, 21 Mar 2010 18:21:47 +0100 Subject: 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. --- src/command.c | 49 ++++++++++++++++++++ src/crossfade.c | 94 +++++++++++++++++++++++++++++++++++---- src/crossfade.h | 8 +++- src/decoder/_flac_common.c | 11 ++--- src/decoder/flac_decoder_plugin.c | 7 ++- src/decoder/flac_metadata.c | 43 ++++++++++++++++++ src/decoder/flac_metadata.h | 4 ++ src/decoder/mad_decoder_plugin.c | 47 ++++++++++++++++++++ src/decoder_api.c | 12 +++++ src/decoder_api.h | 11 +++++ src/decoder_control.c | 50 +++++++++++++++++++++ src/decoder_control.h | 13 ++++++ src/decoder_thread.c | 11 +++++ src/pcm_mix.c | 11 ++++- src/player_control.c | 31 +++++++++++++ src/player_control.h | 14 ++++++ src/player_thread.c | 16 ++++++- src/playlist_state.c | 10 +++++ 18 files changed, 423 insertions(+), 19 deletions(-) (limited to 'src') 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); @@ -1450,6 +1473,30 @@ handle_crossfade(struct client *client, G_GNUC_UNUSED int argc, char *argv[]) return COMMAND_RETURN_OK; } +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[]) { @@ -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 #include +#include +#include + +#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 +#include + +#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 +#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 #include +#include 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), -- cgit v1.2.3