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

+5
View File
@@ -250,6 +250,11 @@ export interface NodeContext {
fx: string,
fxParams: FxParams,
): number;
/**
* How many seconds one run of that sound asset lasts, so a change can span it; 0 where it does not
* exist or has not been loaded yet.
*/
soundLength(assetId: string): number;
/** Silences every voice of that sound; `''` silences everything this run started. */
stopSound(assetId: string): void;
/**
+6 -3
View File
@@ -61,7 +61,9 @@ export const PlaySound: NodeDef = {
'unless "detached" is on. "fx" sends it through a sound FX asset; left empty, it plays as it is. ' +
'"fx params" takes values for its parameters from a Sound FX parameters node. "voice" is the ' +
'number of this one play, for Set sound FX parameters to change it while it plays; 0 where ' +
'nothing was played.',
'nothing was played. "length" is how many seconds one run of the sound lasts, to hand to the ' +
'"time" of Set sound FX parameters so a change spans the sound; 0 where nothing was played or ' +
'the sound has not been loaded yet. A pitch changed while it plays makes it longer or shorter.',
inputs: [
exec(),
soundPort(),
@@ -71,7 +73,7 @@ export const PlaySound: NodeDef = {
soundFxPort(),
{ key: 'fxParams', label: 'fx params', type: 'fxparams' },
],
outputs: [exec(), num('voice')],
outputs: [exec(), num('voice'), num('length')],
params: [],
run: (ctx, inputs) => {
const sound = asString(inputs.sound);
@@ -85,7 +87,8 @@ export const PlaySound: NodeDef = {
fxParamsOf(inputs.fxParams),
)
: 0;
return { next: 'exec', outputs: { voice } };
const length = voice ? ctx.soundLength(sound) : 0;
return { next: 'exec', outputs: { voice, length } };
},
};
+6
View File
@@ -28,6 +28,11 @@ export interface AudioSink {
* where it is not given). 0 is a quick glide.
*/
tune(voice: number, params: FxParams, time?: number, ease?: EaseShare): void;
/**
* How many seconds one run of that file lasts, the same with a pitch it starts on (#312); 0 while
* its bytes have not arrived.
*/
length(file: string): number;
/** Silences every voice of that file; `''` silences all of them. */
stop(file: string): void;
/** Silences every voice started by that owner – what destroying a behavior does. */
@@ -37,6 +42,7 @@ export interface AudioSink {
export const SILENT_AUDIO: AudioSink = {
play: () => 0,
tune: () => {},
length: () => 0,
stop: () => {},
stopOwner: () => {},
};
+4
View File
@@ -166,6 +166,10 @@ export function behaviorContext(
},
playSound: (assetId, volume, loop, detached, fx, fxParams) =>
playSound(run, assetId, volume, loop, detached ? '' : entity.id, fx, fxParams),
soundLength: (assetId) => {
const file = soundFileOf(project, assetId);
return file ? run.audio.length(file) : 0;
},
// An id nothing resolves stops nothing, rather than silencing the whole run by accident;
// only the empty id means "everything", which is what the node promises.
stopSound: (assetId) => {
+4
View File
@@ -227,6 +227,10 @@ export class SoundLibrary implements AudioSink {
return this.buffers.get(file)?.duration ?? null;
}
length(file: string): number {
return this.duration(file) ?? 0;
}
/**
* Starts loading every file not held yet. Waiting for the first play left that play late: its
* start was not heard, and a graph tuning it in the meantime had moved on by the time it started.