Welcome
... to the afseq scripting guide! afseq, also known as nerdo-rhythm, is an experimental imperative music sequence generator engine.
It allows creating music sequences programmatically using either plain Rust as a library (static, precompiled) or Lua as a scripting engine (dynamic, runtime interpreted) for live music coding.
This book only covers the Lua API for live music coding. For instructions and examples on creating rhythms in plain Rust, see the afseq crate docs.
Key Features
Programmatic
: Build musical phrases using Lua scripts or Rust code.
Modular
: Combine and reuse rhythm components dynamically.
Flexible
: Create anything from simple beats to complex nested rhythms.
Generative
: Make evolving compositions that change over time.
Dynamic
: Modify patterns live, during playback.
External Control
: Connect with MIDI/OSC controllers for hands-on parameter tweaking.
Template Rhythms
: Create user configurable rhythm templates.
Cycles Mini-Notation
: Use familiar Tidal Cycles mini-notation for rapid pattern creation.
Installation
afseq is a Rust library that deals with raw musical event generation only. It does not generate any audio. You must use an application with built-in support for afseq to use it.
If you are familiar with Rust, you can also use play-script.rs
from the examples in the git repository to test out afseq scripts using a basic sample player.
Getting Started
Quickstart
: Start with a quick introduction through practical examples.
Guide
: Get into details with a more comprehensive documentation.
Examples
: More advanced examples, created and explained step by step.
Reference
: Read raw technical specifications of the Lua API.
Quickstart
This guide will take you from basic patterns to advanced rhythmic sequencing through practical examples. Each example builds on previous concepts and includes comments to help you understand how things work.
note
afseq uses Lua as a scripting language. If you're not familiar with Lua, don't worry. Lua is very easy to pick up and fortunately there are great tutorials out there, such as this one.
Table of Contents
- Basic Patterns
- Rhythm Variations
- Notes and Scales
- Cycles Mini-Notation
- Dynamic Patterns & Emitters
- Advanced Techniques
Basic Patterns
Quarter Note Pulse
-- The most basic rhythm: steady quarter notes
return rhythm {
unit = "1/4", -- Quarter note timing grid
emit = "c4" -- Play middle C on each pulse
}
- TRY THIS: Change unit to
"1/8"
for eighth notes - TRY THIS: Replace
"c4"
with"e4"
or"g4"
for different notes
This creates a steady pulse playing C4 on every quarter note. The unit
parameter sets the timing grid, while emit
defines what note to play.
Alternating Notes
-- Create a pattern that alternates between notes
return rhythm {
unit = "1/8", -- Eighth note timing grid
pattern = {1, 0, 1, 1}, -- Play-rest-play-play pattern
emit = {"c4", "d4"} -- Alternates between C4 and D4
}
- TRY THIS: Change pattern to
{1, 1, 0, 1}
for a different rhythm - TRY THIS: Add more notes to emit like
{"c4", "d4", "e4", "g4"}
Here, pattern
controls when notes play (1) or rest (0). The emit
parameter cycles through the provided notes when a step triggers.
Subdivided Pulses
-- Pattern with mixed note lengths
return rhythm {
unit = "1/4",
pattern = {1, {1, 1, 1, 1}}, -- One quarter note, then four sixteenth notes
emit = {"c4", "c5", "d4", "e4", "g4"} -- C4 (quarter), C5, d4, e4, g4 (sixteenth)
}
- TRY THIS: Try more complex subdivisions like {{1, 1}, {1, {1, 1}}}
- TRY THIS: Change the unit to "1/8" to make everything faster
Nested arrays in the pattern create subdivisions, allowing for more complex rhythms within the basic unit.
Euclidean Rhythms
-- Distributes notes evenly across steps (common in many music traditions)
return rhythm {
unit = "1/16",
pattern = pattern.euclidean(3, 8), -- 3 hits spread over 8 steps
emit = "c4" -- Basic note
}
- TRY THIS: Try different combinations like
(5, 8)
or(7, 16)
- TRY THIS: Use
pattern = pattern.euclidean(3, 8) + pattern.euclidean(5, 8)
to chain different patterns
Euclidean patterns distribute a number of notes evenly across steps, creating naturally pleasing rhythmic patterns found in many musical traditions.
The Pattern API contains various tools to programatically create patterns.
Triplet Resolution
-- Create swing or triplet feel
return rhythm {
unit = "1/8",
resolution = 2/3, -- Triplet feel (3 notes in space of 2)
emit = {"c4 v0.3", "e4 v0.5", "g4 v0.8"} -- v specifies volume, d delay, p panning
}
- TRY THIS: Change resolution to
"5/4"
for a different swing feel - TRY THIS: Add values such as
d0.2
between 0 and 1 to delay specific notes
The resolution
parameter modifies the timing grid, enabling triplet feels, swing rhythms, and polyrhythms.
Notes and Scales
Basic Note Stacks
-- Simple chord by stacking notes
return rhythm {
unit = "1/1",
emit = {{"c4", "e4", "g4"}, "c4"} -- C major chord followed by a single C4
}
- TRY THIS: Try different chord combinations like
{"d4", "f4", "a4"}
for D minor - TRY THIS: Add
v
values to create dynamics:{"c4 v0.8", "e4 v0.6", "g4 v0.4"}
A table of notes allows emitting multiple notes at once, creating chords and harmonies.
Chord Notation
-- Using chord notation shortcuts
return rhythm {
unit = "1/1",
emit = {
"c4'M", -- C major using ' chord notation
"d4'm", -- D minor
"g4'7" -- G dominant 7th
}
}
- TRY THIS: Use other chord modes like
'm5
,'+
, or'dim
- TRY THIS: Add inversions with
note("c4'M"):transpose({12, 0, 0})
Chord notation provides a quick way to specify common chord types without listing individual notes.
Working with Scales
-- Advanced chord and scale operations
return rhythm {
unit = "1/1",
emit = {
chord("c4", "major"), -- C major via the chord function
chord("c4", {0, 4, 7}), -- C major via custom intervals
scale("c", "major"):chord(1), -- C major from 1st degree of C major scale
scale("c", "major"):chord(5) -- G major from 5th degree of C major scale
}
}
- TRY THIS: Use other scales like
"minor"
,"dorian"
, or"pentatonic"
- TRY THIS: Try different chord degrees:
scale("c", "major"):chord(2)
for D minor
The scale()
function allows creating chords from scale degrees, enabling more musical chord progressions.
See available modes and scales at the API docs. See Notes and Scales for more ways to create and manipulate notes and chords.
Tidal Cycles Mini-Notation
Basic Cycle
-- Using tidal cycles notation for concise patterns
return rhythm {
unit = "1/4", -- Emit a cycle every beat
emit = cycle("c4 e4 g4") -- C major arpeggio
}
-- The simplified notation emits a cycle per bar
return cycle("c4 e4 g4")
Tidal Cycles' mini-notation provides a concise way to express patterns.
Alternating Cycles
-- Switching between different patterns
return rhythm {
unit = "1/4",
emit = cycle("[c4 e4 g4]|[d4 f4 a4]") -- Randomly select one of two chords
}
- TRY THIS: Add more patterns with
|
like[c4|c5 e4 g4]|[d4 f4|g5 a4]|[e4 g4 b4]
- TRY THIS: Try simultaneous notes with square brackets
[c4 e4]
The |
operator in cycles randomly selects different patterns.
Euclidean Rhythms in Cycles
-- Euclidean patterns in tidal cycles notation
return cycle("c4(3,8) e4(5,8) g4(7,8)") -- Different Euclidean rhythms
- TRY THIS: Combine with alternation:
c4(3,8)|e4(5,8)
- TRY THIS: Change the numbers for different distributions
Tidal Cycles mini-notation also supports Euclidean patterns with the (n,k)
notation, where n
is the number of notes and k
is the number of steps.
See Cycles Guide for more example and info about Tidal Cycles in afseq.
Dynamic Patterns & Emitters
Random Note Selection
-- Randomly select notes from a list
local notes = {"c4", "d4", "e4", "g4"}
return rhythm {
unit = "1/8",
emit = function(context)
return notes[math.random(#notes)] -- Pick random note from array
end
}
- TRY THIS: Use notes from a specific scale with
local notes = scale("c4", "major").notes
- TRY THIS: Add amplitude variation with
note(some_note):amplify(0.5 + math.random() * 0.5)
Using functions for emitters enables dynamic behavior, like randomly selecting notes from a predefined set.
Probability-Based Emitting
-- Emit notes with certain probability
return rhythm {
unit = "1/8",
pattern = {1, 1, 1, 1}, -- Regular pattern
emit = function(context)
if math.random() < 0.3 then -- 30% chance to emit
return "c4"
end
end
}
- TRY THIS: Vary probability by step position:
if math.random() < (context.step % 4) / 4 then
- TRY THIS: Higher probability on downbeats:
if math.random() < ((context.pulse_step - 1) % 2 == 0 and 0.8 or 0.2) then
This pattern uses a gate function to filter notes with a 30% probability, creating a sparse, probabilistic pattern. Gates are evaluated after patterns but before emission, giving you control over which triggered notes actually play.
Stateful Arpeggiator
-- Create patterns that remember previous states
return rhythm {
unit = "1/8",
emit = function(init_context)
local notes = {"c4", "e4", "g4", "b4"}
local index = 1
return function(context)
local note = notes[index]
index = math.imod(index + 1, #notes) -- Cycle through notes
return note
end
end
}
- TRY THIS: Add direction changes:
if index >= #notes or index <= 1 then direction = direction * -1 end
- TRY THIS: Change notes based on time:
notes = scale("C4", {"major","minor"}[math.floor(context.time) % 2 + 1])
.notes
This example demonstrates stateful emitters that remember their position between calls, enabling sequences and other time-dependent behaviors.
Advanced Techniques
Conditional Gate
-- Filter which notes actually play using gates
return rhythm {
unit = "1/8",
pattern = {1, 0.1, 1, 0.5, 1, 0.2, 1, 0.1}, -- probability values
gate = function(context)
-- always play on even-numbered step values
return (context.pulse_step - 1) % 2 == 0 or
-- else use pulse values as probablities
context.pulse_value >= math.random()
end,
emit = "c4"
}
- TRY THIS: Create a threshold gate:
context.pulse_value > 0.5
- TRY THIS: Only play when a specific MIDI note is held:
context.trigger.notes[1].key == "C4"
Gates filter which triggered notes actually play, adding another layer of control to your patterns.
Dynamic Cycles
-- Identifiers in cycles can be dynamically mapped to something else
local s = scale("C4", "minor")
return rhythm {
unit = "1/4",
emit = cycle("I III V VII"):map(function(context, value)
-- value here is a single roman number from the cycle above
local degree = value
-- apply value as roman number chord degree
return s:chord(degree)
end)
}
- TRY THIS: Change scale to
"major", "dorian", or "pentatonic minor"
- TRY THIS: Add parameters:
parameter.enum("scale", "minor", {"major", "minor", "pentatonic"})
This example uses musical scale knowledge to generate chord progressions based on scale degrees.
See Parameters on how to add template parameters to rhythms.
Generative Melody with Constraints
-- Create melodies that follow musical rules
return rhythm {
unit = "1/8",
emit = function(init_context)
local pentatonic = scale("c4", "pentatonic minor").notes
local last_note = 1
return function(context)
local next_note = math.random(#pentatonic)
-- Prefer steps of 1 or 2 scale degrees (smoother melodies)
while math.abs(next_note - last_note) > 2 do
next_note = math.random(#pentatonic)
end
last_note = next_note
return pentatonic[next_note]
end
end
}
- TRY THIS: Add occasional jumps:
if math.random() < 0.1 then ...
(allow larger intervals) - TRY THIS: Change directions based on contour: add direction variable that occasionally flips
This generates melodies that follow musical constraints, like preferring small intervals for more natural-sounding melodies.
Further Resources
Remember to experiment by modifying these examples! The best way to learn is by tweaking parameters and seeing what happens.
Guide
A Rhythm
is the main building block in afseq. It lets you define when and what to play.
afseq consumes Lua script files that define rhythms as specified in the API documentation.
Components
- TimeBase defines the time unit of a rhythm.
- Pattern → Gate → Emitter perform the basic event generation in 3 stages.
- Parameters change behavior of components during runtime.
All content in rhythms can be either static or dynamic:
-
Static content is defined as a Lua table of events. There are various helpers included in the API, such as note scales, chords, and pattern creation tools.
-
Dynamic content is generated on the fly by Lua functions while the rhythm runs. Generators are functions with local state, which can e.g. be used to apply replicable randomization.
Cycle emitters use the Tidal Cycles mini-notation to create patterns in a flexible, condensed form.
Examples
See Quickstart for a set of simple examples to start and to play around with.
The Examples section contains more advanced and guided examples.
Timebase
A rhythm's timebase
represents the unit of time for the rhythm, either in musical beats or wall-clock time (seconds, ms). It defines the unit and duration of a single step in patterns. The time base is static and thus can't be changed during runtime.
The default time unit of rhythm is a beat.
The BPM and signature (beats per bar) settings are configured by the application which is running the rhythm.
Supported Time Units
Beat-Time
"bars"
using the host's beats per bar setting"beats"
alias for 1/4"1/1"
4 * 1/4"1/2"
2 * 1/4"1/4"
a beat"1/8"
0.5 * 1/4"1/16"
0.25 * 1/4"1/32"
0.125 * 1/4"1/64"
0.0625 * 1/4
Wallclock-Time
"ms"
millisecond"seconds"
whole seconds
Resolution
The resolution
parameter acts as an additional multiplier to the time unit and can be any positive real number. You can use it to scale the unit or to create odd time signatures.
Examples
A slightly off beat time unit.
return rhythm {
unit = "beats",
resolution = 1.01,
emit = "c4"
}
Sixteenth tripplets
return rhythm {
unit = "1/16",
resolution = 2/3,
emit = "c4"
}
2 Seconds
return rhythm {
unit = "seconds",
resolution = 2,
emit = "c4"
}
Pattern
A rhythm's pattern
is a sequence of pulse values that defines the temporal occurence of events. It feeds the emitter with pulse values that are optionally filtered out by a gate. It is created from a list of pulses with possible subdivisions, an optional number of repeats and an optional time offset.
A pattern can be generated by algorithms such as Euclidean rhythms, or it can be expressed as a static Lua table, or it can be generated by a dynamic generator - a Lua function.
The default pattern is an endless repeated pulse train of 1's.
Offset
By default, the pattern starts running immediately when the rhythm is triggered. Using the offset
property you can delay the start by the amount specified in the rhythm's unit.
» offset = 4
delay start by 4 * rhythm's unit.
Repeat
By default, a pattern repeats endlessly. To create one-shot patterns, or patterns that repeat only a few times, you can use the repeats
property.
» repeats = false
one-shot
» repeats = 1
play pattern twice, then stop
Static Patterns
The simplest form of a pattern is a Lua table (array) of numbers or boolean values:
» pattern = {0, 1, 1}
skip, trigger, trigger - repeat
» pattern = {1, 0, 1, 1}
1/2th followed by two 1/4th triggers in a 1/4th unit rhythm
» pattern = {0.8, 0, 1, 0.2}
pulse values are passed as numbers to gates and emitters in function contexts
tip
The default gate implementation skips all 0 pulses and passes all other pulse values to the emitter. When using a custom gate function, you can e.g. use the pulse value as a probability or you can use the pulse value as a volume value in a custom emitter function to create accents.
Sub-Divisions
Each number value in the specified Lua table represents a single pulse in the rhythm's specified time unit. By using tables instead of pulse numbers as values, you can cram sub-patterns into a pattern to create more complex rhythms.
» pattern = {{1, 1, 1}, {1, 1}}
triplet followed by two quarter notes with unit 1/2
» pattern = {{1, 0}, {0, 1}}
basic bossanova rhythm with unit 1/4
Dynamic Patterns
When using a Lua function instead of a table as a pattern generator, you can dynamically generate pulse values.
» pattern = function(context) return math.random() end
randomly emit pulse values between 0 and 1
The expected return value of a dynamic pattern function is a pulse value (true
, false
, 0
, 1
) or nil
.
Use functions in order to create dynamic patterns that can interact with user interaction, or to create probability based pattern rules. To connect the functions to its runtime use the passed context
argument.
See generators for more info about using functions as generators.
Pattern Library
afseq comes with a built-in pattern library, which contains a bunch of helper functions and generators to ease creating patterns.
» pattern = pattern.from{0, 1} * 3 + {1, 0}
combine sub patterns
» pattern = pattern.new(12, function(k, v) return k % 3 == 1 end),
functionally create patterns
» pattern = pattern.euclidean{3, 8, -1}
create euclidean patterns
See Pattern API Lua reference for more info and examples.
Examples
Static pattern.
return rhythm {
pattern = { 1, 0, 0, 1 },
emit = "c4"
}
Cram pulses into a single pulse slot via subdivisions in static patterns.
return rhythm {
pattern = { 1, { 1, 1, 1 } },
emit = "c4"
}
Static pattern created using the "pattern" lib
return rhythm {
pattern = pattern.from{1, 0} * 5 + {1, 1},
emit = "c4"
}
Euclidean pattern created using the "patterns" lib.
return rhythm {
pattern = pattern.euclidean(7, 16, 2),
emit = "c4"
}
Stateless function.
return rhythm {
pattern = function(context)
return math.random(0, 1)
end,
emit = "c4"
}
Stateful generator.
return rhythm {
pattern = function(init_context)
local rand = math.randomstate(12345)
local triggers = table.new{ 0, 6, 10 }
return function(context)
local step = (context.pulse_step - 1) % 16
return rand() > 0.8 and triggers:contains(step)
end
end,
emit = "c4"
}
See generators for more info about stateful generators.
Gate
A rhythm's gate
is an optional filter unit that determines whether or not an event should be passed from the pattern to the emitter. It can be used to dynamically filter out pulse events.
The default gate is a threshold gate, which passes all pulse values > 0.
Examples
Seeded probability gate, using the pattern pulse values as probability.
return rhythm {
pattern = { 0, { 0.5, 1 }, 1, { 1, 0.8 } },
gate = function(init_context)
local rand = math.randomstate(12366)
return function(context)
return context.pulse_value > rand()
end
end,
emit = { "c4" }
}
A gate which filters out pulse values with on a configurable threshold.
return rhythm {
inputs = {
parameter.number("threshold", 0.5, {0, 1})
},
pattern = {
0.2, { 0.5, 1 }, 0.9, { 1, 0.8 }
},
gate = function(context)
return context.pulse_value >= context.inputs.threshold
end,
emit = { "c4" }
}
See generators for more info about stateful generators and parameters about rhythm input parameters.
Emitter
A rhythm's emitter
generates events for incoming pulse values. Just like the pattern, it can be made up of a static list of events or it can be a dynamic generator - a Lua function.
In addition to dynamic Lua functions, you can also use a tidal cycle as an emitter.
The default emitter generates a single middle C note value for each incoming pulse.
Event Types
Currently afseq only supports monophonic or polyphonic note events as emitter output. This is likely to change in the future to allow other musically interesting events to be emitted.
Note values can be expressed as:
- Raw integer values like
48
, which are interpreted as MIDI note numbers. - Raw note strings such as
"c4"
(single notes) or"d#4'maj"
(chords). - Lua note tables like
{ key = 48, volume = 0.1 }
. - Lua API note objects such as
note(48):volume(0.1)
ornote("c4", "g4")
ornote("c4'min"):transpose({-12, 0, 0})
. - Lua
nil
values, empty tables{}
, empty strings""
, or"-"
strings are interpreted as rests. - The string
"off"
or"~"
is interpreted as note off.
See notes & scales for more information about the different ways to create and manipulate notes and chords.
Static Emitters
The simplest form of a emitter is a Lua table (array) of note or nil values (a rest).
Static emitter arrays define note event sequences. Each incoming, possibly filtered, gated pulse from the pattern picks the next event in the sequence, then moves on in the sequence. Sequences are repeated as long as the pattern is running.
» emit = { "c4", "d#4", "g4" }
arpeggio - sequence
» emit = { { "c4", "d#4", "g4" } }
single chord - single event
To ease distinguishing polyponic contents, use sequence
and note
:
» emit = sequence("c4", "d#4", "g4")
arpeggio - sequence
» emit = note("c4", "d#4", "g4")
single chord - single event
Dynamic Emitters
Dynamic emitter functions return single note events. Each incoming, possibly filtered, gated impulse from the pattern will trigger the emit function to create the next event as long as the pattern is running.
» emit = function(context) return math.random() > 0.5 and "c4" or "c5" end
randomly emit c4 or c5 notes
» emit = function(context) return context.pulse_count % 2 == 1 and "c4" or "c5" end
alternate c4 and c5 notes
The expected return value of a dynamic emitter function is a single monophonic or polyphonic note value, or a value that can be converted into a note.
See API docs for context for more info about the context passed to dynamic functions.
Cycle Emitters
Cycle emitters emit a whole cycle for a single pulse. So any incoming, possibly filtered, gated pulse from the pattern will trigger a full cycle as long as the pattern is running.
» emit = cycle("[c4, d#4, g4]")
a single chord
You probably won't use custom patterns or gate functions with cycles, but it's possible to sequence entire cycles with them, or use cycles as single note generators too:
» emit = cycle("[c4 d#4 g4]")
arpeggio
» emit = cycle("[c4 <d#4 d4> g4|g5]")
arpeggio with variations
See cycles for more info about Tidal Cycles support in afseq.
Examples
Sequence of c4, g4 notes.
return rhythm {
emit = { "c4", "g4" }
}
Chord of c4, d#4, g4 notes.
return rhythm {
emit = sequence(
{ "c4", "d#4", "g4" }, -- or "c4'min"
{ "---", "off", "off" }
)
}
Sequence of c4, g4 with volume 0.5.
return rhythm {
emit = sequence{"c4", "g4"}:volume(0.5)
}
Stateless function.
return rhythm {
emit = function(context)
return 48 + math.random(1, 4) * 5
end
}
Stateful generator.
return rhythm {
emit = function(init_context)
local count, step, notes = 1, 2, scale("c5", "minor").notes
return function(context)
local key = notes[count]
count = (count + step - 1) % #notes + 1
return { key = key, volume = 0.5 }
end
end
}
Note pattern using the "pattern" lib.
local tritone = scale("c5", "tritone")
return rhythm {
emit = pattern.from(tritone:chord(1, 4)):euclidean(6) +
pattern.from(tritone:chord(5, 4)):euclidean(6)
}
Tidal cycle.
return rhythm {
emit = cycle("<[a3 c4 e4 a4]*3 [d4 g3 g4 c4]>")
}
See generators for more info about stateful generators.
Notes, Chords & Scales
Note values, such as those specified in the emitter, can be expressed and modified in various ways. Sometimes it's easier to generate notes programmatically using note numbers. Other times you may want to write down a chord in a more expressible form.
Notes
Note Numbers
Raw integer values like 48
, are interpreted as MIDI note numbers in the note
function and emitter. Valid MIDI notes are 0-127
.
» emit = 48
emit a single c4 note
Note Tables
Instead of using a string, you can also specify notes via a Lua table with the following properties.
"key"
- REQUIRED - MIDI Note number such as48
or a string, such as"c4"
"instrument"
OPTIONAL - Instrument/Sample/Patch number >= 0"volume"
- OPTIONAL - Volume number in range [0.0 - 1.0]"panning"
- OPTIONAL - Panning factor in range [-1.0 - 1.0] where 0 is center"delay"
- OPTIONAL - Delay factor in range [0.0 - 1.0]
» emit = { key = 48, volume = 0.1 }
a c4 with volume 0.1
Note Strings
Note strings such as "c4"
are interpreted as {KEY}{MOD}{OCT}
where MOD and OCT are optional.
Valid keys are c,d,e,f,g,a,b
. Valid modifiers are #
and b
. Valid octaves are values 0-10
» emit = { "c4" }
emit a single c4 note
Other note properties can be specified in the string notation as well.
'#'
instrument'v'
volume'p'
panning'd'
delay
» emit = { "f#4 #1 v0.2" }
emit a f sharp with instrument 1 and volume 0.2
Note Chord Strings
To create a chords from a note string, append a '
character to the key and specify a chord mode.
» emit = "d#4'maj"
d#4 major chord
See chord Lua API for a list of all supported modes.
Just like regular notes, additional note properties can be added to the chord string as well.
» emit = "c4'69 #1 v0.5"
patch 1, volume 0.5
Note Objects
Note numbers, strings and tables, as described above can be fed into a note object in the LuaAPI, which allows further transformation of the note.
This is especially handy for chords, but also can be more verbose than using note string attributes.
» emit = note(48):volume(0.1)
c4 note with volume of 0.1
» emit = note({key = "c4"}):volume(0.2)
c4 note with volume of 0.2
» emit = note("c4'min"):transpose({12, 0, 0})
1st chord inversion
See note Lua API for details.
Note Chord Objects
Notes objects can also be created using the chords
function.
» emit = chord(48, "major")
c4 major notes
This also allows the use of custom interval tables.
» emit = chord(48, {0,4,7})):volume(0.2)
custom c4 major chord with volume 0.2
See chord Lua API for details.
NB: The sequence Lua API has a similar interface to modify notes within a sequence.
Note Offs and Rests
To create rest values use Lua nil
values, empty tables {}
, empty strings ""
, or "-"
strings.
To create off notes use the string "off"
or "~"
.
Scales
To make working with chords and chord progressions, and programming music in general, easier, afseq also has a simple scale API to create chords and notes from scales.
Scale objects
Scale objects can be created from a note key and mode name, or custom intervals.
» scale("c4", "minor").notes
"c4", "d", "d#4", "f4", "g4", "g#4" "a#4"
» scale("c4", {0,2,3,5,7,8,10}).notes
same as above
Common Scales
See scale Lua API for a list of all supported modes.
Custom Scales
Custom scales can be created by using an interval table with numbers from 0-11
in ascending order.
» scale("c4", {0,3,5,7}).notes
"c4", "d#4", "f4", "g4", "a4"
Scale Chords
The scale's chord
function allows to generate chords from the scale's intervals.
local cmin = scale("c4", "minor")
return rhythm {
emit = sequence(
note(cmin:chord("i", 4)), --> note{48, 51, 55, 58}
note(cmin:chord(5)):transpose({12, 0, 0}), --> Gm 1st inversion
)
}
See scale Lua API for more information about scale objects.
Cycles
In addition to static arrays of notes or dynamic generator functions, emitters in afseq can also emit cycles using the tidal cycles mini-notation
Introduction
Cycles let you create repeating musical patterns using a simple text notation. Think of it like writing drum patterns or melodies in a compact, readable way.
-- Basic pattern with notes and rests
return cycle("c4 d4 <e4 g4> _")
This plays:
- C4
- D4
- Alternate between E4 and G4
- play no event:
_
means rest
Pattern Basics
Key symbols to know:
Symbol | Meaning | Example |
---|---|---|
| Separates steps | c4 d4 |
, | Parallel patterns | [c4,e4], [g4,a4] |
< > | Alternates between values | <c4 e4 g4> |
| | Random choice | c4|d4|e4 |
* | Repeat | c4*4 |
_ | Elongate | c4 _ d4 |
~ | Rest | c4 ~ d4 |
tip
Please see Tidal Cycles Mini-Notation Reference for a complete overview of the cycle notation.
Basic Examples
Basic scale:
return cycle("c4 d4 e4 f4 g4 a4 b4 c5")
Drum pattern:
return cycle("[c1 ~ e1 ~]*2")
Random melody:
return cycle("[c4|d4|e4|f4|g4|a4]*8")
Combining with Patterns
Control when patterns play using rhythm
:
return rhythm {
unit = "bars", -- Timing unit
pattern = {1, 0}, -- Play on odd bars only
emit = cycle("c4 d4 e4 f4")
}
Advanced Usage
Limitations
There's no exact specification for how tidal cycles work, and it's constantly evolving, but at the moment we support the mini notation as it works in Tidal, with the following limitations and changes:
-
Stacks and random choices are valid without brackets (
a | b
is parsed as[a | b]
) -
:
sets the instrument or remappable target instead of selecting samples but also allows setting note attributes such as instrument/volume/pan/delay (e.g.c4:v0.1:p0.5
) -
In bjorklund expressions, operators within are not supported (e.g.
bd(<3 2>, 8)
is not supported)
Timing
The base time of a pattern in tidal is specified as cycles per second. In afseq, the time of a cycle instead is given in cycles per pattern pulse units.
-- emits an entire cycle every beat
return rhythm {
unit = "beats",
emit = cycle("c4 d4 e4") -- triplet
}
Sequencing
An emitter in afseq gets triggered for each incoming non-gated pattern pulse. This is true for cycles are well and allows you to sequence entire cycles too.
-- emit an entire cycle's every bar, then pause for two bars, then repeat
return rhythm {
unit = "bars",
pattern = { 1, 0, 0 },
emit = cycle("c d e f")
}
You can also use the mini notation to emit single notes only, making use of tidal's note alternating and randomization features only:
-- emit a single note from a cycle in an euclidean pattern
return rhythm {
unit = "beats",
pattern = pattern.euclidean(5, 8),
emit = cycle("<c d e f g|b>")
}
Seeding
afseq's general random number generator is also used in cycles. So when you seed the global number generator, you can also seed the cycle's random operations with math.randomseed(12345)
.
Note Attributes
You can set note attributes in cycle patterns using chained :
expressions:
-- Set instrument (2), panning (-0.5), and delay (0.25)
cycle("d4:2:p-0.5:d0.25")
-- Set instrument (1) with alternating volumes (0.1, 0.2)
cycle("c4:1:<v0.1 v0.2>")
-- Set multiple attributes with randomization
cycle("c4:[v0.5:d0.1|v0.8]")
Supported note attributes are:
- Instrument:
:#X
- same as:X
, without the#
- Volume:
:vX
- with X [0.0-1.0] - Panning:
:pX
- with X [-1.0 to 1.0] - Delay:
:dX
- with X [0.0-1.0)
Note that X
must be written as floating point number for volume, panning and delay: c4:p-1.0
and c4:p.8
is valid, while c4:p-1
is not valid!
If you want to use expressions (like slowing down) for an attribute pattern on the right side, you'll have to wrap it in square brackets, otherwise the expression applies to the entire pattern, not just the attributes'.
-- This slows down the output
cycle("[c4 d#4 e4]:<v.1 v.2>/2")
-- This slows down the alternating for the volume
cycle("[c4 d#4 e4]:[<v.1 v.2>/2]")
A shorthand for assigning attributes exists in the form of :v=X
where X
can be a pattern. This way, you can supply float values without having to repeat the name of the target attribute.
-- Set volume to rise for each cycle
cycle("[c4 d#4 e4]:v=<.1 .2 .3 .4>")
-- This would be the same as
cycle("[c4 d#4 e4]:<v.1 v.2 v.3 v.4>")
Mapping
Notes and chords in cycles are expressed as note strings in afseq. But you can also dynamically evaluate and map cycle identifiers using the cycle map
function.
This allows you, for example, to inject input parameters into cycles or to use custom identifiers.
Using custom identifiers with a static map (a Lua table):
return cycle("[bd*4], [_ sn]*2"):map{
bd = note("c4 #0"),
sn = note("g4 #1")
}
Using custom identifiers with a dynamic map function (a Lua function):
return cycle("[bd*4], [_ sn]*2"):map(function(context, value)
if value == "bd" then
return note("c4 #0")
elseif value == "sn" then
return note("g4 #1")
end
end)
Advanced Examples
Chord progression
return cycle("[c'M g'M a'm f'M]/4")
A polyrhythm
return cycle("[C3 D#4 F3 G#4], [[D#3?0.2 G4 F4]/64]*63")
Alternate panning with note attributes
cycle("c4:<p-0.5 p0.0 p0.5>")
Mapped multi channel beats
-- use [=[ and ]=] as Lua multiline string
return cycle([=[
[<h1 h2 h2>*12],
[kd ~]*2 ~ [~ kd] ~,
[~ s1]*2,
[~ s2]*8
]=]):map({
kd = "c4 #0", -- Kick
s1 = "c4 #1", -- Snare
s2 = "c4 #1 v0.1", -- Ghost snare
h1 = "c4 #2", -- Hat
h2 = "c4 #2 v0.2", -- Hat
})
Dynamically mapped roman chord numbers with user defined scale
return rhythm {
unit = "1/1",
resolution = 4,
inputs = {
parameter.enum("mode", "major", { "major", "minor" })
},
emit = cycle("I V III VI"):map(
function(init_context, value)
local s = scale("c4", init_context.inputs.mode)
return function(context, value)
return value ~= "_" and s:chord(value) or value
end
end
)
}
Input Parameters
Rhythm inputs
allow user controlled parameter values to be injected into a rhythm. This allows you to write more flexible rhythms that can be used as templates or to automate functions within the rhythm.
Input parameters can be accessed in dynamic pattern, gate, emitter or cycle function contexts
.
Parameter Types
Currenty available parameter types are:
- boolean - on/off switches -
parameter.boolean
- integer - integer value ranges -
parameter.integer
- number - real number value ranges -
parameter.number
- string - enumeration value sets -
parameter.enum
Parameter access
When defining a parameter, each parameter has a unique string id set. This id can then be used to access the actual paramter value in the function contexts.
Definition:
» inputs = { parameter.boolean("enabled", true) }
Usage:
» emit = function(context) return context.inputs.enabled and "c5" or nil }
Usage, if you've got spaces in your ids (not recommended):
» emit = function(context) return context.inputs["enabled"] and "c5" or nil }
Examples
Euclidean pattern generator with user configurable steps, pulses, offset value.
return rhythm {
inputs = {
parameter.integer('steps', 12, {1, 64}, "Steps",
"Number of on steps in the pattern"),
parameter.integer('pulses', 16, {1, 64}, "Pulses",
"Total number of on & off pulses"),
parameter.integer('offset', 0, {-16, 16}, "Offset",
"Rotates on pattern left (values > 0) or right (values < 0)"),
},
unit = "1/1",
pattern = function(context)
return pattern.euclidean(
math.min(context.inputs.steps, context.inputs.pulses),
context.inputs.pulses,
context.inputs.offset)
end,
emit = "c4"
}
Random bass line generator with user defined custom scales and variations (seeds).
local scales = {"Chromatic", "Minor", "Major"}
return rhythm {
inputs = {
parameter.enum('scale', scales[1], scales, "Scale"),
parameter.integer('notes', 7, {1, 12}, "#Notes"),
parameter.integer('variation', 0, {0, 0xff}, "Variation"),
},
unit = "1/1",
pattern = function(context)
local rand = math.randomstate(2345 + context.inputs.variation)
return pattern.euclidean(rand(3, 16), 16, 0)
end,
emit = function(context)
local notes = scale("c4", context.inputs.scale).notes
local rand = math.randomstate(127364 + context.inputs.variation)
local notes = pattern.new(context.inputs.notes):map(function(_)
return notes[rand(#notes)]
end)
return notes[math.imod(context.step, #notes)]
end
}
Drum pattern cycle with configurable note values for each drumkit instrument.
return rhythm {
unit = "1/1",
inputs = {
parameter.integer("bd_note", 48, {0, 127}),
parameter.integer("sn_note", 70, {0, 127}),
parameter.integer("hh_note", 98, {0, 127})
},
emit = cycle([[
[<hh1 hh2 hh2>*12],
[bd1 ~]*2 ~ [~ bd2] ~,
[~ sn1]*2,
[~ sn2]*8
]]):map(function(context, value)
for _, id in pairs{"bd", "sn", "hh"} do
local number = value:match(id.."(%d+)")
if number then
return note(context.inputs[id.."_note"]):volume(
number == "2" and 0.2 or 1.0)
end
end
end)
}
Advanced Topics
This chapter contains more advanced topics about rhythms.
Please read the basic concepts from the guide first to get you started...
Generators
Patterns, Gates and Emitters can use Lua functions to dynamically generate or evaluate content.
Anonymous Lua functions, as used in rhythms, are actually closures. They keep a record of their environment, so all (up)values which are declared outside of the anonymous function are accessible from within the function itself.
We can use this in afseq scripts to keep track of a rhythm's global or local state.
Runtime
To better understand how local and global states are relevant here, we first need to understand how rhythms are evaluated.
Let's say we're in a DAW that supports afseq. This DAW triggers your rhythm script when a single note is triggered. If we now want to allow polyphonic playback of scripts, only one script instance is actually created per instrument or track. So a single script will be triggered multiple times with multiple notes.
This means that all notes triggered by the DAW will share the same global state within a rhythm script. But this also means that in order to create local states for each individual note trigger, you'll need to keep track of a local state somehow.
Functions
In the following example, an emitter function keeps track of its state by referencing a globally defined counter
variable.
local counter = 0
return rhythm {
emit = function(context)
local midi_note = counter
counter = (counter + 1) % 128
return note(midi_note)
end,
}
When playing a single instance of this rhythm, you'll get an event stream of increasing note values. As expected. But when triggering this script multiple times polyphonically, each triggerd script instance increases the counter on its own, so you'll get multiple streams with note values increased by multiple note steps.
Contexts
The easiest way to deal with this, is using the function's passed context. Apart from global playback information such as the BPM or sample rate, the context also keeps track of the rhythm's internal playback state.
A context
passed to pattern functions only contains the global playback status. A context
passed to gate and emitter functions contains the global playback status and status of the pattern.
See pattern context API, gate context API, emitter context API for details.
Contexts also may contain user controlled input variables. See input parameters for more info about this.
By making use of the context we can now rewrite the example above to:
return rhythm {
emit = function(context)
-- NB: pulse_step is an 1 based index, midi notes start with 0
local midi_note = (context.pulse_step - 1) % 128
return note(midi_note)
end
}
Because the context is unique for each newly triggered rhythm instance, we now get multiple continously increasing note event streams again.
Generators
Generators in afseq are pattern, gate or emit functions, that do return another function. This is similar to how iterators work in Lua. By returning a function from a function you can create a new local state that is valid for the returned function only.
Let's use our counter example again with such a generator:
return rhythm {
emit = function(init_context)
local counter = 0 -- local state!
return function(context)
local midi_note = counter
counter = (counter + 1) % 128
return note(midi_note)
end
end,
}
Here the outer function is called once when the rhythm is started - just to create the local state and to return the actual emit function. The returned function is then called repeatedly while the rhythm instance is running, operating on the local state it was initialised with.
When to use what?
-
If you have a function that does not depend on an (external) state, simply use a global or anonymous function.
-
If you have a function which only depends on the rhythm playback context, use a global or anonymous function too and only make use of the passed context.
-
If you need to keep track of local states separately for each new rhythm run, use a generator.
-
If you need a mix of local and global state, use a generator which also reaches out to global and local variables.
See also advanced topic about randomization, which makes use the the generator concept to keep track of local random states.
Randomization
Controlled randomness can be a lot of fun when creating music algorithmically, so afseq supports a number of randomisation techniques to deal with pseudo randomness.
Random Number Generation
You can use the standard Lua math.random()
to create pseudo-random numbers in afseq, and can use math.randomseed()
to seed them.
Note that the standard Lua random implementation is overridden by afseq, to use a Xoshiro256PlusPlus random number generator. This ensures that seeded random operations behave the same on all platforms and architectures.
Here's a simple example which creates a random melody line based on a scale.
-- create a scale to pick notes from
local cmin = scale("c", "minor")
-- pick 10 random notes from the scale
local random_notes = pattern.new(10, function()
return cmin.notes[math.random(#cmin.notes)]
end)
return rhythm {
emit = random_notes
}
Random Number Seeding
You can use math.randomseed()
to seed the global random number generator.
-- create a scale to pick notes from
local cmin = scale("c", "minor")
-- pick the same random 10 notes from the scale every time
math.randomseed(1234)
local random_notes = pattern.new(10, function()
return cmin.notes[math.random(#cmin.notes)]
end)
return rhythm {
emit = random_notes
}
Local Random Number Generators
When seeding the RNG, each time a rhythm is (re)started, an existing rhythm instance will continue to run. The global state of a rhythm script is not recreated each time the rhythm is played again.
See generators for details of how afseq handles global and local states in general.
To create multiple separate local random states, use the non standard math.randomstate(seed)
function to create local, possibly seeded random number generators.
local cmin = scale("c", "minor")
return rhythm {
emit = function(init_context)
local rand = math.randomstate(1234) -- a local random number generator
return function(context)
return note(cmin.notes[rand(#cmin.notes)])
end
end
}
In the example above, each newly triggered rhythm instance will result in the same sequence of random notes, and multiple running instances will not interfere with each other.
Examples
TODO
here will be a few guided example scripts...
Lua API Reference
The following chapters contain a complete listing of the afseq Lua scripting API. The content has been auto-generated from the LuaLS Type Definitions, so you can read and use the definition files directly too.
You can also use the LuaLS type definitions directly for autocompletion and to view the API documentation in e.g. vscode and other editors that support the LuaLS language server.
First install the sumneko.lua vscode extension.
Then download a copy of the afseq type definitions folder (./types/nerdo
) and configure your workspace to use the files in your project. To do this, add the following to your project's /.vscode/settings.json
file
{
"Lua.workspace.library": ["PATH/TO/NERDO_DEFINITION_FOLDER"]
}
chord
Global
Functions
chord(key : NoteValue
, mode : ChordName
)
->
Note
Create a new chord from the given key notes and a chord name or an array of custom intervals.
NB: Chords also can also be defined via strings in function
note
and via the a scale'schord
function. See examples below.Chord names also can be shortened by using the following synonyms:
- "min" | "m" | "-" -> "minor"
- "maj" | "M" | "^" -> "major"
- "minMaj" | "mM" | "-^" -> "minMajor"
- "+" | "aug" -> "augmented"
- "o" | "dim" -> "diminished"
- "5 -> "five"
- "6 -> "six"
- "69" -> "sixNine"
- "7 -> "seven"
- "9 -> "nine"
- "11" -> "eleven"
examples:
chord("c4", "minor") --> {"c4", "d#4", "f4"} chord({key = 48, volume = 0.5}, "minor") --> {"c4 v0.5", "d#4 v0.5", "f4 v0.5"} --same as: chord("c4", {0, 4, 7}) chord("c4 v0.5", {0, 4, 7}) --or: note("c4'major") note("c4'major v0.5") --or: note(scale("c4", "major"):chord("i", 3)) note(scale("c4", "major"):chord("i", 3)):volume(0.5)
-- Available chords. mode: | "major" | "major7" | "major9" | "major11" | "major13" | "minor" | "minor#5" | "minor6" | "minor69" | "minor7b5" | "minor7" | "minor7#5" | "minor7b9" | "minor7#9" | "minor9" | "minor11" | "minor13" | "minorMajor7" | "add9" | "add11" | "add13" | "dom7" | "dom9" | "dom11" | "dom13" | "7b5" | "7#5" | "7b9" | "five" | "six" | "sixNine" | "seven" | "nine" | "eleven" | "thirteen" | "augmented" | "diminished" | "diminished7" | "sus2" | "sus4" | "7sus2" | "7sus4" | "9sus2" | "9sus4"
chord_names()
->
string
[]
Return supported chord names.
Aliases
ChordName
string
| "7#5"
| "7b5"
| "7b9"
| "7sus2"
| "7sus4"
| "9sus2"
| "9sus4"
| "add11"
| "add13"
| "add9"
| "augmented"
| "diminished"
| "diminished7"
| "dom11"
| "dom13"
| "dom7"
| "dom9"
| "eleven"
| "five"
| "major"
| "major11"
| "major13"
| "major7"
| "major9"
| "minor"
| "minor#5"
| "minor11"
| "minor13"
| "minor6"
| "minor69"
| "minor7"
| "minor7#5"
| "minor7#9"
| "minor7b5"
| "minor7b9"
| "minor9"
| "minorMajor7"
| "nine"
| "seven"
| "six"
| "sixNine"
| "sus2"
| "sus4"
| "thirteen"
-- Available chords. ChordName: | "major" | "major7" | "major9" | "major11" | "major13" | "minor" | "minor#5" | "minor6" | "minor69" | "minor7b5" | "minor7" | "minor7#5" | "minor7b9" | "minor7#9" | "minor9" | "minor11" | "minor13" | "minorMajor7" | "add9" | "add11" | "add13" | "dom7" | "dom9" | "dom11" | "dom13" | "7b5" | "7#5" | "7b9" | "five" | "six" | "sixNine" | "seven" | "nine" | "eleven" | "thirteen" | "augmented" | "diminished" | "diminished7" | "sus2" | "sus4" | "7sus2" | "7sus4" | "9sus2" | "9sus4"
NoteValue
string
| number
| NoteTable
| nil
cycle
Global
Functions
cycle(input : string
)
->
Cycle
Create a note sequence from a Tidal Cycles mini-notation string.
cycle
accepts a mini-notation as used by Tidal Cycles, with the following differences:
- Stacks and random choices are valid without brackets (
a | b
is parsed as[a | b]
):
sets the instrument or remappable target instead of selecting samples but also allows setting note attributes such as instrument/volume/pan/delay (e.g.c4:v0.1:p0.5
)- In bjorklund expressions, operators within and on the right side are not supported (e.g.
bd(<3 2>, 8)
andbd(3, 8)*2
are not supported)examples:
--A chord sequence cycle("[c4, e4, g4] [e4, g4, b4] [g4, b4, d5] [b4, d5, f#5]")
--Arpeggio pattern with variations cycle("<c4 e4 g4> <e4 g4> <g4 b4 d5> <b4 f5>")
--Euclidean Rhythms cycle("c4(3,8) e4(5,8) g4(7,8)")
--Map custom identifiers to notes cycle("bd(3,8)"):map({ bd = "c4 #1" })
Aliases
CycleMapFunction
(context : CycleMapContext
, value : string
) ->
CycleMapNoteValue
CycleMapGenerator
(context : CycleMapContext
, value : string
) ->
CycleMapFunction
CycleMapNoteValue
Note
| NoteValue
| NoteValue
[]
NoteValue
string
| number
| NoteTable
| nil
PlaybackState
"running"
| "seeking"
-- - *seeking*: The emitter is auto-seeked to a target time. All results are discarded. Avoid -- unnecessary computations while seeking, and only maintain your generator's internal state. -- - *running*: The emitter is played back regularly. Results are audible. PlaybackState: | "seeking" | "running"
Cycle
Functions
map(self, map : CycleMapFunction
| CycleMapGenerator
| { })
->
Cycle
Map names in in the cycle to custom note events.
By default, strings in cycles are interpreted as notes, and integer values as MIDI note values. Custom identifiers such as "bd" are undefined and will result into a rest, when they are not mapped explicitly.
examples:
--Using a static map table cycle("bd [bd, sn]"):map({ bd = "c4", sn = "e4 #1 v0.2" })
--Using a static map table with targets cycle("bd:1 <bd:5, bd:7>"):map({ -- instrument #1,5,7 are set additionally, as specified bd = { key = "c4", volume = 0.5 }, })
--Using a dynamic map function cycle("4 5 4 <5 [4|6]>"):map(function(context, value) -- emit a random note with 'value' as octave return math.random(0, 11) + value * 12 end)
--Using a dynamic map function generator cycle("4 5 4 <4 [5|7]>"):map(function(init_context) local notes = scale("c", "minor").notes return function(context, value) -- emit a 'cmin' note arp with 'value' as octave local note = notes[math.imod(context.step, #notes)] local octave = tonumber(value) return { key = note + octave * 12 } end end)
--Using a dynamic map function to map values to chord degrees cycle("1 5 1 [6|7]"):map(function(init_context) local cmin = scale("c", "minor") return function(context, value) return note(cmin:chord(tonumber(value))) end end)
Aliases
CycleMapFunction
(context : CycleMapContext
, value : string
) ->
CycleMapNoteValue
CycleMapGenerator
(context : CycleMapContext
, value : string
) ->
CycleMapFunction
CycleMapNoteValue
Note
| NoteValue
| NoteValue
[]
NoteValue
string
| number
| NoteTable
| nil
PlaybackState
"running"
| "seeking"
-- - *seeking*: The emitter is auto-seeked to a target time. All results are discarded. Avoid -- unnecessary computations while seeking, and only maintain your generator's internal state. -- - *running*: The emitter is played back regularly. Results are audible. PlaybackState: | "seeking" | "running"
CycleMapContext
Context passed to 'cycle:map` functions.
Properties
playback : PlaybackState
Specifies how the cycle currently is running.
channel : integer
channel/voice index within the cycle. each channel in the cycle gets emitted and thus mapped separately, starting with the first channel index 1.
step : integer
Continues step counter for each channel, incrementing with each new mapped value in the cycle. Starts from 1 when the cycle starts running or after it got reset.
step_length : number
step length fraction within the cycle, where 1 is the total duration of a single cycle run.
trigger : Note
?
Note that triggered the rhythm, if any.
inputs : table<string
, boolean
| string
| number
>
Current input parameter values: parameter ids as keys, parameter values as values
beats_per_min : number
Project's tempo in beats per minutes.
beats_per_bar : integer
Project's beats per bar setting.
samples_per_sec : integer
Project's sample rate in samples per second.
Aliases
PlaybackState
"running"
| "seeking"
-- - *seeking*: The emitter is auto-seeked to a target time. All results are discarded. Avoid -- unnecessary computations while seeking, and only maintain your generator's internal state. -- - *running*: The emitter is played back regularly. Results are audible. PlaybackState: | "seeking" | "running"
input
- InputParameter
- Parameter
- Functions
- boolean(id :
InputParameterId
, default :InputParameterBooleanDefault
, name :InputParameterName``?
, description :InputParameterDescription``?
) - integer(id :
InputParameterId
, default :InputParameterIntegerDefault
, range :InputParameterIntegerRange``?
, name :InputParameterName``?
, description :InputParameterDescription``?
) - number(id :
InputParameterId
, default :InputParameterNumberDefault
, range :InputParameterNumberRange``?
, name :InputParameterName``?
, description :InputParameterDescription``?
) - enum(id :
InputParameterId
, default :InputParameterEnumDefault
, values :string
[], name :InputParameterName``?
, description :InputParameterDescription``?
)
- boolean(id :
- Aliases
- Functions
InputParameter
Opaque input parameter user data. Construct new input parameters via the
parameter.XXX(...)
functions.
Parameter
Contains functions to construct new input parameters. Input parameter values can be accessed via functionn
contexts
in pattern, gate and emitter functions or generators.
Functions
boolean(id : InputParameterId
, default : InputParameterBooleanDefault
, name : InputParameterName
?
, description : InputParameterDescription
?
)
Creates an InputParameter with "boolean" Lua type with the given default value and other optional properties.
integer(id : InputParameterId
, default : InputParameterIntegerDefault
, range : InputParameterIntegerRange
?
, name : InputParameterName
?
, description : InputParameterDescription
?
)
Creates an InputParameter with "integer" Lua type with the given default value and other optional properties.
number(id : InputParameterId
, default : InputParameterNumberDefault
, range : InputParameterNumberRange
?
, name : InputParameterName
?
, description : InputParameterDescription
?
)
Creates an InputParameter with "number" Lua type with the given default value and other optional properties.
enum(id : InputParameterId
, default : InputParameterEnumDefault
, values : string
[], name : InputParameterName
?
, description : InputParameterDescription
?
)
Creates an InputParameter with a "string" Lua type with the given default value, set of valid values to choose from and other optional properties.
Aliases
InputParameterBooleanDefault
Default boolean value.
InputParameterDescription
Optional long description of the parameter describing what the parameter does.
InputParameterEnumDefault
Default string value. Must be a valid string within the specified value set.
InputParameterId
Unique id of the parameter. The id will be used in the
input
context table as key.
InputParameterIntegerDefault
Default integer value. Must be in the specified value range.
InputParameterIntegerRange
Optional value range. When undefined (0.0 - 1.0)
InputParameterName
Optional name of the parameter as displayed to the user. When undefined, the id is used.
InputParameterNumberDefault
Default number value. Must be in the specified value range.
InputParameterNumberRange
Optional value range. When undefined (0 - 100)
note
Global
Functions
note(...NoteValue
)
->
Note
Create a new monophonic or polyphonic note (a chord) from a number value, a note string, chord string or array of note values.
In note strings the following prefixes are used to specify optional note attributes:
-'#' -> instrument (integer > 0) -'v' -> volume (float in range [0-1]) -'p' -> panning (float in range [-1-1]) -'d' -> delay (float in range [0-1])
examples:
note(48) --> middle C note("c4") --> middle C note("c4 #2 v0.5 d0.3") --> middle C with additional properties note({key="c4", volume=0.5}) --> middle C with volume 0.5 note("c4'maj v0.7") --> C4 major chord with volume 0.7 note("c4", "e4 v0.5", "off") --> custom chord with a c4, e4 and 'off' note
note_number(note : NoteValue
)
->
integer
Convert a note string or note table to a raw MIDI note number in range 0-127 or 0xFF for nil or empty note strings or 0xFE for note offs
Examples:
note_value("c4") --> 48 note_value(note("c4")) --> 48 note_value("") --> 0xFF note_value("off") --> 0xFE note_value("xyz") --> error
Aliases
NoteValue
string
| number
| NoteTable
| nil
Note
Properties
notes : NoteTable
[]
Functions
transpose(self, step : integer
| integer
[])
->
Note
Transpose the notes key with the specified step or steps.
Values outside of the valid key range (0 - 127) will be clamped.
examples:
note("c4"):transpose(12) note("c'maj"):transpose(5) note("c'maj"):transpose({0, 0, -12})
amplify(self, factor : number
| number
[])
->
Note
Multiply the note's volume attribute with the specified factor or factors.
Values outside of the valid volume range (0 - 1) will be clamped.
examples:
note({"c4 0.5", "g4"}):amplify(0.5) note("c'maj 0.5"):amplify({2.0, 1.0, 0.3})
volume(self, volume : number
| number
[])
->
Note
Set the note's volume attribute to the specified value or values.
examples:
note({"c4", "g4"}):volume(0.5) note("c'maj"):volume(0.5) note("c'maj"):volume({0.1, 0.2, 0.3})
instrument(self, instrument : number
| number
[])
->
Note
Set the note's instrument attribute to the specified value or values.
panning(self, panning : number
| number
[])
->
Note
Set the note's panning attribute to the specified value or values.
delay(self, delay : number
| number
[])
->
Note
Set the note's delay attribute to the specified value or values.
NoteTable
Properties
key : string
| number
Note key & octave string (or MIDI note number as setter)
instrument : number
?
Instrument/Sample/Patch >= 0
volume : number
?
Volume in range [0.0 - 1.0]
panning : number
?
Panning factor in range [-1.0 - 1.0] where 0 is center
delay : number
?
Delay factor in range [0.0 - 1.0]
pattern
- Pattern
- Functions
- new(length :
integer``?
, value :PulseValue
| (index :integer
)->
PulseValue``?
) - from(...
PulseValue
|PulseValue
[]) - copy(self :
Pattern
) - distributed(steps :
integer
|table
, length :integer
, offset :integer``?
, empty_value :PulseValue``?
) - euclidean(steps :
integer
|table
, length :integer
, offset :integer``?
, empty_value :PulseValue``?
) - unpack(self :
Pattern
) - subrange(self :
Pattern
, i :integer
, j :integer``?
, empty_value :PulseValue``?
) - take(self :
Pattern
, length :integer
, empty_value :PulseValue``?
) - clear(self :
Pattern
) - init(self :
Pattern
, value :PulseValue
| (index :integer
)->
PulseValue
, length :integer``?
) - map(self :
Pattern
, fun : (index :integer
, value :PulseValue
)->
PulseValue
) - reverse(self :
Pattern
) - rotate(self :
Pattern
, amount :integer
) - push_back(self :
Pattern
, ...PulseValue
[] |PulseValue
) - pop_back(self :
Pattern
) - repeat_n(self :
Pattern
, count :integer
) - spread(self :
Pattern
, amount :number
, empty_value :PulseValue``?
) - tostring(self :
Pattern
)
- new(length :
- Aliases
- Functions
Pattern
Array alike table with helper functions to ease creating rhythmic patterns.
examples:
-- using + and * operators to combine patterns pattern.from{ 0, 1 } * 3 + { 1, 0 }
-- repeating, spreading and subsets pattern.from{ 0, 1, { 1, 1 } }:repeat_n(4):spread(1.25):take(16)
-- euclidean patterns pattern.euclidean(12, 16) pattern.from{ 1, 0.5, 1, 1 }:euclidean(12)
-- generate/init from functions pattern.new(8):init(1) --> 1,1,1,1,1,1,1,1 pattern.new(12):init(function() return math.random(0.5, 1.0) end ) pattern.new(16):init(scale("c", "minor").notes_iter())
-- generate note patterns pattern.from{ "c4", "g4", "a4" } * 7 + { "a4", "g4", "c4" }
-- generate chords from degree values pattern.from{ 1, 5, 6, 4 }:map(function(index, degree) return scale("c", "minor"):chord(degree) end)
Functions
new(length : integer
?
, value : PulseValue
| (index : integer
) ->
PulseValue
?
)
->
Pattern
Create a new empty pattern or pattern with the given length and pulse value.
examples:
pattern.new(4,1) --> {1,1,1,1} pattern.new(4, function() return math.random() end)
from(...PulseValue
| PulseValue
[])
->
Pattern
Create a new pattern from an existing set of values or tables. When passing tables, those will be flattened.
examples:
pattern.from(1,0,1,0) --> {1,0,1,0} pattern.from({1,0},{1,0}) --> {1,0,1,0}
copy(self : Pattern
)
->
Pattern
create a shallow-copy of the given pattern (or self)
examples:
local p = pattern.from(1, 0) local p2 = p:copy() --> {1,0}
distributed(steps : integer
| table
, length : integer
, offset : integer
?
, empty_value : PulseValue
?
)
->
Pattern
Create an new pattern or spread and existing pattern evenly within the given length. Similar, but not exactly like
euclidean
.Shortcut for
pattern.from{1,1,1}:spread(length / #self):rotate(offset)
examples:
pattern.distributed(3, 8) --> {1,0,0,1,0,1,0} pattern.from{1,1}:distributed(4, 1) --> {0,1,0,1}
euclidean(steps : integer
| table
, length : integer
, offset : integer
?
, empty_value : PulseValue
?
)
->
Pattern
Create a new euclidean rhythm pattern with the given pulses or number of new pulses in the given length and optionally rotate the contents. Euclidean Rhythm
examples:
pattern.euclidean(3, 8) --> {1,0,0,1,0,0,1,0} pattern.from{"a", "b", "c"}:euclidean(8, 0, "-") --> {"a","-","-","b","-","-","c","-"}
unpack(self : Pattern
)
->
... : PulseValue
Shortcut for table.unpack(pattern): returns elements from this pattern as var args.
examples:
local p = pattern.from{1,2,3,4} local v1, v2, v3, v4 = p:unpack()
subrange(self : Pattern
, i : integer
, j : integer
?
, empty_value : PulseValue
?
)
->
Pattern
Get sub range from the pattern as new pattern. When the given length is past end of this pattern its filled up with empty values.
examples:
local p = pattern.from{1,2,3,4} p = p:subrange(2,3) --> {2,3} p = p:subrange(1,4,"X") --> {2,3,"X","X"}
take(self : Pattern
, length : integer
, empty_value : PulseValue
?
)
->
Pattern
Get first n items from the pattern as new pattern. When the given length is past end of this pattern its filled up with empty values.
examples:
local p = pattern.from{1,2,3,4} p = p:take(2) --> {1,2} p = p:take(4, "") --> {1,2,"",""}
clear(self : Pattern
)
->
Pattern
Clear a pattern, remove all its contents.
examples:
local p = pattern.from{1,0} p:clear() --> {}
init(self : Pattern
, value : PulseValue
| (index : integer
) ->
PulseValue
, length : integer
?
)
->
Pattern
Fill pattern with the given value or generator function in length.
examples:
local p = pattern.from{0,0} p:init(1) --> {1,1} p:init("X", 3) --> {"X","X", "X"}
map(self : Pattern
, fun : (index : integer
, value : PulseValue
) ->
PulseValue
)
->
Pattern
Apply the given function to every item in the pattern.
examples:
local p = pattern.from{1,3,5} p:map(function(k, v) return scale("c", "minor"):degree(v) end) --> {48, 51, 55}
reverse(self : Pattern
)
->
Pattern
Invert the order of items.
examples:
local p = pattern.from{1,2,3} p:reverse() --> {3,2,1}
rotate(self : Pattern
, amount : integer
)
->
Pattern
Shift contents by the given amount to the left (negative amount) or right.
examples:
local p = pattern.from{1,0,0} p:rotate(1) --> {0,1,0} p:rotate(-2) --> {0,0,1}
push_back(self : Pattern
, ...PulseValue
[] | PulseValue
)
->
Pattern
Push a single or multiple number of items or other pattern contents to the end of the pattern. Note: When passing array alike tables or patterns, they will be unpacked.
examples:
local p = pattern.new() p:push_back(1) --> {1} p:push_back(2,3) --> {1,2,3} p:push_back{4} --> {1,2,3,4} p:push_back({5,{6,7}) --> {1,2,3,4,5,6,7}
pop_back(self : Pattern
)
Remove an entry from the back of the pattern. returns the popped item.
examples:
local p = pattern.from({1,2}) p:pop_back() --> {1} p:pop_back() --> {} p:pop_back() --> {}
repeat_n(self : Pattern
, count : integer
)
->
Pattern
Duplicate the pattern n times.
examples:
local p = pattern.from{1,2,3} patterns:repeat_n(2) --> {1,2,3,1,2,3}
spread(self : Pattern
, amount : number
, empty_value : PulseValue
?
)
->
Pattern
Expand (with amount > 1) or shrink (amount < 1) the length of the pattern by the given factor, spreading allowed content evenly and filling gaps with 0 or the given empty value.
examples:
local p = pattern.from{1,1} p:spread(2) --> {1,0,1,0} p:spread(1/2) --> {1,1}
tostring(self : Pattern
)
->
string
Serialze a pattern for display/debugging purposes.
examples:
pattern.euclidean(3, 8):tostring() --> "{1, 1, 1, 0}"
Aliases
PulseValue
boolean
| string
| number
| table
Valid pulse value in a pattern
rhythm
- Global
- EmitterContext
- GateContext
- PatternContext
- RhythmOptions
- Properties
- unit :
"ms"
|"seconds"
|"bars"
|"beats"
|"1/1"
|"1/2"
|"1/4"
|"1/8"
|"1/16"
|"1/32"
|"1/64"
- resolution :
number``?
- offset :
number``?
- inputs :
InputParameter
[] - pattern :
boolean
|number
|0
|1
|Pulse
|nil
[] | (context :PatternContext
)->
boolean
|number
|0
|1
|Pulse
|nil
| (context :PatternContext
)->
(context :PatternContext
)->
boolean
|number
|0
|1
|Pulse
|nil
- repeats :
boolean
|integer
- gate : (context :
GateContext
)->
boolean
| (context :GateContext
)->
(context :GateContext
)->
boolean
- emit :
Cycle
|Sequence
|Note
|NoteValue
|Note
|NoteValue
[] | (context :EmitterContext
)->
NoteValue
| (context :EmitterContext
)->
(context :EmitterContext
)->
NoteValue
- unit :
- Aliases
- Properties
Global
Functions
rhythm(options : RhythmOptions
)
->
userdata
Create a new rhythm with the given configuration.
examples:
--trigger notes in an euclidean triplet pattern return rhythm { unit = "1/16", resolution = 2/3, pattern = pattern.euclidean(12, 16), emit = { "c4", "g4", { "c5 v0.7", "g5 v0.4" }, "a#4" } }
-- trigger a chord sequence every 4 bars after 4 bars return rhythm { unit = "bars", resolution = 4, offset = 1, emit = { note("c4'm"), note("g3'm7"):transpose({ 0, 12, 0, 0 }) } }
--trigger notes in a seeded, random subdivision pattern local seed = 23498 return rhythm { unit = "1/8", pattern = { 1, { 0, 1 }, 0, 0.3, 0.2, 1, { 0.5, 0.1, 1 }, 0.5 }, gate = function(init_context) local rand = math.randomstate(seed) return function(context) return context.pulse_value > rand() end end, emit = { "c4" } }
--trigger random notes in a seeded random pattern from a pentatonic scale local cmin = scale("c5", "pentatonic minor").notes return rhythm { unit = "1/16", pattern = function(context) return (context.pulse_step % 4 == 1) or (math.random() > 0.8) end, emit = function(context) return { key = cmin[math.random(#cmin)], volume = 0.7 } end }
--emit a tidal cycle every new bar return rhythm { unit = "bars", emit = cycle("[c4 [f5 f4]*2]|[c4 [g5 g4]*3]") }
Aliases
NoteValue
string
| number
| NoteTable
| nil
Pulse
boolean
| number
| boolean
| number
| 0
| 1
| Pulse
| nil
[] | 0
| 1
| nil
-- Single pulse value or a nested subdivision of pulses within a pattern. Pulse: | 0 | 1
EmitterContext
Context passed to functions in 'emit'.
Properties
trigger : Note
?
Note that triggered the rhythm, if any.
inputs : table<string
, boolean
| string
| number
>
Current input parameter values: parameter ids as keys, parameter values as values
beats_per_min : number
Project's tempo in beats per minutes.
beats_per_bar : integer
Project's beats per bar setting.
samples_per_sec : integer
Project's sample rate in samples per second.
pulse_step : integer
Continues pulse counter, incrementing with each new skipped or emitted pulse. Unlike
step
in emitter this includes all pulses, so it also counts pulses which do not emit events. Starts from 1 when the rhythm starts running or is reset.
pulse_time_step : number
Continues pulse time counter, incrementing with each new skipped or emitted pulse. Starts from 0 and increases with each new pulse by the pulse's step time duration.
pulse_time : number
Current pulse's step time as fraction of a full step in the pattern. For simple pulses this will be 1, for pulses in subdivisions this will be the reciprocal of the number of steps in the subdivision, relative to the parent subdivisions pulse step time.
examples:
{1, {1, 1}} --> step times: {1, {0.5, 0.5}}
pulse_value : number
Current pulse value. For binary pulses this will be 1, 0 pulse values will not cause the emitter to be called, so they never end up here. Values between 0 and 1 will be used as probabilities and thus are maybe emitted or skipped.
playback : PlaybackState
Specifies how the emitter currently is running.
step : integer
Continues step counter, incrementing with each new emitted pulse. Unlike
pulse_step
this does not include skipped, zero values pulses so it basically counts how often the emit function already got called. Starts from 1 when the rhythm starts running or is reset.
Aliases
PlaybackState
"running"
| "seeking"
-- - *seeking*: The emitter is auto-seeked to a target time. All results are discarded. Avoid -- unnecessary computations while seeking, and only maintain your generator's internal state. -- - *running*: The emitter is played back regularly. Results are audible. PlaybackState: | "seeking" | "running"
GateContext
Context passed to functions in
gate
.
Properties
trigger : Note
?
Note that triggered the rhythm, if any.
inputs : table<string
, boolean
| string
| number
>
Current input parameter values: parameter ids as keys, parameter values as values
beats_per_min : number
Project's tempo in beats per minutes.
beats_per_bar : integer
Project's beats per bar setting.
samples_per_sec : integer
Project's sample rate in samples per second.
pulse_step : integer
Continues pulse counter, incrementing with each new skipped or emitted pulse. Unlike
step
in emitter this includes all pulses, so it also counts pulses which do not emit events. Starts from 1 when the rhythm starts running or is reset.
pulse_time_step : number
Continues pulse time counter, incrementing with each new skipped or emitted pulse. Starts from 0 and increases with each new pulse by the pulse's step time duration.
pulse_time : number
Current pulse's step time as fraction of a full step in the pattern. For simple pulses this will be 1, for pulses in subdivisions this will be the reciprocal of the number of steps in the subdivision, relative to the parent subdivisions pulse step time.
examples:
{1, {1, 1}} --> step times: {1, {0.5, 0.5}}
pulse_value : number
Current pulse value. For binary pulses this will be 1, 0 pulse values will not cause the emitter to be called, so they never end up here. Values between 0 and 1 will be used as probabilities and thus are maybe emitted or skipped.
PatternContext
Pattern passed to functions in
pattern
andgate
.
Properties
trigger : Note
?
Note that triggered the rhythm, if any.
inputs : table<string
, boolean
| string
| number
>
Current input parameter values: parameter ids as keys, parameter values as values
beats_per_min : number
Project's tempo in beats per minutes.
beats_per_bar : integer
Project's beats per bar setting.
samples_per_sec : integer
Project's sample rate in samples per second.
pulse_step : integer
Continues pulse counter, incrementing with each new skipped or emitted pulse. Unlike
step
in emitter this includes all pulses, so it also counts pulses which do not emit events. Starts from 1 when the rhythm starts running or is reset.
pulse_time_step : number
Continues pulse time counter, incrementing with each new skipped or emitted pulse. Starts from 0 and increases with each new pulse by the pulse's step time duration.
RhythmOptions
Construction options for a new rhythm.
Properties
unit : "ms"
| "seconds"
| "bars"
| "beats"
| "1/1"
| "1/2"
| "1/4"
| "1/8"
| "1/16"
| "1/32"
| "1/64"
Base time unit of the emitter. Use
resolution
to apply an additional factor, in order to create other less common rhythm bases.examples:
-- slightly off beat pulse unit = "beats", resolution = 1.01
-- triplet unit = "1/16", resolution = 2/3
resolution : number
?
Factor which is applied on
unit
to specify the final time resolution of the emitter.examples:
-- slightly off beat pulse unit = "beats", resolution = 1.01
-- triplet unit = "1/16", resolution = 2/3
offset : number
?
Optional offset in
unit * resolution
time units. By default 0. When set, the rhythm's event output will be delayed by the given offset value.examples:
-- start emitting after 4*4 beats unit = "1/4", resolution = 4, offset = 4
inputs : InputParameter
[]
Define optional input parameters for the rhythm. Input parameters can dynamically change a rhythms behavior everywhere where
context
s are passed, e.g. in pattern, gate, emitter or cycle map generator functions.examples:
-- trigger a single note as specified by input parameter 'note' -- when input parameter 'enabled' is true, else triggers nothing. return rhythm { inputs = { parameter.boolean("enabled", true), parameter.integer("note", 48, { 0, 127 }) }, emit = function(context) if context.inputs.enabled then -- boolean value return note(context.inputs.note) -- integer value else return nil end end }
pattern : boolean
| number
| 0
| 1
| Pulse
| nil
[] | (context : PatternContext
) ->
boolean
| number
| 0
| 1
| Pulse
| nil
| (context : PatternContext
) ->
(context : PatternContext
) ->
boolean
| number
| 0
| 1
| Pulse
| nil
Specify the rhythmical pattern of the rhythm. With the default
gate
implementation, each pulse with a value of1
ortrue
will cause an event from theemitter
property to be triggered in the emitters time unit.0
,false
ornil
values do not trigger.Patterns may contains subdivisions, sub tables of pulses, to "cram" multiple pulses into a single pulse's time interval. This way more complex rhythmical patterns can be created.
When no pattern is defined, a constant pulse value of
1
is triggered.Just like the
emitter
property, patterns can either be a static array of values or a function or generators which produces values dynamically.examples:
-- static pattern pattern = { 1, 0, 0, 1 }
-- "cram" pulses into a single pulse slot via subdivisions pattern = { 1, { 1, 1, 1 } }
-- patterns created via the "patterns" lib pattern = pattern.from{ 1, 0 } * 3 + { 1, 1 } pattern = pattern.euclidean(7, 16, 2)
-- stateless pattern function pattern = function(context) return math.random(0, 1) end
-- stateful generator function pattern = function(init_context) local triggers = table.new{ 0, 6, 10 } return function(context) local step = (context.pulse_step - 1) % 16 return triggers:contains(step) end end
repeats : boolean
| integer
If and how many times a pattern should repeat. When 0 or false, the pattern does not repeat and plays back only once. When true, the pattern repeats endlessly, which is the default. When a number > 0, this specifies the number of times the pattern repeats until it stops.
Note: When
pattern
is a function or iterator, the repeat count is the number of function calls or iteration steps. When the pattern is a pulse array, this is the number of times the whole pattern gets repeated.examples:
-- one-shot repeat = 0 -- also a one-shot repeat = false -- play the pattern 4 times repeat = 3 -- play & repeat forever (default) repeat = true
gate : (context : GateContext
) ->
boolean
| (context : GateContext
) ->
(context : GateContext
) ->
boolean
Optional pulse train filter function or generator function which filters events between the pattern and emitter. By default a threshold gate, which passes all pulse values greater than zero.
Custom function should returns true when a pattern pulse value should be passed, and false when the emitter should be skipped.
examples:
-- probability gate: skips all 0s, passes all 1s. pulse alues in range (0, 1) are -- maybe passed, using the pulse value as probablility. gate = function(context) return context.pulse_value > math.random() end
-- threshold gate: skips all pulse values below a given threshold value gate = function(context) return context.pulse_value > 0.5 end
emit : Cycle
| Sequence
| Note
| NoteValue
| Note
| NoteValue
[] | (context : EmitterContext
) ->
NoteValue
| (context : EmitterContext
) ->
(context : EmitterContext
) ->
NoteValue
Specify the melodic pattern of the rhythm. For every pulse in the rhythmical pattern, the event from the specified emit sequence. When the end of the sequence is reached, it starts again from the beginning.
To generate notes dynamically, you can pass a function or a function iterator, instead of a static array or sequence of notes.
Events can also be generated using the tidal cycle mini-notation. Cycles are repeated endlessly by default, and have the duration of a single pulse in the pattern. Patterns can be used to sequence cycles too.
examples:
-- a sequence of c4, g4 emit = {"c4", "g4"}
-- a chord of c4, d#4, g4 emit = {{"c4", "d#4", "g4"}} -- or {"c4'min"}
-- a sequence of c4, g4 with volume 0.5 emit = sequence{"c4", "g4"}:volume(0.5)
-- stateless generator function emit = function(context) return 48 + math.random(1, 4) * 5 end
-- stateful generator function emit = function(init_context) local count, step, notes = 1, 2, scale("c5", "minor").notes return function(context) local key = notes[count] count = (count + step - 1) % #notes + 1 return { key = key, volume = 0.5 } end end
-- a note pattern local tritone = scale("c5", "tritone") --[...] emit = pattern.from(tritone:chord(1, 4)):euclidean(6) + pattern.from(tritone:chord(5, 4)):euclidean(6)
-- a tidal cycle emit = cycle("<[a3 c4 e4 a4]*3 [d4 g3 g4 c4]>"),
Aliases
NoteValue
string
| number
| NoteTable
| nil
Pulse
boolean
| number
| boolean
| number
| 0
| 1
| Pulse
| nil
[] | 0
| 1
| nil
-- Single pulse value or a nested subdivision of pulses within a pattern. Pulse: | 0 | 1
scale
Global
Functions
scale(key : string
| number
, mode : ScaleMode
)
->
Scale
Create a new scale from the given key notes and a mode name.
Mode names can also be shortened by using the following synonyms:
- "8-tone" -> "eight-tone"
- "9-tone" -> "nine-tone"
- "aug" -> "augmented"
- "dim" -> "diminished"
- "dom" -> "Dominant"
- "egypt" -> "egyptian"
- "harm" -> "harmonic"
- "hungary" -> "hungarian"
- "roman" -> "romanian"
- "min" -> "minor"
- "maj" -> "major"
- "nat" -> "natural"
- "penta" -> "pentatonic"
- "span" -> "spanish",
examples:
scale("c4", "minor").notes -> {"c4", "d4", "d#4", "f4", "g4", "g#4", "a#4"}
-- Available scale mode names. mode: | "chromatic" | "major" | "minor" | "natural major" | "natural minor" | "pentatonic major" | "pentatonic minor" | "pentatonic egyptian" | "blues major" | "blues minor" | "whole tone" | "augmented" | "prometheus" | "tritone" | "harmonic major" | "harmonic minor" | "melodic minor" | "all minor" | "dorian" | "phrygian" | "phrygian dominant" | "lydian" | "lydian augmented" | "mixolydian" | "locrian" | "locrian major" | "super locrian" | "neapolitan major" | "neapolitan minor" | "romanian minor" | "spanish gypsy" | "hungarian gypsy" | "enigmatic" | "overtone" | "diminished half" | "diminished whole" | "spanish eight-tone" | "nine-tone"
scale_names()
->
string
[]
Return supported scale mode names.
Aliases
DegreeValue
integer
| "i"
| "ii"
| "iii"
| "iv"
| "v"
| "vi"
| "vii"
-- Roman number or plain number as degree in range [1 - 7] DegreeValue: | "i" | "ii" | "iii" | "iv" | "v" | "vi" | "vii"
NoteValue
string
| number
| NoteTable
| nil
ScaleMode
string
| "all minor"
| "augmented"
| "blues major"
| "blues minor"
| "chromatic"
| "diminished half"
| "diminished whole"
| "dorian"
| "enigmatic"
| "harmonic major"
| "harmonic minor"
| "hungarian gypsy"
| "locrian major"
| "locrian"
| "lydian augmented"
| "lydian"
| "major"
| "melodic minor"
| "minor"
| "mixolydian"
| "natural major"
| "natural minor"
| "neapolitan major"
| "neapolitan minor"
| "nine-tone"
| "overtone"
| "pentatonic egyptian"
| "pentatonic major"
| "pentatonic minor"
| "phrygian dominant"
| "phrygian"
| "prometheus"
| "romanian minor"
| "spanish eight-tone"
| "spanish gypsy"
| "super locrian"
| "tritone"
| "whole tone"
-- Available scale mode names. ScaleMode: | "chromatic" | "major" | "minor" | "natural major" | "natural minor" | "pentatonic major" | "pentatonic minor" | "pentatonic egyptian" | "blues major" | "blues minor" | "whole tone" | "augmented" | "prometheus" | "tritone" | "harmonic major" | "harmonic minor" | "melodic minor" | "all minor" | "dorian" | "phrygian" | "phrygian dominant" | "lydian" | "lydian augmented" | "mixolydian" | "locrian" | "locrian major" | "super locrian" | "neapolitan major" | "neapolitan minor" | "romanian minor" | "spanish gypsy" | "hungarian gypsy" | "enigmatic" | "overtone" | "diminished half" | "diminished whole" | "spanish eight-tone" | "nine-tone"
Scale
Properties
notes : integer
[]
Scale note values as integers, in ascending order of the mode, starting from the scale's key note.
Functions
chord(self, degree : DegreeValue
, note_count : integer
?
)
->
notes : integer
[]
Create a chord from the given degree, built from the scale's intervals. Skips nth notes from the root as degree, then takes every second note from the remaining scale to create a chord. By default a triad is created.
examples:
local cmin = scale("c4", "minor") cmin:chord("i", 4) --> {48, 51, 55, 58} note(cmin:chord(5)):transpose({12, 0, 0}) --> Gm 1st inversion
-- Roman number or plain number as degree in range [1 - 7] degree: | "i" | "ii" | "iii" | "iv" | "v" | "vi" | "vii"
degree(self, ...DegreeValue
)
->
... : integer
Get a single or multiple notes by its degree from the scale, using the given roman number string or a plain number as interval index. Allows picking intervals from the scale to e.g. create chords with roman number notation.
examples:
local cmin = scale("c4", "minor") cmin:degree(1) --> 48 ("c4") cmin:degree(5) --> 55 cmin:degree("i", "iii", "v") --> 48, 50, 55
-- Roman number or plain number as degree in range [1 - 7] ...(param): | "i" | "ii" | "iii" | "iv" | "v" | "vi" | "vii"
notes_iter(self, count : integer
?
)
Create an iterator function that returns up to
count
notes from the scale. If the count exceeds the number of notes in the scale, then notes from the next octave are taken.The iterator function returns nil when the maximum number of MIDI notes has been reached, or when the given optional
count
parameter has been exceeded.examples:
--collect 16 notes of a c major scale local cmaj = scale("c4", "major") local notes = {} for note in cmin:notes_iter(16) do table.insert(notes, note) end -- same using the `pattern` library local notes = pattern.new(16):init(cmaj.notes_iter())
fit(self, ...NoteValue
)
->
integer
[]
Fit given note value(s) into scale by moving them to the nearest note in the scale.
examples:
local cmin = scale("c4", "minor") cmin:fit("c4", "d4", "f4") --> 48, 50, 53 (cmaj -> cmin)
Aliases
DegreeValue
integer
| "i"
| "ii"
| "iii"
| "iv"
| "v"
| "vi"
| "vii"
-- Roman number or plain number as degree in range [1 - 7] DegreeValue: | "i" | "ii" | "iii" | "iv" | "v" | "vi" | "vii"
NoteValue
string
| number
| NoteTable
| nil
sequence
Global
Functions
sequence(...Note
| NoteValue
)
->
Sequence
Create a sequence from an array of note values or note value varargs.
Using
sequence
instead of a raw{}
table can be useful to ease transforming the note content and to explicitly pass a sequence of e.g. single notes to the emitter.examples:
-- sequence of C4, C5 and an empty note sequence(48, "c5", {}) -- sequence of a +5 transposed C4 and G4 major chord sequence("c4'maj", "g4'maj"):transpose(5)
Aliases
NoteValue
string
| number
| NoteTable
| nil
Sequence
Properties
notes : NoteTable
[][]
Functions
transpose(self, step : integer
| integer
[])
->
Sequence
Transpose all notes key values with the specified step value or values.
Values outside of the valid key range (0 - 127) will be clamped.
examples:
sequence("c4", "d#5"):transpose(12) sequence(note("c'maj"), note("c'maj")):transpose({0, 5})
amplify(self, factor : number
| number
[])
->
Sequence
Multiply all notes volume values with the specified factor or factors.
Values outside of the valid volume range (0 - 1) will be clamped.
examples:
sequence({"c4 0.5", "g4"}):amplify(0.5) sequence("c'maj 0.5"):amplify({2.0, 1.0, 0.3})
instrument(self, instrument : number
| number
[])
->
Note
Set the instrument attribute of all notes to the specified value or values.
volume(self, volume : number
| number
[])
->
Sequence
Set the volume attribute of all notes to the specified value or values.
examples:
sequence({"c4", "g4"}):volume(0.5) sequence("c'maj"):volume(0.5) sequence("c'maj"):volume({0.1, 0.2, 0.3})
panning(self, panning : number
| number
[])
->
Note
Set the panning attribute of all notes to the specified value or values.
delay(self, delay : number
| number
[])
->
Sequence
Set the delay attribute of all notes to the specified value or values.
Lua Module Extensions
math
Functions
imod(index : integer
, length : integer
)
->
integer
Wrap a lua 1 based integer index into the given array/table length.
->
(index - 1) % length + 1
random(m : integer
, n : integer
)
->
integer
math.random()
: Returns a float in the range [0,1).math.random(n)
: Returns a integer in the range [1, n].math.random(m, n)
: Returns a integer in the range [m, n].
randomseed(x : integer
)
math.randomseed(x, y)
: Concatenatex
andy
into a 128-bitseed
to reinitialize the pseudo-random generator.math.randomseed(x)
: Equate tomath.randomseed(x, 0)
.math.randomseed()
: Generates a seed with a weak attempt for randomness.
randomstate(seed : integer
?
)
->
(m : integer
?
, n : integer
?
) ->
number
Create a new local random number state with the given optional seed value.
When no seed value is specified, the global
math.randomseed
value is used. When no global seed value is available, a new unique random seed is created.Random states can be useful to create multiple, separate seeded random number generators, e.g. in pattern, gate or emit generators, which get reset with the generator functions.
examples:
return rhythm { emit = function(init_context) -- use a unique random sequence every time the rhythm gets (re)triggered local rand = math.randomstate(12345) return function(context) if rand(1, 10) > 5 then return "c5" else return "g4" end end }
table
Functions
new(t : table
?
)
->
table
| tablelib
Create a new empty table, or convert an exiting table to an object that uses the global 'table.XXX' functions as methods, just like strings in Lua do.
examples:
t = table.new(); t:insert("a"); print(t[1]) -> "a"; t = table.new{1,2,3}; print(t:concat("|")); -> "1|2|3";
contains(t : table
, value : any
, start_index : integer
?
)
->
boolean
Test if the table contains an entry matching the given value, starting from element number start_index or 1.
examples:
t = {"a", "b"}; table.contains(t, "a") --> true t = {a=1, b=2}; table.contains(t, 2) --> true t = {"a", "b"}; table.contains(t, "c") --> false
find(t : table
, value : any
, start_index : integer
?
)
->
key : any
Find first match of given value, starting from element number start_index or 1.
Returns the first key that matches the value or nil
examples:
t = {"a", "b"}; table.find(t, "a") --> 1 t = {a=1, b=2}; table.find(t, 2) --> "b" t = {"a", "b", "a"}; table.find(t, "a", 2) --> "3" t = {"a", "b"}; table.find(t, "c") --> nil
tostring(t : table
)
->
string
Serialze a table to a string for display/debugging purposes.
copy(t : table
)
->
table
Copy the metatable and all elements non recursively into a new table. Creates a clone with shared references.
Lua Builtin Types
any
A type for a dynamic argument, it can be anything at run-time.
boolean
A built-in type representing a boolean (true or false) value, see details
function
A built-in type representing functions, see details
integer
A helper type that represents whole numbers, a subset of number
lightuserdata
A built-in type representing a pointer, see details
nil
A built-in type representing a non-existant value, see details. When you see
?
at the end of types, it means they can be nil.
number
A built-in type representing floating point numbers, see details
self
A type that represents an instance that you call a function on. When you see a function signature starting with this type, you should use
:
to call the function on the instance, this way you can omit this first argument.local object = SomeClass() object:do_something(123)
string
A built-in type representing a string of characters, see details
table
A built-in type representing associative arrays, see details
unknown
A dummy type for something that cannot be inferred before run-time.
userdata
A built-in type representing array values, see details.