Ramp sound FX parameters over a time on the audio clock
game-maker / gate (push) Successful in 3m21s
game-maker / suite (push) Successful in 15m26s

A pitch sweep counted down in On update or On fixed update runs in
game time, while the sound runs on the clock of the audio device. A
short sound magnifies any offset between the two, so the sweep came
out different on every play and most of all on the first.

- Set sound FX parameters has a new input time in seconds. The values
  run evenly to their target in that time, scheduled on the audio
  clock with linearRampToValueAtTime or exponentialRampToValueAtTime.
- Evenly in their own unit: a pitch in semitones and a gain in
  decibels, an EQ band and a Mix share linearly in their value.
- A time of 0 or below keeps the quick glide of about 0.1 s.
- Each change starts from where the voice is now. Whatever was still
  planned is dropped, so a change never queues behind an older one.
  Firefox lacks cancelAndHoldAtTime, there the value is held by hand.
- A voice still waiting on its bytes starts a timed change with the
  sound, so the whole sweep is heard.

Tests:
- node-catalog: time is handed on, a time below 0 is a quick glide.
- sound-library: semitones and decibels ramp in their own steps, a
  timed change on a waiting voice starts with the sound.
- sound-library + node-catalog + soundfx: 419 tests, green.
- e2e 34 on melektaus-atx: ALL PASSED, 31 checks. A 2 s ramp from
  depth 7 to -5 read speed 0.979, 0.926 and 0.778 half a second on.
- Counter-check: folding a timed change on a waiting voice into its
  start values fails the new test.
- The gate did not run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-05 21:47:13 +02:00
1 parent ed2b58b3c0
commit 501a1524ed
9 files changed
+154 -29

No files matched your search

+2 -2
View File
@@ -214,7 +214,7 @@ export type FakeContext = NodeContext & {
fxParams: Readonly<Record<string, number>>;
}[];
silenced: string[];
tuned: { voice: number; fxParams: Readonly<Record<string, number>> }[];
tuned: { voice: number; fxParams: Readonly<Record<string, number>>; time: number }[];
/** The scenes the context was asked to load, in order. */
loaded: string[];
/** The time scale and the pause the context holds, as the game nodes left them. */
@@ -271,7 +271,7 @@ export function fakeContext(overrides: Partial<NodeContext> = {}): FakeContext {
ctx.played.push({ assetId, volume, loop, detached, fx, fxParams }),
stopSound: (assetId) => ctx.silenced.push(assetId),
tuned: [],
tuneSound: (voice, fxParams) => ctx.tuned.push({ voice, fxParams }),
tuneSound: (voice, fxParams, time) => ctx.tuned.push({ voice, fxParams, time }),
loadScene: (sceneId) => ctx.loaded.push(sceneId),
spawn: () => null,
addJoint: () => null,