Circle of Fifths

circle-of-fifthsVisualizers

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

CGDAEBF♯C♯G♯D♯A♯F

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

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

PropTypeDefaultDescription
weightsnumber[]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 } | nullnullThe tonal centre to point at, e.g. from tonalCenter(weights). Strength drives both length and opacity. Omit for no needle.
highlightPcsnumber[]Pitch classes to ring-mark — typically the chord sounding right now. Reduced mod 12, so raw MIDI numbers work.
sizenumber240Overall square size in pixels.
showLabelsbooleantrueDraw the pitch-class names around the rim.
colorstringBar 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 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.