Audacity 3.2.0
LoudnessBase.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 LoudnessBase.cpp
6
7 Max Maisel
8
9*******************************************************************//*******************************************************************/
15#include "LoudnessBase.h"
16#include "EffectOutputTracks.h"
17#include "EBUR128.h"
18#include <cmath>
20#include "WaveTrack.h"
21#include "ShuttleAutomation.h"
22
24 "Loudness Normalization") };
25
27{
28 static CapturedParameters<
30 parameters;
31 return parameters;
32}
33
35{
36 Parameters().Reset(*this);
38}
39
41{
42}
43
44// ComponentInterface implementation
45
47{
48 return Symbol;
49}
50
52{
53 return XO("Sets the loudness of one or more tracks");
54}
55
57{
58 return L"Loudness_Normalization";
59}
60
61// EffectDefinitionInterface implementation
62
64{
65 return EffectTypeProcess;
66}
67
68// Effect implementation
69
71{
72 const float ratio = DB_TO_LINEAR(
73 (mNormalizeTo == kLoudness) ? // LU use 10*log10(...) instead of
74 // 20*log10(...) so multiply level by 2
75 std::clamp<double>(
76 mLUFSLevel * 2, LUFSLevel.min, LUFSLevel.max) : // RMS
77 std::clamp<double>(mRMSLevel, RMSLevel.min, RMSLevel.max));
78
79 // Iterate over each track
80 EffectOutputTracks outputs { *mTracks, GetType(), { { mT0, mT1 } } };
81 bool bGoodResult = true;
82 auto topMsg = XO("Normalizing Loudness...\n");
83
84 AllocBuffers(outputs.Get());
85 mProgressVal = 0;
86
87 for (auto pTrack : outputs.Get().Selected<WaveTrack>())
88 {
89 // Get start and end times from track
90 double trackStart = pTrack->GetStartTime();
91 double trackEnd = pTrack->GetEndTime();
92
93 // Set the current bounds to whichever left marker is
94 // greater and whichever right marker is less:
95 const double curT0 = std::max(trackStart, mT0);
96 const double curT1 = std::min(trackEnd, mT1);
97
98 // Get the track rate
99 mCurRate = pTrack->GetRate();
100
101 wxString msg;
102 auto trackName = pTrack->GetName();
103 // This affects only the progress indicator update during ProcessOne
104 mSteps = (mNormalizeTo == kLoudness) ? 2 : 1;
105
106 mProgressMsg = topMsg + XO("Analyzing: %s").Format(trackName);
107
108 const auto channels = pTrack->Channels();
109 auto nChannels = mStereoInd ? 1 : channels.size();
110 mProcStereo = nChannels > 1;
111
112 const auto processOne = [&](WaveChannel& track) {
113 std::optional<EBUR128> loudnessProcessor;
114 float RMS[2];
115
116 if (mNormalizeTo == kLoudness)
117 {
118 loudnessProcessor.emplace(mCurRate, nChannels);
119 if (!ProcessOne(
120 track, nChannels, curT0, curT1, 0, &*loudnessProcessor))
121 // Processing failed -> abort
122 return false;
123 }
124 else
125 {
126 // RMS
127 if (mProcStereo)
128 {
129 size_t idx = 0;
130 for (const auto pChannel : channels)
131 {
132 if (!GetTrackRMS(*pChannel, curT0, curT1, RMS[idx]))
133 return false;
134 ++idx;
135 }
136 }
137 else
138 {
139 if (!GetTrackRMS(track, curT0, curT1, RMS[0]))
140 return false;
141 }
142 }
143
144 // Calculate normalization values the analysis results
145 float extent;
146 if (mNormalizeTo == kLoudness)
147 extent = loudnessProcessor->IntegrativeLoudness();
148 else
149 {
150 // RMS
151 extent = RMS[0];
152 if (mProcStereo)
153 // RMS: use average RMS, average must be calculated in quadratic
154 // domain.
155 extent = sqrt((RMS[0] * RMS[0] + RMS[1] * RMS[1]) / 2.0);
156 }
157
158 if (extent == 0.0)
159 {
160 FreeBuffers();
161 return false;
162 }
163 float mult = ratio / extent;
164
165 if (mNormalizeTo == kLoudness)
166 {
167 // Target half the LUFS value if mono (or independent processed
168 // stereo) shall be treated as dual mono.
169 if (nChannels == 1 && (mDualMono || !IsMono(track)))
170 mult /= 2.0;
171
172 // LUFS are related to square values so the multiplier must be the
173 // xroot.
174 mult = sqrt(mult);
175 }
176
177 mProgressMsg = topMsg + XO("Processing: %s").Format(trackName);
178 if (!ProcessOne(track, nChannels, curT0, curT1, mult, nullptr))
179 {
180 // Processing failed -> abort
181 return false;
182 }
183 return true;
184 };
185
186 if (mStereoInd)
187 {
188 for (const auto pChannel : channels)
189 if (!(bGoodResult = processOne(*pChannel)))
190 goto done;
191 }
192 else
193 {
194 // processOne captured nChannels which is 2 and is passed to
195 // LoadBufferBlock, StoreBufferBlock which find the track from the
196 // channel and iterate channels
197 if (!(bGoodResult = processOne(**pTrack->Channels().begin())))
198 break;
199 }
200 }
201done:
202
203 if (bGoodResult)
204 outputs.Commit();
205
206 FreeBuffers();
207 return bGoodResult;
208}
209
210// LoudnessBase implementation
211
215{
217 bool stereoTrackFound = false;
218 double maxSampleRate = 0;
219 mProcStereo = false;
220
221 for (auto track : outputs.Selected<WaveTrack>() + &Track::Any)
222 {
224 std::max(mTrackBufferCapacity, track->GetMaxBlockSize());
225 maxSampleRate = std::max(maxSampleRate, track->GetRate());
226
227 // There is a stereo track
228 if (track->NChannels() == 2)
229 stereoTrackFound = true;
230 }
231
232 // Initiate a processing buffer. This buffer will (most likely)
233 // be shorter than the length of the track being processed.
235
236 if (!mStereoInd && stereoTrackFound)
238}
239
241{
242 mTrackBuffer[0].reset();
243 mTrackBuffer[1].reset();
244}
245
247 WaveChannel& track, const double curT0, const double curT1, float& rms)
248{
249 // set mRMS. No progress bar here as it's fast.
250 float _rms = WaveChannelUtilities::GetRMS(track, curT0, curT1); // may throw
251 rms = _rms;
252 return true;
253}
254
262 WaveChannel& track, size_t nChannels, const double curT0, const double curT1,
263 const float mult, EBUR128* pLoudnessProcessor)
264{
265 // Transform the marker timepoints to samples
266 auto start = track.TimeToLongSamples(curT0);
267 auto end = track.TimeToLongSamples(curT1);
268
269 // Get the length of the buffer (as double). len is
270 // used simply to calculate a progress meter, so it is easier
271 // to make it a double now than it is to do it later
272 mTrackLen = (end - start).as_double();
273
274 // Abort if the right marker is not to the right of the left marker
275 if (curT1 <= curT0)
276 return false;
277
278 // Go through the track one buffer at a time. s counts which
279 // sample the current buffer starts at.
280 auto s = start;
281 while (s < end)
282 {
283 // Get a block of samples (smaller than the size of the buffer)
284 // Adjust the block size if it is the final block in the track
285 auto blockLen =
287
288 const size_t remainingLen = (end - s).as_size_t();
289 blockLen = blockLen > remainingLen ? remainingLen : blockLen;
290 LoadBufferBlock(track, nChannels, s, blockLen);
291
292 // Process the buffer.
293 if (pLoudnessProcessor)
294 {
295 if (!AnalyseBufferBlock(*pLoudnessProcessor))
296 return false;
297 }
298 else
299 {
300 if (!ProcessBufferBlock(mult))
301 return false;
302 if (!StoreBufferBlock(track, nChannels, s, blockLen))
303 return false;
304 }
305
306 // Increment s one blockfull of samples
307 s += blockLen;
308 }
309
310 // Return true because the effect processing succeeded ... unless cancelled
311 return true;
312}
313
315 WaveChannel& track, size_t nChannels, sampleCount pos, size_t len)
316{
317 size_t idx = 0;
318 const auto getOne = [&](WaveChannel& channel) {
319 // Get the samples from the track and put them in the buffer
320 channel.GetFloats(mTrackBuffer[idx].get(), pos, len);
321 };
322
323 if (nChannels == 1)
324 getOne(track);
325 else
326 for (const auto channel : track.GetTrack().Channels())
327 {
328 getOne(*channel);
329 ++idx;
330 }
331 mTrackBufferLen = len;
332}
333
337{
338 for (size_t i = 0; i < mTrackBufferLen; i++)
339 {
340 loudnessProcessor.ProcessSampleFromChannel(mTrackBuffer[0][i], 0);
341 if (mProcStereo)
342 loudnessProcessor.ProcessSampleFromChannel(mTrackBuffer[1][i], 1);
343 loudnessProcessor.NextSample();
344 }
345
346 if (!UpdateProgress())
347 return false;
348 return true;
349}
350
352{
353 for (size_t i = 0; i < mTrackBufferLen; i++)
354 {
355 mTrackBuffer[0][i] = mTrackBuffer[0][i] * mult;
356 if (mProcStereo)
357 mTrackBuffer[1][i] = mTrackBuffer[1][i] * mult;
358 }
359
360 if (!UpdateProgress())
361 return false;
362 return true;
363}
364
366 WaveChannel& track, size_t nChannels, sampleCount pos, size_t len)
367{
368 size_t idx = 0;
369 const auto setOne = [&](WaveChannel& channel) {
370 // Copy the newly-changed samples back onto the track.
371 return channel.SetFloats(mTrackBuffer[idx].get(), pos, len);
372 };
373
374 if (nChannels == 1)
375 return setOne(track);
376 else
377 {
378 for (auto channel : track.GetTrack().Channels())
379 {
380 if (!setOne(*channel))
381 return false;
382 ++idx;
383 }
384 return true;
385 }
386}
387
389{
390 mProgressVal +=
391 (double(1 + mProcStereo) * double(mTrackBufferLen) /
392 (double(GetNumWaveTracks()) * double(mSteps) * mTrackLen));
394}
int min(int a, int b)
EffectType
@ EffectTypeProcess
XO("Cut/Copy/Paste")
#define DB_TO_LINEAR(x)
Definition: MemoryX.h:338
size_t limitSampleBufferSize(size_t bufferSize, sampleCount limit)
Definition: SampleCount.cpp:22
void reinit(Integral count, bool initialize=false)
Definition: MemoryX.h:59
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:123
void SetLinearEffectFlag(bool linearEffectFlag)
Definition: EffectBase.cpp:210
std::shared_ptr< TrackList > mTracks
Definition: EffectBase.h:116
double mT0
Definition: EffectBase.h:122
bool TotalProgress(double frac, const TranslatableString &={}) const
Definition: Effect.cpp:335
int GetNumWaveTracks() const
Definition: Effect.h:139
Performs effect computation.
Use this object to copy the input tracks to tentative outputTracks.
Interface for manipulations of an Effect's settings.
virtual void Reset(Effect &effect) const =0
static constexpr EffectParameter LUFSLevel
Definition: LoudnessBase.h:95
bool UpdateProgress()
Floats mTrackBuffer[2]
Definition: LoudnessBase.h:84
bool Process(EffectInstance &instance, EffectSettings &settings) override
bool mProcStereo
Definition: LoudnessBase.h:88
void LoadBufferBlock(WaveChannel &track, size_t nChannels, sampleCount pos, size_t len)
double mProgressVal
Definition: LoudnessBase.h:78
double mLUFSLevel
Definition: LoudnessBase.h:73
const EffectParameterMethods & Parameters() const override
EffectType GetType() const override
Type determines how it behaves.
TranslatableString GetDescription() const override
ManualPageID ManualPage() const override
Name of a page in the Audacity alpha manual, default is empty.
void AllocBuffers(TrackList &outputs)
static constexpr EffectParameter StereoInd
Definition: LoudnessBase.h:92
static const ComponentInterfaceSymbol Symbol
Definition: LoudnessBase.h:34
double mCurRate
Definition: LoudnessBase.h:82
TranslatableString mProgressMsg
Definition: LoudnessBase.h:80
bool AnalyseBufferBlock(EBUR128 &loudnessProcessor)
ComponentInterfaceSymbol GetSymbol() const override
static constexpr EffectParameter DualMono
Definition: LoudnessBase.h:101
static bool GetTrackRMS(WaveChannel &track, double curT0, double curT1, float &rms)
double mTrackLen
Definition: LoudnessBase.h:81
bool StoreBufferBlock(WaveChannel &track, size_t nChannels, sampleCount pos, size_t len)
static constexpr EffectParameter NormalizeTo
Definition: LoudnessBase.h:104
size_t mTrackBufferCapacity
Definition: LoudnessBase.h:87
void FreeBuffers()
size_t mTrackBufferLen
Definition: LoudnessBase.h:86
static constexpr EffectParameter RMSLevel
Definition: LoudnessBase.h:98
bool ProcessOne(WaveChannel &track, size_t nChannels, double curT0, double curT1, float mult, EBUR128 *pLoudnessProcessor)
bool ProcessBufferBlock(float mult)
virtual ~LoudnessBase()
double mRMSLevel
Definition: LoudnessBase.h:74
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.
WaveTrack & GetTrack()
Definition: WaveTrack.h:841
size_t GetBestBlockSize(sampleCount t) const
A hint for sizing of well aligned fetches.
Definition: WaveTrack.h:851
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)
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.