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.
This commit is contained in:
parent
e9b75d462c
commit
e7a515c8b1
1
NEWS
1
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")
|
||||
|
|
|
@ -338,6 +338,18 @@
|
|||
<returnvalue>crossfade in seconds</returnvalue>
|
||||
</para>
|
||||
</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>:
|
||||
|
@ -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>
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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 (chunks > max_chunks)
|
||||
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) {
|
||||
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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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).
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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) -
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue