first commit
This commit is contained in:
31
docs/media/audio-fx/audio-fx-plugin-microphone/README.md
Normal file
31
docs/media/audio-fx/audio-fx-plugin-microphone/README.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# @suaveplan/audio-fx-plugin-microphone - microphone plugin for the @suaveplan/audio-fx-plugin-* family.
|
||||
|
||||
microphone 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
|
||||
|
||||
- [microphone](./src/microphone/microphone.md) — `MicrophoneSimulationPlugin` is the universal microphone-simulation source plugin. It synthesises a microphone-coloured…
|
||||
- [microphone](./src/microphone/microphone.md) — `MicrophoneSimulationPlugin` is the universal microphone-simulation source plugin. It synthesises a microphone-coloured…
|
||||
- [schemas](./src/schemas/schemas.md) — Zod schemas validating the construction-time options, the eight control groups, the full configuration tree, and the pr…
|
||||
- [schemas](./src/schemas/schemas.md) — Zod schemas validating the construction-time options, the eight control groups, the full configuration tree, and the pr…
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Composition
|
||||
|
||||
```ts
|
||||
import { PluginChain, DefaultBusRegistry } from "@suaveplan/audio-fx-plugin-base/chain";
|
||||
import { /* Plugin */ } from "@suaveplan/audio-fx-plugin-microphone";
|
||||
|
||||
const plugin = new /* Plugin */();
|
||||
const chain = new PluginChain({
|
||||
buses: new DefaultBusRegistry(),
|
||||
plugins: [plugin],
|
||||
});
|
||||
```
|
||||
|
||||
## Status
|
||||
|
||||
Replaces the `./microphone` subpath of the bundled `@suaveplan/audio-plugins-fx`.
|
||||
@@ -0,0 +1,70 @@
|
||||
# `microphone.types`
|
||||
|
||||
## Purpose
|
||||
|
||||
TypeScript-only type definitions for `MicrophoneSimulationPlugin`'s control groups, configuration tree, preset payload, and UI section descriptor. Co-located with the implementation so the public type surface stays aligned with the Zod validator in `schemas/schemas.ts`. The control-group split (FrequencyControls, ResonanceControls, …) mirrors the main-branch `microphone.types.ts` so a UI built against either codebase can render the other.
|
||||
|
||||
## Features
|
||||
|
||||
- Eight `*Controls` interfaces, one per microphone signal stage.
|
||||
- `MicrophoneConfig` aggregates them under named sections; consumers can pass `Partial<MicrophoneConfig>` to merge with current state.
|
||||
- `MicrophoneSimulationPluginOptions` is a thin shell carrying the partial config.
|
||||
- `MicrophonePresetData` carries the full live config so a preset restores every section.
|
||||
- `MicrophoneUIControl` / `MicrophoneUISection` describe the UI without prescribing a renderer.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import type {
|
||||
MicrophoneConfig,
|
||||
MicrophoneSimulationPluginOptions,
|
||||
} from "@suaveplan/audio-fx-plugin-microphone";
|
||||
|
||||
const opts: MicrophoneSimulationPluginOptions = {
|
||||
config: { noise: { level: 0.1, color: "pink" } },
|
||||
};
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import type {
|
||||
MicrophoneConfig,
|
||||
MicrophoneUISection,
|
||||
} from "@suaveplan/audio-fx-plugin-microphone";
|
||||
|
||||
function indexControlsById(
|
||||
sections: ReadonlyArray<MicrophoneUISection>
|
||||
): Record<string, MicrophoneUISection["controls"][number]> {
|
||||
const out: Record<string, MicrophoneUISection["controls"][number]> = {};
|
||||
for (const section of sections) {
|
||||
for (const control of section.controls) {
|
||||
out[control.id] = control;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function presetFrom(config: MicrophoneConfig): MicrophoneConfig {
|
||||
return { ...config };
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `FrequencyControls`, `ResonanceControls`, `DistortionControls`, `NoiseControls`, `DynamicsControls`, `GainControls`, `CharacterControls`, `WowAndFlutterControls` — one `readonly`-fielded interface per signal stage.
|
||||
- `MicrophoneConfig` — the eight control groups composed into one object.
|
||||
- `MicrophoneSimulationPluginOptions` — `{ config?: Partial<MicrophoneConfig> }`.
|
||||
- `MicrophonePresetData` — `{ config: MicrophoneConfig }`.
|
||||
- `MicrophoneUIControl` — `{ id, label, min, max, step, unit, group }`.
|
||||
- `MicrophoneUISection` — `{ id, label, controls: ReadonlyArray<MicrophoneUIControl> }`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The eight `*Controls` interfaces are kept distinct (not collapsed into one flat record) because:
|
||||
|
||||
1. UI code can iterate the sections rather than the flat field list, producing a grouped form for free.
|
||||
2. `Partial<MicrophoneConfig>` provides per-section partials; consumers can pass `setMicrophoneConfig({ noise: { level: 0.7 } })` without restating other fields.
|
||||
3. The Zod schema for each section is reusable in RPC contracts that target one section at a time.
|
||||
|
||||
`MicrophoneUIControl.group` is typed as `keyof MicrophoneConfig` (a string literal union over the eight section names) rather than a freeform string so the type system enforces consistency: adding a section requires updating the interface name, the schema, and the UI descriptor in lockstep, all caught at compile time.
|
||||
@@ -0,0 +1,89 @@
|
||||
# `microphone`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
`MicrophoneSimulationPlugin` is the universal microphone-simulation source plugin. It synthesises a microphone-coloured signal from a deterministic noise generator (selectable white or pink) passed through a one-pole low-pass filter, an input/output gain stage, optional `tanh`-soft-clip distortion, a "warmth + presence" tonal stage, and wow/flutter amplitude modulation. The plugin is a source (`io.inputs[0].count === 0`, `io.outputs[0].count === 1`).
|
||||
|
||||
The configuration is grouped into eight control families (frequency, resonance, distortion, noise, dynamics, gain, character, wowFlutter); each family is defined as a structured interface so editors can render the UI by iterating `getUISections()`. The plugin extends `AbstractAudioFxPlugin` and runs identically across browser / server / worklet renderers.
|
||||
|
||||
## Features
|
||||
|
||||
- Eight control groups (`MicrophoneConfig`): `frequency`, `resonance`, `distortion`, `noise`, `dynamics`, `gain`, `character`, `wowFlutter`. Each is a typed `*Controls` interface with a Zod schema and a UI section descriptor.
|
||||
- One automatable parameter: `outputGain` (0..4) for top-of-chain level.
|
||||
- `getUISections()` returns a `ReadonlyArray<MicrophoneUISection>` with stable `id`s, min/max/step/unit/group metadata, so editors can render the full surface without hard-coding any of it.
|
||||
- Deterministic noise generator (xorshift32 RNG); `reset()` re-seeds so two reset-then-process runs produce byte-identical output — useful for regression testing.
|
||||
- `createMicrophonePlugin(options)` factory mirrors the main-branch surface for migration parity.
|
||||
- Pink-noise mode uses Paul Kellet's three-pole approximation, the same one the main-branch package ships, so the spectral colour matches.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { MicrophoneSimulationPlugin } from "@suaveplan/audio-fx-plugin-microphone";
|
||||
|
||||
const mic = new MicrophoneSimulationPlugin();
|
||||
chain.add(mic);
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
MicrophoneSimulationPlugin,
|
||||
createMicrophonePlugin,
|
||||
} from "@suaveplan/audio-fx-plugin-microphone";
|
||||
|
||||
const lofi = createMicrophonePlugin({
|
||||
config: {
|
||||
frequency: { cutoffHz: 3_500, tiltDb: 3 },
|
||||
noise: { level: 0.2, color: "pink" },
|
||||
distortion: { drive: 0.6, mix: 0.4 },
|
||||
wowFlutter: {
|
||||
wowRateHz: 1.2,
|
||||
wowDepth: 0.3,
|
||||
flutterRateHz: 10,
|
||||
flutterDepth: 0.15,
|
||||
},
|
||||
},
|
||||
});
|
||||
await lofi.setup(
|
||||
{ sampleRate: 48_000, channels: 1, blockSize: 512 },
|
||||
ctx
|
||||
);
|
||||
|
||||
// Drive a UI form from the section descriptor.
|
||||
for (const section of lofi.getUISections()) {
|
||||
form.addSection(section.id, section.label);
|
||||
for (const control of section.controls) {
|
||||
form.addControl(control);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `MicrophoneSimulationPlugin` (class extends `AbstractAudioFxPlugin`)
|
||||
- `new MicrophoneSimulationPlugin(options?: MicrophoneSimulationPluginOptions)` — throws `AUDIO_FX.INVALID_OPTIONS` on malformed options.
|
||||
- `getMicrophoneConfig(): MicrophoneConfig`
|
||||
- `setMicrophoneConfig(partial: Partial<MicrophoneConfig>): void` — merge-updates the live config; missing keys stay at their current values.
|
||||
- `getUISections(): ReadonlyArray<MicrophoneUISection>`
|
||||
- Inherited accessor for `outputGain`.
|
||||
- `createMicrophonePlugin(options?)` — factory equivalent.
|
||||
- Types: `FrequencyControls`, `ResonanceControls`, `DistortionControls`, `NoiseControls`, `DynamicsControls`, `GainControls`, `CharacterControls`, `WowAndFlutterControls`, `MicrophoneConfig`, `MicrophoneUIControl`, `MicrophoneUISection`.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The noise generator is xorshift32 seeded with a fixed constant (`0x12345678`). Determinism matters for testing: the "reset re-seeds" test asserts that two runs after `reset()` produce byte-identical output. A `Math.random()`-based generator would fail that test on every invocation because the RNG state would advance between runs. The xorshift32 output is mapped to `[-1, 1]` via the standard `(u32 / 0xffffffff) * 2 - 1` formula.
|
||||
|
||||
Pink-noise mode uses Paul Kellet's three-pole approximation rather than a true 1/f filter because the latter requires a much higher-order filter to be accurate and would add per-sample CPU cost out of proportion with the perceptual improvement. The three-pole shape is the same as the main-branch implementation, so a side-by-side spectral comparison passes within a couple of dB.
|
||||
|
||||
The filter is a one-pole low-pass (`y = y_prev + α · (x - y_prev)`, with `α = ω / (ω + 1)`), not a biquad. The single-pole shape is what consumer microphones colour the signal with — biquad-class shaping is the territory of the `FilterPlugin` and `EQPlugin` siblings, which a caller can chain downstream. Cutoff is clamped to `[20, 0.45·sr]` so the bilinear transform stays stable across the full range.
|
||||
|
||||
Wow and flutter use two independent sine LFOs modulating the output gain. The wow rate is around 0.6 Hz by default (slow tape drift); flutter is around 8 Hz (fast tape wobble). Both depths are scaled by small constants (0.1 and 0.05 respectively) so the user-facing range `[0, 1]` translates to ±10% and ±5% amplitude variation — perceptually-sensible defaults rather than mathematically-extreme ones.
|
||||
|
||||
`MicrophoneConfig` is consumed as a tree of `Partial<*Controls>` so callers can update only the field they care about. `setMicrophoneConfig` merges with the current state per-section, so a `setMicrophoneConfig({ noise: { level: 0.7 } })` call does **not** require respecifying `color` — the previous value is preserved.
|
||||
|
||||
The plugin declares only one parameter (`outputGain`); every other control lives on the `MicrophoneConfig` tree because they are configuration, not real-time automation. If a future revision wants to automate a specific control (e.g., automate `cutoffHz`), promoting it to a parameter is a one-line change that does not break the section descriptor (which would still list it under `frequency`).
|
||||
@@ -0,0 +1,68 @@
|
||||
# `microphone/schemas`
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
|
||||
## Purpose
|
||||
|
||||
Zod schemas validating the construction-time options, the eight control groups, the full configuration tree, and the preset payload of `MicrophoneSimulationPlugin`. These run on the public boundary so any malformed object 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
|
||||
|
||||
- Eight per-section schemas: `FrequencyControlsSchema`, `ResonanceControlsSchema`, `DistortionControlsSchema`, `NoiseControlsSchema`, `DynamicsControlsSchema`, `GainControlsSchema`, `CharacterControlsSchema`, `WowAndFlutterControlsSchema`. Each enforces its documented range with `z.number().finite()` to block `NaN`/`Infinity`.
|
||||
- `MicrophoneConfigSchema` composes the eight sections into one object.
|
||||
- `MicrophoneSimulationPluginOptionsSchema` wraps `MicrophoneConfigSchema.partial()` so callers can pass any subset of fields.
|
||||
- `MicrophonePresetDataSchema` is `.strict()` and demands the full `MicrophoneConfig` payload.
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { MicrophoneConfigSchema } from "@suaveplan/audio-fx-plugin-microphone";
|
||||
|
||||
const parsed = MicrophoneConfigSchema.partial().parse({
|
||||
noise: { level: 0.05, color: "white" },
|
||||
});
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
MicrophoneConfigSchema,
|
||||
NoiseControlsSchema,
|
||||
} from "@suaveplan/audio-fx-plugin-microphone";
|
||||
|
||||
// Validate a section in isolation (useful for an RPC that updates only one
|
||||
// part of the mic state).
|
||||
function setNoise(plugin: MicrophoneSimulationPlugin, raw: unknown) {
|
||||
const noise = NoiseControlsSchema.parse(raw);
|
||||
plugin.setMicrophoneConfig({ noise });
|
||||
}
|
||||
|
||||
const partial = MicrophoneConfigSchema.partial().parse({
|
||||
frequency: { cutoffHz: 4_000, tiltDb: 0 },
|
||||
});
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
- `FrequencyControlsSchema: z.ZodObject<{ cutoffHz, tiltDb }>`
|
||||
- `ResonanceControlsSchema: z.ZodObject<{ q }>`
|
||||
- `DistortionControlsSchema: z.ZodObject<{ drive, mix }>`
|
||||
- `NoiseControlsSchema: z.ZodObject<{ level, color }>`
|
||||
- `DynamicsControlsSchema: z.ZodObject<{ thresholdDb, ratio }>`
|
||||
- `GainControlsSchema: z.ZodObject<{ inputDb, outputDb }>`
|
||||
- `CharacterControlsSchema: z.ZodObject<{ warmth, presence }>`
|
||||
- `WowAndFlutterControlsSchema: z.ZodObject<{ wowRateHz, wowDepth, flutterRateHz, flutterDepth }>`
|
||||
- `MicrophoneConfigSchema: z.ZodObject<…>`
|
||||
- `MicrophoneSimulationPluginOptionsSchema: z.ZodObject<{ config? }>`
|
||||
- `MicrophonePresetDataSchema: z.ZodObject<{ config }, "strict">`
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
The per-section schemas are exported as named values rather than inlined into `MicrophoneConfigSchema` so they can be reused by callers that want to validate a single section at a time — for example, an RPC endpoint that updates only the noise floor. The combined `MicrophoneConfigSchema` is built by composing the section schemas, so adding a field to a section is a single-place change.
|
||||
|
||||
`MicrophonePresetDataSchema` demands the full `MicrophoneConfig` (not a partial). Presets restore the entire microphone state in one shot; allowing a partial would leave the restored plugin in an indeterminate state where some sections came from the preset and others from whatever happened to be loaded. The `.strict()` modifier additionally rejects unknown fields so a forwards-incompatible preset payload is caught at the boundary rather than silently dropping unknown keys.
|
||||
Reference in New Issue
Block a user