player/CrossFade: use std::chrono::duration

This commit is contained in:
Max Kellermann 2018-09-22 19:24:34 +02:00
parent 863722545f
commit 224400074c
8 changed files with 56 additions and 54 deletions

View File

@ -152,13 +152,13 @@ handle_status(Client &client, gcc_unused Request args, Response &r)
pc.GetMixRampDb(), pc.GetMixRampDb(),
state); state);
if (pc.GetCrossFade() > 0) if (pc.GetCrossFade() > FloatDuration::zero())
r.Format(COMMAND_STATUS_CROSSFADE ": %lu\n", r.Format(COMMAND_STATUS_CROSSFADE ": %lu\n",
std::lround(pc.GetCrossFade())); std::lround(pc.GetCrossFade().count()));
if (pc.GetMixRampDelay() > 0) if (pc.GetMixRampDelay() > FloatDuration::zero())
r.Format(COMMAND_STATUS_MIXRAMPDELAY ": %f\n", r.Format(COMMAND_STATUS_MIXRAMPDELAY ": %f\n",
pc.GetMixRampDelay()); pc.GetMixRampDelay().count());
song = playlist.GetCurrentPosition(); song = playlist.GetCurrentPosition();
if (song >= 0) { if (song >= 0) {
@ -316,8 +316,8 @@ handle_seekcur(Client &client, Request args, gcc_unused Response &r)
CommandResult CommandResult
handle_crossfade(Client &client, Request args, gcc_unused Response &r) handle_crossfade(Client &client, Request args, gcc_unused Response &r)
{ {
unsigned xfade_time = args.ParseUnsigned(0); FloatDuration duration{args.ParseUnsigned(0)};
client.GetPlayerControl().SetCrossFade(xfade_time); client.GetPlayerControl().SetCrossFade(duration);
return CommandResult::OK; return CommandResult::OK;
} }
@ -332,7 +332,7 @@ handle_mixrampdb(Client &client, Request args, gcc_unused Response &r)
CommandResult CommandResult
handle_mixrampdelay(Client &client, Request args, gcc_unused Response &r) handle_mixrampdelay(Client &client, Request args, gcc_unused Response &r)
{ {
float delay_secs = args.ParseFloat(0); FloatDuration delay_secs{args.ParseFloat(0)};
client.GetPlayerControl().SetMixRampDelay(delay_secs); client.GetPlayerControl().SetMixRampDelay(delay_secs);
return CommandResult::OK; return CommandResult::OK;
} }

View File

@ -283,11 +283,9 @@ PlayerControl::LockSeek(std::unique_ptr<DetachedSong> song, SongTime t)
} }
void void
PlayerControl::SetCrossFade(float _cross_fade_seconds) noexcept PlayerControl::SetCrossFade(FloatDuration duration) noexcept
{ {
if (_cross_fade_seconds < 0) cross_fade.duration = std::max(duration, FloatDuration::zero());
_cross_fade_seconds = 0;
cross_fade.duration = _cross_fade_seconds;
idle_add(IDLE_OPTIONS); idle_add(IDLE_OPTIONS);
} }
@ -301,9 +299,9 @@ PlayerControl::SetMixRampDb(float _mixramp_db) noexcept
} }
void void
PlayerControl::SetMixRampDelay(float _mixramp_delay_seconds) noexcept PlayerControl::SetMixRampDelay(FloatDuration _mixramp_delay) noexcept
{ {
cross_fade.mixramp_delay = _mixramp_delay_seconds; cross_fade.mixramp_delay = _mixramp_delay;
idle_add(IDLE_OPTIONS); idle_add(IDLE_OPTIONS);
} }

View File

@ -557,9 +557,9 @@ private:
} }
public: public:
void SetCrossFade(float cross_fade_seconds) noexcept; void SetCrossFade(FloatDuration duration) noexcept;
float GetCrossFade() const noexcept { auto GetCrossFade() const noexcept {
return cross_fade.duration; return cross_fade.duration;
} }
@ -569,9 +569,9 @@ public:
return cross_fade.mixramp_db; return cross_fade.mixramp_db;
} }
void SetMixRampDelay(float mixramp_delay_seconds) noexcept; void SetMixRampDelay(FloatDuration mixramp_delay) noexcept;
float GetMixRampDelay() const noexcept { auto GetMixRampDelay() const noexcept {
return cross_fade.mixramp_delay; return cross_fade.mixramp_delay;
} }

