Timeline: complete specification of ZoomWindow expected behaviour

Writing this specification unveiled a limitation of our internal
time base implementation, which is a 64bit microsecond grid.
As it turns out, any grid based time representation will always
be not precise enough to handle some relevant time specifications,
which are defined by a divisor. Most notably this affects the precise
display of frame duration in the GUI, and even more relevant,
the sample accurate editing of sound in the timeline.

Thus I decided to perform the internal computation in ZoomWindow
as rational numbers, based on boost::rational

Note: implementation stubbed only, test fails
This commit is contained in:
Fischlurch 2022-11-04 03:40:36 +01:00
parent f1b3f4e666
commit f2ef893adb
6 changed files with 782 additions and 33 deletions

80
src/lib/rational.hpp Normal file
View file

@ -0,0 +1,80 @@
/*
RATIONAL.hpp - support for precise rational arithmetics
Copyright (C) Lumiera.org
2022, Hermann Vosseler <Ichthyostega@web.de>
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of
the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
/** @file rational.hpp
** Rational number support, based on `boost::rational`.
** As an extension to integral arithmetics, rational numbers can be defined
** as a pair (numerator, denominator); since most calculations imply multiplication
** by common factors, each calculation will be followed by normalisation to greatest
** common denominator, to keep numbers within value range. Obviously, this incurs
** a significant performance penalty while on the other hand allowing for lossless
** computations on fractional scales, which can be notoriously difficult to handle
** with floating point numbers. The primary motivation for using this number format
** is for handling fractional time values properly, e.g 1/30 sec or 1/44100 sec.
**
** The underlying implementation from boost::rational can be parametrised with various
** integral data types; since our time handling is based on 64bit integers, we mainly
** use the specialisation `boost::rational<int64_t>`.
**
** @note all compatible integral types can be automatically converted to rational
** numbers, which is a lossless conversion. The opposite is not true: to get
** a "ordinary" number be it integral or floating point an explicit
** conversion using `rational_cast<NUM> (fraction)` is necessary, which
** performs the division of `numerator/denominator` in the target value domain.
**
** @see zoom-window.hpp
** @see timevalue.hpp
*/
#ifndef LIB_RATIONAL_H
#define LIB_RATIONAL_H
#include <stdint.h>
#include <boost/rational.hpp>
namespace util {
using Rat = boost::rational<int64_t>;
using boost::rational_cast;
} // namespace util
/**
* user defined literal for constant rational numbers.
* \code
* Rat twoThirds = 2_r/3;
* \endcode
*/
inline util::Rat
operator""_r (unsigned long long num)
{
return util::Rat{num};
}
#endif /*LIB_RATIONAL_H*/

View file

@ -93,13 +93,13 @@ namespace model {
int
translateTimeToPixels (TimeValue startTimePoint) const override
{
return _raw(startTimePoint) * zoomWindow_.px_per_sec() / Time::SCALE;
return rational_cast<int> (_raw(startTimePoint) * zoomWindow_.px_per_sec() / Time::SCALE); ////////////////////TICKET #1196 : support canvas origin offset
}
TimeValue
applyScreenDelta(Time anchor, double deltaPx) const override
{
return anchor + TimeValue{gavl_time_t(Time::SCALE * deltaPx / zoomWindow_.px_per_sec())};
return anchor + TimeValue{rational_cast<gavl_time_t> (int64_t(Time::SCALE * deltaPx) / zoomWindow_.px_per_sec())}; //////TODO correct yet confusingly written
}
};

View file

