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