player/Thread: add option "mixramp_analyzer"

This commit is contained in:
Max Kellermann 2021-12-03 19:59:54 +01:00
parent c884e2f285
commit 35c11afd54
11 changed files with 248 additions and 3 deletions

2
NEWS
View File

@ -1,4 +1,6 @@
ver 0.24 (not yet released)
* player
- add option "mixramp_analyzer" to scan MixRamp tags on-the-fly
ver 0.23.6 (not yet released)

View File

@ -658,13 +658,16 @@ MPD enables MixRamp if:
- :ref:`mixrampdb <command_mixrampdb>` is set to a reasonable value,
e.g.::
mpc mixrampdb -17
- both songs have MixRamp tags
- both songs have MixRamp tags (or ``mixramp_analyzer`` is enabled)
- both songs have the same audio format (or :ref:`audio_output_format`
is configured)
The `MixRamp <http://sourceforge.net/projects/mixramp>`__ tool can be
used to add MixRamp tags to your song files.
used to add MixRamp tags to your song files. To analyze songs
on-the-fly, you can enable the ``mixramp_analyzer`` option in
:file:`mpd.conf`::
mixramp_analyzer "yes"
Client Connections

View File

@ -80,6 +80,9 @@ enum class ConfigOption {
DESPOTIFY_USER,
DESPOTIFY_PASSWORD,
DESPOTIFY_HIGH_BITRATE,
MIXRAMP_ANALYZER,
MAX
};

View File

@ -67,6 +67,7 @@ PlayerConfig::PlayerConfig(const ConfigData &config)
return ParseAudioFormat(s, true);
})),
replay_gain(config)
replay_gain(config),
mixramp_analyzer(config.GetBool(ConfigOption::MIXRAMP_ANALYZER, false))
{
}

View File

@ -39,6 +39,8 @@ struct PlayerConfig {
ReplayGainConfig replay_gain;
bool mixramp_analyzer = false;
PlayerConfig() = default;
explicit PlayerConfig(const ConfigData &config);

View File

@ -76,6 +76,7 @@ const ConfigTemplate config_param_templates[] = {
{ "despotify_user", false, true },
{ "despotify_password", false, true },
{ "despotify_high_bitrate", false, true },
{ "mixramp_analyzer" },
};
static constexpr unsigned n_config_param_templates =

View File

@ -419,6 +419,10 @@ public:
return mix_ramp.GetStart();
}
void SetMixRampStart(std::string &&s) noexcept {
mix_ramp.SetStart(std::move(s));
}
const char *GetMixRampEnd() const noexcept {
return mix_ramp.GetEnd();
}
@ -427,6 +431,10 @@ public:
return previous_mix_ramp.GetEnd();
}
void SetMixRampPreviousEnd(std::string &&s) noexcept {
previous_mix_ramp.SetEnd(std::move(s));
}
void SetMixRamp(MixRampInfo &&new_value) noexcept {
mix_ramp = std::move(new_value);
}

105
src/pcm/MixRampGlue.cxx Normal file
View File

@ -0,0 +1,105 @@
/*
* Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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.
*
* 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.
*/
#include "MixRampGlue.hxx"
#include "MixRampAnalyzer.hxx"
#include "AudioFormat.hxx"
#include "MusicPipe.hxx"
#include "MusicChunk.hxx"
#include "util/Compiler.h"
#include <stdio.h>
static std::string
StartToString(const MixRampArray &a) noexcept
{
std::string s;
MixRampItem last{};
for (const auto &i : a) {
if (i.time < FloatDuration{} || i == last)
continue;
char buffer[64];
sprintf(buffer, "%.2f %.2f;", i.volume, i.time.count());
last = i;
s.append(buffer);
}
return s;
}
static std::string
EndToString(const MixRampArray &a, FloatDuration total_time) noexcept
{
std::string s;
MixRampItem last{};
for (const auto &i : a) {
if (i.time < FloatDuration{} || i == last)
continue;
char buffer[64];
sprintf(buffer, "%.2f %.2f;",
i.volume, (total_time - i.time).count());
last = i;
s.append(buffer);
}
return s;
}
static std::string
ToString(const MixRampData &mr, FloatDuration total_time,
MixRampDirection direction) noexcept
{
switch (direction) {
case MixRampDirection::START:
return StartToString(mr.start);
case MixRampDirection::END:
return EndToString(mr.end, total_time);
}
gcc_unreachable();
}
std::string
AnalyzeMixRamp(const MusicPipe &pipe, const AudioFormat &audio_format,
MixRampDirection direction) noexcept
{
if (audio_format.sample_rate != ReplayGainAnalyzer::SAMPLE_RATE ||
audio_format.channels != ReplayGainAnalyzer::CHANNELS ||
audio_format.format != SampleFormat::FLOAT)
// TODO: auto-convert
return {};
const auto *chunk = pipe.Peek();
if (chunk == nullptr)
return {};
MixRampAnalyzer a;
do {
a.Process(ConstBuffer<ReplayGainAnalyzer::Frame>::FromVoid({chunk->data, chunk->length}));
} while ((chunk = chunk->next.get()) != nullptr);
return ToString(a.GetResult(), a.GetTime(), direction);
}

