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