← All modules
AvailableInstrument

Pretty Chord

136 chord banks with random selection.

Engine preview

Browser version

About

Pretty Chord makes no sound of its own. Like the arpeggiator it is a note source: it works out what seven chords you have loaded, then plays them into whatever instrument is mounted. What you hear on this page is the preview engine's own synth voice being handed chords.

The chords come from the pretty-chords dataset, 136 banks, each a real progression with a key, a mode and explicit MIDI voicings. That last part is why the banks sound different from one another rather than like 136 lists of triads: a bank stores where each note actually sits, so one keeps its ninth on top and another spreads across two octaves, and loading a bank loads that shape along with the harmony.

The two variation controls are deliberately separate. One rerolls WHAT the chords are while matching an exact integer version of the selected twelve-bin target across the whole bank. The other rerolls HOW THEY SIT, shuffling octaves while leaving the harmony untouched. You can hear a target-matched progression in the bank's shape, or the bank's harmony in a new shape, and either can be undone without disturbing the other.

Undo is a number. Every musical thing on this page is derived from parameters, a bank id, two seeds, a style index, so setting the seeds back to zero returns the bank's arrays exactly as the dataset stores them, in the dataset's own order. There is no history to lose and nothing to save: the same numbers give the same chords in the browser, in an offline render and in the test suite, which is also what would let this module run headless.

This is a preview module. The scheduling is real, every note is committed to the AudioContext clock as an absolute time, so timer jitter can delay the decision but never move the note, but the voice it drives is the website's stand-in engine rather than a compiled kernel, and no parity claim applies.

Signal graph.

Experimental

Migration fallback: approximated from parameter groups, not a runtime wiring diagram.

How it works.

The chord set is a pure function of five numbers. Given a bank id, a note seed, a voicing seed, a style index and a voicing style, one function returns the seven chords, no clock, no audio, no stored state. That is what makes every claim on this page checkable in the test suite rather than by ear, and it is why the revert guarantee is asserted against the live dataset: with both seeds at zero the function must hand back the bank's arrays element for element, so a well-meaning tidying sort anywhere in the pipeline fails the test rather than quietly redefining what revert means.

Randomizing the notes keeps everything about a chord except its harmony. The size is preserved, the bass is redrawn only from the pitch classes reachable within a whole tone of where it already was, and each new pitch class is placed in the octave nearest the tone it replaces, so a wide chord stays wide and a close one stays close. The upper voices are drawn without replacement, which is what stops a peaked profile like blues from returning the same tone three times and collapsing the chord to a unison.

Randomizing the voicing keeps the harmony and moves the octaves, and it is drift-free by construction. The bass is pinned to the chord's original lowest note and the ceiling is a fixed distance above that pin rather than a function of the current spread, so mashing the control forever reshuffles the upper voices without walking the chord up the keyboard or fanning it wider each time. Every pitch class still appears exactly once.

The scheduler is the repo's standard lookahead loop, the same one the chord player and the arpeggiator use. A timer wakes every 25 ms, asks the pure core for every note whose onset falls in the next 120 ms, and commits each one to the engine with an absolute AudioContext time. Because the time is computed rather than taken from the timer, a stalled main thread can delay the decision but cannot move the note. The tempo is read through a callback at that moment, which is why a tempo change lands on the next chord instead of being frozen at whatever it was when you pressed play.

The sequence is a timeline rather than a grid. Each step carries its own length, 'auto', meaning it inherits the global chord length, or a pinned half, one, two or four beats, so a step's onset is the sum of the lengths before it and the loop is the sum of them all. Gate and strum follow the step that owns them, which keeps the module's original promise (the spread is a fraction of the chord's own play time) true once the chords no longer all have the same play time: the same 35% spread is a shorter gesture on a shorter chord. Rests hold their own duration too, so a four-beat rest is four beats of silence.

It hooks note handling rather than the audio graph, and it does so narrowly. Only the seven white keys of C4 to B4 are claimed, plus C5, and only on the one bank in the dataset that ships an eighth chord, because binding a key for a chord that does not exist would swallow a playable note on the other 133 banks. A black key or any other octave is not intercepted at all and falls through to the voice underneath. That is the escape hatch, and it is deliberate, an instrument that swallowed the whole keybed would take away the one thing you want chords for, which is playing something over them.

Goes well with.

Preview

Chordex

Generate a voiced chord from one note.

PreviewNo build recorded
Preview

Arpeggiator

Walk held notes through a rhythmic pattern.

PreviewNot built for macOS, Windows or Linux
Preview

OSC Mini

A compact subtractive synth for learning the signal path.

Preview