Show a Reverb node as a device with a round screen of its tail (#389)
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:
1 parent
c75e72d4ee
commit
e8021b1fd3
11 files changed
+1249
-29
No files matched your search
@@ -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';
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in new issue
Block a user