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