1 /**********************************************************************
2 
3   Audacity: A Digital Audio Editor
4 
5   Envelope.cpp
6 
7   Dominic Mazzoni (original author)
8   Dr William Bland (integration - the Calculus kind)
9   Monty (xiphmont) (important bug fixes)
10 
11 *******************************************************************//**
12 
13 \class Envelope
14 \brief Piecewise linear or piecewise exponential function from double to double
15 
16   This class manages an envelope - i.e. a function
17   that the user can edit by dragging control points around.  The
18   envelope is most commonly used to control the amplitude of a
19   waveform, but it is also used to shape the Equalization curve, and in
20   TimeTrack to determine a time warp.
21 
22 *//****************************************************************//**
23 
24 \class EnvPoint
25 \brief EnvPoint, derived from XMLTagHandler, provides Envelope with
26 a draggable point type.
27 
28 *//*******************************************************************/
29 
30 #include "Envelope.h"
31 
32 
33 
34 #include <math.h>
35 
36 #include <wx/wxcrtvararg.h>
37 #include <wx/brush.h>
38 #include <wx/pen.h>
39 #include <wx/textfile.h>
40 #include <wx/log.h>
41 #include <wx/utils.h>
42 
43 static const double VALUE_TOLERANCE = 0.001;
44 
Envelope(bool exponential,double minValue,double maxValue,double defaultValue)45 Envelope::Envelope(bool exponential, double minValue, double maxValue, double defaultValue)
46    : mDB(exponential)
47    , mMinValue(minValue)
48    , mMaxValue(maxValue)
49    , mDefaultValue { ClampValue(defaultValue) }
50 {
51 }
52 
~Envelope()53 Envelope::~Envelope()
54 {
55 }
56 
ConsistencyCheck()57 bool Envelope::ConsistencyCheck()
58 {
59    bool consistent = true;
60 
61    bool disorder;
62    do {
63       disorder = false;
64       for ( size_t ii = 0, count = mEnv.size(); ii < count; ) {
65          // Find range of points with equal T
66          const double thisT = mEnv[ii].GetT();
67          double nextT = 0.0f;
68          auto nextI = ii + 1;
69          while ( nextI < count && thisT == ( nextT = mEnv[nextI].GetT() ) )
70             ++nextI;
71 
72          if ( nextI < count && nextT < thisT )
73             disorder = true;
74 
75          while ( nextI - ii > 2 ) {
76             // too many coincident time values
77             if ((int)ii == mDragPoint || (int)nextI - 1 == mDragPoint)
78                // forgivable
79                ;
80             else {
81                consistent = false;
82                // repair it
83                Delete( nextI - 2 );
84                if (mDragPoint >= (int)nextI - 2)
85                   --mDragPoint;
86                --nextI, --count;
87                // wxLogError
88             }
89          }
90 
91          ii = nextI;
92       }
93 
94       if (disorder) {
95          consistent = false;
96          // repair it
97          std::stable_sort( mEnv.begin(), mEnv.end(),
98             []( const EnvPoint &a, const EnvPoint &b )
99                { return a.GetT() < b.GetT(); } );
100       }
101    } while ( disorder );
102 
103    return consistent;
104 }
105 
106 /// Rescale function for time tracks (could also be used for other tracks though).
107 /// This is used to load old time track project files where the envelope used a 0 to 1
108 /// range instead of storing the actual time track values. This function will change the range of the envelope
109 /// and rescale all envelope points accordingly (unlike SetRange, which clamps the envelope points to the NEW range).
110 /// @minValue - the NEW minimum value
111 /// @maxValue - the NEW maximum value
RescaleValues(double minValue,double maxValue)112 void Envelope::RescaleValues(double minValue, double maxValue)
113 {
114    double oldMinValue = mMinValue;
115    double oldMaxValue = mMaxValue;
116    mMinValue = minValue;
117    mMaxValue = maxValue;
118 
119    // rescale the default value
120    double factor = (mDefaultValue - oldMinValue) / (oldMaxValue - oldMinValue);
121    mDefaultValue = ClampValue(mMinValue + (mMaxValue - mMinValue) * factor);
122 
123    // rescale all points
124    for( unsigned int i = 0; i < mEnv.size(); i++ ) {
125       factor = (mEnv[i].GetVal() - oldMinValue) / (oldMaxValue - oldMinValue);
126       mEnv[i].SetVal( this, mMinValue + (mMaxValue - mMinValue) * factor );
127    }
128 
129 }
130 
131 /// Flatten removes all points from the envelope to
132 /// make it horizontal at a chosen y-value.
133 /// @value - the y-value for the flat envelope.
Flatten(double value)134 void Envelope::Flatten(double value)
135 {
136    mEnv.clear();
137    mDefaultValue = ClampValue(value);
138 }
139 
SetDragPoint(int dragPoint)140 void Envelope::SetDragPoint(int dragPoint)
141 {
142    mDragPoint = std::max(-1, std::min(int(mEnv.size() - 1), dragPoint));
143    mDragPointValid = (mDragPoint >= 0);
144 }
145 
SetDragPointValid(bool valid)146 void Envelope::SetDragPointValid(bool valid)
147 {
148    mDragPointValid = (valid && mDragPoint >= 0);
149    if (mDragPoint >= 0 && !valid) {
150       // We're going to be deleting the point; On
151       // screen we show this by having the envelope move to
152       // the position it will have after deletion of the point.
153       // Without deleting the point we move it left or right
154       // to the same position as the previous or next point.
155 
156       static const double big = std::numeric_limits<double>::max();
157       auto size = mEnv.size();
158 
159       if( size <= 1) {
160          // There is only one point - just move it
161          // off screen and at default height.
162          // temporary state when dragging only!
163          mEnv[mDragPoint].SetT(big);
164          mEnv[mDragPoint].SetVal( this, mDefaultValue );
165          return;
166       }
167       else if ( mDragPoint + 1 == (int)size ) {
168          // Put the point at the height of the last point, but also off screen.
169          mEnv[mDragPoint].SetT(big);
170          mEnv[mDragPoint].SetVal( this, mEnv[ size - 1 ].GetVal() );
171       }
172       else {
173          // Place it exactly on its right neighbour.
174          // That way the drawing code will overpaint the dark dot with
175          // a light dot, as if it were deleted.
176          const auto &neighbor = mEnv[mDragPoint + 1];
177          mEnv[mDragPoint].SetT(neighbor.GetT());
178          mEnv[mDragPoint].SetVal( this, neighbor.GetVal() );
179       }
180    }
181 }
182 
MoveDragPoint(double newWhen,double value)183 void Envelope::MoveDragPoint(double newWhen, double value)
184 {
185    SetDragPointValid(true);
186    if (!mDragPointValid)
187       return;
188 
189    // We'll limit the drag point time to be between those of the preceding
190    // and next envelope point.
191    double limitLo = 0.0;
192    double limitHi = mTrackLen;
193 
194    if (mDragPoint > 0)
195       limitLo = std::max(limitLo, mEnv[mDragPoint - 1].GetT());
196    if (mDragPoint + 1 < (int)mEnv.size())
197       limitHi = std::min(limitHi, mEnv[mDragPoint + 1].GetT());
198 
199    EnvPoint &dragPoint = mEnv[mDragPoint];
200    const double tt =
201       std::max(limitLo, std::min(limitHi, newWhen));
202 
203    // This might temporary violate the constraint that at most two
204    // points share a time value.
205    dragPoint.SetT(tt);
206    dragPoint.SetVal( this, value );
207 }
208 
ClearDragPoint()209 void Envelope::ClearDragPoint()
210 {
211    if (!mDragPointValid && mDragPoint >= 0)
212       Delete(mDragPoint);
213 
214    mDragPoint = -1;
215    mDragPointValid = false;
216 }
217 
SetRange(double minValue,double maxValue)218 void Envelope::SetRange(double minValue, double maxValue) {
219    mMinValue = minValue;
220    mMaxValue = maxValue;
221    mDefaultValue = ClampValue(mDefaultValue);
222    for( unsigned int i = 0; i < mEnv.size(); i++ )
223       mEnv[i].SetVal( this, mEnv[i].GetVal() ); // this clamps the value to the NEW range
224 }
225 
226 // This is used only during construction of an Envelope by complete or partial
227 // copy of another, or when truncating a track.
AddPointAtEnd(double t,double val)228 void Envelope::AddPointAtEnd( double t, double val )
229 {
230    mEnv.push_back( EnvPoint{ t, val } );
231 
232    // Assume copied points were stored by nondecreasing time.
233    // Allow no more than two points at exactly the same time.
234    // Maybe that happened, because extra points were inserted at the boundary
235    // of the copied range, which were not in the source envelope.
236    auto nn = mEnv.size() - 1;
237    while ( nn >= 2 && mEnv[ nn - 2 ].GetT() == t ) {
238       // Of three or more points at the same time, erase one in the middle,
239       // not the one newly added.
240       mEnv.erase( mEnv.begin() + nn - 1 );
241       --nn;
242    }
243 }
244 
Envelope(const Envelope & orig,double t0,double t1)245 Envelope::Envelope(const Envelope &orig, double t0, double t1)
246    : mDB(orig.mDB)
247    , mMinValue(orig.mMinValue)
248    , mMaxValue(orig.mMaxValue)
249    , mDefaultValue(orig.mDefaultValue)
250 {
251    mOffset = wxMax(t0, orig.mOffset);
252    mTrackLen = wxMin(t1, orig.mOffset + orig.mTrackLen) - mOffset;
253 
254    auto range1 = orig.EqualRange( t0 - orig.mOffset, 0 );
255    auto range2 = orig.EqualRange( t1 - orig.mOffset, 0 );
256    CopyRange(orig, range1.first, range2.second);
257 }
258 
Envelope(const Envelope & orig)259 Envelope::Envelope(const Envelope &orig)
260    : mDB(orig.mDB)
261    , mMinValue(orig.mMinValue)
262    , mMaxValue(orig.mMaxValue)
263    , mDefaultValue(orig.mDefaultValue)
264 {
265    mOffset = orig.mOffset;
266    mTrackLen = orig.mTrackLen;
267    CopyRange(orig, 0, orig.GetNumberOfPoints());
268 }
269 
CopyRange(const Envelope & orig,size_t begin,size_t end)270 void Envelope::CopyRange(const Envelope &orig, size_t begin, size_t end)
271 {
272    size_t len = orig.mEnv.size();
273    size_t i = begin;
274 
275    // Create the point at 0 if it needs interpolated representation
276    if ( i > 0 )
277       AddPointAtEnd(0, orig.GetValue(mOffset));
278 
279    // Copy points from inside the copied region
280    for (; i < end; ++i) {
281       const EnvPoint &point = orig[i];
282       const double when = point.GetT() + (orig.mOffset - mOffset);
283       AddPointAtEnd(when, point.GetVal());
284    }
285 
286    // Create the final point if it needs interpolated representation
287    // If the last point of e was exactly at t1, this effectively copies it too.
288    if (mTrackLen > 0 && i < len)
289       AddPointAtEnd( mTrackLen, orig.GetValue(mOffset + mTrackLen));
290 }
291 
292 #if 0
293 /// Limit() limits a double value to a range.
294 /// TODO: Move to a general utilities source file.
295 static double Limit( double Lo, double Value, double Hi )
296 {
297    if( Value < Lo )
298       return Lo;
299    if( Value > Hi )
300       return Hi;
301    return Value;
302 }
303 #endif
304 
HandleXMLTag(const std::string_view & tag,const AttributesList & attrs)305 bool Envelope::HandleXMLTag(const std::string_view& tag, const AttributesList& attrs)
306 {
307    // Return unless it's the envelope tag.
308    if (tag != "envelope")
309       return false;
310 
311    int numPoints = -1;
312 
313    for (auto pair : attrs)
314    {
315       auto attr = pair.first;
316       auto value = pair.second;
317 
318       if (attr == "numpoints")
319          value.TryGet(numPoints);
320    }
321 
322    if (numPoints < 0)
323       return false;
324 
325    mEnv.clear();
326    mEnv.reserve(numPoints);
327    return true;
328 }
329 
HandleXMLChild(const std::string_view & tag)330 XMLTagHandler *Envelope::HandleXMLChild(const std::string_view& tag)
331 {
332    if (tag != "controlpoint")
333       return NULL;
334 
335    mEnv.push_back( EnvPoint{} );
336    return &mEnv.back();
337 }
338 
WriteXML(XMLWriter & xmlFile) const339 void Envelope::WriteXML(XMLWriter &xmlFile) const
340 // may throw
341 {
342    unsigned int ctrlPt;
343 
344    xmlFile.StartTag(wxT("envelope"));
345    xmlFile.WriteAttr(wxT("numpoints"), mEnv.size());
346 
347    for (ctrlPt = 0; ctrlPt < mEnv.size(); ctrlPt++) {
348       const EnvPoint &point = mEnv[ctrlPt];
349       xmlFile.StartTag(wxT("controlpoint"));
350       xmlFile.WriteAttr(wxT("t"), point.GetT(), 12);
351       xmlFile.WriteAttr(wxT("val"), point.GetVal(), 12);
352       xmlFile.EndTag(wxT("controlpoint"));
353    }
354 
355    xmlFile.EndTag(wxT("envelope"));
356 }
357 
Delete(int point)358 void Envelope::Delete( int point )
359 {
360    mEnv.erase(mEnv.begin() + point);
361 }
362 
Insert(int point,const EnvPoint & p)363 void Envelope::Insert(int point, const EnvPoint &p)
364 {
365    mEnv.insert(mEnv.begin() + point, p);
366 }
367 
Insert(double when,double value)368 void Envelope::Insert(double when, double value)
369 {
370    mEnv.push_back( EnvPoint{ when, value });
371 }
372 
373 /*! @excsafety{No-fail} */
CollapseRegion(double t0,double t1,double sampleDur)374 void Envelope::CollapseRegion( double t0, double t1, double sampleDur )
375 {
376    if ( t1 <= t0 )
377       return;
378 
379    // This gets called when somebody clears samples.
380 
381    // Snip points in the interval (t0, t1), shift values left at times after t1.
382    // For the boundaries of the interval, preserve the left-side limit at the
383    // start and right-side limit at the end.
384 
385    const auto epsilon = sampleDur / 2;
386    t0 = std::max( 0.0, std::min( mTrackLen, t0 - mOffset ) );
387    t1 = std::max( 0.0, std::min( mTrackLen, t1 - mOffset ) );
388    bool leftPoint = true, rightPoint = true;
389 
390    // Determine the start of the range of points to remove from the array.
391    auto range0 = EqualRange( t0, 0 );
392    auto begin = range0.first;
393    if ( begin == range0.second ) {
394       if ( t0 > epsilon ) {
395          // There was no point exactly at t0;
396          // insert a point to preserve the value.
397          auto val = GetValueRelative( t0 );
398          InsertOrReplaceRelative( t0, val );
399          ++begin;
400       }
401       else
402          leftPoint = false;
403    }
404    else
405       // We will keep the first (or only) point that was at t0.
406       ++begin;
407 
408    // We want end to be the index one past the range of points to remove from
409    // the array.
410    // At first, find index of the first point after t1:
411    auto range1 = EqualRange( t1, 0 );
412    auto end = range1.second;
413    if ( range1.first == end ) {
414       if ( mTrackLen - t1 > epsilon ) {
415          // There was no point exactly at t1; insert a point to preserve the value.
416          auto val = GetValueRelative( t1 );
417          InsertOrReplaceRelative( t1, val );
418          // end is now the index of this NEW point and that is correct.
419       }
420       else
421          rightPoint = false;
422    }
423    else
424       // We will keep the last (or only) point that was at t1.
425       --end;
426 
427    if ( end < begin ) {
428       if ( leftPoint )
429          rightPoint = false;
430    }
431    else
432       mEnv.erase( mEnv.begin() + begin, mEnv.begin() + end );
433 
434    // Shift points left after deleted region.
435    auto len = mEnv.size();
436    for ( size_t i = begin; i < len; ++i ) {
437       auto &point = mEnv[i];
438       if (rightPoint && (int)i == begin)
439          // Avoid roundoff error.
440          // Make exactly equal times of neighboring points so that we have
441          // a real discontinuity.
442          point.SetT( t0 );
443       else
444          point.SetT( point.GetT() - (t1 - t0) );
445    }
446 
447    // See if the discontinuity is removable.
448    if ( rightPoint )
449       RemoveUnneededPoints( begin, true );
450    if ( leftPoint )
451       RemoveUnneededPoints( begin - 1, false );
452 
453    mTrackLen -= ( t1 - t0 );
454 }
455 
456 // This operation is trickier than it looks; the basic rub is that
457 // a track's envelope runs the range from t=0 to t=tracklen; the t=0
458 // envelope point applies to the first sample, but the t=tracklen
459 // envelope point applies one-past the last actual sample.
460 // t0 should be in the domain of this; if not, it is trimmed.
461 /*! @excsafety{No-fail} */
PasteEnvelope(double t0,const Envelope * e,double sampleDur)462 void Envelope::PasteEnvelope( double t0, const Envelope *e, double sampleDur )
463 {
464    const bool wasEmpty = (this->mEnv.size() == 0);
465    auto otherSize = e->mEnv.size();
466    const double otherDur = e->mTrackLen;
467    const auto otherOffset = e->mOffset;
468    const auto deltat = otherOffset + otherDur;
469 
470    if ( otherSize == 0 && wasEmpty && e->mDefaultValue == this->mDefaultValue )
471    {
472       // msmeyer: The envelope is empty and has the same default value, so
473       // there is nothing that must be inserted, just return. This avoids
474       // the creation of unnecessary duplicate control points
475       // MJS: but the envelope does get longer
476       // PRL:  Assuming t0 is in the domain of the envelope
477       mTrackLen += deltat;
478       return;
479    }
480 
481    // Make t0 relative to the offset of the envelope we are pasting into,
482    // and trim it to the domain of this
483    t0 = std::min( mTrackLen, std::max( 0.0, t0 - mOffset ) );
484 
485    // Adjust if the insertion point rounds off near a discontinuity in this
486    if ( true )
487    {
488       double newT0;
489       auto range = EqualRange( t0, sampleDur );
490       auto index = range.first;
491       if ( index + 2 == range.second &&
492            ( newT0 = mEnv[ index ].GetT() ) == mEnv[ 1 + index ].GetT() )
493          t0 = newT0;
494    }
495 
496    // Open up a space
497    double leftVal = e->GetValue( 0 );
498    double rightVal = e->GetValueRelative( otherDur );
499    // This range includes the right-side limit of the left end of the space,
500    // and the left-side limit of the right end:
501    const auto range = ExpandRegion( t0, deltat, &leftVal, &rightVal );
502    // Where to put the copied points from e -- after the first of the
503    // two points in range:
504    auto insertAt = range.first + 1;
505 
506    // Copy points from e -- maybe skipping those at the extremes
507    auto end = e->mEnv.end();
508    if ( otherSize != 0 && e->mEnv[ otherSize - 1 ].GetT() == otherDur )
509       // ExpandRegion already made an equivalent limit point
510       --end, --otherSize;
511    auto begin = e->mEnv.begin();
512    if ( otherSize != 0 && otherOffset == 0.0 && e->mEnv[ 0 ].GetT() == 0.0 )
513       ++begin, --otherSize;
514    mEnv.insert( mEnv.begin() + insertAt, begin, end );
515 
516    // Adjust their times
517    for ( size_t index = insertAt, last = insertAt + otherSize;
518          index < last; ++index ) {
519       auto &point = mEnv[ index ];
520       // The mOffset of the envelope-pasted-from is irrelevant.
521       // The GetT() times in it are relative to its start.
522       // The new GetT() times are relative to the envelope-pasted-to start.
523       // We are pasting at t0 relative to the envelope-pasted-to start.
524       // Hence we adjust by just t0.
525       // Bug 1844 was that we also adjusted by the envelope-pasted-from offset.
526       point.SetT( point.GetT() + /*otherOffset +*/ t0 );
527    }
528 
529    // Treat removable discontinuities
530    // Right edge outward:
531    RemoveUnneededPoints( insertAt + otherSize + 1, true );
532    // Right edge inward:
533    RemoveUnneededPoints( insertAt + otherSize, false, false );
534 
535    // Left edge inward:
536    RemoveUnneededPoints( range.first, true, false );
537    // Left edge outward:
538    RemoveUnneededPoints( range.first - 1, false );
539 
540    // Guarantee monotonicity of times, against little round-off mistakes perhaps
541    ConsistencyCheck();
542 }
543 
544 /*! @excsafety{No-fail} */
RemoveUnneededPoints(size_t startAt,bool rightward,bool testNeighbors)545 void Envelope::RemoveUnneededPoints
546    ( size_t startAt, bool rightward, bool testNeighbors )
547 {
548    // startAt is the index of a recently inserted point which might make no
549    // difference in envelope evaluation, or else might cause nearby points to
550    // make no difference.
551 
552    auto isDiscontinuity = [this]( size_t index ) {
553       // Assume array accesses are in-bounds
554       const EnvPoint &point1 = mEnv[ index ];
555       const EnvPoint &point2 = mEnv[ index + 1 ];
556       return point1.GetT() == point2.GetT() &&
557          fabs( point1.GetVal() - point2.GetVal() ) > VALUE_TOLERANCE;
558    };
559 
560    auto remove = [this]( size_t index, bool leftLimit ) {
561       // Assume array accesses are in-bounds
562       const auto &point = mEnv[ index ];
563       auto when = point.GetT();
564       auto val = point.GetVal();
565       Delete( index );  // try it to see if it's doing anything
566       auto val1 = GetValueRelative ( when, leftLimit );
567       if( fabs( val - val1 ) > VALUE_TOLERANCE ) {
568          // put it back, we needed it
569          Insert( index, EnvPoint{ when, val } );
570          return false;
571       }
572       else
573          return true;
574    };
575 
576    auto len = mEnv.size();
577 
578    bool leftLimit =
579       !rightward && startAt + 1 < len && isDiscontinuity( startAt );
580 
581    bool removed = remove( startAt, leftLimit );
582 
583    if ( removed )
584       // The given point was removable.  Done!
585       return;
586 
587    if ( !testNeighbors )
588       return;
589 
590    // The given point was not removable.  But did its insertion make nearby
591    // points removable?
592 
593    int index = startAt + ( rightward ? 1 : -1 );
594    while ( index >= 0 && index < (int)len ) {
595       // Stop at any discontinuity
596       if ( index > 0       && isDiscontinuity( index - 1 ) )
597          break;
598       if ( (index + 1) < (int)len && isDiscontinuity( index ) )
599          break;
600 
601       if ( ! remove( index, false ) )
602          break;
603 
604       --len;
605       if ( ! rightward )
606          --index;
607    }
608 }
609 
610 /*! @excsafety{No-fail} */
ExpandRegion(double t0,double tlen,double * pLeftVal,double * pRightVal)611 std::pair< int, int > Envelope::ExpandRegion
612    ( double t0, double tlen, double *pLeftVal, double *pRightVal )
613 {
614    // t0 is relative time
615 
616    double val = GetValueRelative( t0 );
617    const auto range = EqualRange( t0, 0 );
618 
619    // Preserve the left-side limit.
620    int index = 1 + range.first;
621    if ( index <= range.second )
622       // There is already a control point.
623       ;
624    else {
625       // Make a control point.
626       Insert( range.first, EnvPoint{ t0, val } );
627    }
628 
629    // Shift points.
630    auto len = mEnv.size();
631    for ( unsigned int ii = index; ii < len; ++ii ) {
632       auto &point = mEnv[ ii ];
633       point.SetT( point.GetT() + tlen );
634    }
635 
636    mTrackLen += tlen;
637 
638    // Preserve the right-side limit.
639    if ( index < range.second )
640       // There was a control point already.
641       ;
642    else
643       // Make a control point.
644       Insert( index, EnvPoint{ t0 + tlen, val } );
645 
646    // Make discontinuities at ends, maybe:
647 
648    if ( pLeftVal )
649       // Make a discontinuity at the left side of the expansion
650       Insert( index++, EnvPoint{ t0, *pLeftVal } );
651 
652    if ( pRightVal )
653       // Make a discontinuity at the right side of the expansion
654       Insert( index++, EnvPoint{ t0 + tlen, *pRightVal } );
655 
656    // Return the range of indices that includes the inside limiting points,
657    // none, one, or two
658    return { 1 + range.first, index };
659 }
660 
661 /*! @excsafety{No-fail} */
InsertSpace(double t0,double tlen)662 void Envelope::InsertSpace( double t0, double tlen )
663 {
664    auto range = ExpandRegion( t0 - mOffset, tlen, nullptr, nullptr );
665 
666    // Simplify the boundaries if possible
667    RemoveUnneededPoints( range.second, true );
668    RemoveUnneededPoints( range.first - 1, false );
669 }
670 
Reassign(double when,double value)671 int Envelope::Reassign(double when, double value)
672 {
673    when -= mOffset;
674 
675    int len = mEnv.size();
676    if (len == 0)
677       return -1;
678 
679    int i = 0;
680    while (i < len && when > mEnv[i].GetT())
681       i++;
682 
683    if (i >= len || when < mEnv[i].GetT())
684       return -1;
685 
686    mEnv[i].SetVal( this, value );
687    return 0;
688 }
689 
690 
GetNumberOfPoints() const691 size_t Envelope::GetNumberOfPoints() const
692 {
693    return mEnv.size();
694 }
695 
GetPoints(double * bufferWhen,double * bufferValue,int bufferLen) const696 void Envelope::GetPoints(double *bufferWhen,
697                          double *bufferValue,
698                          int bufferLen) const
699 {
700    int n = mEnv.size();
701    if (n > bufferLen)
702       n = bufferLen;
703    int i;
704    for (i = 0; i < n; i++) {
705       bufferWhen[i] = mEnv[i].GetT() - mOffset;
706       bufferValue[i] = mEnv[i].GetVal();
707    }
708 }
709 
Cap(double sampleDur)710 void Envelope::Cap( double sampleDur )
711 {
712    auto range = EqualRange( mTrackLen, sampleDur );
713    if ( range.first == range.second )
714       InsertOrReplaceRelative( mTrackLen, GetValueRelative( mTrackLen ) );
715 }
716 
717 // Private methods
718 
719 /** @brief Add a control point to the envelope
720  *
721  * @param when the time in seconds when the envelope point should be created.
722  * @param value the envelope value to use at the given point.
723  * @return the index of the NEW envelope point within array of envelope points.
724  */
InsertOrReplaceRelative(double when,double value)725 int Envelope::InsertOrReplaceRelative(double when, double value)
726 {
727 #if defined(_DEBUG)
728    // in debug builds, do a spot of argument checking
729    if(when > mTrackLen + 0.0000001)
730    {
731       wxString msg;
732       msg = wxString::Format(wxT("when %.20f mTrackLen %.20f diff %.20f"), when, mTrackLen, when-mTrackLen);
733       wxASSERT_MSG(when <= (mTrackLen), msg);
734    }
735    if(when < 0)
736    {
737       wxString msg;
738       msg = wxString::Format(wxT("when %.20f mTrackLen %.20f"), when, mTrackLen);
739       wxASSERT_MSG(when >= 0, msg);
740    }
741 #endif
742 
743    when = std::max( 0.0, std::min( mTrackLen, when ) );
744 
745    auto range = EqualRange( when, 0 );
746    int index = range.first;
747 
748    if ( index < range.second )
749       // modify existing
750       // In case of a discontinuity, ALWAYS CHANGING LEFT LIMIT ONLY!
751       mEnv[ index ].SetVal( this, value );
752    else
753      // Add NEW
754       Insert( index, EnvPoint { when, value } );
755 
756    return index;
757 }
758 
EqualRange(double when,double sampleDur) const759 std::pair<int, int> Envelope::EqualRange( double when, double sampleDur ) const
760 {
761    // Find range of envelope points matching the given time coordinate
762    // (within an interval of length sampleDur)
763    // by binary search; if empty, it still indicates where to
764    // insert.
765    const auto tolerance = sampleDur / 2;
766    auto begin = mEnv.begin();
767    auto end = mEnv.end();
768    auto first = std::lower_bound(
769       begin, end,
770       EnvPoint{ when - tolerance, 0.0 },
771       []( const EnvPoint &point1, const EnvPoint &point2 )
772          { return point1.GetT() < point2.GetT(); }
773    );
774    auto after = first;
775    while ( after != end && after->GetT() <= when + tolerance )
776       ++after;
777    return { first - begin, after - begin };
778 }
779 
780 // Control
781 
782 /*! @excsafety{No-fail} */
SetOffset(double newOffset)783 void Envelope::SetOffset(double newOffset)
784 {
785    mOffset = newOffset;
786 }
787 
788 /*! @excsafety{No-fail} */
SetTrackLen(double trackLen,double sampleDur)789 void Envelope::SetTrackLen( double trackLen, double sampleDur )
790 {
791    // Preserve the left-side limit at trackLen.
792    auto range = EqualRange( trackLen, sampleDur );
793    bool needPoint = ( range.first == range.second && trackLen < mTrackLen );
794    double value=0.0;
795    if ( needPoint )
796       value = GetValueRelative( trackLen );
797 
798    mTrackLen = trackLen;
799 
800    // Shrink the array.
801    // If more than one point already at the end, keep only the first of them.
802    int newLen = std::min( 1 + range.first, range.second );
803    mEnv.resize( newLen );
804 
805    if ( needPoint )
806       AddPointAtEnd( mTrackLen, value );
807 }
808 
809 /*! @excsafety{No-fail} */
RescaleTimes(double newLength)810 void Envelope::RescaleTimes( double newLength )
811 {
812    if ( mTrackLen == 0 ) {
813       for ( auto &point : mEnv )
814          point.SetT( 0 );
815    }
816    else {
817       auto ratio = newLength / mTrackLen;
818       for ( auto &point : mEnv )
819          point.SetT( point.GetT() * ratio );
820    }
821    mTrackLen = newLength;
822 }
823 
824 // Accessors
GetValue(double t,double sampleDur) const825 double Envelope::GetValue( double t, double sampleDur ) const
826 {
827    // t is absolute time
828    double temp;
829 
830    GetValues( &temp, 1, t, sampleDur );
831    return temp;
832 }
833 
GetValueRelative(double t,bool leftLimit) const834 double Envelope::GetValueRelative(double t, bool leftLimit) const
835 {
836    double temp;
837 
838    GetValuesRelative(&temp, 1, t, 0.0, leftLimit);
839    return temp;
840 }
841 
842 // relative time
843 /// @param Lo returns last index at or before this time, maybe -1
844 /// @param Hi returns first index after this time, maybe past the end
BinarySearchForTime(int & Lo,int & Hi,double t) const845 void Envelope::BinarySearchForTime( int &Lo, int &Hi, double t ) const
846 {
847    // Optimizations for the usual pattern of repeated calls with
848    // small increases of t.
849    {
850       if (mSearchGuess >= 0 && mSearchGuess < (int)mEnv.size()) {
851          if (t >= mEnv[mSearchGuess].GetT() &&
852              (1 + mSearchGuess == (int)mEnv.size() ||
853               t < mEnv[1 + mSearchGuess].GetT())) {
854             Lo = mSearchGuess;
855             Hi = 1 + mSearchGuess;
856             return;
857          }
858       }
859 
860       ++mSearchGuess;
861       if (mSearchGuess >= 0 && mSearchGuess < (int)mEnv.size()) {
862          if (t >= mEnv[mSearchGuess].GetT() &&
863              (1 + mSearchGuess == (int)mEnv.size() ||
864               t < mEnv[1 + mSearchGuess].GetT())) {
865             Lo = mSearchGuess;
866             Hi = 1 + mSearchGuess;
867             return;
868          }
869       }
870    }
871 
872    Lo = -1;
873    Hi = mEnv.size();
874 
875    // Invariants:  Lo is not less than -1, Hi not more than size
876    while (Hi > (Lo + 1)) {
877       int mid = (Lo + Hi) / 2;
878       // mid must be strictly between Lo and Hi, therefore a valid index
879       if (t < mEnv[mid].GetT())
880          Hi = mid;
881       else
882          Lo = mid;
883    }
884    wxASSERT( Hi == ( Lo+1 ));
885 
886    mSearchGuess = Lo;
887 }
888 
889 // relative time
890 /// @param Lo returns last index before this time, maybe -1
891 /// @param Hi returns first index at or after this time, maybe past the end
BinarySearchForTime_LeftLimit(int & Lo,int & Hi,double t) const892 void Envelope::BinarySearchForTime_LeftLimit( int &Lo, int &Hi, double t ) const
893 {
894    Lo = -1;
895    Hi = mEnv.size();
896 
897    // Invariants:  Lo is not less than -1, Hi not more than size
898    while (Hi > (Lo + 1)) {
899       int mid = (Lo + Hi) / 2;
900       // mid must be strictly between Lo and Hi, therefore a valid index
901       if (t <= mEnv[mid].GetT())
902          Hi = mid;
903       else
904          Lo = mid;
905    }
906    wxASSERT( Hi == ( Lo+1 ));
907 
908    mSearchGuess = Lo;
909 }
910 
911 /// GetInterpolationStartValueAtPoint() is used to select either the
912 /// envelope value or its log depending on whether we are doing linear
913 /// or log interpolation.
914 /// @param iPoint index in env array to look at.
915 /// @return value there, or its (safe) log10.
GetInterpolationStartValueAtPoint(int iPoint) const916 double Envelope::GetInterpolationStartValueAtPoint( int iPoint ) const
917 {
918    double v = mEnv[ iPoint ].GetVal();
919    if( !mDB )
920       return v;
921    else
922       return log10(v);
923 }
924 
GetValues(double * buffer,int bufferLen,double t0,double tstep) const925 void Envelope::GetValues( double *buffer, int bufferLen,
926                           double t0, double tstep ) const
927 {
928    // Convert t0 from absolute to clip-relative time
929    t0 -= mOffset;
930    GetValuesRelative( buffer, bufferLen, t0, tstep);
931 }
932 
GetValuesRelative(double * buffer,int bufferLen,double t0,double tstep,bool leftLimit) const933 void Envelope::GetValuesRelative
934    (double *buffer, int bufferLen, double t0, double tstep, bool leftLimit)
935    const
936 {
937    // JC: If bufferLen ==0 we have probably just allocated a zero sized buffer.
938    // wxASSERT( bufferLen > 0 );
939 
940    const auto epsilon = tstep / 2;
941    int len = mEnv.size();
942 
943    double t = t0;
944    double increment = 0;
945    if ( len > 1 && t <= mEnv[0].GetT() && mEnv[0].GetT() == mEnv[1].GetT() )
946       increment = leftLimit ? -epsilon : epsilon;
947 
948    double tprev, vprev, tnext = 0, vnext, vstep = 0;
949 
950    for (int b = 0; b < bufferLen; b++) {
951 
952       // Get easiest cases out the way first...
953       // IF empty envelope THEN default value
954       if (len <= 0) {
955          buffer[b] = mDefaultValue;
956          t += tstep;
957          continue;
958       }
959 
960       auto tplus = t + increment;
961 
962       // IF before envelope THEN first value
963       if ( leftLimit ? tplus <= mEnv[0].GetT() : tplus < mEnv[0].GetT() ) {
964          buffer[b] = mEnv[0].GetVal();
965          t += tstep;
966          continue;
967       }
968       // IF after envelope THEN last value
969       if ( leftLimit
970             ? tplus > mEnv[len - 1].GetT() : tplus >= mEnv[len - 1].GetT() ) {
971          buffer[b] = mEnv[len - 1].GetVal();
972          t += tstep;
973          continue;
974       }
975 
976       // be careful to get the correct limit even in case epsilon == 0
977       if ( b == 0 ||
978            ( leftLimit ? tplus > tnext : tplus >= tnext ) ) {
979 
980          // We're beyond our tnext, so find the next one.
981          // Don't just increment lo or hi because we might
982          // be zoomed far out and that could be a large number of
983          // points to move over.  That's why we binary search.
984 
985          int lo,hi;
986          if ( leftLimit )
987             BinarySearchForTime_LeftLimit( lo, hi, tplus );
988          else
989             BinarySearchForTime( lo, hi, tplus );
990 
991          // mEnv[0] is before tplus because of eliminations above, therefore lo >= 0
992          // mEnv[len - 1] is after tplus, therefore hi <= len - 1
993          wxASSERT( lo >= 0 && hi <= len - 1 );
994 
995          tprev = mEnv[lo].GetT();
996          tnext = mEnv[hi].GetT();
997 
998          if ( hi + 1 < len && tnext == mEnv[ hi + 1 ].GetT() )
999             // There is a discontinuity after this point-to-point interval.
1000             // Usually will stop evaluating in this interval when time is slightly
1001             // before tNext, then use the right limit.
1002             // This is the right intent
1003             // in case small roundoff errors cause a sample time to be a little
1004             // before the envelope point time.
1005             // Less commonly we want a left limit, so we continue evaluating in
1006             // this interval until shortly after the discontinuity.
1007             increment = leftLimit ? -epsilon : epsilon;
1008          else
1009             increment = 0;
1010 
1011          vprev = GetInterpolationStartValueAtPoint( lo );
1012          vnext = GetInterpolationStartValueAtPoint( hi );
1013 
1014          // Interpolate, either linear or log depending on mDB.
1015          double dt = (tnext - tprev);
1016          double to = t - tprev;
1017          double v;
1018          if (dt > 0.0)
1019          {
1020             v = (vprev * (dt - to) + vnext * to) / dt;
1021             vstep = (vnext - vprev) * tstep / dt;
1022          }
1023          else
1024          {
1025             v = vnext;
1026             vstep = 0.0;
1027          }
1028 
1029          // An adjustment if logarithmic scale.
1030          if( mDB )
1031          {
1032             v = pow(10.0, v);
1033             vstep = pow( 10.0, vstep );
1034          }
1035 
1036          buffer[b] = v;
1037       } else {
1038          if (mDB){
1039             buffer[b] = buffer[b - 1] * vstep;
1040          }else{
1041             buffer[b] = buffer[b - 1] + vstep;
1042          }
1043       }
1044 
1045       t += tstep;
1046    }
1047 }
1048 
1049 // relative time
NumberOfPointsAfter(double t) const1050 int Envelope::NumberOfPointsAfter(double t) const
1051 {
1052    int lo,hi;
1053    BinarySearchForTime( lo, hi, t );
1054 
1055    return mEnv.size() - hi;
1056 }
1057 
1058 // relative time
NextPointAfter(double t) const1059 double Envelope::NextPointAfter(double t) const
1060 {
1061    int lo,hi;
1062    BinarySearchForTime( lo, hi, t );
1063    if (hi >= (int)mEnv.size())
1064       return t;
1065    else
1066       return mEnv[hi].GetT();
1067 }
1068 
Average(double t0,double t1) const1069 double Envelope::Average( double t0, double t1 ) const
1070 {
1071   if( t0 == t1 )
1072     return GetValue( t0 );
1073   else
1074     return Integral( t0, t1 ) / (t1 - t0);
1075 }
1076 
AverageOfInverse(double t0,double t1) const1077 double Envelope::AverageOfInverse( double t0, double t1 ) const
1078 {
1079   if( t0 == t1 )
1080     return 1.0 / GetValue( t0 );
1081   else
1082     return IntegralOfInverse( t0, t1 ) / (t1 - t0);
1083 }
1084 
1085 //
1086 // Integration and debugging functions
1087 //
1088 // The functions below are used by the TimeTrack and possibly for
1089 // other debugging.  They do not affect normal amplitude envelopes
1090 // for waveforms, nor frequency envelopes for equalization.
1091 // The 'Average' function also uses 'Integral'.
1092 //
1093 
1094 // A few helper functions to make the code below more readable.
InterpolatePoints(double y1,double y2,double factor,bool logarithmic)1095 static double InterpolatePoints(double y1, double y2, double factor, bool logarithmic)
1096 {
1097    if(logarithmic)
1098       // you can use any base you want, it doesn't change the result
1099       return exp(log(y1) * (1.0 - factor) + log(y2) * factor);
1100    else
1101       return y1 * (1.0 - factor) + y2 * factor;
1102 }
IntegrateInterpolated(double y1,double y2,double time,bool logarithmic)1103 static double IntegrateInterpolated(double y1, double y2, double time, bool logarithmic)
1104 {
1105    // Calculates: integral(interpolate(y1, y2, x), x = 0 .. time)
1106    // Integrating logarithmic interpolated segments is surprisingly simple. You can check this formula here:
1107    // http://www.wolframalpha.com/input/?i=integrate+10%5E%28log10%28y1%29*%28T-x%29%2FT%2Blog10%28y2%29*x%2FT%29+from+0+to+T
1108    // Again, the base you use for interpolation is irrelevant, the formula below should always use the natural
1109    // logarithm (i.e. 'log' in C/C++). If the denominator is too small, it's better to use linear interpolation
1110    // because the rounding errors would otherwise get too large. The threshold value is 1.0e-5 because at that
1111    // point the rounding errors become larger than the difference between linear and logarithmic (I tested this in Octave).
1112    if(logarithmic)
1113    {
1114       double l = log(y1 / y2);
1115       if(fabs(l) < 1.0e-5) // fall back to linear interpolation
1116          return (y1 + y2) * 0.5 * time;
1117       return (y1 - y2) / l * time;
1118    }
1119    else
1120    {
1121       return (y1 + y2) * 0.5 * time;
1122    }
1123 }
IntegrateInverseInterpolated(double y1,double y2,double time,bool logarithmic)1124 static double IntegrateInverseInterpolated(double y1, double y2, double time, bool logarithmic)
1125 {
1126    // Calculates: integral(1 / interpolate(y1, y2, x), x = 0 .. time)
1127    // This one is a bit harder. Linear:
1128    // http://www.wolframalpha.com/input/?i=integrate+1%2F%28y1*%28T-x%29%2FT%2By2*x%2FT%29+from+0+to+T
1129    // Logarithmic:
1130    // http://www.wolframalpha.com/input/?i=integrate+1%2F%2810%5E%28log10%28y1%29*%28T-x%29%2FT%2Blog10%28y2%29*x%2FT%29%29+from+0+to+T
1131    // Here both cases need a special case for y1 == y2. The threshold is 1.0e5 again, this is still the
1132    // best value in both cases.
1133    double l = log(y1 / y2);
1134    if(fabs(l) < 1.0e-5) // fall back to average
1135       return 2.0 / (y1 + y2) * time;
1136    if(logarithmic)
1137       return (y1 - y2) / (l * y1 * y2) * time;
1138    else
1139       return l / (y1 - y2) * time;
1140 }
SolveIntegrateInverseInterpolated(double y1,double y2,double time,double area,bool logarithmic)1141 static double SolveIntegrateInverseInterpolated(double y1, double y2, double time, double area, bool logarithmic)
1142 {
1143    // Calculates: solve (integral(1 / interpolate(y1, y2, x), x = 0 .. res) = area) for res
1144    // Don't try to derive these formulas by hand :). The threshold is 1.0e5 again.
1145    double a = area / time, res;
1146    if(logarithmic)
1147    {
1148       double l = log(y1 / y2);
1149       if(fabs(l) < 1.0e-5) // fall back to average
1150          res = a * (y1 + y2) * 0.5;
1151       else if(1.0 + a * y1 * l <= 0.0)
1152          res = 1.0;
1153       else
1154          res = log1p(a * y1 * l) / l;
1155    }
1156    else
1157    {
1158       if(fabs(y2 - y1) < 1.0e-5) // fall back to average
1159          res = a * (y1 + y2) * 0.5;
1160       else
1161          res = y1 * expm1(a * (y2 - y1)) / (y2 - y1);
1162    }
1163    return std::max(0.0, std::min(1.0, res)) * time;
1164 }
1165 
1166 // We should be able to write a very efficient memoizer for this
1167 // but make sure it gets reset when the envelope is changed.
Integral(double t0,double t1) const1168 double Envelope::Integral( double t0, double t1 ) const
1169 {
1170    if(t0 == t1)
1171       return 0.0;
1172    if(t0 > t1)
1173    {
1174       return -Integral(t1, t0); // this makes more sense than returning the default value
1175    }
1176 
1177    unsigned int count = mEnv.size();
1178    if(count == 0) // 'empty' envelope
1179       return (t1 - t0) * mDefaultValue;
1180 
1181    t0 -= mOffset;
1182    t1 -= mOffset;
1183 
1184    double total = 0.0, lastT, lastVal;
1185    unsigned int i; // this is the next point to check
1186    if(t0 < mEnv[0].GetT()) // t0 preceding the first point
1187    {
1188       if(t1 <= mEnv[0].GetT())
1189          return (t1 - t0) * mEnv[0].GetVal();
1190       i = 1;
1191       lastT = mEnv[0].GetT();
1192       lastVal = mEnv[0].GetVal();
1193       total += (lastT - t0) * lastVal;
1194    }
1195    else if(t0 >= mEnv[count - 1].GetT()) // t0 at or following the last point
1196    {
1197       return (t1 - t0) * mEnv[count - 1].GetVal();
1198    }
1199    else // t0 enclosed by points
1200    {
1201       // Skip any points that come before t0 using binary search
1202       int lo, hi;
1203       BinarySearchForTime(lo, hi, t0);
1204       lastVal = InterpolatePoints(mEnv[lo].GetVal(), mEnv[hi].GetVal(), (t0 - mEnv[lo].GetT()) / (mEnv[hi].GetT() - mEnv[lo].GetT()), mDB);
1205       lastT = t0;
1206       i = hi; // the point immediately after t0.
1207    }
1208 
1209    // loop through the rest of the envelope points until we get to t1
1210    while (1)
1211    {
1212       if(i >= count) // the requested range extends beyond the last point
1213       {
1214          return total + (t1 - lastT) * lastVal;
1215       }
1216       else if(mEnv[i].GetT() >= t1) // this point follows the end of the range
1217       {
1218          double thisVal = InterpolatePoints(mEnv[i - 1].GetVal(), mEnv[i].GetVal(), (t1 - mEnv[i - 1].GetT()) / (mEnv[i].GetT() - mEnv[i - 1].GetT()), mDB);
1219          return total + IntegrateInterpolated(lastVal, thisVal, t1 - lastT, mDB);
1220       }
1221       else // this point precedes the end of the range
1222       {
1223          total += IntegrateInterpolated(lastVal, mEnv[i].GetVal(), mEnv[i].GetT() - lastT, mDB);
1224          lastT = mEnv[i].GetT();
1225          lastVal = mEnv[i].GetVal();
1226          i++;
1227       }
1228    }
1229 }
1230 
IntegralOfInverse(double t0,double t1) const1231 double Envelope::IntegralOfInverse( double t0, double t1 ) const
1232 {
1233    if(t0 == t1)
1234       return 0.0;
1235    if(t0 > t1)
1236    {
1237       return -IntegralOfInverse(t1, t0); // this makes more sense than returning the default value
1238    }
1239 
1240    unsigned int count = mEnv.size();
1241    if(count == 0) // 'empty' envelope
1242       return (t1 - t0) / mDefaultValue;
1243 
1244    t0 -= mOffset;
1245    t1 -= mOffset;
1246 
1247    double total = 0.0, lastT, lastVal;
1248    unsigned int i; // this is the next point to check
1249    if(t0 < mEnv[0].GetT()) // t0 preceding the first point
1250    {
1251       if(t1 <= mEnv[0].GetT())
1252          return (t1 - t0) / mEnv[0].GetVal();
1253       i = 1;
1254       lastT = mEnv[0].GetT();
1255       lastVal = mEnv[0].GetVal();
1256       total += (lastT - t0) / lastVal;
1257    }
1258    else if(t0 >= mEnv[count - 1].GetT()) // t0 at or following the last point
1259    {
1260       return (t1 - t0) / mEnv[count - 1].GetVal();
1261    }
1262    else // t0 enclosed by points
1263    {
1264       // Skip any points that come before t0 using binary search
1265       int lo, hi;
1266       BinarySearchForTime(lo, hi, t0);
1267       lastVal = InterpolatePoints(mEnv[lo].GetVal(), mEnv[hi].GetVal(), (t0 - mEnv[lo].GetT()) / (mEnv[hi].GetT() - mEnv[lo].GetT()), mDB);
1268       lastT = t0;
1269       i = hi; // the point immediately after t0.
1270    }
1271 
1272    // loop through the rest of the envelope points until we get to t1
1273    while (1)
1274    {
1275       if(i >= count) // the requested range extends beyond the last point
1276       {
1277          return total + (t1 - lastT) / lastVal;
1278       }
1279       else if(mEnv[i].GetT() >= t1) // this point follows the end of the range
1280       {
1281          double thisVal = InterpolatePoints(mEnv[i - 1].GetVal(), mEnv[i].GetVal(), (t1 - mEnv[i - 1].GetT()) / (mEnv[i].GetT() - mEnv[i - 1].GetT()), mDB);
1282          return total + IntegrateInverseInterpolated(lastVal, thisVal, t1 - lastT, mDB);
1283       }
1284       else // this point precedes the end of the range
1285       {
1286          total += IntegrateInverseInterpolated(lastVal, mEnv[i].GetVal(), mEnv[i].GetT() - lastT, mDB);
1287          lastT = mEnv[i].GetT();
1288          lastVal = mEnv[i].GetVal();
1289          i++;
1290       }
1291    }
1292 }
1293 
SolveIntegralOfInverse(double t0,double area) const1294 double Envelope::SolveIntegralOfInverse( double t0, double area ) const
1295 {
1296    if(area == 0.0)
1297       return t0;
1298 
1299    const auto count = mEnv.size();
1300    if(count == 0) // 'empty' envelope
1301       return t0 + area * mDefaultValue;
1302 
1303    // Correct for offset!
1304    t0 -= mOffset;
1305    return mOffset + [&] {
1306       // Now we can safely assume t0 is relative time!
1307       double lastT, lastVal;
1308       int i; // this is the next point to check
1309       if(t0 < mEnv[0].GetT()) // t0 preceding the first point
1310       {
1311          if (area < 0) {
1312             return t0 + area * mEnv[0].GetVal();
1313          }
1314          else {
1315             i = 1;
1316             lastT = mEnv[0].GetT();
1317             lastVal = mEnv[0].GetVal();
1318             double added = (lastT - t0) / lastVal;
1319             if(added >= area)
1320                return t0 + area * mEnv[0].GetVal();
1321             area -= added;
1322          }
1323       }
1324       else if(t0 >= mEnv[count - 1].GetT()) // t0 at or following the last point
1325       {
1326          if (area < 0) {
1327             i = (int)count - 2;
1328             lastT = mEnv[count - 1].GetT();
1329             lastVal = mEnv[count - 1].GetVal();
1330             double added = (lastT - t0) / lastVal; // negative
1331             if(added <= area)
1332                return t0 + area * mEnv[count - 1].GetVal();
1333             area -= added;
1334          }
1335          else {
1336             return t0 + area * mEnv[count - 1].GetVal();
1337          }
1338       }
1339       else // t0 enclosed by points
1340       {
1341          // Skip any points that come before t0 using binary search
1342          int lo, hi;
1343          BinarySearchForTime(lo, hi, t0);
1344          lastVal = InterpolatePoints(mEnv[lo].GetVal(), mEnv[hi].GetVal(), (t0 - mEnv[lo].GetT()) / (mEnv[hi].GetT() - mEnv[lo].GetT()), mDB);
1345          lastT = t0;
1346          if (area < 0)
1347             i = lo;
1348          else
1349             i = hi; // the point immediately after t0.
1350       }
1351 
1352       if (area < 0) {
1353          // loop BACKWARDS through the rest of the envelope points until we get to t1
1354          // (which is less than t0)
1355          while (1)
1356          {
1357             if(i < 0) // the requested range extends beyond the leftmost point
1358             {
1359                return lastT + area * lastVal;
1360             }
1361             else
1362             {
1363                double added =
1364                   -IntegrateInverseInterpolated(mEnv[i].GetVal(), lastVal, lastT - mEnv[i].GetT(), mDB);
1365                if(added <= area)
1366                   return lastT - SolveIntegrateInverseInterpolated(lastVal, mEnv[i].GetVal(), lastT - mEnv[i].GetT(), -area, mDB);
1367                area -= added;
1368                lastT = mEnv[i].GetT();
1369                lastVal = mEnv[i].GetVal();
1370                --i;
1371             }
1372          }
1373       }
1374       else {
1375          // loop through the rest of the envelope points until we get to t1
1376          while (1)
1377          {
1378             if(i >= (int)count) // the requested range extends beyond the last point
1379             {
1380                return lastT + area * lastVal;
1381             }
1382             else
1383             {
1384                double added = IntegrateInverseInterpolated(lastVal, mEnv[i].GetVal(), mEnv[i].GetT() - lastT, mDB);
1385                if(added >= area)
1386                   return lastT + SolveIntegrateInverseInterpolated(lastVal, mEnv[i].GetVal(), mEnv[i].GetT() - lastT, area, mDB);
1387                area -= added;
1388                lastT = mEnv[i].GetT();
1389                lastVal = mEnv[i].GetVal();
1390                i++;
1391             }
1392          }
1393       }
1394    }();
1395 }
1396 
print() const1397 void Envelope::print() const
1398 {
1399    for( unsigned int i = 0; i < mEnv.size(); i++ )
1400       wxPrintf( "(%.2f, %.2f)\n", mEnv[i].GetT(), mEnv[i].GetVal() );
1401 }
1402 
checkResult(int n,double a,double b)1403 static void checkResult( int n, double a, double b )
1404 {
1405    if( (a-b > 0 ? a-b : b-a) > 0.0000001 )
1406    {
1407       wxPrintf( "Envelope:  Result #%d is: %f, should be %f\n", n, a, b );
1408       //exit( -1 );
1409    }
1410 }
1411 
testMe()1412 void Envelope::testMe()
1413 {
1414    double t0=0, t1=0;
1415 
1416    SetExponential(false);
1417 
1418    Flatten(0.5);
1419    checkResult( 1, Integral(0.0,100.0), 50);
1420    checkResult( 2, Integral(-10.0,10.0), 10);
1421 
1422    Flatten(0.5);
1423    checkResult( 3, Integral(0.0,100.0), 50);
1424    checkResult( 4, Integral(-10.0,10.0), 10);
1425    checkResult( 5, Integral(-20.0,-10.0), 5);
1426 
1427    Flatten(0.5);
1428    InsertOrReplaceRelative( 5.0, 0.5 );
1429    checkResult( 6, Integral(0.0,100.0), 50);
1430    checkResult( 7, Integral(-10.0,10.0), 10);
1431 
1432    Flatten(0.0);
1433    InsertOrReplaceRelative( 0.0, 0.0 );
1434    InsertOrReplaceRelative( 5.0, 1.0 );
1435    InsertOrReplaceRelative( 10.0, 0.0 );
1436    t0 = 10.0 - .1;
1437    t1 = 10.0 + .1;
1438    double result = Integral(0.0,t1);
1439    double resulta = Integral(0.0,t0);
1440    double resultb = Integral(t0,t1);
1441    // Integrals should be additive
1442    checkResult( 8, result - resulta - resultb, 0);
1443 
1444    Flatten(0.0);
1445    InsertOrReplaceRelative( 0.0, 0.0 );
1446    InsertOrReplaceRelative( 5.0, 1.0 );
1447    InsertOrReplaceRelative( 10.0, 0.0 );
1448    t0 = 10.0 - .1;
1449    t1 = 10.0 + .1;
1450    checkResult( 9, Integral(0.0,t1), 5);
1451    checkResult( 10, Integral(0.0,t0), 4.999);
1452    checkResult( 11, Integral(t0,t1), .001);
1453 
1454    mEnv.clear();
1455    InsertOrReplaceRelative( 0.0, 0.0 );
1456    InsertOrReplaceRelative( 5.0, 1.0 );
1457    InsertOrReplaceRelative( 10.0, 0.0 );
1458    checkResult( 12, NumberOfPointsAfter( -1 ), 3 );
1459    checkResult( 13, NumberOfPointsAfter( 0 ), 2 );
1460    checkResult( 14, NumberOfPointsAfter( 1 ), 2 );
1461    checkResult( 15, NumberOfPointsAfter( 5 ), 1 );
1462    checkResult( 16, NumberOfPointsAfter( 7 ), 1 );
1463    checkResult( 17, NumberOfPointsAfter( 10 ), 0 );
1464    checkResult( 18, NextPointAfter( 0 ), 5 );
1465    checkResult( 19, NextPointAfter( 5 ), 10 );
1466 }
1467 
1468 #include "ZoomInfo.h"
GetValues(const Envelope & env,double alignedTime,double sampleDur,double * buffer,int bufferLen,int leftOffset,const ZoomInfo & zoomInfo)1469 void Envelope::GetValues
1470    ( const Envelope &env,
1471      double alignedTime, double sampleDur,
1472      double *buffer, int bufferLen, int leftOffset,
1473      const ZoomInfo &zoomInfo )
1474 {
1475    // Getting many envelope values, corresponding to pixel columns, which may
1476    // not be uniformly spaced in time when there is a fisheye.
1477 
1478    double prevDiscreteTime=0.0, prevSampleVal=0.0, nextSampleVal=0.0;
1479    for ( int xx = 0; xx < bufferLen; ++xx ) {
1480       auto time = zoomInfo.PositionToTime( xx, -leftOffset );
1481       if ( sampleDur <= 0 )
1482          // Sample interval not defined (as for time track)
1483          buffer[xx] = env.GetValue( time );
1484       else {
1485          // The level of zoom-in may resolve individual samples.
1486          // If so, then instead of evaluating the envelope directly,
1487          // we draw a piecewise curve with knees at each sample time.
1488          // This actually makes clearer what happens as you drag envelope
1489          // points and make discontinuities.
1490          auto leftDiscreteTime = alignedTime +
1491             sampleDur * floor( ( time - alignedTime ) / sampleDur );
1492          if ( xx == 0 || leftDiscreteTime != prevDiscreteTime ) {
1493             prevDiscreteTime = leftDiscreteTime;
1494             prevSampleVal =
1495                env.GetValue( prevDiscreteTime, sampleDur );
1496             nextSampleVal =
1497                env.GetValue( prevDiscreteTime + sampleDur, sampleDur );
1498          }
1499          auto ratio = ( time - leftDiscreteTime ) / sampleDur;
1500          if ( env.GetExponential() )
1501             buffer[ xx ] = exp(
1502                ( 1.0 - ratio ) * log( prevSampleVal )
1503                   + ratio * log( nextSampleVal ) );
1504          else
1505             buffer[ xx ] =
1506                ( 1.0 - ratio ) * prevSampleVal + ratio * nextSampleVal;
1507       }
1508    }
1509 }
1510