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

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

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 Patterns

Control when patterns play using rhythm:

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

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

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

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 pattern. 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 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 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 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
  )
}