1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-08-10 17:11:17 +02:00

Wave track duplication: don't append empty clips, do copy cutlines

This commit is contained in:
Paul Licameli 2017-03-09 09:24:51 -05:00
commit 9119d703ce
13 changed files with 159 additions and 128 deletions

View File

@ -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<LabelTrack>(GetDirManager());
const auto lt = static_cast<LabelTrack*>(tmp.get());

View File

@ -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);

View File

@ -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()));

View File

@ -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{};

View File

@ -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;

View File

@ -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);

View File

@ -385,12 +385,10 @@ bool Sequence::GetRMS(sampleCount start, sampleCount len,
return true;
}
bool Sequence::Copy(sampleCount s0, sampleCount s1, std::unique_ptr<Sequence> &dest) const
std::unique_ptr<Sequence> 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<Sequence> &d
wxUnusedVar(numBlocks);
wxASSERT(b0 <= b1);
dest = std::make_unique<Sequence>(mDirManager, mSampleFormat);
auto dest = std::make_unique<Sequence>(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<Sequence> &d
dest->AppendBlock(block); // Increase ref count or duplicate file
}
return ConsistencyCheck(wxT("Sequence::Copy()"));
if (! ConsistencyCheck(wxT("Sequence::Copy()")))
return {};
return dest;
}
namespace {

View File

@ -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<Sequence> &dest) const;
std::unique_ptr<Sequence> Copy(sampleCount s0, sampleCount s1) const;
bool Paste(sampleCount s0, const Sequence *src);
size_t GetIdealAppendLen() const;

View File

@ -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;}

View File

@ -300,21 +300,22 @@ static void ComputeSpectrumUsingRealFFTf
}
}
WaveClip::WaveClip(const std::shared_ptr<DirManager> &projDirManager, sampleFormat format, int rate)
WaveClip::WaveClip(const std::shared_ptr<DirManager> &projDirManager,
sampleFormat format, int rate)
{
mOffset = 0;
mRate = rate;
mSequence = std::make_unique<Sequence>(projDirManager, format);
mEnvelope = std::make_unique<Envelope>();
mWaveCache = std::make_unique<WaveCache>();
mSpecCache = std::make_unique<SpecCache>();
mSpecPxCache = std::make_unique<SpecPxCache>(1);
mAppendBufferLen = 0;
mDirty = 0;
mIsPlaceholder = false;
}
WaveClip::WaveClip(const WaveClip& orig, const std::shared_ptr<DirManager> &projDirManager)
WaveClip::WaveClip(const WaveClip& orig,
const std::shared_ptr<DirManager> &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<DirManager> &proj
mOffset = orig.mOffset;
mRate = orig.mRate;
mSequence = std::make_unique<Sequence>(*orig.mSequence, projDirManager);
mEnvelope = std::make_unique<Envelope>();
mEnvelope->Paste(0.0, orig.mEnvelope.get());
mEnvelope->SetOffset(orig.GetOffset());
mEnvelope->SetTrackLen((orig.mSequence->GetNumSamples().as_double()) / orig.mRate);
mWaveCache = std::make_unique<WaveCache>();
mSpecCache = std::make_unique<SpecCache>();
mSpecPxCache = std::make_unique<SpecPxCache>(1);
if ( copyCutlines )
for (const auto &clip: orig.mCutLines)
mCutLines.push_back(make_movable<WaveClip>(*clip, projDirManager));
mCutLines.push_back
( make_movable<WaveClip>( *clip, projDirManager, true ) );
mAppendBufferLen = 0;
mDirty = 0;
mIsPlaceholder = orig.GetIsPlaceholder();
}
WaveClip::WaveClip(const WaveClip& orig,
const std::shared_ptr<DirManager> &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<WaveCache>();
mSpecCache = std::make_unique<SpecCache>();
mSpecPxCache = std::make_unique<SpecPxCache>(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<Envelope>();
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<Sequence> oldSequence = std::move(mSequence);
if (!other->mSequence->Copy(s0, s1, mSequence))
{
mSequence = std::move(oldSequence);
return false;
}
mEnvelope = std::make_unique<Envelope>();
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<WaveClip>(*other, mSequence->GetDirManager());
std::make_unique<WaveClip>(*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<WaveClip>(*cutline, mSequence->GetDirManager()));
make_movable<WaveClip>
( *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<WaveClip>
(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<WaveClip>
(*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

View File

@ -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<DirManager> &projDirManager);
WaveClip(const WaveClip& orig,
const std::shared_ptr<DirManager> &projDirManager,
bool copyCutlines);
// Copy only a range from the given WaveClip
WaveClip(const WaveClip& orig,
const std::shared_ptr<DirManager> &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<SpecPxCache> 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<SpecPxCache> mSpecPxCache;
double mOffset;
protected:
mutable wxRect mDisplayRect {};
double mOffset { 0 };
int mRate;
int mDirty;
bool mIsCutLine;
int mDirty { 0 };
std::unique_ptr<Sequence> mSequence;
std::unique_ptr<Envelope> mEnvelope;
mutable std::unique_ptr<WaveCache> mWaveCache;
mutable ODLock mWaveCacheMutex;
mutable ODLock mWaveCacheMutex {};
mutable std::unique_ptr<SpecCache> 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

View File

@ -132,7 +132,8 @@ WaveTrack::WaveTrack(const WaveTrack &orig):
Init(orig);
for (const auto &clip : orig.mClips)
mClips.push_back(make_movable<WaveClip>(*clip, mDirManager));
mClips.push_back
( make_movable<WaveClip>( *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<WaveClip>(*clip, mDirManager));
(make_movable<WaveClip>(*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<WaveClip>(*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<WaveClip>
(*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
}
}
}
// 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<WaveClip>(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<WaveClip>(*clip, mDirManager));
clipsToAdd.push_back
( make_movable<WaveClip>( *clip, mDirManager, true ) );
clipsToAdd.back()->Clear(t0, clip->GetEndTime());
// right
clipsToAdd.push_back(make_movable<WaveClip>(*clip, mDirManager));
clipsToAdd.push_back
( make_movable<WaveClip>( *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<WaveClip>(*clip, mDirManager);
auto newClip =
make_movable<WaveClip>( *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<WaveClip>(*c, mDirManager);
auto newClip = make_movable<WaveClip>( *c, mDirManager, true );
if (!c->Clear(t, c->GetEndTime()))
{
return false;

View File

@ -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,