A Limiter only said what it does in rows of numbers. How much it took away, and when, could not be seen at all, and a short sound is over before any needle could show it. Now the node is a device of its own size, and its screen catches the reduction from the moment a sound starts and holds it once the sound ends. - LimiterPanel draws a 720x280 plate of brushed brick. The threshold knob is large, ceiling and release are small beside it, auto-gain is a lever. A LIMIT lamp lights while it takes something away. - The screen hangs the reduction down from the top, 0 to 24 dB by the square root like the Compressor's meter. The level coming in runs along the bottom, orange above the limit. Time runs from the start of the sound over 1, 2, 4 or 8 s, and a longer sound rolls. - ReductionCapture in limiterView holds the capture after 0.4 s of silence, without the silence, until the next sound. It writes the most taken away and when, and NOW or LASTED on the right. - The Limiter is tapped now, like the Compressor. The face reads the loudest sample since the last frame on both sides. - SpectrumTaps.levels takes a lateBy. The side in front is read 5 ms earlier, so a peak going in is met by what it became. - limiterReduction leaves the auto-gain out and solves Wet's blend for the limited part. Less than 0.2 dB counts as none. - A capture is also held when the node leaves the picture. Changing a knob or the lever leaves it as it is. - Hook __gm.limiter.panel(id) for the sound FX and instrument graphs. Decided myself: - The capture stays held until the next sound, not for a fixed time. - The screen has no wet knob, as the Compressor's has none. - The Limiter's two taps cost two gains of 1 per voice. The analysers only exist while the node is in the picture. Measured on stein2: the seeded blip at -4.4 dBFS against -12 dB showed 7.56 dB. Three hits at 0, -8 and -2 dBFS showed 11.9 dB at the first. In e2e 34 on melektaus-atx, -10.44 dBFS in against -20 dB showed 9.56 dB, the difference to the hundredth. Tests: - Unit: limiter-view 13 tests (new), spectrum 6 (1 new), soundfx 67 with the Limiter's taps. - Counter-test: without Wet's share in the formula the blend test fails. - e2e 34: ALL PASSED, 138 checks (1 new). - Gate: CHECK EXIT 0, 290 files, 4445 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
45 lines
2.2 KiB
TypeScript
45 lines
2.2 KiB
TypeScript
import type { GraphNode } from '../../core/graph';
|
||
import { InstrumentChip, InstrumentEnvelope } from '../../core/instrument/nodes';
|
||
import {
|
||
SoundFxCompressor,
|
||
SoundFxDelay,
|
||
SoundFxFilter,
|
||
SoundFxLimiter,
|
||
SoundFxParametricEq,
|
||
SoundFxReverb,
|
||
SoundFxSaturation,
|
||
} from '../../core/soundfx/nodes';
|
||
import type { NodeFace } from '../graph/nodeFace';
|
||
import type { EditorState } from '../state';
|
||
import { ChipFace } from './ChipFace';
|
||
import { CompressorFace } from './CompressorFace';
|
||
import { CurveFace } from './CurveFace';
|
||
import { DelayFace } from './DelayFace';
|
||
import { EnvelopeFace } from './EnvelopeFace';
|
||
import { EqPanel } from './EqPanel';
|
||
import { FilterPanel } from './FilterPanel';
|
||
import { KnobFace } from './KnobFace';
|
||
import { GAIN_DEVICE, MIX_DEVICE, PAN_DEVICE, PITCH_DEVICE } from './KnobPanel';
|
||
import { LimiterFace } from './LimiterFace';
|
||
import { ReverbFace } from './ReverbFace';
|
||
import { SaturationFace } from './SaturationFace';
|
||
|
||
/** The nodes drawn as a face of their own instead of rows (#380, #381, #384–#386, #388–#390, #392, #393, #396, #397), by type. */
|
||
export function nodeFaceFor(state: EditorState, assetId: string, node: GraphNode): NodeFace | null {
|
||
if (node.type === InstrumentChip.type) return new ChipFace(state, assetId, node.id);
|
||
if (node.type === InstrumentEnvelope.type) return new EnvelopeFace(state, assetId, node.id);
|
||
if (node.type === SoundFxCompressor.type) return new CompressorFace(state, assetId, node.id);
|
||
if (node.type === SoundFxParametricEq.type)
|
||
return new CurveFace(new EqPanel(state, true), assetId, node.id);
|
||
if (node.type === SoundFxFilter.type) return new CurveFace(new FilterPanel(state, true), assetId, node.id);
|
||
const knobbed = [GAIN_DEVICE, PITCH_DEVICE, MIX_DEVICE, PAN_DEVICE].find(
|
||
(device) => device.type === node.type,
|
||
);
|
||
if (knobbed) return new KnobFace(state, assetId, node.id, knobbed);
|
||
if (node.type === SoundFxReverb.type) return new ReverbFace(state, assetId, node.id);
|
||
if (node.type === SoundFxDelay.type) return new DelayFace(state, assetId, node.id);
|
||
if (node.type === SoundFxSaturation.type) return new SaturationFace(state, assetId, node.id);
|
||
if (node.type === SoundFxLimiter.type) return new LimiterFace(state, assetId, node.id);
|
||
return null;
|
||
}
|