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

Quickstart

This guide will take you from basic patterns to advanced rhythmic sequencing through practical examples. Each example builds on previous concepts and includes comments to help you understand how things work.

note

afseq uses Lua as a scripting language. If you're not familiar with Lua, don't worry. Lua is very easy to pick up and fortunately there are great tutorials out there, such as this one.

Table of Contents

Basic Patterns

Quarter Note Pulse

-- The most basic rhythm: steady quarter notes
return rhythm {
  unit = "1/4", -- Quarter note timing grid
  emit = "c4"   -- Play middle C on each pulse
}
  • TRY THIS: Change unit to "1/8" for eighth notes
  • TRY THIS: Replace "c4" with "e4" or "g4" for different notes

This creates a steady pulse playing C4 on every quarter note. The unit parameter sets the timing grid, while emit defines what note to play.

Alternating Notes

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

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

Subdivided Pulses

-- Pattern with mixed note lengths
return rhythm {
  unit = "1/4",
  pattern = {1, {1, 1, 1, 1}},    -- One quarter note, then four sixteenth notes
  emit = {"c4", "c5", "d4", "e4", "g4"} -- C4 (quarter), C5, d4, e4, g4 (sixteenth)
}
  • TRY THIS: Try more complex subdivisions like {{1, 1}, {1, {1, 1}}}
  • TRY THIS: Change the unit to "1/8" to make everything faster

Nested arrays in the pattern create subdivisions, allowing for more complex rhythms within the basic unit.

Euclidean Rhythms

-- Distributes notes evenly across steps (common in many music traditions)
return rhythm {
  unit = "1/16",
  pattern = pattern.euclidean(3, 8),  -- 3 hits spread over 8 steps
  emit = "c4" -- Basic note
}
  • TRY THIS: Try different combinations like (5, 8) or (7, 16)
  • TRY THIS: Use pattern = pattern.euclidean(3, 8) + pattern.euclidean(5, 8) to chain different patterns

Euclidean patterns distribute a number of notes evenly across steps, creating naturally pleasing rhythmic patterns found in many musical traditions.

The Pattern API contains various tools to programatically create patterns.

Triplet Resolution

-- Create swing or triplet feel
return rhythm {
  unit = "1/8",
  resolution = 2/3, -- Triplet feel (3 notes in space of 2)
  emit = {"c4 v0.3", "e4 v0.5", "g4 v0.8"} -- v specifies volume, d delay, p panning
}
  • TRY THIS: Change resolution to "5/4" for a different swing feel
  • TRY THIS: Add values such as d0.2 between 0 and 1 to delay specific notes

The resolution parameter modifies the timing grid, enabling triplet feels, swing rhythms, and polyrhythms.

Notes and Scales

Basic Note Stacks

-- Simple chord by stacking notes
return rhythm {
  unit = "1/1",
  emit = {{"c4", "e4", "g4"}, "c4"}  -- C major chord followed by a single C4
}
  • TRY THIS: Try different chord combinations like {"d4", "f4", "a4"} for D minor
  • TRY THIS: Add v values to create dynamics: {"c4 v0.8", "e4 v0.6", "g4 v0.4"}

A table of notes allows emitting multiple notes at once, creating chords and harmonies.

Chord Notation

-- Using chord notation shortcuts
return rhythm {
  unit = "1/1",
  emit = {
    "c4'M", -- C major using ' chord notation
    "d4'm", -- D minor
    "g4'7"  -- G dominant 7th
  }
}
  • TRY THIS: Use other chord modes like 'm5, '+, or 'dim
  • TRY THIS: Add inversions with note("c4'M"):transpose({12, 0, 0})

Chord notation provides a quick way to specify common chord types without listing individual notes.

Working with Scales

-- Advanced chord and scale operations
return rhythm {
  unit = "1/1",
  emit = {
    chord("c4", "major"),         -- C major via the chord function
    chord("c4", {0, 4, 7}),       -- C major via custom intervals
    scale("c", "major"):chord(1), -- C major from 1st degree of C major scale
    scale("c", "major"):chord(5)  -- G major from 5th degree of C major scale
  }
}
  • TRY THIS: Use other scales like "minor", "dorian", or "pentatonic"
  • TRY THIS: Try different chord degrees: scale("c", "major"):chord(2) for D minor

The scale() function allows creating chords from scale degrees, enabling more musical chord progressions.

See available modes and scales at the API docs. See Notes and Scales for more ways to create and manipulate notes and chords.

Tidal Cycles Mini-Notation

Basic Cycle

-- Using tidal cycles notation for concise patterns
return rhythm {
  unit = "1/4", -- Emit a cycle every beat
  emit = cycle("c4 e4 g4") -- C major arpeggio
}
-- The simplified notation emits a cycle per bar
return cycle("c4 e4 g4") 

Tidal Cycles' mini-notation provides a concise way to express patterns.

Alternating Cycles

-- Switching between different patterns
return rhythm {
  unit = "1/4",
  emit = cycle("[c4 e4 g4]|[d4 f4 a4]") -- Randomly select one of two chords
}
  • TRY THIS: Add more patterns with | like [c4|c5 e4 g4]|[d4 f4|g5 a4]|[e4 g4 b4]
  • TRY THIS: Try simultaneous notes with square brackets [c4 e4]

