Welcome

... to the afseq scripting guide!

Introduction

afseq, also known as nerdo-rhythm, is an experimental imperative-style music sequence generator engine.

It allows you to programmatically create music sequences either in plain Rust as library (static, compiled) or in Lua as a scripting engine (dynamic, interpreted). So it's also suitable for live coding music.

In addition to its imperative event generator approach, it also supports the creation of musical events using tidalcycle's mini-notation.

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.

You can also use play-script.rs from the examples in the git repository to test out afseq scripts using a basic sample player that plays a sample from the example assets folder using the script which has the same name as the audio file.

Scripting

afseq uses Lua as a scripting language to dynamically generate content.

If you're not familiar with Lua, don't worry. Lua is very easy to pick up if you have used another imperative programming language before, and fortunately there are great tutorials out there, such as this one.

Creating Rhythms

Ready to program some music? Then let's dive into the next chapter which will give you an overview of the overall architecture of a rhythm, the main building block in afseq.


Note: This guide covers the afseq Lua scripting API. For instructions on creating rhythms in plain Rust, see the afseq crate docs.

Rhythm

afseq consumes Lua script files that define rhythms, the main building block in afseq.

A rhythm programatically generates musical events.

The Lua API uses configuration tables to define the rhythm and their sub-components, so the main building blocks of a script are defined via Lua tables and functions as specified in the API documentation.

Components

  • TimeBase defines the time unit of a rhythm.
  • Pattern -> Gate -> Emitter do perform the basic event generation in 3 stages.
  • Parameters change behaviour of components during runtime.
     *Inputs*
 Optional user controlled parameters.
       ↓ ↓ ↓
┌────────────────────────────────────────────────────────┐ 
│   *Time base*                                          │
│ Basic unit of time and the increment of a pulse.       │
│ ┌┄┄┄┄┄┄┄┄┄┄┄┄┄┐                                        │
│ │  *Pattern*  │ e.g. { 0, 1, 0, 1, 1 }                 │
│ └┄┄┄┄┄┄┄┄┄┄┄┄┄┘                                        │
│ Defines the basic rhythmic pattern as a pulse train.   │
│        ↓                                               │
│ ┌┄┄┄┄┄┄┄┄┄┄┄┄┄┐                                        │
│ │   *Gate*    │ e.g. { return pulse > 0.5 }            │
│ └┄┄┄┄┄┄┄┄┄┄┄┄┄┘                                        │
│ Passes or suppresses pattern pulses.                   │
│        ↓                                               │
│ ┌┄┄┄┄┄┄┄┄┄┄┄┄┄┐                                        │
│ │  *Emitter*  │ e.g. sequence{ "c4", "d#4", "g#4" }    │
│ └┄┄┄┄┄┄┄┄┄┄┄┄┄┘                                        │
│ Generates events for each incoming filtered pulse.     │
└────────────────────────────────────────────────────────┘
       ↓ ↓ ↓
    [o] [x] [o] 
   *Event Stream*

By separating the rhythmical pattern from the tonal part of a musical sequence, each part of a sequence can be freely modified, composed and (re)combined.

All content in rhythms can be either static, a Lua table of events, or dynamic, a Lua function that generates events on the fly.

Dynamic functions or generators can also be controlled, automated via input parameters to change their behaviour at runtime in response to user input (e.g. MIDI controllers, DAW parameter automation). This also allows creating more flexible rhythm templates.

Examples

A simple rhythm with a static pattern and emitter, using the default gate implementation.

-- sequence of 1/4th c4 and two 1/8 c5 notes.
return rhythm {
  unit = "1/4",
  pattern = { 1, { 1, 1 } },
  emit = { "c4", "c5", "c5" }
}

A rhythm with default pattern and gate, emitting a Tidal Cycle.

-- emit a tidal cycle every bar
return rhythm {
  unit = "1/1",
  emit = cycle("a4 e4@2 <c4 c5>")
}

A rhythm, using a Lua function as dynamic pattern generator.

-- maybe trigger a c4 on every 2nd 1/4.
return rhythm {
  unit = "1/4",
  pattern = function(context) 
    if context.pulse_step % 2 == 1 then
      return math.random() > 0.5 and 1 or 0
    else
      return 1
    end 
  end,
  emit = "c4"
}

A rhythm with a static pattern and emitter and a dynamic seeded probablility gate.

-- change for other variations or set to nil to get *really* random behavior 
local seed = 12345678

return rhythm {
  unit = "1/4",
  pattern = { 1, { 1, 0.1, 0.2 }, 1, { 1, 0, 0, 0.3 } },
  gate = function(init_context)
    local rand = math.randomstate(seed)
    return function(context)
      -- use pulse values as trigger probabilities
      return context.pulse_value >= rand() 
    end
  end,
  emit = { "c4'-7 v0.6", "d#4'M v0.4", "g4'- v0.3" },
}

