90 #ifndef STAGE_MODEL_ZOOM_WINDOW_H 91 #define STAGE_MODEL_ZOOM_WINDOW_H 101 #include <functional> 117 using util::rational_cast;
118 using util::can_represent_Product;
155 return util::rational_cast<
double> (r);
180 const FSecs DEFAULT_CANVAS{23};
181 const Rat DEFAULT_METRIC{25};
182 const uint MAX_PX_WIDTH{100000};
191 const int64_t HAZARD_DEGREE{util::ilog2(
LIM_HAZARD)};
192 const int64_t MAXDIM {util::ilog2 (std::numeric_limits<int64_t>::max())};
195 toxicDegree (Rat poison,
const int64_t THRESHOLD =HAZARD_DEGREE)
197 int64_t magNum = util::ilog2(abs(poison.numerator()));
198 int64_t magDen = util::ilog2(abs(poison.denominator()));
199 int64_t degree = max (magNum, magDen);
200 return max (0, degree - THRESHOLD);
226 startWin_, afterWin_;
229 std::function<void()> changeSignal_{};
233 : startAll_{ensureNonEmpty(timeline).start()}
234 , afterAll_{ensureNonEmpty(timeline).end()}
235 , startWin_{startAll_}
236 , afterWin_{afterAll_}
237 , px_per_sec_{establishMetric (pxWidth, startWin_, afterWin_)}
239 pxWidth = this->pxWidth();
240 ASSERT (0 < pxWidth);
252 return TimeSpan{startAll_, afterAll_};
258 return TimeSpan{startWin_, afterWin_};
270 REQUIRE (startWin_ < afterWin_);
290 fireChangeNotification();
303 fireChangeNotification();
318 steps > 0 ? Rat{px_per_sec_.numerator() << steps
319 ,px_per_sec_.denominator()}
320 : Rat{px_per_sec_.numerator()
321 ,px_per_sec_.denominator() << -steps});
338 fireChangeNotification();
352 fireChangeNotification();
359 fireChangeNotification();
363 setOverallDuration (
Duration duration)
366 fireChangeNotification();
373 fireChangeNotification();
385 fireChangeNotification();
400 FSecs tarDur =
_FSecs(target.end()-target.start());
401 Rat a = FSecs{afterWin_-startWin_};
402 Rat b = FSecs{startWin_}*
_FSecs(target.end()) - FSecs{afterWin_}*
_FSecs((target.start()));
405 Time startNew {a * FSecs{startWin_} + b};
406 Time afterNew {a * FSecs{afterWin_} + b};
409 fireChangeNotification();
423 fireChangeNotification();
431 fireChangeNotification();
438 FSecs dur{afterWin_-startWin_};
439 int64_t limPages = 2 * rational_cast<int64_t> (MAX_TIMESPAN/dur);
440 steps = util::limited(-limPages, steps, +limPages);
441 FSecs scroll = steps * dur/2;
442 if (abs(scroll) < MICRO_TICK) scroll = sgn(steps) * MICRO_TICK;
453 FSecs canvasOffset{posToShow - startAll_};
455 fireChangeNotification();
462 FSecs canvasDuration{afterAll_-startAll_};
464 fireChangeNotification();
470 int64_t scale = max (_raw(afterAll_-startAll_), MAX_PX_WIDTH);
471 Rat factor{int64_t(scale*percentage), scale};
478 UNIMPLEMENTED (
"navigate Zoom History");
487 changeSignal_ = std::forward<FUN> (trigger);
491 detachChangeNotification()
493 changeSignal_ = std::function<void()>();
499 fireChangeNotification()
501 if (changeSignal_) changeSignal_();
531 int toxicity = toxicDegree (poison);
532 return toxicity ? reQuant (poison, max (poison.denominator() >> toxicity, 64))
549 if (util::can_represent_Product(duration, factor))
551 return duration * factor;
554 auto guess{approx(duration) * approx (factor)};
555 if (approx(MAX_TIMESPAN) < abs(guess))
556 return MAX_TIMESPAN * sgn(guess);
565 struct ReductionStrategy
577 return isFeasible()? u : 0;
583 REQUIRE (isFeasible());
584 f2 = reQuant (f2, q, u);
585 return invert? Rat{f2, f1}
592 REQUIRE (u and q and f2);
593 int dim_u = util::ilog2 (abs (u));
594 int dim_q = util::ilog2 (abs (q));
595 if (dim_q > dim_u)
return true;
596 int dim_f = util::ilog2 (abs (f2));
597 int deltaQ = dim_u - dim_q;
598 int headroom = MAXDIM - dim_f;
599 return headroom > deltaQ;
602 using Cases = std::array<ReductionStrategy, 4>;
607 auto [reduction,rem] = util::iDiv (
Time::SCALE, duration.denominator());
608 if (rem != 0) reduction = 1;
609 int64_t durationQuant = duration.denominator()*reduction;
610 int64_t durationTicks = duration.numerator()*reduction;
613 Cases cases{{{durationTicks , durationQuant , factor.numerator() , factor.denominator() ,
false}
614 ,{factor.numerator() , factor.denominator(), duration.numerator() , duration.denominator(),
false}
615 ,{duration.denominator(), duration.numerator(), factor.denominator() , factor.numerator() ,
true}
616 ,{factor.denominator() , factor.numerator() , duration.denominator(), duration.numerator() ,
true}
620 ReductionStrategy* solution{
nullptr};
621 int64_t maxLimit = 0;
622 for (
auto& candidate: cases)
624 int64_t limit = candidate.determineLimit();
625 if (limit > maxLimit)
628 solution = &candidate;
632 ASSERT (solution and maxLimit > 0);
633 return detox (solution->calculateResult());
647 if (util::can_represent_Sum (t1,t2))
652 auto guess{approx(t1) + approx(t2)};
653 if (approx(MAX_TIMESPAN) < abs(guess))
654 return MAX_TIMESPAN * sgn(guess);
658 int64_t n1 = t1.numerator();
659 int64_t d1 = t1.denominator();
660 int s1 = sgn(n1)*sgn(d1);
661 n1 = abs(n1); d1 = abs(d1);
662 int64_t n2 = t2.numerator();
663 int64_t d2 = t2.denominator();
664 int s2 = sgn(n2)*sgn(d2);
665 n2 = abs(n2); d2 = abs(d2);
667 int64_t u = d1<d2? d1:d2;
673 and (MAXDIM<=util::ilog2(n1) or MAXDIM<=util::ilog2(n2)))
676 n1 = d1==u? n1 : reQuant (n1,d1, u);
677 n2 = d2==u? n2 : reQuant (n2,d2, u);
678 FSecs res{s1*n1 + s2*n2, u};
680 auto f128 = [](Rat n){
return rational_cast<
long double>(n); };
681 ENSURE (abs (f128(res) - (f128(t1)+f128(t2))) < 1.0/u);
698 ensureNonEmpty (
TimeSpan const& span)
701 ,util::isnil(span.duration())?
Duration{DEFAULT_CANVAS}
710 auto sizeAtRequestedScale = approx(zoomFactor) * approx(duration);
711 ENSURE (abs(pxWidth - sizeAtRequestedScale) <= 1
712 ,
"ZoomWindow: established size or metric misses expectation " 713 "by more than 1px. %upx != %1.6f expected pixel." 714 , pxWidth, sizeAtRequestedScale);
723 auto zn = zoomFactor.numerator();
724 auto zd = zoomFactor.denominator();
725 auto dn = duration.numerator();
726 auto dd = duration.denominator();
727 auto [secs,r] = util::iDiv (dn, dd);
728 auto [px1,r1] = util::iDiv (secs*zn, zd);
729 auto [px2,r2] = util::iDiv (r*zn, dd*zd);
730 auto pxr = (r1*dd +r2) /(dd*zd);
731 ENSURE (0 <= px1 and 0 <= px2 and 0<= pxr);
732 return px1 + px2 + pxr;
740 return min (FSecs{
LIM_HAZARD * pxWidth, 1000}, MAX_TIMESPAN);
755 REQUIRE (0 < pxWidth and 0 < dur and 0 < rawMetric);
758 int64_t magDen = ilog2(rawMetric.denominator());
759 int reduction = toxicDegree (rawMetric);
760 int quant = max (magDen-reduction, 16);
763 Rat adjMetric = util::reQuant (rawMetric, int64_t(1) << quant);
768 double epsilon = std::numeric_limits<double>::epsilon()
769 , dn = dur.numerator()
770 , dd = dur.denominator()
771 , md = adjMetric.denominator()
772 , mn = (pxWidth+epsilon)*md*dd/dn;
774 int64_t num = mn, den = adjMetric.denominator();
775 if (epsilon < mn - num)
777 int headroom = max (1, HAZARD_DEGREE - max (ilog2(num), ilog2(den)));
778 int64_t scale = int64_t(1) << headroom;
781 if (pxWidth > dn/dd*num/den)
784 adjMetric = Rat{num, den};
786 double impliedDur = double(pxWidth)*den/num;
787 double relError = abs(dn/dd /impliedDur -1);
788 double quantErr = 1.0/(num-1);
789 ENSURE (quantErr > relError,
"metric misses duration by " 790 "%3.2f%% > %3.2f%% (=relative quantisation error)" 791 ,100*relError, 100.0*quantErr);
797 establishMetric (uint pxWidth,
Time startWin,
Time afterWin)
799 REQUIRE (startWin < afterWin);
800 FSecs dur =
_FSecs(afterWin-startWin);
801 if (pxWidth == 0 or pxWidth > MAX_PX_WIDTH)
802 pxWidth = max<uint> (1, rational_cast<uint> (DEFAULT_METRIC * dur));
803 Rat metric = Rat(pxWidth) / dur;
815 REQUIRE (changedMetric > 0);
816 REQUIRE (afterWin_> startWin_);
817 FSecs dur{afterWin_-startWin_};
819 dur = Rat(pxWidth) /
detox (changedMetric);
820 dur = min (dur, MAX_TIMESPAN);
821 dur = max (dur, MICRO_TICK);
828 establishWindowDuration (
Duration{timeDur});
830 px_per_sec_ = conformMetricToWindow (pxWidth);
831 ENSURE (
_FSecs(afterWin_-startWin_) <= MAX_TIMESPAN);
836 conformMetricToWindow (uint pxWidth)
838 REQUIRE (pxWidth > 0);
839 REQUIRE (afterWin_> startWin_);
840 FSecs dur{afterWin_-startWin_};
841 Rat adjMetric = Rat(pxWidth) / dur;
842 if (not toxicDegree(adjMetric)
858 REQUIRE (pxWidth > 0);
859 FSecs dur{afterWin_-startWin_};
864 establishWindowDuration (dur);
869 conformWindowToCanvas()
871 FSecs dur{afterWin_-startWin_};
872 REQUIRE (dur <= MAX_TIMESPAN);
873 startAll_ = max (startAll_, Time::MIN);
875 if (dur <=
_FSecs(afterAll_-startAll_))
877 if (afterWin_ > afterAll_)
879 Offset shift{afterWin_ - afterAll_};
884 if (startWin_ < startAll_)
886 Offset shift{startAll_ - startWin_};
893 startWin_ = startAll_;
894 afterWin_ = afterAll_;
896 ENSURE (startAll_ <= startWin_);
897 ENSURE (afterWin_ <= afterAll_);
898 ENSURE (Time::MIN <= startWin_);
903 conformToBounds (Rat changedMetric)
905 if (changedMetric > ZOOM_MAX_RESOLUTION)
910 startAll_ = min (startAll_, startWin_);
911 afterAll_ = max (afterAll_, afterWin_);
912 ENSURE (Time::MIN <= startWin_);
914 ENSURE (startAll_ <= startWin_);
915 ENSURE (afterWin_ <= afterAll_);
916 ENSURE (px_per_sec_ <= ZOOM_MAX_RESOLUTION);
917 ENSURE (px_per_sec_ <= changedMetric);
933 if (px==0) px = pxWidth();
934 conformWindowToCanvas();
935 px_per_sec_ = conformMetricToWindow (px);
936 conformToBounds (px_per_sec_);
948 startAll_ = ensureNonEmpty(canvas).start();
949 afterAll_ = ensureNonEmpty(canvas).end();
960 startWin_ = ensureNonEmpty(window).start();
961 afterWin_ = ensureNonEmpty(window).end();
963 startAll_ = min (startAll_, startWin_);
964 afterAll_ = max (afterAll_, afterWin_);
974 startAll_ = ensureNonEmpty(canvas).start();
975 afterAll_ = ensureNonEmpty(canvas).end();
976 startWin_ = ensureNonEmpty(window).start();
977 afterWin_ = ensureNonEmpty(window).end();
990 changedMetric = min (
detox(changedMetric), ZOOM_MAX_RESOLUTION);
991 if (changedMetric == px_per_sec_)
return;
1004 duration = DEFAULT_CANVAS;
1008 establishWindowDuration (duration);
1009 px_per_sec_ = conformMetricToWindow (px);
1018 pxWidth = util::limited (1u, pxWidth, MAX_PX_WIDTH);
1019 FSecs adaptedWindow{Rat{pxWidth} / px_per_sec_};
1020 adaptedWindow = max (adaptedWindow, MICRO_TICK);
1022 establishWindowDuration (adaptedWindow);
1034 REQUIRE (afterWin_ > startWin_);
1035 REQUIRE (afterAll_ > startAll_);
1037 FSecs duration{afterWin_-startWin_};
1038 Rat posFactor = canvasOffset / FSecs{afterAll_-startAll_};
1040 FSecs partBeforeAnchor =
scaleSafe (duration, posFactor);
1041 startWin_ = startAll_ +
Offset{
addSafe (canvasOffset, -partBeforeAnchor)};
1042 establishWindowDuration (duration);
1043 startAll_ = min (startAll_, startWin_);
1044 afterAll_ = max (afterAll_, afterWin_);
1060 establishWindowDuration (
Duration duration)
1063 afterWin_ = startWin_ + duration;
1100 FSecs possibleRange = (afterAll_-startAll_) - (afterWin_-startWin_);
1101 if (possibleRange <= 0)
1105 Rat posFactor = FSecs{startWin_-startAll_} / possibleRange;
1121 posFactor = util::limited (0, posFactor, 1);
1122 if (toxicDegree(posFactor, 20))
1123 posFactor = util::reQuant(posFactor, 1 << 20);
1124 posFactor = (2*posFactor - 1);
1125 posFactor = posFactor*posFactor*posFactor;
1126 posFactor = (posFactor + 1) / 2;
1127 posFactor = util::limited (0, posFactor, 1);
1128 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)