# envelope

> The envelope's controller from @audio-ui/core: breakpoints held or controlled, moved, added, removed and bent by pointer and keyboard, and the geometry that draws a bent segment.

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

```ts
import { createEnvelopeController, CURVE_EPSILON, CURVE_HIT_TOLERANCE, CURVE_RATE, CURVE_SAMPLES, /* … */ } from "@audio-ui/core/envelope";
```

- Adapters: `@audio-ui/react/envelope`, `@audio-ui/elements/envelope`
- Used by: `arrangement`, `dawproject`, `use-arrangement`

| Export | Kind | What it is |
| --- | --- | --- |
| `createEnvelopeController` | function | An envelope's behavior with no view attached: its points, held or controlled, every way of moving, adding, removing and bending them, and what it looks like as a snapshot. |
| `EnvelopeChangeEventDetails` | type | What `onValueChange` receives with each change: the `index` of the point or segment, the reason, the event behind it, and `cancel()` to refuse the change. |
| `EnvelopeChangeEventReason` | type | Why the points changed: `"drag"`, `"keyboard"`, `"add"`, `"remove"` or `"curve"` (a bend). |
| `EnvelopeCommitEventDetails` | type | What `onValueCommitted` receives once a gesture ends: the `index` of the point or segment, the reason and the event behind it. |
| `EnvelopeCommitEventReason` | type | Why the points were committed: the same reasons as a change. |
| `EnvelopeController` | interface | What `createEnvelopeController` returns: the snapshot to draw from, and a call for each gesture and key that moves, adds, removes, bends or straightens. |
| `EnvelopeOptions` | interface | The options of `createEnvelopeController`: the points, held or controlled, how few and how many there may be, the grid they snap to, the keyboard step, the formatting and the callbacks. |
| `EnvelopeSnap` | type | A grid step in normalized units: one for both axes, or one for each (0 leaves that axis free). |
| `EnvelopeSnapshot` | interface | What a view draws an envelope from. |
| `Breakpoint` | type | A point of the ramp, and how the segment leaving it bends: `curve` from -1 to 1, 0 or unset for a straight line. |
| `CURVE_EPSILON` | const | A curve this close to flat is taken as straight, and dropped. |
| `CURVE_HIT_TOLERANCE` | const | How far from a curve, as a share of the graph's height, a press still counts as on it. |
| `CURVE_RATE` | const | How far a segment can bend: the rate of its exponential at the ends of -1…1, which puts the middle of the segment at 0.4% of its rise (or 99.6%). |
| `CURVE_SAMPLES` | const | Straight lines between samples of a bent segment: where its drawing starts, before it adds vertices where the bend is tight. |
| `CURVE_STEP_MULTIPLIER` | const | Keyboard step for a segment's curve, as a multiple of `step`. |
| `clampToNeighbors` | function | Clamp a candidate point's x between its neighbors in `base` (so points can never cross or reorder) and y to [0,1]. |
| `curveForMidpoint` | function | The curve that puts a segment's midpoint at height `y`, or null when the segment is flat and has nothing to bend. |
| `curveKeyDelta` | function | How far a key bends a segment, or null for a key that does not: up lifts the handle, so a rising segment bends one way and a falling one the other. |
| `curveSamples` | function | Where along a bent segment to put the vertices of its drawing: evenly in length, and evenly in height, so a steep stretch is sampled as finely as a flat one and does not turn into a corner. |
| `insertPoint` | function | `point` added to `base`. |
| `LARGE_STEP_MULTIPLIER` | const | Shift + arrows move this many steps at once. |
| `levelAt` | function | Where a bent segment is at a share `t` of its length, as a level. |
| `polylinePoints` | function | Polyline coordinates: flat lead-in from x=0 to the first point, the points themselves with each bent segment sampled between them, flat lead-out from the last point to x=1. |
| `roundCoord` | function | One decimal place is plenty of precision for a 0-100 SVG coordinate, and keeps the `points` string short during drag. |
| `samePoints` | function | Whether two lists hold the same points, curves included, in the same order. |
| `sanitizePoints` | function | The points kept on the graph: each within 0 and 1, a curve within -1 and 1 (dropped when it is flat), in order along x. |
| `segmentNear` | function | The segment that has the point (x, y) on it or close to it, or null: one that is not flat, since a flat segment has nothing to bend. |
| `segmentShape` | function | Where along a bent segment a share `t` of its length lands, as a share of its rise: the exponential of an analog envelope stage, `(e^(k·t) - 1) / (e^k - 1)` with `k` the curve times `CURVE_RATE`. |
| `snapTo` | function | Rounds a normalized coordinate to the nearest multiple of `step`; a step of 0 leaves it. |
| `sortPoints` | function | The points in order along x. |

