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:
committed by
Max Kellermann
parent
e9b75d462c
commit
e7a515c8b1
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user