# knob

> The knob's controller from @audio-ui/core: a bounded value on a dial, turned by a vertical, circular or revolving drag, stepped, scrolled, typed and reset, and the geometry of its arcs.

```bash
npm install @audio-ui/core
```

```ts
import { createKnobController, ANGLE_GAP_RAD, ARC_OUTER_RADIUS, arcPath, CENTER_DEAD_ZONE_MIN_PX, /* … */ } from "@audio-ui/core/knob";
```

- Adapters: `@audio-ui/react/knob`, `@audio-ui/elements/knob`

| Export | Kind | What it is |
| --- | --- | --- |
| `createKnobController` | function | A knob's behavior with no view attached: its value, held or controlled, every way of turning it, and what it looks like as a snapshot. |
| `KnobChangeEventDetails` | type | What `onValueChange` receives with each change: the reason, the event behind it, and `cancel()` to refuse the change. |
| `KnobChangeEventReason` | type | Why the value changed: `"drag"`, `"keyboard"`, `"wheel"`, `"input-change"` (a typed value) or `"reset"`. |
| `KnobCommitEventDetails` | type | What `onValueCommitted` receives once a gesture ends: the reason and the event behind it. |
| `KnobCommitEventReason` | type | Why the value was committed: the same reasons as a change. |
| `KnobController` | interface | What `createKnobController` returns: the snapshot to draw from, and the press, keys, wheel, typed value and reset that turn it. |
| `KnobDragMode` | type | How a drag turns a knob: up and down, around its centre, or by the turns of the pointer. |
| `KnobOptions` | interface | The options of `createKnobController`: the value, held or controlled, its range, scale, steps and anchor, the drag mode, the formatting and the callbacks. |
| `KnobSnapshot` | interface | What a view draws a knob from. |
| `ANGLE_GAP_RAD` | const | Gap at the bottom of the dial: the sweep runs clockwise from bottom-left to bottom-right. |
| `ARC_OUTER_RADIUS` | const | Outer edge of the arcs in the viewBox; each stroke is centered inside it. |
| `arcPath` | function | SVG arc along the dial between two normalized positions (0…1). |
| `CENTER_DEAD_ZONE_MIN_PX` | const | The smallest the hub's dead zone may be, in px, however small the knob. |
| `CENTER_DEAD_ZONE_REL` | const | Circular drag: inside this fraction of the diameter the angle is synced but not accumulated, so crossing the hub doesn't jump. |
| `DEFAULT_ARC_STROKE_WIDTH` | const | The width of the track and indicator arcs in the 48 by 48 view box, unless given. |
| `DOUBLE_TAP_MAX_MS` | const | The longest two taps may be apart, in ms, to count as a double tap and reset the value. |
| `FINE_DRAG` | const | Holding Shift turns the knob this much slower than the pointer moves. |
| `LARGE_POSITION_STEP` | const | With a scale that is not linear, a large step (Shift or Page Up and Down) moves this share of the travel. |
| `LINEAR` | const | The linear scale, resolved once. |
| `POSITION_STEP` | const | With a scale that isn't linear, a key or wheel notch moves this share of the travel. |
| `pointerAngle` | function | The angle of a pointer around the centre of `rect`, in radians, 0 pointing right. |
| `START_ANGLE` | const | Rotation at `min`, in degrees clockwise from 12 o'clock. |
| `STATE_ATTRIBUTES_MAPPING` | const | `disabled`, `dragging` and the drag mode become data attributes; the numbers stay in `state`. |
| `SWEEP_ANGLE` | const | Rotation travelled from `min` to `max`, in degrees. |
| `steppedValue` | function | The value `direction` steps away from `current`: by `step` (or `largeStep`) on a linear scale, by a share of the travel on any other, and always by at least one `step`. |
| `TAP_MAX_MOVE_PX` | const | A press that moves less than this is a tap; two quick taps reset the value. |
| `VERTICAL_DRAG_RANGE_PX` | const | Vertical drag: pointer travel, in px, for the whole `min…max` range. |
| `VIEWBOX_CENTER` | const | Center of the 48×48 viewBox the arcs are drawn in. |
| `WHEEL_DELTA_EPSILON` | const | A wheel movement smaller than this is ignored. |

## Usage

The controller follows the [contract](/docs/core/controllers) every controller shares: create it once, draw from its snapshot, send it the input.

```ts
import { createKnobController } from "@audio-ui/core/knob";

const knob = createKnobController({ min: 20, max: 20000, value: 1000, scale: "log", format: { maximumFractionDigits: 0 } });
```

| Option | Meaning |
| --- | --- |
| `value`, `defaultValue`, `min`, `max`, `step`, `largeStep` | As for the [fader](/docs/core/fader) |
| `scale` | `"linear"`, `"log"`, or a function between 0 and 1 |
| `dragMode` | How a drag turns into a value: `"vertical"`, `"circular"` (the angle around the centre) or `"revolution"` (the turns of the pointer) |
| `anchor` | The value the arc grows from, such as 0 on a pan knob |
| `format`, `locale`, `disabled` | As for the fader |

A knob resets on a double tap and on Alt with a press, inside the controller: it has no reset gesture in the element. `press(event, control)` takes the control, the element that gives the angle; `keyDown(event)` returns `true` when it handled the key. The snapshot has the `angle` of the pointer, the `percent` and the `anchorPercent`, ready for an arc.

A vertical drag covers the whole range over 150 px (`VERTICAL_DRAG_RANGE_PX`), and Shift turns it four times slower (`FINE_DRAG`). On a scale that is not linear, a key or a wheel notch moves 1% of the travel (`POSITION_STEP`), so a log knob steps as evenly as it looks; `steppedValue` is that rule as a function.

## Drawing the dial

The dial sweeps 288 degrees (`SWEEP_ANGLE`), clockwise from the bottom left, with its gap at the bottom. `arcPath(radius, from, to)` is the SVG arc between two positions from 0 to 1 in its 48 by 48 view box, and `pointerAngle` the angle of a pointer around the centre of an element.

**In React**

The [Knob](/docs/components/base/knob) draws this controller in React.

**In Vue, Svelte and Web**

[`<audio-ui-knob>`](/docs/elements/knob) draws this controller in your framework.