## Usage

An envelope is a list of breakpoints, each a point from 0 to 1 on both axes, with a `curve` from -1 to 1 for how the segment leaving it bends (0, or none, for a straight line). The controller holds that list and every gesture on it, and follows the [contract](/docs/core/controllers) every controller shares:

```ts
import { createEnvelopeController } from "@audio-ui/core/envelope";

const envelope = createEnvelopeController({
  defaultValue: [
    { x: 0, y: 0 },
    { x: 0.1, y: 1, curve: -0.5 },
    { x: 0.4, y: 0.6 },
    { x: 1, y: 0 },
  ],
  minPoints: 2,
  snap: 0.05,
  onValueCommitted: (points) => save(points),
});
const unsubscribe = envelope.subscribe(() => draw(envelope.getSnapshot()));
```

| Option | Meaning |
| --- | --- |
| `value`, `defaultValue` | The points, controlled, or the uncontrolled start (an ADSR-like shape unless given) |
| `minPoints`, `maxPoints` | How few (2 by default) and how many points it may have |
| `snap` | A grid step for both axes, or `{ x, y }` for each, 0 leaving an axis free; Cmd or Ctrl held skips it |
| `step` | What an arrow key moves a point by, 0.01 by default |
| `format`, `locale` | How `formatPoint` writes a point for a screen reader |
| `disabled` | Takes every input away |

## The input

Each gesture has its own call, given the element it happens on and the graph's element, which gives the coordinates:

- `pressPoint(index, event, handle, graph)` drags a point, or removes it with Alt held; a point stays between its neighbours, so points never cross.
- `pressSegment(index, event, handle, graph)` drags a segment's handle to bend it, and `pressBend(event, graph)`, an Alt + press on the graph, bends the segment under the pointer.
- `addPointAt({ x, y }, event)` adds a point and returns its index, or `null` at `maxPoints`; `removePoint(index, event)` takes one away, down to `minPoints`.
- `straightenSegment(index, event)` and `straightenAt({ x, y }, event)` make a bent segment straight again.
- `keyPoint(index, event)` moves a focused point with the arrows, Shift for a larger step, and removes it with Delete or Backspace; `keySegment(index, event)` bends a focused segment with Up and Down and straightens it with Delete. Each returns `true` when it handled the key.

The snapshot holds the points and what is being dragged (`draggingIndex`, `draggingSegmentIndex`), with `blockedIndex` and `isAddBlocked` for a moment after a removal or an addition was refused, for a view to show it. `dispose()` releases that feedback's timers when the view goes.

## Geometry

The rest of the module draws the envelope: `polylinePoints` is the polyline through the points with each bent segment sampled, `levelAt` the level along a bent segment, `curveForMidpoint` the curve that puts a segment's middle at a height, and `segmentNear` the segment under a point. `sortPoints`, `sanitizePoints`, `clampToNeighbors` and `insertPoint` keep a list valid.

**In React**

The [Envelope](/docs/components/base/envelope) draws this controller in React.

**In Vue, Svelte and Web**

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

