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