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