Circle of Fifths
circle-of-fifthsVisualizersThe circle of fifths is the layout that makes tonality visible: neighbouring slots are a fifth apart, so the seven notes of any key form one unbroken arc and a modulation is a short slide round the rim — where a chromatic keyboard scatters the same information all over the octave. Each pitch class becomes a radial bar, its length proportional to how much that note sounded. The optional needle points at the tonal centre, with both its length and its opacity scaled by confidence, so an ambiguous or chromatic passage gets a short faint needle rather than a decisive-looking lie. Compute the centre with tonalCenter from the tonal-center library; the component deliberately does not, so a precomputed or smoothed estimate renders through the same wheel.
A C-major histogram on the wheel. The seven notes of the key form one unbroken arc, and the needle points at the tonal centre — sitting just off C, pulled by the relative minor.
Install
Copies the source into your project. Anything it depends on comes with it.
$ npx shadcn add @syntho-ui/circle-of-fifthsPulls 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 { CircleOfFifths } from "@/components/ui/circle-of-fifths"
import { tonalCenter } from "@/lib/tonal-center"
// Chromatic order, C..B — peak-normalised for you.
const weights = [1, 0, 0.55, 0, 0.8, 0.6, 0, 0.9, 0, 0.55, 0, 0.45]
<CircleOfFifths
weights={weights}
needle={tonalCenter(weights)}
highlightPcs={[0, 4, 7]}
/>Props
| Prop | Type | Default | Description |
|---|---|---|---|
| weights | number[] | — | Length-12 pitch-class weights in CHROMATIC order (0 = C … 11 = B). Peak-normalised internally, so the units are yours — note counts, seconds held, velocity sums. |
| needle | { angleRad: number, strength: number } | null | null | The tonal centre to point at, e.g. from tonalCenter(weights). Strength drives both length and opacity. Omit for no needle. |
| highlightPcs | number[] | — | Pitch classes to ring-mark — typically the chord sounding right now. Reduced mod 12, so raw MIDI numbers work. |
| size | number | 240 | Overall square size in pixels. |
| showLabels | boolean | true | Draw the pitch-class names around the rim. |
| color | string | — | Bar and needle 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.