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 "ChannelView.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 std::weak_ptr<const Channel> wChannel
35) : mEnvelope{ pEnvelope }
36 , mwChannel{ move(wChannel) }
37{
38}
39
41{
42#ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
44#endif
45}
46
48{}
49
50std::shared_ptr<const Channel> EnvelopeHandle::FindChannel() const
51{
52 return mwChannel.lock();
53}
54
55UIHandlePtr EnvelopeHandle::HitAnywhere(std::weak_ptr<EnvelopeHandle> &holder,
56 Envelope *envelope, std::weak_ptr<const Channel> wChannel, bool timeTrack)
57{
58 auto result = AssignUIHandlePtr(holder,
59 std::make_shared<EnvelopeHandle>(envelope, move(wChannel)));
60 result->mTimeTrack = timeTrack;
61 return result;
62}
63
64namespace {
66 (const AudacityProject &project, const TimeTrack &tt,
67 double &dBRange, bool &dB, float &zoomMin, float &zoomMax)
68 {
69 const auto &viewInfo = ViewInfo::Get( project );
70 dBRange = DecibelScaleCutoff.Read();
71 dB = tt.GetDisplayLog();
72 zoomMin = tt.GetRangeLower(), zoomMax = tt.GetRangeUpper();
73 if (dB) {
74 // MB: silly way to undo the work of GetWaveYPos while still getting a logarithmic scale
75 zoomMin = LINEAR_TO_DB(std::max(1.0e-7, double(zoomMin))) / dBRange + 1.0;
76 zoomMax = LINEAR_TO_DB(std::max(1.0e-7, double(zoomMax))) / dBRange + 1.0;
77 }
78 }
79}
80
82 std::weak_ptr<EnvelopeHandle> &holder,
83 const wxMouseState &state, const wxRect &rect,
84 const AudacityProject *pProject, const std::shared_ptr<TimeTrack> &tt)
85{
86 auto envelope = tt->GetEnvelope();
87 if (!envelope)
88 return {};
89 bool dB;
90 double dBRange;
91 float zoomMin, zoomMax;
92 GetTimeTrackData( *pProject, *tt, dBRange, dB, zoomMin, zoomMax);
93 return EnvelopeHandle::HitEnvelope(holder, state, rect, pProject, envelope,
94 std::dynamic_pointer_cast<const Channel>(tt),
95 zoomMin, zoomMax, dB, dBRange, true);
96}
97
99(std::weak_ptr<EnvelopeHandle> &holder,
100 const wxMouseState &state, const wxRect &rect,
101 const AudacityProject *pProject, const std::shared_ptr<WaveTrack> &wt)
102{
105 auto &viewInfo = ViewInfo::Get(*pProject);
106 auto time = viewInfo.PositionToTime(state.m_x, rect.GetX());
107 Envelope *const envelope = wt->GetEnvelopeAtTime(time);
108
109 if (!envelope)
110 return {};
111
112 // Get envelope point, range 0.0 to 1.0
113 const bool dB = !WaveformSettings::Get(*wt).isLinear();
114
115 float zoomMin, zoomMax;
116 auto &cache = WaveformScale::Get(*wt);
117 cache.GetDisplayBounds(zoomMin, zoomMax);
118
119 const float dBRange = WaveformSettings::Get(*wt).dBRange;
120
121 return EnvelopeHandle::HitEnvelope(holder, state, rect, pProject, envelope,
122 std::dynamic_pointer_cast<const Channel>(wt),
123 zoomMin, zoomMax, dB, dBRange, false);
124}
125
126UIHandlePtr EnvelopeHandle::HitEnvelope(std::weak_ptr<EnvelopeHandle> &holder,
127 const wxMouseState &state, const wxRect &rect,
128 const AudacityProject *pProject,
129 Envelope *envelope, std::weak_ptr<const Channel> wChannel,
130 float zoomMin, float zoomMax,
131 bool dB, float dBRange, bool timeTrack)
132{
133 const auto &viewInfo = ViewInfo::Get( *pProject );
134
135 const double envValue =
136 envelope->GetValue(viewInfo.PositionToTime(state.m_x, rect.x));
137
138 // Get y position of envelope point.
139 int yValue = GetWaveYPos(envValue,
140 zoomMin, zoomMax,
141 rect.height, dB, true, dBRange, false) + rect.y;
142
143 // Get y position of center line
144 int ctr = GetWaveYPos(0.0,
145 zoomMin, zoomMax,
146 rect.height, dB, true, dBRange, false) + rect.y;
147
148 // Get y distance of mouse from center line (in pixels).
149 int yMouse = abs(ctr - state.m_y);
150 // Get y distance of envelope from center line (in pixels)
151 yValue = abs(ctr - yValue);
152
153 // JKC: It happens that the envelope is actually drawn offset from its
154 // 'true' position (it is 3 pixels wide). yMisalign is really a fudge
155 // factor to allow us to hit it exactly, but I wouldn't dream of
156 // calling it yFudgeFactor :)
157 const int yMisalign = 2;
158 // Perhaps yTolerance should be put into preferences?
159 const int yTolerance = 5; // how far from envelope we may be and count as a hit.
160 int distance;
161
162 // For amplification using the envelope we introduced the idea of contours.
163 // The contours have the same shape as the envelope, which may be partially off-screen.
164 // The contours are closer in to the center line.
165 // Beware very short rectangles! Make this at least 1
166 int ContourSpacing = std::max(1,
167 static_cast<int>(rect.height / (2 * (zoomMax - zoomMin))));
168 const int MaxContours = 2;
169
170 // Adding ContourSpacing/2 selects a region either side of the contour.
171 int yDisplace = yValue - yMisalign - yMouse + ContourSpacing / 2;
172 if (yDisplace > (MaxContours * ContourSpacing))
173 return {};
174 // Subtracting the ContourSpacing/2 we added earlier ensures distance is centred on the contour.
175 distance = abs((yDisplace % ContourSpacing) - ContourSpacing / 2);
176 if (distance >= yTolerance)
177 return {};
178
179 return HitAnywhere(holder, envelope, move(wChannel), timeTrack);
180}
181
183(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
184{
185 using namespace RefreshCode;
186 const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
187 if ( unsafe )
188 return Cancelled;
189
190 const wxMouseEvent &event = evt.event;
191 const auto &viewInfo = ViewInfo::Get( *pProject );
192 const auto pView = std::static_pointer_cast<ChannelView>(evt.pCell);
193 const auto pTrack = pView ? pView->FindTrack().get() : nullptr;
194
195 mpEnvelopeEditor.reset();
196
197 unsigned result = Cancelled;
198 if (pTrack)
199 result = pTrack->TypeSwitch< decltype(RefreshNone) >(
200 [&](WaveTrack &wt) {
201 if (!mEnvelope)
202 return Cancelled;
203
205 auto &cache = WaveformScale::Get(wt);
206 cache.GetDisplayBounds(mLower, mUpper);
208 mpEnvelopeEditor = std::make_unique<EnvelopeEditor>(*mEnvelope, true);
209 return RefreshNone;
210 },
211 [&](TimeTrack &tt) {
212 if (!mEnvelope)
213 return Cancelled;
214 GetTimeTrackData( *pProject, tt, mdBRange, mLog, mLower, mUpper);
215 mpEnvelopeEditor = std::make_unique<EnvelopeEditor>(*mEnvelope, false);
216
217 return RefreshNone;
218 },
219 [](Track &) {
220 return Cancelled;
221 }
222 );
223
224 if (result & Cancelled)
225 return result;
226
227 mRect = evt.rect;
228
229 const bool needUpdate = ForwardEventToEnvelopes(event, viewInfo);
230 return needUpdate ? RefreshCell : RefreshNone;
231}
232
234(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
235{
236 using namespace RefreshCode;
237 const wxMouseEvent &event = evt.event;
238 const auto &viewInfo = ViewInfo::Get( *pProject );
239 const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
240 if (unsafe) {
241 this->Cancel(pProject);
242 return RefreshCell | Cancelled;
243 }
244
245 const bool needUpdate = ForwardEventToEnvelopes(event, viewInfo);
246 return needUpdate ? RefreshCell : RefreshNone;
247}
248
250(const TrackPanelMouseState &, AudacityProject *pProject)
251{
252 const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
253 static auto disabledCursor =
254 ::MakeCursor(wxCURSOR_NO_ENTRY, DisabledCursorXpm, 16, 16);
255 static auto envelopeCursor =
256 ::MakeCursor(wxCURSOR_ARROW, EnvCursorXpm, 16, 16);
257
258 auto message = mTimeTrack
259 ? XO("Click and drag to warp playback time")
260 : XO("Click and drag to edit the amplitude envelope");
261
262 return {
263 message,
264 (unsafe
265 ? &*disabledCursor
266 : &*envelopeCursor)
267 };
268}
269
271(const TrackPanelMouseEvent &evt, AudacityProject *pProject,
272 wxWindow *)
273{
274 const wxMouseEvent &event = evt.event;
275 const auto &viewInfo = ViewInfo::Get( *pProject );
276 const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
277 if (unsafe)
278 return this->Cancel(pProject);
279
280 const bool needUpdate = ForwardEventToEnvelopes(event, viewInfo);
281
282 ProjectHistory::Get( *pProject ).PushState(
283 /* i18n-hint: (verb) Audacity has just adjusted the envelope .*/
284 XO("Adjusted envelope."),
285 /* i18n-hint: The envelope is a curve that controls the audio loudness.*/
286 XO("Envelope")
287 );
288
289 mpEnvelopeEditor.reset();
290
291 using namespace RefreshCode;
292 return needUpdate ? RefreshCell : RefreshNone;
293}
294
296{
297 ProjectHistory::Get( *pProject ).RollbackState();
298 mpEnvelopeEditor.reset();
300}
301
303 (const wxMouseEvent &event, const ViewInfo &viewInfo)
304{
308
309 // AS: I'm not sure why we can't let the Envelope take care of
310 // redrawing itself. ?
311 return mpEnvelopeEditor && mpEnvelopeEditor->MouseEvent(
312 event, mRect, viewInfo, mLog, mdBRange, mLower, mUpper);
313}
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
const auto project
int GetWaveYPos(float value, float min, float max, int height, bool dB, bool outer, float dBr, bool clip)
Definition: TrackArt.cpp:59
std::unique_ptr< wxCursor > MakeCursor(int WXUNUSED(CursorId), const char *const pXpm[36], int HotX, int HotY)
Definition: TrackPanel.cpp:188
std::shared_ptr< Subclass > AssignUIHandlePtr(std::weak_ptr< Subclass > &holder, const std::shared_ptr< Subclass > &pNew)
Definition: UIHandle.h:159
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
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)
std::unique_ptr< EnvelopeEditor > mpEnvelopeEditor
Result Drag(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
Result Click(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
std::weak_ptr< const Channel > mwChannel
HitTestPreview Preview(const TrackPanelMouseState &state, AudacityProject *pProject) override
std::shared_ptr< const Channel > FindChannel() const 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
static UIHandlePtr HitEnvelope(std::weak_ptr< EnvelopeHandle > &holder, const wxMouseState &state, const wxRect &rect, const AudacityProject *pProject, Envelope *envelope, std::weak_ptr< const Channel > wChannel, float zoomMin, float zoomMax, bool dB, float dBRange, bool timeTrack)
static UIHandlePtr HitAnywhere(std::weak_ptr< EnvelopeHandle > &holder, Envelope *envelope, std::weak_ptr< const Channel > wChannel, bool timeTrack)
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:837
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:205
A kind of Track used to 'warp time'.
Definition: TimeTrack.h:24
bool GetDisplayLog() const
Definition: TimeTrack.h:94
double GetRangeLower() const
Definition: TimeTrack.cpp:112
double GetRangeUpper() const
Definition: TimeTrack.cpp:117
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:122
Result mChangeHighlight
Definition: UIHandle.h:147
unsigned Result
Definition: UIHandle.h:39
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:235
A Track that contains audio waveform data.
Definition: WaveTrack.h:222
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