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