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