Let a looped sample play out past its loop when the note ends #331
With "Mode" and "Loop" as two fields, One-shot plus Loop ran until Stop was pressed: letting go of the key did nothing. The two fields are now one, "Play", with four entries that each say what happens while the note is held and when it is let go. - Gate and One-shot play the sound once, as before. Loop repeats it and fades out with the note. - "Loop, then release" is new: the sampler's sustain loop. Let go, it finishes the round it is in and plays what comes after "Loop end" to the end of the sound. - A buffer source stops looping and plays on by itself. The pitch worklet gets a "leave" message. The phase vocoder then lets the round finish that its newest frame has read into, so nothing read already changes under the frames still sounding. - Samples saved with "Mode" and "Loop" are turned into "Play" on load. Gate and One-shot stay, either with Loop becomes Loop, since playing until stopped is gone. - The keys of the Instrument tab stay half lit while a note let go is still heard. Esc on the keys stops everything, like Stop. - Striking a ringing key again plays a new note beside it rather than stopping the old one, so a drum can be hit quickly. This differs from what was proposed in the issue discussion. Tests: - instrument + pitch-player + sound-library + node-catalog: 477 tests, green (12 new). - e2e 34 (melektaus-atx): ALL PASSED, 45 checks (+1). Let go after a loop of 0.05 s, the note played out for 0.31 s on the audio clock, the key half lit meanwhile. - Counter-check: a release that does not leave the loop fails the 2 new player tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8a4e4a6762
commit
144dee0a97
14 files changed
+285
-29
No files matched your search
+35
-1
@@ -787,7 +787,7 @@ export async function sound(t) {
|
||||
const ins = __gm.state.project.assets.find((a) => a.id === ${JSON.stringify(insId)});
|
||||
Object.assign(ins.graph.nodes.find((n) => n.type === 'InstrumentSample').params, {
|
||||
sound: ${JSON.stringify(soundId)},
|
||||
loop: true,
|
||||
play: 'loop',
|
||||
});`);
|
||||
const insReady = await json('{ info: __gm.instrument.info(), keys: __gm.instrument.keys() }');
|
||||
check(
|
||||
@@ -824,6 +824,40 @@ export async function sound(t) {
|
||||
JSON.stringify(letGo),
|
||||
);
|
||||
|
||||
// "Loop, then release": held, it loops 0.05 to 0.1 s; let go, it plays the 0.15 s after the loop.
|
||||
const setPlay = (fields) =>
|
||||
mutate(`
|
||||
const ins = __gm.state.project.assets.find((a) => a.id === ${JSON.stringify(insId)});
|
||||
Object.assign(ins.graph.nodes.find((n) => n.type === 'InstrumentSample').params, ${JSON.stringify(fields)});`);
|
||||
await setPlay({ play: 'release', loopStart: 0.05, loopEnd: 0.1 });
|
||||
const beforeRelease = await stats();
|
||||
await t.mouse('mousePressed', e4.x, e4.y);
|
||||
await waitFor(stats, (s) => s.started > beforeRelease.started);
|
||||
await sleep(600);
|
||||
const heldLoop = await stats();
|
||||
await t.mouse('mouseReleased', e4.x, e4.y);
|
||||
const leftAt = await audioNow();
|
||||
const ringingKeys = await json('__gm.instrument.keys()');
|
||||
const playedOut = await waitFor(audioNow, (s) => s.playing === 0);
|
||||
const afterKeys = await waitFor(
|
||||
() => json('__gm.instrument.keys()'),
|
||||
(keys) => keys.ringing.length === 0,
|
||||
);
|
||||
// 0.15 s after the loop, up to two rounds more (the one it is in, the one the worklet read into)
|
||||
// and the 150 ms waitFor polls at: a fade would be gone in 0.01 s.
|
||||
const outFor = playedOut.clock - leftAt.clock;
|
||||
check(
|
||||
'"Loop, then release" loops while held, and let go plays on past the loop with its key half lit',
|
||||
heldLoop.playing === 1 &&
|
||||
ringingKeys.held.length === 0 &&
|
||||
ringingKeys.ringing.join() === '64' &&
|
||||
outFor > 0.12 &&
|
||||
outFor < 0.5 &&
|
||||
afterKeys.ringing.length === 0,
|
||||
JSON.stringify({ heldLoop: heldLoop.playing, ringingKeys, outFor, afterKeys }),
|
||||
);
|
||||
await setPlay({ play: 'loop', loopStart: 0, loopEnd: 0 });
|
||||
|
||||
// The click left the keys focused. Z is C3 there – no sound in this instrument –, one octave up C4.
|
||||
const beforeZ = await stats();
|
||||
await press('z');
|
||||
|
||||
Reference in new issue
Block a user