The | operator in cycles randomly selects different patterns.

Euclidean Rhythms in Cycles

-- Euclidean patterns in tidal cycles notation
return cycle("c4(3,8) e4(5,8) g4(7,8)")  -- Different Euclidean rhythms
  • TRY THIS: Combine with alternation: c4(3,8)|e4(5,8)
  • TRY THIS: Change the numbers for different distributions

Tidal Cycles mini-notation also supports Euclidean patterns with the (n,k) notation, where n is the number of notes and k is the number of steps.

See Cycles Guide for more example and info about Tidal Cycles in afseq.

Dynamic Patterns & Emitters

Random Note Selection

-- Randomly select notes from a list
local notes = {"c4", "d4", "e4", "g4"}
return rhythm {
  unit = "1/8",
  emit = function(context)
    return notes[math.random(#notes)] -- Pick random note from array
  end
}
  • TRY THIS: Use notes from a specific scale with local notes = scale("c4", "major").notes
  • TRY THIS: Add amplitude variation with note(some_note):amplify(0.5 + math.random() * 0.5)

Using functions for emitters enables dynamic behavior, like randomly selecting notes from a predefined set.

Probability-Based Emitting

-- Emit notes with certain probability
return rhythm {
  unit = "1/8",
  pattern = {1, 1, 1, 1},  -- Regular pattern
  emit = function(context)
    if math.random() < 0.3 then  -- 30% chance to emit
      return "c4"
    end
  end
}
  • TRY THIS: Vary probability by step position: if math.random() < (context.step % 4) / 4 then
  • TRY THIS: Higher probability on downbeats: if math.random() < ((context.pulse_step - 1) % 2 == 0 and 0.8 or 0.2) then

This pattern uses a gate function to filter notes with a 30% probability, creating a sparse, probabilistic pattern. Gates are evaluated after patterns but before emission, giving you control over which triggered notes actually play.

Stateful Arpeggiator

-- Create patterns that remember previous states
return rhythm {
  unit = "1/8",
  emit = function(init_context)
    local notes = {"c4", "e4", "g4", "b4"}
    local index = 1
    return function(context)
      local note = notes[index]
      index = math.imod(index + 1, #notes) -- Cycle through notes
      return note
    end
  end
}
  • TRY THIS: Add direction changes: if index >= #notes or index <= 1 then direction = direction * -1 end
  • TRY THIS: Change notes based on time: notes = scale("C4", {"major","minor"}[math.floor(context.time) % 2 + 1]).notes

This example demonstrates stateful emitters that remember their position between calls, enabling sequences and other time-dependent behaviors.

Advanced Techniques

Conditional Gate

-- Filter which notes actually play using gates
return rhythm {
  unit = "1/8",
  pattern = {1, 0.1, 1, 0.5, 1, 0.2, 1, 0.1}, -- probability values
  gate = function(context)
    -- always play on even-numbered step values
    return (context.pulse_step - 1) % 2 == 0 or
      -- else use pulse values as probablities
      context.pulse_value >= math.random() 
  end,
  emit = "c4"
}
  • TRY THIS: Create a threshold gate: context.pulse_value > 0.5
  • TRY THIS: Only play when a specific MIDI note is held: context.trigger.notes[1].key == "C4"

Gates filter which triggered notes actually play, adding another layer of control to your patterns.

Dynamic Cycles

-- Identifiers in cycles can be dynamically mapped to something else
local s = scale("C4", "minor")
return rhythm {
  unit = "1/4",
  emit = cycle("I III V VII"):map(function(context, value)
    -- value here is a single roman number from the cycle above
    local degree = value
    -- apply value as roman number chord degree
    return s:chord(degree)
  end)
}
  • TRY THIS: Change scale to "major", "dorian", or "pentatonic minor"
  • TRY THIS: Add parameters: parameter.enum("scale", "minor", {"major", "minor", "pentatonic"})

This example uses musical scale knowledge to generate chord progressions based on scale degrees.

See Parameters on how to add template parameters to rhythms.

Generative Melody with Constraints

-- Create melodies that follow musical rules
return rhythm {
  unit = "1/8",
  emit = function(init_context)
    local pentatonic = scale("c4", "pentatonic minor").notes
    local last_note = 1
    return function(context)
      local next_note = math.random(#pentatonic)
      -- Prefer steps of 1 or 2 scale degrees (smoother melodies)
      while math.abs(next_note - last_note) > 2 do
        next_note = math.random(#pentatonic)
      end
      last_note = next_note
      return pentatonic[next_note]
    end
  end
}
  • TRY THIS: Add occasional jumps: if math.random() < 0.1 then ... (allow larger intervals)
  • TRY THIS: Change directions based on contour: add direction variable that occasionally flips

This generates melodies that follow musical constraints, like preferring small intervals for more natural-sounding melodies.

Further Resources

Remember to experiment by modifying these examples! The best way to learn is by tweaking parameters and seeing what happens.