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