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"
31#include "WaveTrack.h"
32#include "../widgets/valnum.h"
33#include "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 EffectOutputTracks outputs { *mTracks, GetType(), { { mT0, mT1 } } };
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 : outputs.Get().Selected<WaveTrack>()) {
124 // Get start and end times from track
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 = std::max(trackStart, mT0);
131 mCurT1 = std::min(trackEnd, mT1);
132
133 // Process only if the right marker is to the right of the left marker
134 if (mCurT1 > mCurT0) {
135 wxString trackName = track->GetName();
136
137 std::vector<float> extents;
138 float maxExtent{ std::numeric_limits<float>::lowest() };
139 std::vector<float> offsets;
140
141 const auto channels = track->Channels();
142 // mono or 'stereo tracks independently'
143 const bool oneChannel = (channels.size() == 1 || mStereoInd);
144 auto msg = oneChannel
145 ? topMsg +
146 XO("Analyzing: %s").Format(trackName)
147 : topMsg +
148 // TODO: more-than-two-channels-message
149 XO("Analyzing first track of stereo pair: %s").Format(trackName);
150
151 const auto progressReport = [&](double fraction){
152 return !TotalProgress(
153 (progress + fraction / double(2 * GetNumWaveTracks())), msg);
154 };
155
156 // Analysis loop over channels collects offsets and extent
157 for (auto channel : channels) {
158 float offset = 0;
159 float extent = 0;
160 bGoodResult = AnalyseTrack(*channel, progressReport, mGain, mDC,
161 mCurT0, mCurT1, offset, extent);
162 if (!bGoodResult)
163 goto break2;
164 progress += 1.0 / double(2 * GetNumWaveTracks());
165 extents.push_back(extent);
166 maxExtent = std::max(maxExtent, extent);
167 offsets.push_back(offset);
168 // TODO: more-than-two-channels-message
169 if (!oneChannel)
170 msg = topMsg +
171 XO("Analyzing second track of stereo pair: %s")
172 .Format(trackName);
173 }
174
175 if (oneChannel) {
176 if (track->NChannels() == 1)
177 // really mono
178 msg = topMsg +
179 XO("Processing: %s").Format(trackName);
180 else
181 //'stereo tracks independently'
182 // TODO: more-than-two-channels-message
183 msg = topMsg +
184 XO("Processing stereo channels independently: %s")
185 .Format(trackName);
186 }
187 else
188 msg = topMsg +
189 // TODO: more-than-two-channels-message
190 XO("Processing first track of stereo pair: %s")
191 .Format(trackName);
192
193 // Use multiplier in the second, processing loop over channels
194 auto pOffset = offsets.begin();
195 auto pExtent = extents.begin();
196 for (const auto channel : channels) {
197 const auto extent = oneChannel ? *pExtent++: maxExtent;
198 if ((extent > 0) && mGain)
199 mMult = ratio / extent;
200 else
201 mMult = 1.0;
202 if (false ==
203 (bGoodResult = ProcessOne(*channel, msg, progress, *pOffset++)))
204 goto break2;
205 // TODO: more-than-two-channels-message
206 msg = topMsg +
207 XO("Processing second track of stereo pair: %s")
208 .Format(trackName);
209 }
210 }
211 }
212
213 break2:
214
215 if (bGoodResult)
216 outputs.Commit();
217
218 return bGoodResult;
219}
220
221std::unique_ptr<EffectEditor> EffectNormalize::PopulateOrExchange(
223 const EffectOutputs *)
224{
225 mUIParent = S.GetParent();
226 mCreating = true;
227
228 S.StartVerticalLay(0);
229 {
230 S.StartMultiColumn(2, wxALIGN_CENTER);
231 {
232 S.StartVerticalLay(false);
233 {
234 mDCCheckBox = S.Validator<wxGenericValidator>(&mDC)
235 .AddCheckBox(XXO("&Remove DC offset (center on 0.0 vertically)"),
236 mDC);
237
238 S.StartHorizontalLay(wxALIGN_LEFT, false);
239 {
241 .MinSize()
242 .Validator<wxGenericValidator>(&mGain)
243 .AddCheckBox(XXO("&Normalize peak amplitude to "),
244 mGain);
245
247 .Name(XO("Peak amplitude dB"))
248 .Validator<FloatingPointValidator<double>>(
249 2,
250 &mPeakLevel,
251 NumValidatorStyle::ONE_TRAILING_ZERO,
253 PeakLevel.max )
254 .AddTextBox( {}, L"", 10);
255 mLeveldB = S.AddVariableText(XO("dB"), false,
256 wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
257 mWarning = S.AddVariableText( {}, false,
258 wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
259 }
260 S.EndHorizontalLay();
261
263 .Validator<wxGenericValidator>(&mStereoInd)
264 .AddCheckBox(XXO("N&ormalize stereo channels independently"),
265 mStereoInd);
266 }
267 S.EndVerticalLay();
268 }
269 S.EndMultiColumn();
270 }
271 S.EndVerticalLay();
272 mCreating = false;
273 return nullptr;
274}
275
277{
278 if (!mUIParent->TransferDataToWindow())
279 {
280 return false;
281 }
282
283 UpdateUI();
284
285 return true;
286}
287
289{
290 if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
291 {
292 return false;
293 }
294
295 return true;
296}
297
298// EffectNormalize implementation
299
301 const ProgressReport &report,
302 const bool gain, const bool dc, const double curT0, const double curT1,
303 float &offset, float &extent)
304{
305 bool result = true;
306 float min, max;
307 if (gain) {
308 // set mMin, mMax. No progress bar here as it's fast.
309 auto pair =
310 WaveChannelUtilities::GetMinMax(track, curT0, curT1); // may throw
311 min = pair.first, max = pair.second;
312
313 if (dc) {
314 result = AnalyseTrackData(track, report, curT0, curT1, offset);
315 min += offset;
316 max += offset;
317 }
318 }
319 else if (dc) {
320 min = -1.0, max = 1.0; // sensible defaults?
321 result = AnalyseTrackData(track, report, curT0, curT1, offset);
322 min += offset;
323 max += offset;
324 }
325 else {
326 wxFAIL_MSG("Analysing Track when nothing to do!");
327 min = -1.0, max = 1.0; // sensible defaults?
328 offset = 0.0;
329 }
330 extent = fmax(fabs(min), fabs(max));
331 return result;
332}
333
334//AnalyseTrackData() takes a track, transforms it to bunch of buffer-blocks,
335//and executes selected AnalyseOperation on it...
337 const ProgressReport &report, const double curT0, const double curT1,
338 float &offset)
339{
340 bool rc = true;
341
342 //Transform the marker timepoints to samples
343 auto start = track.TimeToLongSamples(curT0);
344 auto end = track.TimeToLongSamples(curT1);
345
346 //Get the length of the buffer (as double). len is
347 //used simply to calculate a progress meter, so it is easier
348 //to make it a double now than it is to do it later
349 auto len = (end - start).as_double();
350
351 //Initiate a processing buffer. This buffer will (most likely)
352 //be shorter than the length of the track being processed.
353 Floats buffer{ track.GetMaxBlockSize() };
354
355 double sum = 0.0; // dc offset inits
356
357 sampleCount blockSamples;
358 sampleCount totalSamples = 0;
359
360 //Go through the track one buffer at a time. s counts which
361 //sample the current buffer starts at.
362 auto s = start;
363 while (s < end) {
364 //Get a block of samples (smaller than the size of the buffer)
365 //Adjust the block size if it is the final block in the track
366 const auto block = limitSampleBufferSize(
367 track.GetBestBlockSize(s),
368 end - s
369 );
370
371 //Get the samples from the track and put them in the buffer
372 track.GetFloats(
373 buffer.get(), s, block, FillFormat::fillZero, true, &blockSamples);
374 totalSamples += blockSamples;
375
376 //Process the buffer.
377 sum = AnalyseDataDC(buffer.get(), block, sum);
378
379 //Increment s one blockfull of samples
380 s += block;
381
382 //Update the Progress meter
383 if (!report((s - start).as_double() / len)) {
384 rc = false; //lda .. break, not return, so that buffer is deleted
385 break;
386 }
387 }
388 if (totalSamples > 0)
389 // calculate actual offset (amount that needs to be added on)
390 offset = -sum / totalSamples.as_double();
391 else
392 offset = 0.0;
393
394 //Return true because the effect processing succeeded ... unless cancelled
395 return rc;
396}
397
398//ProcessOne() takes a track, transforms it to bunch of buffer-blocks,
399//and executes ProcessData, on it...
400// uses mMult and offset to normalize a track.
401// mMult must be set before this is called
403 const TranslatableString &msg, double &progress, float offset)
404{
405 bool rc = true;
406
407 //Transform the marker timepoints to samples
408 auto start = track.TimeToLongSamples(mCurT0);
409 auto end = track.TimeToLongSamples(mCurT1);
410
411 //Get the length of the buffer (as double). len is
412 //used simply to calculate a progress meter, so it is easier
413 //to make it a double now than it is to do it later
414 auto len = (end - start).as_double();
415
416 //Initiate a processing buffer. This buffer will (most likely)
417 //be shorter than the length of the track being processed.
418 Floats buffer{ track.GetMaxBlockSize() };
419
420 //Go through the track one buffer at a time. s counts which
421 //sample the current buffer starts at.
422 auto s = start;
423 while (s < end) {
424 //Get a block of samples (smaller than the size of the buffer)
425 //Adjust the block size if it is the final block in the track
426 const auto block = limitSampleBufferSize(
427 track.GetBestBlockSize(s),
428 end - s
429 );
430
431 //Get the samples from the track and put them in the buffer
432 track.GetFloats(buffer.get(), s, block);
433
434 //Process the buffer.
435 ProcessData(buffer.get(), block, offset);
436
437 //Copy the newly-changed samples back onto the track.
438 if (!track.SetFloats(buffer.get(), s, block)) {
439 rc = false;
440 break;
441 }
442
443 //Increment s one blockfull of samples
444 s += block;
445
446 //Update the Progress meter
447 if (TotalProgress(progress +
448 ((s - start).as_double() / len)/double(2*GetNumWaveTracks()), msg)) {
449 rc = false; //lda .. break, not return, so that buffer is deleted
450 break;
451 }
452 }
453 progress += 1.0/double(2*GetNumWaveTracks());
454
455 //Return true because the effect processing succeeded ... unless cancelled
456 return rc;
457}
458
460double EffectNormalize::AnalyseDataDC(float *buffer, size_t len, double sum)
461{
462 for(decltype(len) i = 0; i < len; i++)
463 sum += (double)buffer[i];
464 return sum;
465}
466
467void EffectNormalize::ProcessData(float *buffer, size_t len, float offset)
468{
469 for(decltype(len) i = 0; i < len; i++) {
470 float adjFrame = (buffer[i] + offset) * mMult;
471 buffer[i] = adjFrame;
472 }
473}
474
475void EffectNormalize::OnUpdateUI(wxCommandEvent & WXUNUSED(evt))
476{
477 UpdateUI();
478}
479
481{
482
483 if (!mUIParent->TransferDataFromWindow())
484 {
485 mWarning->SetLabel(_("(Maximum 0dB)"));
487 return;
488 }
489 mWarning->SetLabel(wxT(""));
490
491 // Disallow level stuff if not normalizing
492 mLevelTextCtrl->Enable(mGain);
493 mLeveldB->Enable(mGain);
494 mStereoIndCheckBox->Enable(mGain);
495
496 // Disallow OK/Preview if doing nothing
498}
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:337
size_t limitSampleBufferSize(size_t bufferSize, sampleCount limit)
Definition: SampleCount.cpp:22
#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:114
std::shared_ptr< TrackList > mTracks
Definition: EffectBase.h:107
double mT0
Definition: EffectBase.h:113
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:288
static double AnalyseDataDC(float *buffer, size_t len, double sum)
Definition: Normalize.cpp:460
wxStaticText * mWarning
Definition: Normalize.h:92
static bool AnalyseTrackData(const WaveChannel &track, const ProgressReport &report, double curT0, double curT1, float &offset)
Definition: Normalize.cpp:336
double mCurT0
Definition: Normalize.h:84
EffectType GetType() const override
Type determines how it behaves.
Definition: Normalize.cpp:82
wxStaticText * mLeveldB
Definition: Normalize.h:91
static constexpr EffectParameter StereoInd
Definition: Normalize.h:105
bool Process(EffectInstance &instance, EffectSettings &settings) override
Definition: Normalize.cpp:94
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:221
bool TransferDataToWindow(const EffectSettings &settings) override
Definition: Normalize.cpp:276
const EffectParameterMethods & Parameters() const override
Definition: Normalize.cpp:35
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:65
double mCurT1
Definition: Normalize.h:85
virtual ~EffectNormalize()
Definition: Normalize.cpp:59
static const ComponentInterfaceSymbol Symbol
Definition: Normalize.h:32
void OnUpdateUI(wxCommandEvent &evt)
Definition: Normalize.cpp:475
static constexpr EffectParameter ApplyGain
Definition: Normalize.h:103
bool ProcessOne(WaveChannel &track, const TranslatableString &msg, double &progress, float offset)
Definition: Normalize.cpp:402
TranslatableString GetDescription() const override
Definition: Normalize.cpp:70
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:300
void ProcessData(float *buffer, size_t len, float offset)
Definition: Normalize.cpp:467
bool CheckWhetherSkipEffect(const EffectSettings &settings) const override
After Init(), tell whether Process() should be skipped.
Definition: Normalize.cpp:89
ManualPageID ManualPage() const override
Name of a page in the Audacity alpha manual, default is empty.
Definition: Normalize.cpp:75
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
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
bool SetFloats(const float *buffer, sampleCount start, size_t len, sampleFormat effectiveFormat=widestSampleFormat)
Random-access assignment of a range of samples.
Definition: WaveTrack.h:162
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:129
size_t GetBestBlockSize(sampleCount t) const
A hint for sizing of well aligned fetches.
Definition: WaveTrack.h:850
size_t GetMaxBlockSize() const
Definition: WaveTrack.h:858
A Track that contains audio waveform data.
Definition: WaveTrack.h:203
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
WAVE_TRACK_API std::pair< float, float > GetMinMax(const WaveChannel &channel, double t0, double t1, bool mayThrow=true)
BuiltinEffectsModule::Registration< EffectNormalize > reg
Definition: Normalize.cpp:46
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
const Type min
Minimum value.
const Type max
Maximum value.
Externalized state of a plug-in.