Audacity  2.2.2
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;
40  static const double Db2Signal[];
42 };
43 
45  // Yuck, why is this a choice control and not a slider? I'm leaving this
46  // table of names alone for now -- PRL
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 const double Enums::Db2Signal[] =
63 // -20dB -25dB -30dB -35dB -40dB -45dB -50dB -55dB -60dB -65dB -70dB -75dB -80dB
64 { 0.10000, 0.05620, 0.03160, 0.01780, 0.01000, 0.00562, 0.00316, 0.00178, 0.00100, 0.000562, 0.000316, 0.000178, 0.0001000 };
65 
66 
67 const size_t Enums::NumDbChoices = WXSIZEOF(Enums::DbChoices);
68 
69 static_assert( Enums::NumDbChoices == WXSIZEOF( Enums::Db2Signal ),
70  "size mismatch" );
71 
72 // Declaration of RegionList
73 class RegionList : public std::list < Region > {};
74 
76 {
80 };
81 
83 {
84  { wxT("Truncate"), XO("Truncate Detected Silence") },
85  { wxT("Compress"), XO("Compress Excess Silence") }
86 };
87 
89  // Compatible with 2.1.0 and before
90  { wxT("0"), 0 }, // Remap to Truncate Detected Silence
91  { wxT("1"), 1 }, // Remap to Compress Excess Silence
92 };
93 
94 static const size_t nObsoleteActions = WXSIZEOF( kObsoleteActions );
95 
96 // Define defaults, minimums, and maximums for each parameter
97 #define DefaultAndLimits(name, def, min, max) \
98  static const double DEF_ ## name = (def); \
99  static const double MIN_ ## name = (min); \
100  static const double MAX_ ## name = (max);
101 
102 // Define keys, defaults, minimums, and maximums for the effect parameters
103 //
104 // Name Type Key Def Min Max Scale
105 Param( DbIndex, int, wxT("Db"), 0, 0, Enums::NumDbChoices - 1, 1 );
106 Param( ActIndex, int, wxT("Action"), kTruncate, 0, nActions - 1, 1 );
107 Param( Minimum, double, wxT("Minimum"), 0.5, 0.001, 10000.0, 1 );
108 Param( Truncate, double, wxT("Truncate"), 0.5, 0.0, 10000.0, 1 );
109 Param( Compress, double, wxT("Compress"), 50.0, 0.0, 99.9, 1 );
110 Param( Independent, bool, wxT("Independent"), false, false, true, 1 );
111 
112 static const size_t DEF_BlendFrameCount = 100;
113 
114 // Lower bound on the amount of silence to find at a time -- this avoids
115 // detecting silence repeatedly in low-frequency sounds.
116 static const double DEF_MinTruncMs = 0.001;
117 
118 // Typical fraction of total time taken by detection (better to guess low)
119 const double detectFrac = 0.4;
120 
121 BEGIN_EVENT_TABLE(EffectTruncSilence, wxEvtHandler)
122  EVT_CHOICE(wxID_ANY, EffectTruncSilence::OnControlChange)
123  EVT_TEXT(wxID_ANY, EffectTruncSilence::OnControlChange)
125 
127 {
128  mInitialAllowedSilence = DEF_Minimum;
129  mTruncLongestAllowedSilence = DEF_Truncate;
130  mSilenceCompressPercent = DEF_Compress;
131  mTruncDbChoiceIndex = DEF_DbIndex;
132  mActionIndex = DEF_ActIndex;
133  mbIndependent = DEF_Independent;
134 
135  SetLinearEffectFlag(false);
136 
137  // This used to be changeable via the audacity.cfg/registery. Doubtful that was
138  // ever done.
139  //
140  // Original comment:
141  //
142  // mBlendFrameCount only retrieved from prefs ... not using dialog
143  // Only way to change (for windows) is thru registry
144  // The values should be figured dynamically ... too many frames could be invalid
145  mBlendFrameCount = DEF_BlendFrameCount;
146 }
147 
149 {
150 }
151 
152 // IdentInterface implementation
153 
155 {
157 }
158 
160 {
161  return _("Automatically reduces the length of passages where the volume is below a specified level");
162 }
163 
165 {
166  return wxT("Truncate_Silence");
167 }
168 
169 // EffectDefinitionInterface implementation
170 
172 {
173  return EffectTypeProcess;
174 }
175 
176 // EffectClientInterface implementation
177 
179  S.SHUTTLE_ENUM_PARAM( mTruncDbChoiceIndex, DbIndex,
180  Enums::DbChoices, Enums::NumDbChoices );
181  S.SHUTTLE_ENUM_PARAM( mActionIndex, ActIndex, kActionStrings, nActions );
182  S.SHUTTLE_PARAM( mInitialAllowedSilence, Minimum );
183  S.SHUTTLE_PARAM( mTruncLongestAllowedSilence, Truncate );
184  S.SHUTTLE_PARAM( mSilenceCompressPercent, Compress );
185  S.SHUTTLE_PARAM( mbIndependent, Independent );
186  return true;
187 }
188 
190 {
191  parms.Write(KEY_DbIndex, Enums::DbChoices[mTruncDbChoiceIndex].Internal());
192  parms.Write(KEY_ActIndex, kActionStrings[mActionIndex].Internal());
193  parms.Write(KEY_Minimum, mInitialAllowedSilence);
194  parms.Write(KEY_Truncate, mTruncLongestAllowedSilence);
195  parms.Write(KEY_Compress, mSilenceCompressPercent);
196  parms.Write(KEY_Independent, mbIndependent);
197 
198  return true;
199 }
200 
202 {
203  ReadAndVerifyDouble(Minimum);
204  ReadAndVerifyDouble(Truncate);
205  ReadAndVerifyDouble(Compress);
206  ReadAndVerifyEnum(DbIndex, Enums::DbChoices, Enums::NumDbChoices);
207  ReadAndVerifyEnumWithObsoletes(ActIndex, kActionStrings, nActions,
208  kObsoleteActions, nObsoleteActions);
209  ReadAndVerifyBool(Independent);
210 
211  mInitialAllowedSilence = Minimum;
212  mTruncLongestAllowedSilence = Truncate;
213  mSilenceCompressPercent = Compress;
214  mTruncDbChoiceIndex = DbIndex;
215  mActionIndex = ActIndex;
216  mbIndependent = Independent;
217 
218  return true;
219 }
220 
221 // Effect implementation
222 
223 double EffectTruncSilence::CalcPreviewInputLength(double /* previewLength */)
224 {
225  double inputLength = mT1 - mT0;
226  double minInputLength = inputLength;
227 
228  // Master list of silent regions
229  RegionList silences;
230 
231  // Start with the whole selection silent
232  silences.push_back(Region(mT0, mT1));
233 
235  int whichTrack = 0;
236 
237  for (Track *t = iter.First(); t; t = iter.Next()) {
238  WaveTrack *const wt = static_cast<WaveTrack *>(t);
239 
240  RegionList trackSilences;
241 
242  auto index = wt->TimeToLongSamples(mT0);
243  sampleCount silentFrame = 0; // length of the current silence
244 
245  Analyze(silences, trackSilences, wt, &silentFrame, &index, whichTrack, &inputLength, &minInputLength);
246 
247  whichTrack++;
248  }
249  return inputLength;
250 }
251 
252 
254 {
255  wxString base = wxT("/Effects/TruncateSilence/");
256 
257  // Migrate settings from 2.1.0 or before
258 
259  // Already migrated, so bail
260  if (gPrefs->Exists(base + wxT("Migrated")))
261  {
262  return true;
263  }
264 
265  // Load the old "current" settings
266  if (gPrefs->Exists(base))
267  {
268  mTruncDbChoiceIndex = gPrefs->Read(base + wxT("DbChoiceIndex"), 4L);
270  { // corrupted Prefs?
271  mTruncDbChoiceIndex = 4L;
272  }
273  mActionIndex = gPrefs->Read(base + wxT("ProcessChoice"), 0L);
274  if ((mActionIndex < 0) || (mActionIndex > 1))
275  { // corrupted Prefs?
276  mActionIndex = 0L;
277  }
278  gPrefs->Read(base + wxT("InitialAllowedSilence"), &mInitialAllowedSilence, 0.5);
279  if ((mInitialAllowedSilence < 0.001) || (mInitialAllowedSilence > 10000.0))
280  { // corrupted Prefs?
282  }
283  gPrefs->Read(base + wxT("LongestAllowedSilence"), &mTruncLongestAllowedSilence, 0.5);
284  if ((mTruncLongestAllowedSilence < 0.0) || (mTruncLongestAllowedSilence > 10000.0))
285  { // corrupted Prefs?
287  }
288  gPrefs->Read(base + wxT("CompressPercent"), &mSilenceCompressPercent, 50.0);
289  if ((mSilenceCompressPercent < 0.0) || (mSilenceCompressPercent > 100.0))
290  { // corrupted Prefs?
292  }
293 
295  }
296 
297  // Do not migrate again
298  gPrefs->Write(base + wxT("Migrated"), true);
299 
300  return true;
301 }
302 
304 {
305  const bool success =
308  : ProcessAll();
309 
310  if (success)
312 
313  return success;
314 }
315 
317 {
318  unsigned nGroups = 0;
319 
320  const bool syncLock = ::GetActiveProject()->IsSyncLocked();
321 
322  // Check if it's permissible
323  {
325  for (Track *track = iter.First(); track;
326  track = iter.Next(true) // skip linked tracks
327  ) {
328  if (syncLock) {
329  Track *const link = track->GetLink();
331  for (Track *track2 = syncIter.StartWith(track); track2; track2 = syncIter.Next()) {
332  if (track2->GetKind() == Track::Wave &&
333  !(track2 == track || track2 == link) &&
334  track2->GetSelected()) {
335  ::Effect::MessageBox(_("When truncating independently, there may only be one selected audio track in each Sync-Locked Track Group."));
336  return false;
337  }
338  }
339  }
340 
341  ++nGroups;
342  }
343  }
344 
345  if (nGroups == 0)
346  // nothing to do
347  return true;
348 
349  // Now do the work
350 
351  // Copy tracks
353  double newT1 = 0.0;
354 
355  {
356  unsigned iGroup = 0;
358  for (Track *track = iter.First(); track;
359  ++iGroup, track = iter.Next(true) // skip linked tracks
360  ) {
361  Track *const link = track->GetLink();
362  Track *const last = link ? link : track;
363 
364  RegionList silences;
365 
366  if (!FindSilences(silences, mOutputTracks.get(), track, last))
367  return false;
368  // Treat tracks in the sync lock group only
369  Track *groupFirst, *groupLast;
370  if (syncLock) {
371  SyncLockedTracksIterator syncIter(mOutputTracks.get());
372  groupFirst = syncIter.StartWith(track);
373  groupLast = syncIter.Last();
374  }
375  else {
376  groupFirst = track;
377  groupLast = last;
378  }
379  double totalCutLen = 0.0;
380  if (!DoRemoval(silences, iGroup, nGroups, groupFirst, groupLast, totalCutLen))
381  return false;
382  newT1 = std::max(newT1, mT1 - totalCutLen);
383  }
384  }
385 
386  mT1 = newT1;
387 
388  return true;
389 }
390 
392 {
393  // Copy tracks
395 
396  // Master list of silent regions.
397  // This list should always be kept in order.
398  RegionList silences;
399 
401  if (FindSilences(silences, inputTracks(), iter.First(), iter.Last())) {
402  TrackListIterator iterOut(mOutputTracks.get());
403  double totalCutLen = 0.0;
404  Track *const first = iterOut.First();
405  if (DoRemoval(silences, 0, 1, first, iterOut.Last(), totalCutLen)) {
406  mT1 -= totalCutLen;
407  return true;
408  }
409  }
410 
411  return false;
412 }
413 
415  (RegionList &silences, TrackList *list, Track *firstTrack, Track *lastTrack)
416 {
417  // Start with the whole selection silent
418  silences.push_back(Region(mT0, mT1));
419 
420  // Remove non-silent regions in each track
422  int whichTrack = 0;
423  bool lastSeen = false;
424  for (Track *t = iter.StartWith(firstTrack); !lastSeen && t; t = iter.Next())
425  {
426  lastSeen = (t == lastTrack);
427  WaveTrack *const wt = static_cast<WaveTrack *>(t);
428 
429  // Smallest silent region to detect in frames
430  auto minSilenceFrames =
431  sampleCount(std::max(mInitialAllowedSilence, DEF_MinTruncMs) * wt->GetRate());
432 
433  //
434  // Scan the track for silences
435  //
436  RegionList trackSilences;
437 
438  auto index = wt->TimeToLongSamples(mT0);
439  sampleCount silentFrame = 0;
440 
441  // Detect silences
442  bool cancelled = !(Analyze(silences, trackSilences, wt, &silentFrame, &index, whichTrack));
443 
444  // Buffer has been freed, so we're OK to return if cancelled
445  if (cancelled)
446  {
447  ReplaceProcessedTracks(false);
448  return false;
449  }
450 
451  if (silentFrame >= minSilenceFrames)
452  {
453  // Track ended in silence -- record region
454  trackSilences.push_back(Region(
455  wt->LongSamplesToTime(index - silentFrame),
456  wt->LongSamplesToTime(index)
457  ));
458  }
459 
460  // Intersect with the overall silent region list
461  Intersect(silences, trackSilences);
462  whichTrack++;
463  }
464 
465  return true;
466 }
467 
469 (const RegionList &silences, unsigned iGroup, unsigned nGroups, Track *firstTrack, Track *lastTrack,
470  double &totalCutLen)
471 {
472  //
473  // Now remove the silent regions from all selected / sync-lock selected tracks.
474  //
475 
476  // Loop over detected regions in reverse (so cuts don't change time values
477  // down the line)
478  int whichReg = 0;
479  RegionList::const_reverse_iterator rit;
480  for (rit = silences.rbegin(); rit != silences.rend(); ++rit)
481  {
482  const Region &region = *rit;
483  const Region *const r = &region;
484 
485  // Progress dialog and cancellation. Do additional cleanup before return.
486  const double frac = detectFrac +
487  (1 - detectFrac) * (iGroup + whichReg / double(silences.size())) / nGroups;
488  if (TotalProgress(frac))
489  {
490  ReplaceProcessedTracks(false);
491  return false;
492  }
493 
494  // Intersection may create regions smaller than allowed; ignore them.
495  // Allow one nanosecond extra for consistent results with exact milliseconds of allowed silence.
496  if ((r->end - r->start) < (mInitialAllowedSilence - 0.000000001))
497  continue;
498 
499  // Find NEW silence length as requested
500  double inLength = r->end - r->start;
501  double outLength;
502 
503  switch (mActionIndex)
504  {
505  case kTruncate:
506  outLength = std::min(mTruncLongestAllowedSilence, inLength);
507  break;
508  case kCompress:
509  outLength = mInitialAllowedSilence +
510  (inLength - mInitialAllowedSilence) * mSilenceCompressPercent / 100.0;
511  break;
512  default: // Not currently used.
513  outLength = std::min(mInitialAllowedSilence +
514  (inLength - mInitialAllowedSilence) * mSilenceCompressPercent / 100.0,
515  mTruncLongestAllowedSilence);
516  }
517 
518  double cutLen = std::max(0.0, inLength - outLength);
519  totalCutLen += cutLen;
520 
521  TrackListIterator iterOut(mOutputTracks.get());
522  bool lastSeen = false;
523  for (Track *t = iterOut.StartWith(firstTrack); t && !lastSeen; t = iterOut.Next())
524  {
525  lastSeen = (t == lastTrack);
526  if (!(t->GetSelected() || t->IsSyncLockSelected()))
527  continue;
528 
529  // Don't waste time past the end of a track
530  if (t->GetEndTime() < r->start)
531  continue;
532 
533  // Don't waste time cutting nothing.
534  if( cutLen == 0.0 )
535  continue;
536 
537  double cutStart = (r->start + r->end - cutLen) / 2;
538  double cutEnd = cutStart + cutLen;
539  if (t->GetKind() == Track::Wave)
540  {
541  // In WaveTracks, clear with a cross-fade
542  WaveTrack *const wt = static_cast<WaveTrack*>(t);
543  auto blendFrames = mBlendFrameCount;
544  // Round start/end times to frame boundaries
545  cutStart = wt->LongSamplesToTime(wt->TimeToLongSamples(cutStart));
546  cutEnd = wt->LongSamplesToTime(wt->TimeToLongSamples(cutEnd));
547 
548  // Make sure the cross-fade does not affect non-silent frames
549  if (wt->LongSamplesToTime(blendFrames) > inLength)
550  {
551  // Result is not more than blendFrames:
552  blendFrames = wt->TimeToLongSamples(inLength).as_size_t();
553  }
554 
555  // Perform cross-fade in memory
556  Floats buf1{ blendFrames };
557  Floats buf2{ blendFrames };
558  auto t1 = wt->TimeToLongSamples(cutStart) - blendFrames / 2;
559  auto t2 = wt->TimeToLongSamples(cutEnd) - blendFrames / 2;
560 
561  wt->Get((samplePtr)buf1.get(), floatSample, t1, blendFrames);
562  wt->Get((samplePtr)buf2.get(), floatSample, t2, blendFrames);
563 
564  for (decltype(blendFrames) i = 0; i < blendFrames; ++i)
565  {
566  buf1[i] = ((blendFrames-i) * buf1[i] + i * buf2[i]) /
567  (double)blendFrames;
568  }
569 
570  // Perform the cut
571  wt->Clear(cutStart, cutEnd);
572 
573  // Write cross-faded data
574  wt->Set((samplePtr)buf1.get(), floatSample, t1, blendFrames);
575  }
576  else
577  // Non-wave tracks: just do a sync-lock adjust
578  t->SyncLockAdjust(cutEnd, cutStart);
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 = Enums::Db2Signal[mTruncDbChoiceIndex];
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  auto dbChoices =
755  LocalizedStrings( Enums::DbChoices, Enums::NumDbChoices );
756  mTruncDbChoice = S.AddChoice(_("Level:"), wxT(""), &dbChoices);
757  mTruncDbChoice->SetValidator(wxGenericValidator(&mTruncDbChoiceIndex));
758  S.SetSizeHints(-1, -1);
759  S.AddSpace(0); // 'choices' already includes units.
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:461
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
A list of TrackListNode items.
Definition: Track.h:618
bool SaveUserPreset(const wxString &name) override
Definition: Effect.cpp:600
int MessageBox(const wxString &message, long style=DefaultMessageBoxStyle, const wxString &titleStr=wxString{})
Definition: Effect.cpp:2660
bool TotalProgress(double frac)
Definition: Effect.cpp:1977
Derived from ShuttleGuiBase, an Audacity specific class for shuttling data to and from GUI...
Definition: ShuttleGui.h:409
wxChoice * mTruncDbChoice
Definition: TruncSilence.h:109
static const double Db2Signal[]
wxString GetCurrentSettingsGroup() override
Definition: Effect.cpp:801
void CopyInputTracks()
Definition: Effect.cpp:2036
wxArrayString LocalizedStrings(const IdentInterfaceSymbol strings[], size_t nStrings)
Definition: Internat.cpp:303
void ReplaceProcessedTracks(const bool bGoodResult)
Definition: Effect.cpp:2162
wxString GetDescription() override
IdentInterfaceSymbol GetSymbol() override
Track * Next(bool skiplinked=false) override
Definition: Track.cpp:709
void EndMultiColumn()
Enums is a helper class for Shuttle. It defines enumerations which are used in effects dialogs...
double CalcPreviewInputLength(double previewLength) override
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:678
double GetEndTime() const override
Get the time at which the last clip in the track ends, plus recorded stuff.
Definition: WaveTrack.cpp:1873
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
Track * StartWith(Track *val) override
Definition: Track.cpp:548
TrackList * inputTracks() const
Definition: Effect.h:458
virtual ~EffectTruncSilence()
size_t limitSampleBufferSize(size_t bufferSize, sampleCount limit)
Definition: Types.h:178
void EndHorizontalLay()
Track * Next(bool skiplinked=false) override
Definition: Track.cpp:569
static const IdentInterfaceSymbol kActionStrings[nActions]
static CommandParameters::ObsoleteMap kObsoleteActions[]
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:540
#define ReadAndVerifyEnum(name, list, listSize)
Definition: Effect.h:786
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:110
kActions
void Set(samplePtr buffer, sampleFormat format, sampleCount start, size_t len)
Definition: WaveTrack.cpp:2052
wxTextCtrl * mTruncLongestAllowedSilenceT
Definition: TruncSilence.h:112
bool FindSilences(RegionList &silences, TrackList *list, Track *firstTrack, Track *lastTrack)
wxString ManualPage() override
void StartHorizontalLay(int PositionFlags=wxALIGN_CENTRE, int iProp=1)
Track * Last(bool skiplinked=false) override
Definition: Track.cpp:592
static const size_t NumDbChoices
double mTruncLongestAllowedSilence
Definition: TruncSilence.h:103
void StartMultiColumn(int nCols, int PositionFlags=wxALIGN_LEFT)
wxChoice * AddChoice(const wxString &Prompt, const wxString &Selected, const wxArrayString *pChoices)
Definition: ShuttleGui.cpp:371
wxCheckBox * mIndependent
Definition: TruncSilence.h:114
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:101
#define ReadAndVerifyDouble(name)
Definition: Effect.h:798
#define ReadAndVerifyEnumWithObsoletes(name, list, listSize, obsoleteList, nObsolete)
Definition: Effect.h:791
int min(int a, int b)
static const size_t DEF_BlendFrameCount
IdentInterfaceSymbol pairs a persistent string identifier used internally with an optional...
size_t GetMaxBlockSize() const
Definition: WaveTrack.cpp:1625
static const size_t nObsoleteActions
bool DefineParams(ShuttleParams &S) override
wxTextCtrl * mInitialAllowedSilenceT
Definition: TruncSilence.h:111
wxWindow * mUIParent
Definition: Effect.h:472
An iterator for a TrackList.
Definition: Track.h:401
_("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
EffectType
static const IdentInterfaceSymbol DbChoices[]
double end
Definition: WaveTrack.h:47
int GetNumWaveTracks()
Definition: Effect.h:343
Track * GetLink() const
Definition: Track.cpp:269
bool IsSyncLocked()
Definition: Project.cpp:5651
sampleCount TimeToLongSamples(double t0) const
Convert correctly between an (absolute) time in seconds and a number of samples.
Definition: WaveTrack.cpp:1843
AUDACITY_DLL_API AudacityProject * GetActiveProject()
Definition: Project.cpp:308
bool SetAutomationParameters(CommandParameters &parms) override
wxStaticBox * StartStatic(const wxString &Str, int iProp=0)
Definition: ShuttleGui.cpp:763
bool GetAutomationParameters(CommandParameters &parms) override
#define ReadAndVerifyBool(name)
Definition: Effect.h:800
Track * First(TrackList *val=NULL) override
Definition: Track.cpp:558
double mSilenceCompressPercent
Definition: TruncSilence.h:104
wxTextCtrl * mSilenceCompressPercentT
Definition: TruncSilence.h:113
double mInitialAllowedSilence
Definition: TruncSilence.h:102
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
END_EVENT_TABLE()
wxSizerItem * AddSpace(int width, int height)
double GetRate() const
Definition: WaveTrack.cpp:398
bool Get(samplePtr buffer, sampleFormat format, sampleCount start, size_t len, fillFormat fill=fillZero, bool mayThrow=true, sampleCount *pNumCopied=nullptr) const
Definition: WaveTrack.cpp:1971
bool TransferDataToWindow() override
double LongSamplesToTime(sampleCount pos) const
Convert correctly between an number of samples and an (absolute) time in seconds. ...
Definition: WaveTrack.cpp:1848
Track * StartWith(Track *member) override
Definition: Track.cpp:662
void OnControlChange(wxCommandEvent &evt)
size_t as_size_t() const
Definition: Types.h:92
std::shared_ptr< TrackList > mOutputTracks
Definition: Effect.h:459
bool TransferDataFromWindow() override
virtual bool EnableApply(bool enable=true)
Definition: Effect.cpp:1886
double mT0
Definition: Effect.h:460
static const double DEF_MinTruncMs