Audacity  2.2.2
Normalize.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 *******************************************************************//*******************************************************************/
16 
17 
18 #include "../Audacity.h" // for rint from configwin.h
19 #include "Normalize.h"
20 
21 #include <math.h>
22 
23 #include <wx/intl.h>
24 #include <wx/valgen.h>
25 
26 #include "../Internat.h"
27 #include "../Prefs.h"
28 #include "../ShuttleGui.h"
29 #include "../WaveTrack.h"
30 #include "../widgets/valnum.h"
31 
32 // Define keys, defaults, minimums, and maximums for the effect parameters
33 //
34 // Name Type Key Def Min Max Scale
35 Param( Level, double, wxT("Level"), -1.0, -145.0, 0.0, 1 );
36 Param( RemoveDC, bool, wxT("RemoveDcOffset"), true, false, true, 1 );
37 Param( ApplyGain, bool, wxT("ApplyGain"), true, false, true, 1 );
38 Param( StereoInd, bool, wxT("StereoIndependent"), false, false, true, 1 );
39 
40 BEGIN_EVENT_TABLE(EffectNormalize, wxEvtHandler)
41  EVT_CHECKBOX(wxID_ANY, EffectNormalize::OnUpdateUI)
42  EVT_TEXT(wxID_ANY, EffectNormalize::OnUpdateUI)
44 
46 {
47  mLevel = DEF_Level;
48  mDC = DEF_RemoveDC;
49  mGain = DEF_ApplyGain;
50  mStereoInd = DEF_StereoInd;
51 
52  SetLinearEffectFlag(false);
53 }
54 
56 {
57 }
58 
59 // IdentInterface implementation
60 
62 {
64 }
65 
67 {
68  return _("Sets the peak amplitude of one or more tracks");
69 }
70 
72 {
73  return wxT("Normalize");
74 }
75 
76 // EffectIdentInterface implementation
77 
79 {
80  return EffectTypeProcess;
81 }
82 
83 // EffectClientInterface implementation
84 
85 bool EffectNormalize::GetAutomationParameters(EffectAutomationParameters & parms)
86 {
87  parms.Write(KEY_Level, mLevel);
88  parms.Write(KEY_ApplyGain, mGain);
89  parms.Write(KEY_RemoveDC, mDC);
90  parms.Write(KEY_StereoInd, mStereoInd);
91 
92  return true;
93 }
94 
95 bool EffectNormalize::SetAutomationParameters(EffectAutomationParameters & parms)
96 {
97  ReadAndVerifyDouble(Level);
98  ReadAndVerifyBool(ApplyGain);
99  ReadAndVerifyBool(RemoveDC);
100  ReadAndVerifyBool(StereoInd);
101 
102  mLevel = Level;
103  mGain = ApplyGain;
104  mDC = RemoveDC;
105  mStereoInd = StereoInd;
106 
107  return true;
108 }
109 
110 // Effect implementation
111 
113 {
114  return ((mGain == false) && (mDC == false));
115 }
116 
118 {
119  wxString base = wxT("/Effects/Normalize/");
120 
121  // Migrate settings from 2.1.0 or before
122 
123  // Already migrated, so bail
124  if (gPrefs->Exists(base + wxT("Migrated")))
125  {
126  return true;
127  }
128 
129  // Load the old "current" settings
130  if (gPrefs->Exists(base))
131  {
132  int boolProxy = gPrefs->Read(base + wxT("RemoveDcOffset"), 1);
133  mDC = (boolProxy == 1);
134  boolProxy = gPrefs->Read(base + wxT("Normalize"), 1);
135  mGain = (boolProxy == 1);
136  gPrefs->Read(base + wxT("Level"), &mLevel, -1.0);
137  if(mLevel > 0.0) // this should never happen
138  mLevel = -mLevel;
139  boolProxy = gPrefs->Read(base + wxT("StereoIndependent"), 0L);
140  mStereoInd = (boolProxy == 1);
141 
143 
144  // Do not migrate again
145  gPrefs->Write(base + wxT("Migrated"), true);
146  gPrefs->Flush();
147  }
148 
149  return true;
150 }
151 
153 {
154  if (mGain == false && mDC == false)
155  return true;
156 
157  float ratio;
158  if( mGain )
159  // same value used for all tracks
160  ratio = DB_TO_LINEAR(TrapDouble(mLevel, MIN_Level, MAX_Level));
161  else
162  ratio = 1.0;
163 
164  //Iterate over each track
165  this->CopyInputTracks(); // Set up mOutputTracks.
166  bool bGoodResult = true;
168  WaveTrack *track = (WaveTrack *) iter.First();
169  WaveTrack *prevTrack;
170  prevTrack = track;
171  int curTrackNum = 0;
172  wxString topMsg;
173  if(mDC && mGain)
174  topMsg = _("Removing DC offset and Normalizing...\n");
175  else if(mDC && !mGain)
176  topMsg = _("Removing DC offset...\n");
177  else if(!mDC && mGain)
178  topMsg = _("Normalizing without removing DC offset...\n");
179  else if(!mDC && !mGain)
180  topMsg = _("Not doing anything...\n"); // shouldn't get here
181 
182  while (track) {
183  //Get start and end times from track
184  double trackStart = track->GetStartTime();
185  double trackEnd = track->GetEndTime();
186 
187  //Set the current bounds to whichever left marker is
188  //greater and whichever right marker is less:
189  mCurT0 = mT0 < trackStart? trackStart: mT0;
190  mCurT1 = mT1 > trackEnd? trackEnd: mT1;
191 
192  // Process only if the right marker is to the right of the left marker
193  if (mCurT1 > mCurT0) {
194  wxString msg;
195  auto trackName = track->GetName();
196 
197  if(!track->GetLinked() || mStereoInd)
198  msg =
199  topMsg + wxString::Format( _("Analyzing: %s"), trackName );
200  else
201  msg =
202  topMsg + wxString::Format( _("Analyzing first track of stereo pair: %s"), trackName );
203  float offset, min, max;
204  bGoodResult = AnalyseTrack(track, msg, curTrackNum, offset, min, max);
205  if (!bGoodResult )
206  break;
207  if(!track->GetLinked() || mStereoInd) {
208  // mono or 'stereo tracks independently'
209  float extent = wxMax(fabs(max), fabs(min));
210  if( (extent > 0) && mGain )
211  mMult = ratio / extent;
212  else
213  mMult = 1.0;
214  msg =
215  topMsg + wxString::Format( _("Processing: %s"), trackName );
216  if(track->GetLinked() || prevTrack->GetLinked()) // only get here if there is a linked track but we are processing independently
217  msg =
218  topMsg + wxString::Format( _("Processing stereo channels independently: %s"), trackName );
219 
220  if (!ProcessOne(track, msg, curTrackNum, offset))
221  {
222  bGoodResult = false;
223  break;
224  }
225  }
226  else
227  {
228  // we have a linked stereo track
229  // so we need to find it's min, max and offset
230  // as they are needed to calc the multiplier for both tracks
231  track = (WaveTrack *) iter.Next(); // get the next one
232  msg =
233  topMsg + wxString::Format( _("Analyzing second track of stereo pair: %s"), trackName );
234  float offset2, min2, max2;
235  bGoodResult = AnalyseTrack(track, msg, curTrackNum + 1, offset2, min2, max2);
236  if ( !bGoodResult )
237  break;
238  float extent = wxMax(fabs(min), fabs(max));
239  extent = wxMax(extent, fabs(min2));
240  extent = wxMax(extent, fabs(max2));
241  if( (extent > 0) && mGain )
242  mMult = ratio / extent; // we need to use this for both linked tracks
243  else
244  mMult = 1.0;
245  track = (WaveTrack *) iter.Prev(); // go back to the first linked one
246  msg =
247  topMsg + wxString::Format( _("Processing first track of stereo pair: %s"), trackName );
248  if (!ProcessOne(track, msg, curTrackNum, offset))
249  {
250  bGoodResult = false;
251  break;
252  }
253  track = (WaveTrack *) iter.Next(); // go to the second linked one
254  curTrackNum++; // keeps progress bar correct
255  msg =
256  topMsg + wxString::Format( _("Processing second track of stereo pair: %s"), trackName );
257  if (!ProcessOne(track, msg, curTrackNum, offset2))
258  {
259  bGoodResult = false;
260  break;
261  }
262  }
263  }
264 
265  //Iterate to the next track
266  prevTrack = track;
267  track = (WaveTrack *) iter.Next();
268  curTrackNum++;
269  }
270 
271  this->ReplaceProcessedTracks(bGoodResult);
272  return bGoodResult;
273 }
274 
276 {
277  mCreating = true;
278 
279  S.StartVerticalLay(0);
280  {
281  S.StartMultiColumn(2, wxALIGN_CENTER);
282  {
283  S.StartVerticalLay(false);
284  {
285  mDCCheckBox = S.AddCheckBox(_("Remove DC offset (center on 0.0 vertically)"),
286  mDC ? wxT("true") : wxT("false"));
287  mDCCheckBox->SetValidator(wxGenericValidator(&mDC));
288 
289  S.StartHorizontalLay(wxALIGN_CENTER, false);
290  {
291  mGainCheckBox = S.AddCheckBox(_("Normalize maximum amplitude to"),
292  mGain ? wxT("true") : wxT("false"));
293  mGainCheckBox->SetValidator(wxGenericValidator(&mGain));
294 
295  FloatingPointValidator<double> vldLevel(2, &mLevel, NUM_VAL_ONE_TRAILING_ZERO);
296  vldLevel.SetRange(MIN_Level, MAX_Level);
297  mLevelTextCtrl = S.AddTextBox( {}, wxT(""), 10);
298  mLevelTextCtrl->SetName(_("Maximum amplitude dB"));
299  mLevelTextCtrl->SetValidator(vldLevel);
300  mLeveldB = S.AddVariableText(_("dB"), false,
301  wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
302  mWarning = S.AddVariableText( {}, false,
303  wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT);
304  }
305  S.EndHorizontalLay();
306 
307 
308  mStereoIndCheckBox = S.AddCheckBox(_("Normalize stereo channels independently"),
309  mStereoInd ? wxT("true") : wxT("false"));
310  mStereoIndCheckBox->SetValidator(wxGenericValidator(&mStereoInd));
311  }
312  S.EndVerticalLay();
313  }
314  S.EndMultiColumn();
315  }
316  S.EndVerticalLay();
317 
318  mCreating = false;
319 }
320 
322 {
323  if (!mUIParent->TransferDataToWindow())
324  {
325  return false;
326  }
327 
328  UpdateUI();
329 
330  return true;
331 }
332 
334 {
335  if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
336  {
337  return false;
338  }
339 
340  return true;
341 }
342 
343 // EffectNormalize implementation
344 
345 bool EffectNormalize::AnalyseTrack(const WaveTrack * track, const wxString &msg,
346  int curTrackNum,
347  float &offset, float &min, float &max)
348 {
349  if(mGain) {
350  // Since we need complete summary data, we need to block until the OD tasks are done for this track
351  // TODO: should we restrict the flags to just the relevant block files (for selections)
352  while (track->GetODFlags()) {
353  // update the gui
355  0, _("Waiting for waveform to finish computing...")) )
356  return false;
357  wxMilliSleep(100);
358  }
359 
360  // set mMin, mMax. No progress bar here as it's fast.
361  auto pair = track->GetMinMax(mCurT0, mCurT1); // may throw
362  min = pair.first, max = pair.second;
363 
364  } else {
365  min = -1.0, max = 1.0; // sensible defaults?
366  }
367 
368  if(mDC) {
369  auto rc = AnalyseDC(track, msg, curTrackNum, offset);
370  min += offset;
371  max += offset;
372  return rc;
373  } else {
374  offset = 0.0;
375  return true;
376  }
377 }
378 
379 //AnalyseDC() takes a track, transforms it to bunch of buffer-blocks,
380 //and executes AnalyzeData on it...
381 bool EffectNormalize::AnalyseDC(const WaveTrack * track, const wxString &msg,
382  int curTrackNum,
383  float &offset)
384 {
385  bool rc = true;
386 
387  offset = 0.0; // we might just return
388 
389  if(!mDC) // don't do analysis if not doing dc removal
390  return(rc);
391 
392  //Transform the marker timepoints to samples
393  auto start = track->TimeToLongSamples(mCurT0);
394  auto end = track->TimeToLongSamples(mCurT1);
395 
396  //Get the length of the buffer (as double). len is
397  //used simply to calculate a progress meter, so it is easier
398  //to make it a double now than it is to do it later
399  auto len = (end - start).as_double();
400 
401  //Initiate a processing buffer. This buffer will (most likely)
402  //be shorter than the length of the track being processed.
403  Floats buffer{ track->GetMaxBlockSize() };
404 
405  mSum = 0.0; // dc offset inits
406  mCount = 0;
407 
408  //Go through the track one buffer at a time. s counts which
409  //sample the current buffer starts at.
410  auto s = start;
411  while (s < end) {
412  //Get a block of samples (smaller than the size of the buffer)
413  //Adjust the block size if it is the final block in the track
414  const auto block = limitSampleBufferSize(
415  track->GetBestBlockSize(s),
416  end - s
417  );
418 
419  //Get the samples from the track and put them in the buffer
420  track->Get((samplePtr) buffer.get(), floatSample, s, block);
421 
422  //Process the buffer.
423  AnalyzeData(buffer.get(), block);
424 
425  //Increment s one blockfull of samples
426  s += block;
427 
428  //Update the Progress meter
429  if (TrackProgress(curTrackNum,
430  ((s - start).as_double() / len)/2.0, msg)) {
431  rc = false; //lda .. break, not return, so that buffer is deleted
432  break;
433  }
434  }
435 
436  offset = -mSum / mCount.as_double(); // calculate actual offset (amount that needs to be added on)
437 
438  //Return true because the effect processing succeeded ... unless cancelled
439  return rc;
440 }
441 
442 //ProcessOne() takes a track, transforms it to bunch of buffer-blocks,
443 //and executes ProcessData, on it...
444 // uses mMult and offset to normalize a track.
445 // mMult must be set before this is called
447  WaveTrack * track, const wxString &msg, int curTrackNum, float offset)
448 {
449  bool rc = true;
450 
451  //Transform the marker timepoints to samples
452  auto start = track->TimeToLongSamples(mCurT0);
453  auto end = track->TimeToLongSamples(mCurT1);
454 
455  //Get the length of the buffer (as double). len is
456  //used simply to calculate a progress meter, so it is easier
457  //to make it a double now than it is to do it later
458  auto len = (end - start).as_double();
459 
460  //Initiate a processing buffer. This buffer will (most likely)
461  //be shorter than the length of the track being processed.
462  Floats buffer{ track->GetMaxBlockSize() };
463 
464  //Go through the track one buffer at a time. s counts which
465  //sample the current buffer starts at.
466  auto s = start;
467  while (s < end) {
468  //Get a block of samples (smaller than the size of the buffer)
469  //Adjust the block size if it is the final block in the track
470  const auto block = limitSampleBufferSize(
471  track->GetBestBlockSize(s),
472  end - s
473  );
474 
475  //Get the samples from the track and put them in the buffer
476  track->Get((samplePtr) buffer.get(), floatSample, s, block);
477 
478  //Process the buffer.
479  ProcessData(buffer.get(), block, offset);
480 
481  //Copy the newly-changed samples back onto the track.
482  track->Set((samplePtr) buffer.get(), floatSample, s, block);
483 
484  //Increment s one blockfull of samples
485  s += block;
486 
487  //Update the Progress meter
488  if (TrackProgress(curTrackNum,
489  0.5+((s - start).as_double() / len)/2.0, msg)) {
490  rc = false; //lda .. break, not return, so that buffer is deleted
491  break;
492  }
493  }
494 
495  //Return true because the effect processing succeeded ... unless cancelled
496  return rc;
497 }
498 
499 void EffectNormalize::AnalyzeData(float *buffer, size_t len)
500 {
501  for(decltype(len) i = 0; i < len; i++)
502  mSum += (double)buffer[i];
503  mCount += len;
504 }
505 
506 void EffectNormalize::ProcessData(float *buffer, size_t len, float offset)
507 {
508  for(decltype(len) i = 0; i < len; i++) {
509  float adjFrame = (buffer[i] + offset) * mMult;
510  buffer[i] = adjFrame;
511  }
512 }
513 
514 void EffectNormalize::OnUpdateUI(wxCommandEvent & WXUNUSED(evt))
515 {
516  UpdateUI();
517 }
518 
520 {
521  if (!mUIParent->TransferDataFromWindow())
522  {
523  mWarning->SetLabel(_(". Maximum 0dB."));
524  EnableApply(false);
525  return;
526  }
527  mWarning->SetLabel(wxT(""));
528 
529  // Disallow level stuff if not normalizing
530  mLevelTextCtrl->Enable(mGain);
531  mLeveldB->Enable(mGain);
532  mStereoIndCheckBox->Enable(mGain);
533 
534  // Disallow OK/Preview if doing nothing
535  EnableApply(mGain || mDC);
536 }
double mT1
Definition: Effect.h:460
bool SaveUserPreset(const wxString &name) override
Definition: Effect.cpp:615
bool TrackProgress(int whichTrack, double frac, const wxString &=wxEmptyString)
Definition: Effect.cpp:1978
bool ProcessOne(WaveTrack *t, const wxString &msg, int curTrackNum, float offset)
Definition: Normalize.cpp:446
void AnalyzeData(float *buffer, size_t len)
Definition: Normalize.cpp:499
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI...
Definition: ShuttleGui.h:366
wxCheckBox * mDCCheckBox
Definition: Normalize.h:86
ProgressDialog * mProgress
Definition: Effect.h:452
bool Get(samplePtr buffer, sampleFormat format, sampleCount start, size_t len, fillFormat fill=fillZero, bool mayThrow=true) const
Definition: WaveTrack.cpp:1950
wxString GetCurrentSettingsGroup() override
Definition: Effect.cpp:814
void CopyInputTracks()
Definition: Effect.cpp:2029
void OnUpdateUI(wxCommandEvent &evt)
Definition: Normalize.cpp:514
void ReplaceProcessedTracks(const bool bGoodResult)
Definition: Effect.cpp:2160
void EndMultiColumn()
unsigned int GetODFlags() const
gets an int with OD flags so that we can determine which ODTasks should be run on this track after sa...
Definition: WaveTrack.cpp:1562
void ProcessData(float *buffer, size_t len, float offset)
Definition: Normalize.cpp:506
bool GetLinked() const
Definition: Track.h:218
bool Startup() override
Definition: Normalize.cpp:117
wxCheckBox * mGainCheckBox
Definition: Normalize.h:85
bool TransferDataFromWindow() override
Definition: Normalize.cpp:333
#define NORMALIZE_PLUGIN_SYMBOL
Definition: Normalize.h:25
Param(Level, double, wxT("Level"),-1.0,-145.0, 0.0, 1)
EffectType GetType() override
Definition: Normalize.cpp:78
double mCurT0
Definition: Normalize.h:79
wxTextCtrl * mLevelTextCtrl
Definition: Normalize.h:87
size_t GetBestBlockSize(sampleCount t) const
Definition: WaveTrack.cpp:1586
wxString GetSymbol() override
Definition: Normalize.cpp:61
void EndHorizontalLay()
Definition: ShuttleGui.cpp:975
void EndVerticalLay()
Definition: ShuttleGui.cpp:991
bool GetAutomationParameters(EffectAutomationParameters &parms) override
Definition: Normalize.cpp:85
wxFileConfig * gPrefs
Definition: Prefs.cpp:72
wxTextCtrl * AddTextBox(const wxString &Caption, const wxString &Value, const int nChars)
Definition: ShuttleGui.cpp:493
wxStaticText * mWarning
Definition: Normalize.h:89
wxCheckBox * AddCheckBox(const wxString &Prompt, const wxString &Selected)
Definition: ShuttleGui.cpp:267
void Set(samplePtr buffer, sampleFormat format, sampleCount start, size_t len)
Definition: WaveTrack.cpp:2027
void StartHorizontalLay(int PositionFlags=wxALIGN_CENTRE, int iProp=1)
Definition: ShuttleGui.cpp:966
bool Process() override
Definition: Normalize.cpp:152
void StartMultiColumn(int nCols, int PositionFlags=wxALIGN_LEFT)
Definition: ShuttleGui.cpp:998
A Track that contains audio waveform data.
Definition: WaveTrack.h:60
An Effect to bring the peak level up to a chosen level.
Definition: Normalize.h:27
ProgressResult Update(int value, const wxString &message=wxEmptyString)
#define ReadAndVerifyDouble(name)
Definition: Effect.h:787
double mLevel
Definition: Normalize.h:74
int min(int a, int b)
size_t GetMaxBlockSize() const
Definition: WaveTrack.cpp:1604
bool AnalyseTrack(const WaveTrack *track, const wxString &msg, int curTrackNum, float &offset, float &min, float &max)
Definition: Normalize.cpp:345
sampleCount mCount
Definition: Normalize.h:83
wxWindow * mUIParent
Definition: Effect.h:471
void PopulateOrExchange(ShuttleGui &S) override
Definition: Normalize.cpp:275
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop"))), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom"))), OnMoveTrack) void TrackMenuTable::OnSetName(wxCommandEvent &)
double TrapDouble(double x, double min, double max)
Definition: Effect.h:724
bool SetAutomationParameters(EffectAutomationParameters &parms) override
Definition: Normalize.cpp:95
sampleCount TimeToLongSamples(double t0) const
Convert correctly between an (absolute) time in seconds and a number of samples.
Definition: WaveTrack.cpp:1822
wxStaticText * mLeveldB
Definition: Normalize.h:88
wxString GetDescription() override
Definition: Normalize.cpp:66
wxStaticText * AddVariableText(const wxString &Str, bool bCenter=false, int PositionFlags=0)
Definition: ShuttleGui.cpp:373
#define ReadAndVerifyBool(name)
Definition: Effect.h:789
double mCurT1
Definition: Normalize.h:80
std::pair< float, float > GetMinMax(double t0, double t1, bool mayThrow=true) const
Definition: WaveTrack.cpp:1877
bool TransferDataToWindow() override
Definition: Normalize.cpp:321
bool CheckWhetherSkipEffect() override
Definition: Normalize.cpp:112
virtual ~EffectNormalize()
Definition: Normalize.cpp:55
END_EVENT_TABLE()
wxCheckBox * mStereoIndCheckBox
Definition: Normalize.h:90
const double MIN_Threshold_Linear DB_TO_LINEAR(MIN_Threshold_dB)
std::shared_ptr< TrackList > mOutputTracks
Definition: Effect.h:458
bool AnalyseDC(const WaveTrack *track, const wxString &msg, int curTrackNum, float &offset)
Definition: Normalize.cpp:381
virtual bool EnableApply(bool enable=true)
Definition: Effect.cpp:1879
double mT0
Definition: Effect.h:459
wxString ManualPage() override
Definition: Normalize.cpp:71
void StartVerticalLay(int iProp=1)
Definition: ShuttleGui.cpp:982