diff --git a/src/Envelope.cpp b/src/Envelope.cpp index 4a84e361c..67eff8799 100644 --- a/src/Envelope.cpp +++ b/src/Envelope.cpp @@ -74,7 +74,7 @@ void Envelope::RescaleValues(double minValue, double maxValue) // rescale all points for( unsigned int i = 0; i < mEnv.size(); i++ ) { factor = (mEnv[i].GetVal() - oldMinValue) / (oldMaxValue - oldMinValue); - mEnv[i].SetVal(mMinValue + (mMaxValue - mMinValue) * factor); + mEnv[i].SetVal( this, mMinValue + (mMaxValue - mMinValue) * factor ); } } @@ -112,13 +112,13 @@ void Envelope::SetDragPointValid(bool valid) // off screen and at default height. // temporary state when dragging only! mEnv[mDragPoint].SetT(big); - mEnv[mDragPoint].SetVal(mDefaultValue); + mEnv[mDragPoint].SetVal( this, mDefaultValue ); return; } else if ( mDragPoint + 1 == size ) { // Put the point at the height of the last point, but also off screen. mEnv[mDragPoint].SetT(big); - mEnv[mDragPoint].SetVal(mEnv[ size - 1 ].GetVal()); + mEnv[mDragPoint].SetVal( this, mEnv[ size - 1 ].GetVal() ); } else { // Place it exactly on its right neighbour. @@ -126,7 +126,7 @@ void Envelope::SetDragPointValid(bool valid) // a light dot, as if it were deleted. const auto &neighbor = mEnv[mDragPoint + 1]; mEnv[mDragPoint].SetT(neighbor.GetT()); - mEnv[mDragPoint].SetVal(neighbor.GetVal()); + mEnv[mDragPoint].SetVal( this, neighbor.GetVal() ); } } } @@ -154,7 +154,7 @@ void Envelope::MoveDragPoint(double newWhen, double value) // This might temporary violate the constraint that at most two // points share a time value. dragPoint.SetT(tt); - dragPoint.SetVal(value); + dragPoint.SetVal( this, value ); } void Envelope::ClearDragPoint() @@ -171,13 +171,26 @@ void Envelope::SetRange(double minValue, double maxValue) { mMaxValue = maxValue; mDefaultValue = ClampValue(mDefaultValue); for( unsigned int i = 0; i < mEnv.size(); i++ ) - mEnv[i].SetVal(mEnv[i].GetVal()); // this clamps the value to the NEW range + mEnv[i].SetVal( this, mEnv[i].GetVal() ); // this clamps the value to the NEW range } -EnvPoint *Envelope::AddPointAtEnd( double t, double val ) +// This is used only during construction of an Envelope by complete or partial +// copy of another, or when truncating a track. +void Envelope::AddPointAtEnd( double t, double val ) { - mEnv.push_back(EnvPoint(this, t, val)); - return &mEnv.back(); + mEnv.push_back( EnvPoint{ t, val } ); + + // Assume copied points were stored by nondecreasing time. + // Allow no more than two points at exactly the same time. + // Maybe that happened, because extra points were inserted at the boundary + // of the copied range, which were not in the source envelope. + auto nn = mEnv.size() - 1; + while ( nn >= 2 && mEnv[ nn - 2 ].GetT() == t ) { + // Of three or more points at the same time, erase one in the middle, + // not the one newly added. + mEnv.erase( mEnv.begin() + nn - 1 ); + --nn; + } } Envelope::Envelope(const Envelope &orig, double t0, double t1) @@ -342,7 +355,8 @@ XMLTagHandler *Envelope::HandleXMLChild(const wxChar *tag) if (wxStrcmp(tag, wxT("controlpoint"))) return NULL; - return AddPointAtEnd(0,0); + mEnv.push_back( EnvPoint{} ); + return &mEnv.back(); } void Envelope::WriteXML(XMLWriter &xmlFile) const @@ -592,36 +606,62 @@ bool EnvelopeEditor::MouseEvent(const wxMouseEvent & event, wxRect & r, return false; } -void Envelope::CollapseRegion(double t0, double t1) +void Envelope::CollapseRegion( double t0, double t1, double sampleTime ) // NOFAIL-GUARANTEE { - // This gets called when somebody clears samples. All of the - // control points within the region disappear and the points - // to the right get shifted over. + // This gets called when somebody clears samples. - t0 -= mOffset; - t1 -= mOffset; + // Snip points in the interval (t0, t1), shift values left at times after t1. + // For the boundaries of the interval, preserve the left-side limit at the + // start and right-side limit at the end. - t0 = std::max(0.0, std::min(mTrackLen, t0)); - t1 = std::max(0.0, std::min(mTrackLen, t1)); + const auto epsilon = sampleTime / 2; + t0 = std::max( 0.0, std::min( mTrackLen, t0 - mOffset ) ); + t1 = std::max( 0.0, std::min( mTrackLen, t1 - mOffset ) ); - int len = mEnv.size(); - int i; - - // Remove points in deleted region. - for (i = 0; i < len - 0; i++) - if (mEnv[i].GetT() >= t0 && mEnv[i].GetT() < t1) { - Delete(i); - len--; - i--; + // Determine the start of the range of points to remove from the array. + auto range0 = EqualRange( t0, 0 ); + auto begin = range0.first; + if ( begin == range0.second ) { + if ( t0 > epsilon ) { + // There was no point exactly at t0; + // insert a point to preserve the value. + auto val = GetValueRelative( t0 ); + InsertOrReplaceRelative( t0, val ); + ++begin; } + } + else + // We will keep the first (or only) point that was at t0. + ++begin; + + // We want end to be the index one past the range of points to remove from + // the array. + // At first, find index of the first point after t1: + auto range1 = EqualRange( t1, 0 ); + auto end = range1.second; + if ( range1.first == end ) { + if ( mTrackLen - t1 > epsilon ) { + // There was no point exactly at t1; insert a point to preserve the value. + auto val = GetValueRelative( t1 ); + InsertOrReplaceRelative( t1, val ); + // end is now the index of this NEW point and that is correct. + } + } + else + // We will keep the last (or only) point that was at t1. + --end; + + mEnv.erase( mEnv.begin() + begin, mEnv.begin() + end ); // Shift points left after deleted region. - for (i = 0; i < len; i++) - if (mEnv[i].GetT() >= t1) - mEnv[i].SetT(mEnv[i].GetT() - (t1 - t0)); + auto len = mEnv.size(); + for ( size_t i = begin; i < len; ++i ) { + auto &point = mEnv[i]; + point.SetT( point.GetT() - (t1 - t0) ); + } - mTrackLen -= (t1-t0); + mTrackLen -= ( t1 - t0 ); } // This operation is trickier than it looks; the basic rub is that @@ -869,17 +909,37 @@ void Envelope::RemoveUnneededPoints(double time, double tolerence) } } -void Envelope::InsertSpace(double t0, double tlen) +void Envelope::InsertSpace( double t0, double tlen ) // NOFAIL-GUARANTEE { t0 -= mOffset; - unsigned int len = mEnv.size(); - unsigned int i; + // Preserve the left-side limit at the split. + auto val = GetValueRelative( t0 ); + auto range = EqualRange( t0, 0 ); + + size_t index; + if ( range.first < range.second ) + // There is already a control point. + index = 1 + range.first; + else + // Make a control point. + index = 1 + InsertOrReplaceRelative( t0, val ); + + // Shift points. + auto len = mEnv.size(); + for ( ; index < len; ++index ) { + auto &point = mEnv[ index ]; + point.SetT( point.GetT() + tlen ); + } + + // Preserve the right-side limit. + if ( 1 + range.first < range.second ) + // There was a control point already. + ; + else + InsertOrReplaceRelative( t0 + tlen, val ); - for (i = 0; i < len; i++) - if (mEnv[i].GetT() > t0) - mEnv[i].SetT(mEnv[i].GetT() + tlen); mTrackLen += tlen; } @@ -898,7 +958,7 @@ int Envelope::Reassign(double when, double value) if (i >= len || when < mEnv[i].GetT()) return -1; - mEnv[i].SetVal(value); + mEnv[i].SetVal( this, value ); return 0; } @@ -978,15 +1038,12 @@ int Envelope::InsertOrReplaceRelative(double when, double value) while (i < len && when > mEnv[i].GetT()) i++; - if(i < len && when == mEnv[i].GetT()) { - + if(i < len && when == mEnv[i].GetT()) // modify existing - mEnv[i].SetVal(value); - - } + mEnv[i].SetVal( this, value ); else { // Add NEW - EnvPoint e(this, when, value); + EnvPoint e{ when, value }; if (i < len) { Insert(i, e); } else { @@ -1007,12 +1064,12 @@ std::pair Envelope::EqualRange( double when, double sampleTime ) const auto end = mEnv.end(); auto first = std::lower_bound( begin, end, - EnvPoint{ const_cast( this ), when - tolerance, 0.0 }, + EnvPoint{ when - tolerance, 0.0 }, []( const EnvPoint &point1, const EnvPoint &point2 ) { return point1.GetT() < point2.GetT(); } ); auto after = first; - while ( after != end && after->GetT() < when + tolerance ) + while ( after != end && after->GetT() <= when + tolerance ) ++after; return { first - begin, after - begin }; } @@ -1026,16 +1083,20 @@ void Envelope::SetOffset(double newOffset) } void Envelope::SetTrackLen(double trackLen) +// NOFAIL-GUARANTEE { - mTrackLen = trackLen; + // Preserve the right-side limit at trackLen. + bool needPoint = ( trackLen < mTrackLen ); + double value; + if ( needPoint ) + value = GetValueRelative( trackLen ); - int len = mEnv.size(); - for (int i = 0; i < len; i++) - if (mEnv[i].GetT() > mTrackLen) { - Delete(i); - len--; - i--; - } + mTrackLen = trackLen; + int newLen = EqualRange( trackLen, 0 ).second; + mEnv.resize( newLen ); + + if ( needPoint ) + AddPointAtEnd( mTrackLen, value ); } void Envelope::RescaleTimes( double newLength ) @@ -1063,6 +1124,14 @@ double Envelope::GetValue(double t) const return temp; } +double Envelope::GetValueRelative(double t) const +{ + double temp; + + GetValuesRelative(&temp, 1, t, 1.0); + return temp; +} + // relative time /// @param Lo returns last index at or before this time, maybe -1 /// @param Hi returns first index after this time, maybe past the end @@ -1129,7 +1198,12 @@ void Envelope::GetValues(double *buffer, int bufferLen, { // Convert t0 from absolute to clip-relative time t0 -= mOffset; + GetValuesRelative( buffer, bufferLen, t0, tstep); +} +void Envelope::GetValuesRelative(double *buffer, int bufferLen, + double t0, double tstep) const +{ // JC: If bufferLen ==0 we have probably just allocated a zero sized buffer. // wxASSERT( bufferLen > 0 ); diff --git a/src/Envelope.h b/src/Envelope.h index d560b5358..9db7f1dfc 100644 --- a/src/Envelope.h +++ b/src/Envelope.h @@ -36,12 +36,13 @@ class ZoomInfo; class EnvPoint final : public XMLTagHandler { public: - inline EnvPoint(Envelope *envelope, double t, double val); + EnvPoint() {} + inline EnvPoint( double t, double val ) : mT{ t }, mVal{ val } {} double GetT() const { return mT; } void SetT(double t) { mT = t; } double GetVal() const { return mVal; } - inline void SetVal(double val); + inline void SetVal( Envelope *pEnvelope, double val ); bool HandleXMLTag(const wxChar *tag, const wxChar **attrs) override { @@ -52,7 +53,7 @@ public: if (!wxStrcmp(attr, wxT("t"))) SetT(Internat::CompatibleToDouble(value)); else if (!wxStrcmp(attr, wxT("val"))) - SetVal(Internat::CompatibleToDouble(value)); + SetVal( nullptr, Internat::CompatibleToDouble(value) ); } return true; } @@ -66,9 +67,8 @@ public: } private: - Envelope *mEnvelope; - double mT; - double mVal; + double mT {}; + double mVal {}; }; @@ -118,8 +118,11 @@ public: float zoomMin, float zoomMax, bool mirrored) const; // Handling Cut/Copy/Paste events - void CollapseRegion(double t0, double t1); + // sampleTime determines when the endpoint of the collapse is near enough + // to an endpoint of the domain, that an extra control point is not needed. + void CollapseRegion(double t0, double t1, double sampleTime); void Paste(double t0, const Envelope *e); + void InsertSpace(double t0, double tlen); void RemoveUnneededPoints(double time = -1, double tolerence = 0.001); @@ -145,6 +148,9 @@ public: (double *buffer, int bufferLen, int leftOffset, const ZoomInfo &zoomInfo) const; private: + double GetValueRelative(double t) const; + void GetValuesRelative + (double *buffer, int len, double t0, double tstep) const; // relative time int NumberOfPointsAfter(double t) const; // relative time @@ -212,7 +218,7 @@ private: void MoveDragPoint(double newWhen, double value); // May delete the drag point. Restores envelope consistency. void ClearDragPoint(); - EnvPoint * AddPointAtEnd( double t, double val ); + void AddPointAtEnd( double t, double val ); void CopyRange(const Envelope &orig, size_t begin, size_t end); // relative time void BinarySearchForTime( int &Lo, int &Hi, double t ) const; @@ -243,16 +249,11 @@ private: mutable int mSearchGuess { -2 }; }; -inline EnvPoint::EnvPoint(Envelope *envelope, double t, double val) +inline void EnvPoint::SetVal( Envelope *pEnvelope, double val ) { - mEnvelope = envelope; - mT = t; - mVal = mEnvelope->ClampValue(val); -} - -inline void EnvPoint::SetVal(double val) -{ - mVal = mEnvelope->ClampValue(val); + if ( pEnvelope ) + val = pEnvelope->ClampValue(val); + mVal = val; } // A class that holds state for the duration of dragging diff --git a/src/TimeTrack.cpp b/src/TimeTrack.cpp index 4edfac787..bc2260e61 100644 --- a/src/TimeTrack.cpp +++ b/src/TimeTrack.cpp @@ -22,6 +22,7 @@ #include "widgets/Ruler.h" #include "Envelope.h" #include "Prefs.h" +#include "Project.h" #include "Internat.h" #include "ViewInfo.h" #include "AllThemeResources.h" @@ -108,7 +109,8 @@ Track::Holder TimeTrack::Copy( double t0, double t1, bool ) const void TimeTrack::Clear(double t0, double t1) { - mEnvelope->CollapseRegion(t0, t1); + auto sampleTime = 1.0 / GetActiveProject()->GetRate(); + mEnvelope->CollapseRegion( t0, t1, sampleTime ); } void TimeTrack::Paste(double t, const Track * src) diff --git a/src/WaveClip.cpp b/src/WaveClip.cpp index b49ece960..aea155503 100644 --- a/src/WaveClip.cpp +++ b/src/WaveClip.cpp @@ -1607,7 +1607,7 @@ void WaveClip::Paste(double t0, const WaveClip* other) mCutLines.push_back(std::move(holder)); } -void WaveClip::InsertSilence(double t, double len) +void WaveClip::InsertSilence( double t, double len, double *pEnvelopeValue ) // STRONG-GUARANTEE { sampleCount s0; @@ -1619,10 +1619,35 @@ void WaveClip::InsertSilence(double t, double len) // use NOFAIL-GUARANTEE OffsetCutLines(t, len); - GetEnvelope()->InsertSpace(t, len); + + const auto sampleTime = 1.0 / GetRate(); + auto pEnvelope = GetEnvelope(); + if ( pEnvelopeValue ) { + + // Preserve limit value at the end + auto oldLen = pEnvelope->GetTrackLen(); + auto oldT = pEnvelope->GetOffset() + oldLen; + auto newLen = oldLen + len; + pEnvelope->InsertOrReplace( oldT, pEnvelope->GetValue( oldT ) ); + + // Ramp across the silence to the given value + pEnvelope->SetTrackLen( newLen ); + pEnvelope->InsertOrReplace + ( pEnvelope->GetOffset() + newLen, *pEnvelopeValue ); + } + else + pEnvelope->InsertSpace( t, len ); + MarkChanged(); } +void WaveClip::AppendSilence( double len, double envelopeValue ) +// STRONG-GUARANTEE +{ + auto t = GetEndTime(); + InsertSilence( t, len, &envelopeValue ); +} + void WaveClip::Clear(double t0, double t1) // STRONG-GUARANTEE { @@ -1676,7 +1701,8 @@ void WaveClip::Clear(double t0, double t1) } // Collapse envelope - GetEnvelope()->CollapseRegion(t0, t1); + auto sampleTime = 1.0 / GetRate(); + GetEnvelope()->CollapseRegion( t0, t1, sampleTime ); if (t0 < GetStartTime()) Offset(-(GetStartTime() - t0)); @@ -1728,7 +1754,8 @@ void WaveClip::ClearAndAddCutLine(double t0, double t1) GetSequence()->Delete(s0, s1-s0); // Collapse envelope - GetEnvelope()->CollapseRegion(t0, t1); + auto sampleTime = 1.0 / GetRate(); + GetEnvelope()->CollapseRegion( t0, t1, sampleTime ); if (t0 < GetStartTime()) Offset(-(GetStartTime() - t0)); diff --git a/src/WaveClip.h b/src/WaveClip.h index 1c45da356..0438b34d8 100644 --- a/src/WaveClip.h +++ b/src/WaveClip.h @@ -321,7 +321,11 @@ public: /** Insert silence - note that this is an efficient operation for large * amounts of silence */ - void InsertSilence(double t, double len); + void InsertSilence( double t, double len, double *pEnvelopeValue = nullptr ); + + /** Insert silence at the end, and causes the envelope to ramp + linearly to the given value */ + void AppendSilence( double len, double envelopeValue ); /// Get access to cut lines list WaveClipHolders &GetCutLines() { return mCutLines; } diff --git a/src/WaveTrack.cpp b/src/WaveTrack.cpp index be88dc146..21decc67c 100644 --- a/src/WaveTrack.cpp +++ b/src/WaveTrack.cpp @@ -1138,26 +1138,7 @@ void WaveTrack::HandleClear(double t0, double t1, clipsToDelete.push_back( clip.get() ); auto newClip = make_movable( *clip, mDirManager, true ); - /* We are going to DELETE part of the clip here. The clip may - * have envelope points, and we need to ensure that the envelope - * outside of the cleared region is not affected. This means - * putting in "glue" points where the clip enters and leaves the - * region being cleared. If one of the ends of the clip is inside - * the region, then one of the glue points will be redundant. */ // clip->Clear keeps points < t0 and >= t1 via Envelope::CollapseRegion - if (clip->GetEnvelope()->GetNumberOfPoints() > 0) { // don't insert env pts if none exist - double val; - if (clip->WithinClip(t0)) { - // start of region within clip - val = clip->GetEnvelope()->GetValue(t0); - newClip->GetEnvelope()->InsertOrReplace(t0 - 1.0 / clip->GetRate(), val); - } - if (clip->WithinClip(t1)) - { // end of region within clip - val = clip->GetEnvelope()->GetValue(t1); - newClip->GetEnvelope()->InsertOrReplace(t1 , val); - } - } newClip->Clear(t0,t1); newClip->GetEnvelope()->RemoveUnneededPoints(t0); @@ -1583,12 +1564,15 @@ void WaveTrack::Join(double t0, double t1) if (clip->GetOffset() - t > (1.0 / mRate)) { double addedSilence = (clip->GetOffset() - t); //printf("Adding %.6f seconds of silence\n"); - newClip->InsertSilence(t, addedSilence); + auto offset = clip->GetOffset(); + auto value = clip->GetEnvelope()->GetValue( offset ); + newClip->AppendSilence( addedSilence, value ); t += addedSilence; } //printf("Pasting at %.6f\n", t); newClip->Paste(t, clip); + t = newClip->GetEndTime(); auto it = FindClip(mClips, clip); @@ -2398,12 +2382,6 @@ void WaveTrack::SplitAt(double t) { double val; t = LongSamplesToTime(TimeToLongSamples(t)); // put t on a sample - val = c->GetEnvelope()->GetValue(t); - //make two envelope points to preserve the value. - //handle the case where we split on the 1st sample (without this we hit an assert) - if(t - 1.0/c->GetRate() >= c->GetOffset()) - c->GetEnvelope()->InsertOrReplace(t - 1.0 / c->GetRate(), val); // frame end points - c->GetEnvelope()->InsertOrReplace(t, val); auto newClip = make_movable( *c, mDirManager, true ); c->Clear(t, c->GetEndTime()); newClip->Clear(c->GetStartTime(), t);