Audacity  3.0.3
StretchHandle.cpp
Go to the documentation of this file.
1 /**********************************************************************
2 
3 Audacity: A Digital Audio Editor
4 
5 StretchHandle.cpp
6 
7 Paul Licameli split from TrackPanel.cpp
8 
9 **********************************************************************/
10 
11 
12 
13 #ifdef USE_MIDI
14 #include "../lib-src/header-substitutes/allegro.h"
15 
16 #include "StretchHandle.h"
17 
18 #include "../../../ui/CommonTrackPanelCell.h"
19 #include "../../../../HitTestResult.h"
20 #include "../../../../NoteTrack.h"
21 #include "../../../../ProjectAudioIO.h"
22 #include "../../../../ProjectHistory.h"
23 #include "../../../../ProjectSettings.h"
24 #include "../../../../RefreshCode.h"
25 #include "../../../../TrackPanelMouseEvent.h"
26 #include "../../../../UndoManager.h"
27 #include "ViewInfo.h"
28 #include "../../../../../images/Cursors.h"
29 
30 #include <algorithm>
31 
33 ( const std::shared_ptr<NoteTrack> &pTrack, const StretchState &stretchState )
34  : mpTrack{ pTrack }
35  , mStretchState{ stretchState }
36 {}
37 
39 {
40  static auto disabledCursor =
41  ::MakeCursor(wxCURSOR_NO_ENTRY, DisabledCursorXpm, 16, 16);
42  static auto stretchLeftCursor =
43  ::MakeCursor(wxCURSOR_BULLSEYE, StretchLeftCursorXpm, 16, 16);
44  static auto stretchRightCursor =
45  ::MakeCursor(wxCURSOR_BULLSEYE, StretchRightCursorXpm, 16, 16);
46  static auto stretchCursor =
47  ::MakeCursor(wxCURSOR_BULLSEYE, StretchCursorXpm, 16, 16);
48 
49  if (unsafe) {
50  return { {}, &*disabledCursor };
51  }
52  else {
53  wxCursor *pCursor = NULL;
54  switch (stretchMode) {
55  default:
56  wxASSERT(false);
57  case stretchLeft:
58  pCursor = &*stretchLeftCursor; break;
59  case stretchCenter:
60  pCursor = &*stretchCursor; break;
61  case stretchRight:
62  pCursor = &*stretchRightCursor; break;
63  }
64  return {
65  XO("Click and drag to stretch selected region."),
66  pCursor
67  };
68  }
69 }
70 
72 (std::weak_ptr<StretchHandle> &holder,
73  const TrackPanelMouseState &st, const AudacityProject *pProject,
74  const std::shared_ptr<NoteTrack> &pTrack)
75 {
76  StretchState stretchState;
77  const wxMouseState &state = st.state;
78 
79  // later, we may want a different policy, but for now, stretch is
80  // selected when the cursor is near the center of the track and
81  // within the selection
82  auto &viewInfo = ViewInfo::Get( *pProject );
83 
84  if (!pTrack || !pTrack->GetSelected())
85  return {};
86 
87  const wxRect &rect = st.rect;
88  int center = rect.y + rect.height / 2;
89  int distance = abs(state.m_y - center);
90  const int yTolerance = 10;
91  wxInt64 leftSel = viewInfo.TimeToPosition(viewInfo.selectedRegion.t0(), rect.x);
92  wxInt64 rightSel = viewInfo.TimeToPosition(viewInfo.selectedRegion.t1(), rect.x);
93  // Something is wrong if right edge comes before left edge
94  wxASSERT(!(rightSel < leftSel));
95  if (!(leftSel <= state.m_x && state.m_x <= rightSel &&
96  distance < yTolerance))
97  return {};
98 
99  // find nearest beat to sel0, sel1
100  static const double minPeriod = 0.05; // minimum beat period
101  stretchState.mBeatCenter = { 0, 0 };
102 
103  auto t0 = GetT0(*pTrack, viewInfo);
104  auto t1 = GetT1(*pTrack, viewInfo);
105 
106  if (t0 >= t1)
107  return {};
108 
109  stretchState.mBeat0 = pTrack->NearestBeatTime( t0 );
110  stretchState.mOrigSel0Quantized = stretchState.mBeat0.first;
111 
112  stretchState.mBeat1 = pTrack->NearestBeatTime( t1 );
113  stretchState.mOrigSel1Quantized = stretchState.mBeat1.first;
114 
115  // If there is not (almost) a beat to stretch that is slower
116  // than 20 beats per second, don't stretch
117  if ( within( stretchState.mBeat0.second,
118  stretchState.mBeat1.second, 0.9 ) ||
119  ( stretchState.mBeat1.first - stretchState.mBeat0.first ) /
120  ( stretchState.mBeat1.second - stretchState.mBeat0.second )
121  < minPeriod )
122  return {};
123 
124  auto selStart = viewInfo.PositionToTime( state.m_x, rect.x );
125  selStart = std::max(t0, std::min(t1, selStart));
126  stretchState.mBeatCenter = pTrack->NearestBeatTime( selStart );
127  if ( within( stretchState.mBeat0.second,
128  stretchState.mBeatCenter.second, 0.1 ) ) {
129  stretchState.mMode = stretchLeft;
130  stretchState.mLeftBeats = 0;
131  stretchState.mRightBeats =
132  stretchState.mBeat1.second - stretchState.mBeat0.second;
133  }
134  else if ( within( stretchState.mBeat1.second,
135  stretchState.mBeatCenter.second, 0.1 ) ) {
136  stretchState.mMode = stretchRight;
137  stretchState.mLeftBeats =
138  stretchState.mBeat1.second - stretchState.mBeat0.second;
139  stretchState.mRightBeats = 0;
140  }
141  else {
142  stretchState.mMode = stretchCenter;
143  stretchState.mLeftBeats =
144  stretchState.mBeat1.second - stretchState.mBeatCenter.second;
145  stretchState.mRightBeats =
146  stretchState.mBeatCenter.second - stretchState.mBeat0.second;
147  }
148 
149  auto result = std::make_shared<StretchHandle>( pTrack, stretchState );
150  result = AssignUIHandlePtr(holder, result);
151  return result;
152 }
153 
155 {
156 }
157 
159 (const TrackPanelMouseEvent &evt, AudacityProject *pProject)
160 {
161  using namespace RefreshCode;
162  const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
163  if ( unsafe )
164  return Cancelled;
165 
166  const wxMouseEvent &event = evt.event;
167 
168  if (event.LeftDClick() ||
169  !event.LeftDown() ||
170  evt.pCell == NULL)
171  return Cancelled;
172 
173 
174  mLeftEdge = evt.rect.GetLeft();
175  auto &viewInfo = ViewInfo::Get( *pProject );
176 
177  viewInfo.selectedRegion.setTimes
178  ( mStretchState.mBeat0.first, mStretchState.mBeat1.first );
179 
180  // Full refresh since the label area may need to indicate
181  // newly selected tracks. (I'm really not sure if the label area
182  // needs to be refreshed or how to just refresh non-label areas.-RBD)
183 
184  return RefreshAll;
185 }
186 
188 (const TrackPanelMouseEvent &evt, AudacityProject *pProject)
189 {
190  using namespace RefreshCode;
191  const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
192  if (unsafe) {
193  this->Cancel(pProject);
194  return RefreshAll | Cancelled;
195  }
196 
197  const wxMouseEvent &event = evt.event;
198  const int x = event.m_x;
199 
200  Track *clickedTrack=nullptr;
201  if (evt.pCell)
202  clickedTrack =
203  static_cast<CommonTrackPanelCell*>(evt.pCell.get())->FindTrack().get();
204 
205  if (clickedTrack == nullptr && mpTrack != nullptr)
206  clickedTrack = mpTrack.get();
207  Stretch(pProject, x, mLeftEdge, clickedTrack);
208  return RefreshAll;
209 }
210 
212 (const TrackPanelMouseState &, AudacityProject *pProject)
213 {
214  const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
215  return HitPreview( mStretchState.mMode, unsafe );
216 }
217 
219 (const TrackPanelMouseEvent &, AudacityProject *pProject,
220  wxWindow *)
221 {
222  using namespace RefreshCode;
223 
224  const bool unsafe = ProjectAudioIO::Get( *pProject ).IsAudioActive();
225  if (unsafe) {
226  this->Cancel(pProject);
227  return RefreshAll | Cancelled;
228  }
229 
230  bool left = mStretchState.mMode == stretchLeft;
231  bool right = mStretchState.mMode == stretchRight;
232  const auto &settings = ProjectSettings::Get( *pProject );
233  auto &viewInfo = ViewInfo::Get( *pProject );
234  if ( settings.IsSyncLocked() && ( left || right ) ) {
235  for ( auto track :
236  TrackList::SyncLockGroup( mpTrack.get() ) ) {
237  if ( track != mpTrack.get() ) {
238  if ( left ) {
239  auto origT0 = mStretchState.mOrigSel0Quantized;
240  auto diff = viewInfo.selectedRegion.t0() - origT0;
241  if ( diff > 0)
242  track->SyncLockAdjust( origT0 + diff, origT0 );
243  else
244  track->SyncLockAdjust( origT0, origT0 - diff );
245  track->Offset( diff );
246  }
247  else {
248  auto origT1 = mStretchState.mOrigSel1Quantized;
249  auto diff = viewInfo.selectedRegion.t1() - origT1;
250  track->SyncLockAdjust( origT1, origT1 + diff );
251  }
252  }
253  }
254  }
255 
256  /* i18n-hint: (noun) The track that is used for MIDI notes which can be
257  dragged to change their duration.*/
258  ProjectHistory::Get( *pProject ).PushState(XO("Stretch Note Track"),
259  /* i18n-hint: In the history list, indicates a MIDI note has
260  been dragged to change its duration (stretch it). Using either past
261  or present tense is fine here. If unsure, go for whichever is
262  shorter.*/
263  XO("Stretch"),
265  return RefreshAll;
266 }
267 
269 {
270  ProjectHistory::Get( *pProject ).RollbackState();
272 }
273 
274 double StretchHandle::GetT0(const Track &track, const ViewInfo &viewInfo)
275 {
276  return std::max(track.GetStartTime(), viewInfo.selectedRegion.t0());
277 }
278 
279 double StretchHandle::GetT1(const Track &track, const ViewInfo &viewInfo)
280 {
281  return std::min(track.GetEndTime(), viewInfo.selectedRegion.t1());
282 }
283 
284 void StretchHandle::Stretch(AudacityProject *pProject, int mouseXCoordinate, int trackLeftEdge,
285  Track *pTrack)
286 {
287  auto &viewInfo = ViewInfo::Get( *pProject );
288 
289  if (pTrack == NULL && mpTrack != NULL)
290  pTrack = mpTrack.get();
291 
292  if (pTrack) pTrack->TypeSwitch( [&](NoteTrack *pNt) {
293  double moveto =
294  std::max(0.0, viewInfo.PositionToTime(mouseXCoordinate, trackLeftEdge));
295 
296  double dur, left_dur, right_dur;
297 
298  // check to make sure tempo is not higher than 20 beats per second
299  // (In principle, tempo can be higher, but not infinity.)
300  double minPeriod = 0.05; // minimum beat period
301 
302  // make sure target duration is not too short
303  // Take quick exit if so, without changing the selection.
304  auto t0 = mStretchState.mBeat0.first;
305  auto t1 = mStretchState.mBeat1.first;
306  switch ( mStretchState.mMode ) {
307  case stretchLeft: {
308  dur = t1 - moveto;
309  if (dur < mStretchState.mRightBeats * minPeriod)
310  return;
311  pNt->StretchRegion
313  pNt->Offset( moveto - t0 );
314  mStretchState.mBeat0.first = moveto;
315  viewInfo.selectedRegion.setT0(moveto);
316  break;
317  }
318  case stretchRight: {
319  dur = moveto - t0;
320  if (dur < mStretchState.mLeftBeats * minPeriod)
321  return;
322  pNt->StretchRegion
324  viewInfo.selectedRegion.setT1(moveto);
325  mStretchState.mBeat1.first = moveto;
326  break;
327  }
328  case stretchCenter: {
329  moveto = std::max(t0, std::min(t1, moveto));
330  left_dur = moveto - t0;
331  right_dur = t1 - moveto;
332  if ( left_dur < mStretchState.mLeftBeats * minPeriod ||
333  right_dur < mStretchState.mRightBeats * minPeriod )
334  return;
335  pNt->StretchRegion
337  pNt->StretchRegion
339  mStretchState.mBeatCenter.first = moveto;
340  break;
341  }
342  default:
343  wxASSERT(false);
344  break;
345  }
346  });
347 }
348 #endif
TrackPanelMouseEvent::pCell
std::shared_ptr< TrackPanelCell > pCell
Definition: TrackPanelMouseEvent.h:61
StretchHandle::stretchRight
@ stretchRight
Definition: StretchHandle.h:28
ViewInfo::Get
static ViewInfo & Get(AudacityProject &project)
Definition: ViewInfo.cpp:241
StretchHandle.h
StretchHandle::StretchState
Definition: StretchHandle.h:39
StretchHandle::StretchState::mRightBeats
double mRightBeats
Definition: StretchHandle.h:48
TrackPanelMouseEvent::rect
const wxRect & rect
Definition: TrackPanelMouseEvent.h:59
RefreshCode::RefreshAll
@ RefreshAll
Definition: RefreshCode.h:26
StretchHandle::Cancel
Result Cancel(AudacityProject *pProject) override
Definition: StretchHandle.cpp:268
RefreshCode::RefreshNone
@ RefreshNone
Definition: RefreshCode.h:21
Track::GetEndTime
virtual double GetEndTime() const =0
StretchHandle::stretchLeft
@ stretchLeft
Definition: StretchHandle.h:26
MakeCursor
std::unique_ptr< wxCursor > MakeCursor(int WXUNUSED(CursorId), const char *const pXpm[36], int HotX, int HotY)
Definition: TrackPanel.cpp:182
StretchHandle::GetT0
static double GetT0(const Track &track, const ViewInfo &viewInfo)
Definition: StretchHandle.cpp:274
ViewInfo
Definition: ViewInfo.h:202
StretchHandle::Preview
HitTestPreview Preview(const TrackPanelMouseState &state, AudacityProject *pProject) override
Definition: StretchHandle.cpp:212
StretchHandle::mpTrack
std::shared_ptr< NoteTrack > mpTrack
Definition: StretchHandle.h:96
RefreshCode::Cancelled
@ Cancelled
Definition: RefreshCode.h:23
XO
#define XO(s)
Definition: Internat.h:31
ProjectSettings::Get
static ProjectSettings & Get(AudacityProject &project)
Definition: ProjectSettings.cpp:44
StretchHandle::StretchState::mOrigSel0Quantized
double mOrigSel0Quantized
Definition: StretchHandle.h:50
StretchHandle::~StretchHandle
virtual ~StretchHandle()
Definition: StretchHandle.cpp:154
ProjectAudioIO::Get
static ProjectAudioIO & Get(AudacityProject &project)
Definition: ProjectAudioIO.cpp:22
TrackPanelMouseState::rect
const wxRect & rect
Definition: TrackPanelMouseEvent.h:39
StretchHandle::Stretch
void Stretch(AudacityProject *pProject, int mouseXCoordinate, int trackLeftEdge, Track *pTrack)
Definition: StretchHandle.cpp:284
ProjectAudioIO::IsAudioActive
bool IsAudioActive() const
Definition: ProjectAudioIO.cpp:51
NotifyingSelectedRegion::t1
double t1() const
Definition: ViewInfo.h:48
StretchHandle::StretchState::mBeatCenter
QuantizedTimeAndBeat mBeatCenter
Definition: StretchHandle.h:44
StretchHandle::Drag
Result Drag(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
Definition: StretchHandle.cpp:188
StretchHandle::stretchCenter
@ stretchCenter
Definition: StretchHandle.h:27
StretchHandle::GetT1
static double GetT1(const Track &track, const ViewInfo &viewInfo)
Definition: StretchHandle.cpp:279
StretchHandle::StretchState::mBeat1
QuantizedTimeAndBeat mBeat1
Definition: StretchHandle.h:46
Track::GetStartTime
virtual double GetStartTime() const =0
ViewInfo::selectedRegion
NotifyingSelectedRegion selectedRegion
Definition: ViewInfo.h:229
NoteTrack::StretchRegion
bool StretchRegion(QuantizedTimeAndBeat t0, QuantizedTimeAndBeat t1, double newDur)
Definition: NoteTrack.cpp:722
Track::Offset
void Offset(double t)
Definition: Track.h:444
StretchHandle::StretchState::mOrigSel1Quantized
double mOrigSel1Quantized
Definition: StretchHandle.h:50
StretchHandle::StretchState::mMode
StretchEnum mMode
Definition: StretchHandle.h:40
StretchHandle::mLeftEdge
int mLeftEdge
Definition: StretchHandle.h:97
UIHandle::Result
unsigned Result
Definition: UIHandle.h:38
UIHandlePtr
std::shared_ptr< UIHandle > UIHandlePtr
Definition: CellularPanel.h:28
HitTestPreview
Definition: HitTestResult.h:20
StretchHandle::StretchHandle
StretchHandle(const StretchHandle &)
StretchHandle::StretchState::mLeftBeats
double mLeftBeats
Definition: StretchHandle.h:47
anonymous_namespace{TrackPanel.cpp}::FindTrack
std::shared_ptr< Track > FindTrack(TrackPanelCell *pCell)
Definition: TrackPanel.cpp:530
ViewInfo.h
NoteTrack::NearestBeatTime
QuantizedTimeAndBeat NearestBeatTime(double time) const
Definition: NoteTrack.cpp:677
Track::GetSelected
bool GetSelected() const
Definition: Track.h:431
StretchHandle::StretchEnum
StretchEnum
Definition: StretchHandle.h:24
StretchHandle::StretchState::mBeat0
QuantizedTimeAndBeat mBeat0
Definition: StretchHandle.h:45
min
int min(int a, int b)
Definition: CompareAudioCommand.cpp:106
ProjectHistory::PushState
void PushState(const TranslatableString &desc, const TranslatableString &shortDesc)
Definition: ProjectHistory.cpp:90
StretchHandle::HitTest
static UIHandlePtr HitTest(std::weak_ptr< StretchHandle > &holder, const TrackPanelMouseState &state, const AudacityProject *pProject, const std::shared_ptr< NoteTrack > &pTrack)
Definition: StretchHandle.cpp:72
Track::TypeSwitch
R TypeSwitch(const Functions &...functions)
Use this function rather than testing track type explicitly and making down-casts.
Definition: Track.h:709
StretchHandle::Release
Result Release(const TrackPanelMouseEvent &event, AudacityProject *pProject, wxWindow *pParent) override
Definition: StretchHandle.cpp:219
Track
Abstract base class for an object holding data associated with points on a time axis.
Definition: Track.h:239
ProjectHistory::RollbackState
void RollbackState()
Definition: ProjectHistory.cpp:117
AudacityProject
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:92
TrackPanelMouseEvent
Definition: TrackPanelMouseEvent.h:46
TrackPanelMouseState
Definition: TrackPanelMouseEvent.h:28
UndoPush::CONSOLIDATE
@ CONSOLIDATE
NotifyingSelectedRegion::t0
double t0() const
Definition: ViewInfo.h:47
StretchHandle::mStretchState
StretchState mStretchState
Definition: StretchHandle.h:99
StretchHandle::Click
Result Click(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
Definition: StretchHandle.cpp:159
AssignUIHandlePtr
std::shared_ptr< Subclass > AssignUIHandlePtr(std::weak_ptr< Subclass > &holder, const std::shared_ptr< Subclass > &pNew)
Definition: UIHandle.h:151
CommonTrackPanelCell
Definition: CommonTrackPanelCell.h:28
TrackList::SyncLockGroup
static TrackIterRange< Track > SyncLockGroup(Track *pTrack)
Definition: Track.cpp:650
settings
static Settings & settings()
Definition: TrackInfo.cpp:86
RefreshCode
Namespace containing an enum 'what to do on a refresh?'.
Definition: RefreshCode.h:16
within
bool within(A a, B b, DIST d)
Definition: TrackPanel.cpp:163
TrackPanelMouseEvent::event
wxMouseEvent & event
Definition: TrackPanelMouseEvent.h:58
StretchHandle::HitPreview
static HitTestPreview HitPreview(StretchEnum stretchMode, bool unsafe)
Definition: StretchHandle.cpp:38
TrackPanelMouseState::state
wxMouseState & state
Definition: TrackPanelMouseEvent.h:38
ProjectHistory::Get
static ProjectHistory & Get(AudacityProject &project)
Definition: ProjectHistory.cpp:26
NoteTrack
A Track that is used for Midi notes. (Somewhat old code).
Definition: NoteTrack.h:67