Audacity  2.3.1
EnvelopeHandle.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3 Audacity: A Digital Audio Editor
4 
5 EnvelopeHandle.cpp
6 
7 Paul Licameli split from TrackPanel.cpp
8 
9 **********************************************************************/
10 
11 #include "../../Audacity.h"
12 #include "EnvelopeHandle.h"
13 #include "../../Experimental.h"
14 
15 #include "../../MemoryX.h"
16 
17 #include "../../Envelope.h"
18 #include "../../HitTestResult.h"
19 #include "../../prefs/WaveformSettings.h"
20 #include "../../Project.h"
21 #include "../../RefreshCode.h"
22 #include "../../TimeTrack.h"
23 #include "../../TrackArtist.h"
24 #include "../../TrackPanelMouseEvent.h"
25 #include "../../ViewInfo.h"
26 #include "../../WaveTrack.h"
27 #include "../../../images/Cursors.h"
28 
30  : mEnvelope{ pEnvelope }
31 {
32 }
33 
35 {
36 #ifdef EXPERIMENTAL_TRACK_PANEL_HIGHLIGHTING
38 #endif
39 }
40 
42 {}
43 
45 (std::weak_ptr<EnvelopeHandle> & WXUNUSED(holder), Envelope *envelope, bool timeTrack)
46 {
47  auto result = std::make_shared<EnvelopeHandle>( envelope );
48  result->mTimeTrack = timeTrack;
49  return result;
50 }
51 
52 namespace {
53  void GetTimeTrackData
54  (const AudacityProject &project, const TimeTrack &tt,
55  double &dBRange, bool &dB, float &zoomMin, float &zoomMax)
56  {
57  const auto &viewInfo = project.GetViewInfo();
58  dBRange = viewInfo.dBr;
59  dB = tt.GetDisplayLog();
60  zoomMin = tt.GetRangeLower(), zoomMax = tt.GetRangeUpper();
61  if (dB) {
62  // MB: silly way to undo the work of GetWaveYPos while still getting a logarithmic scale
63  zoomMin = LINEAR_TO_DB(std::max(1.0e-7, double(zoomMin))) / dBRange + 1.0;
64  zoomMax = LINEAR_TO_DB(std::max(1.0e-7, double(zoomMax))) / dBRange + 1.0;
65  }
66  }
67 }
68 
70 (std::weak_ptr<EnvelopeHandle> &holder,
71  const wxMouseState &state, const wxRect &rect,
72  const AudacityProject *pProject, const std::shared_ptr<TimeTrack> &tt)
73 {
74  auto envelope = tt->GetEnvelope();
75  if (!envelope)
76  return {};
77  bool dB;
78  double dBRange;
79  float zoomMin, zoomMax;
80  GetTimeTrackData( *pProject, *tt, dBRange, dB, zoomMin, zoomMax);
82  (holder, state, rect, pProject, envelope, zoomMin, zoomMax, dB, dBRange,
83  true);
84 }
85 
87 (std::weak_ptr<EnvelopeHandle> &holder,
88  const wxMouseState &state, const wxRect &rect,
89  const AudacityProject *pProject, const std::shared_ptr<WaveTrack> &wt)
90 {
93  Envelope *const envelope = wt->GetEnvelopeAtX(state.GetX());
94 
95  if (!envelope)
96  return {};
97 
98  const int displayType = wt->GetDisplay();
99  // Not an envelope hit, unless we're using a type of wavetrack display
100  // suitable for envelopes operations, ie one of the Wave displays.
101  if (displayType != WaveTrack::Waveform)
102  return {}; // No envelope, not a hit, so 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 ViewInfo &viewInfo = pProject->GetViewInfo();
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  int ContourSpacing = (int)(rect.height / (2 * (zoomMax - zoomMin)));
155  const int MaxContours = 2;
156 
157  // Adding ContourSpacing/2 selects a region either side of the contour.
158  int yDisplace = yValue - yMisalign - yMouse + ContourSpacing / 2;
159  if (yDisplace > (MaxContours * ContourSpacing))
160  return {};
161  // Subtracting the ContourSpacing/2 we added earlier ensures distance is centred on the contour.
162  distance = abs((yDisplace % ContourSpacing) - ContourSpacing / 2);
163  if (distance >= yTolerance)
164  return {};
165 
166  return HitAnywhere(holder, envelope, timeTrack);
167 }
168 
170 (const TrackPanelMouseEvent &evt, AudacityProject *pProject)
171 {
172  using namespace RefreshCode;
173  const bool unsafe = pProject->IsAudioActive();
174  if ( unsafe )
175  return Cancelled;
176 
177  const wxMouseEvent &event = evt.event;
178  const ViewInfo &viewInfo = pProject->GetViewInfo();
179  const auto pTrack = static_cast<Track*>(evt.pCell.get());
180 
181  mEnvelopeEditors.clear();
182 
183  unsigned result = Cancelled;
184  if (pTrack)
185  result = pTrack->TypeSwitch< decltype(RefreshNone) >(
186  [&](WaveTrack *wt) {
187  if (wt->GetDisplay() != WaveTrack::Waveform)
188  return Cancelled;
189 
190  if (!mEnvelope)
191  return Cancelled;
192 
193  mLog = !wt->GetWaveformSettings().isLinear();
194  wt->GetDisplayBounds(&mLower, &mUpper);
195  mdBRange = wt->GetWaveformSettings().dBRange;
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 e2 = channel->GetEnvelopeAtX(event.GetX());
203  if (e2)
204  mEnvelopeEditors.push_back(
205  std::make_unique< EnvelopeEditor >( *e2, true ) );
206  else {
207  // There isn't necessarily an envelope there; no guarantee a
208  // linked track has the same WaveClip structure...
209  }
210  }
211  }
212 
213  return RefreshNone;
214  },
215  [&](TimeTrack *tt) {
216  if (!mEnvelope)
217  return Cancelled;
218  GetTimeTrackData( *pProject, *tt, mdBRange, mLog, mLower, mUpper);
219  mEnvelopeEditors.push_back(
220  std::make_unique< EnvelopeEditor >( *mEnvelope, false )
221  );
222 
223  return RefreshNone;
224  },
225  [](Track *) {
226  return Cancelled;
227  }
228  );
229 
230  if (result & Cancelled)
231  return result;
232 
233  mRect = evt.rect;
234 
235  const bool needUpdate = ForwardEventToEnvelopes(event, viewInfo);
236  return needUpdate ? RefreshCell : RefreshNone;
237 }
238 
240 (const TrackPanelMouseEvent &evt, AudacityProject *pProject)
241 {
242  using namespace RefreshCode;
243  const wxMouseEvent &event = evt.event;
244  const ViewInfo &viewInfo = pProject->GetViewInfo();
245  const bool unsafe = pProject->IsAudioActive();
246  if (unsafe) {
247  this->Cancel(pProject);
248  return RefreshCell | Cancelled;
249  }
250 
251  const bool needUpdate = ForwardEventToEnvelopes(event, viewInfo);
252  return needUpdate ? RefreshCell : RefreshNone;
253 }
254 
256 (const TrackPanelMouseState &, const AudacityProject *pProject)
257 {
258  const bool unsafe = pProject->IsAudioActive();
259  static auto disabledCursor =
260  ::MakeCursor(wxCURSOR_NO_ENTRY, DisabledCursorXpm, 16, 16);
261  static auto envelopeCursor =
262  ::MakeCursor(wxCURSOR_ARROW, EnvCursorXpm, 16, 16);
263 
264  wxString message;
265  if (mTimeTrack)
266  message = _("Click and drag to warp playback time");
267  else
268  message = _("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 ViewInfo &viewInfo = pProject->GetViewInfo();
284  const bool unsafe = pProject->IsAudioActive();
285  if (unsafe)
286  return this->Cancel(pProject);
287 
288  const bool needUpdate = ForwardEventToEnvelopes(event, viewInfo);
289 
290  pProject->PushState(
291  /* i18n-hint: (verb) Audacity has just adjusted the envelope .*/
292  _("Adjusted envelope."),
293  /* i18n-hint: The envelope is a curve that controls the audio loudness.*/
294  _("Envelope")
295  );
296 
297  mEnvelopeEditors.clear();
298 
299  using namespace RefreshCode;
300  return needUpdate ? RefreshCell : RefreshNone;
301 }
302 
304 {
305  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 }
bool GetDisplayLog() const
Definition: TimeTrack.h:130
std::shared_ptr< UIHandle > UIHandlePtr
Definition: CellularPanel.h:25
ViewInfo is used mainly to hold the zooming, selection and scroll information. It also has some statu...
Definition: ViewInfo.h:141
double GetRangeLower() const
Definition: TimeTrack.h:124
double PositionToTime(wxInt64 position, wxInt64 origin=0, bool ignoreFisheye=false) const
Definition: ViewInfo.cpp:49
Draggable curve used in TrackPanel for varying amplification.
Definition: Envelope.h:77
float dBr
Definition: ViewInfo.h:53
EnvelopeHandle(const EnvelopeHandle &)=delete
static UIHandlePtr WaveTrackHitTest(std::weak_ptr< EnvelopeHandle > &holder, const wxMouseState &state, const wxRect &rect, const AudacityProject *pProject, const std::shared_ptr< WaveTrack > &wt)
Result Cancel(AudacityProject *pProject) override
Result Release(const TrackPanelMouseEvent &event, AudacityProject *pProject, wxWindow *pParent) override
bool ForwardEventToEnvelopes(const wxMouseEvent &event, const ViewInfo &viewInfo)
Result Drag(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
Result mChangeHighlight
Definition: UIHandle.h:150
std::vector< std::unique_ptr< EnvelopeEditor > > mEnvelopeEditors
AudacityProject provides the main window, with tools and tracks contained within it.
Definition: Project.h:175
A kind of Track used to 'warp time'.
Definition: TimeTrack.h:29
unsigned Result
Definition: UIHandle.h:37
double GetRangeUpper() const
Definition: TimeTrack.h:125
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)
virtual ~EnvelopeHandle()
void RollbackState()
Definition: Project.cpp:4657
A Track that contains audio waveform data.
Definition: WaveTrack.h:60
Fundamental data object of Audacity, placed in the TrackPanel. Classes derived form it include the Wa...
Definition: Track.h:190
double GetValue(double t, double sampleDur=0) const
Get envelope value at time t.
Definition: Envelope.cpp:1121
bool IsAudioActive() const
Definition: Project.cpp:1462
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom")).Raw()), OnMoveTrack)#define SET_TRACK_NAME_PLUGIN_SYMBOLclass SetTrackNameCommand:public AudacityCommand
static UIHandlePtr HitAnywhere(std::weak_ptr< EnvelopeHandle > &holder, Envelope *envelope, bool timeTrack)
#define LINEAR_TO_DB(x)
Definition: Audacity.h:217
void PushState(const wxString &desc, const wxString &shortDesc)
Definition: Project.cpp:4617
std::shared_ptr< TrackPanelCell > pCell
Result Click(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
std::unique_ptr< wxCursor > MakeCursor(int WXUNUSED(CursorId), const char *const pXpm[36], int HotX, int HotY)
Definition: TrackPanel.cpp:261
HitTestPreview Preview(const TrackPanelMouseState &state, const AudacityProject *pProject) override
int GetWaveYPos(float value, float min, float max, int height, bool dB, bool outer, float dBr, bool clip)
const ViewInfo & GetViewInfo() const
Definition: Project.h:207
static auto Channels(TrackType *pTrack) -> TrackIterRange< TrackType >
Definition: Track.h:1305
void Enter(bool forward) override
static UIHandlePtr TimeTrackHitTest(std::weak_ptr< EnvelopeHandle > &holder, const wxMouseState &state, const wxRect &rect, const AudacityProject *pProject, const std::shared_ptr< TimeTrack > &tt)