mirror of
https://github.com/cookiengineer/audacity
synced 2025-06-16 08:09:32 +02:00
Check in new Truncate Silence routine as Experimental
This commit is contained in:
parent
3d76f55cad
commit
6860fca483
@ -148,6 +148,8 @@ extern void AddPages( AudacityProject * pProj, GuiFactory & Factory, wxNotebo
|
|||||||
//#define AUTOMATED_INPUT_LEVEL_ADJUSTMENT
|
//#define AUTOMATED_INPUT_LEVEL_ADJUSTMENT
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
// AWD: new Truncate Silence code
|
||||||
|
//#define EXPERIMENTAL_TRUNC_SILENCE
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -26,12 +26,19 @@
|
|||||||
#include "../Audacity.h"
|
#include "../Audacity.h"
|
||||||
|
|
||||||
#include <wx/wx.h>
|
#include <wx/wx.h>
|
||||||
|
#include <wx/list.h>
|
||||||
|
#include <wx/listimpl.cpp>
|
||||||
|
#include <limits>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "../Experimental.h"
|
||||||
#include "../Prefs.h"
|
#include "../Prefs.h"
|
||||||
#include "../Project.h"
|
#include "../Project.h"
|
||||||
|
#include "../WaveTrack.h"
|
||||||
#include "TruncSilence.h"
|
#include "TruncSilence.h"
|
||||||
|
|
||||||
|
WX_DEFINE_LIST(RegionList);
|
||||||
|
|
||||||
EffectTruncSilence::EffectTruncSilence()
|
EffectTruncSilence::EffectTruncSilence()
|
||||||
{
|
{
|
||||||
Init();
|
Init();
|
||||||
@ -110,6 +117,7 @@ bool EffectTruncSilence::TransferParameters( Shuttle & shuttle )
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifndef EXPERIMENTAL_TRUNC_SILENCE
|
||||||
#define QUARTER_SECOND_MS 250
|
#define QUARTER_SECOND_MS 250
|
||||||
bool EffectTruncSilence::Process()
|
bool EffectTruncSilence::Process()
|
||||||
{
|
{
|
||||||
@ -442,6 +450,293 @@ bool EffectTruncSilence::Process()
|
|||||||
return !cancelled;
|
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<double>::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)
|
void EffectTruncSilence::BlendFrames(float* buffer, int blendFrameCount, int leftIndex, int rightIndex)
|
||||||
{
|
{
|
||||||
float* bufOutput = &buffer[leftIndex];
|
float* bufOutput = &buffer[leftIndex];
|
||||||
|
@ -18,6 +18,14 @@
|
|||||||
#define __AUDACITY_EFFECT_TRUNC_SILENCE__
|
#define __AUDACITY_EFFECT_TRUNC_SILENCE__
|
||||||
|
|
||||||
#include "Effect.h"
|
#include "Effect.h"
|
||||||
|
#include "../Experimental.h"
|
||||||
|
|
||||||
|
#include <wx/list.h>
|
||||||
|
|
||||||
|
// Declaration of RegionList
|
||||||
|
struct REGION;
|
||||||
|
typedef struct REGION Region;
|
||||||
|
WX_DECLARE_LIST(Region, RegionList);
|
||||||
|
|
||||||
class EffectTruncSilence: public Effect {
|
class EffectTruncSilence: public Effect {
|
||||||
|
|
||||||
@ -53,6 +61,9 @@ public:
|
|||||||
private:
|
private:
|
||||||
//ToDo ... put BlendFrames in Effects, Project, or other class
|
//ToDo ... put BlendFrames in Effects, Project, or other class
|
||||||
void BlendFrames(float* buffer, int leftIndex, int rightIndex, int blendFrameCount);
|
void BlendFrames(float* buffer, int leftIndex, int rightIndex, int blendFrameCount);
|
||||||
|
#ifdef EXPERIMENTAL_TRUNC_SILENCE
|
||||||
|
void Intersect(RegionList &dest, const RegionList &src);
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
sampleCount mBlendFrameCount;
|
sampleCount mBlendFrameCount;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user