Audacity 3.2.0
NormalizeBase.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 Normalize.cpp
6
7 Dominic Mazzoni
8 Vaughan Johnson (Preview)
9
10***********************************************************************/
11#include "NormalizeBase.h"
12#include "EffectOutputTracks.h"
13#include "ShuttleAutomation.h"
15#include "WaveTrack.h"
16#include <cmath>
17
19{
20 static CapturedParameters<
22 parameters;
23 return parameters;
24}
25
27
29{
30 Parameters().Reset(*this);
32}
33
35{
36}
37
38// ComponentInterface implementation
39
41{
42 return Symbol;
43}
44
46{
47 return XO("Sets the peak amplitude of one or more tracks");
48}
49
51{
52 return L"Normalize";
53}
54
55// EffectDefinitionInterface implementation
56
58{
59 return EffectTypeProcess;
60}
61
62// Effect implementation
63
65{
66 return ((mGain == false) && (mDC == false));
67}
68
70{
71 if (mGain == false && mDC == false)
72 return true;
73
74 float ratio;
75 if (mGain)
76 {
77 // same value used for all tracks
78 ratio = DB_TO_LINEAR(
79 std::clamp<double>(mPeakLevel, PeakLevel.min, PeakLevel.max));
80 }
81 else
82 {
83 ratio = 1.0;
84 }
85
86 // Iterate over each track
87 EffectOutputTracks outputs { *mTracks, GetType(), { { mT0, mT1 } } };
88 bool bGoodResult = true;
89 double progress = 0;
90 TranslatableString topMsg;
91 if (mDC && mGain)
92 topMsg = XO("Removing DC offset and Normalizing...\n");
93 else if (mDC && !mGain)
94 topMsg = XO("Removing DC offset...\n");
95 else if (!mDC && mGain)
96 topMsg = XO("Normalizing without removing DC offset...\n");
97 else if (!mDC && !mGain)
98 topMsg = XO("Not doing anything...\n"); // shouldn't get here
99
100 for (auto track : outputs.Get().Selected<WaveTrack>())
101 {
102 // Get start and end times from track
103 double trackStart = track->GetStartTime();
104 double trackEnd = track->GetEndTime();
105
106 // Set the current bounds to whichever left marker is
107 // greater and whichever right marker is less:
108 mCurT0 = std::max(trackStart, mT0);
109 mCurT1 = std::min(trackEnd, mT1);
110
111 // Process only if the right marker is to the right of the left marker
112 if (mCurT1 > mCurT0)
113 {
114 wxString trackName = track->GetName();
115
116 std::vector<float> extents;
117 float maxExtent { std::numeric_limits<float>::lowest() };
118 std::vector<float> offsets;
119
120 const auto channels = track->Channels();
121 // mono or 'stereo tracks independently'
122 const bool oneChannel = (channels.size() == 1 || mStereoInd);
123 auto msg = oneChannel ?
124 topMsg + XO("Analyzing: %s").Format(trackName) :
125 topMsg +
126 // TODO: more-than-two-channels-message
127 XO("Analyzing first track of stereo pair: %s")
128 .Format(trackName);
129
130 const auto progressReport = [&](double fraction) {
131 return !TotalProgress(
132 (progress + fraction / double(2 * GetNumWaveTracks())), msg);
133 };
134
135 // Analysis loop over channels collects offsets and extent
136 for (auto channel : channels)
137 {
138 float offset = 0;
139 float extent = 0;
140 bGoodResult = AnalyseTrack(
141 *channel, progressReport, mGain, mDC, mCurT0, mCurT1, offset,
142 extent);
143 if (!bGoodResult)
144 goto break2;
145 progress += 1.0 / double(2 * GetNumWaveTracks());
146 extents.push_back(extent);
147 maxExtent = std::max(maxExtent, extent);
148 offsets.push_back(offset);
149 // TODO: more-than-two-channels-message
150 if (!oneChannel)
151 msg = topMsg + XO("Analyzing second track of stereo pair: %s")
152 .Format(trackName);
153 }
154
155 if (oneChannel)
156 {
157 if (track->NChannels() == 1)
158 // really mono
159 msg = topMsg + XO("Processing: %s").Format(trackName);
160 else
161 //'stereo tracks independently'
162 // TODO: more-than-two-channels-message
163 msg = topMsg + XO("Processing stereo channels independently: %s")
164 .Format(trackName);
165 }
166 else
167 msg = topMsg +
168 // TODO: more-than-two-channels-message
169 XO("Processing first track of stereo pair: %s")
170 .Format(trackName);
171
172 // Use multiplier in the second, processing loop over channels
173 auto pOffset = offsets.begin();
174 auto pExtent = extents.begin();
175 for (const auto channel : channels)
176 {
177 const auto extent = oneChannel ? *pExtent++ : maxExtent;
178 if ((extent > 0) && mGain)
179 mMult = ratio / extent;
180 else
181 mMult = 1.0;
182 if (
183 false ==
184 (bGoodResult = ProcessOne(*channel, msg, progress, *pOffset++)))
185 goto break2;
186 // TODO: more-than-two-channels-message
187 msg = topMsg + XO("Processing second track of stereo pair: %s")
188 .Format(trackName);
189 }
190 }
191 }
192
193break2:
194
195 if (bGoodResult)
196 outputs.Commit();
197
198 return bGoodResult;
199}
200
202 const WaveChannel& track, const ProgressReport& report, const bool gain,
203 const bool dc, const double curT0, const double curT1, float& offset,
204 float& extent)
205{
206 bool result = true;
207 float min, max;
208 if (gain)
209 {
210 // set mMin, mMax. No progress bar here as it's fast.
211 auto pair =
212 WaveChannelUtilities::GetMinMax(track, curT0, curT1); // may throw
213 min = pair.first, max = pair.second;
214
215 if (dc)
216 {
217 result = AnalyseTrackData(track, report, curT0, curT1, offset);
218 min += offset;
219 max += offset;
220 }
221 }
222 else if (dc)
223 {
224 min = -1.0, max = 1.0; // sensible defaults?
225 result = AnalyseTrackData(track, report, curT0, curT1, offset);
226 min += offset;
227 max += offset;
228 }
229 else
230 {
231 wxFAIL_MSG("Analysing Track when nothing to do!");
232 min = -1.0, max = 1.0; // sensible defaults?
233 offset = 0.0;
234 }
235 extent = fmax(fabs(min), fabs(max));
236 return result;
237}
238
239// AnalyseTrackData() takes a track, transforms it to bunch of buffer-blocks,
240// and executes selected AnalyseOperation on it...
242 const WaveChannel& track, const ProgressReport& report, const double curT0,
243 const double curT1, float& offset)
244{
245 bool rc = true;
246
247 // Transform the marker timepoints to samples
248 auto start = track.TimeToLongSamples(curT0);
249 auto end = track.TimeToLongSamples(curT1);
250
251 // Get the length of the buffer (as double). len is
252 // used simply to calculate a progress meter, so it is easier
253 // to make it a double now than it is to do it later
254 auto len = (end - start).as_double();
255
256 // Initiate a processing buffer. This buffer will (most likely)
257 // be shorter than the length of the track being processed.
258 Floats buffer { track.GetMaxBlockSize() };
259
260 double sum = 0.0; // dc offset inits
261
262 sampleCount blockSamples;
263 sampleCount totalSamples = 0;
264
265 // Go through the track one buffer at a time. s counts which
266 // sample the current buffer starts at.
267 auto s = start;
268 while (s < end)
269 {
270 // Get a block of samples (smaller than the size of the buffer)
271 // Adjust the block size if it is the final block in the track
272 const auto block =
274
275 // Get the samples from the track and put them in the buffer
276 track.GetFloats(
277 buffer.get(), s, block, FillFormat::fillZero, true, &blockSamples);
278 totalSamples += blockSamples;
279
280 // Process the buffer.
281 sum = AnalyseDataDC(buffer.get(), block, sum);
282
283 // Increment s one blockfull of samples
284 s += block;
285
286 // Update the Progress meter
287 if (!report((s - start).as_double() / len))
288 {
289 rc = false; // lda .. break, not return, so that buffer is deleted
290 break;
291 }
292 }
293 if (totalSamples > 0)
294 // calculate actual offset (amount that needs to be added on)
295 offset = -sum / totalSamples.as_double();
296 else
297 offset = 0.0;
298
299 // Return true because the effect processing succeeded ... unless cancelled
300 return rc;
301}
302
303// ProcessOne() takes a track, transforms it to bunch of buffer-blocks,
304// and executes ProcessData, on it...
305// uses mMult and offset to normalize a track.
306// mMult must be set before this is called
308 WaveChannel& track, const TranslatableString& msg, double& progress,
309 float offset)
310{
311 bool rc = true;
312
313 // Transform the marker timepoints to samples
314 auto start = track.TimeToLongSamples(mCurT0);
315 auto end = track.TimeToLongSamples(mCurT1);
316
317 // Get the length of the buffer (as double). len is
318 // used simply to calculate a progress meter, so it is easier
319 // to make it a double now than it is to do it later
320 auto len = (end - start).as_double();
321
322 // Initiate a processing buffer. This buffer will (most likely)
323 // be shorter than the length of the track being processed.
324 Floats buffer { track.GetMaxBlockSize() };
325
326 // Go through the track one buffer at a time. s counts which
327 // sample the current buffer starts at.
328 auto s = start;
329 while (s < end)
330 {
331 // Get a block of samples (smaller than the size of the buffer)
332 // Adjust the block size if it is the final block in the track
333 const auto block =
335
336 // Get the samples from the track and put them in the buffer
337 track.GetFloats(buffer.get(), s, block);
338
339 // Process the buffer.
340 ProcessData(buffer.get(), block, offset);
341
342 // Copy the newly-changed samples back onto the track.
343 if (!track.SetFloats(buffer.get(), s, block))
344 {
345 rc = false;
346 break;
347 }
348
349 // Increment s one blockfull of samples
350 s += block;
351
352 // Update the Progress meter
353 if (TotalProgress(
354 progress + ((s - start).as_double() / len) /
355 double(2 * GetNumWaveTracks()),
356 msg))
357 {
358 rc = false; // lda .. break, not return, so that buffer is deleted
359 break;
360 }
361 }
362 progress += 1.0 / double(2 * GetNumWaveTracks());
363
364 // Return true because the effect processing succeeded ... unless cancelled
365 return rc;
366}
367
369double NormalizeBase::AnalyseDataDC(float* buffer, size_t len, double sum)
370{
371 for (decltype(len) i = 0; i < len; i++)
372 sum += (double)buffer[i];
373 return sum;
374}
375
376void NormalizeBase::ProcessData(float* buffer, size_t len, float offset)
377{
378 for (decltype(len) i = 0; i < len; i++)
379 {
380 float adjFrame = (buffer[i] + offset) * mMult;
381 buffer[i] = adjFrame;
382 }
383}
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
Generates EffectParameterMethods overrides from variadic template arguments.
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
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 StereoInd
Definition: NormalizeBase.h:83
EffectType GetType() const override
Type determines how it behaves.
ManualPageID ManualPage() const override
Name of a page in the Audacity alpha manual, default is empty.
static bool AnalyseTrackData(const WaveChannel &track, const ProgressReport &report, double curT0, double curT1, float &offset)
void ProcessData(float *buffer, size_t len, float offset)
std::function< bool(double fraction)> ProgressReport
Definition: NormalizeBase.h:52
TranslatableString GetDescription() const override
const EffectParameterMethods & Parameters() const override
ComponentInterfaceSymbol GetSymbol() const override
double mPeakLevel
Definition: NormalizeBase.h:63
static constexpr EffectParameter PeakLevel
Definition: NormalizeBase.h:74
static const ComponentInterfaceSymbol Symbol
Definition: NormalizeBase.h:26
static bool AnalyseTrack(const WaveChannel &track, const ProgressReport &report, bool gain, bool dc, double curT0, double curT1, float &offset, float &extent)
static double AnalyseDataDC(float *buffer, size_t len, double sum)
bool ProcessOne(WaveChannel &track, const TranslatableString &msg, double &progress, float offset)
bool CheckWhetherSkipEffect(const EffectSettings &settings) const override
After Init(), tell whether Process() should be skipped.
static constexpr EffectParameter RemoveDC
Definition: NormalizeBase.h:77
bool Process(EffectInstance &instance, EffectSettings &settings) override
virtual ~NormalizeBase()
static constexpr EffectParameter ApplyVolume
Definition: NormalizeBase.h:80
Holds a msgid for the translation catalog; may also bind format arguments.
bool SetFloats(const float *buffer, sampleCount start, size_t len, sampleFormat effectiveFormat=widestSampleFormat)
Random-access assignment of a range of samples.
Definition: WaveTrack.h:162
bool GetFloats(float *buffer, sampleCount start, size_t len, fillFormat fill=FillFormat::fillZero, bool mayThrow=true, sampleCount *pNumWithinClips=nullptr) const
"narrow" overload fetches from the unique channel
Definition: WaveTrack.h:129
size_t GetBestBlockSize(sampleCount t) const
A hint for sizing of well aligned fetches.
Definition: WaveTrack.h:851
size_t GetMaxBlockSize() const
Definition: WaveTrack.h:859
A Track that contains audio waveform data.
Definition: WaveTrack.h:203
sampleCount TimeToLongSamples(double t0) const
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
double as_double() const
Definition: SampleCount.h:46
WAVE_TRACK_API std::pair< float, float > GetMinMax(const WaveChannel &channel, double t0, double t1, bool mayThrow=true)
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
const Type min
Minimum value.
const Type max
Maximum value.
Externalized state of a plug-in.