first commit
This commit is contained in:
31
docs/media/audio-fx/audio-fx-plugin-distortion/README.md
Normal file
31
docs/media/audio-fx/audio-fx-plugin-distortion/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# @suaveplan/audio-fx-plugin-distortion - distortion plugin for the @suaveplan/audio-fx-plugin-* family.
|
||||
|
||||
distortion 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
|
||||
|
||||
- [distortion](./src/distortion/distortion.md) — `DistortionPlugin` is the universal three-mode distortion: `waveshaper` (smooth `tanh` saturation), `bitcrusher` (unifo…
|
||||
- [distortion](./src/distortion/distortion.md) — `DistortionPlugin` is the universal three-mode distortion: `waveshaper` (smooth `tanh` saturation), `bitcrusher` (unifo…
|
||||
- [schemas](./src/schemas/schemas.md) — Zod schemas validating the construction-time options and preset payload of `DistortionPlugin`. These run at the public…
|
||||
- [schemas](./src/schemas/schemas.md) — Zod schemas validating the construction-time options and preset payload of `DistortionPlugin`. These run at the public…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Composition
|
||||
|
||||
```ts
|
||||
import { PluginChain, DefaultBusRegistry } from "@suaveplan/audio-fx-plugin-base/chain";
|
||||
import { /* Plugin */ } from "@suaveplan/audio-fx-plugin-distortion";
|
||||
|
||||
const plugin = new /* Plugin */();
|
||||
const chain = new PluginChain({
|
||||
buses: new DefaultBusRegistry(),
|
||||
plugins: [plugin],
|
||||
});
|
||||
```
|
||||
|
||||
## Status
|
||||
|
||||
Replaces the `./distortion` subpath of the bundled `@suaveplan/audio-plugins-fx`.
|
||||
@@ -0,0 +1,69 @@
|
||||
# `distortion.types`
|
||||
|
||||
## Purpose
|
||||
|
||||
TypeScript-only type definitions for `DistortionPlugin`'s construction options, preset payload, and mode enum. Co-located with the implementation so the public type surface stays aligned with the Zod validator in `schemas/schemas.ts`.
|
||||
|
||||
The `DistortionMode` union is the canonical naming of the three transfer functions; saved presets travel by name (not index) so a future parameter reordering does not break stored data.
|
||||
|
||||
## Features
|
||||
|
||||
- `DistortionMode` — textual enum (`"waveshaper" | "bitcrusher" | "overdrive"`) used in options and presets.
|
||||
- `DistortionPluginOptions` — all-optional construction options; the default constructor (`new DistortionPlugin()`) is valid.
|
||||
- `DistortionPresetData` — preset payload carrying only the non-numeric `mode`; all numeric parameters travel through the universal parameter snapshot.
|
||||
- `readonly` fields prevent accidental mutation of saved options.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import type {
|
||||
DistortionMode,
|
||||
DistortionPluginOptions,
|
||||
} from "@suaveplan/audio-fx-plugin-distortion";
|
||||
|
||||
const opts: DistortionPluginOptions = { initialMode: "bitcrusher" };
|
||||
const mode: DistortionMode = opts.initialMode ?? "waveshaper";
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import type {
|
||||
DistortionMode,
|
||||
DistortionPluginOptions,
|
||||
} from "@suaveplan/audio-fx-plugin-distortion";
|
||||
|
||||
const modes: readonly DistortionMode[] = [
|
||||
"waveshaper",
|
||||
"bitcrusher",
|
||||
"overdrive",
|
||||
];
|
||||
|
||||
function nextMode(current: DistortionMode): DistortionMode {
|
||||
const i = modes.indexOf(current);
|
||||
return modes[(i + 1) % modes.length]!;
|
||||
}
|
||||
|
||||
const opts: DistortionPluginOptions = {
|
||||
initialMode: nextMode("waveshaper"),
|
||||
initialDriveDb: 18,
|
||||
initialOutputGainDb: -3,
|
||||
};
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `DistortionMode = "waveshaper" | "bitcrusher" | "overdrive"`
|
||||
- `DistortionPluginOptions`
|
||||
- `readonly initialMode?: DistortionMode`
|
||||
- `readonly initialDriveDb?: number`
|
||||
- `readonly initialBitDepth?: number`
|
||||
- `readonly initialOutputGainDb?: number`
|
||||
- `DistortionPresetData`
|
||||
- `readonly mode: DistortionMode`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The `mode` parameter in the plugin is a numeric enum (0..2) for compatibility with the universal `Parameter` shape, which carries `number` values. The plugin maps that index to/from the textual `DistortionMode` enum at the boundaries (`getPresetData` / `applyPresetData`). This keeps the runtime fast (no string comparisons in the inner loop) while making saved presets human-readable and forward-compatible.
|
||||
|
||||
`DistortionPresetData` deliberately includes only `mode`. Every numeric parameter (`drive`, `bits`, `outputGain`) is already part of the universal parameter snapshot that `AbstractAudioFxPlugin` builds, so re-encoding them here would double-store data and risk drift. The base class's preset round-trip honours both the universal parameters and the plugin-specific `presetData` field, applied in that order so plugin-specific decoding always wins for fields it cares about.
|
||||
@@ -0,0 +1,76 @@
|
||||
# `distortion`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`DistortionPlugin` is the universal three-mode distortion: `waveshaper` (smooth `tanh` saturation), `bitcrusher` (uniform quantisation to a chosen bit depth), and `overdrive` (cubic soft-clip). All three are stateless transfer functions applied per sample without allocation, so the plugin runs equally well on the audio thread, in a worklet, in an offline renderer, or under server-side rendering.
|
||||
|
||||
Every mode is bounded by construction: `tanh` is bounded in `[-1, 1]` for any input; the bitcrusher pre-clamps to `[-1, 1]` before quantisation; the cubic soft-clip clamps to `[-2/3, 2/3]`. That property is the contract enforced by the spec test "waveshaper output stays finite and bounded for an over-unity input".
|
||||
|
||||
## Features
|
||||
|
||||
- Four parameters: `mode` (enum 0..2), `drive` (dB, 0..40), `bits` (bitcrusher depth 1..16), `outputGain` (dB, -24..24).
|
||||
- Drive pre-gain raises the input level *into* the transfer function — useful for waveshaper/overdrive to push the signal further into the non-linear region.
|
||||
- `outputGain` compensates for level changes caused by shaping so the plugin can drop into an existing chain without raising perceived loudness.
|
||||
- Mode switching is parameter-driven, so a single instance can crossfade between modes at runtime (snap by default; for click-free morphing, use the parameter automation API on `mode`).
|
||||
- Preset payload carries the textual `mode` enum so saved presets are robust to future parameter-index changes.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { DistortionPlugin } from "@suaveplan/audio-fx-plugin-distortion";
|
||||
|
||||
const dist = new DistortionPlugin({
|
||||
initialMode: "waveshaper",
|
||||
initialDriveDb: 12,
|
||||
initialOutputGainDb: -3,
|
||||
});
|
||||
chain.add(dist);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { DistortionPlugin } from "@suaveplan/audio-fx-plugin-distortion";
|
||||
|
||||
// Lo-fi bitcrusher pad: 6-bit quantisation with mild gain compensation.
|
||||
const lofi = new DistortionPlugin({
|
||||
initialMode: "bitcrusher",
|
||||
initialBitDepth: 6,
|
||||
initialDriveDb: 0,
|
||||
initialOutputGainDb: 4,
|
||||
});
|
||||
await lofi.setup(
|
||||
{ sampleRate: 48_000, channels: 2, blockSize: 512 },
|
||||
ctx
|
||||
);
|
||||
|
||||
// Automate bit depth from 8 down to 3 over four seconds for a tape-eject
|
||||
// effect. `bits` is automatable so the same plugin handles both static
|
||||
// and time-varying use.
|
||||
const startTime = 0;
|
||||
const endTime = 4;
|
||||
lofi.setParameterAt("bits", 8, startTime);
|
||||
lofi.setParameterAt("bits", 3, endTime);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `DistortionPlugin` (class extends `AbstractAudioFxPlugin`)
|
||||
- `new DistortionPlugin(options?: DistortionPluginOptions)` — throws `AUDIO_FX.INVALID_OPTIONS` on malformed options.
|
||||
- Inherited parameter accessors for `mode`, `drive`, `bits`, `outputGain`.
|
||||
- Types: `DistortionMode = "waveshaper" | "bitcrusher" | "overdrive"`.
|
||||
- Schemas: `DistortionPluginOptionsSchema`, `DistortionPresetDataSchema`, `DistortionModeSchema`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The waveshaper transfer is plain `Math.tanh(drive · x)`. We chose `tanh` over `atan` or piecewise-polynomial shapers because it has a clean analytic bound (`|tanh(y)| < 1`), is monotonic everywhere, and is what most reference distortion plugins use, so the perceptual character matches what producers expect. Per-sample `Math.tanh` is fast enough on V8 / JavaScriptCore not to warrant a lookup table for the typical block sizes used in real-time audio.
|
||||
|
||||
The bitcrusher pre-clamps to `[-1, 1]` before quantisation because uniform rounding on an unbounded input would produce arbitrarily large output (e.g. `round(5 * 4) / 4 = 5`). The spec demands "the output takes only the expected quantised set of values" — that test would fail on an unclamped input. The number of distinct output values is `2^bits + 1` (`2^(bits-1)` per side plus zero) — for `bits = 3`, that's 9 levels.
|
||||
|
||||
The overdrive cubic `x - x³/3` is the standard "Reaper-style" soft-clip. Inside `[-1, 1]` it is monotonic and approximately linear near zero; outside `[-1, 1]` it would diverge, so we clamp to the saturation value `±2/3` (which equals `f(±1)`). This keeps the transfer function continuous at the clamp boundaries — no audible discontinuity even on huge transients.
|
||||
|
||||
`reset()` and `setupState()` are no-ops by design: distortion is a memory-less transfer function, so there is no internal buffer that would need clearing. Tests still exercise `reset()` to lock in the contract that it does not throw, which protects against accidentally adding state in a future revision without updating the docs.
|
||||
@@ -0,0 +1,66 @@
|
||||
# `distortion/schemas`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Zod schemas validating the construction-time options and preset payload of `DistortionPlugin`. These run at the public boundary so any malformed object — wrong mode name, out-of-range drive, non-integer bit depth, NaN gain — is rejected with `AUDIO_FX.INVALID_OPTIONS` or `AUDIO_FX.INVALID_PRESET` before it can corrupt runtime state.
|
||||
|
||||
Separating schemas from the plugin module lets `index.ts` re-export them so callers can introspect the option surface (for forms, RPC validation, or storybook controls) without importing the audio implementation.
|
||||
|
||||
## Features
|
||||
|
||||
- `DistortionModeSchema` — `z.enum(["waveshaper", "bitcrusher", "overdrive"])` — used in both options and preset payloads.
|
||||
- `DistortionPluginOptionsSchema` — validates all four optional construction fields with their documented ranges (`drive` 0..40 dB, `bits` 1..16 integer, `outputGain` ±24 dB).
|
||||
- `DistortionPresetDataSchema` — `.strict()` object carrying only `mode`; extra fields are rejected.
|
||||
- `bits` is `z.number().int()` so float bit-depths (which would round unpredictably) cannot sneak through.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { DistortionPluginOptionsSchema } from "@suaveplan/audio-fx-plugin-distortion";
|
||||
|
||||
const parsed = DistortionPluginOptionsSchema.parse({
|
||||
initialMode: "waveshaper",
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
DistortionModeSchema,
|
||||
DistortionPresetDataSchema,
|
||||
} from "@suaveplan/audio-fx-plugin-distortion";
|
||||
|
||||
// Validate a preset blob coming off the wire before applying it.
|
||||
function safeApplyPreset(plugin: DistortionPlugin, payload: unknown) {
|
||||
const parsed = DistortionPresetDataSchema.safeParse(payload);
|
||||
if (!parsed.success) {
|
||||
log.warn("dropped bad distortion preset", parsed.error.issues);
|
||||
return;
|
||||
}
|
||||
plugin.applyPreset({
|
||||
version: 1,
|
||||
parameters: {},
|
||||
presetData: parsed.data,
|
||||
});
|
||||
}
|
||||
|
||||
const modes = DistortionModeSchema.options;
|
||||
console.log(modes); // ["waveshaper", "bitcrusher", "overdrive"]
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `DistortionModeSchema: z.ZodEnum<["waveshaper", "bitcrusher", "overdrive"]>`
|
||||
- `DistortionPluginOptionsSchema: z.ZodObject<{ initialMode?, initialDriveDb?, initialBitDepth?, initialOutputGainDb? }>`
|
||||
- `DistortionPresetDataSchema: z.ZodObject<{ mode: DistortionMode }, "strict">`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`DistortionModeSchema` is exported as a named schema (not inlined) so the same value is reused by `DistortionPluginOptionsSchema.initialMode` and `DistortionPresetDataSchema.mode`. That guarantees both validation paths agree byte-for-byte on the legal mode set — adding a fourth mode in a future revision is a one-line change in `schemas.ts`.
|
||||
|
||||
`DistortionPresetDataSchema` uses `.strict()` for the same reason as the other plugins: unknown fields almost always indicate a payload from a sibling plugin or a forwards-incompatible schema, and silent rejection at the boundary is much less harmful than partial application. The combined "mode-must-be-known, payload-must-be-exact" stance keeps the preset surface stable across plugin versions.
|
||||
Reference in New Issue
Block a user