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