first commit
This commit is contained in:
31
docs/media/audio-fx/audio-fx-plugin-phaser/README.md
Normal file
31
docs/media/audio-fx/audio-fx-plugin-phaser/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# @suaveplan/audio-fx-plugin-phaser - phaser plugin for the @suaveplan/audio-fx-plugin-* family.
|
||||
|
||||
phaser 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
|
||||
|
||||
- [phaser](./src/phaser/phaser.md) — `PhaserPlugin` is the universal all-pass phaser. A cascade of first-order all-pass sections (configurable from 2 to 12)…
|
||||
- [phaser](./src/phaser/phaser.md) — `PhaserPlugin` is the universal all-pass phaser. A cascade of first-order all-pass sections (configurable from 2 to 12)…
|
||||
- [schemas](./src/schemas/schemas.md) — Zod schemas validating the construction-time options and preset payload of `PhaserPlugin`. These run on the public boun…
|
||||
- [schemas](./src/schemas/schemas.md) — Zod schemas validating the construction-time options and preset payload of `PhaserPlugin`. These run on the public boun…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Composition
|
||||
|
||||
```ts
|
||||
import { PluginChain, DefaultBusRegistry } from "@suaveplan/audio-fx-plugin-base/chain";
|
||||
import { /* Plugin */ } from "@suaveplan/audio-fx-plugin-phaser";
|
||||
|
||||
const plugin = new /* Plugin */();
|
||||
const chain = new PluginChain({
|
||||
buses: new DefaultBusRegistry(),
|
||||
plugins: [plugin],
|
||||
});
|
||||
```
|
||||
|
||||
## Status
|
||||
|
||||
Replaces the `./phaser` subpath of the bundled `@suaveplan/audio-plugins-fx`.
|
||||
@@ -0,0 +1,51 @@
|
||||
# `phaser.types`
|
||||
|
||||
## Purpose
|
||||
|
||||
TypeScript-only type definitions for `PhaserPlugin`'s construction options. Co-located with the implementation so the public type surface stays aligned with the Zod validator in `schemas/schemas.ts`. The interface gives consumers typed access to construction options for forms, dependency injection, and storybook controls without pulling in the plugin runtime.
|
||||
|
||||
## Features
|
||||
|
||||
- `PhaserPluginOptions` — all fields optional so the default constructor (`new PhaserPlugin()`) is valid.
|
||||
- `readonly` properties emphasise that the options object is consumed once at construction.
|
||||
- Numeric ranges are documented in the Zod schema, not duplicated in TypeScript types.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import type { PhaserPluginOptions } from "@suaveplan/audio-fx-plugin-phaser";
|
||||
|
||||
const opts: PhaserPluginOptions = { initialDepth: 0.6 };
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import type { PhaserPluginOptions } from "@suaveplan/audio-fx-plugin-phaser";
|
||||
|
||||
const presets: Record<string, PhaserPluginOptions> = {
|
||||
subtle: { initialStages: 4, initialDepth: 0.4, initialWet: 0.3 },
|
||||
classic: { initialStages: 6, initialDepth: 0.7, initialWet: 0.5 },
|
||||
jet: { initialStages: 10, initialDepth: 0.95, initialWet: 0.8 },
|
||||
};
|
||||
|
||||
function load(name: keyof typeof presets): PhaserPluginOptions {
|
||||
return presets[name]!;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `PhaserPluginOptions`
|
||||
- `readonly initialRateHz?: number`
|
||||
- `readonly initialDepth?: number`
|
||||
- `readonly initialStages?: number`
|
||||
- `readonly initialFeedback?: number`
|
||||
- `readonly initialWet?: number`
|
||||
- `readonly initialCenterHz?: number`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`initialStages` is typed as `number` rather than as a literal union (`2 | 3 | ... | 12`) because the parameter system stores all parameter values as `number` and accepting the wider type avoids a no-op cast at every call site. The Zod schema enforces integer-ness and range at the boundary; the plugin rounds the live value each block, which yields the same effective behaviour as a literal union without polluting the public type surface.
|
||||
|
||||
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,70 @@
|
||||
# `phaser`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`PhaserPlugin` is the universal all-pass phaser. A cascade of first-order all-pass sections (configurable from 2 to 12) has its break frequencies modulated together by a shared LFO. Mixing the cascade's output with the dry signal creates the moving notches that define the phaser sound. Feedback from the last stage back into the first stage strengthens the resonant peaks at the notch edges.
|
||||
|
||||
The plugin extends `AbstractAudioFxPlugin`, so it runs identically across browser / server / worklet renderers. State is per-channel arrays sized for the maximum stage count; `reset()` zeros them in place without reallocation.
|
||||
|
||||
## Features
|
||||
|
||||
- Six parameters: `rate` (Hz, 0.05..10), `depth` (0..1), `stages` (int, 2..12), `feedback` (0..0.95), `wet` (0..1), `center` (Hz, 100..5000).
|
||||
- LFO-modulated break frequency: `break = center · 2^(lfo · depth · 2)` so depth controls the modulation span in octaves around `center`.
|
||||
- `wet === 0` early-out: copies the dry path bit-exact, satisfying the spec's "fully-dry mix is transparent" contract.
|
||||
- Feedback clamped at 0.95 at the parameter boundary so the system stays bounded for any bounded input — proven by the test "max feedback over a long sine never produces non-finite samples".
|
||||
- Stages capped at 12 so a single instance can run from a thin "subtle two-stage phaser" all the way up to a deep "Bass Synth 12-stage" without re-allocating delay state.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { PhaserPlugin } from "@suaveplan/audio-fx-plugin-phaser";
|
||||
|
||||
const phaser = new PhaserPlugin({
|
||||
initialRateHz: 0.5,
|
||||
initialDepth: 0.7,
|
||||
initialWet: 0.5,
|
||||
});
|
||||
chain.add(phaser);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import { PhaserPlugin } from "@suaveplan/audio-fx-plugin-phaser";
|
||||
|
||||
// Deep "jet flange" phaser: 10 stages, heavy feedback, slow modulation,
|
||||
// near-full wet. Designed for synth pads where you want the slow sweep
|
||||
// to dominate the timbre rather than sit behind it.
|
||||
const jet = new PhaserPlugin({
|
||||
initialRateHz: 0.25,
|
||||
initialDepth: 0.95,
|
||||
initialStages: 10,
|
||||
initialFeedback: 0.85,
|
||||
initialWet: 0.8,
|
||||
initialCenterHz: 600,
|
||||
});
|
||||
await jet.setup(
|
||||
{ sampleRate: 48_000, channels: 2, blockSize: 512 },
|
||||
ctx
|
||||
);
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `PhaserPlugin` (class extends `AbstractAudioFxPlugin`)
|
||||
- `new PhaserPlugin(options?: PhaserPluginOptions)` — throws `AUDIO_FX.INVALID_OPTIONS` on malformed options.
|
||||
- Inherited parameter accessors for `rate`, `depth`, `stages`, `feedback`, `wet`, `center`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The first-order all-pass section is implemented as `y = a·x + xPrev − a·yPrev`, where the bilinear coefficient `a = (tan(πf/sr) − 1) / (tan(πf/sr) + 1)`. This form has unity magnitude response (all-pass) and phase response that swings 180° around `f`. Cascading N sections produces N evenly-spaced notches in the mixed dry+wet output; the LFO sweeps these notches across the spectrum to create the phasing motion.
|
||||
|
||||
`stages` is declared non-automatable because changing the count mid-block would either leave stale state in the unused stages or require gain compensation. The parameter accepts integer values 2..12; the inner loop iterates the rounded value each block, so users can still change stages between blocks without artefacts beyond the first-block transient.
|
||||
|
||||
The break-frequency clamp `[20, 0.45·sr]` is set well below Nyquist so the bilinear transform stays numerically stable even at the high-depth extreme. The `0.45·sr` upper bound matches the policy used in the state-variable filter plugin and was chosen so the all-pass coefficient stays in `(-1, 1)` across the parameter range; pushing closer to Nyquist would risk the coefficient approaching ±1 and amplifying float-precision noise.
|
||||
|
||||
Feedback runs through a per-channel `feedbackState` updated after each sample's cascade output. Clamping `feedback ≤ 0.95` keeps the loop strictly under unity gain so the output cannot diverge. 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.
|
||||
@@ -0,0 +1,57 @@
|
||||
# `phaser/schemas`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Zod schemas validating the construction-time options and preset payload of `PhaserPlugin`. 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
|
||||
|
||||
- `PhaserPluginOptionsSchema` — validates all six optional construction fields with their documented ranges.
|
||||
- `initialStages` is `z.number().int().min(2).max(12)` so non-integer stage counts cannot sneak through.
|
||||
- `PhaserPresetDataSchema` — empty strict object; the phaser has no plugin-specific preset payload, so any extra keys are rejected.
|
||||
- All numeric fields use `z.number().finite()` so `NaN` and `Infinity` cannot sneak through.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { PhaserPluginOptionsSchema } from "@suaveplan/audio-fx-plugin-phaser";
|
||||
|
||||
const parsed = PhaserPluginOptionsSchema.parse({ initialDepth: 0.6 });
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
PhaserPluginOptionsSchema,
|
||||
PhaserPresetDataSchema,
|
||||
} from "@suaveplan/audio-fx-plugin-phaser";
|
||||
|
||||
// Driver for an editor form: pull the keys + ranges from the schema.
|
||||
const optionFields = PhaserPluginOptionsSchema.keyof().options;
|
||||
for (const field of optionFields) {
|
||||
form.addRow(field);
|
||||
}
|
||||
|
||||
// Reject foreign preset payloads at the IPC boundary.
|
||||
function isPhaserPreset(blob: unknown): boolean {
|
||||
return PhaserPresetDataSchema.safeParse(blob).success;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `PhaserPluginOptionsSchema: z.ZodObject<{ initialRateHz?, initialDepth?, initialStages?, initialFeedback?, initialWet?, initialCenterHz? }>`
|
||||
- `PhaserPresetDataSchema: z.ZodObject<{}, "strict">`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
`PhaserPresetDataSchema` is `.strict()` so unknown fields are rejected, matching the sibling-plugin policy. The phaser has no plugin-specific preset payload today: every parameter (`rate`, `depth`, `stages`, `feedback`, `wet`, `center`) travels through the universal parameter snapshot that `AbstractAudioFxPlugin` builds, and the empty `presetData` field is a marker that the plugin opts out of plugin-specific state.
|
||||
|
||||
The maximum feedback is `0.95`, not `1.0`. Allowing feedback at unity 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. This boundary is the same as for the chorus, delay, and flanger plugins so all four feedback-bearing effects share one mental model.
|
||||
Reference in New Issue
Block a user