Pitch Class Clock

pitch-class-clockVisualizers

Collapsing 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.

CC♯DD♯EFF♯GG♯AA♯B

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-clock

Pulls in

control-theme

First time? Add the registry to your components.json once:

components.json
{
  "registries": {
    "@syntho-ui": "https://engine.syntho.app/r/{name}.json"
  }
}

Usage

Controlled or uncontrolled, whichever suits the surrounding state.

example.tsx
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

PropTypeDefaultDescription
notesnumber[][]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.
sizenumber220Overall square size in pixels.
nodeRadiusnumber14Radius of each note node; also the ring's inset from the edge.
startAtTopbooleantruePut ring index 0 at twelve o'clock. False puts it at three o'clock.
showLabelsbooleantrueDraw the note name inside each node.
colorstringHighlight 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 slider and switch roles with live aria-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.