Skip to content

Commit

Permalink
Merge pull request #4031 from ywwg/update-replaygain
Browse files Browse the repository at this point in the history
Adjust ReplayGain: Allow user to update the replaygain value based on a deck pregain value.
  • Loading branch information
uklotzde authored Jul 28, 2021
2 parents 2d399f2 + 0b624a0 commit 460f3a8
Show file tree
Hide file tree
Showing 11 changed files with 176 additions and 56 deletions.
42 changes: 41 additions & 1 deletion src/mixer/basetrackplayer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,11 @@ BaseTrackPlayerImpl::BaseTrackPlayerImpl(

m_pRateRatio = make_parented<ControlProxy>(getGroup(), "rate_ratio", this);
m_pPitchAdjust = make_parented<ControlProxy>(getGroup(), "pitch_adjust", this);

m_pUpdateReplayGainFromPregain = std::make_unique<ControlPushButton>(
ConfigKey(getGroup(), "update_replaygain_from_pregain"));
m_pUpdateReplayGainFromPregain->connectValueChangeRequest(this,
&BaseTrackPlayerImpl::slotUpdateReplayGainFromPregain);
}

BaseTrackPlayerImpl::~BaseTrackPlayerImpl() {
Expand Down Expand Up @@ -367,6 +372,10 @@ void BaseTrackPlayerImpl::connectLoadedTrack() {
&Track::replayGainUpdated,
this,
&BaseTrackPlayerImpl::slotSetReplayGain);
connect(m_pLoadedTrack.get(),
&Track::replayGainAdjusted,
this,
&BaseTrackPlayerImpl::slotAdjustReplayGain);

connect(m_pLoadedTrack.get(),
&Track::colorUpdated,
Expand Down Expand Up @@ -599,14 +608,30 @@ void BaseTrackPlayerImpl::slotCloneChannel(EngineChannel* pChannel) {

void BaseTrackPlayerImpl::slotSetReplayGain(mixxx::ReplayGain replayGain) {
// Do not change replay gain when track is playing because
// this may lead to an unexpected volume change
// this may lead to an unexpected volume change.
if (m_pPlay->get() == 0.0) {
setReplayGain(replayGain.getRatio());
} else {
m_replaygainPending = true;
}
}

void BaseTrackPlayerImpl::slotAdjustReplayGain(mixxx::ReplayGain replayGain) {
const double factor = m_pReplayGain->get() / replayGain.getRatio();
const double newPregain = m_pPreGain->get() * factor;

// There is a very slight chance that there will be a buffer call in between these sets.
// Therefore, we first adjust the control that is being lowered before the control
// that is being raised. Worst case, the volume goes down briefly before rectifying.
if (factor < 1.0) {
m_pPreGain->set(newPregain);
setReplayGain(replayGain.getRatio());
} else {
setReplayGain(replayGain.getRatio());
m_pPreGain->set(newPregain);
}
}

void BaseTrackPlayerImpl::slotSetTrackColor(const mixxx::RgbColor::optional_t& color) {
m_pTrackColor->forceSet(trackColorToDouble(color));
}
Expand Down Expand Up @@ -712,6 +737,21 @@ void BaseTrackPlayerImpl::slotShiftCuesMillisButton(double value, double millise
slotShiftCuesMillis(milliseconds);
}

void BaseTrackPlayerImpl::slotUpdateReplayGainFromPregain(double pressed) {
if (pressed <= 0) {
return;
}
if (!m_pLoadedTrack) {
return;
}
const double gain = m_pPreGain->get();
// Gain is at unity already, ignore and return.
if (gain == 1.0) {
return;
}
m_pLoadedTrack->adjustReplayGainFromPregain(gain);
}

void BaseTrackPlayerImpl::setReplayGain(double value) {
m_pReplayGain->set(value);
m_replaygainPending = false;
Expand Down
6 changes: 6 additions & 0 deletions src/mixer/basetrackplayer.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,9 @@ class BaseTrackPlayerImpl : public BaseTrackPlayer {
void slotTrackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack);
void slotLoadFailed(TrackPointer pTrack, const QString& reason);
void slotSetReplayGain(mixxx::ReplayGain replayGain);
// When the replaygain is adjusted, we modify the track pregain
// to compensate so there is no audible change in volume.
void slotAdjustReplayGain(mixxx::ReplayGain replayGain);
void slotSetTrackColor(const mixxx::RgbColor::optional_t& color);
void slotPlayToggled(double);

Expand All @@ -97,6 +100,7 @@ class BaseTrackPlayerImpl : public BaseTrackPlayer {
void slotWaveformZoomSetDefault(double pressed);
void slotShiftCuesMillis(double milliseconds);
void slotShiftCuesMillisButton(double value, double milliseconds);
void slotUpdateReplayGainFromPregain(double pressed);

private:
void setReplayGain(double value);
Expand Down Expand Up @@ -142,6 +146,8 @@ class BaseTrackPlayerImpl : public BaseTrackPlayer {
std::unique_ptr<ControlPushButton> m_pShiftCuesLaterSmall;
std::unique_ptr<ControlObject> m_pShiftCues;

std::unique_ptr<ControlObject> m_pUpdateReplayGainFromPregain;

parented_ptr<ControlProxy> m_pReplayGain;
parented_ptr<ControlProxy> m_pPlay;
parented_ptr<ControlProxy> m_pLowFilter;
Expand Down
34 changes: 32 additions & 2 deletions src/test/replaygaintest.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#include <gtest/gtest.h>

#include "track/replaygain.h"

#include <QtDebug>

#include "test/mockedenginebackendtest.h"
#include "track/replaygain.h"

namespace {

class ReplayGainTest : public testing::Test {
Expand Down Expand Up @@ -156,4 +157,33 @@ TEST_F(ReplayGainTest, NormalizePeak) {
normalizePeak(mixxx::ReplayGain::kPeakClip + mixxx::ReplayGain::kPeakClip);
}

class AdjustReplayGainTest : public MockedEngineBackendTest {};

TEST_F(AdjustReplayGainTest, AdjustReplayGainUpdatesPregain) {
// Initialize fake track replaygain so it's not zero.
mixxx::ReplayGain replayGain;
replayGain.setRatio(1.0);
m_pTrack1->setReplayGain(replayGain);
// Load the same track in decks 1 and 2 so we can see that the pregain is adjusted on both
// decks.
m_pMixerDeck2->slotLoadTrack(m_pTrack1, false);
// Because of this artificial process we have to manually set the replaygain CO for the second
// deck.
m_pMixerDeck2->slotSetReplayGain(replayGain);

ControlObject::getControl(ConfigKey(m_sGroup1, "pregain"))->set(1.2);
ControlObject::getControl(ConfigKey(m_sGroup1, "update_replaygain_from_pregain"))->set(1.0);

// The pregain value is folded into the replaygain value, and all pregains for all decks that
// have the same track loaded are adjusted so that the audible volume of the track does not
// change.
EXPECT_DOUBLE_EQ(1.0, ControlObject::getControl(ConfigKey(m_sGroup1, "pregain"))->get());
EXPECT_NEAR(0.83333333,
ControlObject::getControl(ConfigKey(m_sGroup2, "pregain"))->get(),
.005);
EXPECT_DOUBLE_EQ(1.2, ControlObject::getControl(ConfigKey(m_sGroup1, "replaygain"))->get());
EXPECT_DOUBLE_EQ(1.2, ControlObject::getControl(ConfigKey(m_sGroup2, "replaygain"))->get());
EXPECT_DOUBLE_EQ(1.2, m_pTrack1->getReplayGain().getRatio());
}

} // anonymous namespace
4 changes: 2 additions & 2 deletions src/track/replaygain.h
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ class ReplayGain final {
m_peak = normalizePeak(m_peak);
}

private:
private:
double m_ratio;
CSAMPLE m_peak;
};
Expand All @@ -119,7 +119,7 @@ QDebug operator<<(QDebug dbg, const ReplayGain& arg) {
return dbg << "ratio =" << arg.getRatio() << "/" << "peak =" << arg.getPeak();
}

}
} // namespace mixxx

Q_DECLARE_TYPEINFO(mixxx::ReplayGain, Q_MOVABLE_TYPE);
Q_DECLARE_METATYPE(mixxx::ReplayGain)
10 changes: 10 additions & 0 deletions src/track/track.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,16 @@ void Track::setReplayGain(const mixxx::ReplayGain& replayGain) {
}
}

void Track::adjustReplayGainFromPregain(double gain) {
QMutexLocker lock(&m_qMutex);
mixxx::ReplayGain replayGain = m_record.getMetadata().getTrackInfo().getReplayGain();
replayGain.setRatio(gain * replayGain.getRatio());
if (compareAndSet(m_record.refMetadata().refTrackInfo().ptrReplayGain(), replayGain)) {
markDirtyAndUnlock(&lock);
emit replayGainAdjusted(replayGain);
}
}

mixxx::Bpm Track::getBpmWhileLocked() const {
// BPM values must be synchronized at all times!
DEBUG_ASSERT(m_record.getMetadata().getTrackInfo().getBpm() == getBeatsPointerBpm(m_pBeats));
Expand Down
5 changes: 5 additions & 0 deletions src/track/track.h
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ class Track : public QObject {

// Set ReplayGain
void setReplayGain(const mixxx::ReplayGain&);
// Adjust ReplayGain by multiplying the given gain amount.
void adjustReplayGainFromPregain(double);
// Returns ReplayGain
mixxx::ReplayGain getReplayGain() const;

Expand Down Expand Up @@ -413,6 +415,9 @@ class Track : public QObject {
void coverArtUpdated();
void beatsUpdated();
void replayGainUpdated(mixxx::ReplayGain replayGain);
// This signal indicates that ReplayGain is being adjusted, and pregains should be
// adjusted in the opposite direction to compensate (no audible change).
void replayGainAdjusted(const mixxx::ReplayGain&);
void colorUpdated(const mixxx::RgbColor::optional_t& color);
void cuesUpdated();
void analyzed();
Expand Down
Loading

0 comments on commit 460f3a8

Please sign in to comment.