/********************************************************************** Audacity: A Digital Audio Editor Track.h Dominic Mazzoni **********************************************************************/ #ifndef __AUDACITY_TRACK__ #define __AUDACITY_TRACK__ #include "Audacity.h" #include "MemoryX.h" #include #include #include #include #include #include #include #include "Experimental.h" #include "SampleFormat.h" #include "tracks/ui/CommonTrackPanelCell.h" #include "xml/XMLTagHandler.h" #ifdef __WXMSW__ #pragma warning(disable:4284) #endif class wxTextFile; class DirManager; class Track; class LabelTrack; class TimeTrack; class TrackControls; class TrackVRulerControls; class TrackPanelResizerCell; class WaveTrack; class NoteTrack; class AudacityProject; class ZoomInfo; class SelectHandle; class TimeShiftHandle; WX_DEFINE_USER_EXPORTED_ARRAY(Track*, TrackArray, class AUDACITY_DLL_API); using WaveTrackArray = std::vector < std::shared_ptr< WaveTrack > > ; using WaveTrackConstArray = std::vector < std::shared_ptr < const WaveTrack > >; using NoteTrackArray = std::vector < std::shared_ptr < NoteTrack > >; #if defined(USE_MIDI) class NoteTrack; #endif class TrackList; using ListOfTracks = std::list< std::shared_ptr< Track > >; using TrackNodePointer = std::pair< ListOfTracks::iterator, ListOfTracks* >; inline bool operator == (const TrackNodePointer &a, const TrackNodePointer &b) { return a.second == b.second && a.first == b.first; } inline bool operator != (const TrackNodePointer &a, const TrackNodePointer &b) { return !(a == b); } class ViewInfo; // This is an in-session identifier of track objects across undo states // It does not persist between sessions // Default constructed value is not equal to the id of any track that has ever // been added to a TrackList, or (directly or transitively) copied from such // (A pending additional track that is not yet applied is not considered added) // TrackIds are assigned uniquely across projects class TrackId { public: TrackId() : mValue(-1) {} explicit TrackId (long value) : mValue(value) {} bool operator == (const TrackId &other) const { return mValue == other.mValue; } bool operator != (const TrackId &other) const { return mValue != other.mValue; } // Define this in case you want to key a std::map on TrackId // The operator does not mean anything else bool operator < (const TrackId &other) const { return mValue < other.mValue; } private: long mValue; }; class AUDACITY_DLL_API Track /* not final */ : public CommonTrackPanelCell, public XMLTagHandler { friend class TrackList; friend class TrackListIterator; friend class SyncLockedTracksIterator; // To be TrackDisplay private: TrackId mId; protected: std::weak_ptr mList; TrackNodePointer mNode{}; int mIndex; int mY; int mHeight; wxString mName; wxString mDefaultName; bool mSelected; bool mLinked; bool mMinimized; public: TrackId GetId() const { return mId; } private: void SetId( TrackId id ) { mId = id; } public: // Given a bare pointer, find a shared_ptr. But this is not possible for // a track not owned by any project, so the result can be null. template inline static std::shared_ptr Pointer( Track *t ) { if (t) { auto pList = t->mList.lock(); if (pList) return std::static_pointer_cast(*t->mNode.first); } return {}; } template inline static std::shared_ptr Pointer( const Track *t ) { if (t) { auto pList = t->mList.lock(); if (pList) { std::shared_ptr p{ *t->mNode.first }; // Let you change the type, but not cast away the const return std::static_pointer_cast(p); } } return {}; } // Cause certain overriding tool modes (Zoom; future ones?) to behave // uniformly in all tracks, disregarding track contents. // Do not further override this... std::vector HitTest (const TrackPanelMouseState &, const AudacityProject *pProject) final override; public: // Rather override this for subclasses: virtual std::vector DetailedHitTest (const TrackPanelMouseState &, const AudacityProject *pProject, int currentTool, bool bMultiTool) = 0; mutable wxSize vrulerSize; // Return another, associated TrackPanelCell object that implements the // drop-down, close and minimize buttons, etc. std::shared_ptr GetTrackControl(); // Return another, associated TrackPanelCell object that implements the // mouse actions for the vertical ruler std::shared_ptr GetVRulerControl(); // Return another, associated TrackPanelCell object that implements the // click and drag to resize std::shared_ptr GetResizer(); // This just returns a constant and can be overriden by subclasses // to specify a different height for the case that the track is minimized. virtual int GetMinimizedHeight() const; int GetActualHeight() const { return mHeight; } int GetIndex() const; void SetIndex(int index); int GetY() const; private: // Always maintain a strictly contiguous layout of tracks. // So client code is not permitted to modify this attribute directly. void SetY(int y); // No need yet to make this virtual void DoSetY(int y); public: int GetHeight() const; void SetHeight(int h); protected: virtual void DoSetHeight(int h); public: bool GetMinimized() const; void SetMinimized(bool isMinimized); protected: virtual void DoSetMinimized(bool isMinimized); public: Track *GetLink() const; private: TrackNodePointer GetNode() const; void SetOwner (const std::weak_ptr &list, TrackNodePointer node); // Keep in Track protected: int mChannel; double mOffset; mutable std::shared_ptr mDirManager; public: enum { LeftChannel = 0, RightChannel = 1, MonoChannel = 2 }; enum TrackKindEnum { None, Wave, #if defined(USE_MIDI) Note, #endif Label, Time, All }; enum : unsigned { DefaultHeight = 150 }; Track(const std::shared_ptr &projDirManager); Track(const Track &orig); virtual ~ Track(); void Init(const Track &orig); using Holder = std::unique_ptr; virtual Holder Duplicate() const = 0; // Called when this track is merged to stereo with another, and should // take on some paramaters of its partner. virtual void Merge(const Track &orig); wxString GetName() const { return mName; } void SetName( const wxString &n ) { mName = n; } wxString GetDefaultName() const { return mDefaultName; } void SetDefaultName( const wxString &n ) { mDefaultName = n; } bool GetSelected() const { return mSelected; } virtual void SetSelected(bool s); bool GetLinked () const { return mLinked; } void SetLinked (bool l); private: // No need yet to make this virtual void DoSetLinked(bool l); public: virtual int GetChannel() const { return mChannel;}; virtual double GetOffset() const = 0; void Offset(double t) { SetOffset(GetOffset() + t); } virtual void SetOffset (double o) { mOffset = o; } void SetChannel(int c) { mChannel = c; } virtual void SetPan( float ){ ;} virtual void SetPanFromChannelType(){ ;}; // AS: Note that the dirManager is mutable. This is // mostly to support "Duplicate" of const objects, // but in general, mucking with the dir manager is // separate from the Track. const std::shared_ptr &GetDirManager() const { return mDirManager; } // Create a NEW track and modify this track // Return non-NULL or else throw // May assume precondition: t0 <= t1 virtual Holder Cut(double WXUNUSED(t0), double WXUNUSED(t1)) = 0; // Create a NEW track and don't modify this track // Return non-NULL or else throw // Note that subclasses may want to distinguish tracks stored in a clipboard // from those stored in a project // May assume precondition: t0 <= t1 virtual Holder Copy (double WXUNUSED(t0), double WXUNUSED(t1), bool forClipboard = true) const = 0; // May assume precondition: t0 <= t1 virtual void Clear(double WXUNUSED(t0), double WXUNUSED(t1)) = 0; virtual void Paste(double WXUNUSED(t), const Track * WXUNUSED(src)) = 0; // This can be used to adjust a sync-lock selected track when the selection // is replaced by one of a different length. virtual void SyncLockAdjust(double oldT1, double newT1); // May assume precondition: t0 <= t1 virtual void Silence(double WXUNUSED(t0), double WXUNUSED(t1)) = 0; // May assume precondition: t0 <= t1 virtual void InsertSilence(double WXUNUSED(t), double WXUNUSED(len)) = 0; virtual int GetKind() const { return None; } // XMLTagHandler callback methods -- NEW virtual for writing virtual void WriteXML(XMLWriter &xmlFile) const = 0; // Returns true if an error was encountered while trying to // open the track from XML virtual bool GetErrorOpening() { return false; } virtual double GetStartTime() const = 0; virtual double GetEndTime() const = 0; // Checks if sync-lock is on and any track in its sync-lock group is selected. bool IsSyncLockSelected() const; protected: std::shared_ptr FindTrack() override; // These are called to create controls on demand: virtual std::shared_ptr GetControls() = 0; virtual std::shared_ptr GetVRulerControls() = 0; // These hold the controls: std::shared_ptr mpControls; std::shared_ptr mpVRulerContols; std::shared_ptr mpResizer; std::weak_ptr mSelectHandle; std::weak_ptr mTimeShiftHandle; }; class AUDACITY_DLL_API AudioTrack /* not final */ : public Track { public: AudioTrack(const std::shared_ptr &projDirManager) : Track{ projDirManager } {} AudioTrack(const Track &orig) : Track{ orig } {} // Serialize, not with tags of its own, but as attributes within a tag. void WriteXMLAttributes(XMLWriter &WXUNUSED(xmlFile)) const {} // Return true iff the attribute is recognized. bool HandleXMLAttribute(const wxChar * /*attr*/, const wxChar * /*value*/) { return false; } }; class AUDACITY_DLL_API PlayableTrack /* not final */ : public AudioTrack { public: PlayableTrack(const std::shared_ptr &projDirManager) : AudioTrack{ projDirManager } {} PlayableTrack(const Track &orig) : AudioTrack{ orig } {} bool GetMute () const { return mMute; } bool GetSolo () const { return mSolo; } void SetMute (bool m) { mMute = m; } void SetSolo (bool s) { mSolo = s; } void Init( const PlayableTrack &init ); void Merge( const Track &init ) override; // Serialize, not with tags of its own, but as attributes within a tag. void WriteXMLAttributes(XMLWriter &xmlFile) const; // Return true iff the attribute is recognized. bool HandleXMLAttribute(const wxChar *attr, const wxChar *value); protected: bool mMute { false }; bool mSolo { false }; }; class AUDACITY_DLL_API TrackListIterator /* not final */ : public std::iterator< std::forward_iterator_tag, Track *const > { public: // The default-constructed value can serve as the end iterator for // traversal over any track list. TrackListIterator() {} explicit TrackListIterator(TrackList * val); explicit TrackListIterator(TrackList * val, TrackNodePointer p); TrackListIterator(const TrackListIterator&) = default; TrackListIterator& operator=(const TrackListIterator&) = default; virtual ~TrackListIterator() {} // Iterate functions virtual Track *First(TrackList * val = nullptr); virtual Track *StartWith(Track * val); virtual Track *Next(bool skiplinked = false); virtual Track *Prev(bool skiplinked = false); virtual Track *Last(bool skiplinked = false); Track *RemoveCurrent(); // deletes track, returns next // Provide minimal STL forward-iterator idiom: // unlike Next, this is non-mutating. // An end iterator may be safely dereferenced, returning nullptr. Track *operator * () const; TrackListIterator &operator++ () { (void) Next(); return *this; } TrackListIterator operator++ (int) { auto copy = *this; operator++(); return copy; } bool operator == (const TrackListIterator &other) const; bool operator != (const TrackListIterator &other) const { return !(*this == other); } protected: friend TrackList; TrackList *l {}; TrackNodePointer cur{}; }; class AUDACITY_DLL_API TrackListConstIterator : public std::iterator< std::forward_iterator_tag, const Track *const > { public: // The default-constructed value can serve as the end iterator for // traversal over any track list. TrackListConstIterator() {} explicit TrackListConstIterator( const TrackList * val, TrackNodePointer p) : mIter(const_cast(val), p) {} explicit TrackListConstIterator( const TrackList * val) : mIter(const_cast(val)) {} TrackListConstIterator(const TrackListConstIterator&) = default; TrackListConstIterator& operator=(const TrackListConstIterator&) = default; ~TrackListConstIterator() {} // Iterate functions const Track *First(const TrackList * val = NULL) { return mIter.First(const_cast(val)); } const Track *StartWith(const Track * val) { return mIter.StartWith(const_cast(val)); } const Track *Next(bool skiplinked = false) { return mIter.Next(skiplinked); } const Track *Prev(bool skiplinked = false) { return mIter.Prev(skiplinked); } const Track *Last(bool skiplinked = false) { return mIter.Last(skiplinked); } // Provide minimal STL forward-iterator idiom: // unlike Next, this is non-mutating. // An end iterator may be safely dereferenced, returning nullptr. const Track *operator * () const { return *mIter; } TrackListConstIterator &operator++ () { (void) Next(); return *this; } TrackListConstIterator operator++ (int) { auto copy = *this; operator++(); return copy; } bool operator == (const TrackListConstIterator &other) const { return mIter == other.mIter; } bool operator != (const TrackListConstIterator &other) const { return !(*this == other); } private: TrackListIterator mIter; }; // TrackListCondIterator (base class for iterators that iterate over all tracks) // that meet a condition) class AUDACITY_DLL_API TrackListCondIterator /* not final */ : public TrackListIterator { public: TrackListCondIterator(TrackList *val = NULL) : TrackListIterator(val) {} virtual ~TrackListCondIterator() {} // Iteration functions Track *First(TrackList *val = NULL) override; Track *StartWith(Track *val) override; Track *Next(bool skiplinked = false) override; Track *Prev(bool skiplinked = false) override; Track *Last(bool skiplinked = false) override; protected: // NEW virtual virtual bool Condition(Track *t) = 0; }; // // TrackListOfKindIterator // // Based on TrackListIterator and returns only tracks of the specified type. // class AUDACITY_DLL_API TrackListOfKindIterator /* not final */ : public TrackListCondIterator { public: TrackListOfKindIterator(int kind, TrackList * val = NULL); virtual ~TrackListOfKindIterator() {} protected: virtual bool Condition(Track *t) override; private: int kind; }; // // SelectedTrackListOfKindIterator // // Based on TrackListOfKindIterator and returns only tracks selected. // class AUDACITY_DLL_API SelectedTrackListOfKindIterator final : public TrackListOfKindIterator { public: SelectedTrackListOfKindIterator(int kind, TrackList * val = NULL) : TrackListOfKindIterator(kind, val) {} virtual ~SelectedTrackListOfKindIterator() {} protected: bool Condition(Track *t) override; }; // // VisibleTrackIterator // // Based on TrackListIterator returns only the currently visible tracks. // class AUDACITY_DLL_API VisibleTrackIterator final : public TrackListCondIterator { public: VisibleTrackIterator(AudacityProject *project); virtual ~VisibleTrackIterator() {} protected: bool Condition(Track *t) override; private: AudacityProject *mProject; wxRect mPanelRect; }; // SyncLockedTracksIterator returns only tracks belonging to the sync-locked tracks // in which the starting track is a member. class AUDACITY_DLL_API SyncLockedTracksIterator final : public TrackListIterator { public: SyncLockedTracksIterator(TrackList * val); virtual ~SyncLockedTracksIterator() {} // Iterate functions Track *StartWith(Track *member) override; Track *Next(bool skiplinked = false) override; Track *Prev(bool skiplinked = false) override; Track *Last(bool skiplinked = false) override; private: bool IsGoodNextTrack(const Track *t) const; bool mInLabelSection; }; /** \brief TrackList is a flat linked list of tracks supporting Add, Remove, * Clear, and Contains, plus serialization of the list of tracks. */ struct TrackListEvent : public wxCommandEvent { TrackListEvent(wxEventType commandType = wxEVT_NULL, int winid = 0) : wxCommandEvent{ commandType, winid } {} TrackListEvent( const TrackListEvent& ) = default; wxEvent *Clone() const override { return new TrackListEvent(*this); } std::weak_ptr mpTrack; }; // Posted when tracks are reordered but otherwise unchanged. wxDECLARE_EXPORTED_EVENT(AUDACITY_DLL_API, EVT_TRACKLIST_PERMUTED, wxCommandEvent); // Posted when some track was added or changed its height. // Cast to TrackListEvent and examine mpTrack to retrieve it. wxDECLARE_EXPORTED_EVENT(AUDACITY_DLL_API, EVT_TRACKLIST_RESIZING, wxCommandEvent); // Posted when a track has been deleted from a tracklist. // Also posted when one track replaces another wxDECLARE_EXPORTED_EVENT(AUDACITY_DLL_API, EVT_TRACKLIST_DELETION, wxCommandEvent); class TrackList final : public wxEvtHandler, public ListOfTracks { // privatize this, make you use Swap instead: using ListOfTracks::swap; // Create an empty TrackList TrackList(); // Disallow copy TrackList(const TrackList &that) = delete; TrackList &operator= (const TrackList&) = delete; // Allow move TrackList(TrackList &&that) : TrackList() { Swap(that); } TrackList& operator= (TrackList&&); void clear() = delete; public: // Create an empty TrackList static std::shared_ptr Create(); // Move is defined in terms of Swap void Swap(TrackList &that); // Destructor virtual ~TrackList(); // Hide the inherited begin() and end() using iterator = TrackListIterator; using const_iterator = TrackListConstIterator; using value_type = Track *; iterator begin() { return iterator{ this, { ListOfTracks::begin(), this } }; } iterator end() { return {}; } const_iterator begin() const { return const_iterator{ this }; } const_iterator end() const { return {}; } const_iterator cbegin() const { return begin(); } const_iterator cend() const { return end(); } friend class Track; friend class TrackListIterator; friend class SyncLockedTracksIterator; /// For use in sorting: assume each iterator points into this list, no duplications void Permute(const std::vector &permutation); Track *FindById( TrackId id ); /// Add a Track, giving it a fresh id template Track *Add(std::unique_ptr &&t); /// Add a Track, giving it a fresh id template Track *AddToHead(std::unique_ptr &&t); /// Add a Track, giving it a fresh id template Track *Add(std::shared_ptr &&t); /// Replace first track with second track, give back a holder /// Give the replacement the same id as the replaced ListOfTracks::value_type Replace(Track * t, ListOfTracks::value_type &&with); /// Remove this Track or all children of this TrackList. /// Return an iterator to what followed the removed track. TrackNodePointer Remove(Track *t); /// Make the list empty void Clear(bool sendEvent = true); /** Select a track, and if it is linked to another track, select it, too. */ void Select(Track * t, bool selected = true); Track *GetPrev(Track * t, bool linked = false) const; /** Return a track in the list that comes after Track t * @param t a track in the list * @param linked if true, skips over linked tracks, if false returns the next track even if it is a linked track **/ Track *GetNext(Track * t, bool linked = false) const; int GetGroupHeight(Track * t) const; bool CanMoveUp(Track * t) const; bool CanMoveDown(Track * t) const; bool MoveUp(Track * t); bool MoveDown(Track * t); bool Move(Track * t, bool up) { return up ? MoveUp(t) : MoveDown(t); } TimeTrack *GetTimeTrack(); const TimeTrack *GetTimeTrack() const; /** \brief Find out how many channels this track list mixes to * * This is used in exports of the tracks to work out whether to export in * Mono, Stereo etc. @param selectionOnly Whether to consider the entire track * list or only the selected members of it */ unsigned GetNumExportChannels(bool selectionOnly) const; WaveTrackArray GetWaveTrackArray(bool selectionOnly, bool includeMuted = true); WaveTrackConstArray GetWaveTrackConstArray(bool selectionOnly, bool includeMuted = true) const; #if defined(USE_MIDI) NoteTrackArray GetNoteTrackArray(bool selectionOnly); #endif /// Mainly a test function. Uses a linear search, so could be slow. bool Contains(const Track * t) const; // Return non-null only if the weak pointer is not, and the track is // owned by this list; constant time. template std::shared_ptr Lock(const std::weak_ptr &wTrack) { auto pTrack = wTrack.lock(); if (pTrack) { auto pList = pTrack->mList.lock(); if (pTrack && this == pList.get()) return pTrack; } return {}; } bool empty() const; size_t size() const; double GetStartTime() const; double GetEndTime() const; double GetMinOffset() const; int GetHeight() const; #if LEGACY_PROJECT_FILE_SUPPORT // File I/O bool Load(wxTextFile * in, DirManager * dirManager) override; bool Save(wxTextFile * out, bool overwrite) override; #endif private: bool isNull(TrackNodePointer p) const { return (p.second == this && p.first == ListOfTracks::end()) || (p.second == &mPendingUpdates && p.first == mPendingUpdates.end()); } TrackNodePointer getEnd() const { return { const_cast(this)->ListOfTracks::end(), const_cast(this)}; } TrackNodePointer getBegin() const { return { const_cast(this)->ListOfTracks::begin(), const_cast(this)}; } // Move an iterator to the next node, if any; else stay at end TrackNodePointer getNext(TrackNodePointer p) const { if ( isNull(p) ) return p; auto q = p; ++q.first; return q; } // Move an iterator to the previous node, if any; else wrap to end TrackNodePointer getPrev(TrackNodePointer p) const { if (p == getBegin()) return getEnd(); else { auto q = p; --q.first; return q; } } void RecalcPositions(TrackNodePointer node); void PermutationEvent(); void DeletionEvent(); void ResizingEvent(TrackNodePointer node); void SwapNodes(TrackNodePointer s1, TrackNodePointer s2); std::weak_ptr mSelf; // Nondecreasing during the session. // Nonpersistent. // Used to assign ids to added tracks. static long sCounter; public: using Updater = std::function< void(Track &dest, const Track &src) >; // Start a deferred update of the project. // The return value is a duplicate of the given track. // While ApplyPendingTracks or ClearPendingTracks is not yet called, // there may be other direct changes to the project that push undo history. // Meanwhile the returned object can accumulate other changes for a deferred // push, and temporarily shadow the actual project track for display purposes. // The Updater function, if not null, merges state (from the actual project // into the pending track) which is not meant to be overridden by the // accumulated pending changes. // To keep the display consistent, the Y and Height values, minimized state, // and Linked state must be copied, and this will be done even if the // Updater does not do it. // Pending track will have the same TrackId as the actual. // Pending changed tracks will not occur in iterations. std::shared_ptr RegisterPendingChangedTrack( Updater updater, Track *src ); // Like the previous, but for a NEW track, not a replacement track. Caller // supplies the track, and there are no updates. // Pending track will have an unassigned TrackId. // Pending changed tracks WILL occur in iterations, always after actual // tracks, and in the sequence that they were added. They can be // distinguished from actual tracks by TrackId. void RegisterPendingNewTrack( const std::shared_ptr &pTrack ); // Invoke the updaters of pending tracks. Pass any exceptions from the // updater functions. void UpdatePendingTracks(); // Forget pending track additions and changes; // if requested, give back the pending added tracks. void ClearPendingTracks( ListOfTracks *pAdded = nullptr ); // Change the state of the project. // Strong guarantee for project state in case of exceptions. // Will always clear the pending updates. // Return true if the state of the track list really did change. bool ApplyPendingTracks(); // Find anything registered with RegisterPendingChangedTrack and not yet // cleared or applied std::shared_ptr FindPendingChangedTrack(TrackId id) const; bool HasPendingTracks() const; private: // Need to put pending tracks into a list so that GetLink() works ListOfTracks mPendingUpdates; // This is in correspondence with mPendingUpdates std::vector< Updater > mUpdaters; }; class AUDACITY_DLL_API TrackFactory { private: TrackFactory(const std::shared_ptr &dirManager, const ZoomInfo *zoomInfo): mDirManager(dirManager) , mZoomInfo(zoomInfo) { } const std::shared_ptr mDirManager; const ZoomInfo *const mZoomInfo; friend class AudacityProject; friend class BenchmarkDialog; public: // These methods are defined in WaveTrack.cpp, NoteTrack.cpp, // LabelTrack.cpp, and TimeTrack.cpp respectively std::unique_ptr DuplicateWaveTrack(const WaveTrack &orig); std::unique_ptr NewWaveTrack(sampleFormat format = (sampleFormat)0, double rate = 0); std::unique_ptr NewLabelTrack(); std::unique_ptr NewTimeTrack(); #if defined(USE_MIDI) std::unique_ptr NewNoteTrack(); #endif }; #endif