Audacity 3.2.0
WaveformVRulerControls.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3Audacity: A Digital Audio Editor
4
5WaveformVRulerControls.cpp
6
7Paul Licameli split from WaveTrackVRulerControls.cpp
8
9**********************************************************************/
10
12
13#include "WaveformVZoomHandle.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#include "../../../../widgets/LinearUpdater.h"
25#include "../../../../widgets/RealFormat.h"
26#include "../../../../widgets/CustomUpdaterValue.h"
27
29
30// These are doubles beacuse of the type of value in Label,
31// but for the purpose of labelling the linear dB waveform ruler,
32// these should always be integer numbers.
33using LinearDBValues = std::vector<double>;
34
36
37void RegenerateLinearDBValues(int dBRange, float min, float max, int height)
38{
39 majorValues.clear();
40 minorValues.clear();
41 minorMinorValues.clear();
42
43 majorValues.push_back(0);
44 majorValues.push_back(-dBRange);
45 majorValues.push_back(2 * -dBRange);
46
47 const double EPSILON = .1e-5;
48
49 // No marks allowed within CENTER_SPACING pixels from the center
50 // Calculate the closest allowed major / minor and remove everything around those
51 const double CENTER = height / 2;
52 const int CENTER_SPACING = 30;
53 const double SIZE_SCALE = (max - min) / 2;
54
55 double centerSpacingMark = 0;
56
57 for (double major = 0.1; major < .95; major += .1) {
58 double val = std::round(major * 10) / 10;
59 double mappedVal = std::trunc(-dBRange * val);
60 double pixDist = CENTER - ((1 - DB_TO_LINEAR(mappedVal)) * CENTER) / SIZE_SCALE;
61 if (pixDist > CENTER_SPACING) {
62 centerSpacingMark = major;
63 majorValues.push_back(mappedVal);
64 majorValues.push_back(-2 * dBRange - mappedVal);
65 }
66 }
67 for (double minor = 0.05; minor < 1 ; minor += .1) {
68 double val = std::round(minor * 100) / 100;
69 double mappedVal = std::trunc(-dBRange * val);
70 if (minor < centerSpacingMark) {
71 minorValues.push_back(mappedVal);
72 minorValues.push_back(-2 * dBRange - mappedVal);
73 }
74 }
75 for (int minorMinor = 1; minorMinor < dBRange - EPSILON; minorMinor++) {
76 double absDist = fabs(fabs(dBRange - minorMinor) - dBRange) / dBRange;
77 if (absDist < centerSpacingMark) {
78 if ((minorMinor % (int)std::round(dBRange / 20)) != 0) {
79 minorMinorValues.push_back(-minorMinor);
80 minorMinorValues.push_back(-2 * dBRange + minorMinor);
81 }
82 }
83 }
84
85}
86
87std::vector<UIHandlePtr> WaveformVRulerControls::HitTest(
88 const TrackPanelMouseState &st,
89 const AudacityProject *pProject)
90{
91 std::vector<UIHandlePtr> results;
92
93 if ( st.state.GetX() <= st.rect.GetRight() - kGuard ) {
94 auto pTrack = FindTrack()->SharedPointer<WaveTrack>( );
95 if (pTrack) {
96 auto result = std::make_shared<WaveformVZoomHandle>(
97 pTrack, st.rect, st.state.m_y );
98 result = AssignUIHandlePtr(mVZoomHandle, result);
99 results.push_back(result);
100 }
101 }
102
103 auto more = TrackVRulerControls::HitTest(st, pProject);
104 std::copy(more.begin(), more.end(), std::back_inserter(results));
105
106 return results;
107}
108
110 const TrackPanelMouseEvent &evt, AudacityProject *pProject)
111{
112 using namespace RefreshCode;
113 const auto pTrack = FindTrack();
114 if (!pTrack)
115 return RefreshNone;
116 const auto wt = static_cast<WaveTrack*>(pTrack.get());
117 return DoHandleWheelRotation( evt, pProject, wt );
118}
119
120namespace {
122 WaveformScale &cache, const WaveTrack &track)
123{
124 cache.SetLastDBRange(WaveformSettings::Get(track).dBRange);
125}
126
128 WaveformScale &cache, const WaveTrack &track)
129{
130 cache.SetLastScaleType(WaveformSettings::Get(track).scaleType);
131}
132}
133
135 const TrackPanelMouseEvent &evt, AudacityProject *pProject, WaveTrack *wt)
136{
137 using namespace RefreshCode;
138 const wxMouseEvent &event = evt.event;
139
140 if (!(event.ShiftDown() || event.CmdDown()))
141 return RefreshNone;
142
143 // Always stop propagation even if the ruler didn't change. The ruler
144 // is a narrow enough target.
145 evt.event.Skip(false);
146
147 auto steps = evt.steps;
148
149 using namespace WaveTrackViewConstants;
150 auto &settings = WaveformSettings::Get(*wt);
151 auto &cache = WaveformScale::Get(*wt);
152 const bool isDB = !settings.isLinear();
153 // Special cases for Waveform (logarithmic) dB only.
154 // Set the bottom of the dB scale but only if it's visible
155 if (isDB && event.ShiftDown() && event.CmdDown()) {
156 float min, max;
157 cache.GetDisplayBounds(min, max);
158 if (!(min < 0.0 && max > 0.0))
159 return RefreshNone;
160
161 float olddBRange = settings.dBRange;
162 for (auto channel : TrackList::Channels(wt)) {
163 auto &channelSettings = WaveformSettings::Get(*channel);
164 if (steps < 0)
165 // Zoom out
166 channelSettings.NextLowerDBRange();
167 else
168 channelSettings.NextHigherDBRange();
169 }
170
171 float newdBRange = settings.dBRange;
172
173 // Is y coordinate within the rectangle half-height centered about
174 // the zero level?
175 const auto &rect = evt.rect;
176 const auto zeroLevel = cache.ZeroLevelYCoordinate(rect);
177 const bool fixedMagnification =
178 (4 * std::abs(event.GetY() - zeroLevel) < rect.GetHeight());
179
180 if (fixedMagnification) {
181 // Vary the db limit without changing
182 // magnification; that is, peaks and troughs move up and down
183 // rigidly, as parts of the wave near zero are exposed or hidden.
184 const float extreme = (LINEAR_TO_DB(2) + newdBRange) / newdBRange;
185 max = std::min(extreme, max * olddBRange / newdBRange);
186 min = std::max(-extreme, min * olddBRange / newdBRange);
187 for (auto channel : TrackList::Channels(wt)) {
188 auto &cache = WaveformScale::Get(*channel);
189 SetLastdBRange(cache, *channel);
190 cache.SetDisplayBounds(min, max);
191 }
192 }
193 }
194 else if (event.CmdDown() && !event.ShiftDown()) {
195 const int yy = event.m_y;
197 pProject, wt,
198 (steps < 0)
199 ? kZoomOut
200 : kZoomIn,
201 evt.rect, yy, yy, true);
202 }
203 else if (!event.CmdDown() && event.ShiftDown()) {
204 // Scroll some fixed number of pixels, independent of zoom level or track height:
205 static const float movement = 10.0f;
206 const int height = evt.rect.GetHeight();
207 {
208 float topLimit = 2.0;
209 if (isDB) {
210 const float dBRange = WaveformSettings::Get(*wt).dBRange;
211 topLimit = (LINEAR_TO_DB(topLimit) + dBRange) / dBRange;
212 }
213 const float bottomLimit = -topLimit;
214 float top, bottom;
215 auto &cache = WaveformScale::Get(*wt);
216 cache.GetDisplayBounds(bottom, top);
217 const float range = top - bottom;
218 const float delta = range * steps * movement / height;
219 float newTop = std::min(topLimit, top + delta);
220 const float newBottom = std::max(bottomLimit, newTop - range);
221 newTop = std::min(topLimit, newBottom + range);
222 for (auto channel : TrackList::Channels(wt))
223 WaveformScale::Get(*channel)
224 .SetDisplayBounds(newBottom, newTop);
225 }
226 }
227 else
228 return RefreshNone;
229
230 ProjectHistory::Get( *pProject ).ModifyState(true);
231
232 return RefreshCell | UpdateVRuler;
233}
234
237 const wxRect &rect_, unsigned iPass )
238{
239 TrackVRulerControls::Draw( context, rect_, iPass );
240 WaveTrackVRulerControls::DoDraw( *this, context, rect_, iPass );
241}
242
243void WaveformVRulerControls::UpdateRuler( const wxRect &rect )
244{
245 const auto wt = std::static_pointer_cast< WaveTrack >( FindTrack() );
246 if (!wt)
247 return;
248 DoUpdateVRuler( rect, wt.get() );
249}
250
252
254 const wxRect &rect, const WaveTrack *wt )
255{
257
258 // All waves have a ruler in the info panel
259 // The ruler needs a bevelled surround.
260 auto &settings = WaveformSettings::Get(*wt);
261 const float dBRange = settings.dBRange;
262
263 float min, max;
264 auto &cache = WaveformScale::Get(*wt);
265 cache.GetDisplayBounds(min, max);
266 const float lastdBRange = cache.GetLastDBRange();
267 if (dBRange != lastdBRange)
268 SetLastdBRange(cache, *wt);
269
270 auto scaleType = settings.scaleType;
271
272 if (settings.isLinear()) {
273 // Waveform
274
275 if (cache.GetLastScaleType() != WaveformSettings::stLinearAmp &&
276 cache.GetLastScaleType() != WaveformSettings::stLinearDb &&
277 cache.GetLastScaleType() != -1)
278 {
279 // do a translation into the linear space
280 SetLastScaleType(cache, *wt);
281 SetLastdBRange(cache, *wt);
282 float sign = (min >= 0 ? 1 : -1);
283 if (min != 0.) {
284 min = DB_TO_LINEAR(fabs(min) * dBRange - dBRange);
285 if (min < 0.0)
286 min = 0.0;
287 min *= sign;
288 }
289 sign = (max >= 0 ? 1 : -1);
290
291 if (max != 0.) {
292 max = DB_TO_LINEAR(fabs(max) * dBRange - dBRange);
293 if (max < 0.0)
294 max = 0.0;
295 max *= sign;
296 }
297 cache.SetDisplayBounds(min, max);
298 }
299
300 vruler->SetDbMirrorValue( 0.0 );
301 vruler->SetBounds(rect.x, rect.y, rect.x + rect.width, rect.y + rect.height - 1);
302 vruler->SetOrientation(wxVERTICAL);
303 vruler->SetRange(max, min);
304 vruler->SetFormat(&RealFormat::LinearInstance());
305 if (scaleType == WaveformSettings::stLinearAmp) {
306 vruler->SetLabelEdges(false);
307 vruler->SetUnits({});
308 vruler->SetUpdater(&LinearUpdater::Instance());
309 }
310 else {
311 RegenerateLinearDBValues(dBRange, min, max, rect.GetHeight());
312 vruler->SetLabelEdges(true);
313 vruler->SetUnits(XO("dB"));
314 vruler->SetUpdater(&updater);
315 RulerUpdater::Labels major, minor, minorMinor;
316 std::vector<LinearDBValues> values = { majorValues, minorValues, minorMinorValues };
317 for (int ii = 0; ii < 3; ii++) {
319 int size = (ii == 0) ? majorValues.size() :
320 (ii == 1) ? minorValues.size() : minorMinorValues.size();
321 for (int i = 0; i < size; i++) {
322 double value = (ii == 0) ? majorValues[i] :
323 (ii == 1) ? minorValues[i] : minorMinorValues[i];
325
326 if (value == -dBRange)
327 lab.value = 0;
328 else {
329 float sign = (value > -dBRange ? 1 : -1);
330 if (value < -dBRange)
331 value = -2 * dBRange - value;
332 lab.value = DB_TO_LINEAR(value) * sign;
333 }
334
335 wxString s = (value == -dBRange) ?
336 wxString(L"-\u221e") : wxString::FromDouble(value);
337 // \u221e represents the infinity symbol
338 // Should this just be -dBRange so it is consistent?
339 //wxString s = wxString::FromDouble(value);
340 lab.text = Verbatim(s);
341
342 labs.push_back(lab);
343 }
344 if (ii == 0)
345 major = labs;
346 else if (ii == 1)
347 minor = labs;
348 else
349 minorMinor = labs;
350 }
351 updater.SetData(major, minor, minorMinor);
352 }
353 }
354 else {
355 vruler->SetUnits({});
356
357 auto &cache = WaveformScale::Get(*wt);
358
359 if (cache.GetLastScaleType() != WaveformSettings::stLogarithmicDb &&
360 // When Logarithmic Amp happens, put that here
361 cache.GetLastScaleType() != -1)
362 {
363 // do a translation into the dB space
364 SetLastScaleType(cache, *wt);
365 SetLastdBRange(cache, *wt);
366 float sign = (min >= 0 ? 1 : -1);
367 if (min != 0.) {
368 min = (LINEAR_TO_DB(fabs(min)) + dBRange) / dBRange;
369 if (min < 0.0)
370 min = 0.0;
371 min *= sign;
372 }
373 sign = (max >= 0 ? 1 : -1);
374
375 if (max != 0.) {
376 max = (LINEAR_TO_DB(fabs(max)) + dBRange) / dBRange;
377 if (max < 0.0)
378 max = 0.0;
379 max *= sign;
380 }
381 cache.SetDisplayBounds(min, max);
382 }
383 else if (dBRange != lastdBRange) {
384 SetLastdBRange(cache, *wt);
385 // Remap the max of the scale
386 float newMax = max;
387
388 // This commented out code is problematic.
389 // min and max may be correct, and this code cause them to change.
390#ifdef ONLY_LABEL_POSITIVE
391 const float sign = (max >= 0 ? 1 : -1);
392 if (max != 0.) {
393
394 // Ugh, duplicating from TrackPanel.cpp
395#define ZOOMLIMIT 0.001f
396
397 const float extreme = LINEAR_TO_DB(2);
398 // recover dB value of max
399 const float dB = std::min(extreme, (float(fabs(max)) * lastdBRange - lastdBRange));
400 // find NEW scale position, but old max may get trimmed if the db limit rises
401 // Don't trim it to zero though, but leave max and limit distinct
402 newMax = sign * std::max(ZOOMLIMIT, (dBRange + dB) / dBRange);
403 // Adjust the min of the scale if we can,
404 // so the db Limit remains where it was on screen, but don't violate extremes
405 if (min != 0.)
406 min = std::max(-extreme, newMax * min / max);
407 }
408#endif
409 cache.SetDisplayBounds(min, newMax);
410 }
411
412 // Old code was if ONLY_LABEL_POSITIVE were defined.
413 // it uses the +1 to 0 range only.
414 // the enabled code uses +1 to -1, and relies on set ticks labelling knowing about
415 // the dB scale.
416#ifdef ONLY_LABEL_POSITIVE
417 if (max > 0) {
418#endif
419 int top = 0;
420 float topval = 0;
421 int bot = rect.height;
422 float botval = -dBRange;
423
424#ifdef ONLY_LABEL_POSITIVE
425 if (min < 0) {
426 bot = top + (int)((max / (max - min))*(bot - top));
427 min = 0;
428 }
429
430 if (max > 1) {
431 top += (int)((max - 1) / (max - min) * (bot - top));
432 max = 1;
433 }
434
435 if (max < 1 && max > 0)
436 topval = -((1 - max) * dBRange);
437
438 if (min > 0) {
439 botval = -((1 - min) * dBRange);
440 }
441#else
442 topval = -((1 - max) * dBRange);
443 botval = -((1 - min) * dBRange);
444 vruler->SetDbMirrorValue( dBRange );
445#endif
446 vruler->SetBounds(rect.x, rect.y + top, rect.x + rect.width, rect.y + bot - 1);
447 vruler->SetOrientation(wxVERTICAL);
448 vruler->SetRange(topval, botval);
449#ifdef ONLY_LABEL_POSITIVE
450 }
451 else
452 vruler->SetBounds(0.0, 0.0, 0.0, 0.0); // A.C.H I couldn't find a way to just disable it?
453#endif
454 vruler->SetFormat(&RealFormat::LogInstance());
455 vruler->SetLabelEdges(true);
456 vruler->SetUpdater(&LinearUpdater::Instance());
457 }
458 vruler->GetMaxSize( &wt->vrulerSize.first, &wt->vrulerSize.second );
459}
int min(int a, int b)
const wxChar * values
XO("Cut/Copy/Paste")
#define LINEAR_TO_DB(x)
Definition: MemoryX.h:561
#define DB_TO_LINEAR(x)
Definition: MemoryX.h:560
static Settings & settings()
Definition: TrackInfo.cpp:87
const int kGuard
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
std::shared_ptr< Subclass > AssignUIHandlePtr(std::weak_ptr< Subclass > &holder, const std::shared_ptr< Subclass > &pNew)
Definition: UIHandle.h:151
std::vector< double > LinearDBValues
static CustomUpdaterValue updater
static LinearDBValues majorValues
static LinearDBValues minorMinorValues
void RegenerateLinearDBValues(int dBRange, float min, float max, int height)
static LinearDBValues minorValues
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
std::shared_ptr< Track > FindTrack()
void SetData(RulerUpdater::Labels majorLabels, RulerUpdater::Labels minorLabels, RulerUpdater::Labels minorMinorLabels)
Definition: CustomUpdater.h:27
static const LinearUpdater & Instance()
void ModifyState(bool bWantsAutoSave)
static ProjectHistory & Get(AudacityProject &project)
static const RealFormat & LinearInstance()
Definition: RealFormat.cpp:14
static const RealFormat & LogInstance()
Definition: RealFormat.cpp:20
std::vector< Label > Labels
Definition: RulerUpdater.h:74
std::pair< int, int > vrulerSize
Definition: Track.h:387
static auto Channels(TrackType *pTrack) -> TrackIterRange< TrackType >
Definition: Track.h:1544
void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass) override
std::vector< UIHandlePtr > HitTest(const TrackPanelMouseState &state, const AudacityProject *pProject) override
A Track that contains audio waveform data.
Definition: WaveTrack.h:51
void SetLastDBRange(int range)
void SetDisplayBounds(float min, float max)
static WaveformScale & Get(const WaveTrack &track)
Mutative access to attachment even if the track argument is const.
void SetLastScaleType(int type)
static WaveformSettings & Get(const WaveTrack &track)
void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass) override
~WaveformVRulerControls() override
std::weak_ptr< WaveformVZoomHandle > mVZoomHandle
static void DoUpdateVRuler(const wxRect &rect, const WaveTrack *wt)
unsigned HandleWheelRotation(const TrackPanelMouseEvent &event, AudacityProject *pProject) override
static unsigned DoHandleWheelRotation(const TrackPanelMouseEvent &event, AudacityProject *pProject, WaveTrack *wt)
std::vector< UIHandlePtr > HitTest(const TrackPanelMouseState &state, const AudacityProject *) override
void UpdateRuler(const wxRect &rect) override
static void DoZoom(AudacityProject *pProject, WaveTrack *pTrack, WaveTrackViewConstants::ZoomActions ZoomKind, const wxRect &rect, int zoomStart, int zoomEnd, bool fixedMousePoint)
Namespace containing an enum 'what to do on a refresh?'.
Definition: RefreshCode.h:16
AUDACITY_DLL_API Ruler & ScratchRuler()
AUDACITY_DLL_API void DoDraw(TrackVRulerControls &controls, TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass)
void SetLastScaleType(WaveformScale &cache, const WaveTrack &track)
void SetLastdBRange(WaveformScale &cache, const WaveTrack &track)
fastfloat_really_inline void round(adjusted_mantissa &am, callback cb) noexcept
Definition: fast_float.h:2512
An array of these created by the Updater is used to determine what and where text annotations to the ...
Definition: RulerUpdater.h:60
std::optional< TranslatableString > text
Definition: RulerUpdater.h:67