player/Thread: add option "mixramp_analyzer"
This commit is contained in:
parent
c884e2f285
commit
35c11afd54
2
NEWS
2
NEWS
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -80,6 +80,9 @@ enum class ConfigOption {
|
|||
DESPOTIFY_USER,
|
||||
DESPOTIFY_PASSWORD,
|
||||
DESPOTIFY_HIGH_BITRATE,
|
||||
|
||||
MIXRAMP_ANALYZER,
|
||||
|
||||
MAX
|
||||
};
|
||||
|
||||
|
|
|
@ -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))
|
||||
{
|
||||
}
|
||||
|
|
|
@ -39,6 +39,8 @@ struct PlayerConfig {
|
|||
|
||||
ReplayGainConfig replay_gain;
|
||||
|
||||
bool mixramp_analyzer = false;
|
||||
|
||||
PlayerConfig() = default;
|
||||
|
||||
explicit PlayerConfig(const ConfigData &config);
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
|
@ -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'))
|
||||
|
|
|
@ -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 =
|
||||
|
|
Loading…
Reference in New Issue