34
src/pcm/MixRampGlue.hxx Normal file
View File

@ -0,0 +1,34 @@
/*
* Copyright 2003-2021 The Music Player Daemon Project
* http://www.musicpd.org
*
* 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.
*
* 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.
*/
#pragma once
#include <string>
struct AudioFormat;
class MusicPipe;
enum class MixRampDirection {
START, END
};
[[gnu::pure]]
std::string
AnalyzeMixRamp(const MusicPipe &pipe, const AudioFormat &audio_format,
MixRampDirection direction) noexcept;

View File

@ -49,6 +49,7 @@ pcm_sources = [
'AudioCompress/compress.c',
'ReplayGainAnalyzer.cxx',
'MixRampAnalyzer.cxx',
'MixRampGlue.cxx',
]
libsamplerate_dep = dependency('samplerate', version: '>= 0.1.3', required: get_option('libsamplerate'))

View File

@ -44,6 +44,7 @@
#include "MusicChunk.hxx"
#include "song/DetachedSong.hxx"
#include "CrossFade.hxx"
#include "pcm/MixRampGlue.hxx"
#include "tag/Tag.hxx"
#include "Idle.hxx"
#include "util/Compiler.h"
@ -328,6 +329,17 @@ private:
*/
bool OpenOutput() noexcept;
std::string UnlockAnalyzeMixRamp(const MusicPipe &pipe,
const AudioFormat &audio_format,
MixRampDirection direction) noexcept;
/**
* @return false if more chunks of the next song are needed to
* scan for MixRamp data
*/
[[nodiscard]]
bool MixRampScannerReady() noexcept;
void CheckCrossFade() noexcept;
/**
@ -473,6 +485,75 @@ real_song_duration(const DetachedSong &song,
return {SongTime(decoder_duration) - start_time};
}
std::string
Player::UnlockAnalyzeMixRamp(const MusicPipe &_pipe,
const AudioFormat &audio_format,
MixRampDirection direction) noexcept
{
const ScopeUnlock unlock(pc.mutex);
return AnalyzeMixRamp(_pipe, audio_format, direction);
}
inline bool
Player::MixRampScannerReady() noexcept
{
assert(pipe);
assert(dc.pipe);
if (!pc.cross_fade.IsMixRampEnabled())
return true;
if (!pc.config.mixramp_analyzer)
/* always ready if the scanner is disabled */
return true;
if (dc.GetMixRampPreviousEnd() == nullptr) {
// TODO: scan incrementally backwards until mixrampdb is reached
auto s = UnlockAnalyzeMixRamp(*pipe, play_audio_format,
MixRampDirection::END);
if (!s.empty()) {
FmtDebug(player_domain, "Analyzed MixRamp end: {}", s);
dc.SetMixRampPreviousEnd(std::move(s));
}
if (dc.GetMixRampStart() == nullptr)
/* scan the next song in the next call; first,
let the main loop submit a few more chunks
to the outputs for playback to avoid
xrun */
return false;
}
if (dc.GetMixRampStart() == nullptr) {
const std::size_t want_pipe_bytes =
dc.out_audio_format.TimeToSize(std::chrono::seconds{20});
const std::size_t want_pipe_chunks =
std::min((want_pipe_bytes + sizeof(MusicChunk::data) - 1)
/ sizeof(MusicChunk::data),
buffer.GetSize() / std::size_t{3});
if (dc.pipe->GetSize() < want_pipe_chunks) {
/* need more data */
if (!buffer.IsFull()) {
decoder_woken = true;
dc.Signal();
}
return false;
}
// TODO: scan incrementally until mixrampdb is reached
auto s = UnlockAnalyzeMixRamp(*dc.pipe, dc.out_audio_format,
MixRampDirection::START);
if (!s.empty()) {
FmtDebug(player_domain, "Analyzed MixRamp start: {}", s);
dc.SetMixRampStart(std::move(s));
}
}
return true;
}
bool
Player::OpenOutput() noexcept
{
@ -813,6 +894,10 @@ Player::CheckCrossFade() noexcept
return;
}
if (!MixRampScannerReady())
/* need more chunks for the MixRamp scanner */
return;
/* enable cross fading in this song? if yes, calculate how
many chunks will be required for it */
cross_fade_chunks =