first commit

This commit is contained in:
2026-05-29 08:20:56 +01:00
commit 949299f58d
6368 changed files with 527176 additions and 0 deletions

View File

@@ -0,0 +1,78 @@
# `glados.types`
## Purpose
TypeScript-only type definitions for `GLaDOSPlugin`'s preset enum, stage configuration, construction options, and preset payload. Co-located with the implementation so the public type surface stays aligned with the Zod validator in `schemas/schemas.ts`.
`GLaDOSPreset` is the canonical preset-name enum; saved presets travel by name (not numeric index) so a future revision adding new presets does not break stored data.
## Features
- `GLaDOSPreset` — textual enum covering the six built-in presets.
- `GLaDOSConfig` — six numeric stage values, all `readonly`.
- `GLaDOSPluginOptions` — `preset?` selects a starting preset; `config?` supplies a `Partial<GLaDOSConfig>` overlay applied on top.
- `GLaDOSPresetData` — preset payload carrying both `preset` (intent) and the full `config` (effective state) so restore is deterministic.
## Usage: Basic Example
```ts
import type {
GLaDOSConfig,
GLaDOSPluginOptions,
GLaDOSPreset,
} from "@suaveplan/audio-fx-plugin-glados";
const opts: GLaDOSPluginOptions = { preset: "wheatley" };
const preset: GLaDOSPreset = opts.preset ?? "glados-mainline";
```
## Usage: Advanced Example
```ts
import type {
GLaDOSConfig,
GLaDOSPluginOptions,
GLaDOSPreset,
} from "@suaveplan/audio-fx-plugin-glados";
function blend(a: GLaDOSConfig, b: GLaDOSConfig, t: number): GLaDOSConfig {
return {
pitchShift: a.pitchShift + (b.pitchShift - a.pitchShift) * t,
formantShift: a.formantShift + (b.formantShift - a.formantShift) * t,
roboticIntensity:
a.roboticIntensity + (b.roboticIntensity - a.roboticIntensity) * t,
distortionAmount:
a.distortionAmount + (b.distortionAmount - a.distortionAmount) * t,
lowPassCutoff: a.lowPassCutoff + (b.lowPassCutoff - a.lowPassCutoff) * t,
reverbAmount: a.reverbAmount + (b.reverbAmount - a.reverbAmount) * t,
};
}
const opts: GLaDOSPluginOptions = {
preset: "robotic-generic",
config: { roboticIntensity: 0.9, reverbAmount: 0.3 },
};
```
## API Reference
- `GLaDOSPreset` = `"glados-mainline" | "glados-test-chamber" | "glados-cake-deception" | "wheatley" | "cave-johnson" | "robotic-generic"`
- `GLaDOSConfig`
- `readonly pitchShift: number` (-12..12 semitones)
- `readonly formantShift: number` (-12..12 semitones)
- `readonly roboticIntensity: number` (0..1)
- `readonly distortionAmount: number` (0..1)
- `readonly lowPassCutoff: number` (20..20 000 Hz)
- `readonly reverbAmount: number` (0..1)
- `GLaDOSPluginOptions`
- `readonly preset?: GLaDOSPreset`
- `readonly config?: Partial<GLaDOSConfig>`
- `GLaDOSPresetData`
- `readonly preset: GLaDOSPreset`
- `readonly config: GLaDOSConfig`
## Implementation Notes
`GLaDOSPresetData` encodes both the preset name *and* the live config because the user can apply a preset and then override individual stages — restoring the preset name alone would lose those overrides. Storing both keeps restore deterministic. The pattern matches the audio-analyzer plugin's `AudioAnalyzerPresetData` which encodes both the preset name and the toggle state for the same reason.
`GLaDOSPluginOptions.config` is a `Partial<GLaDOSConfig>` overlay applied on top of the selected preset (or the default config when no preset is given). This lets callers express things like "start with the test-chamber preset but with extra reverb" in a single options object without two function calls.

View File

