audacia/src/effects/TruncSilence.cpp
Paul Licameli 4d09705a73 Change XO to XXO in many more places, with no effects at all...
... because the two macros have the same expansion, and are both checked for
in the --keyword arguments passed to msgfmt by locale/update_po_files.sh.

This commit makes ONLY such changes, and comments in Internat.h.  It is big
but quite harmless.

The intention is to introduce a type distinction in a later release, by defining
XXO differently.  XXO is used where & characters in strings (for hotkeys of menu
items or control prompts) are permitted, XO where not.
2020-05-22 13:07:50 -04:00

1032 lines
31 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
TruncSilence.cpp
Lynn Allan (from DM's Normalize)
Philip Van Baren (more options and boundary fixes)
*******************************************************************//**
\class EffectTruncSilence
\brief Truncate Silence automatically reduces the length of passages
where the volume is below a set threshold level.
*//*******************************************************************/
#include "../Audacity.h"
#include "TruncSilence.h"
#include "LoadEffects.h"
#include <algorithm>
#include <list>
#include <limits>
#include <math.h>
#include <wx/checkbox.h>
#include <wx/choice.h>
#include <wx/valgen.h>
#include "../Prefs.h"
#include "../Project.h"
#include "../ProjectSettings.h"
#include "../Shuttle.h"
#include "../ShuttleGui.h"
#include "../WaveTrack.h"
#include "../widgets/valnum.h"
#include "../widgets/AudacityMessageBox.h"
class Enums {
public:
static const size_t NumDbChoices;
static const EnumValueSymbol DbChoices[];
};
const EnumValueSymbol Enums::DbChoices[] = {
// Table of text values, only for reading what was stored in legacy config
// files.
// It was inappropriate to make this a discrete choice control.
{ wxT("-20 dB") },
{ wxT("-25 dB") },
{ wxT("-30 dB") },
{ wxT("-35 dB") },
{ wxT("-40 dB") },
{ wxT("-45 dB") },
{ wxT("-50 dB") },
{ wxT("-55 dB") },
{ wxT("-60 dB") },
{ wxT("-65 dB") },
{ wxT("-70 dB") },
{ wxT("-75 dB") },
{ wxT("-80 dB") }
};
// Map from position in table above to numerical value.
static inline double enumToDB( int val ) { return -( 5.0 * val + 20.0 ); }
const size_t Enums::NumDbChoices = WXSIZEOF(Enums::DbChoices);
// Declaration of RegionList
class RegionList : public std::list < Region > {};
enum kActions
{
kTruncate,
kCompress,
nActions
};
static const EnumValueSymbol kActionStrings[nActions] =
{
{ XO("Truncate Detected Silence") },
{ XO("Compress Excess Silence") }
};
static CommandParameters::ObsoleteMap kObsoleteActions[] = {
// Compatible with 2.1.0 and before
{ wxT("0"), 0 }, // Remap to Truncate Detected Silence
{ wxT("1"), 1 }, // Remap to Compress Excess Silence
};
static const size_t nObsoleteActions = WXSIZEOF( kObsoleteActions );
// Define defaults, minimums, and maximums for each parameter
#define DefaultAndLimits(name, def, min, max) \
static const double DEF_ ## name = (def); \
static const double MIN_ ## name = (min); \
static const double MAX_ ## name = (max);
// Define keys, defaults, minimums, and maximums for the effect parameters
//
// Name Type Key Def Min Max Scale
// This one is legacy and is intentionally not reported by DefineParams:
Param( DbIndex, int, wxT("Db"), 0, 0, Enums::NumDbChoices - 1, 1 );
Param( Threshold, double, wxT("Threshold"), -20.0, -80.0, -20.0, 1 );
Param( ActIndex, int, wxT("Action"), kTruncate, 0, nActions - 1, 1 );
Param( Minimum, double, wxT("Minimum"), 0.5, 0.001, 10000.0, 1 );
Param( Truncate, double, wxT("Truncate"), 0.5, 0.0, 10000.0, 1 );
Param( Compress, double, wxT("Compress"), 50.0, 0.0, 99.9, 1 );
Param( Independent, bool, wxT("Independent"), false, false, true, 1 );
static const size_t DEF_BlendFrameCount = 100;
// Lower bound on the amount of silence to find at a time -- this avoids
// detecting silence repeatedly in low-frequency sounds.
static const double DEF_MinTruncMs = 0.001;
// Typical fraction of total time taken by detection (better to guess low)
const double detectFrac = 0.4;
const ComponentInterfaceSymbol EffectTruncSilence::Symbol
{ XO("Truncate Silence") };
namespace{ BuiltinEffectsModule::Registration< EffectTruncSilence > reg; }
BEGIN_EVENT_TABLE(EffectTruncSilence, wxEvtHandler)
EVT_CHOICE(wxID_ANY, EffectTruncSilence::OnControlChange)
EVT_TEXT(wxID_ANY, EffectTruncSilence::OnControlChange)
END_EVENT_TABLE()
EffectTruncSilence::EffectTruncSilence()
{
mInitialAllowedSilence = DEF_Minimum;
mTruncLongestAllowedSilence = DEF_Truncate;
mSilenceCompressPercent = DEF_Compress;
mThresholdDB = DEF_Threshold;
mActionIndex = DEF_ActIndex;
mbIndependent = DEF_Independent;
SetLinearEffectFlag(false);
// This used to be changeable via the audacity.cfg/registry. Doubtful that was
// ever done.
//
// Original comment:
//
// 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
mBlendFrameCount = DEF_BlendFrameCount;
}
EffectTruncSilence::~EffectTruncSilence()
{
}
// ComponentInterface implementation
ComponentInterfaceSymbol EffectTruncSilence::GetSymbol()
{
return Symbol;
}
TranslatableString EffectTruncSilence::GetDescription()
{
return XO("Automatically reduces the length of passages where the volume is below a specified level");
}
wxString EffectTruncSilence::ManualPage()
{
return wxT("Truncate_Silence");
}
// EffectDefinitionInterface implementation
EffectType EffectTruncSilence::GetType()
{
return EffectTypeProcess;
}
// EffectClientInterface implementation
bool EffectTruncSilence::DefineParams( ShuttleParams & S ){
S.SHUTTLE_PARAM( mThresholdDB, Threshold );
S.SHUTTLE_ENUM_PARAM( mActionIndex, ActIndex, kActionStrings, nActions );
S.SHUTTLE_PARAM( mInitialAllowedSilence, Minimum );
S.SHUTTLE_PARAM( mTruncLongestAllowedSilence, Truncate );
S.SHUTTLE_PARAM( mSilenceCompressPercent, Compress );
S.SHUTTLE_PARAM( mbIndependent, Independent );
return true;
}
bool EffectTruncSilence::GetAutomationParameters(CommandParameters & parms)
{
parms.Write(KEY_Threshold, mThresholdDB);
parms.Write(KEY_ActIndex, kActionStrings[mActionIndex].Internal());
parms.Write(KEY_Minimum, mInitialAllowedSilence);
parms.Write(KEY_Truncate, mTruncLongestAllowedSilence);
parms.Write(KEY_Compress, mSilenceCompressPercent);
parms.Write(KEY_Independent, mbIndependent);
return true;
}
bool EffectTruncSilence::SetAutomationParameters(CommandParameters & parms)
{
ReadAndVerifyDouble(Minimum);
ReadAndVerifyDouble(Truncate);
ReadAndVerifyDouble(Compress);
// This control migrated from a choice to a text box in version 2.3.0
double myThreshold {};
bool newParams = [&] {
ReadAndVerifyDouble(Threshold); // macro may return false
myThreshold = Threshold;
return true;
} ();
if ( !newParams ) {
// Use legacy param:
ReadAndVerifyEnum(DbIndex, Enums::DbChoices, Enums::NumDbChoices);
myThreshold = enumToDB( DbIndex );
}
ReadAndVerifyEnumWithObsoletes(ActIndex, kActionStrings, nActions,
kObsoleteActions, nObsoleteActions);
ReadAndVerifyBool(Independent);
mInitialAllowedSilence = Minimum;
mTruncLongestAllowedSilence = Truncate;
mSilenceCompressPercent = Compress;
mThresholdDB = myThreshold;
mActionIndex = ActIndex;
mbIndependent = Independent;
return true;
}
// Effect implementation
double EffectTruncSilence::CalcPreviewInputLength(double /* previewLength */)
{
double inputLength = mT1 - mT0;
double minInputLength = inputLength;
// Master list of silent regions
RegionList silences;
// Start with the whole selection silent
silences.push_back(Region(mT0, mT1));
int whichTrack = 0;
for (auto wt : inputTracks()->Selected< const WaveTrack >()) {
RegionList trackSilences;
auto index = wt->TimeToLongSamples(mT0);
sampleCount silentFrame = 0; // length of the current silence
Analyze(silences, trackSilences, wt, &silentFrame, &index, whichTrack, &inputLength, &minInputLength);
whichTrack++;
}
return inputLength;
}
bool EffectTruncSilence::Startup()
{
wxString base = wxT("/Effects/TruncateSilence/");
// 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 truncDbChoiceIndex = gPrefs->Read(base + wxT("DbChoiceIndex"), 4L);
if ((truncDbChoiceIndex < 0) || (truncDbChoiceIndex >= Enums::NumDbChoices))
{ // corrupted Prefs?
truncDbChoiceIndex = 4L;
}
mThresholdDB = enumToDB( truncDbChoiceIndex );
mActionIndex = gPrefs->Read(base + wxT("ProcessChoice"), 0L);
if ((mActionIndex < 0) || (mActionIndex > 1))
{ // corrupted Prefs?
mActionIndex = 0L;
}
gPrefs->Read(base + wxT("InitialAllowedSilence"), &mInitialAllowedSilence, 0.5);
if ((mInitialAllowedSilence < 0.001) || (mInitialAllowedSilence > 10000.0))
{ // corrupted Prefs?
mInitialAllowedSilence = 0.5;
}
gPrefs->Read(base + wxT("LongestAllowedSilence"), &mTruncLongestAllowedSilence, 0.5);
if ((mTruncLongestAllowedSilence < 0.0) || (mTruncLongestAllowedSilence > 10000.0))
{ // corrupted Prefs?
mTruncLongestAllowedSilence = 0.5;
}
gPrefs->Read(base + wxT("CompressPercent"), &mSilenceCompressPercent, 50.0);
if ((mSilenceCompressPercent < 0.0) || (mSilenceCompressPercent > 100.0))
{ // corrupted Prefs?
mSilenceCompressPercent = 50.0;
}
SaveUserPreset(GetCurrentSettingsGroup());
}
// Do not migrate again
gPrefs->Write(base + wxT("Migrated"), true);
return true;
}
bool EffectTruncSilence::Process()
{
const bool success =
mbIndependent
? ProcessIndependently()
: ProcessAll();
if (success)
ReplaceProcessedTracks(true);
return success;
}
bool EffectTruncSilence::ProcessIndependently()
{
unsigned nGroups = 0;
const auto &settings = ProjectSettings::Get( *FindProject() );
const bool syncLock = settings.IsSyncLocked();
// Check if it's permissible
{
for (auto track : inputTracks()->SelectedLeaders< const WaveTrack >() ) {
if (syncLock) {
auto channels = TrackList::Channels(track);
auto otherTracks =
TrackList::SyncLockGroup(track).Filter<const WaveTrack>()
+ &Track::IsSelected
- [&](const Track *pTrack){
return channels.contains(pTrack); };
if (otherTracks) {
::Effect::MessageBox(
XO(
"When truncating independently, there may only be one selected audio track in each Sync-Locked Track Group.") );
return false;
}
}
++nGroups;
}
}
if (nGroups == 0)
// nothing to do
return true;
// Now do the work
// Copy tracks
CopyInputTracks(true);
double newT1 = 0.0;
{
unsigned iGroup = 0;
for (auto track : mOutputTracks->SelectedLeaders< WaveTrack >() ) {
Track *const last = *TrackList::Channels(track).rbegin();
RegionList silences;
if (!FindSilences(silences, mOutputTracks.get(), track, last))
return false;
// Treat tracks in the sync lock group only
Track *groupFirst, *groupLast;
if (syncLock) {
auto trackRange = TrackList::SyncLockGroup(track);
groupFirst = *trackRange.begin();
groupLast = *trackRange.rbegin();
}
else {
groupFirst = track;
groupLast = last;
}
double totalCutLen = 0.0;
if (!DoRemoval(silences, iGroup, nGroups, groupFirst, groupLast, totalCutLen))
return false;
newT1 = std::max(newT1, mT1 - totalCutLen);
++iGroup;
}
}
mT1 = newT1;
return true;
}
bool EffectTruncSilence::ProcessAll()
{
// Copy tracks
CopyInputTracks(true);
// Master list of silent regions.
// This list should always be kept in order.
RegionList silences;
auto trackRange0 = inputTracks()->Selected< const WaveTrack >();
if (FindSilences(
silences, inputTracks(), *trackRange0.begin(), *trackRange0.rbegin())) {
auto trackRange = mOutputTracks->Any();
double totalCutLen = 0.0;
if (DoRemoval(silences, 0, 1,
*trackRange.begin(), *trackRange.rbegin(), totalCutLen)) {
mT1 -= totalCutLen;
return true;
}
}
return false;
}
bool EffectTruncSilence::FindSilences
(RegionList &silences, const TrackList *list,
const Track *firstTrack, const Track *lastTrack)
{
// Start with the whole selection silent
silences.push_back(Region(mT0, mT1));
// Remove non-silent regions in each track
int whichTrack = 0;
for (auto wt :
list->Selected< const WaveTrack >()
.StartingWith( firstTrack ).EndingAfter( lastTrack ) )
{
// Smallest silent region to detect in frames
auto minSilenceFrames =
sampleCount(std::max(mInitialAllowedSilence, DEF_MinTruncMs) * wt->GetRate());
//
// Scan the track for silences
//
RegionList trackSilences;
auto index = wt->TimeToLongSamples(mT0);
sampleCount silentFrame = 0;
// Detect silences
bool cancelled = !(Analyze(silences, trackSilences, wt, &silentFrame, &index, whichTrack));
// Buffer has been freed, so we're OK to return if cancelled
if (cancelled)
{
ReplaceProcessedTracks(false);
return false;
}
if (silentFrame >= minSilenceFrames)
{
// Track ended in silence -- record region
trackSilences.push_back(Region(
wt->LongSamplesToTime(index - silentFrame),
wt->LongSamplesToTime(index)
));
}
// Intersect with the overall silent region list
Intersect(silences, trackSilences);
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.
//
// Loop over detected regions in reverse (so cuts don't change time values
// down the line)
int whichReg = 0;
RegionList::const_reverse_iterator rit;
for (rit = silences.rbegin(); rit != silences.rend(); ++rit)
{
const Region &region = *rit;
const Region *const r = &region;
// Progress dialog and cancellation. Do additional cleanup before return.
const double frac = detectFrac +
(1 - detectFrac) * (iGroup + whichReg / double(silences.size())) / nGroups;
if (TotalProgress(frac))
{
ReplaceProcessedTracks(false);
return false;
}
// Intersection may create regions smaller than allowed; ignore them.
// Allow one nanosecond extra for consistent results with exact milliseconds of allowed silence.
if ((r->end - r->start) < (mInitialAllowedSilence - 0.000000001))
continue;
// Find NEW silence length as requested
double inLength = r->end - r->start;
double outLength;
switch (mActionIndex)
{
case kTruncate:
outLength = std::min(mTruncLongestAllowedSilence, inLength);
break;
case kCompress:
outLength = mInitialAllowedSilence +
(inLength - mInitialAllowedSilence) * mSilenceCompressPercent / 100.0;
break;
default: // Not currently used.
outLength = std::min(mInitialAllowedSilence +
(inLength - mInitialAllowedSilence) * mSilenceCompressPercent / 100.0,
mTruncLongestAllowedSilence);
}
const double cutLen = std::max(0.0, inLength - outLength);
// Don't waste time cutting nothing.
if( cutLen == 0.0 )
continue;
totalCutLen += cutLen;
double cutStart = (r->start + r->end - cutLen) / 2;
double cutEnd = cutStart + cutLen;
(mOutputTracks->Any()
.StartingWith(firstTrack).EndingAfter(lastTrack)
+ &Track::IsSelectedOrSyncLockSelected
- [&](const Track *pTrack) { return
// Don't waste time past the end of a track
pTrack->GetEndTime() < r->start;
}
).Visit(
[&](WaveTrack *wt) {
// In WaveTracks, clear with a cross-fade
auto blendFrames = mBlendFrameCount;
// 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)
{
// Result is not more than blendFrames:
blendFrames = wt->TimeToLongSamples(inLength).as_size_t();
}
// Perform cross-fade in memory
Floats buf1{ blendFrames };
Floats buf2{ blendFrames };
auto t1 = wt->TimeToLongSamples(cutStart) - blendFrames / 2;
auto t2 = wt->TimeToLongSamples(cutEnd) - blendFrames / 2;
wt->Get((samplePtr)buf1.get(), floatSample, t1, blendFrames);
wt->Get((samplePtr)buf2.get(), floatSample, t2, blendFrames);
for (decltype(blendFrames) 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.get(), floatSample, t1, blendFrames);
},
[&](Track *t) {
// Non-wave tracks: just do a sync-lock adjust
t->SyncLockAdjust(cutEnd, cutStart);
}
);
++whichReg;
}
return true;
}
bool EffectTruncSilence::Analyze(RegionList& silenceList,
RegionList& trackSilences,
const WaveTrack *wt,
sampleCount* silentFrame,
sampleCount* index,
int whichTrack,
double* inputLength /*= NULL*/,
double* minInputLength /*= NULL*/)
{
// Smallest silent region to detect in frames
auto minSilenceFrames = sampleCount(std::max( mInitialAllowedSilence, DEF_MinTruncMs) * wt->GetRate());
double truncDbSilenceThreshold = DB_TO_LINEAR( mThresholdDB );
auto blockLen = wt->GetMaxBlockSize();
auto start = wt->TimeToLongSamples(mT0);
auto end = wt->TimeToLongSamples(mT1);
sampleCount outLength = 0;
double previewLength;
gPrefs->Read(wxT("/AudioIO/EffectsPreviewLen"), &previewLength, 6.0);
// Minimum required length in samples.
const sampleCount previewLen( previewLength * wt->GetRate() );
// Keep position in overall silences list for optimization
RegionList::iterator rit(silenceList.begin());
// Allocate buffer
Floats buffer{ blockLen };
// Loop through current track
while (*index < end) {
if (inputLength && ((outLength >= previewLen) || (*index - start > wt->TimeToLongSamples(*minInputLength)))) {
*inputLength = std::min<double>(*inputLength, *minInputLength);
if (outLength >= previewLen) {
*minInputLength = *inputLength;
}
return true;
}
if (!inputLength) {
// Show progress dialog, test for cancellation
bool cancelled = TotalProgress(
detectFrac * (whichTrack +
(*index - start).as_double() /
(end - start).as_double()) /
(double)GetNumWaveTracks());
if (cancelled)
return false;
}
// Optimization: if not in a silent region skip ahead to the next one
double curTime = wt->LongSamplesToTime(*index);
for ( ; rit != silenceList.end(); ++rit) {
// Find the first silent region ending after current time
if (rit->end >= curTime) {
break;
}
}
if (rit == silenceList.end()) {
// No more regions -- no need to process the rest of the track
if (inputLength) {
// Add available samples up to previewLength.
auto remainingTrackSamples = wt->TimeToLongSamples(wt->GetEndTime()) - *index;
auto requiredTrackSamples = previewLen - outLength;
outLength += (remainingTrackSamples > requiredTrackSamples)? requiredTrackSamples : remainingTrackSamples;
}
break;
}
else if (rit->start > curTime) {
// End current silent region, skip ahead
if (*silentFrame >= minSilenceFrames) {
trackSilences.push_back(Region(
wt->LongSamplesToTime(*index - *silentFrame),
wt->LongSamplesToTime(*index)
));
}
*silentFrame = 0;
auto newIndex = wt->TimeToLongSamples(rit->start);
if (inputLength) {
auto requiredTrackSamples = previewLen - outLength;
// Add non-silent sample to outLength
outLength += ((newIndex - *index) > requiredTrackSamples)? requiredTrackSamples : newIndex - *index;
}
*index = newIndex;
}
// End of optimization
// Limit size of current block if we've reached the end
auto count = limitSampleBufferSize( blockLen, end - *index );
// Fill buffer
wt->Get((samplePtr)(buffer.get()), floatSample, *index, count);
// Look for silenceList in current block
for (decltype(count) i = 0; i < count; ++i) {
if (inputLength && ((outLength >= previewLen) || (outLength > wt->TimeToLongSamples(*minInputLength)))) {
*inputLength = wt->LongSamplesToTime(*index + i) - wt->LongSamplesToTime(start);
break;
}
if (fabs(buffer[i]) < truncDbSilenceThreshold) {
(*silentFrame)++;
}
else {
sampleCount allowed = 0;
if (*silentFrame >= minSilenceFrames) {
if (inputLength) {
switch (mActionIndex) {
case kTruncate:
outLength += wt->TimeToLongSamples(mTruncLongestAllowedSilence);
break;
case kCompress:
allowed = wt->TimeToLongSamples(mInitialAllowedSilence);
outLength += sampleCount(
allowed.as_double() +
(*silentFrame - allowed).as_double()
* mSilenceCompressPercent / 100.0
);
break;
// default: // Not currently used.
}
}
// Record the silent region
trackSilences.push_back(Region(
wt->LongSamplesToTime(*index + i - *silentFrame),
wt->LongSamplesToTime(*index + i)
));
}
else if (inputLength) { // included as part of non-silence
outLength += *silentFrame;
}
*silentFrame = 0;
if (inputLength) {
++outLength; // Add non-silent sample to outLength
}
}
}
// Next block
*index += count;
}
if (inputLength) {
*inputLength = std::min<double>(*inputLength, *minInputLength);
if (outLength >= previewLen) {
*minInputLength = *inputLength;
}
}
return true;
}
void EffectTruncSilence::PopulateOrExchange(ShuttleGui & S)
{
wxASSERT(nActions == WXSIZEOF(kActionStrings));
S.AddSpace(0, 5);
S.StartStatic(XO("Detect Silence"));
{
S.StartMultiColumn(3, wxALIGN_CENTER_HORIZONTAL);
{
// Threshold
mThresholdText = S
.Validator<FloatingPointValidator<double>>(
3, &mThresholdDB, NumValidatorStyle::NO_TRAILING_ZEROES,
MIN_Threshold, MAX_Threshold
)
.NameSuffix(XO("db"))
.AddTextBox(XXO("&Threshold:"), wxT(""), 0);
S.AddUnits(XO("dB"));
// Ignored silence
mInitialAllowedSilenceT = S.Validator<FloatingPointValidator<double>>(
3, &mInitialAllowedSilence,
NumValidatorStyle::NO_TRAILING_ZEROES,
MIN_Minimum, MAX_Minimum)
.NameSuffix(XO("seconds"))
.AddTextBox(XXO("&Duration:"), wxT(""), 12);
S.AddUnits(XO("seconds"));
}
S.EndMultiColumn();
}
S.EndStatic();
S.StartStatic(XO("Action"));
{
S.StartHorizontalLay();
{
// Action choices
auto actionChoices = Msgids( kActionStrings, nActions );
mActionChoice = S
.Validator<wxGenericValidator>(&mActionIndex)
.MinSize( { -1, -1 } )
.AddChoice( {}, actionChoices );
}
S.EndHorizontalLay();
S.StartMultiColumn(3, wxALIGN_CENTER_HORIZONTAL);
{
// Truncation / Compression factor
mTruncLongestAllowedSilenceT = S.Validator<FloatingPointValidator<double>>(
3, &mTruncLongestAllowedSilence,
NumValidatorStyle::NO_TRAILING_ZEROES,
MIN_Truncate, MAX_Truncate
)
.NameSuffix(XO("seconds"))
.AddTextBox(XXO("Tr&uncate to:"), wxT(""), 12);
S.AddUnits(XO("seconds"));
mSilenceCompressPercentT = S.Validator<FloatingPointValidator<double>>(
3, &mSilenceCompressPercent,
NumValidatorStyle::NO_TRAILING_ZEROES,
MIN_Compress, MAX_Compress
)
.NameSuffix(XO("%"))
.AddTextBox(XXO("C&ompress to:"), wxT(""), 12);
S.AddUnits(XO("%"));
}
S.EndMultiColumn();
S.StartMultiColumn(2, wxALIGN_CENTER_HORIZONTAL);
{
mIndependent = S.AddCheckBox(XXO("Trunc&ate tracks independently"),
mbIndependent);
}
S.EndMultiColumn();
}
S.EndStatic();
UpdateUI();
}
bool EffectTruncSilence::TransferDataToWindow()
{
if (!mUIParent->TransferDataToWindow())
{
return false;
}
return true;
}
bool EffectTruncSilence::TransferDataFromWindow()
{
if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
{
return false;
}
mbIndependent = mIndependent->IsChecked();
return true;
}
// EffectTruncSilence implementation
// 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;
RegionList::iterator 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();
// This logic, causing the loop to run once after end of src, must occur
// each time srcIter is updated
if (srcIter == src.end())
{
lastRun = true;
}
while (srcIter != src.end() || lastRun)
{
// Don't use curSrc unless lastRun is false!
RegionList::const_iterator curSrc;
if (lastRun)
{
// The last non-silent region extends as far as possible
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(nsEnd, curDest->end);
// The first region
curDest->end = nsStart;
// Insert second region after first
RegionList::iterator nextIt(destIter);
++nextIt;
// This should just read: destIter = dest.insert(nextIt, r); but we
// work around two two wxList::insert() bugs. First, in some
// versions it returns the wrong value. Second, in some versions,
// it crashes when you insert at list end.
if (nextIt == dest.end())
dest.push_back(r);
else
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;
}
}
}
}
/*
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;
}
}
*/
void EffectTruncSilence::UpdateUI()
{
switch (mActionIndex)
{
case kTruncate:
mTruncLongestAllowedSilenceT->Enable(true);
mSilenceCompressPercentT->Enable(false);
break;
case kCompress:
mTruncLongestAllowedSilenceT->Enable(false);
mSilenceCompressPercentT->Enable(true);
}
}
void EffectTruncSilence::OnControlChange(wxCommandEvent & WXUNUSED(evt))
{
mActionChoice->GetValidator()->TransferFromWindow();
UpdateUI();
if (!EnableApply(mUIParent->TransferDataFromWindow()))
{
return;
}
}