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