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 "Decibels.h"
18#include "../../EnvelopeEditor.h"
19#include "../../HitTestResult.h"
20#include "../../prefs/WaveformSettings.h"
21#include "ProjectAudioIO.h"
22#include "ProjectHistory.h"
23#include "../../RefreshCode.h"
24#include "../../TimeTrack.h"
25#include "../../TrackArt.h"
26#include "../../TrackPanelMouseEvent.h"
27#include "ViewInfo.h"
28#include "../../WaveTrack.h"
29#include "../../../images/Cursors.h"
30
31#include <wx/event.h>
32
34 : mEnvelope{ pEnvelope }
35{
36}
37
39{
40#ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
42#endif
43}
44
46{}
47
49(std::weak_ptr<EnvelopeHandle> &holder, Envelope *envelope, bool timeTrack)
50{
51 auto result = AssignUIHandlePtr(holder, std::make_shared<EnvelopeHandle>(envelope));
52 result->mTimeTrack = timeTrack;
53 return result;
54}
55
56namespace {
58 (const AudacityProject &project, const TimeTrack &tt,
59 double &dBRange, bool &dB, float &zoomMin, float &zoomMax)
60 {
61 const auto &viewInfo = ViewInfo::Get( project );
62 dBRange = DecibelScaleCutoff.Read();
63 dB = tt.GetDisplayLog();
64 zoomMin = tt.GetRangeLower(), zoomMax = tt.GetRangeUpper();
65 if (dB) {
66 // MB: silly way to undo the work of GetWaveYPos while still getting a logarithmic scale
67 zoomMin = LINEAR_TO_DB(std::max(1.0e-7, double(zoomMin))) / dBRange + 1.0;
68 zoomMax = LINEAR_TO_DB(std::max(1.0e-7, double(zoomMax))) / dBRange + 1.0;
69 }
70 }
71}
72
74(std::weak_ptr<EnvelopeHandle> &holder,
75 const wxMouseState &state, const wxRect &rect,
76 const AudacityProject *pProject, const std::shared_ptr<TimeTrack> &tt)
77{
78 auto envelope = tt->GetEnvelope();
79 if (!envelope)
80 return {};
81 bool dB;
82 double dBRange;
83 float zoomMin, zoomMax;
84 GetTimeTrackData( *pProject, *tt, dBRange, dB, zoomMin, zoomMax);
86 (holder, state, rect, pProject, envelope, zoomMin, zoomMax, dB, dBRange,
87 true);
88}
89
91(std::weak_ptr<EnvelopeHandle> &holder,
92 const wxMouseState &state, const wxRect &rect,
93 const AudacityProject *pProject, const std::shared_ptr<WaveTrack> &wt)
94{
97 auto &viewInfo = ViewInfo::Get(*pProject);
98 auto time = viewInfo.PositionToTime(state.m_x, rect.GetX());
99 Envelope *const envelope = wt->GetEnvelopeAtTime(time);
100
101 if (!envelope)
102 return {};
103
104 // Get envelope point, range 0.0 to 1.0
105 const bool dB = !wt->GetWaveformSettings().isLinear();
106
107 float zoomMin, zoomMax;
108 wt->GetDisplayBounds(&zoomMin, &zoomMax);
109
110 const float dBRange = wt->GetWaveformSettings().dBRange;
111
113 (holder, state, rect, pProject, envelope, zoomMin, zoomMax, dB, dBRange, false);
114}
115
117(std::weak_ptr<EnvelopeHandle> &holder,
118 const wxMouseState &state, const wxRect &rect, const AudacityProject *pProject,
119 Envelope *envelope, float zoomMin, float zoomMax,
120 bool dB, float dBRange, bool timeTrack)
121{
122 const auto &viewInfo = ViewInfo::Get( *pProject );
123
124 const double envValue =
125 envelope->GetValue(viewInfo.PositionToTime(state.m_x, rect.x));
126
127 // Get y position of envelope point.
128 int yValue = GetWaveYPos(envValue,
129 zoomMin, zoomMax,
130 rect.height, dB, true, dBRange, false) + rect.y;
131
132 // Get y position of center line
133 int ctr = GetWaveYPos(0.0,
134 zoomMin, zoomMax,
135 rect.height, dB, true, dBRange, false) + rect.y;
136
137 // Get y distance of mouse from center line (in pixels).
138 int yMouse = abs(ctr - state.m_y);
139 // Get y distance of envelope from center line (in pixels)
140 yValue = abs(ctr - yValue);
141
142 // JKC: It happens that the envelope is actually drawn offset from its
143 // 'true' position (it is 3 pixels wide). yMisalign is really a fudge
144 // factor to allow us to hit it exactly, but I wouldn't dream of
145 // calling it yFudgeFactor :)
146 const int yMisalign = 2;
147 // Perhaps yTolerance should be put into preferences?
148 const int yTolerance = 5; // how far from envelope we may be and count as a hit.
149 int distance;
150
151 // For amplification using the envelope we introduced the idea of contours.
152 // The contours have the same shape as the envelope, which may be partially off-screen.
153 // The contours are closer in to the center line.
154 // Beware very short rectangles! Make this at least 1
155 int ContourSpacing = std::max(1,
156 static_cast<int>(rect.height / (2 * (zoomMax - zoomMin))));
157 const int MaxContours = 2;
158
159 // Adding ContourSpacing/2 selects a region either side of the contour.
160 int yDisplace = yValue - yMisalign - yMouse + ContourSpacing / 2;
161 if (yDisplace > (MaxContours * ContourSpacing))
162 return {};
163 // Subtracting the ContourSpacing/2 we added earlier ensures distance is centred on the contour.
164 distance = abs((yDisplace % ContourSpacing) - ContourSpacing / 2);
165 if (distance >= yTolerance)
166 return {};
167
168 return HitAnywhere(holder, envelope, timeTrack);
169}
170
172(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
173{
174 using namespace RefreshCode;
175 const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
176 if ( unsafe )
177 return Cancelled;
178
179 const wxMouseEvent &event = evt.event;
180 const auto &viewInfo = ViewInfo::Get( *pProject );
181 const auto pView = std::static_pointer_cast<TrackView>(evt.pCell);
182 const auto pTrack = pView ? pView->FindTrack().get() : nullptr;
183
184 mEnvelopeEditors.clear();
185
186 unsigned result = Cancelled;
187 if (pTrack)
188 result = pTrack->TypeSwitch< decltype(RefreshNone) >(
189 [&](WaveTrack *wt) {
190 if (!mEnvelope)
191 return Cancelled;
192
196 auto channels = TrackList::Channels( wt );
197 for ( auto channel : channels ) {
198 if (channel == wt)
199 mEnvelopeEditors.push_back(
200 std::make_unique< EnvelopeEditor >( *mEnvelope, true ) );
201 else {
202 auto time =
203 viewInfo.PositionToTime(event.GetX(), evt.rect.GetX());
204 auto e2 = channel->GetEnvelopeAtTime(time);
205 if (e2)
206 mEnvelopeEditors.push_back(
207 std::make_unique< EnvelopeEditor >( *e2, true ) );
208 else {
209 // There isn't necessarily an envelope there; no guarantee a
210 // linked track has the same WaveClip structure...
211 }
212 }
213 }
214
215 return RefreshNone;
216 },
217 [&](TimeTrack *tt) {
218 if (!mEnvelope)
219 return Cancelled;
220 GetTimeTrackData( *pProject, *tt, mdBRange, mLog, mLower, mUpper);
221 mEnvelopeEditors.push_back(
222 std::make_unique< EnvelopeEditor >( *mEnvelope, false )
223 );
224
225 return RefreshNone;
226 },
227 [](Track *) {
228 return Cancelled;
229 }
230 );
231
232 if (result & Cancelled)
233 return result;
234
235 mRect = evt.rect;
236
237 const bool needUpdate = ForwardEventToEnvelopes(event, viewInfo);
238 return needUpdate ? RefreshCell : RefreshNone;
239}
240
242(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
243{
244 using namespace RefreshCode;
245 const wxMouseEvent &event = evt.event;
246 const auto &viewInfo = ViewInfo::Get( *pProject );
247 const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
248 if (unsafe) {
249 this->Cancel(pProject);
250 return RefreshCell | Cancelled;
251 }
252
253 const bool needUpdate = ForwardEventToEnvelopes(event, viewInfo);
254 return needUpdate ? RefreshCell : RefreshNone;
255}
256
258(const TrackPanelMouseState &, AudacityProject *pProject)
259{
260 const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
261 static auto disabledCursor =
262 ::MakeCursor(wxCURSOR_NO_ENTRY, DisabledCursorXpm, 16, 16);
263 static auto envelopeCursor =
264 ::MakeCursor(wxCURSOR_ARROW, EnvCursorXpm, 16, 16);
265
266 auto message = mTimeTrack
267 ? XO("Click and drag to warp playback time")
268 : XO("Click and drag to edit the amplitude envelope");
269
270 return {
271 message,
272 (unsafe
273 ? &*disabledCursor
274 : &*envelopeCursor)
275 };
276}
277
279(const TrackPanelMouseEvent &evt, AudacityProject *pProject,
280 wxWindow *)
281{
282 const wxMouseEvent &event = evt.event;
283 const auto &viewInfo = ViewInfo::Get( *pProject );
284 const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
285 if (unsafe)
286 return this->Cancel(pProject);
287
288 const bool needUpdate = ForwardEventToEnvelopes(event, viewInfo);
289
290 ProjectHistory::Get( *pProject ).PushState(
291 /* i18n-hint: (verb) Audacity has just adjusted the envelope .*/
292 XO("Adjusted envelope."),
293 /* i18n-hint: The envelope is a curve that controls the audio loudness.*/
294 XO("Envelope")
295 );
296
297 mEnvelopeEditors.clear();
298
299 using namespace RefreshCode;
300 return needUpdate ? RefreshCell : RefreshNone;
301}
302
304{
305 ProjectHistory::Get( *pProject ).RollbackState();
306 mEnvelopeEditors.clear();
308}
309
311 (const wxMouseEvent &event, const ViewInfo &viewInfo)
312{
316
317 // AS: I'm not sure why we can't let the Envelope take care of
318 // redrawing itself. ?
319 bool needUpdate = false;
320 for (const auto &pEditor : mEnvelopeEditors) {
321 needUpdate =
322 pEditor->MouseEvent(
323 event, mRect, viewInfo, mLog, mdBRange, mLower, mUpper)
324 || needUpdate;
325 }
326
327 return needUpdate;
328}
std::shared_ptr< UIHandle > UIHandlePtr
Definition: CellularPanel.h:28
IntSetting DecibelScaleCutoff
Negation of this value is the lowest dB level that should be shown in dB scales.
Definition: Decibels.cpp:12
XO("Cut/Copy/Paste")
#define LINEAR_TO_DB(x)
Definition: MemoryX.h:544
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:90
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:828
bool IsAudioActive() const
static ProjectAudioIO & Get(AudacityProject &project)
void PushState(const TranslatableString &desc, const TranslatableString &shortDesc)
static ProjectHistory & Get(AudacityProject &project)
bool Read(T *pVar) const
overload of Read returning a boolean that is true if the value was previously defined *‍/
Definition: Prefs.h:200
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:121
double GetRangeUpper() const
Definition: TimeTrack.cpp:126
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:1541
Result mChangeHighlight
Definition: UIHandle.h:139
unsigned Result
Definition: UIHandle.h:38
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:234
A Track that contains audio waveform data.
Definition: WaveTrack.h:57
void GetDisplayBounds(float *min, float *max) const
Definition: WaveTrack.cpp:336
const WaveformSettings & GetWaveformSettings() const
Definition: WaveTrack.cpp:831
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