# device-signal

> A device's live sound from its engine, for its panel's signal: what goes in and comes out, its key, its taps and its readings as live sources, read in one frame loop and only while watched

```bash
npx shadcn@latest add @audio-ui/device-signal
```

```ts
import { deriveSource, curveDot, peakDots, createDeviceSignal } from "@/lib/device-signal";
```

- Files: `lib/device-signal.ts`
- Depends on: nothing; npm: `@audio-ui/core`, `@audio-ui/engine`
- Used by: `arrangement-engine`, `device-panel`, `device-panels`, `use-device-signal`

| Export | Kind | What it is |
| --- | --- | --- |
| `SignalTarget` | interface | What a signal listens to: a device's engine, or anything with an output. |
| `StereoSamples` | interface | The two channels of a point, their latest samples, -1 to 1, and how alike they are: what a stereo field is drawn from. |
| `SpectrumOptions` | interface |  |
| `ScopeOptions` | interface |  |
| `HistoryOptions` | interface |  |
| `SignalPoint` | interface | One point of a device's sound, such as what goes in or what comes out, read as live sources. |
| `DeviceSignal` | interface | Everything a device's face can show of its sound, as live sources: what goes in (an effect's), what comes out, the points its engine names in its `taps` and the readings it reports, each one present when the device declares it in `signals`. |
| `deriveSource` | function | A source computed from another, once per change however many watch it: a face's own reading of a signal, such as a level turned into a height. |
| `curveDot` | function | A dot on a transfer curve at the level of `levels`, the louder of its channels: where the sound sits on the curve now, for a `Plot.Dot` (`x` the level in, `y` what `curve` makes of it). |
| `peakDots` | function | Two dots on a shaper's curve, from -1 to 1, at the held peak of `levels`, one each side of zero: how far along the curve the sound swings, from the clean middle towards where it bends. |
| `createDeviceSignal` | function | The live signal of a device, from its engine, as a face shows it: what goes in and comes out, its readings, and the taps its device declares in `signals` (its `modulator` and its `wet` sound). |

## Usage

`createDeviceSignal(target, device)` reads an engine as a panel draws it. `target` is read each frame, so the signal follows the engine a host makes again and is silent while it returns `null`; `device` says which points it has: `input` for an effect, `key` for a device with a sidechain, and the taps it declares in `signals`.

```ts
import { createDeviceSignal } from "@/lib/device-signal";

const signal = createDeviceSignal(() => engine, compressorDevice);

signal.input?.levels; // the RMS and held peak of each channel, in dB
signal.output.spectrum({ minHz: 20, maxHz: 20_000 }); // dB along a log axis
signal.output.scope({ fit: true }); // the newest wave, held still on each cycle
signal.wet?.history({ of: "level", seconds: 3 }); // a reverb's or a delay's tail over time
signal.readings; // what the engine reports, such as the gain it takes off
```

A component holding an engine uses [`useDeviceSignal`](/docs/hooks/use-device-signal) instead, and a project's engine gives one per device with `ArrangementEngine.signal(id, device)`.

Each point (`input`, `output`, `key`, `wet`, `modulator`) reads:

- `levels`: the RMS of each channel in dB, rising at once and falling at 30 dB a second, and the loudest sample, held a moment, in the shape a `Meter` paints.
- `active`: whether sound passes, `false` once it has been silent for 400 ms. A panel's frame is idle while its input is not active.
- `spectrum({ minHz, maxHz, points })`: dB along a log frequency axis.
- `scope({ length, fit })`: the newest `length` samples from where the wave last rose through zero; with `fit`, scaled so the loudest of the last moments reaches 1.
- `history({ seconds, points, of, newestFirst })`: the signal itself (`"value"`) or its level in dB (`"level"`) over the last `seconds`, its slots counted on the audio clock; `newestFirst` puts now at the start.
- `stereo`: the two channels' newest samples, how alike they are from -1 to 1, and whether both are silent.

A point puts its analysers on the engine when the first of its sources is watched and takes them off with the last, and every source of the page is read in one animation frame that sleeps once nothing is watched. A source publishes silence once rather than every frame.

Three helpers turn a source into what a face draws, without a React render:

- `deriveSource(source, compute)`: a source computed from another, once per change however many watch it.
- `curveDot(levels, curve)`: a point on a transfer curve at the louder channel's level, for a `Plot.Dot`.
- `peakDots(levels, curve)`: two points on a shaper's curve at the held peak, `positive` and `negative`.

```tsx
const dot = useMemo(() => {
  const levels = signal?.input?.levels;
  return levels && curveDot(levels, curve);
}, [curve, signal]);

{dot ? <PlotDot className="group-data-idle/device:hidden" source={dot} /> : null}
```

The [Device signal](/docs/guides/device-signal) guide says what each kind of device draws and how a device declares its taps.
