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 ®ion = *rit;
498 const Region *const r = ®ion;
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