diff --git a/src/LabelTrack.cpp b/src/LabelTrack.cpp index 57a22d7ae..92280ed17 100644 --- a/src/LabelTrack.cpp +++ b/src/LabelTrack.cpp @@ -2361,7 +2361,7 @@ Track::Holder LabelTrack::SplitCut(double t0, double t1) } #endif -Track::Holder LabelTrack::Copy(double t0, double t1) const +Track::Holder LabelTrack::Copy(double t0, double t1, bool) const { auto tmp = std::make_unique(GetDirManager()); const auto lt = static_cast(tmp.get()); diff --git a/src/LabelTrack.h b/src/LabelTrack.h index 053ea12d4..1f9b1fd9c 100644 --- a/src/LabelTrack.h +++ b/src/LabelTrack.h @@ -155,11 +155,7 @@ class AUDACITY_DLL_API LabelTrack final : public Track #endif Track::Holder Cut (double t0, double t1) override; - // JKC Do not add the const modifier to Copy(), Clear() - // or Paste() because then it - // is no longer recognised as a virtual function matching the - // one in Track. - Track::Holder Copy (double t0, double t1) const override; + Track::Holder Copy (double t0, double t1, bool forClipboard = true) const override; bool Clear(double t0, double t1) override; bool Paste(double t, const Track * src) override; bool Repeat(double t0, double t1, int n); diff --git a/src/Menus.cpp b/src/Menus.cpp index e5ba71083..a7d81acf7 100644 --- a/src/Menus.cpp +++ b/src/Menus.cpp @@ -4789,8 +4789,9 @@ void AudacityProject::OnDuplicate() while (n) { if (n->GetSelected()) { + // Make copies not for clipboard but for direct addition to the project auto dest = n->Copy(mViewInfo.selectedRegion.t0(), - mViewInfo.selectedRegion.t1()); + mViewInfo.selectedRegion.t1(), false); if (dest) { dest->Init(*n); dest->SetOffset(wxMax(mViewInfo.selectedRegion.t0(), n->GetOffset())); diff --git a/src/NoteTrack.cpp b/src/NoteTrack.cpp index 5e6f97d82..3bcf57e6b 100644 --- a/src/NoteTrack.cpp +++ b/src/NoteTrack.cpp @@ -454,7 +454,7 @@ Track::Holder NoteTrack::Cut(double t0, double t1) return std::move(newTrack); } -Track::Holder NoteTrack::Copy(double t0, double t1) const +Track::Holder NoteTrack::Copy(double t0, double t1, bool) const { if (t1 <= t0) return{}; diff --git a/src/NoteTrack.h b/src/NoteTrack.h index fafdb6dff..30a034f22 100644 --- a/src/NoteTrack.h +++ b/src/NoteTrack.h @@ -91,7 +91,7 @@ class AUDACITY_DLL_API NoteTrack final : public Track { // High-level editing Track::Holder Cut (double t0, double t1) override; - Track::Holder Copy (double t0, double t1) const override; + Track::Holder Copy (double t0, double t1, bool forClipboard = true) const override; bool Trim (double t0, double t1) /* not override */; bool Clear(double t0, double t1) override; bool Paste(double t, const Track *src) override; diff --git a/src/Project.cpp b/src/Project.cpp index 7517e4df0..4804d040e 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -4905,6 +4905,7 @@ void AudacityProject::EditClipboardByLabel( EditDestFunction action ) if( n->GetKind() == Track::Wave && ( allTracks || n->GetSelected() ) ) { WaveTrack *wt = ( WaveTrack* )n; + // This track accumulates the needed clips, right to left: Track::Holder merged; for( int i = (int)regions.size() - 1; i >= 0; i-- ) { @@ -4923,6 +4924,11 @@ void AudacityProject::EditClipboardByLabel( EditDestFunction action ) merged->Offset( regions.at(i + 1).start - region.end); + // dest may have a placeholder clip at the end that is + // removed when pasting, which is okay because we proceed + // right to left. Any placeholder already in merged is kept. + // Only the rightmost placeholder is important in the final + // result. bool bResult = merged->Paste( 0.0 , dest.get() ); wxASSERT(bResult); // TO DO: Actually handle this. wxUnusedVar(bResult); diff --git a/src/Sequence.cpp b/src/Sequence.cpp index 47d23053e..6c7baab15 100644 --- a/src/Sequence.cpp +++ b/src/Sequence.cpp @@ -385,12 +385,10 @@ bool Sequence::GetRMS(sampleCount start, sampleCount len, return true; } -bool Sequence::Copy(sampleCount s0, sampleCount s1, std::unique_ptr &dest) const +std::unique_ptr Sequence::Copy(sampleCount s0, sampleCount s1) const { - dest.reset(); - if (s0 >= s1 || s0 >= mNumSamples || s1 < 0) - return false; + return {}; int numBlocks = mBlock.size(); @@ -402,7 +400,7 @@ bool Sequence::Copy(sampleCount s0, sampleCount s1, std::unique_ptr &d wxUnusedVar(numBlocks); wxASSERT(b0 <= b1); - dest = std::make_unique(mDirManager, mSampleFormat); + auto dest = std::make_unique(mDirManager, mSampleFormat); dest->mBlock.reserve(b1 - b0 + 1); SampleBuffer buffer(mMaxSamples, mSampleFormat); @@ -445,7 +443,10 @@ bool Sequence::Copy(sampleCount s0, sampleCount s1, std::unique_ptr &d dest->AppendBlock(block); // Increase ref count or duplicate file } - return ConsistencyCheck(wxT("Sequence::Copy()")); + if (! ConsistencyCheck(wxT("Sequence::Copy()"))) + return {}; + + return dest; } namespace { diff --git a/src/Sequence.h b/src/Sequence.h index 5c2cdc0b7..716882739 100644 --- a/src/Sequence.h +++ b/src/Sequence.h @@ -103,7 +103,7 @@ class PROFILE_DLL_API Sequence final : public XMLTagHandler{ bool GetWaveDisplay(float *min, float *max, float *rms, int* bl, size_t len, const sampleCount *where); - bool Copy(sampleCount s0, sampleCount s1, std::unique_ptr &dest) const; + std::unique_ptr Copy(sampleCount s0, sampleCount s1) const; bool Paste(sampleCount s0, const Sequence *src); size_t GetIdealAppendLen() const; diff --git a/src/Track.h b/src/Track.h index cc18b309b..64a8fe05a 100644 --- a/src/Track.h +++ b/src/Track.h @@ -216,7 +216,11 @@ class AUDACITY_DLL_API Track /* not final */ : public XMLTagHandler virtual Holder Cut(double WXUNUSED(t0), double WXUNUSED(t1)) { return{}; } // Create a NEW track and don't modify this track (or return null for failure) - virtual Holder Copy(double WXUNUSED(t0), double WXUNUSED(t1)) const { return{}; } + // Note that subclasses may want to distinguish tracks stored in a clipboard + // from those stored in a project + virtual Holder Copy + (double WXUNUSED(t0), double WXUNUSED(t1), bool forClipboard = true) const + { return{}; } // Return true for success virtual bool Clear(double WXUNUSED(t0), double WXUNUSED(t1)) {return false;} diff --git a/src/WaveClip.cpp b/src/WaveClip.cpp index 00c7ef09a..ed13caafd 100644 --- a/src/WaveClip.cpp +++ b/src/WaveClip.cpp @@ -300,21 +300,22 @@ static void ComputeSpectrumUsingRealFFTf } } -WaveClip::WaveClip(const std::shared_ptr &projDirManager, sampleFormat format, int rate) +WaveClip::WaveClip(const std::shared_ptr &projDirManager, + sampleFormat format, int rate) { - mOffset = 0; mRate = rate; mSequence = std::make_unique(projDirManager, format); + mEnvelope = std::make_unique(); + mWaveCache = std::make_unique(); mSpecCache = std::make_unique(); mSpecPxCache = std::make_unique(1); - mAppendBufferLen = 0; - mDirty = 0; - mIsPlaceholder = false; } -WaveClip::WaveClip(const WaveClip& orig, const std::shared_ptr &projDirManager) +WaveClip::WaveClip(const WaveClip& orig, + const std::shared_ptr &projDirManager, + bool copyCutlines) { // essentially a copy constructor - but you must pass in the // current project's DirManager, because we might be copying @@ -323,22 +324,69 @@ WaveClip::WaveClip(const WaveClip& orig, const std::shared_ptr &proj mOffset = orig.mOffset; mRate = orig.mRate; mSequence = std::make_unique(*orig.mSequence, projDirManager); + mEnvelope = std::make_unique(); mEnvelope->Paste(0.0, orig.mEnvelope.get()); mEnvelope->SetOffset(orig.GetOffset()); mEnvelope->SetTrackLen((orig.mSequence->GetNumSamples().as_double()) / orig.mRate); + mWaveCache = std::make_unique(); mSpecCache = std::make_unique(); mSpecPxCache = std::make_unique(1); - for (const auto &clip: orig.mCutLines) - mCutLines.push_back(make_movable(*clip, projDirManager)); + if ( copyCutlines ) + for (const auto &clip: orig.mCutLines) + mCutLines.push_back + ( make_movable( *clip, projDirManager, true ) ); - mAppendBufferLen = 0; - mDirty = 0; mIsPlaceholder = orig.GetIsPlaceholder(); } +WaveClip::WaveClip(const WaveClip& orig, + const std::shared_ptr &projDirManager, + bool copyCutlines, + double t0, double t1) +{ + // Copy only a range of the other WaveClip + + mOffset = orig.mOffset; + mRate = orig.mRate; + + mWaveCache = std::make_unique(); + mSpecCache = std::make_unique(); + mSpecPxCache = std::make_unique(1); + + mIsPlaceholder = orig.GetIsPlaceholder(); + + sampleCount s0, s1; + + orig.TimeToSamplesClip(t0, &s0); + orig.TimeToSamplesClip(t1, &s1); + + mSequence = orig.mSequence->Copy(s0, s1); + + mEnvelope = std::make_unique(); + mEnvelope->CopyFrom(orig.mEnvelope.get(), + mOffset + s0.as_double()/mRate, + mOffset + s1.as_double()/mRate); + + if ( copyCutlines ) + // Copy cutline clips that fall in the range + for (const auto &ppClip : orig.mCutLines) + { + const WaveClip* clip = ppClip.get(); + double cutlinePosition = orig.mOffset + clip->GetOffset(); + if (cutlinePosition >= t0 && cutlinePosition <= t1) + { + auto newCutLine = + make_movable< WaveClip >( *clip, projDirManager, true ); + newCutLine->SetOffset( cutlinePosition - t0 ); + mCutLines.push_back(std::move(newCutLine)); + } + } +} + + WaveClip::~WaveClip() { } @@ -1277,6 +1325,9 @@ bool WaveClip::GetRMS(float *rms, double t0, void WaveClip::ConvertToSampleFormat(sampleFormat format) { + // Note: it is not necessary to do this recursively to cutlines. + // They get converted as needed when they are expanded. + bool bChanged; bool bResult = mSequence->ConvertToSampleFormat(format, &bChanged); if (bResult && bChanged) @@ -1477,30 +1528,6 @@ void WaveClip::WriteXML(XMLWriter &xmlFile) const xmlFile.EndTag(wxT("waveclip")); } -bool WaveClip::CreateFromCopy(double t0, double t1, const WaveClip* other) -{ - sampleCount s0, s1; - - other->TimeToSamplesClip(t0, &s0); - other->TimeToSamplesClip(t1, &s1); - - std::unique_ptr oldSequence = std::move(mSequence); - if (!other->mSequence->Copy(s0, s1, mSequence)) - { - mSequence = std::move(oldSequence); - return false; - } - - mEnvelope = std::make_unique(); - mEnvelope->CopyFrom(other->mEnvelope.get(), - mOffset + s0.as_double()/mRate, - mOffset + s1.as_double()/mRate); - - MarkChanged(); - - return true; -} - bool WaveClip::Paste(double t0, const WaveClip* other) { const bool clipNeedsResampling = other->mRate != mRate; @@ -1512,7 +1539,7 @@ bool WaveClip::Paste(double t0, const WaveClip* other) if (clipNeedsResampling || clipNeedsNewFormat) { newClip = - std::make_unique(*other, mSequence->GetDirManager()); + std::make_unique(*other, mSequence->GetDirManager(), true); if (clipNeedsResampling) // The other clip's rate is different from ours, so resample if (!newClip->Resample(mRate)) @@ -1542,7 +1569,11 @@ bool WaveClip::Paste(double t0, const WaveClip* other) for (const auto &cutline: pastedClip->mCutLines) { mCutLines.push_back( - make_movable(*cutline, mSequence->GetDirManager())); + make_movable + ( *cutline, mSequence->GetDirManager(), + // Recursively copy cutlines of cutlines. They don't need + // their offsets adjusted. + true)); mCutLines.back()->Offset(t0 - mOffset); } @@ -1635,32 +1666,23 @@ bool WaveClip::ClearAndAddCutLine(double t0, double t1) if (t0 > GetEndTime() || t1 < GetStartTime()) return true; // time out of bounds - auto newClip = make_movable - (mSequence->GetDirManager(), mSequence->GetSampleFormat(), mRate); - double clip_t0 = t0; - double clip_t1 = t1; - if (clip_t0 < GetStartTime()) - clip_t0 = GetStartTime(); - if (clip_t1 > GetEndTime()) - clip_t1 = GetEndTime(); + const double clip_t0 = std::max( t0, GetStartTime() ); + const double clip_t1 = std::min( t1, GetEndTime() ); + + auto newClip = make_movable + (*this, mSequence->GetDirManager(), true, clip_t0, clip_t1); - newClip->SetOffset(this->mOffset); - if (!newClip->CreateFromCopy(clip_t0, clip_t1, this)) - return false; newClip->SetOffset(clip_t0-mOffset); - // Sort out cutlines that belong to the NEW cutline + // Remove cutlines from this clip that were in the selection, shift + // left those that were after the selection // May DELETE as we iterate, so don't use range-for for (auto it = mCutLines.begin(); it != mCutLines.end();) { WaveClip* clip = it->get(); double cutlinePosition = mOffset + clip->GetOffset(); if (cutlinePosition >= t0 && cutlinePosition <= t1) - { - clip->SetOffset(cutlinePosition - newClip->GetOffset() - mOffset); - newClip->mCutLines.push_back(std::move(*it)); // transfer ownership!! it = mCutLines.erase(it); - } else { if (cutlinePosition >= t1) @@ -1755,11 +1777,6 @@ bool WaveClip::RemoveCutLine(double cutLinePosition) return false; } -void WaveClip::RemoveAllCutLines() -{ - mCutLines.clear(); -} - void WaveClip::OffsetCutLines(double t0, double len) { for (const auto &cutLine : mCutLines) @@ -1799,6 +1816,9 @@ void WaveClip::SetRate(int rate) bool WaveClip::Resample(int rate, ProgressDialog *progress) { + // Note: it is not necessary to do this recursively to cutlines. + // They get resampled as needed when they are expanded. + if (rate == mRate) return true; // Nothing to do diff --git a/src/WaveClip.h b/src/WaveClip.h index e03783dc0..83726f231 100644 --- a/src/WaveClip.h +++ b/src/WaveClip.h @@ -215,7 +215,15 @@ public: // essentially a copy constructor - but you must pass in the // current project's DirManager, because we might be copying // from one project to another - WaveClip(const WaveClip& orig, const std::shared_ptr &projDirManager); + WaveClip(const WaveClip& orig, + const std::shared_ptr &projDirManager, + bool copyCutlines); + + // Copy only a range from the given WaveClip + WaveClip(const WaveClip& orig, + const std::shared_ptr &projDirManager, + bool copyCutlines, + double t0, double t1); virtual ~WaveClip(); @@ -266,9 +274,6 @@ public: * has changed, like when member functions SetSamples() etc. are called. */ void MarkChanged() { mDirty++; } - /// Create clip from copy, discarding previous information in the clip - bool CreateFromCopy(double t0, double t1, const WaveClip* other); - /** Getting high-level data from the for screen display and clipping * calculations and Contrast */ bool GetWaveDisplay(WaveDisplay &display, @@ -339,7 +344,6 @@ public: /// Remove cut line, without expanding the audio in it bool RemoveCutLine(double cutLinePosition); - void RemoveAllCutLines(); /// Offset cutlines right to time 't0' by time amount 'len' void OffsetCutLines(double t0, double len); @@ -367,35 +371,35 @@ public: XMLTagHandler *HandleXMLChild(const wxChar *tag) override; void WriteXML(XMLWriter &xmlFile) const /* not override */; - // Cache of values to colour pixels of Spectrogram - used by TrackArtist - mutable std::unique_ptr mSpecPxCache; - // AWD, Oct 2009: for pasting whitespace at the end of selection bool GetIsPlaceholder() const { return mIsPlaceholder; } void SetIsPlaceholder(bool val) { mIsPlaceholder = val; } -protected: - mutable wxRect mDisplayRect; +public: + // Cache of values to colour pixels of Spectrogram - used by TrackArtist + mutable std::unique_ptr mSpecPxCache; - double mOffset; +protected: + mutable wxRect mDisplayRect {}; + + double mOffset { 0 }; int mRate; - int mDirty; - bool mIsCutLine; + int mDirty { 0 }; std::unique_ptr mSequence; std::unique_ptr mEnvelope; mutable std::unique_ptr mWaveCache; - mutable ODLock mWaveCacheMutex; + mutable ODLock mWaveCacheMutex {}; mutable std::unique_ptr mSpecCache; - SampleBuffer mAppendBuffer; - size_t mAppendBufferLen; + SampleBuffer mAppendBuffer {}; + size_t mAppendBufferLen { 0 }; // Cut Lines are nothing more than ordinary wave clips, with the // offset relative to the start of the clip. - WaveClipHolders mCutLines; + WaveClipHolders mCutLines {}; // AWD, Oct. 2009: for whitespace-at-end-of-selection pasting - bool mIsPlaceholder; + bool mIsPlaceholder { false }; }; #endif diff --git a/src/WaveTrack.cpp b/src/WaveTrack.cpp index f0192fdc7..0eebf39ba 100644 --- a/src/WaveTrack.cpp +++ b/src/WaveTrack.cpp @@ -132,7 +132,8 @@ WaveTrack::WaveTrack(const WaveTrack &orig): Init(orig); for (const auto &clip : orig.mClips) - mClips.push_back(make_movable(*clip, mDirManager)); + mClips.push_back + ( make_movable( *clip, mDirManager, true ) ); } // Copy the track metadata but not the contents. @@ -630,7 +631,7 @@ bool WaveTrack::Trim (double t0, double t1) -Track::Holder WaveTrack::Copy(double t0, double t1) const +Track::Holder WaveTrack::Copy(double t0, double t1, bool forClipboard) const { if (t1 <= t0) return{}; @@ -641,6 +642,10 @@ Track::Holder WaveTrack::Copy(double t0, double t1) const newTrack->Init(*this); + // PRL: Why shouldn't cutlines be copied and pasted too? I don't know, but + // that was the old behavior. But this function is also used by the + // Duplicate command and I changed its behavior in that case. + for (const auto &clip : mClips) { if (t0 <= clip->GetStartTime() && t1 >= clip->GetEndTime()) @@ -649,25 +654,20 @@ Track::Holder WaveTrack::Copy(double t0, double t1) const //printf("copy: clip %i is in copy region\n", (int)clip); newTrack->mClips.push_back - (make_movable(*clip, mDirManager)); + (make_movable(*clip, mDirManager, ! forClipboard)); WaveClip *const newClip = newTrack->mClips.back().get(); - newClip->RemoveAllCutLines(); newClip->Offset(-t0); } - else - if (t1 > clip->GetStartTime() && t0 < clip->GetEndTime()) + else if (t1 > clip->GetStartTime() && t0 < clip->GetEndTime()) { // Clip is affected by command //printf("copy: clip %i is affected by command\n", (int)clip); - auto newClip = make_movable(*clip, mDirManager); - newClip->RemoveAllCutLines(); - double clip_t0 = t0; - double clip_t1 = t1; - if (clip_t0 < clip->GetStartTime()) - clip_t0 = clip->GetStartTime(); - if (clip_t1 > clip->GetEndTime()) - clip_t1 = clip->GetEndTime(); + const double clip_t0 = std::max(t0, clip->GetStartTime()); + const double clip_t1 = std::min(t1, clip->GetEndTime()); + + auto newClip = make_movable + (*clip, mDirManager, ! forClipboard, clip_t0, clip_t1); //printf("copy: clip_t0=%f, clip_t1=%f\n", clip_t0, clip_t1); @@ -675,27 +675,16 @@ Track::Holder WaveTrack::Copy(double t0, double t1) const if (newClip->GetOffset() < 0) newClip->SetOffset(0); - //printf("copy: clip offset is now %f\n", newClip->GetOffset()); - - if (!newClip->CreateFromCopy(clip_t0, clip_t1, clip.get())) - { - //printf("paste: CreateFromCopy(%f, %f, %i) returns false, quitting\n", - // clip_t0, clip_t1, (int)clip); - // JKC: July 2007, previously we did 'return false' here which - // could leave *dest undefined. - // I think this is dealing with clips that don't have any sequence content - // i.e. we don't copy cut lines and such - anyone like to explain more? - } - else - { - newTrack->mClips.push_back(std::move(newClip)); // transfer ownership - } + newTrack->mClips.push_back(std::move(newClip)); // transfer ownership } } // AWD, Oct 2009: If the selection ends in whitespace, create a placeholder // clip representing that whitespace - if (newTrack->GetEndTime() + 1.0 / newTrack->GetRate() < t1 - t0) + // PRL: Only if we want the track for pasting into other tracks. Not if it + // goes directly into a project as in the Duplicate command. + if (forClipboard && + newTrack->GetEndTime() + 1.0 / newTrack->GetRate() < t1 - t0) { auto placeholder = make_movable(mDirManager, newTrack->GetSampleFormat(), @@ -1069,7 +1058,8 @@ bool WaveTrack::HandleClear(double t0, double t1, { if (!clip->ClearAndAddCutLine(t0,t1)) return false; - } else + } + else { if (split) { // Three cases: @@ -1088,11 +1078,13 @@ bool WaveTrack::HandleClear(double t0, double t1, // NEW clips out of the left and right halves... // left - clipsToAdd.push_back(make_movable(*clip, mDirManager)); + clipsToAdd.push_back + ( make_movable( *clip, mDirManager, true ) ); clipsToAdd.back()->Clear(t0, clip->GetEndTime()); // right - clipsToAdd.push_back(make_movable(*clip, mDirManager)); + clipsToAdd.push_back + ( make_movable( *clip, mDirManager, true ) ); WaveClip *const right = clipsToAdd.back().get(); right->Clear(clip->GetStartTime(), t1); right->Offset(t1 - clip->GetStartTime()); @@ -1349,7 +1341,8 @@ bool WaveTrack::Paste(double t0, const Track *src) // AWD Oct. 2009: Don't actually paste in placeholder clips if (!clip->GetIsPlaceholder()) { - auto newClip = make_movable(*clip, mDirManager); + auto newClip = + make_movable( *clip, mDirManager, true ); newClip->Resample(mRate); newClip->Offset(t0); newClip->MarkChanged(); @@ -2377,7 +2370,7 @@ bool WaveTrack::SplitAt(double t) if(t - 1.0/c->GetRate() >= c->GetOffset()) c->GetEnvelope()->Insert(t - c->GetOffset() - 1.0/c->GetRate(), val); // frame end points c->GetEnvelope()->Insert(t - c->GetOffset(), val); - auto newClip = make_movable(*c, mDirManager); + auto newClip = make_movable( *c, mDirManager, true ); if (!c->Clear(t, c->GetEndTime())) { return false; diff --git a/src/WaveTrack.h b/src/WaveTrack.h index 2fbb03ed5..977ca6474 100644 --- a/src/WaveTrack.h +++ b/src/WaveTrack.h @@ -167,8 +167,14 @@ class AUDACITY_DLL_API WaveTrack final : public Track { // Track::Holder Cut(double t0, double t1) override; - Track::Holder Copy(double t0, double t1) const override; + + // 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 */; + bool Clear(double t0, double t1) override; bool Paste(double t0, const Track *src) override; bool ClearAndPaste(double t0, double t1,