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