Summary
pan(amount) in src/bundles/sound/src/functions.ts builds its left/right channel waves as two independent wrappers around the same underlying (squashed) source wave: gainWave(wave, leftGain) and gainWave(wave, rightGain). When play() later samples both channels, wave(t) gets called once via the left wrapper and again via the right wrapper, for every timestamp - the same underlying wave is evaluated twice per sample instead of once. pan_mod is worse: it does this for both the source wave and the modulator wave, so four evaluations happen where two would do.
For purely mathematical waves (sine_wave etc.) this is just wasted CPU. But if the wave traces back to a student-supplied closure (via closureToWave), it means the CSE machine actually re-runs the student's function body twice (or four times, for pan_mod) per sample instead of once - genuinely duplicated work and an extra, unnecessary "step" through their code.
Why this isn't a quick fix
The natural fix is to share one sample between both channel closures, but play() currently samples the entire left channel first, then the entire right channel (not interleaved sample-by-sample) - see sampleWave/play in the same file. So a naive "cache the last computed value" approach wouldn't help, since by the time the right channel is sampled the cache has long since moved past the relevant t.
Suggested direction
Either:
- Cache every sampled value across the whole duration (trades memory for correctness), or
- Restructure
play()'s sampling loop (and sampleWave) to interleave left/right sampling per-timestamp, so a single-slot memoization actually works.
Context
Flagged by CodeRabbit's review on #796 (the sound Conductor migration), correctly labeled a "heavy lift" rather than a quick patch - deferred to this follow-up rather than rushed into that PR. See the review thread: #796 (comment)
Summary
pan(amount)insrc/bundles/sound/src/functions.tsbuilds its left/right channel waves as two independent wrappers around the same underlying (squashed) source wave:gainWave(wave, leftGain)andgainWave(wave, rightGain). Whenplay()later samples both channels,wave(t)gets called once via the left wrapper and again via the right wrapper, for every timestamp - the same underlying wave is evaluated twice per sample instead of once.pan_modis worse: it does this for both the source wave and the modulator wave, so four evaluations happen where two would do.For purely mathematical waves (
sine_waveetc.) this is just wasted CPU. But if the wave traces back to a student-supplied closure (viaclosureToWave), it means the CSE machine actually re-runs the student's function body twice (or four times, forpan_mod) per sample instead of once - genuinely duplicated work and an extra, unnecessary "step" through their code.Why this isn't a quick fix
The natural fix is to share one sample between both channel closures, but
play()currently samples the entire left channel first, then the entire right channel (not interleaved sample-by-sample) - seesampleWave/playin the same file. So a naive "cache the last computed value" approach wouldn't help, since by the time the right channel is sampled the cache has long since moved past the relevantt.Suggested direction
Either:
play()'s sampling loop (andsampleWave) to interleave left/right sampling per-timestamp, so a single-slot memoization actually works.Context
Flagged by CodeRabbit's review on #796 (the sound Conductor migration), correctly labeled a "heavy lift" rather than a quick patch - deferred to this follow-up rather than rushed into that PR. See the review thread: #796 (comment)