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