Audacity  2.2.2
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 *******************************************************************//****************************************************************//*******************************************************************/
28 
29 #include "Envelope.h"
30 #include "Experimental.h"
31 #include "ViewInfo.h"
32 
33 #include <math.h>
34 
35 #include <wx/dc.h>
36 #include <wx/brush.h>
37 #include <wx/event.h>
38 #include <wx/pen.h>
39 #include <wx/textfile.h>
40 #include <wx/log.h>
41 
42 #include "AColor.h"
43 #include "DirManager.h"
44 #include "TrackArtist.h"
45 
46 static const double VALUE_TOLERANCE = 0.001;
47 
48 Envelope::Envelope(bool exponential, double minValue, double maxValue, double defaultValue)
49  : mDB(exponential)
50  , mMinValue(minValue)
51  , mMaxValue(maxValue)
52  , mDefaultValue { ClampValue(defaultValue) }
53 {
54 }
55 
57 {
58 }
59 
61 {
62  bool consistent = true;
63 
64  bool disorder;
65  do {
66  disorder = false;
67  for ( size_t ii = 0, count = mEnv.size(); ii < count; ) {
68  // Find range of points with equal T
69  const double thisT = mEnv[ii].GetT();
70  double nextT = 0.0f;
71  auto nextI = ii + 1;
72  while ( nextI < count && thisT == ( nextT = mEnv[nextI].GetT() ) )
73  ++nextI;
74 
75  if ( nextI < count && nextT < thisT )
76  disorder = true;
77 
78  while ( nextI - ii > 2 ) {
79  // too many coincident time values
80  if ((int)ii == mDragPoint || (int)nextI - 1 == mDragPoint)
81  // forgivable
82  ;
83  else {
84  consistent = false;
85  // repair it
86  Delete( nextI - 2 );
87  if (mDragPoint >= (int)nextI - 2)
88  --mDragPoint;
89  --nextI, --count;
90  // wxLogError
91  }
92  }
93 
94  ii = nextI;
95  }
96 
97  if (disorder) {
98  consistent = false;
99  // repair it
100  std::stable_sort( mEnv.begin(), mEnv.end(),
101  []( const EnvPoint &a, const EnvPoint &b )
102  { return a.GetT() < b.GetT(); } );
103  }
104  } while ( disorder );
105 
106  return consistent;
107 }
108 
115 void Envelope::RescaleValues(double minValue, double maxValue)
116 {
117  double oldMinValue = mMinValue;
118  double oldMaxValue = mMaxValue;
119  mMinValue = minValue;
120  mMaxValue = maxValue;
121 
122  // rescale the default value
123  double factor = (mDefaultValue - oldMinValue) / (oldMaxValue - oldMinValue);
125 
126  // rescale all points
127  for( unsigned int i = 0; i < mEnv.size(); i++ ) {
128  factor = (mEnv[i].GetVal() - oldMinValue) / (oldMaxValue - oldMinValue);
129  mEnv[i].SetVal( this, mMinValue + (mMaxValue - mMinValue) * factor );
130  }
131 
132 }
133 
137 void Envelope::Flatten(double value)
138 {
139  mEnv.clear();
140  mDefaultValue = ClampValue(value);
141 }
142 
143 void Envelope::SetDragPoint(int dragPoint)
144 {
145  mDragPoint = std::max(-1, std::min(int(mEnv.size() - 1), dragPoint));
146  mDragPointValid = (mDragPoint >= 0);
147 }
148 
150 {
151  mDragPointValid = (valid && mDragPoint >= 0);
152  if (mDragPoint >= 0 && !valid) {
153  // We're going to be deleting the point; On
154  // screen we show this by having the envelope move to
155  // the position it will have after deletion of the point.
156  // Without deleting the point we move it left or right
157  // to the same position as the previous or next point.
158 
159  static const double big = std::numeric_limits<double>::max();
160  auto size = mEnv.size();
161 
162  if( size <= 1) {
163  // There is only one point - just move it
164  // off screen and at default height.
165  // temporary state when dragging only!
166  mEnv[mDragPoint].SetT(big);
167  mEnv[mDragPoint].SetVal( this, mDefaultValue );
168  return;
169  }
170  else if ( mDragPoint + 1 == (int)size ) {
171  // Put the point at the height of the last point, but also off screen.
172  mEnv[mDragPoint].SetT(big);
173  mEnv[mDragPoint].SetVal( this, mEnv[ size - 1 ].GetVal() );
174  }
175  else {
176  // Place it exactly on its right neighbour.
177  // That way the drawing code will overpaint the dark dot with
178  // a light dot, as if it were deleted.
179  const auto &neighbor = mEnv[mDragPoint + 1];
180  mEnv[mDragPoint].SetT(neighbor.GetT());
181  mEnv[mDragPoint].SetVal( this, neighbor.GetVal() );
182  }
183  }
184 }
185 
186 void Envelope::MoveDragPoint(double newWhen, double value)
187 {
188  SetDragPointValid(true);
189  if (!mDragPointValid)
190  return;
191 
192  // We'll limit the drag point time to be between those of the preceding
193  // and next envelope point.
194  double limitLo = 0.0;
195  double limitHi = mTrackLen;
196 
197  if (mDragPoint > 0)
198  limitLo = std::max(limitLo, mEnv[mDragPoint - 1].GetT());
199  if (mDragPoint + 1 < (int)mEnv.size())
200  limitHi = std::min(limitHi, mEnv[mDragPoint + 1].GetT());
201 
202  EnvPoint &dragPoint = mEnv[mDragPoint];
203  const double tt =
204  std::max(limitLo, std::min(limitHi, newWhen));
205 
206  // This might temporary violate the constraint that at most two
207  // points share a time value.
208  dragPoint.SetT(tt);
209  dragPoint.SetVal( this, value );
210 }
211 
213 {
214  if (!mDragPointValid && mDragPoint >= 0)
216 
217  mDragPoint = -1;
218  mDragPointValid = false;
219 }
220 
221 void Envelope::SetRange(double minValue, double maxValue) {
222  mMinValue = minValue;
223  mMaxValue = maxValue;
225  for( unsigned int i = 0; i < mEnv.size(); i++ )
226  mEnv[i].SetVal( this, mEnv[i].GetVal() ); // this clamps the value to the NEW range
227 }
228 
229 // This is used only during construction of an Envelope by complete or partial
230 // copy of another, or when truncating a track.
231 void Envelope::AddPointAtEnd( double t, double val )
232 {
233  mEnv.push_back( EnvPoint{ t, val } );
234 
235  // Assume copied points were stored by nondecreasing time.
236  // Allow no more than two points at exactly the same time.
237  // Maybe that happened, because extra points were inserted at the boundary
238  // of the copied range, which were not in the source envelope.
239  auto nn = mEnv.size() - 1;
240  while ( nn >= 2 && mEnv[ nn - 2 ].GetT() == t ) {
241  // Of three or more points at the same time, erase one in the middle,
242  // not the one newly added.
243  mEnv.erase( mEnv.begin() + nn - 1 );
244  --nn;
245  }
246 }
247 
248 Envelope::Envelope(const Envelope &orig, double t0, double t1)
249  : mDB(orig.mDB)
250  , mMinValue(orig.mMinValue)
251  , mMaxValue(orig.mMaxValue)
252  , mDefaultValue(orig.mDefaultValue)
253 {
254  mOffset = wxMax(t0, orig.mOffset);
255  mTrackLen = wxMin(t1, orig.mOffset + orig.mTrackLen) - mOffset;
256 
257  auto range1 = orig.EqualRange( t0 - orig.mOffset, 0 );
258  auto range2 = orig.EqualRange( t1 - orig.mOffset, 0 );
259  CopyRange(orig, range1.first, range2.second);
260 }
261 
263  : mDB(orig.mDB)
264  , mMinValue(orig.mMinValue)
265  , mMaxValue(orig.mMaxValue)
266  , mDefaultValue(orig.mDefaultValue)
267 {
268  mOffset = orig.mOffset;
269  mTrackLen = orig.mTrackLen;
270  CopyRange(orig, 0, orig.GetNumberOfPoints());
271 }
272 
273 void Envelope::CopyRange(const Envelope &orig, size_t begin, size_t end)
274 {
275  size_t len = orig.mEnv.size();
276  size_t i = begin;
277 
278  // Create the point at 0 if it needs interpolated representation
279  if ( i > 0 )
280  AddPointAtEnd(0, orig.GetValue(mOffset));
281 
282  // Copy points from inside the copied region
283  for (; i < end; ++i) {
284  const EnvPoint &point = orig[i];
285  const double when = point.GetT() + (orig.mOffset - mOffset);
286  AddPointAtEnd(when, point.GetVal());
287  }
288 
289  // Create the final point if it needs interpolated representation
290  // If the last point of e was exatly at t1, this effectively copies it too.
291  if (mTrackLen > 0 && i < len)
293 }
294 
295 #if 0
296 static double Limit( double Lo, double Value, double Hi )
299 {
300  if( Value < Lo )
301  return Lo;
302  if( Value > Hi )
303  return Hi;
304  return Value;
305 }
306 #endif
307 
309 static void DrawPoint(wxDC & dc, const wxRect & r, int x, int y, bool top)
310 {
311  if (y >= 0 && y <= r.height) {
312  wxRect circle(r.x + x, r.y + (top ? y - 1: y - 2), 4, 4);
313  dc.DrawEllipse(circle);
314  }
315 }
316 
319 
322 (TrackPanelDrawingContext &context, const wxRect & r, const ZoomInfo &zoomInfo,
323  bool dB, double dBRange,
324  float zoomMin, float zoomMax, bool mirrored) const
325 {
326  auto &dc = context.dc;
327  bool highlight = false;
328 #ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
329  auto target = dynamic_cast<EnvelopeHandle*>(context.target.get());
330  highlight = target && target->GetEnvelope() == this;
331 #endif
332  wxPen &pen = highlight ? AColor::uglyPen : AColor::envelopePen;
333  dc.SetPen( pen );
334  dc.SetBrush(*wxWHITE_BRUSH);
335 
336  for (int i = 0; i < (int)mEnv.size(); i++) {
337  const double time = mEnv[i].GetT() + mOffset;
338  const wxInt64 position = zoomInfo.TimeToPosition(time);
339  if (position >= 0 && position < r.width) {
340  // Change colour if this is the draggable point...
341  if (i == mDragPoint) {
342  dc.SetPen( pen );
343  dc.SetBrush(AColor::envelopeBrush);
344  }
345 
346  double v = mEnv[i].GetVal();
347  int x = (int)(position);
348  int y, y2;
349 
350  y = GetWaveYPos(v, zoomMin, zoomMax, r.height, dB,
351  true, dBRange, false);
352  if (!mirrored) {
353  DrawPoint(dc, r, x, y, true);
354  }
355  else {
356  y2 = GetWaveYPos(-v-.000000001, zoomMin, zoomMax, r.height, dB,
357  true, dBRange, false);
358 
359  // This follows the same logic as the envelop drawing in
360  // TrackArtist::DrawEnvelope().
361  // TODO: make this calculation into a reusable function.
362  if (y2 - y < 9) {
363  int value = (int)((zoomMax / (zoomMax - zoomMin)) * r.height);
364  y = value - 4;
365  y2 = value + 4;
366  }
367 
368  DrawPoint(dc, r, x, y, true);
369  DrawPoint(dc, r, x, y2, false);
370 
371  // Contour
372  y = GetWaveYPos(v, zoomMin, zoomMax, r.height, dB,
373  false, dBRange, false);
374  y2 = GetWaveYPos(-v-.000000001, zoomMin, zoomMax, r.height, dB,
375  false, dBRange, false);
376  if (y <= y2) {
377  DrawPoint(dc, r, x, y, true);
378  DrawPoint(dc, r, x, y2, false);
379  }
380  }
381 
382  // Change colour back again if was the draggable point.
383  if (i == mDragPoint) {
384  dc.SetPen( pen );
385  dc.SetBrush(*wxWHITE_BRUSH);
386  }
387  }
388  }
389 }
390 
391 bool Envelope::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
392 {
393  // Return unless it's the envelope tag.
394  if (wxStrcmp(tag, wxT("envelope")))
395  return false;
396 
397  int numPoints = 0;
398  long nValue = -1;
399 
400  while (*attrs) {
401  const wxChar *attr = *attrs++;
402  const wxChar *value = *attrs++;
403  if (!value)
404  break;
405  const wxString strValue = value;
406  if( !wxStrcmp(attr, wxT("numpoints")) &&
407  XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue))
408  numPoints = nValue;
409  }
410  if (numPoints < 0)
411  return false;
412 
413  mEnv.clear();
414  mEnv.reserve(numPoints);
415  return true;
416 }
417 
419 {
420  if (wxStrcmp(tag, wxT("controlpoint")))
421  return NULL;
422 
423  mEnv.push_back( EnvPoint{} );
424  return &mEnv.back();
425 }
426 
427 void Envelope::WriteXML(XMLWriter &xmlFile) const
428 // may throw
429 {
430  unsigned int ctrlPt;
431 
432  xmlFile.StartTag(wxT("envelope"));
433  xmlFile.WriteAttr(wxT("numpoints"), mEnv.size());
434 
435  for (ctrlPt = 0; ctrlPt < mEnv.size(); ctrlPt++) {
436  const EnvPoint &point = mEnv[ctrlPt];
437  xmlFile.StartTag(wxT("controlpoint"));
438  xmlFile.WriteAttr(wxT("t"), point.GetT(), 12);
439  xmlFile.WriteAttr(wxT("val"), point.GetVal(), 12);
440  xmlFile.EndTag(wxT("controlpoint"));
441  }
442 
443  xmlFile.EndTag(wxT("envelope"));
444 }
445 
446 namespace
447 {
448 inline int SQR(int x) { return x * x; }
449 }
450 
458 float EnvelopeEditor::ValueOfPixel( int y, int height, bool upper,
459  bool dB, double dBRange,
460  float zoomMin, float zoomMax)
461 {
462  float v = ::ValueOfPixel(y, height, 0 != mContourOffset, dB, dBRange, zoomMin, zoomMax);
463 
464  // MB: this is mostly equivalent to what the old code did, I'm not sure
465  // if anything special is needed for asymmetric ranges
466  if(upper)
467  return mEnvelope.ClampValue(v);
468  else
469  return mEnvelope.ClampValue(-v);
470 }
471 
478 bool EnvelopeEditor::HandleMouseButtonDown(const wxMouseEvent & event, wxRect & r,
479  const ZoomInfo &zoomInfo,
480  bool dB, double dBRange,
481  float zoomMin, float zoomMax)
482 {
483  int ctr = (int)(r.height * zoomMax / (zoomMax - zoomMin));
484  bool upper = !mMirrored || (zoomMin >= 0.0) || (event.m_y - r.y < ctr);
485 
486  int clip_y = event.m_y - r.y;
487  if(clip_y < 0) clip_y = 0; //keeps point in rect r, even if mouse isn't
488  if(clip_y > r.GetBottom()) clip_y = r.GetBottom();
489 
490  int bestNum = -1;
491  int bestDistSqr = 100; // Must be within 10 pixel radius.
492 
493  // Member variables hold state that will be needed in dragging.
494  mButton = event.GetButton();
495  mContourOffset = false;
496 
497  // wxLogDebug(wxT("Y:%i Height:%i Offset:%i"), y, height, mContourOffset );
498  int len = mEnvelope.GetNumberOfPoints();
499 
500  // TODO: extract this into a function FindNearestControlPoint()
501  // TODO: also fix it so that we can drag the last point on an envelope.
502  for (int i = 0; i < len; i++) { //search for control point nearest click
503  const double time = mEnvelope[i].GetT() + mEnvelope.GetOffset();
504  const wxInt64 position = zoomInfo.TimeToPosition(time);
505  if (position >= 0 && position < r.width) {
506 
507  int x = (int)(position);
508  int y[4];
509  int numControlPoints;
510 
511  // Outer control points
512  double value = mEnvelope[i].GetVal();
513  y[0] = GetWaveYPos(value, zoomMin, zoomMax, r.height,
514  dB, true, dBRange, false);
515  y[1] = GetWaveYPos(-value, zoomMin, zoomMax, r.height,
516  dB, true, dBRange, false);
517 
518  // Inner control points(contour)
519  y[2] = GetWaveYPos(value, zoomMin, zoomMax, r.height,
520  dB, false, dBRange, false);
521  y[3] = GetWaveYPos(-value -.00000001, zoomMin, zoomMax,
522  r.height, dB, false, dBRange, false);
523 
524  numControlPoints = 4;
525 
526  if (y[2] > y[3])
527  numControlPoints = 2;
528 
529  if (!mMirrored)
530  numControlPoints = 1;
531 
532  const int deltaXSquared = SQR(x - (event.m_x - r.x));
533  for(int j=0; j<numControlPoints; j++){
534 
535  const int dSqr = deltaXSquared + SQR(y[j] - (event.m_y - r.y));
536  if (dSqr < bestDistSqr) {
537  bestNum = i;
538  bestDistSqr = dSqr;
539  mContourOffset = (bool)(j > 1);
540  }
541  }
542  }
543  }
544 
545  if (bestNum >= 0) {
546  mEnvelope.SetDragPoint(bestNum);
547  }
548  else {
549  // TODO: Extract this into a function CreateNewPoint
550  const double when = zoomInfo.PositionToTime(event.m_x, r.x);
551 
552  // if (when <= 0 || when >= mTrackLen)
553  // return false;
554 
555  const double v = mEnvelope.GetValue( when );
556 
557  int ct = GetWaveYPos( v, zoomMin, zoomMax, r.height, dB,
558  false, dBRange, false) ;
559  int cb = GetWaveYPos( -v-.000000001, zoomMin, zoomMax, r.height, dB,
560  false, dBRange, false) ;
561  if (ct <= cb || !mMirrored) {
562  int t = GetWaveYPos( v, zoomMin, zoomMax, r.height, dB,
563  true, dBRange, false) ;
564  int b = GetWaveYPos( -v, zoomMin, zoomMax, r.height, dB,
565  true, dBRange, false) ;
566 
567  ct = (t + ct) / 2;
568  cb = (b + cb) / 2;
569 
570  if (mMirrored &&
571  (event.m_y - r.y) > ct &&
572  ((event.m_y - r.y) < cb))
573  mContourOffset = true;
574  else
575  mContourOffset = false;
576  }
577 
578  double newVal = ValueOfPixel(clip_y, r.height, upper, dB, dBRange,
579  zoomMin, zoomMax);
580 
582  mDirty = true;
583  }
584 
585  mUpper = upper;
586 
587  // const int dragPoint = mEnvelope.GetDragPoint();
588  // mInitialVal = mEnvelope[dragPoint].GetVal();
589  // mInitialY = event.m_y+mContourOffset;
590 
591  return true;
592 }
593 
594 void EnvelopeEditor::MoveDragPoint(const wxMouseEvent & event, wxRect & r,
595  const ZoomInfo &zoomInfo, bool dB, double dBRange,
596  float zoomMin, float zoomMax)
597 {
598  int clip_y = event.m_y - r.y;
599  if(clip_y < 0) clip_y = 0;
600  if(clip_y > r.height) clip_y = r.height;
601  double newVal = ValueOfPixel(clip_y, r.height, mUpper, dB, dBRange,
602  zoomMin, zoomMax);
603 
604  // We no longer tolerate multiple envelope points at the same t.
605  // epsilon is less than the time offset of a single sample
606  // TODO: However because mTrackEpsilon assumes 200KHz this use
607  // of epsilon is a tad bogus. What we need to do instead is DELETE
608  // a duplicated point on a mouse up.
609  double newWhen = zoomInfo.PositionToTime(event.m_x, r.x) - mEnvelope.GetOffset();
610  mEnvelope.MoveDragPoint(newWhen, newVal);
611 }
612 
613 bool EnvelopeEditor::HandleDragging(const wxMouseEvent & event, wxRect & r,
614  const ZoomInfo &zoomInfo, bool dB, double dBRange,
615  float zoomMin, float zoomMax,
616  float WXUNUSED(eMin), float WXUNUSED(eMax))
617 {
618  mDirty = true;
619 
620  wxRect larger = r;
621  larger.Inflate(10, 10);
622 
623  if (larger.Contains(event.m_x, event.m_y))
624  {
625  // IF we're in the rect THEN we're not deleting this point (anymore).
626  // ...we're dragging it.
627  MoveDragPoint( event, r, zoomInfo, dB, dBRange, zoomMin, zoomMax);
628  return true;
629  }
630 
632  // IF we already know we're deleting THEN no envelope point to update.
633  return false;
634 
635  // Invalidate the point
637  return true;
638 }
639 
640 // Exit dragging mode and delete dragged point if neccessary.
642 {
644  mButton = wxMOUSE_BTN_NONE;
645  return true;
646 }
647 
648 void Envelope::Delete( int point )
649 {
650  mEnv.erase(mEnv.begin() + point);
651 }
652 
653 void Envelope::Insert(int point, const EnvPoint &p)
654 {
655  mEnv.insert(mEnv.begin() + point, p);
656 }
657 
658 // Returns true if parent needs to be redrawn
659 bool EnvelopeEditor::MouseEvent(const wxMouseEvent & event, wxRect & r,
660  const ZoomInfo &zoomInfo, bool dB, double dBRange,
661  float zoomMin, float zoomMax)
662 {
663  if (event.ButtonDown() && mButton == wxMOUSE_BTN_NONE)
664  return HandleMouseButtonDown( event, r, zoomInfo, dB, dBRange,
665  zoomMin, zoomMax);
666  if (event.Dragging() && mEnvelope.GetDragPoint() >= 0)
667  return HandleDragging( event, r, zoomInfo, dB, dBRange,
668  zoomMin, zoomMax);
669  if (event.ButtonUp() && event.GetButton() == mButton)
670  return HandleMouseButtonUp();
671  return false;
672 }
673 
674 void Envelope::CollapseRegion( double t0, double t1, double sampleDur )
675 // NOFAIL-GUARANTEE
676 {
677  if ( t1 <= t0 )
678  return;
679 
680  // This gets called when somebody clears samples.
681 
682  // Snip points in the interval (t0, t1), shift values left at times after t1.
683  // For the boundaries of the interval, preserve the left-side limit at the
684  // start and right-side limit at the end.
685 
686  const auto epsilon = sampleDur / 2;
687  t0 = std::max( 0.0, std::min( mTrackLen, t0 - mOffset ) );
688  t1 = std::max( 0.0, std::min( mTrackLen, t1 - mOffset ) );
689  bool leftPoint = true, rightPoint = true;
690 
691  // Determine the start of the range of points to remove from the array.
692  auto range0 = EqualRange( t0, 0 );
693  auto begin = range0.first;
694  if ( begin == range0.second ) {
695  if ( t0 > epsilon ) {
696  // There was no point exactly at t0;
697  // insert a point to preserve the value.
698  auto val = GetValueRelative( t0 );
699  InsertOrReplaceRelative( t0, val );
700  ++begin;
701  }
702  else
703  leftPoint = false;
704  }
705  else
706  // We will keep the first (or only) point that was at t0.
707  ++begin;
708 
709  // We want end to be the index one past the range of points to remove from
710  // the array.
711  // At first, find index of the first point after t1:
712  auto range1 = EqualRange( t1, 0 );
713  auto end = range1.second;
714  if ( range1.first == end ) {
715  if ( mTrackLen - t1 > epsilon ) {
716  // There was no point exactly at t1; insert a point to preserve the value.
717  auto val = GetValueRelative( t1 );
718  InsertOrReplaceRelative( t1, val );
719  // end is now the index of this NEW point and that is correct.
720  }
721  else
722  rightPoint = false;
723  }
724  else
725  // We will keep the last (or only) point that was at t1.
726  --end;
727 
728  mEnv.erase( mEnv.begin() + begin, mEnv.begin() + end );
729 
730  // Shift points left after deleted region.
731  auto len = mEnv.size();
732  for ( size_t i = begin; i < len; ++i ) {
733  auto &point = mEnv[i];
734  if (rightPoint && (int)i == begin)
735  // Avoid roundoff error.
736  // Make exactly equal times of neighboring points so that we have
737  // a real discontinuity.
738  point.SetT( t0 );
739  else
740  point.SetT( point.GetT() - (t1 - t0) );
741  }
742 
743  // See if the discontinuity is removable.
744  if ( rightPoint )
745  RemoveUnneededPoints( begin, true );
746  if ( leftPoint )
747  RemoveUnneededPoints( begin - 1, false );
748 
749  mTrackLen -= ( t1 - t0 );
750 }
751 
752 // This operation is trickier than it looks; the basic rub is that
753 // a track's envelope runs the range from t=0 to t=tracklen; the t=0
754 // envelope point applies to the first sample, but the t=tracklen
755 // envelope point applies one-past the last actual sample.
756 // t0 should be in the domain of this; if not, it is trimmed.
757 void Envelope::Paste( double t0, const Envelope *e, double sampleDur )
758 // NOFAIL-GUARANTEE
759 {
760  const bool wasEmpty = (this->mEnv.size() == 0);
761  auto otherSize = e->mEnv.size();
762  const double otherDur = e->mTrackLen;
763  const auto otherOffset = e->mOffset;
764  const auto deltat = otherOffset + otherDur;
765 
766  if ( otherSize == 0 && wasEmpty && e->mDefaultValue == this->mDefaultValue )
767  {
768  // msmeyer: The envelope is empty and has the same default value, so
769  // there is nothing that must be inserted, just return. This avoids
770  // the creation of unnecessary duplicate control points
771  // MJS: but the envelope does get longer
772  // PRL: Assuming t0 is in the domain of the envelope
773  mTrackLen += deltat;
774  return;
775  }
776 
777  // Make t0 relative and trim it to the domain of this
778  t0 = std::min( mTrackLen, std::max( 0.0, t0 - mOffset ) );
779 
780  // Adjust if the insertion point rounds off near a discontinuity in this
781  if ( true )
782  {
783  double newT0;
784  auto range = EqualRange( t0, sampleDur );
785  auto index = range.first;
786  if ( index + 2 == range.second &&
787  ( newT0 = mEnv[ index ].GetT() ) == mEnv[ 1 + index ].GetT() )
788  t0 = newT0;
789  }
790 
791  // Open up a space
792  double leftVal = e->GetValue( 0 );
793  double rightVal = e->GetValueRelative( otherDur );
794  // This range includes the right-side limit of the left end of the space,
795  // and the left-side limit of the right end:
796  const auto range = ExpandRegion( t0, deltat, &leftVal, &rightVal );
797  // Where to put the copied points from e -- after the first of the
798  // two points in range:
799  auto insertAt = range.first + 1;
800 
801  // Copy points from e -- maybe skipping those at the extremes
802  auto end = e->mEnv.end();
803  if ( otherSize != 0 && e->mEnv[ otherSize - 1 ].GetT() == otherDur )
804  // ExpandRegion already made an equivalent limit point
805  --end, --otherSize;
806  auto begin = e->mEnv.begin();
807  if ( otherSize != 0 && otherOffset == 0.0 && e->mEnv[ 0 ].GetT() == 0.0 )
808  ++begin, --otherSize;
809  mEnv.insert( mEnv.begin() + insertAt, begin, end );
810 
811  // Adjust their times
812  for ( size_t index = insertAt, last = insertAt + otherSize;
813  index < last; ++index ) {
814  auto &point = mEnv[ index ];
815  point.SetT( point.GetT() + otherOffset + t0 );
816  }
817 
818  // Treat removable discontinuities
819  // Right edge outward:
820  RemoveUnneededPoints( insertAt + otherSize + 1, true );
821  // Right edge inward:
822  RemoveUnneededPoints( insertAt + otherSize, false, false );
823 
824  // Left edge inward:
825  RemoveUnneededPoints( range.first, true, false );
826  // Left edge outward:
827  RemoveUnneededPoints( range.first - 1, false );
828 
829  // Guarantee monotonicity of times, against little round-off mistakes perhaps
831 }
832 
834  ( size_t startAt, bool rightward, bool testNeighbors )
835 // NOFAIL-GUARANTEE
836 {
837  // startAt is the index of a recently inserted point which might make no
838  // difference in envelope evaluation, or else might cause nearby points to
839  // make no difference.
840 
841  auto isDiscontinuity = [this]( size_t index ) {
842  // Assume array accesses are in-bounds
843  const EnvPoint &point1 = mEnv[ index ];
844  const EnvPoint &point2 = mEnv[ index + 1 ];
845  return point1.GetT() == point2.GetT() &&
846  fabs( point1.GetVal() - point2.GetVal() ) > VALUE_TOLERANCE;
847  };
848 
849  auto remove = [this]( size_t index, bool leftLimit ) {
850  // Assume array accesses are in-bounds
851  const auto &point = mEnv[ index ];
852  auto when = point.GetT();
853  auto val = point.GetVal();
854  Delete( index ); // try it to see if it's doing anything
855  auto val1 = GetValueRelative ( when, leftLimit );
856  if( fabs( val - val1 ) > VALUE_TOLERANCE ) {
857  // put it back, we needed it
858  Insert( index, EnvPoint{ when, val } );
859  return false;
860  }
861  else
862  return true;
863  };
864 
865  auto len = mEnv.size();
866 
867  bool leftLimit =
868  !rightward && startAt + 1 < len && isDiscontinuity( startAt );
869 
870  bool removed = remove( startAt, leftLimit );
871 
872  if ( removed )
873  // The given point was removable. Done!
874  return;
875 
876  if ( !testNeighbors )
877  return;
878 
879  // The given point was not removable. But did its insertion make nearby
880  // points removable?
881 
882  int index = startAt + ( rightward ? 1 : -1 );
883  while ( index >= 0 && index < (int)len ) {
884  // Stop at any discontinuity
885  if ( index > 0 && isDiscontinuity( index - 1 ) )
886  break;
887  if ( (index + 1) < (int)len && isDiscontinuity( index ) )
888  break;
889 
890  if ( ! remove( index, false ) )
891  break;
892 
893  --len;
894  if ( ! rightward )
895  --index;
896  }
897 }
898 
899 std::pair< int, int > Envelope::ExpandRegion
900  ( double t0, double tlen, double *pLeftVal, double *pRightVal )
901 // NOFAIL-GUARANTEE
902 {
903  // t0 is relative time
904 
905  double val = GetValueRelative( t0 );
906  const auto range = EqualRange( t0, 0 );
907 
908  // Preserve the left-side limit.
909  int index = 1 + range.first;
910  if ( index <= range.second )
911  // There is already a control point.
912  ;
913  else {
914  // Make a control point.
915  Insert( range.first, EnvPoint{ t0, val } );
916  }
917 
918  // Shift points.
919  auto len = mEnv.size();
920  for ( unsigned int ii = index; ii < len; ++ii ) {
921  auto &point = mEnv[ ii ];
922  point.SetT( point.GetT() + tlen );
923  }
924 
925  mTrackLen += tlen;
926 
927  // Preserve the right-side limit.
928  if ( index < range.second )
929  // There was a control point already.
930  ;
931  else
932  // Make a control point.
933  Insert( index, EnvPoint{ t0 + tlen, val } );
934 
935  // Make discontinuities at ends, maybe:
936 
937  if ( pLeftVal )
938  // Make a discontinuity at the left side of the expansion
939  Insert( index++, EnvPoint{ t0, *pLeftVal } );
940 
941  if ( pRightVal )
942  // Make a discontinuity at the right side of the expansion
943  Insert( index++, EnvPoint{ t0 + tlen, *pRightVal } );
944 
945  // Return the range of indices that includes the inside limiting points,
946  // none, one, or two
947  return { 1 + range.first, index };
948 }
949 
950 void Envelope::InsertSpace( double t0, double tlen )
951 // NOFAIL-GUARANTEE
952 {
953  auto range = ExpandRegion( t0 - mOffset, tlen, nullptr, nullptr );
954 
955  // Simplify the boundaries if possible
956  RemoveUnneededPoints( range.second, true );
957  RemoveUnneededPoints( range.first - 1, false );
958 }
959 
960 int Envelope::Reassign(double when, double value)
961 {
962  when -= mOffset;
963 
964  int len = mEnv.size();
965  if (len == 0)
966  return -1;
967 
968  int i = 0;
969  while (i < len && when > mEnv[i].GetT())
970  i++;
971 
972  if (i >= len || when < mEnv[i].GetT())
973  return -1;
974 
975  mEnv[i].SetVal( this, value );
976  return 0;
977 }
978 
979 
981 {
982  return mEnv.size();
983 }
984 
985 void Envelope::GetPoints(double *bufferWhen,
986  double *bufferValue,
987  int bufferLen) const
988 {
989  int n = mEnv.size();
990  if (n > bufferLen)
991  n = bufferLen;
992  int i;
993  for (i = 0; i < n; i++) {
994  bufferWhen[i] = mEnv[i].GetT() - mOffset;
995  bufferValue[i] = mEnv[i].GetVal();
996  }
997 }
998 
999 void Envelope::Cap( double sampleDur )
1000 {
1001  auto range = EqualRange( mTrackLen, sampleDur );
1002  if ( range.first == range.second )
1004 }
1005 
1006 // Private methods
1007 
1014 int Envelope::InsertOrReplaceRelative(double when, double value)
1015 {
1016 #if defined(__WXDEBUG__)
1017  // in debug builds, do a spot of argument checking
1018  if(when > mTrackLen + 0.0000001)
1019  {
1020  wxString msg;
1021  msg = wxString::Format(wxT("when %.20f mTrackLen %.20f diff %.20f"), when, mTrackLen, when-mTrackLen);
1022  wxASSERT_MSG(when <= (mTrackLen), msg);
1023  }
1024  if(when < 0)
1025  {
1026  wxString msg;
1027  msg = wxString::Format(wxT("when %.20f mTrackLen %.20f"), when, mTrackLen);
1028  wxASSERT_MSG(when >= 0, msg);
1029  }
1030 #endif
1031 
1032  when = std::max( 0.0, std::min( mTrackLen, when ) );
1033 
1034  auto range = EqualRange( when, 0 );
1035  int index = range.first;
1036 
1037  if ( index < range.second )
1038  // modify existing
1039  // In case of a discontinuity, ALWAYS CHANGING LEFT LIMIT ONLY!
1040  mEnv[ index ].SetVal( this, value );
1041  else
1042  // Add NEW
1043  Insert( index, EnvPoint { when, value } );
1044 
1045  return index;
1046 }
1047 
1048 std::pair<int, int> Envelope::EqualRange( double when, double sampleDur ) const
1049 {
1050  // Find range of envelope points matching the given time coordinate
1051  // (within an interval of length sampleDur)
1052  // by binary search; if empty, it still indicates where to
1053  // insert.
1054  const auto tolerance = sampleDur / 2;
1055  auto begin = mEnv.begin();
1056  auto end = mEnv.end();
1057  auto first = std::lower_bound(
1058  begin, end,
1059  EnvPoint{ when - tolerance, 0.0 },
1060  []( const EnvPoint &point1, const EnvPoint &point2 )
1061  { return point1.GetT() < point2.GetT(); }
1062  );
1063  auto after = first;
1064  while ( after != end && after->GetT() <= when + tolerance )
1065  ++after;
1066  return { first - begin, after - begin };
1067 }
1068 
1069 // Control
1070 
1071 void Envelope::SetOffset(double newOffset)
1072 // NOFAIL-GUARANTEE
1073 {
1074  mOffset = newOffset;
1075 }
1076 
1077 void Envelope::SetTrackLen( double trackLen, double sampleDur )
1078 // NOFAIL-GUARANTEE
1079 {
1080  // Preserve the left-side limit at trackLen.
1081  auto range = EqualRange( trackLen, sampleDur );
1082  bool needPoint = ( range.first == range.second && trackLen < mTrackLen );
1083  double value=0.0;
1084  if ( needPoint )
1085  value = GetValueRelative( trackLen );
1086 
1087  mTrackLen = trackLen;
1088 
1089  // Shrink the array.
1090  // If more than one point already at the end, keep only the first of them.
1091  int newLen = std::min( 1 + range.first, range.second );
1092  mEnv.resize( newLen );
1093 
1094  if ( needPoint )
1095  AddPointAtEnd( mTrackLen, value );
1096 }
1097 
1098 void Envelope::RescaleTimes( double newLength )
1099 // NOFAIL-GUARANTEE
1100 {
1101  if ( mTrackLen == 0 ) {
1102  for ( auto &point : mEnv )
1103  point.SetT( 0 );
1104  }
1105  else {
1106  auto ratio = newLength / mTrackLen;
1107  for ( auto &point : mEnv )
1108  point.SetT( point.GetT() * ratio );
1109  }
1110  mTrackLen = newLength;
1111 }
1112 
1113 // Accessors
1114 double Envelope::GetValue( double t, double sampleDur ) const
1115 {
1116  // t is absolute time
1117  double temp;
1118 
1119  GetValues( &temp, 1, t, sampleDur );
1120  return temp;
1121 }
1122 
1123 double Envelope::GetValueRelative(double t, bool leftLimit) const
1124 {
1125  double temp;
1126 
1127  GetValuesRelative(&temp, 1, t, 0.0, leftLimit);
1128  return temp;
1129 }
1130 
1131 // relative time
1134 void Envelope::BinarySearchForTime( int &Lo, int &Hi, double t ) const
1135 {
1136  // Optimizations for the usual pattern of repeated calls with
1137  // small increases of t.
1138  {
1139  if (mSearchGuess >= 0 && mSearchGuess < (int)mEnv.size()) {
1140  if (t >= mEnv[mSearchGuess].GetT() &&
1141  (1 + mSearchGuess == (int)mEnv.size() ||
1142  t < mEnv[1 + mSearchGuess].GetT())) {
1143  Lo = mSearchGuess;
1144  Hi = 1 + mSearchGuess;
1145  return;
1146  }
1147  }
1148 
1149  ++mSearchGuess;
1150  if (mSearchGuess >= 0 && mSearchGuess < (int)mEnv.size()) {
1151  if (t >= mEnv[mSearchGuess].GetT() &&
1152  (1 + mSearchGuess == (int)mEnv.size() ||
1153  t < mEnv[1 + mSearchGuess].GetT())) {
1154  Lo = mSearchGuess;
1155  Hi = 1 + mSearchGuess;
1156  return;
1157  }
1158  }
1159  }
1160 
1161  Lo = -1;
1162  Hi = mEnv.size();
1163 
1164  // Invariants: Lo is not less than -1, Hi not more than size
1165  while (Hi > (Lo + 1)) {
1166  int mid = (Lo + Hi) / 2;
1167  // mid must be strictly between Lo and Hi, therefore a valid index
1168  if (t < mEnv[mid].GetT())
1169  Hi = mid;
1170  else
1171  Lo = mid;
1172  }
1173  wxASSERT( Hi == ( Lo+1 ));
1174 
1175  mSearchGuess = Lo;
1176 }
1177 
1178 // relative time
1181 void Envelope::BinarySearchForTime_LeftLimit( int &Lo, int &Hi, double t ) const
1182 {
1183  Lo = -1;
1184  Hi = mEnv.size();
1185 
1186  // Invariants: Lo is not less than -1, Hi not more than size
1187  while (Hi > (Lo + 1)) {
1188  int mid = (Lo + Hi) / 2;
1189  // mid must be strictly between Lo and Hi, therefore a valid index
1190  if (t <= mEnv[mid].GetT())
1191  Hi = mid;
1192  else
1193  Lo = mid;
1194  }
1195  wxASSERT( Hi == ( Lo+1 ));
1196 
1197  mSearchGuess = Lo;
1198 }
1199 
1206 {
1207  double v = mEnv[ iPoint ].GetVal();
1208  if( !mDB )
1209  return v;
1210  else
1211  return log10(v);
1212 }
1213 
1214 void Envelope::GetValues( double *buffer, int bufferLen,
1215  double t0, double tstep ) const
1216 {
1217  // Convert t0 from absolute to clip-relative time
1218  t0 -= mOffset;
1219  GetValuesRelative( buffer, bufferLen, t0, tstep);
1220 }
1221 
1223  (double *buffer, int bufferLen, double t0, double tstep, bool leftLimit)
1224  const
1225 {
1226  // JC: If bufferLen ==0 we have probably just allocated a zero sized buffer.
1227  // wxASSERT( bufferLen > 0 );
1228 
1229  const auto epsilon = tstep / 2;
1230  int len = mEnv.size();
1231 
1232  double t = t0;
1233  double increment = 0;
1234  if ( len > 1 && t <= mEnv[0].GetT() && mEnv[0].GetT() == mEnv[1].GetT() )
1235  increment = leftLimit ? -epsilon : epsilon;
1236 
1237  double tprev, vprev, tnext = 0, vnext, vstep = 0;
1238 
1239  for (int b = 0; b < bufferLen; b++) {
1240 
1241  // Get easiest cases out the way first...
1242  // IF empty envelope THEN default value
1243  if (len <= 0) {
1244  buffer[b] = mDefaultValue;
1245  t += tstep;
1246  continue;
1247  }
1248 
1249  auto tplus = t + increment;
1250 
1251  // IF before envelope THEN first value
1252  if ( leftLimit ? tplus <= mEnv[0].GetT() : tplus < mEnv[0].GetT() ) {
1253  buffer[b] = mEnv[0].GetVal();
1254  t += tstep;
1255  continue;
1256  }
1257  // IF after envelope THEN last value
1258  if ( leftLimit
1259  ? tplus > mEnv[len - 1].GetT() : tplus >= mEnv[len - 1].GetT() ) {
1260  buffer[b] = mEnv[len - 1].GetVal();
1261  t += tstep;
1262  continue;
1263  }
1264 
1265  // be careful to get the correct limit even in case epsilon == 0
1266  if ( b == 0 ||
1267  ( leftLimit ? tplus > tnext : tplus >= tnext ) ) {
1268 
1269  // We're beyond our tnext, so find the next one.
1270  // Don't just increment lo or hi because we might
1271  // be zoomed far out and that could be a large number of
1272  // points to move over. That's why we binary search.
1273 
1274  int lo,hi;
1275  if ( leftLimit )
1276  BinarySearchForTime_LeftLimit( lo, hi, tplus );
1277  else
1278  BinarySearchForTime( lo, hi, tplus );
1279 
1280  // mEnv[0] is before tplus because of eliminations above, therefore lo >= 0
1281  // mEnv[len - 1] is after tplus, therefore hi <= len - 1
1282  wxASSERT( lo >= 0 && hi <= len - 1 );
1283 
1284  tprev = mEnv[lo].GetT();
1285  tnext = mEnv[hi].GetT();
1286 
1287  if ( hi + 1 < len && tnext == mEnv[ hi + 1 ].GetT() )
1288  // There is a discontinuity after this point-to-point interval.
1289  // Usually will stop evaluating in this interval when time is slightly
1290  // before tNext, then use the right limit.
1291  // This is the right intent
1292  // in case small roundoff errors cause a sample time to be a little
1293  // before the envelope point time.
1294  // Less commonly we want a left limit, so we continue evaluating in
1295  // this interval until shortly after the discontinuity.
1296  increment = leftLimit ? -epsilon : epsilon;
1297  else
1298  increment = 0;
1299 
1300  vprev = GetInterpolationStartValueAtPoint( lo );
1301  vnext = GetInterpolationStartValueAtPoint( hi );
1302 
1303  // Interpolate, either linear or log depending on mDB.
1304  double dt = (tnext - tprev);
1305  double to = t - tprev;
1306  double v;
1307  if (dt > 0.0)
1308  {
1309  v = (vprev * (dt - to) + vnext * to) / dt;
1310  vstep = (vnext - vprev) * tstep / dt;
1311  }
1312  else
1313  {
1314  v = vnext;
1315  vstep = 0.0;
1316  }
1317 
1318  // An adjustment if logarithmic scale.
1319  if( mDB )
1320  {
1321  v = pow(10.0, v);
1322  vstep = pow( 10.0, vstep );
1323  }
1324 
1325  buffer[b] = v;
1326  } else {
1327  if (mDB){
1328  buffer[b] = buffer[b - 1] * vstep;
1329  }else{
1330  buffer[b] = buffer[b - 1] + vstep;
1331  }
1332  }
1333 
1334  t += tstep;
1335  }
1336 }
1337 
1339  ( double alignedTime, double sampleDur,
1340  double *buffer, int bufferLen, int leftOffset,
1341  const ZoomInfo &zoomInfo )
1342  const
1343 {
1344  // Getting many envelope values, corresponding to pixel columns, which may
1345  // not be uniformly spaced in time when there is a fisheye.
1346 
1347  double prevDiscreteTime=0.0, prevSampleVal=0.0, nextSampleVal=0.0;
1348  for ( int xx = 0; xx < bufferLen; ++xx ) {
1349  auto time = zoomInfo.PositionToTime( xx, -leftOffset );
1350  if ( sampleDur <= 0 )
1351  // Sample interval not defined (as for time track)
1352  buffer[xx] = GetValue( time );
1353  else {
1354  // The level of zoom-in may resolve individual samples.
1355  // If so, then instead of evaluating the envelope directly,
1356  // we draw a piecewise curve with knees at each sample time.
1357  // This actually makes clearer what happens as you drag envelope
1358  // points and make discontinuities.
1359  auto leftDiscreteTime = alignedTime +
1360  sampleDur * floor( ( time - alignedTime ) / sampleDur );
1361  if ( xx == 0 || leftDiscreteTime != prevDiscreteTime ) {
1362  prevDiscreteTime = leftDiscreteTime;
1363  prevSampleVal =
1364  GetValue( prevDiscreteTime, sampleDur );
1365  nextSampleVal =
1366  GetValue( prevDiscreteTime + sampleDur, sampleDur );
1367  }
1368  auto ratio = ( time - leftDiscreteTime ) / sampleDur;
1369  if ( GetExponential() )
1370  buffer[ xx ] = exp(
1371  ( 1.0 - ratio ) * log( prevSampleVal )
1372  + ratio * log( nextSampleVal ) );
1373  else
1374  buffer[ xx ] =
1375  ( 1.0 - ratio ) * prevSampleVal + ratio * nextSampleVal;
1376  }
1377  }
1378 }
1379 
1380 // relative time
1382 {
1383  int lo,hi;
1384  BinarySearchForTime( lo, hi, t );
1385 
1386  return mEnv.size() - hi;
1387 }
1388 
1389 // relative time
1390 double Envelope::NextPointAfter(double t) const
1391 {
1392  int lo,hi;
1393  BinarySearchForTime( lo, hi, t );
1394  if (hi >= (int)mEnv.size())
1395  return t;
1396  else
1397  return mEnv[hi].GetT();
1398 }
1399 
1400 double Envelope::Average( double t0, double t1 ) const
1401 {
1402  if( t0 == t1 )
1403  return GetValue( t0 );
1404  else
1405  return Integral( t0, t1 ) / (t1 - t0);
1406 }
1407 
1408 double Envelope::AverageOfInverse( double t0, double t1 ) const
1409 {
1410  if( t0 == t1 )
1411  return 1.0 / GetValue( t0 );
1412  else
1413  return IntegralOfInverse( t0, t1 ) / (t1 - t0);
1414 }
1415 
1416 //
1417 // Integration and debugging functions
1418 //
1419 // The functions below are used by the TimeTrack and possibly for
1420 // other debugging. They do not affect normal amplitude envelopes
1421 // for waveforms, nor frequency envelopes for equalization.
1422 // The 'Average' function also uses 'Integral'.
1423 //
1424 
1425 // A few helper functions to make the code below more readable.
1426 static double InterpolatePoints(double y1, double y2, double factor, bool logarithmic)
1427 {
1428  if(logarithmic)
1429  // you can use any base you want, it doesn't change the result
1430  return exp(log(y1) * (1.0 - factor) + log(y2) * factor);
1431  else
1432  return y1 * (1.0 - factor) + y2 * factor;
1433 }
1434 static double IntegrateInterpolated(double y1, double y2, double time, bool logarithmic)
1435 {
1436  // Calculates: integral(interpolate(y1, y2, x), x = 0 .. time)
1437  // Integrating logarithmic interpolated segments is surprisingly simple. You can check this formula here:
1438  // 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
1439  // Again, the base you use for interpolation is irrelevant, the formula below should always use the natural
1440  // logarithm (i.e. 'log' in C/C++). If the denominator is too small, it's better to use linear interpolation
1441  // because the rounding errors would otherwise get too large. The threshold value is 1.0e-5 because at that
1442  // point the rounding errors become larger than the difference between linear and logarithmic (I tested this in Octave).
1443  if(logarithmic)
1444  {
1445  double l = log(y1 / y2);
1446  if(fabs(l) < 1.0e-5) // fall back to linear interpolation
1447  return (y1 + y2) * 0.5 * time;
1448  return (y1 - y2) / l * time;
1449  }
1450  else
1451  {
1452  return (y1 + y2) * 0.5 * time;
1453  }
1454 }
1455 static double IntegrateInverseInterpolated(double y1, double y2, double time, bool logarithmic)
1456 {
1457  // Calculates: integral(1 / interpolate(y1, y2, x), x = 0 .. time)
1458  // This one is a bit harder. Linear:
1459  // http://www.wolframalpha.com/input/?i=integrate+1%2F%28y1*%28T-x%29%2FT%2By2*x%2FT%29+from+0+to+T
1460  // Logarithmic:
1461  // 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
1462  // Here both cases need a special case for y1 == y2. The threshold is 1.0e5 again, this is still the
1463  // best value in both cases.
1464  double l = log(y1 / y2);
1465  if(fabs(l) < 1.0e-5) // fall back to average
1466  return 2.0 / (y1 + y2) * time;
1467  if(logarithmic)
1468  return (y1 - y2) / (l * y1 * y2) * time;
1469  else
1470  return l / (y1 - y2) * time;
1471 }
1472 static double SolveIntegrateInverseInterpolated(double y1, double y2, double time, double area, bool logarithmic)
1473 {
1474  // Calculates: solve (integral(1 / interpolate(y1, y2, x), x = 0 .. res) = area) for res
1475  // Don't try to derive these formulas by hand :). The threshold is 1.0e5 again.
1476  double a = area / time, res;
1477  if(logarithmic)
1478  {
1479  double l = log(y1 / y2);
1480  if(fabs(l) < 1.0e-5) // fall back to average
1481  res = a * (y1 + y2) * 0.5;
1482  else if(1.0 + a * y1 * l <= 0.0)
1483  res = 1.0;
1484  else
1485  res = log1p(a * y1 * l) / l;
1486  }
1487  else
1488  {
1489  if(fabs(y2 - y1) < 1.0e-5) // fall back to average
1490  res = a * (y1 + y2) * 0.5;
1491  else
1492  res = y1 * expm1(a * (y2 - y1)) / (y2 - y1);
1493  }
1494  return std::max(0.0, std::min(1.0, res)) * time;
1495 }
1496 
1497 // We should be able to write a very efficient memoizer for this
1498 // but make sure it gets reset when the envelope is changed.
1499 double Envelope::Integral( double t0, double t1 ) const
1500 {
1501  if(t0 == t1)
1502  return 0.0;
1503  if(t0 > t1)
1504  {
1505  return -Integral(t1, t0); // this makes more sense than returning the default value
1506  }
1507 
1508  unsigned int count = mEnv.size();
1509  if(count == 0) // 'empty' envelope
1510  return (t1 - t0) * mDefaultValue;
1511 
1512  t0 -= mOffset;
1513  t1 -= mOffset;
1514 
1515  double total = 0.0, lastT, lastVal;
1516  unsigned int i; // this is the next point to check
1517  if(t0 < mEnv[0].GetT()) // t0 preceding the first point
1518  {
1519  if(t1 <= mEnv[0].GetT())
1520  return (t1 - t0) * mEnv[0].GetVal();
1521  i = 1;
1522  lastT = mEnv[0].GetT();
1523  lastVal = mEnv[0].GetVal();
1524  total += (lastT - t0) * lastVal;
1525  }
1526  else if(t0 >= mEnv[count - 1].GetT()) // t0 at or following the last point
1527  {
1528  return (t1 - t0) * mEnv[count - 1].GetVal();
1529  }
1530  else // t0 enclosed by points
1531  {
1532  // Skip any points that come before t0 using binary search
1533  int lo, hi;
1534  BinarySearchForTime(lo, hi, t0);
1535  lastVal = InterpolatePoints(mEnv[lo].GetVal(), mEnv[hi].GetVal(), (t0 - mEnv[lo].GetT()) / (mEnv[hi].GetT() - mEnv[lo].GetT()), mDB);
1536  lastT = t0;
1537  i = hi; // the point immediately after t0.
1538  }
1539 
1540  // loop through the rest of the envelope points until we get to t1
1541  while (1)
1542  {
1543  if(i >= count) // the requested range extends beyond the last point
1544  {
1545  return total + (t1 - lastT) * lastVal;
1546  }
1547  else if(mEnv[i].GetT() >= t1) // this point follows the end of the range
1548  {
1549  double thisVal = InterpolatePoints(mEnv[i - 1].GetVal(), mEnv[i].GetVal(), (t1 - mEnv[i - 1].GetT()) / (mEnv[i].GetT() - mEnv[i - 1].GetT()), mDB);
1550  return total + IntegrateInterpolated(lastVal, thisVal, t1 - lastT, mDB);
1551  }
1552  else // this point precedes the end of the range
1553  {
1554  total += IntegrateInterpolated(lastVal, mEnv[i].GetVal(), mEnv[i].GetT() - lastT, mDB);
1555  lastT = mEnv[i].GetT();
1556  lastVal = mEnv[i].GetVal();
1557  i++;
1558  }
1559  }
1560 }
1561 
1562 double Envelope::IntegralOfInverse( double t0, double t1 ) const
1563 {
1564  if(t0 == t1)
1565  return 0.0;
1566  if(t0 > t1)
1567  {
1568  return -IntegralOfInverse(t1, t0); // this makes more sense than returning the default value
1569  }
1570 
1571  unsigned int count = mEnv.size();
1572  if(count == 0) // 'empty' envelope
1573  return (t1 - t0) / mDefaultValue;
1574 
1575  t0 -= mOffset;
1576  t1 -= mOffset;
1577 
1578  double total = 0.0, lastT, lastVal;
1579  unsigned int i; // this is the next point to check
1580  if(t0 < mEnv[0].GetT()) // t0 preceding the first point
1581  {
1582  if(t1 <= mEnv[0].GetT())
1583  return (t1 - t0) / mEnv[0].GetVal();
1584  i = 1;
1585  lastT = mEnv[0].GetT();
1586  lastVal = mEnv[0].GetVal();
1587  total += (lastT - t0) / lastVal;
1588  }
1589  else if(t0 >= mEnv[count - 1].GetT()) // t0 at or following the last point
1590  {
1591  return (t1 - t0) / mEnv[count - 1].GetVal();
1592  }
1593  else // t0 enclosed by points
1594  {
1595  // Skip any points that come before t0 using binary search
1596  int lo, hi;
1597  BinarySearchForTime(lo, hi, t0);
1598  lastVal = InterpolatePoints(mEnv[lo].GetVal(), mEnv[hi].GetVal(), (t0 - mEnv[lo].GetT()) / (mEnv[hi].GetT() - mEnv[lo].GetT()), mDB);
1599  lastT = t0;
1600  i = hi; // the point immediately after t0.
1601  }
1602 
1603  // loop through the rest of the envelope points until we get to t1
1604  while (1)
1605  {
1606  if(i >= count) // the requested range extends beyond the last point
1607  {
1608  return total + (t1 - lastT) / lastVal;
1609  }
1610  else if(mEnv[i].GetT() >= t1) // this point follows the end of the range
1611  {
1612  double thisVal = InterpolatePoints(mEnv[i - 1].GetVal(), mEnv[i].GetVal(), (t1 - mEnv[i - 1].GetT()) / (mEnv[i].GetT() - mEnv[i - 1].GetT()), mDB);
1613  return total + IntegrateInverseInterpolated(lastVal, thisVal, t1 - lastT, mDB);
1614  }
1615  else // this point precedes the end of the range
1616  {
1617  total += IntegrateInverseInterpolated(lastVal, mEnv[i].GetVal(), mEnv[i].GetT() - lastT, mDB);
1618  lastT = mEnv[i].GetT();
1619  lastVal = mEnv[i].GetVal();
1620  i++;
1621  }
1622  }
1623 }
1624 
1625 double Envelope::SolveIntegralOfInverse( double t0, double area ) const
1626 {
1627  if(area == 0.0)
1628  return t0;
1629 
1630  const auto count = mEnv.size();
1631  if(count == 0) // 'empty' envelope
1632  return t0 + area * mDefaultValue;
1633 
1634  // Correct for offset!
1635  t0 -= mOffset;
1636  return mOffset + [&] {
1637  // Now we can safely assume t0 is relative time!
1638  double lastT, lastVal;
1639  int i; // this is the next point to check
1640  if(t0 < mEnv[0].GetT()) // t0 preceding the first point
1641  {
1642  if (area < 0) {
1643  return t0 + area * mEnv[0].GetVal();
1644  }
1645  else {
1646  i = 1;
1647  lastT = mEnv[0].GetT();
1648  lastVal = mEnv[0].GetVal();
1649  double added = (lastT - t0) / lastVal;
1650  if(added >= area)
1651  return t0 + area * mEnv[0].GetVal();
1652  area -= added;
1653  }
1654  }
1655  else if(t0 >= mEnv[count - 1].GetT()) // t0 at or following the last point
1656  {
1657  if (area < 0) {
1658  i = (int)count - 2;
1659  lastT = mEnv[count - 1].GetT();
1660  lastVal = mEnv[count - 1].GetVal();
1661  double added = (lastT - t0) / lastVal; // negative
1662  if(added <= area)
1663  return t0 + area * mEnv[count - 1].GetVal();
1664  area -= added;
1665  }
1666  else {
1667  return t0 + area * mEnv[count - 1].GetVal();
1668  }
1669  }
1670  else // t0 enclosed by points
1671  {
1672  // Skip any points that come before t0 using binary search
1673  int lo, hi;
1674  BinarySearchForTime(lo, hi, t0);
1675  lastVal = InterpolatePoints(mEnv[lo].GetVal(), mEnv[hi].GetVal(), (t0 - mEnv[lo].GetT()) / (mEnv[hi].GetT() - mEnv[lo].GetT()), mDB);
1676  lastT = t0;
1677  if (area < 0)
1678  i = lo;
1679  else
1680  i = hi; // the point immediately after t0.
1681  }
1682 
1683  if (area < 0) {
1684  // loop BACKWARDS through the rest of the envelope points until we get to t1
1685  // (which is less than t0)
1686  while (1)
1687  {
1688  if(i < 0) // the requested range extends beyond the leftmost point
1689  {
1690  return lastT + area * lastVal;
1691  }
1692  else
1693  {
1694  double added =
1695  -IntegrateInverseInterpolated(mEnv[i].GetVal(), lastVal, lastT - mEnv[i].GetT(), mDB);
1696  if(added <= area)
1697  return lastT - SolveIntegrateInverseInterpolated(lastVal, mEnv[i].GetVal(), lastT - mEnv[i].GetT(), -area, mDB);
1698  area -= added;
1699  lastT = mEnv[i].GetT();
1700  lastVal = mEnv[i].GetVal();
1701  --i;
1702  }
1703  }
1704  }
1705  else {
1706  // loop through the rest of the envelope points until we get to t1
1707  while (1)
1708  {
1709  if(i >= (int)count) // the requested range extends beyond the last point
1710  {
1711  return lastT + area * lastVal;
1712  }
1713  else
1714  {
1715  double added = IntegrateInverseInterpolated(lastVal, mEnv[i].GetVal(), mEnv[i].GetT() - lastT, mDB);
1716  if(added >= area)
1717  return lastT + SolveIntegrateInverseInterpolated(lastVal, mEnv[i].GetVal(), mEnv[i].GetT() - lastT, area, mDB);
1718  area -= added;
1719  lastT = mEnv[i].GetT();
1720  lastVal = mEnv[i].GetVal();
1721  i++;
1722  }
1723  }
1724  }
1725  }();
1726 }
1727 
1728 void Envelope::print() const
1729 {
1730  for( unsigned int i = 0; i < mEnv.size(); i++ )
1731  wxPrintf( "(%.2f, %.2f)\n", mEnv[i].GetT(), mEnv[i].GetVal() );
1732 }
1733 
1734 static void checkResult( int n, double a, double b )
1735 {
1736  if( (a-b > 0 ? a-b : b-a) > 0.0000001 )
1737  {
1738  wxPrintf( "Envelope: Result #%d is: %f, should be %f\n", n, a, b );
1739  //exit( -1 );
1740  }
1741 }
1742 
1744 {
1745  double t0=0, t1=0;
1746 
1747  SetExponential(false);
1748 
1749  Flatten(0.5);
1750  checkResult( 1, Integral(0.0,100.0), 50);
1751  checkResult( 2, Integral(-10.0,10.0), 10);
1752 
1753  Flatten(0.5);
1754  checkResult( 3, Integral(0.0,100.0), 50);
1755  checkResult( 4, Integral(-10.0,10.0), 10);
1756  checkResult( 5, Integral(-20.0,-10.0), 5);
1757 
1758  Flatten(0.5);
1759  InsertOrReplaceRelative( 5.0, 0.5 );
1760  checkResult( 6, Integral(0.0,100.0), 50);
1761  checkResult( 7, Integral(-10.0,10.0), 10);
1762 
1763  Flatten(0.0);
1764  InsertOrReplaceRelative( 0.0, 0.0 );
1765  InsertOrReplaceRelative( 5.0, 1.0 );
1766  InsertOrReplaceRelative( 10.0, 0.0 );
1767  t0 = 10.0 - .1;
1768  t1 = 10.0 + .1;
1769  double result = Integral(0.0,t1);
1770  double resulta = Integral(0.0,t0);
1771  double resultb = Integral(t0,t1);
1772  // Integrals should be additive
1773  checkResult( 8, result - resulta - resultb, 0);
1774 
1775  Flatten(0.0);
1776  InsertOrReplaceRelative( 0.0, 0.0 );
1777  InsertOrReplaceRelative( 5.0, 1.0 );
1778  InsertOrReplaceRelative( 10.0, 0.0 );
1779  t0 = 10.0 - .1;
1780  t1 = 10.0 + .1;
1781  checkResult( 9, Integral(0.0,t1), 5);
1782  checkResult( 10, Integral(0.0,t0), 4.999);
1783  checkResult( 11, Integral(t0,t1), .001);
1784 
1785  mEnv.clear();
1786  InsertOrReplaceRelative( 0.0, 0.0 );
1787  InsertOrReplaceRelative( 5.0, 1.0 );
1788  InsertOrReplaceRelative( 10.0, 0.0 );
1789  checkResult( 12, NumberOfPointsAfter( -1 ), 3 );
1790  checkResult( 13, NumberOfPointsAfter( 0 ), 2 );
1791  checkResult( 14, NumberOfPointsAfter( 1 ), 2 );
1792  checkResult( 15, NumberOfPointsAfter( 5 ), 1 );
1793  checkResult( 16, NumberOfPointsAfter( 7 ), 1 );
1794  checkResult( 17, NumberOfPointsAfter( 10 ), 0 );
1795  checkResult( 18, NextPointAfter( 0 ), 5 );
1796  checkResult( 19, NextPointAfter( 5 ), 10 );
1797 }
1798 
1799 EnvelopeEditor::EnvelopeEditor(Envelope &envelope, bool mirrored)
1800  : mEnvelope(envelope)
1801  , mMirrored(mirrored)
1802  , mContourOffset(-1)
1803  // , mInitialVal(-1.0)
1804  // , mInitialY(-1)
1805  , mUpper(false)
1806  , mButton(wxMOUSE_BTN_NONE)
1807  , mDirty(false)
1808 {
1809 }
1810 
1812 {
1813 }
double mMaxValue
Definition: Envelope.h:266
bool HandleDragging(const wxMouseEvent &event, wxRect &r, const ZoomInfo &zoomInfo, bool dB, double dBRange, float zoomMin=-1.0, float zoomMax=1.0, float eMin=0., float eMax=2.)
Definition: Envelope.cpp:613
void DrawPoints(TrackPanelDrawingContext &context, const wxRect &r, const ZoomInfo &zoomInfo, bool dB, double dBRange, float zoomMin, float zoomMax, bool mirrored) const
TODO: This should probably move to track artist.
Definition: Envelope.cpp:322
const bool mMirrored
Definition: Envelope.h:315
void RescaleValues(double minValue, double maxValue)
Definition: Envelope.cpp:115
double Average(double t0, double t1) const
Definition: Envelope.cpp:1400
double GetT() const
Definition: Envelope.h:41
void MoveDragPoint(double newWhen, double value)
Definition: Envelope.cpp:186
double AverageOfInverse(double t0, double t1) const
Definition: Envelope.cpp:1408
double mTrackLen
The length of the envelope, which is the same as the length of the underlying track (normally) ...
Definition: Envelope.h:258
void Flatten(double value)
Definition: Envelope.cpp:137
bool HandleXMLTag(const wxChar *tag, const wxChar **attrs) override
Definition: Envelope.cpp:391
size_t GetNumberOfPoints() const
Return number of points.
Definition: Envelope.cpp:980
bool mDB
Definition: Envelope.h:265
static void checkResult(int n, double a, double b)
Definition: Envelope.cpp:1734
void SetOffset(double newOffset)
Definition: Envelope.cpp:1071
int mSearchGuess
Definition: Envelope.h:273
double mMinValue
Definition: Envelope.h:266
void SetExponential(bool db)
Definition: Envelope.h:99
Envelope(bool exponential, double minValue, double maxValue, double defaultValue)
Definition: Envelope.cpp:48
XMLTagHandler * HandleXMLChild(const wxChar *tag) override
Definition: Envelope.cpp:418
int GetDragPoint() const
Definition: Envelope.h:231
double PositionToTime(wxInt64 position, wxInt64 origin=0, bool ignoreFisheye=false) const
Definition: ViewInfo.cpp:49
virtual ~Envelope()
Definition: Envelope.cpp:56
double SolveIntegralOfInverse(double t0, double area) const
Definition: Envelope.cpp:1625
Draggable curve used in TrackPanel for varying amplification.
Definition: Envelope.h:77
void SetDragPointValid(bool valid)
Definition: Envelope.cpp:149
bool ConsistencyCheck()
Definition: Envelope.cpp:60
void Paste(double t0, const Envelope *e, double sampleDur)
Definition: Envelope.cpp:757
double NextPointAfter(double t) const
Definition: Envelope.cpp:1390
int Reassign(double when, double value)
Move a point at when to value.
Definition: Envelope.cpp:960
void BinarySearchForTime(int &Lo, int &Hi, double t) const
Definition: Envelope.cpp:1134
void CopyRange(const Envelope &orig, size_t begin, size_t end)
Definition: Envelope.cpp:273
void GetPoints(double *bufferWhen, double *bufferValue, int bufferLen) const
Returns the sets of when and value pairs.
Definition: Envelope.cpp:985
void print() const
Definition: Envelope.cpp:1728
double GetValueRelative(double t, bool leftLimit=false) const
Definition: Envelope.cpp:1123
void ClearDragPoint()
Definition: Envelope.cpp:212
void SetDragPoint(int dragPoint)
Definition: Envelope.cpp:143
void InsertSpace(double t0, double tlen)
Definition: Envelope.cpp:950
void RemoveUnneededPoints(size_t startAt, bool rightward, bool testNeighbors=true)
Definition: Envelope.cpp:834
void testMe()
Definition: Envelope.cpp:1743
static wxPen uglyPen
Definition: AColor.h:147
static bool IsGoodInt(const wxString &strInt)
Check that the supplied string can be converted to a long (32bit) integer.
static double IntegrateInverseInterpolated(double y1, double y2, double time, bool logarithmic)
Definition: Envelope.cpp:1455
void BinarySearchForTime_LeftLimit(int &Lo, int &Hi, double t) const
Definition: Envelope.cpp:1181
static wxPen envelopePen
Definition: AColor.h:122
int InsertOrReplaceRelative(double when, double value)
Add a control point to the envelope.
Definition: Envelope.cpp:1014
void Cap(double sampleDur)
Definition: Envelope.cpp:999
bool HandleMouseButtonUp()
Definition: Envelope.cpp:641
void RescaleTimes(double newLength)
Definition: Envelope.cpp:1098
Envelope & mEnvelope
Definition: Envelope.h:314
void WriteXML(XMLWriter &xmlFile) const
Definition: Envelope.cpp:427
int InsertOrReplace(double when, double value)
Add a point at a particular absolute time coordinate.
Definition: Envelope.h:194
static double SolveIntegrateInverseInterpolated(double y1, double y2, double time, double area, bool logarithmic)
Definition: Envelope.cpp:1472
bool MouseEvent(const wxMouseEvent &event, wxRect &r, const ZoomInfo &zoomInfo, bool dB, double dBRange, float zoomMin=-1.0, float zoomMax=1.0)
Definition: Envelope.cpp:659
void SetTrackLen(double trackLen, double sampleDur=0.0)
Definition: Envelope.cpp:1077
void Delete(int point)
DELETE a point by its position in array.
Definition: Envelope.cpp:648
double IntegralOfInverse(double t0, double t1) const
Definition: Envelope.cpp:1562
int mContourOffset
Number of pixels contour is from the true envelope.
Definition: Envelope.h:318
void MoveDragPoint(const wxMouseEvent &event, wxRect &r, const ZoomInfo &zoomInfo, bool dB, double dBRange, float zoomMin, float zoomMax)
Definition: Envelope.cpp:594
float ValueOfPixel(int y, int height, bool upper, bool dB, double dBRange, float zoomMin, float zoomMax)
Definition: Envelope.cpp:458
static double IntegrateInterpolated(double y1, double y2, double time, bool logarithmic)
Definition: Envelope.cpp:1434
This class is an interface which should be implemented by classes which wish to be able to load and s...
Definition: XMLTagHandler.h:70
int mDragPoint
Definition: Envelope.h:271
wxInt64 TimeToPosition(double time, wxInt64 origin=0, bool ignoreFisheye=false) const
STM: Converts a project time to screen x position.
Definition: ViewInfo.cpp:59
static const double VALUE_TOLERANCE
Definition: Envelope.cpp:46
void SetT(double t)
Definition: Envelope.h:42
int min(int a, int b)
double GetValue(double t, double sampleDur=0) const
Get envelope value at time t.
Definition: Envelope.cpp:1114
double mOffset
The time at which the envelope starts, i.e. the start offset.
Definition: Envelope.h:255
void SetVal(Envelope *pEnvelope, double val)
Definition: Envelope.h:276
static wxBrush envelopeBrush
Definition: AColor.h:124
void AddPointAtEnd(double t, double val)
Definition: Envelope.cpp:231
void GetValuesRelative(double *buffer, int len, double t0, double tstep, bool leftLimit=false) const
Definition: Envelope.cpp:1223
double ClampValue(double value)
Definition: Envelope.h:107
std::pair< int, int > ExpandRegion(double t0, double tlen, double *pLeftVal, double *pRightVal)
Definition: Envelope.cpp:900
double GetVal() const
Definition: Envelope.h:43
bool GetDragPointValid() const
Definition: Envelope.h:236
static double InterpolatePoints(double y1, double y2, double factor, bool logarithmic)
Definition: Envelope.cpp:1426
void SetRange(double minValue, double maxValue)
Definition: Envelope.cpp:221
Envelope * GetEnvelope() const
void Insert(int point, const EnvPoint &p)
insert a point
Definition: Envelope.cpp:653
bool mDragPointValid
Definition: Envelope.h:270
double mDefaultValue
Definition: Envelope.h:267
void GetValues(double *buffer, int len, double t0, double tstep) const
Get many envelope points at once.
Definition: Envelope.cpp:1214
std::pair< int, int > EqualRange(double when, double sampleDur) const
Definition: Envelope.cpp:1048
Base class for XMLFileWriter and XMLStringWriter that provides the general functionality for creating...
Definition: XMLWriter.h:22
double GetInterpolationStartValueAtPoint(int iPoint) const
Definition: Envelope.cpp:1205
int GetWaveYPos(float value, float min, float max, int height, bool dB, bool outer, float dBr, bool clip)
void CollapseRegion(double t0, double t1, double sampleDur)
Definition: Envelope.cpp:674
int NumberOfPointsAfter(double t) const
Definition: Envelope.cpp:1381
EnvArray mEnv
Definition: Envelope.h:252
double Integral(double t0, double t1) const
Definition: Envelope.cpp:1499
EnvPoint, derived from XMLTagHandler, provides Envelope with a draggable point type.
Definition: Envelope.h:35
bool HandleMouseButtonDown(const wxMouseEvent &event, wxRect &r, const ZoomInfo &zoomInfo, bool dB, double dBRange, float zoomMin=-1.0, float zoomMax=1.0)
Definition: Envelope.cpp:478
static void DrawPoint(wxDC &dc, const wxRect &r, int x, int y, bool top)
TODO: This should probably move to track artist.
Definition: Envelope.cpp:309
EnvelopeEditor(Envelope &envelope, bool mirrored)
Definition: Envelope.cpp:1799
double GetOffset() const
Definition: Envelope.h:95