Cycles
In addition to static arrays of notes or dynamic generator functions, emitters in afseq can also emit cycles using the tidal cycles mini-notation.
Tidal Cycles allows you to make patterns with code using a custom functional approach. It includes a language for describing flexible (e.g. polyphonic, polyrhythmic, generative) sequences of sounds, notes, parameters, and all kind of information.
Usage
To create cycles in afseq, use the cycle
function in the emitter and pass it a mini-notation as string.
> emit = cycle("c4 d#4 <a5 g5> _")
note
Please see Tidal Cycles Mini-Notation Reference for a complete overview of the cycle notation.
Limitations
There's no exact specification for how tidal cycles work, and it's constantly evolving, but at the moment we support the mini notation as it works in Tidal, with the following limitations and changes:
-
Stacks and random choices are valid without brackets (
a | b
is parsed as[a | b]
) -
Operators currently only accept numbers on the right side (
a3*2
is valid,a3*<1 2>
is not) -
:
- Sets the instrument or remappable target instead of selecting samples
Timing
The base time of a pattern in tidal is specified as cycles per second. In afseq, the time of a cycle instead is given in cycles per pattern pulse units.
-- emits an entire cycle every bar
return rhythm {
unit = "bars",
emit = cycle("c d e f")
}
Sequencing
An emitter in afseq gets triggered for each incoming non-gated pattern pulse. This is true for cycles are well and allows you to sequence entire cycles too.
-- emit an entire cycle's every bar, then pause for a bar, then repeat
return rhythm {
unit = "bars",
pattern = {1, 0},
emit = cycle("c d e f")
}
You can also use the mini notation to emit single notes only, making use of tidal's note alternating and randomization features only:
-- emit a single note from a cycle in an euclidean pattern
return rhythm {
unit = "beats",
pattern = pattern.euclidean(5, 8),
emit = cycle("<c d e f g|b>")
}
Seeding
afseq's general random number generator is also used in cycles. So when you seed the global number generator, you can also seed the cycle's random operations with math.randomseed(12345)
.
Mapping
Notes and chords in cycles are expressed as note strings in afseq. But you can also dynamically evaluate and map cycle identifiers using the cycle map
function.
This allows you, for example, to inject input parameters into cycles or to use custom identifiers.
Using custom identifiers with a static map (a Lua table):
return rhythm {
unit = "bars",
emit = cycle("[bd*4], [_ sn]*2"):map({
["bd"] = note("c4 #0"),
["sn"] = note("g4 #1")
})
}
Using custom identifiers with a dynamic map function (a Lua function):
return rhythm {
unit = "bars",
emit = cycle("[bd*4], [_ sn]*2"):map(function(context, value)
if value == "bd" then
return note("c4 #0")
elseif value == "sn" then
return note("g4 #1")
end
end)
}
Examples
A simple polyrhythm
return rhythm {
unit = "1/1",
emit = cycle("[C3 D#4 F3 G#4], [[D#3?0.2 G4 F4]/64]*63")
}
Mapped multi channel beats
return rhythm {
unit = "1/1",
emit = cycle([[
[<h1 h2 h2>*12],
[kd ~]*2 ~ [~ kd] ~,
[~ s1]*2,
[~ s2]*8
]]):map({
kd = "c4 #0", -- Kick
s1 = "c4 #1", -- Snare
s2 = "c4 #1 v0.1", -- Ghost snare
h1 = "c4 #2", -- Hat
h2 = "c4 #2 v0.2", -- Hat
})
}
Dynamically mapped roman chord numbers with user defined scale
return rhythm {
unit = "1/1",
resolution = 4,
inputs = {
parameter.enum("mode", "major", {"major", "minor"})
},
emit = cycle("I V III VI"):map(
function(init_context, value)
local s = scale("c4", init_context.inputs.mode)
return function(context, value)
return value ~= "_" and s:chord(value) or value
end
end
)
}