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:
parent
f1b3f4e666
commit
f2ef893adb
6 changed files with 782 additions and 33 deletions
80
src/lib/rational.hpp
Normal file
80
src/lib/rational.hpp
Normal 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*/
|
||||
|
|
@ -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
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -10505,18 +10505,20 @@ Wiring requests are small stateful value objects. They will be collected, sorted
|
|||
&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<int64_t>}}}. 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">
|
||||
|
|
|
|||
|
|
@ -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 "Grid""/>
|
||||
</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ö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 ≙ Timeline">
|
||||
<icon BUILTIN="button_cancel"/>
|
||||
<node CREATED="1667254042344" ID="ID_1886040600" MODIFIED="1667254193233" TEXT="visualWindow ≙ was sichtbar ist"/>
|
||||
<node CREATED="1667254330394" ID="ID_192603163" MODIFIED="1667254344380" TEXT="⟹ visualWindow kann darü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 ≙ Canvas">
|
||||
<icon BUILTIN="forward"/>
|
||||
<node CREATED="1667254042344" ID="ID_6801078" MODIFIED="1667254193233" TEXT="visualWindow ≙ was sichtbar ist"/>
|
||||
<node CREATED="1667254132888" ID="ID_907807797" MODIFIED="1667254189985" TEXT="⟹ visualWindow ∈ Canvas"/>
|
||||
<node CREATED="1667254209434" ID="ID_1299162840" MODIFIED="1667254216213" TEXT="stöß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ß (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ä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ü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ß 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ü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 ⟹ 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ächsten Pixel">
|
||||
<node CREATED="1667349764204" ID="ID_1815738825" MODIFIED="1667349818467" TEXT="Konsequenz ⟹ visible Window anpassen"/>
|
||||
<node CREATED="1667349788035" ID="ID_281010107" MODIFIED="1667349803183" TEXT="Problem: Window kann größer werden als Canvas">
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
</node>
|
||||
<node CREATED="1667349853811" ID="ID_1951953580" MODIFIED="1667350032602" TEXT="Canvas vergrößern? oder visible Window verkleinern?">
|
||||
<richcontent TYPE="NOTE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<ul>
|
||||
<li>
|
||||
Canvas vergrößern ⟹ führt zu kontinuierliecher Drift; der Canvas wird fortlaufend größer
|
||||
</li>
|
||||
<li>
|
||||
Window verkleinern ⟹ heißt daß 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: µ-Tick pro Pixel?">
|
||||
<icon BUILTIN="help"/>
|
||||
<node CREATED="1667350440373" ID="ID_1896628944" MODIFIED="1667350460022" TEXT="Vorsicht... Minimum wäre dann 1µT/px"/>
|
||||
<node CREATED="1667350475136" ID="ID_1672716361" MODIFIED="1667350817413" TEXT="⟹ 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 µ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 µ-Ticks hatten wir seinerzeit gewählt, weil sie einerseits hinreichend genau sind, andererseits sehr einfach zu implementieren, und dennoch die Darstellung extrem großer Zeitspannen ermö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ä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ächlich kann ein µTick-Grid auch Sound-Samples korrekt addressieren — man darf dann nur nicht diese Zeit-Werte für weitere Berechnungen verwenden (denn sonst sammeln sich Rundungsfehler an). Es könnte also eine Implementierung eben <i>wissen, </i>daß hier Sound-Samples dargestellt/verarbeitet werden, und intern mit der exakten Skala arbeiten. Im Grunde ist das ein Lösungsvorgriff auf die 3.Lö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 µ-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ären auch numerisch viel gutmütiger"/>
|
||||
<node CREATED="1667488026717" ID="ID_267648014" MODIFIED="1667488073400" TEXT="man kö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ß 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="µ-Ticks sind immer ziemlich große Zahlen"/>
|
||||
<node CREATED="1667491149381" ID="ID_1912148103" MODIFIED="1667491191987" TEXT="besser: Kurz-Notation einfü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ä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ühren">
|
||||
<icon BUILTIN="pencil"/>
|
||||
<node CREATED="1667517450483" ID="ID_854101466" MODIFIED="1667517461957" TEXT="Rat = boost::rational<int64_t>"/>
|
||||
<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ächste Fä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-Ä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ß wird ⟹ Metrik dann angepaß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änkung der Testbarkeit hier">
|
||||
<icon BUILTIN="messagebox_warning"/>
|
||||
</node>
|
||||
<node CREATED="1667318668477" ID="ID_841988354" MODIFIED="1667318701059" TEXT="Regel für AnchorPoint sollte offen bleiben">
|
||||
<icon BUILTIN="yes"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1667260470855" ID="ID_1346204475" MODIFIED="1667260481690" TEXT="Grenzfälle / Fehlerbehandlung ausleuchten">
|
||||
<icon BUILTIN="flag-yellow"/>
|
||||
<node CREATED="1667517252905" ID="ID_335958554" MODIFIED="1667517293430" TEXT="größtmöglicher Zoom ≙ 2px / µ-Tick"/>
|
||||
<node CREATED="1667517299247" ID="ID_1450395718" MODIFIED="1667517317049" TEXT="kleinstmögliches window ≙ 1 µ-Tick"/>
|
||||
<node CREATED="1667517317821" ID="ID_1713703867" MODIFIED="1667517344899" TEXT="weitest möglicher zoom-out ≙ 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äzise Pixel-Werte sind Präferenz"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#d2beaf" COLOR="#5c4d6e" CREATED="1666913274942" ID="ID_4743528" MODIFIED="1666913286194" TEXT="Design überprü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 ü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äsentieren"/>
|
||||
<node CREATED="1667486681289" ID="ID_1245609548" MODIFIED="1667486715504" TEXT="egal ob man nun floating-point nimmt, oder eine feste integrale Basis (µ-Ticks)"/>
|
||||
</node>
|
||||
<node BACKGROUND_COLOR="#d2beaf" COLOR="#5c4d6e" CREATED="1667486847011" ID="ID_1394182591" MODIFIED="1667486860346" TEXT="sehe folgende Lö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äre das die 1.Wahl"/>
|
||||
<node CREATED="1667487249733" ID="ID_344272813" MODIFIED="1667487263783" TEXT="hat aber mö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äre dann FSecs"/>
|
||||
<node CREATED="1667486909138" ID="ID_1371778930" MODIFIED="1667486921835" TEXT="ein anderes das µ-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ürde also neben das Standard-Format ein toleriertes zweites Format stellen, welches dann ein Bürger zweiter Klasse wäre, aber auf allen wichtien APIs als 2.Alternative mit auftaucht. Zudem würde man gewisse Abkürzungs-Pfade schaffen, auf denen die alternative Spec dann <i>verlustfrei durchgereicht </i>werden kann.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
es ist überhaupt nicht klar, welches Format dann der Standardfall sein sollte
|
||||
</li>
|
||||
<li>
|
||||
das läuft vor allem auf eine Performance-Betrachtung hinaus, und einen trade-off, wo man ggfs Fehler durch 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 µ-Grid wirklich Probleme haben"/>
|
||||
<node CREATED="1667487342753" ID="ID_463347104" MODIFIED="1667487469159">
|
||||
<richcontent TYPE="NODE"><html>
|
||||
<head>
|
||||
|
||||
</head>
|
||||
<body>
|
||||
<p>
|
||||
wenn man zum µ-Grid eine eindeutige Rundungs-Regel hinzufügt,
|
||||
</p>
|
||||
<p>
|
||||
kann es praktisch auch Sound-Samples korrekt addressieren:<br /><br />1/96000 ≙ 10,41666666666666666667 <i>µ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=""dies sind 96kHz-Samples""/>
|
||||
<node CREATED="1667487528568" ID="ID_745033416" MODIFIED="1667487546865" TEXT="dann kann eine Implementierung "downstream" 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?">
|
||||
|
|
|
|||
Loading…
Reference in a new issue