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 "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 "prefs/WaveformScale.h"
30#include "WaveTrack.h"
31#include "../../../images/Cursors.h"
32
33#include <wx/event.h>
34
36 std::weak_ptr<const Channel> wChannel
37) : mEnvelope{ pEnvelope }
38 , mwChannel{ move(wChannel) }
39{
40}
41
43{
44#ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
46#endif
47}
48
50{}
51
52std::shared_ptr<const Track> EnvelopeHandle::FindTrack() const
53{
54 return TrackFromChannel(mwChannel.lock());
55}
56
57UIHandlePtr EnvelopeHandle::HitAnywhere(std::weak_ptr<EnvelopeHandle> &holder,
58 Envelope *envelope, std::weak_ptr<const Channel> wChannel, bool timeTrack)
59{
60 auto result = AssignUIHandlePtr(holder,
61 std::make_shared<EnvelopeHandle>(envelope, move(wChannel)));
62 result->mTimeTrack = timeTrack;
63 return result;
64}
65
66namespace {
68 (const AudacityProject &project, const TimeTrack &tt,
69 double &dBRange, bool &dB, float &zoomMin, float &zoomMax)
70 {
71 const auto &viewInfo = ViewInfo::Get( project );
72 dBRange = DecibelScaleCutoff.Read();
73 dB = tt.GetDisplayLog();
74 zoomMin = tt.GetRangeLower(), zoomMax = tt.GetRangeUpper();
75 if (dB) {
76 // MB: silly way to undo the work of GetWaveYPos while still getting a logarithmic scale
77 zoomMin = LINEAR_TO_DB(std::max(1.0e-7, double(zoomMin))) / dBRange + 1.0;
78 zoomMax = LINEAR_TO_DB(std::max(1.0e-7, double(zoomMax))) / dBRange + 1.0;
79 }
80 }
81}
82
84 std::weak_ptr<EnvelopeHandle> &holder,
85 const wxMouseState &state, const wxRect &rect,
86 const AudacityProject *pProject, const std::shared_ptr<TimeTrack> &tt)
87{
88 auto envelope = tt->GetEnvelope();
89 if (!envelope)
90 return {};
91 bool dB;
92 double dBRange;
93 float zoomMin, zoomMax;
94 GetTimeTrackData( *pProject, *tt, dBRange, dB, zoomMin, zoomMax);
95 return EnvelopeHandle::HitEnvelope(holder, state, rect, pProject, envelope,
96 std::dynamic_pointer_cast<const Channel>(tt),
97 zoomMin, zoomMax, dB, dBRange, true);
98}
99
101(std::weak_ptr<EnvelopeHandle> &holder,
102 const wxMouseState &state, const wxRect &rect,
103 const AudacityProject *pProject, const std::shared_ptr<WaveChannel> &wc)
104{
107 auto &viewInfo = ViewInfo::Get(*pProject);
108 auto time = viewInfo.PositionToTime(state.m_x, rect.GetX());
109 const auto envelope = WaveChannelUtilities::GetEnvelopeAtTime(*wc, time);
110 if (!envelope)
111 return {};
112
113 // Get envelope point, range 0.0 to 1.0
114 const bool dB = !WaveformSettings::Get(*wc).isLinear();
115
116 float zoomMin, zoomMax;
117 auto &cache = WaveformScale::Get(*wc);
118 cache.GetDisplayBounds(zoomMin, zoomMax);
119
120 const float dBRange = WaveformSettings::Get(*wc).dBRange;
121
122 return EnvelopeHandle::HitEnvelope(holder, state, rect, pProject, envelope,
123 wc, 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 pChannel = pView ? pView->FindChannel().get() : nullptr;
194
195 mpEnvelopeEditor.reset();
196
197 unsigned result = Cancelled;
198 if (const auto pWt = dynamic_cast<WaveChannel*>(pChannel)) {
199 auto &wt = *pWt;
200 if (!mEnvelope)
201 result = Cancelled;
202 else {
204 auto &cache = WaveformScale::Get(wt);
205 cache.GetDisplayBounds(mLower, mUpper);
207 mpEnvelopeEditor = std::make_unique<EnvelopeEditor>(*mEnvelope, true);
208 result = RefreshNone;
209 }
210 }
211 else if (const auto pTt = dynamic_cast<TimeTrack*>(pChannel)) {
212 auto &tt = *pTt;
213 if (!mEnvelope)
214 result = Cancelled;
215 else {
216 GetTimeTrackData( *pProject, tt, mdBRange, mLog, mLower, mUpper);
217 mpEnvelopeEditor = std::make_unique<EnvelopeEditor>(*mEnvelope, false);
218
219 result = RefreshNone;
220 }
221 }
222
223 if (result & Cancelled)
224 return result;
225
226 mRect = evt.rect;
227
228 const bool needUpdate = ForwardEventToEnvelopes(event, viewInfo);
229 return needUpdate ? RefreshCell : RefreshNone;
230}
231
233(const TrackPanelMouseEvent &evt, AudacityProject *pProject)
234{
235 using namespace RefreshCode;
236 const wxMouseEvent &event = evt.event;
237 const auto &viewInfo = ViewInfo::Get( *pProject );
238 const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
239 if (unsafe) {
240 this->Cancel(pProject);
241 return RefreshCell | Cancelled;
242 }
243
244 const bool needUpdate = ForwardEventToEnvelopes(event, viewInfo);
245 return needUpdate ? RefreshCell : RefreshNone;
246}
247
249(const TrackPanelMouseState &, AudacityProject *pProject)
250{
251 const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
252 static auto disabledCursor =
253 ::MakeCursor(wxCURSOR_NO_ENTRY, DisabledCursorXpm, 16, 16);
254 static auto envelopeCursor =
255 ::MakeCursor(wxCURSOR_ARROW, EnvCursorXpm, 16, 16);
256
257 auto message = mTimeTrack
258 ? XO("Click and drag to warp playback time")
259 : XO("Click and drag to edit the amplitude envelope");
260
261 return {
262 message,
263 (unsafe
264 ? &*disabledCursor
265 : &*envelopeCursor)
266 };
267}
268
270(const TrackPanelMouseEvent &evt, AudacityProject *pProject,
271 wxWindow *)
272{
273 const wxMouseEvent &event = evt.event;
274 const auto &viewInfo = ViewInfo::Get( *pProject );
275 const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
276 if (unsafe)
277 return this->Cancel(pProject);
278
279 const bool needUpdate = ForwardEventToEnvelopes(event, viewInfo);
280
281 ProjectHistory::Get( *pProject ).PushState(
282 /* i18n-hint: (verb) Audacity has just adjusted the envelope .*/
283 XO("Adjusted envelope."),
284 /* i18n-hint: The envelope is a curve that controls the audio loudness.*/
285 XO("Envelope")
286 );
287
288 mpEnvelopeEditor.reset();
289
290 using namespace RefreshCode;
291 return needUpdate ? RefreshCell : RefreshNone;
292}
293
295{
296 ProjectHistory::Get( *pProject ).RollbackState();
297 mpEnvelopeEditor.reset();
299}
300
302 (const wxMouseEvent &event, const ViewInfo &viewInfo)
303{
307
308 // AS: I'm not sure why we can't let the Envelope take care of
309 // redrawing itself. ?
310 return mpEnvelopeEditor && mpEnvelopeEditor->MouseEvent(
311 event, mRect, viewInfo, mLog, mdBRange, mLower, mUpper);
312}
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:339
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:880
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:95
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