Files
feuerwerk/tools
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
..
Plugins belong to the project, and they load before it: the project file gained the field plugins: ["demo-plugin.js"] (default empty, no formatVersion bump), the Plugins tab's switch writes into it as an ordinary undo step (SetPluginActiveCommand) instead of into a server-wide plugins.json, and the start order is turned around - startPlugins only fetches the list of files, then editor.boot() runs and ProjectStore.load(id, prepare) hands the raw JSON through before it is deserialized, where pluginsOf(raw) reads the list and PluginManager.syncTo() activates exactly those; that is the point of the issue, because what a plugin registers has to stand there before normalizeGraph would throw it away as unknown (#126, #127), a project switch first stops what the new project does not name and then starts what it does, a file the server does not have is passed over silently, and after signing out the manager is dead so a late event cannot start anything in a torn-down session; on the server plugins.json, PluginStore::setActive, isActive, the route PUT ?r=plugins/{file} and the 403 for inactive modules are gone and GET ?r=plugins only answers {file,bytes,updatedAt}, which makes every plugin file fetchable - the consequence accepted in #117, written down in the wiki for #115 and #116; publishPlugins(root) only copies files now, the seeder switches the demo plugin on in the fixture project, and the Plugins tab carries a puzzle piece as its icon while only a plugin's own tab still goes without one (the knob and the socket are cubic curves, not arcs, which Pixi's parser draws as a spike after an L); 3 new e2e checks in module 38 and 5 new unit tests cover the project as the owner of the switch, its undo, the order, pluginsOf against crude input and an unknown file name (#119)
2026-09-20 14:24:27 +02:00