@ -79,6 +79,7 @@
#include "lib/error.hpp"
#include "lib/rational.hpp"
#include "lib/time/timevalue.hpp"
#include "lib/nocopy.hpp"
//#include "lib/idi/entry-id.hpp"
@ -101,6 +102,9 @@ namespace model {
using lib::time::FSecs;
using lib::time::Time;
using util::Rat;
using util::rational_cast;
namespace {
/** the deepest zoom is to use 2px per micro-tick */
const uint ZOOM_MAX_RESOLUTION = 2 * TimeValue::SCALE;
@ -125,7 +129,7 @@ namespace model {
{
TimeVar startAll_, afterAll_,
startWin_, afterWin_;
uint px_per_sec_;
uint px_per_sec_; ///////////////////TODO use rational
public:
ZoomWindow (TimeSpan timeline =TimeSpan{Time::ZERO, FSecs(23)})
@ -136,6 +140,14 @@ namespace model {
, px_per_sec_{25}
{ }
ZoomWindow (uint pxWidth, TimeSpan timeline =TimeSpan{Time::ZERO, FSecs(23)})
: startAll_{timeline.start()}
, afterAll_{nonEmpty(timeline.end())}
, startWin_{startAll_}
, afterWin_{afterAll_}
, px_per_sec_{25}
{ }
TimeSpan
overallSpan() const
{
@ -148,17 +160,30 @@ namespace model {
return TimeSpan{startWin_, afterWin_};
}
uint
Rat
px_per_sec() const
{
return px_per_sec_;
}
uint
pxWidth() const
{
REQUIRE (0 < _raw(afterWin_ - startWin_));
return rational_cast<uint> (px_per_sec() / FSecs(afterWin_-startWin_));
}
/* === Mutators === */
void
setMetric (uint px_per_sec)
calibrateExtension (uint pxWidth)
{
UNIMPLEMENTED ("calibrateExtension");
}
void
setMetric (Rat px_per_sec)
{
UNIMPLEMENTED ("setMetric");
}

View file

@ -28,15 +28,7 @@
#include "lib/test/run.hpp"
#include "lib/test/test-helper.hpp"
#include "stage/model/zoom-window.hpp"
//#include "lib/util.hpp"
//#include <utility>
//#include <memory>
//using util::isSameObject;
//using std::make_unique;
//using std::move;
namespace stage{
@ -44,14 +36,10 @@ namespace model{
namespace test {
namespace { // Test fixture...
namespace { // simplified notation for expected results...
// template<typename X>
// struct DummyWidget
// : public sigc::trackable
// {
// X val = 1 + rand() % 100;
// };
inline Time _t(int secs) { return Time(FSecs(secs)); }
inline Time _t(int s, int div) { return Time(FSecs(s,div)); }
}
@ -73,7 +61,21 @@ namespace test {
virtual void
run (Arg)
{
// Explanation of the notation used in this test...
CHECK (_t(10) == Time{FSecs(10)}); // Time point at t = 10sec
CHECK (_t(10,3) == Time{FSecs(10,3)}); // Time point at t = 10/3sec (fractional number)
CHECK (FSecs(10,3) == FSecs(10)/3); // fractional number arithmetics
CHECK (FSecs(10)/3 == 10_r/3); // _r is a user defined literal to denote 64-bit fractional
CHECK (10_r/3 == Rat(10,3));
CHECK (Rat(10/3) == boost::rational<int64_t>(10,3)); // using Rat = boost::rational<int64_t>
CHECK (rational_cast<float> (10_r/3) == 3.33333f); // rational_cast performs the division with indicated type
verify_simpleUsage();
verify_setup();
verify_calibration();
verify_metric();
verify_window();
verify_scroll();
}
@ -82,23 +84,312 @@ namespace test {
verify_simpleUsage()
{
ZoomWindow zoomWin;
CHECK (zoomWin.overallSpan() == TimeSpan(Time::ZERO, Time(FSecs(23))));
CHECK (zoomWin.visible() == TimeSpan(Time::ZERO, Time(FSecs(23))));
CHECK (zoomWin.overallSpan() == TimeSpan(_t(0), FSecs(23)));
CHECK (zoomWin.visible() == TimeSpan(_t(0), FSecs(23)));
CHECK (zoomWin.px_per_sec() == 25);
zoomWin.nudgeMetric(+1);
CHECK (zoomWin.px_per_sec() == 50);
CHECK (zoomWin.visible() == TimeSpan(Time(FSecs(23,4)), FSecs(23,2)));
CHECK (zoomWin.overallSpan() == TimeSpan(Time::ZERO, Time(FSecs(23))));
CHECK (zoomWin.visible() == TimeSpan(_t(23,4), FSecs(23,2)));
CHECK (zoomWin.overallSpan() == TimeSpan(_t(0), FSecs(23)));
zoomWin.nudgeVisiblePos(-1);
CHECK (zoomWin.px_per_sec() == 50);
CHECK (zoomWin.visible() == TimeSpan(Time::ZERO, FSecs(23,2)));
CHECK (zoomWin.overallSpan() == TimeSpan(Time::ZERO, Time(FSecs(23))));
CHECK (zoomWin.visible() == TimeSpan(_t(0), FSecs(23,2)));
CHECK (zoomWin.overallSpan() == TimeSpan(_t(0), FSecs(23)));
}
/** @test */
/** @test verify the possible variations for initial setup of the zoom window
* - can be defined either the canvas duration, or an explicit extension
* given in pixels, or both
* - window extension, when given, defines the visible span
* - otherwise the whole canvas is visible, thereby defining the metric
*/
void
verify_setup()
{
ZoomWindow win1;
CHECK (win1.overallSpan() == TimeSpan(_t(0), FSecs(23)));
CHECK (win1.visible() == win1.overallSpan());
CHECK (win1.px_per_sec() == 25);
CHECK (win1.pxWidth() == 23*25);
ZoomWindow win2{TimeSpan{_t(-1), _t(+1)}};
CHECK (win2.overallSpan() == TimeSpan(_t(-1), FSecs(2)));
CHECK (win2.visible() == win2.overallSpan());
CHECK (win2.px_per_sec() == 25);
CHECK (win2.pxWidth() == 2*25);
ZoomWindow win3{555};
CHECK (win3.overallSpan() == TimeSpan(_t(0), FSecs(23)));
CHECK (win3.pxWidth() == 555);
CHECK (win3.px_per_sec() == 555_r/23);
CHECK (win3.visible() == win3.overallSpan());
ZoomWindow win4{555, TimeSpan{_t(-10), _t(-5)}};
CHECK (win4.overallSpan() == TimeSpan(_t(10), FSecs(5)));
CHECK (win4.pxWidth() == 555);
CHECK (win4.px_per_sec() == 111);
CHECK (win4.visible() == win4.overallSpan());
}
/** @test verify defining and retaining of the effective extension in pixels
* - changes to the extension are applied by adjusting the visible window
* - visible window's start position is maintained
* - unless the resulting window would exceed the overall canvas,
* in which case the window is shifted, retaining metrics
* - however, if resulting window can not be made to fit, it is truncated
* to current canvas and metric is adjusted to keep overall pixel extension
*/
void
verify_calibration()
{
ZoomWindow win;
CHECK (win.overallSpan() == TimeSpan(_t(0), FSecs(23)));
CHECK (win.visible() == TimeSpan(_t(0), FSecs(23)));
CHECK (win.pxWidth() == 23*25);
win.calibrateExtension(25);
CHECK (win.overallSpan() == TimeSpan(_t(0), FSecs(23)));
CHECK (win.visible() == TimeSpan(_t(0), FSecs(1)));
CHECK (win.px_per_sec() == 25);
CHECK (win.pxWidth() == 25);
win.setOverallRange(TimeSpan{_t(-50), _t(50)});
CHECK (win.overallSpan() == TimeSpan(_t(-50), FSecs(100)));
CHECK (win.visible() == TimeSpan(_t(0), FSecs(1)));
CHECK (win.px_per_sec() == 25);
CHECK (win.pxWidth() == 25);
win.calibrateExtension(100);
CHECK (win.overallSpan() == TimeSpan(_t(-50), FSecs(100)));
CHECK (win.visible() == TimeSpan(_t(0), FSecs(4)));
CHECK (win.px_per_sec() == 25);
CHECK (win.pxWidth() == 100);
win.setRanges (TimeSpan{_t(-50), _t(10)}, TimeSpan{_t(-10), FSecs(10)});
CHECK (win.overallSpan() == TimeSpan(_t(-50), FSecs(60)));
CHECK (win.visible() == TimeSpan(_t(-10), _t(0)));
CHECK (win.px_per_sec() == 10);
CHECK (win.pxWidth() == 100);
win.calibrateExtension(500);
CHECK (win.overallSpan() == TimeSpan(_t(-50), FSecs(60)));
CHECK (win.visible() == TimeSpan(_t(-40), FSecs(50)));
CHECK (win.px_per_sec() == 10);
CHECK (win.pxWidth() == 500);
win.setOverallDuration (Duration{FSecs(30)});
CHECK (win.overallSpan() == TimeSpan(_t(-50), _t(-20)));
CHECK (win.visible() == TimeSpan(_t(-50), FSecs(30)));
CHECK (win.px_per_sec() == 500_r/30);
CHECK (win.pxWidth() == 500);
win.calibrateExtension(300);
CHECK (win.overallSpan() == TimeSpan(_t(-50), _t(-20)));
CHECK (win.visible() == TimeSpan(_t(-50), FSecs(30)*3/5));
CHECK (win.px_per_sec() == 500_r/30);
CHECK (win.pxWidth() == 300);
}
/** @test zoom in and out, thereby adjusting the metric
* - window extension in pixels is always retained
* - window is shifted when surpassing canvas bounds
* - metric is adjusted to keep excess window within pixel extension
* - otherwise zooming is centred around an anchor position, favouring centre
*/
void
verify_metric()
{
ZoomWindow win{1280, TimeSpan{_t(0), FSecs(64)}};
CHECK (win.px_per_sec() == 20);
win.nudgeMetric(+1);
CHECK (win.overallSpan() == TimeSpan(_t(0), _t(64)));
CHECK (win.visible() == TimeSpan(_t(-32,2), FSecs(32)));
CHECK (win.px_per_sec() == 40);
CHECK (win.pxWidth() == 1280);
win.setVisiblePos(0.0);
CHECK (win.visible() == TimeSpan(_t(0), FSecs(32))); // zoom window moved to left side of overall range
win.nudgeMetric(+15);
CHECK (win.overallSpan() == TimeSpan(_t(0), _t(64)));
CHECK (win.visible() == TimeSpan(_t(0), FSecs(32,32768))); // now anchor position is at left bound
CHECK (win.px_per_sec() == 40*32768);
CHECK (win.pxWidth() == 1200);
// Note: already getting close to the time grid...
CHECK (win.visible().end() == TimeValue(976));
CHECK (Time(FSecs(32,32768)) == TimeValue(976));
CHECK (rational_cast<double> (32_r/32768 * Time::SCALE) == 976.5625);
win.nudgeMetric(+1);
CHECK (win.overallSpan() == TimeSpan(_t(0), _t(64)));
CHECK (win.px_per_sec() == ZOOM_MAX_RESOLUTION); // further zoom has been capped at 2px per µ-tick
CHECK (win.visible() == TimeSpan(_t(0), FSecs(1280_r/ZOOM_MAX_RESOLUTION)));
CHECK (win.pxWidth() == 1280);
win.nudgeMetric(+1);
CHECK (win.px_per_sec() == ZOOM_MAX_RESOLUTION);
win.setMetric(10*ZOOM_MAX_RESOLUTION);
CHECK (win.px_per_sec() == ZOOM_MAX_RESOLUTION);
// so this is the deepest zoom possible....
CHECK (win.visible().duration() == TimeValue(640));
CHECK (TimeValue(640) == _t(1280,ZOOM_MAX_RESOLUTION));
// and this the absolutely smallest possible zoom window
win.calibrateExtension(2);
CHECK (win.overallSpan() == TimeSpan(_t(0), _t(64)));
CHECK (win.visible().duration() == TimeValue(1));
CHECK (win.px_per_sec() == ZOOM_MAX_RESOLUTION);
CHECK (win.pxWidth() == 2);
win.calibrateExtension(1);
CHECK (win.visible().duration() == TimeValue(1)); // window is guaranteed to be non-empty
CHECK (win.px_per_sec() == ZOOM_MAX_RESOLUTION / 2); // zoom scale has thus been lowered to prevent window from vanishing
CHECK (win.pxWidth() == 1);
win.calibrateExtension(1280);
CHECK (win.visible().duration() == TimeValue(1280));
CHECK (win.px_per_sec() == ZOOM_MAX_RESOLUTION / 2);
CHECK (win.pxWidth() == 1280);
CHECK (win.overallSpan() == TimeSpan(_t(0), _t(64)));
win.nudgeMetric(-5);
CHECK (win.visible().duration() == Duration{32 * FSecs(1280/Time::SCALE)});
CHECK (win.px_per_sec() == ZOOM_MAX_RESOLUTION / 64);
CHECK (win.pxWidth() == 1280);
CHECK (win.overallSpan() == TimeSpan(_t(0), _t(64)));
win.nudgeMetric(-12);
CHECK (win.visible() == win.overallSpan()); // zoom out stops at full canvas size
CHECK (win.overallSpan() == TimeSpan(_t(0), _t(64)));
CHECK (win.px_per_sec() == 20);
CHECK (win.pxWidth() == 1280);
// but canvas can be forcibly extended by »reverse zooming«
win.expandVisibleRange (TimeSpan{_t(60), _t(62)}); // zoom such as to bring current window at given relative position
CHECK (win.px_per_sec() == 20_r/64*2); // scale thus adjusted to reduce 64 sec to 2 sec (scale can be fractional!)
CHECK (win.visible().duration() == _t(64 * 32)); // zoom window has been inversely expanded by factor 64/2 == 32
CHECK (win.visible() == win.overallSpan()); // zoom fully covers the expanded canvas
CHECK (win.overallSpan() == TimeSpan(_t(-1920), _t(128))); // and overall canvas has been expanded to embed the previous window
CHECK (win.overallSpan().duration() == _t(2048)); // ... at indicated relative position (2sec ⟼ 64sec, one window size before end)
// metric can be explicitly set (e.g. 5px per sound sample)
win.setMetric (5 * 1_r/44100);
CHECK (win.pxWidth() == 1280);
CHECK (win.visible().duration() == Duration{FSecs(5,44100)*1280});
CHECK (win.overallSpan().duration() == _t(2048));
}
/** @test position and extension of the visible window can be set explicitly */
void
verify_window()
{
ZoomWindow win{1280, TimeSpan{_t(0), FSecs(64)}};
CHECK (win.visible() == win.overallSpan());
CHECK (win.px_per_sec() == 20);
win.setVisibleDuration (Duration{FSecs(23,30)});
CHECK (win.visible().duration() == _t(23,30));
CHECK (win.visible().start() == Time(FSecs(64)/2 - FSecs(23/30)/2)); // when zooming down from full range, zoom anchor is window centre
CHECK (win.px_per_sec() == 1280_r/23*30);
CHECK (win.pxWidth() == 1280);
win.setVisibleRange (TimeSpan{_t(12), FSecs(16)});
CHECK (win.visible() == TimeSpan(_t(12), _t(12+16)));
CHECK (win.overallSpan() == TimeSpan(_t(0), _t(64)));
CHECK (win.px_per_sec() == 1280_r/16);
CHECK (win.pxWidth() == 1280);
win.setVisiblePos(_t(12)); // bring a specific position into sight
CHECK (win.visible().start() < _t(12)); // window is placed such as to enclose this desired position
CHECK (win.visible().duration() == _t(16)); // window size and metric not changed
CHECK (win.overallSpan() == TimeSpan(_t(0), _t(64)));
CHECK (win.px_per_sec() == 1280_r/16);
CHECK (win.pxWidth() == 1280);
win.setVisiblePos(0.80); // positioning relatively within overall canvas
CHECK (win.visible().start() < Time{FSecs(64)*8/10}); // window will enclose the desired anchor position
CHECK (win.visible().end() > Time{FSecs(64)*8/10});
CHECK (win.px_per_sec() == 1280_r/16);
CHECK (win.pxWidth() == 1280);
// manipulate canvas extension explicitly
win.setOverallDuration (Duration{FSecs(3600)});
CHECK (win.overallSpan() == TimeSpan(_t(0), _t(3600)));
CHECK (win.px_per_sec() == 1280_r/16);
CHECK (win.pxWidth() == 1280);
CHECK (win.visible().duration() == _t(16)); // window position and size not affected
CHECK (win.visible().start() < Time{FSecs(64)*8/10});
CHECK (win.visible().end() > Time{FSecs(64)*8/10});
// reposition nominal canvas anchoring
win.setOverallRange (TimeSpan{_t(-64), _t(-32)});
CHECK (win.overallSpan() == TimeSpan(_t(-64), FSecs(32))); // canvas nominally covers a completely different time range now
CHECK (win.px_per_sec() == 1280_r/16); // metric is retained
CHECK (win.pxWidth() == 1280);
CHECK (win.visible() == TimeSpan(_t(-32-16), FSecs(16))); // window scrolled left to remain within canvas
win.setOverallStart (_t(100));
CHECK (win.overallSpan() == TimeSpan(_t(100), FSecs(32)));
CHECK (win.visible() == TimeSpan(_t(100), FSecs(16))); // window scrolled right to remain within canvas
CHECK (win.px_per_sec() == 1280_r/16); // metric is retained
win.setOverallRange (TimeSpan{_t(50), _t(52)});
CHECK (win.overallSpan() == TimeSpan(_t(100), FSecs(2)));
CHECK (win.visible() == TimeSpan(_t(100), FSecs(16))); // window truncated to fit into canvas
CHECK (win.px_per_sec() == 1280_r/2); // metric need to be adjusted
CHECK (win.pxWidth() == 1280);
}
/** @test sliding the visible window, possibly expanding canvas */
void
verify_scroll()
{
ZoomWindow win{1280, TimeSpan{_t(0), FSecs(16)}};
CHECK (win.visible() == win.overallSpan());
CHECK (win.visible() == TimeSpan(_t(0), FSecs(16)));
CHECK (win.px_per_sec() == 80);
win.nudgeVisiblePos(+1);
CHECK (win.visible() == TimeSpan(_t(8), FSecs(16))); // window shifted forward by half a page
CHECK (win.overallSpan() == TimeSpan(_t(0), FSecs(16+8))); // canvas expanded accordingly
CHECK (win.px_per_sec() == 80); // metric is retained
CHECK (win.pxWidth() == 1280);
win.nudgeVisiblePos(-3);
CHECK (win.visible() == TimeSpan(_t(-16), FSecs(16))); // window shifted backwards by three times half window sizes
CHECK (win.overallSpan() == TimeSpan(_t(-16), FSecs(16+8+32))); // canvas is always expanded accordingly, never shrinked
CHECK (win.px_per_sec() == 80); // metric is retained
CHECK (win.pxWidth() == 1280);
win.setVisiblePos(0.50);
CHECK (win.visible() == TimeSpan(_t((56/2-16) -8), FSecs(16))); // window positioned to centre of canvas
win.setVisiblePos(-0.50);
CHECK (win.visible() == TimeSpan(_t(-16), FSecs(16))); // relative positioning limited at lower bound
win.setVisiblePos(2.34);
CHECK (win.visible() == TimeSpan(_t(56-16-16), FSecs(16))); // relative positioning limited at upper bound
win.setVisiblePos(_t(200));
CHECK (win.visible() == TimeSpan(_t(56-16-16), FSecs(16))); // absolute positioning limited likewise
win.setVisiblePos(_t(-200));
CHECK (win.visible() == TimeSpan(_t(-16), FSecs(16)));
CHECK (win.px_per_sec() == 80); // metric retained
CHECK (win.pxWidth() == 1280);
win.setVisibleRange(TimeSpan{_t(-200), FSecs(32)}); // but explicit positioning outside of canvas is possible
CHECK (win.overallSpan() == TimeSpan(_t(-200), _t(16+8))); // ...and will expand canvas
CHECK (win.visible() == TimeSpan(_t(-200), FSecs(32)));
CHECK (win.px_per_sec() == 40);
CHECK (win.pxWidth() == 1280);
}
};

View file

@ -10505,18 +10505,20 @@ Wiring requests are small stateful value objects. They will be collected, sorted
&amp;rarr; ConManager
</pre>
</div>
<div title="ZoomWindow" creator="Ichthyostega" modifier="Ichthyostega" created="202210281528" modified="202210282204" tags="spec GuiPattern draft" changecount="16">
<div title="ZoomWindow" creator="Ichthyostega" modifier="Ichthyostega" created="202210281528" modified="202211040231" tags="spec GuiPattern draft" changecount="18">
<pre>//A component for uniform handling of zoom scale and visible interval on the timeline.//
Working with and arranging media requires a lot of //navigation// and changes of //zoom detail level.// More specifically, the editor is required to repeatedly return //to the same locations// and show arrangements at the same alternating scale levels. Most existing editing applications approach this topic naïvely, by just responding to some coarse grained interaction controls -- thereby creating the need for a lot of superfluous and tedious search and navigation activities, causing constant grind for the user. And resolving these obnoxious shortcomings turns out as a never ending task, precisely due to the naïve and ad hoc approach initially taken.
Based on these observations, the design of the Lumiera UI calls for centralisation of all zoom- and navigation handling into a single component, instantiated once for every visible context, outfitted with the ability to capture and maintain a //history of zoom and navigation activities.// This component is called »''Zoom Window''«, since it represents a window-like local visible interval, embedded into a larger time span covering the whole timeline. The //current zoom state// is thus defined by
* the overall {{{TimeSpan}}} of the timeline, including a start time (inclusive) and an end time (exclusive)
* the overall {{{TimeSpan}}} of the timeline canvas, including a start time (inclusive) and an end time (exclusive)
* the //visible interval// („window“), likewise modelled as {{{time::TimeSpan}}}
* the //scale// defined as pixels per second {{red{10/2022 -- imposing a hard limit at 1px / sec ≙ 20min per screen page}}}
* the //scale// defined as pixels per second
** this is a //fractional value// -- allowing to specify multiples of frame sizes or sound sample durations
** maximum scale limited to 2px per µ-tick; widest zoom limited to overall canvas duration
!Requirement Analysis
The Timeline UI is {{red{as of 10/2022}}} specified sufficiently to serve as framework to determine the requirements of a zoom handling component
!!!User
!!!Usages
;~CanvasHook + ~DisplayMetric
:info | pull
:*Pixel / sec
@ -10601,10 +10603,18 @@ The Timeline UI is {{red{as of 10/2022}}} specified sufficiently to serve as fra
#*translations
#*window offset
#*overall range
#*resulting window extension in pixels
!!!Semantics
The {{{overallSpan}}} corresponds to the whole canvas extension
The {{{visibleWindow}}} corresponds to the actually visible part, and the //metric// needs to be calibrated, to make the window's extension in pixel match the actual size in the UI. All further manipulations are assumed to keep that pixel extension constant. The actual duration of the timeline is in no way limited, but usually you'd expect the timeline to fit onto the canvas -- the canvas may even be extended when the user wants to extend the timeline beyond existing bounds.
!Implementation
The above requirement analysis reveals a common handling scheme, which is best served by a conventional design with an object, mutator and getter methods and encapsulated state. Moreover, a single client for push notification can be identified: the ~TimelineLayout (implementation of the [[DisplayManager interface|TimelineDisplayManager]]; it suffices to notify this collaboration partner; since at implementation level the ZoomWindow is itself embedded by mix-in into the ~TimelineLayout, the latter can easily read the resulting zoom parameters and react accordingly.
Lumiera uses a µs-grid as base for the internal time representation {{red{11/2022 might revisit this decision, see #1258}}}, which generally is a good balance between performance and the addressable value range. However, for these calculations aimed at pixel precise drawing, this even this micro grid turns out to be not precise enough, especially for sample accurate sound automation. To work around this limitation, the ~ZoomWindow //metric// (scale factor) is represented as fractional number (implemented as {{{boost::rational&lt;int64_t&gt;}}}. This allows to carry out internal calculations involving scale factors with lossless integral arithmetics, and thus precisely to retain a given window extension in screen pixels.
</pre>
</div>
<div title="automation" modifier="Ichthyostega" created="200805300057" modified="200805300125" tags="overview">

View file

@ -24268,7 +24268,7 @@
<node CREATED="1541857252860" ID="ID_298952812" MODIFIED="1557498707226" TEXT="Zeit-Rasterung (Time-Grid)"/>
<node CREATED="1541857273978" ID="ID_10543409" MODIFIED="1557498707226" TEXT="managing the Zoom-Window"/>
</node>
<node CREATED="1541857340976" ID="ID_433103364" MODIFIED="1557498707226" TEXT="Lumiera Time Framework">
<node CREATED="1541857340976" ID="ID_433103364" LINK="#ID_1054510557" MODIFIED="1667337152477" TEXT="Lumiera Time Framework">
<node CREATED="1541857349526" ID="ID_1259099260" MODIFIED="1557498707226" TEXT="bietet Konzept &quot;Grid&quot;"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1666452057169" ID="ID_1202075896" MODIFIED="1666452205554" TEXT="Aufgabe: DisplayMetric bereitstellen">
@ -38244,6 +38244,48 @@
<node CREATED="1666965749637" ID="ID_1024412644" MODIFIED="1666965752128" TEXT="overall range"/>
</node>
</node>
<node CREATED="1667253467195" ID="ID_583983335" MODIFIED="1667253470876" TEXT="Semantics">
<node CREATED="1667254016236" ID="ID_1599338404" MODIFIED="1667260350202" TEXT="zwei m&#xf6;gliche Auslegungen">
<linktarget COLOR="#5cbca7" DESTINATION="ID_1599338404" ENDARROW="Default" ENDINCLINATION="-266;683;" ID="Arrow_ID_1992699101" SOURCE="ID_1789035337" STARTARROW="None" STARTINCLINATION="-195;-13;"/>
<node CREATED="1667254281663" ID="ID_931025074" MODIFIED="1667254961650" TEXT="overallSpan &#x2259; Timeline">
<icon BUILTIN="button_cancel"/>
<node CREATED="1667254042344" ID="ID_1886040600" MODIFIED="1667254193233" TEXT="visualWindow &#x2259; was sichtbar ist"/>
<node CREATED="1667254330394" ID="ID_192603163" MODIFIED="1667254344380" TEXT="&#x27f9; visualWindow kann dar&#xfc;ber hinausreichen"/>
<node CREATED="1667254349351" ID="ID_1260830853" MODIFIED="1667254356411" TEXT="Canvas ergibt sich implizit"/>
</node>
<node CREATED="1667253493640" ID="ID_29660572" MODIFIED="1667254964809" TEXT="overallSpan &#x2259; Canvas">
<icon BUILTIN="forward"/>
<node CREATED="1667254042344" ID="ID_6801078" MODIFIED="1667254193233" TEXT="visualWindow &#x2259; was sichtbar ist"/>
<node CREATED="1667254132888" ID="ID_907807797" MODIFIED="1667254189985" TEXT="&#x27f9; visualWindow &#x2208; Canvas"/>
<node CREATED="1667254209434" ID="ID_1299162840" MODIFIED="1667254216213" TEXT="st&#xf6;&#xdf;t am Rand des Canvas an"/>
</node>
</node>
<node CREATED="1667254983055" ID="ID_1939856323" MODIFIED="1667254987549" TEXT="Eichung der Metrik">
<node CREATED="1667254988850" ID="ID_627529918" MODIFIED="1667255165088" TEXT="mu&#xdf; (initial) explizit gesezt werden">
<icon BUILTIN="info"/>
</node>
<node CREATED="1667255003826" ID="ID_457439715" MODIFIED="1667255160405" TEXT="soll der Ausdehnung im Interface entsprechen">
<icon BUILTIN="yes"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1667255062416" ID="ID_888315158" MODIFIED="1667255155045" TEXT="Erwartung: erh&#xe4;lt sich von selbst numerisch konstant">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1667255198638" ID="ID_1458374199" MODIFIED="1667255216695" TEXT="da wir Integer-Arithmetik machen"/>
<node CREATED="1667255218491" ID="ID_1646057612" MODIFIED="1667255227637" TEXT="und stets logisch-konsistent vorgehen"/>
</node>
<node CREATED="1667255234369" ID="ID_713280456" MODIFIED="1667255281215" TEXT="falls das nicht klappt....">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
...dann m&#252;ssen wir die Vorgabe speichern, und in jedem Schritt korrigierend eingreifen
</p>
</body>
</html></richcontent>
</node>
</node>
</node>
</node>
<node CREATED="1541860963705" ID="ID_13414263" MODIFIED="1557498707234" TEXT="Design">
<node CREATED="1541860967281" ID="ID_1676646773" MODIFIED="1557498707234" TEXT="manipulativ - mutable">
@ -38478,7 +38520,202 @@
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1667260101665" ID="ID_1250580560" MODIFIED="1667260118385" TEXT="mu&#xdf; die Ausdehnung in Pixel beachten">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1667260119686" ID="ID_1154266769" MODIFIED="1667260272187" TEXT="neuer Getter daf&#xfc;r">
<icon BUILTIN="flag-yellow"/>
</node>
<node COLOR="#435e98" CREATED="1667260284128" ID="ID_1789035337" MODIFIED="1667260350202" TEXT="Frage nach der Semantik?">
<arrowlink COLOR="#5cbca7" DESTINATION="ID_1599338404" ENDARROW="Default" ENDINCLINATION="-266;683;" ID="Arrow_ID_1992699101" STARTARROW="None" STARTINCLINATION="-195;-13;"/>
<icon BUILTIN="help"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1667260130116" ID="ID_299421938" MODIFIED="1667260272189" TEXT="sollte stets konstant bleiben &#x27f9; im Test beobachten">
<icon BUILTIN="flag-yellow"/>
</node>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1667349674019" ID="ID_723979887" MODIFIED="1667349702385" TEXT="Problem: Rechnung geht nicht auf">
<icon BUILTIN="broken-line"/>
<node CREATED="1667349751008" ID="ID_952333352" MODIFIED="1667349762431" TEXT="runde auf n&#xe4;chsten Pixel">
<node CREATED="1667349764204" ID="ID_1815738825" MODIFIED="1667349818467" TEXT="Konsequenz &#x27f9; visible Window anpassen"/>
<node CREATED="1667349788035" ID="ID_281010107" MODIFIED="1667349803183" TEXT="Problem: Window kann gr&#xf6;&#xdf;er werden als Canvas">
<icon BUILTIN="messagebox_warning"/>
</node>
<node CREATED="1667349853811" ID="ID_1951953580" MODIFIED="1667350032602" TEXT="Canvas vergr&#xf6;&#xdf;ern? oder visible Window verkleinern?">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<ul>
<li>
Canvas vergr&#246;&#223;ern &#10233; f&#252;hrt zu kontinuierliecher Drift; der Canvas wird fortlaufend gr&#246;&#223;er
</li>
<li>
Window verkleinern &#10233; hei&#223;t da&#223; man u.U den ganzen Canvas nicht ohne Rest darstellen kann
</li>
</ul>
</body>
</html></richcontent>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1667350040338" ID="ID_1862995353" MODIFIED="1667350064695" TEXT="eigentliches Problem: diese Effekte sind relativ stark">
<icon BUILTIN="idea"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1667349713685" ID="ID_47270921" MODIFIED="1667349842399" TEXT="Metrik-Definition ist zu grobgranular">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1667350180367" ID="ID_494068879" MODIFIED="1667350205920" TEXT="Sekunden sind eine ziemlich beliebige Basiseinheit"/>
<node COLOR="#990033" CREATED="1667350206652" ID="ID_918270163" MODIFIED="1667528235333" TEXT="stattdessen: &#xb5;-Tick pro Pixel?">
<icon BUILTIN="help"/>
<node CREATED="1667350440373" ID="ID_1896628944" MODIFIED="1667350460022" TEXT="Vorsicht... Minimum w&#xe4;re dann 1&#xb5;T/px"/>
<node CREATED="1667350475136" ID="ID_1672716361" MODIFIED="1667350817413" TEXT="&#x27f9; Sound-Samples lassen sich nicht sauber darstellen">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
alle 20 Schritte ein Sprung, bzw. sogar nur alle 10 Schritte bei 96kHz, denn 1/96000 = 10.41666666 &#181;Tick
</p>
</body>
</html></richcontent>
</node>
<node CREATED="1667350536992" ID="ID_1838246834" MODIFIED="1667476855366" TEXT="das stellt die Implementierungs-Basis in Frage">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
Die &#181;-Ticks hatten wir seinerzeit gew&#228;hlt, weil sie einerseits hinreichend genau sind, andererseits sehr einfach zu implementieren, und dennoch die Darstellung extrem gro&#223;er Zeitspannen erm&#246;glichen
</p>
</body>
</html></richcontent>
<arrowlink COLOR="#af1a4c" DESTINATION="ID_767502481" ENDARROW="Default" ENDINCLINATION="96;-430;" ID="Arrow_ID_1672039685" STARTARROW="None" STARTINCLINATION="-670;38;"/>
<icon BUILTIN="stop-sign"/>
<node BACKGROUND_COLOR="#d2beaf" COLOR="#5c4d6e" CREATED="1667487735017" ID="ID_67642064" MODIFIED="1667487922535" TEXT="schwerwiegende Diskusison auf sp&#xe4;ter verschoben (#1258)">
<icon BUILTIN="hourglass"/>
</node>
<node CREATED="1667487765399" ID="ID_1902275269" MODIFIED="1667487918703" TEXT="denn das Problem tritt nur stellenweise auf und ist halb-theoretisch">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
Tats&#228;chlich kann ein &#181;Tick-Grid auch Sound-Samples korrekt addressieren &#8212; man darf dann nur nicht diese Zeit-Werte f&#252;r weitere Berechnungen verwenden (denn sonst sammeln sich Rundungsfehler an). Es k&#246;nnte also eine Implementierung eben <i>wissen, </i>da&#223; hier Sound-Samples dargestellt/verarbeitet werden, und intern mit der exakten Skala arbeiten. Im Grunde ist das ein L&#246;sungsvorgriff auf die 3.L&#246;sungsvariante (Problem ignorieren und per Metadaten tunneln)... siehe Diskussion in #1258
</p>
</body>
</html></richcontent>
<icon BUILTIN="idea"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#f0d5c5" COLOR="#990033" CREATED="1667487705856" ID="ID_1110286196" MODIFIED="1667487720215" TEXT="fraktionale &#xb5;-Tick pro Pixel">
<icon BUILTIN="help"/>
<node CREATED="1667487984471" ID="ID_1826402546" MODIFIED="1667487992109" TEXT="intern also eine Bruchzahl speichern"/>
<node CREATED="1667487993113" ID="ID_452961268" MODIFIED="1667488005420" TEXT="Zahlen w&#xe4;ren auch numerisch viel gutm&#xfc;tiger"/>
<node CREATED="1667488026717" ID="ID_267648014" MODIFIED="1667488073400" TEXT="man k&#xf6;nnte dennoch den grobgranularen Getter bestehen lassen">
<icon BUILTIN="button_cancel"/>
<node CREATED="1667488090679" ID="ID_829965437" MODIFIED="1667488105799" TEXT="Gefahr: die Werte sind nur nahezu exakt">
<icon BUILTIN="messagebox_warning"/>
</node>
<node CREATED="1667488074367" ID="ID_710829210" MODIFIED="1667488114221" TEXT="Nein! das verlockt gradezu zu falscher Verwendung">
<icon BUILTIN="stop-sign"/>
</node>
<node CREATED="1667489964330" ID="ID_15160345" MODIFIED="1667491231003" TEXT="wenn schon: dann mu&#xdf; er einen Bruch liefern">
<arrowlink COLOR="#685b9d" DESTINATION="ID_1961281272" ENDARROW="Default" ENDINCLINATION="48;-77;" ID="Arrow_ID_1581272535" STARTARROW="None" STARTINCLINATION="-422;27;"/>
</node>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1667491005864" ID="ID_822261439" MODIFIED="1667528245728" TEXT="Metrik als Bruch behaldeln">
<icon BUILTIN="pencil"/>
<node CREATED="1667491014630" ID="ID_1961281272" MODIFIED="1667491231004" TEXT="bleibe aber beim Schema px_per_sec">
<linktarget COLOR="#685b9d" DESTINATION="ID_1961281272" ENDARROW="Default" ENDINCLINATION="48;-77;" ID="Arrow_ID_1581272535" SOURCE="ID_15160345" STARTARROW="None" STARTINCLINATION="-422;27;"/>
<icon BUILTIN="yes"/>
<node CREATED="1667491099595" ID="ID_1763425828" MODIFIED="1667491114813" TEXT="konkret erwarte ich da handliche Werte"/>
<node CREATED="1667491115829" ID="ID_650357166" MODIFIED="1667491143008" TEXT="&#xb5;-Ticks sind immer ziemlich gro&#xdf;e Zahlen"/>
<node CREATED="1667491149381" ID="ID_1912148103" MODIFIED="1667491191987" TEXT="besser: Kurz-Notation einf&#xfc;hren (user-defined-literal)">
<icon BUILTIN="idea"/>
</node>
</node>
<node BACKGROUND_COLOR="#ccb59b" COLOR="#6e2a38" CREATED="1667488158059" ID="ID_898168096" MODIFIED="1667491200716" TEXT="neue Zielvorgabe: Pixel-Angaben sind stets pr&#xe4;zise">
<font ITALIC="true" NAME="SansSerif" SIZE="14"/>
<icon BUILTIN="yes"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1667517436741" ID="ID_1018648571" MODIFIED="1667528257052" TEXT="Bruch-Notation einf&#xfc;hren">
<icon BUILTIN="pencil"/>
<node CREATED="1667517450483" ID="ID_854101466" MODIFIED="1667517461957" TEXT="Rat = boost::rational&lt;int64_t&gt;"/>
<node CREATED="1667517463369" ID="ID_1946096292" MODIFIED="1667517490241">
<richcontent TYPE="NODE"><html>
<head>
</head>
<body>
<p>
user-defined Literal: <font face="Monospaced" color="#5618bd">_r</font>
</p>
</body>
</html></richcontent>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1667260366701" ID="ID_1895903283" MODIFIED="1667528268261" TEXT="n&#xe4;chste F&#xe4;lle">
<icon BUILTIN="pencil"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1667260373164" ID="ID_1630007510" MODIFIED="1667260483982" TEXT="Konstruktor">
<icon BUILTIN="flag-yellow"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1667260420318" ID="ID_477529431" MODIFIED="1667260481687" TEXT="komplett default">
<icon BUILTIN="flag-yellow"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1667260430580" ID="ID_750011979" MODIFIED="1667260481689" TEXT="partielle Angaben">
<icon BUILTIN="flag-yellow"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1667260502770" ID="ID_723001683" MODIFIED="1667260512843" TEXT="Metrik kalibrieren">
<icon BUILTIN="flag-yellow"/>
<node CREATED="1667517362063" ID="ID_692971631" MODIFIED="1667517374025" TEXT="Metrik-&#xc4;nderung wird auf das aktuelle window angewendet"/>
<node CREATED="1667517374757" ID="ID_1436498397" MODIFIED="1667517393335" TEXT="danach aber shift/scoll, damit das Window im Canvas bleibt"/>
<node CREATED="1667517394850" ID="ID_360062482" MODIFIED="1667517421035" TEXT="Beschnitt, wenn das Window zu gro&#xdf; wird &#x27f9; Metrik dann angepa&#xdf;t"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1667318608370" ID="ID_888607544" MODIFIED="1667318613506" TEXT="Metrik manipulieren">
<icon BUILTIN="flag-yellow"/>
<node CREATED="1667318655807" ID="ID_1664936439" MODIFIED="1667318699099" TEXT="starke Einschr&#xe4;nkung der Testbarkeit hier">
<icon BUILTIN="messagebox_warning"/>
</node>
<node CREATED="1667318668477" ID="ID_841988354" MODIFIED="1667318701059" TEXT="Regel f&#xfc;r AnchorPoint sollte offen bleiben">
<icon BUILTIN="yes"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1667260470855" ID="ID_1346204475" MODIFIED="1667260481690" TEXT="Grenzf&#xe4;lle / Fehlerbehandlung ausleuchten">
<icon BUILTIN="flag-yellow"/>
<node CREATED="1667517252905" ID="ID_335958554" MODIFIED="1667517293430" TEXT="gr&#xf6;&#xdf;tm&#xf6;glicher Zoom &#x2259; 2px / &#xb5;-Tick"/>
<node CREATED="1667517299247" ID="ID_1450395718" MODIFIED="1667517317049" TEXT="kleinstm&#xf6;gliches window &#x2259; 1 &#xb5;-Tick"/>
<node CREATED="1667517317821" ID="ID_1713703867" MODIFIED="1667517344899" TEXT="weitest m&#xf6;glicher zoom-out &#x2259; Canvas-size"/>
<node CREATED="1667517346296" ID="ID_1169099153" MODIFIED="1667517356781" TEXT="aber: kann Canvas per reverse-Zoom expandieren"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1667318726445" ID="ID_1535955412" MODIFIED="1667318747575" TEXT="visibleRange setzen">
<icon BUILTIN="flag-yellow"/>
<node CREATED="1667528162442" ID="ID_1415942522" MODIFIED="1667528170526" TEXT="setzten einer Anker-Position"/>
<node CREATED="1667528171215" ID="ID_1526154403" MODIFIED="1667528175965" TEXT="relativ positionieren"/>
<node CREATED="1667528176866" ID="ID_1620735407" MODIFIED="1667528184219" TEXT="Canvas explizit erweitern"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1667318713048" ID="ID_1238239373" MODIFIED="1667318718031" TEXT="scrollen">
<icon BUILTIN="flag-yellow"/>
<node CREATED="1667528185832" ID="ID_1091652690" MODIFIED="1667528199122" TEXT="schrittweise incl Canvas erweitern"/>
<node CREATED="1667528199785" ID="ID_1692959674" MODIFIED="1667528207840" TEXT="limits bei relativem positionieren"/>
<node CREATED="1667528209948" ID="ID_914020436" MODIFIED="1667528219542" TEXT="explizit setzen und Canvas erweitern"/>
</node>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1667488193842" ID="ID_1347640673" MODIFIED="1667488208549" TEXT="Invarianten">
<font BOLD="true" NAME="SansSerif" SIZE="14"/>
<icon BUILTIN="forward"/>
<node CREATED="1667488212533" ID="ID_553762141" MODIFIED="1667488247411" TEXT="overall Span ist stets der gesamte Canvas"/>
<node CREATED="1667488258934" ID="ID_1924856810" MODIFIED="1667488270574" TEXT="visible Span ist ausnahmslos stets darin enthalten"/>
<node CREATED="1667488271316" ID="ID_1005066146" MODIFIED="1667488283070" TEXT="visible Span in Pixel umgerechnet bleibt absolut konstant"/>
<node CREATED="1667488303192" ID="ID_486782619" MODIFIED="1667488317608" TEXT="pr&#xe4;zise Pixel-Werte sind Pr&#xe4;ferenz"/>
</node>
<node BACKGROUND_COLOR="#d2beaf" COLOR="#5c4d6e" CREATED="1666913274942" ID="ID_4743528" MODIFIED="1666913286194" TEXT="Design &#xfc;berpr&#xfc;fen">
<icon BUILTIN="hourglass"/>
@ -55313,7 +55550,106 @@
</node>
</node>
</node>
<node CREATED="1667336713909" ID="ID_259014029" MODIFIED="1667336726184" TEXT="Framework">
<font NAME="SansSerif" SIZE="14"/>
<node CREATED="1667336734205" ID="ID_1054510557" MODIFIED="1667336746223" TEXT="Lumiera Time">
<node CREATED="1667336756809" ID="ID_286019153" MODIFIED="1667336765133" TEXT="lib::time::Time ist ein Zeitpunkt"/>
<node CREATED="1667336768112" ID="ID_120722451" MODIFIED="1667336788066" TEXT="lib::time::TimeValue ist ein interne Zeit-Implementierung"/>
<node CREATED="1667336795340" ID="ID_1772915486" MODIFIED="1667336809222" TEXT="lib::time::TimeSpan ist ein Intervall mit Start und Dauer"/>
<node CREATED="1667336814578" ID="ID_443069684" MODIFIED="1667337242282" TEXT="Quantisierung">
<node CREATED="1667336818154" ID="ID_228764070" MODIFIED="1667336839107" TEXT="TimeValue + symbolische Grid-Referenz"/>
<node CREATED="1667336841967" ID="ID_1852849270" MODIFIED="1667336856426" TEXT="Grid &#xfc;ber das Advice-System publiziert"/>
</node>
<node BACKGROUND_COLOR="#d2beaf" COLOR="#5c4d6e" CREATED="1667476797049" FOLDED="true" ID="ID_767502481" MODIFIED="1667487664229" TEXT="Diskussion: interne Zeitbasis">
<linktarget COLOR="#af1a4c" DESTINATION="ID_767502481" ENDARROW="Default" ENDINCLINATION="96;-430;" ID="Arrow_ID_1672039685" SOURCE="ID_1838246834" STARTARROW="None" STARTINCLINATION="-670;38;"/>
<icon BUILTIN="hourglass"/>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1667486621881" ID="ID_566152244" MODIFIED="1667486629636" TEXT="#1258 clarify internal time base">
<icon BUILTIN="flag-yellow"/>
</node>
<node CREATED="1667486644110" ID="ID_1984451593" MODIFIED="1667486648120" TEXT="Problem">
<icon BUILTIN="messagebox_warning"/>
<node CREATED="1667486649845" ID="ID_93808620" MODIFIED="1667486680650" TEXT="grid-based Time-Scale kann niemals alle relevanten Werte exakt repr&#xe4;sentieren"/>
<node CREATED="1667486681289" ID="ID_1245609548" MODIFIED="1667486715504" TEXT="egal ob man nun floating-point nimmt, oder eine feste integrale Basis (&#xb5;-Ticks)"/>
</node>
<node BACKGROUND_COLOR="#d2beaf" COLOR="#5c4d6e" CREATED="1667486847011" ID="ID_1394182591" MODIFIED="1667486860346" TEXT="sehe folgende L&#xf6;sungen">
<icon BUILTIN="hourglass"/>
<node CREATED="1667486862210" ID="ID_860525476" MODIFIED="1667487583167" TEXT="Umstellen auf Bruch-Darstellung">
<icon BUILTIN="full-1"/>
<node CREATED="1667487232831" ID="ID_1006730826" MODIFIED="1667487247673" TEXT="rein auf logischer Basis w&#xe4;re das die 1.Wahl"/>
<node CREATED="1667487249733" ID="ID_344272813" MODIFIED="1667487263783" TEXT="hat aber m&#xf6;glicherweise einen starken Performance-Impact">
<node CREATED="1667487265203" ID="ID_1416124429" MODIFIED="1667487277405" TEXT="doppelt so viel Daten pro Zeitpunk"/>
<node CREATED="1667487277956" ID="ID_1373749694" MODIFIED="1667487299330" TEXT="gcd-Normalisierung nach jeder Multiplikation"/>
</node>
</node>
<node CREATED="1667486880606" ID="ID_293148639" MODIFIED="1667487585840" TEXT="mehrere Basis-Zeitformate erlauben">
<icon BUILTIN="full-2"/>
<node CREATED="1667486901227" ID="ID_869733709" MODIFIED="1667486908494" TEXT="eines davon w&#xe4;re dann FSecs"/>
<node CREATED="1667486909138" ID="ID_1371778930" MODIFIED="1667486921835" TEXT="ein anderes das &#xb5;-Grid"/>
<node CREATED="1667486924968" ID="ID_1851075217" MODIFIED="1667486937530" TEXT="Integration problematisch">
<node CREATED="1667486951149" ID="ID_1186754865" MODIFIED="1667486966790" TEXT="std::chrono macht genau dies">
<icon BUILTIN="idea"/>
</node>
<node CREATED="1667486938994" ID="ID_1194275689" MODIFIED="1667486950390" TEXT="erfordert Metaprogrammierung"/>
<node CREATED="1667486968874" ID="ID_782557379" MODIFIED="1667487226117" TEXT="Alternative: breite APIs">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
Man w&#252;rde also neben das Standard-Format ein toleriertes zweites Format stellen, welches dann ein B&#252;rger zweiter Klasse w&#228;re, aber auf allen wichtien APIs als 2.Alternative mit auftaucht. Zudem w&#252;rde man gewisse Abk&#252;rzungs-Pfade schaffen, auf denen die alternative Spec dann <i>verlustfrei durchgereicht </i>werden kann.
</p>
<ul>
<li>
es ist &#252;berhaupt nicht klar, welches Format dann der Standardfall sein sollte
</li>
<li>
das l&#228;uft vor allem auf eine Performance-Betrachtung hinaus, und einen trade-off, wo man ggfs Fehler durch&#160;andere Programmierer akzeptiert
</li>
</ul>
</body>
</html></richcontent>
</node>
</node>
</node>
<node CREATED="1667487313780" ID="ID_605045817" MODIFIED="1667487588066" TEXT="Problem nur umgehen">
<icon BUILTIN="full-3"/>
<node CREATED="1667487323563" ID="ID_255906636" MODIFIED="1667487341802" TEXT="es sind ja nur ganz wenige Stellen, die mit einem &#xb5;-Grid wirklich Probleme haben"/>
<node CREATED="1667487342753" ID="ID_463347104" MODIFIED="1667487469159">
<richcontent TYPE="NODE"><html>
<head>
</head>
<body>
<p>
wenn man zum &#181;-Grid eine eindeutige Rundungs-Regel hinzuf&#252;gt,
</p>
<p>
kann es praktisch auch Sound-Samples korrekt addressieren:<br /><br />1/96000 &#8793; 10,41666666666666666667 <i>&#181;Ticks</i>
</p>
</body>
</html></richcontent>
<icon BUILTIN="info"/>
</node>
<node CREATED="1667487486510" ID="ID_1354208250" MODIFIED="1667487501583" TEXT="Notwendigkeit einer genaueren Behandlung per Metadaten durchreichen">
<node CREATED="1667487512650" ID="ID_968209763" MODIFIED="1667487526228" TEXT="&quot;dies sind 96kHz-Samples&quot;"/>
<node CREATED="1667487528568" ID="ID_745033416" MODIFIED="1667487546865" TEXT="dann kann eine Implementierung &quot;downstream&quot; mit Divisor arbeiten"/>
<node CREATED="1667487547957" ID="ID_1232559601" MODIFIED="1667487568454" TEXT="und Output geht an die Soundkarte mit 96kHz"/>
</node>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#d2beaf" COLOR="#5c4d6e" CREATED="1667336859796" ID="ID_1810244902" MODIFIED="1667337099718" TEXT="Timecode">
<icon BUILTIN="hourglass"/>
<node CREATED="1667336865688" ID="ID_1539710424" MODIFIED="1667336875998" TEXT="kann aus einer lib::time::QuTime erzeugt werden"/>
<node CREATED="1667336880473" ID="ID_789960595" MODIFIED="1667337093278" TEXT="Stand 2011... (2022) nur SMPTE-Format implementiert">
<icon BUILTIN="flag-orange"/>
</node>
</node>
</node>
</node>
<node CREATED="1482524641484" ID="ID_1651495185" MODIFIED="1557498707236" TEXT="Architektur">
<font NAME="SansSerif" SIZE="14"/>
<node CREATED="1544199543455" ID="ID_970297070" MODIFIED="1557498707236" TEXT="Struktur">
<node CREATED="1544199550166" ID="ID_16943853" MODIFIED="1557498707236" TEXT="Layer">
<node CREATED="1544199559524" ID="ID_1685135526" MODIFIED="1557498707236" TEXT="rein gedankliche Gliederung"/>
@ -65576,10 +65912,17 @@
<font ITALIC="true" NAME="SansSerif" SIZE="14"/>
<icon BUILTIN="yes"/>
</node>
<node CREATED="1581902038450" ID="ID_676891454" MODIFIED="1581902066045" TEXT="nicht einfach realisierbar">
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1581902038450" ID="ID_676891454" MODIFIED="1667336289185" TEXT="nicht einfach realisierbar">
<icon BUILTIN="stop-sign"/>
</node>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1667336290112" ID="ID_166419409" LINK="https://issues.lumiera.org/ticket/1055#comment:3" MODIFIED="1667336380910" TEXT="siehe Kommentar zu #1055">
<icon BUILTIN="idea"/>
<node CREATED="1667336386235" ID="ID_1595778098" MODIFIED="1667336411267" TEXT="Vorsicht: soll nicht zur Denkfaulheit verleiten">
<icon BUILTIN="messagebox_warning"/>
</node>
<node CREATED="1667336412887" ID="ID_1127777473" MODIFIED="1667336438440" TEXT="std::chrono::duration vs Time als Zeit-Punkt"/>
</node>
</node>
<node CREATED="1581834456924" ID="ID_515357939" MODIFIED="1581834461008" TEXT="Typ-Fragen">
<node CREATED="1581834461832" ID="ID_1605358591" MODIFIED="1581834471551" TEXT="warum ist der FrameCnt ein signed integer?">