1 /**********************************************************************
2 
3 Audacity: A Digital Audio Editor
4 
5 WaveformVRulerControls.cpp
6 
7 Paul Licameli split from WaveTrackVRulerControls.cpp
8 
9 **********************************************************************/
10 
11 #include "WaveformVRulerControls.h"
12 
13 #include "WaveformVZoomHandle.h"
14 #include "WaveTrackVRulerControls.h"
15 
16 #include "NumberScale.h"
17 #include "../../../../ProjectHistory.h"
18 #include "../../../../RefreshCode.h"
19 #include "../../../../TrackPanelMouseEvent.h"
20 #include "../../../../UIHandle.h"
21 #include "../../../../WaveTrack.h"
22 #include "../../../../prefs/WaveformSettings.h"
23 #include "../../../../widgets/Ruler.h"
24 
25 WaveformVRulerControls::~WaveformVRulerControls() = default;
26 
HitTest(const TrackPanelMouseState & st,const AudacityProject * pProject)27 std::vector<UIHandlePtr> WaveformVRulerControls::HitTest(
28    const TrackPanelMouseState &st,
29    const AudacityProject *pProject)
30 {
31    std::vector<UIHandlePtr> results;
32 
33    if ( st.state.GetX() <= st.rect.GetRight() - kGuard ) {
34       auto pTrack = FindTrack()->SharedPointer<WaveTrack>(  );
35       if (pTrack) {
36          auto result = std::make_shared<WaveformVZoomHandle>(
37             pTrack, st.rect, st.state.m_y );
38          result = AssignUIHandlePtr(mVZoomHandle, result);
39          results.push_back(result);
40       }
41    }
42 
43    auto more = TrackVRulerControls::HitTest(st, pProject);
44    std::copy(more.begin(), more.end(), std::back_inserter(results));
45 
46    return results;
47 }
48 
HandleWheelRotation(const TrackPanelMouseEvent & evt,AudacityProject * pProject)49 unsigned WaveformVRulerControls::HandleWheelRotation(
50    const TrackPanelMouseEvent &evt, AudacityProject *pProject)
51 {
52    using namespace RefreshCode;
53    const auto pTrack = FindTrack();
54    if (!pTrack)
55       return RefreshNone;
56    const auto wt = static_cast<WaveTrack*>(pTrack.get());
57    return DoHandleWheelRotation( evt, pProject, wt );
58 }
59 
DoHandleWheelRotation(const TrackPanelMouseEvent & evt,AudacityProject * pProject,WaveTrack * wt)60 unsigned WaveformVRulerControls::DoHandleWheelRotation(
61    const TrackPanelMouseEvent &evt, AudacityProject *pProject, WaveTrack *wt)
62 {
63    using namespace RefreshCode;
64    const wxMouseEvent &event = evt.event;
65 
66    if (!(event.ShiftDown() || event.CmdDown()))
67       return RefreshNone;
68 
69    // Always stop propagation even if the ruler didn't change.  The ruler
70    // is a narrow enough target.
71    evt.event.Skip(false);
72 
73    auto steps = evt.steps;
74 
75    using namespace WaveTrackViewConstants;
76    const bool isDB =
77       wt->GetWaveformSettings().scaleType == WaveformSettings::stLogarithmic;
78    // Special cases for Waveform dB only.
79    // Set the bottom of the dB scale but only if it's visible
80    if (isDB && event.ShiftDown() && event.CmdDown()) {
81       float min, max;
82       wt->GetDisplayBounds(&min, &max);
83       if (!(min < 0.0 && max > 0.0))
84          return RefreshNone;
85 
86       WaveformSettings &settings =
87          wt->GetWaveformSettings();
88       float olddBRange = settings.dBRange;
89       for (auto channel : TrackList::Channels(wt)) {
90          WaveformSettings &channelSettings =
91             channel->GetWaveformSettings();
92          if (steps < 0)
93             // Zoom out
94             channelSettings.NextLowerDBRange();
95          else
96             channelSettings.NextHigherDBRange();
97       }
98 
99       float newdBRange = settings.dBRange;
100 
101       // Is y coordinate within the rectangle half-height centered about
102       // the zero level?
103       const auto &rect = evt.rect;
104       const auto zeroLevel = wt->ZeroLevelYCoordinate(rect);
105       const bool fixedMagnification =
106       (4 * std::abs(event.GetY() - zeroLevel) < rect.GetHeight());
107 
108       if (fixedMagnification) {
109          // Vary the db limit without changing
110          // magnification; that is, peaks and troughs move up and down
111          // rigidly, as parts of the wave near zero are exposed or hidden.
112          const float extreme = (LINEAR_TO_DB(2) + newdBRange) / newdBRange;
113          max = std::min(extreme, max * olddBRange / newdBRange);
114          min = std::max(-extreme, min * olddBRange / newdBRange);
115          for (auto channel : TrackList::Channels(wt)) {
116             channel->SetLastdBRange();
117             channel->SetDisplayBounds(min, max);
118          }
119       }
120    }
121    else if (event.CmdDown() && !event.ShiftDown()) {
122       const int yy = event.m_y;
123       WaveformVZoomHandle::DoZoom(
124          pProject, wt,
125          (steps < 0)
126             ? kZoomOut
127             : kZoomIn,
128          evt.rect, yy, yy, true);
129    }
130    else if (!event.CmdDown() && event.ShiftDown()) {
131       // Scroll some fixed number of pixels, independent of zoom level or track height:
132       static const float movement = 10.0f;
133       const int height = evt.rect.GetHeight();
134       {
135          float topLimit = 2.0;
136          if (isDB) {
137             const float dBRange = wt->GetWaveformSettings().dBRange;
138             topLimit = (LINEAR_TO_DB(topLimit) + dBRange) / dBRange;
139          }
140          const float bottomLimit = -topLimit;
141          float top, bottom;
142          wt->GetDisplayBounds(&bottom, &top);
143          const float range = top - bottom;
144          const float delta = range * steps * movement / height;
145          float newTop = std::min(topLimit, top + delta);
146          const float newBottom = std::max(bottomLimit, newTop - range);
147          newTop = std::min(topLimit, newBottom + range);
148          for (auto channel : TrackList::Channels(wt))
149             channel->SetDisplayBounds(newBottom, newTop);
150       }
151    }
152    else
153       return RefreshNone;
154 
155    ProjectHistory::Get( *pProject ).ModifyState(true);
156 
157    return RefreshCell | UpdateVRuler;
158 }
159 
Draw(TrackPanelDrawingContext & context,const wxRect & rect_,unsigned iPass)160 void WaveformVRulerControls::Draw(
161    TrackPanelDrawingContext &context,
162    const wxRect &rect_, unsigned iPass )
163 {
164    TrackVRulerControls::Draw( context, rect_, iPass );
165    WaveTrackVRulerControls::DoDraw( *this, context, rect_, iPass );
166 }
167 
UpdateRuler(const wxRect & rect)168 void WaveformVRulerControls::UpdateRuler( const wxRect &rect )
169 {
170    const auto wt = std::static_pointer_cast< WaveTrack >( FindTrack() );
171    if (!wt)
172       return;
173    DoUpdateVRuler( rect, wt.get() );
174 }
175 
DoUpdateVRuler(const wxRect & rect,const WaveTrack * wt)176 void WaveformVRulerControls::DoUpdateVRuler(
177    const wxRect &rect, const WaveTrack *wt )
178 {
179    auto vruler = &WaveTrackVRulerControls::ScratchRuler();
180 
181    // All waves have a ruler in the info panel
182    // The ruler needs a bevelled surround.
183    const float dBRange =
184       wt->GetWaveformSettings().dBRange;
185 
186    WaveformSettings::ScaleType scaleType =
187    wt->GetWaveformSettings().scaleType;
188 
189    if (scaleType == WaveformSettings::stLinear) {
190       // Waveform
191 
192       float min, max;
193       wt->GetDisplayBounds(&min, &max);
194       if (wt->GetLastScaleType() != scaleType &&
195           wt->GetLastScaleType() != -1)
196       {
197          // do a translation into the linear space
198          wt->SetLastScaleType();
199          wt->SetLastdBRange();
200          float sign = (min >= 0 ? 1 : -1);
201          if (min != 0.) {
202             min = DB_TO_LINEAR(fabs(min) * dBRange - dBRange);
203             if (min < 0.0)
204                min = 0.0;
205             min *= sign;
206          }
207          sign = (max >= 0 ? 1 : -1);
208 
209          if (max != 0.) {
210             max = DB_TO_LINEAR(fabs(max) * dBRange - dBRange);
211             if (max < 0.0)
212                max = 0.0;
213             max *= sign;
214          }
215          wt->SetDisplayBounds(min, max);
216       }
217 
218       vruler->SetDbMirrorValue( 0.0 );
219       vruler->SetBounds(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height - 1);
220       vruler->SetOrientation(wxVERTICAL);
221       vruler->SetRange(max, min);
222       vruler->SetFormat(Ruler::RealFormat);
223       vruler->SetUnits({});
224       vruler->SetLabelEdges(false);
225       vruler->SetLog(false);
226    }
227    else {
228       wxASSERT(scaleType == WaveformSettings::stLogarithmic);
229       scaleType = WaveformSettings::stLogarithmic;
230 
231       vruler->SetUnits({});
232 
233       float min, max;
234       wt->GetDisplayBounds(&min, &max);
235       float lastdBRange;
236 
237       if (wt->GetLastScaleType() != scaleType &&
238           wt->GetLastScaleType() != -1)
239       {
240          // do a translation into the dB space
241          wt->SetLastScaleType();
242          wt->SetLastdBRange();
243          float sign = (min >= 0 ? 1 : -1);
244          if (min != 0.) {
245             min = (LINEAR_TO_DB(fabs(min)) + dBRange) / dBRange;
246             if (min < 0.0)
247                min = 0.0;
248             min *= sign;
249          }
250          sign = (max >= 0 ? 1 : -1);
251 
252          if (max != 0.) {
253             max = (LINEAR_TO_DB(fabs(max)) + dBRange) / dBRange;
254             if (max < 0.0)
255                max = 0.0;
256             max *= sign;
257          }
258          wt->SetDisplayBounds(min, max);
259       }
260       else if (dBRange != (lastdBRange = wt->GetLastdBRange())) {
261          wt->SetLastdBRange();
262          // Remap the max of the scale
263          float newMax = max;
264 
265          // This commented out code is problematic.
266          // min and max may be correct, and this code cause them to change.
267 #ifdef ONLY_LABEL_POSITIVE
268          const float sign = (max >= 0 ? 1 : -1);
269          if (max != 0.) {
270 
271             // Ugh, duplicating from TrackPanel.cpp
272 #define ZOOMLIMIT 0.001f
273 
274             const float extreme = LINEAR_TO_DB(2);
275             // recover dB value of max
276             const float dB = std::min(extreme, (float(fabs(max)) * lastdBRange - lastdBRange));
277             // find NEW scale position, but old max may get trimmed if the db limit rises
278             // Don't trim it to zero though, but leave max and limit distinct
279             newMax = sign * std::max(ZOOMLIMIT, (dBRange + dB) / dBRange);
280             // Adjust the min of the scale if we can,
281             // so the db Limit remains where it was on screen, but don't violate extremes
282             if (min != 0.)
283                min = std::max(-extreme, newMax * min / max);
284          }
285 #endif
286          wt->SetDisplayBounds(min, newMax);
287       }
288 
289       // Old code was if ONLY_LABEL_POSITIVE were defined.
290       // it uses the +1 to 0 range only.
291       // the enabled code uses +1 to -1, and relies on set ticks labelling knowing about
292       // the dB scale.
293 #ifdef ONLY_LABEL_POSITIVE
294       if (max > 0) {
295 #endif
296          int top = 0;
297          float topval = 0;
298          int bot = rect.height;
299          float botval = -dBRange;
300 
301 #ifdef ONLY_LABEL_POSITIVE
302          if (min < 0) {
303             bot = top + (int)((max / (max - min))*(bot - top));
304             min = 0;
305          }
306 
307          if (max > 1) {
308             top += (int)((max - 1) / (max - min) * (bot - top));
309             max = 1;
310          }
311 
312          if (max < 1 && max > 0)
313             topval = -((1 - max) * dBRange);
314 
315          if (min > 0) {
316             botval = -((1 - min) * dBRange);
317          }
318 #else
319          topval = -((1 - max) * dBRange);
320          botval = -((1 - min) * dBRange);
321          vruler->SetDbMirrorValue( dBRange );
322 #endif
323          vruler->SetBounds(rect.x, rect.y + top, rect.x + rect.width, rect.y + bot - 1);
324          vruler->SetOrientation(wxVERTICAL);
325          vruler->SetRange(topval, botval);
326 #ifdef ONLY_LABEL_POSITIVE
327       }
328       else
329          vruler->SetBounds(0.0, 0.0, 0.0, 0.0); // A.C.H I couldn't find a way to just disable it?
330 #endif
331       vruler->SetFormat(Ruler::RealLogFormat);
332       vruler->SetLabelEdges(true);
333       vruler->SetLog(false);
334    }
335    vruler->GetMaxSize( &wt->vrulerSize.x, &wt->vrulerSize.y );
336 }
337