kiki | | repl learn ref idioms birds oracle fish hymns sing

1. the song format

Sing runs a short kiki program called a song. A song assigns patterns to names, then returns a matrix where each row becomes a track.

The variable t is always available. It is the 16-step timeline 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15. You use it to build patterns.

The simplest song writes out 16 values by hand. Non-zero means sound, zero means silence:

The grid below the editor shows your pattern. Lit cells fire, dark cells rest. Press play to hear it. Press play again to stop.

The last line must return a matrix: [row1; row2; row3]. Each row is a track. The semicolons separate them.


2. rhythm with modulo

Writing 16 numbers by hand works, but math can be more expressive. The :! operator is modulo. t :! 4 gives the remainder when each step is divided by 4: 0 1 2 3 0 1 2 3 0 1 2 3 0 1 2 3.

(t :! 4) := 0 asks "where does that cycle equal zero?" The answer is steps 0, 4, 8, 12, or every 4 steps. This is how most patterns in sing are written built up.

Try changing 4 to 8 (kicks on every other step) or 3. Press play after each change to hear it.

To hit a single specific step, skip the modulo and use t := N directly. This fires only on step 14.


3. named helpers

Modulo patterns can express anything, but kiki ships helpers that say common things more directly. The most useful for rhythm are euclid, rotate, prob, and rand.

euclid k n distributes exactly k hits as evenly as possible across n steps using the Euclidean rhythm algorithm.

Try euclid 3 16 for a sparse feel, or euclid 7 16 for something denser and off-kilter.

rotate n is curried: rotate 4 returns a function that shifts any pattern left by 4 steps, wrapping around. This lets you take one euclid pattern and offset copies of it so the hits don't all land on the same steps:

Because rotate is curried, r is a reusable function. You can apply the same rotation to as many patterns as you like.

prob p n generates a probabilistic pattern of length n with exactly p * n hits placed randomly. rand n is shorthand for prob 0.5 n. Both are density-preserving, so the hit count is fixed, only the placement varies each time you evaluate.

Press Cmd+Return while the beat is playing to re-evaluate without stopping. The hat and ghost tracks will reshuffle each time.


4. stacking tracks

A snare at steps 4 and 12 (t :! 8) := 4 gives a cycle of 8, equal to 4 at those two positions.

Add a hi-hat on every other step with (t :! 2) := 0. Stack all three rows into a matrix:

Try changing the hat to (t :! 4) := 0 (hats every 4 steps) or t := 0 (one hit per loop).


5. voice names

The track name picks the synthesizer. Sing reads the name and maps it to a voice.

A name containing kick plays a kick drum. snare plays a snare. hat plays a hi-hat. bass or sub plays a bass synth. bell or pluck plays a bell. pad plays a soft pad. Anything else plays a plain sine synth.

The match is flexible though, so big_kick, kick2, and my_wicked_kick_track all trigger the kick voice. Try renaming a track and listening to the difference.


6. pitch

For pitched voices (bass, bell, pad, synth), the cell value sets pitch. Zero is always silence. Higher values produce higher notes.

For the bass voice, values in the range 18 to 30 give a recognizable low tone. Adding 12 moves an octave higher; adding 1 moves a half step.

(t :! 4) cycles through 0 1 2 3. Multiply by 5 first, then add 18, to map that to four pitches (18, 23, 28, 33) repeating every 4 steps. The outer parentheses matter because kiki evaluates right to left, so without them 5 :+ 18 is computed first, giving a different result.

Try changing the multiplier (the 5) or the starting value (the 18) to change the gap between notes or shift the whole thing up or down. A multiplier of 7 gives wider gaps.

Bell works the same way. Here the bell fires every third step, with the pitch rising as the step number rises.

(t :! 3) := 0 is a 0/1 mask that fires every third step. Multiplying by (t :+ 24) gives each hit a pitch based on which step it lands on.


7. combining patterns

Add two patterns with :+. This lets you combine two patterns into one. A kick every 4 steps plus a kick on the last step.

The same technique adds an extra hit at step 15 to a hat that otherwise only fires on even steps (0, 2, 4, ...)

For pitched voices, :+ also adds to the pitch value. To combine a pitch pattern with a rhythm so notes only fire where both are non-zero, use :* instead. The bell example in section 5 did this: ((t :! 3) := 0) is the on/off mask, multiplied by the pitch expression to silence the off steps.


8. tempo

Put bpm: 140 anywhere in your song to override the slider. This takes effect when you press play or eval.

You can also give bpm an array of values. Sing cycles through them one per step. Grouping values in pairs — two slow, then two fast — makes alternating notes land earlier or later, creating a shuffle. The grouping matters: the BPM at a step sets the gap to the next step, so a single slow-fast-slow-fast alternation cancels out across pairs of notes:

Small differences produce a gentle shuffle. Large differences produce a more dramatic stagger. Try 100 100 140 140 to hear the extreme version.

range 120 130 generates every integer from 120 up to (but not including) 130. Assigned to bpm, Sing cycles through the array one value per step. Keep the window narrow for a subtle drift; widen it for a more dramatic sweep.

Combine range with rand to land each step randomly at the floor or ceiling of a window. rand 16 gives a 16-step pattern of 0s and 1s. Name it first, then scale it, so rnd :* 40 maps each 0 to 0 and each 1 to 40, and then 120 :+ (rnd :* 40) puts each step at either 120 or 160 BPM. The naming step matters! Without it, kiki reads rand 16 :* 40 right-to-left as rand 640. Whoops!

Swap rand 16 for prob 0.25 16 to weight the balance toward slower steps. 12 at 120, 4 at 160. Re-evaluate with Cmd+Return while playing to reshuffle.


9. putting it together

Here we've got a five-track beat with kick, snare, hi-hat, bass, and bell.

Named lambdas are variables so you can define them in a song like anything else. Here fib is a recursive Fibonacci function. fib' t applies it to every step using the map adverb. The values grow really large pretty fast, so :! 12 folds them into one octave. The bell gets a pitch sequence that wanders with fib-character instead of rising linearly.

The workflow in sing is to press play, then keep editing the score and press Cmd+Return (or the eval button) to update the pattern without stopping.

Good things to try from here: change the bass multiplier (4) to change the gaps between notes; swap the bell offset (3 in (t :! 4) := 3) to shift its hit to a different position within each group; add a pad track with a slow-moving pitch expression.

The reference has the full operator list, and idioms shows common patterns for building sequences.