Audacity 3.2.0
Track.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 Track.cpp
6
7 Dominic Mazzoni
8
9*******************************************************************//*******************************************************************/
23
24#include "Track.h"
25
26#include <algorithm>
27#include <numeric>
28
29#include <float.h>
30#include <wx/file.h>
31#include <wx/textfile.h>
32#include <wx/log.h>
33
34#include "BasicUI.h"
35#include "Project.h"
36
38
39#ifdef _MSC_VER
40//Disable truncation warnings
41#pragma warning( disable : 4786 )
42#endif
43
45: vrulerSize(36,0)
46{
47 mSelected = false;
48
49 mIndex = 0;
50
51 mOffset = 0.0;
52
54}
55
56Track::Track(const Track &orig)
57: vrulerSize( orig.vrulerSize )
58{
59 mIndex = 0;
60 Init(orig);
61 mOffset = orig.mOffset;
62}
63
64// Copy all the track properties except the actual contents
65void Track::Init(const Track &orig)
66{
67 mId = orig.mId;
68
69 mName = orig.mName;
70
71 mSelected = orig.mSelected;
72
73 // Deep copy of any group data
75 std::make_unique<ChannelGroupData>(*orig.mpGroupData) : nullptr;
76
77 mChannel = orig.mChannel;
78}
79
80void Track::SetName( const wxString &n )
81{
82 if ( mName != n ) {
83 mName = n;
84 Notify();
85 }
86}
87
89{
90 if (mSelected != s) {
91 mSelected = s;
92 auto pList = mList.lock();
93 if (pList)
94 pList->SelectionEvent( SharedPointer() );
95 }
96}
97
98void Track::EnsureVisible( bool modifyState )
99{
100 auto pList = mList.lock();
101 if (pList)
102 pList->EnsureVisibleEvent( SharedPointer(), modifyState );
103}
104
105void Track::Merge(const Track &orig)
106{
107 mSelected = orig.mSelected;
108}
109
111{
112 // invoke "virtual constructor" to copy track object proper:
113 auto result = Clone();
114
115 AttachedTrackObjects::ForEach([&](auto &attachment){
116 // Copy view state that might be important to undo/redo
117 attachment.CopyTo( *result );
118 });
119
120 return result;
121}
122
124{
125}
126
127
129{
130 wxASSERT(mList.lock() == NULL || this == mNode.first->get());
131 return mNode;
132}
133
135(const std::weak_ptr<TrackList> &list, TrackNodePointer node)
136{
137 // BUG: When using this function to clear an owner, we may need to clear
138 // focused track too. Otherwise focus could remain on an invisible (or deleted) track.
139 mList = list;
140 mNode = node;
141}
142
144{
145 return mIndex;
146}
147
148void Track::SetIndex(int index)
149{
150 mIndex = index;
151}
152
153void Track::SetLinkType(LinkType linkType, bool completeList)
154{
155 auto pList = mList.lock();
156 if (pList && !pList->mPendingUpdates.empty()) {
157 auto orig = pList->FindById( GetId() );
158 if (orig && orig != this) {
159 orig->SetLinkType(linkType);
160 return;
161 }
162 }
163
164 DoSetLinkType(linkType, completeList);
165
166 if (pList) {
167 pList->RecalcPositions(mNode);
168 pList->ResizingEvent(mNode);
169 }
170}
171
173{
174 if (!mpGroupData)
175 // Make on demand
176 mpGroupData = std::make_unique<ChannelGroupData>();
177 return *mpGroupData;
178}
179
181{
182 auto pTrack = this;
183 if (auto pList = GetOwner())
184 if (auto pLeader = *pList->FindLeader(pTrack))
185 pTrack = pLeader;
186 // May make on demand
187 return pTrack->MakeGroupData();
188}
189
191{
192 // May make group data on demand, but consider that logically const
193 return const_cast<Track *>(this)->GetGroupData();
194}
195
196void Track::DoSetLinkType(LinkType linkType, bool completeList)
197{
198 auto oldType = GetLinkType();
199 if (linkType == oldType)
200 // No change
201 return;
202
203 if (oldType == LinkType::None) {
204 // Becoming linked
205
206 // First ensure there is no partner
207 if (auto partner = GetLinkedTrack())
208 partner->mpGroupData.reset();
209 assert(!GetLinkedTrack());
210
211 // Change the link type
212 MakeGroupData().mLinkType = linkType;
213
214 // If this acquired a partner, it loses any old group data
215 if (auto partner = GetLinkedTrack())
216 partner->mpGroupData.reset();
217 }
218 else if (linkType == LinkType::None) {
219 // Becoming unlinked
220 assert(mpGroupData);
221 if (HasLinkedTrack()) {
222 // Make independent copy of group data in the partner, which should
223 // have had none
224 auto partner = GetLinkedTrack();
225 assert(!partner->mpGroupData);
226 partner->mpGroupData =
227 std::make_unique<ChannelGroupData>(*mpGroupData);
228 partner->mpGroupData->mLinkType = LinkType::None;
229 }
230 mpGroupData->mLinkType = LinkType::None;
231 }
232 else {
233 // Remaining linked, changing the type
234 assert(mpGroupData);
235 MakeGroupData().mLinkType = linkType;
236 }
237
238 // Assertion checks only in a debug build, does not have side effects!
239 assert(LinkConsistencyCheck(completeList));
240}
241
243{
244 mChannel = c;
245}
246
248{
249 auto pList = mList.lock();
250 if (!pList)
251 return nullptr;
252
253 if (!pList->isNull(mNode)) {
254 if (HasLinkedTrack()) {
255 auto next = pList->getNext( mNode );
256 if ( !pList->isNull( next ) )
257 return next.first->get();
258 }
259
260 if (mNode.first != mNode.second->begin()) {
261 auto prev = pList->getPrev( mNode );
262 if ( !pList->isNull( prev ) ) {
263 auto track = prev.first->get();
264 if (track && track->HasLinkedTrack())
265 return track;
266 }
267 }
268 }
269
270 return nullptr;
271}
272
273bool Track::HasLinkedTrack() const noexcept
274{
275 return mpGroupData && mpGroupData->mLinkType != LinkType::None;
276}
277
278void Track::Notify( int code )
279{
280 auto pList = mList.lock();
281 if (pList)
282 pList->DataEvent( SharedPointer(), code );
283}
284
285void Track::SyncLockAdjust(double oldT1, double newT1)
286{
287 if (newT1 > oldT1) {
288 // Insert space within the track
289
290 if (oldT1 > GetEndTime())
291 return;
292
293 auto tmp = Cut(oldT1, GetEndTime());
294
295 Paste(newT1, tmp.get());
296 }
297 else if (newT1 < oldT1) {
298 // Remove from the track
299 Clear(newT1, oldT1);
300 }
301}
302
304{
305 DoSetMute(orig.DoGetMute());
306 DoSetSolo(orig.DoGetSolo());
307 AudioTrack::Init( orig );
308}
309
310void PlayableTrack::Merge( const Track &orig )
311{
312 auto pOrig = dynamic_cast<const PlayableTrack *>(&orig);
313 wxASSERT( pOrig );
314 DoSetMute(pOrig->DoGetMute());
315 DoSetSolo(pOrig->DoGetSolo());
316 AudioTrack::Merge( *pOrig );
317}
318
320{
321 if ( DoGetMute() != m ) {
322 DoSetMute(m);
323 Notify();
324 }
325}
326
328{
329 if ( DoGetSolo() != s ) {
330 DoSetSolo(s);
331 Notify();
332 }
333}
334
336{
337 return mMute.load(std::memory_order_relaxed);
338}
339
341{
342 mMute.store(value, std::memory_order_relaxed);
343}
344
346{
347 return mSolo.load(std::memory_order_relaxed);
348}
349
351{
352 mSolo.store(value, std::memory_order_relaxed);
353}
354
355// Serialize, not with tags of its own, but as attributes within a tag.
357{
358 xmlFile.WriteAttr(wxT("mute"), DoGetMute());
359 xmlFile.WriteAttr(wxT("solo"), DoGetSolo());
361}
362
363// Return true iff the attribute is recognized.
364bool PlayableTrack::HandleXMLAttribute(const std::string_view &attr, const XMLAttributeValueView &value)
365{
366 long nValue;
367
368 if (attr == "mute" && value.TryGet(nValue)) {
369 DoSetMute(nValue != 0);
370 return true;
371 }
372 else if (attr == "solo" && value.TryGet(nValue)) {
373 DoSetSolo(nValue != 0);
374 return true;
375 }
376
377 return AudioTrack::HandleXMLAttribute(attr, value);
378}
379
380bool Track::Any() const
381 { return true; }
382
384 { return GetSelected(); }
385
386bool Track::IsLeader() const
387{
388 return !GetLinkedTrack() || HasLinkedTrack();
389}
390
392 { return IsSelected() && IsLeader(); }
393
395(const Track *n, Track *dest)
396{
397 if (dest) {
398 dest->SetChannel(n->GetChannel());
399 dest->mpGroupData = n->mpGroupData ?
400 std::make_unique<ChannelGroupData>(*n->mpGroupData) : nullptr;
401 dest->SetName(n->GetName());
402 }
403}
404
405bool Track::LinkConsistencyFix(bool doFix, bool completeList)
406{
407 // Sanity checks for linked tracks; unsetting the linked property
408 // doesn't fix the problem, but it likely leaves us with orphaned
409 // sample blocks instead of much worse problems.
410 bool err = false;
411 if (completeList && HasLinkedTrack()) {
412 if (auto link = GetLinkedTrack()) {
413 // A linked track's partner should never itself be linked
414 if (link->HasLinkedTrack()) {
415 err = true;
416 if (doFix) {
417 wxLogWarning(
418 L"Left track %s had linked right track %s with extra right "
419 "track link.\n Removing extra link from right track.",
420 GetName(), link->GetName());
421 link->SetLinkType(LinkType::None);
422 }
423 }
424
425 // Channels should be left and right
426 if ( !( (GetChannel() == Track::LeftChannel &&
427 link->GetChannel() == Track::RightChannel) ||
429 link->GetChannel() == Track::LeftChannel) ) ) {
430 err = true;
431 if (doFix) {
432 wxLogWarning(
433 L"Track %s and %s had left/right track links out of order. "
434 "Setting tracks to not be linked.",
435 GetName(), link->GetName());
437 }
438 }
439 }
440 else {
441 err = true;
442 if (doFix) {
443 wxLogWarning(
444 L"Track %s had link to NULL track. Setting it to not be linked.",
445 GetName());
447 }
448 }
449 }
450 return ! err;
451}
452
453// TrackList
454//
455// The TrackList sends events whenever certain updates occur to the list it
456// is managing. Any other classes that may be interested in get these updates
457// should use TrackList::Subscribe().
458//
459
460// same value as in the default constructed TrackId:
461long TrackList::sCounter = -1;
462
464 [](AudacityProject &project) { return TrackList::Create( &project ); }
465};
466
468{
469 return project.AttachedObjects::Get< TrackList >( key );
470}
471
473{
474 return Get( const_cast< AudacityProject & >( project ) );
475}
476
478 : mOwner{ pOwner }
479{
480}
481
482// Factory function
483std::shared_ptr<TrackList> TrackList::Create( AudacityProject *pOwner )
484{
485 return std::make_shared<TrackList>( pOwner );
486}
487
488#if 0
490{
491 if (this != &that) {
492 this->Clear();
493 Swap(that);
494 }
495 return *this;
496}
497#endif
498
500{
501 auto SwapLOTs = [](
502 ListOfTracks &a, const std::weak_ptr< TrackList > &aSelf,
503 ListOfTracks &b, const std::weak_ptr< TrackList > &bSelf )
504 {
505 a.swap(b);
506 for (auto it = a.begin(), last = a.end(); it != last; ++it)
507 (*it)->SetOwner(aSelf, {it, &a});
508 for (auto it = b.begin(), last = b.end(); it != last; ++it)
509 (*it)->SetOwner(bSelf, {it, &b});
510 };
511
512 const auto self = shared_from_this();
513 const auto otherSelf = that.shared_from_this();
514 SwapLOTs( *this, self, that, otherSelf );
515 SwapLOTs( this->mPendingUpdates, self, that.mPendingUpdates, otherSelf );
516 mUpdaters.swap(that.mUpdaters);
517}
518
520{
521 Clear(false);
522}
523
524wxString TrackList::MakeUniqueTrackName(const wxString& baseTrackName) const
525{
526 int n = 1;
527 while(true)
528 {
529 auto name = wxString::Format("%s %d", baseTrackName, n++);
530
531 bool found {false};
532 for(const auto track : Any())
533 {
534 if(track->GetName() == name)
535 {
536 found = true;
537 break;
538 }
539 }
540 if(!found)
541 return name;
542 }
543}
544
546{
547 if ( isNull( node ) )
548 return;
549
550 Track *t;
551 int i = 0;
552
553 auto prev = getPrev( node );
554 if ( !isNull( prev ) ) {
555 t = prev.first->get();
556 i = t->GetIndex() + 1;
557 }
558
559 const auto theEnd = end();
560 for (auto n = Find( node.first->get() ); n != theEnd; ++n) {
561 t = *n;
562 t->SetIndex(i++);
563 }
564
566}
567
569{
570 BasicUI::CallAfter( [wThis = weak_from_this(), event = std::move(event)]{
571 if (auto pThis = wThis.lock())
572 pThis->Publish(event);
573 } );
574}
575
576void TrackList::SelectionEvent( const std::shared_ptr<Track> &pTrack )
577{
579}
580
581void TrackList::DataEvent( const std::shared_ptr<Track> &pTrack, int code )
582{
583 QueueEvent({
584 TrackListEvent::TRACK_DATA_CHANGE, pTrack, code });
585}
586
588 const std::shared_ptr<Track> &pTrack, bool modifyState )
589{
591 pTrack, static_cast<int>(modifyState) });
592}
593
595{
596 QueueEvent({ TrackListEvent::PERMUTED, *node.first });
597}
598
600{
601 QueueEvent({
603 node.second && node.first != node.second->end()
604 ? *node.first
605 : nullptr
606 });
607}
608
610{
611 QueueEvent({ TrackListEvent::ADDITION, *node.first });
612}
613
615{
616 QueueEvent({ TrackListEvent::RESIZING, *node.first });
617}
618
621{
622 auto it = const_cast<TrackList*>(this)->getEnd();
623 return {
624 { it, it, it, &Track::Any },
625 { it, it, it, &Track::Any }
626 };
627}
628
631{
632 auto iter = Find(pTrack);
633 while( *iter && ! ( *iter )->IsLeader() )
634 --iter;
635 return iter.Filter( &Track::IsLeader );
636}
637
639{
640 if (!track.HasLinkedTrack())
641 return false;
642 auto pOwner = track.GetOwner();
643 if (!pOwner)
644 return false;
645 auto pPartner = pOwner->GetNext(&track, false);
646 if (!pPartner)
647 return false;
648
649 // Swap channels, avoiding copying of GroupData
650 auto pData = move(track.mpGroupData);
651 assert(pData);
652 pOwner->MoveUp(pPartner);
653 pPartner->mpGroupData = move(pData);
654 pPartner->SetChannel(Track::LeftChannel);
656 return true;
657}
658
659void TrackList::Permute(const std::vector<TrackNodePointer> &permutation)
660{
661 for (const auto iter : permutation) {
662 ListOfTracks::value_type track = *iter.first;
663 erase(iter.first);
664 Track *pTrack = track.get();
665 pTrack->SetOwner(shared_from_this(),
666 { insert(ListOfTracks::end(), track), this });
667 }
668 auto n = getBegin();
671}
672
674{
675 // Linear search. Tracks in a project are usually very few.
676 // Search only the non-pending tracks.
677 auto it = std::find_if( ListOfTracks::begin(), ListOfTracks::end(),
678 [=](const ListOfTracks::value_type &ptr){ return ptr->GetId() == id; } );
679 if (it == ListOfTracks::end())
680 return {};
681 return it->get();
682}
683
684Track *TrackList::DoAddToHead(const std::shared_ptr<Track> &t)
685{
686 Track *pTrack = t.get();
687 push_front(ListOfTracks::value_type(t));
688 auto n = getBegin();
689 pTrack->SetOwner(shared_from_this(), n);
690 pTrack->SetId( TrackId{ ++sCounter } );
692 AdditionEvent(n);
693 return front().get();
694}
695
696Track *TrackList::DoAdd(const std::shared_ptr<Track> &t)
697{
698 push_back(t);
699
700 auto n = getPrev( getEnd() );
701
702 t->SetOwner(shared_from_this(), n);
703 t->SetId( TrackId{ ++sCounter } );
705 AdditionEvent(n);
706 return back().get();
707}
708
709auto TrackList::Replace(Track * t, const ListOfTracks::value_type &with) ->
710 ListOfTracks::value_type
711{
712 ListOfTracks::value_type holder;
713 if (t && with) {
714 auto node = t->GetNode();
715 t->SetOwner({}, {});
716
717 holder = *node.first;
718
719 Track *pTrack = with.get();
720 *node.first = with;
721 pTrack->SetOwner(shared_from_this(), node);
722 pTrack->SetId( t->GetId() );
723 RecalcPositions(node);
724
725 DeletionEvent(node);
726 AdditionEvent(node);
727 }
728 return holder;
729}
730
732{
733 auto list = track.mList.lock();
734 if (list.get() == this)
735 {
736 auto channels = TrackList::Channels(&track);
737 for (auto c : channels)
738 {
739 c->SetLinkType(Track::LinkType::None);
740 c->SetChannel(Track::ChannelType::MonoChannel);
741 }
742 }
743 else
745}
746
747bool TrackList::MakeMultiChannelTrack(Track& track, int nChannels, bool aligned)
748{
749 if (nChannels != 2)
750 return false;
751
752 auto list = track.mList.lock();
753 if (list.get() == this)
754 {
755 if (*list->FindLeader(&track) != &track)
756 return false;
757
758 auto first = list->Find(&track);
759 auto canLink = [&]() -> bool {
760 int count = nChannels;
761 for (auto it = first, end = TrackList::end(); it != end && count; ++it)
762 {
763 if ((*it)->HasLinkedTrack())
764 return false;
765 --count;
766 }
767 return count == 0;
768 }();
769
770 if (!canLink)
771 return false;
772
773 (*first)->SetChannel(Track::LeftChannel);
774 auto second = std::next(first);
775 (*second)->SetChannel(Track::RightChannel);
776 (*first)->SetLinkType(aligned ? Track::LinkType::Aligned : Track::LinkType::Group);
777 }
778 else
780 return true;
781}
782
784{
785 auto result = getEnd();
786 if (t) {
787 auto node = t->GetNode();
788 t->SetOwner({}, {});
789
790 if ( !isNull( node ) ) {
791 ListOfTracks::value_type holder = *node.first;
792
793 result = getNext( node );
794 erase(node.first);
795 if ( !isNull( result ) )
796 RecalcPositions(result);
797
798 DeletionEvent(result);
799 }
800 }
801 return result;
802}
803
804void TrackList::Clear(bool sendEvent)
805{
806 // Null out the back-pointers to this in tracks, in case there
807 // are outstanding shared_ptrs to those tracks, making them outlive
808 // the temporary ListOfTracks below.
809 for ( auto pTrack: *this )
810 pTrack->SetOwner( {}, {} );
811 for ( auto pTrack: mPendingUpdates )
812 pTrack->SetOwner( {}, {} );
813
814 ListOfTracks tempList;
815 tempList.swap( *this );
816
817 ListOfTracks updating;
818 updating.swap( mPendingUpdates );
819
820 mUpdaters.clear();
821
822 if (sendEvent)
824}
825
827Track *TrackList::GetNext(Track * t, bool linked) const
828{
829 if (t) {
830 auto node = t->GetNode();
831 if ( !isNull( node ) ) {
832 if ( linked && t->HasLinkedTrack() )
833 node = getNext( node );
834
835 if ( !isNull( node ) )
836 node = getNext( node );
837
838 if ( !isNull( node ) )
839 return node.first->get();
840 }
841 }
842
843 return nullptr;
844}
845
846Track *TrackList::GetPrev(Track * t, bool linked) const
847{
848 if (t) {
849 TrackNodePointer prev;
850 auto node = t->GetNode();
851 if ( !isNull( node ) ) {
852 // linked is true and input track second in team?
853 if (linked) {
854 prev = getPrev( node );
855 if( !isNull( prev ) &&
856 !t->HasLinkedTrack() && t->GetLinkedTrack() )
857 // Make it the first
858 node = prev;
859 }
860
861 prev = getPrev( node );
862 if ( !isNull( prev ) ) {
863 // Back up once
864 node = prev;
865
866 // Back up twice sometimes when linked is true
867 if (linked) {
868 prev = getPrev( node );
869 if( !isNull( prev ) &&
870 !(*node.first)->HasLinkedTrack() && (*node.first)->GetLinkedTrack() )
871 node = prev;
872 }
873
874 return node.first->get();
875 }
876 }
877 }
878
879 return nullptr;
880}
881
883{
884 return GetPrev(t, true) != NULL;
885}
886
888{
889 return GetNext(t, true) != NULL;
890}
891
892// This is used when you want to swap the channel group starting
893// at s1 with that starting at s2.
894// The complication is that the tracks are stored in a single
895// linked list.
897{
898 // if a null pointer is passed in, we want to know about it
899 wxASSERT(!isNull(s1));
900 wxASSERT(!isNull(s2));
901
902 // Deal with first track in each team
903 s1 = ( * FindLeader( s1.first->get() ) )->GetNode();
904 s2 = ( * FindLeader( s2.first->get() ) )->GetNode();
905
906 // Safety check...
907 if (s1 == s2)
908 return;
909
910 // Be sure s1 is the earlier iterator
911 if ((*s1.first)->GetIndex() >= (*s2.first)->GetIndex())
912 std::swap(s1, s2);
913
914 // For saving the removed tracks
915 using Saved = std::vector< ListOfTracks::value_type >;
916 Saved saved1, saved2;
917
918 auto doSave = [&] ( Saved &saved, TrackNodePointer &s ) {
919 size_t nn = Channels( s.first->get() ).size();
920 saved.resize( nn );
921 // Save them in backwards order
922 while( nn-- )
923 saved[nn] = *s.first, s.first = erase(s.first);
924 };
925
926 doSave( saved1, s1 );
927 // The two ranges are assumed to be disjoint but might abut
928 const bool same = (s1 == s2);
929 doSave( saved2, s2 );
930 if (same)
931 // Careful, we invalidated s1 in the second doSave!
932 s1 = s2;
933
934 // Reinsert them
935 auto doInsert = [&] ( Saved &saved, TrackNodePointer &s ) {
936 Track *pTrack;
937 for (auto & pointer : saved)
938 pTrack = pointer.get(),
939 // Insert before s, and reassign s to point at the new node before
940 // old s; which is why we saved pointers in backwards order
941 pTrack->SetOwner(shared_from_this(),
942 s = { insert(s.first, pointer), this } );
943 };
944 // This does not invalidate s2 even when it equals s1:
945 doInsert( saved2, s1 );
946 // Even if s2 was same as s1, this correctly inserts the saved1 range
947 // after the saved2 range, when done after:
948 doInsert( saved1, s2 );
949
950 // Now correct the Index in the tracks, and other things
951 RecalcPositions(s1);
953}
954
956{
957 if (t) {
958 Track *p = GetPrev(t, true);
959 if (p) {
960 SwapNodes(p->GetNode(), t->GetNode());
961 return true;
962 }
963 }
964
965 return false;
966}
967
969{
970 if (t) {
971 Track *n = GetNext(t, true);
972 if (n) {
973 SwapNodes(t->GetNode(), n->GetNode());
974 return true;
975 }
976 }
977
978 return false;
979}
980
981bool TrackList::Contains(const Track * t) const
982{
983 return make_iterator_range( *this ).contains( t );
984}
985
987{
988 return begin() == end();
989}
990
991size_t TrackList::size() const
992{
993 int cnt = 0;
994
995 if (!empty())
996 cnt = getPrev( getEnd() ).first->get()->GetIndex() + 1;
997
998 return cnt;
999}
1000
1001namespace {
1002 // Abstract the common pattern of the following three member functions
1003 inline double Accumulate
1004 (const TrackList &list,
1005 double (Track::*memfn)() const,
1006 double ident,
1007 const double &(*combine)(const double&, const double&))
1008 {
1009 // Default the answer to zero for empty list
1010 if (list.empty()) {
1011 return 0.0;
1012 }
1013
1014 // Otherwise accumulate minimum or maximum of track values
1015 return list.Any().accumulate(ident, combine, memfn);
1016 }
1017}
1018
1020{
1021 return Accumulate(*this, &Track::GetOffset, DBL_MAX, std::min);
1022}
1023
1025{
1026 return Accumulate(*this, &Track::GetStartTime, DBL_MAX, std::min);
1027}
1028
1030{
1031 return Accumulate(*this, &Track::GetEndTime, -DBL_MAX, std::max);
1032}
1033
1034std::shared_ptr<Track>
1036{
1037 std::shared_ptr<Track> pTrack;
1038 if (src) {
1039 pTrack = src->Clone(); // not duplicate
1040 // Share the satellites with the original, though they do not point back
1041 // to the pending track
1042 ((AttachedTrackObjects&)*pTrack) = *src; // shallow copy
1043 }
1044
1045 if (pTrack) {
1046 mUpdaters.push_back( updater );
1047 mPendingUpdates.push_back( pTrack );
1048 auto n = mPendingUpdates.end();
1049 --n;
1050 pTrack->SetOwner(shared_from_this(), {n, &mPendingUpdates});
1051 }
1052
1053 return pTrack;
1054}
1055
1056void TrackList::RegisterPendingNewTrack( const std::shared_ptr<Track> &pTrack )
1057{
1058 Add<Track>( pTrack );
1059 pTrack->SetId( TrackId{} );
1060}
1061
1063{
1064 auto pUpdater = mUpdaters.begin();
1065 for (const auto &pendingTrack : mPendingUpdates) {
1066 // Copy just a part of the track state, according to the update
1067 // function
1068 const auto &updater = *pUpdater;
1069 auto src = FindById( pendingTrack->GetId() );
1070 if (pendingTrack && src) {
1071 if (updater)
1072 updater( *pendingTrack, *src );
1073 pendingTrack->DoSetLinkType(src->GetLinkType());
1074 }
1075 ++pUpdater;
1076 }
1077}
1078
1081{
1082 for (const auto &pTrack: mPendingUpdates)
1083 pTrack->SetOwner( {}, {} );
1084 mPendingUpdates.clear();
1085 mUpdaters.clear();
1086
1087 if (pAdded)
1088 pAdded->clear();
1089
1090 // To find the first node that remains after the first deleted one
1091 TrackNodePointer node;
1092 bool foundNode = false;
1093
1094 for (auto it = ListOfTracks::begin(), stop = ListOfTracks::end();
1095 it != stop;) {
1096 if (it->get()->GetId() == TrackId{}) {
1097 do {
1098 if (pAdded)
1099 pAdded->push_back( *it );
1100 (*it)->SetOwner( {}, {} );
1101 it = erase( it );
1102 }
1103 while (it != stop && it->get()->GetId() == TrackId{});
1104
1105 if (!foundNode && it != stop) {
1106 node = (*it)->GetNode();
1107 foundNode = true;
1108 }
1109 }
1110 else
1111 ++it;
1112 }
1113
1114 if (!empty()) {
1116 DeletionEvent( node );
1117 }
1118}
1119
1122{
1123 bool result = false;
1124
1125 ListOfTracks additions;
1126 ListOfTracks updates;
1127 {
1128 // Always clear, even if one of the update functions throws
1129 auto cleanup = finally( [&] { ClearPendingTracks( &additions ); } );
1131 updates.swap( mPendingUpdates );
1132 }
1133
1134 // Remaining steps must be No-fail-guarantee so that this function
1135 // gives Strong-guarantee
1136
1137 std::vector< std::shared_ptr<Track> > reinstated;
1138
1139 for (auto &pendingTrack : updates) {
1140 if (pendingTrack) {
1141 pendingTrack->AttachedTrackObjects::ForEach([&](auto &attachment){
1142 attachment.Reparent( pendingTrack );
1143 });
1144 auto src = FindById( pendingTrack->GetId() );
1145 if (src)
1146 this->Replace(src, pendingTrack), result = true;
1147 else
1148 // Perhaps a track marked for pending changes got deleted by
1149 // some other action. Recreate it so we don't lose the
1150 // accumulated changes.
1151 reinstated.push_back(pendingTrack);
1152 }
1153 }
1154
1155 // If there are tracks to reinstate, append them to the list.
1156 for (auto &pendingTrack : reinstated)
1157 if (pendingTrack)
1158 this->Add( pendingTrack ), result = true;
1159
1160 // Put the pending added tracks back into the list, preserving their
1161 // positions.
1162 bool inserted = false;
1163 ListOfTracks::iterator first;
1164 for (auto &pendingTrack : additions) {
1165 if (pendingTrack) {
1166 auto iter = ListOfTracks::begin();
1167 std::advance( iter, pendingTrack->GetIndex() );
1168 iter = ListOfTracks::insert( iter, pendingTrack );
1169 pendingTrack->SetOwner( shared_from_this(), {iter, this} );
1170 pendingTrack->SetId( TrackId{ ++sCounter } );
1171 if (!inserted) {
1172 first = iter;
1173 inserted = true;
1174 }
1175 }
1176 }
1177 if (inserted) {
1178 TrackNodePointer node{first, this};
1179 RecalcPositions(node);
1180 AdditionEvent(node);
1181 result = true;
1182 }
1183
1184 return result;
1185}
1186
1188{
1189 // Linear search. Tracks in a project are usually very few.
1190 auto pList = mList.lock();
1191 if (pList) {
1192 const auto id = GetId();
1193 const auto end = pList->mPendingUpdates.end();
1194 auto it = std::find_if(
1195 pList->mPendingUpdates.begin(), end,
1196 [=](const ListOfTracks::value_type &ptr){ return ptr->GetId() == id; } );
1197 if (it != end)
1198 return *it;
1199 }
1200 return SharedPointer();
1201}
1202
1203std::shared_ptr<const Track> Track::SubstitutePendingChangedTrack() const
1204{
1205 return const_cast<Track*>(this)->SubstitutePendingChangedTrack();
1206}
1207
1208std::shared_ptr<const Track> Track::SubstituteOriginalTrack() const
1209{
1210 auto pList = mList.lock();
1211 if (pList) {
1212 const auto id = GetId();
1213 const auto pred = [=]( const ListOfTracks::value_type &ptr ) {
1214 return ptr->GetId() == id; };
1215 const auto end = pList->mPendingUpdates.end();
1216 const auto it = std::find_if( pList->mPendingUpdates.begin(), end, pred );
1217 if (it != end) {
1218 const auto &list2 = (const ListOfTracks &) *pList;
1219 const auto end2 = list2.end();
1220 const auto it2 = std::find_if( list2.begin(), end2, pred );
1221 if ( it2 != end2 )
1222 return *it2;
1223 }
1224 }
1225 return SharedPointer();
1226}
1227
1229{
1230 static Track::TypeInfo info{
1231 { "generic", "generic", XO("Generic Track") }, false };
1232 return info;
1233}
1234
1236{
1237 return true;
1238}
1239
1241{
1242 return {};
1243}
1244
1246{
1247 return {};
1248}
1249
1250// Serialize, not with tags of its own, but as attributes within a tag.
1252 XMLWriter &xmlFile, bool includeNameAndSelected) const
1253{
1254 if (includeNameAndSelected) {
1255 xmlFile.WriteAttr(wxT("name"), GetName());
1256 xmlFile.WriteAttr(wxT("isSelected"), this->GetSelected());
1257 }
1258 AttachedTrackObjects::ForEach([&](auto &attachment){
1259 attachment.WriteXMLAttributes( xmlFile );
1260 });
1261}
1262
1263// Return true iff the attribute is recognized.
1265 const std::string_view& attr, const XMLAttributeValueView& valueView)
1266{
1267 long nValue = -1;
1268
1269 bool handled = false;
1270 AttachedTrackObjects::ForEach([&](auto &attachment){
1271 handled = handled || attachment.HandleXMLAttribute( attr, valueView );
1272 });
1273 if (handled)
1274 ;
1275 else if (attr == "name") {
1276 SetName(valueView.ToWString());
1277 return true;
1278 }
1279 else if (attr == "isSelected" && valueView.TryGet(nValue)) {
1280 this->SetSelected(nValue != 0);
1281 return true;
1282 }
1283 return false;
1284}
1285
1287{
1288 auto pList = mList.lock();
1289 if (pList) {
1290 pList->RecalcPositions(mNode);
1291 pList->ResizingEvent(mNode);
1292 }
1293}
1294
1296{
1297 static Track::TypeInfo info{
1298 { "audio", "audio", XO("Audio Track") },
1299 false, &Track::ClassTypeInfo() };
1300 return info;
1301}
1302
1304{
1305 static Track::TypeInfo info{
1306 { "playable", "playable", XO("Playable Track") },
1307 false, &AudioTrack::ClassTypeInfo() };
1308 return info;
1309}
1310
1312
1314{
1315 if ( !mPendingUpdates.empty() )
1316 return true;
1317 if (end() != std::find_if(begin(), end(), [](const Track *t){
1318 return t->GetId() == TrackId{};
1319 }))
1320 return true;
1321 return false;
1322}
1323
1325{
1326 return mpGroupData ? mpGroupData->mLinkType : LinkType::None;
1327}
1328
1330{
1331 if (auto owner = GetOwner())
1332 {
1333 auto leader = *owner->FindLeader(this);
1334 return leader != this && leader->GetLinkType() == Track::LinkType::Aligned;
1335 }
1336 return false;
1337}
Toolkit-neutral facade for basic user interface services.
int min(int a, int b)
const TranslatableString name
Definition: Distortion.cpp:82
MessageBoxException for violation of preconditions or assertions.
#define THROW_INCONSISTENCY_EXCEPTION
Throw InconsistencyException, using C++ preprocessor to identify the source code location.
#define XO(s)
Definition: Internat.h:31
IteratorRange< Iterator > make_iterator_range(const Iterator &i1, const Iterator &i2)
Definition: MemoryX.h:423
static CommandHandlerObject & ident(AudacityProject &project)
static const AudacityProject::AttachedObjects::RegisteredFactory key
Definition: Track.cpp:463
declares abstract base class Track, TrackList, and iterators over TrackList
std::pair< ListOfTracks::iterator, ListOfTracks * > TrackNodePointer
Pairs a std::list iterator and a pointer to a list, for comparison purposes.
Definition: Track.h:48
std::list< std::shared_ptr< Track > > ListOfTracks
Definition: Track.h:42
int id
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:89
bool HandleXMLAttribute(const std::string_view &, const XMLAttributeValueView &)
Definition: Track.h:901
static const TypeInfo & ClassTypeInfo()
Definition: Track.cpp:1295
void WriteXMLAttributes(XMLWriter &WXUNUSED(xmlFile)) const
Definition: Track.h:898
Client code makes static instance from a factory of attachments; passes it to Get or Find as a retrie...
Definition: ClientData.h:266
Utility to register hooks into a host class that attach client data.
Definition: ClientData.h:220
void ForEach(const Function &function)
Invoke function on each ClientData object that has been created in this.
Definition: ClientData.h:380
AudioTrack subclass that can also be audibly replayed by the program.
Definition: Track.h:909
bool HandleXMLAttribute(const std::string_view &attr, const XMLAttributeValueView &value)
Definition: Track.cpp:364
std::atomic< bool > mMute
Atomic because it may be read by worker threads in playback.
Definition: Track.h:941
void DoSetSolo(bool value)
Definition: Track.cpp:350
bool DoGetMute() const
Definition: Track.cpp:335
void Init(const PlayableTrack &init)
Definition: Track.cpp:303
void Merge(const Track &init) override
Definition: Track.cpp:310
void DoSetMute(bool value)
Definition: Track.cpp:340
static const TypeInfo & ClassTypeInfo()
Definition: Track.cpp:1303
void WriteXMLAttributes(XMLWriter &xmlFile) const
Definition: Track.cpp:356
void SetSolo(bool s)
Definition: Track.cpp:327
bool DoGetSolo() const
Definition: Track.cpp:345
std::atomic< bool > mSolo
Atomic because it may be read by worker threads in playback.
Definition: Track.h:943
void SetMute(bool m)
Definition: Track.cpp:319
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:225
virtual void Merge(const Track &orig)
Definition: Track.cpp:105
bool GetSelected() const
Definition: Track.h:461
void EnsureVisible(bool modifyState=false)
Definition: Track.cpp:98
Track()
Definition: Track.cpp:44
virtual void SetSelected(bool s)
Definition: Track.cpp:88
virtual void Paste(double WXUNUSED(t), const Track *WXUNUSED(src))=0
virtual Holder Cut(double WXUNUSED(t0), double WXUNUSED(t1))=0
bool IsSelected() const
Definition: Track.cpp:383
virtual void Clear(double WXUNUSED(t0), double WXUNUSED(t1))=0
void SetChannel(ChannelType c) noexcept
Definition: Track.cpp:242
virtual ~Track()
Definition: Track.cpp:123
static void FinishCopy(const Track *n, Track *dest)
Definition: Track.cpp:395
virtual bool SupportsBasicEditing() const
Whether this track type implements cut-copy-paste; by default, true.
Definition: Track.cpp:1235
ChannelType
Definition: Track.h:274
@ LeftChannel
Definition: Track.h:275
@ RightChannel
Definition: Track.h:276
@ MonoChannel
Definition: Track.h:277
virtual double GetStartTime() const =0
static const TypeInfo & ClassTypeInfo()
Definition: Track.cpp:1228
virtual Holder Clone() const =0
bool IsSelectedLeader() const
Definition: Track.cpp:391
virtual void SyncLockAdjust(double oldT1, double newT1)
Definition: Track.cpp:285
double mOffset
Definition: Track.h:440
TrackNodePointer GetNode() const
Retrieve mNode with debug checks.
Definition: Track.cpp:128
virtual bool LinkConsistencyFix(bool doFix=true, bool completeList=true)
Check consistency of channel groups, and maybe fix it.
Definition: Track.cpp:405
ChannelGroupData & GetGroupData()
Definition: Track.cpp:180
std::shared_ptr< Track > SubstitutePendingChangedTrack()
Definition: Track.cpp:1187
std::shared_ptr< const Track > SubstituteOriginalTrack() const
Definition: Track.cpp:1208
std::shared_ptr< TrackList > GetOwner() const
Definition: Track.h:402
bool mSelected
Definition: Track.h:266
void SetLinkType(LinkType linkType, bool completeList=true)
Definition: Track.cpp:153
std::shared_ptr< Subclass > SharedPointer()
Definition: Track.h:290
bool HasLinkedTrack() const noexcept
Returns true for leaders of multichannel groups.
Definition: Track.cpp:273
TrackId GetId() const
Definition: Track.h:280
virtual Holder Duplicate() const
Definition: Track.cpp:110
std::shared_ptr< Track > Holder
Definition: Track.h:361
wxString mName
Definition: Track.h:263
void SetOwner(const std::weak_ptr< TrackList > &list, TrackNodePointer node)
Update mNode when Track is added to TrackList, or removed from it.
Definition: Track.cpp:135
void Notify(int code=-1)
Definition: Track.cpp:278
void SetId(TrackId id)
Definition: Track.h:282
bool LinkConsistencyCheck(bool completeList)
Do the non-mutating part of consistency fix only and return status.
Definition: Track.h:397
bool HandleCommonXMLAttribute(const std::string_view &attr, const XMLAttributeValueView &valueView)
Definition: Track.cpp:1264
void SetIndex(int index)
Definition: Track.cpp:148
wxString GetName() const
Definition: Track.h:458
void Init(const Track &orig)
Definition: Track.cpp:65
virtual ConstIntervals GetIntervals() const
Report times on the track where important intervals begin and end, for UI to snap to.
Definition: Track.cpp:1240
virtual ChannelType GetChannel() const
Definition: Track.h:471
void WriteCommonXMLAttributes(XMLWriter &xmlFile, bool includeNameAndSelected=true) const
Definition: Track.cpp:1251
void AdjustPositions()
Definition: Track.cpp:1286
std::weak_ptr< TrackList > mList
Definition: Track.h:258
virtual double GetOffset() const =0
TrackNodePointer mNode
Holds iterator to self, so that TrackList::Find can be constant-time.
Definition: Track.h:261
ChannelType mChannel
Definition: Track.h:439
virtual double GetEndTime() const =0
std::vector< Interval > Intervals
Definition: Track.h:328
bool IsLeader() const
Definition: Track.cpp:386
Track * GetLinkedTrack() const
Definition: Track.cpp:247
ChannelGroupData & MakeGroupData()
Definition: Track.cpp:172
LinkType
For two tracks describes the type of the linkage.
Definition: Track.h:229
LinkType GetLinkType() const noexcept
Definition: Track.cpp:1324
int mIndex
0-based position of this track in its TrackList
Definition: Track.h:262
bool IsAlignedWithLeader() const
Returns true if the leader track has link type LinkType::Aligned.
Definition: Track.cpp:1329
void DoSetLinkType(LinkType linkType, bool completeList=true)
Definition: Track.cpp:196
std::unique_ptr< ChannelGroupData > mpGroupData
Definition: Track.h:255
std::vector< ConstInterval > ConstIntervals
Definition: Track.h:330
bool Any() const
Definition: Track.cpp:380
void SetName(const wxString &n)
Definition: Track.cpp:80
TrackId mId
Identifies the track only in-session, not persistently.
Definition: Track.h:253
int GetIndex() const
Definition: Track.cpp:143
An in-session identifier of track objects across undo states. It does not persist between sessions.
Definition: Track.h:151
Iterator over only members of a TrackList of the specified subtype, optionally filtered by a predicat...
Definition: Track.h:1000
A flat linked list of tracks supporting Add, Remove, Clear, and Contains, serialization of the list o...
Definition: Track.h:1330
void PermutationEvent(TrackNodePointer node)
Definition: Track.cpp:594
bool MoveUp(Track *t)
Definition: Track.cpp:955
void RegisterPendingNewTrack(const std::shared_ptr< Track > &pTrack)
Definition: Track.cpp:1056
bool empty() const
Definition: Track.cpp:986
bool CanMoveUp(Track *t) const
Definition: Track.cpp:882
static long sCounter
Definition: Track.h:1715
bool MakeMultiChannelTrack(Track &first, int nChannels, bool aligned)
Converts channels to a multichannel track.
Definition: Track.cpp:747
static std::shared_ptr< TrackList > Create(AudacityProject *pOwner)
Definition: Track.cpp:483
double GetEndTime() const
Definition: Track.cpp:1029
bool CanMoveDown(Track *t) const
Definition: Track.cpp:887
std::vector< Updater > mUpdaters
This is in correspondence with mPendingUpdates.
Definition: Track.h:1769
double GetMinOffset() const
Definition: Track.cpp:1019
void ResizingEvent(TrackNodePointer node)
Definition: Track.cpp:614
TrackNodePointer getBegin() const
Definition: Track.h:1673
ListOfTracks::value_type Replace(Track *t, const ListOfTracks::value_type &with)
Definition: Track.cpp:709
iterator end()
Definition: Track.h:1383
wxString MakeUniqueTrackName(const wxString &baseTrackName) const
Returns string that contains baseTrackName, but is guaranteed to be unique among other tracks in that...
Definition: Track.cpp:524
TrackList & operator=(const TrackList &)=delete
TrackKind * Add(const std::shared_ptr< TrackKind > &t)
Definition: Track.h:1556
double GetStartTime() const
Definition: Track.cpp:1024
bool MoveDown(Track *t)
Definition: Track.cpp:968
bool isNull(TrackNodePointer p) const
Definition: Track.h:1667
TrackNodePointer getNext(TrackNodePointer p) const
Move an iterator to the next node, if any; else stay at end.
Definition: Track.h:1678
size_t size() const
Definition: Track.cpp:991
void Swap(TrackList &that)
Definition: Track.cpp:499
void AdditionEvent(TrackNodePointer node)
Definition: Track.cpp:609
void RecalcPositions(TrackNodePointer node)
Definition: Track.cpp:545
auto Find(Track *pTrack) -> TrackIter< TrackType >
Turn a pointer into a TrackIter (constant time); get end iterator if this does not own the track.
Definition: Track.h:1391
auto Any() -> TrackIterRange< TrackType >
Definition: Track.h:1429
TrackNodePointer getEnd() const
Definition: Track.h:1670
bool ApplyPendingTracks()
Definition: Track.cpp:1121
std::function< void(Track &dest, const Track &src) > Updater
Definition: Track.h:1718
bool Contains(const Track *t) const
Mainly a test function. Uses a linear search, so could be slow.
Definition: Track.cpp:981
TrackIterRange< Track > EmptyRange() const
Definition: Track.cpp:619
void EnsureVisibleEvent(const std::shared_ptr< Track > &pTrack, bool modifyState)
Definition: Track.cpp:587
void DataEvent(const std::shared_ptr< Track > &pTrack, int code)
Definition: Track.cpp:581
static TrackList & Get(AudacityProject &project)
Definition: Track.cpp:467
Track * DoAdd(const std::shared_ptr< Track > &t)
Definition: Track.cpp:696
TrackNodePointer Remove(Track *t)
Remove the Track and return an iterator to what followed it.
Definition: Track.cpp:783
static bool SwapChannels(Track &track)
If the given track is one of a pair of channels, swap them.
Definition: Track.cpp:638
iterator begin()
Definition: Track.h:1382
Track * GetNext(Track *t, bool linked=false) const
Return a track in the list that comes after Track t.
Definition: Track.cpp:827
Track * GetPrev(Track *t, bool linked=false) const
Definition: Track.cpp:846
std::shared_ptr< Track > RegisterPendingChangedTrack(Updater updater, Track *src)
Definition: Track.cpp:1035
void ClearPendingTracks(ListOfTracks *pAdded=nullptr)
Definition: Track.cpp:1080
void UpdatePendingTracks()
Definition: Track.cpp:1062
Track * DoAddToHead(const std::shared_ptr< Track > &t)
Definition: Track.cpp:684
Track * FindById(TrackId id)
Definition: Track.cpp:673
void Permute(const std::vector< TrackNodePointer > &permutation)
For use in sorting: assume each iterator points into this list, no duplications.
Definition: Track.cpp:659
TrackIter< Track > FindLeader(Track *pTrack)
Definition: Track.cpp:629
void QueueEvent(TrackListEvent event)
Definition: Track.cpp:568
void SwapNodes(TrackNodePointer s1, TrackNodePointer s2)
Definition: Track.cpp:896
TrackList(const TrackList &that)=delete
void UnlinkChannels(Track &track)
Removes linkage if track belongs to a group.
Definition: Track.cpp:731
bool HasPendingTracks() const
Definition: Track.cpp:1313
void DeletionEvent(TrackNodePointer node={})
Definition: Track.cpp:599
virtual ~TrackList()
Definition: Track.cpp:519
void Clear(bool sendEvent=true)
Make the list empty.
Definition: Track.cpp:804
void SelectionEvent(const std::shared_ptr< Track > &pTrack)
Definition: Track.cpp:576
TrackNodePointer getPrev(TrackNodePointer p) const
Move an iterator to the previous node, if any; else wrap to end.
Definition: Track.h:1688
static auto Channels(TrackType *pTrack) -> TrackIterRange< TrackType >
Definition: Track.h:1533
ListOfTracks mPendingUpdates
Shadow tracks holding append-recording in progress; need to put them into a list so that GetLink() wo...
Definition: Track.h:1767
A view into an attribute value. The class does not take the ownership of the data.
wxString ToWString() const
Convert the view value to wxString.
bool TryGet(bool &value) const noexcept
Try to get a boolean value from the view.
Base class for XMLFileWriter and XMLStringWriter that provides the general functionality for creating...
Definition: XMLWriter.h:26
void WriteAttr(const wxString &name, const Identifier &value)
Definition: XMLWriter.h:37
wxString Find(const FilePath &path)
void CallAfter(Action action)
Schedule an action to be done later, and in the main thread.
Definition: BasicUI.cpp:38
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for, if Traits<Type>::iterated_type is defined.
Definition: PackedArray.h:126
auto begin(const Ptr< Type, BaseDeleter > &p)
Enables range-for, if Traits<Type>::iterated_type is defined.
Definition: PackedArray.h:112
std::optional< LogWindowUpdater > pUpdater
Definition: LogWindow.cpp:53
void swap(std::unique_ptr< Alg_seq > &a, std::unique_ptr< Alg_seq > &b)
Definition: NoteTrack.cpp:753
double Accumulate(const TrackList &list, double(Track::*memfn)() const, double ident, const double &(*combine)(const double &, const double &))
Definition: Track.cpp:1004
LinkType mLinkType
Definition: Track.h:245
virtual ~TrackIntervalData()
Range between two TrackIters, usable in range-for statements, and with Visit member functions.
Definition: Track.h:1161
Notification of changes in individual tracks of TrackList, or of TrackList's composition.
Definition: Track.h:1282
@ RESIZING
Posted when some track changed its height.
Definition: Track.h:1298
@ SELECTION_CHANGE
Posted when the set of selected tracks changes.
Definition: Track.h:1285
@ DELETION
Posted when a track has been deleted from a tracklist. Also posted when one track replaces another.
Definition: Track.h:1305
@ ADDITION
Posted when a track has been added to a tracklist. Also posted when one track replaces another.
Definition: Track.h:1301
@ PERMUTED
Posted when tracks are reordered but otherwise unchanged.
Definition: Track.h:1295
@ TRACK_REQUEST_VISIBLE
Posted when a track needs to be scrolled into view.
Definition: Track.h:1291
@ TRACK_DATA_CHANGE
Posted when certain fields of a track change.
Definition: Track.h:1288