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