Audacity 3.2.0
EnvelopeHandle.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3Audacity: A Digital Audio Editor
4
5EnvelopeHandle.cpp
6
7Paul Licameli split from TrackPanel.cpp
8
9**********************************************************************/
10
11
12#include "EnvelopeHandle.h"
13
14#include "TrackView.h"
15
16#include "Envelope.h"
17#include "../../EnvelopeEditor.h"
18#include "../../HitTestResult.h"
19#include "../../prefs/WaveformSettings.h"
20#include "../../ProjectAudioIO.h"
21#include "ProjectHistory.h"
22#include "../../RefreshCode.h"
23#include "../../TimeTrack.h"
24#include "../../TrackArt.h"
25#include "../../TrackPanelMouseEvent.h"
26#include "ViewInfo.h"
27#include "../../WaveTrack.h"
28#include "../../../images/Cursors.h"
29
30#include <wx/event.h>
31
33 : mEnvelope{ pEnvelope }
34{
35}
36
38{
39#ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
41#endif
42}
43
45{}
46
48(std::weak_ptr<EnvelopeHandle> &holder, Envelope *envelope, bool timeTrack)
49{
50 auto result = AssignUIHandlePtr(holder, std::make_shared<EnvelopeHandle>(envelope));
51 result->mTimeTrack = timeTrack;
52 return result;
53}
54
55namespace {
57 (const AudacityProject &project, const TimeTrack &tt,
58 double &dBRange, bool &dB, float &zoomMin, float &zoomMax)
59 {
60 const auto &viewInfo = ViewInfo::Get( project );
61 dBRange = viewInfo.dBr;
62 dB = tt.GetDisplayLog();
63 zoomMin = tt.GetRangeLower(), zoomMax = tt.GetRangeUpper();
64 if (dB) {
65 // MB: silly way to undo the work of GetWaveYPos while still getting a logarithmic scale
66 zoomMin = LINEAR_TO_DB(std::max(1.0e-7, double(zoomMin))) / dBRange + 1.0;
67 zoomMax = LINEAR_TO_DB(std::max(1.0e-7, double(zoomMax))) / dBRange + 1.0;
68 }
69 }
70}
71
73(std::weak_ptr<EnvelopeHandle> &holder,
74 const wxMouseState &state, const wxRect &rect,
75 const AudacityProject *pProject, const std::shared_ptr<TimeTrack> &tt)
76{
77 auto envelope = tt->GetEnvelope();
78 if (!envelope)
79 return {};
80 bool dB;
81 double dBRange;
82 float zoomMin, zoomMax;
83 GetTimeTrackData( *pProject, *tt, dBRange, dB, zoomMin, zoomMax);
85 (holder, state, rect, pProject, envelope, zoomMin, zoomMax, dB, dBRange,
86 true);
87}
88
90(std::weak_ptr<EnvelopeHandle> &holder,
91 const wxMouseState &state, const wxRect &rect,
92 const AudacityProject *pProject, const std::shared_ptr<WaveTrack> &wt)
93{
96 auto &viewInfo = ViewInfo::Get(*pProject);
97 auto time = viewInfo.PositionToTime(state.m_x, rect.GetX());
98 Envelope *const envelope = wt->GetEnvelopeAtTime(time);
99
100 if (!envelope)
101 return {};
102
103 // Get envelope point, range 0.0 to 1.0
104 const bool dB = !wt->GetWaveformSettings().isLinear();
105
106 float zoomMin, zoomMax;
107 wt->GetDisplayBounds(&zoomMin, &zoomMax);
108
109 const float dBRange = wt->GetWaveformSettings().dBRange;
110
112 (holder, state, rect, pProject, envelope, zoomMin, zoomMax, dB, dBRange, false);
113}
114
116(std::weak_ptr<EnvelopeHandle> &holder,
117 const wxMouseState &state, const wxRect &rect, const AudacityProject *pProject,
118 Envelope *envelope, float zoomMin, float zoomMax,
119 bool dB, float dBRange, bool timeTrack)
120{
121 const auto &viewInfo = ViewInfo::Get( *pProject );
122
123 const double envValue =
124 envelope->GetValue(viewInfo.PositionToTime(state.m_x, rect.x));
125
126 // Get y position of envelope point.
127 int yValue = GetWaveYPos(envValue,
128 zoomMin, zoomMax,
129 rect.height, dB, true, dBRange, false) + rect.y;
130
131 // Get y position of center line
132 int ctr = GetWaveYPos(0.0,
133 zoomMin, zoomMax,
134 rect.height, dB, true, dBRange, false) + rect.y;
135
136 // Get y distance of mouse from center line (in pixels).
137 int yMouse = abs(ctr - state.m_y);
138 // Get y distance of envelope from center line (in pixels)
139 yValue = abs(ctr - yValue);
140
141 // JKC: It happens that the envelope is actually drawn offset from its
142 // 'true' position (it is 3 pixels wide). yMisalign is really a fudge
143 // factor to allow us to hit it exactly, but I wouldn't dream of
144 // calling it yFudgeFactor :)
145 const int yMisalign = 2;
146 // Perhaps yTolerance should be put into preferences?
147 const int yTolerance = 5; // how far from envelope we may be and count as a hit.
148 int distance;
149
150 // For amplification using the envelope we introduced the idea of contours.
151 // The contours have the same shape as the envelope, which may be partially off-screen.
152 // The contours are closer in to the center line.
153 // Beware very short rectangles! Make this at least 1
154 int ContourSpacing = std::max(1,
155 static_cast<int>(rect.height / (2 * (zoomMax - zoomMin))));
156 const int MaxContours = 2;
157
158 // Adding ContourSpacing/2 selects a region either side of the contour.
159 int yDisplace = yValue - yMisalign - yMouse + ContourSpacing / 2;
160 if (yDisplace > (MaxContours * ContourSpacing))
161 return {};
162 // Subtracting the ContourSpacing/2 we added earlier ensures distance is centred on the contour.
163 distance = abs((yDisplace % ContourSpacing) - ContourSpacing / 2);
164 if (distance >= yTolerance)
165 return {};
166
167 return HitAnywhere(holder, envelope, timeTrack);
168}
169
171(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
172{
173 using namespace RefreshCode;
174 const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
175 if ( unsafe )
176 return Cancelled;
177
178 const wxMouseEvent &event = evt.event;
179 const auto &viewInfo = ViewInfo::Get( *pProject );
180 const auto pView = std::static_pointer_cast<TrackView>(evt.pCell);
181 const auto pTrack = pView ? pView->FindTrack().get() : nullptr;
182
183 mEnvelopeEditors.clear();
184
185 unsigned result = Cancelled;
186 if (pTrack)
187 result = pTrack->TypeSwitch< decltype(RefreshNone) >(
188 [&](WaveTrack *wt) {
189 if (!mEnvelope)
190 return Cancelled;
191
195 auto channels = TrackList::Channels( wt );
196 for ( auto channel : channels ) {
197 if (channel == wt)
198 mEnvelopeEditors.push_back(
199 std::make_unique< EnvelopeEditor >( *mEnvelope, true ) );
200 else {
201 auto time =
202 viewInfo.PositionToTime(event.GetX(), evt.rect.GetX());
203 auto e2 = channel->GetEnvelopeAtTime(time);
204 if (e2)
205 mEnvelopeEditors.push_back(
206 std::make_unique< EnvelopeEditor >( *e2, true ) );
207 else {
208 // There isn't necessarily an envelope there; no guarantee a
209 // linked track has the same WaveClip structure...
210 }
211 }
212 }
213
214 return RefreshNone;
215 },
216 [&](TimeTrack *tt) {
217 if (!mEnvelope)
218 return Cancelled;
219 GetTimeTrackData( *pProject, *tt, mdBRange, mLog, mLower, mUpper);
220 mEnvelopeEditors.push_back(
221 std::make_unique< EnvelopeEditor >( *mEnvelope, false )
222 );
223
224 return RefreshNone;
225 },
226 [](Track *) {
227 return Cancelled;
228 }
229 );
230
231 if (result & Cancelled)
232 return result;
233
234 mRect = evt.rect;
235
236 const bool needUpdate = ForwardEventToEnvelopes(event, viewInfo);
237 return needUpdate ? RefreshCell : RefreshNone;
238}
239
241(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
242{
243 using namespace RefreshCode;
244 const wxMouseEvent &event = evt.event;
245 const auto &viewInfo = ViewInfo::Get( *pProject );
246 const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
247 if (unsafe) {
248 this->Cancel(pProject);
249 return RefreshCell | Cancelled;
250 }
251
252 const bool needUpdate = ForwardEventToEnvelopes(event, viewInfo);
253 return needUpdate ? RefreshCell : RefreshNone;
254}
255
257(const TrackPanelMouseState &, AudacityProject *pProject)
258{
259 const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
260 static auto disabledCursor =
261 ::MakeCursor(wxCURSOR_NO_ENTRY, DisabledCursorXpm, 16, 16);
262 static auto envelopeCursor =
263 ::MakeCursor(wxCURSOR_ARROW, EnvCursorXpm, 16, 16);
264
265 auto message = mTimeTrack
266 ? XO("Click and drag to warp playback time")
267 : XO("Click and drag to edit the amplitude envelope");
268
269 return {
270 message,
271 (unsafe
272 ? &*disabledCursor
273 : &*envelopeCursor)
274 };
275}
276
278(const TrackPanelMouseEvent &evt, AudacityProject *pProject,
279 wxWindow *)
280{
281 const wxMouseEvent &event = evt.event;
282 const auto &viewInfo = ViewInfo::Get( *pProject );
283 const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
284 if (unsafe)
285 return this->Cancel(pProject);
286
287 const bool needUpdate = ForwardEventToEnvelopes(event, viewInfo);
288
289 ProjectHistory::Get( *pProject ).PushState(
290 /* i18n-hint: (verb) Audacity has just adjusted the envelope .*/
291 XO("Adjusted envelope."),
292 /* i18n-hint: The envelope is a curve that controls the audio loudness.*/
293 XO("Envelope")
294 );
295
296 mEnvelopeEditors.clear();
297
298 using namespace RefreshCode;
299 return needUpdate ? RefreshCell : RefreshNone;
300}
301
303{
304 ProjectHistory::Get( *pProject ).RollbackState();
305 mEnvelopeEditors.clear();
307}
308
310 (const wxMouseEvent &event, const ViewInfo &viewInfo)
311{
315
316 // AS: I'm not sure why we can't let the Envelope take care of
317 // redrawing itself. ?
318 bool needUpdate = false;
319 for (const auto &pEditor : mEnvelopeEditors) {
320 needUpdate =
321 pEditor->MouseEvent(
322 event, mRect, viewInfo, mLog, mdBRange, mLower, mUpper)
323 || needUpdate;
324 }
325
326 return needUpdate;
327}
std::shared_ptr< UIHandle > UIHandlePtr
Definition: CellularPanel.h:28
#define XO(s)
Definition: Internat.h:31
#define LINEAR_TO_DB(x)
Definition: MemoryX.h:536
int GetWaveYPos(float value, float min, float max, int height, bool dB, bool outer, float dBr, bool clip)
Definition: TrackArt.cpp:49
std::unique_ptr< wxCursor > MakeCursor(int WXUNUSED(CursorId), const char *const pXpm[36], int HotX, int HotY)
Definition: TrackPanel.cpp:185
std::shared_ptr< Subclass > AssignUIHandlePtr(std::weak_ptr< Subclass > &holder, const std::shared_ptr< Subclass > &pNew)
Definition: UIHandle.h:151
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:89
std::vector< std::unique_ptr< EnvelopeEditor > > mEnvelopeEditors
static UIHandlePtr WaveTrackHitTest(std::weak_ptr< EnvelopeHandle > &holder, const wxMouseState &state, const wxRect &rect, const AudacityProject *pProject, const std::shared_ptr< WaveTrack > &wt)
virtual ~EnvelopeHandle()
static UIHandlePtr TimeTrackHitTest(std::weak_ptr< EnvelopeHandle > &holder, const wxMouseState &state, const wxRect &rect, const AudacityProject *pProject, const std::shared_ptr< TimeTrack > &tt)
static UIHandlePtr HitEnvelope(std::weak_ptr< EnvelopeHandle > &holder, const wxMouseState &state, const wxRect &rect, const AudacityProject *pProject, Envelope *envelope, float zoomMin, float zoomMax, bool dB, float dBRange, bool timeTrack)
static UIHandlePtr HitAnywhere(std::weak_ptr< EnvelopeHandle > &holder, Envelope *envelope, bool timeTrack)
Result Drag(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
Result Click(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
HitTestPreview Preview(const TrackPanelMouseState &state, AudacityProject *pProject) override
Envelope * mEnvelope
bool ForwardEventToEnvelopes(const wxMouseEvent &event, const ViewInfo &viewInfo)
Result Cancel(AudacityProject *pProject) override
EnvelopeHandle(const EnvelopeHandle &)=delete
void Enter(bool forward, AudacityProject *) override
Result Release(const TrackPanelMouseEvent &event, AudacityProject *pProject, wxWindow *pParent) override
Piecewise linear or piecewise exponential function from double to double.
Definition: Envelope.h:72
double GetValue(double t, double sampleDur=0) const
Get envelope value at time t.
Definition: Envelope.cpp:823
bool IsAudioActive() const
static ProjectAudioIO & Get(AudacityProject &project)
void PushState(const TranslatableString &desc, const TranslatableString &shortDesc)
static ProjectHistory & Get(AudacityProject &project)
A kind of Track used to 'warp time'.
Definition: TimeTrack.h:24
bool GetDisplayLog() const
Definition: TimeTrack.h:96
double GetRangeLower() const
Definition: TimeTrack.cpp:122
double GetRangeUpper() const
Definition: TimeTrack.cpp:127
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:225
static auto Channels(TrackType *pTrack) -> TrackIterRange< TrackType >
Definition: Track.h:1539
Result mChangeHighlight
Definition: UIHandle.h:139
unsigned Result
Definition: UIHandle.h:38
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
A Track that contains audio waveform data.
Definition: WaveTrack.h:57
void GetDisplayBounds(float *min, float *max) const
Definition: WaveTrack.cpp:340
const WaveformSettings & GetWaveformSettings() const
Definition: WaveTrack.cpp:847
bool isLinear() const
Namespace containing an enum 'what to do on a refresh?'.
Definition: RefreshCode.h:16
void GetTimeTrackData(const AudacityProject &project, const TimeTrack &tt, double &dBRange, bool &dB, float &zoomMin, float &zoomMax)
std::shared_ptr< TrackPanelCell > pCell