Audacity 3.2.0
Loudness.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 Loudness.cpp
6
7 Max Maisel
8
9*******************************************************************//*******************************************************************/
15
16
17
18#include "Loudness.h"
19
20#include <math.h>
21
22#include <wx/simplebook.h>
23#include <wx/valgen.h>
24
25#include "Internat.h"
26#include "Prefs.h"
27#include "../ProjectFileManager.h"
28#include "../ShuttleGui.h"
29#include "../WaveTrack.h"
30#include "../widgets/valnum.h"
31#include "../widgets/ProgressDialog.h"
32
33#include "LoadEffects.h"
34
36{
37 { XO("perceived loudness") },
38 { XO("RMS") }
39};
40
42{
45 > parameters;
46 return parameters;
47}
48
49BEGIN_EVENT_TABLE(EffectLoudness, wxEvtHandler)
50 EVT_CHOICE(wxID_ANY, EffectLoudness::OnChoice)
51 EVT_CHECKBOX(wxID_ANY, EffectLoudness::OnUpdateUI)
52 EVT_TEXT(wxID_ANY, EffectLoudness::OnUpdateUI)
54
56{ XO("Loudness Normalization") };
57
59
61{
62 Parameters().Reset(*this);
64}
65
67{
68}
69
70// ComponentInterface implementation
71
73{
74 return Symbol;
75}
76
78{
79 return XO("Sets the loudness of one or more tracks");
80}
81
83{
84 return L"Loudness_Normalization";
85}
86
87// EffectDefinitionInterface implementation
88
90{
91 return EffectTypeProcess;
92}
93
94// Effect implementation
95
97{
99 // LU use 10*log10(...) instead of 20*log10(...)
100 // so multiply level by 2 and use standard DB_TO_LINEAR macro.
101 mRatio = DB_TO_LINEAR(std::clamp<double>(mLUFSLevel*2, LUFSLevel.min, LUFSLevel.max));
102 else // RMS
103 mRatio = DB_TO_LINEAR(std::clamp<double>(mRMSLevel, RMSLevel.min, RMSLevel.max));
104
105 // Iterate over each track
106 this->CopyInputTracks(); // Set up mOutputTracks.
107 bool bGoodResult = true;
108 auto topMsg = XO("Normalizing Loudness...\n");
109
110 AllocBuffers();
111 mProgressVal = 0;
112
113 for(auto track : mOutputTracks->Selected<WaveTrack>()
115 {
116 // Get start and end times from track
117 // PRL: No accounting for multiple channels ?
118 double trackStart = track->GetStartTime();
119 double trackEnd = track->GetEndTime();
120
121 // Set the current bounds to whichever left marker is
122 // greater and whichever right marker is less:
123 mCurT0 = mT0 < trackStart? trackStart: mT0;
124 mCurT1 = mT1 > trackEnd? trackEnd: mT1;
125
126 // Get the track rate
127 mCurRate = track->GetRate();
128
129 wxString msg;
130 auto trackName = track->GetName();
131 mSteps = 2;
132
134 topMsg + XO("Analyzing: %s").Format( trackName );
135
136 auto range = mStereoInd
138 : TrackList::Channels(track);
139
140 mProcStereo = range.size() > 1;
141
143 {
144 mLoudnessProcessor.reset(safenew EBUR128(mCurRate, range.size()));
145 mLoudnessProcessor->Initialize();
146 if(!ProcessOne(range, true))
147 {
148 // Processing failed -> abort
149 bGoodResult = false;
150 break;
151 }
152 }
153 else // RMS
154 {
155 size_t idx = 0;
156 for(auto channel : range)
157 {
158 if(!GetTrackRMS(channel, mRMS[idx]))
159 {
160 bGoodResult = false;
161 return false;
162 }
163 ++idx;
164 }
165 mSteps = 1;
166 }
167
168 // Calculate normalization values the analysis results
169 float extent;
171 extent = mLoudnessProcessor->IntegrativeLoudness();
172 else // RMS
173 {
174 extent = mRMS[0];
175 if(mProcStereo)
176 // RMS: use average RMS, average must be calculated in quadratic domain.
177 extent = sqrt((mRMS[0] * mRMS[0] + mRMS[1] * mRMS[1]) / 2.0);
178 }
179
180 if(extent == 0.0)
181 {
182 mLoudnessProcessor.reset();
183 FreeBuffers();
184 return false;
185 }
186 mMult = mRatio / extent;
187
189 {
190 // Target half the LUFS value if mono (or independent processed stereo)
191 // shall be treated as dual mono.
192 if(range.size() == 1 && (mDualMono || track->GetChannel() != Track::MonoChannel))
193 mMult /= 2.0;
194
195 // LUFS are related to square values so the multiplier must be the root.
196 mMult = sqrt(mMult);
197 }
198
199 mProgressMsg = topMsg + XO("Processing: %s").Format( trackName );
200 if(!ProcessOne(range, false))
201 {
202 // Processing failed -> abort
203 bGoodResult = false;
204 break;
205 }
206 }
207
208 this->ReplaceProcessedTracks(bGoodResult);
209 mLoudnessProcessor.reset();
210 FreeBuffers();
211 return bGoodResult;
212}
213
214std::unique_ptr<EffectUIValidator> EffectLoudness::PopulateOrExchange(
216 const EffectOutputs *)
217{
218 mUIParent = S.GetParent();
219 S.StartVerticalLay(0);
220 {
221 S.StartMultiColumn(2, wxALIGN_CENTER);
222 {
223 S.StartVerticalLay(false);
224 {
225 S.StartHorizontalLay(wxALIGN_LEFT, false);
226 {
227 S.AddVariableText(XO("&Normalize"), false,
228 wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
229
230 mChoice = S
231 .Validator<wxGenericValidator>( &mNormalizeTo )
232 .AddChoice( {},
234 mNormalizeTo );
235 S
236 .AddVariableText(XO("t&o"), false,
237 wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
238
239 // Use a notebook so we can have two controls but show only one
240 // They target different variables with their validators
241 mBook =
242 S
243 .StartSimplebook();
244 {
245 S.StartNotebookPage({});
246 {
247 S.StartHorizontalLay(wxALIGN_LEFT, false);
248 {
249 S
250 /* i18n-hint: LUFS is a particular method for measuring loudnesss */
251 .Name( XO("Loudness LUFS") )
252 .Validator<FloatingPointValidator<double>>(
253 2, &mLUFSLevel,
254 NumValidatorStyle::ONE_TRAILING_ZERO,
256 .AddTextBox( {}, L"", 10);
257
258 /* i18n-hint: LUFS is a particular method for measuring loudnesss */
259 S
260 .AddVariableText(XO("LUFS"), false,
261 wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
262 }
263 S.EndHorizontalLay();
264 }
265 S.EndNotebookPage();
266
267 S.StartNotebookPage({});
268 {
269 S.StartHorizontalLay(wxALIGN_LEFT, false);
270 {
271 S
272 .Name( XO("RMS dB") )
273 .Validator<FloatingPointValidator<double>>(
274 2, &mRMSLevel,
275 NumValidatorStyle::ONE_TRAILING_ZERO,
277 .AddTextBox( {}, L"", 10);
278
279 S
280 .AddVariableText(XO("dB"), false,
281 wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
282 }
283 S.EndHorizontalLay();
284 }
285 S.EndNotebookPage();
286 }
287 S.EndSimplebook();
288
289 mWarning =
290 S
291 .AddVariableText( {}, false,
292 wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
293 }
294 S.EndHorizontalLay();
295
297 .Validator<wxGenericValidator>( &mStereoInd )
298 .AddCheckBox(XXO("Normalize &stereo channels independently"),
299 mStereoInd );
300
302 .Validator<wxGenericValidator>( &mDualMono )
303 .AddCheckBox(XXO("&Treat mono as dual-mono (recommended)"),
304 mDualMono );
305 }
306 S.EndVerticalLay();
307 }
308 S.EndMultiColumn();
309 }
310 S.EndVerticalLay();
311 return nullptr;
312}
313
315{
316 if (!mUIParent->TransferDataToWindow())
317 {
318 return false;
319 }
320
321 // adjust controls which depend on mchoice
322 wxCommandEvent dummy;
323 OnChoice(dummy);
324 return true;
325}
326
328{
329 if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
330 {
331 return false;
332 }
333 return true;
334}
335
336// EffectLoudness implementation
337
341{
343 bool stereoTrackFound = false;
344 double maxSampleRate = 0;
345 mProcStereo = false;
346
347 for(auto track : mOutputTracks->Selected<WaveTrack>() + &Track::Any)
348 {
349 mTrackBufferCapacity = std::max(mTrackBufferCapacity, track->GetMaxBlockSize());
350 maxSampleRate = std::max(maxSampleRate, track->GetRate());
351
352 // There is a stereo track
353 if(track->IsLeader())
354 stereoTrackFound = true;
355 }
356
357 // Initiate a processing buffer. This buffer will (most likely)
358 // be shorter than the length of the track being processed.
360
361 if(!mStereoInd && stereoTrackFound)
363}
364
366{
367 mTrackBuffer[0].reset();
368 mTrackBuffer[1].reset();
369}
370
372{
373 // set mRMS. No progress bar here as it's fast.
374 float _rms = track->GetRMS(mCurT0, mCurT1); // may throw
375 rms = _rms;
376 return true;
377}
378
386{
387 WaveTrack* track = *range.begin();
388
389 // Transform the marker timepoints to samples
390 auto start = track->TimeToLongSamples(mCurT0);
391 auto end = track->TimeToLongSamples(mCurT1);
392
393 // Get the length of the buffer (as double). len is
394 // used simply to calculate a progress meter, so it is easier
395 // to make it a double now than it is to do it later
396 mTrackLen = (end - start).as_double();
397
398 // Abort if the right marker is not to the right of the left marker
399 if(mCurT1 <= mCurT0)
400 return false;
401
402 // Go through the track one buffer at a time. s counts which
403 // sample the current buffer starts at.
404 auto s = start;
405 while(s < end)
406 {
407 // Get a block of samples (smaller than the size of the buffer)
408 // Adjust the block size if it is the final block in the track
409 auto blockLen = limitSampleBufferSize(
410 track->GetBestBlockSize(s),
412
413 const size_t remainingLen = (end - s).as_size_t();
414 blockLen = blockLen > remainingLen ? remainingLen : blockLen;
415 LoadBufferBlock(range, s, blockLen);
416
417 // Process the buffer.
418 if(analyse)
419 {
420 if(!AnalyseBufferBlock())
421 return false;
422 }
423 else
424 {
425 if(!ProcessBufferBlock())
426 return false;
427 StoreBufferBlock(range, s, blockLen);
428 }
429
430 // Increment s one blockfull of samples
431 s += blockLen;
432 }
433
434 // Return true because the effect processing succeeded ... unless cancelled
435 return true;
436}
437
439 sampleCount pos, size_t len)
440{
441 // Get the samples from the track and put them in the buffer
442 int idx = 0;
443 for(auto channel : range)
444 {
445 channel->GetFloats(mTrackBuffer[idx].get(), pos, len );
446 ++idx;
447 }
448 mTrackBufferLen = len;
449}
450
454{
455 for(size_t i = 0; i < mTrackBufferLen; i++)
456 {
457 mLoudnessProcessor->ProcessSampleFromChannel(mTrackBuffer[0][i], 0);
458 if(mProcStereo)
459 mLoudnessProcessor->ProcessSampleFromChannel(mTrackBuffer[1][i], 1);
460 mLoudnessProcessor->NextSample();
461 }
462
463 if(!UpdateProgress())
464 return false;
465 return true;
466}
467
469{
470 for(size_t i = 0; i < mTrackBufferLen; i++)
471 {
472 mTrackBuffer[0][i] = mTrackBuffer[0][i] * mMult;
473 if(mProcStereo)
474 mTrackBuffer[1][i] = mTrackBuffer[1][i] * mMult;
475 }
476
477 if(!UpdateProgress())
478 return false;
479 return true;
480}
481
483 sampleCount pos, size_t len)
484{
485 int idx = 0;
486 for(auto channel : range)
487 {
488 // Copy the newly-changed samples back onto the track.
489 channel->Set((samplePtr) mTrackBuffer[idx].get(), floatSample, pos, len);
490 ++idx;
491 }
492}
493
495{
496 mProgressVal += (double(1+mProcStereo) * double(mTrackBufferLen)
497 / (double(GetNumWaveTracks()) * double(mSteps) * mTrackLen));
499}
500
501void EffectLoudness::OnChoice(wxCommandEvent & WXUNUSED(evt))
502{
503 mChoice->GetValidator()->TransferFromWindow();
504 mBook->SetSelection( mNormalizeTo );
505 UpdateUI();
507}
508
509void EffectLoudness::OnUpdateUI(wxCommandEvent & WXUNUSED(evt))
510{
511 UpdateUI();
512}
513
515{
516 if (!mUIParent->TransferDataFromWindow())
517 {
518 mWarning->SetLabel(_("(Maximum 0dB)"));
519 // TODO: recalculate layout here
521 return;
522 }
523 mWarning->SetLabel(wxT(""));
525}
wxT("CloseDown"))
END_EVENT_TABLE()
EffectType
@ EffectTypeProcess
XO("Cut/Copy/Paste")
XXO("&Cut/Copy/Paste Toolbar")
#define _(s)
Definition: Internat.h:75
static const EnumValueSymbol kNormalizeTargetStrings[EffectLoudness::nAlgos]
Definition: Loudness.cpp:35
#define safenew
Definition: MemoryX.h:10
#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
TranslatableStrings Msgids(const EnumValueSymbol strings[], size_t nStrings)
Convenience function often useful when adding choice controls.
#define S(N)
Definition: ToChars.cpp:64
void reinit(Integral count, bool initialize=false)
Definition: MemoryX.h:57
Generates EffectParameterMethods overrides from variadic template arguments.
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
Implements EBU-R128 loudness measurement.
Definition: EBUR128.h:22
double mT1
Definition: EffectBase.h:109
std::shared_ptr< TrackList > mOutputTracks
Definition: EffectBase.h:107
void SetLinearEffectFlag(bool linearEffectFlag)
Definition: EffectBase.cpp:223
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 loudness level up to a chosen level.
Definition: Loudness.h:30
double mCurRate
Definition: Loudness.h:99
bool UpdateProgress()
Definition: Loudness.cpp:494
bool Process(EffectInstance &instance, EffectSettings &settings) override
Definition: Loudness.cpp:96
size_t mTrackBufferCapacity
Definition: Loudness.h:114
wxCheckBox * mDualMonoCheckBox
Definition: Loudness.h:110
wxSimplebook * mBook
Definition: Loudness.h:106
bool mStereoInd
Definition: Loudness.h:87
double mTrackLen
Definition: Loudness.h:98
float mRMS[2]
Definition: Loudness.h:103
static const ComponentInterfaceSymbol Symbol
Definition: Loudness.h:41
wxCheckBox * mStereoIndCheckBox
Definition: Loudness.h:109
wxWeakRef< wxWindow > mUIParent
Definition: Loudness.h:85
std::unique_ptr< EBUR128 > mLoudnessProcessor
Definition: Loudness.h:104
wxChoice * mChoice
Definition: Loudness.h:107
void UpdateUI()
Definition: Loudness.cpp:514
ComponentInterfaceSymbol GetSymbol() const override
Definition: Loudness.cpp:72
ManualPageID ManualPage() const override
Name of a page in the Audacity alpha manual, default is empty.
Definition: Loudness.cpp:82
int mNormalizeTo
Definition: Loudness.h:91
bool GetTrackRMS(WaveTrack *track, float &rms)
Definition: Loudness.cpp:371
bool ProcessBufferBlock()
Definition: Loudness.cpp:468
double mCurT1
Definition: Loudness.h:94
void OnUpdateUI(wxCommandEvent &evt)
Definition: Loudness.cpp:509
const EffectParameterMethods & Parameters() const override
Definition: Loudness.cpp:41
bool AnalyseBufferBlock()
Definition: Loudness.cpp:453
TranslatableString GetDescription() const override
Definition: Loudness.cpp:77
bool TransferDataToWindow(const EffectSettings &settings) override
Update controls for the settings.
Definition: Loudness.cpp:314
std::unique_ptr< EffectUIValidator > PopulateOrExchange(ShuttleGui &S, EffectInstance &instance, EffectSettingsAccess &access, const EffectOutputs *pOutputs) override
Add controls to effect panel; always succeeds.
Definition: Loudness.cpp:214
float mRatio
Definition: Loudness.h:102
EffectType GetType() const override
Type determines how it behaves.
Definition: Loudness.cpp:89
static constexpr EffectParameter StereoInd
Definition: Loudness.h:120
static constexpr EffectParameter LUFSLevel
Definition: Loudness.h:122
void StoreBufferBlock(TrackIterRange< WaveTrack > range, sampleCount pos, size_t len)
Definition: Loudness.cpp:482
bool mProcStereo
Definition: Loudness.h:115
static constexpr EffectParameter RMSLevel
Definition: Loudness.h:124
static constexpr EffectParameter DualMono
Definition: Loudness.h:126
virtual ~EffectLoudness()
Definition: Loudness.cpp:66
void FreeBuffers()
Definition: Loudness.cpp:365
bool TransferDataFromWindow(EffectSettings &settings) override
Update the given settings from controls.
Definition: Loudness.cpp:327
void AllocBuffers()
Definition: Loudness.cpp:340
double mProgressVal
Definition: Loudness.h:95
size_t mTrackBufferLen
Definition: Loudness.h:113
wxStaticText * mWarning
Definition: Loudness.h:108
double mRMSLevel
Definition: Loudness.h:89
bool mDualMono
Definition: Loudness.h:90
static constexpr EffectParameter NormalizeTo
Definition: Loudness.h:128
void OnChoice(wxCommandEvent &evt)
Definition: Loudness.cpp:501
TranslatableString mProgressMsg
Definition: Loudness.h:97
double mCurT0
Definition: Loudness.h:93
bool ProcessOne(TrackIterRange< WaveTrack > range, bool analyse)
Definition: Loudness.cpp:385
void LoadBufferBlock(TrackIterRange< WaveTrack > range, sampleCount pos, size_t len)
Definition: Loudness.cpp:438
Floats mTrackBuffer[2]
Definition: Loudness.h:112
double mLUFSLevel
Definition: Loudness.h:88
Hold values to send to effect output meters.
Interface for manipulations of an Effect's settings.
virtual void Reset(Effect &effect) const =0
static bool EnableApply(wxWindow *parent, bool enable=true)
Enable or disable the Apply button of the dialog that contains parent.
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
@ MonoChannel
Definition: Track.h:284
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 GetBestBlockSize(sampleCount t) const override
This returns a nonnegative number of samples meant to size a memory buffer.
Definition: WaveTrack.cpp:1782
float GetRMS(double t0, double t1, bool mayThrow=true) const
Definition: WaveTrack.cpp:2075
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
BuiltinEffectsModule::Registration< EffectLoudness > reg
Definition: Loudness.cpp:58
const Type min
Minimum value.
Definition: Shuttle.h:30
const Type max
Maximum value.
Definition: Shuttle.h:31
Externalized state of a plug-in.
Iterator begin() const
Definition: MemoryX.h:273
Range between two TrackIters, usable in range-for statements, and with Visit member functions.
Definition: Track.h:1167