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