Rhythm Wheel

rhythm-wheelControls

A bar as a circle, and the shape the pattern makes.

$ pnpm dlx shadcn@latest add @pluginui/core-rhythm-wheel
Loading interactive preview…

Install

Copies the source into your project. Anything it depends on comes with it. Run this in your React project with shadcn initialized; no registry configuration is needed.

Pulls in

rhythm-mathuse-first-interactioncontrol-theme
Install using the registry URL
$ pnpm dlx shadcn@latest add https://pluginui.com/r/core-rhythm-wheel.json

Usage

Connect the documented props to your application or host.

example.tsx
import { RhythmWheel } from "@/components/ui/rhythm-wheel"
import { euclidPattern, maskPulses, rotateRhythm } from "maruaudio/ui/shared/rhythm-math"

// The primary story: a generated pattern, read-only, with the host's playhead.
const pattern = rotateRhythm(euclidPattern(3, 8), rotation)
<RhythmWheel
  steps={8}
  pulses={maskPulses(pattern, { velocity: 0.9, accents: [0] })}
  playhead={stepPosition}
  showIntervals
/>

// Editable: the host owns the pulses, every tap reports out.
<RhythmWheel
  steps={16}
  pulses={pulses}
  onToggle={(step, active) =>
    setPulses((current) =>
      active
        ? [...current, { step, velocity: 0.9 }]
        : current.filter((pulse) => pulse.step !== step),
    )
  }
/>
About Rhythm Wheel

A sequencer row is a line, and a line has an end. A bar loops, which is why drum machines have always drawn it as a ring of lights. This ring holds N steps, lights the struck ones and joins consecutive onsets into the pattern's rhythm polygon. E(3,8) makes a lopsided triangle you learn to recognise; four on the floor makes a square. Rotating a pattern spins its shape without changing it, the same necklace property used by pitch-class-clock to give every major triad the same triangle. Both use the same ring convention (step 0 at twelve o'clock, clockwise), pinned bit-identical in the adjacent maths. The generator is Euclidean: pulses spread as evenly as whole steps allow. Toussaint showed this produces traditional bell and clave patterns, so two knobs take you through a catalogue of real rhythms. Velocity controls each node's size and brightness; at a dozen pixels, one cue reads as distance rather than dynamics. Accent adds a separate outline ring, so a quiet accented hit keeps both cues. The host supplies the playhead, which may be fractional; this component does not animate itself. Without onToggle, it draws a generated pattern. With onToggle, each node becomes a toggle button whose arrow keys wrap around the ring.

Props

PropTypeDefaultDescription
stepsnumber—Number of steps in the bar, or nodes around the ring.
pulsesRhythmPulse[]—The struck steps: { step, velocity?, accent? }. Anything absent is a silent step. Velocity drives node size and brightness together; accent adds an outline ring in the secondary colour.
playheadnumber | null—Where the host's clock is, as an absolute step position. Fractional for a smooth sweep, and positions past the end of the bar fold into it, so a free-running counter passes straight through. null draws no playhead.
onToggle(step, active) => void—Report toggles to make nodes keyboard-reachable buttons. Arrow keys move around the wrapping ring; Home/End jump to the downbeat and final step; Enter/Space toggles. Without the callback, the wheel is a read-only drawing with a summary label. Taps register on pointer down, as on a pad, so there is no drag gesture.
showPolygonbooleantrueDraw the inter-onset polygon under the nodes. Faint on purpose: it is the pattern's shape, not the pattern.
showIntervalsbooleanfalsePrint circular inter-onset intervals in the centre, such as “3-3-2” for a tresillo, the way a drummer says it aloud.
labelstring—Centre text, overriding the interval readout.
groupSizenumber4Steps per group tick. Ticks sit outside the ring so the beats stay countable without adding ink inside it; the downbeat's is longer and lit, because a circle has no left-hand end. 0 disables the grouping.
size / nodeRadiusnumber220 / 12Square size in pixels, and the radius of a full-velocity node. The ring is inset by the node radius so nodes sit fully inside the box.
onInteraction(event) => void—Fires once on the first gesture of any kind. Use it to resume a suspended AudioContext.
colorstring—Override the accent colour for this instance.

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.