Commit Graph
70 Commits
Author SHA1 Message Date
adminandClaude Opus 5.5 b6749cc43d Show Tremolo, Vibrato, Phaser and Flanger as devices that swing (#398)
The four effects that swing were still drawn as rows of numbers. Like
the Delay, the Saturation and the Limiter, each node now shows a device
of a fixed size. Its screen draws what it does over the first seconds
of a sound, and a light plays that in real time, over and over.

- One device for all four (ModulationPanel, ModulationFace and
  modulationView), as KnobPanel is for Gain, Pitch, Mix and Pan. A short
  description gives each its title, plate, buttons and knobs.
- Tremolo, 640x280 in cream: a wave its volume shapes, the volume as
  its outline. It says DOWN TO 50%, EVERY 200 ms and DIPS -6 dB.
- Vibrato, 640x280 in plum: the pitch in semitones, cool below and warm
  above. Under a wet below 1 the dry pitch stands beside it. It says
  how late the voice is (LATE 0.93 ms).
- Phaser, 760x280 in mustard, and Flanger, 680x280 in steel blue: the
  response from 20 Hz to 20 kHz, and under it a lane of the turn or the
  delay over time. The response is drawn where the light is, so the
  notches swirl and the comb jets. A comb finer than a column fills it.
- The light loops over 1, 2, 4 or 8 s, two LFO swings at least, for a
  Flanger as long as its sweep takes to reach 20 ms. The face only
  keeps the editor drawing while something swings and is heard.
- A Rate lamp beside each name goes from dark to lit with the swing.
- Nothing is listened to. The pictures use the worklets' own numbers:
  lfoAt, vibratoSpeedUp (now exported from lfo.ts), the allpass chain
  with its feedback, and the comb, reckoned at 48 kHz.
- Phaser and Flanger take 4 preset columns. Phased Pad was looked at
  in a picture, nothing overlaps.
- Exported from nodes.ts: LFO_SHAPE, lfoShapeOf, TREMOLO_ROWS,
  VIBRATO_ROWS, PHASER_STAGE_ROW and phaserStagesOf. FLANGER_MAX_MS is
  exported from phaser.ts.

Decided here, as the request left the look open:

- A notch counts where the response falls below -3 dB, not some dB
  under the loudest point. Feedback rings peaks far above 0 dB, and the
  first rule counted 7 notches on a Phaser of 12 stages.
- Rate and Depth of Tremolo and Vibrato are big knobs, Wet is small.

Tests:
  Unit modulation-view (stein2): 22 tests, new. Phaser and Flanger
    give the dB of their worklets on a sine within 0.005 dB, the
    Tremolo its samples, the Vibrato the pitch it reads at.
  Counter-check: with the sign of the Phaser's feedback flipped in one
    place, exactly the worklet test fails.
  e2e 34 (atx): ALL PASSED, 142 checks, 4 new. Each device has its
    size, its ports outside, a light that runs by itself, and a button
    and a knob as one undo step each. Picking 8 stages on the Phaser
    draws 4 notches.
  Gate (atx): CHECK EXIT 0, 291 files, 4467 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:02:50 +02:00
adminandClaude Opus 5.5 28909f0adf Show a Limiter node as a device that holds what it took away (#397)
game-maker / gate (push) Successful in 4m23s
game-maker / suite (push) Successful in 16m6s
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>
2026-10-10 20:30:59 +02:00
adminandClaude Opus 5.5 80b25488ce Show a Saturation node as a device that glows with its drive (#396)
game-maker / gate (push) Successful in 4m5s
game-maker / suite (push) Successful in 16m2s
A Saturation in the graph was a stack of rows. The user wanted a device
of its own style and size, like the Delay and the Reverb, that shows
warmth and heat the harder the sound is driven.

- The node is a copper plate of 720x280 with a button per type, the
  auto-gain lever, a big drive knob and small knobs for tone, output
  and wet. Each change is one undo step, wired numbers lock their knob.
- The screen draws a sine at -12 dBFS, the level auto-gain is reckoned
  at, as a grey ghost. Over it is the wave that comes out: bent by the
  type, its DC taken out, auto-gain and output after it, blended by
  wet. Under it stand the auto-gain and the peak.
- The heat is the drive over 48 dB, as far as wet lets it be heard.
  The wave goes from cold steel through ember red to white, a glow
  rises from below and flickers, and embers fly up from about 8 dB on.
  A lamp beside the name glows with it.
- The node keeps the editor drawing only while there is heat. Without
  drive it draws nothing, unlike the Delay, which always animates.
- The Reverb's private type button is now PickButton with its own
  colours, and the Saturation's type buttons use it as well.
- No instrument preset uses a Saturation, so no preset needs more
  columns.

Tests:
- Unit saturation-view: 14 new tests.
- Counter-check: without taking out the DC, exactly the DC test fails.
- e2e 34 (atx): ALL PASSED, 137 checks, one new for type, lever and
  drive with undo.
- Gate (atx): CHECK EXIT 0, 289 files, 4431 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:05:11 +02:00
adminandClaude Opus 5.5 8c53ce0cbe Replace the 5-Band-EQ with the Parametric EQ and remove it (#395)
game-maker / gate (push) Successful in 4m13s
game-maker / suite (push) Successful in 16m21s
The Parametric EQ does all the 5-Band-EQ did and more, so the editor
keeps only one EQ. The old node was used in one project only, with
nothing linked to its band inputs.

- "Garden of Sacrifices" was converted once on the project file, not
  by a load migration (#137). Its 5 nodes are Parametric EQs now with
  the same filters: low shelf 80 Hz, peaks at 250 Hz, 1 kHz and 4 kHz
  with Q 1, high shelf 12 kHz. A band at 0 became no band.
- Two values below -24 dB were held to -24, as the Parametric EQ goes
  no further. Both are in the asset "Delay" (high -60, low -36). The
  user will rework them by ear.
- The write went through the API's own Store (lock, revision 20181 to
  20182), so a tab with the old revision gets a 409 instead of
  overwriting it.
- Removed with the node: its stage kind "filter" and the type
  FxFilter, which nothing else made, and the player's branch for it.
  A Pan is now the last branch of fxNodes, a Flanger that of stagesOf.
- Tests that took the 5-Band-EQ as "some effect" use the Pan or the
  Parametric EQ now. The test for a parameter feeding a band now
  feeds the cutoff of a Filter, which had no such test yet.
- A project that still names the type, such as an old snapshot of
  "Garden of Sacrifices", loads without those nodes. Unknown types
  were always left out on load.

Tests:
- Unit (stein2): soundfx 67, instrument 36, envelope 13, sound-library
  127, all green
- Counter-check: with the cutoff not taken from the parameter, only
  the new Filter test goes red
- The converted project loads and compiles: Delay, Vocals and Drums
  play their bands (3+1, 4 and 5)
- e2e 34 (atx): ALL PASSED, 136 checks, the palette check now looks
  for the Parametric EQ
- Gate (atx): CHECK EXIT 0, 288 files, 4417 tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 19:46:03 +02:00
adminandClaude Opus 5.5 3f7eb03143 Check what a streamed sound holds by the library's own count (#378)
game-maker / gate (push) Successful in 4m8s
game-maker / suite (push) Successful in 16m22s
The e2e check that a streamed sound costs less than a decoded one
compared how much the renderer process grew. Alone, module 34 saw it
grow by 1 to 8 MB while streaming. After the 33 modules before it in
CI it grew by 25 to 66 MB, and suite went red (CI tasks 611, 614 and
617, the last with 66.7 MB streamed against 95.9 MB decoded).

- SoundLibrary.stats tells decodedBytes, the samples of every file
  decoded whole, and streaming.held, every head of a copy and what is
  sent ahead to the worklet.
- StreamFile.headBytes and StreamFeed.ahead give their share.
- The e2e check now asks the library: streamed it holds less than
  4 MB more than before and nothing decoded whole, decoded it holds
  more than 40 MB, the five minutes being 50 MB at 44.1 kHz.
- The growth of the renderer stays in the log of that check. The
  counter-proof that the renderer grows by more than 40 MB when the
  sound is decoded stays as it was.

Tests:
- Unit sound-library: the streaming test also checks the bytes held.
- e2e 34 (melektaus-atx): 136 checks. Streamed 0.76 MB more held and
  none decoded whole, decoded 50.5 MB. The renderer grew by 4 MB and
  49 MB.
- Gate (melektaus-atx): 288 files, 4416 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 19:01:00 +02:00
adminandClaude Opus 5.5 d4a45c127b Show a Delay node as a device with its echoes over time (#393)
After the Chip, the Envelope, the Reverb and the one-knob devices, the
Delay is drawn as a device of its own size too. Its screen shows what
time, feedback, damping, wet and ping-pong make of a sound, and a light
runs over the echoes at the beat they come at.

- DelayPanel: a 680x280 plate in the Compressor's brushed aluminium,
  now exported as ALUMINIUM. A screen, levers for Sync and Ping-pong,
  knobs for Time, Feedback, Damping and Wet, and an Echo lamp.
- The screen draws the sound as a white bar and each echo as a bar one
  step later. A bar is as tall as the echo is loud, evenly in dB to
  -60. It turns from warm white to rust as the damping takes the highs.
  With ping-pong the bars alternate above and below, the first left.
- At most 24 bars are drawn, and the screen says how many more are
  heard. Under it stand the step and the end of the tail, above it
  where the highs of the first echo end.
- The running light: DelayFace.tick returns true while the node is in
  the picture, so the editor keeps drawing, as for the Particles tab.
  The light goes round the span in real time, a bar glows as it is
  passed, and the lamp blinks with every echo but not with the sound.
- On beats the screen counts at 120 BPM, as the tempo of the voice is
  not known in the graph. The time knob then names the note, dotted
  and triplet ones included. In seconds, 2 s and up reads "2 s max".
- delayView holds the rules apart from the drawing: layout, tapers,
  echoTrain, echoCutoff for k rounds of the Butterworth low-pass,
  sweepAt and the texts. At wet 0 the rest is drawn faint.
- delayEchoes in the core counts the echoes heard. The tail of a voice
  and the screen use it both. DELAY_ROWS, DELAY_SYNC and PING_PONG are
  exported.
- Knob.reformat writes the number anew when its format changes, here
  when Sync switches between seconds and beats.
- The test hook __gm.delay.panel returns knobs, levers, idle settings,
  the echoes drawn, the texts and where the light is.
- Instrument presets give the Delay 4 columns. Echo Lead and Bell use
  one. In the picture of Echo Lead nothing overlaps.

Found on the way: a node with a face draws no setting rows. The e2e
check of a bypassed Delay (#354) read "Bypass: true" from them. It now
checks the faded node and the stages heard, as before.

Tests:
- Unit delay-view: 17 tests, new. Counter-check: the first ping-pong
  echo on the right turns the side test red.
- e2e 34 (melektaus-atx): 136 checks, 1 new.
- Gate (melektaus-atx): 288 files, 4416 tests.
- Pictures looked at: default, ping-pong, beats with damping, 83
  echoes at 50 ms, wet 0, and the preset Echo Lead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 18:51:06 +02:00
adminandClaude Opus 5.5 389d9555fa 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>
2026-10-10 18:26:17 +02:00
adminandClaude Opus 5.5 e70954115c 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>
2026-10-10 17:37:03 +02:00
adminandClaude Opus 5.5 e8021b1fd3 Show a Reverb node as a device with a round screen of its tail (#389)
game-maker / gate (push) Successful in 4m5s
game-maker / suite (push) Successful in 15m49s
The Compressor, the Gain, the Chip and the Envelope are devices in the
graph. The Reverb was still a list of rows, and how long and how dark
its tail rings was left to the numbers.

- The node is a plate of 640x280 in pale lavender. Its inputs are on
  the left, its output on the right.
- A round screen draws the tail as rings out from the sound. A ring's
  radius is its time, as the root of its seconds, on one scale up to
  10.5 s for every Reverb. So a Room is a small disc and a Cave fills
  two thirds.
- A ring is as bright as the tail is loud there, evenly in dB. Its
  colour is how much of the highs is left, from ice to violet, with
  the cut-off of the impulse response (reverbCutoff, now shared).
- The pre-delay is a dark gap round the dot, a dashed ring marks
  -60 dB. Seeded dots stand for the reflections. Wet fades the tail
  against the dot.
- A type of Sound draws the peaks of its sound from the library, each
  against its loudest. It says LOADING until they are there.
- A button for each type, a list of the sounds and four chrome knobs:
  Decay, Damping, Pre-delay and Wet.
- A knob the type does not take is greyed but turns. It goes over to
  Custom with the type's numbers, as one undo step. Picking a sound
  makes the type Sound, also one step.
- NodeParamDrag can write several settings as one drag for that.
- Test hook __gm.reverb.panel(nodeId), for the sound FX and the
  instrument graph.

Tests:
- Unit reverb-view: 10 new tests for the plate, the knob turn, the
  numbers per type, the scale, the tail and the texts. A screen that
  ignores damping turns the highs test red.
- e2e 34 (atx): 1 new check (device between its ports, decay knob of
  a Hall goes to Custom as one undo step, Cave button, a sound from
  the list makes it Sound and draws that sound).
- Probe pictures on stein2: Room, Hall, Cave, Plate, Custom with
  damping 1 and pre-delay 0.3 s, Sound.
- Gate (atx): CHECK EXIT 0, 287 files, 4393 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 16:35:59 +02:00
adminandClaude Opus 5.5 c75e72d4ee Show an Envelope node as a device with its ADSR curve (#388)
game-maker / gate (push) Successful in 4m24s
game-maker / suite (push) Successful in 15m50s
The Compressor, the Gain and the Chip are devices in the graph. The
Envelope was still a list of rows, and its shape was left to the
imagination of whoever read the numbers.

- The node is a plate of 520x280 in pale sea green. Its inputs are
  on the left, out and value on the right.
- A dark screen draws the curve over a note in amber. A letter sits
  above each stage, its time below it, and the sustain in percent
  above its line.
- The time on the screen is squeezed. Each stage is as wide as the
  root of its seconds, at least 3 %, and the hold has a fixed fifth.
  So 5 ms of attack still shows beside a release of 3 s.
- The falls use SETTLE, as the player does. With sustain 0 there is
  no hold and no release, and the screen says NOTE ENDS.
- Attack, Decay, Sustain and Release are chrome knobs. The times use
  taper 4, so 5 ms sits an eighth of the way and 1 s not quite half.
- From and To have no range a knob could turn through. They are
  NumberFields on the plate, typed in or scrubbed.
- The decay at sustain 1 and From/To while nothing takes value are
  faded. While a link takes value, the bezel says what it moves
  between, as VALUE 400 TO 3500. The UI font has no arrow.
- Instrument presets give an Envelope 3 columns of 240.
- Test hook __gm.envelope.panel(nodeId).

Tests:
- Unit envelope-view: 6 new tests for the plate, the knob turn, the
  idle settings and the curve. A linear decay turns two of them red.
- e2e 34 (atx): 1 new check on the Laser preset (device between its
  ports, curve ends with the decay, decay knob and scrubbed To field
  each one undo step).
- Probe pictures on stein2: Synth Brass preset, pad, pluck, organ
  and an Envelope whose value moves a Chip.
- Gate (atx): CHECK EXIT 0, 286 files, 4383 tests. The first run was
  red on the font test (#214) for an arrow in the bezel text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 16:06:15 +02:00
adminandClaude Opus 5.5 eb78f3cbf3 Show a Chip node as a device with its wave and its pitch (#386)
game-maker / gate (push) Successful in 4m5s
game-maker / suite (push) Failing after 16m5s
The Chip had eleven numbers in rows and nothing that showed what they
do to a note. Its node is now a device of one size like the
Compressor's, and two screens draw the wave and the pitch from the
settings, worked right in the instrument graph.

- ChipPanel is 840x280 in the warm grey of a handheld console. A
  button per wave, the "follow note" lever and a chrome knob per
  number, grouped Oscillator, Slide, Vibrato, Arpeggio and Repeat.
- The wave screen draws two periods as loud as the Chip plays them,
  a square at its duty. toneValue and periodicSteps come out of
  chip.ts, so the screen and the worklet share one formula.
- The pitch screen draws what slide, delta slide, vibrato, arpeggio
  and repeat make of the pitch over a note. It ends where the note
  falls below "min frequency" and starts from A4 while the Chip
  follows the note.
- Idle settings are faded, wired ones greyed and locked, as the rows
  were. Every change is one undo step.
- Knobs take a bipolar range: 0 straight up, the taper bends each
  half. Slide, delta slide, duty sweep and arpeggio use it.
- The Compressor's auto-gain lever is now a Lever with a name of its
  own, which the Chip uses for "follow note".
- Presets give a Chip five columns, as a Filter has four since #385.

Found on the way:

- With the Chip this wide the Laser preset is about 3060 units. In a
  1280 px window Fit stops at the least zoom of 0.25 and cuts the
  Notes node off. The zoom limit is left as it is.

Tests:
- Unit: chip-view (9 new), knob (1 new), chip, chip-presets and
  hardware-view, 53 green
- e2e 34 on melektaus-atx: ALL PASSED, 131 checks (1 new). The
  first run was red on the check itself: a Laser's "min frequency"
  can lie above A4, and the screen then rightly says the note ends
  at once.
- Gate on melektaus-atx: CHECK EXIT 0, 285 files, 4377 tests
- Counter-check: a square drawn with its duty mirrored turns the wave
  test red

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:55:35 +02:00
adminandClaude Opus 5.5 c087993000 Edit a Filter in its node and resize EQ and Filter nodes (#385)
A Filter sounds like one band of the Parametric EQ, but it keeps what
the EQ cannot: linked numbers, the all-pass and +-40 dB. So it stays a
node of its own and gets the EQ's editor with itself as the one band.
The editors also stop running out of their room: what does not fit is
left out instead of being cut.

- The curve of the EQ is now BandCurve with a host the EQ and the
  Filter both fill. CurvePanel holds what their panels share.
- FilterPanel shows the Filter as one band. A drag of its handle sets
  frequency and gain as one undo step. A number fed by a link is
  greyed out and the drag leaves it alone.
- A double-click on the Filter's title opens the same in a tab of its
  own. Delete there does nothing, the node stays.
- The Filter is tapped like the EQ, so its node and its tab draw the
  spectrum of what passes it. A tap is two gain nodes per voice, the
  analysers are made only while someone listens.
- EqFace and FilterFace are one CurveFace now, given the panel.
- EQ and Filter nodes have a corner to resize them, drawn like the
  floating window's. The size is saved on the node as `size`, each
  drag is one undo step, the least is 240 x 120.
- In the node the help line is gone. The row of numbers comes in only
  once the curve keeps 120 px of height. A group of the row that does
  not fit is not drawn, the one on the right first.
- A frequency or dB label that would run into the one before it is
  left out.
- The all-pass is drawn flat. The curve's maths takes every shape a
  Filter has.

Found on the way:
- The instrument presets set nodes 240 apart. A Filter with its
  editor is about 700 wide and covered the Bitcrusher, so a Filter
  now takes four columns.

Tests:
- Unit: 8 new (filter band, resize command and save, fitting rules),
  stage lists with a Filter now hold its taps
- e2e 34 (atx): ALL PASSED, 130 checks (4 new)
- Gate (atx): CHECK EXIT 0, 284 files, 4367 tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 14:30:00 +02:00
adminandClaude Opus 5.5 17f47f6956 Drop the Gain's engraved name and centre its knob (#384)
The plate was taller than its one knob needs, and the engraved
"GAIN" only repeated the node's title. Without it the plate is
square, and the knob sits in its middle.

- The plate is 160x160 instead of 160x220, without the "GAIN"
  engraving.
- The knob is centred on what it shows: the "DB" caption and the
  dial. Since #382 its number hangs inside the dial's open bottom,
  so it adds no height.
- CAPTION_AIR (4) takes out the empty space above the caption's
  capitals. Without it the knob sat a little too low.

Measured on a picture at zoom 0.8: 19 px of air above "DB" and
19 px below the number. The user's screenshot showed about 14
above and 35 below.

Tests:
- Unit hardware-view: 12 tests green. The layout test now asks
  for the same air above and below.
- e2e 34 (melektaus-atx): ALL PASSED, 126 checks.
- Gate (melektaus-atx): CHECK EXIT 0, 283 files, 4359 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 13:29:28 +02:00
adminandClaude Opus 5.5 01ee61868b Show a Gain node as an orange device with one knob (#384)
A Gain node now draws a small device in the graph, as the Compressor
does since #380: the brushed plate in orange, 160x220 graph units, with
one chrome knob between its inputs and its output.

- GainPanel and GainFace join the node faces. The knob drags, resets
  on a double-click and keeps the wheel for the graph (#383). Each turn
  is one undo step, a linked Gain (dB) greys it.
- The knob turns with a taper of 0.26, so 0 dB stands straight up and
  the range below -24 dB takes a fifth of the turn.
- drawPlate, engrave and the brushed look move out of CompressorPanel
  so both devices share them. The Knob takes an arc colour, because
  the orange arc vanished on the orange plate.
- No meter and no tap. Gain sits in every voice of an instrument, and
  a tap would add two audio nodes to each note for a display nobody
  asked for.

Found on the way:
- Knob.dialGlobal added half the dial unscaled. In a zoomed graph it
  pointed beside the dial. The Compressor's e2e only hit by luck.

Tests:
- Unit: 2 new for the Gain plate and its taper.
- e2e 34 on atx: ALL PASSED, 126 checks (1 new).
- Gate on atx: CHECK EXIT 0, 283 files, 4359 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 13:07:44 +02:00
adminandClaude Opus 5.5 79a04e6764 Edit a Parametric EQ in its node, let the wheel only zoom graphs (#381)
The small curve below the EQ node's rows (#375) only showed the bands.
Like the Compressor since #380, the node now shows the editor of its
tab between its inputs and its output, and is worked right there. A
double-click on the node still opens the same editor in a tab.

Two wishes for the nodes with a panel came on top and share the same
files, so they are in this commit as well: the numbers below the
Compressor's knobs read as plain labels (#382), and the wheel over a
node zooms the graph instead of turning a value (#383).

#381, the EQ in its node:
- EqPanel holds the curve, the band's numbers and the help line, taken
  out of EqEditor. The tab puts its one panel under a header, each EQ
  node has a panel of its own at 720 x 360 (EqFace).
- A press on the curve neither picks nor moves the node. A
  double-click on the curve adds a band, one on the title or the edge
  opens the tab.
- In the node a press on the Type or Slope button stays with the
  button, so the node is not picked or moved.
- The spectrum runs in the node while it is in the picture. The tab
  lets go of the tap once, like the Compressor's, and Editor.tick lets
  every unseen tab and graph go before any seen one listens.
- The count of bands moved from the tab's header into the band row,
  so the node shows it as well.
- The node picture of #375 is gone with its only user: NodePicture,
  EQ_PICTURE and NodeView's drawing of it.

#382, the numbers below the Compressor's knobs in its node:
- A Knob with typed: false shows its number as a centred Label without
  a box, in the panel's ink. The ratio reads 4:1 (ratioText).
- The label is as high as its text and sits up in the dial's open
  bottom, right under the ends of the scale.
- Label takes a colour of its own.

#383, the wheel in a graph:
- GraphCanvas takes the wheel on the way down and stops it there. It
  zooms over a node too, and no knob or curve in a node sees it.
- The tabs keep their wheel: a knob's step, a band's Q and the zoom
  of the EQ's dB.

Found on the way:
- A Menu opened below a button in the zoomed graph would have been
  placed by the button's own height. openBelow now takes the bottom
  edge as drawn.
- The band row did not lay out its labels after a text change when
  its own size stayed. In the tab a later resize hid it, in the node
  "Band 1" and the count were missing. The row now lays out itself.
- Handle and point positions for the checks were unscaled. They now
  go through toGlobal and hold at the graph's zoom.

Tests:
- Unit parametric-eq: 16 tests. The #375 tests now check
  eqCurvePoints and eqRangeOf directly. compressor-view: ratioText.
- e2e 34 (atx): ALL PASSED, 125 checks (3 new: the EQ panel and its
  ports, editing in the node, the spectrum in the node with the tab
  open). The Compressor node's check reads its numbers as labels and
  8:1. Both node checks now expect the wheel to zoom the graph and
  leave the ratio, the Q and the dB zoom alone.
- The unseen-tab check now leaves for the Scene tab, since the node
  in the graph listens to the same tap.
- Counter-check: the tab letting go of the tap every unseen frame
  stalls the node's spectrum with the tab open (1 frame against 22,
  probe on stein2).
- Gate (atx): CHECK EXIT 0, 283 files, 4357 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 12:40:11 +02:00
adminandClaude Opus 5.5 1f2b64fb4c Show a Compressor's panel in its node, its tab on a double-click (#380)
game-maker / gate (push) Successful in 4m2s
game-maker / suite (push) Successful in 16m14s
The panel of #379 opened in a window of its own and replaced the tab.
The user wanted the device in the graph instead, and the tab of #378
back on a double-click. The switch Hardware look goes again.

- A node can show a face instead of its rows (`NodeFace`). Its inputs
  sit on the left of it and its outputs on the right, in the middle of
  the height. Each side is as wide as its longest port name.
- A Compressor node shows the panel at full size, 820x280 graph units.
  Its settings and numbers are not listed as rows, the panel has them.
- The knobs and the lever work in the node. A drag, a notch of the
  wheel and a double-click are one undo step each. A press on them
  neither picks nor moves the node, and the wheel does not zoom the
  graph. The number below a knob is only shown, typing stays in the
  tab and the inspector.
- The meter and the LEDs run while the node is in the picture of a sound
  FX or instrument graph that is seen. Out of the picture, folded or
  hidden, the node lets go of its taps.
- The window of one size, `fixedSize`, `refit`, `keptSize` and the rule
  that kept a Compressor out of the tab row are gone again.
- A knob's drag as one undo step lives in `nodeParamDrag.ts` now, for
  the tab and the node. The numbers of a node and what auto-gain says
  moved to `compressorView.ts`.

Found and fixed on the way:
- The tab and the node listen to the same taps. The tab let go of them
  on every frame it was not seen, so the node's meter stood still while
  the tab was open. Now the tab lets go once, and in a frame everything
  unseen lets go before anything seen listens.

Tests:
- Unit: compressor-view (2 new), documents (the test of `keptSize` is
  gone with it)
- e2e 34 (stein2, melektaus-atx was off): ALL PASSED, 122 checks. The
  four checks of #379 are now four of the node: ports beside the panel,
  knobs and lever in the node, the needle when played (21.7 dB with the
  tab open), and the double-click opening the tab.
- Gate (stein2): CHECK EXIT 0, 283 files, 4356 tests
- Counter-checks: an old auto-gain number read as off turns exactly its
  unit test red. A tab that lets go on every frame leaves the node's
  needle at 0 dB and no LED lit (probe on stein2).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 11:41:28 +02:00
adminandClaude Opus 5.5 9917253751 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>
2026-10-10 02:06:56 +02:00
adminandClaude Opus 5.5 2d4909d248 Fit the Compressor's tab to any size, make auto-gain a switch (#378)
Two things came up on the new tab. Floated and sized down, it spilled
its knobs out of its window, and sized up it left the window empty.
Auto-gain lifted far too much: it brought 0 dB back to 0 dB, which at
-40 dB, 4:1 and knee 6 is +28.1 dB, so a sound at -24 dB came out at
-6 dB. And as a knob from 0 to 1 it blended between makeup and
auto-gain, so at 1 the makeup knob did nothing.

Layout:

- The knobs stay on the bottom line, centred, with the help line
  above them.
- The levels fill the height left above. The curve is a square at
  their right, centred up and down. It grows with the tab from 180 to
  360 px and takes no more than 40 % of its width (picturesLayout).
- A floated window cannot be sized below what its pane needs. A pane
  says so in its minWidth and minHeight, and the Compressor's is every
  knob and the smallest pictures. Other panes keep 320 x 200.
- A pane put into an open window that is too small grows the window.

Auto-gain, as decided in #378:

- A Compressor's auto-gain gives back what it takes from a sound at
  -18 dB, so that one comes out as loud as it went in. At the numbers
  above that is +14.6 dB.
- Auto-gain is a switch on the Compressor, the Limiter and the
  Saturation alike, a setting and no longer an input. The makeup or
  output is added on top of it instead of being blended with it.
- A project from before reads a number as on above 0. Nodes never
  set keep their defaults: off on the Compressor, on on the other two.
- The tab shows the switch at the right of the knobs, with the dB it
  gives below it. With it on, the curve has a line at -18 dB from the
  curve up to the diagonal.
- Every level reading keeps the gain of its time. Before, the whole
  picture was read by the current makeup, so after a change the
  tinting of the last seconds was wrong or missing.

Findings:

- My first e2e run of the layout clicked Dock where it was before the
  window was sized up, so the window stayed open and the next check
  went red. The check reads the button again.

Tests:

- Gate (melektaus-atx): 282 files, 4344 tests (6 new)
- e2e 34 (melektaus-atx): ALL PASSED, 118 checks (2 new)
- Offline in Chrome: a sine at -18 dB through -24 dB and 4:1 came out
  at -17.92 dB with auto-gain on and -22.42 dB with it off.
- Counter-checks: the curve set at the top instead of the middle, and
  every reading read at a gain of 0, each fail exactly their test.
- Looked at the window in screenshots: at its least every knob and
  the switch show. At 1280 x 797 the levels fill the height, the
  curve is centred at the right and the knobs are at the bottom.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 00:57:26 +02:00
adminandClaude Opus 5.5 1df38bd430 Edit a Compressor in a tab of its own, with knobs and levels (#378)
A Compressor had only its number rows in the node. Its numbers are
easier to set by feel on a dial, and what it does is easier to judge
when the level going in and the level going out are seen side by side.

- Knob is a new widget in the toolkit: a slider bent into a circle,
  its name above and a number field below. A drag up or down turns
  it, Shift a tenth as far, a notch of the wheel a fiftieth of the
  turn, a double-click sets the default, the number takes a typed
  value. A taper gives the low end more of the turn.
- The Compressor's tab opens on a double-click, one per node, like
  the Parametric EQ's. It has a knob for each of the seven numbers,
  greyed while its input is linked.
- Its curve shows level in to level out from -60 to 0 dB. The point
  where it bends sets the threshold, the end of the curve sets the
  ratio that ends there.
- Its levels show the last 4 s on both sides of the node. Where it
  takes more than 0.5 dB away, the stretch below the level coming in
  is tinted. The makeup is left out of that.
- The Compressor is tapped like the EQ. SpectrumTaps listens to more
  than one node at once and reads the loudest sample of the last
  25 ms on each side.
- The node tabs share one map in the editor (nodeTabs), so a third
  kind needs no new plumbing.

Findings:

- CI task 614 for 23922ee was red on the memory check of the stream:
  the tab's renderer grew by 94 MB with nothing decoded. Play mode
  made garbage of its own and was read before a collection. Both
  readings in play now come after one, and the check compares the
  growth streamed with the growth decoded (3.7 MB against 59.8 MB).
- The ratio handle sat on the right edge of the picture, where a
  widget's hit area ends. The curve is inset by the grab radius.

Tests:

- Gate (melektaus-atx): 282 files, 4338 tests (17 new)
- e2e 34 (melektaus-atx): ALL PASSED, 116 checks (6 new)
- Counter-checks: an inverted ratio search, stop() letting go of
  every node and a drag without its least step each fail exactly
  their test.
- Looked at the tab in a screenshot: a blip at -4.4 dB came out at
  -16.7 dB, the gap tinted, the threshold line at -24.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 22:40:12 +02:00
adminandClaude Opus 5.5 23922ee4a2 Show the zoom of the music tabs and the EQ as a transition (#377)
game-maker / gate (push) Successful in 3m58s
game-maker / suite (push) Failing after 14m51s
The zoom transition of #309 left out the Pattern, Song and Sound tabs
and the Parametric EQ's curve. There a wheel step or Fit still jumped,
whatever "Zoom (ms)" in the general settings said.

- axisTween wraps ViewTween for a single axis. The music tabs zoom
  their time across and their rows, tracks or height down apart, so
  each keeps one tween per axis and either goes its own way.
- Fit by its button is shown as a transition too, as in the other
  tabs. The fit on opening a document still jumps.
- A middle-button pan first lands a transition under way, and saving
  a document's view lands it on the step it was heading for.
- The EQ's range is saved on the node at once. While the zoom is on
  its way the curve draws a range of its own and writes nothing.
  Its dB lines are those of the new range, sliding to their place,
  so no line shows a crooked number.

Found on the way, not part of #377:
- CI task 611 for 82e96e6 failed the stream check of e2e 34. The
  renderer had grown by 60.1 MB with nothing decoded. The check
  summed the RSS of every Chrome renderer, and the test Chrome runs
  four of them. It now measures the largest one, the tab's own, and
  writes all of them into the check's detail.

Tests:
- view-tween.test.ts: 18 tests, 2 new. They fail when axisTween
  writes the wrong offset.
- e2e 34, 64, 65 on melektaus-atx: ALL PASSED, 202 checks, 4 new.
  At 1000 ms each zoom is between its two steps a frame after the
  wheel and lands on the step a jump reaches.
- Stream check: the tab's renderer grew by 8.6 MB streamed and by
  66.9 MB decoded.
- Gate on melektaus-atx: CHECK EXIT 0, 280 files, 4321 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 21:28:50 +02:00
adminandClaude Opus 5.5 82e96e680f Decode a clip from a file of its own, not its whole sound (#349)
game-maker / gate (push) Successful in 4m3s
game-maker / suite (push) Failing after 14m58s
A clip was cut from its sound's file, so a game decoded the whole file
for it and held it for as long as the page was open. A long recording
played through a few short clips cost the recording. Every clip now has
a copy of its cut as Opus, with Stream on or off, and a game decodes
that instead.

- StreamKeeper makes the copy of each clip whether its sound streams
  or not. Stream only decides whether a sound gets a copy of its own.
- clipCopyTable says which cut decodes from which copy. The sound
  library loads the copy for a clip unless the whole file is decoded
  already. Then the clip is cut from that as before.
- projectFiles decodes the whole file ahead only where something may
  play the sound itself: no clips, a clip without a fitting copy, or
  the sound's id anywhere in an asset that can name one. That last
  test walks the data, not the fields, so no place is missed.
- A copy that cannot be had falls back to the whole file. A library
  that bakes keeps to the whole files, as the bake's fingerprint does.

Tests:
- Gate: CHECK EXIT 0, 280 files, 4319 tests (7 new: streams +4,
  sound-library +3).
- e2e 34: ALL PASSED, 108 checks (2 new). With Stream off a fresh
  library decodes the ten-second reversed clip's copy and not the
  five-minute file. It is the cut made from the original to within
  0.0091 a sample and 0.013 dB in any 20 ms. Once a node plays the
  sound itself, the whole file is decoded ahead again.
- e2e 65: all passed with this change.
- Counter-checks red as they should be: the library ignoring copies,
  the whole file always decoded ahead, clip copies only with Stream.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 20:53:49 +02:00
adminandClaude Opus 5.5 2764919bb8 Stream long sounds from an Opus copy instead of decoding them (#349)
A sound was decoded whole as soon as the project opened, in the editor
and in a released game: five minutes of stereo held some 115 MB for as
long as the page was open. A sound with Stream on now plays from a copy
as Opus in Ogg, fetched a piece at a time and decoded a few seconds
ahead by WebCodecs, so a game holds a few megabytes for it.

- The Stream switch is a row of the sound, off by default. The Sound
  tab suggests it from a minute on and says how far the copy has got.
- StreamKeeper makes the copies in the browser, a moment after the
  last change, one at a time: the sound's own and one per clip of its
  cut, fades and Reverse baked in. A copy that is no longer wanted is
  taken off and deleted once the project is saved without it.
- streamTable says which played names stream from which copy. A clip
  without a fitting copy, a Sample and a Reverb impulse stay decoded.
  A sound with Stream on is never decoded ahead.
- The page fetches by Range and takes the whole file from a server
  that ignores it, as the CI's PHP server does. It keeps the bytes
  and the first two seconds decoded, the rest goes four seconds ahead.
- A jump decodes 32 packets before the place first. Measured in
  Chrome, what follows is then the same to the bit as decoding from
  the start. With 4 packets it was 0.06 off.
- The stream-player on the FX worklet reads the samples at the rate
  of the context along a Catmull-Rom curve. What comes late is silent
  and skipped once it is there, so a clip keeps its beat.
- The opening stretch goes with the player as it is made. Sent by
  message it came a few milliseconds late, and 557 samples of the
  attack were lost.
- Pitch on a streamed sound is a shifter after the player, as on a
  bus: the length stays and it is heard 42.7 ms late.
- Bakes of patterns and songs (#340) always stream now.
- The song timeline draws a streamed sound from its copy decoded at
  8 kHz, keeping only the peaks.

Tests:
- Gate: CHECK EXIT 0, 280 files, 4312 tests (31 new: ogg +3,
  stream-queue 6, streams 11, streaming 6, sound-library +5).
- e2e 34: ALL PASSED, 106 checks (7 new). Five minutes of mono,
  4.8 MB as WAV, 3.6 MB as its copy: streamed the renderer grows by
  5.8 MB, decoded by 69.6 MB. Offline at 48 kHz, start, loop seam and
  a reversed clip are the copy decoded whole to the sample (0).
- Counter-checks red as they should be: no pre-roll, no catch-up of
  late samples at the end, no opening stretch, a streamed sound
  decoded ahead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 20:27:32 +02:00
adminandClaude Opus 5.5 3377524958 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>
2026-10-09 16:36:33 +02:00
adminandClaude Opus 5.5 687377682c Draw the EQ's curve in its node and a spectrum behind it (#375, #374)
Both were asked for in #367 "for later". The node of a Parametric EQ
now shows its curve in small, and its tab shows what passes the node
while it is seen.

#375, the curve in the node:

- NodeDef has an optional `picture`: a height, and shapes made from
  the settings as plain point lists (guide, area, line). It is
  general, the Parametric EQ is its first user.
- NodeView draws it in a box below the rows, again only when the box
  or the settings change. A drag in the EQ tab changes them, so the
  node follows it live.
- Without a band the box stays, flat on its 0 dB line, so the node
  does not grow at the first band.
- The axes of the curve, `eqCurvePoints` and `fillShapes` move from
  editor/soundfx/eqView to core/soundfx/parametricEq. Tab and node
  draw the sum from the same function.

#374, the spectrum:

- A node marked `tapped` gets a `tap` stage in front of its own
  stages and one behind them when compiled, in a sound FX and in an
  instrument alike. Its id is asset and node, since a copied asset
  keeps its node ids.
- A tap is a gain of 1. SpectrumTaps (new, viewport/spectrum.ts)
  holds the tap of every voice and bus while it plays.
- While the EQ tab is seen, in the tab row or in a window, the taps
  of that node feed one analyser per side (8192 points). Nothing is
  made before a tap of the node plays, so no AudioContext is opened
  early.
- The tab draws both behind the curve, on a scale of 0 to -96 dBFS:
  the sound coming in faint and grey, the sound going on in the
  colour of a sound port. The analyser smooths it, and once the
  sound has gone the sum still hands it silence, so it falls away.
- Once the tab is not seen, Editor.tick lets the analysers go, like
  the meters of the Song tab.
- A tap does nothing to the sound, so `stats().last.stages` leaves it
  out, and a bypassed EQ keeps its taps.

Decided on my own:

- Every sound passing the node counts: Play of the sound FX tab, an
  instrument's keys, a song, a run. The issue proposed that.
- The spectrum has no labels of its own. Its scale is in the wiki.

Tests:

  Gate (melektaus-atx)     275 files, 4247 tests (11 new), EXIT 0
  e2e 34 (melektaus-atx)   ALL PASSED, 98 checks (2 new), twice

Counter-checks, each put back by copy and compared with cmp:

- The node's curve drawn without its bands: 2 unit tests fail.
- The spectrum without the loudest bin between two points: 1 fails.
- SpectrumTaps.stop leaving the taps linked: 1 fails.
- Taps that never feed the analyser: e2e 34 fails in exactly the
  spectrum check (frames 0). Run in the container only.

The screenshots of the second run were looked at: the blip as one
peak behind the curve, and the dip at 4 kHz in the node.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 15:58:53 +02:00
adminandClaude Opus 5.5 25084f8576 Refactoring round 10: split SoundLibrary, fold sound duplicates (#85)
game-maker / gate (push) Successful in 4m3s
game-maker / suite (push) Successful in 14m52s
Refactoring round 10 covers everything since round 9 (808e1ee): 110
commits with about 47 000 new lines, nearly all of them sound, sound
FX, instruments, patterns and songs. Behaviour is unchanged.

Duplicate code (jscpd over src, 50 tokens, TypeScript only, measured
on 808e1ee and here alike: 47 clones / 426 lines before, 33 / 245
after; round 9 itself ended at 22 / 160 by this count):

- PreviewParams (new, editor/soundfx) is the header's parameter bar
  of the Sound FX and the Instrument tab: values, defaults, fields
  and the voices a moved field reaches. Both tabs had it line for
  line.
- fxWorklet: an Effect base runs the bit crusher, phaser, flanger,
  limiter, LFO and bus shifter, a Source base the pitch player and
  the Chip. Start is the one wait for a voice's first sample, which
  four processors wrote out each.
- nodes/shared has checkPort, detachedPort, fxParamsPort and
  letRingPort. The Play and Stop nodes of sounds, notes, patterns and
  songs spelled each port out, up to five times.
- parameterValues builds both Sound FX parameters and Instrument
  parameters, which differed in name and text only.
- SetLoopCommand sets the ruler loop of a pattern or a song. It
  replaces SetPatternLoopCommand and SetSongLoopCommand.
- addLink in music/routing is the one rule for a link that both the
  song routing and the Master routing keep.
- eqCurves works out each band and their sum for the EQ tab, which
  did that in its drawing code. It replaces eqCurveDb, which only a
  test called.
- Smaller ones: the eight neighbour directions of a tile cell, and
  drawFloatingBox for the shadowed box of Popup and FloatWindow.
- Left on purpose: Play pattern and Play song stay two parallel node
  definitions, and the smaller clones under ten lines stay.

Architecture:

- SoundLibrary.ts goes from 2897 to 2188 lines. What a stage of a
  sound FX becomes as Web Audio nodes, and how the knobs of a playing
  voice move, is now viewport/fxNodes.ts. None of it keeps state of
  the library.

Dead code (TS language service, references per export and member):

- soundRefIds, LFO_SHAPE_INDEX, TREMOLO_ROWS, VIBRATO_ROWS and
  REVERB_ROWS had no reader. The four routing node defs are
  registered without an unused exported name.
- soundFileOf and portTypeOf were called by tests only. The tests
  now ask findSound and portOf, which the editor uses. DRY moves
  from src into the one test that uses it.
- 51 values used only in their own file lose their `export`. Types
  and the node catalogues stay exported.
- Kept on purpose: neighboursOf is the cell by cell reading the
  indexed neighboursIn is checked against, and PitchShifter.latency
  is the test's one view of how late the shifter is.

Comments and docs:

- Seven comments told history ("no longer", "any more", "as
  before", "of before #354"). Nine comment blocks ran far past the
  line width. Proof that this changed no code: the bundle was md5
  identical, and the touched tests and e2e modules were identical
  after esbuild.
- README: sound FX, instrument, pattern, song and bus in the asset
  list, the pages Instruments, Patterns and Songs, 1919 checks.
- Wiki: see its own commit.

Tests:

  Gate (melektaus-atx)    274 files, 4236 tests, CHECK EXIT 0
  e2e 34, 64, 65          ALL PASSED, 180 checks
  Unit, touched areas     16 files, 804 tests

No check was added, the e2e sum stays at 1919. Counter-checks, each
put back by copy and compared with cmp:

- addLink without the loop rule: 2 tests fail, one per routing.
- eqCurves with the sum of the last band only: 1 test fails.
- Start in the worklet never waiting: e2e 34 fails in exactly the
  four checks that hang on a voice's first sample (Flanger, Tremolo,
  Chip, a pitched note planned ahead).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 15:31:33 +02:00
adminandClaude Opus 5.5 9ab1bbc507 Edit a Parametric EQ in a tab of its own (#370)
game-maker / gate (push) Successful in 4m21s
game-maker / suite (push) Successful in 15m17s
The EQ's editor floated over the editor as a window since #367. The
user wants it as a tab for now. Whether any tab can float and dock
again is weighed apart from this.

- A double-click on the node asks for the tab through
  `EditorState.openEq`, as `openValues` does for a Variables tab.
- The tab is a document `eq:<asset>:<node>`, one per EQ node. A
  second double-click brings the open one up.
- Its name is the asset's, with `EQ 2`, `EQ 3` when the asset has
  more than one. The icon is the sound FX's.
- The tab closes with its node, its asset, or a new project, through
  the same `syncDocuments` as every document.
- `EqWindow` becomes `EqEditor`, a `VStack` with a `TabHeader`. The
  curve takes the room of the tab, 320 x 160 at least.
- The window's title bar, dragging and Close button are gone.
- Delete in the EQ's tab removes the picked band. It never falls
  through to the selection, which is still the EQ's node.
- The hint below the curve is shorter, it was cut off at 1280 px.

Tests:
- e2e 34 (melektaus-atx): ALL PASSED, 96 checks, 2 rewritten. The
  tab opens on a double-click and goes with its node.
- Gate (melektaus-atx): CHECK EXIT 0, 274 files, 4231 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 13:45:02 +02:00
adminandClaude Opus 5.5 af2312a5e7 Play every tab's preview straight into the master (#368)
Since #351 the tabs played their asset along its links in the Master
routing, as the game does. A song linked into a reverb bus was heard
through that bus while it was edited. A preview should sound like the
asset itself, so the tabs leave the Master routing out again.

- The Sound, Sound FX, Instrument, Pattern and Song tab play into the
  master. The sound FX of the asset still applies, and so does the
  master's own volume and FX.
- Inside a song its own routing of tracks and sends still counts.
- The game is unchanged: Play nodes go along the routing as before.

Tests:
- e2e 34 (melektaus-atx): ALL PASSED, 96 checks. The routing check
  now holds the mix of the two buses as before, and the Sound tab on
  the master with the bus silent.
- Counter-probe e2e: the Sound tab along the routing again, the
  routing check red.
- Gate (melektaus-atx): CHECK EXIT 0, 274 files, 4230 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 13:09:36 +02:00
adminandClaude Opus 5.5 1f13794b0c Shape a sound with a Parametric EQ and its own curve editor (#367)
game-maker / gate (push) Successful in 4m17s
game-maker / suite (push) Successful in 16m14s
A sound FX or an instrument had a 5-Band-EQ with fixed bands and a
Filter with one band. The Parametric EQ holds up to ten bands of any
type, and a double-click on the node opens an editor that draws the
curve they make together.

- The bands are a list on the node (`bands`): type, frequency, gain,
  Q and slope. A hand-edited project is held to the editor's ranges
  and to ten bands.
- Each band compiles to the existing `pass` stage, so the audio core
  is unchanged. A peak or a shelf at 0 dB is left out.
- The curve is worked out in `parametricEq.ts` with the biquad
  formulas of the Web Audio spec, so the editor draws what is heard.
- The editor is a window that floats over the editor without a
  backdrop, so the tab's Play stays usable. A double-click adds a
  peak, a drag moves a band, the wheel turns its Q, a right-click
  removes it. Fields below the curve set the picked band. It opens in
  the middle of the screen and moves by its title bar.
- A drag or a scrub writes the node as a preview and makes one undo
  step on release. The panels hear of the preview once a frame, and
  the window lays itself out only when one of its texts changes.
- The area under the curve is filled as one shape for each side of
  0 dB. As a single shape it crossed itself and came apart.
- The curve works out each band's coefficients once and adds the sum
  up from the bands. A drag took 295 ms of script time in a profile
  before and 64 ms after.

Decided on my own:
- Bands are added by a double-click, so a single click can let go of
  the picked band without adding one.
- The cuts are labelled "Low cut" and "High cut" as in an EQ, not
  "High-pass" and "Low-pass" as on the Filter.
- The bands have no input, so no parameter or Envelope moves them.

Tests:
- Unit parametric-eq (new) and soundfx: 69 green. Counter-probe with
  no stages emitted: the compile test red. Counter-probe with one
  fill shape: the fill test red.
- e2e 34 (melektaus-atx): ALL PASSED, 96 checks (3 new).
- Counter-probe e2e: no stages emitted, the playing check red.
- Gate (melektaus-atx): CHECK EXIT 0, 274 files, 4230 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 12:00:43 +02:00
adminandClaude Opus 5.5 87ec62aace Keep the scheduler check in e2e 34 from flaking on a clock tick (#366)
The check "the scheduler plans a looped bar" read the position and
the audio clock one after the other and compared them to 1 tick. In
Chrome the audio clock moves on by a render block even inside one
task, so now and then the two reads lay 2.9 ms (3.5 ticks) apart.
It was red in CI task 580 and in 2 of 5 runs during #351.

- The clock is now read before and after the position. The position
  has to lie between the two, with 1 tick to spare, also across the
  seam of the loop.
- The scheduler itself is unchanged, it was never wrong.

Tests:
- e2e 34 (melektaus-atx): ALL PASSED, 93 checks, 3 of 3 runs.
- Counter-probe with the position 10 ticks off: this check red.
- Only the e2e file changed. node --check, Prettier and ESLint are
  clean on it, the gate did not run again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:51:15 +02:00
adminandClaude Opus 5.5 0e73fd85b9 Route sounds, songs and buses in one Master routing graph (#351)
game-maker / gate (push) Successful in 4m20s
game-maker / suite (push) Failing after 15m17s
A project bus could only go into one other bus, and a sound, an
instrument, a pattern or a song into exactly one bus. A shared reverb
needs a sound to go there in part and on dry at the same time. Instead
of a list of sends on every asset, the project gets one routing graph
like the routing of a song (#350), one level up.

- Menu -> Master routing opens a tab with one graph per project. It has
  a node per sound, instrument, pattern, song and bus, named after its
  asset, and one Master. Embedded assets have none, their song routes
  them.
- The nodes follow the assets. Every edit settles the routing in the
  same undo step (KeepRoutingCommand), so an undone delete brings the
  links of its asset back. A new asset gets a node and a link to the
  master. What went only into a deleted bus goes to the master.
- New nodes start in three columns: what plays, the buses, and the
  master a row above, so no link into it runs behind a bus.
- Links are sends: -60 to +12 dB, and a Mode on links out of a bus or a
  song. Out of a sound, an instrument or a pattern a link always takes
  the sound after its volume and FX. Loops are refused.
- A node without a link is silent and says "Goes nowhere".
- The rows Bus and Into are gone. Play nodes with a picked bus still
  overrule the routing.
- A click on a node shows its asset in the inspector, the Master shows
  the master volume and sound FX. A double click opens the asset's tab.
- The graph lives in project.routing and is held to the assets when a
  project is read, but nothing is added there. The editor adds the
  nodes when it opens a project. An asset without a node plays on the
  master, so old projects sound as before without a migration.
- In the mix an asset with one link at 0 dB goes straight into that bus.
  A sound or instrument with more gets a bus of its own with the sends.
  A pattern or song sends from the master of its playing, which is
  where its Mode taps. Buses of the mix can have sends now too.

Found:

- The check "the scheduler plans a looped bar" in e2e 34 wobbles. It
  failed in 2 of 5 runs here and in CI task 580 at 490fcba, before
  this change. HEAD passed it 2 of 2. Not fixed here.

Tests:

- Unit master-routing (new, 10), sound-library (2 new), buses
  rewritten onto the routing, 5 lists without the Bus row. 5 counter
  checks red.
- e2e 34 (melektaus-atx): ALL PASSED, 93 checks (3 new, 2 rewritten).
  Counter check with the Sound tab ignoring the routing: the routing
  check red.
- Gate (melektaus-atx): CHECK EXIT 0, 273 files, 4225 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 09:18:46 +02:00
adminandClaude Opus 5.5 cf96442835 Keep the Sound tab smooth while it pans (#360)
game-maker / gate (push) Successful in 3m51s
game-maker / suite (push) Successful in 15m55s
Panning the Sound tab drew the whole wave anew on every move, one
filled rectangle per pixel column, and gave the ruler's labels new
texts whenever a tick left the area. At 2560 px across that took the
tab down to 16 fps.

- The wave is drawn half the area's width past either side of it. A
  pan within that moves the drawing. Zoom, height, size or a pan
  past the margin draw it anew.
- The bars of the wave share one fill instead of one fill each.
- A ruler label keeps the second it already shows. Before, a pan
  shifted every label by one and each had to be rendered anew.
- A clip's name is given its style only when the style changes.
- The rule for what a drawing covers is pure in `waveView.ts`
  (`shownSeconds`, `drawnSeconds`, `coversShown`).

Measured with a copy of the user's project, "Take 1" with 4 clips,
240 pan moves with the middle button, headless on stein2:

- 2560x1440: main thread 712 -> 373 ms/s, texts rendered 694 -> 65
- 1280x800: main thread 429 -> 225 ms/s

What is left in the profile is Pixi's render pass and the pointer
events, which hovering pays as well.

Tests:
- Unit wave-view: 12 green (1 new), counter-probe red
- e2e 34 (stein2, melektaus-atx was down): ALL PASSED, 92 checks
  (1 new). Counter-probe with the labels given out by position: red,
  7 labels set anew instead of 1.
- Gate (stein2): 272 files, 4210 tests green

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-09 06:19:05 +02:00
adminandClaude Opus 5.5 95edf9959b Show the clips not picked faint over a sound's wave (#357)
Only the picked clip showed in the wave, between its handles. Where the
other clips lay could only be read from the strip above, so cutting one
next to another meant looking up and down all the time.

- Every clip but the picked one is drawn over the wave as a pale tint
  with a thin edge, as high as the file reaches.
- With the sound picked, all its clips are faint. While a drag spans a
  clip, that one is left out.
- Test hook soundTab.faintClips() names the faint clips.

Tests:
- e2e 34 (melektaus-atx): ALL PASSED, 91 checks (1 extended)
- Gate (melektaus-atx): CHECK EXIT 0, 271 files, 4195 tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 21:13:17 +02:00
adminandClaude Opus 5.5 852dce5142 Shrink a clip with the wave and drop it with a click beside (#357)
Zoomed out, the wave left room above and below it, but the bright file,
the picked clip and its handles still took the whole height. And a click
in the wave beside the picked clip kept it picked, so only the strip
could take it back to the sound.

- fileReach in waveView.ts is how far up and down the file reaches:
  full scale and its inset, never past the area. At level 1 it is the
  whole height, zoomed in too.
- The bright file, the shade outside the clip and the two handles are
  drawn within that reach.
- A click over the wave beside the picked clip picks the sound. Inside
  the clip or on a handle nothing changes, and a drag still spans the
  clip anew.

Tests:
- Unit wave-view: 11 green (1 new), a counter-check red
- e2e 34 (melektaus-atx): ALL PASSED, 91 checks (1 new)
- Gate (melektaus-atx): CHECK EXIT 0, 271 files, 4195 tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 21:13:09 +02:00
adminandClaude Opus 5.5 42a3759d9d Cut a sound into named clips, as a texture into frames (#357)
A sound could only be cut to one stretch, set on the asset itself. To
play two parts of one file, the file had to be imported twice. Now a
sound is always the whole file, and its clips are the stretches of it,
each with a name and a cut of its own.

- A clip is { id, name, props } on SoundAsset.clips. Its id shares the
  generator with asset ids, so a sound field holds a sound or a clip,
  as a texture field holds a texture or a frame.
- props are the cut: start, end, the fades and their curves, reverse.
  Volume, loop, sound FX and bus stay on the sound for all its clips.
  The cut rows left the sound's schema for CLIP_ROWS.
- findSound resolves either id. Play sound, the Sample of an
  instrument, a Reverb impulse, song items, the scheduler and the test
  sound of the Sound FX tab all go through it.
- Every sound field offers each sound followed by its clips, "Kick",
  "Kick / Clip 1". A clip that is gone reads as "<id>?".
- The Sound tab has a strip under the ruler with the clips as bands.
  A drag there makes a clip, a click picks one, beside the bands the
  sound. Only the picked clip has handles. Reverse, fades and curves
  in the header work on it and are greyed out without one.
- Whole file is gone from the header, as the user asked.
- Picking a clip is a selection of its own, like a frame. The
  inspector shows its name, its cut rows and Delete clip. Delete
  takes it away without asking, as with a frame. Ctrl+Z brings it
  back.
- Copies get fresh clip ids: Duplicate, Embed a copy, a song
  duplicated with its takes, and the copy a split or paste makes of
  an embedded take. An item on a clip plays the same clip of its copy.
- A double-click on a song item playing a clip opens its sound with
  the clip picked. The Sound row of an item on an embedded take
  offers the take and its clips, which no list shows.
- A new take stays the whole sound. Clips are made by hand, as the
  user decided.
- No migration in the code, as the user decided. The one project
  with cut sounds was changed over by hand.

Deviation from the issue: deleting a used clip does not warn. A frame
does not warn either, and the issue had promised "like a frame".

Tests:
- Unit sound-clips (new): 11 green, 7 counter-checks each red
- Unit sounds, embedded: adjusted to clips, green
- e2e 34 (melektaus-atx): ALL PASSED, 90 checks (3 new, 12 rewritten)
- Counter-check in the browser: clips left out of the sound fields,
  the picker check fails
- Gate (melektaus-atx): CHECK EXIT 0, 271 files, 4194 tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 20:18:47 +02:00
adminandClaude Opus 5.5 c7fa1d15b0 Zoom the height of a sound's wave over its level scale (#357)
The Sound tab always drew the wave at the full height of the tab. Quiet
stretches were hard to see, and there was no room above a loud wave.

- A level scale in dB stands left of the wave, a mark every 6 dB down
  from 0, as long as a mark lies inside the area and at least 14 px
  from the middle.
- The wheel over the scale zooms the height, from 0.25x to 16x. Over
  the wave it zooms the time as before.
- What is louder than the area stops at its edge. The fade ramps grow
  with the wave.
- Fit puts the height back to 1x. The tab keeps the height with its
  view when another tab is shown.
- The rules live in waveView.ts (zoomLevel, levelMarks), the hooks
  give the height, the scale and the top of a full-scale wave.

Tests:
- Unit wave-view: 10 green (2 new), 2 counter-checks each red
- e2e 34 (melektaus-atx): ALL PASSED, 87 checks (1 new)
- Gate (melektaus-atx): CHECK EXIT 0, 270 files, 4183 tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 19:21:02 +02:00
adminandClaude Opus 5.5 25c1d02b06 Keep the audio thread free of ended voices and too many vocoders (#356)
game-maker / gate (push) Successful in 3m45s
game-maker / suite (push) Successful in 14m44s
A song that had been played a few times in a long session stuttered
at regular intervals until the page was reloaded. A quick run up the
keys of a sampled instrument made every sound drop out for a second
or two. Both came from work the audio thread should not have had.

- Every worklet node of a voice listened on its port. Chrome never
  collects a port that is listened to, and through its listener not
  the voice either. Every node of the voice stayed on its bus and was
  worked on in every block. A way or stage on the worklet now closes
  its port once it ends or is stopped, and the processor closes its
  own side when it is told to stop.
- A voice without echoes now leaves its bus as soon as its sound and
  what its stages hold back are through. Before, only a voice with
  echoes did.
- At most 12 ways play through the pitch worklet at once. One more
  cuts the oldest in 10 ms, a note already let go before one still
  held. Each runs a phase vocoder, whose first block alone took
  2.7 ms of the 2.67 ms a block has on stein2.
- Play for the game ends everything the tabs play: the piece holding
  the transport and every key or preview (`stopPreviews`).
- Closing a Sound, Sound FX, Instrument, Pattern or Song tab stops
  what it plays of that asset (`AssetTabEditor.closed`).
- A preset in the Instrument tab plays nothing any more. With
  Audition on it is heard like any other change.
- Test hooks: `__gm.instrument.play`, `__gm.sound.playing`, and
  `vocoders` in the stats of the sound library.

Measured:
- A copy of the project, its Song 2 played for 12 s and stopped, six
  times (Chrome on stein2, CDP WebAudio): before, 746 nodes were left
  after the first time and 4469 after the sixth, the load at rest
  grew from 0.01 to 0.06. After, 7 nodes are left every time (the
  master and one bus) and the load at rest stays at 0.01 to 0.02.
- Pitch-player voices live in Chrome on melektaus-atx: 12 take 0.50
  of the audio thread, 16 take 0.71, 24 take 0.99. 30 struck 40 ms
  apart held it at 0.99 from the first second on.

Tests:
- Unit sound-library: 110 green (3 new), 4 counter-probes each red
- e2e 34 (melektaus-atx): ALL PASSED, 86 checks (3 new, 2 changed)
- e2e counter-probe with game start and preset undone: their 3
  checks red, and the old check of Play note in the game as well,
  which now counts on the preview being gone
- e2e counter-probe with tab close undone: exactly its check red. A
  first form of it held a key and stayed green, since the keys let go
  of a held key by themselves once the tab shows no asset
- Gate (melektaus-atx): 270 files, 4181 tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 17:57:18 +02:00
adminandClaude Opus 5.5 7a17030472 Give every sound FX effect a Bypass and a Wet (#354)
A node could only be taken out of a sound FX by unplugging it, and
only six of them could be blended with the sound they get. Every
effect now has both, in the sound FX graph and in the instrument
graph alike. The Mix, the Input and the Output have neither.

- Bypass is a checkbox setting. A bypassed node is left out when
  the graph is compiled, so it costs nothing and holds nothing back.
  The graph draws it faded, and its body says "Bypass: true".
- Wet is a number input like the others. It can be a parameter, be
  moved by Set sound FX parameters and, where the node allows it for
  its other numbers, follow an Envelope.
- A blend is a stage of its own (`wet`) around what the node adds.
  The sound as it came in is held back as far as the node is late,
  so a Compressor, a Limiter or a Vibrato do not comb it out.
- The six nodes that had a Mix keep their key `mix` and only call
  it Wet. Saved values, parameters and edges stay as they were.
- The Delay crossfades now: the sound alone at 0, the sound and its
  echoes in full at 0.5, the echoes alone at 1. Before, the sound
  was always in full and `mix` was the echoes.
- The Pitch takes its semitones by Wet instead of blending, through
  a share `times` on the amount that an instrument binds as well.

Findings:

- A Delay saved before this sounds different. It sounds the same at
  half its old value. The two chip presets with a Delay (Echo Lead,
  Bell) are halved to keep their sound.
- Project cbbf160bb7e8 has two Delays (mix 1 and 0.5). They are
  left as they are.

Tests:

- Unit: soundfx 65 (8 new), sound-library 107 (3 new), instrument
  35 (1 new), envelope 13. Two delay tests and the filter ports test
  follow the new numbers.
- Counter-checks: 8, each turns its test red.
- e2e 34 (melektaus-atx): ALL PASSED, 83 checks (2 new). The
  ping-pong check at wet 0.25 measures what it did at mix 0.5.
- Gate (melektaus-atx): CHECK EXIT 0, 268 files, 4154 tests.
- After the gate the derived ports of a blend are asked for once
  instead of twice: typecheck, lint and the three unit files green
  again (113 tests).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 08:20:52 +02:00
adminandClaude Opus 5.5 d349251a01 Put a sound in a room, a hall or a cave with a Reverb node (#328)
game-maker / gate (push) Successful in 4m0s
game-maker / suite (push) Failing after 13m51s
A sound FX had no space around it. The Reverb node folds the sound
with an impulse response: one it makes itself, or a sound asset as the
answer of a real space. It works on a voice and on a bus.

- Type: Room, Hall, Cave and Plate have fixed numbers. Custom shows
  Decay (up to 10 s), Damping and Pre-delay. Sound shows the pick of a
  sound asset and Pre-delay. The rows are derived ports, the sound pick
  an idle param, so they only appear for their type.
- The made impulse response is seeded noise per side, 60 dB down at
  Decay, through a low-pass closing from 20 kHz toward 500 Hz as far
  as Damping says. It is made once per rate, decay and damping, the
  last 16 are kept.
- A sound as impulse response: the sound FX compilers now get the
  project, so a node can turn a sound asset into its file. The build
  key holds that file, so a cut or swapped sound builds again. The
  instrument compiler does the same for a Reverb inside an instrument.
- The library loads that file with the voice, a sequence or before a
  bus builds its chain. A missing file leaves the reverb silent.
- Mix blends dry and reverb and is the only number that moves while
  the sound plays, as the issue said. The voice rings for pre-delay
  plus decay, or as long as its sound.
- The open point from #325 about sends on project buses is #351, not
  part of this.

Tests:
- Unit reverb (new) 4, soundfx 57 (4 new), sound-library 104 (2 new),
  green.
- Counter-checks, each turns one test red: the impulse file not loaded
  with the voice, the tail without the length of the sound.
- e2e 34 (melektaus-atx): ALL PASSED, 81 checks (1 new). Chrome
  offline: silent during the pre-delay, 35.9 dB fall over 0.6 s of a
  1 s decay (36 expected), the dry half at 0.25 for mix 0.5. A sound
  with one sample at 0.2 s gives the impulse back 0.25 s later and
  nowhere else.
- Gate (melektaus-atx) over the end of this series #318 to #328:
  CHECK EXIT 0, 267 files, 4134 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:27:22 +02:00
adminandClaude Opus 5.5 071f194326 Swing the volume or the pitch with Tremolo and Vibrato (#320)
Alarms, sirens and wobbly retro sounds need a volume or a pitch that
swings. Two new nodes do that, on a voice and on a bus alike.

- Tremolo: Rate (0 to 20 Hz), Depth (0 to 100 %), Shape (Sine,
  Triangle, Square, Saw). The volume swings between full and
  1 - depth.
- Vibrato: Rate (0.5 to 20 Hz), Depth (0 to 4 semitones), Shape. The
  shape is the curve of the pitch, a Square jumps between two pitches.
- As the issue decided: two nodes, phase 0 on the first sample of the
  voice, no fade-in time. Both run on the FX worklet, the classes are
  in lfo.ts and tested in Node.
- Deviation from the issue text, as in the plan: the Vibrato is a delay
  that swings, not playbackRate. A bus has no playbackRate, and the
  sound keeps its length.
- The delay swings around a center taken from the start values. The
  center counts as the latency of the node, so a voice starts that
  much earlier and a Mix and a bus make up for it.
- Known limit, in the node text and the wiki: a depth turned up or a
  rate turned down after the start asks for a delay below 0. The lower
  half of the swing then flattens.

Finding: the first e2e run was red at the edge of a window. The phase
is summed up, so the square flips one sample later than 2400 steps of
10/48000 suggest. The check keeps 10 samples away from the edges.

Tests:
- Unit lfo (new) 5, soundfx 53 (2 new), sound-library 102 (1 new),
  green.
- Counter-checks, each turns one test red: the swing turned over (up
  and down swapped), the start time not passed to the worklet.
- e2e 34 (melektaus-atx): ALL PASSED, 80 checks (1 new). Chrome
  offline: Tremolo square silent before its voice, then 0.5 and 0 for
  50 ms each. Vibrato square at 5 Hz, 1 semitone: 1059.5 Hz and
  940.5 Hz.
- The gate runs once on the last commit of this series.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:13:18 +02:00
adminandClaude Opus 5.5 d3a4cb7d0e Warm up or distort a sound with a Saturation node (#323)
A sound FX had no way to bend a sound. The Saturation node pushes it
into a curve, from warmth to a hard distortion, on a voice or a bus.

- Eight types, each a fixed curve made once: Soft (tanh), Cubic, Hard
  clip, Tube, Fuzz, Foldback, Sine fold and Rectify. The issue asked
  for as many as useful.
- Drive (0 to 48 dB) is a gain in front of the curve. The curve spans
  256 times full scale, so the drive glides like any gain.
- Output and Auto-gain blend as on the Compressor. Auto-gain brings a
  sine at -12 dBFS back to the RMS it came in with.
- Tone is a low-pass after the curve, 1 kHz at 0 to 20 kHz at 1, as
  recommended in the plan. A 10 Hz high-pass takes out the DC the
  lopsided types make.
- Mix blends with the dry sound. The 4x oversampling is 192 samples
  late in Chrome, so the dry way is held back as far. The node counts
  as late by that much for voices, Mixes and buses.
- A test hook gives e2e the auto-gain at a drive.

Tests:
- Unit saturation (new) 5, soundfx 51 (2 new), sound-library 101
  (1 new), green.
- Counter-checks, each turns tests red: no hold-back of the dry way
  (1), the level without auto-gain (2).
- e2e 34 (melektaus-atx): ALL PASSED, 79 checks (1 new). Chrome
  offline: 192 samples late at 48 and at 44.1 kHz. Half dry at 1125 Hz
  0.00 dB. A sine at -12 dBFS driven 24 dB into Soft comes out at
  0.00 dB with full auto-gain.
- The gate runs once on the last commit of this series.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 21:04:16 +02:00
adminandClaude Opus 5.5 a645026568 Tame the loudness with a Compressor and a Limiter node (#326)
A sound FX could not control dynamics. A Compressor squeezes the
loudness together, and a Limiter keeps every peak below a line. Both
work on a voice and on a bus, where they tame everything at once.

- Compressor: DynamicsCompressorNode with Threshold, Ratio, Knee,
  Attack and Release, then Makeup and Auto-gain from 0 to 1. After the
  node the sound gets (1 - w) * makeup + w * A dB. A is what the curve
  takes off at 0 dBFS.
- The node adds 0.6 of A by itself, as the spec says. That gain is
  taken back out, so 0 really means no makeup.
- The knee shape is left to the browser by the spec. dynamics.ts ports
  Chromium's curve. It gives the gain Chrome was measured to add at
  four settings to three places.
- Limiter: a class on the FX worklet with Threshold, Ceiling, Release
  and Auto-gain. It looks 5 ms ahead: the smallest gain of the window
  ahead, averaged over the window, then a release. No sample passes the
  lower of threshold and ceiling. Auto-gain raises it toward the
  ceiling.
- Deviation from the plan in the issue: the Limiter has a Threshold as
  well. Without it Auto-gain has nothing to measure against.
- Latency is made up on voices now too. A voice starts as much earlier
  as its stages are late (Compressor 6 ms, Limiter 5 ms). The other
  way of a Mix and the other layers of a note are held back to the
  latest, and a bus counts it into its latency.
- A voice with a Limiter counts as ringing, so the worklet is stopped
  only after what it still holds is out.

Tests:
- Unit dynamics (new) 8, soundfx 49 (3 new), sound-library 100 (4 new),
  green.
- Counter-checks, each turns tests red: no look-ahead in the Limiter
  (3), no taking back of the node's gain (1), a voice not started
  earlier (1), no stage latency on a bus (1), no hold-back in a Mix (1).
- e2e 34 (melektaus-atx): ALL PASSED, 78 checks (1 new). Chrome
  offline: Compressor 288 samples late, Limiter 240. Full scale through
  -12 dB 4:1 hard knee: +0.19 dB with full auto-gain, -8.81 dB without.
  A sine at 2 through the Limiter at -6 dB peaks at -6.00 dB, with
  auto-gain 1 at -1.00 dB.
- The gate runs once on the last commit of this series.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 20:57:22 +02:00
adminandClaude Opus 5.5 c99bda6489 Move a sound left or right with a Pan node (#319)
A sound FX had no way to place a sound in the stereo field. The new
Pan node does that on a voice, a bus and a track alike.

- One input, Pan from -1 (left) to 1 (right), 0 by default. Behind it
  is a StereoPannerNode. A typed-in 0 leaves the node out, a pan fed
  from a parameter stays at any value.
- It moves with Set sound FX parameters, and an Envelope can move it
  in an instrument.
- As the issue decided, no pan from the position in Play sound and no
  width control for stereo sounds.

Finding: the issue and the plan said a stereo sound gets a balance.
Web Audio pans stereo differently: the side it goes to stays and the
other side moves over into it. The node description and the wiki say
so, the e2e check measures it.

Tests:
- Unit soundfx 46 (1 new), sound-library 97 (1 new), green.
- Counter-check: a pan from a parameter at 0 left out, 1 test red.
- e2e 34 (melektaus-atx): ALL PASSED, 77 checks (1 new). Chrome
  offline, a mono sine at 0.5 gives 0.5 / 0 at -1 and 0.354 / 0.354
  at 0. A stereo one at 0.5 panned 0.5 gives 0.354 / 0.854.
- The gate runs once on the last commit of this series.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 20:47:53 +02:00
adminandClaude Opus 5.5 e1ca6a5f5a Shape a sound with every filter type, a Q and a slope (#318)
The Filter node from #335 only knew low-pass, high-pass and band-pass
and had a Resonance from 0 to 1. It now has every type Web Audio
offers, a Q as a plain factor and a slope up to 48 dB per octave.

- Types: low-pass, high-pass, band-pass, notch, low shelf, high shelf,
  peaking and all-pass. The cutoff row is now labelled Frequency, its
  key stays `cutoff`.
- Q from 0.1 to 30, 0.707 by default, replaces Resonance. Web Audio
  takes the Q of a low- or high-pass in dB, so it is converted when the
  voice is wired. No project used the node.
- Gain in dB is an input only for the shelves and the peak. It is a
  derived port, so the sound FX compiler now resolves derived ports.
- Slope 12, 24 or 48 dB/oct runs 1, 2 or 4 filters in a row. They
  share one cutoff carried by a ConstantSource, only the last has the
  Q. Shelves, peak and all-pass hide the row.
- The Chip Presets get the Q their resonance gave before. Mutate moves
  Q in octaves and gain by up to a dB.
- bindStage in the instrument compiler binds every amount of a stage
  generically, so new stages need no case of their own there.

Finding: FLAT_Q was in dB and the delay damping used it. It is a
factor now, the damping takes it through passQ and is unchanged.

Tests:
- Unit envelope 13, sound-library 96 (2 new), chip-presets 14,
  soundfx 45, all green.
- Counter-checks: the Q on every filter in a row, the gain row for
  every type. Each turns one test red.
- e2e 34 (melektaus-atx): ALL PASSED, 76 checks (1 new). Chrome
  offline, 2 kHz through a low-pass at 1 kHz: -12.37 dB at 12 dB/oct,
  -49.5 dB at 48. A +6 dB peak gives 6.00 dB, a notch -124 dB.
- The gate runs once on the last commit of this series.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 20:43:49 +02:00
adminandClaude Opus 5.5 3a52f2290f Mix sounds through buses and a master (#325)
game-maker / gate (push) Successful in 3m36s
game-maker / suite (push) Failing after 14m30s
Until now every sound went straight to the speakers. There was no
point where all of them came together, so nothing could turn the
music down against the effects, and a compressor or a shared echo
over everything had nowhere to sit. The bus technique of #346 was
there already, only the end of the chain was missing.

- Bus asset (Audio): Volume, Sound FX and Into. Into is empty for
  the master and never offers the bus itself nor a bus that already
  goes into it. The list is worked out when the menu opens, so a
  change made meanwhile counts. A loop that is in a project anyway
  sends the buses on it into the master.
- The master is no asset. Its volume and sound FX are two new rows
  under Master in Project settings, one undo step each. A project
  written before has 1 and none, which is how it sounded.
- Sound, instrument, pattern and song have a row Bus, empty for the
  master. Play sound, Play note, Play pattern and Play song have an
  input bus: From the asset, Master or a bus. Inside a song or a
  pattern only the bus of the song or pattern counts.
- Set bus volume (bus, volume, time, easing) and Bus volume. An
  empty bus is the master. The volume set holds for the whole run,
  across scenes, over that of the asset. GameRun resets it at start
  and end. Set sound FX parameters without a voice now reaches the
  sound FX of the bus picked in its new bus input.
- SoundLibrary: the master is the bus "mix", each bus "mix:<id>".
  Every bus, the playing masters of #346 included, ends there, and
  only the master is heard. setMix sets them up once a context
  exists, so an editor nobody listened to opens no audio device. The
  same mix again changes nothing, which lets the editor hand it over
  on every change of the project.
- The tabs play as the game does: Sound, Sound FX, Instrument,
  Pattern and Song tab through the bus of their asset and the master.
- Pause game does nothing to the buses (the user's answer 4b). A
  game that wants quiet in its pause menu sets a bus volume itself.
- Streaming long music files is #349, split off as asked.

Finding on the way: the Into menu first worked from a list made when
the inspector row was built. After another bus was sent into the
open one, e2e 34 saw it still offered. It is now asked on open.

The SoundLibrary tests expected voices to end at the destination.
They now end in the master, which adds its input, level and pan to
every wiring and two gains to every list. Those expectations are
updated, nothing else in them changed.

Tests:
- Unit buses (new): 15 green. Ten counter-checks, each red on its
  own: loop check, bus picked, reset at run start, bus of Set sound
  FX parameters, master volume in the settings, and in SoundLibrary
  default into the master, voices into the master, run volume over
  the mix, no context before a play, a playing on its bus.
- Unit sound-library: 90 green (5 new). Node catalog: 416 green
  (9 new cases).
- e2e 34 (melektaus-atx): ALL PASSED, 75 checks (3 new). The voice
  of the Sound tab was heard on mix:<bus>, that bus into the other,
  that one at 0.5 into the master. Master volume 0.6 via the dialog,
  undo back to 1.
- Gate (melektaus-atx): CHECK EXIT 0, 262 files, 4072 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 19:13:32 +02:00
adminandClaude Opus 5.5 101b12504e Play a sound backwards with a Reverse switch on the asset (#321)
A sound asset can now play its stretch backwards. The switch sits on
the asset, so every way the sound is heard plays it reversed: Play
sound, a song item, an instrument's Sample and a Pitch in a sound FX.
There is no Reverse node in the sound FX graph. The user dropped it,
because playing backwards needs the whole sound in advance, which a
bus or a Chip does not have.

- The cut gets a `reverse` flag, stored as the asset property
  `reverse` (default off). The cut name ends in `~reverse` when it is
  on, so a reversed sound over the whole file is a cut of its own.
- The handles keep marking the same part of the file. The reversed
  copy is made once, like any cut, and the fades follow what is heard:
  Fade in ramps at the end handle, Fade out at the start handle.
- The Sound tab has a toggle with a left-pointing play triangle. Its
  playhead runs from right to left, and the wave draws the ramps at
  the edges they now belong to. The inspector has a Reverse row.
- The icon is a tab header icon (`HEADER_ICON_SVG`, next to the snap
  magnet), not a top bar icon. The first gate run had it in the top
  bar set, and the test that fixes that set went red.
- A song item draws the waveform of a reversed sound backwards.
  Lengths stay as they are.
- The header label reads "Fade in" instead of "Fade in (ms)" and the
  fade fields are 8 px narrower. With the new button the readout was
  cut to "0.03 s of 0..." at 1280 px, now it shows in full. The
  inspector keeps the unit.

Tests:
- Unit sounds 22, sound-library 85 (1 new each). Counter-checks:
  without turning the samples round, without moving the fade in to
  the end, and without mirroring the peaks, all red.
- e2e 34: ALL PASSED, 72 checks (2 new). The reversed stretch starts
  with the last samples of the forward one, and the playhead ran
  0.75, 0.60, 0.45, 0.30, 0.15 over the whole file.
- Gate: first run red, 1 of 4031 (the top bar icon list). After
  moving the icon, CHECK EXIT 0, 260 files, 4031 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 16:08:37 +02:00
adminandClaude Opus 5.5 01fd6e6464 Swirl and comb a sound with a Phaser and a Flanger node (#341)
sfxr's "phaser" is a comb filter, the sound plus a copy delayed by up
to 2.9 ms. That is a flanger, not the phaser a guitarist knows. Both
effects come in as nodes of their own, so each name fits its sound and
sfxr is covered in full. Both run on the FX worklet like the
Bitcrusher, because a feedback loop in Web Audio costs a render quantum
per round.

- Phaser: first-order allpasses (Stages 2, 4, 6, 8, 12) around
  Frequency, moved by a sine LFO Depth octaves wide at Rate Hz, with
  Feedback and Mix. An Envelope can move Frequency and Mix.
- Flanger: a delay line of Offset ms. Sweep moves it on in ms/s from
  the start of the voice, an LFO adds up to Depth ms. As in sfxr the
  delay is mirrored at 0, and it never goes past 20 ms. Feedback comes
  back exactly one delay later. An Envelope can move Offset and Mix.
- Mix 0 leaves the sound as it is, 1 is half sound and half copy. The
  notches are deepest there and the peaks stay at the sound's level.
  sfxr adds both at full, so its sound comes out 6 dB louder.
- The Flanger's processor gets the start of its voice and begins the
  sweep on that sample. stageNodes takes it as a new argument `at`.
- Explosion gets a Flanger in one draw out of two, with sfxr's offset
  and sweep and no LFO. Offset is offset^2 * 2.89 ms, Sweep is
  ramp^2 * 125 ms/s, signed so the direction holds.
- Randomize draws a Flanger's offset and sweep like sfxr. Its LFO and
  feedback and all of a Phaser are drawn within bounds of their own.
  Mix and Stages stay, as a Bitcrusher's Mix does. Mutate moves them
  too, and a 0 stays 0.
- New instrument preset Phased Pad: saw, Envelope, low-pass, Phaser of
  six stages. The other 14 are unchanged.
- The Bitcrusher's worklet setup is now shared with both nodes
  (workletNodes), its behaviour is unchanged.

Tests:
- Unit phaser (new): 8 green. Counter-check with clamping at 0 instead
  of mirroring and a flipped allpass coefficient: 3 red.
- Unit soundfx, sound-library, chip-presets: 45 / 76 / 14 green.
  Counter-checks: start time not passed on, phaser frequency eased
  linearly, sweep sign dropped, Phaser left out of Mutate, each red.
- Unit core, viewport, graph: 179 files, 2979 tests green.
- e2e 34 (melektaus-atx): ALL PASSED, 70 checks (1 new), first run.
  Chrome offline: 1 kHz notch RMS 0.0000 with two stages, 0.5657
  with four. Flanger echo after 48 samples, swept echo at 533/534.
  About 47 Phasers (12 stages) or 63 Flangers fill the audio thread.
- Gate (melektaus-atx): CHECK EXIT 0, 257 files, 3991 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 12:11:54 +02:00
adminandClaude Opus 5.5 29b604dc07 Fade a sound's start and end apart, each along its own curve (#345)
A cut sound had one edge fade for both ends, always a straight ramp.
Now its start and its end each have a fade of their own, and each
fade has a curve of its own.

- Fade in (ms) and Fade out (ms) replace Edge fade (ms), 5 ms each by
  default. Like before they apply only to an edge inside the file.
- Fade in curve and Fade out curve each offer Linear (the default),
  Equal power, S-curve, Exponential and Logarithmic. A fade out runs
  its curve backwards.
- The Sound tab's header has a field and a curve dropdown per edge.
  Each dropdown shows its curve as a small picture, rising or falling,
  and names it in its tooltip. With two named dropdowns the header was
  too wide at 1280 px and cut off the readout on the right.
- The inspector shows the same four rows, the curves by name.
- The wave draws each ramp in its curve rather than as a line.
- A cut plays by
  <file>#<start>~<end>~<fadeIn>~<fadeOut>~<curveIn>~<curveOut>.
- No migration, as asked in the issue: the old fade key is no longer
  read, so saved sounds start with 5 ms each and Linear on both.

Tests:
- Unit sounds, sound-library: 96 tests
- Counter-check (fade out along the fade in's curve): 1 red
- e2e 34 (melektaus-atx): ALL PASSED - 69 checks
- Gate (melektaus-atx): CHECK EXIT 0, 256 files, 3978 tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 09:48:59 +02:00
adminandClaude Opus 5.5 6b49a0bea7 Give each account its own folder of uploaded files to delete (#344)
Uploaded images, fonts and sounds belonged to nobody. Every account
saw every file in the pickers, and only an administrator could
delete one, through the API alone. Now every account uploads into a
folder of its own, sees its own files and nobody's, and can delete a
file of its own that no project uses.

- Files are stored as data/<kind>/<folder>/<sha256>.<ext>, and the
  folder is part of asset.file. A name without a folder belongs to
  nobody.
- The folder is a random 12-character name kept in users.json and
  given with the first upload. Renaming an account moves no file, and
  a released game's URLs carry no account name.
- The list shows the caller's own files and nobody's. An
  administrator sees all, each with owner and deletable.
- DELETE takes the name with its folder. It answers 403 unless the
  file is the caller's or the caller is an administrator, and 409
  while a project, snapshot or release of any account names it
  (FileUsage). The 409 names the caller's own projects only.
- The pickers have a Delete button. The first press asks, the second
  deletes, and a refusal shows the server's reason.
- A deleted account's folder stays and only administrators see it.
- tools/migrate-file-owners.php moved every used file into the folder
  of the account using it (a copy each for two accounts) and rewrote
  projects, snapshots and releases. A project's revision goes up, so
  an open editor gets a conflict instead of saving old names back.
- Font families are named after the hash alone, so a folder never
  ends up in a CSS family name.

Run on stein2 on 2026-10-07, backup in
/root/config-backup-20261007/data-before-344: 25 files used,
27 copies, 8 projects, 53 snapshots and 1 release rewritten, 30
unused files stay nobody's. Afterwards all 600 references resolve to
a file in their owner's folder.

Found while building: the Delete button cut its new label to "Del…"
because a button does not measure itself again on a new text. The
picker now lays its button row out again.

Tests:
- PHP files.test.php (new): 46 checks, counter-check 3 red
- Unit core + editor: 220 files, 3403 tests
- e2e 34 (melektaus-atx): ALL PASSED - 67 checks (2 new)
- Gate (melektaus-atx): CHECK EXIT 0, 256 files, 3977 tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 08:25:13 +02:00
adminandClaude Opus 5.5 a2d0a02645 Play tunes on 14 instrument presets next to the sfxr sounds (#329)
game-maker / gate (push) Canceled after 9s
game-maker / suite (push) Canceled after 13s
The Preset menu only had sfxr's effects: each a fixed pitch on every
key that dies away by itself. Now its top half holds instruments to
play melodies, basses and drums on.

- Instruments: Square Lead, Pulse Lead, Echo Lead, Octave Arp, Synth
  Brass, Detuned Saws, String Pad, Organ, Flute, Bell, Pluck, Triangle
  Bass, Pluck Bass and Drum Kit. A line in the menu parts them from the
  seven effects below.
- Their Chips follow the note, and all but Bell, Pluck and the drums
  hold it while the key is held.
- They come out the same every time they are picked. Randomize and
  Mutate make a variant, as before.
- They use what the graph already has: two Chips through a Mix for
  Detuned Saws, String Pad, Organ, Flute and Bell, an Envelope on a
  Filter's cutoff for Brass, Pluck and Pluck Bass, a Pitch for the
  detune and the octave, a Delay for Echo Lead and Bell.
- Drum Kit uses single notes 36 to 46: kick, snare, closed and open
  hat on their General MIDI keys, each ending by itself.
- A small Sketch helper lays the nodes on a grid. The sfxr presets use
  it too and come out as before.
- Numbers keep to the steps of their rows. Pulse Lead's duty is 0.13,
  not 0.125, because the field rounds to 0.01.

Tests:
- Unit chip-presets: 12 green (5 new). Every instrument compiles
  without a problem, doubles its frequency an octave up, holds unless
  struck and is the same on every pick. The drum kit sounds on exactly
  36, 38, 42 and 46. Countercheck with Follow note forced off: red.
- e2e 34 (CI image on stein2, melektaus-atx was off): ALL PASSED,
  65 checks (1 new). A first run had the scheduler's looped-bar check
  red by 10 ticks between two clock reads, which the presets do not
  touch; the second run was green.
- Gate (stein2): CHECK EXIT 0, 256 files, 3974 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 07:09:37 +02:00
adminandClaude Opus 5.5 f3ae19431a Make an 8-bit sound with a Preset, Randomize or Mutate (#329)
game-maker / gate (push) Successful in 3m25s
game-maker / suite (push) Failing after 14m27s
A Chip could play a sound, but finding a good one meant typing a dozen
numbers. sfxr's buttons do that in one click: a kind of sound drawn
anew every time, a wild draw, or a small nudge. The Instrument tab now
has the same, and plays the result at once.

- Preset menu with Pickup, Laser, Explosion, Powerup, Hit, Jump and
  Blip. It puts a new graph in place of the instrument's, as one undo
  step: Notes for all 128 notes, Chip, Envelope, Filter, Bitcrusher,
  Output. The Chip keeps its own frequency with follow note off, so
  every key plays the same effect, and sustain 0 ends it by itself.
- The presets are sfxr's generators, worked out into Hz, semitones and
  seconds with sfxr's own formulas, in chipPresets.ts. sfxr's envelope
  holds and falls in a line, ours falls quick at first. So the decay
  is three times sfxr's hold and fall.
- Randomize draws every number of the Chips, Envelopes, Filters and
  Bitcrushers anew, as sfxr's randomizer does. Waves, filter types,
  the Bitcrusher's mix and an Envelope's from and to stay.
- Mutate moves each of those numbers a little, one in two. A 0 stays
  0, so nothing that was off comes on.
- An input fed by an edge is left alone by both. Both are grey while
  the graph has none of those nodes.
- Audition, off by default as in the Pattern tab, plays the note last
  played again 150 ms after a change to the graph rests.
- Preset, Randomize and Mutate always play that note, for up to 2 s.
- ReplaceGraphCommand swaps nodes and edges and keeps groups and notes.
- The info on the right of the header kept 220 px and was cut off
  with the four new buttons. 140 px still fit "No sound picked".

Not done: punch, filter sweeps and the phaser of sfxr (#341).

Tests:
- Unit chip-presets (new) 7. Counter-check: a wrong Hz formula and
  fed inputs not left alone turn 3 of them red. Unit tests of the
  instrument tab, graph commands and sound FX nodes: 49 files, 851
  green.
- e2e 34 (melektaus-atx): ALL PASSED, 64 checks (4 new): Preset,
  Randomize and Mutate, Audition, three undos back to the Sample.
- Screenshot with a Laser preset: the header fits at 1280 px, the
  chain is fitted, the Chip is a saw with its duty grey.
- Gate (melektaus-atx): CHECK EXIT 0, 256 files, 3969 tests.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 23:29:39 +02:00