Open a Compressor as a device in a window of one size (#379)

The tab from #378 shows a Compressor as an editor. The user wanted it
to look like a device as well, after a brushed aluminium plug-in, with
a meter of the gain reduction instead of the pictures. The tab stays
and is switched to per node.

- Hardware look is a switch under Settings, on by default for new and
  existing Compressors. Only the editor reads it.
- The panel is 820x280 px and opens straight in a window of that size.
  The window has no sizing corner and no Dock. A pane says so through
  `fixedSize`, and `keptSize` keeps the window's middle when it
  changes to that size.
- A Compressor in this look never sits in the tab row. Opening one
  floats it, and switching the look on moves its tab into the window.
- The panel has chrome knobs (threshold and makeup large), a meter
  with a needle that moves like a VU's, an LCD with the name and the
  reading, a lever with a lamp for auto-gain, and LEDs for the level
  going out. Everything is drawn in code.
- The Knob takes a size and a chrome look, and leaves out its caption
  when it has no name, so the panel engraves its own.
- `autoGainOn` is now `switchOn`, because the new switch reads the
  same way.
- Where parts sit, the meter's scale, the needle's speed, the LEDs and
  the plate's grain are in `hardwareView.ts`, apart from the drawing.

Decided myself:
- The meter's scale runs by the square root, so the first dB is about
  as wide as the last nine. It is red from 10 dB on.
- The LEDs light one every 3 dB from -39 to 0 dB and fall by 20 dB a
  second.
- Switching the look changes the graph's fingerprint, like any other
  setting of a node. The sound FX is built again once.

Findings, fixed on the way:
- Pixi's `arc` goes on from the last point of the path. The meter's
  scale had a line from the corner of the face until each arc got a
  `moveTo` to its own start.
- A text put into a Graphics makes Pixi warn that only Containers take
  children. The LCD is a Container now.
- The first browser check read the needle at one moment and missed the
  short sound. The panel keeps the most its needle and LEDs showed.

Tests:
- Unit: hardware-view (10 new), documents (1 new)
- e2e 34 (melektaus-atx): ALL PASSED, 122 checks (4 new). Played,
  the needle reached 20.9 dB and 5 LEDs lit.
- Gate (melektaus-atx): CHECK EXIT 0, 283 files, 4355 tests (11 new)
- Counter-checks: a window that keeps its corner instead of its
  middle, and a linear meter scale, each turn exactly their test red.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-10 02:06:56 +02:00
1 parent 2d4909d248
commit 9917253751
14 files changed
+1153 -52

No files matched your search

+15 -7
View File
@@ -1015,12 +1015,20 @@ export const COMPRESS_AUTO_GAIN = {
default: false,
};
/** Whether a node's auto-gain is on: its switch, or its default where none was ever set. */
export function autoGainOn(settings: PropertyBag, row: { key: string; default: boolean }): boolean {
/** Whether a node's switch is on: as it was set, or its default where it never was. */
export function switchOn(settings: PropertyBag, row: { key: string; default: boolean }): boolean {
const value = settings[row.key];
return value === undefined ? row.default : booleanOf(value);
}
/** Whether a Compressor's editor is the hardware panel (#379) or the tab of #378. Only its editor reads it. */
export const COMPRESS_HARDWARE_LOOK = {
key: 'hardwareLook',
label: 'Hardware look',
kind: 'boolean' as const,
default: true,
};
/** The numbers of a Compressor, in the order its tab shows them as knobs (#378). */
export const COMPRESS_ROWS = {
threshold: COMPRESS_THRESHOLD,
@@ -1038,11 +1046,11 @@ export const SoundFxCompressor: SoundFxNodeDef = effect(
category: 'sound',
tapped: true,
description:
'Squeezes the loudness of the sound together: what goes above "threshold" (dB) comes out only 1/"ratio" as much louder, so 4 makes 8 dB over it 2 dB. "knee" (dB) eases into that from the threshold on instead of at once. "attack" is how fast it turns down once the sound is over the threshold, "release" how fast it lets go, both in seconds. "makeup" (dB) raises it again afterwards. "Auto-gain" on gives back what it takes from a sound at -18 dB, so that one comes out as loud as it went in; the makeup is added on top. It is 6 ms late, which a voice, a Mix and a bus make up for. All of it can change while the sound plays. Double-click the node to open its editor: a knob for each number, its curve with threshold and ratio to drag, and the level coming in and going out over the last seconds.' +
'Squeezes the loudness of the sound together: what goes above "threshold" (dB) comes out only 1/"ratio" as much louder, so 4 makes 8 dB over it 2 dB. "knee" (dB) eases into that from the threshold on instead of at once. "attack" is how fast it turns down once the sound is over the threshold, "release" how fast it lets go, both in seconds. "makeup" (dB) raises it again afterwards. "Auto-gain" on gives back what it takes from a sound at -18 dB, so that one comes out as loud as it went in; the makeup is added on top. It is 6 ms late, which a voice, a Mix and a bus make up for. All of it can change while the sound plays. Double-click the node to open its editor: with "Hardware look" on, a panel of a fixed size in a window, with a meter of what it takes away; off, a tab with a knob for each number, its curve with threshold and ratio to drag, and the level coming in and going out over the last seconds.' +
BLENDS,
inputs: [audioIn, ...Object.values(COMPRESS_ROWS).map(numberIn)],
outputs: [audioOut],
params: [COMPRESS_AUTO_GAIN],
params: [COMPRESS_AUTO_GAIN, COMPRESS_HARDWARE_LOOK],
emit: (sounds, amounts, settings) =>
withStages(arriving(sounds), [
{
@@ -1053,7 +1061,7 @@ export const SoundFxCompressor: SoundFxNodeDef = effect(
attack: held(amounts.attack, COMPRESS_ATTACK),
release: held(amounts.release, COMPRESS_RELEASE),
makeup: held(amounts.makeup, MAKEUP),
autoGain: autoGainOn(settings, COMPRESS_AUTO_GAIN),
autoGain: switchOn(settings, COMPRESS_AUTO_GAIN),
},
]),
},
@@ -1088,7 +1096,7 @@ export const SoundFxLimiter: SoundFxNodeDef = effect(
threshold: held(amounts.threshold, LIMIT_THRESHOLD),
ceiling: held(amounts.ceiling, CEILING),
release: held(amounts.release, LIMIT_RELEASE),
autoGain: autoGainOn(settings, LIMIT_AUTO_GAIN),
autoGain: switchOn(settings, LIMIT_AUTO_GAIN),
},
]),
},
@@ -1139,7 +1147,7 @@ export const SoundFxSaturation: SoundFxNodeDef = effect(
drive: held(amounts.drive, DRIVE),
tone: held(amounts.tone, TONE),
output: held(amounts.output, OUTPUT),
autoGain: autoGainOn(settings, SATURATION_AUTO_GAIN),
autoGain: switchOn(settings, SATURATION_AUTO_GAIN),
mix,
},
]);