first commit
This commit is contained in:
31
docs/media/audio-fx/audio-fx-plugin-flanger/README.md
Normal file
31
docs/media/audio-fx/audio-fx-plugin-flanger/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# @suaveplan/audio-fx-plugin-flanger - flanger plugin for the @suaveplan/audio-fx-plugin-* family.
|
||||
|
||||
flanger 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
|
||||
|
||||
- [flanger](./src/flanger/flanger.md) — `FlangerPlugin` is the universal comb-filter flanger. A short LFO-modulated delay line (sub-10 ms) is summed with the d…
|
||||
- [flanger](./src/flanger/flanger.md) — `FlangerPlugin` is the universal comb-filter flanger. A short LFO-modulated delay line (sub-10 ms) is summed with the d…
|
||||
- [schemas](./src/schemas/schemas.md) — Zod schemas validating the construction-time options and preset payload of `FlangerPlugin`. These run on the public bou…
|
||||
- [schemas](./src/schemas/schemas.md) — Zod schemas validating the construction-time options and preset payload of `FlangerPlugin`. These run on the public bou…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Composition
|
||||
|
||||
```ts
|
||||
import { PluginChain, DefaultBusRegistry } from "@suaveplan/audio-fx-plugin-base/chain";
|
||||
import { /* Plugin */ } from "@suaveplan/audio-fx-plugin-flanger";
|
||||
|
||||
const plugin = new /* Plugin */();
|
||||
const chain = new PluginChain({
|
||||
buses: new DefaultBusRegistry(),
|
||||
plugins: [plugin],
|
||||
});
|
||||
```
|
||||
|
||||
## Status
|
||||
|
||||
Replaces the `./flanger` subpath of the bundled `@suaveplan/audio-plugins-fx`.
|
||||
@@ -0,0 +1,50 @@
|
||||
# `flanger.types`
|
||||
|
||||
## Purpose
|
||||
|
||||
TypeScript-only type definitions for `FlangerPlugin`'s construction options. Co-located with the implementation so the public type surface stays aligned with the Zod validator in `schemas/schemas.ts`. Consumers can import the type for typed form state or storybook controls without pulling in the plugin's runtime code.
|
||||
|
||||
## Features
|
||||
|
||||
- `FlangerPluginOptions` — all fields optional so the default constructor (`new FlangerPlugin()`) is valid.
|
||||
- `readonly` properties — emphasises that the options object is consumed once at construction and never mutated.
|
||||
- Numeric ranges live in the Zod schema (single source of truth) rather than in TypeScript types.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import type { FlangerPluginOptions } from "@suaveplan/audio-fx-plugin-flanger";
|
||||
|
||||
const opts: FlangerPluginOptions = { initialDepthMs: 2 };
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import type { FlangerPluginOptions } from "@suaveplan/audio-fx-plugin-flanger";
|
||||
|
||||
const presets: Record<string, FlangerPluginOptions> = {
|
||||
subtle: { initialDepthMs: 0.5, initialFeedback: 0.3, initialWet: 0.3 },
|
||||
classic: { initialDepthMs: 2, initialFeedback: 0.5, initialWet: 0.5 },
|
||||
jet: { initialDepthMs: 3, initialFeedback: 0.9, initialWet: 0.7 },
|
||||
};
|
||||
|
||||
function load(name: keyof typeof presets): FlangerPluginOptions {
|
||||
return presets[name]!;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `FlangerPluginOptions`
|
||||
- `readonly initialRateHz?: number`
|
||||
- `readonly initialDepthMs?: number`
|
||||
- `readonly initialFeedback?: number`
|
||||
- `readonly initialWet?: number`
|
||||
- `readonly initialBaseDelayMs?: number`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The interface deliberately mirrors `ChorusPluginOptions` field-for-field except that `depth` is shorter-range (`0..5 ms` versus the chorus's `0..20 ms`) and `baseDelay` is much shorter (`0.1..10 ms` versus `5..30 ms`). This consistency lets a UI render both plugins from a shared component if it wants to, while the schema enforces the perceptually-distinct parameter ranges that define each effect.
|
||||
|
||||
All fields are `readonly` so structural-equality checks on saved options (e.g., undo/redo stacks) do not need defensive cloning. The constructor never mutates the passed object — it copies relevant values into the parameter list once and forgets the input.
|
||||
@@ -0,0 +1,69 @@
|
||||
# `flanger`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`FlangerPlugin` is the universal comb-filter flanger. A short LFO-modulated delay line (sub-10 ms) is summed with the dry signal; the close tap spacing produces evenly-spaced comb peaks and notches in the frequency response. Feedback emphasises the comb peaks, producing the "metallic" or "jet-engine" timbre that defines the flange sound.
|
||||
|
||||
The plugin extends `AbstractAudioFxPlugin` so it runs identically across browser, server, offline, and worklet renderers. State is a per-channel circular `Float32Array` sized for the plugin's maximum delay; `reset()` zeros all buffers in place without reallocation.
|
||||
|
||||
## Features
|
||||
|
||||
- Five parameters: `rate` (Hz, 0.05..10), `depth` (ms, 0..5), `feedback` (0..0.95), `wet` (0..1), `baseDelay` (ms, 0.1..10).
|
||||
- Sine LFO drives the delay tap around `baseDelay` with peak-to-peak excursion of `2 · depth`.
|
||||
- Linear-interpolated fractional read keeps the modulation smooth at low rates and high depths.
|
||||
- Feedback clamped at the parameter boundary (max 0.95) so the comb feedback loop stays bounded for any bounded input.
|
||||
- `wet === 0` early-out: returns the dry path sample-for-sample with no allocation and no buffer writes — keeps the spec contract "fully-dry mix is transparent" verifiable by static inspection.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { FlangerPlugin } from "@suaveplan/audio-fx-plugin-flanger";
|
||||
|
||||
const flanger = new FlangerPlugin({
|
||||
initialRateHz: 0.4,
|
||||
initialDepthMs: 2,
|
||||
initialFeedback: 0.6,
|
||||
initialWet: 0.5,
|
||||
});
|
||||
chain.add(flanger);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { FlangerPlugin } from "@suaveplan/audio-fx-plugin-flanger";
|
||||
|
||||
// "Through-zero" style deep flange: slow rate, very short base delay,
|
||||
// near-max feedback for the classic resonant jet sweep.
|
||||
const jet = new FlangerPlugin({
|
||||
initialRateHz: 0.2,
|
||||
initialDepthMs: 3,
|
||||
initialFeedback: 0.9,
|
||||
initialWet: 0.7,
|
||||
initialBaseDelayMs: 0.5,
|
||||
});
|
||||
await jet.setup(
|
||||
{ sampleRate: 48_000, channels: 2, blockSize: 512 },
|
||||
ctx
|
||||
);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `FlangerPlugin` (class extends `AbstractAudioFxPlugin`)
|
||||
- `new FlangerPlugin(options?: FlangerPluginOptions)` — throws `AUDIO_FX.INVALID_OPTIONS` on malformed options.
|
||||
- Inherited parameter accessors for `rate`, `depth`, `feedback`, `wet`, `baseDelay`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The delay line is a circular `Float32Array` per channel sized for `MAX_DELAY_MS = 20 ms` plus a 4-sample safety margin (the fractional read indexes `i0` and `i0 + 1`). Twenty milliseconds is well over `baseDelay.max + 2·depth.max = 10 + 10 = 20`, with the extra margin so the wrap path is never hit on the hottest combination of parameters.
|
||||
|
||||
The LFO is mapped to the delay tap via `delay = base + (sin + 1)/2 · depth`, not `delay = base + sin · depth`. Mapping to the half-positive range `[0, depth]` means the flange always *adds* delay to the base — never *subtracts* — so the line never reads near `writeIndex - 0` (which would wrap aliased noise into the output). It also matches the standard convention used by most reference flanger plugins so the perceptual sweep direction is what producers expect.
|
||||
|
||||
Feedback at the comb-filter resonance is intentionally allowed to climb to 0.95 because the flanger character lives at the resonance edge. The test "max feedback over a long sine never produces non-finite samples" exercises this boundary against a one-second sine at `feedback = 0.95` and verifies every output sample is finite and amplitude-bounded. Allowing `1.0` would create a marginally stable system whose output diverges for any non-zero input; clamping at 0.95 leaves enough headroom that even a noise-floor leak stays bounded over arbitrarily long renders.
|
||||
|
||||
The `wet === 0` early-out short-circuits the entire DSP loop with a `Float32Array.set` copy per channel — both fast and bit-exact. This satisfies the spec's "wet=0 is bit-transparent" contract without relying on coincidence (`0 * read + dry === dry` would only hold for IEEE-clean values, not subnormals).
|
||||
@@ -0,0 +1,56 @@
|
||||
# `flanger/schemas`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Zod schemas validating the construction-time options and preset payload of `FlangerPlugin`. These run on the public boundary between user code and the plugin: any malformed object hitting the constructor or `applyPreset` is rejected with `AUDIO_FX.INVALID_OPTIONS` / `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
|
||||
|
||||
- `FlangerPluginOptionsSchema` — validates all five optional construction fields with their documented ranges (`rate` 0.05..10 Hz, `depth` 0..5 ms, `feedback` 0..0.95, `wet` 0..1, `baseDelay` 0.1..10 ms).
|
||||
- `FlangerPresetDataSchema` — empty strict object: the flanger has no plugin-specific preset payload, so any extra keys are rejected by `.strict()`.
|
||||
- All numeric fields use `z.number().finite()` so `NaN` and `Infinity` cannot sneak through.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { FlangerPluginOptionsSchema } from "@suaveplan/audio-fx-plugin-flanger";
|
||||
|
||||
const parsed = FlangerPluginOptionsSchema.parse({ initialDepthMs: 1.5 });
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
FlangerPluginOptionsSchema,
|
||||
FlangerPresetDataSchema,
|
||||
} from "@suaveplan/audio-fx-plugin-flanger";
|
||||
|
||||
// Pull keys + ranges from the schema for a generated form.
|
||||
const fields = FlangerPluginOptionsSchema.keyof().options;
|
||||
for (const field of fields) {
|
||||
form.addRow(field);
|
||||
}
|
||||
|
||||
// Strict preset validation at the IPC boundary.
|
||||
function isFlangerPreset(blob: unknown): boolean {
|
||||
return FlangerPresetDataSchema.safeParse(blob).success;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `FlangerPluginOptionsSchema: z.ZodObject<{ initialRateHz?, initialDepthMs?, initialFeedback?, initialWet?, initialBaseDelayMs? }>`
|
||||
- `FlangerPresetDataSchema: z.ZodObject<{}, "strict">`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The maximum feedback is `0.95`, matching the chorus, phaser, and delay plugins so all four feedback-bearing effects share one mental model. The flanger lives close to that boundary by design — the resonant comb peaks are the effect's character — but the same hard cap keeps the loop strictly under unity gain so the output cannot diverge.
|
||||
|
||||
`FlangerPresetDataSchema` is `.strict()` so unknown fields are rejected, matching the sibling-plugin policy. The flanger has no plugin-specific preset payload today: every parameter (`rate`, `depth`, `feedback`, `wet`, `baseDelay`) travels through the universal parameter snapshot that `AbstractAudioFxPlugin` builds, and the empty `presetData` field marks that the plugin opts out of plugin-specific state.
|
||||
Reference in New Issue
Block a user