Pitch Class Clock
pitch-class-clockVisualizersCollapsing pitch to pitch class throws away octave and voicing, and what survives is the thing that actually names a chord. Drawn on a ring, a chord becomes a polygon — and the polygon is a fingerprint: every major triad is the same triangle at a different rotation, every minor triad its mirror, a diminished seventh a perfect square. Transposition rotates the shape; changing the quality changes it. The ring order is yours to pick, and it decides which chords look simple: chromatic spaces notes by semitone so scales are arcs, fifths clusters diatonic material so triads open out. Nodes stay equidistant in every ordering, so the shapes remain comparable.
Every major triad is the same triangle at a different rotation — step through the chords to watch it turn, and switch the ring order to see the same chord take a different shape.
Install
Copies the source into your project. Anything it depends on comes with it.
$ npx shadcn add @syntho-ui/pitch-class-clockPulls in
First time? Add the registry to your components.json once:
{
"registries": {
"@syntho-ui": "https://engine.syntho.app/r/{name}.json"
}
}Usage
Controlled or uncontrolled, whichever suits the surrounding state.
import { PitchClassClock } from "@/components/ui/pitch-class-clock"
// MIDI numbers or pitch classes — both are reduced mod 12.
<PitchClassClock notes={[60, 64, 67]} sequence="fifths" chordShape="polygon" />Props
| Prop | Type | Default | Description |
|---|---|---|---|
| notes | number[] | [] | The sounding notes, as MIDI numbers or pitch classes. Reduced mod 12 and deduped, so [60, 64, 67] and [0, 4, 7] light the same nodes. |
| sequence | "chromatic" | "fifths" | "+2" | "chromatic" | Ring ordering: by semitone, by fifth, or by whole tone. Note that +2 is not coprime with 12, so that dial shows each whole-tone class twice. |
| chordShape | "polygon" | "lines" | "none" | "polygon" | How the sounding notes are joined: a filled polygon, an open path, or not at all. |
| size | number | 220 | Overall square size in pixels. |
| nodeRadius | number | 14 | Radius of each note node; also the ring's inset from the edge. |
| startAtTop | boolean | true | Put ring index 0 at twelve o'clock. False puts it at three o'clock. |
| showLabels | boolean | true | Draw the note name inside each node. |
| color | string | — | Highlight and connector colour. Defaults to the --uikit-accent token. |
Interaction
- Drag with pointer capture, so the gesture keeps tracking past the edge of the control.
- Shift while dragging for a fine pass; release it mid-gesture and coarse tracking resumes.
- Double-click resets to
defaultValue. - Arrow keys step normally, Shift+arrow steps finely, PageUp/PageDown jump by ten, Home/End go to the extremes.
- Proper
sliderandswitchroles with livearia-valuetext, so a screen reader announces the formatted value rather than the raw number. - The scroll wheel adjusts the value while the pointer is over the control; stepped ranges move at least one declared step per notch by default.