first commit
This commit is contained in:
31
docs/media/audio-fx/audio-fx-plugin-gate/README.md
Normal file
31
docs/media/audio-fx/audio-fx-plugin-gate/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# @suaveplan/audio-fx-plugin-gate - gate plugin for the @suaveplan/audio-fx-plugin-* family.
|
||||
|
||||
gate plugin for the @suaveplan/audio-fx-plugin-* family.
|
||||
|
||||
Member of the `@suaveplan/audio-fx-plugin-*` family. Universal (browser / server / offline / worklet identical). Depends on `@suaveplan/audio-fx-plugin-base` for the `AudioFxPlugin` contract; declares no other `@suaveplan/audio-fx-plugin-*` deps.
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
## Documentation tree
|
||||
|
||||
- [gate](./src/gate/gate.md) — `GatePlugin` is the universal noise gate. When the input envelope falls below `threshold`, the gate closes after `hold`…
|
||||
- [gate](./src/gate/gate.md) — `GatePlugin` is the universal noise gate. When the input envelope falls below `threshold`, the gate closes after `hold`…
|
||||
- [schemas](./src/schemas/schemas.md) — The `gate.schemas` module owns the Zod boundary guards for `GatePlugin` — construction options and the plugin-specific…
|
||||
- [schemas](./src/schemas/schemas.md) — The `gate.schemas` module owns the Zod boundary guards for `GatePlugin` — construction options and the plugin-specific…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Composition
|
||||
|
||||
```ts
|
||||
import { PluginChain, DefaultBusRegistry } from "@suaveplan/audio-fx-plugin-base/chain";
|
||||
import { /* Plugin */ } from "@suaveplan/audio-fx-plugin-gate";
|
||||
|
||||
const plugin = new /* Plugin */();
|
||||
const chain = new PluginChain({
|
||||
buses: new DefaultBusRegistry(),
|
||||
plugins: [plugin],
|
||||
});
|
||||
```
|
||||
|
||||
## Status
|
||||
|
||||
Replaces the `./gate` subpath of the bundled `@suaveplan/audio-plugins-fx`.
|
||||
74
docs/media/audio-fx/audio-fx-plugin-gate/src/gate/gate.md
Normal file
74
docs/media/audio-fx/audio-fx-plugin-gate/src/gate/gate.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# `gate`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`GatePlugin` is the universal noise gate. When the input envelope falls below `threshold`, the gate closes after `hold` ms via the `release` envelope; when the envelope rises back above `threshold`, the gate opens via `attack`. The envelope is linked across input channels so a transient on any channel opens the gate for the whole frame, keeping the stereo image phase-coherent during gated passages.
|
||||
|
||||
The plugin extends `AbstractAudioFxPlugin`, so it runs identically across browser / server / worklet renderers. Gate state is one number (`currentGain`) plus a hold-sample counter; `reset()` zeros both in place.
|
||||
|
||||
## Features
|
||||
|
||||
- Four parameters: `threshold` (dB, -80..0), `attack` (ms, 0.1..100), `hold` (ms, 0..500), `release` (ms, 1..1000).
|
||||
- Linked envelope detection: `max(|in_c|)` across all input channels per sample so a stereo transient on either channel opens the gate.
|
||||
- One-pole attack/release smoothing with `coef = 1 − exp(-1/(τ · sr))` per direction.
|
||||
- Hold counter resets every time the envelope crosses the threshold so a sustained-but-fluctuating signal stays gated open.
|
||||
- `getCurrentGain()` accessor exposes the live gate gain for metering / VCAs that mirror the gate.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { GatePlugin } from "@suaveplan/audio-fx-plugin-gate";
|
||||
|
||||
const gate = new GatePlugin({
|
||||
initialThresholdDb: -40,
|
||||
initialAttackMs: 1,
|
||||
initialHoldMs: 20,
|
||||
initialReleaseMs: 100,
|
||||
});
|
||||
chain.add(gate);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { GatePlugin } from "@suaveplan/audio-fx-plugin-gate";
|
||||
|
||||
// Tight drum-bus gate: fast attack/release, short hold, aggressive
|
||||
// threshold. Useful for cleaning up cymbal bleed under snare hits.
|
||||
const gate = new GatePlugin({
|
||||
initialThresholdDb: -25,
|
||||
initialAttackMs: 0.5,
|
||||
initialHoldMs: 30,
|
||||
initialReleaseMs: 60,
|
||||
});
|
||||
await gate.setup({ sampleRate: 48_000, channels: 2, blockSize: 512 }, ctx);
|
||||
|
||||
// Drive a UI meter from the live gate gain.
|
||||
requestAnimationFrame(function tick() {
|
||||
meter.write(gate.getCurrentGain());
|
||||
requestAnimationFrame(tick);
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `GatePlugin` (class extends `AbstractAudioFxPlugin`)
|
||||
- `new GatePlugin(options?: GatePluginOptions)` — throws `AUDIO_FX.INVALID_OPTIONS` on malformed options.
|
||||
- `getCurrentGain(): number` — current gate gain (0..1).
|
||||
- Inherited parameter accessors for `threshold`, `attack`, `hold`, `release`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The envelope detector is per-sample peak across channels: `level = max(|in_c[i]|)`. This is the cheapest detector that still responds to fast transients; an RMS detector would smooth out kick-drum attacks, which is rarely what a noise gate wants.
|
||||
|
||||
The hold counter resets every time `level >= thresholdLin`, then decrements while the envelope is below threshold. While `hold > 0` the target gain stays at `1`; once `hold === 0` the target drops to `0` and the release ramp engages. This implements the spec's "after the hold time" semantics literally.
|
||||
|
||||
`attackCoef` and `releaseCoef` are one-pole filter coefficients (`1 − exp(-1/(τ · sr))`). At each sample, `gain += coef · (target − gain)`, asymptoting toward `target` with the relevant time constant. With `release = 100 ms` at 48 kHz the gain falls by ~63% in the first 100 ms, ~95% in 300 ms, effectively closed within ~500 ms.
|
||||
|
||||
The threshold is converted to linear via `10^(thresholdDb / 20)` once per block; per-sample the gate compares `level` (already linear) to that constant.
|
||||
|
||||
The plugin has no `setSidechain` member — gates are usually program-side detection; if a future build wants an external key input, `setSidechain` can be added behind a `hasSidechain: true` capability flag without API breakage.
|
||||
@@ -0,0 +1,61 @@
|
||||
# `schemas`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
The `gate.schemas` module owns the Zod boundary guards for `GatePlugin` — construction options and the plugin-specific preset payload. Every public input runs through these schemas before the plugin touches any state; failures surface as `AUDIO_FX.INVALID_OPTIONS` / `AUDIO_FX.INVALID_PRESET` with the Zod issue list in `context.issues`.
|
||||
|
||||
Keeping the schemas in a sibling folder follows the package-wide convention: hosts can import the schemas directly, run their own `safeParse` against UI input, and surface field-level errors before constructing a `GatePlugin`.
|
||||
|
||||
## Features
|
||||
|
||||
- `GatePluginOptionsSchema` — every field optional, ranged to match the parameter declarations in `gate.ts`: `threshold ∈ [-80, 0] dB`, `attack ∈ [0.1, 100] ms`, `hold ∈ [0, 500] ms`, `release ∈ [1, 1000] ms`.
|
||||
- `GatePresetDataSchema` — `z.object({}).strict()`. The gate has no plugin-specific state beyond its automatable parameters; the universal `parameters` map carries everything that round-trips.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { GatePluginOptionsSchema } from "@suaveplan/audio-fx-plugin-gate";
|
||||
|
||||
const parsed = GatePluginOptionsSchema.safeParse(formValues);
|
||||
if (!parsed.success) {
|
||||
return showFieldErrors(parsed.error.issues);
|
||||
}
|
||||
const gate = new GatePlugin(parsed.data);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
GatePluginOptionsSchema,
|
||||
GatePresetDataSchema,
|
||||
} from "@suaveplan/audio-fx-plugin-gate";
|
||||
|
||||
// Validate an untrusted preset payload from a shared template
|
||||
// library before applyPreset. The plugin would catch the same issue,
|
||||
// but the host wants a friendlier error before a plugin instance is
|
||||
// constructed.
|
||||
const result = GatePresetDataSchema.safeParse(payload.data);
|
||||
if (!result.success) {
|
||||
return reject("Gate preset is corrupt.");
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `GatePluginOptionsSchema` — `z.ZodObject` matching `GatePluginOptions`. All fields optional and ranged.
|
||||
- `GatePresetDataSchema` — strict empty object; rejects any unknown keys.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`GatePluginOptionsSchema` uses `.finite()` on every numeric field so `NaN` and `±Infinity` are rejected at the boundary; the one-pole envelope follower's `exp(-1 / (τ · sr))` calculation would otherwise propagate non-finite coefficients into the smoothing state and contaminate every subsequent block.
|
||||
|
||||
`attack.min(0.1)` and `release.min(1)` match the parameter declarations; values smaller than this break the one-pole smoothing assumption (the coefficient approaches `1` from below) and produce numerical artefacts in single-precision float. `hold.min(0)` allows a zero-hold gate (closes immediately when the envelope drops), which is useful for hyper-tight drum gates.
|
||||
|
||||
`threshold.min(-80)` covers the practical noise floor of consumer audio; values below `-80 dB` are effectively "gate never closes" and don't need their own UI affordance. The upper bound at `0 dB` is the maximum useful threshold — at `0 dB` the gate is essentially always closed since real-world signals rarely exceed `0 dBFS`.
|
||||
|
||||
The strict empty `GatePresetDataSchema` rejects any unknown keys so a stale preset format with phantom fields fails loud rather than silently dropping data.
|
||||
Reference in New Issue
Block a user