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