diff --git a/packages/core/pattern.mjs b/packages/core/pattern.mjs
index 4874a35f1..34a63c931 100644
--- a/packages/core/pattern.mjs
+++ b/packages/core/pattern.mjs
@@ -1023,6 +1023,7 @@ function _composeOp(a, b, func) {
div: [numeralArgs((a, b) => a / b)],
mod: [numeralArgs(_mod)],
pow: [numeralArgs(Math.pow)],
+ log2: [numeralArgs(Math.log2)],
band: [numeralArgs((a, b) => a & b)],
bor: [numeralArgs((a, b) => a | b)],
bxor: [numeralArgs((a, b) => a ^ b)],
@@ -3112,3 +3113,25 @@ export let xfade = (a, pos, b) => {
Pattern.prototype.xfade = function (pos, b) {
return xfade(this, pos, b);
};
+
+/**
+ * creates a structure pattern from divisions of a cycle
+ * especially useful for creating rhythms
+ * @name beat
+ * @example
+ * s("bd").beat("0:7:10", 16)
+ * @example
+ * s("sd").beat("4:12", 16)
+ */
+const __beat = (join) => (t, div, pat) => {
+ t = Fraction(t).mod(div);
+ div = Fraction(div);
+ const b = t.div(div);
+ const e = t.add(1).div(div);
+ return join(pat.fmap((x) => pure(x)._compress(b, e)));
+};
+
+export const { beat } = register(
+ ['beat'],
+ __beat((x) => x.innerJoin()),
+);
diff --git a/packages/core/signal.mjs b/packages/core/signal.mjs
index 36f07df54..51aac609c 100644
--- a/packages/core/signal.mjs
+++ b/packages/core/signal.mjs
@@ -5,7 +5,7 @@ This program is free software: you can redistribute it and/or modify it under th
*/
import { Hap } from './hap.mjs';
-import { Pattern, fastcat, reify, silence, stack, register } from './pattern.mjs';
+import { Pattern, fastcat, pure, register, reify, silence, stack } from './pattern.mjs';
import Fraction from './fraction.mjs';
import { id, keyAlias, getCurrentKeyboardState } from './util.mjs';
@@ -160,6 +160,37 @@ const timeToRands = (t, n) => timeToRandsPrime(timeToIntSeed(t), n);
*/
export const run = (n) => saw.range(0, n).floor().segment(n);
+/**
+ * Creates a pattern from a binary number.
+ *
+ * @name binary
+ * @param {number} n - input number to convert to binary
+ * @example
+ * "hh".s().struct(binary(5))
+ * // "hh".s().struct("1 0 1")
+ */
+export const binary = (n) => {
+ const nBits = reify(n).log2(0).floor().add(1);
+ return binaryN(n, nBits);
+};
+
+/**
+ * Creates a pattern from a binary number, padded to n bits long.
+ *
+ * @name binaryN
+ * @param {number} n - input number to convert to binary
+ * @param {number} nBits - pattern length, defaults to 16
+ * @example
+ * "hh".s().struct(binaryN(55532, 16))
+ * // "hh".s().struct("1 1 0 1 1 0 0 0 1 1 1 0 1 1 0 0")
+ */
+export const binaryN = (n, nBits = 16) => {
+ nBits = reify(nBits);
+ // Shift and mask, putting msb on the right-side
+ const bitPos = run(nBits).mul(-1).add(nBits.sub(1));
+ return reify(n).segment(nBits).brshift(bitPos).band(pure(1));
+};
+
export const randrun = (n) => {
return signal((t) => {
// Without adding 0.5, the first cycle is always 0,1,2,3,...
@@ -499,7 +530,7 @@ export const undegrade = register('undegrade', (pat) => pat._undegradeBy(0.5), t
export const sometimesBy = register('sometimesBy', function (patx, func, pat) {
return reify(patx)
- .fmap((x) => stack(pat._degradeBy(x), func(pat._undegradeBy(1 - x))))
+ .fmap((x) => stack(pat._degradeBy(x), func(pat)._undegradeBy(1 - x)))
.innerJoin();
});
diff --git a/packages/core/test/pattern.test.mjs b/packages/core/test/pattern.test.mjs
index 31ec48681..2d29ea5db 100644
--- a/packages/core/test/pattern.test.mjs
+++ b/packages/core/test/pattern.test.mjs
@@ -46,12 +46,14 @@ import {
rev,
time,
run,
+ binaryN,
pick,
stackLeft,
stackRight,
stackCentre,
s_cat,
calculateTactus,
+ sometimes,
} from '../index.mjs';
import { steady } from '../signal.mjs';
@@ -958,6 +960,18 @@ describe('Pattern', () => {
expect(run(4).firstCycle()).toStrictEqual(sequence(0, 1, 2, 3).firstCycle());
});
});
+ describe('binaryN', () => {
+ it('Can make a binary pattern from a decimal', () => {
+ expect(binaryN(55532).firstCycle()).toStrictEqual(
+ sequence(1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0).firstCycle(),
+ );
+ });
+ it('Can make a binary pattern from patterned inputs', () => {
+ expect(binaryN(pure(0x1337), pure(14)).firstCycle()).toStrictEqual(
+ sequence(0, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1).firstCycle(),
+ );
+ });
+ });
describe('ribbon', () => {
it('Can ribbon', () => {
expect(cat(0, 1, 2, 3, 4, 5, 6, 7).ribbon(2, 4).fast(4).firstCycle()).toStrictEqual(
@@ -1253,4 +1267,13 @@ describe('Pattern', () => {
expect(s('bev').chop(8).loopAt(2).tactus).toStrictEqual(Fraction(4));
});
});
+ describe('sometimes', () => {
+ it('works with constant functions', () => {
+ expect(
+ pure('a')
+ .sometimes((x) => pure('b'))
+ .fast(16).firstCycleValues.length,
+ ).toStrictEqual(16);
+ });
+ });
});
diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap
index 761c95fa7..c071b5365 100644
--- a/test/__snapshots__/examples.test.mjs.snap
+++ b/test/__snapshots__/examples.test.mjs.snap
@@ -954,6 +954,24 @@ exports[`runs examples > example "bank" example index 0 1`] = `
]
`;
+exports[`runs examples > example "beat" example index 0 1`] = `
+[
+ "[ 0/1 → 1/16 | s:bd ]",
+ "[ 1/1 → 17/16 | s:bd ]",
+ "[ 2/1 → 33/16 | s:bd ]",
+ "[ 3/1 → 49/16 | s:bd ]",
+]
+`;
+
+exports[`runs examples > example "beat" example index 1 1`] = `
+[
+ "[ 1/48 → 1/12 | s:sd ]",
+ "[ 49/48 → 13/12 | s:sd ]",
+ "[ 97/48 → 25/12 | s:sd ]",
+ "[ 145/48 → 37/12 | s:sd ]",
+]
+`;
+
exports[`runs examples > example "begin" example index 0 1`] = `
[
"[ 0/1 → 1/2 | s:rave begin:0 ]",
@@ -967,6 +985,60 @@ exports[`runs examples > example "begin" example index 0 1`] = `
]
`;
+exports[`runs examples > example "binary" example index 0 1`] = `
+[
+ "[ 0/1 → 1/3 | s:hh ]",
+ "[ 2/3 → 1/1 | s:hh ]",
+ "[ 1/1 → 4/3 | s:hh ]",
+ "[ 5/3 → 2/1 | s:hh ]",
+ "[ 2/1 → 7/3 | s:hh ]",
+ "[ 8/3 → 3/1 | s:hh ]",
+ "[ 3/1 → 10/3 | s:hh ]",
+ "[ 11/3 → 4/1 | s:hh ]",
+]
+`;
+
+exports[`runs examples > example "binaryN" example index 0 1`] = `
+[
+ "[ 0/1 → 1/16 | s:hh ]",
+ "[ 1/16 → 1/8 | s:hh ]",
+ "[ 3/16 → 1/4 | s:hh ]",
+ "[ 1/4 → 5/16 | s:hh ]",
+ "[ 1/2 → 9/16 | s:hh ]",
+ "[ 9/16 → 5/8 | s:hh ]",
+ "[ 5/8 → 11/16 | s:hh ]",
+ "[ 3/4 → 13/16 | s:hh ]",
+ "[ 13/16 → 7/8 | s:hh ]",
+ "[ 1/1 → 17/16 | s:hh ]",
+ "[ 17/16 → 9/8 | s:hh ]",
+ "[ 19/16 → 5/4 | s:hh ]",
+ "[ 5/4 → 21/16 | s:hh ]",
+ "[ 3/2 → 25/16 | s:hh ]",
+ "[ 25/16 → 13/8 | s:hh ]",
+ "[ 13/8 → 27/16 | s:hh ]",
+ "[ 7/4 → 29/16 | s:hh ]",
+ "[ 29/16 → 15/8 | s:hh ]",
+ "[ 2/1 → 33/16 | s:hh ]",
+ "[ 33/16 → 17/8 | s:hh ]",
+ "[ 35/16 → 9/4 | s:hh ]",
+ "[ 9/4 → 37/16 | s:hh ]",
+ "[ 5/2 → 41/16 | s:hh ]",
+ "[ 41/16 → 21/8 | s:hh ]",
+ "[ 21/8 → 43/16 | s:hh ]",
+ "[ 11/4 → 45/16 | s:hh ]",
+ "[ 45/16 → 23/8 | s:hh ]",
+ "[ 3/1 → 49/16 | s:hh ]",
+ "[ 49/16 → 25/8 | s:hh ]",
+ "[ 51/16 → 13/4 | s:hh ]",
+ "[ 13/4 → 53/16 | s:hh ]",
+ "[ 7/2 → 57/16 | s:hh ]",
+ "[ 57/16 → 29/8 | s:hh ]",
+ "[ 29/8 → 59/16 | s:hh ]",
+ "[ 15/4 → 61/16 | s:hh ]",
+ "[ 61/16 → 31/8 | s:hh ]",
+]
+`;
+
exports[`runs examples > example "bite" example index 0 1`] = `
[
"[ 0/1 → 1/8 | note:Bb3 ]",
diff --git a/test/__snapshots__/tunes.test.mjs.snap b/test/__snapshots__/tunes.test.mjs.snap
index f851077a8..840f9f329 100644
--- a/test/__snapshots__/tunes.test.mjs.snap
+++ b/test/__snapshots__/tunes.test.mjs.snap
@@ -8,6 +8,7 @@ exports[`renders tunes > tune: amensister 1`] = `
"[ 0/1 → 1/4 | n:0 s:amencutup room:0.5 ]",
"[ 1/16 → 1/8 | s:breath room:1 shape:0.6 begin:0.875 end:0.9375 ]",
"[ 1/8 → 3/16 | s:breath room:1 shape:0.6 begin:0.8125 end:0.875 ]",
+ "[ 1/8 → 1/4 | n:0 s:amencutup room:0.5 ]",
"[ 1/8 → 1/4 | note:45 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:300.174310575404 ]",
"[ 1/8 → 1/4 | note:45 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:300.174310575404 ]",
"[ 3/16 → 1/4 | s:breath room:1 shape:0.6 begin:0.75 end:0.8125 ]",
@@ -17,22 +18,20 @@ exports[`renders tunes > tune: amensister 1`] = `
"[ 1/4 → 3/8 | note:A1 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:300.7878869297153 ]",
"[ 5/16 → 3/8 | s:breath room:1 shape:0.6 begin:0.625 end:0.6875 ]",
"[ 3/8 → 7/16 | s:breath room:1 shape:0.6 begin:0.5625 end:0.625 ]",
- "[ 3/8 → 1/2 | n:1 s:amencutup room:0.5 ]",
"[ 3/8 → 1/2 | note:F1 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:302.11020572391345 ]",
"[ 3/8 → 1/2 | note:F1 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:302.11020572391345 ]",
"[ 7/16 → 1/2 | s:breath room:1 shape:0.6 begin:0.5 end:0.5625 ]",
"[ 1/2 → 9/16 | s:breath room:1 shape:0.6 begin:0.4375 end:0.5 ]",
+ "[ 1/2 → 5/8 | n:2 s:amencutup room:0.5 ]",
"[ 1/2 → 3/4 | n:2 s:amencutup room:0.5 ]",
"[ 9/16 → 5/8 | s:breath room:1 shape:0.6 begin:0.375 end:0.4375 ]",
"[ 5/8 → 11/16 | s:breath room:1 shape:0.6 begin:0.3125 end:0.375 ]",
"[ 11/16 → 3/4 | s:breath room:1 shape:0.6 begin:0.25 end:0.3125 ]",
"[ 3/4 → 13/16 | s:breath room:1 shape:0.6 begin:0.1875 end:0.25 ]",
- "[ 3/4 → 7/8 | n:3 s:amencutup room:0.5 ]",
"[ 3/4 → 7/8 | note:Bb0 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:312.54769231985796 ]",
"[ 3/4 → 7/8 | note:Bb0 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:312.54769231985796 ]",
"[ 13/16 → 7/8 | s:breath room:1 shape:0.6 begin:0.125 end:0.1875 ]",
"[ 7/8 → 15/16 | s:breath room:1 shape:0.6 begin:0.0625 end:0.125 ]",
- "[ 7/8 → 1/1 | n:3 s:amencutup room:0.5 ]",
"[ 7/8 → 1/1 | note:D1 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:318.7927796831686 ]",
"[ 7/8 → 1/1 | note:D1 s:sawtooth gain:0.4 decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 resonance:10 cutoff:318.7927796831686 ]",
"[ 15/16 → 1/1 | s:breath room:1 shape:0.6 begin:0 end:0.0625 ]",
@@ -6906,10 +6905,10 @@ exports[`renders tunes > tune: flatrave 1`] = `
"[ 1/8 → 1/4 | s:hh n:1 speed:0.5 delay:0.5 end:0.020001936784171157 bank:RolandTR909 room:0.5 gain:0.4 ]",
"[ 1/8 → 1/4 | s:hh n:1 speed:0.5 delay:0.5 end:0.020001936784171157 bank:RolandTR909 room:0.5 gain:0.4 ]",
"[ 1/8 → 1/4 | note:G1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 cutoff:800 resonance:8 ]",
- "[ 1/4 → 3/8 | s:hh n:1 end:0.02000875429921906 bank:RolandTR909 room:0.5 gain:0.4 ]",
- "[ 1/4 → 3/8 | s:hh n:1 end:0.02000875429921906 bank:RolandTR909 room:0.5 gain:0.4 ]",
"[ 1/4 → 3/8 | note:G1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 cutoff:800 resonance:8 ]",
"[ 3/8 → 1/2 | s:hh n:1 end:0.020023446730265706 bank:RolandTR909 room:0.5 gain:0.4 ]",
+ "[ 1/2 → 5/8 | s:hh n:1 speed:0.5 delay:0.5 end:0.020048626493108724 bank:RolandTR909 room:0.5 gain:0.4 ]",
+ "[ 1/2 → 5/8 | s:hh n:1 speed:0.5 delay:0.5 end:0.020048626493108724 bank:RolandTR909 room:0.5 gain:0.4 ]",
"[ 1/2 → 5/8 | note:G1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 cutoff:800 resonance:8 ]",
"[ 1/2 → 1/1 | s:bd bank:RolandTR909 ]",
"[ 1/2 → 1/1 | s:cp bank:RolandTR909 ]",
@@ -6917,7 +6916,6 @@ exports[`renders tunes > tune: flatrave 1`] = `
"[ 5/8 → 3/4 | s:hh n:1 end:0.020086608138500644 bank:RolandTR909 room:0.5 gain:0.4 ]",
"[ 5/8 → 3/4 | s:hh n:1 end:0.020086608138500644 bank:RolandTR909 room:0.5 gain:0.4 ]",
"[ 5/8 → 3/4 | note:G1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 cutoff:800 resonance:8 ]",
- "[ 3/4 → 7/8 | s:hh n:1 end:0.02013941880355398 bank:RolandTR909 room:0.5 gain:0.4 ]",
"[ 7/8 → 1/1 | note:G1 s:sawtooth decay:0.1 sustain:0 lpattack:0.1 lpenv:-4 cutoff:800 resonance:8 ]",
]
`;
diff --git a/website/src/config.ts b/website/src/config.ts
index dd003c185..a404f542a 100644
--- a/website/src/config.ts
+++ b/website/src/config.ts
@@ -100,6 +100,7 @@ export const SIDEBAR: Sidebar = {
{ text: 'Coding syntax', link: 'learn/code' },
{ text: 'Pitch', link: 'understand/pitch' },
{ text: 'Cycles', link: 'understand/cycles' },
+ { text: 'Voicings', link: 'understand/voicings' },
{ text: 'Pattern Alignment', link: 'technical-manual/alignment' },
{ text: 'Strudel vs Tidal', link: 'learn/strudel-vs-tidal' },
],
diff --git a/website/src/pages/learn/factories.mdx b/website/src/pages/learn/factories.mdx
index 6a2e165b2..b43d3abfa 100644
--- a/website/src/pages/learn/factories.mdx
+++ b/website/src/pages/learn/factories.mdx
@@ -55,6 +55,14 @@ These are the equivalents used by the Mini Notation:
## run
-
+
+
+## binary
+
+
+
+## binaryN
+
+
After Pattern Constructors, let's see what [Time Modifiers](/learn/time-modifiers) are available.
diff --git a/website/src/pages/understand/voicings.mdx b/website/src/pages/understand/voicings.mdx
new file mode 100644
index 000000000..7d9c13d90
--- /dev/null
+++ b/website/src/pages/understand/voicings.mdx
@@ -0,0 +1,325 @@
+---
+title: Understanding Chord Voicings
+layout: ../../layouts/MainLayout.astro
+---
+
+import { MiniRepl } from '../../docs/MiniRepl';
+import { PitchSlider } from '../../components/PitchSlider';
+import Box from '@components/Box.astro';
+
+# Understanding Chords and Voicings
+
+Let's dig deeper into how chords and voicings work in strudel.
+I'll try to keep theory jargon to a minimum, so hopefully this is approachable for anyone interested.
+
+## What is a chord
+
+Playing more than one note at a time is generally called a `chord`. Here's an example:
+
+").room(.5)`} />
+
+Here's the same with midi numbers:
+
+").room(.5)`} />
+
+Here, we have two 3-note chords played in a loop.
+You could already stop here and write chords in this style, which is totally fine and gives you control over individual notes.
+One downside is that it can be difficult to find good sounding chords and maybe you're yearning for a way to organize chords in some other way.
+
+## Labeling Chords
+
+Chords are typically given different labels depending on the relationship of the notes within.
+In the number example above, we have `48,51,55` and `53,57,60`.
+
+To analyze the relationship of those notes, they are typically compared to some `root`, which is often the lowest note.
+In our case, the `roots` would be `48` (= `c3`) and `53` (= `f3`).
+We can express the same chords relative to those `roots` like this:
+
+".add("<48 53>")).room(.5)`} />
+
+Now within each chord, each number represents the distance from the root.
+A distance between pitches is typically called `interval`, but let's stick to distance for now.
+
+Now we can see that our 2 chords are actually quite similar, as the only difference is the middle note (and the root of course).
+They are part of a group of chords called `triads` which are chords with 3 notes.
+
+### Triads
+
+These 4 shapes are the most common types of `triads` you will encounter:
+
+| shape | label |
+| ----- | ---------- |
+| 0,4,7 | major |
+| 0,3,7 | minor |
+| 0,3,6 | diminished |
+| 0,4,8 | augmented |
+
+Here they are in succession:
+
+".add("60"))
+.room(.5)._pitchwheel()`}
+/>
+
+Many types of music often only use minor and major chords, so we already have the knowledge to accompany songs. Here's one:
+
+\`.add(\`<
+a c d f
+a e a e
+>\`)).room(.5)`}
+/>
+
+These are the chords for "The House of the Rising Sun" by The Animals.
+So far, it doesn't sound too exciting, but at least it's recognizable.
+
+## Voicings
+
+A `voicing` is one of many ways a certain chord shape can be arranged.
+The term comes from choral music, where chords can be sung in different ways by assigning different notes to each voice.
+For example we could add 12 semitones to one or more notes in the chord:
+
+".add("48"))
+.room(.5)`}
+/>
+
+Notes that are 12 semitone steps apart (= 1 `octave`) are considered to be equal in a harmonic sense, which is why they get the same note letter.
+Here's the same example with note letters:
+
+")
+.room(.5)`}
+/>
+
+These types of voicings are also called `inversions`. There are many other ways we could `voice` this minor chord:
+
+".add("48"))
+.room(.5)`}
+/>
+
+Here we are changing the flavour of the chord slightly by
+
+1. doubling notes 12 steps higher,
+2. using very wide distances
+3. omitting notes
+
+## Voice Leading
+
+When we want to meaningfully connect chords in a sequence, the chosen voicings affect the way each chord transitions to the next.
+Let's revisit "The House of the Rising Sun", this time using our newly acquired voicing techniques:
+
+\`.add(\`<
+a c d f
+a e a e
+>\`)).room(.5)`}
+ punchcard
+/>
+
+These voicings make the chords sound more connected and less jumpy, compared to the earlier version, which didn't focus on voicing.
+The way chords interact is also called `voice leading`, reminiscent of how an
+individual choir voice would move through a sequence of chords.
+
+For example, try singing the top voice in the above example. Then try the same
+on the example not focusing on voice leading. Which one's easier?
+
+Naturally, there are many ways a progression of chords could be voiced and there is no definitive right or wrong.
+
+## Chord Symbols
+
+Musicians playing chord-based music often use a `lead sheet`, which is a simplified notation for a piece of music.
+These sheets condense the essential elements, such as chords, into symbols that make the music easy to read and follow.
+For example, a lead sheet for "The House of the Rising Sun" might include chords written like this:
+
+```
+Am | C | D | F
+Am | E | Am | E
+```
+
+Here, each symbol consists of the `root` of the chord and optionally an `m` to signal it's a minor chord (just the root note means it's major).
+We could mirror that notation in strudel using the `pick` function:
+
+"
+ .pick({
+ Am: "57,60,64",
+ C: "55,60,64",
+ D: "50,57,66",
+ F: "57,60,65",
+ E: "56,59,64",
+ })
+ .note().room(.5)`}
+ punchcard
+/>
+
+## The voicing function
+
+Coming up with good sounding voicings that connect well can be a difficult and time consuming process.
+The `chord` and `voicing` functions can be used to automate that:
+
+").voicing().room(.5)`} punchcard />
+
+Here we're also using chord symbols but the voicings will be automatically generated with smooth `voice leading`, minimizing jumps.
+It is inspired by the way a piano or guitar player would pick chords to accompany a song.
+
+## Voicing Dictionaries
+
+The voicing function internally uses so called `voicing dictionaries`, which can also be customized:
+
+")
+ .dict('house').anchor(66)
+ .voicing().room(.5)`}
+ punchcard
+/>
+
+In a `voicing dictionary`, each chord symbol is assigned one or more voicings.
+The `voicing` function then picks the voicing that is closest to the `anchor` (defaults to `c5`).
+
+The handy thing about this approach is that a `voicing dictionary` can be used to play any chord progression with automated voice leading!
+
+## The default dictionary
+
+When using the default dictionary, you can use these chord symbols:
+
+```
+2 5 6 7 9 11 13 69 add9
+o h sus ^ - ^7 -7 7sus
+h7 o7 ^9 ^13 ^7#11 ^9#11
+^7#5 -6 -69 -^7 -^9 -9
+-add9 -11 -7b5 h9 -b6 -#5
+7b9 7#9 7#11 7b5 7#5 9#11
+9b5 9#5 7b13 7#9#5 7#9b5
+7#9#11 7b9#11 7b9b5 7b9#5
+7b9#9 7b9b13 7alt 13#11
+13b9 13#9 7b9sus 7susadd3
+9sus 13sus 7b13sus
+aug M m M7 m7 M9 M13
+M7#11 M9#11 M7#5 m6 m69
+m^7 -M7 m^9 -M9 m9 madd9
+m11 m7b5 mb6 m#5 mM7 mM9
+```
+
+The available chords and the format is very much inspired by [ireal pro chords](https://technimo.helpshift.com/hc/en/3-ireal-pro/faq/88-chord-symbols-used-in-ireal-pro/).
+Some symbols are synonymous:
+
+- "-" is the same as "m", for example C-7 = Cm7
+- "^" is the same as "M", for example C^7 = CM7
+- "+" is the same as "aug"
+
+You can decide which ones you prefer. There is no international standard for these symbols.
+To get a full chord, the symbols have to be prefixed with a root pitch, e.g. D7#11 is the 7#11 chord relative to the pitch D.
+
+Here are all possible chords with root C:
+
+\`).voicing().room(.5)`}
+ punchcard
+/>
+
+Note that the default dictionary contains multiple ways (= `voicings`) to play each chord symbol.
+By default, the `voicing` function tries to minimize jumps.
+You can alter the picked voicings in various ways, which are now explained in further detail:
+
+## anchor
+
+The `anchor` is a note that is used to align the voicings to:
+
+").chord("C").voicing().room(.5)`} punchcard />
+
+By default, the anchor is the highest possible note the voicing can contain.
+When deciding which voicing of the dictionary to pick for a certain chord, the voicing with a top note closest to the anchor wins.
+
+Note that the anchors in the above example match up with the top notes in the pianoroll.
+Like `note`, anchor accepts either midi numbers or note names.
+
+## mode
+
+With `mode`, you can change the way the voicing relates to the `anchor`:
+
+").chord("C").anchor("c5").voicing().room(.5)`}
+ punchcard
+/>
+
+The modes are:
+
+- `below`: the top note of the voicing is lower than or equal to the anchor (default)
+- `above`: the bottom note of the voicing is higher than or equal to the anchor
+- `duck`: the top note of the voicing is lower than the anchor
+- `root`: the bottom note of the voicing is always the root note closest to the anchor
+
+The `anchor` can also be set from within the `mode` function:
+
+:c5").chord("C").voicing().room(.5)`} punchcard />
+
+## n
+
+The `n` control can be used with `voicing` to select individual notes:
+
+>").voicing()
+.clip("4 3 2 1").room(.5)`}
+ punchcard
+/>
+
+## Example
+
+Here's an example of a Jazz Blues in F:
+
+\`)
+$: n("7 8 [10 9] 8").set(chords).voicing().dec(.2)
+$: chords.struct("- x - x").voicing().room(.5)
+$: n("0 - 1 -").set(chords).mode("root:g2").voicing()
+`}
+ punchcard
+/>
+
+The chords are reused for melody, chords and bassline of the tune.