Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Welcome

... to the pattrns scripting guide! pattrns is an experimental imperative music sequence generator engine that lets you create musical patterns programmatically using Lua scripts for algorithmic or live music coding.

This guide focuses on the Lua scripting API. For Rust development, see the pattrns crate docs.

Key Features

Programmatic : Build musical patterns using dynamic scripts or precompiled code.
Modular : Combine and reuse patterns components dynamically.
Flexible : Create anything from simple beats to complex musical phrases.
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.
Templates : Create user configurable pattern templates.
Cycles Mini-Notation : Use familiar Tidal Cycles mini-notation for rapid pattern creation.

Applications

Online Playground : Experiment with pattrns scripts directly in your browser
Rust Examples : Write or test scripts in Rust or Lua with a basic sample player.

Documentation

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: pattrns 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.

Online Playground

All the examples in this quickstart are available in the Online Playground as well, where you can run and modify them directly in your browser — no installation required.

Table of Contents

Basic Patterns

Quarter Note Pulse

-- The most basic rhythm: steady quarter notes
return pattern {
  unit = "1/4", -- Quarter note timing grid
  event = "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 event defines what note to play.

Alternating Notes

-- Create a pattern that alternates between notes
return pattern {
  unit = "1/8",         -- Eighth note timing grid
  pulse = {1, 0, 1, 1}, -- Play-rest-play-play pattern
  event = {"c4", "d4"}  -- Alternates between C4 and D4
}
  • TRY THIS: Change pattern to {1, 1, 0, 1} for a different rhythm
  • TRY THIS: Add more note events like {"c4", "d4", "e4", "g4"}

Here, pulse controls when notes play (1) or rest (0). The event parameter cycles through the provided notes when a step triggers.

Note properties

return pattern {
  unit = "1/8",
  event = { "c4 v0.2", "off d0.5", "g4 v0.8" }
}
  • TRY THIS: Play specific instruments with # such as c4 #8
  • TRY THIS: Add delays to some of the notes

Note events can be expressed in different ways. Use properties such as v = volume [0 - 1] p = panning [-1 - 1] d = delay [0 - 1] # = instrument to modify notes.

See Notes & Scales for more info out how to express note events.

Rhythm Variations

Subdivided Pulses

-- Pattern with mixed note lengths
return pattern {
  unit = "1/4",
  pulse = {1, {1, 1, 1, 1}}, -- One quarter note, then four sixteenth notes
  event = {"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 pulse create subdivisions, allowing for more complex rhythms within the basic unit.

Triplet Resolution

-- Create swing or triplet feel
return pattern {
  unit = "1/8",
  resolution = 2/3, -- Triplet feel (3 notes in space of 2)
  event = {"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.

Euclidean Rhythms

-- Distributes notes evenly across steps (common in many music traditions)
return pattern {
  unit = "1/16",
  pulse = pulse.euclidean(3, 8), -- 3 hits spread over 8 steps
  event = "c4" -- Basic note
}
  • TRY THIS: Try different combinations like (5, 8) or (7, 16, -2)
  • TRY THIS: Use pulse = pulse.euclidean(3, 8) + pulse.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 Pulse API contains various tools to programatically create patterns.

Notes and Scales

Basic Note Stacks

-- Simple chord by stacking notes
return pattern {
  unit = "1/1",
  event = {{"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 pattern {
  unit = "1/1",
  event = {
    "c4'M",   -- C major using ' chord notation
    "d4'm",   -- D minor
    "g4'dom7" -- 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 pattern {
  unit = "1/1",
  event = {
    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 an easy way to e.g. create 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 pattern {
  unit = "1/4", -- Emit a cycle every beat
  event = 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 pattern {
  unit = "1/4",
  event = 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 pattrns.

Dynamic Pulses & Events

Random Note Selection

-- Randomly select notes from a list
local notes = {"c4", "d4", "e4", "g4"}
return pattern {
  unit = "1/8",
  event = 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 pattern {
  unit = "1/8",
  pulse = {1, 1, 1, 1},  -- Regular pattern
  event = 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 pulse. 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 pattern {
  unit = "1/8",
  event = 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 pattern {
  unit = "1/8",
  pulse = {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,
  event = "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 pattern {
  unit = "1/4",
  event = 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 patterns.

Generative Melody with Constraints

-- Create melodies that follow musical rules
return pattern {
  unit = "1/8",
  event = 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 Pattern is the main building block in pattrns. It lets you define when and what to play.

pattrns consumes Lua script files that define patterns as specified in the API documentation.

Components

  • TimeBase defines the time unit of a pattern.
  • PulseGateEvent perform the basic event generation in 3 stages.
  • Parameters allow changing behavior of components during runtime.

All content in patterns can be either static or dynamic:

  • Static content is defined as a Lua table. There are various helpers included in the API, such as note scales, chords, and pulse table to ease creating static content.

  • Dynamic content is generated on the fly by Lua functions while the pattern runs. Generators are functions with local state, which can e.g. be used to apply replicable randomization.

Cycle events use 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 pattern's timebase represents the unit of time for the pattern, 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 pattern is a beat.

The BPM and signature (beats per bar) settings are configured by the application which is running the pattern.

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 property 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 pattern {
  unit = "beats", 
  resolution = 1.01,
  event = "c4"
}

Sixteenth tripplets.

return pattern {
  unit = "1/16", 
  resolution = 2/3,
  event = "c4"
}

A beat expressed with resolution.

return pattern {
  unit = "1/1", 
  resolution = 1/4,
  event = "c4"
}

2 Seconds.

return pattern {
  unit = "seconds", 
  resolution = 2,
  event = "c4"
}

Pulse

A pattern's pulse defines the temporal occurence of events - the rhythm. It triggers events 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.

Pulses can be generated by algorithms such as Euclidean rhythms, can be expressed as a static Lua tables, or can be generated by a dynamic generator - a Lua function.

The default pulse is an endless repeated pulse train of 1's.

Offset

By default, the pattern starts running immediately when the pattern is triggered. Using the offset property you can delay the start by the amount specified in the pattern's unit.

» offset = 4 delay start by 4 * pattern'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 pulse is a Lua table (array) of numbers or boolean values:

» pulse = {0, 1, 1} skip, trigger, trigger - repeat

» pulse = {1, 0, 1, 1} 1/2th followed by two 1/4th triggers in a 1/4th unit rhythm

» pulse = {0.8, 0, 1, 0.2} pulse values are passed as numbers to gate and event in function contexts

tip

The default gate implementation skips all 0 pulses and passes all other pulse values to the event 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 event function to create accents.

Sub-Divisions

Each number value in the specified Lua table represents a single pulse in the pattern'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.

» pulse = {{1, 1, 1}, {1, 1}} triplet followed by two quarter notes with unit 1/2

» pulse = {{1, 0}, {0, 1}} basic bossanova rhythm with unit 1/4

Dynamic Patterns

When using a Lua function instead of a table as a pulse generator, you can dynamically generate pulse values.

» pulse = function(context) return math.random() end randomly emit pulse values between 0 and 1

The expected return value of a dynamic pattern function is (true, false, 0, 1) or nil and will be converted to a number when passed to the event emitter.

Use functions in order to create dynamic rhythms that can interact with the user, or to create probability based trigger rules. To connect the functions to its runtime use the passed context argument.

See generators for more info about using functions as generators.

Pulse Library

pattrns comes with a built-in pulse Lua library, which contains a bunch of helper functions and generators to ease creating pulse arrays.

» pulse = pulse.from{0, 1} * 3 + {1, 0} combine sub pulses

» pulse = pulse.new(12, function(k, v) return k % 3 == 1 end), functionally create pulses

» pulse = pulse.euclidean{3, 8, -1} create euclidean rhythms

See Pattern API Lua reference for more info and examples.

Examples

Static pulse.

return pattern {
  pulse = { 1, 0, 0, 1 },
  event = "c4"
}

Cram pulses into a single pulse slot via subdivisions.

return pattern {
  pulse = { 1, { 1, 1, 1 } },
  event = "c4"
}

Static pulse pattern created using the "pulse" library.

return pattern {
  pulse = pulse.from{1, 0} * 5 + {1, 1},
  event = "c4"
}

Euclidean pattern created using the "pulse" library.

return pattern {
  pulse = pulse.euclidean(7, 16, 2),
  event = "c4"
}

Stateless function.

return pattern {
  pulse = function(context)
    return math.random(0, 1)
  end,
  event = "c4"
}

Stateful function.

return pattern {
  pulse = function(init_context)
    local rand = math.randomstate(12345)
    local triggers = table.from{ 0, 6, 10 }
    return function(context)
      local step = (context.pulse_step - 1) % 16
      return rand() > 0.8 and triggers:contains(step)
    end
  end,
  event = "c4"
}

See generators for more info about stateful functions.

Gate

A pattern's gate is an optional filter unit that determines whether or not an event should be passed from the pulse to the event stage. It can be used to statically or dynamically filter out pulses.

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 pattern {
  pulse = { 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,
  event = { "c4" }
}

A gate which filters out pulse values with on a configurable threshold.

return pattern {
  parameter = { 
    parameter.number("threshold", 0.5, {0, 1}) 
  },
  pulse = { 
    0.2, { 0.5, 1 }, 0.9, { 1, 0.8 } 
  },
  gate = function(context)
    return context.pulse_value >= context.parameter.threshold
  end,
  event = { "c4" }
}

See generators for more info about stateful generators and parameters about pattern parameters.

Event

A pattern's event defines which events should be played for incoming pulse values. Just like the pulse, 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 event emitter.

The default event implementation emits a single middle C note value for each incoming pulse.

Event Types

Currently pattrns only supports monophonic or polyphonic note events as event output. This is likely to change in the future to allow other musically interesting events to be emitted.

Note values can be expressed as:

  • Integer values like 48, which are interpreted as MIDI note numbers.
  • 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 an event (sequence) is a Lua array of note values.

Static event arrays define note event sequences. Each incoming, possibly filtered, gated pulse from the pulse picks the next event in the sequence, then moves on in the sequence. Sequences are repeated as long as the pattern is running.

» event = { "c4", "d#4", "g4" } arpeggio - sequence

» event = { { "c4", "d#4", "g4" } } single chord - single event

To ease distinguishing polyponic contents, use sequence and note:

» event = sequence("c4", "d#4", "g4") arpeggio - sequence

» event = note("c4", "d#4", "g4") single chord - single event

Dynamic Emitters

Dynamic event functions return single note events. Each incoming, possibly filtered, gated value from the pulse will trigger the event function to create the next event as long as the pattern is running.

» event = function(context) return math.random() > 0.5 and "c4" or "c5" end randomly emit c4 or c5 notes

» event = 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 event 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 Events

Cycle event emitters emit a whole cycle for a single pulse. So any incoming, possibly filtered, gated value from the pulse will trigger a full cycle as long as the pattern is running.

» event = cycle("[c4, d#4, g4]") a single chord

You likey won't use custom pulse or gate functions with cycles, but it's possible to sequence entire cycles with them, or use cycles as single note generators too:

» event = cycle("[c4 d#4 g4]") arpeggio

» event = cycle("[c4 <d#4 d4> g4|g5]") arpeggio with variations

See cycles for more info about Tidal Cycles support in pattrns.

Examples

Sequence of c4, g4 notes.

return pattern {
  event = { "c4", "g4" }
}

Chord of c4, d#4, g4 notes.

return pattern {
  event = sequence(
    { "c4",  "d#4",  "g4"  }, -- or "c4'min"
    { "---", "off",  "off" }
  ) 
}

Sequence of c4, g4 with volume 0.5.

return pattern {
  event = sequence{"c4", "g4"}:volume(0.5)
}

Stateless function.

return pattern {
  event = function(context)
    return 48 + math.random(1, 4) * 5
  end
}

Stateful generator.

return pattern {
  event = 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 "pulse" lib.

local tritone = scale("c5", "tritone")
return pattern {
  event = pulse.from(tritone:chord(1, 4)):euclidean(6) +
    pulse.from(tritone:chord(5, 4)):euclidean(6)
}

Tidal cycle.

return pattern {
  event = 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 event, can be expressed and modified in various ways. Sometimes it's easier to generate notes programmatically using MIDI note numbers. Other times you may want to write down a chord in a more expressible form via a string.

Notes

Note Numbers

Raw integer values like 48, are interpreted as MIDI note numbers in the note function and event property. Valid MIDI notes are 0-127.

» event = 48 emit a single c4 note event

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]

» event = { 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

» event = "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

» event = { "f#4 #1 v0.2" } emit a f sharp for instrument 1 with 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.

» event = "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.

» event = "c4'69 #1 v0.5" instrument 1, volume 0.5

Note Objects

Note numbers, strings and tables, as described above, can be fed into a note object in the LuaAPI as well, which allows further transformation of the note.

This is especially handy for chords, but also can be more verbose than using note string attributes.

» event = note(48):volume(0.1) c4 note with volume of 0.1

» event = note({key = "c4"}):volume(0.2) c4 note with volume of 0.2

» event = note("c4'min"):transpose({12, 0, 0}) C minor 1st inversion

See note Lua API for details.

Note Chord Objects

Notes objects can also be created using the chords function.

» event = chord(48, "major") c4 major notes

This also allows the use of custom interval tables.

» event = 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, pattrns 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 pattern {
  event = 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, event in pattrns can also use 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:

  1. C4
  2. D4
  3. Alternate between E4 and G4
  4. play no event: _ means rest

Pattern Basics

Key symbols to know:

SymbolMeaningExample
Separates stepsc4 d4
,Parallel patterns[c4,e4], [g4,a4]
< >Alternates between values<c4 e4 g4>
|Random choicec4|d4|e4
*Repeatc4*4
_Elongatec4 _ d4
~Restc4 ~ 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 Pulses

Control when cycles play using pulse:

return pattern {
  unit = "bars",     -- Timing unit
  pulse = {1, 0},    -- Play on odd bars only
  event = 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 pattrns, the time of a cycle instead is given in cycles per pattern pulse units.

-- emits an entire cycle every beat
return pattern {
  unit = "beats",
  event = cycle("c4 d4 e4") -- triplet
}

Sequencing

An event in pattrns gets triggered for each incoming non-gated pattern pulse. This is true for cycles are well and allows you to sequence cycles too.

-- emit an entire cycle's every bar, then pause for two bars, then repeat
return pattern {
  unit = "bars",
  pulse = { 1, 0, 0 },
  event = cycle("c d e f")
}

You can also use the mini notation to emit single notes only, e.g. making use of the cycle's note alternating and randomization features only:

-- emit a single note from a cycle in an euclidean pattern
return pattern {
  unit = "beats",
  pulse = pulse.euclidean(5, 8),
  event = cycle("<c d e f g|b>")
}

Seeding

pattrns'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 pulse. 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 pattrns. But you can also dynamically evaluate and map cycle identifiers using the cycle map function.

This allows you, for example, to inject 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 pattern {
  unit = "1/1",
  resolution = 4,
  parameter = {
    parameter.enum("mode", "major", { "major", "minor" })
  },
  event = cycle("I V III VI"):map(
    function(init_context, value)
      local s = scale("c4", init_context.parameter.mode)
      return function(context, value)
        return value ~= "_" and s:chord(value) or value
      end
    end
  )
}

Parameters

Pattern parameters allow user controlled parameter values to be injected into a pattern. This allows you to write more flexible patterns that can be used as templates or to automate functions within the pattern.

Input parameters can be accessed in dynamic pulse, gate, event 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 is used to access the actual paramter value in the function contexts.

Definition:

» parameter = { parameter.boolean("enabled", true) }

Usage:

» event = function(context) return context.parameter.enabled and "c5" or nil }

Usage, if you've got spaces in your ids (not recommended):

» event = function(context) return context.parameter["enabled"] and "c5" or nil }

Examples

Euclidean pattern generator with user configurable steps, pulses, offset value.

return pattern {
  parameter = {
    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",
  pulse = function(context)
    return pulse.euclidean(
      math.min(context.parameter.steps, context.parameter.pulses), 
      context.parameter.pulses, 
      context.parameter.offset)
  end,
  event = "c4"
}

Random bass line generator with user defined custom scales and variations (seeds).

local scales = {"Chromatic", "Minor", "Major"}
return pattern {
  parameter = {
    parameter.enum('scale', scales[1], scales, "Scale"),
    parameter.integer('notes', 7, {1, 12}, "#Notes"),
    parameter.integer('variation', 0, {0, 0xff}, "Variation"),
  },
  unit = "1/1",
  pulse = function(context)
    local rand = math.randomstate(2345 + context.parameter.variation)
    return pulse.euclidean(rand(3, 16), 16, 0)
  end,
  event = function(context)
    local notes = scale("c4", context.parameter.scale).notes
    local rand = math.randomstate(127364 + context.parameter.variation)
    local notes = pulse.new(context.parameter.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 pattern {
  unit = "1/1",
  parameter = {
    parameter.integer("bd_note", 48, {0, 127}),
    parameter.integer("sn_note", 70, {0, 127}),
    parameter.integer("hh_note", 98, {0, 127})
  },
  event = 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 patterns.

Please read the basic concepts from the guide first to get you started...

Generators

Pulse, Gate and Event can be specified as Lua functions to dynamically generate or evaluate content.

Anonymous Lua functions, as used in patterns, 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 pattrns scripts to keep track of a pattern's global or local state.

Runtime

To better understand how local and global states are relevant here, we first need to understand how patterns are evaluated.

Let's say we're in a DAW that supports pattrns. This DAW triggers your pattern 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 pattern 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 event function keeps track of its state by referencing a globally defined counter variable.

local counter = 0
return pattern {
  event = function(context)
    local midi_note = counter 
    counter = (counter + 1) % 128 
    return note(midi_note) 
  end, 
}

When playing a single instance of this pattern, 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 pattern's internal playback state.

A context passed to pattern functions only contains the global playback status. A context passed to gate and event functions contains the global playback status and status of the pulse.

See pattern context API, gate context API, event context API for details.

Contexts also may contain user controlled input variables. See parameters for more info about this.

By making use of the context we can now rewrite the example above to:

return pattern {
  event = 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 pattern instance, we now get multiple continously increasing note event streams again.

Generators

Generators in pattrns 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 pattern {
  event = 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 pattern is started - just to create the local state and to return the actual emit function. The returned function is then called repeatedly while the pattern 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 pattern 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 pattern 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 pattrns 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 pattrns, and can use math.randomseed() to seed them.

Note that the standard Lua random implementation is overridden by pattrns, 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 = pulse.new(10, function()
  return cmin.notes[math.random(#cmin.notes)] 
end)

return pattern {
  event = 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 = pulse.new(10, function() 
  return cmin.notes[math.random(#cmin.notes)] 
end)

return pattern {
  event = random_notes
}

Local Random Number Generators

When seeding the RNG, each time a pattern is (re)started, an existing pattern instance will continue to run. The global state of a pattern script is not recreated each time the pattern is played again.

See generators for details of how pattrns 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 pattern {
  event = 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 pattrns 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 pattrns type definitions folder (./types/pattrns) 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"
  • "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 | Note | 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) and bd(3, 8)*2 are not supported)

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

NoteValue | NoteValue[]

NoteValue

string | number | Note | NoteTable | nil

PlaybackState

"running" | "seeking"

-- - *seeking*: The pattern is auto-seeked to a target time. All events are discarded. Avoid
--   unnecessary computations while seeking, and only maintain your generator's internal state.
-- - *running*: The pattern is played back regularly. Events are emitted and 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

NoteValue | NoteValue[]

NoteValue

string | number | Note | NoteTable | nil

PlaybackState

"running" | "seeking"

-- - *seeking*: The pattern is auto-seeked to a target time. All events are discarded. Avoid
--   unnecessary computations while seeking, and only maintain your generator's internal state.
-- - *running*: The pattern is played back regularly. Events are emitted and 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 pattern, if any. Usually will ne a monophic note. To access the raw note number value use: context.trigger.notes[1].key

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

Current parameter values: parameter ids are keys, parameter values are values. To access a parameter with id enabled use: context.parameter.enabled

beats_per_min : number

Project's tempo in beats per minutes.

beats_per_bar : integer

Project's beats per bar settings - usually will be 4.

samples_per_sec : integer

Project's audio playback sample rate in samples per second.


Aliases

PlaybackState

"running" | "seeking"

-- - *seeking*: The pattern is auto-seeked to a target time. All events are discarded. Avoid
--   unnecessary computations while seeking, and only maintain your generator's internal state.
-- - *running*: The pattern is played back regularly. Events are emitted and audible.
PlaybackState:
    | "seeking"
    | "running"

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 (number in range [0-1])
 -'p' -> panning (number in range [-1-1])
 -'d' -> delay (number 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 0xFE for nil or empty note strings or 0xFF for note offs

Examples:

note_number("c4") --> 48
note_number(note("c4")) --> 48
note_number("-") --> 0xFE
note_number("off") --> 0xFF
note_number("xyz") --> error

Aliases

NoteValue

string | number | Note | NoteTable | nil

Note


Properties

notes : NoteTable[]


Functions

transpose(self, step : integer | integer[])

->Note

Transpose the note 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]

parameter

Parameter

Opaque parameter user data. Construct new parameters via the parameter.XXX(...) functions.


Functions

boolean(id : ParameterId, default : ParameterBooleanDefault, name : ParameterName?, description : ParameterDescription?)

->Parameter

Creates an Parameter with "boolean" Lua type with the given default value and other optional properties.

integer(id : ParameterId, default : ParameterIntegerDefault, range : ParameterIntegerRange?, name : ParameterName?, description : ParameterDescription?)

->Parameter

Creates an Parameter with "integer" Lua type with the given default value and other optional properties.

number(id : ParameterId, default : ParameterNumberDefault, range : ParameterNumberRange?, name : ParameterName?, description : ParameterDescription?)

->Parameter

Creates an Parameter with "number" Lua type with the given default value and other optional properties.

enum(id : ParameterId, default : ParameterEnumDefault, values : string[], name : ParameterName?, description : ParameterDescription?)

->Parameter

Creates an Parameter with a "string" Lua type with the given default value, set of valid values to choose from and other optional properties.


Aliases

ParameterBooleanDefault

boolean

Default boolean value.

ParameterDescription

string

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

ParameterEnumDefault

string

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

ParameterId

string

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

ParameterIntegerDefault

integer

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

ParameterIntegerRange

{ 1 : integer, 2 : integer }

Optional value range. When undefined (0.0 - 1.0)

ParameterName

string

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

ParameterNumberDefault

number

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

ParameterNumberRange

{ 1 : number, 2 : number }

Optional value range. When undefined (0 - 100)

pattern

Global


Functions

pattern(options : PatternOptions)

->userdata

Create a new pattern with the given properties table:

examples:

--trigger notes in an euclidean triplet pattern
return pattern {
  unit = "1/16",
  resolution = 2/3,
  pulse = pulse.euclidean(12, 16),
  event = { "c4", "g4", { "c5 v0.7", "g5 v0.4" }, "a#4" }
}
-- trigger a chord sequence every 4 bars after 4 bars
return pattern {
  unit = "bars",
  resolution = 4,
  offset = 1,
  event = {
    note("c4'm"),
    note("g3'm7"):transpose({ 0, 12, 0, 0 })
  }
}
--trigger notes in a seeded, random subdivision pattern
local seed = 23498
return pattern {
  unit = "1/8",
  pulse = { 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,
  event = { "c4" }
}
--trigger random notes in a seeded random pattern from a pentatonic scale
local cmin = scale("c5", "pentatonic minor").notes
return pattern {
  unit = "1/16",
  pulse = function(context)
    return (context.pulse_step % 4 == 1) or (math.random() > 0.8)
  end,
  event = function(context)
    return { key = cmin[math.random(#cmin)], volume = 0.7 }
  end
}
--emit a tidal cycle every bar
return pattern {
  unit = "bars",
  event = cycle("[c4 [f5 f4]*2]|[c4 [g5 g4]*3]")
}

Aliases

NoteValue

string | number | Note | NoteTable | nil

PulseValue

boolean | number | boolean | number | 0 | 1 | PulseValue | nil[] | 0 | 1 | nil

-- Single pulse value or a nested subdivision of pulses within a rhythm's pulse.
PulseValue:
    | 0
    | 1

EventContext

Event related context passed to functions in 'emit'.


Properties

trigger : Note?

Note that triggered the pattern, if any. Usually will ne a monophic note. To access the raw note number value use: context.trigger.notes[1].key

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

Current parameter values: parameter ids are keys, parameter values are values. To access a parameter with id enabled use: context.parameter.enabled

beats_per_min : number

Project's tempo in beats per minutes.

beats_per_bar : integer

Project's beats per bar settings - usually will be 4.

samples_per_sec : integer

Project's audio playback sample rate in samples per second.

pulse_step : integer

Continues pulse counter, incrementing with each new skipped or emitted pulse. Unlike step in event this includes all pulses, so it also counts pulses which do not emit events. Starts from 1 when the pattern starts running or after it got 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 pulse. 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 0 or 1, but it can be any number value.

playback : PlaybackState

Specifies how the pattern 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 event function already got called. Starts from 1 when the pattern starts running or is reset.


Aliases

PlaybackState

"running" | "seeking"

-- - *seeking*: The pattern is auto-seeked to a target time. All events are discarded. Avoid
--   unnecessary computations while seeking, and only maintain your generator's internal state.
-- - *running*: The pattern is played back regularly. Events are emitted and audible.
PlaybackState:
    | "seeking"
    | "running"

GateContext

Pulse value context passed to functions in gate and event.


Properties

trigger : Note?

Note that triggered the pattern, if any. Usually will ne a monophic note. To access the raw note number value use: context.trigger.notes[1].key

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

Current parameter values: parameter ids are keys, parameter values are values. To access a parameter with id enabled use: context.parameter.enabled

beats_per_min : number

Project's tempo in beats per minutes.

beats_per_bar : integer

Project's beats per bar settings - usually will be 4.

samples_per_sec : integer

Project's audio playback sample rate in samples per second.

pulse_step : integer

Continues pulse counter, incrementing with each new skipped or emitted pulse. Unlike step in event this includes all pulses, so it also counts pulses which do not emit events. Starts from 1 when the pattern starts running or after it got 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 pulse. 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 0 or 1, but it can be any number value.

PatternOptions

Construction options for a new pattern.


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 pattern. Use resolution to apply an additional factor, in order to create other less common time 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 pattern.

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 pattern'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

parameter : Parameter[]

Define optional parameters for the pattern. Parameters can dynamically change a patterns behavior everywhere where contexts are passed, e.g. in pulse, gate, event or cycle map generator functions.

examples:

-- trigger a single note as specified by parameter 'note'
-- when parameter 'enabled' is true, else triggers nothing.
return pattern {
  parameter = {
    parameter.boolean("enabled", true),
    parameter.integer("note", 48, { 0, 127 })
  },
  event = function(context)
    if context.parameter.enabled then -- boolean value
      return note(context.parameter.note) -- integer value
    else
      return nil
    end
  end
}

pulse : boolean | number | 0 | 1 | PulseValue | nil[] | (context : PulseContext) -> boolean | number | 0 | 1 | PulseValue | nil | (context : PulseContext) -> (context : PulseContext) -> boolean | number | 0 | 1 | PulseValue | nil

Defines the rhythmical part of the pattern. With the default gate implementation, each pulse with a value of 1 or true will cause an event from the event property to be triggered in the pattern's time unit. 0, false or nil values do not trigger.

Pulses may contain 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 pulse is defined, a constant pulse value of 1 is triggered.

Just like the event property, pulses can either be a static array of values or a function or generators which produces pulse values dynamically.

examples:

-- static pattern
pulse = { 1, 0, 0, 1 }
-- "cram" pulses into a single pulse slot via sub-divisions
pulse = { 1, { 1, 1, 1 } }
-- pulses created via the "pulse" lib
pulse = pulse.from{ 1, 0 } * 3 + { 1, 1 }
pulse = pulse.euclidean(7, 16, 2)
-- stateless pulse function
pulse = function(context)
  return math.random(0, 1)
end
-- stateful pulse function
pulse = 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 pulse 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 pulse 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 which filters events between the pulse and event emitter. By default a threshold gate, which passes all pulse values greater than zero.

Functions return true when a pulse value should be passed, and false when the event 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

event : Cycle | Sequence | Note | NoteValue | NoteValue[] | (context : EventContext) -> NoteValue | (context : EventContext) -> (context : EventContext) -> NoteValue

Specify the event values of the pattern. For every pulse in the pulse pattern, an event is picked from the specified event sequence. When the end of the sequence is reached, it starts again from the beginning.

To generate events dynamically, you can pass a function or a function iterator, instead of a static array or sequence of notes.

Events can also be generated via a tidal cycle mini-notation. Cycles are repeated endlessly by default, and have the duration of a single step in the patterns. Pulses can be used to sequence cycles too.

examples:

-- a sequence of c4, g4
event = {"c4", "g4"}
-- a chord of c4, d#4, g4
event = {{"c4", "d#4", "g4"}} -- or {"c4'min"}
-- a sequence of c4, g4 with volume 0.5
event = sequence{"c4", "g4"}:volume(0.5)
-- stateless generator function
event = function(context)
  return 48 + math.random(1, 4) * 5
end
-- stateful generator function
event = 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")
--[...]
event = pulse.from(tritone:chord(1, 4)):euclidean(6) +
  pulse.from(tritone:chord(5, 4)):euclidean(6)
-- a tidal cycle
event = cycle("<[a3 c4 e4 a4]*3 [d4 g3 g4 c4]>"),

Aliases

NoteValue

string | number | Note | NoteTable | nil

PulseValue

boolean | number | boolean | number | 0 | 1 | PulseValue | nil[] | 0 | 1 | nil

-- Single pulse value or a nested subdivision of pulses within a rhythm's pulse.
PulseValue:
    | 0
    | 1

PulseContext

Pulse timing context passed to functions in pulse and gate.


Properties

trigger : Note?

Note that triggered the pattern, if any. Usually will ne a monophic note. To access the raw note number value use: context.trigger.notes[1].key

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

Current parameter values: parameter ids are keys, parameter values are values. To access a parameter with id enabled use: context.parameter.enabled

beats_per_min : number

Project's tempo in beats per minutes.

beats_per_bar : integer

Project's beats per bar settings - usually will be 4.

samples_per_sec : integer

Project's audio playback sample rate in samples per second.

pulse_step : integer

Continues pulse counter, incrementing with each new skipped or emitted pulse. Unlike step in event this includes all pulses, so it also counts pulses which do not emit events. Starts from 1 when the pattern starts running or after it got 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

Pulse

Table with helper functions to ease creating rhythmic patterns.

examples:

-- using + and * operators to combine patterns
pulse.from{ 0, 1 } * 3 + { 1, 0 }
-- repeating, spreading and subsets
pulse.from{ 0, 1, { 1, 1 } }:repeat_n(4):spread(1.25):take(16)
-- euclidean patterns
pulse.euclidean(12, 16)
pulse.from{ 1, 0.5, 1, 1 }:euclidean(12)
-- generate/init from functions
pulse.new(8):init(1) --> 1,1,1,1,1,1,1,1
pulse.new(12):init(function() return math.random(0.5, 1.0) end )
pulse.new(16):init(scale("c", "minor").notes_iter())
-- generate pulses with note values
pulse.from{ "c4", "g4", "a4" } * 7 + { "a4", "g4", "c4" }
-- generate chords from degree values
pulse.from{ 1, 5, 6, 4 }:map(function(index, degree)
  return scale("c", "minor"):chord(degree)
end)

Functions

new(length : integer?, value : PulseTableValue | (index : integer) -> PulseTableValue?)

->Pulse

Create a new empty pulse table or a pulse table with the given length and value.

examples:

pulse.new(4) --> {0,0,0,0}
pulse.new(4, 1) --> {1,1,1,1}
pulse.new(4, function() return math.random() end)

from(...PulseTableValue | PulseTableValue[])

->Pulse

Create a new pulse table from an existing set of values or tables. When passing tables, those will be flattened.

examples:

pulse.from(1,0,1,0) --> {1,0,1,0}
pulse.from({1,0},{1,0}) --> {1,0,1,0}

copy(self : Pulse)

->Pulse

create a shallow-copy of the given pulse table (or self)

examples:

local p = pulse.from(1, 0)
local p2 = p:copy() --> {1,0}

distributed(steps : integer | table, length : integer, offset : integer?, empty_value : PulseTableValue?)

->Pulse

Create an new pulse table or spread and existing pulse evenly within the given length. Similar, but not exactly like euclidean.

Shortcut for pulse.from{1,1,1}:spread(length / #self):rotate(offset)

examples:

pulse.distributed(3, 8) --> {1,0,0,1,0,1,0}
pulse.from{1,1}:distributed(4, 1) --> {0,1,0,1}

euclidean(steps : integer | table, length : integer, offset : integer?, empty_value : PulseTableValue?)

->Pulse

Create a new euclidean rhythm pulse table with the given pulses or number of new pulses in the given length. Optionally rotate the contents too. Euclidean Rhythm

examples:

pulse.euclidean(3, 8)
 --> {1,0,0,1,0,0,1,0}
pulse.from{"x", "x", "x"}:euclidean(8, 0, "-")
 --> {"x","-","-","x","-","-","x","-"}

unpack(self : Pulse)

->... : PulseTableValue

Shortcut for table.unpack(pulse): returns elements from this pulse as var args.

examples:

local p = pulse.from{1,2,3,4}
local v1, v2, v3, v4 = p:unpack()

subrange(self : Pulse, i : integer, j : integer?, empty_value : PulseTableValue?)

->Pulse

Fetch a sub-range from the pulse table as new pulse table. When the given length is past end of this pulse it is filled up with empty values.

examples:

local p = pulse.from{1,2,3,4}
p = p:subrange(2,3) --> {2,3}
p = p:subrange(1,4,"X") --> {2,3,"X","X"}

take(self : Pulse, length : integer, empty_value : PulseTableValue?)

->Pulse

Get first n items from the pulse as new pulse table. When the given length is past end of this pulse its filled up with empty values.

examples:

local p = pulse.from{1,2,3,4}
p = p:take(2) --> {1,2}
p = p:take(4, "") --> {1,2,"",""}

clear(self : Pulse)

->Pulse

Clear a pulse table, remove all its contents.

examples:

local p = pulse.from{1,0}
p:clear() --> {}

init(self : Pulse, value : PulseTableValue | (index : integer) -> PulseTableValue, length : integer?)

->Pulse

Fill pulse table with the given value or generator function in the given length.

examples:

local p = pulse.from{0,0}
p:init(1) --> {1,1}
p:init("X", 3) --> {"X","X", "X"}
p:init(function(i) return math.random() end, 3)

map(self : Pulse, fun : (index : integer, value : PulseTableValue) -> PulseTableValue)

->Pulse

Apply the given function to every item in the pulse table.

examples:

local p = pulse.from{1,3,5}
p:map(function(k, v)
  return scale("c", "minor"):degree(v)
end) --> {48, 51, 55}

reverse(self : Pulse)

->Pulse

Invert the order of items in the pulse table.

examples:

local p = pulse.from{1,2,3}
p:reverse() --> {3,2,1}

rotate(self : Pulse, amount : integer)

->Pulse

Shift contents by the given amount to the left (negative amount) or right.

examples:

local p = pulse.from{1,0,0}
p:rotate(1) --> {0,1,0}
p:rotate(-2) --> {0,0,1}

push_back(self : Pulse, ...PulseTableValue[] | PulseTableValue)

->Pulse

Push a single or multiple items or other pulse contents to the end of the pulse. Note: When passing array alike tables or patterns, they will be unpacked.

examples:

local p = pulse.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 : Pulse)

->PulseTableValue

Remove an entry from the back of the pulse table. returns the removed item.

examples:

local p = pulse.from({1,2})
p:pop_back() --> {1}
p:pop_back() --> {}
p:pop_back() --> {}

repeat_n(self : Pulse, count : integer)

->Pulse

Repeat contents of the pulse table n times.

examples:

local p = pulse.from{1,2,3}
patterns:repeat_n(2) --> {1,2,3,1,2,3}

spread(self : Pulse, amount : number, empty_value : PulseTableValue?)

->Pulse

Expand (with amount > 1) or shrink (amount < 1) the length of the pulse table by the given factor, spreading allowed content evenly and filling gaps with 0 or the given empty value.

examples:

local p = pulse.from{1,1}
p:spread(2) --> {1,0,1,0}
p:spread(1/2) --> {1,1}

tostring(self : Pulse)

->string

Serialze a pulse table for display/debugging purposes.

examples:

pulse.euclidean(3, 8):tostring() --> "{1, 0, 0, 1, 0, 0, 1, 0}"

Aliases

PulseTableValue

boolean | string | number | table

Valid pulse value in a pulse table

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 -> {48, 50, 51, 53, 55, 56, 58}
-- 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" | "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"
    | "I"
    | "II"
    | "III"
    | "IV"
    | "V"
    | "VI"
    | "VII"

NoteValue

string | number | Note | 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"
    | "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 index value. 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"
    | "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 `pulse` library
local notes = pulse.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" | "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"
    | "I"
    | "II"
    | "III"
    | "IV"
    | "V"
    | "VI"
    | "VII"

NoteValue

string | number | Note | NoteTable | nil

sequence

Global


Functions

sequence(...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 event 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 | Note | NoteTable | nil

Sequence


Properties

notes : NoteTable[][]


Functions

transpose(self, step : integer | integer[])

->Sequence

Transpose all note's 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 note's 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 pattern {
  event = function(init_context)
    -- use a unique random sequence every time the pattern 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()

->table | tablelib

Create a new empty table that uses the global 'table.XXX' functions as methods, just like strings in Lua do. See also table.from.

examples:

t = table.new(); t:insert("a"); print(t[1]) -> "a";

from(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.from{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

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