diff --git a/tools/e2e/34-sound.mjs b/tools/e2e/34-sound.mjs index 33bf7944..1c43a9d7 100644 --- a/tools/e2e/34-sound.mjs +++ b/tools/e2e/34-sound.mjs @@ -1714,6 +1714,8 @@ export async function sound(t) { // quarters at 150 BPM in a loop: every note is planned before its second comes, each 0.4 s // after the one before, also across the seam into the next round. Where the music stands // is the audio clock less the output latency, counted from the first note. + // #366: the audio clock moves on by a render block even between two reads in one expression, so + // it is read before and after the position, and the position lies between the two. const bar = [60, 62, 64, 65].map((pitch, i) => ({ tick: i * 480, length: 240, @@ -1730,13 +1732,21 @@ export async function sound(t) { 40, ); const where = await json( - '({ position: __gm.music.position(), clock: __gm.sound.clock(), latency: __gm.music.latency() })', + `({ + before: __gm.sound.clock(), + position: __gm.music.position(), + after: __gm.sound.clock(), + latency: __gm.music.latency(), + })`, ); await evaluate('__gm.music.stop()'); const musicOff = await waitFor(stats, (s) => s.playing === 0); const apart = planned.slice(1, 9).map((p, i) => +(p.at - planned[i].at).toFixed(6)); const ahead = planned.map((p) => +(p.at - p.planned).toFixed(3)); - const heardTicks = (((where.clock - where.latency - planned[0].at) * 150 * 480) / 60) % 1920; + const ticksHeardAt = (clock) => ((clock - where.latency - planned[0].at) * 150 * 480) / 60; + const heardTicks = [ticksHeardAt(where.before), ticksHeardAt(where.after)]; + // How far the position lies past the first read, around the loop of 1920 ticks. + const pastBefore = (((where.position.ticks - heardTicks[0]) % 1920) + 1920) % 1920; check( 'the scheduler plans a looped bar on the audio clock: every note ahead of its second, 0.4 s apart across the seam', planned.length >= 9 && @@ -1747,11 +1757,11 @@ export async function sound(t) { planned.every((p) => p.voice > 0 && Math.abs(p.duration - 0.2) < 1e-9 && p.tempo === 150) && apart.every((gap) => Math.abs(gap - 0.4) < 1e-6) && ahead.every((s) => s > 0 && s <= 0.16) && - Math.abs(where.position.ticks - heardTicks) < 1 && + (pastBefore <= heardTicks[1] - heardTicks[0] + 1 || pastBefore > 1919) && where.position.bar === 1 && musicOff.playing === 0 && (await json('__gm.music.playing()')) === false, - JSON.stringify({ apart, ahead, where, heardTicks, playing: musicOff.playing }), + JSON.stringify({ apart, ahead, where, heardTicks, pastBefore, playing: musicOff.playing }), ); // A note planned ahead through the pitch worklet starts on its sample, also inside a render block: