1 /**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 EnvelopeEditor.cpp
6
7 Paul Licameli split this from Envelope.cpp
8
9 **********************************************************************/
10
11
12 #include "EnvelopeEditor.h"
13
14
15
16 #include <wx/dc.h>
17 #include <wx/event.h>
18
19 #include "AColor.h"
20 #include "Envelope.h"
21 #include "TrackArtist.h"
22 #include "TrackPanelDrawingContext.h"
23 #include "ViewInfo.h"
24
25 namespace {
DrawPoint(wxDC & dc,const wxRect & r,int x,int y,bool top)26 void DrawPoint(wxDC & dc, const wxRect & r, int x, int y, bool top)
27 {
28 if (y >= 0 && y <= r.height) {
29 wxRect circle(r.x + x, r.y + (top ? y - 1: y - 2), 4, 4);
30 dc.DrawEllipse(circle);
31 }
32 }
33 }
34
DrawPoints(const Envelope & env,TrackPanelDrawingContext & context,const wxRect & r,bool dB,double dBRange,float zoomMin,float zoomMax,bool mirrored,int origin)35 void EnvelopeEditor::DrawPoints(const Envelope &env,
36 TrackPanelDrawingContext &context, const wxRect & r,
37 bool dB, double dBRange,
38 float zoomMin, float zoomMax, bool mirrored, int origin)
39 {
40 auto &dc = context.dc;
41 const auto artist = TrackArtist::Get( context );
42 const auto &zoomInfo = *artist->pZoomInfo;
43
44 bool highlight = false;
45 #ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
46 auto target = dynamic_cast<EnvelopeHandle*>(context.target.get());
47 highlight = target && target->GetEnvelope() == this;
48 #endif
49 wxPen &pen = highlight ? AColor::uglyPen : AColor::envelopePen;
50 dc.SetPen( pen );
51 dc.SetBrush(*wxWHITE_BRUSH);
52
53 for (int i = 0; i < (int)env.GetNumberOfPoints(); i++) {
54 const double time = env[i].GetT() + env.GetOffset();
55 const wxInt64 position = zoomInfo.TimeToPosition(time, origin);
56 if (position >= 0 && position < r.width) {
57 // Change colour if this is the draggable point...
58 if (i == env.GetDragPoint()) {
59 dc.SetPen( pen );
60 dc.SetBrush(AColor::envelopeBrush);
61 }
62
63 double v = env[i].GetVal();
64 int x = (int)(position);
65 int y, y2;
66
67 y = GetWaveYPos(v, zoomMin, zoomMax, r.height, dB,
68 true, dBRange, false);
69 if (!mirrored) {
70 DrawPoint(dc, r, x, y, true);
71 }
72 else {
73 y2 = GetWaveYPos(-v-.000000001, zoomMin, zoomMax, r.height, dB,
74 true, dBRange, false);
75
76 // This follows the same logic as the envelop drawing in
77 // TrackArt::DrawEnvelope().
78 // TODO: make this calculation into a reusable function.
79 if (y2 - y < 9) {
80 int value = (int)((zoomMax / (zoomMax - zoomMin)) * r.height);
81 y = value - 4;
82 y2 = value + 4;
83 }
84
85 DrawPoint(dc, r, x, y, true);
86 DrawPoint(dc, r, x, y2, false);
87
88 // Contour
89 y = GetWaveYPos(v, zoomMin, zoomMax, r.height, dB,
90 false, dBRange, false);
91 y2 = GetWaveYPos(-v-.000000001, zoomMin, zoomMax, r.height, dB,
92 false, dBRange, false);
93 if (y <= y2) {
94 DrawPoint(dc, r, x, y, true);
95 DrawPoint(dc, r, x, y2, false);
96 }
97 }
98
99 // Change colour back again if was the draggable point.
100 if (i == env.GetDragPoint()) {
101 dc.SetPen( pen );
102 dc.SetBrush(*wxWHITE_BRUSH);
103 }
104 }
105 }
106 }
107
EnvelopeEditor(Envelope & envelope,bool mirrored)108 EnvelopeEditor::EnvelopeEditor(Envelope &envelope, bool mirrored)
109 : mEnvelope(envelope)
110 , mMirrored(mirrored)
111 , mContourOffset(-1)
112 // , mInitialVal(-1.0)
113 // , mInitialY(-1)
114 , mUpper(false)
115 , mButton(wxMOUSE_BTN_NONE)
116 , mDirty(false)
117 {
118 }
119
~EnvelopeEditor()120 EnvelopeEditor::~EnvelopeEditor()
121 {
122 }
123
124 namespace
125 {
SQR(int x)126 inline int SQR(int x) { return x * x; }
127 }
128
129 /// ValueOfPixel() converts a y position on screen to an envelope value.
130 /// @param y - y position, usually of the mouse.relative to the clip.
131 /// @param height - height of the rectangle we are in.
132 /// @upper - true if we are on the upper line, false if on lower.
133 /// @dB - display mode either linear or log.
134 /// @zoomMin - vertical scale, typically -1.0
135 /// @zoomMax - vertical scale, typically +1.0
ValueOfPixel(int y,int height,bool upper,bool dB,double dBRange,float zoomMin,float zoomMax)136 float EnvelopeEditor::ValueOfPixel( int y, int height, bool upper,
137 bool dB, double dBRange,
138 float zoomMin, float zoomMax)
139 {
140 float v = ::ValueOfPixel(y, height, 0 != mContourOffset, dB, dBRange, zoomMin, zoomMax);
141
142 // MB: this is mostly equivalent to what the old code did, I'm not sure
143 // if anything special is needed for asymmetric ranges
144 if(upper)
145 return mEnvelope.ClampValue(v);
146 else
147 return mEnvelope.ClampValue(-v);
148 }
149
150 /// HandleMouseButtonDown either finds an existing control point or adds a NEW one
151 /// which is then recorded as the point to drag.
152 /// This is slightly complicated by there possibly being four control points for
153 /// a given time value:
154 /// We have an upper and lower envelope line.
155 /// Also we may be showing an inner envelope (at 0.5 the range).
HandleMouseButtonDown(const wxMouseEvent & event,wxRect & r,const ZoomInfo & zoomInfo,bool dB,double dBRange,float zoomMin,float zoomMax)156 bool EnvelopeEditor::HandleMouseButtonDown(const wxMouseEvent & event, wxRect & r,
157 const ZoomInfo &zoomInfo,
158 bool dB, double dBRange,
159 float zoomMin, float zoomMax)
160 {
161 int ctr = (int)(r.height * zoomMax / (zoomMax - zoomMin));
162 bool upper = !mMirrored || (zoomMin >= 0.0) || (event.m_y - r.y < ctr);
163
164 int clip_y = event.m_y - r.y;
165 if(clip_y < 0) clip_y = 0; //keeps point in rect r, even if mouse isn't
166 if(clip_y > r.GetBottom()) clip_y = r.GetBottom();
167
168 int bestNum = -1;
169 int bestDistSqr = 100; // Must be within 10 pixel radius.
170
171 // Member variables hold state that will be needed in dragging.
172 mButton = event.GetButton();
173 mContourOffset = false;
174
175 // wxLogDebug(wxT("Y:%i Height:%i Offset:%i"), y, height, mContourOffset );
176 int len = mEnvelope.GetNumberOfPoints();
177
178 // TODO: extract this into a function FindNearestControlPoint()
179 // TODO: also fix it so that we can drag the last point on an envelope.
180 for (int i = 0; i < len; i++) { //search for control point nearest click
181 const double time = mEnvelope[i].GetT() + mEnvelope.GetOffset();
182 const wxInt64 position = zoomInfo.TimeToPosition(time);
183 if (position >= 0 && position < r.width) {
184
185 int x = (int)(position);
186 int y[4];
187 int numControlPoints;
188
189 // Outer control points
190 double value = mEnvelope[i].GetVal();
191 y[0] = GetWaveYPos(value, zoomMin, zoomMax, r.height,
192 dB, true, dBRange, false);
193 y[1] = GetWaveYPos(-value, zoomMin, zoomMax, r.height,
194 dB, true, dBRange, false);
195
196 // Inner control points(contour)
197 y[2] = GetWaveYPos(value, zoomMin, zoomMax, r.height,
198 dB, false, dBRange, false);
199 y[3] = GetWaveYPos(-value -.00000001, zoomMin, zoomMax,
200 r.height, dB, false, dBRange, false);
201
202 numControlPoints = 4;
203
204 if (y[2] > y[3])
205 numControlPoints = 2;
206
207 if (!mMirrored)
208 numControlPoints = 1;
209
210 const int deltaXSquared = SQR(x - (event.m_x - r.x));
211 for(int j=0; j<numControlPoints; j++){
212
213 const int dSqr = deltaXSquared + SQR(y[j] - (event.m_y - r.y));
214 if (dSqr < bestDistSqr) {
215 bestNum = i;
216 bestDistSqr = dSqr;
217 mContourOffset = (bool)(j > 1);
218 }
219 }
220 }
221 }
222
223 if (bestNum >= 0) {
224 mEnvelope.SetDragPoint(bestNum);
225 }
226 else {
227 // TODO: Extract this into a function CreateNewPoint
228 const double when = zoomInfo.PositionToTime(event.m_x, r.x);
229
230 // if (when <= 0 || when >= mTrackLen)
231 // return false;
232
233 const double v = mEnvelope.GetValue( when );
234
235 int ct = GetWaveYPos( v, zoomMin, zoomMax, r.height, dB,
236 false, dBRange, false) ;
237 int cb = GetWaveYPos( -v-.000000001, zoomMin, zoomMax, r.height, dB,
238 false, dBRange, false) ;
239 if (ct <= cb || !mMirrored) {
240 int t = GetWaveYPos( v, zoomMin, zoomMax, r.height, dB,
241 true, dBRange, false) ;
242 int b = GetWaveYPos( -v, zoomMin, zoomMax, r.height, dB,
243 true, dBRange, false) ;
244
245 ct = (t + ct) / 2;
246 cb = (b + cb) / 2;
247
248 if (mMirrored &&
249 (event.m_y - r.y) > ct &&
250 ((event.m_y - r.y) < cb))
251 mContourOffset = true;
252 else
253 mContourOffset = false;
254 }
255
256 double newVal = ValueOfPixel(clip_y, r.height, upper, dB, dBRange,
257 zoomMin, zoomMax);
258
259 mEnvelope.SetDragPoint(mEnvelope.InsertOrReplace(when, newVal));
260 mDirty = true;
261 }
262
263 mUpper = upper;
264
265 // const int dragPoint = mEnvelope.GetDragPoint();
266 // mInitialVal = mEnvelope[dragPoint].GetVal();
267 // mInitialY = event.m_y+mContourOffset;
268
269 return true;
270 }
271
MoveDragPoint(const wxMouseEvent & event,wxRect & r,const ZoomInfo & zoomInfo,bool dB,double dBRange,float zoomMin,float zoomMax)272 void EnvelopeEditor::MoveDragPoint(const wxMouseEvent & event, wxRect & r,
273 const ZoomInfo &zoomInfo, bool dB, double dBRange,
274 float zoomMin, float zoomMax)
275 {
276 int clip_y = event.m_y - r.y;
277 if(clip_y < 0) clip_y = 0;
278 if(clip_y > r.height) clip_y = r.height;
279 double newVal = ValueOfPixel(clip_y, r.height, mUpper, dB, dBRange,
280 zoomMin, zoomMax);
281
282 // We no longer tolerate multiple envelope points at the same t.
283 // epsilon is less than the time offset of a single sample
284 // TODO: However because mTrackEpsilon assumes 200KHz this use
285 // of epsilon is a tad bogus. What we need to do instead is DELETE
286 // a duplicated point on a mouse up.
287 double newWhen = zoomInfo.PositionToTime(event.m_x, r.x) - mEnvelope.GetOffset();
288 mEnvelope.MoveDragPoint(newWhen, newVal);
289 }
290
HandleDragging(const wxMouseEvent & event,wxRect & r,const ZoomInfo & zoomInfo,bool dB,double dBRange,float zoomMin,float zoomMax,float WXUNUSED (eMin),float WXUNUSED (eMax))291 bool EnvelopeEditor::HandleDragging(const wxMouseEvent & event, wxRect & r,
292 const ZoomInfo &zoomInfo, bool dB, double dBRange,
293 float zoomMin, float zoomMax,
294 float WXUNUSED(eMin), float WXUNUSED(eMax))
295 {
296 mDirty = true;
297
298 wxRect larger = r;
299 larger.Inflate(10, 10);
300
301 if (larger.Contains(event.m_x, event.m_y))
302 {
303 // IF we're in the rect THEN we're not deleting this point (anymore).
304 // ...we're dragging it.
305 MoveDragPoint( event, r, zoomInfo, dB, dBRange, zoomMin, zoomMax);
306 return true;
307 }
308
309 if(!mEnvelope.GetDragPointValid())
310 // IF we already know we're deleting THEN no envelope point to update.
311 return false;
312
313 // Invalidate the point
314 mEnvelope.SetDragPointValid(false);
315 return true;
316 }
317
318 // Exit dragging mode and delete dragged point if necessary.
HandleMouseButtonUp()319 bool EnvelopeEditor::HandleMouseButtonUp()
320 {
321 mEnvelope.ClearDragPoint();
322 mButton = wxMOUSE_BTN_NONE;
323 return true;
324 }
325
326 // Returns true if parent needs to be redrawn
MouseEvent(const wxMouseEvent & event,wxRect & r,const ZoomInfo & zoomInfo,bool dB,double dBRange,float zoomMin,float zoomMax)327 bool EnvelopeEditor::MouseEvent(const wxMouseEvent & event, wxRect & r,
328 const ZoomInfo &zoomInfo, bool dB, double dBRange,
329 float zoomMin, float zoomMax)
330 {
331 if (event.ButtonDown() && mButton == wxMOUSE_BTN_NONE)
332 return HandleMouseButtonDown( event, r, zoomInfo, dB, dBRange,
333 zoomMin, zoomMax);
334 if (event.Dragging() && mEnvelope.GetDragPoint() >= 0)
335 return HandleDragging( event, r, zoomInfo, dB, dBRange,
336 zoomMin, zoomMax);
337 if (event.ButtonUp() && event.GetButton() == mButton)
338 return HandleMouseButtonUp();
339 return false;
340 }
341