@@ -0,0 +1,76 @@
# `glados`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
`GLaDOSPlugin` is the universal voice-effect insert. It runs the input signal through a fixed six-stage chain — granular pitch shift → formant tilt → robotic-intensity tremolo → soft-clip distortion → low-pass filter → short feedback reverb — and exposes a typed setter for each stage plus six named presets. The plugin is an insert (`io.inputs[0].count === io.outputs[0].count === "preserve"`), so it accepts any input channel count and emits the same shape.
The plugin extends `AbstractAudioFxPlugin`, runs identically across browser / server / worklet renderers, and validates every setter at the boundary: out-of-range values throw `AUDIO_FX.INVALID_OPTIONS` with the offending value attached. Six built-in presets cover the GLaDOS family (mainline, test chamber, cake deception) plus Wheatley, Cave Johnson, and a generic robotic voice.
## Features
- Six chained stages, each with a typed setter: `setPitchShift`, `setFormantShift`, `setRoboticIntensity`, `setDistortionAmount`, `setLowPassCutoff`, `setReverbAmount`.
- Six built-in presets (`glados-mainline`, `glados-test-chamber`, `glados-cake-deception`, `wheatley`, `cave-johnson`, `robotic-generic`) selected via the construction `preset` option or `applyGladosPreset(name)`.
- `getGLaDOSPresets()` exposes the read-only preset table so editors can render preset dropdowns and preview the values.
- Per-stage range validation: `pitchShift`/`formantShift` ∈ [-12, 12] semitones, `roboticIntensity`/`distortionAmount`/`reverbAmount` ∈ [0, 1], `lowPassCutoff` ∈ [20, 20 000] Hz.
- `applyGladosPreset` is named distinctly from `AbstractAudioFxPlugin.applyPreset(snapshot)` so the two methods do not collide.
## Usage: Basic Example
```ts
import { GLaDOSPlugin } from "@suaveplan/audio-fx-plugin-glados";
const glados = new GLaDOSPlugin({ preset: "glados-mainline" });
chain.add(glados);
```
## Usage: Advanced Example
```ts
import { GLaDOSPlugin, getGLaDOSPresets } from "@suaveplan/audio-fx-plugin-glados";
const presetNames = Object.keys(getGLaDOSPresets()) as ReadonlyArray<
keyof ReturnType<typeof getGLaDOSPresets>
>;
const voice = new GLaDOSPlugin({ preset: "wheatley" });
await voice.setup(
{ sampleRate: 48_000, channels: 2, blockSize: 512 },
ctx
);
// Customise the wheatley preset with extra reverb.
voice.setReverbAmount(0.4);
voice.setLowPassCutoff(5_500);
// Preview every preset on the same line of dialogue.
for (const name of presetNames) {
voice.applyGladosPreset(name);
await player.playDemo();
}
```
## API Reference
- `GLaDOSPlugin` (class extends `AbstractAudioFxPlugin`)
- `new GLaDOSPlugin(options?: GLaDOSPluginOptions)` — throws `AUDIO_FX.INVALID_OPTIONS` on malformed options.
- `applyGladosPreset(name: GLaDOSPreset): void`, `getCurrentPreset(): GLaDOSPreset`, `getGladosConfig(): GLaDOSConfig`.
- `setPitchShift(-12..12)`, `setFormantShift(-12..12)`, `setRoboticIntensity(0..1)`, `setDistortionAmount(0..1)`, `setLowPassCutoff(20..20000)`, `setReverbAmount(0..1)` — each throws `AUDIO_FX.INVALID_OPTIONS` on out-of-range.
- `getGLaDOSPresets()` — read-only view of the six built-in presets.
- Types: `GLaDOSPreset` literal union, `GLaDOSConfig`, `GLaDOSPluginOptions`, `GLaDOSPresetData`.
## Implementation Notes
The pitch-shift stage is a per-channel granular ring buffer with linear-interpolated fractional read — the same shape used by `PitchShifterPlugin` but with a smaller window because vocal content does not need the larger windows that drum or instrumental material benefits from. For pitch shifts within the GLaDOS-typical range (-5 to +5 semitones) this produces an audibly clean result. Going further out is supported but the perceptual artefacts increase.
The formant-tilt stage is a simple gain shift (`10^(formantShift / 8)`) rather than a true formant analyzer. A real formant-shift implementation needs spectral analysis + cepstrum manipulation, which is much heavier and would not measurably improve the GLaDOS character. The simple gain matches what most game-audio implementations use for "robotic voice" effects.
The robotic-intensity stage is a 30 Hz tremolo modulating amplitude — fast enough to read as "robotic clicking" rather than "vibrato". The depth is mapped linearly from the `roboticIntensity` knob so the user-facing range `[0, 1]` translates to `0` (no tremolo) through `1` (full chopping). The 30 Hz rate is faster than typical tremolo because that's what makes the voice sound mechanical rather than emotional.
The distortion stage is a `tanh` soft-clip with parallel dry/wet routing — same shape as the `DistortionPlugin`'s waveshaper mode but with a fixed drive that scales with the user's `distortionAmount` knob.
The low-pass is a one-pole filter (the same shape used by `MicrophoneSimulationPlugin`), and the reverb is a short feedback delay (`REVERB_DELAY_MS = 30 ms` per channel). Both are intentionally simpler than their dedicated sibling-plugin counterparts because the GLaDOS character does not need surgical control — the user wants "more or less" of each colour, not a fully parametric filter and convolution.
Preset application replaces the *entire* config in one shot via `{ ...PRESETS[preset] }`. Individual setter overrides applied after `applyGladosPreset` will stick; the next preset application replaces them. The preset payload encodes both the preset name and the live config so `applyPreset(snap)` restores any per-stage overrides made after the preset was originally applied.

