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