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 because 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 if (const auto pChannel = FindWaveChannel()) {
96 auto result = std::make_shared<WaveformVZoomHandle>(
97 pChannel, st.rect, st.state.m_y );
98 result = AssignUIHandlePtr(mVZoomHandle, result);
99 results.push_back(result);
100 }
101 }
102
103 auto more = ChannelVRulerControls::HitTest(st, pProject);
104 std::copy(more.begin(), more.end(), std::back_inserter(results));
105
106 return results;
107}
108
109std::shared_ptr<WaveChannel> WaveformVRulerControls::FindWaveChannel()
110{
111 return FindChannel<WaveChannel>();
112}
113
115 const TrackPanelMouseEvent &evt, AudacityProject *pProject)
116{
117 using namespace RefreshCode;
118 const auto pChannel = FindWaveChannel();
119 if (!pChannel)
120 return RefreshNone;
121 return DoHandleWheelRotation(evt, pProject, *pChannel);
122}
123
124namespace {
126 WaveformScale &cache, const WaveChannel &wc)
127{
128 cache.SetLastDBRange(WaveformSettings::Get(wc).dBRange);
129}
130
132 WaveformScale &cache, const WaveChannel &wc)
133{
134 cache.SetLastScaleType(WaveformSettings::Get(wc).scaleType);
135}
136}
137
139 const TrackPanelMouseEvent &evt, AudacityProject *pProject, WaveChannel &wc)
140{
141 using namespace RefreshCode;
142 const wxMouseEvent &event = evt.event;
143
144 if (!(event.ShiftDown() || event.CmdDown()))
145 return RefreshNone;
146
147 // Always stop propagation even if the ruler didn't change. The ruler
148 // is a narrow enough target.
149 evt.event.Skip(false);
150
151 auto steps = evt.steps;
152
153 using namespace WaveChannelViewConstants;
155 auto &cache = WaveformScale::Get(wc);
156 const bool isDB = !settings.isLinear();
157 // Special cases for Waveform (logarithmic) dB only.
158 // Set the bottom of the dB scale but only if it's visible
159 if (isDB && event.ShiftDown() && event.CmdDown()) {
160 float min, max;
161 cache.GetDisplayBounds(min, max);
162 if (!(min < 0.0 && max > 0.0))
163 return RefreshNone;
164
165 float olddBRange = settings.dBRange;
166 if (steps < 0)
167 // Zoom out
168 settings.NextLowerDBRange();
169 else
170 settings.NextHigherDBRange();
171
172 float newdBRange = settings.dBRange;
173
174 // Is y coordinate within the rectangle half-height centered about
175 // the zero level?
176 const auto &rect = evt.rect;
177 const auto zeroLevel = cache.ZeroLevelYCoordinate(rect);
178 const bool fixedMagnification =
179 (4 * std::abs(event.GetY() - zeroLevel) < rect.GetHeight());
180
181 if (fixedMagnification) {
182 // Vary the db limit without changing
183 // magnification; that is, peaks and troughs move up and down
184 // rigidly, as parts of the wave near zero are exposed or hidden.
185 const float extreme = (LINEAR_TO_DB(2) + newdBRange) / newdBRange;
186 max = std::min(extreme, max * olddBRange / newdBRange);
187 min = std::max(-extreme, min * olddBRange / newdBRange);
188 SetLastdBRange(cache, wc);
189 cache.SetDisplayBounds(min, max);
190 }
191 }
192 else if (event.CmdDown() && !event.ShiftDown()) {
193 const int yy = event.m_y;
195 pProject, wc,
196 (steps < 0)
197 ? kZoomOut
198 : kZoomIn,
199 evt.rect, yy, yy, true);
200 }
201 else if (!event.CmdDown() && event.ShiftDown()) {
202 // Scroll some fixed number of pixels, independent of zoom level or track height:
203 static const float movement = 10.0f;
204 const int height = evt.rect.GetHeight();
205 {
206 float topLimit = 2.0;
207 if (isDB) {
208 const float dBRange = settings.dBRange;
209 topLimit = (LINEAR_TO_DB(topLimit) + dBRange) / dBRange;
210 }
211 const float bottomLimit = -topLimit;
212 float top, bottom;
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 cache.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 pChannel = FindWaveChannel();
241 if (!pChannel)
242 return;
243 DoUpdateVRuler(rect, *pChannel);
244}
245
247
249 const wxRect &rect, const WaveChannel &wc)
250{
252
253 // All waves have a ruler in the info panel
254 // The ruler needs a bevelled surround.
256 const float dBRange = settings.dBRange;
257
258 float min, max;
259 auto &cache = WaveformScale::Get(wc);
260 cache.GetDisplayBounds(min, max);
261 const float lastdBRange = cache.GetLastDBRange();
262 if (dBRange != lastdBRange)
263 SetLastdBRange(cache, wc);
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, wc);
276 SetLastdBRange(cache, wc);
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 if (cache.GetLastScaleType() != WaveformSettings::stLogarithmicDb &&
354 // When Logarithmic Amp happens, put that here
355 cache.GetLastScaleType() != -1)
356 {
357 // do a translation into the dB space
358 SetLastScaleType(cache, wc);
359 SetLastdBRange(cache, wc);
360 float sign = (min >= 0 ? 1 : -1);
361 if (min != 0.) {
362 min = (LINEAR_TO_DB(fabs(min)) + dBRange) / dBRange;
363 if (min < 0.0)
364 min = 0.0;
365 min *= sign;
366 }
367 sign = (max >= 0 ? 1 : -1);
368
369 if (max != 0.) {
370 max = (LINEAR_TO_DB(fabs(max)) + dBRange) / dBRange;
371 if (max < 0.0)
372 max = 0.0;
373 max *= sign;
374 }
375 cache.SetDisplayBounds(min, max);
376 }
377 else if (dBRange != lastdBRange) {
378 SetLastdBRange(cache, wc);
379 // Remap the max of the scale
380 float newMax = max;
381
382 // This commented out code is problematic.
383 // min and max may be correct, and this code cause them to change.
384#ifdef ONLY_LABEL_POSITIVE
385 const float sign = (max >= 0 ? 1 : -1);
386 if (max != 0.) {
387
388 // Ugh, duplicating from TrackPanel.cpp
389#define ZOOMLIMIT 0.001f
390
391 const float extreme = LINEAR_TO_DB(2);
392 // recover dB value of max
393 const float dB = std::min(
394 extreme, (float(fabs(max)) * lastdBRange - lastdBRange));
395 // find NEW scale position, but old max may get trimmed if the db limit rises
396 // Don't trim it to zero though, but leave max and limit distinct
397 newMax = sign * std::max(ZOOMLIMIT, (dBRange + dB) / dBRange);
398 // Adjust the min of the scale if we can,
399 // so the db Limit remains where it was on screen, but don't violate extremes
400 if (min != 0.)
401 min = std::max(-extreme, newMax * min / max);
402 }
403#endif
404 cache.SetDisplayBounds(min, newMax);
405 }
406
407 // Old code was if ONLY_LABEL_POSITIVE were defined.
408 // it uses the +1 to 0 range only.
409 // the enabled code uses +1 to -1, and relies on set ticks labelling knowing about
410 // the dB scale.
411#ifdef ONLY_LABEL_POSITIVE
412 if (max > 0) {
413#endif
414 int top = 0;
415 float topval = 0;
416 int bot = rect.height;
417 float botval = -dBRange;
418
419#ifdef ONLY_LABEL_POSITIVE
420 if (min < 0) {
421 bot = top + (int)((max / (max - min))*(bot - top));
422 min = 0;
423 }
424
425 if (max > 1) {
426 top += (int)((max - 1) / (max - min) * (bot - top));
427 max = 1;
428 }
429
430 if (max < 1 && max > 0)
431 topval = -((1 - max) * dBRange);
432
433 if (min > 0) {
434 botval = -((1 - min) * dBRange);
435 }
436#else
437 topval = -((1 - max) * dBRange);
438 botval = -((1 - min) * dBRange);
439 vruler->SetDbMirrorValue( dBRange );
440#endif
441 vruler->SetBounds(rect.x, rect.y + top, rect.x + rect.width, rect.y + bot - 1);
442 vruler->SetOrientation(wxVERTICAL);
443 vruler->SetRange(topval, botval);
444#ifdef ONLY_LABEL_POSITIVE
445 }
446 else
447 vruler->SetBounds(0.0, 0.0, 0.0, 0.0); // A.C.H I couldn't find a way to just disable it?
448#endif
449 vruler->SetFormat(&RealFormat::LogInstance());
450 vruler->SetLabelEdges(true);
451 vruler->SetUpdater(&LinearUpdater::Instance());
452 }
453 auto &size = ChannelView::Get(wc).vrulerSize;
454 vruler->GetMaxSize(&size.first, &size.second);
455}
const int kGuard
int min(int a, int b)
const wxChar * values
XO("Cut/Copy/Paste")
#define LINEAR_TO_DB(x)
Definition: MemoryX.h:338
#define DB_TO_LINEAR(x)
Definition: MemoryX.h:337
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: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
void Draw(TrackPanelDrawingContext &context, const wxRect &rect, unsigned iPass) 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
void SetLastDBRange(int range)
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
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