A game could start and stop a song, but not steer one while it plays. The battle theme could not take over on the next bar, a song could not jump to a marker, and a track could not fall away when the player hides. This is stage 3 of 5 in #338. - Quantize: now, next beat, next bar (the default) or next marker. The spot is the next one as the song is heard, and the change is planned on the audio clock to the sample, round the loop as the ruler counts. - Set song track turns one track in dB on top of the song's level, over a fade, on the bus input before its FX and sends. - Switch song plays another song, or the same one from a marker, in place of the playing one. It keeps the playback number, so Stop song, Set song track and On beat go on reaching it. - A playing now has a key for its buses apart from its public number. During a switch the old and the new song each have buses of their own. - Play song has a "live tracks" switch. With it off a baked song plays its bake, and Set song track on it writes a line to the log. - A Switch song to a marker plays the notes, as a bake only plays from its start. Found on the way: - A cut on a bar line faded over seconds in Chrome. cancelAndHoldAtTime holds nothing where nothing is planned later, so the ramp ran from the bus's last change. The turn now sets its start value on the spot. It had been measured at 0.52 of full level 0.2 s past the bar line. Tests: - Unit: 23 new cases in beats, song-play, sound-library and the node catalog. Counter-checks: 17 bends, all compile, all red. - e2e 65 (atx): ALL PASSED, 73 checks. 65k3 is new. The switch landed three bars after the start under the same number, and the track cut on the next bar line with nothing in between. With the old ramp 65k3 is red (0.53 and 0.51 between). - Gate (atx): CHECK EXIT 0, 334 files, 5144 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
72 lines
2.7 KiB
TypeScript
72 lines
2.7 KiB
TypeScript
import type { NodeDef, PortDef } from '../../NodeRegistry';
|
||
import { asBoolean, asNumber, asString } from '../../values';
|
||
import {
|
||
checkPort,
|
||
detachedPort,
|
||
exec,
|
||
fxParamsPort,
|
||
num,
|
||
playedBusPort,
|
||
soundFxPort,
|
||
withDefault,
|
||
} from '../shared';
|
||
import { fxParamsOf } from './PlaySound';
|
||
|
||
export const songPort = (): PortDef => ({
|
||
key: 'song',
|
||
label: 'song',
|
||
type: 'string',
|
||
field: { key: 'song', label: 'Song', kind: 'asset', default: '', assetTypes: ['song'] },
|
||
});
|
||
|
||
export const PlaySong: NodeDef = {
|
||
type: 'PlaySong',
|
||
label: 'Play song',
|
||
category: 'action',
|
||
description:
|
||
'Plays a song asset at its tempo, its tracks with the volume, pan, mute and solo they have. With ' +
|
||
'"loop" it plays from its start and then round and round the loop set on its ruler – an intro ' +
|
||
'once, then the loop – until a Stop song ends it; without, once to its end. "volume" is the ' +
|
||
'level of the whole song, "fade in" the seconds it rises there from silence. Each play is a ' +
|
||
'playing of its own: firing this twice plays it twice, side by side. It belongs to this object ' +
|
||
'like a sound and stops when the object is destroyed or the scene is left, unless "detached" ' +
|
||
'is on. "fx" sends the whole song through one sound FX, after its tracks and before "volume"; ' +
|
||
'left empty, the one the song names, and None is none. ' +
|
||
'"fx params" takes values for its parameters from a Sound FX parameters node. "playback" is the ' +
|
||
'number of this playing, for Stop song, Set song track, Switch song and Set sound FX parameters; ' +
|
||
'0 where the song has nothing to play. "bus" is the bus all of it is heard on; left empty, the ' +
|
||
'one the song names. A song with a bake plays the bake, one sound for all its tracks; with "live ' +
|
||
'tracks" it plays its notes instead, as the Song tab does, so Set song track can turn its tracks.',
|
||
inputs: [
|
||
exec(),
|
||
songPort(),
|
||
withDefault(num('volume'), 1),
|
||
withDefault(num('fadeIn', 'fade in'), 0),
|
||
checkPort('loop', 'loop', 'Loop', true),
|
||
detachedPort(),
|
||
soundFxPort(),
|
||
fxParamsPort(),
|
||
playedBusPort(),
|
||
checkPort('live', 'live tracks', 'Live tracks', false),
|
||
],
|
||
outputs: [exec(), num('playback')],
|
||
params: [],
|
||
run: (ctx, inputs) => {
|
||
const song = asString(inputs.song);
|
||
const playback = song
|
||
? ctx.playSong(
|
||
song,
|
||
asNumber(inputs.volume),
|
||
asNumber(inputs.fadeIn),
|
||
asBoolean(inputs.loop),
|
||
asBoolean(inputs.detached),
|
||
asString(inputs.fx),
|
||
fxParamsOf(inputs.fxParams),
|
||
asString(inputs.bus),
|
||
asBoolean(inputs.live),
|
||
)
|
||
: 0;
|
||
return { next: 'exec', outputs: { playback } };
|
||
},
|
||
};
|