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#include "Loudness.h"
16#include "EBUR128.h"
17#include "EffectEditor.h"
18#include "EffectOutputTracks.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 "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{
98 const float ratio = DB_TO_LINEAR(
100 ? // LU use 10*log10(...) instead of 20*log10(...)
101 // so multiply level by 2
102 std::clamp<double>(mLUFSLevel * 2, LUFSLevel.min, LUFSLevel.max)
103 : // RMS
104 std::clamp<double>(mRMSLevel, RMSLevel.min, RMSLevel.max)
105 );
106
107 // Iterate over each track
108 EffectOutputTracks outputs { *mTracks, {{ mT0, mT1 }} };
109 bool bGoodResult = true;
110 auto topMsg = XO("Normalizing Loudness...\n");
111
112 AllocBuffers(outputs.Get());
113 mProgressVal = 0;
114
115 for (auto pTrack : outputs.Get().Selected<WaveTrack>()) {
116 // Get start and end times from track
117 double trackStart = pTrack->GetStartTime();
118 double trackEnd = pTrack->GetEndTime();
119
120 // Set the current bounds to whichever left marker is
121 // greater and whichever right marker is less:
122 const double curT0 = std::max(trackStart, mT0);
123 const double curT1 = std::min(trackEnd, mT1);
124
125 // Get the track rate
126 mCurRate = pTrack->GetRate();
127
128 wxString msg;
129 auto trackName = pTrack->GetName();
130 // This affects only the progress indicator update during ProcessOne
131 mSteps = (mNormalizeTo == kLoudness) ? 2 : 1;
132
134 topMsg + XO("Analyzing: %s").Format(trackName);
135
136 const auto channels = pTrack->Channels();
137 auto nChannels = mStereoInd ? 1 : channels.size();
138 mProcStereo = nChannels > 1;
139
140 const auto processOne = [&](WaveChannel &track){
141 std::optional<EBUR128> loudnessProcessor;
142 float RMS[2];
143
144 if (mNormalizeTo == kLoudness) {
145 loudnessProcessor.emplace(mCurRate, nChannels);
146 if (!ProcessOne(track, nChannels,
147 curT0, curT1, 0, &*loudnessProcessor))
148 // Processing failed -> abort
149 return false;
150 }
151 else {
152 // RMS
153 if (mProcStereo) {
154 size_t idx = 0;
155 for (const auto pChannel : channels) {
156 if (!GetTrackRMS(*pChannel, curT0, curT1, RMS[idx]))
157 return false;
158 ++idx;
159 }
160 }
161 else {
162 if (!GetTrackRMS(track, curT0, curT1, RMS[0]))
163 return false;
164 }
165 }
166
167 // Calculate normalization values the analysis results
168 float extent;
169 if (mNormalizeTo == kLoudness)
170 extent = loudnessProcessor->IntegrativeLoudness();
171 else {
172 // RMS
173 extent = RMS[0];
174 if (mProcStereo)
175 // RMS: use average RMS, average must be calculated in quadratic
176 // domain.
177 extent = sqrt((RMS[0] * RMS[0] + RMS[1] * RMS[1]) / 2.0);
178 }
179
180 if (extent == 0.0) {
181 FreeBuffers();
182 return false;
183 }
184 float mult = ratio / extent;
185
186 if (mNormalizeTo == kLoudness) {
187 // Target half the LUFS value if mono (or independent processed
188 // stereo) shall be treated as dual mono.
189 if (nChannels == 1 &&
190 (mDualMono || !IsMono(track)))
191 mult /= 2.0;
192
193 // LUFS are related to square values so the multiplier must be the
194 // xroot.
195 mult = sqrt(mult);
196 }
197
198 mProgressMsg = topMsg + XO("Processing: %s").Format( trackName );
199 if (!ProcessOne(track, nChannels, curT0, curT1, mult, nullptr)) {
200 // Processing failed -> abort
201 return false;
202 }
203 return true;
204 };
205
206 if (mStereoInd) {
207 for (const auto pChannel : channels)
208 if (!(bGoodResult = processOne(*pChannel)))
209 goto done;
210 }
211 else {
212 if (!(bGoodResult = processOne(*pTrack)))
213 break;
214 }
215 }
216done:
217
218 if (bGoodResult)
219 outputs.Commit();
220
221 FreeBuffers();
222 return bGoodResult;
223}
224
225std::unique_ptr<EffectEditor> EffectLoudness::PopulateOrExchange(
227 const EffectOutputs *)
228{
229 mUIParent = S.GetParent();
230 S.StartVerticalLay(0);
231 {
232 S.StartMultiColumn(2, wxALIGN_CENTER);
233 {
234 S.StartVerticalLay(false);
235 {
236 S.StartHorizontalLay(wxALIGN_LEFT, false);
237 {
238 S.AddVariableText(XO("&Normalize"), false,
239 wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
240
241 mChoice = S
242 .Validator<wxGenericValidator>( &mNormalizeTo )
243 .AddChoice( {},
245 mNormalizeTo );
246 S
247 .AddVariableText(XO("t&o"), false,
248 wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
249
250 // Use a notebook so we can have two controls but show only one
251 // They target different variables with their validators
252 mBook =
253 S
254 .StartSimplebook();
255 {
256 S.StartNotebookPage({});
257 {
258 S.StartHorizontalLay(wxALIGN_LEFT, false);
259 {
260 S
261 /* i18n-hint: LUFS is a particular method for measuring loudnesss */
262 .Name( XO("Loudness LUFS") )
263 .Validator<FloatingPointValidator<double>>(
264 2, &mLUFSLevel,
265 NumValidatorStyle::ONE_TRAILING_ZERO,
267 .AddTextBox( {}, L"", 10);
268
269 /* i18n-hint: LUFS is a particular method for measuring loudnesss */
270 S
271 .AddVariableText(XO("LUFS"), false,
272 wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
273 }
274 S.EndHorizontalLay();
275 }
276 S.EndNotebookPage();
277
278 S.StartNotebookPage({});
279 {
280 S.StartHorizontalLay(wxALIGN_LEFT, false);
281 {
282 S
283 .Name( XO("RMS dB") )
284 .Validator<FloatingPointValidator<double>>(
285 2, &mRMSLevel,
286 NumValidatorStyle::ONE_TRAILING_ZERO,
288 .AddTextBox( {}, L"", 10);
289
290 S
291 .AddVariableText(XO("dB"), false,
292 wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
293 }
294 S.EndHorizontalLay();
295 }
296 S.EndNotebookPage();
297 }
298 S.EndSimplebook();
299
300 mWarning =
301 S
302 .AddVariableText( {}, false,
303 wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
304 }
305 S.EndHorizontalLay();
306
308 .Validator<wxGenericValidator>( &mStereoInd )
309 .AddCheckBox(XXO("Normalize &stereo channels independently"),
310 mStereoInd );
311
313 .Validator<wxGenericValidator>( &mDualMono )
314 .AddCheckBox(XXO("&Treat mono as dual-mono (recommended)"),
315 mDualMono );
316 }
317 S.EndVerticalLay();
318 }
319 S.EndMultiColumn();
320 }
321 S.EndVerticalLay();
322 return nullptr;
323}
324
326{
327 if (!mUIParent->TransferDataToWindow())
328 {
329 return false;
330 }
331
332 // adjust controls which depend on mchoice
333 wxCommandEvent dummy;
334 OnChoice(dummy);
335 return true;
336}
337
339{
340 if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
341 {
342 return false;
343 }
344 return true;
345}
346
347// EffectLoudness implementation
348
352{
354 bool stereoTrackFound = false;
355 double maxSampleRate = 0;
356 mProcStereo = false;
357
358 for (auto track : outputs.Selected<WaveTrack>() + &Track::Any) {
359 mTrackBufferCapacity = std::max(mTrackBufferCapacity, track->GetMaxBlockSize());
360 maxSampleRate = std::max(maxSampleRate, track->GetRate());
361
362 // There is a stereo track
363 if(track->IsLeader())
364 stereoTrackFound = true;
365 }
366
367 // Initiate a processing buffer. This buffer will (most likely)
368 // be shorter than the length of the track being processed.
370
371 if(!mStereoInd && stereoTrackFound)
373}
374
376{
377 mTrackBuffer[0].reset();
378 mTrackBuffer[1].reset();
379}
380
382 const double curT0, const double curT1, float &rms)
383{
384 // set mRMS. No progress bar here as it's fast.
385 float _rms = track.GetRMS(curT0, curT1); // may throw
386 rms = _rms;
387 return true;
388}
389
396bool EffectLoudness::ProcessOne(WaveChannel &track, size_t nChannels,
397 const double curT0, const double curT1, const float mult,
398 EBUR128 *pLoudnessProcessor)
399{
400 // Transform the marker timepoints to samples
401 auto start = track.TimeToLongSamples(curT0);
402 auto end = track.TimeToLongSamples(curT1);
403
404 // Get the length of the buffer (as double). len is
405 // used simply to calculate a progress meter, so it is easier
406 // to make it a double now than it is to do it later
407 mTrackLen = (end - start).as_double();
408
409 // Abort if the right marker is not to the right of the left marker
410 if (curT1 <= curT0)
411 return false;
412
413 // Go through the track one buffer at a time. s counts which
414 // sample the current buffer starts at.
415 auto s = start;
416 while (s < end) {
417 // Get a block of samples (smaller than the size of the buffer)
418 // Adjust the block size if it is the final block in the track
419 auto blockLen = limitSampleBufferSize(
420 track.GetBestBlockSize(s),
422
423 const size_t remainingLen = (end - s).as_size_t();
424 blockLen = blockLen > remainingLen ? remainingLen : blockLen;
425 LoadBufferBlock(track, nChannels, s, blockLen);
426
427 // Process the buffer.
428 if (pLoudnessProcessor) {
429 if (!AnalyseBufferBlock(*pLoudnessProcessor))
430 return false;
431 }
432 else {
433 if (!ProcessBufferBlock(mult))
434 return false;
435 if (!StoreBufferBlock(track, nChannels, s, blockLen))
436 return false;
437 }
438
439 // Increment s one blockfull of samples
440 s += blockLen;
441 }
442
443 // Return true because the effect processing succeeded ... unless cancelled
444 return true;
445}
446
447void EffectLoudness::LoadBufferBlock(WaveChannel &track, size_t nChannels,
448 sampleCount pos, size_t len)
449{
450 size_t idx = 0;
451 const auto getOne = [&](WaveChannel &channel) {
452 // Get the samples from the track and put them in the buffer
453 channel.GetFloats(mTrackBuffer[idx].get(), pos, len);
454 };
455
456 if (nChannels == 1)
457 getOne(track);
458 else
459 for (const auto channel : track.GetTrack().Channels()) {
460 getOne(*channel);
461 ++idx;
462 }
463 mTrackBufferLen = len;
464}
465
469{
470 for(size_t i = 0; i < mTrackBufferLen; i++)
471 {
472 loudnessProcessor.ProcessSampleFromChannel(mTrackBuffer[0][i], 0);
473 if (mProcStereo)
474 loudnessProcessor.ProcessSampleFromChannel(mTrackBuffer[1][i], 1);
475 loudnessProcessor.NextSample();
476 }
477
478 if (!UpdateProgress())
479 return false;
480 return true;
481}
482
484{
485 for(size_t i = 0; i < mTrackBufferLen; i++)
486 {
487 mTrackBuffer[0][i] = mTrackBuffer[0][i] * mult;
488 if (mProcStereo)
489 mTrackBuffer[1][i] = mTrackBuffer[1][i] * mult;
490 }
491
492 if(!UpdateProgress())
493 return false;
494 return true;
495}
496
497bool EffectLoudness::StoreBufferBlock(WaveChannel &track, size_t nChannels,
498 sampleCount pos, size_t len)
499{
500 size_t idx = 0;
501 const auto setOne = [&](WaveChannel &channel){
502 // Copy the newly-changed samples back onto the track.
503 return channel.Set(
504 (samplePtr) mTrackBuffer[idx].get(), floatSample, pos, len);
505 };
506
507 if (nChannels == 1)
508 return setOne(track);
509 else {
510 for (auto channel : track.GetTrack().Channels()) {
511 if (!setOne(*channel))
512 return false;
513 ++idx;
514 }
515 return true;
516 }
517}
518
520{
521 mProgressVal += (double(1 + mProcStereo) * double(mTrackBufferLen)
522 / (double(GetNumWaveTracks()) * double(mSteps) * mTrackLen));
524}
525
526void EffectLoudness::OnChoice(wxCommandEvent & WXUNUSED(evt))
527{
528 mChoice->GetValidator()->TransferFromWindow();
529 mBook->SetSelection( mNormalizeTo );
530 UpdateUI();
532}
533
534void EffectLoudness::OnUpdateUI(wxCommandEvent & WXUNUSED(evt))
535{
536 UpdateUI();
537}
538
540{
541 if (!mUIParent->TransferDataFromWindow())
542 {
543 mWarning->SetLabel(_("(Maximum 0dB)"));
544 // TODO: recalculate layout here
546 return;
547 }
548 mWarning->SetLabel(wxT(""));
550}
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
static const EnumValueSymbol kNormalizeTargetStrings[EffectLoudness::nAlgos]
Definition: Loudness.cpp:35
#define DB_TO_LINEAR(x)
Definition: MemoryX.h:561
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
void ProcessSampleFromChannel(float x_in, size_t channel) const
Definition: EBUR128.cpp:83
void NextSample()
Definition: EBUR128.cpp:98
double mT1
Definition: EffectBase.h:116
void SetLinearEffectFlag(bool linearEffectFlag)
Definition: EffectBase.cpp:214
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:341
int GetNumWaveTracks() const
Definition: Effect.h:139
Performs effect computation.
An Effect to bring the loudness level up to a chosen level.
Definition: Loudness.h:33
double mCurRate
Definition: Loudness.h:102
bool UpdateProgress()
Definition: Loudness.cpp:519
bool Process(EffectInstance &instance, EffectSettings &settings) override
Definition: Loudness.cpp:96
size_t mTrackBufferCapacity
Definition: Loudness.h:112
wxCheckBox * mDualMonoCheckBox
Definition: Loudness.h:108
wxSimplebook * mBook
Definition: Loudness.h:104
bool mStereoInd
Definition: Loudness.h:92
double mTrackLen
Definition: Loudness.h:101
bool AnalyseBufferBlock(EBUR128 &loudnessProcessor)
Definition: Loudness.cpp:468
static const ComponentInterfaceSymbol Symbol
Definition: Loudness.h:44
wxCheckBox * mStereoIndCheckBox
Definition: Loudness.h:107
wxWeakRef< wxWindow > mUIParent
Definition: Loudness.h:90
wxChoice * mChoice
Definition: Loudness.h:105
void UpdateUI()
Definition: Loudness.cpp:539
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:96
void OnUpdateUI(wxCommandEvent &evt)
Definition: Loudness.cpp:534
const EffectParameterMethods & Parameters() const override
Definition: Loudness.cpp:41
TranslatableString GetDescription() const override
Definition: Loudness.cpp:77
bool TransferDataToWindow(const EffectSettings &settings) override
Definition: Loudness.cpp:325
void AllocBuffers(TrackList &outputs)
Definition: Loudness.cpp:351
EffectType GetType() const override
Type determines how it behaves.
Definition: Loudness.cpp:89
static constexpr EffectParameter StereoInd
Definition: Loudness.h:118
static constexpr EffectParameter LUFSLevel
Definition: Loudness.h:120
bool mProcStereo
Definition: Loudness.h:113
bool ProcessBufferBlock(float mult)
Definition: Loudness.cpp:483
static constexpr EffectParameter RMSLevel
Definition: Loudness.h:122
static constexpr EffectParameter DualMono
Definition: Loudness.h:124
virtual ~EffectLoudness()
Definition: Loudness.cpp:66
void LoadBufferBlock(WaveChannel &track, size_t nChannels, sampleCount pos, size_t len)
Definition: Loudness.cpp:447
void FreeBuffers()
Definition: Loudness.cpp:375
bool TransferDataFromWindow(EffectSettings &settings) override
Definition: Loudness.cpp:338
double mProgressVal
Definition: Loudness.h:98
size_t mTrackBufferLen
Definition: Loudness.h:111
std::unique_ptr< EffectEditor > PopulateOrExchange(ShuttleGui &S, EffectInstance &instance, EffectSettingsAccess &access, const EffectOutputs *pOutputs) override
Add controls to effect panel; always succeeds.
Definition: Loudness.cpp:225
wxStaticText * mWarning
Definition: Loudness.h:106
double mRMSLevel
Definition: Loudness.h:94
bool mDualMono
Definition: Loudness.h:95
static constexpr EffectParameter NormalizeTo
Definition: Loudness.h:126
bool ProcessOne(WaveChannel &track, size_t nChannels, double curT0, double curT1, float mult, EBUR128 *pLoudnessProcessor)
Definition: Loudness.cpp:396
bool StoreBufferBlock(WaveChannel &track, size_t nChannels, sampleCount pos, size_t len)
Definition: Loudness.cpp:497
void OnChoice(wxCommandEvent &evt)
Definition: Loudness.cpp:526
TranslatableString mProgressMsg
Definition: Loudness.h:100
static bool GetTrackRMS(WaveChannel &track, double curT0, double curT1, float &rms)
Definition: Loudness.cpp:381
Floats mTrackBuffer[2]
Definition: Loudness.h:110
double mLUFSLevel
Definition: Loudness.h:93
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.
virtual void Reset(Effect &effect) const =0
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI.
Definition: ShuttleGui.h:630
bool Any() const
Definition: Track.cpp:292
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
Definition: Track.h:987
auto Selected() -> TrackIterRange< TrackType >
Definition: Track.h:1108
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
WaveTrack & GetTrack()
Definition: WaveTrack.h:1117
float GetRMS(double t0, double t1, bool mayThrow=true) const
Get root-mean-square.
Definition: WaveTrack.cpp:2767
size_t GetBestBlockSize(sampleCount t) const
A hint for sizing of well aligned fetches.
Definition: WaveTrack.h:1131
A Track that contains audio waveform data.
Definition: WaveTrack.h:220
auto Channels()
Definition: WaveTrack.h:268
sampleCount TimeToLongSamples(double t0) const
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
bool IsMono(const Channel &channel)
Whether the channel is mono.
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:159
BuiltinEffectsModule::Registration< EffectLoudness > reg
Definition: Loudness.cpp:58
__finl float_x4 __vecc sqrt(const float_x4 &a)
const Type min
Minimum value.
const Type max
Maximum value.
Externalized state of a plug-in.