81 #ifndef STAGE_MODEL_ZOOM_WINDOW_H 82 #define STAGE_MODEL_ZOOM_WINDOW_H 108 using util::rational_cast;
109 using util::can_represent_Product;
146 return util::rational_cast<
double> (r);
171 const FSecs DEFAULT_CANVAS{23};
172 const Rat DEFAULT_METRIC{25};
173 const uint MAX_PX_WIDTH{100000};
182 const int64_t HAZARD_DEGREE{util::ilog2(
LIM_HAZARD)};
183 const int64_t MAXDIM {util::ilog2 (std::numeric_limits<int64_t>::max())};
186 toxicDegree (Rat poison,
const int64_t THRESHOLD =HAZARD_DEGREE)
188 int64_t magNum = util::ilog2(abs(poison.numerator()));
189 int64_t magDen = util::ilog2(abs(poison.denominator()));
190 int64_t degree = max (magNum, magDen);
191 return max (0, degree - THRESHOLD);
217 startWin_, afterWin_;
220 std::function<void()> changeSignal_{};
224 : startAll_{ensureNonEmpty(timeline).start()}
225 , afterAll_{ensureNonEmpty(timeline).end()}
226 , startWin_{startAll_}
227 , afterWin_{afterAll_}
228 , px_per_sec_{establishMetric (pxWidth, startWin_, afterWin_)}
230 pxWidth = this->pxWidth();
231 ASSERT (0 < pxWidth);
243 return TimeSpan{startAll_, afterAll_};
249 return TimeSpan{startWin_, afterWin_};
261 REQUIRE (startWin_ < afterWin_);
281 fireChangeNotification();
294 fireChangeNotification();
309 steps > 0 ? Rat{px_per_sec_.numerator() << steps
310 ,px_per_sec_.denominator()}
311 : Rat{px_per_sec_.numerator()
312 ,px_per_sec_.denominator() << -steps});
329 fireChangeNotification();
343 fireChangeNotification();
350 fireChangeNotification();
354 setOverallDuration (
Duration duration)
357 fireChangeNotification();
364 fireChangeNotification();
376 fireChangeNotification();
391 FSecs tarDur =
_FSecs(target.end()-target.start());
392 Rat a = FSecs{afterWin_-startWin_};
393 Rat b = FSecs{startWin_}*
_FSecs(target.end()) - FSecs{afterWin_}*
_FSecs((target.start()));
396 Time startNew {a * FSecs{startWin_} + b};
397 Time afterNew {a * FSecs{afterWin_} + b};
400 fireChangeNotification();
414 fireChangeNotification();
422 fireChangeNotification();
429 FSecs dur{afterWin_-startWin_};
430 int64_t limPages = 2 * rational_cast<int64_t> (MAX_TIMESPAN/dur);
431 steps = util::limited(-limPages, steps, +limPages);
432 FSecs scroll = steps * dur/2;
433 if (abs(scroll) < MICRO_TICK) scroll = sgn(steps) * MICRO_TICK;
444 FSecs canvasOffset{posToShow - startAll_};
446 fireChangeNotification();
453 FSecs canvasDuration{afterAll_-startAll_};
455 fireChangeNotification();
461 int64_t scale = max (_raw(afterAll_-startAll_), MAX_PX_WIDTH);
462 Rat factor{int64_t(scale*percentage), scale};
469 UNIMPLEMENTED (
"navigate Zoom History");
478 changeSignal_ = std::forward<FUN> (trigger);
482 detachChangeNotification()
484 changeSignal_ = std::function<void()>();
490 fireChangeNotification()
492 if (changeSignal_) changeSignal_();
522 int toxicity = toxicDegree (poison);
523 return toxicity ? reQuant (poison, max (poison.denominator() >> toxicity, 64))
540 if (util::can_represent_Product(duration, factor))
542 return duration * factor;
545 auto guess{approx(duration) * approx (factor)};
546 if (approx(MAX_TIMESPAN) < abs(guess))
547 return MAX_TIMESPAN * sgn(guess);
556 struct ReductionStrategy
568 return isFeasible()? u : 0;
574 REQUIRE (isFeasible());
575 f2 = reQuant (f2, q, u);
576 return invert? Rat{f2, f1}
583 REQUIRE (u and q and f2);
584 int dim_u = util::ilog2 (abs (u));
585 int dim_q = util::ilog2 (abs (q));
586 if (dim_q > dim_u)
return true;
587 int dim_f = util::ilog2 (abs (f2));
588 int deltaQ = dim_u - dim_q;
589 int headroom = MAXDIM - dim_f;
590 return headroom > deltaQ;
593 using Cases = std::array<ReductionStrategy, 4>;
598 auto [reduction,rem] = util::iDiv (
Time::SCALE, duration.denominator());
599 if (rem != 0) reduction = 1;
600 int64_t durationQuant = duration.denominator()*reduction;
601 int64_t durationTicks = duration.numerator()*reduction;
604 Cases cases{{{durationTicks , durationQuant , factor.numerator() , factor.denominator() ,
false}
605 ,{factor.numerator() , factor.denominator(), duration.numerator() , duration.denominator(),
false}
606 ,{duration.denominator(), duration.numerator(), factor.denominator() , factor.numerator() ,
true}
607 ,{factor.denominator() , factor.numerator() , duration.denominator(), duration.numerator() ,
true}
611 ReductionStrategy* solution{
nullptr};
612 int64_t maxLimit = 0;
613 for (
auto& candidate: cases)
615 int64_t limit = candidate.determineLimit();
616 if (limit > maxLimit)
619 solution = &candidate;
623 ASSERT (solution and maxLimit > 0);
624 return detox (solution->calculateResult());
638 if (util::can_represent_Sum (t1,t2))
643 auto guess{approx(t1) + approx(t2)};
644 if (approx(MAX_TIMESPAN) < abs(guess))
645 return MAX_TIMESPAN * sgn(guess);
649 int64_t n1 = t1.numerator();
650 int64_t d1 = t1.denominator();
651 int s1 = sgn(n1)*sgn(d1);
652 n1 = abs(n1); d1 = abs(d1);
653 int64_t n2 = t2.numerator();
654 int64_t d2 = t2.denominator();
655 int s2 = sgn(n2)*sgn(d2);
656 n2 = abs(n2); d2 = abs(d2);
658 int64_t u = d1<d2? d1:d2;
664 and (MAXDIM<=util::ilog2(n1) or MAXDIM<=util::ilog2(n2)))
667 n1 = d1==u? n1 : reQuant (n1,d1, u);
668 n2 = d2==u? n2 : reQuant (n2,d2, u);
669 FSecs res{s1*n1 + s2*n2, u};
671 auto f128 = [](Rat n){
return rational_cast<
long double>(n); };
672 ENSURE (abs (f128(res) - (f128(t1)+f128(t2))) < 1.0/u);
689 ensureNonEmpty (
TimeSpan const& span)
692 ,util::isnil(span.duration())?
Duration{DEFAULT_CANVAS}
701 auto sizeAtRequestedScale = approx(zoomFactor) * approx(duration);
702 ENSURE (abs(pxWidth - sizeAtRequestedScale) <= 1
703 ,
"ZoomWindow: established size or metric misses expectation " 704 "by more than 1px. %upx != %1.6f expected pixel." 705 , pxWidth, sizeAtRequestedScale);
714 auto zn = zoomFactor.numerator();
715 auto zd = zoomFactor.denominator();
716 auto dn = duration.numerator();
717 auto dd = duration.denominator();
718 auto [secs,r] = util::iDiv (dn, dd);
719 auto [px1,r1] = util::iDiv (secs*zn, zd);
720 auto [px2,r2] = util::iDiv (r*zn, dd*zd);
721 auto pxr = (r1*dd +r2) /(dd*zd);
722 ENSURE (0 <= px1 and 0 <= px2 and 0<= pxr);
723 return px1 + px2 + pxr;
731 return min (FSecs{
LIM_HAZARD * pxWidth, 1000}, MAX_TIMESPAN);
746 REQUIRE (0 < pxWidth and 0 < dur and 0 < rawMetric);
749 int64_t magDen = ilog2(rawMetric.denominator());
750 int reduction = toxicDegree (rawMetric);
751 int quant = max (magDen-reduction, 16);
754 Rat adjMetric = util::reQuant (rawMetric, int64_t(1) << quant);
759 double epsilon = std::numeric_limits<double>::epsilon()
760 , dn = dur.numerator()
761 , dd = dur.denominator()
762 , md = adjMetric.denominator()
763 , mn = (pxWidth+epsilon)*md*dd/dn;
765 int64_t num = mn, den = adjMetric.denominator();
766 if (epsilon < mn - num)
768 int headroom = max (1, HAZARD_DEGREE - max (ilog2(num), ilog2(den)));
769 int64_t scale = int64_t(1) << headroom;
772 if (pxWidth > dn/dd*num/den)
775 adjMetric = Rat{num, den};
777 double impliedDur = double(pxWidth)*den/num;
778 double relError = abs(dn/dd /impliedDur -1);
779 double quantErr = 1.0/(num-1);
780 ENSURE (quantErr > relError,
"metric misses duration by " 781 "%3.2f%% > %3.2f%% (=relative quantisation error)" 782 ,100*relError, 100.0*quantErr);
788 establishMetric (uint pxWidth,
Time startWin,
Time afterWin)
790 REQUIRE (startWin < afterWin);
791 FSecs dur =
_FSecs(afterWin-startWin);
792 if (pxWidth == 0 or pxWidth > MAX_PX_WIDTH)
793 pxWidth = max<uint> (1, rational_cast<uint> (DEFAULT_METRIC * dur));
794 Rat metric = Rat(pxWidth) / dur;
806 REQUIRE (changedMetric > 0);
807 REQUIRE (afterWin_> startWin_);
808 FSecs dur{afterWin_-startWin_};
810 dur = Rat(pxWidth) /
detox (changedMetric);
811 dur = min (dur, MAX_TIMESPAN);
812 dur = max (dur, MICRO_TICK);
819 establishWindowDuration (
Duration{timeDur});
821 px_per_sec_ = conformMetricToWindow (pxWidth);
822 ENSURE (
_FSecs(afterWin_-startWin_) <= MAX_TIMESPAN);
827 conformMetricToWindow (uint pxWidth)
829 REQUIRE (pxWidth > 0);
830 REQUIRE (afterWin_> startWin_);
831 FSecs dur{afterWin_-startWin_};
832 Rat adjMetric = Rat(pxWidth) / dur;
833 if (not toxicDegree(adjMetric)
849 REQUIRE (pxWidth > 0);
850 FSecs dur{afterWin_-startWin_};
855 establishWindowDuration (dur);
860 conformWindowToCanvas()
862 FSecs dur{afterWin_-startWin_};
863 REQUIRE (dur <= MAX_TIMESPAN);
864 startAll_ = max (startAll_, Time::MIN);
866 if (dur <=
_FSecs(afterAll_-startAll_))
868 if (afterWin_ > afterAll_)
870 Offset shift{afterWin_ - afterAll_};
875 if (startWin_ < startAll_)
877 Offset shift{startAll_ - startWin_};
884 startWin_ = startAll_;
885 afterWin_ = afterAll_;
887 ENSURE (startAll_ <= startWin_);
888 ENSURE (afterWin_ <= afterAll_);
889 ENSURE (Time::MIN <= startWin_);
894 conformToBounds (Rat changedMetric)
896 if (changedMetric > ZOOM_MAX_RESOLUTION)
901 startAll_ = min (startAll_, startWin_);
902 afterAll_ = max (afterAll_, afterWin_);
903 ENSURE (Time::MIN <= startWin_);
905 ENSURE (startAll_ <= startWin_);
906 ENSURE (afterWin_ <= afterAll_);
907 ENSURE (px_per_sec_ <= ZOOM_MAX_RESOLUTION);
908 ENSURE (px_per_sec_ <= changedMetric);
924 if (px==0) px = pxWidth();
925 conformWindowToCanvas();
926 px_per_sec_ = conformMetricToWindow (px);
927 conformToBounds (px_per_sec_);
939 startAll_ = ensureNonEmpty(canvas).start();
940 afterAll_ = ensureNonEmpty(canvas).end();
951 startWin_ = ensureNonEmpty(window).start();
952 afterWin_ = ensureNonEmpty(window).end();
954 startAll_ = min (startAll_, startWin_);
955 afterAll_ = max (afterAll_, afterWin_);
965 startAll_ = ensureNonEmpty(canvas).start();
966 afterAll_ = ensureNonEmpty(canvas).end();
967 startWin_ = ensureNonEmpty(window).start();
968 afterWin_ = ensureNonEmpty(window).end();
981 changedMetric = min (
detox(changedMetric), ZOOM_MAX_RESOLUTION);
982 if (changedMetric == px_per_sec_)
return;
995 duration = DEFAULT_CANVAS;
999 establishWindowDuration (duration);
1000 px_per_sec_ = conformMetricToWindow (px);
1009 pxWidth = util::limited (1u, pxWidth, MAX_PX_WIDTH);
1010 FSecs adaptedWindow{Rat{pxWidth} / px_per_sec_};
1011 adaptedWindow = max (adaptedWindow, MICRO_TICK);
1013 establishWindowDuration (adaptedWindow);
1025 REQUIRE (afterWin_ > startWin_);
1026 REQUIRE (afterAll_ > startAll_);
1028 FSecs duration{afterWin_-startWin_};
1029 Rat posFactor = canvasOffset / FSecs{afterAll_-startAll_};
1031 FSecs partBeforeAnchor =
scaleSafe (duration, posFactor);
1032 startWin_ = startAll_ +
Offset{
addSafe (canvasOffset, -partBeforeAnchor)};
1033 establishWindowDuration (duration);
1034 startAll_ = min (startAll_, startWin_);
1035 afterAll_ = max (afterAll_, afterWin_);
1051 establishWindowDuration (
Duration duration)
1054 afterWin_ = startWin_ + duration;
1091 FSecs possibleRange = (afterAll_-startAll_) - (afterWin_-startWin_);
1092 if (possibleRange <= 0)
1096 Rat posFactor = FSecs{startWin_-startAll_} / possibleRange;
1112 posFactor = util::limited (0, posFactor, 1);
1113 if (toxicDegree(posFactor, 20))
1114 posFactor = util::reQuant(posFactor, 1 << 20);
1115 posFactor = (2*posFactor - 1);
1116 posFactor = posFactor*posFactor*posFactor;
1117 posFactor = (posFactor + 1) / 2;
1118 posFactor = util::limited (0, posFactor, 1);
1119 return detox (posFactor);
void setVisibleRange(TimeSpan newWindow)
explicitly set the visible window, possibly expanding the canvas to fit.
a mutable time value, behaving like a plain number, allowing copy and re-accessing ...
void mutateScale(Rat changedMetric)
static const Duration MAX
maximum possible temporal extension
void setVisibleDuration(Duration duration)
explicitly set the duration of the visible window range, working around the relative anchor point; po...
void ensureInvariants(uint px=0)
Procedure to (re)establish the invariants.
static int64_t calcPixelsForDurationAtScale(Rat zoomFactor, FSecs duration)
calculate rational_cast<uint> (zoomFactor * duration)
Rat reQuant(Rat src, int64_t u)
re-Quantise a rational number to a (typically smaller) denominator.
void setVisiblePos(Time posToShow)
scroll the window to bring the denoted position in sight, retaining the current zoom factor...
void attachChangeNotification(FUN &&trigger)
Attach a λ or functor to be triggered on each actual change.
static Rat parabolicAnchorRule(Rat posFactor)
A counter movement rule to place an anchor point, based on a percentage factor.
void conformWindowToMetricLimits(uint pxWidth)
The zoom metric factor must not become "poisonous".
void setRanges(TimeSpan overall, TimeSpan visible)
Set both the overall canvas, as well as the visible part within that canvas.
Rational number support, based on boost::rational.
const int64_t LIM_HAZARD
Maximum quantiser to be handled in fractional arithmetics without hazard.
Rat relativeAnchor() const
define at which proportion to the visible window's duration the anchor should be placed ...
Any copy and copy construction prohibited.
void placeWindowRelativeToAnchor(FSecs duration)
static const gavl_time_t SCALE
Number of micro ticks (µs) per second as basic time scale.
void mutateCanvas(TimeSpan canvas)
void expandVisibleRange(TimeSpan target)
the »reverse zoom operation«: zoom out such as to bring the current window at the designated time spa...
void mutateRanges(TimeSpan canvas, TimeSpan window)
static Rat detox(Rat poison)
Check and possibly sanitise a rational number to avoid internal numeric overflow. ...
Lumiera's internal time value datatype.
static FSecs addSafe(FSecs t1, FSecs t2)
Calculate sum (or difference) of possibly large time durations, avoiding integer wrap-around.
static void ENSURE_matchesExpectedPixWidth(Rat zoomFactor, FSecs duration, uint pxWidth)
Assertion helper: resulting pxWidth matches expectations.
TimeVar operator+(Time const &tval, TimeVar const &tvar)
Mix-Ins to allow or prohibit various degrees of copying and cloning.
void setVisiblePos(Rat percentage)
scroll to reveal position designated relative to overall canvas
A component to ensure uniform handling of zoom scale and visible interval on the timeline.
void nudgeVisiblePos(int64_t steps)
scroll by increments of half window size, possibly expanding.
Rat optimiseMetric(uint pxWidth, FSecs dur, Rat rawMetric)
Reform the effective metric in all dangerous corner cases.
Lumiera GTK UI implementation root.
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...
void nudgeMetric(int steps)
scale up or down on a 2-logarithmic scale.
static FSecs scaleSafe(FSecs duration, Rat factor)
Scale a possibly large time duration by a rational factor, while attempting to avoid integer wrap-aro...
void conformWindowToMetric(Rat changedMetric)
this is the centrepiece of the whole zoom metric logic...
boost::rational< int64_t > FSecs
rational representation of fractional seconds
FSecs _FSecs(TimeValue const &timeVal)
void mutateWindow(TimeSpan window)
Lumiera error handling (C++ interface).
void adaptWindowToPixels(uint pxWidth)
const Rat ZOOM_MAX_RESOLUTION
the deepest zoom is to use 2px per micro-tick
void setOverallRange(TimeSpan range)
redefine the overall canvas range.
static FSecs maxSaneWinExtension(uint pxWidth)
window size beyond that limit would lead to numerically dangerous zoom factors (pixel/duration) ...
Offset measures a distance in time.
void offsetVisiblePos(Offset offset)
scroll by arbitrary offset, possibly expanding canvas.
Duration is the internal Lumiera time metric.
FSecs anchorPoint() const
The anchor point or centre for zooming operations applied to the visible window.
void mutateDuration(FSecs duration, uint px=0)
void setMetric(Rat px_per_sec)
explicitly set the zoom factor, defined as pixel per second
A time interval anchored at a specific point in time.
void calibrateExtension(uint pxWidth)
Define the extension of the window in pixels.
a family of time value like entities and their relationships.
basic constant internal time value.
bool isMicroGridAligned(FSecs duration)
void anchorWindowAtPosition(FSecs canvasOffset)