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