/********************************************************************** Audacity: A Digital Audio Editor TruncSilence.cpp Lynn Allan (from DM's Normalize) *******************************************************************//** \class EffectTruncSilence \brief An Effect. \todo Only works on complete mono track for now \todo mBlendFrameCount only retrieved from prefs ... not using dialog Only way to change (for windows) is thru registry The values should be figured dynamically ... too many frames could be invalid *//****************************************************************//** \class TruncSilenceDialog \brief Dialog used with EffectTruncSilence *//*******************************************************************/ #include "../Audacity.h" #include #include #include "../Prefs.h" #include "../Project.h" #include "TruncSilence.h" EffectTruncSilence::EffectTruncSilence() : mUserPrompted(false) { Init(); } bool EffectTruncSilence::Init() { mTruncLongestAllowedSilentMs = gPrefs->Read(wxT("/CsPresets/TruncLongestAllowedSilentMs"), 200L); if ((mTruncLongestAllowedSilentMs < 0) || (mTruncLongestAllowedSilentMs >= 9999999)) { // corrupted Prefs? mTruncLongestAllowedSilentMs = SKIP_EFFECT_MILLISECOND; gPrefs->Write(wxT("/CsPresets/TruncLongestAllowedSilentMs"), (int)mTruncLongestAllowedSilentMs); } mTruncDbChoiceIndex = gPrefs->Read(wxT("/CsPresets/TruncDbChoiceIndex"), 4L); if ((mTruncDbChoiceIndex < 0) || (mTruncDbChoiceIndex >= Enums::NumDbChoices)) { // corrupted Prefs? mTruncDbChoiceIndex = Enums::NumDbChoices - 1; // Off-Skip gPrefs->Write(wxT("/CsPresets/TruncDbChoiceIndex"), mTruncDbChoiceIndex); mTruncLongestAllowedSilentMs = SKIP_EFFECT_MILLISECOND; gPrefs->Write(wxT("/CsPresets/TruncLongestAllowedSilentMs"), (int)mTruncLongestAllowedSilentMs); } mBlendFrameCount = gPrefs->Read(wxT("/CsPresets/BlendFrameCount"), 100L); if ((mBlendFrameCount < 0) || (mBlendFrameCount >= 5000)) { // corrupted Prefs? mBlendFrameCount = 100; gPrefs->Write(wxT("/CsPresets/BlendFrameCount"), 100); } TrackListIterator iter(mWaveTracks); mTrack = (WaveTrack *) iter.First(); if (mTrack != 0) { int numTracks = GetNumWaveTracks(); if (numTracks != 1) { wxMessageBox(_("Sorry. Truncate Silence only valid with mono track")); return false; } } return true; } bool EffectTruncSilence::CheckWhetherSkipEffect() { bool rc = ((mTruncDbChoiceIndex >= (Enums::NumDbChoices - 1)) || (mTruncLongestAllowedSilentMs >= SKIP_EFFECT_MILLISECOND)); return rc; } void EffectTruncSilence::End() { } bool EffectTruncSilence::PromptUser() { TruncSilenceDialog dlog(mParent, -1, _NoAcc("Truncate Silence...")); dlog.mTruncLongestAllowedSilentMs = mTruncLongestAllowedSilentMs; dlog.mTruncDbChoiceIndex = mTruncDbChoiceIndex; dlog.TransferDataToWindow(); dlog.CentreOnParent(); dlog.ShowModal(); if (!dlog.GetReturnCode()) { return false; } mUserPrompted = true; mTruncLongestAllowedSilentMs = dlog.mTruncLongestAllowedSilentMs; gPrefs->Write(wxT("/CsPresets/TruncLongestAllowedSilentMs"), (long)mTruncLongestAllowedSilentMs); mTruncDbChoiceIndex = dlog.mTruncDbChoiceIndex; gPrefs->Write(wxT("/CsPresets/TruncDbChoiceIndex"), mTruncDbChoiceIndex); return true; } bool EffectTruncSilence::TransferParameters( Shuttle & shuttle ) { shuttle.TransferEnum(wxT("Db"),mTruncDbChoiceIndex,Enums::NumDbChoices,Enums::GetDbChoices()); shuttle.TransferLongLong(wxT("Duration"),mTruncLongestAllowedSilentMs,200); return true; } bool EffectTruncSilence::Process() { ProcessOne(); mUserPrompted = false; return true; } #define QUARTER_SECOND_MS 250 bool EffectTruncSilence::ProcessOne() { float curFrame; float fabsCurFrame; int consecutiveSilentFrames = 0; int samplesCleared = 0; int truncIndex = 0; double curRate = mTrack->GetRate(); int quarterSecondFrames = (int)((curRate * QUARTER_SECOND_MS)/ 1000.0); int truncLongestAllowedSilentSamples = int((mTruncLongestAllowedSilentMs * curRate) / 1000.0); double truncDbSilenceThreshold = Enums::Db2Signal[mTruncDbChoiceIndex]; bool ignoringFrames = false; double selectionEnd = mT1; double trackEnd = mTrack->GetEndTime(); longSampleCount selectionStartLongSampleCount = mTrack->TimeToLongSamples(mT0); longSampleCount selectionEndLongSampleCount = mTrack->TimeToLongSamples(mT1); sampleCount selectionStartSampleCount = selectionStartLongSampleCount; sampleCount selectionEndSampleCount = selectionEndLongSampleCount; sampleCount idealBlockLen = mTrack->GetMaxBlockSize() * 8; // bigger buffer reduces 'reset' sampleCount index = selectionStartSampleCount; sampleCount indexAtLoopStart = index; sampleCount outTrackOffset = selectionStartSampleCount; float *buffer = new float[idealBlockLen]; bool rc; int rampInFrames = (truncLongestAllowedSilentSamples / 4); if (rampInFrames > quarterSecondFrames) { rampInFrames = quarterSecondFrames; } while (index < selectionEndSampleCount) { rc = mTrack->Get((samplePtr)buffer, floatSample, index, idealBlockLen); truncIndex = 0; indexAtLoopStart = index; ignoringFrames = false; // may be unneccesary to reset, but safer consecutiveSilentFrames = 0; // may be unneccesary to reset, but safer sampleCount limit = idealBlockLen; if ((index + idealBlockLen) > selectionEndSampleCount) { limit = selectionEndSampleCount - index; } for (sampleCount i = 0; i < limit; ++i) { index++; curFrame = buffer[i]; fabsCurFrame = (float)fabs(curFrame); if (fabsCurFrame < truncDbSilenceThreshold) { consecutiveSilentFrames++; if (consecutiveSilentFrames > truncLongestAllowedSilentSamples) { ignoringFrames = true; continue; // ignore this frame (equivalent to cutting it) } // otherwise, keep sample to be part of allowed silence } else { if (ignoringFrames == true) { sampleCount curOffset = i - rampInFrames; truncIndex -= rampInFrames; // backup into ignored frames wxASSERT((truncIndex < idealBlockLen) && (curOffset > 0) && ((curOffset + rampInFrames) < idealBlockLen)); for (int fr = 0; fr < rampInFrames; ++fr) { buffer[truncIndex] = buffer[curOffset + fr]; truncIndex++; } if(((truncIndex - rampInFrames) - mBlendFrameCount) >= 0) BlendFrames(buffer, mBlendFrameCount, ((truncIndex - rampInFrames) - mBlendFrameCount), ((i - rampInFrames) - mBlendFrameCount)); } consecutiveSilentFrames = 0; ignoringFrames = false; } wxASSERT(truncIndex < idealBlockLen); buffer[truncIndex] = curFrame; // Can get here either because > dbThreshold truncIndex++; // or silence duration isn't longer than allowed } samplesCleared += (limit - truncIndex); rc = mTrack->Set((samplePtr)buffer, floatSample, outTrackOffset, truncIndex); outTrackOffset += truncIndex; bool cancellingFlag = TrackProgress(0, ((double)index / (double)selectionEndSampleCount)); if (cancellingFlag == true) { delete [] buffer; return false; } } double lenToCut = (samplesCleared / curRate); double middle = selectionEnd - lenToCut; mTrack->Cut(middle, selectionEnd, (Track**)&mTrack); mT0 = 0.0; mT1 = trackEnd - lenToCut; if (mUserPrompted == true) { AudacityProject *proj = GetActiveProject(); proj->OnZoomFit(); } gPrefs->Write(wxT("/Validate/TruncSamplesCleared"), samplesCleared); delete [] buffer; return true; } void EffectTruncSilence::BlendFrames(float* buffer, int blendFrameCount, int leftIndex, int rightIndex) { float* bufOutput = &buffer[leftIndex]; float* bufBefore = &buffer[leftIndex]; float* bufAfter = &buffer[rightIndex]; double beforeFactor = 1.0; double afterFactor = 0.0; double adjFactor = 1.0 / (double)blendFrameCount; for (int j = 0; j < blendFrameCount; ++j) { bufOutput[j] = (float)((bufBefore[j] * beforeFactor) + (bufAfter[j] * afterFactor)); beforeFactor -= adjFactor; afterFactor += adjFactor; } } //---------------------------------------------------------------------------- // TruncSilenceDialog //---------------------------------------------------------------------------- #define ID_LONGEST_SILENCE_TEXT 7000 #define ID_DB_SILENCE_THRESHOLD_CHOICE 7001 BEGIN_EVENT_TABLE(TruncSilenceDialog,wxDialog) EVT_BUTTON( wxID_OK, TruncSilenceDialog::OnOk ) EVT_BUTTON( wxID_CANCEL, TruncSilenceDialog::OnCancel ) END_EVENT_TABLE() TruncSilenceDialog::TruncSilenceDialog(wxWindow *parent, wxWindowID id, const wxString &title) : wxDialog( parent, id, title ) { wxBoxSizer *mainSizer = new wxBoxSizer(wxVERTICAL); wxStaticText *statText = new wxStaticText(this, -1, _("Truncate Silence by Lynn Allan")); mainSizer->Add(statText, 0, wxALIGN_CENTRE | wxALL, 5); wxBoxSizer *hSizer = new wxBoxSizer(wxHORIZONTAL); statText = new wxStaticText(this, -1, _("Max Silent Duration (milliseconds): \n(99999 or greater is off)")); hSizer->Add(statText, 0, wxALIGN_CENTRE | wxALL, 5); wxString truncLongestAllowedSilentMsStr; truncLongestAllowedSilentMsStr.Printf(wxT("%d"), mTruncLongestAllowedSilentMs); mTruncLongestAllowedSilentMsText = new wxTextCtrl(this, ID_LONGEST_SILENCE_TEXT, truncLongestAllowedSilentMsStr, wxDefaultPosition, wxSize(60, -1), 0, wxTextValidator(wxFILTER_NUMERIC)); hSizer->Add(mTruncLongestAllowedSilentMsText, 0, wxALL, 5); mainSizer->Add(hSizer, 0, wxALIGN_CENTRE | wxALL, 5); hSizer = new wxBoxSizer(wxHORIZONTAL); statText = new wxStaticText(this, -1, _("Theshold for silence: ")); hSizer->Add(statText, 0, wxALIGN_CENTRE | wxALL, 5); mTruncDbSilenceThresholdChoice = new wxChoice(this, ID_DB_SILENCE_THRESHOLD_CHOICE, wxDefaultPosition, wxSize(64, -1), Enums::NumDbChoices,Enums::GetDbChoices()); hSizer->Add(mTruncDbSilenceThresholdChoice, 0, wxALIGN_CENTER | wxALL, 4); mainSizer->Add(hSizer, 0, wxALIGN_CENTRE | wxALL, 5); hSizer = new wxBoxSizer(wxHORIZONTAL); wxButton *cancel = new wxButton(this, wxID_CANCEL, _("&Cancel")); hSizer->Add(cancel, 0, wxALIGN_CENTRE|wxALL, 5); wxButton *ok = new wxButton(this, wxID_OK, _("OK")); ok->SetDefault(); hSizer->Add(ok, 0, wxALIGN_CENTRE|wxALL, 5); mainSizer->Add(hSizer, 0, wxALIGN_CENTRE|wxALIGN_CENTER_VERTICAL|wxALL, 5); SetAutoLayout(true); SetSizer(mainSizer); mainSizer->Fit(this); mainSizer->SetSizeHints(this); } bool TruncSilenceDialog::TransferDataToWindow() { mTruncLongestAllowedSilentMsText->SetValue(wxString::Format(wxT("%d"), mTruncLongestAllowedSilentMs)); mTruncDbSilenceThresholdChoice->SetSelection(mTruncDbChoiceIndex); return true; } bool TruncSilenceDialog::TransferDataFromWindow() { long ms; mTruncLongestAllowedSilentMsText->GetValue().ToLong(&ms); mTruncLongestAllowedSilentMs = ms; mTruncDbChoiceIndex = mTruncDbSilenceThresholdChoice->GetSelection(); return true; } void TruncSilenceDialog::OnOk(wxCommandEvent &event) { TransferDataFromWindow(); EndModal(true); } void TruncSilenceDialog::OnCancel(wxCommandEvent &event) { EndModal(false); }