Hand out the length of a played sound from Play sound
game-maker / gate (push) Successful in 3m12s
game-maker / suite (push) Failing after 26m24s

Wired into the time of Set sound FX parameters, a change then spans
exactly one run of the clip, so a fade or a filter sweep ends with its
last sample.

- Play sound has a second output, length, in seconds. It is 0 where
  nothing was played or the sound has not loaded yet.
- The AudioSink gives the length of a file, the SoundLibrary takes it
  from the decoded buffer. A pitch the voice starts on keeps it (#312).
- A pitch changed while the voice plays still changes its length, so a
  pitch sweep over length does not end with the sound. A pitch that
  keeps the length in real time is #317.

Tests:
- soundfx, sounds, keep-on-scene-load, node-catalog, sound-library:
  462 tests green.
- Counter-check: with length always 0, the catalog case and the new
  soundfx case fail.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-05 22:27:44 +02:00
1 parent 297fca3e94
commit 804a97b0a9
10 files changed
+53 -11

No files matched your search

+1
View File
@@ -56,6 +56,7 @@ function start(f: Fixture, audio?: AudioSink) {
const sink: AudioSink = audio ?? {
play: () => 0,
tune: () => {},
length: () => 0,
stop: () => {},
stopOwner: (owner: string) => stoppedOwners.push(owner),
};
+3 -3
View File
@@ -1980,14 +1980,14 @@ const CASES: Record<string, Case[]> = {
name: 'no sound is a no-op that still continues the flow',
inputs: { sound: '', volume: 1, loop: '' },
next: 'exec',
outputs: { voice: 0 },
outputs: { voice: 0, length: 0 },
after: (ctx) => expect(ctx.played).toEqual([]),
},
{
name: 'hands the id on with the volume and loop it was given, and gives out the voice (#315)',
name: 'hands the id on with the volume and loop it was given, and gives out the voice (#315) and its length',
inputs: { sound: 'snd-1', volume: 0.5, loop: 'true' },
next: 'exec',
outputs: { voice: 1 },
outputs: { voice: 1, length: 0.25 },
// What the two mean – a factor on the asset's volume, an answer that overrules its loop
// flag – is the BehaviorSystem's to resolve, since only it holds the project (see
// `sounds.test.ts`).
+21 -5
View File
@@ -257,19 +257,26 @@ describe('a Mix in a sound FX graph (#316)', () => {
});
/**
* Records what it was asked to play, with the line of effects its values make, and every tune. A
* voice is numbered by how many have been played, so the first is 1.
* Records what it was asked to play, with the line of effects its values make, and every tune with
* its time on the side. A voice is numbered by how many have been played, so the first is 1. Every
* file lasts a quarter of a second.
*/
function fakeAudio(): AudioSink & {
played: { file: string; owner: string; fx: FxChain | undefined }[];
tuned: { voice: number; params: Readonly<Record<string, number>> }[];
times: (number | undefined)[];
} {
const sink = {
played: [] as { file: string; owner: string; fx: FxChain | undefined }[],
tuned: [] as { voice: number; params: Readonly<Record<string, number>> }[],
times: [] as (number | undefined)[],
play: (file: string, _volume: number, _loop: boolean, owner: string, fx?: FxVoice) =>
sink.played.push({ file, owner, fx: fx && fxChainOf(fx.plan, fx.params) }),
tune: (voice: number, params: Readonly<Record<string, number>>) => sink.tuned.push({ voice, params }),
tune: (voice: number, params: Readonly<Record<string, number>>, time?: number) => {
sink.tuned.push({ voice, params });
sink.times.push(time);
},
length: () => 0.25,
stop: () => {},
stopOwner: () => {},
};
@@ -503,9 +510,9 @@ describe('Sound FX parameters and Play sound', () => {
describe('Set sound FX parameters (#315)', () => {
/**
* Pressing x plays Jump through an FX twice, the first `sound` – then hands `depth` to the voice
* the first play gave out.
* the first play gave out, over the length of that play where `spanned`.
*/
function tuning(sound?: string) {
function tuning(sound?: string, spanned = false) {
const f = makeSpriteScene('Box');
const { fx } = makeFx(f);
const created = new CreateSoundCommand('Jump', FILE);
@@ -525,6 +532,7 @@ describe('Set sound FX parameters (#315)', () => {
[first, 'voice', set, 'voice'],
[params, 'params', set, 'fxParams'],
]);
if (spanned) edit.wire([[first, 'length', set, 'time']]);
return f;
}
@@ -545,4 +553,12 @@ describe('Set sound FX parameters (#315)', () => {
expect(audio.played).toHaveLength(1);
expect(audio.tuned).toEqual([]);
});
it('spans a change over the length its Play sound hands out', () => {
const audio = fakeAudio();
const { input, frame } = startWorld(tuning(undefined, true), { behaviors: true, audio });
input.press('x');
frame();
expect(audio.times).toEqual([0.25]);
});
});
+1
View File
@@ -43,6 +43,7 @@ function fakeAudio(): AudioSink & {
play: (file: string, volume: number, loop: boolean, owner: string) =>
sink.played.push({ file, volume, loop, owner }),
tune: () => {},
length: () => 0,
stop: (file: string) => sink.stopped.push(file),
stopOwner: (owner: string) => sink.stoppedOwners.push(owner),
};