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