forked from audacity/audacity
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathEnvelope.cpp
More file actions
1813 lines (1569 loc) · 56.2 KB
/
Copy pathEnvelope.cpp
File metadata and controls
1813 lines (1569 loc) · 56.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/**********************************************************************
Audacity: A Digital Audio Editor
Envelope.cpp
Dominic Mazzoni (original author)
Dr William Bland (integration - the Calculus kind)
Monty (xiphmont) (important bug fixes)
*******************************************************************//**
\class Envelope
\brief Draggable curve used in TrackPanel for varying amplification.
This class manages an envelope - i.e. a piecewise linear funtion
that the user can edit by dragging control points around. The
envelope is most commonly used to control the amplitude of a
waveform, but it is also used to shape the Equalization curve.
*//****************************************************************//**
\class EnvPoint
\brief EnvPoint, derived from XMLTagHandler, provides Envelope with
a draggable point type.
*//*******************************************************************/
#include "Envelope.h"
#include "Experimental.h"
#include "ViewInfo.h"
#include <math.h>
#include <wx/dc.h>
#include <wx/brush.h>
#include <wx/event.h>
#include <wx/pen.h>
#include <wx/textfile.h>
#include <wx/log.h>
#include "AColor.h"
#include "DirManager.h"
#include "TrackArtist.h"
static const double VALUE_TOLERANCE = 0.001;
Envelope::Envelope(bool exponential, double minValue, double maxValue, double defaultValue)
: mDB(exponential)
, mMinValue(minValue)
, mMaxValue(maxValue)
, mDefaultValue { ClampValue(defaultValue) }
{
}
Envelope::~Envelope()
{
}
bool Envelope::ConsistencyCheck()
{
bool consistent = true;
bool disorder;
do {
disorder = false;
for ( size_t ii = 0, count = mEnv.size(); ii < count; ) {
// Find range of points with equal T
const double thisT = mEnv[ii].GetT();
double nextT = 0.0f;
auto nextI = ii + 1;
while ( nextI < count && thisT == ( nextT = mEnv[nextI].GetT() ) )
++nextI;
if ( nextI < count && nextT < thisT )
disorder = true;
while ( nextI - ii > 2 ) {
// too many coincident time values
if ((int)ii == mDragPoint || (int)nextI - 1 == mDragPoint)
// forgivable
;
else {
consistent = false;
// repair it
Delete( nextI - 2 );
if (mDragPoint >= (int)nextI - 2)
--mDragPoint;
--nextI, --count;
// wxLogError
}
}
ii = nextI;
}
if (disorder) {
consistent = false;
// repair it
std::stable_sort( mEnv.begin(), mEnv.end(),
[]( const EnvPoint &a, const EnvPoint &b )
{ return a.GetT() < b.GetT(); } );
}
} while ( disorder );
return consistent;
}
/// Rescale function for time tracks (could also be used for other tracks though).
/// This is used to load old time track project files where the envelope used a 0 to 1
/// range instead of storing the actual time track values. This function will change the range of the envelope
/// and rescale all envelope points accordingly (unlike SetRange, which clamps the envelope points to the NEW range).
/// @minValue - the NEW minimum value
/// @maxValue - the NEW maximum value
void Envelope::RescaleValues(double minValue, double maxValue)
{
double oldMinValue = mMinValue;
double oldMaxValue = mMaxValue;
mMinValue = minValue;
mMaxValue = maxValue;
// rescale the default value
double factor = (mDefaultValue - oldMinValue) / (oldMaxValue - oldMinValue);
mDefaultValue = ClampValue(mMinValue + (mMaxValue - mMinValue) * factor);
// rescale all points
for( unsigned int i = 0; i < mEnv.size(); i++ ) {
factor = (mEnv[i].GetVal() - oldMinValue) / (oldMaxValue - oldMinValue);
mEnv[i].SetVal( this, mMinValue + (mMaxValue - mMinValue) * factor );
}
}
/// Flatten removes all points from the envelope to
/// make it horizontal at a chosen y-value.
/// @value - the y-value for the flat envelope.
void Envelope::Flatten(double value)
{
mEnv.clear();
mDefaultValue = ClampValue(value);
}
void Envelope::SetDragPoint(int dragPoint)
{
mDragPoint = std::max(-1, std::min(int(mEnv.size() - 1), dragPoint));
mDragPointValid = (mDragPoint >= 0);
}
void Envelope::SetDragPointValid(bool valid)
{
mDragPointValid = (valid && mDragPoint >= 0);
if (mDragPoint >= 0 && !valid) {
// We're going to be deleting the point; On
// screen we show this by having the envelope move to
// the position it will have after deletion of the point.
// Without deleting the point we move it left or right
// to the same position as the previous or next point.
static const double big = std::numeric_limits<double>::max();
auto size = mEnv.size();
if( size <= 1) {
// There is only one point - just move it
// off screen and at default height.
// temporary state when dragging only!
mEnv[mDragPoint].SetT(big);
mEnv[mDragPoint].SetVal( this, mDefaultValue );
return;
}
else if ( mDragPoint + 1 == (int)size ) {
// Put the point at the height of the last point, but also off screen.
mEnv[mDragPoint].SetT(big);
mEnv[mDragPoint].SetVal( this, mEnv[ size - 1 ].GetVal() );
}
else {
// Place it exactly on its right neighbour.
// That way the drawing code will overpaint the dark dot with
// a light dot, as if it were deleted.
const auto &neighbor = mEnv[mDragPoint + 1];
mEnv[mDragPoint].SetT(neighbor.GetT());
mEnv[mDragPoint].SetVal( this, neighbor.GetVal() );
}
}
}
void Envelope::MoveDragPoint(double newWhen, double value)
{
SetDragPointValid(true);
if (!mDragPointValid)
return;
// We'll limit the drag point time to be between those of the preceding
// and next envelope point.
double limitLo = 0.0;
double limitHi = mTrackLen;
if (mDragPoint > 0)
limitLo = std::max(limitLo, mEnv[mDragPoint - 1].GetT());
if (mDragPoint + 1 < (int)mEnv.size())
limitHi = std::min(limitHi, mEnv[mDragPoint + 1].GetT());
EnvPoint &dragPoint = mEnv[mDragPoint];
const double tt =
std::max(limitLo, std::min(limitHi, newWhen));
// This might temporary violate the constraint that at most two
// points share a time value.
dragPoint.SetT(tt);
dragPoint.SetVal( this, value );
}
void Envelope::ClearDragPoint()
{
if (!mDragPointValid && mDragPoint >= 0)
Delete(mDragPoint);
mDragPoint = -1;
mDragPointValid = false;
}
void Envelope::SetRange(double minValue, double maxValue) {
mMinValue = minValue;
mMaxValue = maxValue;
mDefaultValue = ClampValue(mDefaultValue);
for( unsigned int i = 0; i < mEnv.size(); i++ )
mEnv[i].SetVal( this, mEnv[i].GetVal() ); // this clamps the value to the NEW range
}
// This is used only during construction of an Envelope by complete or partial
// copy of another, or when truncating a track.
void Envelope::AddPointAtEnd( double t, double val )
{
mEnv.push_back( EnvPoint{ t, val } );
// Assume copied points were stored by nondecreasing time.
// Allow no more than two points at exactly the same time.
// Maybe that happened, because extra points were inserted at the boundary
// of the copied range, which were not in the source envelope.
auto nn = mEnv.size() - 1;
while ( nn >= 2 && mEnv[ nn - 2 ].GetT() == t ) {
// Of three or more points at the same time, erase one in the middle,
// not the one newly added.
mEnv.erase( mEnv.begin() + nn - 1 );
--nn;
}
}
Envelope::Envelope(const Envelope &orig, double t0, double t1)
: mDB(orig.mDB)
, mMinValue(orig.mMinValue)
, mMaxValue(orig.mMaxValue)
, mDefaultValue(orig.mDefaultValue)
{
mOffset = wxMax(t0, orig.mOffset);
mTrackLen = wxMin(t1, orig.mOffset + orig.mTrackLen) - mOffset;
auto range1 = orig.EqualRange( t0 - orig.mOffset, 0 );
auto range2 = orig.EqualRange( t1 - orig.mOffset, 0 );
CopyRange(orig, range1.first, range2.second);
}
Envelope::Envelope(const Envelope &orig)
: mDB(orig.mDB)
, mMinValue(orig.mMinValue)
, mMaxValue(orig.mMaxValue)
, mDefaultValue(orig.mDefaultValue)
{
mOffset = orig.mOffset;
mTrackLen = orig.mTrackLen;
CopyRange(orig, 0, orig.GetNumberOfPoints());
}
void Envelope::CopyRange(const Envelope &orig, size_t begin, size_t end)
{
size_t len = orig.mEnv.size();
size_t i = begin;
// Create the point at 0 if it needs interpolated representation
if ( i > 0 )
AddPointAtEnd(0, orig.GetValue(mOffset));
// Copy points from inside the copied region
for (; i < end; ++i) {
const EnvPoint &point = orig[i];
const double when = point.GetT() + (orig.mOffset - mOffset);
AddPointAtEnd(when, point.GetVal());
}
// Create the final point if it needs interpolated representation
// If the last point of e was exatly at t1, this effectively copies it too.
if (mTrackLen > 0 && i < len)
AddPointAtEnd( mTrackLen, orig.GetValue(mOffset + mTrackLen));
}
#if 0
/// Limit() limits a double value to a range.
/// TODO: Move to a general utilities source file.
static double Limit( double Lo, double Value, double Hi )
{
if( Value < Lo )
return Lo;
if( Value > Hi )
return Hi;
return Value;
}
#endif
/// TODO: This should probably move to track artist.
static void DrawPoint(wxDC & dc, const wxRect & r, int x, int y, bool top)
{
if (y >= 0 && y <= r.height) {
wxRect circle(r.x + x, r.y + (top ? y - 1: y - 2), 4, 4);
dc.DrawEllipse(circle);
}
}
#include "TrackPanelDrawingContext.h"
#include "tracks/ui/EnvelopeHandle.h"
/// TODO: This should probably move to track artist.
void Envelope::DrawPoints
(TrackPanelDrawingContext &context, const wxRect & r, const ZoomInfo &zoomInfo,
bool dB, double dBRange,
float zoomMin, float zoomMax, bool mirrored) const
{
auto &dc = context.dc;
bool highlight = false;
#ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
auto target = dynamic_cast<EnvelopeHandle*>(context.target.get());
highlight = target && target->GetEnvelope() == this;
#endif
wxPen &pen = highlight ? AColor::uglyPen : AColor::envelopePen;
dc.SetPen( pen );
dc.SetBrush(*wxWHITE_BRUSH);
for (int i = 0; i < (int)mEnv.size(); i++) {
const double time = mEnv[i].GetT() + mOffset;
const wxInt64 position = zoomInfo.TimeToPosition(time);
if (position >= 0 && position < r.width) {
// Change colour if this is the draggable point...
if (i == mDragPoint) {
dc.SetPen( pen );
dc.SetBrush(AColor::envelopeBrush);
}
double v = mEnv[i].GetVal();
int x = (int)(position);
int y, y2;
y = GetWaveYPos(v, zoomMin, zoomMax, r.height, dB,
true, dBRange, false);
if (!mirrored) {
DrawPoint(dc, r, x, y, true);
}
else {
y2 = GetWaveYPos(-v-.000000001, zoomMin, zoomMax, r.height, dB,
true, dBRange, false);
// This follows the same logic as the envelop drawing in
// TrackArtist::DrawEnvelope().
// TODO: make this calculation into a reusable function.
if (y2 - y < 9) {
int value = (int)((zoomMax / (zoomMax - zoomMin)) * r.height);
y = value - 4;
y2 = value + 4;
}
DrawPoint(dc, r, x, y, true);
DrawPoint(dc, r, x, y2, false);
// Contour
y = GetWaveYPos(v, zoomMin, zoomMax, r.height, dB,
false, dBRange, false);
y2 = GetWaveYPos(-v-.000000001, zoomMin, zoomMax, r.height, dB,
false, dBRange, false);
if (y <= y2) {
DrawPoint(dc, r, x, y, true);
DrawPoint(dc, r, x, y2, false);
}
}
// Change colour back again if was the draggable point.
if (i == mDragPoint) {
dc.SetPen( pen );
dc.SetBrush(*wxWHITE_BRUSH);
}
}
}
}
bool Envelope::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
{
// Return unless it's the envelope tag.
if (wxStrcmp(tag, wxT("envelope")))
return false;
int numPoints = 0;
long nValue = -1;
while (*attrs) {
const wxChar *attr = *attrs++;
const wxChar *value = *attrs++;
if (!value)
break;
const wxString strValue = value;
if( !wxStrcmp(attr, wxT("numpoints")) &&
XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue))
numPoints = nValue;
}
if (numPoints < 0)
return false;
mEnv.clear();
mEnv.reserve(numPoints);
return true;
}
XMLTagHandler *Envelope::HandleXMLChild(const wxChar *tag)
{
if (wxStrcmp(tag, wxT("controlpoint")))
return NULL;
mEnv.push_back( EnvPoint{} );
return &mEnv.back();
}
void Envelope::WriteXML(XMLWriter &xmlFile) const
// may throw
{
unsigned int ctrlPt;
xmlFile.StartTag(wxT("envelope"));
xmlFile.WriteAttr(wxT("numpoints"), mEnv.size());
for (ctrlPt = 0; ctrlPt < mEnv.size(); ctrlPt++) {
const EnvPoint &point = mEnv[ctrlPt];
xmlFile.StartTag(wxT("controlpoint"));
xmlFile.WriteAttr(wxT("t"), point.GetT(), 12);
xmlFile.WriteAttr(wxT("val"), point.GetVal(), 12);
xmlFile.EndTag(wxT("controlpoint"));
}
xmlFile.EndTag(wxT("envelope"));
}
namespace
{
inline int SQR(int x) { return x * x; }
}
/// ValueOfPixel() converts a y position on screen to an envelope value.
/// @param y - y position, usually of the mouse.relative to the clip.
/// @param height - height of the rectangle we are in.
/// @upper - true if we are on the upper line, false if on lower.
/// @dB - display mode either linear or log.
/// @zoomMin - vertical scale, typically -1.0
/// @zoomMax - vertical scale, typically +1.0
float EnvelopeEditor::ValueOfPixel( int y, int height, bool upper,
bool dB, double dBRange,
float zoomMin, float zoomMax)
{
float v = ::ValueOfPixel(y, height, 0 != mContourOffset, dB, dBRange, zoomMin, zoomMax);
// MB: this is mostly equivalent to what the old code did, I'm not sure
// if anything special is needed for asymmetric ranges
if(upper)
return mEnvelope.ClampValue(v);
else
return mEnvelope.ClampValue(-v);
}
/// HandleMouseButtonDown either finds an existing control point or adds a NEW one
/// which is then recorded as the point to drag.
/// This is slightly complicated by there possibly being four control points for
/// a given time value:
/// We have an upper and lower envelope line.
/// Also we may be showing an inner envelope (at 0.5 the range).
bool EnvelopeEditor::HandleMouseButtonDown(const wxMouseEvent & event, wxRect & r,
const ZoomInfo &zoomInfo,
bool dB, double dBRange,
float zoomMin, float zoomMax)
{
int ctr = (int)(r.height * zoomMax / (zoomMax - zoomMin));
bool upper = !mMirrored || (zoomMin >= 0.0) || (event.m_y - r.y < ctr);
int clip_y = event.m_y - r.y;
if(clip_y < 0) clip_y = 0; //keeps point in rect r, even if mouse isn't
if(clip_y > r.GetBottom()) clip_y = r.GetBottom();
int bestNum = -1;
int bestDistSqr = 100; // Must be within 10 pixel radius.
// Member variables hold state that will be needed in dragging.
mButton = event.GetButton();
mContourOffset = false;
// wxLogDebug(wxT("Y:%i Height:%i Offset:%i"), y, height, mContourOffset );
int len = mEnvelope.GetNumberOfPoints();
// TODO: extract this into a function FindNearestControlPoint()
// TODO: also fix it so that we can drag the last point on an envelope.
for (int i = 0; i < len; i++) { //search for control point nearest click
const double time = mEnvelope[i].GetT() + mEnvelope.GetOffset();
const wxInt64 position = zoomInfo.TimeToPosition(time);
if (position >= 0 && position < r.width) {
int x = (int)(position);
int y[4];
int numControlPoints;
// Outer control points
double value = mEnvelope[i].GetVal();
y[0] = GetWaveYPos(value, zoomMin, zoomMax, r.height,
dB, true, dBRange, false);
y[1] = GetWaveYPos(-value, zoomMin, zoomMax, r.height,
dB, true, dBRange, false);
// Inner control points(contour)
y[2] = GetWaveYPos(value, zoomMin, zoomMax, r.height,
dB, false, dBRange, false);
y[3] = GetWaveYPos(-value -.00000001, zoomMin, zoomMax,
r.height, dB, false, dBRange, false);
numControlPoints = 4;
if (y[2] > y[3])
numControlPoints = 2;
if (!mMirrored)
numControlPoints = 1;
const int deltaXSquared = SQR(x - (event.m_x - r.x));
for(int j=0; j<numControlPoints; j++){
const int dSqr = deltaXSquared + SQR(y[j] - (event.m_y - r.y));
if (dSqr < bestDistSqr) {
bestNum = i;
bestDistSqr = dSqr;
mContourOffset = (bool)(j > 1);
}
}
}
}
if (bestNum >= 0) {
mEnvelope.SetDragPoint(bestNum);
}
else {
// TODO: Extract this into a function CreateNewPoint
const double when = zoomInfo.PositionToTime(event.m_x, r.x);
// if (when <= 0 || when >= mTrackLen)
// return false;
const double v = mEnvelope.GetValue( when );
int ct = GetWaveYPos( v, zoomMin, zoomMax, r.height, dB,
false, dBRange, false) ;
int cb = GetWaveYPos( -v-.000000001, zoomMin, zoomMax, r.height, dB,
false, dBRange, false) ;
if (ct <= cb || !mMirrored) {
int t = GetWaveYPos( v, zoomMin, zoomMax, r.height, dB,
true, dBRange, false) ;
int b = GetWaveYPos( -v, zoomMin, zoomMax, r.height, dB,
true, dBRange, false) ;
ct = (t + ct) / 2;
cb = (b + cb) / 2;
if (mMirrored &&
(event.m_y - r.y) > ct &&
((event.m_y - r.y) < cb))
mContourOffset = true;
else
mContourOffset = false;
}
double newVal = ValueOfPixel(clip_y, r.height, upper, dB, dBRange,
zoomMin, zoomMax);
mEnvelope.SetDragPoint(mEnvelope.InsertOrReplace(when, newVal));
mDirty = true;
}
mUpper = upper;
// const int dragPoint = mEnvelope.GetDragPoint();
// mInitialVal = mEnvelope[dragPoint].GetVal();
// mInitialY = event.m_y+mContourOffset;
return true;
}
void EnvelopeEditor::MoveDragPoint(const wxMouseEvent & event, wxRect & r,
const ZoomInfo &zoomInfo, bool dB, double dBRange,
float zoomMin, float zoomMax)
{
int clip_y = event.m_y - r.y;
if(clip_y < 0) clip_y = 0;
if(clip_y > r.height) clip_y = r.height;
double newVal = ValueOfPixel(clip_y, r.height, mUpper, dB, dBRange,
zoomMin, zoomMax);
// We no longer tolerate multiple envelope points at the same t.
// epsilon is less than the time offset of a single sample
// TODO: However because mTrackEpsilon assumes 200KHz this use
// of epsilon is a tad bogus. What we need to do instead is DELETE
// a duplicated point on a mouse up.
double newWhen = zoomInfo.PositionToTime(event.m_x, r.x) - mEnvelope.GetOffset();
mEnvelope.MoveDragPoint(newWhen, newVal);
}
bool EnvelopeEditor::HandleDragging(const wxMouseEvent & event, wxRect & r,
const ZoomInfo &zoomInfo, bool dB, double dBRange,
float zoomMin, float zoomMax,
float WXUNUSED(eMin), float WXUNUSED(eMax))
{
mDirty = true;
wxRect larger = r;
larger.Inflate(10, 10);
if (larger.Contains(event.m_x, event.m_y))
{
// IF we're in the rect THEN we're not deleting this point (anymore).
// ...we're dragging it.
MoveDragPoint( event, r, zoomInfo, dB, dBRange, zoomMin, zoomMax);
return true;
}
if(!mEnvelope.GetDragPointValid())
// IF we already know we're deleting THEN no envelope point to update.
return false;
// Invalidate the point
mEnvelope.SetDragPointValid(false);
return true;
}
// Exit dragging mode and delete dragged point if neccessary.
bool EnvelopeEditor::HandleMouseButtonUp()
{
mEnvelope.ClearDragPoint();
mButton = wxMOUSE_BTN_NONE;
return true;
}
void Envelope::Delete( int point )
{
mEnv.erase(mEnv.begin() + point);
}
void Envelope::Insert(int point, const EnvPoint &p)
{
mEnv.insert(mEnv.begin() + point, p);
}
// Returns true if parent needs to be redrawn
bool EnvelopeEditor::MouseEvent(const wxMouseEvent & event, wxRect & r,
const ZoomInfo &zoomInfo, bool dB, double dBRange,
float zoomMin, float zoomMax)
{
if (event.ButtonDown() && mButton == wxMOUSE_BTN_NONE)
return HandleMouseButtonDown( event, r, zoomInfo, dB, dBRange,
zoomMin, zoomMax);
if (event.Dragging() && mEnvelope.GetDragPoint() >= 0)
return HandleDragging( event, r, zoomInfo, dB, dBRange,
zoomMin, zoomMax);
if (event.ButtonUp() && event.GetButton() == mButton)
return HandleMouseButtonUp();
return false;
}
void Envelope::CollapseRegion( double t0, double t1, double sampleDur )
// NOFAIL-GUARANTEE
{
if ( t1 <= t0 )
return;
// This gets called when somebody clears samples.
// Snip points in the interval (t0, t1), shift values left at times after t1.
// For the boundaries of the interval, preserve the left-side limit at the
// start and right-side limit at the end.
const auto epsilon = sampleDur / 2;
t0 = std::max( 0.0, std::min( mTrackLen, t0 - mOffset ) );
t1 = std::max( 0.0, std::min( mTrackLen, t1 - mOffset ) );
bool leftPoint = true, rightPoint = true;
// Determine the start of the range of points to remove from the array.
auto range0 = EqualRange( t0, 0 );
auto begin = range0.first;
if ( begin == range0.second ) {
if ( t0 > epsilon ) {
// There was no point exactly at t0;
// insert a point to preserve the value.
auto val = GetValueRelative( t0 );
InsertOrReplaceRelative( t0, val );
++begin;
}
else
leftPoint = false;
}
else
// We will keep the first (or only) point that was at t0.
++begin;
// We want end to be the index one past the range of points to remove from
// the array.
// At first, find index of the first point after t1:
auto range1 = EqualRange( t1, 0 );
auto end = range1.second;
if ( range1.first == end ) {
if ( mTrackLen - t1 > epsilon ) {
// There was no point exactly at t1; insert a point to preserve the value.
auto val = GetValueRelative( t1 );
InsertOrReplaceRelative( t1, val );
// end is now the index of this NEW point and that is correct.
}
else
rightPoint = false;
}
else
// We will keep the last (or only) point that was at t1.
--end;
mEnv.erase( mEnv.begin() + begin, mEnv.begin() + end );
// Shift points left after deleted region.
auto len = mEnv.size();
for ( size_t i = begin; i < len; ++i ) {
auto &point = mEnv[i];
if (rightPoint && (int)i == begin)
// Avoid roundoff error.
// Make exactly equal times of neighboring points so that we have
// a real discontinuity.
point.SetT( t0 );
else
point.SetT( point.GetT() - (t1 - t0) );
}
// See if the discontinuity is removable.
if ( rightPoint )
RemoveUnneededPoints( begin, true );
if ( leftPoint )
RemoveUnneededPoints( begin - 1, false );
mTrackLen -= ( t1 - t0 );
}
// This operation is trickier than it looks; the basic rub is that
// a track's envelope runs the range from t=0 to t=tracklen; the t=0
// envelope point applies to the first sample, but the t=tracklen
// envelope point applies one-past the last actual sample.
// t0 should be in the domain of this; if not, it is trimmed.
void Envelope::Paste( double t0, const Envelope *e, double sampleDur )
// NOFAIL-GUARANTEE
{
const bool wasEmpty = (this->mEnv.size() == 0);
auto otherSize = e->mEnv.size();
const double otherDur = e->mTrackLen;
const auto otherOffset = e->mOffset;
const auto deltat = otherOffset + otherDur;
if ( otherSize == 0 && wasEmpty && e->mDefaultValue == this->mDefaultValue )
{
// msmeyer: The envelope is empty and has the same default value, so
// there is nothing that must be inserted, just return. This avoids
// the creation of unnecessary duplicate control points
// MJS: but the envelope does get longer
// PRL: Assuming t0 is in the domain of the envelope
mTrackLen += deltat;
return;
}
// Make t0 relative and trim it to the domain of this
t0 = std::min( mTrackLen, std::max( 0.0, t0 - mOffset ) );
// Adjust if the insertion point rounds off near a discontinuity in this
if ( true )
{
double newT0;
auto range = EqualRange( t0, sampleDur );
auto index = range.first;
if ( index + 2 == range.second &&
( newT0 = mEnv[ index ].GetT() ) == mEnv[ 1 + index ].GetT() )
t0 = newT0;
}
// Open up a space
double leftVal = e->GetValue( 0 );
double rightVal = e->GetValueRelative( otherDur );
// This range includes the right-side limit of the left end of the space,
// and the left-side limit of the right end:
const auto range = ExpandRegion( t0, deltat, &leftVal, &rightVal );
// Where to put the copied points from e -- after the first of the
// two points in range:
auto insertAt = range.first + 1;
// Copy points from e -- maybe skipping those at the extremes
auto end = e->mEnv.end();
if ( otherSize != 0 && e->mEnv[ otherSize - 1 ].GetT() == otherDur )
// ExpandRegion already made an equivalent limit point
--end, --otherSize;
auto begin = e->mEnv.begin();
if ( otherSize != 0 && otherOffset == 0.0 && e->mEnv[ 0 ].GetT() == 0.0 )
++begin, --otherSize;
mEnv.insert( mEnv.begin() + insertAt, begin, end );
// Adjust their times
for ( size_t index = insertAt, last = insertAt + otherSize;
index < last; ++index ) {
auto &point = mEnv[ index ];
point.SetT( point.GetT() + otherOffset + t0 );
}
// Treat removable discontinuities
// Right edge outward:
RemoveUnneededPoints( insertAt + otherSize + 1, true );
// Right edge inward:
RemoveUnneededPoints( insertAt + otherSize, false, false );
// Left edge inward:
RemoveUnneededPoints( range.first, true, false );
// Left edge outward:
RemoveUnneededPoints( range.first - 1, false );
// Guarantee monotonicity of times, against little round-off mistakes perhaps
ConsistencyCheck();
}
void Envelope::RemoveUnneededPoints
( size_t startAt, bool rightward, bool testNeighbors )
// NOFAIL-GUARANTEE
{
// startAt is the index of a recently inserted point which might make no
// difference in envelope evaluation, or else might cause nearby points to
// make no difference.
auto isDiscontinuity = [this]( size_t index ) {
// Assume array accesses are in-bounds
const EnvPoint &point1 = mEnv[ index ];
const EnvPoint &point2 = mEnv[ index + 1 ];
return point1.GetT() == point2.GetT() &&
fabs( point1.GetVal() - point2.GetVal() ) > VALUE_TOLERANCE;
};
auto remove = [this]( size_t index, bool leftLimit ) {
// Assume array accesses are in-bounds
const auto &point = mEnv[ index ];
auto when = point.GetT();
auto val = point.GetVal();
Delete( index ); // try it to see if it's doing anything
auto val1 = GetValueRelative ( when, leftLimit );
if( fabs( val - val1 ) > VALUE_TOLERANCE ) {
// put it back, we needed it
Insert( index, EnvPoint{ when, val } );
return false;
}
else
return true;
};
auto len = mEnv.size();
bool leftLimit =
!rightward && startAt + 1 < len && isDiscontinuity( startAt );
bool removed = remove( startAt, leftLimit );
if ( removed )
// The given point was removable. Done!
return;
if ( !testNeighbors )
return;
// The given point was not removable. But did its insertion make nearby
// points removable?
int index = startAt + ( rightward ? 1 : -1 );
while ( index >= 0 && index < (int)len ) {
// Stop at any discontinuity
if ( index > 0 && isDiscontinuity( index - 1 ) )
break;
if ( (index + 1) < (int)len && isDiscontinuity( index ) )
break;
if ( ! remove( index, false ) )
break;
--len;
if ( ! rightward )
--index;
}
}
std::pair< int, int > Envelope::ExpandRegion
( double t0, double tlen, double *pLeftVal, double *pRightVal )
// NOFAIL-GUARANTEE
{
// t0 is relative time
double val = GetValueRelative( t0 );
const auto range = EqualRange( t0, 0 );
// Preserve the left-side limit.
int index = 1 + range.first;
if ( index <= range.second )
// There is already a control point.
;
else {
// Make a control point.
Insert( range.first, EnvPoint{ t0, val } );
}
// Shift points.
auto len = mEnv.size();
for ( unsigned int ii = index; ii < len; ++ii ) {
auto &point = mEnv[ ii ];
point.SetT( point.GetT() + tlen );
}
mTrackLen += tlen;
// Preserve the right-side limit.
if ( index < range.second )
// There was a control point already.
;
else
// Make a control point.
Insert( index, EnvPoint{ t0 + tlen, val } );
// Make discontinuities at ends, maybe:
if ( pLeftVal )
// Make a discontinuity at the left side of the expansion
Insert( index++, EnvPoint{ t0, *pLeftVal } );
if ( pRightVal )
// Make a discontinuity at the right side of the expansion
Insert( index++, EnvPoint{ t0 + tlen, *pRightVal } );
// Return the range of indices that includes the inside limiting points,
// none, one, or two
return { 1 + range.first, index };
}
void Envelope::InsertSpace( double t0, double tlen )
// NOFAIL-GUARANTEE
{
auto range = ExpandRegion( t0 - mOffset, tlen, nullptr, nullptr );
// Simplify the boundaries if possible
RemoveUnneededPoints( range.second, true );
RemoveUnneededPoints( range.first - 1, false );
}
int Envelope::Reassign(double when, double value)
{
when -= mOffset;
int len = mEnv.size();
if (len == 0)
return -1;
int i = 0;
while (i < len && when > mEnv[i].GetT())
i++;
if (i >= len || when < mEnv[i].GetT())
return -1;
mEnv[i].SetVal( this, value );
return 0;
}
size_t Envelope::GetNumberOfPoints() const
{
return mEnv.size();
}
void Envelope::GetPoints(double *bufferWhen,
double *bufferValue,
int bufferLen) const
{
int n = mEnv.size();
if (n > bufferLen)
n = bufferLen;
int i;
for (i = 0; i < n; i++) {
bufferWhen[i] = mEnv[i].GetT() - mOffset;
bufferValue[i] = mEnv[i].GetVal();
}
}
void Envelope::Cap( double sampleDur )
{