1 /**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 WaveTrack.cpp
6
7 Dominic Mazzoni
8
9 *******************************************************************//**
10
11 \class WaveTrack
12 \brief A Track that contains audio waveform data.
13
14 *//****************************************************************//**
15
16 \class WaveTrack::Location
17 \brief Used only by WaveTrack, a special way to hold location that
18 can accommodate merged regions.
19
20 *//****************************************************************/
21
22 /*!
23 @class WaveTrackFactory
24 @brief Used to create or clone a WaveTrack, with appropriate context
25 from the project that will own the track.
26 */
27
28
29 #include "WaveTrack.h"
30
31
32
33 #include "WaveClip.h"
34
35 #include <wx/defs.h>
36 #include <wx/intl.h>
37 #include <wx/debug.h>
38 #include <wx/log.h>
39
40 #include <float.h>
41 #include <math.h>
42 #include <algorithm>
43 #include <optional>
44
45 #include "float_cast.h"
46
47 #include "Envelope.h"
48 #include "Sequence.h"
49
50 #include "Project.h"
51 #include "ProjectRate.h"
52
53 #include "Prefs.h"
54
55 #include "effects/TimeWarper.h"
56 #include "QualitySettings.h"
57 #include "prefs/SpectrogramSettings.h"
58 #include "prefs/TracksPrefs.h"
59 #include "prefs/TracksBehaviorsPrefs.h"
60 #include "prefs/WaveformSettings.h"
61
62 #include "InconsistencyException.h"
63
64 #include "tracks/ui/TrackView.h"
65 #include "tracks/ui/TrackControls.h"
66
67 #include "ProjectFormatExtensionsRegistry.h"
68
69 using std::max;
70
71 namespace {
72
AreAligned(const WaveClipPointers & a,const WaveClipPointers & b)73 bool AreAligned(const WaveClipPointers& a, const WaveClipPointers& b)
74 {
75 if (a.size() != b.size())
76 return false;
77
78 const auto compare = [](const WaveClip* a, const WaveClip* b) {
79 // clips are aligned if both sequence start/end
80 // points and play start/end points of the first clip match
81 // the corresponding points of the other clip
82 return a->GetPlayStartTime() == b->GetPlayStartTime() &&
83 a->GetSequenceStartTime() == b->GetSequenceStartTime() &&
84 a->GetPlayEndTime() == b->GetPlayEndTime() &&
85 a->GetSequenceEndTime() == b->GetSequenceEndTime();
86 };
87
88 return std::mismatch(a.begin(), a.end(), b.begin(), compare).first == a.end();
89 }
90
91 //Handles possible future file values
ToLinkType(int value)92 Track::LinkType ToLinkType(int value)
93 {
94 if (value < 0)
95 return Track::LinkType::None;
96 else if (value > 3)
97 return Track::LinkType::Group;
98 return static_cast<Track::LinkType>(value);
99 }
100
101 }
102
103 static ProjectFileIORegistry::ObjectReaderEntry readerEntry{
104 "wavetrack",
__anon8aaf33e20302( )105 []( AudacityProject &project ){
106 auto &trackFactory = WaveTrackFactory::Get( project );
107 auto &tracks = TrackList::Get( project );
108 auto result = tracks.Add(trackFactory.NewWaveTrack());
109 TrackView::Get( *result );
110 TrackControls::Get( *result );
111 return result;
112 }
113 };
114
DuplicateWaveTrack(const WaveTrack & orig)115 WaveTrack::Holder WaveTrackFactory::DuplicateWaveTrack(const WaveTrack &orig)
116 {
117 auto waveTrack = std::static_pointer_cast<WaveTrack>( orig.Duplicate() );
118
119 return waveTrack;
120 }
121
122
NewWaveTrack(sampleFormat format,double rate)123 WaveTrack::Holder WaveTrackFactory::NewWaveTrack(sampleFormat format, double rate)
124 {
125 if (format == (sampleFormat)0)
126 format = QualitySettings::SampleFormatChoice();
127 if (rate == 0)
128 rate = mRate.GetRate();
129
130 auto waveTrack = std::make_shared<WaveTrack> ( mpFactory, format, rate );
131
132 return waveTrack;
133 }
134
WaveTrack(const SampleBlockFactoryPtr & pFactory,sampleFormat format,double rate)135 WaveTrack::WaveTrack( const SampleBlockFactoryPtr &pFactory,
136 sampleFormat format, double rate )
137 : PlayableTrack()
138 , mpFactory(pFactory)
139 {
140 mLegacyProjectFileOffset = 0;
141
142 mFormat = format;
143 mRate = (int) rate;
144 mGain = 1.0;
145 mPan = 0.0;
146 mOldGain[0] = 0.0;
147 mOldGain[1] = 0.0;
148 mWaveColorIndex = 0;
149 SetDefaultName(TracksPrefs::GetDefaultAudioTrackNamePreference());
150 SetName(GetDefaultName());
151 mDisplayMin = -1.0;
152 mDisplayMax = 1.0;
153 mSpectrumMin = mSpectrumMax = -1; // so values will default to settings
154 mLastScaleType = -1;
155 mLastdBRange = -1;
156 }
157
WaveTrack(const WaveTrack & orig)158 WaveTrack::WaveTrack(const WaveTrack &orig):
159 PlayableTrack(orig)
160 , mpFactory( orig.mpFactory )
161 , mpSpectrumSettings(orig.mpSpectrumSettings
162 ? std::make_unique<SpectrogramSettings>(*orig.mpSpectrumSettings)
163 : nullptr
164 )
165 , mpWaveformSettings(orig.mpWaveformSettings
166 ? std::make_unique<WaveformSettings>(*orig.mpWaveformSettings)
167 : nullptr
168 )
169 {
170 mLastScaleType = -1;
171 mLastdBRange = -1;
172
173 mLegacyProjectFileOffset = 0;
174
175 Init(orig);
176
177 for (const auto &clip : orig.mClips)
178 mClips.push_back
179 ( std::make_unique<WaveClip>( *clip, mpFactory, true ) );
180 }
181
182 // Copy the track metadata but not the contents.
Init(const WaveTrack & orig)183 void WaveTrack::Init(const WaveTrack &orig)
184 {
185 PlayableTrack::Init(orig);
186 mpFactory = orig.mpFactory;
187
188 mFormat = orig.mFormat;
189 mWaveColorIndex = orig.mWaveColorIndex;
190 mRate = orig.mRate;
191 mGain = orig.mGain;
192 mPan = orig.mPan;
193 mOldGain[0] = 0.0;
194 mOldGain[1] = 0.0;
195 SetDefaultName(orig.GetDefaultName());
196 SetName(orig.GetName());
197 mDisplayMin = orig.mDisplayMin;
198 mDisplayMax = orig.mDisplayMax;
199 mSpectrumMin = orig.mSpectrumMin;
200 mSpectrumMax = orig.mSpectrumMax;
201 mDisplayLocationsCache.clear();
202 }
203
Reinit(const WaveTrack & orig)204 void WaveTrack::Reinit(const WaveTrack &orig)
205 {
206 Init(orig);
207
208 {
209 auto &settings = orig.mpSpectrumSettings;
210 if (settings)
211 mpSpectrumSettings = std::make_unique<SpectrogramSettings>(*settings);
212 else
213 mpSpectrumSettings.reset();
214 }
215
216 {
217 auto &settings = orig.mpWaveformSettings;
218 if (settings)
219 mpWaveformSettings = std::make_unique<WaveformSettings>(*settings);
220 else
221 mpWaveformSettings.reset();
222 }
223 }
224
Merge(const Track & orig)225 void WaveTrack::Merge(const Track &orig)
226 {
227 orig.TypeSwitch( [&](const WaveTrack *pwt) {
228 const WaveTrack &wt = *pwt;
229 mGain = wt.mGain;
230 mPan = wt.mPan;
231 mDisplayMin = wt.mDisplayMin;
232 mDisplayMax = wt.mDisplayMax;
233 SetSpectrogramSettings(wt.mpSpectrumSettings
234 ? std::make_unique<SpectrogramSettings>(*wt.mpSpectrumSettings) : nullptr);
235 SetWaveformSettings
236 (wt.mpWaveformSettings ? std::make_unique<WaveformSettings>(*wt.mpWaveformSettings) : nullptr);
237 });
238 PlayableTrack::Merge(orig);
239 }
240
~WaveTrack()241 WaveTrack::~WaveTrack()
242 {
243 }
244
GetOffset() const245 double WaveTrack::GetOffset() const
246 {
247 return GetStartTime();
248 }
249
250 /*! @excsafety{No-fail} */
SetOffset(double o)251 void WaveTrack::SetOffset(double o)
252 {
253 double delta = o - GetOffset();
254
255 for (const auto &clip : mClips)
256 // assume No-fail-guarantee
257 clip->Offset(delta);
258
259 mOffset = o;
260 }
261
GetChannelIgnoringPan() const262 auto WaveTrack::GetChannelIgnoringPan() const -> ChannelType {
263 return mChannel;
264 }
265
GetChannel() const266 auto WaveTrack::GetChannel() const -> ChannelType
267 {
268 if( mChannel != Track::MonoChannel )
269 return mChannel;
270 auto pan = GetPan();
271 if( pan < -0.99 )
272 return Track::LeftChannel;
273 if( pan > 0.99 )
274 return Track::RightChannel;
275 return mChannel;
276 }
277
SetPanFromChannelType()278 void WaveTrack::SetPanFromChannelType()
279 {
280 if( mChannel == Track::LeftChannel )
281 SetPan( -1.0f );
282 else if( mChannel == Track::RightChannel )
283 SetPan( 1.0f );
284 }
285
LinkConsistencyCheck()286 bool WaveTrack::LinkConsistencyCheck()
287 {
288 auto err = PlayableTrack::LinkConsistencyCheck();
289
290 auto linkType = GetLinkType();
291 if (static_cast<int>(linkType) == 1 || //Comes from old audacity version
292 linkType == LinkType::Aligned)
293 {
294 auto next = dynamic_cast<WaveTrack*>(*std::next(GetOwner()->Find(this)));
295 if (next == nullptr)
296 {
297 //next track is not a wave track, fix and report error
298 wxLogWarning(
299 wxT("Right track %s is expected to be a WaveTrack.\n Removing link from left wave track %s."),
300 next->GetName(), GetName());
301 SetLinkType(LinkType::None);
302 SetChannel(MonoChannel);
303 err = true;
304 }
305 else
306 {
307 auto newLinkType = AreAligned(SortedClipArray(), next->SortedClipArray())
308 ? LinkType::Aligned : LinkType::Group;
309 //not an error
310 if (newLinkType != linkType)
311 SetLinkType(newLinkType);
312 }
313 }
314 return !err;
315 }
316
317
SetLastScaleType() const318 void WaveTrack::SetLastScaleType() const
319 {
320 mLastScaleType = GetWaveformSettings().scaleType;
321 }
322
SetLastdBRange() const323 void WaveTrack::SetLastdBRange() const
324 {
325 mLastdBRange = GetWaveformSettings().dBRange;
326 }
327
GetDisplayBounds(float * min,float * max) const328 void WaveTrack::GetDisplayBounds(float *min, float *max) const
329 {
330 *min = mDisplayMin;
331 *max = mDisplayMax;
332 }
333
SetDisplayBounds(float min,float max) const334 void WaveTrack::SetDisplayBounds(float min, float max) const
335 {
336 mDisplayMin = min;
337 mDisplayMax = max;
338 }
339
GetSpectrumBounds(float * min,float * max) const340 void WaveTrack::GetSpectrumBounds(float *min, float *max) const
341 {
342 const double rate = GetRate();
343
344 const SpectrogramSettings &settings = GetSpectrogramSettings();
345 const SpectrogramSettings::ScaleType type = settings.scaleType;
346
347 const float top = (rate / 2.);
348
349 float bottom;
350 if (type == SpectrogramSettings::stLinear)
351 bottom = 0.0f;
352 else if (type == SpectrogramSettings::stPeriod) {
353 // special case
354 const auto half = settings.GetFFTLength() / 2;
355 // EAC returns no data for below this frequency:
356 const float bin2 = rate / half;
357 bottom = bin2;
358 }
359 else
360 // logarithmic, etc.
361 bottom = 1.0f;
362
363 {
364 float spectrumMax = mSpectrumMax;
365 if (spectrumMax < 0)
366 spectrumMax = settings.maxFreq;
367 if (spectrumMax < 0)
368 *max = top;
369 else
370 *max = std::max(bottom, std::min(top, spectrumMax));
371 }
372
373 {
374 float spectrumMin = mSpectrumMin;
375 if (spectrumMin < 0)
376 spectrumMin = settings.minFreq;
377 if (spectrumMin < 0)
378 *min = std::max(bottom, top / 1000.0f);
379 else
380 *min = std::max(bottom, std::min(top, spectrumMin));
381 }
382 }
383
SetSpectrumBounds(float min,float max) const384 void WaveTrack::SetSpectrumBounds(float min, float max) const
385 {
386 mSpectrumMin = min;
387 mSpectrumMax = max;
388 }
389
ZeroLevelYCoordinate(wxRect rect) const390 int WaveTrack::ZeroLevelYCoordinate(wxRect rect) const
391 {
392 return rect.GetTop() +
393 (int)((mDisplayMax / (mDisplayMax - mDisplayMin)) * rect.height);
394 }
395
396 template< typename Container >
MakeIntervals(const std::vector<WaveClipHolder> & clips)397 static Container MakeIntervals(const std::vector<WaveClipHolder> &clips)
398 {
399 Container result;
400 for (const auto &clip: clips) {
401 result.emplace_back( clip->GetPlayStartTime(), clip->GetPlayEndTime(),
402 std::make_unique<WaveTrack::IntervalData>( clip ) );
403 }
404 return result;
405 }
406
PasteInto(AudacityProject & project) const407 Track::Holder WaveTrack::PasteInto( AudacityProject &project ) const
408 {
409 auto &trackFactory = WaveTrackFactory::Get( project );
410 auto &pSampleBlockFactory = trackFactory.GetSampleBlockFactory();
411 auto pNewTrack = EmptyCopy( pSampleBlockFactory );
412 pNewTrack->Paste(0.0, this);
413 return pNewTrack;
414 }
415
GetIntervals() const416 auto WaveTrack::GetIntervals() const -> ConstIntervals
417 {
418 return MakeIntervals<ConstIntervals>( mClips );
419 }
420
GetIntervals()421 auto WaveTrack::GetIntervals() -> Intervals
422 {
423 return MakeIntervals<Intervals>( mClips );
424 }
425
FindClipByName(const wxString & name) const426 const WaveClip* WaveTrack::FindClipByName(const wxString& name) const
427 {
428 for (const auto& clip : mClips)
429 {
430 if (clip->GetName() == name)
431 return clip.get();
432 }
433 return nullptr;
434 }
435
Clone() const436 Track::Holder WaveTrack::Clone() const
437 {
438 return std::make_shared<WaveTrack>( *this );
439 }
440
MakeClipCopyName(const wxString & originalName) const441 wxString WaveTrack::MakeClipCopyName(const wxString& originalName) const
442 {
443 auto name = originalName;
444 for (auto i = 1;; ++i)
445 {
446 if (FindClipByName(name) == nullptr)
447 return name;
448 //i18n-hint Template for clip name generation on copy-paste
449 name = XC("%s.%i", "clip name template").Format(originalName, i).Translation();
450 }
451 }
452
MakeNewClipName() const453 wxString WaveTrack::MakeNewClipName() const
454 {
455 auto name = GetName();
456 for (auto i = 1;; ++i)
457 {
458 if (FindClipByName(name) == nullptr)
459 return name;
460 //i18n-hint Template for clip name generation on inserting new empty clip
461 name = XC("%s %i", "clip name template").Format(GetName(), i).Translation();
462 }
463 }
464
GetRate() const465 double WaveTrack::GetRate() const
466 {
467 return mRate;
468 }
469
SetRate(double newRate)470 void WaveTrack::SetRate(double newRate)
471 {
472 wxASSERT( newRate > 0 );
473 newRate = std::max( 1.0, newRate );
474 auto ratio = mRate / newRate;
475 mRate = (int) newRate;
476 for (const auto &clip : mClips) {
477 clip->SetRate((int)newRate);
478 clip->SetSequenceStartTime( clip->GetSequenceStartTime() * ratio );
479 }
480 }
481
GetGain() const482 float WaveTrack::GetGain() const
483 {
484 return mGain;
485 }
486
SetGain(float newGain)487 void WaveTrack::SetGain(float newGain)
488 {
489 if (mGain != newGain) {
490 mGain = newGain;
491 Notify();
492 }
493 }
494
GetPan() const495 float WaveTrack::GetPan() const
496 {
497 return mPan;
498 }
499
SetPan(float newPan)500 void WaveTrack::SetPan(float newPan)
501 {
502 if (newPan > 1.0)
503 newPan = 1.0;
504 else if (newPan < -1.0)
505 newPan = -1.0;
506
507 if ( mPan != newPan ) {
508 mPan = newPan;
509 Notify();
510 }
511 }
512
GetChannelGain(int channel) const513 float WaveTrack::GetChannelGain(int channel) const
514 {
515 float left = 1.0;
516 float right = 1.0;
517
518 if (mPan < 0)
519 right = (mPan + 1.0);
520 else if (mPan > 0)
521 left = 1.0 - mPan;
522
523 if ((channel%2) == 0)
524 return left*mGain;
525 else
526 return right*mGain;
527 }
528
GetOldChannelGain(int channel) const529 float WaveTrack::GetOldChannelGain(int channel) const
530 {
531 return mOldGain[channel%2];
532 }
533
SetOldChannelGain(int channel,float gain)534 void WaveTrack::SetOldChannelGain(int channel, float gain)
535 {
536 mOldGain[channel % 2] = gain;
537 }
538
539
540
541 /*! @excsafety{Strong} */
SetWaveColorIndex(int colorIndex)542 void WaveTrack::SetWaveColorIndex(int colorIndex)
543 {
544 for (const auto &clip : mClips)
545 clip->SetColourIndex( colorIndex );
546 mWaveColorIndex = colorIndex;
547 }
548
GetPlaySamplesCount() const549 sampleCount WaveTrack::GetPlaySamplesCount() const
550 {
551 sampleCount result{ 0 };
552
553 for (const auto& clip : mClips)
554 result += clip->GetPlaySamplesCount();
555
556 return result;
557 }
558
GetSequenceSamplesCount() const559 sampleCount WaveTrack::GetSequenceSamplesCount() const
560 {
561 sampleCount result{ 0 };
562
563 for (const auto& clip : mClips)
564 result += clip->GetSequenceSamplesCount();
565
566 return result;
567 }
568
569 /*! @excsafety{Weak} -- Might complete on only some clips */
ConvertToSampleFormat(sampleFormat format,const std::function<void (size_t)> & progressReport)570 void WaveTrack::ConvertToSampleFormat(sampleFormat format,
571 const std::function<void(size_t)> & progressReport)
572 {
573 for (const auto& clip : mClips)
574 clip->ConvertToSampleFormat(format, progressReport);
575 mFormat = format;
576 }
577
578
IsEmpty(double t0,double t1) const579 bool WaveTrack::IsEmpty(double t0, double t1) const
580 {
581 if (t0 > t1)
582 return true;
583
584 //wxPrintf("Searching for overlap in %.6f...%.6f\n", t0, t1);
585 for (const auto &clip : mClips)
586 {
587 if (!clip->BeforePlayStartTime(t1) && !clip->AfterPlayEndTime(t0)) {
588 //wxPrintf("Overlapping clip: %.6f...%.6f\n",
589 // clip->GetStartTime(),
590 // clip->GetEndTime());
591 // We found a clip that overlaps this region
592 return false;
593 }
594 }
595 //wxPrintf("No overlap found\n");
596
597 // Otherwise, no clips overlap this region
598 return true;
599 }
600
Cut(double t0,double t1)601 Track::Holder WaveTrack::Cut(double t0, double t1)
602 {
603 if (t1 < t0)
604 THROW_INCONSISTENCY_EXCEPTION;
605
606 auto tmp = Copy(t0, t1);
607
608 Clear(t0, t1);
609
610 return tmp;
611 }
612
613 /*! @excsafety{Strong} */
SplitCut(double t0,double t1)614 Track::Holder WaveTrack::SplitCut(double t0, double t1)
615 {
616 if (t1 < t0)
617 THROW_INCONSISTENCY_EXCEPTION;
618
619 // SplitCut is the same as 'Copy', then 'SplitDelete'
620 auto tmp = Copy(t0, t1);
621
622 SplitDelete(t0, t1);
623
624 return tmp;
625 }
626
627 #if 0
628 Track::Holder WaveTrack::CutAndAddCutLine(double t0, double t1)
629 {
630 if (t1 < t0)
631 THROW_INCONSISTENCY_EXCEPTION;
632
633 // Cut is the same as 'Copy', then 'Delete'
634 auto tmp = Copy(t0, t1);
635
636 ClearAndAddCutLine(t0, t1);
637
638 return tmp;
639 }
640 #endif
641
642
643
644 //Trim trims within a clip, rather than trimming everything.
645 //If a bound is outside a clip, it trims everything.
646 /*! @excsafety{Weak} */
Trim(double t0,double t1)647 void WaveTrack::Trim (double t0, double t1)
648 {
649 bool inside0 = false;
650 bool inside1 = false;
651
652 for (const auto &clip : mClips)
653 {
654 if(t1 > clip->GetPlayStartTime() && t1 < clip->GetPlayEndTime())
655 {
656 clip->SetTrimRight(clip->GetTrimRight() + clip->GetPlayEndTime() - t1);
657 inside1 = true;
658 }
659
660 if(t0 > clip->GetPlayStartTime() && t0 < clip->GetPlayEndTime())
661 {
662 clip->SetTrimLeft(clip->GetTrimLeft() + t0 - clip->GetPlayStartTime());
663 inside0 = true;
664 }
665 }
666
667 //if inside0 is false, then the left selector was between
668 //clips, so DELETE everything to its left.
669 if(!inside1 && t1 < GetEndTime())
670 Clear(t1,GetEndTime());
671
672 if(!inside0 && t0 > GetStartTime())
673 SplitDelete(GetStartTime(), t0);
674 }
675
676
677
678
EmptyCopy(const SampleBlockFactoryPtr & pFactory) const679 WaveTrack::Holder WaveTrack::EmptyCopy(
680 const SampleBlockFactoryPtr &pFactory ) const
681 {
682 auto result = std::make_shared<WaveTrack>( pFactory, mFormat, mRate );
683 result->Init(*this);
684 result->mpFactory = pFactory ? pFactory : mpFactory;
685 return result;
686 }
687
Copy(double t0,double t1,bool forClipboard) const688 Track::Holder WaveTrack::Copy(double t0, double t1, bool forClipboard) const
689 {
690 if (t1 < t0)
691 THROW_INCONSISTENCY_EXCEPTION;
692
693 auto result = EmptyCopy();
694 WaveTrack *newTrack = result.get();
695
696 // PRL: Why shouldn't cutlines be copied and pasted too? I don't know, but
697 // that was the old behavior. But this function is also used by the
698 // Duplicate command and I changed its behavior in that case.
699
700 for (const auto &clip : mClips)
701 {
702 if (t0 <= clip->GetPlayStartTime() && t1 >= clip->GetPlayEndTime())
703 {
704 // Whole clip is in copy region
705 //wxPrintf("copy: clip %i is in copy region\n", (int)clip);
706
707 newTrack->mClips.push_back
708 (std::make_unique<WaveClip>(*clip, mpFactory, ! forClipboard));
709 WaveClip *const newClip = newTrack->mClips.back().get();
710 newClip->Offset(-t0);
711 }
712 else if (t1 > clip->GetPlayStartTime() && t0 < clip->GetPlayEndTime())
713 {
714 // Clip is affected by command
715 //wxPrintf("copy: clip %i is affected by command\n", (int)clip);
716
717 const double clip_t0 = std::max(t0, clip->GetPlayStartTime());
718 const double clip_t1 = std::min(t1, clip->GetPlayEndTime());
719
720 auto newClip = std::make_unique<WaveClip>
721 (*clip, mpFactory, ! forClipboard, clip_t0, clip_t1);
722 newClip->SetName(clip->GetName());
723
724 //wxPrintf("copy: clip_t0=%f, clip_t1=%f\n", clip_t0, clip_t1);
725
726 newClip->Offset(-t0);
727 if (newClip->GetPlayStartTime() < 0)
728 newClip->SetPlayStartTime(0);
729
730 newTrack->mClips.push_back(std::move(newClip)); // transfer ownership
731 }
732 }
733
734 // AWD, Oct 2009: If the selection ends in whitespace, create a placeholder
735 // clip representing that whitespace
736 // PRL: Only if we want the track for pasting into other tracks. Not if it
737 // goes directly into a project as in the Duplicate command.
738 if (forClipboard &&
739 newTrack->GetEndTime() + 1.0 / newTrack->GetRate() < t1 - t0)
740 {
741 auto placeholder = std::make_unique<WaveClip>(mpFactory,
742 newTrack->GetSampleFormat(),
743 static_cast<int>(newTrack->GetRate()),
744 0 /*colourindex*/);
745 placeholder->SetIsPlaceholder(true);
746 placeholder->InsertSilence(0, (t1 - t0) - newTrack->GetEndTime());
747 placeholder->Offset(newTrack->GetEndTime());
748 newTrack->mClips.push_back(std::move(placeholder)); // transfer ownership
749 }
750
751 return result;
752 }
753
CopyNonconst(double t0,double t1)754 Track::Holder WaveTrack::CopyNonconst(double t0, double t1)
755 {
756 return Copy(t0, t1);
757 }
758
759 /*! @excsafety{Strong} */
Clear(double t0,double t1)760 void WaveTrack::Clear(double t0, double t1)
761 {
762 HandleClear(t0, t1, false, false);
763 }
764
765 /*! @excsafety{Strong} */
ClearAndAddCutLine(double t0,double t1)766 void WaveTrack::ClearAndAddCutLine(double t0, double t1)
767 {
768 HandleClear(t0, t1, true, false);
769 }
770
GetSpectrogramSettings() const771 const SpectrogramSettings &WaveTrack::GetSpectrogramSettings() const
772 {
773 if (mpSpectrumSettings)
774 return *mpSpectrumSettings;
775 else
776 return SpectrogramSettings::defaults();
777 }
778
GetSpectrogramSettings()779 SpectrogramSettings &WaveTrack::GetSpectrogramSettings()
780 {
781 if (mpSpectrumSettings)
782 return *mpSpectrumSettings;
783 else
784 return SpectrogramSettings::defaults();
785 }
786
GetIndependentSpectrogramSettings()787 SpectrogramSettings &WaveTrack::GetIndependentSpectrogramSettings()
788 {
789 if (!mpSpectrumSettings)
790 mpSpectrumSettings =
791 std::make_unique<SpectrogramSettings>(SpectrogramSettings::defaults());
792 return *mpSpectrumSettings;
793 }
794
SetSpectrogramSettings(std::unique_ptr<SpectrogramSettings> && pSettings)795 void WaveTrack::SetSpectrogramSettings(std::unique_ptr<SpectrogramSettings> &&pSettings)
796 {
797 if (mpSpectrumSettings != pSettings) {
798 mpSpectrumSettings = std::move(pSettings);
799 }
800 }
801
UseSpectralPrefs(bool bUse)802 void WaveTrack::UseSpectralPrefs( bool bUse )
803 {
804 if( bUse ){
805 if( !mpSpectrumSettings )
806 return;
807 // reset it, and next we will be getting the defaults.
808 mpSpectrumSettings.reset();
809 }
810 else {
811 if( mpSpectrumSettings )
812 return;
813 GetIndependentSpectrogramSettings();
814 }
815 }
816
817
818
GetWaveformSettings() const819 const WaveformSettings &WaveTrack::GetWaveformSettings() const
820 {
821 // Create on demand
822 return const_cast<WaveTrack*>(this)->GetWaveformSettings();
823 }
824
GetWaveformSettings()825 WaveformSettings &WaveTrack::GetWaveformSettings()
826 {
827 // Create on demand
828 if (!mpWaveformSettings)
829 mpWaveformSettings = std::make_unique<WaveformSettings>(WaveformSettings::defaults());
830 return *mpWaveformSettings;
831 }
832
SetWaveformSettings(std::unique_ptr<WaveformSettings> && pSettings)833 void WaveTrack::SetWaveformSettings(std::unique_ptr<WaveformSettings> &&pSettings)
834 {
835 if (mpWaveformSettings != pSettings) {
836 mpWaveformSettings = std::move(pSettings);
837 }
838 }
839
840 namespace {
841
842 //Internal structure, which is supposed to contain
843 //data related to clip boundaries, and used during
844 //ClearAndPaste to restore old splits after new
845 //data is pasted
846 struct SplitInfo
847 {
848 //Time, where boundary is located
849 double time;
850 //Contains trimmed data, which should be re-appended to
851 //the clip to the left from the boundary, may be null
852 std::shared_ptr<WaveClip> left;
853 //Contains trimmed data, which should be re-appended to
854 //the clip to the right from the boundary, may be null
855 std::shared_ptr<WaveClip> right;
856 //Contains clip name next to the left from the boundary,
857 //if present, that needs to be re-assigned to the matching
858 //clip after split
859 std::optional<wxString> leftClipName;
860 //Contains clip name next to the right from the boundary,
861 //if present, that needs to be re-assigned to the matching
862 //clip after split
863 std::optional<wxString> rightClipName;
864 };
865
866 }
867
868 //
869 // ClearAndPaste() is a specialized version of HandleClear()
870 // followed by Paste() and is used mostly by effects that
871 // can't replace track data directly using Get()/Set().
872 //
873 // HandleClear() removes any cut/split lines with the
874 // cleared range, but, in most cases, effects want to preserve
875 // the existing cut/split lines, trimmed data and clip names,
876 // so they are saved before the HandleClear()/Paste() and restored after.
877 // When pasted track has split lines with hidden data at same places
878 // as the target one, then only targets hidden data is preserved, and
879 // hidden data from pasted track is discarded.
880 //
881 // If the pasted track overlaps two or more clips, then it will
882 // be pasted with visible split lines. Normally, effects do not
883 // want these extra lines, so they may be merged out.
884 //
885 /*! @excsafety{Weak} -- This WaveTrack remains destructible in case of AudacityException.
886 But some of its cutline clips may have been destroyed. */
ClearAndPaste(double t0,double t1,const Track * src,bool preserve,bool merge,const TimeWarper * effectWarper)887 void WaveTrack::ClearAndPaste(double t0, // Start of time to clear
888 double t1, // End of time to clear
889 const Track *src, // What to paste
890 bool preserve, // Whether to reinsert splits/cuts
891 bool merge, // Whether to remove 'extra' splits
892 const TimeWarper *effectWarper // How does time change
893 )
894 {
895 double dur = std::min(t1 - t0, src->GetEndTime());
896
897 // If duration is 0, then it's just a plain paste
898 if (dur == 0.0) {
899 // use Weak-guarantee
900 Paste(t0, src);
901 return;
902 }
903
904 std::vector<SplitInfo> splits;
905 WaveClipHolders cuts;
906
907 //helper routine, that finds SplitInfo by time value,
908 //or creates a new one if no one exists yet
909 auto get_split = [&](double time) {
910 auto it = std::find_if(splits.begin(), splits.end(), [time](const SplitInfo& split) {
911 return split.time == time;
912 });
913 if(it == splits.end())
914 it = splits.insert(
915 splits.end(),
916 { time, nullptr, nullptr, std::nullopt, std::nullopt }
917 );
918 return it;
919 };
920
921 // If provided time warper was NULL, use a default one that does nothing
922 IdentityTimeWarper localWarper;
923 const TimeWarper *warper = (effectWarper ? effectWarper : &localWarper);
924
925 // Align to a sample
926 t0 = LongSamplesToTime(TimeToLongSamples(t0));
927 t1 = LongSamplesToTime(TimeToLongSamples(t1));
928
929 // Save the cut/split lines whether preserving or not since merging
930 // needs to know if a clip boundary is being crossed since Paste()
931 // will add split lines around the pasted clip if so.
932 for (const auto &clip : mClips) {
933 double st;
934
935 // Remember clip boundaries as locations to split
936 // we need to copy clips, trims and names, because the original ones
937 // could be changed later during Clear/Paste routines
938 st = LongSamplesToTime(TimeToLongSamples(clip->GetPlayStartTime()));
939 if (st >= t0 && st <= t1) {
940 auto it = get_split(st);
941 if (clip->GetTrimLeft() != 0)
942 it->right = std::make_shared<WaveClip>(*clip, mpFactory, false, clip->GetSequenceStartTime(), st);
943 it->rightClipName = clip->GetName();
944 }
945
946 st = LongSamplesToTime(TimeToLongSamples(clip->GetPlayEndTime()));
947 if (st >= t0 && st <= t1) {
948 auto it = get_split(st);
949 if (clip->GetTrimRight() != 0)
950 it->left = std::make_shared<WaveClip>(*clip, mpFactory, false, st, clip->GetSequenceEndTime());
951 it->leftClipName = clip->GetName();
952 }
953
954 // Search for cut lines
955 auto &cutlines = clip->GetCutLines();
956 // May erase from cutlines, so don't use range-for
957 for (auto it = cutlines.begin(); it != cutlines.end(); ) {
958 WaveClip *cut = it->get();
959 double cs = LongSamplesToTime(TimeToLongSamples(clip->GetSequenceStartTime() +
960 cut->GetSequenceStartTime()));
961
962 // Remember cut point
963 if (cs >= t0 && cs <= t1) {
964
965 // Remember the absolute offset and add to our cuts array.
966 cut->SetSequenceStartTime(cs);
967 cuts.push_back(std::move(*it)); // transfer ownership!
968 it = cutlines.erase(it);
969 }
970 else
971 ++it;
972 }
973 }
974
975 const auto tolerance = 2.0 / GetRate();
976
977 // Now, clear the selection
978 HandleClear(t0, t1, false, false);
979 {
980
981 // And paste in the NEW data
982 Paste(t0, src);
983 {
984 // First, merge the NEW clip(s) in with the existing clips
985 if (merge && splits.size() > 0)
986 {
987 // Now t1 represents the absolute end of the pasted data.
988 t1 = t0 + src->GetEndTime();
989
990 // Get a sorted array of the clips
991 auto clips = SortedClipArray();
992
993 // Scan the sorted clips for the first clip whose start time
994 // exceeds the pasted regions end time.
995 {
996 WaveClip *prev = nullptr;
997 for (const auto clip : clips) {
998 // Merge this clip and the previous clip if the end time
999 // falls within it and this isn't the first clip in the track.
1000 if (fabs(t1 - clip->GetPlayStartTime()) < tolerance) {
1001 if (prev)
1002 MergeClips(GetClipIndex(prev), GetClipIndex(clip));
1003 break;
1004 }
1005 prev = clip;
1006 }
1007 }
1008 }
1009
1010 // Refill the array since clips have changed.
1011 auto clips = SortedClipArray();
1012
1013 {
1014 // Scan the sorted clips to look for the start of the pasted
1015 // region.
1016 WaveClip *prev = nullptr;
1017 for (const auto clip : clips) {
1018 if (prev) {
1019 // It must be that clip is what was pasted and it begins where
1020 // prev ends.
1021 // use Weak-guarantee
1022 MergeClips(GetClipIndex(prev), GetClipIndex(clip));
1023 break;
1024 }
1025 if (fabs(t0 - clip->GetPlayEndTime()) < tolerance)
1026 // Merge this clip and the next clip if the start time
1027 // falls within it and this isn't the last clip in the track.
1028 prev = clip;
1029 else
1030 prev = nullptr;
1031 }
1032 }
1033 }
1034
1035 // Restore cut/split lines
1036 if (preserve) {
1037
1038 auto attachLeft = [](WaveClip* target, WaveClip* src)
1039 {
1040 wxASSERT(target->GetTrimLeft() == 0);
1041 if (target->GetTrimLeft() != 0)
1042 return;
1043
1044 auto trim = src->GetPlayEndTime() - src->GetPlayStartTime();
1045 target->Paste(target->GetPlayStartTime(), src);
1046 target->SetTrimLeft(trim);
1047 //Play start time needs to be ajusted after
1048 //prepending data to the sequence
1049 target->Offset(-trim);
1050 };
1051
1052 auto attachRight = [](WaveClip* target, WaveClip* src)
1053 {
1054 wxASSERT(target->GetTrimRight() == 0);
1055 if (target->GetTrimRight() != 0)
1056 return;
1057
1058 auto trim = src->GetPlayEndTime() - src->GetPlayStartTime();
1059 target->Paste(target->GetPlayEndTime(), src);
1060 target->SetTrimRight(trim);
1061 };
1062
1063 // Restore the split lines and trims, transforming the position appropriately
1064 for (const auto& split: splits) {
1065 auto at = LongSamplesToTime(TimeToLongSamples(warper->Warp(split.time)));
1066 for (const auto& clip : GetClips())
1067 {
1068 if (clip->WithinPlayRegion(at))//strictly inside
1069 {
1070 auto newClip = std::make_unique<WaveClip>(*clip, mpFactory, true);
1071
1072 clip->ClearRight(at);
1073 newClip->ClearLeft(at);
1074 if (split.left)
1075 attachRight(clip.get(), split.left.get());
1076 if (split.right)
1077 attachLeft(newClip.get(), split.right.get());
1078 AddClip(std::move(newClip));
1079 break;
1080 }
1081 else if (clip->GetPlayStartSample() == TimeToLongSamples(at) && split.right)
1082 {
1083 attachLeft(clip.get(), split.right.get());
1084 break;
1085 }
1086 else if (clip->GetPlayEndSample() == TimeToLongSamples(at) && split.left)
1087 {
1088 attachRight(clip.get(), split.left.get());
1089 break;
1090 }
1091 }
1092 }
1093
1094 //Restore clip names
1095 for (const auto& split : splits)
1096 {
1097 auto s = TimeToLongSamples(warper->Warp(split.time));
1098 for (auto& clip : GetClips())
1099 {
1100 if (split.rightClipName.has_value() && clip->GetPlayStartSample() == s)
1101 clip->SetName(*split.rightClipName);
1102 else if (split.leftClipName.has_value() && clip->GetPlayEndSample() == s)
1103 clip->SetName(*split.leftClipName);
1104 }
1105 }
1106
1107 // Restore the saved cut lines, also transforming if time altered
1108 for (const auto &clip : mClips) {
1109 double st;
1110 double et;
1111
1112 st = clip->GetPlayStartTime();
1113 et = clip->GetPlayEndTime();
1114
1115 // Scan the cuts for any that live within this clip
1116 for (auto it = cuts.begin(); it != cuts.end();) {
1117 WaveClip *cut = it->get();
1118 //cutlines in this array were orphaned previously
1119 double cs = cut->GetSequenceStartTime();
1120
1121 // Offset the cut from the start of the clip and add it to
1122 // this clips cutlines.
1123 if (cs >= st && cs <= et) {
1124 cut->SetSequenceStartTime(warper->Warp(cs) - st);
1125 clip->GetCutLines().push_back( std::move(*it) ); // transfer ownership!
1126 it = cuts.erase(it);
1127 }
1128 else
1129 ++it;
1130 }
1131 }
1132 }
1133 }
1134 }
1135
1136 /*! @excsafety{Strong} */
SplitDelete(double t0,double t1)1137 void WaveTrack::SplitDelete(double t0, double t1)
1138 {
1139 bool addCutLines = false;
1140 bool split = true;
1141 HandleClear(t0, t1, addCutLines, split);
1142 }
1143
1144 namespace
1145 {
1146 WaveClipHolders::const_iterator
FindClip(const WaveClipHolders & list,const WaveClip * clip,int * distance=nullptr)1147 FindClip(const WaveClipHolders &list, const WaveClip *clip, int *distance = nullptr)
1148 {
1149 if (distance)
1150 *distance = 0;
1151 auto it = list.begin();
1152 for (const auto end = list.end(); it != end; ++it)
1153 {
1154 if (it->get() == clip)
1155 break;
1156 if (distance)
1157 ++*distance;
1158 }
1159 return it;
1160 }
1161
1162 WaveClipHolders::iterator
FindClip(WaveClipHolders & list,const WaveClip * clip,int * distance=nullptr)1163 FindClip(WaveClipHolders &list, const WaveClip *clip, int *distance = nullptr)
1164 {
1165 if (distance)
1166 *distance = 0;
1167 auto it = list.begin();
1168 for (const auto end = list.end(); it != end; ++it)
1169 {
1170 if (it->get() == clip)
1171 break;
1172 if (distance)
1173 ++*distance;
1174 }
1175 return it;
1176 }
1177 }
1178
RemoveAndReturnClip(WaveClip * clip)1179 std::shared_ptr<WaveClip> WaveTrack::RemoveAndReturnClip(WaveClip* clip)
1180 {
1181 // Be clear about who owns the clip!!
1182 auto it = FindClip(mClips, clip);
1183 if (it != mClips.end()) {
1184 auto result = std::move(*it); // Array stops owning the clip, before we shrink it
1185 mClips.erase(it);
1186 return result;
1187 }
1188 else
1189 return {};
1190 }
1191
AddClip(const std::shared_ptr<WaveClip> & clip)1192 bool WaveTrack::AddClip(const std::shared_ptr<WaveClip> &clip)
1193 {
1194 if (clip->GetSequence()->GetFactory() != this->mpFactory)
1195 return false;
1196
1197 // Uncomment the following line after we correct the problem of zero-length clips
1198 //if (CanInsertClip(clip))
1199 mClips.push_back(clip); // transfer ownership
1200
1201 return true;
1202 }
1203
1204 /*! @excsafety{Strong} */
HandleClear(double t0,double t1,bool addCutLines,bool split)1205 void WaveTrack::HandleClear(double t0, double t1,
1206 bool addCutLines, bool split)
1207 {
1208 // For debugging, use an ASSERT so that we stop
1209 // closer to the problem.
1210 wxASSERT( t1 >= t0 );
1211 if (t1 < t0)
1212 THROW_INCONSISTENCY_EXCEPTION;
1213
1214 bool editClipCanMove = GetEditClipsCanMove();
1215
1216 WaveClipPointers clipsToDelete;
1217 WaveClipHolders clipsToAdd;
1218
1219 // We only add cut lines when deleting in the middle of a single clip
1220 // The cut line code is not really prepared to handle other situations
1221 if (addCutLines)
1222 {
1223 for (const auto &clip : mClips)
1224 {
1225 if (!clip->BeforePlayStartTime(t1) && !clip->AfterPlayEndTime(t0) &&
1226 (clip->BeforePlayStartTime(t0) || clip->AfterPlayEndTime(t1)))
1227 {
1228 addCutLines = false;
1229 break;
1230 }
1231 }
1232 }
1233
1234 for (const auto &clip : mClips)
1235 {
1236 if (clip->BeforePlayStartTime(t0) && clip->AfterPlayEndTime(t1))
1237 {
1238 // Whole clip must be deleted - remember this
1239 clipsToDelete.push_back(clip.get());
1240 }
1241 else if (!clip->BeforePlayStartTime(t1) && !clip->AfterPlayEndTime(t0))
1242 {
1243 // Clip data is affected by command
1244 if (addCutLines)
1245 {
1246 // Don't modify this clip in place, because we want a strong
1247 // guarantee, and might modify another clip
1248 clipsToDelete.push_back( clip.get() );
1249 auto newClip = std::make_unique<WaveClip>( *clip, mpFactory, true );
1250 newClip->ClearAndAddCutLine( t0, t1 );
1251 clipsToAdd.push_back( std::move( newClip ) );
1252 }
1253 else
1254 {
1255 if (split) {
1256 // Three cases:
1257
1258 if (clip->BeforePlayStartTime(t0)) {
1259 // Delete from the left edge
1260
1261 // Don't modify this clip in place, because we want a strong
1262 // guarantee, and might modify another clip
1263 clipsToDelete.push_back( clip.get() );
1264 auto newClip = std::make_unique<WaveClip>( *clip, mpFactory, true );
1265 newClip->TrimLeft(t1 - clip->GetPlayStartTime());
1266 clipsToAdd.push_back( std::move( newClip ) );
1267 }
1268 else if (clip->AfterPlayEndTime(t1)) {
1269 // Delete to right edge
1270
1271 // Don't modify this clip in place, because we want a strong
1272 // guarantee, and might modify another clip
1273 clipsToDelete.push_back( clip.get() );
1274 auto newClip = std::make_unique<WaveClip>( *clip, mpFactory, true );
1275 newClip->TrimRight(clip->GetPlayEndTime() - t0);
1276
1277 clipsToAdd.push_back( std::move( newClip ) );
1278 }
1279 else {
1280 // Delete in the middle of the clip...we actually create two
1281 // NEW clips out of the left and right halves...
1282
1283 auto leftClip = std::make_unique<WaveClip>(*clip, mpFactory, true);
1284 leftClip->TrimRight(clip->GetPlayEndTime() - t0);
1285 clipsToAdd.push_back(std::move(leftClip));
1286
1287 auto rightClip = std::make_unique<WaveClip>(*clip, mpFactory, true);
1288 rightClip->TrimLeft(t1 - rightClip->GetPlayStartTime());
1289 clipsToAdd.push_back(std::move(rightClip));
1290
1291 clipsToDelete.push_back(clip.get());
1292 }
1293 }
1294 else {
1295 // (We are not doing a split cut)
1296
1297 // Don't modify this clip in place, because we want a strong
1298 // guarantee, and might modify another clip
1299 clipsToDelete.push_back( clip.get() );
1300 auto newClip = std::make_unique<WaveClip>( *clip, mpFactory, true );
1301
1302 // clip->Clear keeps points < t0 and >= t1 via Envelope::CollapseRegion
1303 newClip->Clear(t0,t1);
1304
1305 clipsToAdd.push_back( std::move( newClip ) );
1306 }
1307 }
1308 }
1309 }
1310
1311 // Only now, change the contents of this track
1312 // use No-fail-guarantee for the rest
1313
1314 if (!split && editClipCanMove)
1315 {
1316 // Clip is "behind" the region -- offset it unless we're splitting
1317 // or we're using the "don't move other clips" mode
1318 for (const auto& clip : mClips)
1319 {
1320 if (clip->BeforePlayStartTime(t1))
1321 clip->Offset(-(t1 - t0));
1322 }
1323 }
1324
1325 for (const auto &clip: clipsToDelete)
1326 {
1327 auto myIt = FindClip(mClips, clip);
1328 if (myIt != mClips.end())
1329 mClips.erase(myIt); // deletes the clip!
1330 else
1331 wxASSERT(false);
1332 }
1333
1334 for (auto &clip: clipsToAdd)
1335 mClips.push_back(std::move(clip)); // transfer ownership
1336 }
1337
SyncLockAdjust(double oldT1,double newT1)1338 void WaveTrack::SyncLockAdjust(double oldT1, double newT1)
1339 {
1340 if (newT1 > oldT1) {
1341 // Insert space within the track
1342
1343 // JKC: This is a rare case where using >= rather than > on a float matters.
1344 // GetEndTime() looks through the clips and may give us EXACTLY the same
1345 // value as T1, when T1 was set to be at the end of one of those clips.
1346 if (oldT1 >= GetEndTime())
1347 return;
1348
1349 // If track is empty at oldT1 insert whitespace; otherwise, silence
1350 if (IsEmpty(oldT1, oldT1))
1351 {
1352 // Check if clips can move
1353 bool clipsCanMove = true;
1354 gPrefs->Read(wxT("/GUI/EditClipCanMove"), &clipsCanMove);
1355 if (clipsCanMove) {
1356 auto tmp = Cut (oldT1, GetEndTime() + 1.0/GetRate());
1357
1358 Paste(newT1, tmp.get());
1359 }
1360 return;
1361 }
1362 else {
1363 // AWD: Could just use InsertSilence() on its own here, but it doesn't
1364 // follow EditClipCanMove rules (Paste() does it right)
1365 auto tmp = std::make_shared<WaveTrack>(
1366 mpFactory, GetSampleFormat(), GetRate() );
1367
1368 tmp->InsertSilence(0.0, newT1 - oldT1);
1369 tmp->Flush();
1370 Paste(oldT1, tmp.get());
1371 }
1372 }
1373 else if (newT1 < oldT1) {
1374 Clear(newT1, oldT1);
1375 }
1376 }
1377
PasteWaveTrack(double t0,const WaveTrack * other)1378 void WaveTrack::PasteWaveTrack(double t0, const WaveTrack* other)
1379 {
1380 //
1381 // Pasting is a bit complicated, because with the existence of multiclip mode,
1382 // we must guess the behaviour the user wants.
1383 //
1384 // Currently, two modes are implemented:
1385 //
1386 // - If a single clip should be pasted, and it should be pasted inside another
1387 // clip, no NEW clips are generated. The audio is simply inserted.
1388 // This resembles the old (pre-multiclip support) behaviour. However, if
1389 // the clip is pasted outside of any clip, a NEW clip is generated. This is
1390 // the only behaviour which is different to what was done before, but it
1391 // shouldn't confuse users too much.
1392 //
1393 // - If multiple clips should be pasted, or a single clip that does not fill
1394 // the duration of the pasted track, these are always pasted as single
1395 // clips, and the current clip is split, when necessary. This may seem
1396 // strange at first, but it probably is better than trying to auto-merge
1397 // anything. The user can still merge the clips by hand (which should be a
1398 // simple command reachable by a hotkey or single mouse click).
1399 //
1400
1401 if (other->GetNumClips() == 0)
1402 return;
1403
1404 //wxPrintf("paste: we have at least one clip\n");
1405
1406 bool singleClipMode = (other->GetNumClips() == 1 &&
1407 other->GetStartTime() == 0.0);
1408
1409 const double insertDuration = other->GetEndTime();
1410 if (insertDuration != 0 && insertDuration < 1.0 / mRate)
1411 // PRL: I added this check to avoid violations of preconditions in other WaveClip and Sequence
1412 // methods, but allow the value 0 so I don't subvert the purpose of commit
1413 // 739422ba70ceb4be0bb1829b6feb0c5401de641e which causes append-recording always to make
1414 // a new clip.
1415 return;
1416
1417 //wxPrintf("Check if we need to make room for the pasted data\n");
1418
1419 auto pastingFromTempTrack = !other->GetOwner();
1420 bool editClipCanMove = GetEditClipsCanMove();
1421
1422 // Make room for the pasted data
1423 if (editClipCanMove) {
1424 if (!singleClipMode) {
1425 // We need to insert multiple clips, so split the current clip and
1426 // move everything to the right, then try to paste again
1427 if (!IsEmpty(t0, GetEndTime())) {
1428 auto tmp = Cut(t0, GetEndTime() + 1.0 / mRate);
1429 Paste(t0 + insertDuration, tmp.get());
1430 }
1431 }
1432 else {
1433 // We only need to insert one single clip, so just move all clips
1434 // to the right of the paste point out of the way
1435 for (const auto& clip : mClips)
1436 {
1437 if (clip->GetPlayStartTime() > t0 - (1.0 / mRate))
1438 clip->Offset(insertDuration);
1439 }
1440 }
1441 }
1442
1443 if (singleClipMode)
1444 {
1445 // Single clip mode
1446 // wxPrintf("paste: checking for single clip mode!\n");
1447
1448 WaveClip* insideClip = nullptr;
1449
1450 for (const auto& clip : mClips)
1451 {
1452 if (editClipCanMove)
1453 {
1454 if (clip->WithinPlayRegion(t0))
1455 {
1456 //wxPrintf("t0=%.6f: inside clip is %.6f ... %.6f\n",
1457 // t0, clip->GetStartTime(), clip->GetEndTime());
1458 insideClip = clip.get();
1459 break;
1460 }
1461 }
1462 else
1463 {
1464 // If clips are immovable we also allow prepending to clips
1465 if (clip->WithinPlayRegion(t0) ||
1466 TimeToLongSamples(t0) == clip->GetPlayStartSample())
1467 {
1468 insideClip = clip.get();
1469 break;
1470 }
1471 }
1472 }
1473
1474 if (insideClip)
1475 {
1476 // Exhibit traditional behaviour
1477 //wxPrintf("paste: traditional behaviour\n");
1478 if (!editClipCanMove)
1479 {
1480 // We did not move other clips out of the way already, so
1481 // check if we can paste without having to move other clips
1482 for (const auto& clip : mClips)
1483 {
1484 if (clip->GetPlayStartTime() > insideClip->GetPlayStartTime() &&
1485 insideClip->GetPlayEndTime() + insertDuration >
1486 clip->GetPlayStartTime())
1487 // Strong-guarantee in case of this path
1488 // not that it matters.
1489 throw SimpleMessageBoxException{
1490 ExceptionType::BadUserAction,
1491 XO("There is not enough room available to paste the selection"),
1492 XO("Warning"),
1493 "Error:_Insufficient_space_in_track"
1494 };
1495 }
1496 }
1497 insideClip->Paste(t0, other->GetClipByIndex(0));
1498 return;
1499 }
1500 // Just fall through and exhibit NEW behaviour
1501 }
1502
1503 // Insert NEW clips
1504 //wxPrintf("paste: multi clip mode!\n");
1505
1506 if (!editClipCanMove && !IsEmpty(t0, t0 + insertDuration - 1.0 / mRate))
1507 // Strong-guarantee in case of this path
1508 // not that it matters.
1509 throw SimpleMessageBoxException{
1510 ExceptionType::BadUserAction,
1511 XO("There is not enough room available to paste the selection"),
1512 XO("Warning"),
1513 "Error:_Insufficient_space_in_track"
1514 };
1515
1516 for (const auto& clip : other->mClips)
1517 {
1518 // AWD Oct. 2009: Don't actually paste in placeholder clips
1519 if (!clip->GetIsPlaceholder())
1520 {
1521 auto newClip =
1522 std::make_unique<WaveClip>(*clip, mpFactory, true);
1523 newClip->Resample(mRate);
1524 newClip->Offset(t0);
1525 newClip->MarkChanged();
1526 if (pastingFromTempTrack)
1527 //Clips from the tracks which aren't bound to any TrackList are
1528 //considered to be new entities, thus named using "new" name template
1529 newClip->SetName(MakeNewClipName());
1530 else
1531 newClip->SetName(MakeClipCopyName(clip->GetName()));
1532 mClips.push_back(std::move(newClip)); // transfer ownership
1533 }
1534 }
1535 }
1536
1537 /*! @excsafety{Weak} */
Paste(double t0,const Track * src)1538 void WaveTrack::Paste(double t0, const Track *src)
1539 {
1540 if (auto other = dynamic_cast<const WaveTrack*>(src))
1541 PasteWaveTrack(t0, other);
1542 else
1543 // THROW_INCONSISTENCY_EXCEPTION; // ?
1544 (void)0;// Empty if intentional.
1545 }
1546
Silence(double t0,double t1)1547 void WaveTrack::Silence(double t0, double t1)
1548 {
1549 if (t1 < t0)
1550 THROW_INCONSISTENCY_EXCEPTION;
1551
1552 auto start = TimeToLongSamples(t0);
1553 auto end = TimeToLongSamples(t1);
1554
1555 for (const auto &clip : mClips)
1556 {
1557 auto clipStart = clip->GetPlayStartSample();
1558 auto clipEnd = clip->GetPlayEndSample();
1559
1560 if (clipEnd > start && clipStart < end)
1561 {
1562 auto offset = std::max(start - clipStart, sampleCount(0));
1563 // Clip sample region and Get/Put sample region overlap
1564 auto length = std::min(end, clipEnd) - (clipStart + offset);
1565
1566 clip->SetSilence(offset, length);
1567 }
1568 }
1569 }
1570
1571 /*! @excsafety{Strong} */
InsertSilence(double t,double len)1572 void WaveTrack::InsertSilence(double t, double len)
1573 {
1574 // Nothing to do, if length is zero.
1575 // Fixes Bug 1626
1576 if( len == 0 )
1577 return;
1578 if (len <= 0)
1579 THROW_INCONSISTENCY_EXCEPTION;
1580
1581 if (mClips.empty())
1582 {
1583 // Special case if there is no clip yet
1584 auto clip = std::make_unique<WaveClip>(mpFactory, mFormat, mRate, this->GetWaveColorIndex());
1585 clip->InsertSilence(0, len);
1586 // use No-fail-guarantee
1587 mClips.push_back( std::move( clip ) );
1588 return;
1589 }
1590 else {
1591 // Assume at most one clip contains t
1592 const auto end = mClips.end();
1593 const auto it = std::find_if( mClips.begin(), end,
1594 [&](const WaveClipHolder &clip) { return clip->WithinPlayRegion(t); } );
1595
1596 // use Strong-guarantee
1597 if (it != end)
1598 it->get()->InsertSilence(t, len);
1599
1600 // use No-fail-guarantee
1601 for (const auto &clip : mClips)
1602 {
1603 if (clip->BeforePlayStartTime(t))
1604 clip->Offset(len);
1605 }
1606 }
1607 }
1608
1609 //Performs the opposite of Join
1610 //Analyses selected region for possible Joined clips and disjoins them
1611 /*! @excsafety{Weak} */
Disjoin(double t0,double t1)1612 void WaveTrack::Disjoin(double t0, double t1)
1613 {
1614 auto minSamples = TimeToLongSamples( WAVETRACK_MERGE_POINT_TOLERANCE );
1615 const size_t maxAtOnce = 1048576;
1616 Floats buffer{ maxAtOnce };
1617 Regions regions;
1618
1619 wxBusyCursor busy;
1620
1621 for (const auto &clip : mClips)
1622 {
1623 double startTime = clip->GetPlayStartTime();
1624 double endTime = clip->GetPlayEndTime();
1625
1626 if( endTime < t0 || startTime > t1 )
1627 continue;
1628
1629 //simply look for a sequence of zeroes and if the sequence
1630 //is greater than minimum number, split-DELETE the region
1631
1632 sampleCount seqStart = -1;
1633 auto start = clip->TimeToSamples(std::max(.0, t0 - startTime));
1634 auto end = clip->TimeToSamples(std::min(endTime, t1) - startTime);
1635
1636 auto len = ( end - start );
1637 for( decltype(len) done = 0; done < len; done += maxAtOnce )
1638 {
1639 auto numSamples = limitSampleBufferSize( maxAtOnce, len - done );
1640
1641 clip->GetSamples( ( samplePtr )buffer.get(), floatSample, start + done,
1642 numSamples );
1643 for( decltype(numSamples) i = 0; i < numSamples; i++ )
1644 {
1645 auto curSamplePos = start + done + i;
1646
1647 //start a NEW sequence
1648 if( buffer[ i ] == 0.0 && seqStart == -1 )
1649 seqStart = curSamplePos;
1650 else if( buffer[ i ] != 0.0 || curSamplePos == end - 1 )
1651 {
1652 if( seqStart != -1 )
1653 {
1654 decltype(end) seqEnd;
1655
1656 //consider the end case, where selection ends in zeroes
1657 if( curSamplePos == end - 1 && buffer[ i ] == 0.0 )
1658 seqEnd = end;
1659 else
1660 seqEnd = curSamplePos;
1661 if( seqEnd - seqStart + 1 > minSamples )
1662 {
1663 regions.push_back(
1664 Region(
1665 startTime + clip->SamplesToTime(seqStart),
1666 startTime + clip->SamplesToTime(seqEnd)
1667 )
1668 );
1669 }
1670 seqStart = -1;
1671 }
1672 }
1673 }
1674 }
1675 }
1676
1677 for( unsigned int i = 0; i < regions.size(); i++ )
1678 {
1679 const Region ®ion = regions.at(i);
1680 SplitDelete(region.start, region.end );
1681 }
1682 }
1683
1684 /*! @excsafety{Weak} */
Join(double t0,double t1)1685 void WaveTrack::Join(double t0, double t1)
1686 {
1687 // Merge all WaveClips overlapping selection into one
1688
1689 WaveClipPointers clipsToDelete;
1690 WaveClip* newClip{};
1691
1692 for (const auto &clip: mClips)
1693 {
1694 if (clip->GetPlayStartTime() < t1-(1.0/mRate) &&
1695 clip->GetPlayEndTime()-(1.0/mRate) > t0) {
1696
1697 // Put in sorted order
1698 auto it = clipsToDelete.begin(), end = clipsToDelete.end();
1699 for (; it != end; ++it)
1700 if ((*it)->GetPlayStartTime() > clip->GetPlayStartTime())
1701 break;
1702 //wxPrintf("Insert clip %.6f at position %d\n", clip->GetStartTime(), i);
1703 clipsToDelete.insert(it, clip.get());
1704 }
1705 }
1706
1707 //if there are no clips to DELETE, nothing to do
1708 if( clipsToDelete.size() == 0 )
1709 return;
1710
1711 auto t = clipsToDelete[0]->GetPlayStartTime();
1712 //preserve left trim data if any
1713 newClip = CreateClip(clipsToDelete[0]->GetSequenceStartTime(),
1714 clipsToDelete[0]->GetName());
1715
1716 for (const auto &clip : clipsToDelete)
1717 {
1718 //wxPrintf("t=%.6f adding clip (offset %.6f, %.6f ... %.6f)\n",
1719 // t, clip->GetOffset(), clip->GetStartTime(), clip->GetEndTime());
1720
1721 if (clip->GetPlayStartTime() - t > (1.0 / mRate)) {
1722 double addedSilence = (clip->GetPlayStartTime() - t);
1723 //wxPrintf("Adding %.6f seconds of silence\n");
1724 auto offset = clip->GetPlayStartTime();
1725 auto value = clip->GetEnvelope()->GetValue( offset );
1726 newClip->AppendSilence( addedSilence, value );
1727 t += addedSilence;
1728 }
1729
1730 //wxPrintf("Pasting at %.6f\n", t);
1731 newClip->Paste(t, clip);
1732
1733 t = newClip->GetPlayEndTime();
1734
1735 auto it = FindClip(mClips, clip);
1736 mClips.erase(it); // deletes the clip
1737 }
1738 }
1739
1740 /*! @excsafety{Partial}
1741 -- Some prefix (maybe none) of the buffer is appended,
1742 and no content already flushed to disk is lost. */
Append(constSamplePtr buffer,sampleFormat format,size_t len,unsigned int stride)1743 bool WaveTrack::Append(constSamplePtr buffer, sampleFormat format,
1744 size_t len, unsigned int stride /* = 1 */)
1745 {
1746 return RightmostOrNewClip()->Append(buffer, format, len, stride);
1747 }
1748
GetBlockStart(sampleCount s) const1749 sampleCount WaveTrack::GetBlockStart(sampleCount s) const
1750 {
1751 for (const auto &clip : mClips)
1752 {
1753 const auto startSample = clip->GetPlayStartSample();
1754 const auto endSample = clip->GetPlayEndSample();
1755 if (s >= startSample && s < endSample)
1756 {
1757 auto blockStartOffset = clip->GetSequence()->GetBlockStart(clip->ToSequenceSamples(s));
1758 return std::max(startSample, clip->GetSequenceStartSample() + blockStartOffset);
1759 }
1760 }
1761
1762 return -1;
1763 }
1764
GetBestBlockSize(sampleCount s) const1765 size_t WaveTrack::GetBestBlockSize(sampleCount s) const
1766 {
1767 auto bestBlockSize = GetMaxBlockSize();
1768
1769 for (const auto &clip : mClips)
1770 {
1771 auto startSample = clip->GetPlayStartSample();
1772 auto endSample = clip->GetPlayEndSample();
1773 if (s >= startSample && s < endSample)
1774 {
1775 bestBlockSize = clip->GetSequence()->GetBestBlockSize(s - clip->GetSequenceStartSample());
1776 break;
1777 }
1778 }
1779
1780 return bestBlockSize;
1781 }
1782
GetMaxBlockSize() const1783 size_t WaveTrack::GetMaxBlockSize() const
1784 {
1785 decltype(GetMaxBlockSize()) maxblocksize = 0;
1786 for (const auto &clip : mClips)
1787 {
1788 maxblocksize = std::max(maxblocksize, clip->GetSequence()->GetMaxBlockSize());
1789 }
1790
1791 if (maxblocksize == 0)
1792 {
1793 // We really need the maximum block size, so create a
1794 // temporary sequence to get it.
1795 maxblocksize = Sequence{ mpFactory, mFormat }.GetMaxBlockSize();
1796 }
1797
1798 wxASSERT(maxblocksize > 0);
1799
1800 return maxblocksize;
1801 }
1802
GetIdealBlockSize()1803 size_t WaveTrack::GetIdealBlockSize()
1804 {
1805 return NewestOrNewClip()->GetSequence()->GetIdealBlockSize();
1806 }
1807
1808 /*! @excsafety{Mixed} */
1809 /*! @excsafety{No-fail} -- The rightmost clip will be in a flushed state. */
1810 /*! @excsafety{Partial}
1811 -- Some initial portion (maybe none) of the append buffer of the rightmost
1812 clip gets appended; no previously saved contents are lost. */
Flush()1813 void WaveTrack::Flush()
1814 {
1815 // After appending, presumably. Do this to the clip that gets appended.
1816 RightmostOrNewClip()->Flush();
1817 }
1818
1819 namespace {
IsValidChannel(const int nValue)1820 bool IsValidChannel(const int nValue)
1821 {
1822 return (nValue >= Track::LeftChannel) && (nValue <= Track::MonoChannel);
1823 }
1824 }
1825
HandleXMLTag(const std::string_view & tag,const AttributesList & attrs)1826 bool WaveTrack::HandleXMLTag(const std::string_view& tag, const AttributesList &attrs)
1827 {
1828 if (tag == "wavetrack") {
1829 double dblValue;
1830 long nValue;
1831
1832 for (auto pair : attrs)
1833 {
1834 auto attr = pair.first;
1835 auto value = pair.second;
1836
1837 if (attr == "rate")
1838 {
1839 // mRate is an int, but "rate" in the project file is a float.
1840 if (!value.TryGet(dblValue) ||
1841 (dblValue < 1.0) || (dblValue > 1000000.0)) // allow a large range to be read
1842 return false;
1843
1844 mRate = lrint(dblValue);
1845 }
1846 else if (attr == "offset" && value.TryGet(dblValue))
1847 {
1848 // Offset is only relevant for legacy project files. The value
1849 // is cached until the actual WaveClip containing the legacy
1850 // track is created.
1851 mLegacyProjectFileOffset = dblValue;
1852 }
1853 else if (this->PlayableTrack::HandleXMLAttribute(attr, value))
1854 {}
1855 else if (this->Track::HandleCommonXMLAttribute(attr, value))
1856 ;
1857 else if (attr == "gain" && value.TryGet(dblValue))
1858 mGain = dblValue;
1859 else if (attr == "pan" && value.TryGet(dblValue) &&
1860 (dblValue >= -1.0) && (dblValue <= 1.0))
1861 mPan = dblValue;
1862 else if (attr == "channel")
1863 {
1864 if (!value.TryGet(nValue) ||
1865 !IsValidChannel(nValue))
1866 return false;
1867 mChannel = static_cast<Track::ChannelType>( nValue );
1868 }
1869 else if (attr == "linked" && value.TryGet(nValue))
1870 SetLinkType(ToLinkType(nValue));
1871 else if (attr == "colorindex" && value.TryGet(nValue))
1872 // Don't use SetWaveColorIndex as it sets the clips too.
1873 mWaveColorIndex = nValue;
1874 else if (attr == "sampleformat" && value.TryGet(nValue) &&
1875 Sequence::IsValidSampleFormat(nValue))
1876 mFormat = static_cast<sampleFormat>(nValue);
1877 } // while
1878 return true;
1879 }
1880
1881 return false;
1882 }
1883
HandleXMLEndTag(const std::string_view & WXUNUSED (tag))1884 void WaveTrack::HandleXMLEndTag(const std::string_view& WXUNUSED(tag))
1885 {
1886 // In case we opened a pre-multiclip project, we need to
1887 // simulate closing the waveclip tag.
1888 NewestOrNewClip()->HandleXMLEndTag("waveclip");
1889 }
1890
HandleXMLChild(const std::string_view & tag)1891 XMLTagHandler *WaveTrack::HandleXMLChild(const std::string_view& tag)
1892 {
1893 //
1894 // This is legacy code (1.2 and previous) and is not called for NEW projects!
1895 //
1896 if (tag == "sequence" || tag == "envelope")
1897 {
1898 // This is a legacy project, so set the cached offset
1899 NewestOrNewClip()->SetSequenceStartTime(mLegacyProjectFileOffset);
1900
1901 // Legacy project file tracks are imported as one single wave clip
1902 if (tag == "sequence")
1903 return NewestOrNewClip()->GetSequence();
1904 else if (tag == "envelope")
1905 return NewestOrNewClip()->GetEnvelope();
1906 }
1907
1908 // JKC... for 1.1.0, one step better than what we had, but still badly broken.
1909 // If we see a waveblock at this level, we'd better generate a sequence.
1910 if (tag == "waveblock")
1911 {
1912 // This is a legacy project, so set the cached offset
1913 NewestOrNewClip()->SetSequenceStartTime(mLegacyProjectFileOffset);
1914 Sequence *pSeq = NewestOrNewClip()->GetSequence();
1915 return pSeq;
1916 }
1917
1918 //
1919 // This is for the NEW file format (post-1.2)
1920 //
1921 if (tag == "waveclip")
1922 return CreateClip();
1923 else
1924 return NULL;
1925 }
1926
WriteXML(XMLWriter & xmlFile) const1927 void WaveTrack::WriteXML(XMLWriter &xmlFile) const
1928 // may throw
1929 {
1930 xmlFile.StartTag(wxT("wavetrack"));
1931 this->Track::WriteCommonXMLAttributes( xmlFile );
1932 xmlFile.WriteAttr(wxT("channel"), mChannel);
1933 xmlFile.WriteAttr(wxT("linked"), static_cast<int>(GetLinkType()));
1934 this->PlayableTrack::WriteXMLAttributes(xmlFile);
1935 xmlFile.WriteAttr(wxT("rate"), mRate);
1936 xmlFile.WriteAttr(wxT("gain"), (double)mGain);
1937 xmlFile.WriteAttr(wxT("pan"), (double)mPan);
1938 xmlFile.WriteAttr(wxT("colorindex"), mWaveColorIndex );
1939 xmlFile.WriteAttr(wxT("sampleformat"), static_cast<long>(mFormat) );
1940
1941 for (const auto &clip : mClips)
1942 {
1943 clip->WriteXML(xmlFile);
1944 }
1945
1946 xmlFile.EndTag(wxT("wavetrack"));
1947 }
1948
GetErrorOpening()1949 bool WaveTrack::GetErrorOpening()
1950 {
1951 for (const auto &clip : mClips)
1952 if (clip->GetSequence()->GetErrorOpening())
1953 return true;
1954
1955 return false;
1956 }
1957
CloseLock()1958 bool WaveTrack::CloseLock()
1959 {
1960 for (const auto &clip : mClips)
1961 clip->CloseLock();
1962
1963 return true;
1964 }
1965
TimeToLongSamples(double t0) const1966 AUDACITY_DLL_API sampleCount WaveTrack::TimeToLongSamples(double t0) const
1967 {
1968 return sampleCount( floor(t0 * mRate + 0.5) );
1969 }
1970
LongSamplesToTime(sampleCount pos) const1971 double WaveTrack::LongSamplesToTime(sampleCount pos) const
1972 {
1973 return pos.as_double() / mRate;
1974 }
1975
GetStartTime() const1976 double WaveTrack::GetStartTime() const
1977 {
1978 bool found = false;
1979 double best = 0.0;
1980
1981 if (mClips.empty())
1982 return 0;
1983
1984 for (const auto &clip : mClips)
1985 if (!found)
1986 {
1987 found = true;
1988 best = clip->GetPlayStartTime();
1989 }
1990 else if (clip->GetPlayStartTime() < best)
1991 best = clip->GetPlayStartTime();
1992
1993 return best;
1994 }
1995
GetEndTime() const1996 double WaveTrack::GetEndTime() const
1997 {
1998 bool found = false;
1999 double best = 0.0;
2000
2001 if (mClips.empty())
2002 return 0;
2003
2004 for (const auto &clip : mClips)
2005 if (!found)
2006 {
2007 found = true;
2008 best = clip->GetPlayEndTime();
2009 }
2010 else if (clip->GetPlayEndTime() > best)
2011 best = clip->GetPlayEndTime();
2012
2013 return best;
2014 }
2015
2016 //
2017 // Getting/setting samples. The sample counts here are
2018 // expressed relative to t=0.0 at the track's sample rate.
2019 //
2020
GetMinMax(double t0,double t1,bool mayThrow) const2021 std::pair<float, float> WaveTrack::GetMinMax(
2022 double t0, double t1, bool mayThrow) const
2023 {
2024 std::pair<float, float> results {
2025 // we need these at extremes to make sure we find true min and max
2026 FLT_MAX, -FLT_MAX
2027 };
2028 bool clipFound = false;
2029
2030 if (t0 > t1) {
2031 if (mayThrow)
2032 THROW_INCONSISTENCY_EXCEPTION;
2033 return results;
2034 }
2035
2036 if (t0 == t1)
2037 return results;
2038
2039 for (const auto &clip: mClips)
2040 {
2041 if (t1 >= clip->GetPlayStartTime() && t0 <= clip->GetPlayEndTime())
2042 {
2043 clipFound = true;
2044 auto clipResults = clip->GetMinMax(t0, t1, mayThrow);
2045 if (clipResults.first < results.first)
2046 results.first = clipResults.first;
2047 if (clipResults.second > results.second)
2048 results.second = clipResults.second;
2049 }
2050 }
2051
2052 if(!clipFound)
2053 {
2054 results = { 0.f, 0.f }; // sensible defaults if no clips found
2055 }
2056
2057 return results;
2058 }
2059
GetRMS(double t0,double t1,bool mayThrow) const2060 float WaveTrack::GetRMS(double t0, double t1, bool mayThrow) const
2061 {
2062 if (t0 > t1) {
2063 if (mayThrow)
2064 THROW_INCONSISTENCY_EXCEPTION;
2065 return 0.f;
2066 }
2067
2068 if (t0 == t1)
2069 return 0.f;
2070
2071 double sumsq = 0.0;
2072 sampleCount length = 0;
2073
2074 for (const auto &clip: mClips)
2075 {
2076 // If t1 == clip->GetStartTime() or t0 == clip->GetEndTime(), then the clip
2077 // is not inside the selection, so we don't want it.
2078 // if (t1 >= clip->GetStartTime() && t0 <= clip->GetEndTime())
2079 if (t1 >= clip->GetPlayStartTime() && t0 <= clip->GetPlayEndTime())
2080 {
2081 auto clipStart = clip->TimeToSequenceSamples(wxMax(t0, clip->GetPlayStartTime()));
2082 auto clipEnd = clip->TimeToSequenceSamples(wxMin(t1, clip->GetPlayEndTime()));
2083
2084 float cliprms = clip->GetRMS(t0, t1, mayThrow);
2085
2086 sumsq += cliprms * cliprms * (clipEnd - clipStart).as_float();
2087 length += (clipEnd - clipStart);
2088 }
2089 }
2090 return length > 0 ? sqrt(sumsq / length.as_double()) : 0.0;
2091 }
2092
Get(samplePtr buffer,sampleFormat format,sampleCount start,size_t len,fillFormat fill,bool mayThrow,sampleCount * pNumWithinClips) const2093 bool WaveTrack::Get(samplePtr buffer, sampleFormat format,
2094 sampleCount start, size_t len, fillFormat fill,
2095 bool mayThrow, sampleCount * pNumWithinClips) const
2096 {
2097 // Simple optimization: When this buffer is completely contained within one clip,
2098 // don't clear anything (because we won't have to). Otherwise, just clear
2099 // everything to be on the safe side.
2100 bool doClear = true;
2101 bool result = true;
2102 sampleCount samplesCopied = 0;
2103 for (const auto &clip: mClips)
2104 {
2105 if (start >= clip->GetPlayStartSample() && start+len <= clip->GetPlayEndSample())
2106 {
2107 doClear = false;
2108 break;
2109 }
2110 }
2111 if (doClear)
2112 {
2113 // Usually we fill in empty space with zero
2114 if( fill == fillZero )
2115 ClearSamples(buffer, format, 0, len);
2116 // but we don't have to.
2117 else if( fill==fillTwo )
2118 {
2119 wxASSERT( format==floatSample );
2120 float * pBuffer = (float*)buffer;
2121 for(size_t i=0;i<len;i++)
2122 pBuffer[i]=2.0f;
2123 }
2124 else
2125 {
2126 wxFAIL_MSG(wxT("Invalid fill format"));
2127 }
2128 }
2129
2130 // Iterate the clips. They are not necessarily sorted by time.
2131 for (const auto &clip: mClips)
2132 {
2133 auto clipStart = clip->GetPlayStartSample();
2134 auto clipEnd = clip->GetPlayEndSample();
2135
2136 if (clipEnd > start && clipStart < start+len)
2137 {
2138 // Clip sample region and Get/Put sample region overlap
2139 auto samplesToCopy =
2140 std::min( start+len - clipStart, clip->GetPlaySamplesCount() );
2141 auto startDelta = clipStart - start;
2142 decltype(startDelta) inclipDelta = 0;
2143 if (startDelta < 0)
2144 {
2145 inclipDelta = -startDelta; // make positive value
2146 samplesToCopy -= inclipDelta;
2147 // samplesToCopy is now either len or
2148 // (clipEnd - clipStart) - (start - clipStart)
2149 // == clipEnd - start > 0
2150 // samplesToCopy is not more than len
2151 //
2152 startDelta = 0;
2153 // startDelta is zero
2154 }
2155 else {
2156 // startDelta is nonnegative and less than len
2157 // samplesToCopy is positive and not more than len
2158 }
2159
2160 if (!clip->GetSamples(
2161 (samplePtr)(((char*)buffer) +
2162 startDelta.as_size_t() *
2163 SAMPLE_SIZE(format)),
2164 format, inclipDelta, samplesToCopy.as_size_t(), mayThrow ))
2165 result = false;
2166 else
2167 samplesCopied += samplesToCopy;
2168 }
2169 }
2170 if( pNumWithinClips )
2171 *pNumWithinClips = samplesCopied;
2172 return result;
2173 }
2174
2175 /*! @excsafety{Weak} */
Set(constSamplePtr buffer,sampleFormat format,sampleCount start,size_t len)2176 void WaveTrack::Set(constSamplePtr buffer, sampleFormat format,
2177 sampleCount start, size_t len)
2178 {
2179 for (const auto &clip: mClips)
2180 {
2181 auto clipStart = clip->GetPlayStartSample();
2182 auto clipEnd = clip->GetPlayEndSample();
2183
2184 if (clipEnd > start && clipStart < start+len)
2185 {
2186 // Clip sample region and Get/Put sample region overlap
2187 auto samplesToCopy =
2188 std::min( start+len - clipStart, clip->GetPlaySamplesCount() );
2189 auto startDelta = clipStart - start;
2190 decltype(startDelta) inclipDelta = 0;
2191 if (startDelta < 0)
2192 {
2193 inclipDelta = -startDelta; // make positive value
2194 samplesToCopy -= inclipDelta;
2195 // samplesToCopy is now either len or
2196 // (clipEnd - clipStart) - (start - clipStart)
2197 // == clipEnd - start > 0
2198 // samplesToCopy is not more than len
2199 //
2200 startDelta = 0;
2201 // startDelta is zero
2202 }
2203 else {
2204 // startDelta is nonnegative and less than len
2205 // samplesToCopy is positive and not more than len
2206 }
2207
2208 clip->SetSamples(
2209 (constSamplePtr)(((const char*)buffer) +
2210 startDelta.as_size_t() *
2211 SAMPLE_SIZE(format)),
2212 format, inclipDelta, samplesToCopy.as_size_t() );
2213 clip->MarkChanged();
2214 }
2215 }
2216 }
2217
GetEnvelopeValues(double * buffer,size_t bufferLen,double t0) const2218 void WaveTrack::GetEnvelopeValues(double *buffer, size_t bufferLen,
2219 double t0) const
2220 {
2221 // The output buffer corresponds to an unbroken span of time which the callers expect
2222 // to be fully valid. As clips are processed below, the output buffer is updated with
2223 // envelope values from any portion of a clip, start, end, middle, or none at all.
2224 // Since this does not guarantee that the entire buffer is filled with values we need
2225 // to initialize the entire buffer to a default value.
2226 //
2227 // This does mean that, in the cases where a usable clip is located, the buffer value will
2228 // be set twice. Unfortunately, there is no easy way around this since the clips are not
2229 // stored in increasing time order. If they were, we could just track the time as the
2230 // buffer is filled.
2231 for (decltype(bufferLen) i = 0; i < bufferLen; i++)
2232 {
2233 buffer[i] = 1.0;
2234 }
2235
2236 double startTime = t0;
2237 auto tstep = 1.0 / mRate;
2238 double endTime = t0 + tstep * bufferLen;
2239 for (const auto &clip: mClips)
2240 {
2241 // IF clip intersects startTime..endTime THEN...
2242 auto dClipStartTime = clip->GetPlayStartTime();
2243 auto dClipEndTime = clip->GetPlayEndTime();
2244 if ((dClipStartTime < endTime) && (dClipEndTime > startTime))
2245 {
2246 auto rbuf = buffer;
2247 auto rlen = bufferLen;
2248 auto rt0 = t0;
2249
2250 if (rt0 < dClipStartTime)
2251 {
2252 // This is not more than the number of samples in
2253 // (endTime - startTime) which is bufferLen:
2254 auto nDiff = (sampleCount)floor((dClipStartTime - rt0) * mRate + 0.5);
2255 auto snDiff = nDiff.as_size_t();
2256 rbuf += snDiff;
2257 wxASSERT(snDiff <= rlen);
2258 rlen -= snDiff;
2259 rt0 = dClipStartTime;
2260 }
2261
2262 if (rt0 + rlen*tstep > dClipEndTime)
2263 {
2264 auto nClipLen = clip->GetPlayEndSample() - clip->GetPlayStartSample();
2265
2266 if (nClipLen <= 0) // Testing for bug 641, this problem is consistently '== 0', but doesn't hurt to check <.
2267 return;
2268
2269 // This check prevents problem cited in http://bugzilla.audacityteam.org/show_bug.cgi?id=528#c11,
2270 // Gale's cross_fade_out project, which was already corrupted by bug 528.
2271 // This conditional prevents the previous write past the buffer end, in clip->GetEnvelope() call.
2272 // Never increase rlen here.
2273 // PRL bug 827: rewrote it again
2274 rlen = limitSampleBufferSize( rlen, nClipLen );
2275 rlen = std::min(rlen, size_t(floor(0.5 + (dClipEndTime - rt0) / tstep)));
2276 }
2277 // Samples are obtained for the purpose of rendering a wave track,
2278 // so quantize time
2279 clip->GetEnvelope()->GetValues(rbuf, rlen, rt0, tstep);
2280 }
2281 }
2282 }
2283
GetClipAtSample(sampleCount sample)2284 WaveClip* WaveTrack::GetClipAtSample(sampleCount sample)
2285 {
2286 for (const auto &clip: mClips)
2287 {
2288 auto start = clip->GetPlayStartSample();
2289 auto len = clip->GetPlaySamplesCount();
2290
2291 if (sample >= start && sample < start + len)
2292 return clip.get();
2293 }
2294
2295 return NULL;
2296 }
2297
2298 // When the time is both the end of a clip and the start of the next clip, the
2299 // latter clip is returned.
GetClipAtTime(double time)2300 WaveClip* WaveTrack::GetClipAtTime(double time)
2301 {
2302
2303 const auto clips = SortedClipArray();
2304 auto p = std::find_if(clips.rbegin(), clips.rend(), [&] (WaveClip* const& clip) {
2305 return time >= clip->GetPlayStartTime() && time <= clip->GetPlayEndTime(); });
2306
2307 // When two clips are immediately next to each other, the GetPlayEndTime() of the first clip
2308 // and the GetPlayStartTime() of the second clip may not be exactly equal due to rounding errors.
2309 // If "time" is the end time of the first of two such clips, and the end time is slightly
2310 // less than the start time of the second clip, then the first rather than the
2311 // second clip is found by the above code. So correct this.
2312 if (p != clips.rend() && p != clips.rbegin() &&
2313 time == (*p)->GetPlayEndTime() &&
2314 (*p)->SharesBoundaryWithNextClip(*(p-1))) {
2315 p--;
2316 }
2317
2318 return p != clips.rend() ? *p : nullptr;
2319 }
2320
GetEnvelopeAtTime(double time)2321 Envelope* WaveTrack::GetEnvelopeAtTime(double time)
2322 {
2323 WaveClip* clip = GetClipAtTime(time);
2324 if (clip)
2325 return clip->GetEnvelope();
2326 else
2327 return NULL;
2328 }
2329
GetSequenceAtTime(double time)2330 Sequence* WaveTrack::GetSequenceAtTime(double time)
2331 {
2332 WaveClip* clip = GetClipAtTime(time);
2333 if (clip)
2334 return clip->GetSequence();
2335 else
2336 return NULL;
2337 }
2338
CreateClip(double offset,const wxString & name)2339 WaveClip* WaveTrack::CreateClip(double offset, const wxString& name)
2340 {
2341 auto clip = std::make_unique<WaveClip>(mpFactory, mFormat, mRate, GetWaveColorIndex());
2342 clip->SetName(name);
2343 clip->SetSequenceStartTime(offset);
2344 mClips.push_back(std::move(clip));
2345
2346 return mClips.back().get();
2347 }
2348
NewestOrNewClip()2349 WaveClip* WaveTrack::NewestOrNewClip()
2350 {
2351 if (mClips.empty()) {
2352 return CreateClip(mOffset, MakeNewClipName());
2353 }
2354 else
2355 return mClips.back().get();
2356 }
2357
2358 /*! @excsafety{No-fail} */
RightmostOrNewClip()2359 WaveClip* WaveTrack::RightmostOrNewClip()
2360 {
2361 if (mClips.empty()) {
2362 return CreateClip(mOffset, MakeNewClipName());
2363 }
2364 else
2365 {
2366 auto it = mClips.begin();
2367 WaveClip *rightmost = (*it++).get();
2368 double maxOffset = rightmost->GetPlayStartTime();
2369 for (auto end = mClips.end(); it != end; ++it)
2370 {
2371 WaveClip *clip = it->get();
2372 double offset = clip->GetPlayStartTime();
2373 if (maxOffset < offset)
2374 maxOffset = offset, rightmost = clip;
2375 }
2376 return rightmost;
2377 }
2378 }
2379
GetClipIndex(const WaveClip * clip) const2380 int WaveTrack::GetClipIndex(const WaveClip* clip) const
2381 {
2382 int result;
2383 FindClip(mClips, clip, &result);
2384 return result;
2385 }
2386
GetClipByIndex(int index)2387 WaveClip* WaveTrack::GetClipByIndex(int index)
2388 {
2389 if(index < (int)mClips.size())
2390 return mClips[index].get();
2391 else
2392 return nullptr;
2393 }
2394
GetClipByIndex(int index) const2395 const WaveClip* WaveTrack::GetClipByIndex(int index) const
2396 {
2397 return const_cast<WaveTrack&>(*this).GetClipByIndex(index);
2398 }
2399
GetNumClips() const2400 int WaveTrack::GetNumClips() const
2401 {
2402 return mClips.size();
2403 }
2404
CanOffsetClips(const std::vector<WaveClip * > & clips,double amount,double * allowedAmount)2405 bool WaveTrack::CanOffsetClips(
2406 const std::vector<WaveClip*> &clips,
2407 double amount,
2408 double *allowedAmount /* = NULL */)
2409 {
2410 if (allowedAmount)
2411 *allowedAmount = amount;
2412
2413 const auto &moving = [&](WaveClip *clip){
2414 // linear search might be improved, but expecting few moving clips
2415 // compared with the fixed clips
2416 return clips.end() != std::find( clips.begin(), clips.end(), clip );
2417 };
2418
2419 for (const auto &c: mClips) {
2420 if ( moving( c.get() ) )
2421 continue;
2422 for (const auto clip : clips) {
2423 if (c->GetPlayStartTime() < clip->GetPlayEndTime() + amount &&
2424 c->GetPlayEndTime() > clip->GetPlayStartTime() + amount)
2425 {
2426 if (!allowedAmount)
2427 return false; // clips overlap
2428
2429 if (amount > 0)
2430 {
2431 if (c->GetPlayStartTime() - clip->GetPlayEndTime() < *allowedAmount)
2432 *allowedAmount = c->GetPlayStartTime() - clip->GetPlayEndTime();
2433 if (*allowedAmount < 0)
2434 *allowedAmount = 0;
2435 } else
2436 {
2437 if (c->GetPlayEndTime() - clip->GetPlayStartTime() > *allowedAmount)
2438 *allowedAmount = c->GetPlayEndTime() - clip->GetPlayStartTime();
2439 if (*allowedAmount > 0)
2440 *allowedAmount = 0;
2441 }
2442 }
2443 }
2444 }
2445
2446 if (allowedAmount)
2447 {
2448 if (*allowedAmount == amount)
2449 return true;
2450
2451 // Check if the NEW calculated amount would not violate
2452 // any other constraint
2453 if (!CanOffsetClips(clips, *allowedAmount, nullptr)) {
2454 *allowedAmount = 0; // play safe and don't allow anything
2455 return false;
2456 }
2457 else
2458 return true;
2459 } else
2460 return true;
2461 }
2462
CanInsertClip(WaveClip * clip,double & slideBy,double & tolerance) const2463 bool WaveTrack::CanInsertClip(
2464 WaveClip* clip, double &slideBy, double &tolerance) const
2465 {
2466 for (const auto &c : mClips)
2467 {
2468 double d1 = c->GetPlayStartTime() - (clip->GetPlayEndTime()+slideBy);
2469 double d2 = (clip->GetPlayStartTime()+slideBy) - c->GetPlayEndTime();
2470 if ( (d1<0) && (d2<0) )
2471 {
2472 // clips overlap.
2473 // Try to rescue it.
2474 // The rescue logic is not perfect, and will typically
2475 // move the clip at most once.
2476 // We divide by 1000 rather than set to 0, to allow for
2477 // a second 'micro move' that is really about rounding error.
2478 if( -d1 < tolerance ){
2479 // right edge of clip overlaps slightly.
2480 // slide clip left a small amount.
2481 slideBy +=d1;
2482 tolerance /=1000;
2483 } else if( -d2 < tolerance ){
2484 // left edge of clip overlaps slightly.
2485 // slide clip right a small amount.
2486 slideBy -= d2;
2487 tolerance /=1000;
2488 }
2489 else
2490 return false; // clips overlap No tolerance left.
2491 }
2492 }
2493
2494 return true;
2495 }
2496
2497 /*! @excsafety{Weak} */
Split(double t0,double t1)2498 void WaveTrack::Split( double t0, double t1 )
2499 {
2500 SplitAt( t0 );
2501 if( t0 != t1 )
2502 SplitAt( t1 );
2503 }
2504
2505 /*! @excsafety{Weak} */
SplitAt(double t)2506 void WaveTrack::SplitAt(double t)
2507 {
2508 for (const auto &c : mClips)
2509 {
2510 if (c->WithinPlayRegion(t))
2511 {
2512 t = LongSamplesToTime(TimeToLongSamples(t));
2513 auto newClip = std::make_unique<WaveClip>( *c, mpFactory, true );
2514 c->TrimRightTo(t);// put t on a sample
2515 newClip->TrimLeftTo(t);
2516
2517 // This could invalidate the iterators for the loop! But we return
2518 // at once so it's okay
2519 mClips.push_back(std::move(newClip)); // transfer ownership
2520 return;
2521 }
2522 }
2523 }
2524
UpdateLocationsCache() const2525 void WaveTrack::UpdateLocationsCache() const
2526 {
2527 auto clips = SortedClipArray();
2528
2529 mDisplayLocationsCache.clear();
2530
2531 // Count number of display locations
2532 int num = 0;
2533 {
2534 const WaveClip *prev = nullptr;
2535 for (const auto clip : clips)
2536 {
2537 //enough for estimation
2538 num += clip->NumCutLines();
2539
2540 if (prev && fabs(prev->GetPlayEndTime() -
2541 clip->GetPlayStartTime()) < WAVETRACK_MERGE_POINT_TOLERANCE)
2542 ++num;
2543 prev = clip;
2544 }
2545 }
2546
2547 if (num == 0)
2548 return;
2549
2550 // Alloc necessary number of display locations
2551 mDisplayLocationsCache.reserve(num);
2552
2553 // Add all display locations to cache
2554 int curpos = 0;
2555
2556 const WaveClip *previousClip = nullptr;
2557 for (const auto clip: clips)
2558 {
2559 for (const auto &cc : clip->GetCutLines())
2560 {
2561 auto cutlinePosition = clip->GetSequenceStartTime() + cc->GetSequenceStartTime();
2562 if (clip->WithinPlayRegion(cutlinePosition))
2563 {
2564 // Add cut line expander point
2565 mDisplayLocationsCache.push_back(WaveTrackLocation{
2566 cutlinePosition,
2567 WaveTrackLocation::locationCutLine
2568 });
2569 }
2570 // If cutline is skipped, we still need to count it
2571 // so that curpos match num at the end
2572 curpos++;
2573 }
2574
2575 if (previousClip)
2576 {
2577 if (fabs(previousClip->GetPlayEndTime() - clip->GetPlayStartTime())
2578 < WAVETRACK_MERGE_POINT_TOLERANCE)
2579 {
2580 // Add merge point
2581 mDisplayLocationsCache.push_back(WaveTrackLocation{
2582 previousClip->GetPlayEndTime(),
2583 WaveTrackLocation::locationMergePoint,
2584 GetClipIndex(previousClip),
2585 GetClipIndex(clip)
2586 });
2587 curpos++;
2588 }
2589 }
2590
2591 previousClip = clip;
2592 }
2593
2594 wxASSERT(curpos == num);
2595 }
2596
2597 // Expand cut line (that is, re-insert audio, then DELETE audio saved in cut line)
2598 /*! @excsafety{Strong} */
ExpandCutLine(double cutLinePosition,double * cutlineStart,double * cutlineEnd)2599 void WaveTrack::ExpandCutLine(double cutLinePosition, double* cutlineStart,
2600 double* cutlineEnd)
2601 {
2602 bool editClipCanMove = GetEditClipsCanMove();
2603
2604 // Find clip which contains this cut line
2605 double start = 0, end = 0;
2606 auto pEnd = mClips.end();
2607 auto pClip = std::find_if( mClips.begin(), pEnd,
2608 [&](const WaveClipHolder &clip) {
2609 return clip->FindCutLine(cutLinePosition, &start, &end); } );
2610 if (pClip != pEnd)
2611 {
2612 auto &clip = *pClip;
2613 if (!editClipCanMove)
2614 {
2615 // We are not allowed to move the other clips, so see if there
2616 // is enough room to expand the cut line
2617 for (const auto &clip2: mClips)
2618 {
2619 if (clip2->GetPlayStartTime() > clip->GetPlayStartTime() &&
2620 clip->GetPlayEndTime() + end - start > clip2->GetPlayStartTime())
2621 // Strong-guarantee in case of this path
2622 throw SimpleMessageBoxException{
2623 ExceptionType::BadUserAction,
2624 XO("There is not enough room available to expand the cut line"),
2625 XO("Warning"),
2626 "Error:_Insufficient_space_in_track"
2627 };
2628 }
2629 }
2630
2631 clip->ExpandCutLine(cutLinePosition);
2632
2633 // Strong-guarantee provided that the following gives No-fail-guarantee
2634
2635 if (cutlineStart)
2636 *cutlineStart = start;
2637 if (cutlineEnd)
2638 *cutlineEnd = end;
2639
2640 // Move clips which are to the right of the cut line
2641 if (editClipCanMove)
2642 {
2643 for (const auto &clip2 : mClips)
2644 {
2645 if (clip2->GetPlayStartTime() > clip->GetPlayStartTime())
2646 clip2->Offset(end - start);
2647 }
2648 }
2649 }
2650 }
2651
RemoveCutLine(double cutLinePosition)2652 bool WaveTrack::RemoveCutLine(double cutLinePosition)
2653 {
2654 for (const auto &clip : mClips)
2655 if (clip->RemoveCutLine(cutLinePosition))
2656 return true;
2657
2658 return false;
2659 }
2660
2661 /*! @excsafety{Strong} */
MergeClips(int clipidx1,int clipidx2)2662 void WaveTrack::MergeClips(int clipidx1, int clipidx2)
2663 {
2664 WaveClip* clip1 = GetClipByIndex(clipidx1);
2665 WaveClip* clip2 = GetClipByIndex(clipidx2);
2666
2667 if (!clip1 || !clip2) // Could happen if one track of a linked pair had a split and the other didn't.
2668 return; // Don't throw, just do nothing.
2669
2670 // Append data from second clip to first clip
2671 // use Strong-guarantee
2672 clip1->Paste(clip1->GetPlayEndTime(), clip2);
2673
2674 // use No-fail-guarantee for the rest
2675 // Delete second clip
2676 auto it = FindClip(mClips, clip2);
2677 mClips.erase(it);
2678 }
2679
2680 /*! @excsafety{Weak} -- Partial completion may leave clips at differing sample rates!
2681 */
Resample(int rate,ProgressDialog * progress)2682 void WaveTrack::Resample(int rate, ProgressDialog *progress)
2683 {
2684 for (const auto &clip : mClips)
2685 clip->Resample(rate, progress);
2686
2687 mRate = rate;
2688 }
2689
2690 namespace {
2691 template < typename Cont1, typename Cont2 >
2692 Cont1 FillSortedClipArray(const Cont2& mClips)
2693 {
2694 Cont1 clips;
2695 for (const auto &clip : mClips)
2696 clips.push_back(clip.get());
2697 std::sort(clips.begin(), clips.end(),
2698 [](const WaveClip *a, const WaveClip *b)
__anon8aaf33e21102(const WaveClip *a, const WaveClip *b) 2699 { return a->GetPlayStartTime() < b->GetPlayStartTime(); });
2700 return clips;
2701 }
2702 }
2703
SortedClipArray()2704 WaveClipPointers WaveTrack::SortedClipArray()
2705 {
2706 return FillSortedClipArray<WaveClipPointers>(mClips);
2707 }
2708
SortedClipArray() const2709 WaveClipConstPointers WaveTrack::SortedClipArray() const
2710 {
2711 return FillSortedClipArray<WaveClipConstPointers>(mClips);
2712 }
2713
2714 ///Deletes all clips' wavecaches. Careful, This may not be threadsafe.
ClearWaveCaches()2715 void WaveTrack::ClearWaveCaches()
2716 {
2717 for (const auto &clip : mClips)
2718 clip->ClearWaveCache();
2719 }
2720
~WaveTrackCache()2721 WaveTrackCache::~WaveTrackCache()
2722 {
2723 }
2724
SetTrack(const std::shared_ptr<const WaveTrack> & pTrack)2725 void WaveTrackCache::SetTrack(const std::shared_ptr<const WaveTrack> &pTrack)
2726 {
2727 if (mPTrack != pTrack) {
2728 if (pTrack) {
2729 mBufferSize = pTrack->GetMaxBlockSize();
2730 if (!mPTrack ||
2731 mPTrack->GetMaxBlockSize() != mBufferSize) {
2732 Free();
2733 mBuffers[0].data = Floats{ mBufferSize };
2734 mBuffers[1].data = Floats{ mBufferSize };
2735 }
2736 }
2737 else
2738 Free();
2739 mPTrack = pTrack;
2740 mNValidBuffers = 0;
2741 }
2742 }
2743
GetFloats(sampleCount start,size_t len,bool mayThrow)2744 const float *WaveTrackCache::GetFloats(
2745 sampleCount start, size_t len, bool mayThrow)
2746 {
2747 constexpr auto format = floatSample;
2748 if (format == floatSample && len > 0) {
2749 const auto end = start + len;
2750
2751 bool fillFirst = (mNValidBuffers < 1);
2752 bool fillSecond = (mNValidBuffers < 2);
2753
2754 // Discard cached results that we no longer need
2755 if (mNValidBuffers > 0 &&
2756 (end <= mBuffers[0].start ||
2757 start >= mBuffers[mNValidBuffers - 1].end())) {
2758 // Complete miss
2759 fillFirst = true;
2760 fillSecond = true;
2761 }
2762 else if (mNValidBuffers == 2 &&
2763 start >= mBuffers[1].start &&
2764 end > mBuffers[1].end()) {
2765 // Request starts in the second buffer and extends past it.
2766 // Discard the first buffer.
2767 // (But don't deallocate the buffer space.)
2768 mBuffers[0] .swap ( mBuffers[1] );
2769 fillSecond = true;
2770 mNValidBuffers = 1;
2771 }
2772 else if (mNValidBuffers > 0 &&
2773 start < mBuffers[0].start &&
2774 0 <= mPTrack->GetBlockStart(start)) {
2775 // Request is not a total miss but starts before the cache,
2776 // and there is a clip to fetch from.
2777 // Not the access pattern for drawing spectrogram or playback,
2778 // but maybe scrubbing causes this.
2779 // Move the first buffer into second place, and later
2780 // refill the first.
2781 // (This case might be useful when marching backwards through
2782 // the track, as with scrubbing.)
2783 mBuffers[0] .swap ( mBuffers[1] );
2784 fillFirst = true;
2785 fillSecond = false;
2786 // Cache is not in a consistent state yet
2787 mNValidBuffers = 0;
2788 }
2789
2790 // Refill buffers as needed
2791 if (fillFirst) {
2792 const auto start0 = mPTrack->GetBlockStart(start);
2793 if (start0 >= 0) {
2794 const auto len0 = mPTrack->GetBestBlockSize(start0);
2795 wxASSERT(len0 <= mBufferSize);
2796 if (!mPTrack->GetFloats(
2797 mBuffers[0].data.get(), start0, len0,
2798 fillZero, mayThrow))
2799 return nullptr;
2800 mBuffers[0].start = start0;
2801 mBuffers[0].len = len0;
2802 if (!fillSecond &&
2803 mBuffers[0].end() != mBuffers[1].start)
2804 fillSecond = true;
2805 // Keep the partially updated state consistent:
2806 mNValidBuffers = fillSecond ? 1 : 2;
2807 }
2808 else {
2809 // Request may fall between the clips of a track.
2810 // Invalidate all. WaveTrack::Get() will return zeroes.
2811 mNValidBuffers = 0;
2812 fillSecond = false;
2813 }
2814 }
2815 wxASSERT(!fillSecond || mNValidBuffers > 0);
2816 if (fillSecond) {
2817 mNValidBuffers = 1;
2818 const auto end0 = mBuffers[0].end();
2819 if (end > end0) {
2820 const auto start1 = mPTrack->GetBlockStart(end0);
2821 if (start1 == end0) {
2822 const auto len1 = mPTrack->GetBestBlockSize(start1);
2823 wxASSERT(len1 <= mBufferSize);
2824 if (!mPTrack->GetFloats(mBuffers[1].data.get(), start1, len1, fillZero, mayThrow))
2825 return nullptr;
2826 mBuffers[1].start = start1;
2827 mBuffers[1].len = len1;
2828 mNValidBuffers = 2;
2829 }
2830 }
2831 }
2832 wxASSERT(mNValidBuffers < 2 || mBuffers[0].end() == mBuffers[1].start);
2833
2834 samplePtr buffer = nullptr; // will point into mOverlapBuffer
2835 auto remaining = len;
2836
2837 // Possibly get an initial portion that is uncached
2838
2839 // This may be negative
2840 const auto initLen =
2841 mNValidBuffers < 1 ? sampleCount( len )
2842 : std::min(sampleCount( len ), mBuffers[0].start - start);
2843
2844 if (initLen > 0) {
2845 // This might be fetching zeroes between clips
2846 mOverlapBuffer.Resize(len, format);
2847 // initLen is not more than len:
2848 auto sinitLen = initLen.as_size_t();
2849 if (!mPTrack->GetFloats(
2850 // See comment below about casting
2851 reinterpret_cast<float *>(mOverlapBuffer.ptr()),
2852 start, sinitLen, fillZero, mayThrow))
2853 return nullptr;
2854 wxASSERT( sinitLen <= remaining );
2855 remaining -= sinitLen;
2856 start += initLen;
2857 buffer = mOverlapBuffer.ptr() + sinitLen * SAMPLE_SIZE(format);
2858 }
2859
2860 // Now satisfy the request from the buffers
2861 for (int ii = 0; ii < mNValidBuffers && remaining > 0; ++ii) {
2862 const auto starti = start - mBuffers[ii].start;
2863 // Treatment of initLen above establishes this loop invariant,
2864 // and statements below preserve it:
2865 wxASSERT(starti >= 0);
2866
2867 // This may be negative
2868 const auto leni =
2869 std::min( sampleCount( remaining ), mBuffers[ii].len - starti );
2870 if (initLen <= 0 && leni == len) {
2871 // All is contiguous already. We can completely avoid copying
2872 // leni is nonnegative, therefore start falls within mBuffers[ii],
2873 // so starti is bounded between 0 and buffer length
2874 return mBuffers[ii].data.get() + starti.as_size_t() ;
2875 }
2876 else if (leni > 0) {
2877 // leni is nonnegative, therefore start falls within mBuffers[ii]
2878 // But we can't satisfy all from one buffer, so copy
2879 if (!buffer) {
2880 mOverlapBuffer.Resize(len, format);
2881 buffer = mOverlapBuffer.ptr();
2882 }
2883 // leni is positive and not more than remaining
2884 const size_t size = sizeof(float) * leni.as_size_t();
2885 // starti is less than mBuffers[ii].len and nonnegative
2886 memcpy(buffer, mBuffers[ii].data.get() + starti.as_size_t(), size);
2887 wxASSERT( leni <= remaining );
2888 remaining -= leni.as_size_t();
2889 start += leni;
2890 buffer += size;
2891 }
2892 }
2893
2894 if (remaining > 0) {
2895 // Very big request!
2896 // Fall back to direct fetch
2897 if (!buffer) {
2898 mOverlapBuffer.Resize(len, format);
2899 buffer = mOverlapBuffer.ptr();
2900 }
2901 // See comment below about casting
2902 if (!mPTrack->GetFloats( reinterpret_cast<float*>(buffer),
2903 start, remaining, fillZero, mayThrow))
2904 return 0;
2905 }
2906
2907 // Overlap buffer was meant for the more general support of sample formats
2908 // besides float, which explains the cast
2909 return reinterpret_cast<const float*>(mOverlapBuffer.ptr());
2910 }
2911 else {
2912 #if 0
2913 // Cache works only for float format.
2914 mOverlapBuffer.Resize(len, format);
2915 if (mPTrack->Get(mOverlapBuffer.ptr(), format, start, len, fillZero, mayThrow))
2916 return mOverlapBuffer.ptr();
2917 #else
2918 // No longer handling other than float format. Therefore len is 0.
2919 #endif
2920 return nullptr;
2921 }
2922 }
2923
Free()2924 void WaveTrackCache::Free()
2925 {
2926 mBuffers[0].Free();
2927 mBuffers[1].Free();
2928 mOverlapBuffer.Free();
2929 mNValidBuffers = 0;
2930 }
2931
operator ++()2932 auto WaveTrack::AllClipsIterator::operator ++ () -> AllClipsIterator &
2933 {
2934 // The unspecified sequence is a post-order, but there is no
2935 // promise whether sister nodes are ordered in time.
2936 if ( !mStack.empty() ) {
2937 auto &pair = mStack.back();
2938 if ( ++pair.first == pair.second ) {
2939 mStack.pop_back();
2940 }
2941 else
2942 push( (*pair.first)->GetCutLines() );
2943 }
2944
2945 return *this;
2946 }
2947
push(WaveClipHolders & clips)2948 void WaveTrack::AllClipsIterator::push( WaveClipHolders &clips )
2949 {
2950 auto pClips = &clips;
2951 while (!pClips->empty()) {
2952 auto first = pClips->begin();
2953 mStack.push_back( Pair( first, pClips->end() ) );
2954 pClips = &(*first)->GetCutLines();
2955 }
2956 }
2957
2958 #include "SampleBlock.h"
VisitBlocks(TrackList & tracks,BlockVisitor visitor,SampleBlockIDSet * pIDs)2959 void VisitBlocks(TrackList &tracks, BlockVisitor visitor,
2960 SampleBlockIDSet *pIDs)
2961 {
2962 for (auto wt : tracks.Any< const WaveTrack >()) {
2963 // Scan all clips within current track
2964 for(const auto &clip : wt->GetAllClips()) {
2965 // Scan all sample blocks within current clip
2966 auto blocks = clip->GetSequenceBlockArray();
2967 for (const auto &block : *blocks) {
2968 auto &pBlock = block.sb;
2969 if ( pBlock ) {
2970 if ( pIDs && !pIDs->insert(pBlock->GetBlockID()).second )
2971 continue;
2972 if ( visitor )
2973 visitor( *pBlock );
2974 }
2975 }
2976 }
2977 }
2978 }
2979
InspectBlocks(const TrackList & tracks,BlockInspector inspector,SampleBlockIDSet * pIDs)2980 void InspectBlocks(const TrackList &tracks, BlockInspector inspector,
2981 SampleBlockIDSet *pIDs)
2982 {
2983 VisitBlocks(
2984 const_cast<TrackList &>(tracks), std::move( inspector ), pIDs );
2985 }
2986
2987 #include "Project.h"
2988 #include "SampleBlock.h"
__anon8aaf33e21202( AudacityProject &project ) 2989 static auto TrackFactoryFactory = []( AudacityProject &project ) {
2990 return std::make_shared< WaveTrackFactory >(
2991 ProjectRate::Get( project ),
2992 SampleBlockFactory::New( project ) );
2993 };
2994
2995 static const AudacityProject::AttachedObjects::RegisteredFactory key2{
2996 TrackFactoryFactory
2997 };
2998
Get(AudacityProject & project)2999 WaveTrackFactory &WaveTrackFactory::Get( AudacityProject &project )
3000 {
3001 return project.AttachedObjects::Get< WaveTrackFactory >( key2 );
3002 }
3003
Get(const AudacityProject & project)3004 const WaveTrackFactory &WaveTrackFactory::Get( const AudacityProject &project )
3005 {
3006 return Get( const_cast< AudacityProject & >( project ) );
3007 }
3008
Reset(AudacityProject & project)3009 WaveTrackFactory &WaveTrackFactory::Reset( AudacityProject &project )
3010 {
3011 auto result = TrackFactoryFactory( project );
3012 project.AttachedObjects::Assign( key2, result );
3013 return *result;
3014 }
3015
Destroy(AudacityProject & project)3016 void WaveTrackFactory::Destroy( AudacityProject &project )
3017 {
3018 project.AttachedObjects::Assign( key2, nullptr );
3019 }
3020
3021 ProjectFormatExtensionsRegistry::Extension smartClipsExtension(
3022 [](const AudacityProject& project) -> ProjectFormatVersion
__anon8aaf33e21302(const AudacityProject& project) 3023 {
3024 const TrackList& trackList = TrackList::Get(project);
3025
3026 for (auto wt : trackList.Any<const WaveTrack>())
3027 {
3028 for (const auto& clip : wt->GetAllClips())
3029 {
3030 if (clip->GetTrimLeft() > 0.0 || clip->GetTrimRight() > 0.0)
3031 return { 3, 1, 0, 0 };
3032 }
3033 }
3034
3035 return BaseProjectFormatVersion;
3036 }
3037 );
3038