Audacity 3.2.0
Normalize.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 Normalize.cpp
6
7 Dominic Mazzoni
8 Vaughan Johnson (Preview)
9
10*******************************************************************//*******************************************************************/
16#include "Normalize.h"
17#include "EffectEditor.h"
18#include "LoadEffects.h"
19
20#include <math.h>
21
22#include <wx/checkbox.h>
23#include <wx/stattext.h>
24#include <wx/valgen.h>
25
26#include "Prefs.h"
27#include "../ProjectFileManager.h"
28#include "ShuttleGui.h"
29#include "WaveTrack.h"
30#include "../widgets/valnum.h"
31#include "ProgressDialog.h"
32
34{
37 > parameters;
38 return parameters;
39}
40
42{ XO("Normalize") };
43
45
46BEGIN_EVENT_TABLE(EffectNormalize, wxEvtHandler)
47 EVT_CHECKBOX(wxID_ANY, EffectNormalize::OnUpdateUI)
48 EVT_TEXT(wxID_ANY, EffectNormalize::OnUpdateUI)
50
52{
53 Parameters().Reset(*this);
54 SetLinearEffectFlag(false);
55}
56
58{
59}
60
61// ComponentInterface implementation
62
64{
65 return Symbol;
66}
67
69{
70 return XO("Sets the peak amplitude of one or more tracks");
71}
72
74{
75 return L"Normalize";
76}
77
78// EffectDefinitionInterface implementation
79
81{
82 return EffectTypeProcess;
83}
84
85// Effect implementation
86
88{
89 return ((mGain == false) && (mDC == false));
90}
91
93{
94 if (mGain == false && mDC == false)
95 return true;
96
97 float ratio;
98 if( mGain )
99 {
100 // same value used for all tracks
101 ratio = DB_TO_LINEAR(std::clamp<double>(mPeakLevel, PeakLevel.min, PeakLevel.max));
102 }
103 else {
104 ratio = 1.0;
105 }
106
107 //Iterate over each track
108 this->CopyInputTracks(); // Set up mOutputTracks.
109 bool bGoodResult = true;
110 double progress = 0;
111 TranslatableString topMsg;
112 if(mDC && mGain)
113 topMsg = XO("Removing DC offset and Normalizing...\n");
114 else if(mDC && !mGain)
115 topMsg = XO("Removing DC offset...\n");
116 else if(!mDC && mGain)
117 topMsg = XO("Normalizing without removing DC offset...\n");
118 else if(!mDC && !mGain)
119 topMsg = XO("Not doing anything...\n"); // shouldn't get here
120
121 for ( auto track : mOutputTracks->Selected< WaveTrack >()
123 //Get start and end times from track
124 // PRL: No accounting for multiple channels?
125 double trackStart = track->GetStartTime();
126 double trackEnd = track->GetEndTime();
127
128 //Set the current bounds to whichever left marker is
129 //greater and whichever right marker is less:
130 mCurT0 = mT0 < trackStart? trackStart: mT0;
131 mCurT1 = mT1 > trackEnd? trackEnd: mT1;
132
133 auto range = mStereoInd
135 : TrackList::Channels(track);
136
137 // Process only if the right marker is to the right of the left marker
138 if (mCurT1 > mCurT0) {
139 wxString trackName = track->GetName();
140
141 float extent;
142 // Will compute a maximum
143 extent = std::numeric_limits<float>::lowest();
144 std::vector<float> offsets;
145
146 auto msg = (range.size() == 1)
147 // mono or 'stereo tracks independently'
148 ? topMsg +
149 XO("Analyzing: %s").Format( trackName )
150 : topMsg +
151 // TODO: more-than-two-channels-message
152 XO("Analyzing first track of stereo pair: %s").Format( trackName );
153
154 // Analysis loop over channels collects offsets and extent
155 for (auto channel : range) {
156 float offset = 0;
157 float extent2 = 0;
158 bGoodResult =
159 AnalyseTrack( channel, msg, progress, offset, extent2 );
160 if ( ! bGoodResult )
161 goto break2;
162 extent = std::max( extent, extent2 );
163 offsets.push_back(offset);
164 // TODO: more-than-two-channels-message
165 msg = topMsg +
166 XO("Analyzing second track of stereo pair: %s").Format( trackName );
167 }
168
169 // Compute the multiplier using extent
170 if( (extent > 0) && mGain ) {
171 mMult = ratio / extent;
172 }
173 else
174 mMult = 1.0;
175
176 if (range.size() == 1) {
177 if (TrackList::NChannels(*track) == 1)
178 // really mono
179 msg = topMsg +
180 XO("Processing: %s").Format( trackName );
181 else
182 //'stereo tracks independently'
183 // TODO: more-than-two-channels-message
184 msg = topMsg +
185 XO("Processing stereo channels independently: %s").Format( trackName );
186 }
187 else
188 msg = topMsg +
189 // TODO: more-than-two-channels-message
190 XO("Processing first track of stereo pair: %s").Format( trackName );
191
192 // Use multiplier in the second, processing loop over channels
193 auto pOffset = offsets.begin();
194 for (auto channel : range) {
195 if (false ==
196 (bGoodResult = ProcessOne(channel, msg, progress, *pOffset++)) )
197 goto break2;
198 // TODO: more-than-two-channels-message
199 msg = topMsg +
200 XO("Processing second track of stereo pair: %s").Format( trackName );
201 }
202 }
203 }
204
205 break2:
206
207 this->ReplaceProcessedTracks(bGoodResult);
208 return bGoodResult;
209}
210
211std::unique_ptr<EffectEditor> EffectNormalize::PopulateOrExchange(
213 const EffectOutputs *)
214{
215 mUIParent = S.GetParent();
216 mCreating = true;
217
218 S.StartVerticalLay(0);
219 {
220 S.StartMultiColumn(2, wxALIGN_CENTER);
221 {
222 S.StartVerticalLay(false);
223 {
224 mDCCheckBox = S.Validator<wxGenericValidator>(&mDC)
225 .AddCheckBox(XXO("&Remove DC offset (center on 0.0 vertically)"),
226 mDC);
227
228 S.StartHorizontalLay(wxALIGN_LEFT, false);
229 {
231 .MinSize()
232 .Validator<wxGenericValidator>(&mGain)
233 .AddCheckBox(XXO("&Normalize peak amplitude to "),
234 mGain);
235
237 .Name(XO("Peak amplitude dB"))
238 .Validator<FloatingPointValidator<double>>(
239 2,
240 &mPeakLevel,
241 NumValidatorStyle::ONE_TRAILING_ZERO,
243 PeakLevel.max )
244 .AddTextBox( {}, L"", 10);
245 mLeveldB = S.AddVariableText(XO("dB"), false,
246 wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
247 mWarning = S.AddVariableText( {}, false,
248 wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
249 }
250 S.EndHorizontalLay();
251
253 .Validator<wxGenericValidator>(&mStereoInd)
254 .AddCheckBox(XXO("N&ormalize stereo channels independently"),
255 mStereoInd);
256 }
257 S.EndVerticalLay();
258 }
259 S.EndMultiColumn();
260 }
261 S.EndVerticalLay();
262 mCreating = false;
263 return nullptr;
264}
265
267{
268 if (!mUIParent->TransferDataToWindow())
269 {
270 return false;
271 }
272
273 UpdateUI();
274
275 return true;
276}
277
279{
280 if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
281 {
282 return false;
283 }
284
285 return true;
286}
287
288// EffectNormalize implementation
289
291 double &progress, float &offset, float &extent)
292{
293 bool result = true;
294 float min, max;
295
296 if(mGain)
297 {
298 // set mMin, mMax. No progress bar here as it's fast.
299 auto pair = track->GetMinMax(mCurT0, mCurT1); // may throw
300 min = pair.first, max = pair.second;
301
302 if(mDC)
303 {
304 result = AnalyseTrackData(track, msg, progress, offset);
305 min += offset;
306 max += offset;
307 }
308 }
309 else if(mDC)
310 {
311 min = -1.0, max = 1.0; // sensible defaults?
312 result = AnalyseTrackData(track, msg, progress, offset);
313 min += offset;
314 max += offset;
315 }
316 else
317 {
318 wxFAIL_MSG("Analysing Track when nothing to do!");
319 min = -1.0, max = 1.0; // sensible defaults?
320 offset = 0.0;
321 }
322 extent = fmax(fabs(min), fabs(max));
323
324 return result;
325}
326
327//AnalyseTrackData() takes a track, transforms it to bunch of buffer-blocks,
328//and executes selected AnalyseOperation on it...
330 double &progress, float &offset)
331{
332 bool rc = true;
333
334 //Transform the marker timepoints to samples
335 auto start = track->TimeToLongSamples(mCurT0);
336 auto end = track->TimeToLongSamples(mCurT1);
337
338 //Get the length of the buffer (as double). len is
339 //used simply to calculate a progress meter, so it is easier
340 //to make it a double now than it is to do it later
341 auto len = (end - start).as_double();
342
343 //Initiate a processing buffer. This buffer will (most likely)
344 //be shorter than the length of the track being processed.
345 Floats buffer{ track->GetMaxBlockSize() };
346
347 mSum = 0.0; // dc offset inits
348
349 sampleCount blockSamples;
350 sampleCount totalSamples = 0;
351
352 //Go through the track one buffer at a time. s counts which
353 //sample the current buffer starts at.
354 auto s = start;
355 while (s < end) {
356 //Get a block of samples (smaller than the size of the buffer)
357 //Adjust the block size if it is the final block in the track
358 const auto block = limitSampleBufferSize(
359 track->GetBestBlockSize(s),
360 end - s
361 );
362
363 //Get the samples from the track and put them in the buffer
364 track->GetFloats(buffer.get(), s, block, fillZero, true, &blockSamples);
365 totalSamples += blockSamples;
366
367 //Process the buffer.
368 AnalyseDataDC(buffer.get(), block);
369
370 //Increment s one blockfull of samples
371 s += block;
372
373 //Update the Progress meter
374 if (TotalProgress(progress +
375 ((s - start).as_double() / len)/double(2*GetNumWaveTracks()), msg)) {
376 rc = false; //lda .. break, not return, so that buffer is deleted
377 break;
378 }
379 }
380 if( totalSamples > 0 )
381 offset = -mSum / totalSamples.as_double(); // calculate actual offset (amount that needs to be added on)
382 else
383 offset = 0.0;
384
385 progress += 1.0/double(2*GetNumWaveTracks());
386 //Return true because the effect processing succeeded ... unless cancelled
387 return rc;
388}
389
390//ProcessOne() takes a track, transforms it to bunch of buffer-blocks,
391//and executes ProcessData, on it...
392// uses mMult and offset to normalize a track.
393// mMult must be set before this is called
395 WaveTrack * track, const TranslatableString &msg, double &progress, float offset)
396{
397 bool rc = true;
398
399 //Transform the marker timepoints to samples
400 auto start = track->TimeToLongSamples(mCurT0);
401 auto end = track->TimeToLongSamples(mCurT1);
402
403 //Get the length of the buffer (as double). len is
404 //used simply to calculate a progress meter, so it is easier
405 //to make it a double now than it is to do it later
406 auto len = (end - start).as_double();
407
408 //Initiate a processing buffer. This buffer will (most likely)
409 //be shorter than the length of the track being processed.
410 Floats buffer{ track->GetMaxBlockSize() };
411
412 //Go through the track one buffer at a time. s counts which
413 //sample the current buffer starts at.
414 auto s = start;
415 while (s < end) {
416 //Get a block of samples (smaller than the size of the buffer)
417 //Adjust the block size if it is the final block in the track
418 const auto block = limitSampleBufferSize(
419 track->GetBestBlockSize(s),
420 end - s
421 );
422
423 //Get the samples from the track and put them in the buffer
424 track->GetFloats(buffer.get(), s, block);
425
426 //Process the buffer.
427 ProcessData(buffer.get(), block, offset);
428
429 //Copy the newly-changed samples back onto the track.
430 track->Set((samplePtr) buffer.get(), floatSample, s, block);
431
432 //Increment s one blockfull of samples
433 s += block;
434
435 //Update the Progress meter
436 if (TotalProgress(progress +
437 ((s - start).as_double() / len)/double(2*GetNumWaveTracks()), msg)) {
438 rc = false; //lda .. break, not return, so that buffer is deleted
439 break;
440 }
441 }
442 progress += 1.0/double(2*GetNumWaveTracks());
443
444 //Return true because the effect processing succeeded ... unless cancelled
445 return rc;
446}
447
449void EffectNormalize::AnalyseDataDC(float *buffer, size_t len)
450{
451 for(decltype(len) i = 0; i < len; i++)
452 mSum += (double)buffer[i];
453}
454
455void EffectNormalize::ProcessData(float *buffer, size_t len, float offset)
456{
457 for(decltype(len) i = 0; i < len; i++) {
458 float adjFrame = (buffer[i] + offset) * mMult;
459 buffer[i] = adjFrame;
460 }
461}
462
463void EffectNormalize::OnUpdateUI(wxCommandEvent & WXUNUSED(evt))
464{
465 UpdateUI();
466}
467
469{
470
471 if (!mUIParent->TransferDataFromWindow())
472 {
473 mWarning->SetLabel(_("(Maximum 0dB)"));
475 return;
476 }
477 mWarning->SetLabel(wxT(""));
478
479 // Disallow level stuff if not normalizing
480 mLevelTextCtrl->Enable(mGain);
481 mLeveldB->Enable(mGain);
482 mStereoIndCheckBox->Enable(mGain);
483
484 // Disallow OK/Preview if doing nothing
486}
wxT("CloseDown"))
END_EVENT_TABLE()
int min(int a, int b)
EffectType
@ EffectTypeProcess
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
#define _(s)
Definition: Internat.h:73
#define DB_TO_LINEAR(x)
Definition: MemoryX.h:561
size_t limitSampleBufferSize(size_t bufferSize, sampleCount limit)
Definition: SampleCount.cpp:22
char * samplePtr
Definition: SampleFormat.h:55
@ fillZero
Definition: SampleFormat.h:60
#define S(N)
Definition: ToChars.cpp:64
Generates EffectParameterMethods overrides from variadic template arguments.
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
double mT1
Definition: EffectBase.h:119
std::shared_ptr< TrackList > mOutputTracks
Definition: EffectBase.h:97
double mT0
Definition: EffectBase.h:118
void ReplaceProcessedTracks(const bool bGoodResult)
Definition: EffectBase.cpp:224
static bool EnableApply(wxWindow *parent, bool enable=true)
Enable or disable the Apply button of the dialog that contains parent.
void CopyInputTracks(bool allSyncLockSelected=false)
Definition: Effect.cpp:400
bool TotalProgress(double frac, const TranslatableString &={}) const
Definition: Effect.cpp:342
int GetNumWaveTracks() const
Definition: Effect.h:141
Performs effect computation.
An Effect to bring the peak level up to a chosen level.
Definition: Normalize.h:26
static constexpr EffectParameter RemoveDC
Definition: Normalize.h:96
bool TransferDataFromWindow(EffectSettings &settings) override
Definition: Normalize.cpp:278
wxStaticText * mWarning
Definition: Normalize.h:87
double mCurT0
Definition: Normalize.h:78
EffectType GetType() const override
Type determines how it behaves.
Definition: Normalize.cpp:80
wxStaticText * mLeveldB
Definition: Normalize.h:86
static constexpr EffectParameter StereoInd
Definition: Normalize.h:100
bool Process(EffectInstance &instance, EffectSettings &settings) override
Definition: Normalize.cpp:92
std::unique_ptr< EffectEditor > PopulateOrExchange(ShuttleGui &S, EffectInstance &instance, EffectSettingsAccess &access, const EffectOutputs *pOutputs) override
Add controls to effect panel; always succeeds.
Definition: Normalize.cpp:211
bool TransferDataToWindow(const EffectSettings &settings) override
Definition: Normalize.cpp:266
const EffectParameterMethods & Parameters() const override
Definition: Normalize.cpp:33
bool AnalyseTrack(const WaveTrack *track, const TranslatableString &msg, double &progress, float &offset, float &extent)
Definition: Normalize.cpp:290
bool AnalyseTrackData(const WaveTrack *track, const TranslatableString &msg, double &progress, float &offset)
Definition: Normalize.cpp:329
static constexpr EffectParameter PeakLevel
Definition: Normalize.h:94
wxWeakRef< wxWindow > mUIParent
Definition: Normalize.h:71
double mPeakLevel
Definition: Normalize.h:73
wxCheckBox * mGainCheckBox
Definition: Normalize.h:83
wxCheckBox * mDCCheckBox
Definition: Normalize.h:84
ComponentInterfaceSymbol GetSymbol() const override
Definition: Normalize.cpp:63
bool ProcessOne(WaveTrack *t, const TranslatableString &msg, double &progress, float offset)
Definition: Normalize.cpp:394
double mCurT1
Definition: Normalize.h:79
virtual ~EffectNormalize()
Definition: Normalize.cpp:57
static const ComponentInterfaceSymbol Symbol
Definition: Normalize.h:30
void OnUpdateUI(wxCommandEvent &evt)
Definition: Normalize.cpp:463
static constexpr EffectParameter ApplyGain
Definition: Normalize.h:98
TranslatableString GetDescription() const override
Definition: Normalize.cpp:68
wxTextCtrl * mLevelTextCtrl
Definition: Normalize.h:85
wxCheckBox * mStereoIndCheckBox
Definition: Normalize.h:88
void ProcessData(float *buffer, size_t len, float offset)
Definition: Normalize.cpp:455
bool CheckWhetherSkipEffect(const EffectSettings &settings) const override
After Init(), tell whether Process() should be skipped.
Definition: Normalize.cpp:87
ManualPageID ManualPage() const override
Name of a page in the Audacity alpha manual, default is empty.
Definition: Normalize.cpp:73
void AnalyseDataDC(float *buffer, size_t len)
Definition: Normalize.cpp:449
Hold values to send to effect output meters.
Interface for manipulations of an Effect's settings.
bool GetFloats(float *buffer, sampleCount start, size_t len, fillFormat fill=fillZero, bool mayThrow=true, sampleCount *pNumWithinClips=nullptr) const
Retrieve samples from a track in floating-point format, regardless of the storage format.
Definition: SampleTrack.h:83
sampleCount TimeToLongSamples(double t0) const
Convert correctly between an (absolute) time in seconds and a number of samples.
Definition: SampleTrack.cpp:54
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:625
bool IsLeader() const
Definition: Track.cpp:296
bool Any() const
Definition: Track.cpp:290
static auto SingletonRange(TrackType *pTrack) -> TrackIterRange< TrackType >
Definition: Track.h:1370
size_t NChannels() const
Definition: Track.cpp:889
static auto Channels(TrackType *pTrack) -> TrackIterRange< TrackType >
Definition: Track.h:1406
Holds a msgid for the translation catalog; may also bind format arguments.
A Validator is an object which checks whether a wxVariant satisfies a certain criterion....
Definition: Validators.h:53
A Track that contains audio waveform data.
Definition: WaveTrack.h:51
size_t GetMaxBlockSize() const override
This returns a nonnegative number of samples meant to size a memory buffer.
Definition: WaveTrack.cpp:1647
size_t GetBestBlockSize(sampleCount t) const override
This returns a nonnegative number of samples meant to size a memory buffer.
Definition: WaveTrack.cpp:1629
std::pair< float, float > GetMinMax(double t0, double t1, bool mayThrow=true) const
Definition: WaveTrack.cpp:1873
void Set(constSamplePtr buffer, sampleFormat format, sampleCount start, size_t len, sampleFormat effectiveFormat=widestSampleFormat)
Definition: WaveTrack.cpp:2028
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
double as_double() const
Definition: SampleCount.h:46
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
BuiltinEffectsModule::Registration< EffectNormalize > reg
Definition: Normalize.cpp:44
const Type min
Minimum value.
const Type max
Maximum value.
Externalized state of a plug-in.