1 /*
2  * Copyright 2003-2021 The Music Player Daemon Project
3  * http://www.musicpd.org
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19 
20 #include "CrossFade.hxx"
21 #include "Chrono.hxx"
22 #include "MusicChunk.hxx"
23 #include "pcm/AudioFormat.hxx"
24 #include "util/NumberParser.hxx"
25 #include "util/Domain.hxx"
26 #include "util/Math.hxx"
27 #include "Log.hxx"
28 
29 #include <cassert>
30 
31 static constexpr Domain cross_fade_domain("cross_fade");
32 
33 inline bool
CanCrossFadeSong(SignedSongTime total_time) const34 CrossFadeSettings::CanCrossFadeSong(SignedSongTime total_time) const noexcept
35 {
36 	return !total_time.IsNegative() &&
37 		total_time >= MIN_TOTAL_TIME &&
38 		duration < std::chrono::duration_cast<FloatDuration>(total_time);
39 }
40 
41 gcc_pure
42 static FloatDuration
mixramp_interpolate(const char * ramp_list,float required_db)43 mixramp_interpolate(const char *ramp_list, float required_db) noexcept
44 {
45 	float last_db = 0;
46 	FloatDuration last_duration = FloatDuration::zero();
47 	bool have_last = false;
48 
49 	/* ramp_list is a string of pairs of dBs and seconds that describe the
50 	 * volume profile. Delimiters are semi-colons between pairs and spaces
51 	 * between the dB and seconds of a pair.
52 	 * The dB values must be monotonically increasing for this to work. */
53 
54 	while (true) {
55 		/* Parse the dB value. */
56 		char *endptr;
57 		const float db = ParseFloat(ramp_list, &endptr);
58 		if (endptr == ramp_list || *endptr != ' ')
59 			break;
60 
61 		ramp_list = endptr + 1;
62 
63 		/* Parse the time. */
64 		FloatDuration duration{ParseFloat(ramp_list, &endptr)};
65 		if (endptr == ramp_list || (*endptr != ';' && *endptr != 0))
66 			break;
67 
68 		ramp_list = endptr;
69 		if (*ramp_list == ';')
70 			++ramp_list;
71 
72 		/* Check for exact match. */
73 		if (db == required_db) {
74 			return duration;
75 		}
76 
77 		/* Save if too quiet. */
78 		if (db < required_db) {
79 			last_db = db;
80 			last_duration = duration;
81 			have_last = true;
82 			continue;
83 		}
84 
85 		/* If required db < any stored value, use the least. */
86 		if (!have_last)
87 			return duration;
88 
89 		/* Finally, interpolate linearly. */
90 		duration = last_duration + (required_db - last_db) * (duration - last_duration) / (db - last_db);
91 		return duration;
92 	}
93 
94 	return FloatDuration(-1);
95 }
96 
97 unsigned
Calculate(SignedSongTime current_total_time,SignedSongTime next_total_time,float replay_gain_db,float replay_gain_prev_db,const char * mixramp_start,const char * mixramp_prev_end,const AudioFormat af,const AudioFormat old_format,unsigned max_chunks) const98 CrossFadeSettings::Calculate(SignedSongTime current_total_time,
99 			     SignedSongTime next_total_time,
100 			     float replay_gain_db, float replay_gain_prev_db,
101 			     const char *mixramp_start, const char *mixramp_prev_end,
102 			     const AudioFormat af,
103 			     const AudioFormat old_format,
104 			     unsigned max_chunks) const noexcept
105 {
106 	unsigned int chunks = 0;
107 
108 	if (!IsEnabled() ||
109 	    !CanCrossFadeSong(current_total_time) ||
110 	    !CanCrossFadeSong(next_total_time) ||
111 	    /* we can't crossfade when the audio formats are different */
112 	    af != old_format)
113 		return 0;
114 
115 	assert(duration > FloatDuration::zero());
116 	assert(af.IsValid());
117 
118 	const auto chunk_duration =
119 		af.SizeToTime<FloatDuration>(sizeof(MusicChunk::data));
120 
121 	if (mixramp_delay <= FloatDuration::zero() ||
122 	    !mixramp_start || !mixramp_prev_end) {
123 		chunks = lround(duration / chunk_duration);
124 	} else {
125 		/* Calculate mixramp overlap. */
126 		const auto mixramp_overlap_current =
127 			mixramp_interpolate(mixramp_start,
128 					    mixramp_db - replay_gain_db);
129 		const auto mixramp_overlap_prev =
130 			mixramp_interpolate(mixramp_prev_end,
131 					    mixramp_db - replay_gain_prev_db);
132 		const auto mixramp_overlap =
133 			mixramp_overlap_current + mixramp_overlap_prev;
134 
135 		if (mixramp_overlap_current >= FloatDuration::zero() &&
136 		    mixramp_overlap_prev >= FloatDuration::zero() &&
137 		    mixramp_delay <= mixramp_overlap) {
138 			chunks = lround((mixramp_overlap - mixramp_delay)
139 					/ chunk_duration);
140 			FmtDebug(cross_fade_domain,
141 				 "will overlap {} chunks, {}s", chunks,
142 				 (mixramp_overlap - mixramp_delay).count());
143 		}
144 	}
145 
146 	if (chunks > max_chunks) {
147 		chunks = max_chunks;
148 		LogWarning(cross_fade_domain,
149 			   "audio_buffer_size too small for computed MixRamp overlap");
150 	}
151 
152 	return chunks;
153 }
154