1 /**********************************************************************
2 
3   Audacity: A Digital Audio Editor
4 
5   Amplify.cpp
6 
7   Dominic Mazzoni
8   Vaughan Johnson (Preview)
9 
10 *******************************************************************//**
11 
12 \class EffectAmplify
13 \brief An Effect that makes a sound louder or softer.
14 
15   This rewritten class supports a smart Amplify effect - it calculates
16   the maximum amount of gain that can be applied to all tracks without
17   causing clipping and selects this as the default parameter.
18 
19 *//*******************************************************************/
20 
21 
22 #include "Amplify.h"
23 #include "LoadEffects.h"
24 
25 #include <math.h>
26 #include <float.h>
27 
28 #include <wx/button.h>
29 #include <wx/checkbox.h>
30 #include <wx/intl.h>
31 #include <wx/sizer.h>
32 #include <wx/slider.h>
33 #include <wx/stattext.h>
34 #include <wx/textctrl.h>
35 #include <wx/valtext.h>
36 #include <wx/log.h>
37 
38 #include "../Shuttle.h"
39 #include "../ShuttleGui.h"
40 #include "../WaveTrack.h"
41 #include "../widgets/valnum.h"
42 
43 
44 enum
45 {
46    ID_Amp = 10000,
47    ID_Peak,
48    ID_Clip
49 };
50 
51 // Define keys, defaults, minimums, and maximums for the effect parameters
52 //
53 //     Name       Type     Key                     Def         Min         Max            Scale
54 Param( Ratio,     float,   wxT("Ratio"),            0.9f,       0.003162f,  316.227766f,   1.0f  );
55 Param( Amp,       float,   wxT(""),                -0.91515f,  -50.0f,     50.0f,         10.0f );
56 Param( Clipping,  bool,    wxT("AllowClipping"),    false,    false,  true,    1  );
57 
58 //
59 // EffectAmplify
60 //
61 
62 const ComponentInterfaceSymbol EffectAmplify::Symbol
63 { XO("Amplify") };
64 
65 namespace{ BuiltinEffectsModule::Registration< EffectAmplify > reg; }
66 
BEGIN_EVENT_TABLE(EffectAmplify,wxEvtHandler)67 BEGIN_EVENT_TABLE(EffectAmplify, wxEvtHandler)
68    EVT_SLIDER(ID_Amp, EffectAmplify::OnAmpSlider)
69    EVT_TEXT(ID_Amp, EffectAmplify::OnAmpText)
70    EVT_TEXT(ID_Peak, EffectAmplify::OnPeakText)
71    EVT_CHECKBOX(ID_Clip, EffectAmplify::OnClipCheckBox)
72 END_EVENT_TABLE()
73 
74 EffectAmplify::EffectAmplify()
75 {
76    mAmp = DEF_Amp;
77    mRatio = DB_TO_LINEAR(mAmp);
78    mRatioClip = 0.0;
79    mCanClip = false;
80    mPeak = 0.0;
81 
82    SetLinearEffectFlag(true);
83 }
84 
~EffectAmplify()85 EffectAmplify::~EffectAmplify()
86 {
87 }
88 
89 // ComponentInterface implementation
90 
GetSymbol()91 ComponentInterfaceSymbol EffectAmplify::GetSymbol()
92 {
93    return Symbol;
94 }
95 
GetDescription()96 TranslatableString EffectAmplify::GetDescription()
97 {
98    // Note: This is useful only after ratio has been set.
99    return XO("Increases or decreases the volume of the audio you have selected");
100 }
101 
ManualPage()102 ManualPageID EffectAmplify::ManualPage()
103 {
104    return L"Amplify";
105 }
106 
107 // EffectDefinitionInterface implementation
108 
GetType()109 EffectType EffectAmplify::GetType()
110 {
111    return EffectTypeProcess;
112 }
113 
114 // EffectClientInterface implementation
115 
GetAudioInCount()116 unsigned EffectAmplify::GetAudioInCount()
117 {
118    return 1;
119 }
120 
GetAudioOutCount()121 unsigned EffectAmplify::GetAudioOutCount()
122 {
123    return 1;
124 }
125 
ProcessBlock(float ** inBlock,float ** outBlock,size_t blockLen)126 size_t EffectAmplify::ProcessBlock(float **inBlock, float **outBlock, size_t blockLen)
127 {
128    for (decltype(blockLen) i = 0; i < blockLen; i++)
129    {
130       outBlock[0][i] = inBlock[0][i] * mRatio;
131    }
132 
133    return blockLen;
134 }
DefineParams(ShuttleParams & S)135 bool EffectAmplify::DefineParams( ShuttleParams & S ){
136    S.SHUTTLE_PARAM( mRatio, Ratio );
137    if (!IsBatchProcessing())
138       S.SHUTTLE_PARAM( mCanClip, Clipping );
139    return true;
140 }
141 
GetAutomationParameters(CommandParameters & parms)142 bool EffectAmplify::GetAutomationParameters(CommandParameters & parms)
143 {
144    parms.WriteFloat(KEY_Ratio, mRatio);
145    if (!IsBatchProcessing())
146       parms.WriteFloat(KEY_Clipping, mCanClip);
147 
148    return true;
149 }
150 
SetAutomationParameters(CommandParameters & parms)151 bool EffectAmplify::SetAutomationParameters(CommandParameters & parms)
152 {
153    ReadAndVerifyFloat(Ratio);
154    mRatio = Ratio;
155 
156    if (!IsBatchProcessing()){
157       ReadAndVerifyBool(Clipping);
158       mCanClip = Clipping;
159    } else {
160       mCanClip = true;
161    }
162 
163    return true;
164 }
165 
LoadFactoryDefaults()166 bool EffectAmplify::LoadFactoryDefaults()
167 {
168    Init();
169 
170    mRatioClip = 0.0;
171    if (mPeak > 0.0)
172    {
173       mRatio = 1.0 / mPeak;
174       mRatioClip = mRatio;
175    }
176    else
177    {
178       mRatio = 1.0;
179    }
180    mCanClip = false;
181 
182    return TransferDataToWindow();
183 }
184 
185 // Effect implementation
186 
Init()187 bool EffectAmplify::Init()
188 {
189    mPeak = 0.0;
190 
191    for (auto t : inputTracks()->Selected< const WaveTrack >())
192    {
193       auto pair = t->GetMinMax(mT0, mT1); // may throw
194       const float min = pair.first, max = pair.second;
195       float newpeak = (fabs(min) > fabs(max) ? fabs(min) : fabs(max));
196 
197       if (newpeak > mPeak)
198       {
199          mPeak = newpeak;
200       }
201    }
202 
203    return true;
204 }
205 
Preview(bool dryOnly)206 void EffectAmplify::Preview(bool dryOnly)
207 {
208    auto cleanup1 = valueRestorer( mRatio );
209    auto cleanup2 = valueRestorer( mPeak );
210 
211    Effect::Preview(dryOnly);
212 }
213 
PopulateOrExchange(ShuttleGui & S)214 void EffectAmplify::PopulateOrExchange(ShuttleGui & S)
215 {
216    enum{ precision = 3 }; // allow (a generous) 3 decimal  places for Amplification (dB)
217 
218    bool batch = IsBatchProcessing();
219    if ( batch )
220    {
221       mCanClip = true;
222       mPeak = 1.0;
223    }
224    else
225    {
226       if (mPeak > 0.0)
227       {
228          mRatio = 1.0 / mPeak;
229          mRatioClip = mRatio;
230       }
231       else
232       {
233          mRatio = 1.0;
234       }
235    }
236 
237    S.AddSpace(0, 5);
238 
239    S.StartVerticalLay(0);
240    {
241       // Amplitude
242       S.StartMultiColumn(2, wxCENTER);
243       {
244          mAmpT = S.Id(ID_Amp)
245             .Validator<FloatingPointValidator<double>>(
246                precision, &mAmp, NumValidatorStyle::ONE_TRAILING_ZERO, MIN_Amp, MAX_Amp
247             )
248             .AddTextBox(XXO("&Amplification (dB):"), wxT(""), 12);
249       }
250       S.EndMultiColumn();
251 
252       // Amplitude
253       S.StartHorizontalLay(wxEXPAND);
254       {
255          mAmpS = S.Id(ID_Amp)
256             .Style(wxSL_HORIZONTAL)
257             .Name(XO("Amplification dB"))
258             .AddSlider( {}, 0, MAX_Amp * SCL_Amp, MIN_Amp * SCL_Amp);
259       }
260       S.EndHorizontalLay();
261 
262       // Peak
263       S.StartMultiColumn(2, wxCENTER);
264       {
265          mNewPeakT = S.Id(ID_Peak)
266             .Validator<FloatingPointValidator<double>>(
267                // One extra decimal place so that rounding is visible to user
268                // (see: bug 958)
269                precision + 1,
270                &mNewPeak, NumValidatorStyle::ONE_TRAILING_ZERO,
271                // min and max need same precision as what we're validating (bug 963)
272                RoundValue( precision + 1, MIN_Amp + LINEAR_TO_DB(mPeak) ),
273                RoundValue( precision + 1, MAX_Amp + LINEAR_TO_DB(mPeak) )
274             )
275             .AddTextBox(XXO("&New Peak Amplitude (dB):"), wxT(""), 12);
276       }
277       S.EndMultiColumn();
278 
279       // Clipping
280       S.StartHorizontalLay(wxCENTER);
281       {
282 
283          mClip = S.Id(ID_Clip).Disable( batch )
284             .AddCheckBox(XXO("Allo&w clipping"), false);
285       }
286       S.EndHorizontalLay();
287    }
288    S.EndVerticalLay();
289 
290    return;
291 }
292 
TransferDataToWindow()293 bool EffectAmplify::TransferDataToWindow()
294 {
295    // limit range of gain
296    double dBInit = LINEAR_TO_DB(mRatio);
297    double dB = TrapDouble(dBInit, MIN_Amp, MAX_Amp);
298    if (dB != dBInit)
299       mRatio = DB_TO_LINEAR(dB);
300 
301    mAmp = LINEAR_TO_DB(mRatio);
302    mAmpT->GetValidator()->TransferToWindow();
303 
304    mAmpS->SetValue((int) (mAmp * SCL_Amp + 0.5f));
305 
306    mNewPeak = LINEAR_TO_DB(mRatio * mPeak);
307    mNewPeakT->GetValidator()->TransferToWindow();
308 
309    mClip->SetValue(mCanClip);
310 
311    CheckClip();
312 
313    return true;
314 }
315 
TransferDataFromWindow()316 bool EffectAmplify::TransferDataFromWindow()
317 {
318    if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
319    {
320       return false;
321    }
322 
323    mRatio = DB_TO_LINEAR(TrapDouble(mAmp * SCL_Amp, MIN_Amp * SCL_Amp, MAX_Amp * SCL_Amp) / SCL_Amp);
324 
325    mCanClip = mClip->GetValue();
326 
327    if (!mCanClip && mRatio * mPeak > 1.0)
328    {
329       mRatio = 1.0 / mPeak;
330    }
331 
332    return true;
333 }
334 
335 // EffectAmplify implementation
336 
CheckClip()337 void EffectAmplify::CheckClip()
338 {
339    EnableApply(mClip->GetValue() || (mPeak > 0.0 && mRatio <= mRatioClip));
340 }
341 
OnAmpText(wxCommandEvent & WXUNUSED (evt))342 void EffectAmplify::OnAmpText(wxCommandEvent & WXUNUSED(evt))
343 {
344    if (!mAmpT->GetValidator()->TransferFromWindow())
345    {
346       EnableApply(false);
347       return;
348    }
349 
350    mRatio = DB_TO_LINEAR(TrapDouble(mAmp * SCL_Amp, MIN_Amp * SCL_Amp, MAX_Amp * SCL_Amp) / SCL_Amp);
351 
352    mAmpS->SetValue((int) (LINEAR_TO_DB(mRatio) * SCL_Amp + 0.5));
353 
354    mNewPeak = LINEAR_TO_DB(mRatio * mPeak);
355    mNewPeakT->GetValidator()->TransferToWindow();
356 
357    CheckClip();
358 }
359 
OnPeakText(wxCommandEvent & WXUNUSED (evt))360 void EffectAmplify::OnPeakText(wxCommandEvent & WXUNUSED(evt))
361 {
362    if (!mNewPeakT->GetValidator()->TransferFromWindow())
363    {
364       EnableApply(false);
365       return;
366    }
367 
368    if (mNewPeak == 0.0)
369       mRatio = mRatioClip;
370    else
371       mRatio = DB_TO_LINEAR(mNewPeak) / mPeak;
372 
373    double ampInit = LINEAR_TO_DB(mRatio);
374    mAmp = TrapDouble(ampInit, MIN_Amp, MAX_Amp);
375    if (mAmp != ampInit)
376       mRatio = DB_TO_LINEAR(mAmp);
377 
378    mAmpT->GetValidator()->TransferToWindow();
379 
380    mAmpS->SetValue((int) (mAmp * SCL_Amp + 0.5f));
381 
382    CheckClip();
383 }
384 
OnAmpSlider(wxCommandEvent & evt)385 void EffectAmplify::OnAmpSlider(wxCommandEvent & evt)
386 {
387    double dB = evt.GetInt() / SCL_Amp;
388    mRatio = DB_TO_LINEAR(TrapDouble(dB, MIN_Amp, MAX_Amp));
389 
390    double dB2 = (evt.GetInt() - 1) / SCL_Amp;
391    double ratio2 = DB_TO_LINEAR(TrapDouble(dB2, MIN_Amp, MAX_Amp));
392 
393    if (!mClip->GetValue() && mRatio * mPeak > 1.0 && ratio2 * mPeak < 1.0)
394    {
395       mRatio = 1.0 / mPeak;
396    }
397 
398    mAmp = LINEAR_TO_DB(mRatio);
399    mAmpT->GetValidator()->TransferToWindow();
400 
401    mNewPeak = LINEAR_TO_DB(mRatio * mPeak);
402    mNewPeakT->GetValidator()->TransferToWindow();
403 
404    CheckClip();
405 }
406 
OnClipCheckBox(wxCommandEvent & WXUNUSED (evt))407 void EffectAmplify::OnClipCheckBox(wxCommandEvent & WXUNUSED(evt))
408 {
409    CheckClip();
410 }
411