hue-wheel

another machine GitHub ↗

Palette

A Palette divides the hue wheel into equal sectors, so that any hue names a slot and any slot names a hue. Slots hold whatever you put in them — the wheel never inspects them. Sectors are divided in perceptual (oklch) hue rather than display (HSV) hue, and the two wheels below are the same palette drawn both ways.

There is no weight field. Every sector is the same size, so a value takes a wider arc by appearing more than once — and because position matters, repeats that are separated put the same value in two different regions of the wheel instead of widening one. The default g1,g2,g2,g1,g3 shows both at once: g2 is one double-width band, while the two g1 sectors stay apart.

new Palette({ slots: T[]; rootHue?: number; crossZone?: number })

Display hue

Angle is HSV hue. Equal sectors, unequal arcs.

Perceptual hue

Angle is oklch hue. The same sectors, evenly spaced.

const palette = new Palette({
  slots: "".split(","),
  rootHue: ,
  crossZone: 
});
palette.hueToSlot();

Slot at this hue

Blend

Near a sector edge the lookup returns two slots rather than switching instantly. The darkened band on the inside of each ring is where that happens — darkest right on a boundary, where the two slots are an even 50/50, fading to nothing by the time one slot has the hue to itself. Drag cross zone to widen or close it; at 0 the bands vanish and slots switch abruptly, at 0.5 they meet in the middle and no hue is ever a single slot.

A neighbour's weight is capped at 0.5, so a slot never loses its majority. The seam inside a repeated band never shades, because a value is not crossfaded with itself — those seams are drawn faint so the repetition stays legible without implying a transition that does not happen.

Bands

A band is a run of adjacent sectors sharing a value — what you would actually draw or label. Adjacent repeats merge into one; separated repeats stay apart. The default list is five sectors but only four bands. Bands wrap, so one straddling 0° is reported once rather than as two fragments; try g1,g2,g3,g1 to see that.

Attaching meaning with map

The wheel is generic, so a slot's value can be anything. Here the identical geometry is rebuilt with chords in the slots and passed through map(parseChord) from @amplib/music-theory, which recognises a named chord when the notes form one. This is what AVVA does — the wheel supplies the mapping, the machine supplies the harmony.

Palette.fromString("CEG,CEG,FAC,GBD").map(parseChord)