CrossFade.cxx 3.8 KB
Newer Older
1
/*
2
 * Copyright (C) 2003-2013 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.hxx"
22
#include "MusicChunk.hxx"
23
#include "audio_format.h"
Max Kellermann's avatar
Max Kellermann committed
24
#include "tag.h"
25

26 27
#include <cmath>

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

#undef G_LOG_DOMAIN
#define G_LOG_DOMAIN "crossfade"

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

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
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. */
87
		if (std::isnan(last_db))
88 89 90 91 92 93 94
			return secs;

		/* Finally, interpolate linearly. */
		secs = last_secs + (required_db - last_db) * (secs - last_secs) / (db - last_db);
		return secs;
	}
}
95

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

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

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

116 117
	chunks_f = (float)audio_format_time_to_size(af) / (float)CHUNK_SIZE;

118
	if (std::isnan(mixramp_delay) || !mixramp_start || !mixramp_prev_end) {
119 120
		chunks = (chunks_f * duration + 0.5);
	} else {
121 122 123
		/* 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);
124 125
		if (!std::isnan(mixramp_overlap) &&
		    mixramp_delay <= mixramp_overlap) {
126 127 128 129 130
			chunks = (chunks_f * (mixramp_overlap - mixramp_delay));
			g_debug("will overlap %d chunks, %fs", chunks,
				mixramp_overlap - mixramp_delay);
		}
	}
131

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

	return chunks;
}