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