View File

@ -33,10 +33,11 @@
static constexpr Domain cross_fade_domain("cross_fade"); static constexpr Domain cross_fade_domain("cross_fade");
gcc_pure gcc_pure
static float static FloatDuration
mixramp_interpolate(const char *ramp_list, float required_db) noexcept mixramp_interpolate(const char *ramp_list, float required_db) noexcept
{ {
float last_db = 0, last_secs = 0; float last_db = 0;
FloatDuration last_duration = FloatDuration::zero();
bool have_last = false; bool have_last = false;
/* ramp_list is a string of pairs of dBs and seconds that describe the /* ramp_list is a string of pairs of dBs and seconds that describe the
@ -54,7 +55,7 @@ mixramp_interpolate(const char *ramp_list, float required_db) noexcept
ramp_list = endptr + 1; ramp_list = endptr + 1;
/* Parse the time. */ /* Parse the time. */
float secs = ParseFloat(ramp_list, &endptr); FloatDuration duration{ParseFloat(ramp_list, &endptr)};
if (endptr == ramp_list || (*endptr != ';' && *endptr != 0)) if (endptr == ramp_list || (*endptr != ';' && *endptr != 0))
break; break;
@ -64,27 +65,27 @@ mixramp_interpolate(const char *ramp_list, float required_db) noexcept
/* Check for exact match. */ /* Check for exact match. */
if (db == required_db) { if (db == required_db) {
return secs; return duration;
} }
/* Save if too quiet. */ /* Save if too quiet. */
if (db < required_db) { if (db < required_db) {
last_db = db; last_db = db;
last_secs = secs; last_duration = duration;
have_last = true; have_last = true;
continue; continue;
} }
/* If required db < any stored value, use the least. */ /* If required db < any stored value, use the least. */
if (!have_last) if (!have_last)
return secs; return duration;
/* Finally, interpolate linearly. */ /* Finally, interpolate linearly. */
secs = last_secs + (required_db - last_db) * (secs - last_secs) / (db - last_db); duration = last_duration + (required_db - last_db) * (duration - last_duration) / (db - last_db);
return secs; return duration;
} }
return -1; return FloatDuration(-1);
} }
unsigned unsigned
@ -99,36 +100,38 @@ CrossFadeSettings::Calculate(SignedSongTime total_time,
float chunks_f; float chunks_f;
if (total_time.IsNegative() || if (total_time.IsNegative() ||
duration < 0 || duration >= total_time.ToDoubleS() || duration <= FloatDuration::zero() ||
duration >= std::chrono::duration_cast<FloatDuration>(total_time) ||
/* we can't crossfade when the audio formats are different */ /* we can't crossfade when the audio formats are different */
af != old_format) af != old_format)
return 0; return 0;
assert(duration >= 0); assert(duration > FloatDuration::zero());
assert(af.IsValid()); assert(af.IsValid());
chunks_f = (float)af.GetTimeToSize() / (float)sizeof(MusicChunk::data); chunks_f = (float)af.GetTimeToSize() / (float)sizeof(MusicChunk::data);
if (mixramp_delay <= 0 || !mixramp_start || !mixramp_prev_end) { if (mixramp_delay <= FloatDuration::zero() ||
chunks = std::lround(chunks_f * duration); !mixramp_start || !mixramp_prev_end) {
chunks = std::lround(chunks_f * duration.count());
} else { } else {
/* Calculate mixramp overlap. */ /* Calculate mixramp overlap. */
const float mixramp_overlap_current = const auto mixramp_overlap_current =
mixramp_interpolate(mixramp_start, mixramp_interpolate(mixramp_start,
mixramp_db - replay_gain_db); mixramp_db - replay_gain_db);
const float mixramp_overlap_prev = const auto mixramp_overlap_prev =
mixramp_interpolate(mixramp_prev_end, mixramp_interpolate(mixramp_prev_end,
mixramp_db - replay_gain_prev_db); mixramp_db - replay_gain_prev_db);
const float mixramp_overlap = const auto mixramp_overlap =
mixramp_overlap_current + mixramp_overlap_prev; mixramp_overlap_current + mixramp_overlap_prev;
if (mixramp_overlap_current >= 0 && if (mixramp_overlap_current >= FloatDuration::zero() &&
mixramp_overlap_prev >= 0 && mixramp_overlap_prev >= FloatDuration::zero() &&
mixramp_delay <= mixramp_overlap) { mixramp_delay <= mixramp_overlap) {
chunks = (chunks_f * (mixramp_overlap - mixramp_delay)); chunks = (chunks_f * (mixramp_overlap - mixramp_delay).count());
FormatDebug(cross_fade_domain, FormatDebug(cross_fade_domain,
"will overlap %d chunks, %fs", chunks, "will overlap %d chunks, %fs", chunks,
mixramp_overlap - mixramp_delay); (mixramp_overlap - mixramp_delay).count());
} }
} }

View File

@ -20,6 +20,7 @@
#ifndef MPD_CROSSFADE_HXX #ifndef MPD_CROSSFADE_HXX
#define MPD_CROSSFADE_HXX #define MPD_CROSSFADE_HXX
#include "Chrono.hxx"
#include "util/Compiler.h" #include "util/Compiler.h"
struct AudioFormat; struct AudioFormat;
@ -29,7 +30,7 @@ struct CrossFadeSettings {
/** /**
* The configured cross fade duration [s]. * The configured cross fade duration [s].
*/ */
float duration; FloatDuration duration;
float mixramp_db; float mixramp_db;
@ -37,7 +38,7 @@ struct CrossFadeSettings {
* The configured MixRapm delay [s]. A non-positive value * The configured MixRapm delay [s]. A non-positive value
* disables MixRamp. * disables MixRamp.
*/ */
float mixramp_delay; FloatDuration mixramp_delay;
CrossFadeSettings() CrossFadeSettings()
:duration(0), :duration(0),

View File

@ -809,7 +809,7 @@ Player::PlayNextChunk() noexcept
cross_fade_tag = Tag::Merge(std::move(cross_fade_tag), cross_fade_tag = Tag::Merge(std::move(cross_fade_tag),
std::move(other_chunk->tag)); std::move(other_chunk->tag));
if (pc.cross_fade.mixramp_delay <= 0) { if (pc.cross_fade.mixramp_delay <= FloatDuration::zero()) {
chunk->mix_ratio = ((float)cross_fade_position) chunk->mix_ratio = ((float)cross_fade_position)
/ cross_fade_chunks; / cross_fade_chunks;
} else { } else {

View File

@ -92,10 +92,10 @@ playlist_state_save(BufferedOutputStream &os, const struct playlist &playlist,
(int)playlist.queue.single); (int)playlist.queue.single);
os.Format(PLAYLIST_STATE_FILE_CONSUME "%i\n", playlist.queue.consume); os.Format(PLAYLIST_STATE_FILE_CONSUME "%i\n", playlist.queue.consume);
os.Format(PLAYLIST_STATE_FILE_CROSSFADE "%i\n", os.Format(PLAYLIST_STATE_FILE_CROSSFADE "%i\n",
(int)pc.GetCrossFade()); (int)pc.GetCrossFade().count());
os.Format(PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n", pc.GetMixRampDb()); os.Format(PLAYLIST_STATE_FILE_MIXRAMPDB "%f\n", pc.GetMixRampDb());
os.Format(PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n", os.Format(PLAYLIST_STATE_FILE_MIXRAMPDELAY "%f\n",
pc.GetMixRampDelay()); pc.GetMixRampDelay().count());
os.Write(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n"); os.Write(PLAYLIST_STATE_FILE_PLAYLIST_BEGIN "\n");
queue_save(os, playlist.queue); queue_save(os, playlist.queue);
os.Write(PLAYLIST_STATE_FILE_PLAYLIST_END "\n"); os.Write(PLAYLIST_STATE_FILE_PLAYLIST_END "\n");
@ -159,14 +159,14 @@ playlist_state_restore(const StateFileConfig &config,
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_CONSUME))) { } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_CONSUME))) {
playlist.SetConsume(StringIsEqual(p, "1")); playlist.SetConsume(StringIsEqual(p, "1"));
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_CROSSFADE))) { } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_CROSSFADE))) {
pc.SetCrossFade(atoi(p)); pc.SetCrossFade(FloatDuration(atoi(p)));
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_MIXRAMPDB))) { } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_MIXRAMPDB))) {
pc.SetMixRampDb(ParseFloat(p)); pc.SetMixRampDb(ParseFloat(p));
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY))) { } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_MIXRAMPDELAY))) {
/* this check discards "nan" which was used /* this check discards "nan" which was used
prior to MPD 0.18 */ prior to MPD 0.18 */
if (IsDigitASCII(*p)) if (IsDigitASCII(*p))
pc.SetMixRampDelay(ParseFloat(p)); pc.SetMixRampDelay(FloatDuration(ParseFloat(p)));
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_RANDOM))) { } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_RANDOM))) {
random_mode = StringIsEqual(p, "1"); random_mode = StringIsEqual(p, "1");
} else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_CURRENT))) { } else if ((p = StringAfterPrefix(line, PLAYLIST_STATE_FILE_CURRENT))) {
@ -232,7 +232,7 @@ playlist_state_get_hash(const playlist &playlist,
(playlist.current >= 0 (playlist.current >= 0
? (playlist.queue.OrderToPosition(playlist.current) << 16) ? (playlist.queue.OrderToPosition(playlist.current) << 16)
: 0) ^ : 0) ^
((int)pc.GetCrossFade() << 20) ^ ((int)pc.GetCrossFade().count() << 20) ^
(unsigned(player_status.state) << 24) ^ (unsigned(player_status.state) << 24) ^
/* note that this takes 2 bits */ /* note that this takes 2 bits */
((int)playlist.queue.single << 25) ^ ((int)playlist.queue.single << 25) ^

View File

@ -23,53 +23,53 @@ public:
char *foo = strdup(input); char *foo = strdup(input);
CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0), CPPUNIT_ASSERT_DOUBLES_EQUAL(double(0),
mixramp_interpolate(foo, 0), mixramp_interpolate(foo, 0).count(),
0.05); 0.05);
free(foo); free(foo);
foo = strdup(input); foo = strdup(input);
CPPUNIT_ASSERT_DOUBLES_EQUAL(float(0), CPPUNIT_ASSERT_DOUBLES_EQUAL(float(0),
mixramp_interpolate(foo, 1), mixramp_interpolate(foo, 1).count(),
0.005); 0.005);
free(foo); free(foo);
foo = strdup(input); foo = strdup(input);
CPPUNIT_ASSERT_DOUBLES_EQUAL(float(0.1), CPPUNIT_ASSERT_DOUBLES_EQUAL(float(0.1),
mixramp_interpolate(foo, 3), mixramp_interpolate(foo, 3).count(),
0.005); 0.005);
free(foo); free(foo);
foo = strdup(input); foo = strdup(input);
CPPUNIT_ASSERT_DOUBLES_EQUAL(float(2.5), CPPUNIT_ASSERT_DOUBLES_EQUAL(float(2.5),
mixramp_interpolate(foo, 6), mixramp_interpolate(foo, 6).count(),
0.01); 0.01);
free(foo); free(foo);
foo = strdup(input); foo = strdup(input);
CPPUNIT_ASSERT(mixramp_interpolate(foo, 6.1) < 0); CPPUNIT_ASSERT(mixramp_interpolate(foo, 6.1) < FloatDuration::zero());
free(foo); free(foo);
foo = strdup(input); foo = strdup(input);
CPPUNIT_ASSERT_DOUBLES_EQUAL(float(0.05), CPPUNIT_ASSERT_DOUBLES_EQUAL(float(0.05),
mixramp_interpolate(foo, 2), mixramp_interpolate(foo, 2).count(),
0.05); 0.05);
free(foo); free(foo);
foo = strdup(input); foo = strdup(input);
CPPUNIT_ASSERT_DOUBLES_EQUAL(float(1.3), CPPUNIT_ASSERT_DOUBLES_EQUAL(float(1.3),
mixramp_interpolate(foo, 4.5), mixramp_interpolate(foo, 4.5).count(),
0.05); 0.05);
free(foo); free(foo);
foo = strdup(input); foo = strdup(input);
CPPUNIT_ASSERT_DOUBLES_EQUAL(float(0.9), CPPUNIT_ASSERT_DOUBLES_EQUAL(float(0.9),
mixramp_interpolate(foo, 4), mixramp_interpolate(foo, 4).count(),
0.05); 0.05);
free(foo); free(foo);
foo = strdup(input); foo = strdup(input);
CPPUNIT_ASSERT_DOUBLES_EQUAL(float(1.7), CPPUNIT_ASSERT_DOUBLES_EQUAL(float(1.7),
mixramp_interpolate(foo, 5), mixramp_interpolate(foo, 5).count(),
0.05); 0.05);
free(foo); free(foo);
} }