Audacity 3.2.0
WaveTrack.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 WaveTrack.cpp
6
7 Dominic Mazzoni
8
9*******************************************************************//****************************************************************//****************************************************************/
21
29#include "WaveTrack.h"
30
31#include "WaveClip.h"
32
33#include <wx/defs.h>
34#include <wx/debug.h>
35#include <wx/log.h>
36
37#include <algorithm>
38#include <float.h>
39#include <math.h>
40#include <numeric>
41#include <optional>
42#include <type_traits>
43#include <unordered_set>
44
45#include "float_cast.h"
46
48#include "ChannelAttachments.h"
50#include "Envelope.h"
51#include "Sequence.h"
53
54#include "TempoChange.h"
55#include "Project.h"
56#include "ProjectRate.h"
57#include "SampleBlock.h"
58
59#include "BasicUI.h"
60#include "Prefs.h"
61#include "QualitySettings.h"
62#include "SyncLock.h"
63#include "TimeWarper.h"
64
65
67
68#include <cmath>
69
70using std::max;
71
75namespace {
77 const WaveTrack::IntervalHolder &pInterval,
78 const std::function<void(double)>& reportProgress,
80{
81 auto &interval = *pInterval;
82 using Interval = WaveTrack::Interval;
83 if (!interval.HasPitchOrSpeed())
84 return pInterval;
85
86 const auto dst = std::make_shared<Interval>(
87 interval.NChannels(), factory, format, interval.GetRate());
88
89 const auto originalPlayStartTime = interval.GetPlayStartTime();
90 const auto originalPlayEndTime = interval.GetPlayEndTime();
91 const auto stretchRatio = interval.GetStretchRatio();
92
93 auto success = false;
94 Finally Do { [&] {
95 if (!success)
96 {
97 interval.TrimLeftTo(originalPlayStartTime);
98 interval.TrimRightTo(originalPlayEndTime);
99 }
100 } };
101
102 // Leave 1 second of raw, unstretched audio before and after visible region
103 // to give the algorithm a chance to be in a steady state when reaching the
104 // play boundaries.
105 const auto tmpPlayStartTime =
106 std::max(interval.GetSequenceStartTime(), originalPlayStartTime - stretchRatio);
107 const auto tmpPlayEndTime =
108 std::min(interval.GetSequenceEndTime(), originalPlayEndTime + stretchRatio);
109 interval.TrimLeftTo(tmpPlayStartTime);
110 interval.TrimRightTo(tmpPlayEndTime);
111
112 constexpr auto sourceDurationToDiscard = 0.;
113 constexpr auto blockSize = 1024;
114 const auto numChannels = interval.NChannels();
115 ClipTimeAndPitchSource stretcherSource { interval, sourceDurationToDiscard,
118 params.timeRatio = stretchRatio;
119 params.pitchRatio = std::pow(2., interval.GetCentShift() / 1200.);
120 params.preserveFormants =
121 interval.GetPitchAndSpeedPreset() == PitchAndSpeedPreset::OptimizeForVoice;
122 StaffPadTimeAndPitch stretcher { interval.GetRate(), numChannels,
123 stretcherSource, std::move(params) };
124
125 // Post-rendering sample counts, i.e., stretched units
126 const auto totalNumOutSamples =
127 sampleCount { interval.GetVisibleSampleCount().as_double() *
128 stretchRatio };
129
130 sampleCount numOutSamples { 0 };
131 AudioContainer container(blockSize, numChannels);
132
133 while (numOutSamples < totalNumOutSamples)
134 {
135 const auto numSamplesToGet =
136 limitSampleBufferSize(blockSize, totalNumOutSamples - numOutSamples);
137 stretcher.GetSamples(container.Get(), numSamplesToGet);
138 constSamplePtr data[2];
139 data[0] = reinterpret_cast<constSamplePtr>(container.Get()[0]);
140 if (interval.NChannels() == 2)
141 data[1] = reinterpret_cast<constSamplePtr>(container.Get()[1]);
142 dst->Append(data, floatSample, numSamplesToGet, 1, widestSampleFormat);
143 numOutSamples += numSamplesToGet;
144 if (reportProgress)
145 reportProgress(
146 numOutSamples.as_double() / totalNumOutSamples.as_double());
147 }
148 dst->Flush();
149
150 // Now we're all like `this` except unstretched. We can clear leading and
151 // trailing, stretching transient parts.
152 dst->SetPlayStartTime(tmpPlayStartTime);
153 dst->ClearLeft(originalPlayStartTime);
154 dst->ClearRight(originalPlayEndTime);
155
156 // We don't preserve cutlines but the relevant part of the envelope.
157 auto dstEnvelope = std::make_unique<Envelope>(interval.GetEnvelope());
158 const auto samplePeriod = 1. / interval.GetRate();
159 dstEnvelope->CollapseRegion(
160 originalPlayEndTime, interval.GetSequenceEndTime() + samplePeriod, samplePeriod);
161 dstEnvelope->CollapseRegion(0, originalPlayStartTime, samplePeriod);
162 dstEnvelope->SetOffset(originalPlayStartTime);
163 dst->SetEnvelope(move(dstEnvelope));
164
165 success = true;
166
167 assert(!dst->HasPitchOrSpeed());
168 return dst;
169}
170}
171
172std::shared_ptr<const WaveTrack::Interval>
173WaveTrack::GetNextInterval(const Interval& interval, PlaybackDirection searchDirection) const
174{
175 std::shared_ptr<const Interval> result;
176 auto bestMatchTime = searchDirection == PlaybackDirection::forward
177 ? std::numeric_limits<double>::max()
178 : std::numeric_limits<double>::lowest();
179
180 for (const auto &other : Intervals()) {
181 if((searchDirection == PlaybackDirection::forward &&
182 (other->Start() > interval.Start() && other->Start() < bestMatchTime))
183 ||
184 (searchDirection == PlaybackDirection::backward &&
185 (other->Start() < interval.Start() && other->Start() > bestMatchTime)))
186 {
187 result = other;
188 bestMatchTime = other->Start();
189 }
190 }
191 return result;
192}
193
195 const Interval& interval, PlaybackDirection searchDirection)
196{
197 return std::const_pointer_cast<Interval>(
198 std::as_const(*this).GetNextInterval(interval, searchDirection));
199}
200
202{
203 IntervalHolder result;
204 for (const auto &interval : Intervals())
205 if (interval->WithinPlayRegion(t))
206 return interval;
207 return nullptr;
208}
209
210namespace {
212 WaveTrackData() = default;
215 ~WaveTrackData() override;
216 std::unique_ptr<ClientData::Cloneable<>> Clone() const override;
217
218 static WaveTrackData &Get(WaveTrack &track);
219 static const WaveTrackData &Get(const WaveTrack &track);
220
221 double GetOrigin() const;
222 void SetOrigin(double origin);
223
224 sampleFormat GetSampleFormat() const;
225 void SetSampleFormat(sampleFormat format);
226
227 float GetGain() const;
228 void SetGain(float value);
229 float GetPan() const;
230 void SetPan(float value);
231
232 int GetRate() const;
233 void SetRate(int value);
234
235private:
237 std::atomic<float> mGain{ 1.0f };
239 std::atomic<float> mPan{ 0.0f };
240
241 int mRate{ 44100 };
242 double mOrigin{ 0.0 };
244};
245
248 [](auto &) { return std::make_unique<WaveTrackData>(); } };
249
251WaveTrackData::WaveTrackData(const WaveTrackData &other) {
252 SetGain(other.GetGain());
253 SetPan(other.GetPan());
254 mRate = other.mRate;
255 mOrigin = other.mOrigin;
256 mFormat = other.mFormat;
257}
258
259WaveTrackData::~WaveTrackData() = default;
260
261std::unique_ptr<ClientData::Cloneable<>> WaveTrackData::Clone() const {
262 return std::make_unique<WaveTrackData>(*this);
263}
264
266 return track.Attachments::Get<WaveTrackData>(waveTrackDataFactory);
267}
268
270{
271 return Get(const_cast<WaveTrack &>(track));
272}
273
274double WaveTrackData::GetOrigin() const
275{
276 return mOrigin;
277}
278void WaveTrackData::SetOrigin(double origin)
279{
280 mOrigin = origin;
281}
282
283sampleFormat WaveTrackData::GetSampleFormat() const
284{
285 return mFormat;
286}
287
288void WaveTrackData::SetSampleFormat(sampleFormat format)
289{
290 mFormat = format;
291}
292
293float WaveTrackData::GetGain() const
294{
295 return mGain.load(std::memory_order_relaxed);
296}
297
298void WaveTrackData::SetGain(float value)
299{
300 mGain.store(value, std::memory_order_relaxed);
301}
302
303float WaveTrackData::GetPan() const
304{
305 return mPan.load(std::memory_order_relaxed);
306}
307
308void WaveTrackData::SetPan(float value)
309{
310 mPan.store(value, std::memory_order_relaxed);
311}
312
314{
315 return mRate;
316}
317
318void WaveTrackData::SetRate(int value)
319{
320 mRate = value;
321}
322
323namespace {
326{
327 if (a.size() != b.size())
328 return false;
329
330 const auto compare = [](const auto &a, const auto &b) {
331 // clips are aligned if both sequence start/end
332 // points and play start/end points of the first clip match
333 // the corresponding points of the other clip
334 return a->GetPlayStartTime() == b->GetPlayStartTime() &&
335 a->GetSequenceStartTime() == b->GetSequenceStartTime() &&
336 a->GetPlayEndTime() == b->GetPlayEndTime() &&
337 a->GetSequenceEndTime() == b->GetSequenceEndTime();
338 };
339
340 return std::mismatch(a.begin(), a.end(), b.begin(), compare).first == a.end();
341}
342}
343
344//Handles possible future file values
346{
347 if (value < 0)
349 else if (value > 3)
351 return static_cast<Track::LinkType>(value);
352}
353
354}
355
357{
359 return std::max(ProjectRate::Get(project).GetRate(),
360 tracks.Any<const WaveTrack>().max(&WaveTrack::GetRate))
361 / 2.0;
362}
363
364static auto DefaultName = XO("Audio");
365
367 : mOwner{ owner }
368{
369}
370
371WaveChannel::~WaveChannel() = default;
372
374{
376
377 if (name.empty() || ( name == DefaultName.MSGID() ))
378 // When nothing was specified,
379 // the default-default is whatever translation of...
380 /* i18n-hint: The default name for an audio track. */
381 return DefaultName.Translation();
382 else
383 return name;
384}
385
387 "wavetrack",
389};
390
391std::shared_ptr<WaveTrack> WaveTrackFactory::Create()
392{
394}
395
396std::shared_ptr<WaveTrack> WaveTrackFactory::DoCreate(size_t nChannels,
397 sampleFormat format, double rate)
398{
399 auto result = std::make_shared<WaveTrack>(
401 // Set the number of channels correctly before building all channel
402 // attachments
403 if (nChannels > 1)
404 result->CreateRight();
405 // Only after make_shared returns, can weak_from_this be used, which
406 // attached object factories may need
407 result->AttachedTrackObjects::BuildAll();
408 return result;
409}
410
411std::shared_ptr<WaveTrack> WaveTrackFactory::Create(sampleFormat format, double rate)
412{
413 return DoCreate(1, format, rate);
414}
415
417{
418 assert(nChannels > 0);
419 assert(nChannels <= 2);
421}
422
424{
425 return CreateMany(nChannels,
427}
428
430{
432 [this](TrackAttachment *pLeft, TrackAttachment *pRight){
433 // Precondition of callback from ClientData::Site
434 assert(pLeft && pRight);
435 const auto pLeftAttachments =
436 dynamic_cast<ChannelAttachmentsBase *>(pLeft);
437 const auto pRightAttachments =
438 dynamic_cast<ChannelAttachmentsBase *>(pRight);
439 // They should have come from the same factory of channel attachments
440 assert((pLeftAttachments == nullptr) == (pRightAttachments == nullptr));
441 if (pLeftAttachments) {
442 // First fixup the back-pointers from channel views to their track
443 pRightAttachments->Reparent(shared_from_this());
444 // Then "steal" them
445 pLeftAttachments->MakeStereo(shared_from_this(),
446 std::move(*pRightAttachments));
447 }
448 });
449}
450
453{
455 [this, ii](TrackAttachment &attachment){
456 if (const auto pAttachments =
457 dynamic_cast<ChannelAttachmentsBase *>(&attachment))
458 pAttachments->Erase(shared_from_this(), ii);
459 });
460}
461
463{
464 assert(nChannels > 0);
465 assert(nChannels <= 2);
466 return CreateMany(nChannels, format, rate)->DetachFirst()
467 ->SharedPointer<WaveTrack>();
468}
469
471{
472 // There are some cases where more than two channels are requested
473 if (nChannels == 2)
474 return TrackList::Temporary(nullptr, DoCreate(nChannels, format, rate));
475 auto result = TrackList::Temporary(nullptr);
476 while (nChannels--)
477 result->Add(DoCreate(1, format, rate));
478 return result;
479}
480
482{
483 return proto.EmptyCopy(nChannels, mpFactory);
484}
485
487{
488 auto &trackFactory = WaveTrackFactory::Get( project );
489 auto &tracks = TrackList::Get( project );
490 auto result = tracks.Add(trackFactory.Create());
491 return result;
492}
493
495 sampleFormat format, double rate )
496 : mpFactory(pFactory)
497 , mChannel(*this)
498{
499 WaveTrackData::Get(*this).SetSampleFormat(format);
500 DoSetRate(static_cast<int>(rate));
501}
502
504 const SampleBlockFactoryPtr &pFactory, sampleFormat format, double rate)
505 -> Holder
506{
507 auto result =
508 std::make_shared<WaveTrack>(CreateToken{}, pFactory, format, rate);
509 // Only after make_shared returns, can weak_from_this be used, which
510 // attached object factories may need
511 // (but this is anyway just the factory for unit test purposes)
512 result->AttachedTrackObjects::BuildAll();
513 return result;
514}
515
517 SampleBlockFactoryPtr pFactory, const WaveClipHolders &orig, bool backup)
518{
519 for (const auto &clip : orig)
520 InsertClip(clips,
521 std::make_shared<WaveClip>(*clip, pFactory, true),
522 false, backup, false);
523}
524
526{
527 return 1;
528}
529
531{
532 return mRightChannel.has_value() ? 2 : 1;
533}
534
536{
537 if (GetTrack().Channels().size() == 1)
539 else if (GetChannelIndex() == 0)
541 else
542 // TODO: more-than-two-channels
544}
545
547{
548 // Not quite meaningful but preserving old behavior
549 return (*Channels().begin())->WaveChannel::GetChannelType();
550}
551
552// Copy the track metadata but not the contents.
553void WaveTrack::Init(const WaveTrack &orig)
554{
556 mpFactory = orig.mpFactory;
557}
558
560{
561}
562
564void WaveTrack::MoveTo(double origin)
565{
566 double delta = origin - GetStartTime();
567 for (const auto &pInterval : Intervals())
568 // assume No-fail-guarantee
569 pInterval->ShiftBy(delta);
570 WaveTrackData::Get(*this).SetOrigin(origin);
571}
572
573auto WaveTrack::DuplicateWithOtherTempo(double newTempo) const -> Holder
574{
575 const auto srcCopy = Duplicate();
576 ::DoProjectTempoChange(*srcCopy, newTempo);
577 return std::static_pointer_cast<WaveTrack>(srcCopy);
578}
579
580bool WaveTrack::LinkConsistencyFix(const bool doFix)
581{
582 // This implies satisfaction of the precondition of SetRate()
583 assert(!doFix || IsLeader());
584
585 const auto removeZeroClips = [](WaveClipHolders& clips) {
586 // Check for zero-length clips and remove them
587 for (auto it = clips.begin(); it != clips.end();)
588 {
589 if ((*it)->IsEmpty())
590 it = clips.erase(it);
591 else
592 ++it;
593 }
594 };
595
597
598 auto linkType = GetLinkType();
599 if (linkType != LinkType::None) {
600 auto next = *TrackList::Channels(this).first.advance(1);
601 if (next == nullptr) {
602 //next track is absent or not a wave track, fix and report error
603 if (doFix) {
604 wxLogWarning(L"Right track %s is expected to be a WaveTrack."
605 "\n Removing link from left wave track %s.",
606 next->GetName(), GetName());
608 }
609 err = true;
610 }
611 else if (doFix) {
612 // non-error upgrades happen here
613 if (!AreAligned(SortedClipArray(), next->SortedClipArray()) ||
615 {
616 SetLinkType(linkType = LinkType::None);
617 }
618 else
619 {
620 SetLinkType(linkType = LinkType::Aligned);
621 //clean up zero clips only after alignment check has completed
622 //this can't break alignment as there should be a "twin"
623 //in the right channel which will also be removed, otherwise
624 //track will be unlinked because AreAligned returned false
625 removeZeroClips(NarrowClips());
626 removeZeroClips(next->NarrowClips());
627 }
628 }
629 }
630 if (doFix) {
631 // More non-error upgrading
632 // Set the common channel group rate from the unzipped leader's rate
633 if (mLegacyRate > 0)
634 {
635 WaveTrack *next{};
636 if (linkType != LinkType::None)
637 next = *TrackList::Channels(this).first.advance(1);
639 mLegacyRate = 0;
640 if (next)
641 next->mLegacyRate = 0;
643 WaveTrackData::Get(*this).SetSampleFormat(mLegacyFormat);
644 if (next && next->mLegacyFormat != undefinedSample)
645 WaveTrackData::Get(*next).SetSampleFormat(mLegacyFormat);
646 }
647 if (linkType == LinkType::None)
648 // Did not visit the other call to removeZeroClips, do it now
649 removeZeroClips(NarrowClips());
650 else
651 // Make a real wide wave track from two deserialized narrow tracks
652 ZipClips();
653 }
654 return !err;
655}
656
658{
659 static const Track::TypeInfo info{
660 { "wave", "wave", XO("Wave Track") },
662 return info;
663}
664
665auto WaveTrack::GetTypeInfo() const -> const TypeInfo &
666{
667 return typeInfo();
668}
669
671{
672 return typeInfo();
673}
674
676 AudacityProject &project, TrackList &list) const
677{
678 auto &trackFactory = WaveTrackFactory::Get(project);
679 auto &pSampleBlockFactory = trackFactory.GetSampleBlockFactory();
680 auto pFirstTrack = EmptyCopy(pSampleBlockFactory);
681 list.Add(pFirstTrack->SharedPointer());
682 pFirstTrack->Paste(0.0, *this);
683 return pFirstTrack->SharedPointer();
684}
685
687{
688 return NarrowClips().size();
689}
690
691auto WaveTrack::GetClip(size_t iInterval) -> IntervalHolder
692{
693 return std::static_pointer_cast<Interval>(DoGetInterval(iInterval));
694}
695
696auto WaveTrack::GetClip(size_t iInterval) const -> IntervalConstHolder
697{
698 return const_cast<WaveTrack&>(*this).GetClip(iInterval);
699}
700
701std::shared_ptr<WideChannelGroupInterval>
703{
704 if (iInterval < NIntervals())
705 return mClips[iInterval];
706 return {};
707}
708
709bool WaveTrack::HasClipNamed(const wxString& name) const
710{
711 auto clips = Intervals();
712 return std::any_of(clips.begin(), clips.end(),
713 [&](const auto &pClip){ return pClip->GetName() == name; });
714}
715
716std::shared_ptr<::Channel> WaveTrack::DoGetChannel(size_t iChannel)
717{
718 auto nChannels = NChannels();
719 if (iChannel >= nChannels)
720 return {};
721 // TODO: more-than-two-channels
722 ::Channel &aliased = (iChannel == 0)
723 ? mChannel
724 : *mRightChannel;
725 // Use aliasing constructor of std::shared_ptr
726 return { shared_from_this(), &aliased };
727}
728
730{
731 return mOwner;
732}
733
734std::shared_ptr<WaveClipChannel>
735WaveChannel::GetInterval(size_t iInterval) { return
736 ::Channel::GetInterval<WaveClipChannel>(iInterval); }
737
738std::shared_ptr<const WaveClipChannel>
739WaveChannel::GetInterval(size_t iInterval) const { return
740 ::Channel::GetInterval<const WaveClipChannel>(iInterval); }
741
743WaveChannel::Intervals() { return ::Channel::Intervals<WaveClipChannel>(); }
744
747 return ::Channel::Intervals<const WaveClipChannel>(); }
748
750{
751 return mClips;
752}
753
755{
756 return mClips;
757}
758
760{
761 auto newTrack = EmptyCopy(NChannels());
762 if(backup)
763 {
764 //Init-time rate and format should be preserved as initialization
765 //phase is not yet completed for that track.
766 newTrack->mLegacyFormat = mLegacyFormat;
767 newTrack->mLegacyRate = mLegacyRate;
768 }
769 newTrack->CopyClips(newTrack->mClips,
770 newTrack->mpFactory, this->mClips, backup);
771 return newTrack;
772}
773
774wxString WaveTrack::MakeClipCopyName(const wxString& originalName) const
775{
776 auto name = originalName;
777 for (auto i = 1;; ++i)
778 {
779 if (!HasClipNamed(name))
780 return name;
781 //i18n-hint Template for clip name generation on copy-paste
782 name = XC("%s.%i", "clip name template").Format(originalName, i).Translation();
783 }
784}
785
787{
788 auto name = GetName();
789 for (auto i = 1;; ++i)
790 {
791 if (!HasClipNamed(name))
792 return name;
793 //i18n-hint Template for clip name generation on inserting new empty clip
794 name = XC("%s %i", "clip name template").Format(GetName(), i).Translation();
795 }
796}
797
799{
800 return GetTrack().GetRate();
801}
802
803double WaveTrack::GetRate() const
804{
805 return WaveTrackData::Get(*this).GetRate();
806}
807
808void WaveTrack::SetRate(double newRate)
809{
810 assert(newRate > 0);
811 newRate = std::max( 1.0, newRate );
812 DoSetRate(newRate);
813
814 for (const auto &clip : Intervals())
815 clip->SetRate(newRate);
816}
817
818void WaveTrack::DoSetRate(double newRate)
819{
820 auto &data = WaveTrackData::Get(*this);
821 data.SetRate(static_cast<int>(newRate));
822}
823
825{
826 return WaveTrackData::Get(*this).GetGain();
827}
828
829void WaveTrack::DoSetGain(float value)
830{
831 WaveTrackData::Get(*this).SetGain(value);
832}
833
834void WaveTrack::SetGain(float newGain)
835{
836 if (GetGain() != newGain) {
837 DoSetGain(newGain);
838 Notify(true);
839 }
840}
841
842float WaveTrack::GetPan() const
843{
844 return WaveTrackData::Get(*this).GetPan();
845}
846
847void WaveTrack::DoSetPan(float value)
848{
849 WaveTrackData::Get(*this).SetPan(value);
850}
851
852void WaveTrack::SetPan(float newPan)
853{
854 if (newPan > 1.0)
855 newPan = 1.0;
856 else if (newPan < -1.0)
857 newPan = -1.0;
858
859 if ( GetPan() != newPan ) {
860 DoSetPan(newPan);
861 Notify(true);
862 }
863}
864
865float WaveChannel::GetChannelGain(int channel) const
866{
867 return GetTrack().GetChannelGain(channel);
868}
869
870float WaveTrack::GetChannelGain(int channel) const
871{
872 // channel is not necessarily less than the channel group width but
873 // a mono track might pan differently according to that
874 float left = 1.0;
875 float right = 1.0;
876
877 const auto pan = GetPan();
878 if (pan < 0)
879 right = (pan + 1.0);
880 else if (pan > 0)
881 left = 1.0 - pan;
882
883 const auto gain = GetGain();
884 if ((channel % 2) == 0)
885 return left * gain;
886 else
887 return right * gain;
888}
889
891{
892 sampleCount result{ 0 };
893
894 for (const auto& clip : Intervals())
895 result += clip->GetVisibleSampleCount();
896
897 return result;
898}
899
901{
902 return WaveTrackData::Get(*this).GetSampleFormat();
903}
904
907 const std::function<void(size_t)> & progressReport)
908{
909 for (const auto &pClip : Intervals())
910 pClip->ConvertToSampleFormat(format, progressReport);
911 WaveTrackData::Get(*this).SetSampleFormat(format);
912}
913
914
915bool WaveTrack::IsEmpty(double t0, double t1) const
916{
917 if (t0 > t1)
918 return true;
919
920 //wxPrintf("Searching for overlap in %.6f...%.6f\n", t0, t1);
921 for (const auto &clip : Intervals())
922 {
923 if (clip->IntersectsPlayRegion(t0, t1)) {
924 //wxPrintf("Overlapping clip: %.6f...%.6f\n",
925 // clip->GetStartTime(),
926 // clip->GetEndTime());
927 // We found a clip that overlaps this region
928 return false;
929 }
930 }
931 //wxPrintf("No overlap found\n");
932
933 // Otherwise, no clips overlap this region
934 return true;
935}
936
937Track::Holder WaveTrack::Cut(double t0, double t1)
938{
939 if (t1 < t0)
941
942 auto result = Copy(t0, t1);
943 Clear(t0, t1);
944 return result;
945}
946
948auto WaveTrack::SplitCut(double t0, double t1) -> Holder
949{
950 if (t1 < t0)
952
953 // SplitCut is the same as 'Copy', then 'SplitDelete'
954 auto result = Copy(t0, t1);
955 SplitDelete(t0, t1);
956 return std::static_pointer_cast<WaveTrack>(result);
957}
958
959//Trim trims within a clip, rather than trimming everything.
960//If a bound is outside a clip, it trims everything.
962void WaveTrack::Trim(double t0, double t1)
963{
964 bool inside0 = false;
965 bool inside1 = false;
966
967 for (const auto &clip : Intervals()) {
968 if (t1 > clip->GetPlayStartTime() && t1 < clip->GetPlayEndTime()) {
969 clip->SetTrimRight(
970 clip->GetTrimRight() + clip->GetPlayEndTime() - t1);
971 inside1 = true;
972 }
973
974 if (t0 > clip->GetPlayStartTime() && t0 < clip->GetPlayEndTime()) {
975 clip->SetTrimLeft(
976 clip->GetTrimLeft() + t0 - clip->GetPlayStartTime());
977 inside0 = true;
978 }
979 }
980
981 //if inside0 is false, then the left selector was between
982 //clips, so DELETE everything to its left.
983 if (const auto endTime = GetEndTime()
984 ; !inside1 && t1 < endTime
985 )
986 Clear(t1, endTime);
987
988 if (const auto startTime = GetStartTime()
989 ; !inside0 && t0 > startTime
990 )
991 SplitDelete(startTime, t0);
992}
993
995 const SampleBlockFactoryPtr &pFactory) const
996{
997 const auto rate = GetRate();
998 auto result = std::make_shared<WaveTrack>(CreateToken{},
999 pFactory, GetSampleFormat(), rate);
1000 if (nChannels > 1)
1001 result->CreateRight();
1002 result->Init(*this);
1003 // Copy state rather than BuildAll()
1004 Track::CopyAttachments(*result, *this, true /* deep copy */);
1005 // The previous line might have destroyed the rate information stored in
1006 // channel group data. The copy is not yet in a TrackList. Reassign rate
1007 // in case the track needs to make WaveClips before it is properly joined
1008 // with the opposite channel in a TrackList.
1009 result->DoSetRate(rate);
1010 result->mpFactory = pFactory ? pFactory : mpFactory;
1011 WaveTrackData::Get(*result).SetOrigin(0);
1012 return result;
1013}
1014
1016 const SampleBlockFactoryPtr &pFactory) const -> Holder
1017{
1018 return EmptyCopy(NChannels(), pFactory);
1019}
1020
1022{
1023 mRightChannel.reset();
1024 for (auto &pClip : mClips)
1025 pClip->DiscardRightChannel();
1027}
1028
1030{
1031 assert(!GetOwner());
1032 MakeMono();
1033
1034 // Make temporary new mono track
1035 auto newTrack = Duplicate();
1036
1037 // Make a list
1038 auto result = TrackList::Temporary(nullptr, shared_from_this());
1039 result->Add(newTrack);
1040 // Destroy the temporary track, widening this track to stereo
1041 ZipClips();
1042
1043 return std::static_pointer_cast<WaveTrack>(result->DetachFirst());
1044}
1045
1046auto WaveTrack::SplitChannels() -> std::vector<Holder>
1047{
1048 std::vector<Holder> result{ SharedPointer<WaveTrack>() };
1049 if (NChannels() == 2) {
1050 auto pOwner = GetOwner();
1051 assert(pOwner); // pre
1052 auto pNewTrack = result.emplace_back(EmptyCopy(1));
1053 for (auto &pClip : mClips)
1054 pNewTrack->mClips.emplace_back(pClip->SplitChannels());
1055 this->mRightChannel.reset();
1056 auto iter = pOwner->Find(this);
1057 pOwner->Insert(*++iter, pNewTrack);
1058 // Fix up the channel attachments to avoid waste of space
1059 result[0]->EraseChannelAttachments(1);
1060 result[1]->EraseChannelAttachments(0);
1061 }
1062 return result;
1063}
1064
1066{
1067 assert(NChannels() == 2);
1068 for (const auto &pClip: mClips)
1069 pClip->SwapChannels();
1070 this->AttachedTrackObjects::ForEach([this](TrackAttachment &attachment){
1071 if (const auto pAttachments =
1072 dynamic_cast<ChannelAttachmentsBase *>(&attachment)) {
1073 pAttachments->SwapChannels(shared_from_this());
1074 }
1075 });
1076}
1077
1078Track::Holder WaveTrack::Copy(double t0, double t1, bool forClipboard) const
1079{
1080 if (t1 < t0)
1082
1083 auto newTrack = EmptyCopy(NChannels());
1084 const auto endTime = std::max(GetEndTime(), t1);
1085 for (const auto pClip : Intervals()) {
1086 // PRL: Why shouldn't cutlines be copied and pasted too? I don't know,
1087 // but that was the old behavior. But this function is also used by the
1088 // Duplicate command and I changed its behavior in that case.
1089 if (pClip->IsEmpty())
1090 continue;
1091 else if (t0 <= pClip->GetPlayStartTime() && t1 >= pClip->GetPlayEndTime())
1092 {
1093 newTrack->CopyWholeClip(*pClip, t0, forClipboard);
1094 }
1095 else if (pClip->CountSamples(t0, t1) >= 1) {
1096 newTrack->CopyPartOfClip(*pClip, t0, t1, forClipboard);
1097 }
1098 }
1099 newTrack->FinishCopy(t0, t1, endTime, forClipboard);
1100 return newTrack;
1101}
1102
1104 double t0, bool forClipboard)
1105{
1106 const auto &pFactory = GetSampleBlockFactory();
1107 const auto newClip =
1108 std::make_shared<Interval>(clip, pFactory, !forClipboard);
1109 InsertInterval(newClip, false, false);
1110 newClip->ShiftBy(-t0);
1111}
1112
1114 double t0, double t1, bool forClipboard)
1115{
1116 const auto &pFactory = GetSampleBlockFactory();
1117 auto newClip = std::make_shared<Interval>(
1118 clip, pFactory, !forClipboard, t0, t1);
1119 newClip->SetName(clip.GetName());
1120 newClip->ShiftBy(-t0);
1121 if (newClip->GetPlayStartTime() < 0)
1122 newClip->SetPlayStartTime(0);
1123 InsertInterval(std::move(newClip), false, false);
1124}
1125
1127 double t0, double t1, double endTime, bool forClipboard)
1128{
1129 // AWD, Oct 2009: If the selection ends in whitespace, create a
1130 // placeholder clip representing that whitespace
1131 // PRL: Only if we want the track for pasting into other tracks. Not if
1132 // it goes directly into a project as in the Duplicate command.
1133 if (forClipboard && endTime + 1.0 / GetRate() < t1 - t0) {
1134 auto placeholder = CreateClip();
1135 placeholder->SetIsPlaceholder(true);
1136 placeholder->InsertSilence(0, (t1 - t0) - GetEndTime());
1137 placeholder->ShiftBy(GetEndTime());
1138 InsertInterval(move(placeholder), true, false);
1139 }
1140}
1141
1143void WaveTrack::Clear(double t0, double t1)
1144{
1145 HandleClear(t0, t1, false, false);
1146}
1147
1149void WaveTrack::ClearAndAddCutLine(double t0, double t1)
1150{
1151 HandleClear(t0, t1, true, false);
1152}
1153
1154namespace {
1155
1156 //Internal structure, which is supposed to contain
1157 //data related to clip boundaries, and used during
1158 //ClearAndPaste to restore old splits after new
1159 //data is pasted
1161 {
1162 //Time, where boundary is located
1163 double time;
1164 //Contains trimmed data, which should be re-appended to
1165 //the clip to the left from the boundary, may be null
1167 //Contains trimmed data, which should be re-appended to
1168 //the clip to the right from the boundary, may be null
1170 //Contains clip name next to the left from the boundary,
1171 //if present, that needs to be re-assigned to the matching
1172 //clip after split
1173 std::optional<wxString> leftClipName;
1174 //Contains clip name next to the right from the boundary,
1175 //if present, that needs to be re-assigned to the matching
1176 //clip after split
1177 std::optional<wxString> rightClipName;
1178 };
1179
1180}
1181
1182//
1183// ClearAndPaste() is a specialized version of HandleClear()
1184// followed by Paste() and is used mostly by effects that
1185// can't replace track data directly using Get()/Set().
1186//
1187// HandleClear() removes any cut/split lines with the
1188// cleared range, but, in most cases, effects want to preserve
1189// the existing cut/split lines, trimmed data and clip names,
1190// so they are saved before the HandleClear()/Paste() and restored after.
1191// When pasted track has split lines with hidden data at same places
1192// as the target one, then only targets hidden data is preserved, and
1193// hidden data from pasted track is discarded.
1194//
1195// If the pasted track overlaps two or more clips, then it will
1196// be pasted with visible split lines. Normally, effects do not
1197// want these extra lines, so they may be merged out.
1198//
1202 double t0, // Start of time to clear
1203 double t1, // End of time to clear
1204 const WaveTrack& src, // What to paste
1205 bool preserve, // Whether to reinsert splits/cuts
1206 bool merge, // Whether to remove 'extra' splits
1207 const TimeWarper* effectWarper, // How does time change
1208 bool clearByTrimming)
1209{
1210 // Get a modifiable copy of `src` because it may come from another project
1211 // with different tempo, making boundary queries incorrect.
1212 const auto& tempo = GetProjectTempo(*this);
1213 if (!tempo.has_value())
1215 const auto copyHolder = src.DuplicateWithOtherTempo(*tempo);
1217 t0, t1, *copyHolder, preserve, merge, effectWarper, clearByTrimming);
1218}
1219
1221 double t0, double t1, const WaveTrack& src, bool preserve, bool merge,
1222 const TimeWarper* effectWarper, bool clearByTrimming)
1223{
1224 const auto srcNChannels = src.NChannels();
1225 assert(srcNChannels == NChannels());
1226 assert(
1227 GetProjectTempo(*this).has_value() &&
1228 GetProjectTempo(*this) == GetProjectTempo(src));
1229
1230 t0 = SnapToSample(t0);
1231 t1 = SnapToSample(t1);
1232
1233 const auto endTime = src.GetEndTime();
1234 double dur = std::min(t1 - t0, endTime);
1235
1236 // If duration is 0, then it's just a plain paste
1237 if (dur == 0.0) {
1238 // use Weak-guarantee
1239 PasteWaveTrack(t0, src, merge);
1240 return;
1241 }
1242
1243 auto &track = *this;
1244
1245 std::vector<SplitInfo> splits;
1246 IntervalHolders cuts;
1247
1248 //helper routine, that finds SplitInfo by time value,
1249 //or creates a new one if no one exists yet
1250 auto get_split = [&](double time) {
1251 auto it = std::find_if(splits.begin(), splits.end(),
1252 [time](const SplitInfo& split) { return split.time == time; });
1253 if(it == splits.end())
1254 it = splits.insert(
1255 splits.end(),
1256 { time, nullptr, nullptr, std::nullopt, std::nullopt }
1257 );
1258 return it;
1259 };
1260
1261 // If provided time warper was NULL, use a default one that does nothing
1262 IdentityTimeWarper localWarper;
1263 const TimeWarper *warper = (effectWarper ? effectWarper : &localWarper);
1264
1265 const auto roundTime = [&track](double t){
1266 return track.SnapToSample(t);
1267 };
1268
1269 // Align to a sample
1270 t0 = roundTime(t0);
1271 t1 = roundTime(t1);
1272
1273 // Save the cut/split lines whether preserving or not since merging
1274 // needs to know if a clip boundary is being crossed since Paste()
1275 // will add split lines around the pasted clip if so.
1276 for (const auto &&clip : track.Intervals()) {
1277 double st;
1278
1279 // Remember clip boundaries as locations to split
1280 // we need to copy clips, trims and names, because the original ones
1281 // could be changed later during Clear/Paste routines
1282 st = roundTime(clip->GetPlayStartTime());
1283 if (st >= t0 && st <= t1) {
1284 auto it = get_split(st);
1285 if (clip->GetTrimLeft() != 0) {
1286 //keep only hidden left part
1287 it->right = track.CopyClip(*clip, false);
1288 it->right->SetTrimLeft(.0);
1289 it->right->ClearRight(clip->GetPlayStartTime());
1290 }
1291 it->rightClipName = clip->GetName();
1292 }
1293
1294 st = roundTime(clip->GetPlayEndTime());
1295 if (st >= t0 && st <= t1) {
1296 auto it = get_split(st);
1297 if (clip->GetTrimRight() != 0) {
1298 //keep only hidden right part
1299 it->left = track.CopyClip(*clip, false);
1300 it->left->SetTrimRight(.0);
1301 it->left->ClearLeft(clip->GetPlayEndTime());
1302 }
1303 it->leftClipName = clip->GetName();
1304 }
1305
1306 // Search for cut lines
1307 auto cutlines = clip->GetCutLines();
1308 for (auto &cut : cutlines) {
1309 const auto unrounded =
1310 clip->GetSequenceStartTime() + cut->GetSequenceStartTime();
1311 const double cs = roundTime(unrounded);
1312
1313 // Remember cut point
1314 if (cs >= t0 && cs <= t1) {
1315 // Remember the absolute offset and add to our cuts array.
1316 cut->SetSequenceStartTime(cs);
1317 bool removed = clip->RemoveCutLine(unrounded);
1318 assert(removed);
1319 cuts.push_back(move(cut));
1320 }
1321 }
1322 }
1323
1324 const auto tolerance = 2.0 / track.GetRate();
1325
1326 // This is not a split-cut operation.
1327 constexpr auto split = false;
1328
1329 // Now, clear the selection
1330 track.HandleClear(t0, t1, false, split, clearByTrimming);
1331
1332 // And paste in the new data
1333 track.PasteWaveTrackAtSameTempo(t0, src, merge);
1334
1335 // First, merge the new clip(s) in with the existing clips
1336 if (merge && splits.size() > 0) {
1337 {
1338 // Now t1 represents the absolute end of the pasted data.
1339 t1 = t0 + endTime;
1340
1341 // Get a sorted array of the clips
1342 auto clips = track.SortedIntervalArray();
1343
1344 // Scan the sorted clips for the first clip whose start time
1345 // exceeds the pasted regions end time.
1346 IntervalHolder prev;
1347 for (const auto clip : clips) {
1348 // Merge this clip and the previous clip if the end time
1349 // falls within it and this isn't the first clip in the track.
1350 if (fabs(t1 - clip->GetPlayStartTime()) < tolerance) {
1351 if (prev && clip->HasEqualPitchAndSpeed(*prev))
1352 track.MergeClips(
1353 track.GetClipIndex(*prev), track.GetClipIndex(*clip));
1354 break;
1355 }
1356 prev = clip;
1357 }
1358 }
1359
1360 {
1361 // Refill the array since clips have changed.
1362 auto clips = track.SortedIntervalArray();
1363
1364 // Scan the sorted clips to look for the start of the pasted
1365 // region.
1366 IntervalHolder prev;
1367 for (const auto clip : clips) {
1368 if (prev) {
1369 // It must be that clip is what was pasted and it begins where
1370 // prev ends.
1371 // use Weak-guarantee
1372 if (clip->HasEqualPitchAndSpeed(*prev))
1373 track.MergeClips(
1374 track.GetClipIndex(*prev), track.GetClipIndex(*clip));
1375 break;
1376 }
1377 if (fabs(t0 - clip->GetPlayEndTime()) < tolerance)
1378 // Merge this clip and the next clip if the start time
1379 // falls within it and this isn't the last clip in the track.
1380 prev = clip;
1381 else
1382 prev = nullptr;
1383 }
1384 }
1385 }
1386
1387 // Restore cut/split lines
1388 if (preserve) {
1389 auto attachLeft = [](Interval& target, Interval& src) {
1390 // What this lambda does is restoring the left hidden data of `target`
1391 // that was cleared by `HandleClear`. Hence, `target` has no left
1392 // hidden data at this stage.
1393 assert(target.GetTrimLeft() == 0);
1394 if (target.GetTrimLeft() != 0)
1395 return;
1396
1397 // `src` was created by copy from `target`, so they have equal width,
1398 // pitch and speed.
1399 assert(target.NChannels() == src.NChannels());
1400 assert(target.HasEqualPitchAndSpeed(src));
1401
1402 auto trim = src.GetPlayEndTime() - src.GetPlayStartTime();
1403 auto success = target.Paste(target.GetPlayStartTime(), src);
1404 assert(success); // because of precondition above
1405 target.SetTrimLeft(trim);
1406 //Play start time needs to be adjusted after
1407 //prepending data to the sequence
1408 target.ShiftBy(-trim);
1409 };
1410
1411 auto attachRight = [](Interval &target, Interval &src)
1412 {
1413 // See `attachLeft` for rationale behind these asserts.
1414 assert(target.GetTrimRight() == 0);
1415 if (target.GetTrimRight() != 0)
1416 return;
1417 assert(target.NChannels() == src.NChannels());
1418 assert(target.HasEqualPitchAndSpeed(src));
1419
1420 auto trim = src.GetPlayEndTime() - src.GetPlayStartTime();
1421 auto success = target.Paste(target.GetPlayEndTime(), src);
1422 assert(success); // because of precondition above
1423 target.SetTrimRight(trim);
1424 };
1425
1426 // Restore the split lines and trims, transforming the position appropriately
1427 for (const auto& split: splits) {
1428 auto at = roundTime(warper->Warp(split.time));
1429 for (const auto &&clip : track.Intervals()) {
1430 // Clips in split began as copies of a clip in the track,
1431 // therefore have the same width, satisfying preconditions to
1432 // attach
1433 if (clip->SplitsPlayRegion(at))//strictly inside
1434 {
1435 auto newClip = CopyClip(*clip, true);
1436 clip->ClearRight(at);
1437 newClip->ClearLeft(at);
1438 if (split.left)
1439 // clip was cleared right
1440 attachRight(*clip, *split.left);
1441 if (split.right)
1442 // new clip was cleared left
1443 attachLeft(*newClip, *split.right);
1444 track.InsertInterval(move(newClip), false);
1445 break;
1446 }
1447 else if (clip->GetPlayStartSample() ==
1448 track.TimeToLongSamples(at) && split.right) {
1449 // Satisfy the precondition of attachLeft first!
1450 const auto trim = clip->GetTrimLeft();
1451 const auto seqStartTime = clip->GetSequenceStartTime();
1452 clip->Clear(seqStartTime, seqStartTime + trim);
1453 // This clearing, although only removing the hidden part, moved
1454 // the clip leftwards. We don't want this in this case.
1455 clip->ShiftBy(trim);
1456 attachLeft(*clip, *split.right);
1457 break;
1458 }
1459 else if (clip->GetPlayEndSample() ==
1460 track.TimeToLongSamples(at) && split.left) {
1461 // Satisfy the precondition of attachRight first!
1462 clip->Clear(
1463 clip->GetPlayEndTime(), clip->GetSequenceEndTime());
1464 attachRight(*clip, *split.left);
1465 break;
1466 }
1467 }
1468 }
1469
1470 //Restore clip names
1471 for (const auto& split : splits)
1472 {
1473 auto s = track.TimeToLongSamples(warper->Warp(split.time));
1474 for (auto &&clip : track.Intervals()) {
1475 if (split.rightClipName.has_value() && clip->GetPlayStartSample() == s)
1476 clip->SetName(*split.rightClipName);
1477 else if (split.leftClipName.has_value() && clip->GetPlayEndSample() == s)
1478 clip->SetName(*split.leftClipName);
1479 }
1480 }
1481
1482 // Restore the saved cut lines, also transforming if time altered
1483 for (const auto &&clip : track.Intervals()) {
1484 const double st = clip->GetPlayStartTime();
1485 const double et = clip->GetPlayEndTime();
1486
1487 // Scan the cuts for any that live within this clip
1488 for (auto &cut : cuts) {
1489 if (!cut)
1490 continue;
1491
1492 //cutlines in this array were orphaned previously
1493 double cs = cut->GetSequenceStartTime();
1494
1495 // Offset the cut from the start of the clip and add it to
1496 // this clips cutlines.
1497 if (cs >= st && cs <= et) {
1498 cut->SetSequenceStartTime(warper->Warp(cs) - st);
1499 clip->AddCutLine(cut);
1500 cut = {};
1501 }
1502 }
1503 }
1504 }
1505}
1506
1508void WaveTrack::SplitDelete(double t0, double t1)
1509{
1510 constexpr bool addCutLines = false;
1511 constexpr bool split = true;
1512 HandleClear(t0, t1, addCutLines, split);
1513}
1514
1515std::ptrdiff_t WaveTrack::FindClip(const Interval &clip)
1516{
1517 auto clips = Intervals();
1518 const auto begin = clips.begin();
1519 const auto pred = [&](auto pClip){ return pClip.get() == &clip; };
1520 auto iter = std::find_if(begin, clips.end(), pred);
1521 return std::distance(begin, iter);
1522}
1523
1524void WaveTrack::RemoveClip(std::ptrdiff_t distance)
1525{
1526 auto &clips = NarrowClips();
1527 if (distance < clips.size())
1528 clips.erase(clips.begin() + distance);
1529}
1530
1532void WaveTrack::HandleClear(double t0, double t1, bool addCutLines,
1533 const bool split, const bool clearByTrimming)
1534{
1535 // For debugging, use an ASSERT so that we stop
1536 // closer to the problem.
1537 wxASSERT( t1 >= t0 );
1538 if (t1 < t0)
1540
1541 t0 = SnapToSample(t0);
1542 t1 = SnapToSample(t1);
1543
1544 IntervalHolders clipsToDelete;
1545 IntervalHolders clipsToAdd;
1546
1547 // We only add cut lines when deleting in the middle of a single clip
1548 // The cut line code is not really prepared to handle other situations
1549 if (addCutLines)
1550 for (const auto &clip : Intervals())
1551 if (clip->PartlyWithinPlayRegion(t0, t1)) {
1552 addCutLines = false;
1553 break;
1554 }
1555
1556 for (const auto &clip : Intervals()) {
1557 if (clip->CoversEntirePlayRegion(t0, t1))
1558 // Whole clip must be deleted - remember this
1559 clipsToDelete.push_back(clip);
1560 else if (clip->IntersectsPlayRegion(t0, t1)) {
1561 // Clip data is affected by command
1562 if (addCutLines) {
1563 // Don't modify this clip in place, because we want a strong
1564 // guarantee, and might modify another clip
1565 clipsToDelete.push_back(clip);
1566 auto newClip = CopyClip(*clip, true);
1567 newClip->ClearAndAddCutLine(t0, t1);
1568 clipsToAdd.push_back(move(newClip));
1569 }
1570 else {
1571 if (split || clearByTrimming) {
1572 // Three cases:
1573
1574 if (clip->BeforePlayRegion(t0)) {
1575 // Delete from the left edge
1576
1577 // Don't modify this clip in place, because we want a strong
1578 // guarantee, and might modify another clip
1579 clipsToDelete.push_back(clip);
1580 auto newClip = CopyClip(*clip, true);
1581 newClip->TrimLeft(t1 - clip->GetPlayStartTime());
1582 if (!split)
1583 // If this is not a split-cut, where things are left in
1584 // place, we need to reposition the clip.
1585 newClip->ShiftBy(t0 - t1);
1586 clipsToAdd.push_back(move(newClip));
1587 }
1588 else if (clip->AfterPlayRegion(t1)) {
1589 // Delete to right edge
1590
1591 // Don't modify this clip in place, because we want a strong
1592 // guarantee, and might modify another clip
1593 clipsToDelete.push_back(clip);
1594 auto newClip = CopyClip(*clip, true);
1595 newClip->TrimRight(clip->GetPlayEndTime() - t0);
1596
1597 clipsToAdd.push_back(move(newClip));
1598 }
1599 else {
1600 // Delete in the middle of the clip...we actually create two
1601 // NEW clips out of the left and right halves...
1602
1603 auto leftClip = CopyClip(*clip, true);
1604 leftClip->TrimRight(clip->GetPlayEndTime() - t0);
1605 clipsToAdd.push_back(move(leftClip));
1606
1607 auto rightClip = CopyClip(*clip, true);
1608 rightClip->TrimLeft(t1 - clip->GetPlayStartTime());
1609 if (!split)
1610 // If this is not a split-cut, where things are left in
1611 // place, we need to reposition the clip.
1612 rightClip->ShiftBy(t0 - t1);
1613 clipsToAdd.push_back(move(rightClip));
1614
1615 clipsToDelete.push_back(clip);
1616 }
1617 }
1618 else {
1619 // (We are not doing a split cut)
1620
1621 // Don't modify this clip in place, because we want a strong
1622 // guarantee, and might modify another clip
1623 clipsToDelete.push_back(clip);
1624 auto newClip = CopyClip(*clip, true);
1625
1626 // clip->Clear keeps points < t0 and >= t1 via Envelope::CollapseRegion
1627 newClip->Clear(t0,t1);
1628
1629 clipsToAdd.push_back(move(newClip));
1630 }
1631 }
1632 }
1633 }
1634
1635 // Only now, change the contents of this track
1636 // use No-fail-guarantee for the rest
1637
1638 for (const auto &clip: clipsToDelete)
1639 RemoveInterval(clip);
1640
1641 const auto moveClipsLeft = !split && GetEditClipsCanMove();
1642 if (moveClipsLeft)
1643 // Clip is "behind" the region -- offset it unless we're splitting
1644 // or we're using the "don't move other clips" mode
1645 for (const auto &clip : Intervals())
1646 if (clip->AtOrBeforePlayRegion(t1))
1647 clip->ShiftBy(-(t1 - t0));
1648
1649 for (auto &clip: clipsToAdd)
1650 InsertInterval(move(clip), false);
1651}
1652
1653void WaveTrack::SyncLockAdjust(double oldT1, double newT1)
1654{
1655 const auto endTime = GetEndTime();
1656 if (newT1 > oldT1 &&
1657 // JKC: This is a rare case where using >= rather than > on a float matters.
1658 // GetEndTime() looks through the clips and may give us EXACTLY the same
1659 // value as T1, when T1 was set to be at the end of one of those clips.
1660 oldT1 >= endTime)
1661 return;
1662 if (newT1 > oldT1) {
1663 // Insert space within the track
1664
1665 // If track is empty at oldT1 insert whitespace; otherwise, silence
1666 if (IsEmpty(oldT1, oldT1)) {
1667 // Check if clips can move
1668 if (EditClipsCanMove.Read()) {
1669 const auto offset = newT1 - oldT1;
1670 const auto rate = GetRate();
1671 for (const auto &clip : Intervals())
1672 if (clip->GetPlayStartTime() > oldT1 - (1.0 / rate))
1673 clip->ShiftBy(offset);
1674 }
1675 return;
1676 }
1677 else {
1678 // AWD: Could just use InsertSilence() on its own here, but it doesn't
1679 // follow EditClipCanMove rules (Paste() does it right)
1680 const auto duration = newT1 - oldT1;
1681 auto tmp = EmptyCopy(mpFactory);
1682 tmp->InsertSilence(0.0, duration);
1683 tmp->Flush();
1684 Paste(oldT1, *tmp);
1685 }
1686 }
1687 else if (newT1 < oldT1)
1688 Clear(newT1, oldT1);
1689}
1690
1691void WaveTrack::PasteWaveTrack(double t0, const WaveTrack& other, bool merge)
1692{
1693 // Get a modifiable copy of `src` because it may come from another project
1694 // with different tempo, making boundary queries incorrect.
1695 const auto& tempo = GetProjectTempo(*this);
1696 if (!tempo.has_value())
1698 const auto copyHolder = other.DuplicateWithOtherTempo(*tempo);
1699 PasteWaveTrackAtSameTempo(t0, *copyHolder, merge);
1700}
1701
1703 double t0, const WaveTrack& other, bool merge)
1704{
1705 const auto otherNChannels = other.NChannels();
1706 assert(otherNChannels == NChannels());
1707 assert(
1708 GetProjectTempo(*this).has_value() &&
1709 GetProjectTempo(*this) == GetProjectTempo(other));
1710 const auto startTime = other.GetStartTime();
1711 const auto endTime = other.GetEndTime();
1712
1713 const auto insertDuration = endTime;
1714 auto &track = *this;
1715 //
1716 // Pasting is a bit complicated, because with the existence of multiclip mode,
1717 // we must guess the behaviour the user wants.
1718 //
1719 // Currently, two modes are implemented:
1720 //
1721 // - If a single clip should be pasted, and it should be pasted inside another
1722 // clip, no NEW clips are generated. The audio is simply inserted.
1723 // This resembles the old (pre-multiclip support) behaviour. However, if
1724 // the clip is pasted outside of any clip, a NEW clip is generated. This is
1725 // the only behaviour which is different to what was done before, but it
1726 // shouldn't confuse users too much.
1727 //
1728 // - If multiple clips should be pasted, or a single clip that does not fill
1729 // the duration of the pasted track, these are always pasted as single
1730 // clips, and the current clip is split, when necessary. This may seem
1731 // strange at first, but it probably is better than trying to auto-merge
1732 // anything. The user can still merge the clips by hand (which should be a
1733 // simple command reachable by a hotkey or single mouse click).
1734 //
1735
1736 if (other.GetNumClips() == 0)
1737 return;
1738
1739 t0 = track.SnapToSample(t0);
1740
1741 //wxPrintf("paste: we have at least one clip\n");
1742
1743 const auto clipAtT0 = track.GetIntervalAtTime(t0);
1744 const auto otherFirstClip = other.GetLeftmostClip();
1745 const auto otherLastClip = other.GetRightmostClip();
1746 const auto pitchAndSpeedMatch =
1747 !clipAtT0 || (clipAtT0->HasEqualPitchAndSpeed(*otherFirstClip) &&
1748 clipAtT0->HasEqualPitchAndSpeed(*otherLastClip));
1749
1750 // `singleClipMode` will try to merge. Only allow this if clips on both ends
1751 // of the selection have equal stretch ratio.
1752 const bool singleClipMode =
1753 other.GetNumClips() == 1 &&
1754 std::abs(startTime) < track.LongSamplesToTime(1) * 0.5 &&
1755 pitchAndSpeedMatch && merge;
1756
1757 const auto rate = track.GetRate();
1758 if (insertDuration != 0 && insertDuration < 1.0 / rate)
1759 // PRL: I added this check to avoid violations of preconditions in other WaveClip and Sequence
1760 // methods, but allow the value 0 so I don't subvert the purpose of commit
1761 // 739422ba70ceb4be0bb1829b6feb0c5401de641e which causes append-recording always to make
1762 // a new clip.
1763 return;
1764
1765 //wxPrintf("Check if we need to make room for the pasted data\n");
1766
1767 auto pastingFromTempTrack = !other.GetOwner();
1768 bool editClipCanMove = GetEditClipsCanMove();
1769
1770 const SimpleMessageBoxException notEnoughSpaceException {
1772 XO("There is not enough room available to paste the selection"),
1773 XO("Warning"), "Error:_Insufficient_space_in_track"
1774 };
1775
1776 // Make room for the pasted data
1777 if (editClipCanMove) {
1778 if (!singleClipMode)
1779 // We need to insert multiple clips, so split the current clip and ...
1780 track.SplitAt(t0);
1781
1782 //else if there is a clip at t0 insert new clip inside it and ...
1783
1784 // ... move everything to the right
1785 for (const auto& clip : track.Intervals())
1786 if (clip->GetPlayStartTime() > t0 - (1.0 / rate))
1787 clip->ShiftBy(insertDuration);
1788 }
1789 else
1790 {
1791 if (!merge)
1792 track.SplitAt(t0);
1793 const auto clipAtT0 = track.GetClipAtTime(t0);
1794 const auto t = clipAtT0 ? clipAtT0->GetPlayEndTime() : t0;
1795 if (!track.IsEmpty(t, t + insertDuration))
1796 throw notEnoughSpaceException;
1797 }
1798
1799 // See if the clipboard data is one clip only and if it should be merged. If
1800 // edit-clip-can-move mode is checked, merging happens only if the pasting
1801 // point splits a clip. If it isn't, merging also happens when the pasting
1802 // point is at the exact beginning of a clip.
1803 if (singleClipMode && merge) {
1804 // Single clip mode
1805 // wxPrintf("paste: checking for single clip mode!\n");
1806
1807 IntervalHolder insideClip{};
1808 for (const auto& clip : track.Intervals()) {
1809 if (editClipCanMove) {
1810 if (clip->SplitsPlayRegion(t0)) {
1811 //wxPrintf("t0=%.6f: inside clip is %.6f ... %.6f\n",
1812 // t0, clip->GetStartTime(), clip->GetEndTime());
1813 insideClip = clip;
1814 break;
1815 }
1816 }
1817 else {
1818 // If clips are immovable we also allow prepending to clips
1819 if (clip->WithinPlayRegion(t0))
1820 {
1821 insideClip = clip;
1822 break;
1823 }
1824 }
1825 }
1826
1827 if (insideClip) {
1828 // Exhibit traditional behaviour
1829 //wxPrintf("paste: traditional behaviour\n");
1830 if (!editClipCanMove) {
1831 // We did not move other clips out of the way already, so
1832 // check if we can paste without having to move other clips
1833 for (const auto& clip : track.Intervals()) {
1834 if (clip->GetPlayStartTime() > insideClip->GetPlayStartTime() &&
1835 insideClip->GetPlayEndTime() + insertDuration >
1836 clip->GetPlayStartTime())
1837 // Strong-guarantee in case of this path
1838 // not that it matters.
1839 throw notEnoughSpaceException;
1840 }
1841 }
1842 if (auto pClip = other.GetClip(0)) {
1843 // This branch only gets executed in `singleClipMode` - we've
1844 // already made sure that stretch ratios are equal, satisfying
1845 // `WaveClip::Paste`'s precondition.
1846 assert(insideClip->GetStretchRatio() == pClip->GetStretchRatio());
1847 // This too should follow from the assertion of the same number
1848 // of channels in the tracks, near the top
1849 assert(insideClip->NChannels() == pClip->NChannels());
1850 bool success = insideClip->Paste(t0, *pClip);
1851 assert(success);
1852 }
1853 return;
1854 }
1855 // Just fall through and exhibit NEW behaviour
1856 }
1857
1858 // Insert NEW clips
1859 //wxPrintf("paste: multi clip mode!\n");
1860
1861 if (!editClipCanMove &&
1862 !track.IsEmpty(t0, t0 + insertDuration - 1.0 / rate))
1863 // Strong-guarantee in case of this path
1864 // not that it matters.
1865 throw notEnoughSpaceException;
1866
1867 for (const auto& clip : other.Intervals()) {
1868 // AWD Oct. 2009: Don't actually paste in placeholder clips
1869 if (!clip->GetIsPlaceholder()) {
1870 const auto name = (pastingFromTempTrack)
1871 //Clips from the tracks which aren't bound to any TrackList are
1872 //considered to be new entities, thus named using "new" name template
1873 ? track.MakeNewClipName()
1874 : track.MakeClipCopyName(clip->GetName());
1875 const auto oldPlayStart = clip->GetPlayStartTime();
1876 const auto newSequenceStart =
1877 (oldPlayStart + t0) - clip->GetTrimLeft();
1878 const auto newClip = CreateClip(newSequenceStart, name, clip.get());
1879 newClip->Resample(rate);
1880 track.InsertInterval(move(newClip), false);
1881 }
1882 }
1883}
1884
1886{
1887 // The channels and all clips in them should have the same sample rate.
1888 std::optional<double> oRate;
1889 auto channels = TrackList::Channels(this);
1890 return std::all_of(channels.begin(), channels.end(),
1891 [&](const WaveTrack *pTrack){
1892 if (!pTrack)
1893 return false;
1894
1895 const auto rate = pTrack->mLegacyRate;
1896 if (!oRate)
1897 oRate = rate;
1898 else if (*oRate != rate)
1899 return false;
1900 return true;
1901 });
1902}
1903
1905{
1906 const auto channels = TrackList::Channels(this);
1907 return std::all_of(channels.begin(), channels.end(),
1908 [&](const WaveTrack *pTrack){
1909 return pTrack && pTrack->mLegacyFormat == mLegacyFormat;
1910 });
1911}
1912
1914 bool newClip, bool backup, bool allowEmpty)
1915{
1916 if (!backup && !clip->GetIsPlaceholder() && !allowEmpty && clip->IsEmpty())
1917 return false;
1918
1919 const auto& tempo = GetProjectTempo(*this);
1920 if (tempo.has_value())
1921 clip->OnProjectTempoChange(std::nullopt, *tempo);
1922 clips.push_back(std::move(clip));
1923 Publish({ clips.back(),
1925
1926 return true;
1927}
1928
1930 std::optional<TimeInterval> interval, ProgressReporter reportProgress)
1931{
1932 // Assert that the interval is reasonable, but this function will be no-op
1933 // anyway if not
1934 assert(!interval.has_value() ||
1935 interval->first <= interval->second);
1936 if (GetNumClips() == 0)
1937 return;
1938 const auto startTime =
1939 interval ? std::max(SnapToSample(interval->first), GetStartTime()) :
1940 GetStartTime();
1941 const auto endTime =
1942 interval ? std::min(SnapToSample(interval->second), GetEndTime()) :
1943 GetEndTime();
1944 if (startTime >= endTime)
1945 return;
1946
1947 // Here we assume that left- and right clips are aligned.
1948 if (auto clipAtT0 = GetClipAtTime(startTime);
1949 clipAtT0 && clipAtT0->SplitsPlayRegion(startTime) &&
1950 clipAtT0->HasPitchOrSpeed())
1951 Split(startTime, startTime);
1952 if (auto clipAtT1 = GetClipAtTime(endTime);
1953 clipAtT1 && clipAtT1->SplitsPlayRegion(endTime) &&
1954 clipAtT1->HasPitchOrSpeed())
1955 Split(endTime, endTime);
1956
1957 IntervalHolders srcIntervals;
1958 auto clip = GetIntervalAtTime(startTime);
1959 while (clip && clip->GetPlayStartTime() < endTime)
1960 {
1961 if (clip->HasPitchOrSpeed())
1962 srcIntervals.push_back(clip);
1964 }
1965
1966 ApplyPitchAndSpeedOnIntervals(srcIntervals, reportProgress);
1967}
1968
1970void WaveTrack::Paste(double t0, const Track &src)
1971{
1972 if (const auto other = dynamic_cast<const WaveTrack*>(&src))
1973 {
1974 // Currently `Paste` isn't used by code that wants the newer "don't merge
1975 // when copy/pasting" behaviour ...
1976 constexpr auto merge = true;
1977 PasteWaveTrack(t0, *other, merge);
1978 }
1979 else
1980 // THROW_INCONSISTENCY_EXCEPTION; // ?
1981 (void)0;// Empty if intentional.
1982}
1983
1984void WaveTrack::Silence(double t0, double t1, ProgressReporter reportProgress)
1985{
1986 if (t1 < t0)
1988
1989 ApplyPitchAndSpeed({ { t0, t1 } }, std::move(reportProgress));
1990
1991 auto start = TimeToLongSamples(t0);
1992 auto end = TimeToLongSamples(t1);
1993
1994 for (const auto &pClip : Intervals()) {
1995 auto clipStart = pClip->GetPlayStartSample();
1996 auto clipEnd = pClip->GetPlayEndSample();
1997 if (clipEnd > start && clipStart < end) {
1998 auto offset = std::max(start - clipStart, sampleCount(0));
1999 // Clip sample region and Get/Put sample region overlap
2000 auto length = std::min(end, clipEnd) - (clipStart + offset);
2001 pClip->SetSilence(offset, length);
2002 }
2003 }
2004}
2005
2007void WaveTrack::InsertSilence(double t, double len)
2008{
2009 // Nothing to do, if length is zero.
2010 // Fixes Bug 1626
2011 if (len == 0)
2012 return;
2013 if (len <= 0)
2015
2016 auto &&clips = Intervals();
2017 if (clips.empty()) {
2018 // Special case if there is no clip yet
2019 auto clip = CreateClip(0);
2020 clip->InsertSilence(0, len);
2021 // use No-fail-guarantee
2022 InsertInterval(move(clip), true);
2023 }
2024 else
2025 {
2026 // Assume at most one clip contains t
2027 const auto end = clips.end();
2028 const auto it = std::find_if(clips.begin(), end,
2029 [&](const IntervalHolder &clip) { return clip->SplitsPlayRegion(t); } );
2030
2031 // use Strong-guarantee
2032 if (it != end)
2033 (*it)->InsertSilence(t, len);
2034
2035 // use No-fail-guarantee
2036 for (const auto &&clip : clips)
2037 if (clip->BeforePlayRegion(t))
2038 clip->ShiftBy(len);
2039 }
2040}
2041
2042//Performs the opposite of Join
2043//Analyses selected region for possible Joined clips and disjoins them
2045void WaveTrack::Disjoin(double t0, double t1)
2046{
2048 const size_t maxAtOnce = 1048576;
2049 std::vector<float> buffer;
2050 std::vector<samplePtr> buffers;
2051 Regions regions;
2052
2053 const size_t width = NChannels();
2054
2055 for (const auto &interval : Intervals()) {
2056 double startTime = interval->Start();
2057 double endTime = interval->End();
2058
2059 if (endTime < t0 || startTime > t1)
2060 continue;
2061
2062 // Assume all clips will have the same width
2063 if (buffer.empty()) {
2064 buffer.resize(maxAtOnce * width);
2065 buffers.resize(width);
2066 auto pBuffer = buffer.data();
2067 for (size_t ii = 0; ii < width; ++ii, pBuffer += maxAtOnce)
2068 buffers[ii] = reinterpret_cast<samplePtr>(pBuffer);
2069 }
2070
2071 const auto allZeroesAt = [&](size_t i) {
2072 auto pData = buffer.data() + i;
2073 for (size_t ii = 0; ii < width; ++ii, pData += maxAtOnce) {
2074 if (*pData != 0.0)
2075 return false;
2076 }
2077 return true;
2078 };
2079
2080 // simply look for a sequence of zeroes (across all channels) and if the
2081 // sequence is longer than the minimum number, split-delete the region
2082
2083 sampleCount seqStart = -1;
2084 auto start = interval->TimeToSamples(std::max(.0, t0 - startTime));
2085 auto end = interval->TimeToSamples(std::min(endTime, t1) - startTime);
2086
2087 auto len = (end - start);
2088 for (decltype(len) done = 0; done < len; done += maxAtOnce) {
2089 auto numSamples = limitSampleBufferSize(maxAtOnce, len - done);
2090
2091 auto bufferIt = buffers.begin();
2092
2093 for (auto channel : interval->Channels())
2094 channel->GetSamples(
2095 *bufferIt++, floatSample, start + done, numSamples);
2096
2097 for (decltype(numSamples) i = 0; i < numSamples; ++i) {
2098 auto curSamplePos = start + done + i;
2099
2100 //start a NEW sequence
2101 if (seqStart == -1 && allZeroesAt(i))
2102 seqStart = curSamplePos;
2103 else if (curSamplePos == end - 1 || !allZeroesAt(i)) {
2104 if (seqStart != -1) {
2105 decltype(end) seqEnd;
2106
2107 //consider the end case, where selection ends in zeroes
2108 if (curSamplePos == end - 1 && allZeroesAt(i))
2109 seqEnd = end;
2110 else
2111 seqEnd = curSamplePos;
2112 if (seqEnd - seqStart + 1 > minSamples) {
2113 regions.push_back(
2114 Region(
2115 startTime + interval->SamplesToTime(seqStart),
2116 startTime + interval->SamplesToTime(seqEnd)
2117 )
2118 );
2119 }
2120 seqStart = -1;
2121 }
2122 }
2123 } // samples
2124 } // blocks
2125 } // finding regions
2126
2127 for (const auto &region : regions)
2128 SplitDelete(region.start, region.end);
2129}
2130
2133 double t0, double t1, const ProgressReporter& reportProgress)
2134{
2135 // Merge all WaveClips overlapping selection into one
2136 const auto &intervals = Intervals();
2137
2138 {
2139 IntervalHolders intervalsToJoin;
2140 for (const auto &interval : intervals)
2141 if (interval->IntersectsPlayRegion(t0, t1))
2142 intervalsToJoin.push_back(interval);
2143 if (intervalsToJoin.size() < 2u)
2144 return;
2145 if (std::any_of(
2146 intervalsToJoin.begin() + 1, intervalsToJoin.end(),
2147 [first = intervalsToJoin[0]](const auto& interval) {
2148 return !first->HasEqualPitchAndSpeed(*interval);
2149 }))
2150 ApplyPitchAndSpeedOnIntervals(intervalsToJoin, reportProgress);
2151 }
2152
2153 IntervalHolders clipsToDelete;
2154 IntervalHolder newClip{};
2155
2156 const auto rate = GetRate();
2157 for (const auto &clip: intervals) {
2158 if (clip->IntersectsPlayRegion(t0, t1)) {
2159 // Put in sorted order
2160 auto it = clipsToDelete.begin(), end = clipsToDelete.end();
2161 for (; it != end; ++it)
2162 if ((*it)->GetPlayStartTime() > clip->GetPlayStartTime())
2163 break;
2164 //wxPrintf("Insert clip %.6f at position %d\n", clip->GetStartTime(), i);
2165 clipsToDelete.insert(it, clip);
2166 }
2167 }
2168
2169 //if there are no clips to delete, nothing to do
2170 if (clipsToDelete.empty())
2171 return;
2172
2173 const auto firstToDelete = clipsToDelete[0].get();
2174 auto t = firstToDelete->GetPlayStartTime();
2175 //preserve left trim data if any
2176 newClip = CreateClip(
2177 firstToDelete->GetSequenceStartTime(),
2178 firstToDelete->GetName());
2179
2180 for (const auto &clip : clipsToDelete) {
2181 // wxPrintf("t=%.6f adding clip (offset %.6f, %.6f ... %.6f)\n",
2182 // t, clip->GetOffset(), clip->GetStartTime(),
2183 // clip->GetEndTime());
2184
2185 if (clip->GetPlayStartTime() - t > (1.0 / rate))
2186 {
2187 double addedSilence = (clip->GetPlayStartTime() - t);
2188 // wxPrintf("Adding %.6f seconds of silence\n");
2189 auto offset = clip->GetPlayStartTime();
2190 auto value = clip->GetEnvelope().GetValue(offset);
2191 newClip->AppendSilence(addedSilence, value);
2192 t += addedSilence;
2193 }
2194
2195 // wxPrintf("Pasting at %.6f\n", t);
2196 bool success = newClip->Paste(t, *clip);
2197 assert(success); // promise of DoCreateClip
2198
2199 t = newClip->GetPlayEndTime();
2200
2201 RemoveClip(FindClip(*clip));
2202 }
2203
2204 InsertInterval(move(newClip), false);
2205}
2206
2211 size_t len, unsigned stride, sampleFormat effectiveFormat)
2212{
2213 const size_t iChannel = GetChannelIndex();
2214 return GetTrack()
2215 .Append(iChannel, buffer, format, len, stride, effectiveFormat);
2216}
2217
2222 size_t len)
2223{
2224 const size_t iChannel = GetChannelIndex();
2225 return GetTrack()
2226 .Append(iChannel, buffer, format, len, 1, widestSampleFormat);
2227}
2228
2234 size_t len, unsigned int stride, sampleFormat effectiveFormat)
2235{
2236 assert(iChannel < NChannels());
2237 auto pTrack = this;
2238 constSamplePtr buffers[]{ buffer };
2239 auto pClip = RightmostOrNewClip();
2240 return pClip->Append(iChannel, 1,
2241 buffers, format, len, stride, effectiveFormat);
2242}
2243
2245{
2246 auto bestBlockSize = GetMaxBlockSize();
2247
2248 for (const auto &clip : Intervals()) {
2249 auto startSample = clip->GetPlayStartSample();
2250 auto endSample = clip->GetPlayEndSample();
2251 if (s >= startSample && s < endSample)
2252 {
2253 // ignore extra channels (this function will soon be removed)
2254 bestBlockSize =
2255 clip->GetBestBlockSize(s - clip->GetSequenceStartSample());
2256 break;
2257 }
2258 }
2259
2260 return bestBlockSize;
2261}
2262
2264{
2265 const auto clips = Intervals();
2266 auto maxblocksize = std::accumulate(clips.begin(), clips.end(), size_t{},
2267 [](size_t acc, auto pClip){
2268 return std::max(acc, pClip->GetMaxBlockSize()); });
2269
2270 if (maxblocksize == 0)
2271 {
2272 // We really need the maximum block size, so create a
2273 // temporary sequence to get it.
2274 maxblocksize =
2276 .GetMaxBlockSize();
2277 }
2278
2279 wxASSERT(maxblocksize > 0);
2280
2281 return maxblocksize;
2282}
2283
2285{
2286 // ignore extra channels (this function will soon be removed)
2287 return (*NewestOrNewClip()->Channels().begin())->GetClip()
2288 .GetSequence(0)->GetIdealBlockSize();
2289}
2290
2291
2292// TODO restore a proper exception safety guarantee; comment below is false
2293// because failure might happen after only one channel is done
2300{
2301 if (NIntervals() == 0)
2302 return;
2303 // After appending, presumably. Do this to the clip that gets appended.
2304 GetRightmostClip()->Flush();
2305}
2306
2308{
2309 for (auto pInterval : Intervals())
2310 pInterval->RepairChannels();
2311}
2312
2314{
2316}
2317
2319{
2320 return this;
2321}
2322
2324{
2325 return PlayableTrack::GetMute();
2326}
2327
2329{
2330 return PlayableTrack::GetSolo();
2331}
2332
2333const char *WaveTrack::WaveTrack_tag = "wavetrack";
2334
2335static constexpr auto Offset_attr = "offset";
2336static constexpr auto Rate_attr = "rate";
2337static constexpr auto Gain_attr = "gain";
2338static constexpr auto Pan_attr = "pan";
2339static constexpr auto Linked_attr = "linked";
2340static constexpr auto SampleFormat_attr = "sampleformat";
2341static constexpr auto Channel_attr = "channel"; // write-only!
2342
2343bool WaveTrack::HandleXMLTag(const std::string_view& tag, const AttributesList &attrs)
2344{
2345 if (tag == WaveTrack_tag) {
2346 double dblValue;
2347 long nValue;
2348
2349 for (const auto& pair : attrs)
2350 {
2351 const auto& attr = pair.first;
2352 const auto& value = pair.second;
2353
2354 if (attr == Rate_attr)
2355 {
2356 // mRate is an int, but "rate" in the project file is a float.
2357 if (!value.TryGet(dblValue) ||
2358 (dblValue < 1.0) || (dblValue > 1000000.0)) // allow a large range to be read
2359 return false;
2360
2361 // Defer the setting of rate until LinkConsistencyFix
2362 mLegacyRate = lrint(dblValue);
2363 }
2364 else if (attr == Offset_attr && value.TryGet(dblValue))
2365 {
2366 // Offset is only relevant for legacy project files. The value
2367 // is cached until the actual WaveClip containing the legacy
2368 // track is created.
2369 mLegacyProjectFileOffset = dblValue;
2370 }
2371 else if (this->WritableSampleTrack::HandleXMLAttribute(attr, value))
2372 {}
2373 else if (this->Track::HandleCommonXMLAttribute(attr, value))
2374 ;
2375 else if (attr == Gain_attr && value.TryGet(dblValue))
2376 DoSetGain(dblValue);
2377 else if (attr == Pan_attr && value.TryGet(dblValue) &&
2378 (dblValue >= -1.0) && (dblValue <= 1.0))
2379 DoSetPan(dblValue);
2380 else if (attr == Linked_attr && value.TryGet(nValue))
2381 SetLinkType(ToLinkType(nValue), false);
2382 else if (attr == SampleFormat_attr && value.TryGet(nValue) &&
2384 {
2385 //Remember sample format until consistency check is performed.
2386 SetLegacyFormat(static_cast<sampleFormat>(nValue));
2387 }
2388 } // while
2389 return true;
2390 }
2391
2392 return false;
2393}
2394
2395void WaveTrack::HandleXMLEndTag(const std::string_view& WXUNUSED(tag))
2396{
2397#if 0
2398 // In case we opened a pre-multiclip project, we need to
2399 // simulate closing the waveclip tag.
2400 NewestOrNewClip()->HandleXMLEndTag(WaveClip::WaveClip_tag);
2401#else
2402 // File compatibility breaks have intervened long since, and the line above
2403 // would now have undesirable side effects
2404#endif
2405}
2406
2407XMLTagHandler *WaveTrack::HandleXMLChild(const std::string_view& tag)
2408{
2409 if (auto pChild = WaveTrackIORegistry::Get().CallObjectAccessor(tag, *this))
2410 // Deserialize any extra attached structures
2411 return pChild;
2412
2413 const auto getClip = [this]() -> WaveClip & {
2414 return (*NewestOrNewClip()->Channels().begin())->GetClip();
2415 };
2416
2417 //
2418 // This is legacy code (1.2 and previous) and is not called for new projects!
2419 if (tag == Sequence::Sequence_tag || tag == "envelope") {
2420 // This is a legacy project, so set the cached offset
2421 NewestOrNewClip()->SetSequenceStartTime(mLegacyProjectFileOffset);
2422
2423 // Legacy project file tracks are imported as one single wave clip
2424 if (tag == Sequence::Sequence_tag)
2425 return getClip().GetSequence(0);
2426 else if (tag == "envelope")
2427 return &getClip().GetEnvelope();
2428 }
2429
2430 // JKC... for 1.1.0, one step better than what we had, but still badly broken.
2431 // If we see a waveblock at this level, we'd better generate a sequence.
2432 if (tag == Sequence::WaveBlock_tag) {
2433 // This is a legacy project, so set the cached offset
2434 NewestOrNewClip()->SetSequenceStartTime(mLegacyProjectFileOffset);
2435 auto pSeq = getClip().GetSequence(0);
2436 return pSeq;
2437 }
2438
2439 // This is for the new file format (post-1.2)
2440 if (tag == WaveClip::WaveClip_tag) {
2441 // Make clips (which don't serialize the rate) consistent with channel rate,
2442 // though the consistency check of channels with each other remains to do.
2443 // Not all `WaveTrackData` fields are properly initialized by now,
2444 // use deserialization helpers.
2445 auto clip = std::make_shared<WaveClip>(1,
2447 const auto xmlHandler = clip.get();
2448 auto &clips = NarrowClips();
2449 clips.push_back(std::move(clip));
2450 Publish({ clips.back(), WaveTrackMessage::Deserialized });
2451 return xmlHandler;
2452 }
2453
2454 return nullptr;
2455}
2456
2457void WaveTrack::WriteXML(XMLWriter &xmlFile) const
2458// may throw
2459{
2460 const auto channels = Channels();
2461 size_t iChannel = 0,
2462 nChannels = channels.size();
2463 for (const auto pChannel : channels)
2464 WriteOneXML(*pChannel, xmlFile, iChannel++, nChannels);
2465}
2466
2467void WaveTrack::WriteOneXML(const WaveChannel &channel, XMLWriter &xmlFile,
2468 size_t iChannel, size_t nChannels)
2469// may throw
2470{
2471 // Track data has always been written using channel-major iteration.
2472 // Do it still this way for compatibility.
2473
2474 // Some values don't vary independently in channels but have been written
2475 // redundantly for each channel. Keep doing this in 3.4 and later in case
2476 // a project is opened in an earlier version.
2477
2478 xmlFile.StartTag(WaveTrack_tag);
2479 auto &track = channel.GetTrack();
2480
2481 // Name, selectedness, etc. are channel group properties
2482 track.Track::WriteCommonXMLAttributes(xmlFile);
2483
2484 // Write the "channel" attribute so earlier versions can interpret stereo
2485 // tracks, but this version doesn't read it
2486 {
2487 enum ChannelType {
2488 LeftChannel = 0,
2489 RightChannel = 1,
2490 MonoChannel = 2
2491 };
2492 const auto channelType = (nChannels == 0)
2493 ? MonoChannel
2494 : (iChannel == 0)
2495 ? LeftChannel
2496 : RightChannel;
2497 xmlFile.WriteAttr(Channel_attr, channelType);
2498 }
2499
2500 // The "linked" flag is used to define the beginning of a channel group
2501 // that isn't mono
2502 const auto linkType = static_cast<int>(
2503 (iChannel == 0) && (nChannels == 2)
2505 : LinkType::None);
2506 xmlFile.WriteAttr(Linked_attr, linkType);
2507
2508 // VS: trying to save tracks that didn't pass all necessary
2509 // initializations on project read from the disk.
2510 const auto useLegacy = track.mLegacyRate != 0;
2511
2512 // More channel group properties written redundantly
2513 track.WritableSampleTrack::WriteXMLAttributes(xmlFile);
2514 xmlFile.WriteAttr(Rate_attr, useLegacy ? track.mLegacyRate : track.GetRate());
2515 xmlFile.WriteAttr(Gain_attr, static_cast<double>(track.GetGain()));
2516 xmlFile.WriteAttr(Pan_attr, static_cast<double>(track.GetPan()));
2517 xmlFile.WriteAttr(SampleFormat_attr, static_cast<long>(useLegacy ? track.mLegacyFormat : track.GetSampleFormat()));
2518
2519 // Other persistent data specified elsewhere;
2520 // NOT written redundantly any more
2521 if (iChannel == 0)
2522 WaveTrackIORegistry::Get().CallWriters(track, xmlFile);
2523
2524 for (const auto &clip : channel.Intervals())
2525 clip->WriteXML(xmlFile);
2526
2527 xmlFile.EndTag(WaveTrack_tag);
2528}
2529
2530std::optional<TranslatableString> WaveTrack::GetErrorOpening() const
2531{
2532 for (const auto &pClip : Intervals()) {
2533 const auto width = pClip->NChannels();
2534 for (size_t ii = 0; ii < width; ++ii)
2535 if (pClip->GetSequence(ii)->GetErrorOpening())
2536 return XO("A track has a corrupted sample sequence.");
2537 }
2538
2539 return {};
2540}
2541
2543 auto clips = Intervals();
2544 if (clips.empty())
2545 return nullptr;
2546 const auto begin = clips.begin(),
2547 iter = std::min_element(begin, clips.end(),
2548 [](const auto& a, const auto b) {
2549 return a->GetPlayStartTime() < b->GetPlayStartTime();
2550 });
2551 return GetClip(std::distance(begin, iter));
2552}
2553
2555 return const_cast<WaveTrack&>(*this).GetLeftmostClip();
2556}
2557
2559 auto clips = Intervals();
2560 if (clips.empty())
2561 return nullptr;
2562 const auto begin = clips.begin(),
2563 iter = std::max_element(begin, clips.end(),
2564 [](const auto& a, const auto b) {
2565 return a->GetPlayEndTime() < b->GetPlayEndTime();
2566 });
2567 return GetClip(std::distance(begin, iter));
2568}
2569
2571 return const_cast<WaveTrack&>(*this).GetRightmostClip();
2572}
2573
2575{
2576 auto clips = Intervals();
2577 return { clips.begin(), clips.end() };
2578}
2579
2581{
2582 return GetTrack().GetStartTime();
2583}
2584
2586{
2588}
2589
2591{
2592 return GetTrack().GetEndTime();
2593}
2594
2596{
2597 return ChannelGroup::GetEndTime();
2598}
2599
2600bool WaveChannel::DoGet(size_t iChannel, size_t nBuffers,
2601 const samplePtr buffers[], sampleFormat format,
2602 sampleCount start, size_t len, bool backwards, fillFormat fill,
2603 bool mayThrow, sampleCount* pNumWithinClips) const
2604{
2605 // These two assertions still remain after the great wide wave track and clip
2606 // refactoring!
2607 assert(iChannel == 0);
2608 assert(nBuffers <= 1);
2609 return GetTrack().DoGet(GetChannelIndex(), std::min<size_t>(nBuffers, 1),
2610 buffers, format, start, len, backwards, fill, mayThrow, pNumWithinClips);
2611}
2612
2613//
2614// Getting/setting samples. The sample counts here are
2615// expressed relative to t=0.0 at the track's sample rate.
2616//
2617
2618bool WaveTrack::DoGet(size_t iChannel, size_t nBuffers,
2619 const samplePtr buffers[], sampleFormat format,
2620 sampleCount start, size_t len, bool backwards, fillFormat fill,
2621 bool mayThrow, sampleCount* pNumWithinClips) const
2622{
2623 const auto nChannels = NChannels();
2624 assert(iChannel + nBuffers <= nChannels); // precondition
2625 return std::all_of(buffers, buffers + nBuffers, [&](samplePtr buffer) {
2626 const auto result = GetOne(mClips, iChannel++,
2627 buffer, format, start, len, backwards, fill, mayThrow,
2628 pNumWithinClips);
2629 return result;
2630 });
2631}
2632
2634 samplePtr buffer, sampleFormat format, sampleCount start, size_t len,
2635 bool backwards, fillFormat fill, bool mayThrow,
2636 sampleCount* pNumWithinClips) const
2637{
2638 if (backwards)
2639 start -= len;
2640 // Simple optimization: When this buffer is completely contained within one clip,
2641 // don't clear anything (because we won't have to). Otherwise, just clear
2642 // everything to be on the safe side.
2643 bool doClear = true;
2644 bool result = true;
2645 sampleCount samplesCopied = 0;
2646 for (const auto &clip: clips)
2647 {
2648 if (start >= clip->GetPlayStartSample() && start+len <= clip->GetPlayEndSample())
2649 {
2650 doClear = false;
2651 break;
2652 }
2653 }
2654 if (doClear)
2655 {
2656 // Usually we fill in empty space with zero
2657 if (fill == FillFormat::fillZero)
2658 ClearSamples(buffer, format, 0, len);
2659 // but we don't have to.
2660 else if (fill == FillFormat::fillTwo)
2661 {
2662 wxASSERT( format==floatSample );
2663 float * pBuffer = (float*)buffer;
2664 for(size_t i=0;i<len;i++)
2665 pBuffer[i]=2.0f;
2666 }
2667 else
2668 {
2669 wxFAIL_MSG(wxT("Invalid fill format"));
2670 }
2671 }
2672
2673 // Iterate the clips. They are not necessarily sorted by time.
2674 for (const auto &clip: clips)
2675 {
2676 auto clipStart = clip->GetPlayStartSample();
2677 auto clipEnd = clip->GetPlayEndSample();
2678
2679 if (clipEnd > start && clipStart < start+len)
2680 {
2681 if (clip->HasPitchOrSpeed())
2682 return false;
2683
2684 // Clip sample region and Get/Put sample region overlap
2685 auto samplesToCopy =
2686 std::min( start+len - clipStart, clip->GetVisibleSampleCount() );
2687 auto startDelta = clipStart - start;
2688 decltype(startDelta) inclipDelta = 0;
2689 if (startDelta < 0)
2690 {
2691 inclipDelta = -startDelta; // make positive value
2692 samplesToCopy -= inclipDelta;
2693 // samplesToCopy is now either len or
2694 // (clipEnd - clipStart) - (start - clipStart)
2695 // == clipEnd - start > 0
2696 // samplesToCopy is not more than len
2697 //
2698 startDelta = 0;
2699 // startDelta is zero
2700 }
2701 else {
2702 // startDelta is nonnegative and less than len
2703 // samplesToCopy is positive and not more than len
2704 }
2705
2706 if (!clip->GetSamples(iChannel,
2707 (samplePtr)(((char*)buffer) +
2708 startDelta.as_size_t() *
2710 format, inclipDelta, samplesToCopy.as_size_t(), mayThrow ))
2711 result = false;
2712 else
2713 samplesCopied += samplesToCopy;
2714 }
2715 }
2716 if( pNumWithinClips )
2717 *pNumWithinClips = samplesCopied;
2718 if (result == true && backwards)
2719 ReverseSamples(buffer, format, 0, len);
2720 return result;
2721}
2722
2724WaveTrack::GetSampleView(double t0, double t1, bool mayThrow) const
2725{
2727 for (const auto& channel : Channels()) {
2728 result.push_back(channel->GetSampleView(t0, t1, mayThrow));
2729 }
2730 return result;
2731}
2732
2734WaveChannel::GetSampleView(double t0, double t1, bool mayThrow) const
2735{
2736 std::vector<std::shared_ptr<const WaveClipChannel>>
2737 intersectingIntervals;
2738 for (const auto &interval : Intervals())
2739 if (interval->Intersects(t0, t1))
2740 intersectingIntervals.push_back(interval);
2741 if (intersectingIntervals.empty())
2742 return { AudioSegmentSampleView {
2743 (TimeToLongSamples(t1) - TimeToLongSamples(t0)).as_size_t() } };
2744 std::sort(
2745 intersectingIntervals.begin(), intersectingIntervals.end(),
2746 [](const auto& a, const auto& b) { return a->Start() < b->Start(); });
2747 std::vector<AudioSegmentSampleView> segments;
2748 segments.reserve(2 * intersectingIntervals.size() + 1);
2749 for (auto i = 0u; i < intersectingIntervals.size();++i)
2750 {
2751 const auto& interval = intersectingIntervals[i];
2752 const auto intervalStartTime = interval->Start();
2753 if (t0 < intervalStartTime)
2754 {
2755 const auto numSamples = TimeToLongSamples(intervalStartTime - t0);
2756 segments.push_back(AudioSegmentSampleView{numSamples.as_size_t()});
2757 t0 = intervalStartTime;
2758 }
2759 const auto intervalT0 = t0 - intervalStartTime;
2760 const auto intervalT1 = std::min(t1, interval->End()) - intervalStartTime;
2761 if(intervalT1 > intervalT0)
2762 {
2763 auto newSegment =
2764 interval->GetSampleView(intervalT0, intervalT1, mayThrow);
2765 t0 += intervalT1 - intervalT0;
2766 segments.push_back(std::move(newSegment));
2767 }
2768 if (t0 == t1)
2769 break;
2770 }
2771 if (t0 < t1)
2772 segments.push_back(AudioSegmentSampleView {
2773 (TimeToLongSamples(t1) - TimeToLongSamples(t0)).as_size_t() });
2774 return segments;
2775}
2776
2779 sampleCount start, size_t len, sampleFormat effectiveFormat)
2780{
2781 for (const auto &clip: Intervals())
2782 {
2783 auto clipStart = clip->GetPlayStartSample();
2784 auto clipEnd = clip->GetPlayEndSample();
2785
2786 if (clipEnd > start && clipStart < start+len)
2787 {
2788 // Test as also in WaveTrack::GetOne()
2789 if (clip->HasPitchOrSpeed())
2790 return false;
2791
2792 // Clip sample region and Get/Put sample region overlap
2793 auto samplesToCopy =
2794 std::min( start+len - clipStart, clip->GetVisibleSampleCount() );
2795 auto startDelta = clipStart - start;
2796 decltype(startDelta) inclipDelta = 0;
2797 if (startDelta < 0)
2798 {
2799 inclipDelta = -startDelta; // make positive value
2800 samplesToCopy -= inclipDelta;
2801 // samplesToCopy is now either len or
2802 // (clipEnd - clipStart) - (start - clipStart)
2803 // == clipEnd - start > 0
2804 // samplesToCopy is not more than len
2805 //
2806 startDelta = 0;
2807 // startDelta is zero
2808 }
2809 else {
2810 // startDelta is nonnegative and less than len
2811 // samplesToCopy is positive and not more than len
2812 }
2813
2814 clip->SetSamples(
2815 buffer + startDelta.as_size_t() * SAMPLE_SIZE(format),
2816 format, inclipDelta, samplesToCopy.as_size_t(), effectiveFormat );
2817 }
2818 }
2819 return true;
2820}
2821
2823{
2825}
2826
2828{
2829 auto result = narrowestSampleFormat;
2830 for (const auto &pClip : Intervals())
2831 result = std::max(result, pClip->GetSampleFormats().Effective());
2832 return result;
2833}
2834
2836{
2837 return GetTrack().HasTrivialEnvelope();
2838}
2839
2841{
2842 auto pTrack = this;
2843 if (!pTrack)
2844 return false;
2845 auto clips = pTrack->Intervals();
2846 return std::all_of(clips.begin(), clips.end(),
2847 [](const auto &pClip){ return pClip->GetEnvelope().IsTrivial(); });
2848}
2849
2851 double* buffer, size_t bufferLen, double t0, bool backwards) const
2852{
2853 return GetTrack().GetEnvelopeValues(buffer, bufferLen, t0, backwards);
2854}
2855
2857 double* buffer, size_t bufferLen, double t0, bool backwards) const
2858{
2859 auto pTrack = this;
2860 if (!pTrack)
2861 return;
2862
2863 if (backwards)
2864 t0 -= bufferLen / pTrack->GetRate();
2865 // The output buffer corresponds to an unbroken span of time which the callers expect
2866 // to be fully valid. As clips are processed below, the output buffer is updated with
2867 // envelope values from any portion of a clip, start, end, middle, or none at all.
2868 // Since this does not guarantee that the entire buffer is filled with values we need
2869 // to initialize the entire buffer to a default value.
2870 //
2871 // This does mean that, in the cases where a usable clip is located, the buffer value will
2872 // be set twice. Unfortunately, there is no easy way around this since the clips are not
2873 // stored in increasing time order. If they were, we could just track the time as the
2874 // buffer is filled.
2875 for (decltype(bufferLen) i = 0; i < bufferLen; i++)
2876 {
2877 buffer[i] = 1.0;
2878 }
2879
2880 double startTime = t0;
2881 const auto rate = pTrack->GetRate();
2882 auto tstep = 1.0 / rate;
2883 double endTime = t0 + tstep * bufferLen;
2884 for (const auto &clip: pTrack->Intervals())
2885 {
2886 // IF clip intersects startTime..endTime THEN...
2887 auto dClipStartTime = clip->GetPlayStartTime();
2888 auto dClipEndTime = clip->GetPlayEndTime();
2889 if ((dClipStartTime < endTime) && (dClipEndTime > startTime))
2890 {
2891 auto rbuf = buffer;
2892 auto rlen = bufferLen;
2893 auto rt0 = t0;
2894
2895 if (rt0 < dClipStartTime)
2896 {
2897 // This is not more than the number of samples in
2898 // (endTime - startTime) which is bufferLen:
2899 auto nDiff = (sampleCount)floor((dClipStartTime - rt0) * rate + 0.5);
2900 auto snDiff = nDiff.as_size_t();
2901 rbuf += snDiff;
2902 wxASSERT(snDiff <= rlen);
2903 rlen -= snDiff;
2904 rt0 = dClipStartTime;
2905 }
2906
2907 if (rt0 + rlen*tstep > dClipEndTime)
2908 {
2909 auto nClipLen = clip->GetPlayEndSample() - clip->GetPlayStartSample();
2910
2911 if (nClipLen <= 0) // Testing for bug 641, this problem is consistently '== 0', but doesn't hurt to check <.
2912 return;
2913
2914 // This check prevents problem cited in http://bugzilla.audacityteam.org/show_bug.cgi?id=528#c11,
2915 // Gale's cross_fade_out project, which was already corrupted by bug 528.
2916 // This conditional prevents the previous write past the buffer end, in clip->GetEnvelope() call.
2917 // Never increase rlen here.
2918 // PRL bug 827: rewrote it again
2919 rlen = limitSampleBufferSize( rlen, nClipLen );
2920 rlen = std::min(rlen, size_t(floor(0.5 + (dClipEndTime - rt0) / tstep)));
2921 }
2922 // Samples are obtained for the purpose of rendering a wave track,
2923 // so quantize time
2924 clip->GetEnvelope().GetValues(rbuf, rlen, rt0, tstep);
2925 }
2926 }
2927 if (backwards)
2928 std::reverse(buffer, buffer + bufferLen);
2929}
2930
2931// When the time is both the end of a clip and the start of the next clip, the
2932// latter clip is returned.
2934{
2935 const auto clips = SortedClipArray();
2936 auto p = std::find_if(
2937 clips.rbegin(), clips.rend(), [&](const auto &pClip) {
2938 return pClip->WithinPlayRegion(time);
2939 });
2940 return p != clips.rend() ? *p : nullptr;
2941}
2942
2943auto WaveTrack::CreateClip(double offset, const wxString& name,
2944 const Interval *pToCopy, bool copyCutlines) -> IntervalHolder
2945{
2946 if (pToCopy) {
2947 auto pNewClip =
2948 std::make_shared<WaveClip>(*pToCopy, mpFactory, copyCutlines);
2949 pNewClip->SetName(name);
2950 pNewClip->SetSequenceStartTime(offset);
2951 return pNewClip;
2952 }
2953 else
2954 return DoCreateClip(offset, name);
2955}
2956
2957auto WaveTrack::CopyClip(const Interval &toCopy, bool copyCutlines)
2959{
2960 return CreateClip(toCopy.GetSequenceStartTime(),
2961 toCopy.GetName(), &toCopy, copyCutlines);
2962}
2963
2965{
2966 mRightChannel.emplace(*this);
2967}
2968
2969auto WaveTrack::DoCreateClip(double offset, const wxString& name) const
2971{
2972 auto clip = std::make_shared<WaveClip>(NChannels(),
2973 mpFactory, GetSampleFormat(), GetRate());
2974 clip->SetName(name);
2975 clip->SetSequenceStartTime(offset);
2976
2977 const auto& tempo = GetProjectTempo(*this);
2978 if (tempo.has_value())
2979 clip->OnProjectTempoChange(std::nullopt, *tempo);
2980 assert(clip->NChannels() == NChannels());
2981 return clip;
2982}
2983
2985{
2986 const auto &intervals = Intervals();
2987 if (intervals.empty()) {
2988 const auto origin = WaveTrackData::Get(*this).GetOrigin();
2989 const auto name = MakeNewClipName();
2990 auto pInterval = CreateClip(origin, name);
2991 InsertInterval(pInterval, true, true);
2992 return pInterval;
2993 }
2994 else
2995 return mClips.back();
2996}
2997
3000{
3001 if (mClips.empty()) {
3002 auto pInterval = CreateClip(
3003 WaveTrackData::Get(*this).GetOrigin(), MakeNewClipName());
3004 InsertInterval(pInterval, true, true);
3005 return pInterval;
3006 }
3007 else {
3008 auto end = mClips.end(),
3009 it = max_element(mClips.begin(), end,
3010 [](const auto &pClip1, const auto &pClip2){
3011 return pClip1->GetPlayStartTime() < pClip2->GetPlayStartTime();
3012 });
3013 assert(it != end);
3014 return *it;
3015 }
3016}
3017
3018// For internal purposes only
3019int WaveTrack::GetClipIndex(const Interval &clip) const
3020{
3021 int result = 0;
3022 const auto &clips = Intervals();
3023 const auto test =
3024 [&](const auto &pOtherClip){ return &clip == pOtherClip.get(); };
3025 auto begin = clips.begin(),
3026 end = clips.end(),
3027 iter = std::find_if(begin, end, test);
3028 return std::distance(begin, iter);
3029}
3030
3032{
3033 return NarrowClips().size();
3034}
3035
3037 const std::vector<Interval*> &movingClips,
3038 double amount,
3039 double *allowedAmount /* = NULL */)
3040{
3041 if (allowedAmount)
3042 *allowedAmount = amount;
3043
3044 const auto &moving = [&](Interval *clip){
3045 // linear search might be improved, but expecting few moving clips
3046 // compared with the fixed clips
3047 return movingClips.end() !=
3048 std::find(movingClips.begin(), movingClips.end(), clip);
3049 };
3050
3051 for (const auto &c: Intervals()) {
3052 if ( moving( c.get() ) )
3053 continue;
3054 for (const auto clip : movingClips) {
3055 if (c->GetPlayStartTime() < clip->GetPlayEndTime() + amount &&
3056 c->GetPlayEndTime() > clip->GetPlayStartTime() + amount)
3057 {
3058 if (!allowedAmount)
3059 return false; // clips overlap
3060
3061 if (amount > 0)
3062 {
3063 if (c->GetPlayStartTime() - clip->GetPlayEndTime() < *allowedAmount)
3064 *allowedAmount = c->GetPlayStartTime() - clip->GetPlayEndTime();
3065 if (*allowedAmount < 0)
3066 *allowedAmount = 0;
3067 } else
3068 {
3069 if (c->GetPlayEndTime() - clip->GetPlayStartTime() > *allowedAmount)
3070 *allowedAmount = c->GetPlayEndTime() - clip->GetPlayStartTime();
3071 if (*allowedAmount > 0)
3072 *allowedAmount = 0;
3073 }
3074 }
3075 }
3076 }
3077
3078 if (allowedAmount)
3079 {
3080 if (*allowedAmount == amount)
3081 return true;
3082
3083 // Check if the NEW calculated amount would not violate
3084 // any other constraint
3085 if (!CanOffsetClips(movingClips, *allowedAmount, nullptr)) {
3086 *allowedAmount = 0; // play safe and don't allow anything
3087 return false;
3088 }
3089 else
3090 return true;
3091 } else
3092 return true;
3093}
3094
3096 const Interval& candidateClip, double& slideBy, double tolerance) const
3097{
3098 const auto &clips = Intervals();
3099 if (clips.empty())
3100 return true;
3101 // Find clip in this that overlaps most with `clip`:
3102 const auto candidateClipStartTime = candidateClip.GetPlayStartTime();
3103 const auto candidateClipEndTime = candidateClip.GetPlayEndTime();
3104 const auto t0 = SnapToSample(candidateClipStartTime + slideBy);
3105 const auto t1 = SnapToSample(candidateClipEndTime + slideBy);
3106 std::vector<double> overlaps;
3107 std::transform(
3108 clips.begin(), clips.end(), std::back_inserter(overlaps),
3109 [&](const auto& pClip) {
3110 return pClip->IntersectsPlayRegion(t0, t1) ?
3111 std::min(pClip->GetPlayEndTime(), t1) -
3112 std::max(pClip->GetPlayStartTime(), t0) :
3113 0.0;
3114 });
3115 const auto maxOverlap = std::max_element(overlaps.begin(), overlaps.end());
3116 if (*maxOverlap > tolerance)
3117 return false;
3118 auto iter = clips.begin();
3119 std::advance(iter, std::distance(overlaps.begin(), maxOverlap));
3120 const auto overlappedClip = *iter;
3121 const auto requiredOffset = slideBy +
3122 *maxOverlap * (overlappedClip->GetPlayStartTime() < t0 ? 1 : -1);
3123 // Brute-force check to see if there's another clip that'd be in the way.
3124 if (std::any_of(
3125 clips.begin(), clips.end(),
3126 [&](const auto& pClip)
3127 {
3128 const auto result = pClip->IntersectsPlayRegion(
3129 SnapToSample(candidateClipStartTime + requiredOffset),
3130 SnapToSample(candidateClipEndTime + requiredOffset));
3131 return result;
3132 }))
3133 return false;
3134 slideBy = requiredOffset;
3135 return true;
3136}
3137
3139void WaveTrack::Split(double t0, double t1)
3140{
3141 SplitAt(t0);
3142 if (t0 != t1)
3143 SplitAt(t1);
3144}
3145
3147auto WaveTrack::SplitAt(double t) -> std::pair<IntervalHolder, IntervalHolder>
3148{
3149 for (const auto &&c : Intervals()) {
3150 if (c->SplitsPlayRegion(t)) {
3151 t = SnapToSample(t);
3152 auto newClip = CopyClip(*c, true);
3153 c->TrimRightTo(t);// put t on a sample
3154 newClip->TrimLeftTo(t);
3155 auto result = std::pair{ c, newClip };
3156
3157 // This could invalidate the iterators for the loop! But we return
3158 // at once so it's okay
3159 InsertInterval(move(newClip), false); // transfer ownership
3160 return result;
3161 }
3162 }
3163 return {};
3164}
3165
3166// Can't promise strong exception safety for a pair of tracks together
3167bool WaveTrack::MergeClips(int clipidx1, int clipidx2)
3168{
3169 const auto clip1 = GetClip(clipidx1);
3170 const auto clip2 = GetClip(clipidx2);
3171
3172 if (!clip1 || !clip2)
3173 return false; // Don't throw, just do nothing.
3174
3175 if (!clip1->HasEqualPitchAndSpeed(*clip2))
3176 return false;
3177
3178 // Append data from second clip to first clip
3179 // use Strong-guarantee
3180 bool success = clip1->Paste(clip1->GetPlayEndTime(), *clip2);
3181 assert(success); // assuming clips of the same track must have same width
3182
3183 // use No-fail-guarantee for the rest
3184 // Delete second clip
3185 RemoveInterval(clip2);
3186 return true;
3187}
3188
3190 const IntervalHolders& srcIntervals,
3191 const ProgressReporter& reportProgress)
3192{
3193 IntervalHolders dstIntervals;
3194 dstIntervals.reserve(srcIntervals.size());
3195 std::transform(
3196 srcIntervals.begin(), srcIntervals.end(),
3197 std::back_inserter(dstIntervals), [&](const IntervalHolder& interval) {
3198 return GetRenderedCopy(interval,
3199 reportProgress, mpFactory, GetSampleFormat());
3200 });
3201
3202 // If we reach this point it means that no error was thrown - we can replace
3203 // the source with the destination intervals.
3204 for (auto i = 0; i < srcIntervals.size(); ++i)
3205 ReplaceInterval(srcIntervals[i], dstIntervals[i]);
3206}
3207
3208namespace {
3210{
3211 // This is used only in assertions
3212 using Set = std::unordered_set<WaveClipHolder>;
3213 return clips.size() == Set{ clips.begin(), clips.end() }.size();
3214}
3215}
3216
3218 bool newClip, bool allowEmpty)
3219{
3220 if (clip) {
3221 constexpr bool backup = false;
3222 InsertClip(mClips, clip, newClip, backup, allowEmpty);
3223 // Detect errors resulting in duplicate shared pointers to clips
3224 assert(ClipsAreUnique(mClips));
3225 }
3226}
3227
3229{
3230 const auto end = mClips.end(),
3231 iter = find(mClips.begin(), end, interval);
3232 if (iter != end)
3233 mClips.erase(iter);
3234}
3235
3237 const IntervalHolder& oldOne, const IntervalHolder& newOne)
3238{
3239 assert(newOne == oldOne || FindClip(*newOne) == Intervals().size());
3240 assert(oldOne->NChannels() == newOne->NChannels());
3241 RemoveInterval(oldOne);
3242 InsertInterval(newOne, false);
3243 newOne->SetName(oldOne->GetName());
3244}
3245
3249{
3250 for (const auto &pClip : Intervals())
3251 pClip->Resample(rate, progress);
3252 DoSetRate(rate);
3253}
3254
3255bool WaveTrack::SetFloats(const float *const *buffers,
3256 sampleCount start, size_t len, sampleFormat effectiveFormat)
3257{
3258 bool result = true;
3259 size_t ii = 0;
3260 for (const auto &pChannel : Channels()) {
3261 const auto buffer = buffers[ii++];
3262 assert(buffer); // precondition
3263 result = pChannel->SetFloats(buffer, start, len, effectiveFormat)
3264 && result;
3265 }
3266 return result;
3267}
3268
3270{
3271 const auto &intervals = Intervals();
3272 IntervalConstHolders clips{ intervals.begin(), intervals.end() };
3273 const auto comp = [](const auto &a, const auto &b) {
3274 return a->GetPlayStartTime() < b->GetPlayStartTime(); };
3275 std::sort(clips.begin(), clips.end(), comp);
3276 return clips;
3277}
3278
3280{
3281 const auto &intervals = Intervals();
3282 IntervalHolders result;
3283 copy(intervals.begin(), intervals.end(), back_inserter(result));
3284 sort(result.begin(), result.end(), [](const auto &pA, const auto &pB){
3285 return pA->GetPlayStartTime() < pB->GetPlayStartTime(); });
3286 return result;
3287}
3288
3290{
3291 const auto &intervals = Intervals();
3292 IntervalConstHolders result;
3293 copy(intervals.begin(), intervals.end(), back_inserter(result));
3294 sort(result.begin(), result.end(), [](const auto &pA, const auto &pB){
3295 return pA->GetPlayStartTime() < pB->GetPlayStartTime(); });
3296 return result;
3297}
3298
3299void WaveTrack::ZipClips(bool mustAlign)
3300{
3301 const auto pOwner = GetOwner();
3302 assert(GetOwner()); // pre
3303 assert(NChannels() == 1); // pre
3304
3305 // If deserializing, first un-link the track, so iterator finds the partner.
3307
3308 auto iter = pOwner->Find(this);
3309 assert(this == *iter);
3310 ++iter;
3311 assert(iter != pOwner->end()); // pre
3312 auto pRight = dynamic_cast<WaveTrack*>(*iter);
3313 assert(pRight && pRight->NChannels() == 1); // pre
3314
3316 if (mustAlign &&
3317 !AreAligned(this->SortedClipArray(), pRight->SortedClipArray()))
3318 return;
3319
3320 CreateRight();
3321
3322 // Now steal right side sample data info. When not requiring alignment,
3323 // because this is a track that just keeps the sample counts of blocks
3324 // above 0 for later purposes -- then there is laxity about consistent
3325 // width of the clips.
3326 auto iterMe = mClips.begin(),
3327 endMe = mClips.end();
3328 auto iterRight = pRight->mClips.begin(),
3329 endRight = pRight->mClips.end();
3330 while (iterMe != endMe && iterRight != endRight) {
3331 (*iterMe)->MakeStereo(std::move(**iterRight), mustAlign);
3332 ++iterMe;
3333 ++iterRight;
3334 }
3335 assert(!mustAlign || (iterMe == endMe && iterRight == endRight));
3336
3337 while (iterRight != endRight) {
3338 // Leftover misaligned mono clips
3339 mClips.emplace_back(move(*iterRight));
3340 ++iterRight;
3341 }
3342
3343 this->MergeChannelAttachments(std::move(*pRight));
3344
3345 pOwner->Remove(*pRight);
3346}
3347
3349 return std::make_shared< WaveTrackFactory >(
3352};
3353
3356};
3357
3359{
3360 return project.AttachedObjects::Get< WaveTrackFactory >( key2 );
3361}
3362
3364{
3365 return Get( const_cast< AudacityProject & >( project ) );
3366}
3367
3369{
3370 auto result = TrackFactoryFactory( project );
3371 project.AttachedObjects::Assign( key2, result );
3372 return *result;
3373}
3374
3376{
3377 project.AttachedObjects::Assign( key2, nullptr );
3378}
3379
3381 L"/GUI/TrackNames/DefaultTrackName",
3382 // Computed default value depends on chosen language
3383 []{ return DefaultName.Translation(); }
3384};
3385
3386// Bug 825 is essentially that SyncLock requires EditClipsCanMove.
3387// SyncLock needs rethinking, but meanwhile this function
3388// fixes the issues of Bug 825 by allowing clips to move when in
3389// SyncLock.
3391{
3392 bool mIsSyncLocked = SyncLockTracks.Read();
3393 if( mIsSyncLocked )
3394 return true;
3395 bool editClipsCanMove;
3396 return EditClipsCanMove.Read();
3397}
3398
3400 L"/GUI/EditClipCanMove", false };
3401
wxT("CloseDown"))
@ BadUserAction
Indicates that the user performed an action that is not allowed.
std::vector< std::shared_ptr< const ClipInterface > > ClipConstHolders
An audio segment is either a whole clip or the silence between clips. Views allow shared references t...
std::vector< AudioSegmentSampleView > ChannelSampleView
static RegisteredToolbarFactory factory
Toolkit-neutral facade for basic user interface services.
Adapts TrackAttachment interface with extra channel index argument.
int min(int a, int b)
EffectDistortionSettings params
Definition: Distortion.cpp:77
const TranslatableString name
Definition: Distortion.cpp:76
XO("Cut/Copy/Paste")
MessageBoxException for violation of preconditions or assertions.
#define THROW_INCONSISTENCY_EXCEPTION
Throw InconsistencyException, using C++ preprocessor to identify the source code location.
#define XC(s, c)
Definition: Internat.h:37
std::vector< std::vector< AudioSegmentSampleView > > ChannelGroupSampleView
Definition: MixerBoard.h:35
PlaybackDirection
an object holding per-project preferred sample rate
std::shared_ptr< SampleBlockFactory > SampleBlockFactoryPtr
Definition: SampleBlock.h:31
size_t limitSampleBufferSize(size_t bufferSize, sampleCount limit)
Definition: SampleCount.cpp:22
void ReverseSamples(samplePtr dst, sampleFormat format, int start, int len)
void ClearSamples(samplePtr dst, sampleFormat format, size_t start, size_t len)
constexpr sampleFormat floatSample
Definition: SampleFormat.h:45
sampleFormat
The ordering of these values with operator < agrees with the order of increasing bit width.
Definition: SampleFormat.h:30
@ narrowestSampleFormat
Two synonyms for previous values that might change if more values were added.
char * samplePtr
Definition: SampleFormat.h:57
#define SAMPLE_SIZE(SampleFormat)
Definition: SampleFormat.h:52
const char * constSamplePtr
Definition: SampleFormat.h:58
constexpr sampleFormat widestSampleFormat
Definition: SampleFormat.h:47
enum FillFormat fillFormat
BoolSetting SyncLockTracks
Definition: SyncLock.cpp:163
const std::optional< double > & GetProjectTempo(const ChannelGroup &group)
Definition: TempoChange.cpp:48
void DoProjectTempoChange(ChannelGroup &group, double newTempo)
Definition: TempoChange.cpp:41
const auto tracks
const auto project
Contains declarations for TimeWarper, IdentityTimeWarper, ShiftTimeWarper, LinearTimeWarper,...
std::function< void(double)> ProgressReporter
Definition: Track.h:48
std::shared_ptr< TrackList > TrackListHolder
Definition: Track.h:42
WaveTrack::Region Region
std::shared_ptr< WaveClip > WaveClipHolder
Definition: WaveClip.h:43
std::vector< WaveClipHolder > WaveClipHolders
Definition: WaveClip.h:45
bool GetEditClipsCanMove()
Definition: WaveTrack.cpp:3390
static constexpr auto Pan_attr
Definition: WaveTrack.cpp:2338
static ProjectFileIORegistry::ObjectReaderEntry readerEntry
Definition: WaveTrack.cpp:386
static constexpr auto Rate_attr
Definition: WaveTrack.cpp:2336
static const AudacityProject::AttachedObjects::RegisteredFactory key2
Definition: WaveTrack.cpp:3354
DEFINE_XML_METHOD_REGISTRY(WaveTrackIORegistry)
static constexpr auto SampleFormat_attr
Definition: WaveTrack.cpp:2340
BoolSetting EditClipsCanMove
Definition: WaveTrack.cpp:3399
static auto DefaultName
Definition: WaveTrack.cpp:364
static constexpr auto Channel_attr
Definition: WaveTrack.cpp:2341
static constexpr auto Offset_attr
Definition: WaveTrack.cpp:2335
static constexpr auto Linked_attr
Definition: WaveTrack.cpp:2339
static auto TrackFactoryFactory
Definition: WaveTrack.cpp:3348
static constexpr auto Gain_attr
Definition: WaveTrack.cpp:2337
StringSetting AudioTrackNameSetting
Definition: WaveTrack.cpp:3380
static const Track::TypeInfo & typeInfo()
Definition: WaveTrack.cpp:657
#define WAVETRACK_MERGE_POINT_TOLERANCE
Definition: WaveTrack.h:67
std::vector< Attribute > AttributesList
Definition: XMLTagHandler.h:40
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
Abstraction of a progress dialog with well defined time-to-completion estimate.
Definition: BasicUI.h:164
This specialization of Setting for bool adds a Toggle method to negate the saved value.
Definition: Prefs.h:346
Holds multiple objects as a single attachment to Track.
double GetEndTime() const
Get the maximum of End() values of intervals, or 0 when none.
Definition: Channel.cpp:61
double GetStartTime() const
Get the minimum of Start() values of intervals, or 0 when none.
Definition: Channel.cpp:50
LinkType
For two tracks describes the type of the linkage.
Definition: Channel.h:512
@ Group
compatibility with projects that were generated by older versions of Audacity
@ Aligned
Tracks are grouped and changes should be synchronized.
virtual double Start() const =0
size_t GetChannelIndex() const
Definition: Channel.cpp:25
Client code makes static instance from a factory of attachments; passes it to Get or Find as a retrie...
Definition: ClientData.h:275
size_t size() const
How many attachment pointers are in the Site.
Definition: ClientData.h:260
void ForCorresponding(Site &other, const Function &function, bool create=true)
Definition: ClientData.h:428
void ForEach(const Function &function)
Invoke function on each ClientData object that has been created in this.
Definition: ClientData.h:389
No change to time at all.
Definition: TimeWarper.h:69
CallbackReturn Publish(const WaveTrackMessage &message)
Send a message to connected callbacks.
Definition: Observer.h:207
bool GetSolo() const
Definition: PlayableTrack.h:48
bool HandleXMLAttribute(const std::string_view &attr, const XMLAttributeValueView &value)
bool GetMute() const
Definition: PlayableTrack.h:47
static ProjectRate & Get(AudacityProject &project)
Definition: ProjectRate.cpp:28
double GetRate() const
Definition: ProjectRate.cpp:53
static SampleBlockFactoryPtr New(AudacityProject &project)
Definition: SampleBlock.cpp:16
Two sample formats, remembering format of original source and describing stored format.
Definition: SampleFormat.h:79
A WaveTrack contains WaveClip(s). A WaveClip contains a Sequence. A Sequence is primarily an interfac...
Definition: Sequence.h:53
static const char * Sequence_tag
Definition: Sequence.h:56
static const char * WaveBlock_tag
Definition: Sequence.h:57
static bool IsValidSampleFormat(const int nValue)
true if nValue is one of the sampleFormat enum values
Definition: Sequence.cpp:1905
bool ReadWithDefault(T *pVar, const T &defaultValue) const
overload of ReadWithDefault returning a boolean that is true if the value was previously defined *‍/
Definition: Prefs.h:213
bool Read(T *pVar) const
overload of Read returning a boolean that is true if the value was previously defined *‍/
Definition: Prefs.h:207
A MessageBoxException that shows a given, unvarying string.
Specialization of Setting for strings.
Definition: Prefs.h:370
Transforms one point in time to another point. For example, a time stretching effect might use one to...
Definition: TimeWarper.h:62
virtual double Warp(double originalTime) const =0
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:110
void Notify(bool allChannels, int code=-1)
Definition: Track.cpp:234
std::shared_ptr< TrackList > GetOwner() const
Definition: Track.h:230
static void CopyAttachments(Track &dst, const Track &src, bool deep)
Copy (deep) or just share (!deep) AttachedTrackObjects.
Definition: Track.cpp:94
virtual bool LinkConsistencyFix(bool doFix=true)
Check consistency of channel groups, and maybe fix it.
Definition: Track.cpp:266
void SetLinkType(LinkType linkType, bool completeList=true)
Definition: Track.cpp:136
std::shared_ptr< Track > Holder
Definition: Track.h:202
bool HandleCommonXMLAttribute(const std::string_view &attr, const XMLAttributeValueView &valueView)
Definition: Track.cpp:819
const wxString & GetName() const
Name is always the same for all channels of a group.
Definition: Track.cpp:64
void Init(const Track &orig)
Definition: Track.cpp:47
bool IsLeader() const
Definition: Track.cpp:261
LinkType GetLinkType() const noexcept
Definition: Track.cpp:853
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
Definition: Track.h:850
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:314
TrackKind * Add(const std::shared_ptr< TrackKind > &t, bool assignIds=true)
Definition: Track.h:1048
static TrackListHolder Temporary(AudacityProject *pProject, const Track::Holder &pTrack={})
Definition: Track.cpp:858
static auto Channels(TrackType *pTrack) -> TrackIterRange< TrackType >
Definition: Track.h:1016
ChannelSampleView GetSampleView(double t0, double t1, bool mayThrow) const
Request channel samples within [t0, t1), not knowing in advance how many this will be.
Definition: WaveTrack.cpp:2734
AudioGraph::ChannelType GetChannelType() const override
Classify this channel.
Definition: WaveTrack.cpp:535
double GetRate() const override
Definition: WaveTrack.cpp:798
double GetStartTime() const override
Definition: WaveTrack.cpp:2580
double GetEndTime() const override
Definition: WaveTrack.cpp:2590
size_t NChannels() const override
A constant property.
Definition: WaveTrack.cpp:525
bool HasTrivialEnvelope() const override
Definition: WaveTrack.cpp:2835
float GetChannelGain(int channel) const override
Takes gain and pan into account.
Definition: WaveTrack.cpp:865
WaveTrack & GetTrack()
Definition: WaveTrack.h:840
void GetEnvelopeValues(double *buffer, size_t bufferLen, double t0, bool backwards) const override
Definition: WaveTrack.cpp:2850
~WaveChannel() override
IteratorRange< IntervalIterator< WaveClipChannel > > Intervals()
Definition: WaveTrack.cpp:743
WaveTrack & mOwner
Definition: WaveTrack.h:197
bool Set(constSamplePtr buffer, sampleFormat format, sampleCount start, size_t len, sampleFormat effectiveFormat=widestSampleFormat)
Random-access assignment of a range of samples.
Definition: WaveTrack.cpp:2778
bool DoGet(size_t iChannel, size_t nBuffers, const samplePtr buffers[], sampleFormat format, sampleCount start, size_t len, bool backwards, fillFormat fill=FillFormat::fillZero, bool mayThrow=true, sampleCount *pNumWithinClips=nullptr) const override
This fails if any clip overlapping the range has non-unit stretch ratio!
Definition: WaveTrack.cpp:2600
std::shared_ptr< WaveClipChannel > GetInterval(size_t iInterval)
Definition: WaveTrack.cpp:735
bool Append(constSamplePtr buffer, sampleFormat format, size_t len)
Definition: WaveTrack.cpp:2221
sampleFormat WidestEffectiveFormat() const override
Definition: WaveTrack.cpp:2822
bool AppendBuffer(constSamplePtr buffer, sampleFormat format, size_t len, unsigned stride, sampleFormat effectiveFormat)
Definition: WaveTrack.cpp:2210
ChannelGroup & DoGetChannelGroup() const override
Subclass must override.
Definition: WaveTrack.cpp:729
WaveChannel(WaveTrack &owner)
Definition: WaveTrack.cpp:366
This allows multiple clips to be a part of one WaveTrack.
Definition: WaveClip.h:238
static const char * WaveClip_tag
Definition: WaveClip.h:249
Used to create or clone a WaveTrack, with appropriate context from the project that will own the trac...
Definition: WaveTrack.h:870
std::shared_ptr< WaveTrack > Create()
Creates an unnamed empty WaveTrack with default sample format and default rate.
Definition: WaveTrack.cpp:391
static void Destroy(AudacityProject &project)
Definition: WaveTrack.cpp:3375
std::shared_ptr< WaveTrack > DoCreate(size_t nChannels, sampleFormat format, double rate)
Definition: WaveTrack.cpp:396
static WaveTrackFactory & Get(AudacityProject &project)
Definition: WaveTrack.cpp:3358
TrackListHolder CreateMany(size_t nChannels)
Creates tracks with project's default rate and format and the given number of channels.
Definition: WaveTrack.cpp:423
static WaveTrackFactory & Reset(AudacityProject &project)
Definition: WaveTrack.cpp:3368
SampleBlockFactoryPtr mpFactory
Definition: WaveTrack.h:943
const ProjectRate & mRate
Definition: WaveTrack.h:942
A Track that contains audio waveform data.
Definition: WaveTrack.h:203
void HandleXMLEndTag(const std::string_view &tag) override
Definition: WaveTrack.cpp:2395
void MakeMono()
Simply discard any right channel.
Definition: WaveTrack.cpp:1021
SampleBlockFactoryPtr mpFactory
Definition: WaveTrack.h:829
bool MergeClips(int clipidx1, int clipidx2)
Definition: WaveTrack.cpp:3167
bool GetMute() const override
May vary asynchronously.
Definition: WaveTrack.cpp:2323
void SetRate(double newRate)
!brief Sets the new rate for the track without resampling it
Definition: WaveTrack.cpp:808
IntervalHolder RightmostOrNewClip()
Get access to the last (rightmost) clip, or create a clip, if there is not already one.
Definition: WaveTrack.cpp:2999
void CopyClips(WaveClipHolders &clips, SampleBlockFactoryPtr pFactory, const WaveClipHolders &orig, bool backup)
Definition: WaveTrack.cpp:516
IntervalConstHolders SortedClipArray() const
Return all WaveClips sorted by clip play start time.
Definition: WaveTrack.cpp:3269
void DoSetPan(float value)
Definition: WaveTrack.cpp:847
static WaveTrack * New(AudacityProject &project)
Definition: WaveTrack.cpp:486
void RemoveClip(std::ptrdiff_t distance)
Definition: WaveTrack.cpp:1524
bool GetOne(const WaveClipHolders &clips, size_t iChannel, samplePtr buffer, sampleFormat format, sampleCount start, size_t len, bool backwards, fillFormat fill, bool mayThrow, sampleCount *pNumWithinClips) const
Definition: WaveTrack.cpp:2633
void SplitDelete(double t0, double t1)
Definition: WaveTrack.cpp:1508
std::vector< Region > Regions
Definition: WaveTrack.h:230
bool HasClipNamed(const wxString &name) const
Definition: WaveTrack.cpp:709
void Silence(double t0, double t1, ProgressReporter reportProgress) override
Definition: WaveTrack.cpp:1984
bool DoGet(size_t iChannel, size_t nBuffers, const samplePtr buffers[], sampleFormat format, sampleCount start, size_t len, bool backwards, fillFormat fill=FillFormat::fillZero, bool mayThrow=true, sampleCount *pNumWithinClips=nullptr) const override
This fails if any clip overlapping the range has non-unit stretch ratio!
Definition: WaveTrack.cpp:2618
IntervalHolder NewestOrNewClip()
Get access to the most recently added clip, or create a clip, if there is not already one....
Definition: WaveTrack.cpp:2984
std::ptrdiff_t FindClip(const Interval &clip)
Definition: WaveTrack.cpp:1515
std::vector< Holder > SplitChannels()
Definition: WaveTrack.cpp:1046
double GetStartTime() const override
Implement WideSampleSequence.
Definition: WaveTrack.cpp:2585
void ConvertToSampleFormat(sampleFormat format, const std::function< void(size_t)> &progressReport={})
Definition: WaveTrack.cpp:906
void ApplyPitchAndSpeedOnIntervals(const std::vector< IntervalHolder > &intervals, const ProgressReporter &reportProgress)
Definition: WaveTrack.cpp:3189
int mLegacyRate
used only during deserialization
Definition: WaveTrack.h:777
void InsertSilence(double t, double len) override
Definition: WaveTrack.cpp:2007
IntervalHolder CreateClip(double offset=.0, const wxString &name=wxEmptyString, const Interval *pToCopy=nullptr, bool copyCutlines=true)
Definition: WaveTrack.cpp:2943
auto Channels()
Definition: WaveTrack.h:263
bool Append(size_t iChannel, constSamplePtr buffer, sampleFormat format, size_t len, unsigned int stride=1, sampleFormat effectiveFormat=widestSampleFormat) override
Definition: WaveTrack.cpp:2232
Track::Holder PasteInto(AudacityProject &project, TrackList &list) const override
Definition: WaveTrack.cpp:675
void WriteXML(XMLWriter &xmlFile) const override
Definition: WaveTrack.cpp:2457
wxString MakeNewClipName() const
Definition: WaveTrack.cpp:786
void CreateRight()
Definition: WaveTrack.cpp:2964
size_t NIntervals() const override
Report the number of intervals.
Definition: WaveTrack.cpp:686
static Holder Create(const SampleBlockFactoryPtr &pFactory, sampleFormat format, double rate)
Factory builds all AttachedTrackObjects.
Definition: WaveTrack.cpp:503
void InsertInterval(const IntervalHolder &interval, bool newClip, bool allowEmpty=false)
Definition: WaveTrack.cpp:3217
static wxString GetDefaultAudioTrackNamePreference()
Definition: WaveTrack.cpp:373
WaveClip Interval
Definition: WaveTrack.h:208
bool LinkConsistencyFix(bool doFix) override
Check consistency of channel groups, and maybe fix it.
Definition: WaveTrack.cpp:580
IntervalConstHolder GetNextInterval(const Interval &interval, PlaybackDirection searchDirection) const
Definition: WaveTrack.cpp:173
std::optional< TranslatableString > GetErrorOpening() const override
Definition: WaveTrack.cpp:2530
std::shared_ptr<::Channel > DoGetChannel(size_t iChannel) override
Definition: WaveTrack.cpp:716
void ZipClips(bool mustAlign=true)
Definition: WaveTrack.cpp:3299
sampleFormat GetSampleFormat() const override
Definition: WaveTrack.cpp:900
void Join(double t0, double t1, const ProgressReporter &reportProgress)
Definition: WaveTrack.cpp:2132
WaveChannel mChannel
Definition: WaveTrack.h:767
IntervalConstHolder GetClipAtTime(double time) const
Definition: WaveTrack.cpp:2933
void ClearAndAddCutLine(double t0, double t1)
Definition: WaveTrack.cpp:1149
void SyncLockAdjust(double oldT1, double newT1) override
Definition: WaveTrack.cpp:1653
const TypeInfo & GetTypeInfo() const override
Definition: WaveTrack.cpp:665
bool SetFloats(const float *const *buffers, sampleCount start, size_t len, sampleFormat effectiveFormat=widestSampleFormat)
Random-access assignment of a range of samples.
Definition: WaveTrack.cpp:3255
float GetPan() const
Definition: WaveTrack.cpp:842
size_t GetIdealBlockSize()
Definition: WaveTrack.cpp:2284
Track::Holder Cut(double t0, double t1) override
Create tracks and modify this track.
Definition: WaveTrack.cpp:937
void CopyWholeClip(const Interval &clip, double t0, bool forClipboard)
Definition: WaveTrack.cpp:1103
bool InsertClip(WaveClipHolders &clips, WaveClipHolder clip, bool newClip, bool backup, bool allowEmpty)
Definition: WaveTrack.cpp:1913
ChannelGroupSampleView GetSampleView(double t0, double t1, bool mayThrow=true) const
Request samples within [t0, t1), not knowing in advance how many this will be.
Definition: WaveTrack.cpp:2724
void Clear(double t0, double t1) override
Definition: WaveTrack.cpp:1143
void Init(const WaveTrack &orig)
Definition: WaveTrack.cpp:553
void DoSetRate(double newRate)
Definition: WaveTrack.cpp:818
std::shared_ptr< Interval > IntervalHolder
Definition: WaveTrack.h:209
void Split(double t0, double t1)
Definition: WaveTrack.cpp:3139
Track::Holder Clone(bool backup) const override
Definition: WaveTrack.cpp:759
void Flush() override
Definition: WaveTrack.cpp:2299
std::pair< IntervalHolder, IntervalHolder > SplitAt(double t)
Definition: WaveTrack.cpp:3147
IntervalHolders SortedIntervalArray()
Return all WaveClips sorted by clip play start time.
Definition: WaveTrack.cpp:3279
void ReplaceInterval(const IntervalHolder &oldOne, const IntervalHolder &newOne)
Definition: WaveTrack.cpp:3236
static const char * WaveTrack_tag
Definition: WaveTrack.h:206
void FinishCopy(double t0, double t1, double endTime, bool forClipboard)
Definition: WaveTrack.cpp:1126
static void WriteOneXML(const WaveChannel &channel, XMLWriter &xmlFile, size_t iChannel, size_t nChannels)
Definition: WaveTrack.cpp:2467
const SampleBlockFactoryPtr & GetSampleBlockFactory() const
Definition: WaveTrack.h:253
int GetClipIndex(const Interval &clip) const
Get the linear index of a given clip (== number of clips if not found)
Definition: WaveTrack.cpp:3019
bool IsEmpty(double t0, double t1) const
Returns true if there are no WaveClips in the specified region.
Definition: WaveTrack.cpp:915
Holder DuplicateWithOtherTempo(double newTempo) const
Definition: WaveTrack.cpp:573
std::vector< IntervalHolder > IntervalHolders
Definition: WaveTrack.h:210
sampleFormat WidestEffectiveFormat() const override
Definition: WaveTrack.cpp:2827
void SetPan(float newPan)
Definition: WaveTrack.cpp:852
XMLTagHandler * HandleXMLChild(const std::string_view &tag) override
Definition: WaveTrack.cpp:2407
bool HandleXMLTag(const std::string_view &tag, const AttributesList &attrs) override
Definition: WaveTrack.cpp:2343
std::optional< WaveChannel > mRightChannel
may be null
Definition: WaveTrack.h:769
static const TypeInfo & ClassTypeInfo()
Definition: WaveTrack.cpp:670
WaveClipHolders & NarrowClips()
Definition: WaveTrack.cpp:749
void Paste(double t0, const Track &src) override
Definition: WaveTrack.cpp:1970
void MoveTo(double o) override
Definition: WaveTrack.cpp:564
void SetLegacyFormat(sampleFormat format)
Definition: WaveTrack.cpp:2313
void ApplyPitchAndSpeed(std::optional< TimeInterval > interval, ProgressReporter reportProgress)
Definition: WaveTrack.cpp:1929
void Trim(double t0, double t1)
Definition: WaveTrack.cpp:962
void Disjoin(double t0, double t1)
Definition: WaveTrack.cpp:2045
Holder MonoToStereo()
Definition: WaveTrack.cpp:1029
void DoSetGain(float value)
Definition: WaveTrack.cpp:829
void Resample(int rate, BasicUI::ProgressDialog *progress=NULL)
Definition: WaveTrack.cpp:3248
IntervalHolder CopyClip(const Interval &toCopy, bool copyCutlines)
Create new clip and add it to this track.
Definition: WaveTrack.cpp:2957
void RepairChannels() override
Definition: WaveTrack.cpp:2307
double GetEndTime() const override
Implement WideSampleSequence.
Definition: WaveTrack.cpp:2595
float GetGain() const
Definition: WaveTrack.cpp:824
void CopyPartOfClip(const Interval &clip, double t0, double t1, bool forClipboard)
Definition: WaveTrack.cpp:1113
std::shared_ptr< WideChannelGroupInterval > DoGetInterval(size_t iInterval) override
Retrieve an interval.
Definition: WaveTrack.cpp:702
AudioGraph::ChannelType GetChannelType() const override
Classify this channel.
Definition: WaveTrack.cpp:546
bool FormatConsistencyCheck() const
Definition: WaveTrack.cpp:1904
Holder SplitCut(double t0, double t1)
Definition: WaveTrack.cpp:948
void SetGain(float newGain)
Definition: WaveTrack.cpp:834
double GetRate() const override
Definition: WaveTrack.cpp:803
void ClearAndPasteAtSameTempo(double t0, double t1, const WaveTrack &src, bool preserve, bool merge, const TimeWarper *effectWarper, bool clearByTrimming)
Definition: WaveTrack.cpp:1220
auto Intervals()
Definition: WaveTrack.h:670
int GetNumClips() const
Definition: WaveTrack.cpp:3031
bool RateConsistencyCheck() const
Whether all clips of an unzipped leader track have a common rate.
Definition: WaveTrack.cpp:1885
bool GetSolo() const override
May vary asynchronously.
Definition: WaveTrack.cpp:2328
float GetChannelGain(int channel) const override
Takes gain and pan into account.
Definition: WaveTrack.cpp:870
void SwapChannels()
Definition: WaveTrack.cpp:1065
const ChannelGroup * FindChannelGroup() const override
Find associated ChannelGroup if any.
Definition: WaveTrack.cpp:2318
std::vector< IntervalConstHolder > IntervalConstHolders
Definition: WaveTrack.h:212
ClipConstHolders GetClipInterfaces() const
Get access to the (visible) clips in the tracks, in unspecified order.
Definition: WaveTrack.cpp:2574
size_t NChannels() const override
A constant property.
Definition: WaveTrack.cpp:530
bool CanOffsetClips(const std::vector< Interval * > &movingClips, double amount, double *allowedAmount=nullptr)
Decide whether the clips could be offset (and inserted) together without overlapping other clips.
Definition: WaveTrack.cpp:3036
size_t GetMaxBlockSize() const
Definition: WaveTrack.cpp:2263
void PasteWaveTrackAtSameTempo(double t0, const WaveTrack &other, bool merge)
Definition: WaveTrack.cpp:1702
void GetEnvelopeValues(double *buffer, size_t bufferLen, double t0, bool backwards) const override
Definition: WaveTrack.cpp:2856
bool HasTrivialEnvelope() const override
Definition: WaveTrack.cpp:2840
void EraseChannelAttachments(size_t index)
Erase all attachments for a given index.
Definition: WaveTrack.cpp:452
void PasteWaveTrack(double t0, const WaveTrack &other, bool merge)
Definition: WaveTrack.cpp:1691
double mLegacyProjectFileOffset
Definition: WaveTrack.h:833
size_t GetBestBlockSize(sampleCount t) const
Definition: WaveTrack.cpp:2244
sampleFormat mLegacyFormat
used only during deserialization
Definition: WaveTrack.h:778
Holder EmptyCopy(size_t nChannels, const SampleBlockFactoryPtr &pFactory={}) const
Definition: WaveTrack.cpp:994
std::shared_ptr< WaveTrack > Holder
Definition: WaveTrack.h:247
static double ProjectNyquistFrequency(const AudacityProject &project)
Definition: WaveTrack.cpp:356
void MergeChannelAttachments(WaveTrack &&other)
Definition: WaveTrack.cpp:429
IntervalHolder GetRightmostClip()
Definition: WaveTrack.cpp:2558
bool CanInsertClip(const Interval &clip, double &slideBy, double tolerance) const
Definition: WaveTrack.cpp:3095
WaveClipHolders mClips
Definition: WaveTrack.h:775
WaveTrack(CreateToken &&, const SampleBlockFactoryPtr &pFactory, sampleFormat format, double rate)
Don't call directly, but use Create.
Definition: WaveTrack.cpp:494
void ClearAndPaste(double t0, double t1, const WaveTrack &src, bool preserve=true, bool merge=true, const TimeWarper *effectWarper=nullptr, bool clearByTrimming=false)
Definition: WaveTrack.cpp:1201
void RemoveInterval(const IntervalHolder &interval)
Definition: WaveTrack.cpp:3228
std::shared_ptr< const Interval > IntervalConstHolder
Definition: WaveTrack.h:211
sampleCount GetVisibleSampleCount() const
Definition: WaveTrack.cpp:890
Track::Holder Copy(double t0, double t1, bool forClipboard=true) const override
Create new tracks and don't modify this track.
Definition: WaveTrack.cpp:1078
virtual ~WaveTrack()
Definition: WaveTrack.cpp:559
IntervalHolder GetClip(size_t iInterval)
Definition: WaveTrack.cpp:691
void HandleClear(double t0, double t1, bool addCutLines, bool split, bool clearByTrimming=false)
Definition: WaveTrack.cpp:1532
WaveClipHolder DoCreateClip(double offset=.0, const wxString &name=wxEmptyString) const
Create a new clip that can be inserted later into the track.
Definition: WaveTrack.cpp:2969
wxString MakeClipCopyName(const wxString &originalName) const
Definition: WaveTrack.cpp:774
IntervalHolder GetLeftmostClip()
Definition: WaveTrack.cpp:2542
IntervalHolder GetIntervalAtTime(double t)
Definition: WaveTrack.cpp:201
virtual size_t NChannels() const =0
Report the number of channels.
sampleCount TimeToLongSamples(double t0) const
double SnapToSample(double t) const
static const TypeInfo & ClassTypeInfo()
Definition: SampleTrack.cpp:60
static XMLMethodRegistry & Get()
Get the unique instance.
void CallWriters(const Host &host, XMLWriter &writer)
This class is an interface which should be implemented by classes which wish to be able to load and s...
Definition: XMLTagHandler.h:42
Base class for XMLFileWriter and XMLStringWriter that provides the general functionality for creating...
Definition: XMLWriter.h:25
virtual void StartTag(const wxString &name)
Definition: XMLWriter.cpp:79
void WriteAttr(const wxString &name, const Identifier &value)
Definition: XMLWriter.h:36
virtual void EndTag(const wxString &name)
Definition: XMLWriter.cpp:102
Positions or offsets within audio files need a wide type.
Definition: SampleCount.h:19
double as_double() const
Definition: SampleCount.h:46
#define lrint(dbl)
Definition: float_cast.h:169
ChannelType
Mutually exclusive channel classifications.
Services * Get()
Fetch the global instance, or nullptr if none is yet installed.
Definition: BasicUI.cpp:202
template struct REGISTRIES_API Cloneable<>
PROJECT_RATE_API sampleFormat SampleFormatChoice()
WAVE_TRACK_API ClipPointers SortedClipArray(WaveChannel &channel)
Get clips sorted by play start time.
std::vector< std::vector< float > > Duplicate(const std::vector< float > &audio, size_t numChannels)
double GetRate(const Track &track)
Definition: TimeTrack.cpp:182
bool AreAligned(const WaveTrack::IntervalConstHolders &a, const WaveTrack::IntervalConstHolders &b)
Definition: WaveTrack.cpp:324
bool ClipsAreUnique(const WaveClipHolders &clips)
Definition: WaveTrack.cpp:3209
static const ChannelGroup::Attachments::RegisteredFactory waveTrackDataFactory
Definition: WaveTrack.cpp:247
WaveTrack::IntervalHolder GetRenderedCopy(const WaveTrack::IntervalHolder &pInterval, const std::function< void(double)> &reportProgress, const SampleBlockFactoryPtr &factory, sampleFormat format)
Definition: WaveTrack.cpp:76
Track::LinkType ToLinkType(int value)
Definition: WaveTrack.cpp:345
const char * end(const char *str) noexcept
Definition: StringUtils.h:106
const char * begin(const char *str) noexcept
Definition: StringUtils.h:101
void copy(const T *src, T *dst, int32_t n)
Definition: VectorOps.h:40
float *const * Get() const
A convenient base class defining abstract virtual Clone() for a given kind of pointer.
Definition: ClientData.h:50
"finally" as in The C++ Programming Language, 4th ed., p. 358 Useful for defining ad-hoc RAII actions...
Definition: MemoryX.h:175
A convenience for use with range-for.
Definition: IteratorX.h:39
@ Deserialized
being read from project file
Definition: WaveTrack.h:76
@ New
newly created and empty
Definition: WaveTrack.h:75
@ Inserted
(partly) copied from another clip, or moved from a track
Definition: WaveTrack.h:77
WaveTrackData & operator=(const WaveTrackData &)=delete