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.
loopis for (short) clock-synchronized loops in terms of beat durations.scorecreates longer form sequences of events for song structure.realis 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: aloopwhich 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: arealtimelinewhich 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