Show a Reverb node as a device with a round screen of its tail (#389)
game-maker / gate (push) Successful in 4m5s
game-maker / suite (push) Successful in 15m49s

The Compressor, the Gain, the Chip and the Envelope are devices in the
graph. The Reverb was still a list of rows, and how long and how dark
its tail rings was left to the numbers.

- The node is a plate of 640x280 in pale lavender. Its inputs are on
  the left, its output on the right.
- A round screen draws the tail as rings out from the sound. A ring's
  radius is its time, as the root of its seconds, on one scale up to
  10.5 s for every Reverb. So a Room is a small disc and a Cave fills
  two thirds.
- A ring is as bright as the tail is loud there, evenly in dB. Its
  colour is how much of the highs is left, from ice to violet, with
  the cut-off of the impulse response (reverbCutoff, now shared).
- The pre-delay is a dark gap round the dot, a dashed ring marks
  -60 dB. Seeded dots stand for the reflections. Wet fades the tail
  against the dot.
- A type of Sound draws the peaks of its sound from the library, each
  against its loudest. It says LOADING until they are there.
- A button for each type, a list of the sounds and four chrome knobs:
  Decay, Damping, Pre-delay and Wet.
- A knob the type does not take is greyed but turns. It goes over to
  Custom with the type's numbers, as one undo step. Picking a sound
  makes the type Sound, also one step.
- NodeParamDrag can write several settings as one drag for that.
- Test hook __gm.reverb.panel(nodeId), for the sound FX and the
  instrument graph.

Tests:
- Unit reverb-view: 10 new tests for the plate, the knob turn, the
  numbers per type, the scale, the tail and the texts. A screen that
  ignores damping turns the highs test red.
- e2e 34 (atx): 1 new check (device between its ports, decay knob of
  a Hall goes to Custom as one undo step, Cave button, a sound from
  the list makes it Sound and draws that sound).
- Probe pictures on stein2: Room, Hall, Cave, Plate, Custom with
  damping 1 and pre-delay 0.3 s, Sound.
- Gate (atx): CHECK EXIT 0, 287 files, 4393 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-10 16:35:59 +02:00
1 parent c75e72d4ee
commit e8021b1fd3
11 files changed
+1249 -29

No files matched your search

+11 -1
View File
@@ -1244,7 +1244,17 @@ const REVERB_DAMPING = { ...numberRow('damping', 'Damping', 0, 1, 0.01), default
const PREDELAY = { ...numberRow('predelay', 'Pre-delay (s)', 0, 0.5, 0.001), default: 0.01 };
const REVERB_MIX = { ...numberRow('mix', 'Wet', 0, 1, 0.01), default: 0.3 };
function reverbRoomOf(settings: PropertyBag): ReverbRoom {
/** The numbers of a Reverb, for its device (#389). */
export const REVERB_ROWS = {
decay: REVERB_DECAY,
damping: REVERB_DAMPING,
predelay: PREDELAY,
mix: REVERB_MIX,
};
export const REVERB_ROOM_KEY = REVERB_ROOM.key;
export const REVERB_IMPULSE_KEY = IMPULSE.key;
export function reverbRoomOf(settings: PropertyBag): ReverbRoom {
return REVERB_ROOMS.find((option) => option.value === settings[REVERB_ROOM.key])?.value ?? 'room';
}
+14 -7
View File
@@ -32,11 +32,19 @@ export const REVERB_PRESETS: Record<'room' | 'hall' | 'cave' | 'plate', ReverbSh
export const REVERB_MAX_DECAY = 10;
/** Where the highs of a fully damped tail have fallen to by its end, and an undamped one stays. */
const OPEN_HZ = 20000;
const DAMPED_HZ = 500;
export const OPEN_HZ = 20000;
export const DAMPED_HZ = 500;
/** A small, fast generator, so every voice gets the same noise and a check the same numbers. */
function noise(seed: number): () => number {
/** Where the highs of a tail are cut `share` of the way through it, 0 to 1, at this damping. */
export function reverbCutoff(damping: number, share: number): number {
return OPEN_HZ * (DAMPED_HZ / OPEN_HZ) ** (Math.max(0, Math.min(1, damping)) * share);
}
/**
* A small, fast generator, so every voice gets the same noise and a check the same numbers; the
* Reverb's device (#389) scatters its reflections with it.
*/
export function seededNoise(seed: number): () => number {
let state = seed >>> 0;
return () => {
state = (state + 0x6d2b79f5) >>> 0;
@@ -54,14 +62,13 @@ function noise(seed: number): () => number {
*/
export function reverbImpulse(sampleRate: number, decay: number, damping: number): Float32Array[] {
const length = Math.max(1, Math.round(Math.min(REVERB_MAX_DECAY, Math.max(0.01, decay)) * sampleRate));
const shut = Math.max(0, Math.min(1, damping));
return [1, 2].map((seed) => {
const next = noise(seed);
const next = seededNoise(seed);
const out = new Float32Array(length);
let low = 0;
for (let i = 0; i < length; i++) {
const t = i / length;
const cutoff = OPEN_HZ * (DAMPED_HZ / OPEN_HZ) ** (shut * t);
const cutoff = reverbCutoff(damping, t);
const keep = Math.exp((-2 * Math.PI * cutoff) / sampleRate);
low = (1 - keep) * next() + keep * low;
out[i] = low * 10 ** (-3 * t);