Lumiera 0.pre.04~rc.1
»edit your freedom«
Loading...
Searching...
No Matches
zoom-window-test.cpp
Go to the documentation of this file.
1/*
2 ZoomWindow(Test) - verify translation from model to screen coordinates
3
4 Copyright (C)
5 2018, Hermann Vosseler <Ichthyostega@web.de>
6
7  **Lumiera** is free software; you can redistribute it and/or modify it
8  under the terms of the GNU General Public License as published by the
9  Free Software Foundation; either version 2 of the License, or (at your
10  option) any later version. See the file COPYING for further details.
11
12* *****************************************************************/
13
61#include "lib/test/run.hpp"
64
65
66namespace stage{
67namespace model{
68namespace test {
69
71
72 namespace { // simplified notation for expected results...
73
74 inline Time _t(int secs) { return Time(FSecs(secs)); }
75 inline Time _t(int s, int div) { return Time(FSecs(s,div)); }
76 }
77
78
79
80
81 /*************************************************************************************/
92 class ZoomWindow_test : public Test
93 {
94
95 virtual void
96 run (Arg)
97 {
98 // Explanation of the notation used in this test...
99 CHECK (_t(10) == Time{FSecs(10)}); // Time point at t = 10sec
100 CHECK (_t(10,3) == Time{FSecs(10,3)}); // Time point at t = 10/3sec (fractional number)
101 CHECK (FSecs(10,3) == FSecs(10)/3); // fractional number arithmetics
102 CHECK (FSecs(10)/3 == 10_r/3); // _r is a user defined literal to denote 64-bit fractional
103 CHECK (Rat(10,3) == 10_r/3);
104 CHECK (Rat(10,3) == boost::rational<int64_t>(10,3)); // using Rat = boost::rational<int64_t>
105 CHECK (rational_cast<float> (10_r/3) == 3.3333333f); // rational_cast calculates division after type conversion
106
108 verify_setup();
113
115
125 }
126
127
129 void
131 {
132 ZoomWindow zoomWin;
133 CHECK (zoomWin.overallSpan() == TimeSpan(_t(0), FSecs(23)));
134 CHECK (zoomWin.visible() == TimeSpan(_t(0), FSecs(23)));
135 CHECK (zoomWin.px_per_sec() == 25);
136
137 zoomWin.nudgeMetric(+1);
138 CHECK (zoomWin.px_per_sec() == 50);
139 CHECK (zoomWin.visible() == TimeSpan(_t(23,4), FSecs(23,2)));
140 CHECK (zoomWin.overallSpan() == TimeSpan(_t(0), FSecs(23)));
141
142 zoomWin.nudgeVisiblePos(-1);
143 CHECK (zoomWin.px_per_sec() == 50);
144 CHECK (zoomWin.visible() == TimeSpan(_t(0), FSecs(23,2)));
145 CHECK (zoomWin.overallSpan() == TimeSpan(_t(0), FSecs(23)));
146 }
147
148
156 void
158 {
159 ZoomWindow win1;
160 CHECK (win1.overallSpan() == TimeSpan(_t(0), FSecs(23)));
161 CHECK (win1.visible() == win1.overallSpan());
162 CHECK (win1.pxWidth() == 25*23);
163 CHECK (win1.px_per_sec() == 25);
164
165 ZoomWindow win2{TimeSpan{_t(-1), _t(+1)}};
166 CHECK (win2.overallSpan() == TimeSpan(_t(-1), FSecs(2)));
167 CHECK (win2.visible() == win2.overallSpan());
168 CHECK (win2.pxWidth() == 25*2);
169 CHECK (win2.px_per_sec() == 25);
170
171 ZoomWindow win3{555};
172 CHECK (win3.overallSpan() == TimeSpan(_t(0), FSecs(23)));
173 CHECK (win3.visible() == win3.overallSpan());
174 CHECK (win3.pxWidth() == 555);
175 CHECK (win3.px_per_sec() == 555_r/23);
176
177 ZoomWindow win4{555, TimeSpan{_t(-10), _t(-5)}};
178 CHECK (win4.overallSpan() == TimeSpan(-Time(0,10), FSecs(5)));
179 CHECK (win4.visible() == win4.overallSpan());
180 CHECK (win4.pxWidth() == 555);
181 CHECK (win4.px_per_sec() == 111);
182 }
183
184
193 void
195 {
196 ZoomWindow win;
197 CHECK (win.overallSpan() == TimeSpan(_t(0), FSecs(23)));
198 CHECK (win.visible() == TimeSpan(_t(0), FSecs(23)));
199 CHECK (win.pxWidth() == 23*25);
200
201 win.calibrateExtension(25);
202 CHECK (win.overallSpan() == TimeSpan(_t(0), FSecs(23)));
203 CHECK (win.visible() == TimeSpan(_t(0), FSecs(1)));
204 CHECK (win.px_per_sec() == 25);
205 CHECK (win.pxWidth() == 25);
206
207 win.setOverallRange(TimeSpan{_t(-50), _t(50)});
208 CHECK (win.overallSpan() == TimeSpan(_t(-50), FSecs(100)));
209 CHECK (win.visible() == TimeSpan(_t(0), FSecs(1)));
210 CHECK (win.px_per_sec() == 25);
211 CHECK (win.pxWidth() == 25);
212
213 win.calibrateExtension(100);
214 CHECK (win.overallSpan() == TimeSpan(_t(-50), FSecs(100)));
215 CHECK (win.visible() == TimeSpan(_t(0), FSecs(4)));
216 CHECK (win.px_per_sec() == 25);
217 CHECK (win.pxWidth() == 100);
218
219 win.setRanges (TimeSpan{_t(-50), _t(10)}, TimeSpan{_t(-10), FSecs(10)});
220 CHECK (win.overallSpan() == TimeSpan(_t(-50), FSecs(60)));
221 CHECK (win.visible() == TimeSpan(_t(-10), _t(0)));
222 CHECK (win.px_per_sec() == 10);
223 CHECK (win.pxWidth() == 100);
224
225 win.calibrateExtension(500);
226 CHECK (win.overallSpan() == TimeSpan(_t(-50), FSecs(60)));
227 CHECK (win.visible() == TimeSpan(_t(-40), FSecs(50)));
228 CHECK (win.px_per_sec() == 10);
229 CHECK (win.pxWidth() == 500);
230
231 win.setOverallDuration (Duration{FSecs(30)});
232 CHECK (win.overallSpan() == TimeSpan(_t(-50), _t(-20)));
233 CHECK (win.visible() == TimeSpan(_t(-50), FSecs(30)));
234 CHECK (win.px_per_sec() == 500_r/30);
235 CHECK (win.pxWidth() == 500);
236
237 win.calibrateExtension(300);
238 CHECK (win.overallSpan() == TimeSpan(_t(-50), _t(-20)));
239 CHECK (win.visible() == TimeSpan(_t(-50), FSecs(30)*3/5));
240 CHECK (win.px_per_sec() == 500_r/30);
241 CHECK (win.pxWidth() == 300);
242 }
243
244
251 void
253 {
254 ZoomWindow win{1280, TimeSpan{_t(0), FSecs(64)}};
255 CHECK (win.px_per_sec() == 20);
256
257 win.nudgeMetric(+1);
258 CHECK (win.overallSpan() == TimeSpan(_t(0), _t(64)));
259 CHECK (win.visible() == TimeSpan(_t(32,2), FSecs(32)));
260 CHECK (win.px_per_sec() == 40);
261 CHECK (win.pxWidth() == 1280);
262
263 win.setVisiblePos(0.0);
264 CHECK (win.visible() == TimeSpan(_t(0), FSecs(32))); // zoom window moved to left side of overall range
265
266 win.nudgeMetric(+15);
267 CHECK (win.overallSpan() == TimeSpan(_t(0), _t(64)));
268 CHECK (win.visible() == TimeSpan(_t(0), FSecs(32,32768) +MICRO_TICK));
269 CHECK (win.visible().start() == _t(0)); // now anchor position is at left bound
270 CHECK (win.visible().end() == TimeValue(977)); // length was rounded up to the next grid position
271 CHECK (Time{FSecs(32,32768)+MICRO_TICK} == TimeValue(977)); // (preferring slightly larger window unless perfect fit)
272 CHECK (Time{FSecs(32,32768) } == TimeValue(976));
273 // scale factor calculated back from actual window width
274 CHECK (win.px_per_sec() == 1280_r/977 * Time::SCALE);
275 CHECK (win.pxWidth() == 1280);
276 // Note: already getting close to the time grid...
277 CHECK (Time(FSecs(32,32768)) == TimeValue(976));
278 CHECK (rational_cast<double> (32_r/32768 * Time::SCALE) == 976.5625);
279
280 win.nudgeMetric(+1);
281 CHECK (win.overallSpan() == TimeSpan(_t(0), _t(64)));
282 CHECK (win.px_per_sec() == ZOOM_MAX_RESOLUTION); // further zoom has been capped at 2px per µ-tick
283 CHECK (win.visible() == TimeSpan(_t(0), FSecs(1280_r/ZOOM_MAX_RESOLUTION)));
284 CHECK (win.pxWidth() == 1280);
285
286 win.nudgeMetric(+1);
287 CHECK (win.px_per_sec() == ZOOM_MAX_RESOLUTION);
288 win.setMetric(10*ZOOM_MAX_RESOLUTION);
289 CHECK (win.px_per_sec() == ZOOM_MAX_RESOLUTION);
290
291 // so this is the deepest zoom possible....
292 CHECK (win.visible().duration() == TimeValue(640));
293 CHECK (TimeValue{640} == Time{Rat(1280)/ZOOM_MAX_RESOLUTION});
294
295 // and this the absolutely smallest possible zoom window
296 win.calibrateExtension(2);
297 CHECK (win.overallSpan() == TimeSpan(_t(0), _t(64)));
298 CHECK (win.visible().duration() == TimeValue(1));
299 CHECK (win.px_per_sec() == ZOOM_MAX_RESOLUTION);
300 CHECK (win.pxWidth() == 2);
301
302 win.calibrateExtension(1);
303 CHECK (win.visible().duration() == TimeValue(1)); // window is guaranteed to be non-empty
304 CHECK (win.px_per_sec() == ZOOM_MAX_RESOLUTION / 2); // zoom scale has thus been lowered to prevent window from vanishing
305 CHECK (win.pxWidth() == 1);
306
307 win.calibrateExtension(1280);
308 CHECK (win.visible().duration() == TimeValue(1280));
309 CHECK (win.visible().duration() == Duration{1280*MICRO_TICK});
310 CHECK (win.px_per_sec() == ZOOM_MAX_RESOLUTION / 2);
311 CHECK (win.pxWidth() == 1280);
312 CHECK (win.overallSpan() == TimeSpan(_t(0), _t(64)));
313
314 win.nudgeMetric(-5);
315 CHECK (win.visible().duration() == Duration{32 * 1280*MICRO_TICK});
316 CHECK (win.px_per_sec() == ZOOM_MAX_RESOLUTION / 64);
317 CHECK (win.pxWidth() == 1280);
318 CHECK (win.overallSpan() == TimeSpan(_t(0), _t(64)));
319
320 win.nudgeMetric(-12);
321 CHECK (win.visible() == win.overallSpan()); // zoom out stops at full canvas size
322 CHECK (win.overallSpan() == TimeSpan(_t(0), _t(64)));
323 CHECK (win.px_per_sec() == 20);
324 CHECK (win.pxWidth() == 1280);
325
326 // but canvas can be forcibly extended by »reverse zooming«
327 win.expandVisibleRange (TimeSpan{_t(60), _t(62)}); // zoom such as to bring current window at given relative position
328 CHECK (win.px_per_sec() == 20_r/64*2); // scale thus adjusted to reduce 64 sec to 2 sec (scale can be fractional!)
329 CHECK (win.visible().duration() == _t(64 * 32)); // zoom window has been inversely expanded by factor 64/2 == 32
330 CHECK (win.visible() == win.overallSpan()); // zoom fully covers the expanded canvas
331 CHECK (win.overallSpan() == TimeSpan(_t(-1920), _t(128))); // and overall canvas has been expanded to embed the previous window
332 CHECK (win.overallSpan().duration() == _t(2048)); // ... at indicated relative position (2sec ⟼ 64sec, one window size before end)
333
334 // metric can be explicitly set (e.g. 5px per sound sample)
335 win.setMetric (5 / (1_r/44100));
336 CHECK (win.pxWidth() == 1280);
337 CHECK (win.px_per_sec() <= 5*44100); // zoom scale was slightly reduced to match exact pixel width
338 CHECK (win.px_per_sec() >= 5*44100 - 1);
339 CHECK (win.visible().duration() == Duration{1280_r/(5*44100) +MICRO_TICK});
340 CHECK (win.visible().duration() == Duration{1280_r/win.px_per_sec()});
341 CHECK (win.overallSpan().duration() == _t(2048));
342 }
343
344
346 void
348 {
349 ZoomWindow win{1280, TimeSpan{_t(0), FSecs(64)}};
350 CHECK (win.visible() == win.overallSpan());
351 CHECK (win.px_per_sec() == 20);
352
353 win.setVisibleDuration (Duration{FSecs(23,30)});
354 CHECK (win.visible().duration() == _t(23,30));
355 CHECK (win.visible().start() == _t(64,2) - _t(23,30*2)); // when zooming down from full range, zoom anchor is window centre
356 CHECK (win.px_per_sec() == 1280_r/_FSecs(_t(23,30))); // scale factor slightly adjusted to match exact pixel width
357 CHECK (win.pxWidth() == 1280);
358
359 win.setVisibleRange (TimeSpan{_t(12), FSecs(16)});
360 CHECK (win.visible() == TimeSpan(_t(12), _t(12+16)));
361 CHECK (win.overallSpan() == TimeSpan(_t(0), _t(64)));
362 CHECK (win.px_per_sec() == 1280_r/16);
363 CHECK (win.pxWidth() == 1280);
364
365 win.setVisiblePos(_t(12)); // bring a specific position into sight
366 CHECK (win.visible().start() < _t(12)); // window is placed such as to enclose this desired position
367 CHECK (win.visible().duration() == _t(16)); // window size and metric not changed
368 CHECK (win.overallSpan() == TimeSpan(_t(0), _t(64)));
369 CHECK (win.px_per_sec() == 1280_r/16);
370 CHECK (win.pxWidth() == 1280);
371
372 win.setVisiblePos(0.80); // positioning relatively within overall canvas
373 CHECK (win.visible().start() < Time{FSecs(64)*8/10}); // window will enclose the desired anchor position
374 CHECK (win.visible().end() > Time{FSecs(64)*8/10});
375 CHECK (win.px_per_sec() == 1280_r/16);
376 CHECK (win.pxWidth() == 1280);
377
378 // manipulate canvas extension explicitly
379 win.setOverallDuration (Duration{FSecs(3600)});
380 CHECK (win.overallSpan() == TimeSpan(_t(0), _t(3600)));
381 CHECK (win.px_per_sec() == 1280_r/16);
382 CHECK (win.pxWidth() == 1280);
383 CHECK (win.visible().duration() == _t(16)); // window position and size not affected
384 CHECK (win.visible().start() < Time{FSecs(64)*8/10});
385 CHECK (win.visible().end() > Time{FSecs(64)*8/10});
386
387 // reposition nominal canvas anchoring
388 win.setOverallRange (TimeSpan{_t(-64), _t(-32)});
389 CHECK (win.overallSpan() == TimeSpan(_t(-64), FSecs(32))); // canvas nominally covers a completely different time range now
390 CHECK (win.px_per_sec() == 1280_r/16); // metric is retained
391 CHECK (win.pxWidth() == 1280);
392 CHECK (win.visible() == TimeSpan(_t(-32-16), FSecs(16))); // window scrolled left to remain within canvas
393
394 win.setOverallStart (_t(100));
395 CHECK (win.overallSpan() == TimeSpan(_t(100), FSecs(32)));
396 CHECK (win.visible() == TimeSpan(_t(100), FSecs(16))); // window scrolled right to remain within canvas
397 CHECK (win.px_per_sec() == 1280_r/16); // metric is retained
398
399 win.setOverallRange (TimeSpan{_t(50), _t(52)});
400 CHECK (win.overallSpan() == TimeSpan(_t(50), FSecs(2)));
401 CHECK (win.visible() == TimeSpan(_t(50), FSecs(2))); // window truncated to fit into canvas
402 CHECK (win.px_per_sec() == 1280_r/2); // metric need to be adjusted
403 CHECK (win.pxWidth() == 1280);
404 }
405
406
408 void
410 {
411 ZoomWindow win{1280, TimeSpan{_t(0), FSecs(16)}};
412 CHECK (win.visible() == win.overallSpan());
413 CHECK (win.visible() == TimeSpan(_t(0), FSecs(16)));
414 CHECK (win.px_per_sec() == 80);
415
416 win.nudgeVisiblePos(+1);
417 CHECK (win.visible() == TimeSpan(_t(8), FSecs(16))); // window shifted forward by half a page
418 CHECK (win.overallSpan() == TimeSpan(_t(0), FSecs(16+8))); // canvas expanded accordingly
419 CHECK (win.px_per_sec() == 80); // metric is retained
420 CHECK (win.pxWidth() == 1280);
421
422 win.nudgeVisiblePos(-3);
423 CHECK (win.visible() == TimeSpan(_t(-16), FSecs(16))); // window shifted backwards by three times half window size
424 CHECK (win.overallSpan() == TimeSpan(_t(-16), FSecs(16+8+16))); // canvas is always expanded accordingly, never shrinked
425 CHECK (win.px_per_sec() == 80); // metric is retained
426 CHECK (win.pxWidth() == 1280);
427
428 win.setVisiblePos(0.50);
429 CHECK (win.visible() == TimeSpan(_t((40/2-16) -8), FSecs(16))); // window positioned to centre of canvas
430 CHECK (win.visible().start() == _t(-4)); // (canvas was already positioned asymmetrically)
431
432 win.setVisiblePos(-0.50);
433 CHECK (win.visible() == TimeSpan(_t(-16-40/2), FSecs(16))); // relative positioning not limited at lower bound
434 CHECK (win.visible().start() == _t(-36)); // (causing also further expansion of canvas)
435 win.setVisiblePos(_t(200)); // absolute positioning likewise not limited
436 CHECK (win.visible() == TimeSpan(_t(200-16), FSecs(16))); // but anchored according to relative anchor pos
437 CHECK (win.px_per_sec() == 80); // metric retained
438 CHECK (win.pxWidth() == 1280);
439
440 win.setVisibleRange(TimeSpan{_t(-200), FSecs(32)}); // but explicit positioning outside of canvas is possible
441 CHECK (win.overallSpan() == TimeSpan(_t(-200), _t(200))); // ...and will expand canvas
442 CHECK (win.visible() == TimeSpan(_t(-200), FSecs(32)));
443 CHECK (win.px_per_sec() == 40);
444 CHECK (win.pxWidth() == 1280);
445 }
446
447
449 void
451 {
452 ZoomWindow win{100, TimeSpan{_t(0), FSecs(4)}};
453 CHECK (win.overallSpan() == TimeSpan(_t(0), _t(4)));
454 CHECK (win.visible() == TimeSpan(_t(0), _t(4)));
455 CHECK (win.px_per_sec() == 25);
456 CHECK (win.pxWidth() == 100);
457
458 bool notified{false};
459 win.nudgeMetric(+1);
460 CHECK (not notified);
461 CHECK (win.px_per_sec() == 50);
462 CHECK (win.visible().duration() == _t(2));
463
464 win.attachChangeNotification([&](){ notified = true; });
465 CHECK (not notified);
466 CHECK (win.px_per_sec() == 50);
467 win.nudgeMetric(+1);
468 CHECK (win.px_per_sec() == 100);
469 CHECK (notified);
470
471 notified = false;
472 CHECK (win.visible().start() == _t(3,2));
473 win.nudgeVisiblePos(+1);
474 CHECK (win.visible().start() == _t(2));
475 CHECK (notified);
476
477 notified = false;
478 CHECK (win.overallSpan() == TimeSpan(_t(0), _t(4)));
479 win.setOverallRange(TimeSpan(_t(-4), _t(4)));
480 CHECK (win.overallSpan() == TimeSpan(_t(-4), _t(4)));
481 CHECK (notified);
482
483 notified = false;
484 CHECK (win.pxWidth() == 100);
485 win.calibrateExtension(200);
486 CHECK (win.pxWidth() == 200);
487 CHECK (win.px_per_sec() == 100);
488 CHECK (notified);
489
490 notified = false;
491 bool otherTriger{false};
492 ZoomWindow wuz{10, TimeSpan{_t(0), FSecs(1)}};
493 wuz.attachChangeNotification([&](){ otherTriger = true; });
494 CHECK (wuz.visible().start() == _t(0));
495 CHECK (not notified);
496 CHECK (not otherTriger);
497 wuz.nudgeVisiblePos(-1);
498 CHECK (not notified);
499 CHECK (otherTriger);
500 CHECK (wuz.visible().start() == _t(-1,2));
501
502 otherTriger = false;
503 CHECK (not notified);
504 win.nudgeMetric(+1);
505 CHECK (not otherTriger);
506 CHECK (notified);
507 CHECK (win.px_per_sec() == 200);
508 CHECK (wuz.px_per_sec() == 10);
509
510 notified = false;
511 otherTriger = false;
512 win.detachChangeNotification();
513 win.nudgeMetric(+1);
514 CHECK (not notified);
515 CHECK (win.px_per_sec() == 400);
516
517 wuz.nudgeMetric(+1);
518 CHECK (not notified);
519 CHECK (otherTriger);
520 CHECK (win.px_per_sec() == 400);
521 CHECK (wuz.px_per_sec() == 20);
522 }
523
524
526 void
528 {
529 ZoomWindow win{0, TimeSpan{_t(0), FSecs(0)}};
530 CHECK (win.visible() == TimeSpan(_t(0), _t(23))); // uses DEFAULT_CANVAS instead of empty TimeSpan
531 CHECK (win.px_per_sec() == 25); // falls back on default initial zoom factor
532 CHECK (win.pxWidth() == 575); // allocates pixels in accordance to default
533
534 win.setOverallDuration(Duration{FSecs(50)});
535 win.setVisibleDuration(Duration{FSecs(0)});
536 CHECK (win.overallSpan() == TimeSpan(_t(0), _t(50)));
537 CHECK (win.visible() == TimeSpan(_t(0), _t(23))); // falls back to DEFAULT_CANVAS size
538 CHECK (win.pxWidth() == 575); // allocates pixels in accordance to default
539
540 win.calibrateExtension(0);
541 CHECK (win.px_per_sec() == 25); // stays at default zoom factor
542 CHECK (win.pxWidth() == 1); // retains 1px window size
543 CHECK (win.visible().duration() == _t(1,25)); // visible window has thus 1/25s duration
544 }
545
546
548 void
550 {
551 ZoomWindow win{1};
552 win.setVisibleDuration(Duration{FSecs(1,25)});
553 win.setOverallRange(TimeSpan(_t(10), _t(0))); // set an "reversed" overall time range
554 CHECK (win.overallSpan() == TimeSpan(_t(0), _t(10))); // range has been re-oriented forward
555 CHECK (win.visible().duration() == Time(40,0));
556 CHECK (win.px_per_sec() == 25);
557 CHECK (win.pxWidth() == 1);
558
559 CHECK (TimeSpan(_t(10), _t(0)).duration() == Duration(FSecs(10))); // TimeSpan is always properly oriented by construction
560 }
561
562
573 void
575 {
576 Rat poison{_raw(Time::MAX)-101010101010101010, _raw(Time::MAX)+23};
577 CHECK (poison == 206435633551724850_r/307445734561825883);
578 CHECK (2_r/3 < poison and poison < 1); // looks innocuous...
579 CHECK (poison + Time::SCALE < 0); // simple calculations fail due to numeric overflow
580 CHECK (poison * Time::SCALE < 0);
581 CHECK (-6 == rational_cast<raw_time_64>(poison * Time::SCALE)); // naive conversion to µ-ticks would lead to overflow
582 CHECK (671453 == _raw(Time(FSecs(poison)))); // however the actual conversion routine is safeguarded
583 CHECK (671453.812f == rational_cast<float>(poison)*Time::SCALE);
584
585 using util::ilog2;
586 CHECK (40 == ilog2 (LIM_HAZARD)); // LIM_HAZARD is based on MAX_INT / Time::Scale
587 CHECK (57 == ilog2 (poison.numerator())); // use the leading bit position as size indicator
588 CHECK (58 == ilog2 (poison.denominator())); // use the maximum of numerator or denominator bit position
589 CHECK (58-40 == 18); // everything beyond LIM_HAZARD counts as "toxic"
590
591 int toxicity = toxicDegree (poison);
592 CHECK (toxicity == 18);
593 int64_t quant = poison.denominator() >> toxicity; // shift away the excess toxic LSB
594 CHECK (quant == 1172812402961);
595 CHECK (ilog2 (quant) == ilog2 (LIM_HAZARD));
596 Rat detoxed = util::reQuant(poison, quant); // and use this "shortened" denominator for re-quantisation
597 CHECK (detoxed == 787489446837_r/1172812402961); // the resulting fraction uses way smaller numbers
598 CHECK (0.671453834f == rational_cast<float> (poison)); // but yields approximately the same effective value
599 CHECK (0.671453834f == rational_cast<float> (detoxed));
600
601 CHECK (detoxed+Time::SCALE == 1172813190450446837_r/1172812402961); // result: usual calculations without failure
602 CHECK (Time(FSecs(detoxed)) > Time::ZERO); // can convert re-quantised number to µ-ticks
603 CHECK (671453 == _raw(Time(FSecs(detoxed))));
604 // and resulting µ-ticks will be effectively the same
605 CHECK (1906 == _raw(TimeValue(1280 / rational_cast<long double>(poison))));
606 CHECK (1906 == _raw(TimeValue(1280 / rational_cast<long double>(detoxed))));
607 }
608
609
616 void
618 {
619 ZoomWindow win{};
620 CHECK (win.visible() == win.overallSpan()); // by default window spans complete canvas
621 CHECK (win.visible().duration() == _t(23)); // ...and has just some handsome extension
622 CHECK (win.px_per_sec() == 25);
623 CHECK (win.pxWidth() == 575);
624
625 Rat poison{_raw(Time::MAX)-101010101010101010, _raw(Time::MAX)+23};
626 CHECK (0 < poison and poison < 1);
627
628 /*--Test-1-----------*/
629 win.setMetric (poison); // inject an evil new value for the metric
630 CHECK (win.visible() == win.overallSpan()); // however, nothing happens
631 CHECK (win.visible().duration() == _t(23)); // since the window is confined to overall canvas size
632 CHECK (win.visible() == TimeSpan(_t(0), _t(23))); // Note: this calculation is fail-safe
633 CHECK (win.px_per_sec() == 25);
634 CHECK (win.pxWidth() == 575);
635
636 win.setOverallDuration(Duration(Time::MAX)); // second test: expand canvas to allow for actual adjustment
637 CHECK (win.overallSpan().duration() == TimeValue{307445734561825860}); // now canvas has ample size (half the possible maximum size)
638 CHECK (win.overallSpan().duration() == Time::MAX);
639 CHECK (win.visible().duration() == _t(23)); // while the visible part remains unaltered
640
641 /*--Test-2-----------*/
642 win.setMetric (poison); // Now attempt again to poison the zoom calculations...
643 CHECK (win.overallSpan().duration() == Time::MAX); // overall canvas unchanged
644 CHECK (win.visible().duration() == TimeValue{856350691}); // visible window expanded (a zoom-out, as required)
645 CHECK (win.px_per_sec() == Rat{win.pxWidth()} / _FSecs(win.visible().duration()));
646 float approxPoison = rational_cast<float> (poison); // the provided (poisonous) metric factor...
647 CHECK (approxPoison == 0.671453834f); // ...is approximately the same...
648 float approxNewMetric = rational_cast<float> (win.px_per_sec()); // ...as the actual new metric factor we got
649 CHECK (approxNewMetric == 0.671453893f);
650 CHECK (win.px_per_sec() != poison); // but it is not exactly the same
651 CHECK (win.px_per_sec() < poison); // rather, it is biased towards slightly smaller values
652
653 Rat poisonousDuration = win.pxWidth() / poison; // Now, to demonstrate this "poison" was actually dangerous
654 CHECK (poisonousDuration == 7071251894921995309_r/8257425342068994); // ...when we attempt to calculate the new duration directly....
655 CHECK (poisonousDuration * Time::SCALE < 0); // ...then a conversion to TimeValue will cause integer wrap
656 CHECK(856.350708f == rational_cast<float> (poisonousDuration)); // yet numerically the duration actually established is almost the same
657 CHECK(856.350708f == rational_cast<float> (_FSecs(win.visible().duration())));
658 CHECK (win.px_per_sec() == 575000000_r/856350691); // the new metric however is comprised of sanitised fractional numbers
659 CHECK (win.pxWidth() == 575); // and the existing pixel width was not changed
660
661 CHECK (win.overallSpan().start() == Time::ZERO);
662 CHECK (win.overallSpan().duration() == TimeValue{307445734561825860});
663 CHECK (win.visible().duration() == TimeValue{856350691});
664
665 /*--Test-3-----------*/
666 win.setVisiblePos (poison); // Yet another way to sneak in our toxic value...
667 CHECK (win.overallSpan().start() == Time::ZERO);
668 CHECK (win.overallSpan().duration() == TimeValue{307445734561825860}); // However, all base values turn out unaffected
669 CHECK (win.visible().duration() == TimeValue{856350691});
670
671 TimeValue targetPos{raw_time_64(_raw(win.overallSpan().duration()) // based on the overall span...
672 * rational_cast<double> (poison))}; // the given toxic factor would point at that target position
673
674 CHECK (targetPos == TimeValue{206435633551724864});
675 CHECK (win.visible().start() == TimeValue{206435633106265625}); // the visible window has been moved to enclose this target
676 CHECK (win.visible().end() == TimeValue{206435633962616316});
677 CHECK (win.visible().start() < targetPos);
678 CHECK (win.visible().end() > targetPos);
679
680 CHECK (win.px_per_sec() == 575000000_r/856350691); // metric and pixel width are retained
681 CHECK (win.pxWidth() == 575);
682
683
684 win.setOverallRange(TimeSpan{Time::MAX, Offset{TimeValue(23)}}); // preparation for Test-4 : shift canvas to end of time
685 CHECK (win.overallSpan() == win.visible()); // consequence: window has been capped to canvas size
686 CHECK (win.overallSpan().start() == TimeValue{307445734561825572}); // window now also located at extreme values
687 CHECK (win.overallSpan().end() == TimeValue{307445734561825860});
688 CHECK (win.overallSpan().duration() == TimeValue{288}); // window (and canvas) were expanded to comply to maximum zoom factor
689 CHECK (win.px_per_sec() == 17968750_r/9); // zoom factor was then slightly reduced to match next pixel boundary
690 CHECK (win.pxWidth() == 575); // established pixel size was retained
691
692 /*--Test-4-----------*/
693 win.setVisiblePos(Time{Time::MIN + TimeValue(13)}); // Test: implicitly provoke poisonous factor through extreme offset
694 CHECK (win.visible().start() == Time::MIN + TimeValue(13)); // even while this position is far off, window start was aligned to it
695 CHECK (win.visible().end() == win.visible().start() + TimeValue{288});
696 CHECK (win.visible().duration() == TimeValue{288});
697
698 CHECK (win.overallSpan().start() == win.visible().start()); // canvas start at window start
699 CHECK (win.overallSpan().end() == TimeValue{307445734561825860}); // canvas end not changed
700 CHECK (_raw(win.overallSpan().duration()) == 614891469123651707); // canvas size was expanded to encompass changed window position
701 CHECK (win.px_per_sec() == 17968750_r/9); // zoom factor not changed
702 CHECK (win.pxWidth() == 575); // established pixel size retained
703 }
704
705
709 void
711 {
712 /*--Test-1-----------*/
713 ZoomWindow win{3, TimeSpan{_t(-1,2), _t(1,2)}}; // setup ZoomWindow to very small pixel size (3px)
714 CHECK (win.overallSpan().duration() == _t(1));
715 CHECK (win.px_per_sec() == 3_r/1);
716 CHECK (win.pxWidth() == 3);
717 win.setOverallRange(TimeSpan{Time::MIN, Time::MAX}); // ...and then also expand canvas to maximal size
718 CHECK (_raw(win.overallSpan().duration()) == 614891469123651720);
719 CHECK (_raw(win.visible().duration()) == 1000000);
720 CHECK (win.px_per_sec() == 3_r/1);
721 CHECK (win.pxWidth() == 3);
722
723 /*--Test-2-----------*/
724 Rat bruteZoom{3_r/(int64_t{1}<<60)};
725 win.setMetric (bruteZoom); // zoom out beyond what is possible and to a toxic factor
726
727 CHECK (_raw(win.overallSpan().duration()) == 614891469123651720); // canvas size not changed
728 CHECK (_raw(win.visible().duration()) == 3298534883328000); // window was expanded,
729 CHECK (_raw(win.visible().duration()) < int64_t{1}<<60 ); // ...but not as much as demanded
730 CHECK (_raw(win.visible().duration()) == 3* LIM_HAZARD*1000); // In fact it was capped at a built-in limit based on pixel size,
731 // to prevent formation of dangerous numbers within metric calculations
732 CHECK (win.visible().start() == - win.visible().end()); // window has been expanded symmetrically to existing position
733 CHECK (win.px_per_sec() > bruteZoom); // the actual zoom factor also reflects the applied limitation,
734 CHECK (win.px_per_sec() == 125_r/137438953472); // to ensure the denominator does not exceed LIM_HAZARD
735 CHECK (win.px_per_sec() == 1000_r/LIM_HAZARD);
736 CHECK (win.px_per_sec() == 3 / _FSecs(win.visible().duration())); // and this value also conforms with the pixel size and window duration
737 CHECK (win.pxWidth() == 3);
738
739 /*--Test-3-----------*/
740 win.setMetric (5_r/std::numeric_limits<int64_t>::max()); // same limiting applies to even more nasty values
741 CHECK (_raw(win.visible().duration()) == 3298534883328000); // still unchanged at limit
742 CHECK (win.px_per_sec() == 125_r/137438953472);
743 CHECK (win.pxWidth() == 3);
744
745 /*--Test-4-----------*/
746 win.setMetric (1001_r/LIM_HAZARD); // but zooming in more than that limit will be honored
747 CHECK (_raw(win.visible().duration()) == 3295239643684316); // ...window now slightly reduced in size
748 CHECK (_raw(win.visible().duration()) < 3 * LIM_HAZARD*1000);
749 CHECK (win.px_per_sec() > 1000_r/LIM_HAZARD);
750 CHECK (win.px_per_sec() == 1001_r/LIM_HAZARD); // (this is what was requested)
751 CHECK (win.px_per_sec() == 1001_r/1099511627776);
752 CHECK (win.pxWidth() == 3);
753
754 /*--Test-5-----------*/
755 win.setMetric (1000_r/LIM_HAZARD * 1024_r/1023); // likewise zooming back out slightly below limit is possible
756 CHECK (_raw(win.visible().duration()) == 3295313657856000); // ...window now again slightly increased, but not at maximum size
757 CHECK (_raw(win.visible().duration()) < 3 * LIM_HAZARD*1000);
758 CHECK (win.px_per_sec() > 1000_r/LIM_HAZARD);
759 CHECK (win.px_per_sec() < 1001_r/LIM_HAZARD);
760 CHECK (win.px_per_sec() == 1000_r/LIM_HAZARD * 1024_r/1023); // zoom factor precisely reproduced in this case
761 CHECK (win.px_per_sec() == 125_r/137304735744);
762 CHECK (win.pxWidth() == 3);
763
764 /*--Test-6-----------*/
765 win.setMetric (1001_r/(LIM_HAZARD-3)); // however, setting »poisonous« factors close below the limit...
766 CHECK (win.px_per_sec() > 1001_r/LIM_HAZARD); // results in a sanitised (simplified) zoom factor
767 CHECK (win.px_per_sec() < 1002_r/LIM_HAZARD);
768 CHECK (1001_r/(LIM_HAZARD-3) == 77_r/84577817521); // This case is especially interesting, since the initial factor isn't »toxic«,
769 // but the resulting duration is not µ-grid aligned, and after fixing that,
770 CHECK (3_r/3295239643675325 * Time::SCALE == 120000_r/131809585747013);// the resulting zoom factor is comprised of very large numbers,
771 CHECK (win.px_per_sec() == 2003_r/2199023255552); // ...which are then simplified and adjusted...
772 CHECK (win.pxWidth() == 3); // ... to match also the pixel size
773
774 CHECK (_raw(Duration{3_r/(77_r/84577817521)}) == 3295239643675324); // This is the duration we'd expect (truncated down)
775 CHECK (_raw(win.visible().duration()) == 3295239643675325); // ...this is the duration we actually get
776 CHECK (_raw(Duration{3_r/win.px_per_sec()}) == 3293594491590614); // Unfortunately, calculating back from the smoothed zoom-metric
777 // .. would yield a duration way off, with an relative error < 1‰
778 CHECK (2003.0f/2002 - 1 == 0.000499486923f); // The reason for this relative error is the small numerator of 2002
779 // (2002 is increased to 2003 to get above 3px)
780
781 /*--Test-7-----------*/
782 win.calibrateExtension (1'000'000'000); // implicit drastic zoom-out by increasing the number of pixels
783 CHECK (win.pxWidth() < 1'000'000'000); // however: this number is capped at a fixed maximum
784 CHECK (win.pxWidth() == MAX_PX_WIDTH); // (which „should be enough“ for the time being...)
785 CHECK (win.px_per_sec() == 89407_r/549755813888); // the zoom metric has been adapted, but to a sanitised value
786 CHECK (win.px_per_sec() > Rat{MAX_PX_WIDTH} /MAX_TIMESPAN);
787 CHECK (win.px_per_sec() < Rat{MAX_PX_WIDTH+1}/MAX_TIMESPAN);
788
789 CHECK (_raw(win.overallSpan().duration()) == 614891469123651720); // overall canvas duration not changed
790 CHECK (_raw(win.visible().duration()) == 614891469123651720); // window duration now expanded to the maximum possible value
791 CHECK (win.overallSpan().end() == TimeValue{ 307445734561825860}); // window now spans the complete time domain
792 CHECK (win.visible().end() == TimeValue{ 307445734561825860});
793 CHECK (win.visible().start() == TimeValue{-307445734561825860});
794
795 // Note: these parameters build up to really »poisonous« values....
796 CHECK (MAX_PX_WIDTH / _FSecs(win.visible().duration()) == 2500000000_r/15372286728091293);
797 CHECK (MAX_PX_WIDTH * 1000000_r/614891469123651720 == 2500000000_r/15372286728091293);
798 CHECK (win.px_per_sec() * _FSecs(win.visible().duration()) < 0); // we can't even calculate the resulting pxWidth() naively
799 CHECK (rational_cast<float>(win.px_per_sec()) // ...while effectively these values are still correct
800 * rational_cast<float>(_FSecs(win.visible().duration())) == 100000.031f);
801 CHECK (rational_cast<float>(MAX_PX_WIDTH*1000000_r/614891469123651720) == 1.62630329e-07f); // theoretical value
802 CHECK (rational_cast<float>(win.px_per_sec()) == 1.62630386e-07f); // value actually chosen
803 CHECK (win.px_per_sec() == 89407_r/549755813888);
804
805 /*--Test-8-----------*/
806 win.setMetric (bruteZoom); // And now put one on top by requesting excessive zoom-out!
807 CHECK (_raw(win.overallSpan().duration()) == 614891469123651720); // overall canvas duration not changed
808 CHECK (_raw(win.visible().duration()) == 614891469123651720); // window duration was capped precisely at DURATION_MAX
809 CHECK (win.px_per_sec() == 89407_r/549755813888); // zoom factor and now hitting again the minimum limit
810 CHECK (MAX_PX_WIDTH /(614891469123651720_r/Time::SCALE) == 2500000000_r/15372286728091293); // (this would be the exact factor)
811 CHECK (2500000000_r/15372286728091293 < 89407_r/549755813888); // zoom factor (again) numerically sanitised
812 CHECK (win.pxWidth() == MAX_PX_WIDTH); // pixel count unchanged at maximum
813 }
814
815
819 void
821 {
822 /*--Test-1-----------*/
823 ZoomWindow win{559, TimeSpan{Time::MAX, Duration{TimeValue(3)}}}; // setup a very small window clinging to Time::MAX
824 CHECK (win.visible().duration() == TimeValue(280)); // duration expanded due to MAX_ZOOM limit
825 CHECK (win.visible().start() == TimeValue(307445734561825580)); // and properly oriented and aligned within domain
826 CHECK (win.visible().end() == TimeValue(307445734561825860));
827 CHECK (win.visible().end() == Time::MAX);
828 CHECK (win.visible() == win.overallSpan());
829 CHECK (win.px_per_sec() == 559_r/280*Time::SCALE);
830 CHECK (win.px_per_sec() == 13975000_r/7);
831 CHECK (win.pxWidth() == 559);
832
833 /*--Test-2-----------*/
834 Time anchorPos{15_r/16 * Offset(Time::MIN)};
835 win.setVisiblePos (anchorPos); // scroll to a target position extremely far off
836 CHECK (win.visible().duration() == TimeValue(280)); // window dimensions retained
837 CHECK (win.px_per_sec() == 13975000_r/7);
838 CHECK (win.pxWidth() == 559);
839 CHECK (win.visible().start() > Time::MIN);
840 CHECK (win.visible().start() == anchorPos); // window now at desired position
841 CHECK (win.visible().end() > anchorPos);
842 CHECK (win.visible().start() == TimeValue(-288230376151711744));
843 CHECK (win.visible().end() == TimeValue(-288230376151711464));
844 CHECK (win.overallSpan().start() == win.visible().start()); // canvas expanded accordingly
845 CHECK (win.overallSpan().end() == Time::MAX);
846
847 /*--Test-3-----------*/
848 win.calibrateExtension (560);
849 CHECK (win.visible().duration() == TimeValue(280)); // effective window dimensions unchanged
850 CHECK (win.px_per_sec() == 2000000_r/1); // but zoom metric slightly adapted
851
852 win.setOverallDuration (Duration::MAX); // now use maximally expanded canvas
853 Duration targetDur{Duration::MAX - FSecs(23)};
854 win.setVisibleDuration(targetDur); // and demand the duration be expanded almost full size
855
856 CHECK (win.visible().duration() == targetDur); // actual duration is the value requested
857 CHECK (win.visible().duration() < Duration::MAX);
858 CHECK (win.visible().start() == Time::MIN); // expansion was anchored at previous position
859 CHECK (win.visible().start() < Time::MAX); // and thus the window now clings to the lower end
860 CHECK (win.visible().end() == TimeValue(307445734538825860));
861 CHECK (Time::MAX - win.visible().end() == TimeValue(23*Time::SCALE));
862 CHECK (win.px_per_sec() == 2003_r/2199023255552); // effective zoom metric has been sanitised numerically
863 CHECK (win.pxWidth() == 560); // but pixel count is matched precisely
864
865 /*--Test-4-----------*/
866 win.setVisiblePos (Rat{std::numeric_limits<int64_t>::max()-23});
867 CHECK (win.visible().duration() == targetDur); // actual duration unchanged
868 CHECK (win.px_per_sec() == 2003_r/2199023255552);
869 CHECK (win.pxWidth() == 560);
870 CHECK (win.visible().end() == Time::MAX); // but window now slinged to the right extreme
871 CHECK (win.visible().start() > Time::MIN);
872 CHECK (win.visible().start() == TimeValue(-307445734538825860));
873
874 /*--Test-5-----------*/
875 win.calibrateExtension (561); // expand by 1 pixel
876 CHECK (win.visible().duration() > targetDur); // actual duration indeed increased
877 CHECK (win.visible().duration() == Duration::MAX); // and then capped at maximum
878 CHECK (win.visible().end() == Time::MAX); // but while initially the upper bound is increased...
879 CHECK (win.visible().start() == Time::MIN);
880 CHECK (win.px_per_sec() == 2007_r/2199023255552); // the smoothed nominal metric was also increased slightly
881 CHECK (win.pxWidth() == 561);
882
883 /*--Test-6-----------*/
884 win.setVisibleDuration (Duration::MAX - Duration(TimeValue(1))); // request slightly different window duration
885 CHECK (win.visible().end() == Time::MAX); // by arbitrary choice, the single µ-tick was removed at start
886 CHECK (win.visible().start() == Time::MIN + TimeValue(1));
887 CHECK (win.px_per_sec() == 2007_r/2199023255552); // the smoothed nominal metric was also increased slightly
888 CHECK (win.pxWidth() == 561);
889
890 win.setVisibleDuration (Duration(TimeValue(1))); // drastically zoom-in
891 CHECK (win.visible().duration() == TimeValue(281)); // ...but we get more than 1 µ-tick
892 CHECK (561_r/_FSecs(TimeValue(1)) > ZOOM_MAX_RESOLUTION); // because the requested window would exceed maximum zoom
893 CHECK (win.px_per_sec() == 561000000_r/281); // and this conflict was resolved by increasing the window
894 CHECK (win.visible().end() == Time::MAX); // while keeping it aligned to the end of the timeline
895 CHECK (win.pxWidth() == 561);
896 }
897
898
901 void
903 {
904 ZoomWindow win{ 1, TimeSpan{Time::MAX, Duration{TimeValue(1)}}}; // use window of 1px size zoomed at 1 µ-tick
905 CHECK (win.visible().start() == Time::MAX - TimeValue(1)); // which is aligned to the end of the time domain
906 CHECK (win.visible().duration() == TimeValue(1));
907
908 win.nudgeVisiblePos (-2); // can be nudged by one window size to the left
909 CHECK (win.visible().start() == Time::MAX - TimeValue(2));
910
911 win.offsetVisiblePos (Offset{Duration::MAX}); // but excess offset is just absorbed
912 CHECK (win.visible().end() == Time::MAX); // window again positioned at the limit
913 CHECK (win.visible().start() == Time::MAX - TimeValue(1));
914 CHECK (win.visible().duration() == TimeValue(1));
915 CHECK (win.overallSpan().duration() == TimeValue(2));
916 CHECK (win.px_per_sec() == 1000000);
917 CHECK (win.pxWidth() == 1);
918
919 win.nudgeVisiblePos (std::numeric_limits<int64_t>::min()); // excess nudging likewise absorbed
920 CHECK (win.overallSpan().duration() == Duration::MAX);
921 CHECK (win.visible().duration() == TimeValue(1));
922 CHECK (win.visible().start() == Time::MIN); // window now positioned at lower limit
923 CHECK (win.visible().end() == Time::MIN + TimeValue(1));
924 CHECK (win.px_per_sec() == 1000000);
925 CHECK (win.pxWidth() == 1);
926
927 win.calibrateExtension (460);
928 win.setVisibleDuration (Duration{Time::MAX - TimeValue(1)}); // arrange window to be 1 µ-tick less than half
929 CHECK (win.visible().duration() == Time::MAX - TimeValue(1));
930 CHECK (win.visible().start() == Time::MIN); // ...so it spans [Time::MIN ... -1]
931 CHECK (win.visible().end() == TimeValue(-1));
932
933 win.nudgeVisiblePos (+2); // thus nudging two times by half-window size...
934 CHECK (win.visible().end() == Time::MAX - TimeValue(2)); // ...still fits into the time domain
935 CHECK (win.visible().start() == TimeValue(-1));
936 win.nudgeVisiblePos (-1);
937 CHECK (win.visible().start() == TimeValue(-153722867280912930)); // navigation within domain works as expected
938 CHECK (win.visible().end() == TimeValue(+153722867280912929));
939
940 win.nudgeVisiblePos (+1000); // requesting an excessive nudge...
941 CHECK (ilogb(500.0 * _raw(Time::MAX)) == 67); // which — naively calculated — would overflow 64-bit
942 CHECK (win.visible().start() == TimeValue(+1)); // but the window just stopped aligned to the upper limit
943 CHECK (win.visible().end() == Time::MAX);
944 CHECK (win.pxWidth() == 460);
945 }
946
947
950 void
952 { // for setup, request a window crossing time domain bounds
954 CHECK (win.overallSpan().duration() == Duration::MAX); // we get a canvas with the requested extension Duration::MAX
955 CHECK (win.overallSpan().end() == Time::MAX); // but shifted into domain to fit
956 CHECK (win.visible().duration() == LIM_HAZARD * 1000); // the visible window however is limited to be smaller
957 CHECK (win.visible().start()+win.visible().end() == Time::ZERO); // and (since this is a zoom-in) it is centred at origin
958 CHECK (win.px_per_sec() == 1_r/(LIM_HAZARD*1000)*Time::SCALE); // Zoom metric is likewise limited, to keep the numbers manageable
959 CHECK (win.px_per_sec() == 125_r/137438953472);
960 CHECK (win.pxWidth() == 1);
961
962 win.nudgeVisiblePos (+1); // can work with this tiny window as expected
963 CHECK (win.visible().start() == Time::ZERO);
964 CHECK (win.visible().end() == LIM_HAZARD*1000);
965 CHECK (win.px_per_sec() == 125_r/137438953472);
966 CHECK (win.pxWidth() == 1);
967
968 win.nudgeMetric (-1); // can not zoom out further
969 CHECK (win.px_per_sec() == 125_r/137438953472);
970 win.nudgeMetric (+1); // but can zoom in
971 CHECK (win.px_per_sec() == 125_r/68719476736);
972 CHECK (win.visible().start() == TimeValue(274877908523000));
973 CHECK (win.visible().end() == TimeValue(824633722411000));
974 CHECK (win.visible().duration() == LIM_HAZARD * 1000 / 2);
975 CHECK (win.pxWidth() == 1);
976
977 win.setVisiblePos (Time{Time::MAX - TimeValue(23)});
978 CHECK (win.visible().end() == Time::MAX);
979 CHECK (win.visible().duration() == LIM_HAZARD * 1000 / 2);
980 CHECK (win.px_per_sec() == 2_r/(LIM_HAZARD*1000)*Time::SCALE);
981 CHECK (win.pxWidth() == 1);
982
983 win.setVisibleRange (TimeSpan{Time::MAX - TimeValue(23) // request a window exceeding domain,
984 ,FSecs{LIM_HAZARD, 1001}}); // but with a zoom slightly above minimal-zoom
985 CHECK (win.visible().end() == Time::MAX); // Resulting window is shifted into domain
986 CHECK (win.visible().duration() == Duration(FSecs{LIM_HAZARD, 1001})); // and has the requested extension
987 CHECK (win.visible().duration() == TimeValue(1098413214561438));
988 CHECK ( FSecs(LIM_HAZARD, 1000) > FSecs(LIM_HAZARD, 1001)); // which is indeed smaller than the maximum duration
989 CHECK (win.px_per_sec() == 2003_r/2199023255552);
990 CHECK (win.pxWidth() == 1);
991 }
992
993
998 void
1000 {
1001 ZoomWindow win{TimeSpan{Time::MIN, Duration{TimeValue(1)}}}; // just request a window spanning the minimally possible value
1002 CHECK (win.overallSpan().duration() == win.visible().duration());
1003 CHECK (win.visible().duration() == TimeValue(1)); // as requested we get a window sized 1 µ-tick
1004 CHECK (win.visible().start() == Time::MIN); // and aligned at the lower domain bound
1005 CHECK (win.visible().end() == Time::MIN + TimeValue(1));
1006 CHECK (win.pxWidth() < ZOOM_MAX_RESOLUTION); // however, can't reach maximum zoom this way
1007 CHECK (win.px_per_sec() == 1000000);
1008 CHECK (win.pxWidth() == 1);
1009
1010 win.setOverallDuration (Duration{FSecs(1)});
1011 win.calibrateExtension (2); // so... get more pixels to work with
1012 CHECK (win.visible().duration() == TimeValue(2)); // ... they are used to expand the window
1013 CHECK (win.px_per_sec() == 1000000); // .. resting at exiting zoom level
1014
1015 win.setMetric (ZOOM_MAX_RESOLUTION);
1016 CHECK (win.px_per_sec() == ZOOM_MAX_RESOLUTION); // now able to reach the maximum zoom level
1017 CHECK (win.px_per_sec() == 2000000); // (which is more or less an arbitrary choice)
1018 CHECK (win.visible().start() == Time::MIN);
1019 CHECK (win.visible().end() == Time::MIN + TimeValue(1)); // while the actual window size is µ-grid aligned
1020 CHECK (win.pxWidth() == 2); // meaning we can not zoom in without limit
1021
1022 win.nudgeVisiblePos (+1); // scroll one »step« to the right
1023 CHECK (win.visible().start() == Time::MIN + TimeValue(1)); // yet this step has been increased to a full window size,
1024 CHECK (win.visible().end() == Time::MIN + TimeValue(2)); // since a smaller scoll-step can not be represented in µ-ticks
1025 CHECK (win.visible().duration() == TimeValue(1));
1026 CHECK (win.px_per_sec() == ZOOM_MAX_RESOLUTION);
1027
1028 win.calibrateExtension (3); // add a third pixel
1029 CHECK (win.visible().duration() == TimeValue(2)); // window extension increased to the next full µ-tick
1030 CHECK (win.px_per_sec() == 3_r/4 * ZOOM_MAX_RESOLUTION); // and the rest was absorbed into the zoom scale
1031 CHECK (win.visible().start() == Time::MIN + TimeValue(1));
1032 CHECK (win.visible().end() == Time::MIN + TimeValue(3));
1033 CHECK (win.pxWidth() == 3);
1034
1035 win.setVisibleDuration (Duration{TimeValue(17)});
1036 CHECK (win.px_per_sec() == 3000000_r/17);
1037 win.setVisibleDuration (Duration{TimeValue(16)});
1038 CHECK (win.px_per_sec() == 187500);
1039 win.setVisibleDuration (Duration{TimeValue(15)});
1040 CHECK (win.px_per_sec() == 200000);
1041 CHECK (win.visible().start() == Time::MIN + TimeValue(1));
1042 CHECK (win.visible().end() == Time::MIN + TimeValue(16));
1043
1044 win.nudgeMetric (-1);
1045 CHECK (win.px_per_sec() == 100000);
1046 CHECK (win.visible().duration() == TimeValue(30));
1047 win.nudgeMetric (+2);
1048 CHECK (win.px_per_sec() == 375000);
1049 CHECK (win.visible().duration() == TimeValue(8));
1050 win.nudgeMetric (+1);
1051 CHECK (win.px_per_sec() == 750000);
1052 CHECK (win.visible().duration() == TimeValue(4));
1053
1054 win.setMetric (2_r/3 * ZOOM_MAX_RESOLUTION);
1055 CHECK (win.px_per_sec() == 1_r/2 * ZOOM_MAX_RESOLUTION); // can't do that, Dave
1056 CHECK (win.px_per_sec() == 1000000);
1057 CHECK (win.visible().duration() == TimeValue(3));
1058 CHECK (win.visible().start() == Time::MIN + TimeValue(1));
1059 CHECK (win.visible().end() == Time::MIN + TimeValue(4));
1060
1061 win.nudgeVisiblePos (-5);
1062 CHECK (win.visible().start() == Time::MIN + TimeValue(0)); // stopped at lower time domain limit
1063 CHECK (win.visible().end() == Time::MIN + TimeValue(3));
1064 CHECK (win.visible().duration() == TimeValue(3));
1065
1066 win.calibrateExtension (MAX_PX_WIDTH); // similar logic applies when using much more pixels
1067 CHECK (win.pxWidth() == 100000);
1068 CHECK (win.visible().duration() == TimeValue(100000));
1069 CHECK (win.px_per_sec() == 1_r/2 * ZOOM_MAX_RESOLUTION);
1070 CHECK (win.visible().start() == Time::MIN + TimeValue(0));
1071 CHECK (win.visible().end() == Time::MIN + TimeValue(100000));
1072
1073 win.setMetric (3_r/2 * ZOOM_MAX_RESOLUTION);
1074 CHECK (win.px_per_sec() == ZOOM_MAX_RESOLUTION); // that's all we get
1075 CHECK (win.visible().duration() == TimeValue(50000)); // (until someone comes up with a good use case for showing more)
1076 CHECK (win.visible().end() == Time::MIN + TimeValue(50000));
1077 CHECK (win.pxWidth() == 100000);
1078 }
1079 };
1080
1081
1084
1085
1086}}} // namespace stage::model::test
Duration is the internal Lumiera time metric.
static const Duration MAX
maximum possible temporal extension
Offset measures a distance in time.
A time interval anchored at a specific point in time.
basic constant internal time value.
static const raw_time_64 SCALE
Number of micro ticks (µs) per second as basic time scale.
Lumiera's internal time value datatype.
static const Time MIN
static const Time ZERO
static const Time MAX
A component to ensure uniform handling of zoom scale and visible interval on the timeline.
TimeSpan overallSpan() const
void nudgeVisiblePos(int64_t steps)
scroll by increments of half window size, possibly expanding.
void calibrateExtension(uint pxWidth)
Define the extension of the window in pixels.
void nudgeMetric(int steps)
scale up or down on a 2-logarithmic scale.
void setOverallDuration(Duration duration)
void setRanges(TimeSpan overall, TimeSpan visible)
Set both the overall canvas, as well as the visible part within that canvas.
void setOverallRange(TimeSpan range)
redefine the overall canvas range.
TimeSpan visible() const
void setVisibleDuration(Duration duration)
explicitly set the duration of the visible window range, working around the relative anchor point; po...
int64_t raw_time_64
Raw µ-tick time representation used in Lumiera.
const Rat ZOOM_MAX_RESOLUTION
the deepest zoom is to use 2px per micro-tick
Lumiera GTK UI implementation root.
Definition guifacade.cpp:37
Test runner and basic definitions for tests.
constexpr int ilog2(I num)
Integral binary logarithm (disregarding fractional part)
int64_t reQuant(int64_t num, int64_t den, int64_t u)
Re-Quantise a number into a new grid, truncating to the next lower grid point.
Definition rational.hpp:120
Simplistic test class runner.
#define LAUNCHER(_TEST_CLASS_, _GROUPS_)
Definition run.hpp:116
A collection of frequently used helper functions to support unit testing.
Abstraction: the current zoom- and navigation state of a view, possibly in multiple dimensions.