View File

@@ -0,0 +1,60 @@
# `glados/schemas`
<!-- begin:auto-docs -->
_(no submodules)_
<!-- end:auto-docs -->
## Purpose
Zod schemas validating the construction-time options, stage configuration, preset enum, and preset payload of `GLaDOSPlugin`. 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
- `GLaDOSPresetSchema` — `z.enum([...six preset names])` reused in options + preset payload.
- `GLaDOSConfigSchema` — validates the six stage values with their documented ranges.
- `GLaDOSPluginOptionsSchema` — `preset?: enum, config?: Partial<GLaDOSConfig>` (via `.partial()`).
- `GLaDOSPresetDataSchema` — `.strict()` object carrying the preset name + full config.
## Usage: Basic Example
```ts
import { GLaDOSConfigSchema } from "@suaveplan/audio-fx-plugin-glados";
const parsed = GLaDOSConfigSchema.partial().parse({
pitchShift: -3,
roboticIntensity: 0.6,
});
```
## Usage: Advanced Example
```ts
import {
GLaDOSPresetDataSchema,
GLaDOSPresetSchema,
} from "@suaveplan/audio-fx-plugin-glados";
// Render a dropdown of presets from the schema's options.
const names = GLaDOSPresetSchema.options;
for (const name of names) ui.addOption(name);
// Strict preset validation at the IPC boundary.
function isGladosPreset(blob: unknown): boolean {
return GLaDOSPresetDataSchema.safeParse(blob).success;
}
```
## API Reference
- `GLaDOSPresetSchema: z.ZodEnum<[...six]>`
- `GLaDOSConfigSchema: z.ZodObject<{ pitchShift, formantShift, roboticIntensity, distortionAmount, lowPassCutoff, reverbAmount }>`
- `GLaDOSPluginOptionsSchema: z.ZodObject<{ preset?, config? }>`
- `GLaDOSPresetDataSchema: z.ZodObject<{ preset, config }, "strict">`
## Implementation Notes
`GLaDOSPresetSchema` is exported as a named schema (not inlined) so the same value is reused by `GLaDOSPluginOptionsSchema.preset` and `GLaDOSPresetDataSchema.preset`. That guarantees both validation paths agree byte-for-byte on the legal preset set — adding a seventh preset is a one-line change in `schemas.ts`.
`GLaDOSPresetDataSchema` is `.strict()` so unknown fields are rejected — matching the sibling-plugin policy. The preset payload carries both the preset name and the live config; storing only the name would lose per-stage overrides made after the preset was originally applied. The schema validates the full config so a forwards-incompatible payload is caught at the boundary.