1 #include "engine/enginepregain.h"
2
3 #include <QtDebug>
4
5 #include "control/controlaudiotaperpot.h"
6 #include "control/controlobject.h"
7 #include "control/controlpotmeter.h"
8 #include "control/controlpushbutton.h"
9 #include "preferences/usersettings.h"
10 #include "util/math.h"
11 #include "util/sample.h"
12
13 namespace {
14
15 // Bends the speed to gain curve for a natural vinyl sound
16 constexpr float kSpeedGainMultiplier = 4.0f;
17 // -1 dB to not risk any clipping even for lossy track that may have samples above 1.0
18 constexpr float kMaxTotalGainBySpeed = 0.9f;
19 // value to normalize gain to 1 at speed one
20 const float kSpeedOneDiv = std::log10((1 * kSpeedGainMultiplier) + 1);
21 } // anonymous namespace
22
23 ControlPotmeter* EnginePregain::s_pReplayGainBoost = nullptr;
24 ControlPotmeter* EnginePregain::s_pDefaultBoost = nullptr;
25 ControlObject* EnginePregain::s_pEnableReplayGain = nullptr;
26
EnginePregain(const QString & group)27 EnginePregain::EnginePregain(const QString& group)
28 : m_dSpeed(1.0),
29 m_dOldSpeed(1.0),
30 m_dNonScratchSpeed(1.0),
31 m_scratching(false),
32 m_fPrevGain(1.0),
33 m_bSmoothFade(false) {
34 m_pPotmeterPregain = new ControlAudioTaperPot(ConfigKey(group, "pregain"), -12, 12, 0.5);
35 //Replay Gain things
36 m_pCOReplayGain = new ControlObject(ConfigKey(group, "replaygain"));
37 m_pTotalGain = new ControlObject(ConfigKey(group, "total_gain"));
38 m_pPassthroughEnabled = ControlObject::getControl(ConfigKey(group, "passthrough"));
39
40 if (s_pReplayGainBoost == nullptr) {
41 s_pReplayGainBoost = new ControlAudioTaperPot(ConfigKey("[ReplayGain]", "ReplayGainBoost"), -12, 12, 0.5);
42 s_pDefaultBoost = new ControlAudioTaperPot(ConfigKey("[ReplayGain]", "DefaultBoost"), -12, 12, 0.5);
43 s_pEnableReplayGain = new ControlObject(ConfigKey("[ReplayGain]", "ReplayGainEnabled"));
44 }
45 }
46
~EnginePregain()47 EnginePregain::~EnginePregain() {
48 delete m_pPotmeterPregain;
49 delete m_pCOReplayGain;
50 delete m_pTotalGain;
51
52 delete s_pEnableReplayGain;
53 s_pEnableReplayGain = nullptr;
54 delete s_pReplayGainBoost;
55 s_pReplayGainBoost = nullptr;
56 delete s_pDefaultBoost;
57 s_pDefaultBoost = nullptr;
58 }
59
setSpeedAndScratching(double speed,bool scratching)60 void EnginePregain::setSpeedAndScratching(double speed, bool scratching) {
61 m_dOldSpeed = m_dSpeed;
62 m_dSpeed = speed;
63 if (!scratching) {
64 m_dNonScratchSpeed = speed;
65 }
66 m_scratching = scratching;
67 }
68
process(CSAMPLE * pInOut,const int iBufferSize)69 void EnginePregain::process(CSAMPLE* pInOut, const int iBufferSize) {
70 const auto fReplayGain = static_cast<CSAMPLE_GAIN>(m_pCOReplayGain->get());
71 CSAMPLE_GAIN fReplayGainCorrection;
72 if (!s_pEnableReplayGain->toBool() || m_pPassthroughEnabled->toBool()) {
73 // Override replaygain value if passing through
74 // TODO(XXX): consider a good default.
75 // Do we expect an replaygain leveled input or
76 // Normalized to 1 input?
77 fReplayGainCorrection = 1; // We expect a replaygain leveled input
78 } else if (fReplayGain == 0) {
79 // use predicted replaygain
80 fReplayGainCorrection = (float)s_pDefaultBoost->get();
81 // We prepare for smoothfading to ReplayGain suggested gain
82 // if ReplayGain value changes or ReplayGain is enabled
83 m_bSmoothFade = true;
84 m_timer.restart();
85 } else {
86 // Here is the point, when ReplayGain Analyzer takes its action,
87 // suggested gain changes from 0 to a nonzero value
88 // We want to smoothly fade to this last.
89 // Anyway we have some the problem that code cannot block the
90 // full process for one second.
91 // So we need to alter gain each time ::process is called.
92
93 const float fullReplayGainBoost =
94 fReplayGain * static_cast<float>(s_pReplayGainBoost->get());
95
96 // This means that a ReplayGain value has been calculated after the
97 // track has been loaded
98 const float kFadeSeconds = 1.0;
99
100 if (m_bSmoothFade) {
101 float seconds = static_cast<float>(m_timer.elapsed().toDoubleSeconds());
102 if (seconds < kFadeSeconds) {
103 // Fade smoothly
104 const float fadeFrac = seconds / kFadeSeconds;
105 fReplayGainCorrection = m_fPrevGain * (1.0f - fadeFrac) +
106 fadeFrac * fullReplayGainBoost;
107 } else {
108 m_bSmoothFade = false;
109 fReplayGainCorrection = fullReplayGainBoost;
110 }
111 } else {
112 // Passing a user defined boost
113 fReplayGainCorrection = fullReplayGainBoost;
114 }
115 }
116
117 // Clamp gain to within [0, 10.0] to prevent insane gains. This can happen
118 // (some corrupt files get really high replay gain values).
119 // 10 allows a maximum replay Gain Boost * calculated replay gain of ~2
120 CSAMPLE_GAIN totalGain = (CSAMPLE_GAIN)m_pPotmeterPregain->get() *
121 math_clamp(fReplayGainCorrection, 0.0f, 10.0f);
122
123 m_pTotalGain->set(totalGain);
124
125 // Vinylsoundemu:
126 // Apply Gain change depending on the speed.
127 // We have measured -Inf dB at x0, -6 dB at x0.3, 0 dB at x1 and 3.5 dB
128 // at 2.5 using a real vinyl.
129 // x5 is the maximum physically speed before the needle is starting to
130 // lose contact to the vinyl.
131 // So we apply a curve here that emulates the gain change up to x 2.5 natural
132 // to 3.5 dB and then limits the gain towards 5.5 dB at x5.
133 // Since the additional gain will lead to undesired clipping,
134 // we do not add more gain then we found in the original track.
135 // This compensates a negative ReplayGain or PreGain setting.
136
137 CSAMPLE_GAIN speedGain = std::log10((fabs(static_cast<CSAMPLE_GAIN>(m_dSpeed)) *
138 kSpeedGainMultiplier) +
139 1) /
140 kSpeedOneDiv;
141 // Limit speed Gain to 0 dB if totalGain is already > 0.9 or Limit the
142 // resulting totalGain to 0.9 for all other cases. This should avoid clipping even
143 // if the source track has some samples above 1.0 due to lossy codecs.
144 if (totalGain > kMaxTotalGainBySpeed) {
145 speedGain = math_min(1.0f, speedGain);
146 } else {
147 speedGain = math_min(kMaxTotalGainBySpeed / totalGain, speedGain);
148 }
149 totalGain *= static_cast<CSAMPLE_GAIN>(speedGain);
150
151 if ((m_dSpeed * m_dOldSpeed < 0) && m_scratching) {
152 // direction changed, go though zero if scratching
153 SampleUtil::applyRampingGain(&pInOut[0], m_fPrevGain, 0, iBufferSize / 2);
154 SampleUtil::applyRampingGain(&pInOut[iBufferSize / 2], 0, totalGain, iBufferSize / 2);
155 } else if (totalGain != m_fPrevGain) {
156 // Prevent sound wave discontinuities by interpolating from old to new gain.
157 SampleUtil::applyRampingGain(pInOut, m_fPrevGain, totalGain, iBufferSize);
158 } else {
159 // SampleUtil deals with aliased buffers and gains of 1 or 0.
160 SampleUtil::applyGain(pInOut, totalGain, iBufferSize);
161 }
162 m_fPrevGain = totalGain;
163 }
164
collectFeatures(GroupFeatureState * pGroupFeatures) const165 void EnginePregain::collectFeatures(GroupFeatureState* pGroupFeatures) const {
166 pGroupFeatures->gain = m_pPotmeterPregain->get();
167 pGroupFeatures->has_gain = true;
168 }
169