/********************************************************************** Audacity: A Digital Audio Editor Normalize.cpp Dominic Mazzoni Vaughan Johnson (Preview) *******************************************************************//** \class EffectNormalize \brief An Effect to bring the peak level up to a chosen level. *//*******************************************************************/ #include "../Audacity.h" // for rint from configwin.h #include #include #include #include "../Internat.h" #include "../Prefs.h" #include "../ShuttleGui.h" #include "../WaveTrack.h" #include "../widgets/valnum.h" #include "Normalize.h" // Define keys, defaults, minimums, and maximums for the effect parameters // // Name Type Key Def Min Max Scale Param( Level, double, XO("Level"), -1.0, -145.0, 0.0, 1 ); Param( RemoveDC, bool, XO("RemoveDcOffset"), true, false, true, 1 ); Param( ApplyGain, bool, XO("ApplyGain"), true, false, true, 1 ); Param( StereoInd, bool, XO("StereoIndependent"), false, false, true, 1 ); BEGIN_EVENT_TABLE(EffectNormalize, wxEvtHandler) EVT_CHECKBOX(wxID_ANY, EffectNormalize::OnUpdateUI) EVT_TEXT(wxID_ANY, EffectNormalize::OnUpdateUI) END_EVENT_TABLE() EffectNormalize::EffectNormalize() { mLevel = DEF_Level; mDC = DEF_RemoveDC; mGain = DEF_ApplyGain; mStereoInd = DEF_StereoInd; SetLinearEffectFlag(false); } EffectNormalize::~EffectNormalize() { } // IdentInterface implementation wxString EffectNormalize::GetSymbol() { return NORMALIZE_PLUGIN_SYMBOL; } wxString EffectNormalize::GetDescription() { return XO("Sets the peak amplitude of a one or more tracks"); } // EffectIdentInterface implementation EffectType EffectNormalize::GetType() { return EffectTypeProcess; } // EffectClientInterface implementation bool EffectNormalize::GetAutomationParameters(EffectAutomationParameters & parms) { parms.Write(KEY_Level, mLevel); parms.Write(KEY_ApplyGain, mGain); parms.Write(KEY_RemoveDC, mDC); parms.Write(KEY_StereoInd, mStereoInd); return true; } bool EffectNormalize::SetAutomationParameters(EffectAutomationParameters & parms) { ReadAndVerifyDouble(Level); ReadAndVerifyBool(ApplyGain); ReadAndVerifyBool(RemoveDC); ReadAndVerifyBool(StereoInd); mLevel = Level; mGain = ApplyGain; mDC = RemoveDC; mStereoInd = StereoInd; return true; } // Effect implementation bool EffectNormalize::CheckWhetherSkipEffect() { return ((mGain == false) && (mDC == false)); } bool EffectNormalize::Startup() { wxString base = wxT("/Effects/Normalize/"); // Migrate settings from 2.1.0 or before // Already migrated, so bail if (gPrefs->Exists(base + wxT("Migrated"))) { return true; } // Load the old "current" settings if (gPrefs->Exists(base)) { int boolProxy = gPrefs->Read(base + wxT("RemoveDcOffset"), 1); mDC = (boolProxy == 1); boolProxy = gPrefs->Read(base + wxT("Normalize"), 1); mGain = (boolProxy == 1); gPrefs->Read(base + wxT("Level"), &mLevel, -1.0); if(mLevel > 0.0) // this should never happen mLevel = -mLevel; boolProxy = gPrefs->Read(base + wxT("StereoIndependent"), 0L); mStereoInd = (boolProxy == 1); SaveUserPreset(GetCurrentSettingsGroup()); // Do not migrate again gPrefs->Write(base + wxT("Migrated"), true); gPrefs->Flush(); } return true; } bool EffectNormalize::Process() { if (mGain == false && mDC == false) return true; float ratio; if( mGain ) // same value used for all tracks ratio = DB_TO_LINEAR(TrapDouble(mLevel, MIN_Level, MAX_Level)); else ratio = 1.0; //Iterate over each track this->CopyInputTracks(); // Set up mOutputTracks. bool bGoodResult = true; SelectedTrackListOfKindIterator iter(Track::Wave, mOutputTracks); WaveTrack *track = (WaveTrack *) iter.First(); WaveTrack *prevTrack; prevTrack = track; mCurTrackNum = 0; wxString topMsg; if(mDC && mGain) topMsg = _("Removing DC offset and Normalizing...\n"); else if(mDC && !mGain) topMsg = _("Removing DC offset...\n"); else if(!mDC && mGain) topMsg = _("Normalizing without removing DC offset...\n"); else if(!mDC && !mGain) topMsg = wxT("Not doing anything)...\n"); // shouldn't get here while (track) { //Get start and end times from track double trackStart = track->GetStartTime(); double trackEnd = track->GetEndTime(); //Set the current bounds to whichever left marker is //greater and whichever right marker is less: mCurT0 = mT0 < trackStart? trackStart: mT0; mCurT1 = mT1 > trackEnd? trackEnd: mT1; // Process only if the right marker is to the right of the left marker if (mCurT1 > mCurT0) { wxString msg; wxString trackName = track->GetName(); if(!track->GetLinked() || mStereoInd) msg = topMsg + _("Analyzing: ") + trackName; else msg = topMsg + _("Analyzing first track of stereo pair: ") + trackName; AnalyseTrack(track, msg); // sets mOffset and offset-adjusted mMin and mMax if(!track->GetLinked() || mStereoInd) { // mono or 'stereo tracks independently' float extent = wxMax(fabs(mMax), fabs(mMin)); if( (extent > 0) && mGain ) mMult = ratio / extent; else mMult = 1.0; msg = topMsg + _("Processing: ") + trackName; if(track->GetLinked() || prevTrack->GetLinked()) // only get here if there is a linked track but we are processing independently msg = topMsg + _("Processing stereo channels independently: ") + trackName; if (!ProcessOne(track, msg)) { bGoodResult = false; break; } } else { // we have a linked stereo track // so we need to find it's min, max and offset // as they are needed to calc the multiplier for both tracks float offset1 = mOffset; // remember ones from first track float min1 = mMin; float max1 = mMax; track = (WaveTrack *) iter.Next(); // get the next one mCurTrackNum++; // keeps progress bar correct msg = topMsg + _("Analyzing second track of stereo pair: ") + trackName; AnalyseTrack(track, msg); // sets mOffset and offset-adjusted mMin and mMax float offset2 = mOffset; // ones for second track float min2 = mMin; float max2 = mMax; float extent = wxMax(fabs(min1), fabs(max1)); extent = wxMax(extent, fabs(min2)); extent = wxMax(extent, fabs(max2)); if( (extent > 0) && mGain ) mMult = ratio / extent; // we need to use this for both linked tracks else mMult = 1.0; mOffset = offset1; track = (WaveTrack *) iter.Prev(); // go back to the first linked one mCurTrackNum--; // keeps progress bar correct msg = topMsg + _("Processing first track of stereo pair: ") + trackName; if (!ProcessOne(track, msg)) { bGoodResult = false; break; } mOffset = offset2; track = (WaveTrack *) iter.Next(); // go to the second linked one mCurTrackNum++; // keeps progress bar correct msg = topMsg + _("Processing second track of stereo pair: ") + trackName; if (!ProcessOne(track, msg)) { bGoodResult = false; break; } } } //Iterate to the next track prevTrack = track; track = (WaveTrack *) iter.Next(); mCurTrackNum++; } this->ReplaceProcessedTracks(bGoodResult); return bGoodResult; } void EffectNormalize::PopulateOrExchange(ShuttleGui & S) { mCreating = true; S.StartVerticalLay(0); { S.StartMultiColumn(2, wxALIGN_CENTER); { S.StartVerticalLay(false); { mDCCheckBox = S.AddCheckBox(_("Remove DC offset (center on 0.0 vertically)"), mDC ? wxT("true") : wxT("false")); mDCCheckBox->SetValidator(wxGenericValidator(&mDC)); S.StartHorizontalLay(wxALIGN_CENTER, false); { mGainCheckBox = S.AddCheckBox(_("Normalize maximum amplitude to"), mGain ? wxT("true") : wxT("false")); mGainCheckBox->SetValidator(wxGenericValidator(&mGain)); FloatingPointValidator vldLevel(2, &mLevel, NUM_VAL_ONE_TRAILING_ZERO); vldLevel.SetRange(MIN_Level, MAX_Level); mLevelTextCtrl = S.AddTextBox(wxT(""), wxT(""), 10); mLevelTextCtrl->SetName(_("Maximum amplitude dB")); mLevelTextCtrl->SetValidator(vldLevel); mLeveldB = S.AddVariableText(_("dB"), false, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT); mWarning = S.AddVariableText( wxT(""), false, wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT); } S.EndHorizontalLay(); mStereoIndCheckBox = S.AddCheckBox(_("Normalize stereo channels independently"), mStereoInd ? wxT("true") : wxT("false")); mStereoIndCheckBox->SetValidator(wxGenericValidator(&mStereoInd)); } S.EndVerticalLay(); } S.EndMultiColumn(); } S.EndVerticalLay(); mCreating = false; } bool EffectNormalize::TransferDataToWindow() { if (!mUIParent->TransferDataToWindow()) { return false; } UpdateUI(); return true; } bool EffectNormalize::TransferDataFromWindow() { if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow()) { return false; } return true; } // EffectNormalize implementation void EffectNormalize::AnalyseTrack(WaveTrack * track, wxString msg) { if(mGain) { // Since we need complete summary data, we need to block until the OD tasks are done for this track // TODO: should we restrict the flags to just the relevant block files (for selections) while (track->GetODFlags()) { // update the gui mProgress->Update(0, wxT("Waiting for waveform to finish computing...")); wxMilliSleep(100); } track->GetMinMax(&mMin, &mMax, mCurT0, mCurT1); // set mMin, mMax. No progress bar here as it's fast. } else { mMin = -1.0, mMax = 1.0; // sensible defaults? } if(mDC) { AnalyseDC(track, msg); // sets mOffset mMin += mOffset; mMax += mOffset; } else { mOffset = 0.0; } } //AnalyseDC() takes a track, transforms it to bunch of buffer-blocks, //and executes AnalyzeData on it... // sets mOffset bool EffectNormalize::AnalyseDC(WaveTrack * track, wxString msg) { bool rc = true; sampleCount s; mOffset = 0.0; // we might just return if(!mDC) // don't do analysis if not doing dc removal return(rc); //Transform the marker timepoints to samples sampleCount start = track->TimeToLongSamples(mCurT0); sampleCount end = track->TimeToLongSamples(mCurT1); //Get the length of the buffer (as double). len is //used simply to calculate a progress meter, so it is easier //to make it a double now than it is to do it later double len = (double)(end - start); //Initiate a processing buffer. This buffer will (most likely) //be shorter than the length of the track being processed. float *buffer = new float[track->GetMaxBlockSize()]; mSum = 0.0; // dc offset inits mCount = 0; //Go through the track one buffer at a time. s counts which //sample the current buffer starts at. s = start; while (s < end) { //Get a block of samples (smaller than the size of the buffer) sampleCount block = track->GetBestBlockSize(s); //Adjust the block size if it is the final block in the track if (s + block > end) block = end - s; //Get the samples from the track and put them in the buffer track->Get((samplePtr) buffer, floatSample, s, block); //Process the buffer. AnalyzeData(buffer, block); //Increment s one blockfull of samples s += block; //Update the Progress meter if (TrackProgress(mCurTrackNum, ((double)(s - start) / len)/2.0, msg)) { rc = false; //lda .. break, not return, so that buffer is deleted break; } } //Clean up the buffer delete[] buffer; mOffset = (float)(-mSum / mCount); // calculate actual offset (amount that needs to be added on) //Return true because the effect processing succeeded ... unless cancelled return rc; } //ProcessOne() takes a track, transforms it to bunch of buffer-blocks, //and executes ProcessData, on it... // uses mMult and mOffset to normalize a track. Needs to have them set before being called bool EffectNormalize::ProcessOne(WaveTrack * track, wxString msg) { bool rc = true; sampleCount s; //Transform the marker timepoints to samples sampleCount start = track->TimeToLongSamples(mCurT0); sampleCount end = track->TimeToLongSamples(mCurT1); //Get the length of the buffer (as double). len is //used simply to calculate a progress meter, so it is easier //to make it a double now than it is to do it later double len = (double)(end - start); //Initiate a processing buffer. This buffer will (most likely) //be shorter than the length of the track being processed. float *buffer = new float[track->GetMaxBlockSize()]; //Go through the track one buffer at a time. s counts which //sample the current buffer starts at. s = start; while (s < end) { //Get a block of samples (smaller than the size of the buffer) sampleCount block = track->GetBestBlockSize(s); //Adjust the block size if it is the final block in the track if (s + block > end) block = end - s; //Get the samples from the track and put them in the buffer track->Get((samplePtr) buffer, floatSample, s, block); //Process the buffer. ProcessData(buffer, block); //Copy the newly-changed samples back onto the track. track->Set((samplePtr) buffer, floatSample, s, block); //Increment s one blockfull of samples s += block; //Update the Progress meter if (TrackProgress(mCurTrackNum, 0.5+((double)(s - start) / len)/2.0, msg)) { rc = false; //lda .. break, not return, so that buffer is deleted break; } } //Clean up the buffer delete[] buffer; //Return true because the effect processing succeeded ... unless cancelled return rc; } void EffectNormalize::AnalyzeData(float *buffer, sampleCount len) { sampleCount i; for(i=0; iTransferDataFromWindow()) { mWarning->SetLabel(_(". Maximum 0dB.")); EnableApply(false); return; } mWarning->SetLabel(wxT("")); // Disallow level stuff if not normalizing mLevelTextCtrl->Enable(mGain); mLeveldB->Enable(mGain); mStereoIndCheckBox->Enable(mGain); // Disallow OK/Preview if doing nothing EnableApply(mGain || mDC); }