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
- Rhythm Variations
- Notes and Scales
- Cycles Mini-Notation
- Dynamic Pulses & Events
- Advanced Techniques
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.