timeline
A library designed to sequence events in time.
If you find yourself writing out the same clock routines to get a basic rhythm going, there is a better way! timeline is built on top of clock, so all the usual details for controlling tempo and clock source apply here as well.
Originally designed by @trentgill
for use with crow, the norns version of timeline
uses the exact same syntax and provides the same features.
This document will provide an introduction to the basics of timeline
. To learn more advanced techniques, including pre-methods and post-methods, see the timeline
extended reference.
Flavors
The library has 3 flavors, each with its own purpose, though they can be combined in interesting ways too.
loop
is for (short) clock-synchronized loops in terms of beat durations.score
creates longer form sequences of events for song structure.real
is free from the clock, calling events over time, and is best for effects and real-world time.
All the events are, by default, automatically launched quantized to the clock.
Syntax | Description |
---|---|
my_beat = timeline.loop{duration, event, …} | Create and automatically launch (at the next beat) a looping sequence of events using beat durations |
my_song = timeline.score{beats_timestamp, event, …} | Create and automatically launch (at the next beat) a sequence of events using beat timestamps |
my_stream = timeline.real{second_timestamp, event, …} | Create and automatically launch (at the next beat) a sequence of events using seconds-based timestamps |
Change Launch Quantization
If you want to change the default quantization for all timelines you can set the launch_default
variable in timeline:
timeline.launch_default = 4 -- switch to every 4th beat, eg. 1 bar in 4/4 time
But sometimes you may want a specific timeline to use a custom quantization setting. Perhaps you want your score
to be quantized to 16, but you want your real
to start immediately with no delay. For these cases, you can use the launch
pre-method:
-- force the score to wait until the next multiple of 16 beats:
my_song = timeline.launch(16):score{...}
-- no quantization! begins the first element of 'real' immediately:
my_stream = timeline.launch(0):real{...}
See the timeline
crow docs for more examples.
Queuing
To queue a timeline
, rather than launching at creation, we can use pre- and post-methods:
Syntax | Description |
---|---|
my_song = timeline.queue():score{timestamp,event, …} | Queue up a loop , score or real sequence (swap score in the syntax example for any other flavor) |
my_song:play() | Play a queued sequence |
my_song:stop() | Stop a playing sequence |
Resetting score
and real
If you have a score
or real
that you would like to repeat endlessly, add the string "reset"
to the event table. This will immediately jump to the beginning of the timeline and start again.
my_song = timeline.score{
0, intro
, 32, verse
, 64, 'reset' -- will jump to beat 0, aka intro (single or double quotes ok)
}
See the crow docs for more examples.
loop
Post-Methods
Note: as of now, only loop
functions can have post-methods applied.
Post-methods determine how a loop
runs, or when it will stop.
By default, loop
will repeat the time-event table endlessly. You can stop the timeline at any moment with the :stop()
method, but you can also make the looping programmatic with the following post-methods:
Syntax | Description |
---|---|
my_loop = timeline.loop{duration, event, …}:unless(predicate) | A loop with an unless post-method will run until the predicate is true |
my_loop = timeline.loop{duration, event, …}:times(count) | A loop with a times post-method will run until the count has been met |
See the crow docs for more examples.
Example: Thirty Second Song
In this example, we’ll showcase the following timeline
syntax:
-
simple
: aloop
which switches between two notes every beat -
one
: aqueue
‘dscore
, which adds note flourishes on beat-centric timestamps- we also use our
'reset'
keyword to loop thescore
- we also use our
-
two
: aqueue
‘dloop
, which adds note flourishes on divisions of the clock -
cutoffs
: aqueue
‘dloop
, which increases our engine’s cutoff value every quarter-of-a-beat, until it performs this task 100times
-
flourishes
: areal
timeline
which schedules changes to our song with seconds-centric timestamps
tl = require 'timeline'
s = require 'sequins'
mu = require 'musicutil'
engine.name = 'PolyPerc'
function init()
root = 48
cutoff = 900
engine.cutoff(cutoff)
-- alternate between two notes:
simple = tl.loop{
1, {send_note, root},
1, {send_note, root+11}
}
-- let's queue up some flourishes:
one = tl.queue():score{
0, {send_note, root+7},
5, {send_note, root+19},
8, 'reset'
}
two = tl.queue():loop{
1.5, {send_note, root+9},
0.75, {send_note, root+14},
}
cutoffs = tl.queue():loop{
0.25, function() cutoff = cutoff+20 engine.cutoff(cutoff) end
}:times(100)
-- and schedule them with 'real' timing:
flourishes = tl.real{
0, {print, 'song starting'},
4, function() one:play() cutoffs:play() end,
9, function() one:stop() two:play() engine.release(2) end,
30, function() one:stop() two:stop() simple:stop() print('song ended') end
}
end
function send_note(note)
engine.hz(mu.note_num_to_freq(note))
end