/********************************************************************** Audacity: A Digital Audio Editor WaveTrack.h Dominic Mazzoni **********************************************************************/ #ifndef __AUDACITY_WAVETRACK__ #define __AUDACITY_WAVETRACK__ #include "Track.h" #include #include #include #include "WaveTrackLocation.h" class ProgressDialog; class SampleBlockFactory; using SampleBlockFactoryPtr = std::shared_ptr; class SpectrogramSettings; class WaveformSettings; class TimeWarper; class Sequence; class WaveClip; // Array of pointers that assume ownership using WaveClipHolder = std::shared_ptr< WaveClip >; using WaveClipHolders = std::vector < WaveClipHolder >; using WaveClipConstHolders = std::vector < std::shared_ptr< const WaveClip > >; // Temporary arrays of mere pointers using WaveClipPointers = std::vector < WaveClip* >; using WaveClipConstPointers = std::vector < const WaveClip* >; // // Tolerance for merging wave tracks (in seconds) // #define WAVETRACK_MERGE_POINT_TOLERANCE 0.01 /// \brief Structure to hold region of a wavetrack and a comparison function /// for sortability. struct Region { Region() : start(0), end(0) {} Region(double start_, double end_) : start(start_), end(end_) {} double start, end; //used for sorting bool operator < (const Region &b) const { return this->start < b.start; } }; using Regions = std::vector < Region >; class Envelope; class AUDACITY_DLL_API WaveTrack final : public PlayableTrack { public: // // Constructor / Destructor / Duplicator // WaveTrack( const SampleBlockFactoryPtr &pFactory, sampleFormat format, double rate); WaveTrack(const WaveTrack &orig); // overwrite data excluding the sample sequence but including display // settings void Reinit(const WaveTrack &orig); private: void Init(const WaveTrack &orig); Track::Holder Clone() const override; friend class WaveTrackFactory; public: typedef WaveTrackLocation Location; using Holder = std::shared_ptr; virtual ~WaveTrack(); double GetOffset() const override; void SetOffset(double o) override; virtual ChannelType GetChannelIgnoringPan() const; ChannelType GetChannel() const override; virtual void SetPanFromChannelType() override; /** @brief Get the time at which the first clip in the track starts * * @return time in seconds, or zero if there are no clips in the track */ double GetStartTime() const override; /** @brief Get the time at which the last clip in the track ends, plus * recorded stuff * * @return time in seconds, or zero if there are no clips in the track. */ double GetEndTime() const override; // // Identifying the type of track // // // WaveTrack parameters // double GetRate() const; void SetRate(double newRate); // Multiplicative factor. Only converted to dB for display. float GetGain() const; void SetGain(float newGain); // -1.0 (left) -> 1.0 (right) float GetPan() const; void SetPan(float newPan) override; // Takes gain and pan into account float GetChannelGain(int channel) const; // Old gain is used in playback in linearly interpolating // the gain. float GetOldChannelGain(int channel) const; void SetOldChannelGain(int channel, float gain); int GetWaveColorIndex() const { return mWaveColorIndex; }; void SetWaveColorIndex(int colorIndex); sampleCount GetNumSamples() const; sampleFormat GetSampleFormat() const { return mFormat; } void ConvertToSampleFormat(sampleFormat format, const std::function & progressReport = {}); const SpectrogramSettings &GetSpectrogramSettings() const; SpectrogramSettings &GetSpectrogramSettings(); SpectrogramSettings &GetIndependentSpectrogramSettings(); void SetSpectrogramSettings(std::unique_ptr &&pSettings); const WaveformSettings &GetWaveformSettings() const; WaveformSettings &GetWaveformSettings(); void SetWaveformSettings(std::unique_ptr &&pSettings); void UseSpectralPrefs( bool bUse=true ); // // High-level editing // Track::Holder Cut(double t0, double t1) override; // Make another track copying format, rate, color, etc. but containing no // clips // It is important to pass the correct factory (that for the project // which will own the copy) in the unusual case that a track is copied from // another project or the clipboard. For copies within one project, the // default will do. Holder EmptyCopy(const SampleBlockFactoryPtr &pFactory = {} ) const; // If forClipboard is true, // and there is no clip at the end time of the selection, then the result // will contain a "placeholder" clip whose only purpose is to make // GetEndTime() correct. This clip is not re-copied when pasting. Track::Holder Copy(double t0, double t1, bool forClipboard = true) const override; Track::Holder CopyNonconst(double t0, double t1) /* not override */; void Clear(double t0, double t1) override; void Paste(double t0, const Track *src) override; // May assume precondition: t0 <= t1 void ClearAndPaste(double t0, double t1, const Track *src, bool preserve = true, bool merge = true, const TimeWarper *effectWarper = NULL) /* not override */; void Silence(double t0, double t1) override; void InsertSilence(double t, double len) override; void SplitAt(double t) /* not override */; void Split(double t0, double t1) /* not override */; // Track::Holder CutAndAddCutLine(double t0, double t1) /* not override */; // May assume precondition: t0 <= t1 void ClearAndAddCutLine(double t0, double t1) /* not override */; Track::Holder SplitCut(double t0, double t1) /* not override */; // May assume precondition: t0 <= t1 void SplitDelete(double t0, double t1) /* not override */; void Join(double t0, double t1) /* not override */; // May assume precondition: t0 <= t1 void Disjoin(double t0, double t1) /* not override */; // May assume precondition: t0 <= t1 void Trim(double t0, double t1) /* not override */; // May assume precondition: t0 <= t1 void HandleClear(double t0, double t1, bool addCutLines, bool split); void SyncLockAdjust(double oldT1, double newT1) override; /** @brief Returns true if there are no WaveClips in the specified region * * @return true if no clips in the track overlap the specified time range, * false otherwise. */ bool IsEmpty(double t0, double t1) const; /** @brief Append the sample data to the WaveTrack. You must call Flush() * after the last Append. * * If there is an existing WaveClip in the WaveTrack then the data is * appended to that clip. If there are no WaveClips in the track, then a NEW * one is created. * * @return true if at least one complete block was created */ bool Append(constSamplePtr buffer, sampleFormat format, size_t len, unsigned int stride=1); /// Flush must be called after last Append void Flush(); ///Invalidates all clips' wavecaches. Careful, This may not be threadsafe. void ClearWaveCaches(); /// /// MM: Now that each wave track can contain multiple clips, we don't /// have a continuous space of samples anymore, but we simulate it, /// because there are a lot of places (e.g. effects) using this interface. /// This interface makes much sense for modifying samples, but note that /// it is not time-accurate, because the "offset" is a double value and /// therefore can lie inbetween samples. But as long as you use the /// same value for "start" in both calls to "Set" and "Get" it is /// guaranteed that the same samples are affected. /// bool Get(samplePtr buffer, sampleFormat format, sampleCount start, size_t len, fillFormat fill = fillZero, bool mayThrow = true, // Report how many samples were copied from within clips, rather than // filled according to fillFormat; but these were not necessarily one // contiguous range. sampleCount * pNumWithinClips = nullptr) const; void Set(constSamplePtr buffer, sampleFormat format, sampleCount start, size_t len); // Fetch envelope values corresponding to uniformly separated sample times // starting at the given time. void GetEnvelopeValues(double *buffer, size_t bufferLen, double t0) const; // May assume precondition: t0 <= t1 std::pair GetMinMax( double t0, double t1, bool mayThrow = true) const; // May assume precondition: t0 <= t1 float GetRMS(double t0, double t1, bool mayThrow = true) const; // // MM: We now have more than one sequence and envelope per track, so // instead of GetSequence() and GetEnvelope() we have the following // function which give the sequence and envelope which contains the given // time. // Sequence* GetSequenceAtTime(double time); Envelope* GetEnvelopeAtTime(double time); WaveClip* GetClipAtSample(sampleCount sample); WaveClip* GetClipAtTime(double time); // // Getting information about the track's internal block sizes // and alignment for efficiency // // This returns a possibly large or negative value sampleCount GetBlockStart(sampleCount t) const; // These return a nonnegative number of samples meant to size a memory buffer size_t GetBestBlockSize(sampleCount t) const; size_t GetMaxBlockSize() const; size_t GetIdealBlockSize(); // // XMLTagHandler callback methods for loading and saving // bool HandleXMLTag(const wxChar *tag, const wxChar **attrs) override; void HandleXMLEndTag(const wxChar *tag) override; XMLTagHandler *HandleXMLChild(const wxChar *tag) override; void WriteXML(XMLWriter &xmlFile) const override; // Returns true if an error occurred while reading from XML bool GetErrorOpening() override; // // Lock and unlock the track: you must lock the track before // doing a copy and paste between projects. // bool CloseLock(); //should be called when the project closes. // not balanced by unlocking calls. /** @brief Convert correctly between an (absolute) time in seconds and a number of samples. * * This method will not give the correct results if used on a relative time (difference of two * times). Each absolute time must be converted and the numbers of samples differenced: * sampleCount start = track->TimeToLongSamples(t0); * sampleCount end = track->TimeToLongSamples(t1); * sampleCount len = (sampleCount)(end - start); * NOT the likes of: * sampleCount len = track->TimeToLongSamples(t1 - t0); * See also WaveTrack::TimeToLongSamples(). * @param t0 The time (floating point seconds) to convert * @return The number of samples from the start of the track which lie before the given time. */ sampleCount TimeToLongSamples(double t0) const; /** @brief Convert correctly between a number of samples and an (absolute) time in seconds. * * @param pos The time number of samples from the start of the track to convert. * @return The time in seconds. */ double LongSamplesToTime(sampleCount pos) const; // Get access to the (visible) clips in the tracks, in unspecified order // (not necessarily sequenced in time). WaveClipHolders &GetClips() { return mClips; } const WaveClipConstHolders &GetClips() const { return reinterpret_cast< const WaveClipConstHolders& >( mClips ); } // Get mutative access to all clips (in some unspecified sequence), // including those hidden in cutlines. class AllClipsIterator : public ValueIterator< WaveClip * > { public: // Constructs an "end" iterator AllClipsIterator () {} // Construct a "begin" iterator explicit AllClipsIterator( WaveTrack &track ) { push( track.mClips ); } WaveClip *operator * () const { if (mStack.empty()) return nullptr; else return mStack.back().first->get(); } AllClipsIterator &operator ++ (); // Define == well enough to serve for loop termination test friend bool operator == ( const AllClipsIterator &a, const AllClipsIterator &b) { return a.mStack.empty() == b.mStack.empty(); } friend bool operator != ( const AllClipsIterator &a, const AllClipsIterator &b) { return !( a == b ); } private: void push( WaveClipHolders &clips ); using Iterator = WaveClipHolders::iterator; using Pair = std::pair< Iterator, Iterator >; using Stack = std::vector< Pair >; Stack mStack; }; // Get const access to all clips (in some unspecified sequence), // including those hidden in cutlines. class AllClipsConstIterator : public ValueIterator< const WaveClip * > { public: // Constructs an "end" iterator AllClipsConstIterator () {} // Construct a "begin" iterator explicit AllClipsConstIterator( const WaveTrack &track ) : mIter{ const_cast< WaveTrack& >( track ) } {} const WaveClip *operator * () const { return *mIter; } AllClipsConstIterator &operator ++ () { ++mIter; return *this; } // Define == well enough to serve for loop termination test friend bool operator == ( const AllClipsConstIterator &a, const AllClipsConstIterator &b) { return a.mIter == b.mIter; } friend bool operator != ( const AllClipsConstIterator &a, const AllClipsConstIterator &b) { return !( a == b ); } private: AllClipsIterator mIter; }; IteratorRange< AllClipsIterator > GetAllClips() { return { AllClipsIterator{ *this }, AllClipsIterator{ } }; } IteratorRange< AllClipsConstIterator > GetAllClips() const { return { AllClipsConstIterator{ *this }, AllClipsConstIterator{ } }; } // Create NEW clip and add it to this track. Returns a pointer // to the newly created clip. WaveClip* CreateClip(); /** @brief Get access to the most recently added clip, or create a clip, * if there is not already one. THIS IS NOT NECESSARILY RIGHTMOST. * * @return a pointer to the most recently added WaveClip */ WaveClip* NewestOrNewClip(); /** @brief Get access to the last (rightmost) clip, or create a clip, * if there is not already one. * * @return a pointer to a WaveClip at the end of the track */ WaveClip* RightmostOrNewClip(); // Get the linear index of a given clip (-1 if the clip is not found) int GetClipIndex(const WaveClip* clip) const; // Get the nth clip in this WaveTrack (will return NULL if not found). // Use this only in special cases (like getting the linked clip), because // it is much slower than GetClipIterator(). WaveClip *GetClipByIndex(int index); const WaveClip* GetClipByIndex(int index) const; // Get number of clips in this WaveTrack int GetNumClips() const; // Add all wave clips to the given array 'clips' and sort the array by // clip start time. The array is emptied prior to adding the clips. WaveClipPointers SortedClipArray(); WaveClipConstPointers SortedClipArray() const; //! Decide whether the clips could be offset (and inserted) together without overlapping other clips /*! @return true if possible to offset by `(allowedAmount ? *allowedAmount : amount)` */ bool CanOffsetClips( const std::vector &clips, //!< not necessarily in this track double amount, //!< signed double *allowedAmount = nullptr /*!< [out] if null, test exact amount only; else, largest (in magnitude) possible offset with same sign */ ); // Before moving a clip into a track (or inserting a clip), use this // function to see if the times are valid (i.e. don't overlap with // existing clips). bool CanInsertClip(WaveClip* clip, double &slideBy, double &tolerance) const; // Remove the clip from the track and return a SMART pointer to it. // You assume responsibility for its memory! std::shared_ptr RemoveAndReturnClip(WaveClip* clip); //! Append a clip to the track; which must have the same block factory as this track; return success bool AddClip(const std::shared_ptr &clip); // Merge two clips, that is append data from clip2 to clip1, // then remove clip2 from track. // clipidx1 and clipidx2 are indices into the clip list. void MergeClips(int clipidx1, int clipidx2); // Cache special locations (e.g. cut lines) for later speedy access void UpdateLocationsCache() const; // Get cached locations const std::vector &GetCachedLocations() const { return mDisplayLocationsCache; } // Expand cut line (that is, re-insert audio, then DELETE audio saved in cut line) void ExpandCutLine(double cutLinePosition, double* cutlineStart = NULL, double* cutlineEnd = NULL); // Remove cut line, without expanding the audio in it bool RemoveCutLine(double cutLinePosition); // This track has been merged into a stereo track. Copy shared parameters // from the NEW partner. void Merge(const Track &orig) override; // Resample track (i.e. all clips in the track) void Resample(int rate, ProgressDialog *progress = NULL); int GetLastScaleType() const { return mLastScaleType; } void SetLastScaleType() const; int GetLastdBRange() const { return mLastdBRange; } void SetLastdBRange() const; void GetDisplayBounds(float *min, float *max) const; void SetDisplayBounds(float min, float max) const; void GetSpectrumBounds(float *min, float *max) const; void SetSpectrumBounds(float min, float max) const; // For display purposes, calculate the y coordinate where the midline of // the wave should be drawn, if display minimum and maximum map to the // bottom and top. Maybe that is out of bounds. int ZeroLevelYCoordinate(wxRect rect) const; class IntervalData final : public Track::IntervalData { public: explicit IntervalData( const std::shared_ptr &pClip ) : pClip{ pClip } {} std::shared_ptr GetClip() const { return pClip; } std::shared_ptr &GetClip() { return pClip; } private: std::shared_ptr pClip; }; ConstIntervals GetIntervals() const override; Intervals GetIntervals() override; protected: // // Protected variables // WaveClipHolders mClips; sampleFormat mFormat; int mRate; float mGain; float mPan; int mWaveColorIndex; float mOldGain[2]; // // Data that should be part of GUIWaveTrack // and will be taken out of the WaveTrack class: // mutable float mDisplayMin; mutable float mDisplayMax; mutable float mSpectrumMin; mutable float mSpectrumMax; mutable int mLastScaleType; // last scale type choice mutable int mLastdBRange; mutable std::vector mDisplayLocationsCache; // // Protected methods // private: TrackKind GetKind() const override { return TrackKind::Wave; } // // Private variables // SampleBlockFactoryPtr mpFactory; wxCriticalSection mFlushCriticalSection; wxCriticalSection mAppendCriticalSection; double mLegacyProjectFileOffset; std::unique_ptr mpSpectrumSettings; std::unique_ptr mpWaveformSettings; }; // This is meant to be a short-lived object, during whose lifetime, // the contents of the WaveTrack are known not to change. It can replace // repeated calls to WaveTrack::Get() (each of which opens and closes at least // one block file). class WaveTrackCache { public: WaveTrackCache() : mBufferSize(0) , mOverlapBuffer() , mNValidBuffers(0) { } explicit WaveTrackCache(const std::shared_ptr &pTrack) : mBufferSize(0) , mOverlapBuffer() , mNValidBuffers(0) { SetTrack(pTrack); } ~WaveTrackCache(); const std::shared_ptr& GetTrack() const { return mPTrack; } void SetTrack(const std::shared_ptr &pTrack); // Uses fillZero always // Returns null on failure // Returned pointer may be invalidated if Get is called again // Do not DELETE[] the pointer constSamplePtr Get( sampleFormat format, sampleCount start, size_t len, bool mayThrow); private: void Free(); struct Buffer { Floats data; sampleCount start; sampleCount len; Buffer() : start(0), len(0) {} void Free() { data.reset(); start = 0; len = 0; } sampleCount end() const { return start + len; } void swap ( Buffer &other ) { data .swap ( other.data ); std::swap( start, other.start ); std::swap( len, other.len ); } }; std::shared_ptr mPTrack; size_t mBufferSize; Buffer mBuffers[2]; GrowableSampleBuffer mOverlapBuffer; int mNValidBuffers; }; #include class SampleBlock; using SampleBlockID = long long; using SampleBlockIDSet = std::unordered_set; class TrackList; using BlockVisitor = std::function< void(SampleBlock&) >; using BlockInspector = std::function< void(const SampleBlock&) >; // Function to visit all sample blocks from a list of tracks. // If a set is supplied, then only visit once each unique block ID not already // in that set, and accumulate those into the set as a side-effect. // The visitor function may be null. void VisitBlocks(TrackList &tracks, BlockVisitor visitor, SampleBlockIDSet *pIDs = nullptr); // Non-mutating version of the above void InspectBlocks(const TrackList &tracks, BlockInspector inspector, SampleBlockIDSet *pIDs = nullptr); class AUDACITY_DLL_API WaveTrackFactory final : public ClientData::Base { public: static WaveTrackFactory &Get( AudacityProject &project ); static const WaveTrackFactory &Get( const AudacityProject &project ); static WaveTrackFactory &Reset( AudacityProject &project ); static void Destroy( AudacityProject &project ); WaveTrackFactory( const ProjectSettings &settings, const SampleBlockFactoryPtr &pFactory) : mSettings{ settings } , mpFactory(pFactory) { } WaveTrackFactory( const WaveTrackFactory & ) PROHIBITED; WaveTrackFactory &operator=( const WaveTrackFactory & ) PROHIBITED; const SampleBlockFactoryPtr &GetSampleBlockFactory() const { return mpFactory; } private: const ProjectSettings &mSettings; SampleBlockFactoryPtr mpFactory; public: std::shared_ptr DuplicateWaveTrack(const WaveTrack &orig); std::shared_ptr NewWaveTrack( sampleFormat format = (sampleFormat)0, double rate = 0); }; #endif // __AUDACITY_WAVETRACK__