On Thursday 19 December 2002 07.09, Tim Hockin wrote:
OK, so it's time for the Daily summary of points
learned about time
:)
Here is the summary as it stands in my notes (so many notes!!)
Time:
----
SAMPLE_RATE: all plugins know this from create()
- units: frames/sec (uint32)
TEMPO: a plugin can have a TEMPO control
- units: ticks/sec (float)
- deliver a TEMPO event at plugin init and whenever the tempo
changes
TIMEBASE: (FIXME: instance-constant by host/user or a dynamic
event?)
Constant for the sequencer at least, I think. (As to GUI level
sequencing, it's usually fixed per song, and you have to do a
destructive conversion of the file to change it. This is because the
timestamps are usually integers, just like in MIDI files.)
- units: ticks/beat (int or float?)
Integer value, at least. Actual format doesn't matter much, since you
won't see many of these events. (That is, int->float conversion cost
can be ignored.)
- let our docs suggest 1920 or 2520
- 2520 is divisible by 2,3,4,5,6,7,8,9,10,12,14,15,18,20 and
more
Yeah. Could use any "magic" value. "Bigger is better", as long as the
number itself doesn't take too many bits in the mantissa.
METER: a plugin can have a METER control (or
controls)
- units: ticks/measure
- deliver a METER event at plugin init and whenever the meter
changes
Yep. However, is it assumed that plugins should always count meter
relative time from where the last METER event was received? I guess
that would be the simplest and "safest" way, although it *does*
reduce musical time accuracy to the sample accuracy level.
"So what?", you say; "We have only sample accurate timing anyway."
Yes - but the METER event is the *base* of your musical time
calculations from the point you receive the event. That is, this
rounding error adds up with the ones you get in your internal
calculations. You could end up a sample off occasionally because of
this.
So, it might be nice if we could squeeze in a field specifying more
exactly when the meter actually starts. Think of it as the fraction
bits of the event timestamp.
TRANSPORT: a plugin can have a TRANSPORT control
- units: absolute ticks (double)
- deliver a TRANSPORT control when transport starts (start tick),
jumps (new tick value) and periodically (recommend each beat or
each measure or 1/sec). Reccommend to send on tempo/meter changes,
too - special event TRANSPORT_CTL - parm = start/stop/freeze - do
we really need a special event? see below...
I think we need a special event, since otherwise you can't move the
transport position when the transport is stopped. (See my post on cue
points.)
In addition to the above, the host can enable:
/* find out the timestamp of the next beat or bar */
uint32_t next_beat = host->time_next(XAP_NEXT_BEAT);
uint32_t next_bar = host->time_next(XAP_NEXT_BAR);
/* get time info about a timestamp, assuming current tempo, etc */
XAP_timeval times;
int r = host->get_time(timestamp, ×,
XAP_TIME_TICKS | XAP_TIME_WALL | XAP_TIME_SMPTE);
//FIXME: other time domains?
/* find out what the transport is doing */
XAP_transport trans = host->get_transport();
Provided the *host* knows anything about that. I think it's a rather
serious restriction to assume that, since the rest is implicitly
capable of handling timelines on a per-Channel basis.
I'd suggest doing the "host forwards callbacks to timeline manager"
thing I proposed. That's just an integer argument for the calls
above. Or we use a single pointer to a XAP_timeline struct, which
contains the callback pointers above.
In addition to all that, some sort of CUEPOINT
mechanism as
proposed to id loop-points, or simpler just a get_loops() method.
Maybe both. As mentioned before, they're not *exactly* the same thing.
NOTE: with this dataset, changing the meter impacts
the perceived
tempo. Since we decided to say tempo is actual BEATS, and the value
of beats can change, and ticks is dependant on beats:
Rate = 48000 samples/sec
Timebase = 100 ticks/beat
Tempo = 120 beats/minute = 2 beats/sec = 200 ticks/sec
Meter = 4/4 = 400 ticks/measure
=> a measure is 2 seconds (4 beats at 2 beats/sec)
change Meter to 8/8
=> a measure is 4 seconds (8 beats at 2 beats per second)
Yes...
We can either accept this, or rethink defining
tempo/timebase on
modifiable beats.
That would mean going back to assuming that QNs (or whatever) are
special. If you're not using QNs as the beat value, why would you
really want to specify the tempo as QNs/second or something?
(Sequencers genarally do, but that's rather a UI + conventions issue.
We're talking about the API now.)
About special events and controls:
I've voiced my dislike of general abuse of controls for this stuff.
In addition to it being a bit of an abuse of controls, we need to
add a 'double' control type and we need to have a special event for
TRANSPORT start/stop (and freeze if we do that). ICK.
TEMPO is not special in any way, and nor is transport start/stop.
Both *can* be normal controls. If they're not, they'll require
special case handling in hosts for defaults and preset handling.
What if we had plugin-global notifier events that told
the plugin
some things:
I don't like the idea of making them plugin global, but that's
basically just a matter of how different from normal controls you
want to make them.
TRANSPORT_STATE start/stop events
CUEPOINT add/remove
TIMEINFO - something time-ish has changed
TIMEBASE change
METER change
TEMPO change
Some of those may actually be best left as controls (TEMPO, METER,
TIMEBASE?).
Yes. (METER would be two controls.)
We could have a CUEPOINT control but it's
getting
ugly.
Yeah. Cue points are really N abstract objects, more like voices.
Guess you could actually treat them as Voice Controls, but that would
be abuse of the dimension of indexing that is voices. (There isn't
one cue point per voice, but rather a bunch of them per timeline.
That could be per Channel or per Plugin.)
We could have special events for TRANSPORT start/stop
but
uggh.
No need. Just have "1" for rolling an "0" for stopped.
Now I know that making some of this plugin-global
means that
different channels can't be on different timelines, but honestly, I
don't think it matters :)
No, it's probably no big deal, but I'm not sure it really changes
anything. Timeline generators and hosts still have to do hairy stuff
to get it right, if it has anything to do with the host.
I simply don't like the idea of blindly assuming that the host *is*
the sequencer. It doesn't eliminate any special cases anyway; the
timeline stuff still needs some "nonstandard" interfaces to work.
I know that sending CUEPOINT and TRANSPORT_STATE to
every plugin is
probably wasteful. It's that dang 'special' event that makes me
gag...
Well, I don't think we can do much about that. Some of them can be
ordinary controls, but some special events are hard to avoid.
Either way, I think it's important to keep in mind that control !=
event. Events are just a transport layer for control data, and other
stuff. Different datatypes already require different events. This
applies to the timeline stuff as well, for the things that just don't
fit in the normal datatypes.
Indeed, this means you get more datatypes to deal with - but how
different is that from a bunch of extra calls in the host struct,
really?
Thoughts? Things I missed? I'm going to start
codifying this,
soon. Fill in the gaps of things I missed.
I'll try to wrap my version up and release it, in case you want the
event handling macros or something. They're adapted from Audiality,
and should be fully functional. Still need a nice sort/merge
implementation for N queues into 1, though. (I haven't used that in
Audiality so far.)
No point in doing *everything* twice. :-)
//David Olofson - Programmer, Composer, Open Source Advocate
.- The Return of Audiality! --------------------------------.
| Free/Open Source Audio Engine for use in Games or Studio. |
| RT and off-line synth. Scripting. Sample accurate timing. |
`--------------------------->
http://olofson.net/audiality -'
---
http://olofson.net ---
http://www.reologica.se ---