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 = !WaveformSettings::Get(*wt).isLinear();
106
107 float zoomMin, zoomMax;
108 auto &cache = WaveformScale::Get(*wt);
109 cache.GetDisplayBounds(zoomMin, zoomMax);
110
111 const float dBRange = WaveformSettings::Get(*wt).dBRange;
112
114 (holder, state, rect, pProject, envelope, zoomMin, zoomMax, dB, dBRange, false);
115}
116
118(std::weak_ptr<EnvelopeHandle> &holder,
119 const wxMouseState &state, const wxRect &rect, const AudacityProject *pProject,
120 Envelope *envelope, float zoomMin, float zoomMax,
121 bool dB, float dBRange, bool timeTrack)
122{
123 const auto &viewInfo = ViewInfo::Get( *pProject );
124
125 const double envValue =
126 envelope->GetValue(viewInfo.PositionToTime(state.m_x, rect.x));
127
128 // Get y position of envelope point.
129 int yValue = GetWaveYPos(envValue,
130 zoomMin, zoomMax,
131 rect.height, dB, true, dBRange, false) + rect.y;
132
133 // Get y position of center line
134 int ctr = GetWaveYPos(0.0,
135 zoomMin, zoomMax,
136 rect.height, dB, true, dBRange, false) + rect.y;
137
138 // Get y distance of mouse from center line (in pixels).
139 int yMouse = abs(ctr - state.m_y);
140 // Get y distance of envelope from center line (in pixels)
141 yValue = abs(ctr - yValue);
142
143 // JKC: It happens that the envelope is actually drawn offset from its
144 // 'true' position (it is 3 pixels wide). yMisalign is really a fudge
145 // factor to allow us to hit it exactly, but I wouldn't dream of
146 // calling it yFudgeFactor :)
147 const int yMisalign = 2;
148 // Perhaps yTolerance should be put into preferences?
149 const int yTolerance = 5; // how far from envelope we may be and count as a hit.
150 int distance;
151
152 // For amplification using the envelope we introduced the idea of contours.
153 // The contours have the same shape as the envelope, which may be partially off-screen.
154 // The contours are closer in to the center line.
155 // Beware very short rectangles! Make this at least 1
156 int ContourSpacing = std::max(1,
157 static_cast<int>(rect.height / (2 * (zoomMax - zoomMin))));
158 const int MaxContours = 2;
159
160 // Adding ContourSpacing/2 selects a region either side of the contour.
161 int yDisplace = yValue - yMisalign - yMouse + ContourSpacing / 2;
162 if (yDisplace > (MaxContours * ContourSpacing))
163 return {};
164 // Subtracting the ContourSpacing/2 we added earlier ensures distance is centred on the contour.
165 distance = abs((yDisplace % ContourSpacing) - ContourSpacing / 2);
166 if (distance >= yTolerance)
167 return {};
168
169 return HitAnywhere(holder, envelope, timeTrack);
170}
171
173(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
174{
175 using namespace RefreshCode;
176 const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
177 if ( unsafe )
178 return Cancelled;
179
180 const wxMouseEvent &event = evt.event;
181 const auto &viewInfo = ViewInfo::Get( *pProject );
182 const auto pView = std::static_pointer_cast<TrackView>(evt.pCell);
183 const auto pTrack = pView ? pView->FindTrack().get() : nullptr;
184
185 mEnvelopeEditors.clear();
186
187 unsigned result = Cancelled;
188 if (pTrack)
189 result = pTrack->TypeSwitch< decltype(RefreshNone) >(
190 [&](WaveTrack *wt) {
191 if (!mEnvelope)
192 return Cancelled;
193
195 auto &cache = WaveformScale::Get(*wt);
196 cache.GetDisplayBounds(mLower, mUpper);
198 auto channels = TrackList::Channels( wt );
199 for ( auto channel : channels ) {
200 if (channel == wt)
201 mEnvelopeEditors.push_back(
202 std::make_unique< EnvelopeEditor >( *mEnvelope, true ) );
203 else {
204 auto time =
205 viewInfo.PositionToTime(event.GetX(), evt.rect.GetX());
206 auto e2 = channel->GetEnvelopeAtTime(time);
207 if (e2)
208 mEnvelopeEditors.push_back(
209 std::make_unique< EnvelopeEditor >( *e2, true ) );
210 else {
211 // There isn't necessarily an envelope there; no guarantee a
212 // linked track has the same WaveClip structure...
213 }
214 }
215 }
216
217 return RefreshNone;
218 },
219 [&](TimeTrack *tt) {
220 if (!mEnvelope)
221 return Cancelled;
222 GetTimeTrackData( *pProject, *tt, mdBRange, mLog, mLower, mUpper);
223 mEnvelopeEditors.push_back(
224 std::make_unique< EnvelopeEditor >( *mEnvelope, false )
225 );
226
227 return RefreshNone;
228 },
229 [](Track *) {
230 return Cancelled;
231 }
232 );
233
234 if (result & Cancelled)
235 return result;
236
237 mRect = evt.rect;
238
239 const bool needUpdate = ForwardEventToEnvelopes(event, viewInfo);
240 return needUpdate ? RefreshCell : RefreshNone;
241}
242
244(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
245{
246 using namespace RefreshCode;
247 const wxMouseEvent &event = evt.event;
248 const auto &viewInfo = ViewInfo::Get( *pProject );
249 const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
250 if (unsafe) {
251 this->Cancel(pProject);
252 return RefreshCell | Cancelled;
253 }
254
255 const bool needUpdate = ForwardEventToEnvelopes(event, viewInfo);
256 return needUpdate ? RefreshCell : RefreshNone;
257}
258
260(const TrackPanelMouseState &, AudacityProject *pProject)
261{
262 const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
263 static auto disabledCursor =
264 ::MakeCursor(wxCURSOR_NO_ENTRY, DisabledCursorXpm, 16, 16);
265 static auto envelopeCursor =
266 ::MakeCursor(wxCURSOR_ARROW, EnvCursorXpm, 16, 16);
267
268 auto message = mTimeTrack
269 ? XO("Click and drag to warp playback time")
270 : XO("Click and drag to edit the amplitude envelope");
271
272 return {
273 message,
274 (unsafe
275 ? &*disabledCursor
276 : &*envelopeCursor)
277 };
278}
279
281(const TrackPanelMouseEvent &evt, AudacityProject *pProject,
282 wxWindow *)
283{
284 const wxMouseEvent &event = evt.event;
285 const auto &viewInfo = ViewInfo::Get( *pProject );
286 const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
287 if (unsafe)
288 return this->Cancel(pProject);
289
290 const bool needUpdate = ForwardEventToEnvelopes(event, viewInfo);
291
292 ProjectHistory::Get( *pProject ).PushState(
293 /* i18n-hint: (verb) Audacity has just adjusted the envelope .*/
294 XO("Adjusted envelope."),
295 /* i18n-hint: The envelope is a curve that controls the audio loudness.*/
296 XO("Envelope")
297 );
298
299 mEnvelopeEditors.clear();
300
301 using namespace RefreshCode;
302 return needUpdate ? RefreshCell : RefreshNone;
303}
304
306{
307 ProjectHistory::Get( *pProject ).RollbackState();
308 mEnvelopeEditors.clear();
310}
311
313 (const wxMouseEvent &event, const ViewInfo &viewInfo)
314{
318
319 // AS: I'm not sure why we can't let the Envelope take care of
320 // redrawing itself. ?
321 bool needUpdate = false;
322 for (const auto &pEditor : mEnvelopeEditors) {
323 needUpdate =
324 pEditor->MouseEvent(
325 event, mRect, viewInfo, mLog, mdBRange, mLower, mUpper)
326 || needUpdate;
327 }
328
329 return needUpdate;
330}
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:562
int GetWaveYPos(float value, float min, float max, int height, bool dB, bool outer, float dBr, bool clip)
Definition: TrackArt.cpp:46
std::unique_ptr< wxCursor > MakeCursor(int WXUNUSED(CursorId), const char *const pXpm[36], int HotX, int HotY)
Definition: TrackPanel.cpp:186
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:22
bool GetDisplayLog() const
Definition: TimeTrack.h:94
double GetRangeLower() const
Definition: TimeTrack.cpp:102
double GetRangeUpper() const
Definition: TimeTrack.cpp:107
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:161
static auto Channels(TrackType *pTrack) -> TrackIterRange< TrackType >
Definition: Track.h:1406
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:51
static WaveformScale & Get(const WaveTrack &track)
Mutative access to attachment even if the track argument is const.
bool isLinear() const
static WaveformSettings & Get(const WaveTrack &track)
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