See Examples in this guide for 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 = 4/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) or note("c4", "g4") or note("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 as 48 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.

Tidal Cycles allows you to make patterns with code using a custom functional approach. It includes a language for describing flexible (e.g. polyphonic, polyrhythmic, generative) sequences of sounds, notes, parameters, and all kind of information.

Usage

To create cycles in afseq, use the cycle function in the emitter and pass it a mini-notation as string.

> emit = cycle("c4 d#4 <a5 g5> _")

note

Please see Tidal Cycles Mini-Notation Reference for a complete overview of the cycle notation.

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])

  • Operators currently only accept numbers on the right side (a3*2 is valid, a3*<1 2> is not)

  • : - Sets the instrument or remappable target instead of selecting samples

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 bar
return rhythm {
  unit = "bars",
  emit = cycle("c d e f")
}

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 a bar, then repeat
return rhythm {
  unit = "bars",
  pattern = {1, 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).

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 rhythm {
  unit = "bars",
  emit = 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 rhythm {
  unit = "bars",
  emit = 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)
}

Examples

A simple polyrhythm

return rhythm {
  unit = "1/1",
  emit = cycle("[C3 D#4 F3 G#4], [[D#3?0.2 G4 F4]/64]*63")
}

Mapped multi channel beats

return rhythm {
  unit = "1/1",
  emit = 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:

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's chord 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"
  • "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"
    | "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" | "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"
    | "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])
  • Operators currently only accept numbers on the right side (a3*2 is valid, a3*<1 2> is not)
  • : - Sets the instrument or remappable target instead of selecting samples Tidal Cycles Reference

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 : integer?

Note value that triggered, started the rhythm, if any.

trigger_volume : number?

Note volume that triggered, started the rhythm, if any.

trigger_offset : integer?

Note slice offset value that triggered, started the rhythm, if any.

inputs : table<string, boolean | string | number>

Current input parameter values, using parameter ids as keys and the actual parameter value as value.

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

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?)

->InputParameter

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?)

->InputParameter

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?)

->InputParameter

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?)

->InputParameter

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

boolean

Default boolean value.

InputParameterDescription

string

Optional long description of the parameter describing what the parameter does.

InputParameterEnumDefault

string

Default string value. Must be a valid string within the specified value set.

InputParameterId

string

Unique id of the parameter. The id will be used in the input context table as key.

InputParameterIntegerDefault

integer

Default integer value. Must be in the specified value range.

InputParameterIntegerRange

{ 1 : integer, 2 : integer }

Optional value range. When undefined (0.0 - 1.0)

InputParameterName

string

Optional name of the parameter as displayed to the user. When undefined, the id is used.

InputParameterNumberDefault

number

Default number value. Must be in the specified value range.

InputParameterNumberRange

{ 1 : number, 2 : number }

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(60) -- 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

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

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

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)

->PulseValue

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


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 = 4/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 'emit' functions.


Properties

trigger_note : integer?

Note value that triggered, started the rhythm, if any.

trigger_volume : number?

Note volume that triggered, started the rhythm, if any.

trigger_offset : integer?

Note slice offset value that triggered, started the rhythm, if any.

inputs : table<string, boolean | string | number>

Current input parameter values, using parameter ids as keys and the actual parameter value as value.

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 gate functions.


Properties

trigger_note : integer?

Note value that triggered, started the rhythm, if any.

trigger_volume : number?

Note volume that triggered, started the rhythm, if any.

trigger_offset : integer?

Note slice offset value that triggered, started the rhythm, if any.

inputs : table<string, boolean | string | number>

Current input parameter values, using parameter ids as keys and the actual parameter value as value.

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

Context passed to pattern and gate functions.


Properties

trigger_note : integer?

Note value that triggered, started the rhythm, if any.

trigger_volume : number?

Note volume that triggered, started the rhythm, if any.

trigger_offset : integer?

Note slice offset value that triggered, started the rhythm, if any.

inputs : table<string, boolean | string | number>

Current input parameter values, using parameter ids as keys and the actual parameter value as value.

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 = 4/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 = 4/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 contexts 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 of 1 or true will cause an event from the emitter property to be triggered in the emitters time unit. 0, false or nil 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?)

->() -> integer | nil

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].

View documents

randomseed(x : integer)

  • math.randomseed(x, y): Concatenate x and y into a 128-bit seed to reinitialize the pseudo-random generator.
  • math.randomseed(x): Equate to math.randomseed(x, 0) .
  • math.randomseed(): Generates a seed with a weak attempt for randomness.

View documents

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
}

See:

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.