1 /**********************************************************************
2 
3   Audacity: A Digital Audio Editor
4 
5   TruncSilence.cpp
6 
7   Lynn Allan (from DM's Normalize)
8   Philip Van Baren (more options and boundary fixes)
9 
10 *******************************************************************//**
11 
12 \class EffectTruncSilence
13 \brief Truncate Silence automatically reduces the length of passages
14        where the volume is below a set threshold level.
15 
16 *//*******************************************************************/
17 
18 
19 #include "TruncSilence.h"
20 #include "LoadEffects.h"
21 
22 #include <algorithm>
23 #include <list>
24 #include <limits>
25 #include <math.h>
26 
27 #include <wx/checkbox.h>
28 #include <wx/choice.h>
29 #include <wx/valgen.h>
30 
31 #include "Prefs.h"
32 #include "Project.h"
33 #include "../ProjectSettings.h"
34 #include "../Shuttle.h"
35 #include "../ShuttleGui.h"
36 #include "../WaveTrack.h"
37 #include "../widgets/valnum.h"
38 #include "../widgets/AudacityMessageBox.h"
39 
40 class Enums {
41 public:
42    static const size_t    NumDbChoices;
43    static const EnumValueSymbol DbChoices[];
44 };
45 
46 const EnumValueSymbol Enums::DbChoices[] = {
47    // Table of text values, only for reading what was stored in legacy config
48    // files.
49    // It was inappropriate to make this a discrete choice control.
50    { wxT("-20 dB") },
51    { wxT("-25 dB") },
52    { wxT("-30 dB") },
53    { wxT("-35 dB") },
54    { wxT("-40 dB") },
55    { wxT("-45 dB") },
56    { wxT("-50 dB") },
57    { wxT("-55 dB") },
58    { wxT("-60 dB") },
59    { wxT("-65 dB") },
60    { wxT("-70 dB") },
61    { wxT("-75 dB") },
62    { wxT("-80 dB") }
63 };
64 
65 // Map from position in table above to numerical value.
enumToDB(int val)66 static inline double enumToDB( int val ) { return -( 5.0 * val + 20.0 ); }
67 
68 const size_t Enums::NumDbChoices = WXSIZEOF(Enums::DbChoices);
69 
70 // Declaration of RegionList
71 class RegionList : public std::list < Region > {};
72 
73 enum kActions
74 {
75    kTruncate,
76    kCompress,
77    nActions
78 };
79 
80 static const EnumValueSymbol kActionStrings[nActions] =
81 {
82    { XO("Truncate Detected Silence") },
83    { XO("Compress Excess Silence") }
84 };
85 
86 static CommandParameters::ObsoleteMap kObsoleteActions[] = {
87    // Compatible with 2.1.0 and before
88    { wxT("0"), 0 }, // Remap to Truncate Detected Silence
89    { wxT("1"), 1 }, // Remap to Compress Excess Silence
90 };
91 
92 static const size_t nObsoleteActions = WXSIZEOF( kObsoleteActions );
93 
94 // Define defaults, minimums, and maximums for each parameter
95 #define DefaultAndLimits(name, def, min, max) \
96    static const double DEF_ ## name = (def); \
97    static const double MIN_ ## name = (min); \
98    static const double MAX_ ## name = (max);
99 
100 // Define keys, defaults, minimums, and maximums for the effect parameters
101 //
102 //     Name       Type     Key               Def         Min      Max                        Scale
103 
104 // This one is legacy and is intentionally not reported by DefineParams:
105 Param( DbIndex,   int,     wxT("Db"),         0,          0,       Enums::NumDbChoices - 1,   1  );
106 
107 Param( Threshold, double,  wxT("Threshold"),  -20.0,      -80.0,   -20.0,                     1  );
108 Param( ActIndex,  int,     wxT("Action"),     kTruncate,  0,       nActions - 1,           1  );
109 Param( Minimum,   double,  wxT("Minimum"),    0.5,        0.001,   10000.0,                   1  );
110 Param( Truncate,  double,  wxT("Truncate"),   0.5,        0.0,     10000.0,                   1  );
111 Param( Compress,  double,  wxT("Compress"),   50.0,       0.0,     99.9,                      1  );
112 Param( Independent, bool,  wxT("Independent"), false,     false,   true,                      1  );
113 
114 static const size_t DEF_BlendFrameCount = 100;
115 
116 // Lower bound on the amount of silence to find at a time -- this avoids
117 // detecting silence repeatedly in low-frequency sounds.
118 static const double DEF_MinTruncMs = 0.001;
119 
120 // Typical fraction of total time taken by detection (better to guess low)
121 const double detectFrac = 0.4;
122 
123 const ComponentInterfaceSymbol EffectTruncSilence::Symbol
124 { XO("Truncate Silence") };
125 
126 namespace{ BuiltinEffectsModule::Registration< EffectTruncSilence > reg; }
127 
BEGIN_EVENT_TABLE(EffectTruncSilence,wxEvtHandler)128 BEGIN_EVENT_TABLE(EffectTruncSilence, wxEvtHandler)
129    EVT_CHOICE(wxID_ANY, EffectTruncSilence::OnControlChange)
130    EVT_TEXT(wxID_ANY, EffectTruncSilence::OnControlChange)
131 END_EVENT_TABLE()
132 
133 EffectTruncSilence::EffectTruncSilence()
134 {
135    mInitialAllowedSilence = DEF_Minimum;
136    mTruncLongestAllowedSilence = DEF_Truncate;
137    mSilenceCompressPercent = DEF_Compress;
138    mThresholdDB = DEF_Threshold;
139    mActionIndex = DEF_ActIndex;
140    mbIndependent = DEF_Independent;
141 
142    SetLinearEffectFlag(false);
143 
144    // This used to be changeable via the audacity.cfg/registry.  Doubtful that was
145    // ever done.
146    //
147    // Original comment:
148    //
149    //   mBlendFrameCount only retrieved from prefs ... not using dialog
150    //   Only way to change (for windows) is thru registry
151    //   The values should be figured dynamically ... too many frames could be invalid
152    mBlendFrameCount = DEF_BlendFrameCount;
153 }
154 
~EffectTruncSilence()155 EffectTruncSilence::~EffectTruncSilence()
156 {
157 }
158 
159 // ComponentInterface implementation
160 
GetSymbol()161 ComponentInterfaceSymbol EffectTruncSilence::GetSymbol()
162 {
163    return Symbol;
164 }
165 
GetDescription()166 TranslatableString EffectTruncSilence::GetDescription()
167 {
168    return XO("Automatically reduces the length of passages where the volume is below a specified level");
169 }
170 
ManualPage()171 ManualPageID EffectTruncSilence::ManualPage()
172 {
173    return L"Truncate_Silence";
174 }
175 
176 // EffectDefinitionInterface implementation
177 
GetType()178 EffectType EffectTruncSilence::GetType()
179 {
180    return EffectTypeProcess;
181 }
182 
183 // EffectClientInterface implementation
184 
DefineParams(ShuttleParams & S)185 bool EffectTruncSilence::DefineParams( ShuttleParams & S ){
186    S.SHUTTLE_PARAM( mThresholdDB, Threshold );
187    S.SHUTTLE_ENUM_PARAM( mActionIndex, ActIndex, kActionStrings, nActions );
188    S.SHUTTLE_PARAM( mInitialAllowedSilence, Minimum );
189    S.SHUTTLE_PARAM( mTruncLongestAllowedSilence, Truncate );
190    S.SHUTTLE_PARAM( mSilenceCompressPercent, Compress );
191    S.SHUTTLE_PARAM( mbIndependent, Independent );
192    return true;
193 }
194 
GetAutomationParameters(CommandParameters & parms)195 bool EffectTruncSilence::GetAutomationParameters(CommandParameters & parms)
196 {
197    parms.Write(KEY_Threshold, mThresholdDB);
198    parms.Write(KEY_ActIndex, kActionStrings[mActionIndex].Internal());
199    parms.Write(KEY_Minimum, mInitialAllowedSilence);
200    parms.Write(KEY_Truncate, mTruncLongestAllowedSilence);
201    parms.Write(KEY_Compress, mSilenceCompressPercent);
202    parms.Write(KEY_Independent, mbIndependent);
203 
204    return true;
205 }
206 
SetAutomationParameters(CommandParameters & parms)207 bool EffectTruncSilence::SetAutomationParameters(CommandParameters & parms)
208 {
209    ReadAndVerifyDouble(Minimum);
210    ReadAndVerifyDouble(Truncate);
211    ReadAndVerifyDouble(Compress);
212 
213    // This control migrated from a choice to a text box in version 2.3.0
214    double myThreshold {};
215    bool newParams = [&] {
216       ReadAndVerifyDouble(Threshold); // macro may return false
217       myThreshold = Threshold;
218       return true;
219    } ();
220 
221    if ( !newParams ) {
222       // Use legacy param:
223       ReadAndVerifyEnum(DbIndex, Enums::DbChoices, Enums::NumDbChoices);
224       myThreshold = enumToDB( DbIndex );
225    }
226 
227    ReadAndVerifyEnumWithObsoletes(ActIndex, kActionStrings, nActions,
228                                   kObsoleteActions, nObsoleteActions);
229    ReadAndVerifyBool(Independent);
230 
231    mInitialAllowedSilence = Minimum;
232    mTruncLongestAllowedSilence = Truncate;
233    mSilenceCompressPercent = Compress;
234    mThresholdDB = myThreshold;
235    mActionIndex = ActIndex;
236    mbIndependent = Independent;
237 
238    return true;
239 }
240 
241 // Effect implementation
242 
CalcPreviewInputLength(double)243 double EffectTruncSilence::CalcPreviewInputLength(double /* previewLength */)
244 {
245    double inputLength = mT1 - mT0;
246    double minInputLength = inputLength;
247 
248    // Master list of silent regions
249    RegionList silences;
250 
251    // Start with the whole selection silent
252    silences.push_back(Region(mT0, mT1));
253 
254    int whichTrack = 0;
255 
256    for (auto wt : inputTracks()->Selected< const WaveTrack >()) {
257       RegionList trackSilences;
258 
259       auto index = wt->TimeToLongSamples(mT0);
260       sampleCount silentFrame = 0; // length of the current silence
261 
262       Analyze(silences, trackSilences, wt, &silentFrame, &index, whichTrack, &inputLength, &minInputLength);
263 
264       whichTrack++;
265    }
266    return inputLength;
267 }
268 
269 
Startup()270 bool EffectTruncSilence::Startup()
271 {
272    wxString base = wxT("/Effects/TruncateSilence/");
273 
274    // Migrate settings from 2.1.0 or before
275 
276    // Already migrated, so bail
277    if (gPrefs->Exists(base + wxT("Migrated")))
278    {
279       return true;
280    }
281 
282    // Load the old "current" settings
283    if (gPrefs->Exists(base))
284    {
285       int truncDbChoiceIndex = gPrefs->Read(base + wxT("DbChoiceIndex"), 4L);
286       if ((truncDbChoiceIndex < 0) || (truncDbChoiceIndex >= Enums::NumDbChoices))
287       {  // corrupted Prefs?
288          truncDbChoiceIndex = 4L;
289       }
290       mThresholdDB = enumToDB( truncDbChoiceIndex );
291       mActionIndex = gPrefs->Read(base + wxT("ProcessChoice"), 0L);
292       if ((mActionIndex < 0) || (mActionIndex > 1))
293       {  // corrupted Prefs?
294          mActionIndex = 0L;
295       }
296       gPrefs->Read(base + wxT("InitialAllowedSilence"), &mInitialAllowedSilence, 0.5);
297       if ((mInitialAllowedSilence < 0.001) || (mInitialAllowedSilence > 10000.0))
298       {  // corrupted Prefs?
299          mInitialAllowedSilence = 0.5;
300       }
301       gPrefs->Read(base + wxT("LongestAllowedSilence"), &mTruncLongestAllowedSilence, 0.5);
302       if ((mTruncLongestAllowedSilence < 0.0) || (mTruncLongestAllowedSilence > 10000.0))
303       {  // corrupted Prefs?
304          mTruncLongestAllowedSilence = 0.5;
305       }
306       gPrefs->Read(base + wxT("CompressPercent"), &mSilenceCompressPercent, 50.0);
307       if ((mSilenceCompressPercent < 0.0) || (mSilenceCompressPercent > 100.0))
308       {  // corrupted Prefs?
309          mSilenceCompressPercent = 50.0;
310       }
311 
312       SaveUserPreset(GetCurrentSettingsGroup());
313    }
314 
315    // Do not migrate again
316    gPrefs->Write(base + wxT("Migrated"), true);
317 
318    return true;
319 }
320 
Process()321 bool EffectTruncSilence::Process()
322 {
323    const bool success =
324       mbIndependent
325       ? ProcessIndependently()
326       : ProcessAll();
327 
328    if (success)
329       ReplaceProcessedTracks(true);
330 
331    return success;
332 }
333 
ProcessIndependently()334 bool EffectTruncSilence::ProcessIndependently()
335 {
336    unsigned nGroups = 0;
337 
338    const auto &settings = ProjectSettings::Get( *FindProject() );
339    const bool syncLock = settings.IsSyncLocked();
340 
341    // Check if it's permissible
342    {
343       for (auto track : inputTracks()->SelectedLeaders< const WaveTrack >() ) {
344          if (syncLock) {
345             auto channels = TrackList::Channels(track);
346             auto otherTracks =
347                TrackList::SyncLockGroup(track).Filter<const WaveTrack>()
348                   + &Track::IsSelected
349                   - [&](const Track *pTrack){
350                         return channels.contains(pTrack); };
351             if (otherTracks) {
352                ::Effect::MessageBox(
353                   XO(
354 "When truncating independently, there may only be one selected audio track in each Sync-Locked Track Group.") );
355                return false;
356             }
357          }
358 
359          ++nGroups;
360       }
361    }
362 
363    if (nGroups == 0)
364       // nothing to do
365       return true;
366 
367    // Now do the work
368 
369    // Copy tracks
370    CopyInputTracks(true);
371    double newT1 = 0.0;
372 
373    {
374       unsigned iGroup = 0;
375       for (auto track : mOutputTracks->SelectedLeaders< WaveTrack >() ) {
376          Track *const last = *TrackList::Channels(track).rbegin();
377 
378          RegionList silences;
379 
380          if (!FindSilences(silences, mOutputTracks.get(), track, last))
381             return false;
382          // Treat tracks in the sync lock group only
383          Track *groupFirst, *groupLast;
384          if (syncLock) {
385             auto trackRange = TrackList::SyncLockGroup(track);
386             groupFirst = *trackRange.begin();
387             groupLast = *trackRange.rbegin();
388          }
389          else {
390             groupFirst = track;
391             groupLast = last;
392          }
393          double totalCutLen = 0.0;
394          if (!DoRemoval(silences, iGroup, nGroups, groupFirst, groupLast, totalCutLen))
395             return false;
396          newT1 = std::max(newT1, mT1 - totalCutLen);
397 
398          ++iGroup;
399       }
400    }
401 
402    mT1 = newT1;
403 
404    return true;
405 }
406 
ProcessAll()407 bool EffectTruncSilence::ProcessAll()
408 {
409    // Copy tracks
410    CopyInputTracks(true);
411 
412    // Master list of silent regions.
413    // This list should always be kept in order.
414    RegionList silences;
415 
416    auto trackRange0 = inputTracks()->Selected< const WaveTrack >();
417    if (FindSilences(
418          silences, inputTracks(), *trackRange0.begin(), *trackRange0.rbegin())) {
419       auto trackRange = mOutputTracks->Any();
420       double totalCutLen = 0.0;
421       if (DoRemoval(silences, 0, 1,
422          *trackRange.begin(), *trackRange.rbegin(), totalCutLen)) {
423          mT1 -= totalCutLen;
424          return true;
425       }
426    }
427 
428    return false;
429 }
430 
FindSilences(RegionList & silences,const TrackList * list,const Track * firstTrack,const Track * lastTrack)431 bool EffectTruncSilence::FindSilences
432    (RegionList &silences, const TrackList *list,
433     const Track *firstTrack, const Track *lastTrack)
434 {
435    // Start with the whole selection silent
436    silences.push_back(Region(mT0, mT1));
437 
438    // Remove non-silent regions in each track
439    int whichTrack = 0;
440    for (auto wt :
441            list->Selected< const WaveTrack >()
442                .StartingWith( firstTrack ).EndingAfter( lastTrack ) )
443    {
444       // Smallest silent region to detect in frames
445       auto minSilenceFrames =
446          sampleCount(std::max(mInitialAllowedSilence, DEF_MinTruncMs) * wt->GetRate());
447 
448       //
449       // Scan the track for silences
450       //
451       RegionList trackSilences;
452 
453       auto index = wt->TimeToLongSamples(mT0);
454       sampleCount silentFrame = 0;
455 
456       // Detect silences
457       bool cancelled = !(Analyze(silences, trackSilences, wt, &silentFrame, &index, whichTrack));
458 
459       // Buffer has been freed, so we're OK to return if cancelled
460       if (cancelled)
461       {
462          ReplaceProcessedTracks(false);
463          return false;
464       }
465 
466       if (silentFrame >= minSilenceFrames)
467       {
468          // Track ended in silence -- record region
469          trackSilences.push_back(Region(
470             wt->LongSamplesToTime(index - silentFrame),
471             wt->LongSamplesToTime(index)
472          ));
473       }
474 
475       // Intersect with the overall silent region list
476       Intersect(silences, trackSilences);
477       whichTrack++;
478    }
479 
480    return true;
481 }
482 
DoRemoval(const RegionList & silences,unsigned iGroup,unsigned nGroups,Track * firstTrack,Track * lastTrack,double & totalCutLen)483 bool EffectTruncSilence::DoRemoval
484 (const RegionList &silences, unsigned iGroup, unsigned nGroups, Track *firstTrack, Track *lastTrack,
485  double &totalCutLen)
486 {
487    //
488    // Now remove the silent regions from all selected / sync-lock selected tracks.
489    //
490 
491    // Loop over detected regions in reverse (so cuts don't change time values
492    // down the line)
493    int whichReg = 0;
494    RegionList::const_reverse_iterator rit;
495    for (rit = silences.rbegin(); rit != silences.rend(); ++rit)
496    {
497       const Region &region = *rit;
498       const Region *const r = &region;
499 
500       // Progress dialog and cancellation. Do additional cleanup before return.
501       const double frac = detectFrac +
502          (1 - detectFrac) * (iGroup + whichReg / double(silences.size())) / nGroups;
503       if (TotalProgress(frac))
504       {
505          ReplaceProcessedTracks(false);
506          return false;
507       }
508 
509       // Intersection may create regions smaller than allowed; ignore them.
510       // Allow one nanosecond extra for consistent results with exact milliseconds of allowed silence.
511       if ((r->end - r->start) < (mInitialAllowedSilence - 0.000000001))
512          continue;
513 
514       // Find NEW silence length as requested
515       double inLength = r->end - r->start;
516       double outLength;
517 
518       switch (mActionIndex)
519       {
520       case kTruncate:
521          outLength = std::min(mTruncLongestAllowedSilence, inLength);
522          break;
523       case kCompress:
524          outLength = mInitialAllowedSilence +
525                         (inLength - mInitialAllowedSilence) * mSilenceCompressPercent / 100.0;
526          break;
527       default: // Not currently used.
528          outLength = std::min(mInitialAllowedSilence +
529                               (inLength - mInitialAllowedSilence) * mSilenceCompressPercent / 100.0,
530                            mTruncLongestAllowedSilence);
531       }
532 
533       const double cutLen = std::max(0.0, inLength - outLength);
534       // Don't waste time cutting nothing.
535       if( cutLen == 0.0 )
536          continue;
537 
538       totalCutLen += cutLen;
539 
540       double cutStart = (r->start + r->end - cutLen) / 2;
541       double cutEnd = cutStart + cutLen;
542       (mOutputTracks->Any()
543          .StartingWith(firstTrack).EndingAfter(lastTrack)
544          + &Track::IsSelectedOrSyncLockSelected
545          - [&](const Track *pTrack) { return
546            // Don't waste time past the end of a track
547            pTrack->GetEndTime() < r->start;
548          }
549       ).Visit(
550          [&](WaveTrack *wt) {
551 
552             // In WaveTracks, clear with a cross-fade
553             auto blendFrames = mBlendFrameCount;
554             // Round start/end times to frame boundaries
555             cutStart = wt->LongSamplesToTime(wt->TimeToLongSamples(cutStart));
556             cutEnd = wt->LongSamplesToTime(wt->TimeToLongSamples(cutEnd));
557 
558             // Make sure the cross-fade does not affect non-silent frames
559             if (wt->LongSamplesToTime(blendFrames) > inLength)
560             {
561                // Result is not more than blendFrames:
562                blendFrames = wt->TimeToLongSamples(inLength).as_size_t();
563             }
564 
565             // Perform cross-fade in memory
566             Floats buf1{ blendFrames };
567             Floats buf2{ blendFrames };
568             auto t1 = wt->TimeToLongSamples(cutStart) - blendFrames / 2;
569             auto t2 = wt->TimeToLongSamples(cutEnd) - blendFrames / 2;
570 
571             wt->GetFloats(buf1.get(), t1, blendFrames);
572             wt->GetFloats(buf2.get(), t2, blendFrames);
573 
574             for (decltype(blendFrames) i = 0; i < blendFrames; ++i)
575             {
576                buf1[i] = ((blendFrames-i) * buf1[i] + i * buf2[i]) /
577                          (double)blendFrames;
578             }
579 
580             // Perform the cut
581             wt->Clear(cutStart, cutEnd);
582 
583             // Write cross-faded data
584             wt->Set((samplePtr)buf1.get(), floatSample, t1, blendFrames);
585          },
586          [&](Track *t) {
587             // Non-wave tracks: just do a sync-lock adjust
588             t->SyncLockAdjust(cutEnd, cutStart);
589          }
590       );
591       ++whichReg;
592    }
593 
594    return true;
595 }
596 
Analyze(RegionList & silenceList,RegionList & trackSilences,const WaveTrack * wt,sampleCount * silentFrame,sampleCount * index,int whichTrack,double * inputLength,double * minInputLength)597 bool EffectTruncSilence::Analyze(RegionList& silenceList,
598                                  RegionList& trackSilences,
599                                  const WaveTrack *wt,
600                                  sampleCount* silentFrame,
601                                  sampleCount* index,
602                                  int whichTrack,
603                                  double* inputLength /*= NULL*/,
604                                  double* minInputLength /*= NULL*/)
605 {
606    // Smallest silent region to detect in frames
607    auto minSilenceFrames = sampleCount(std::max( mInitialAllowedSilence, DEF_MinTruncMs) * wt->GetRate());
608 
609    double truncDbSilenceThreshold = DB_TO_LINEAR( mThresholdDB );
610    auto blockLen = wt->GetMaxBlockSize();
611    auto start = wt->TimeToLongSamples(mT0);
612    auto end = wt->TimeToLongSamples(mT1);
613    sampleCount outLength = 0;
614 
615    double previewLength;
616    gPrefs->Read(wxT("/AudioIO/EffectsPreviewLen"), &previewLength, 6.0);
617    // Minimum required length in samples.
618    const sampleCount previewLen( previewLength * wt->GetRate() );
619 
620    // Keep position in overall silences list for optimization
621    RegionList::iterator rit(silenceList.begin());
622 
623    // Allocate buffer
624    Floats buffer{ blockLen };
625 
626    // Loop through current track
627    while (*index < end) {
628       if (inputLength && ((outLength >= previewLen) || (*index - start > wt->TimeToLongSamples(*minInputLength)))) {
629          *inputLength = std::min<double>(*inputLength, *minInputLength);
630          if (outLength >= previewLen) {
631             *minInputLength = *inputLength;
632          }
633          return true;
634       }
635 
636       if (!inputLength) {
637          // Show progress dialog, test for cancellation
638          bool cancelled = TotalProgress(
639                detectFrac * (whichTrack +
640                              (*index - start).as_double() /
641                              (end - start).as_double()) /
642                              (double)GetNumWaveTracks());
643          if (cancelled)
644             return false;
645       }
646 
647       // Optimization: if not in a silent region skip ahead to the next one
648 
649       double curTime = wt->LongSamplesToTime(*index);
650       for ( ; rit != silenceList.end(); ++rit) {
651          // Find the first silent region ending after current time
652          if (rit->end >= curTime) {
653             break;
654          }
655       }
656 
657       if (rit == silenceList.end()) {
658          // No more regions -- no need to process the rest of the track
659          if (inputLength) {
660             // Add available samples up to previewLength.
661             auto remainingTrackSamples = wt->TimeToLongSamples(wt->GetEndTime()) - *index;
662             auto requiredTrackSamples = previewLen - outLength;
663             outLength += (remainingTrackSamples > requiredTrackSamples)? requiredTrackSamples : remainingTrackSamples;
664          }
665 
666          break;
667       }
668       else if (rit->start > curTime) {
669          // End current silent region, skip ahead
670          if (*silentFrame >= minSilenceFrames)  {
671             trackSilences.push_back(Region(
672                wt->LongSamplesToTime(*index - *silentFrame),
673                wt->LongSamplesToTime(*index)
674             ));
675          }
676          *silentFrame = 0;
677          auto newIndex = wt->TimeToLongSamples(rit->start);
678          if (inputLength) {
679             auto requiredTrackSamples = previewLen - outLength;
680             // Add non-silent sample to outLength
681             outLength += ((newIndex - *index) > requiredTrackSamples)? requiredTrackSamples : newIndex - *index;
682          }
683 
684          *index = newIndex;
685       }
686       // End of optimization
687 
688       // Limit size of current block if we've reached the end
689       auto count = limitSampleBufferSize( blockLen, end - *index );
690 
691       // Fill buffer
692       wt->GetFloats((buffer.get()), *index, count);
693 
694       // Look for silenceList in current block
695       for (decltype(count) i = 0; i < count; ++i) {
696          if (inputLength && ((outLength >= previewLen) || (outLength > wt->TimeToLongSamples(*minInputLength)))) {
697             *inputLength = wt->LongSamplesToTime(*index + i) - wt->LongSamplesToTime(start);
698             break;
699          }
700 
701          if (fabs(buffer[i]) < truncDbSilenceThreshold) {
702             (*silentFrame)++;
703          }
704          else {
705             sampleCount allowed = 0;
706             if (*silentFrame >= minSilenceFrames) {
707                if (inputLength) {
708                   switch (mActionIndex) {
709                      case kTruncate:
710                         outLength += wt->TimeToLongSamples(mTruncLongestAllowedSilence);
711                         break;
712                      case kCompress:
713                         allowed = wt->TimeToLongSamples(mInitialAllowedSilence);
714                         outLength += sampleCount(
715                            allowed.as_double() +
716                               (*silentFrame - allowed).as_double()
717                                  * mSilenceCompressPercent / 100.0
718                         );
719                         break;
720                      // default: // Not currently used.
721                   }
722                }
723 
724                // Record the silent region
725                trackSilences.push_back(Region(
726                   wt->LongSamplesToTime(*index + i - *silentFrame),
727                   wt->LongSamplesToTime(*index + i)
728                ));
729             }
730             else if (inputLength) {   // included as part of non-silence
731                outLength += *silentFrame;
732             }
733             *silentFrame = 0;
734             if (inputLength) {
735                 ++outLength;   // Add non-silent sample to outLength
736             }
737          }
738       }
739       // Next block
740       *index += count;
741    }
742 
743    if (inputLength) {
744       *inputLength = std::min<double>(*inputLength, *minInputLength);
745       if (outLength >= previewLen) {
746          *minInputLength = *inputLength;
747       }
748    }
749 
750    return true;
751 }
752 
753 
PopulateOrExchange(ShuttleGui & S)754 void EffectTruncSilence::PopulateOrExchange(ShuttleGui & S)
755 {
756    wxASSERT(nActions == WXSIZEOF(kActionStrings));
757 
758    S.AddSpace(0, 5);
759 
760    S.StartStatic(XO("Detect Silence"));
761    {
762       S.StartMultiColumn(3, wxALIGN_CENTER_HORIZONTAL);
763       {
764          // Threshold
765          mThresholdText = S
766             .Validator<FloatingPointValidator<double>>(
767                3, &mThresholdDB, NumValidatorStyle::NO_TRAILING_ZEROES,
768                MIN_Threshold, MAX_Threshold
769             )
770             .NameSuffix(XO("db"))
771             .AddTextBox(XXO("&Threshold:"), wxT(""), 0);
772          S.AddUnits(XO("dB"));
773 
774          // Ignored silence
775          mInitialAllowedSilenceT = S.Validator<FloatingPointValidator<double>>(
776                3, &mInitialAllowedSilence,
777                NumValidatorStyle::NO_TRAILING_ZEROES,
778                MIN_Minimum, MAX_Minimum)
779             .NameSuffix(XO("seconds"))
780             .AddTextBox(XXO("&Duration:"), wxT(""), 12);
781          S.AddUnits(XO("seconds"));
782       }
783       S.EndMultiColumn();
784    }
785    S.EndStatic();
786 
787    S.StartStatic(XO("Action"));
788    {
789       S.StartHorizontalLay();
790       {
791          // Action choices
792          auto actionChoices = Msgids( kActionStrings, nActions );
793          mActionChoice = S
794             .Validator<wxGenericValidator>(&mActionIndex)
795             .MinSize( { -1, -1 } )
796             .AddChoice( {}, actionChoices );
797       }
798       S.EndHorizontalLay();
799       S.StartMultiColumn(3, wxALIGN_CENTER_HORIZONTAL);
800       {
801          // Truncation / Compression factor
802 
803          mTruncLongestAllowedSilenceT = S.Validator<FloatingPointValidator<double>>(
804                3, &mTruncLongestAllowedSilence,
805                NumValidatorStyle::NO_TRAILING_ZEROES,
806                MIN_Truncate, MAX_Truncate
807             )
808             .NameSuffix(XO("seconds"))
809             .AddTextBox(XXO("Tr&uncate to:"), wxT(""), 12);
810          S.AddUnits(XO("seconds"));
811 
812          mSilenceCompressPercentT = S.Validator<FloatingPointValidator<double>>(
813                3, &mSilenceCompressPercent,
814                NumValidatorStyle::NO_TRAILING_ZEROES,
815                MIN_Compress, MAX_Compress
816             )
817             .NameSuffix(XO("%"))
818             .AddTextBox(XXO("C&ompress to:"), wxT(""), 12);
819          S.AddUnits(XO("%"));
820       }
821       S.EndMultiColumn();
822 
823       S.StartMultiColumn(2, wxALIGN_CENTER_HORIZONTAL);
824       {
825          mIndependent = S.AddCheckBox(XXO("Trunc&ate tracks independently"),
826             mbIndependent);
827       }
828    S.EndMultiColumn();
829 }
830    S.EndStatic();
831 
832    UpdateUI();
833 }
834 
TransferDataToWindow()835 bool EffectTruncSilence::TransferDataToWindow()
836 {
837    if (!mUIParent->TransferDataToWindow())
838    {
839       return false;
840    }
841 
842    return true;
843 }
844 
TransferDataFromWindow()845 bool EffectTruncSilence::TransferDataFromWindow()
846 {
847    if (!mUIParent->Validate() || !mUIParent->TransferDataFromWindow())
848    {
849       return false;
850    }
851 
852    mbIndependent = mIndependent->IsChecked();
853 
854    return true;
855 }
856 
857 // EffectTruncSilence implementation
858 
859 // Finds the intersection of the ordered region lists, stores in dest
Intersect(RegionList & dest,const RegionList & src)860 void EffectTruncSilence::Intersect(RegionList &dest, const RegionList &src)
861 {
862    RegionList::iterator destIter;
863    destIter = dest.begin();
864    // Any time we reach the end of the dest list we're finished
865    if (destIter == dest.end())
866       return;
867    RegionList::iterator curDest = destIter;
868 
869    // Operation: find non-silent regions in src, remove them from dest.
870    double nsStart = curDest->start;
871    double nsEnd;
872    bool lastRun = false; // must run the loop one extra time
873 
874    RegionList::const_iterator srcIter = src.begin();
875 
876    // This logic, causing the loop to run once after end of src, must occur
877    // each time srcIter is updated
878    if (srcIter == src.end())
879    {
880       lastRun = true;
881    }
882 
883    while (srcIter != src.end() || lastRun)
884    {
885       // Don't use curSrc unless lastRun is false!
886       RegionList::const_iterator curSrc;
887 
888       if (lastRun)
889       {
890          // The last non-silent region extends as far as possible
891          nsEnd = std::numeric_limits<double>::max();
892       }
893       else
894       {
895          curSrc = srcIter;
896          nsEnd = curSrc->start;
897       }
898 
899       if (nsEnd > nsStart)
900       {
901          // Increment through dest until we have a region that could be affected
902          while (curDest->end <= nsStart)
903          {
904             ++destIter;
905             if (destIter == dest.end())
906             {
907                return;
908             }
909             curDest = destIter;
910          }
911 
912          // Check for splitting dest region in two
913          if (nsStart > curDest->start && nsEnd < curDest->end)
914          {
915             // The second region
916             Region r(nsEnd, curDest->end);
917 
918             // The first region
919             curDest->end = nsStart;
920 
921             // Insert second region after first
922             RegionList::iterator nextIt(destIter);
923             ++nextIt;
924 
925             // This should just read: destIter = dest.insert(nextIt, r); but we
926             // work around two two wxList::insert() bugs. First, in some
927             // versions it returns the wrong value. Second, in some versions,
928             // it crashes when you insert at list end.
929             if (nextIt == dest.end())
930                dest.push_back(r);
931             else
932                dest.insert(nextIt, r);
933             ++destIter;          // (now points at the newly-inserted region)
934 
935             curDest = destIter;
936          }
937 
938          // Check for truncating the end of dest region
939          if (nsStart > curDest->start && nsStart < curDest->end &&
940                nsEnd >= curDest->end)
941          {
942             curDest->end = nsStart;
943 
944             ++destIter;
945             if (destIter == dest.end())
946             {
947                return;
948             }
949             curDest = destIter;
950          }
951 
952          // Check for all dest regions that need to be removed completely
953          while (nsStart <= curDest->start && nsEnd >= curDest->end)
954          {
955             destIter = dest.erase(destIter);
956             if (destIter == dest.end())
957             {
958                return;
959             }
960             curDest = destIter;
961          }
962 
963          // Check for truncating the beginning of dest region
964          if (nsStart <= curDest->start &&
965                nsEnd > curDest->start && nsEnd < curDest->end)
966          {
967             curDest->start = nsEnd;
968          }
969       }
970 
971       if (lastRun)
972       {
973          // done
974          lastRun = false;
975       }
976       else
977       {
978          // Next non-silent region starts at the end of this silent region
979          nsStart = curSrc->end;
980          ++srcIter;
981          if (srcIter == src.end())
982          {
983             lastRun = true;
984          }
985       }
986    }
987 }
988 
989 /*
990 void EffectTruncSilence::BlendFrames(float* buffer, int blendFrameCount, int leftIndex, int rightIndex)
991 {
992    float* bufOutput = &buffer[leftIndex];
993    float* bufBefore = &buffer[leftIndex];
994    float* bufAfter  = &buffer[rightIndex];
995    double beforeFactor = 1.0;
996    double afterFactor  = 0.0;
997    double adjFactor = 1.0 / (double)blendFrameCount;
998    for (int j = 0; j < blendFrameCount; ++j)
999    {
1000       bufOutput[j] = (float)((bufBefore[j] * beforeFactor) + (bufAfter[j] * afterFactor));
1001       beforeFactor -= adjFactor;
1002       afterFactor  += adjFactor;
1003    }
1004 }
1005 */
1006 
UpdateUI()1007 void EffectTruncSilence::UpdateUI()
1008 {
1009    switch (mActionIndex)
1010    {
1011    case kTruncate:
1012       mTruncLongestAllowedSilenceT->Enable(true);
1013       mSilenceCompressPercentT->Enable(false);
1014       break;
1015    case kCompress:
1016       mTruncLongestAllowedSilenceT->Enable(false);
1017       mSilenceCompressPercentT->Enable(true);
1018    }
1019 }
1020 
OnControlChange(wxCommandEvent & WXUNUSED (evt))1021 void EffectTruncSilence::OnControlChange(wxCommandEvent & WXUNUSED(evt))
1022 {
1023    mActionChoice->GetValidator()->TransferFromWindow();
1024 
1025    UpdateUI();
1026 
1027    if (!EnableApply(mUIParent->TransferDataFromWindow()))
1028    {
1029       return;
1030    }
1031 }
1032