81#ifndef STAGE_MODEL_ZOOM_WINDOW_H
82#define STAGE_MODEL_ZOOM_WINDOW_H
108 using util::rational_cast;
146 return util::rational_cast<double> (r);
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);
391 FSecs tarDur = _FSecs(target.
end()-target.
start());
430 int64_t limPages = 2 * rational_cast<int64_t> (MAX_TIMESPAN/dur);
432 FSecs scroll = steps * dur/2;
433 if (abs(scroll) < MICRO_TICK) scroll = sgn(steps) * MICRO_TICK;
444 FSecs canvasOffset{posToShow -
startAll_};
462 Rat factor{int64_t(scale*percentage), scale};
469 UNIMPLEMENTED (
"navigate Zoom History");
522 int toxicity = toxicDegree (poison);
523 return toxicity ? reQuant (poison, max (poison.denominator() >> toxicity, 64))
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);
586 if (dim_q > dim_u)
return true;
588 int deltaQ = dim_u - dim_q;
589 int headroom = MAXDIM - dim_f;
590 return headroom > deltaQ;
593 using Cases = std::array<ReductionStrategy, 4>;
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());
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;
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); };
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();
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);
747 REQUIRE (isMicroGridAligned (dur));
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;
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);
790 REQUIRE (startWin < afterWin);
791 FSecs dur = _FSecs(afterWin-startWin);
793 pxWidth = max<uint> (1, rational_cast<uint> (DEFAULT_METRIC * dur));
794 Rat metric = Rat(
pxWidth) / dur;
806 REQUIRE (changedMetric > 0);
811 dur = min (dur, MAX_TIMESPAN);
812 dur = max (dur, MICRO_TICK);
815 if (not isMicroGridAligned (dur))
832 Rat adjMetric = Rat(
pxWidth) / dur;
833 if (not toxicDegree(adjMetric)
863 REQUIRE (dur <= MAX_TIMESPAN);
995 duration = DEFAULT_CANVAS;
1011 adaptedWindow = max (adaptedWindow, MICRO_TICK);
1031 FSecs partBeforeAnchor =
scaleSafe (duration, posFactor);
1092 if (possibleRange <= 0)
1113 if (toxicDegree(posFactor, 20))
1115 posFactor = (2*posFactor - 1);
1116 posFactor = posFactor*posFactor*posFactor;
1117 posFactor = (posFactor + 1) / 2;
1119 return detox (posFactor);
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.
a mutable time value, behaving like a plain number, allowing copy and re-accessing
Lumiera's internal time value datatype.
A component to ensure uniform handling of zoom scale and visible interval on the timeline.
void setVisiblePos(Rat percentage)
scroll to reveal position designated relative to overall canvas
TimeSpan overallSpan() const
void nudgeVisiblePos(int64_t steps)
scroll by increments of half window size, possibly expanding.
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 setVisiblePos(Time posToShow)
scroll the window to bring the denoted position in sight, retaining the current zoom factor,...
void offsetVisiblePos(Offset offset)
scroll by arbitrary offset, possibly expanding canvas.
static Rat parabolicAnchorRule(Rat posFactor)
A counter movement rule to place an anchor point, based on a percentage factor.
ZoomWindow(uint pxWidth, TimeSpan timeline=TimeSpan{Time::ZERO, DEFAULT_CANVAS})
static Rat detox(Rat poison)
Check and possibly sanitise a rational number to avoid internal numeric overflow.
static void ENSURE_matchesExpectedPixWidth(Rat zoomFactor, FSecs duration, uint pxWidth)
Assertion helper: resulting pxWidth matches expectations.
void calibrateExtension(uint pxWidth)
Define the extension of the window in pixels.
void mutateCanvas(TimeSpan canvas)
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 expandVisibleRange(TimeSpan target)
the »reverse zoom operation«: zoom out such as to bring the current window at the designated time spa...
Rat conformMetricToWindow(uint pxWidth)
void setOverallRange(TimeSpan range)
redefine the overall canvas range.
void attachChangeNotification(FUN &&trigger)
Attach a λ or functor to be triggered on each actual change.
void setOverallStart(TimeValue start)
void placeWindowRelativeToAnchor(FSecs duration)
void anchorWindowAtPosition(FSecs canvasOffset)
void mutateRanges(TimeSpan canvas, TimeSpan window)
FSecs anchorPoint() const
The anchor point or centre for zooming operations applied to the visible window.
static Rat establishMetric(uint pxWidth, Time startWin, Time afterWin)
static FSecs addSafe(FSecs t1, FSecs t2)
Calculate sum (or difference) of possibly large time durations, avoiding integer wrap-around.
void mutateScale(Rat changedMetric)
void conformWindowToMetric(Rat changedMetric)
this is the centrepiece of the whole zoom metric logic...
static FSecs maxSaneWinExtension(uint pxWidth)
window size beyond that limit would lead to numerically dangerous zoom factors (pixel/duration)
void ensureInvariants(uint px=0)
Procedure to (re)establish the invariants.
void adaptWindowToPixels(uint pxWidth)
void setMetric(Rat px_per_sec)
explicitly set the zoom factor, defined as pixel per second
void conformWindowToCanvas()
Rat relativeAnchor() const
define at which proportion to the visible window's duration the anchor should be placed
ZoomWindow(TimeSpan timeline=TimeSpan{Time::ZERO, DEFAULT_CANVAS})
void detachChangeNotification()
void mutateWindow(TimeSpan window)
void fireChangeNotification()
void setVisibleStart(TimeValue start)
static int64_t calcPixelsForDurationAtScale(Rat zoomFactor, FSecs duration)
calculate rational_cast<uint> (zoomFactor * duration)
void establishWindowDuration(Duration duration)
void setVisibleRange(TimeSpan newWindow)
explicitly set the visible window, possibly expanding the canvas to fit.
void setVisibleDuration(Duration duration)
explicitly set the duration of the visible window range, working around the relative anchor point; po...
static TimeSpan ensureNonEmpty(TimeSpan const &span)
Rat optimiseMetric(uint pxWidth, FSecs dur, Rat rawMetric)
Reform the effective metric in all dangerous corner cases.
std::function< void()> changeSignal_
void mutateDuration(FSecs duration, uint px=0)
void conformWindowToMetricLimits(uint pxWidth)
The zoom metric factor must not become "poisonous".
void setVisiblePos(double percentage)
void conformToBounds(Rat changedMetric)
Any copy and copy construction prohibited.
Lumiera error handling (C++ interface).
boost::rational< int64_t > FSecs
rational representation of fractional seconds
const int64_t HAZARD_DEGREE
const int64_t LIM_HAZARD
Maximum quantiser to be handled in fractional arithmetics without hazard.
bool isMicroGridAligned(FSecs duration)
FSecs _FSecs(TimeValue const &timeVal)
const FSecs DEFAULT_CANVAS
int toxicDegree(Rat poison, const int64_t THRESHOLD=HAZARD_DEGREE)
const Rat ZOOM_MAX_RESOLUTION
the deepest zoom is to use 2px per micro-tick
Lumiera GTK UI implementation root.
constexpr int ilog2(I num)
Integral binary logarithm (disregarding fractional part)
IDiv< I > iDiv(I num, I den)
boost::rational< int64_t > Rat
bool can_represent_Product(int64_t a, int64_t b)
constexpr NUM limited(NB lowerBound, NUM val, NB upperBound)
force a numeric to be within bounds, inclusively
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.
bool isnil(lib::time::Duration const &dur)
bool can_represent_Sum(Rat a, Rat b)
Mix-Ins to allow or prohibit various degrees of copying and cloning.
Rational number support, based on boost::rational.
a family of time value like entities and their relationships.
Tiny helper functions and shortcuts to be used everywhere Consider this header to be effectively incl...