Show a Pan node as a one-knob device in cyan (#392)
game-maker / gate (push) Successful in 4m13s
game-maker / suite (push) Failing after 15m23s

After the Gain, the Pitch and the Mix, the Pan is the next node that
is just one number. It now gets the same device in the graph instead
of a row for its pan.

- PAN_DEVICE: the Gain's 160x160 plate in cyan with one bipolar knob
  for Pan. The middle stands straight up, and the turn goes evenly to
  L and R, which are engraved at its ends like A and B on the Mix.
- PAN_TAPER in hardwareView is 1, so left and right are heard alike.
  Device and unit test read the same constant.
- PAN_ROW is exported from the core, so the knob has the node's range.
- A linked Pan greys the knob, as for the Gain.
- The test hook __gm.pan.panel returns the same shape as the Gain's.
- No instrument preset uses a Pan, so no preset columns change.

Tests:
- Unit hardware-view: 17 tests, 2 new (the turn, and L and R inside
  the plate and clear of the screws). Counter-check: PAN_TAPER 2
  turns the turn test red.
- e2e 34 (melektaus-atx): 135 checks, 1 new.
- Gate (melektaus-atx): 287 files, 4399 tests.
- Pictures looked at: Gain, Pitch, Mix and Pan in a row, Pan at -0.35.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-10 18:26:17 +02:00
1 parent fe4e0d9f1a
commit 389d9555fa
8 files changed
+102 -12

No files matched your search

+35 -2
View File
@@ -1,7 +1,8 @@
/**
* The Compressor's hardware panel (#379): every part on the plate and none over another, the meter's
* scale and needle, the LEDs, and the grain of the plate the same every time. The Gain's plate
* (#384) and how its one knob turns, the Pitch's knob and the Mix's lever and ends (#390).
* (#384) and how its one knob turns, the Pitch's knob and the Mix's lever and ends (#390), the
* Pan's knob and ends on the Gain's plate (#392).
*/
import { describe, expect, it } from 'vitest';
import {
@@ -27,11 +28,12 @@ import {
MIX_PANEL,
needleAngle,
needleStep,
PAN_TAPER,
PANEL,
PANEL_LAYOUT,
PITCH_TAPER,
} from '../../src/editor/soundfx/hardwareView';
import { GAIN_ROW, PITCH_ROW } from '../../src/core/soundfx/nodes';
import { GAIN_ROW, PAN_ROW, PITCH_ROW } from '../../src/core/soundfx/nodes';
import { knobPosition } from '../../src/ui/knob';
const overlap = (a: Box, b: Box) => a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h;
@@ -215,3 +217,34 @@ describe('the Pitch and the Mix (#390)', () => {
expect(a.y).toBe(b.y);
});
});
describe('the Pan (#392)', () => {
it('turns from the middle straight up, as far left as right, the whole turn for -1 to 1', () => {
const range = {
min: PAN_ROW.min ?? 0,
max: PAN_ROW.max ?? 0,
step: 0.01,
taper: PAN_TAPER,
bipolar: true,
};
expect(knobPosition(0, range)).toBeCloseTo(0.5, 5);
expect(knobPosition(0.5, range)).toBeCloseTo(0.75, 5);
expect(knobPosition(-0.5, range)).toBeCloseTo(0.25, 5);
expect(knobPosition(-1, range)).toBe(0);
expect(knobPosition(1, range)).toBe(1);
});
it('names L and R at the ends of the turn inside the Gain’s plate, clear of the screws', () => {
const spot = GAIN_LAYOUT.knob;
const { a, b } = knobEnds(spot);
for (const end of [a, b]) {
expect(end.x).toBeGreaterThan(0);
expect(end.x).toBeLessThan(GAIN_PANEL.w);
expect(end.y).toBeLessThan(GAIN_PANEL.h);
for (const screw of GAIN_LAYOUT.screws)
expect(Math.hypot(end.x - screw.x, end.y - screw.y)).toBeGreaterThan(14);
}
expect(a.x).toBeLessThan(spot.x);
expect(b.x).toBeGreaterThan(spot.x);
});
});