From 6860fca483a62104a4e6ee70d003fbfa7e3f9c82 Mon Sep 17 00:00:00 2001 From: BusinessmanProgrammerSteve Date: Sat, 13 Mar 2010 15:21:38 +0000 Subject: [PATCH] Check in new Truncate Silence routine as Experimental --- src/Experimental.h | 2 + src/effects/TruncSilence.cpp | 295 +++++++++++++++++++++++++++++++++++ src/effects/TruncSilence.h | 11 ++ 3 files changed, 308 insertions(+) diff --git a/src/Experimental.h b/src/Experimental.h index b45e4bbe2..da1e3f102 100644 --- a/src/Experimental.h +++ b/src/Experimental.h @@ -148,6 +148,8 @@ extern void AddPages( AudacityProject * pProj, GuiFactory & Factory, wxNotebo //#define AUTOMATED_INPUT_LEVEL_ADJUSTMENT #endif +// AWD: new Truncate Silence code +//#define EXPERIMENTAL_TRUNC_SILENCE #endif diff --git a/src/effects/TruncSilence.cpp b/src/effects/TruncSilence.cpp index a87bd2ff9..0686e462d 100644 --- a/src/effects/TruncSilence.cpp +++ b/src/effects/TruncSilence.cpp @@ -26,12 +26,19 @@ #include "../Audacity.h" #include +#include +#include +#include #include +#include "../Experimental.h" #include "../Prefs.h" #include "../Project.h" +#include "../WaveTrack.h" #include "TruncSilence.h" +WX_DEFINE_LIST(RegionList); + EffectTruncSilence::EffectTruncSilence() { Init(); @@ -110,6 +117,7 @@ bool EffectTruncSilence::TransferParameters( Shuttle & shuttle ) return true; } +#ifndef EXPERIMENTAL_TRUNC_SILENCE #define QUARTER_SECOND_MS 250 bool EffectTruncSilence::Process() { @@ -442,6 +450,293 @@ bool EffectTruncSilence::Process() return !cancelled; } +#else // defined(EXPERIMENTAL_TRUNC_SILENCE) + +// AWD: this is the new version! +bool EffectTruncSilence::Process() +{ + // Copy tracks + this->CopyInputTracks(Track::All); + + // Lower bound on the amount of silence to find at a time -- this avoids + // detecting silence repeatedly in low-frequency sounds. + const float minTruncMs = 1.0f; + double truncDbSilenceThreshold = Enums::Db2Signal[mTruncDbChoiceIndex]; + + // Master list of silent regions; it is responsible for deleting them. + // This list should always be kept in order + RegionList silences; + silences.DeleteContents(true); + + // Start with the whole selection silent + Region *sel = new Region; + sel->start = mT0; + sel->end = mT1; + silences.push_back(sel); + + // Remove non-silent regions in each track + SelectedTrackListOfKindIterator iter(Track::Wave, mTracks); + for (Track *t = iter.First(); t; t = iter.Next()) + { + WaveTrack *wt = (WaveTrack *)t; + + // Smallest silent region to detect in frames + sampleCount minSilenceFrames = + sampleCount((wxMax( mTruncInitialAllowedSilentMs, minTruncMs) * + wt->GetRate()) / 1000.0); + + // + // Scan the track for silences + // + RegionList trackSilences; + trackSilences.DeleteContents(true); + sampleCount blockLen = wt->GetMaxBlockSize(); + sampleCount start = wt->TimeToLongSamples(mT0); + sampleCount end = wt->TimeToLongSamples(mT1); + + // Allocate buffer + float *buffer = new float[blockLen]; + + sampleCount index = start; + sampleCount silentFrames = 0; + while (index < end) { + // Limit size of current block if we've reached the end + sampleCount count = blockLen; + if ((index + count) > end) { + count = end - index; + } + + // Fill buffer + wt->Get((samplePtr)(buffer), floatSample, index, count); + + // Look for silences in current block + for (sampleCount i = 0; i < count; ++i) { + if (fabs(buffer[i] < truncDbSilenceThreshold)) { + ++silentFrames; + } + else { + if (silentFrames >= minSilenceFrames) + { + // Record the silent region + Region *r = new Region; + r->start = wt->LongSamplesToTime(index + i - silentFrames); + r->end = wt->LongSamplesToTime(index + i); + trackSilences.push_back(r); + } + silentFrames = 0; + } + } + + // Next block + index += count; + } + + if (silentFrames >= minSilenceFrames) + { + // Track ended in silence -- record region + Region *r = new Region; + r->start = wt->LongSamplesToTime(index - silentFrames); + r->end = wt->LongSamplesToTime(index); + trackSilences.push_back(r); + } + + // Intersect with the overall silent region list + Intersect(silences, trackSilences); + + delete [] buffer; + } + + // + // Now remove the silent regions from all selected/sync-seletcted tracks + // + + // Loop over detected regions in reverse (so cuts don't change time values + // down the line) + RegionList::reverse_iterator rit; + double totalCutLen = 0.0; // For cutting selection at the end + for (rit = silences.rbegin(); rit != silences.rend(); ++rit) { + Region *r = *rit; + + // Intersection may create regions smaller than allowed; ignore them + if (r->end - r->start < mTruncInitialAllowedSilentMs / 1000.0) + continue; + + // Find new silence length as requested + double inLength = r->end - r->start; + double outLength = wxMin( + mTruncInitialAllowedSilentMs / 1000.0 + (inLength - mTruncInitialAllowedSilentMs / 1000.0) / mSilenceCompressRatio, + mTruncLongestAllowedSilentMs); + double cutLen = inLength - outLength; + totalCutLen += cutLen; + + TrackListIterator iterOut(mOutputTracks); + for (Track *t = iterOut.First(); t; t = iterOut.Next()) + { + if (t->GetKind() == Track::Wave && ( + t->GetSelected() || t->IsSynchroSelected())) + { + // In WaveTracks, clear with a cross-fade + WaveTrack *wt = (WaveTrack *)t; + sampleCount blendFrames = mBlendFrameCount; + double cutStart = (r->start + r->end - cutLen) / 2; + double cutEnd = cutStart + cutLen; + // Round start/end times to frame boundaries + cutStart = wt->LongSamplesToTime(wt->TimeToLongSamples(cutStart)); + cutEnd = wt->LongSamplesToTime(wt->TimeToLongSamples(cutEnd)); + + // Make sure the cross-fade does not affect non-silent frames + if (wt->LongSamplesToTime(blendFrames) > inLength) { + blendFrames = wt->TimeToLongSamples(inLength); + } + + // Perform cross-fade in memory + float *buf1 = new float[blendFrames]; + float *buf2 = new float[blendFrames]; + sampleCount t1 = wt->TimeToLongSamples(cutStart) - blendFrames / 2; + sampleCount t2 = wt->TimeToLongSamples(cutEnd) - blendFrames / 2; + + wt->Get((samplePtr)buf1, floatSample, t1, blendFrames); + wt->Get((samplePtr)buf2, floatSample, t2, blendFrames); + + for (sampleCount i = 0; i < blendFrames; ++i) { + buf1[i] = ((blendFrames-i) * buf1[i] + i * buf2[i]) / + (double)blendFrames; + } + + // Perform the cut + wt->Clear(cutStart, cutEnd); + + // Write cross-faded data + wt->Set((samplePtr)buf1, floatSample, t1, blendFrames); + + delete [] buf1; + delete [] buf2; + } + else if (t->GetSelected() || t->IsSynchroSelected()) + { + // Non-wave tracks: just do a sync adjust + double cutStart = (r->start + r->end - cutLen) / 2; + double cutEnd = cutStart + cutLen; + t->SyncAdjust(cutStart, cutEnd); + } + } + } + + mT1 -= totalCutLen; + + ReplaceProcessedTracks(true); + + return true; +} + +// Finds the intersection of the ordered region lists, stores in dest +void EffectTruncSilence::Intersect(RegionList &dest, const RegionList &src) +{ + RegionList::iterator destIter; + destIter = dest.begin(); + // Any time we reach the end of the dest list we're finished + if (destIter == dest.end()) + return; + Region *curDest = *destIter; + + // Operation: find non-silent regions in src, remove them from dest. + double nsStart = curDest->start; + double nsEnd; + bool lastRun = false; // must run the loop one extra time + + RegionList::const_iterator srcIter = src.begin(); + while (srcIter != src.end() || lastRun) + { + // Don't use curSrc unless lastRun is false! + Region *curSrc; + + if (lastRun) + { + // The last non-silent region extends as far as possible + curSrc = NULL; + nsEnd = std::numeric_limits::max(); + } + else + { + curSrc = *srcIter; + nsEnd = curSrc->start; + } + + if (nsEnd > nsStart) + { + // Increment through dest until we have a region that could be affected + while (curDest->end <= nsStart) { + ++destIter; + if (destIter == dest.end()) + return; + curDest = *destIter; + } + + // Check for splitting dest region in two + if (nsStart > curDest->start && nsEnd < curDest->end) { + // The second region + Region *r = new Region; + r->start = nsEnd; + r->end = curDest->end; + + // The first region + curDest->end = nsStart; + + // Insert second + // AWD: wxList::insert() has a bug causing it to return the wrong + // value; this is a workaround. + RegionList::iterator nextIt(destIter); + ++nextIt; + dest.insert(nextIt, r); + ++destIter; // (now points at the newly-inserted region) + curDest = *destIter; + } + + // Check for truncating the end of dest region + if (nsStart > curDest->start && nsStart < curDest->end && + nsEnd >= curDest->end) + { + curDest->end = nsStart; + + ++destIter; + if (destIter == dest.end()) + return; + curDest = *destIter; + } + + // Check for all dest regions that need to be removed completely + while (nsStart <= curDest->start && nsEnd >= curDest->end) { + destIter = dest.erase(destIter); + if (destIter == dest.end()) + return; + curDest = *destIter; + } + + // Check for truncating the beginning of dest region + if (nsStart <= curDest->start && + nsEnd > curDest->start && nsEnd < curDest->end) + { + curDest->start = nsEnd; + } + } + + if (lastRun) { + // done + lastRun = false; + } + else { + // Next non-silent region starts at the end of this silent region + nsStart = curSrc->end; + ++srcIter; + if (srcIter == src.end()) { + lastRun = true; + } + } + } +} + +#endif // EXPERIMENTAL_TRUNC_SILENCE + void EffectTruncSilence::BlendFrames(float* buffer, int blendFrameCount, int leftIndex, int rightIndex) { float* bufOutput = &buffer[leftIndex]; diff --git a/src/effects/TruncSilence.h b/src/effects/TruncSilence.h index 0b054f54e..aae28bd50 100644 --- a/src/effects/TruncSilence.h +++ b/src/effects/TruncSilence.h @@ -18,6 +18,14 @@ #define __AUDACITY_EFFECT_TRUNC_SILENCE__ #include "Effect.h" +#include "../Experimental.h" + +#include + +// Declaration of RegionList +struct REGION; +typedef struct REGION Region; +WX_DECLARE_LIST(Region, RegionList); class EffectTruncSilence: public Effect { @@ -53,6 +61,9 @@ public: private: //ToDo ... put BlendFrames in Effects, Project, or other class void BlendFrames(float* buffer, int leftIndex, int rightIndex, int blendFrameCount); +#ifdef EXPERIMENTAL_TRUNC_SILENCE + void Intersect(RegionList &dest, const RegionList &src); +#endif private: sampleCount mBlendFrameCount;