1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-16 16:10:06 +02:00

Enh52: Truncate Silence has an option to process tracks independently...

When the new checkbox is on, truncate each selected wave track independently.

Refuse to do it if any two selected audio tracks are sync locked to each other.

But label tracks sync-locked with a selected wave track will truncate as
expected.
This commit is contained in:
Paul Licameli 2016-01-21 20:06:40 -05:00
parent 5957032182
commit 513d1c548f
2 changed files with 160 additions and 32 deletions

View File

@ -23,6 +23,8 @@
#include <limits> #include <limits>
#include <math.h> #include <math.h>
#include <wx/checkbox.h>
#include <wx/choice.h>
#include <wx/valgen.h> #include <wx/valgen.h>
#include "../Prefs.h" #include "../Prefs.h"
@ -57,6 +59,7 @@ Param( ActIndex, int, XO("Action"), kTruncate, 0, kNumActions -
Param( Minimum, double, XO("Minimum"), 0.5, 0.001, 10000.0, 1 ); Param( Minimum, double, XO("Minimum"), 0.5, 0.001, 10000.0, 1 );
Param( Truncate, double, XO("Truncate"), 0.5, 0.0, 10000.0, 1 ); Param( Truncate, double, XO("Truncate"), 0.5, 0.0, 10000.0, 1 );
Param( Compress, double, XO("Compress"), 50.0, 0.0, 99.9, 1 ); Param( Compress, double, XO("Compress"), 50.0, 0.0, 99.9, 1 );
Param( Independent, bool, XO("Independent"), false, false, true, 1 );
static const sampleCount DEF_BlendFrameCount = 100; static const sampleCount DEF_BlendFrameCount = 100;
@ -84,6 +87,7 @@ EffectTruncSilence::EffectTruncSilence()
mSilenceCompressPercent = DEF_Compress; mSilenceCompressPercent = DEF_Compress;
mTruncDbChoiceIndex = DEF_DbIndex; mTruncDbChoiceIndex = DEF_DbIndex;
mActionIndex = DEF_ActIndex; mActionIndex = DEF_ActIndex;
mbIndependent = DEF_Independent;
SetLinearEffectFlag(false); SetLinearEffectFlag(false);
@ -130,6 +134,7 @@ bool EffectTruncSilence::GetAutomationParameters(EffectAutomationParameters & pa
parms.Write(KEY_Minimum, mInitialAllowedSilence); parms.Write(KEY_Minimum, mInitialAllowedSilence);
parms.Write(KEY_Truncate, mTruncLongestAllowedSilence); parms.Write(KEY_Truncate, mTruncLongestAllowedSilence);
parms.Write(KEY_Compress, mSilenceCompressPercent); parms.Write(KEY_Compress, mSilenceCompressPercent);
parms.Write(KEY_Independent, mbIndependent);
return true; return true;
} }
@ -145,12 +150,14 @@ bool EffectTruncSilence::SetAutomationParameters(EffectAutomationParameters & pa
ReadAndVerifyDouble(Compress); ReadAndVerifyDouble(Compress);
ReadAndVerifyEnum(DbIndex, mDbChoices); ReadAndVerifyEnum(DbIndex, mDbChoices);
ReadAndVerifyEnum(ActIndex, actions); ReadAndVerifyEnum(ActIndex, actions);
ReadAndVerifyBool(Independent);
mInitialAllowedSilence = Minimum; mInitialAllowedSilence = Minimum;
mTruncLongestAllowedSilence = Truncate; mTruncLongestAllowedSilence = Truncate;
mSilenceCompressPercent = Compress; mSilenceCompressPercent = Compress;
mTruncDbChoiceIndex = DbIndex; mTruncDbChoiceIndex = DbIndex;
mActionIndex = ActIndex; mActionIndex = ActIndex;
mbIndependent = Independent;
// Readjust for 2.1.0 or before // Readjust for 2.1.0 or before
if (mActionIndex >= kNumActions) if (mActionIndex >= kNumActions)
@ -163,7 +170,7 @@ bool EffectTruncSilence::SetAutomationParameters(EffectAutomationParameters & pa
// Effect implementation // Effect implementation
double EffectTruncSilence::CalcPreviewInputLength(double previewLength) double EffectTruncSilence::CalcPreviewInputLength(double /* previewLength */)
{ {
double inputLength = mT1 - mT0; double inputLength = mT1 - mT0;
double minInputLength = inputLength; double minInputLength = inputLength;
@ -182,7 +189,7 @@ double EffectTruncSilence::CalcPreviewInputLength(double previewLength)
int whichTrack = 0; int whichTrack = 0;
for (Track *t = iter.First(); t; t = iter.Next()) { for (Track *t = iter.First(); t; t = iter.Next()) {
WaveTrack *wt = (WaveTrack *)t; WaveTrack *const wt = static_cast<WaveTrack *>(t);
RegionList trackSilences; RegionList trackSilences;
trackSilences.DeleteContents(true); trackSilences.DeleteContents(true);
@ -249,6 +256,83 @@ bool EffectTruncSilence::Startup()
} }
bool EffectTruncSilence::Process() bool EffectTruncSilence::Process()
{
const bool success =
mbIndependent
? ProcessIndependently()
: ProcessAll();
if (success)
ReplaceProcessedTracks(true);
return success;
}
bool EffectTruncSilence::ProcessIndependently()
{
unsigned nGroups = 0;
// Check if it's permissible
{
SelectedTrackListOfKindIterator iter(Track::Wave, mTracks);
for (Track *track = iter.First(); track;
track = iter.Next(true) // skip linked tracks
) {
Track *const link = track->GetLink();
SyncLockedTracksIterator syncIter(mTracks);
for (Track *track2 = syncIter.First(track); track2; track2 = syncIter.Next()) {
if (track2->GetKind() == Track::Wave &&
!(track2 == track || track2 == link) &&
track2->GetSelected()) {
::wxMessageBox(_("When truncating independently, there may only be one selected audio track in each sync-lock group."));
return false;
}
}
++nGroups;
}
}
if (nGroups == 0)
// nothing to do
return true;
// Now do the work
// Copy tracks
CopyInputTracks(Track::All);
double newT1 = 0.0;
{
unsigned iGroup = 0;
SelectedTrackListOfKindIterator iter(Track::Wave, mOutputTracks);
for (Track *track = iter.First(); track;
++iGroup, track = iter.Next(true) // skip linked tracks
) {
Track *const link = track->GetLink();
Track *const last = link ? link : track;
RegionList silences;
silences.DeleteContents(true);
if (!FindSilences(silences, track, last))
return false;
// Treat tracks in the sync lock group only
SyncLockedTracksIterator syncIter(mOutputTracks);
Track *const syncFirst = syncIter.First(track);
double totalCutLen = 0.0;
if (!DoRemoval(silences, iGroup, nGroups, syncFirst, syncIter.Last(), totalCutLen))
return false;
newT1 = std::max(newT1, mT1 - totalCutLen);
}
}
mT1 = newT1;
return true;
}
bool EffectTruncSilence::ProcessAll()
{ {
// Copy tracks // Copy tracks
CopyInputTracks(Track::All); CopyInputTracks(Track::All);
@ -258,6 +342,23 @@ bool EffectTruncSilence::Process()
RegionList silences; RegionList silences;
silences.DeleteContents(true); silences.DeleteContents(true);
SelectedTrackListOfKindIterator iter(Track::Wave, mTracks);
if (FindSilences(silences, iter.First(), iter.Last())) {
TrackListIterator iterOut(mOutputTracks);
double totalCutLen = 0.0;
Track *const first = iterOut.First();
if (DoRemoval(silences, 0, 1, first, iterOut.Last(), totalCutLen)) {
mT1 -= totalCutLen;
return true;
}
}
return false;
}
bool EffectTruncSilence::FindSilences
(RegionList &silences, Track *firstTrack, Track *lastTrack)
{
// Start with the whole selection silent // Start with the whole selection silent
Region *sel = new Region; Region *sel = new Region;
sel->start = mT0; sel->start = mT0;
@ -267,13 +368,15 @@ bool EffectTruncSilence::Process()
// Remove non-silent regions in each track // Remove non-silent regions in each track
SelectedTrackListOfKindIterator iter(Track::Wave, mTracks); SelectedTrackListOfKindIterator iter(Track::Wave, mTracks);
int whichTrack = 0; int whichTrack = 0;
for (Track *t = iter.First(); t; t = iter.Next()) bool lastSeen = false;
for (Track *t = iter.StartWith(firstTrack); !lastSeen && t; t = iter.Next())
{ {
WaveTrack *wt = (WaveTrack *)t; lastSeen = (t == lastTrack);
WaveTrack *const wt = static_cast<WaveTrack *>(t);
// Smallest silent region to detect in frames // Smallest silent region to detect in frames
sampleCount minSilenceFrames = sampleCount minSilenceFrames =
sampleCount(wxMax( mInitialAllowedSilence, DEF_MinTruncMs) * wt->GetRate()); sampleCount(std::max(mInitialAllowedSilence, DEF_MinTruncMs) * wt->GetRate());
// //
// Scan the track for silences // Scan the track for silences
@ -308,6 +411,13 @@ bool EffectTruncSilence::Process()
whichTrack++; whichTrack++;
} }
return true;
}
bool EffectTruncSilence::DoRemoval
(const RegionList &silences, unsigned iGroup, unsigned nGroups, Track *firstTrack, Track *lastTrack,
double &totalCutLen)
{
// //
// Now remove the silent regions from all selected / sync-lock selected tracks. // Now remove the silent regions from all selected / sync-lock selected tracks.
// //
@ -315,14 +425,15 @@ bool EffectTruncSilence::Process()
// Loop over detected regions in reverse (so cuts don't change time values // Loop over detected regions in reverse (so cuts don't change time values
// down the line) // down the line)
int whichReg = 0; int whichReg = 0;
RegionList::reverse_iterator rit; RegionList::const_reverse_iterator rit;
double totalCutLen = 0.0; // For cutting selection at the end
for (rit = silences.rbegin(); rit != silences.rend(); ++rit) for (rit = silences.rbegin(); rit != silences.rend(); ++rit)
{ {
Region *r = *rit; Region *r = *rit;
// Progress dialog and cancellation. Do additional cleanup before return. // Progress dialog and cancellation. Do additional cleanup before return.
if (TotalProgress(detectFrac + (1 - detectFrac) * whichReg / (double)silences.size())) const double frac = detectFrac +
(1 - detectFrac) * (iGroup + whichReg / double(silences.size())) / nGroups;
if (TotalProgress(frac))
{ {
ReplaceProcessedTracks(false); ReplaceProcessedTracks(false);
return false; return false;
@ -340,14 +451,14 @@ bool EffectTruncSilence::Process()
switch (mActionIndex) switch (mActionIndex)
{ {
case kTruncate: case kTruncate:
outLength = wxMin(mTruncLongestAllowedSilence, inLength); outLength = std::min(mTruncLongestAllowedSilence, inLength);
break; break;
case kCompress: case kCompress:
outLength = mInitialAllowedSilence + outLength = mInitialAllowedSilence +
(inLength - mInitialAllowedSilence) * mSilenceCompressPercent / 100.0; (inLength - mInitialAllowedSilence) * mSilenceCompressPercent / 100.0;
break; break;
default: // Not currently used. default: // Not currently used.
outLength = wxMin(mInitialAllowedSilence + outLength = std::min(mInitialAllowedSilence +
(inLength - mInitialAllowedSilence) * mSilenceCompressPercent / 100.0, (inLength - mInitialAllowedSilence) * mSilenceCompressPercent / 100.0,
mTruncLongestAllowedSilence); mTruncLongestAllowedSilence);
} }
@ -356,22 +467,24 @@ bool EffectTruncSilence::Process()
totalCutLen += cutLen; totalCutLen += cutLen;
TrackListIterator iterOut(mOutputTracks); TrackListIterator iterOut(mOutputTracks);
for (Track *t = iterOut.First(); t; t = iterOut.Next()) bool lastSeen = false;
for (Track *t = iterOut.StartWith(firstTrack); t && !lastSeen; t = iterOut.Next())
{ {
lastSeen = (t == lastTrack);
if (!(t->GetSelected() || t->IsSyncLockSelected()))
continue;
// Don't waste time past the end of a track // Don't waste time past the end of a track
if (t->GetEndTime() < r->start) if (t->GetEndTime() < r->start)
{
continue; continue;
}
if (t->GetKind() == Track::Wave && (
t->GetSelected() || t->IsSyncLockSelected()))
{
// In WaveTracks, clear with a cross-fade
WaveTrack *wt = (WaveTrack *)t;
sampleCount blendFrames = mBlendFrameCount;
double cutStart = (r->start + r->end - cutLen) / 2; double cutStart = (r->start + r->end - cutLen) / 2;
double cutEnd = cutStart + cutLen; double cutEnd = cutStart + cutLen;
if (t->GetKind() == Track::Wave)
{
// In WaveTracks, clear with a cross-fade
WaveTrack *const wt = static_cast<WaveTrack*>(t);
sampleCount blendFrames = mBlendFrameCount;
// Round start/end times to frame boundaries // Round start/end times to frame boundaries
cutStart = wt->LongSamplesToTime(wt->TimeToLongSamples(cutStart)); cutStart = wt->LongSamplesToTime(wt->TimeToLongSamples(cutStart));
cutEnd = wt->LongSamplesToTime(wt->TimeToLongSamples(cutEnd)); cutEnd = wt->LongSamplesToTime(wt->TimeToLongSamples(cutEnd));
@ -406,21 +519,13 @@ bool EffectTruncSilence::Process()
delete [] buf1; delete [] buf1;
delete [] buf2; delete [] buf2;
} }
else if (t->GetSelected() || t->IsSyncLockSelected()) else
{
// Non-wave tracks: just do a sync-lock adjust // Non-wave tracks: just do a sync-lock adjust
double cutStart = (r->start + r->end - cutLen) / 2;
double cutEnd = cutStart + cutLen;
t->SyncLockAdjust(cutEnd, cutStart); t->SyncLockAdjust(cutEnd, cutStart);
} }
}
++whichReg; ++whichReg;
} }
mT1 -= totalCutLen;
ReplaceProcessedTracks(true);
return true; return true;
} }
@ -434,7 +539,7 @@ bool EffectTruncSilence::Analyze(RegionList& silenceList,
double* minInputLength /*= NULL*/) double* minInputLength /*= NULL*/)
{ {
// Smallest silent region to detect in frames // Smallest silent region to detect in frames
sampleCount minSilenceFrames = sampleCount(wxMax( mInitialAllowedSilence, DEF_MinTruncMs) * wt->GetRate()); sampleCount minSilenceFrames = sampleCount(std::max( mInitialAllowedSilence, DEF_MinTruncMs) * wt->GetRate());
double truncDbSilenceThreshold = Enums::Db2Signal[mTruncDbChoiceIndex]; double truncDbSilenceThreshold = Enums::Db2Signal[mTruncDbChoiceIndex];
sampleCount blockLen = wt->GetMaxBlockSize(); sampleCount blockLen = wt->GetMaxBlockSize();
@ -642,7 +747,14 @@ void EffectTruncSilence::PopulateOrExchange(ShuttleGui & S)
S.AddUnits(wxT("percent")); S.AddUnits(wxT("percent"));
} }
S.EndMultiColumn(); S.EndMultiColumn();
S.StartMultiColumn(2, wxALIGN_CENTER_HORIZONTAL);
{
mIndependent = S.AddCheckBox(_("Truncate tracks independently"),
mbIndependent ? wxT("true") : wxT("false"));
} }
S.EndMultiColumn();
}
S.EndStatic(); S.EndStatic();
UpdateUI(); UpdateUI();
@ -665,6 +777,8 @@ bool EffectTruncSilence::TransferDataFromWindow()
return false; return false;
} }
mbIndependent = mIndependent->IsChecked();
return true; return true;
} }
@ -807,6 +921,7 @@ void EffectTruncSilence::Intersect(RegionList &dest, const RegionList &src)
} }
} }
/*
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];
@ -822,6 +937,7 @@ void EffectTruncSilence::BlendFrames(float* buffer, int blendFrameCount, int lef
afterFactor += adjFactor; afterFactor += adjFactor;
} }
} }
*/
void EffectTruncSilence::UpdateUI() void EffectTruncSilence::UpdateUI()
{ {

View File

@ -18,15 +18,16 @@
#define __AUDACITY_EFFECT_TRUNC_SILENCE__ #define __AUDACITY_EFFECT_TRUNC_SILENCE__
#include <wx/arrstr.h> #include <wx/arrstr.h>
#include <wx/choice.h>
#include <wx/event.h> #include <wx/event.h>
#include <wx/list.h> #include <wx/list.h>
#include <wx/string.h> #include <wx/string.h>
#include <wx/textctrl.h>
#include "Effect.h" #include "Effect.h"
class ShuttleGui; class ShuttleGui;
class wxChoice;
class wxTextCtrl;
class wxCheckBox;
#define TRUNCATESILENCE_PLUGIN_SYMBOL XO("Truncate Silence") #define TRUNCATESILENCE_PLUGIN_SYMBOL XO("Truncate Silence")
@ -81,18 +82,28 @@ private:
// EffectTruncSilence implementation // EffectTruncSilence implementation
//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);
void Intersect(RegionList &dest, const RegionList & src); void Intersect(RegionList &dest, const RegionList & src);
void OnControlChange(wxCommandEvent & evt); void OnControlChange(wxCommandEvent & evt);
void UpdateUI(); void UpdateUI();
bool ProcessIndependently();
bool ProcessAll();
bool FindSilences
(RegionList &silences, Track *firstTrack, Track *lastTrack);
bool DoRemoval
(const RegionList &silences, unsigned iGroup, unsigned nGroups, Track *firstTrack, Track *lastTrack,
double &totalCutLen);
private: private:
int mTruncDbChoiceIndex; int mTruncDbChoiceIndex;
int mActionIndex; int mActionIndex;
double mInitialAllowedSilence; double mInitialAllowedSilence;
double mTruncLongestAllowedSilence; double mTruncLongestAllowedSilence;
double mSilenceCompressPercent; double mSilenceCompressPercent;
bool mbIndependent;
wxArrayString mDbChoices; wxArrayString mDbChoices;
@ -103,6 +114,7 @@ private:
wxTextCtrl *mInitialAllowedSilenceT; wxTextCtrl *mInitialAllowedSilenceT;
wxTextCtrl *mTruncLongestAllowedSilenceT; wxTextCtrl *mTruncLongestAllowedSilenceT;
wxTextCtrl *mSilenceCompressPercentT; wxTextCtrl *mSilenceCompressPercentT;
wxCheckBox *mIndependent;
DECLARE_EVENT_TABLE(); DECLARE_EVENT_TABLE();
}; };