Show Pitch and Mix nodes as one-knob devices in green and blue (#390)
The Gain has been a small device with one big knob since #384. The Pitch and the Mix are just as much a single number, so they now look and work the same way. The Mix also gets a lever for its curve, which until now was only a row in the inspector. - GainPanel and GainFace become KnobPanel and KnobFace. Each node is described by a KnobDevice: its plate, its row, how the knob turns, and an optional lever. The Gain looks as before. - Pitch: a green 160x160 plate with a bipolar knob for Semitones. 0 stands straight up and taper 2 puts an octave at 43 % of each half, so small steps near 0 are easy and the full +-64 is still there. - Mix: a blue 180x200 plate, the lever on top and the knob below. The knob has A and B engraved at the ends of its turn. The lever reads "crossfade" down and "both at center" up with its lamp lit. A flip is one undo step. - A wired Semitones or Mix greys the knob, as for the Gain. - The instrument presets give Pitch and Mix two columns. In the graph they are 281 and 261 units wide, more than one column of 240. - The test hooks __gm.pitch.panel and __gm.mix.panel return the same shape as __gm.gain.panel, plus the lever. Tests: - Unit hardware-view: 15 tests, 3 new (Pitch turn, lever above the knob and ends on the Mix plate). Counter-check: taper 1 turns the Pitch test red. - e2e 34 (melektaus-atx): 134 checks, 1 new. - Gate (melektaus-atx, together with #391): 287 files, 4397 tests. - Pictures looked at: Gain, Pitch and Mix in a row, Pitch at -24.5, Mix with both curves, the Bell preset without overlaps. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e8021b1fd3
commit
e70954115c
12 files changed
+468
-184
No files matched your search
@@ -6,7 +6,7 @@ import { currentNodePalette } from '../graph/NodePalette';
|
||||
import { ChipPanel } from '../soundfx/ChipPanel';
|
||||
import { EnvelopePanel } from '../soundfx/EnvelopePanel';
|
||||
import { CompressorPanel } from '../soundfx/CompressorPanel';
|
||||
import { GainPanel } from '../soundfx/GainPanel';
|
||||
import { KnobPanel } from '../soundfx/KnobPanel';
|
||||
import { ReverbPanel } from '../soundfx/ReverbPanel';
|
||||
import { CurvePanel } from '../soundfx/CurvePanel';
|
||||
import type { EqEditor } from '../soundfx/EqEditor';
|
||||
@@ -16,6 +16,33 @@ import { centreOf, snapButton, type HookContext } from './hookHelpers';
|
||||
/** The node graphs: behaviors, state machines, particles and shaders. */
|
||||
export function graphHooks({ state, editor }: HookContext) {
|
||||
const { graphEditor } = editor;
|
||||
const knobPanel = (nodeId: string) => {
|
||||
const canvas = [editor.soundFxEditor.canvas, editor.instrumentEditor.canvas].find(
|
||||
(c) => c.faceOf(nodeId)?.widget instanceof KnobPanel,
|
||||
);
|
||||
const panel = canvas?.faceOf(nodeId)?.widget;
|
||||
if (!canvas || !(panel instanceof KnobPanel)) return null;
|
||||
const node = canvas.nodeScreen(nodeId);
|
||||
const ports = (keys: string[], isOutput: boolean) =>
|
||||
Object.fromEntries(keys.map((key) => [key, canvas.portGlobal(nodeId, key, isOutput)]));
|
||||
return {
|
||||
shown: shownOnScreen(panel),
|
||||
rect: screenRect(panel),
|
||||
size: { w: panel.w, h: panel.h },
|
||||
node: node?.rect ?? null,
|
||||
inputs: ports(node?.inputs ?? [], false),
|
||||
outputs: ports(node?.outputs ?? [], true),
|
||||
knob: {
|
||||
value: panel.knob.value,
|
||||
enabled: panel.knob.enabled,
|
||||
number: panel.knob.shown,
|
||||
dial: panel.knob.dialGlobal(),
|
||||
},
|
||||
lever: panel.lever
|
||||
? { on: panel.lever.lit, says: panel.lever.says.text, at: screenCentre(panel.lever) }
|
||||
: null,
|
||||
};
|
||||
};
|
||||
return {
|
||||
graph: {
|
||||
current: () => graphEditor.currentAssetId,
|
||||
@@ -247,33 +274,12 @@ export function graphHooks({ state, editor }: HookContext) {
|
||||
},
|
||||
},
|
||||
/**
|
||||
* The panel a Gain node shows in the sound FX graph (#384): whether it is shown and where, its
|
||||
* knob, and the node's ports.
|
||||
* The panel a Gain node shows (#384), and a Pitch or a Mix (#390): whether it is shown and where,
|
||||
* its knob, the Mix's lever and the node's ports.
|
||||
*/
|
||||
gain: {
|
||||
panel: (nodeId: string) => {
|
||||
const canvas = editor.soundFxEditor.canvas;
|
||||
const panel = canvas.faceOf(nodeId)?.widget;
|
||||
if (!(panel instanceof GainPanel)) return null;
|
||||
const node = canvas.nodeScreen(nodeId);
|
||||
const ports = (keys: string[], isOutput: boolean) =>
|
||||
Object.fromEntries(keys.map((key) => [key, canvas.portGlobal(nodeId, key, isOutput)]));
|
||||
return {
|
||||
shown: shownOnScreen(panel),
|
||||
rect: screenRect(panel),
|
||||
size: { w: panel.w, h: panel.h },
|
||||
node: node?.rect ?? null,
|
||||
inputs: ports(node?.inputs ?? [], false),
|
||||
outputs: ports(node?.outputs ?? [], true),
|
||||
knob: {
|
||||
value: panel.knob.value,
|
||||
enabled: panel.knob.enabled,
|
||||
number: panel.knob.shown,
|
||||
dial: panel.knob.dialGlobal(),
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
gain: { panel: (nodeId: string) => knobPanel(nodeId) },
|
||||
pitch: { panel: (nodeId: string) => knobPanel(nodeId) },
|
||||
mix: { panel: (nodeId: string) => knobPanel(nodeId) },
|
||||
/**
|
||||
* The panel a Chip node shows in the instrument graph (#386): whether it is shown and where, its
|
||||
* knobs, which settings are idle, the picked wave, the lever, both screens and the node's ports.
|
||||
|
||||
Reference in new issue
Block a user