crossfade.c 3.77 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright (C) 2003-2011 The Music Player Daemon Project
3
 * http://www.musicpd.org
4 5 6 7 8 9 10 11 12 13
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
14 15 16 17
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 19
 */

20
#include "config.h"
21
#include "crossfade.h"
22
#include "chunk.h"
23
#include "audio_format.h"
Max Kellermann's avatar
Max Kellermann committed
24
#include "tag.h"
25

26 27
#include <assert.h>
#include <string.h>
28 29 30 31 32 33
#include <stdlib.h>
#include <glib.h>

#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "crossfade"

34
#ifdef G_OS_WIN32
35 36 37 38 39
static char *
strtok_r(char *str, const char *delim, G_GNUC_UNUSED char **saveptr)
{
	return strtok(str, delim);
}
40 41
#endif

42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93
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;
	}
}
94

95
unsigned cross_fade_calc(float duration, float total_time,
96
			 float mixramp_db, float mixramp_delay,
97
			 float replay_gain_db, float replay_gain_prev_db,
98
			 char *mixramp_start, char *mixramp_prev_end,
99
			 const struct audio_format *af,
100
			 const struct audio_format *old_format,
101 102
			 unsigned max_chunks)
{
103 104 105
	unsigned int chunks = 0;
	float chunks_f;
	float mixramp_overlap;
106

107
	if (duration < 0 || duration >= total_time ||
108 109
	    /* we can't crossfade when the audio formats are different */
	    !audio_format_equals(af, old_format))
110 111
		return 0;

112
	assert(duration >= 0);
113
	assert(audio_format_valid(af));
114

115 116 117 118 119
	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 {
120 121 122
		/* Calculate mixramp overlap. */
		mixramp_overlap = mixramp_interpolate(mixramp_start, mixramp_db - replay_gain_db)
		  + mixramp_interpolate(mixramp_prev_end, mixramp_db - replay_gain_prev_db);
123 124 125 126 127 128
		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);
		}
	}
129

130
	if (chunks > max_chunks) {
131
		chunks = max_chunks;
132 133
		g_warning("audio_buffer_size too small for computed MixRamp overlap");
	}
134 135 136

	return chunks;
}