Zoom the Parametric EQ's dB with the wheel, saved on its node (#376)
game-maker / gate (push) Successful in 3m48s
game-maker / suite (push) Successful in 14m59s

Close moves of a few dB were hard to set and to see on a curve that
always spans -24 to +24 dB. The wheel on free space now zooms the dB
the curve shows, and the node's small curve shows the same range.

- The range is one of ±3, ±6, ±12, ±24 and ±48 dB, always the same
  either side of 0. Up zooms in.
- It is saved on the node as `range`, so it is back after loading.
  Like the game scale (#226) it is no undo step. EditorState gets
  `setNodeView` for such a setting of a node.
- The lines across the curve follow the range: every 1, 2, 3, 6 or
  12 dB. A handle whose gain lies beyond the range stays on the edge,
  in sight and in reach.
- The wheel over a handle still turns the band's Q. It no longer does
  so anywhere while a band is picked: after opening, a band nearly
  always is, and the zoom would be out of reach.
- `EqArea` carries the range, so the tab and the node map dB alike.
  The spectrum keeps its own scale.

Tests:
- Unit parametric-eq: 16 green, 5 new (range in compile and save,
  node picture, wheel steps, grid lines, handle on the edge)
- Counter-check: the node's picture without the saved range turns
  the new picture test red
- e2e 34 (melektaus-atx): ALL PASSED, 99 checks, 1 new
- Gate (melektaus-atx): CHECK EXIT 0, 275 files, 4252 tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-09 16:36:33 +02:00
1 parent 41af1a6e51
commit 3377524958
9 files changed
+181 -34

No files matched your search

+54 -4
View File
@@ -18,7 +18,14 @@ import {
MAX_EQ_BANDS,
withBandSet,
} from '../../src/core/soundfx/parametricEq';
import { SPECTRUM_FLOOR_DB, spectrumPeak, spectrumYs } from '../../src/editor/soundfx/eqView';
import {
eqGridDb,
handleOf,
SPECTRUM_FLOOR_DB,
spectrumPeak,
spectrumYs,
wheeledRange,
} from '../../src/editor/soundfx/eqView';
import { graphEditor, makeProject, roundTrip } from '../helpers';
const band = (change: Partial<EqBand>): EqBand => ({
@@ -96,6 +103,15 @@ describe('the bands of a Parametric EQ', () => {
expect(kinds()).toEqual([`in:${id}`, `out:${id}`]);
});
it('keep the zoom of their curve on the node, saved, and it changes nothing heard (#376)', () => {
const f = eqLine([band({ gain: 6 })]);
const stages = () => compileSoundFx(f.fx.graph, undefined, f.fx.id).compiled!.stages;
const before = stages();
f.eq.params.range = 12;
expect(stages()).toEqual(before);
expect(roundTrip(f.project)).toEqual(f.project);
});
it('draw the curve Web Audio plays', () => {
const peak = band({ gain: 6 });
expect(bandDb(peak, 1000)).toBeCloseTo(6, 3);
@@ -138,8 +154,10 @@ describe('the bands of a Parametric EQ', () => {
describe('the curve in the node (#375)', () => {
const picture = SoundFxParametricEq.picture!;
const W = 164;
const area = { w: W, h: picture.height };
const lineOf = (bands: unknown) => picture.shapes({ bands } as never, W).find((s) => s.kind === 'line')!;
const area = { w: W, h: picture.height, range: 24 };
const lineOf = (bands: unknown, range?: number) =>
picture.shapes({ bands, range } as never, W).find((s) => s.kind === 'line')!;
const topOf = (line: { points: number[] }) => Math.min(...line.points.filter((_, i) => i % 2 === 1));
it('has its highest point where the peak is, as high as its gain', () => {
const line = lineOf([band({ gain: 6 })]);
@@ -165,11 +183,43 @@ describe('the curve in the node (#375)', () => {
expect(Math.max(...ys)).toBe(picture.height);
expect(Math.min(...ys)).toBeGreaterThanOrEqual(0);
});
it('shows the dB its tab is zoomed to, and ±24 dB for a range the tab does not offer (#376)', () => {
const peak = [band({ gain: 6 })];
expect(topOf(lineOf(peak))).toBeCloseTo(dbToY(6, area), 1);
expect(topOf(lineOf(peak, 12))).toBeCloseTo(dbToY(6, { ...area, range: 12 }), 1);
expect(topOf(lineOf(peak, 12))).toBeCloseTo(picture.height / 4, 1);
expect(lineOf(peak, 7)).toEqual(lineOf(peak));
});
});
describe('the zoom of the curve in the tab (#376)', () => {
it('goes a range smaller with the wheel up and larger with it down, and stops at either end', () => {
expect(wheeledRange(24, -100)).toBe(12);
expect(wheeledRange(24, 100)).toBe(48);
expect(wheeledRange(3, -100)).toBe(3);
expect(wheeledRange(48, 100)).toBe(48);
});
it('rules the curve with whole dB, as many lines either side of 0', () => {
expect(eqGridDb(3)).toEqual([-2, -1, 0, 1, 2]);
expect(eqGridDb(6)).toEqual([-4, -2, 0, 2, 4]);
expect(eqGridDb(12)).toEqual([-9, -6, -3, 0, 3, 6, 9]);
expect(eqGridDb(24)).toEqual([-18, -12, -6, 0, 6, 12, 18]);
expect(eqGridDb(48)).toEqual([-36, -24, -12, 0, 12, 24, 36]);
});
it('keeps the handle of a gain beyond the range on the edge', () => {
const area = { w: 600, h: 300, range: 3 };
expect(handleOf(band({ gain: 12 }), area).y).toBe(0);
expect(handleOf(band({ gain: -12 }), area).y).toBe(300);
expect(handleOf(band({ gain: 1.5 }), area).y).toBeCloseTo(75, 6);
});
});
describe('the spectrum behind the curve (#374)', () => {
const BIN = 48000 / 8192;
const area = { w: 600, h: 300 };
const area = { w: 600, h: 300, range: 24 };
const xs = Array.from({ length: 301 }, (_, i) => i * 2);
/** All silence but one loud bin at about 1 kHz. */
const sine = () => {