diff --git a/src/viewport/SoundLibrary.ts b/src/viewport/SoundLibrary.ts index 85c03ae0..45fc5505 100644 --- a/src/viewport/SoundLibrary.ts +++ b/src/viewport/SoundLibrary.ts @@ -909,10 +909,15 @@ export class SoundLibrary implements AudioSink { const names = new Set(this.mix.map((bus) => bus.name)); const waits: Promise[] = []; for (const { name, volume, into, sends, fx } of this.mix) { + const made = !this.buses.has(name); const bus = this.bus(name, into); if (sends) this.setSends(bus, sends); else this.route(name, bus, into); - this.glideLevel(bus, this.mixVolumes.get(name) ?? volume); + const level = this.mixVolumes.get(name) ?? volume; + // A bus made now is at its level at once: made with the context, it carries the first sound of + // a run, which would otherwise be heard through a glide down from unity (found by #436). + if (made) this.setLevel(bus, level); + else this.glideLevel(bus, level); waits.push(this.setFx(bus, fx)); } const gone = [...this.buses.keys()].filter( @@ -923,6 +928,13 @@ export class SoundLibrary implements AudioSink { await Promise.all(waits); } + /** The level of a bus at `gain` from now on, without a glide. */ + private setLevel(bus: Bus, gain: number): void { + const to = Math.max(0, gain); + bus.gain.gain.value = to; + bus.set = { ...bus.set, gain: to }; + } + /** A quick glide of the level of a bus to `gain`. */ private glideLevel(bus: Bus, gain: number, time = 0, ease: EaseShare = 'linear'): void { const to = Math.max(0, gain); diff --git a/tests/viewport/sound-library.test.ts b/tests/viewport/sound-library.test.ts index c122c1ce..f05ee6e2 100644 --- a/tests/viewport/sound-library.test.ts +++ b/tests/viewport/sound-library.test.ts @@ -2539,6 +2539,22 @@ describe('the master and the buses of the project (#325)', () => { expect(wires.filter((wire) => wire.endsWith('> destination'))).toEqual(['panner > destination']); }); + it('makes a bus at its level, so the first sound of a run on a quiet bus is not heard gliding down (#436)', async () => { + const library = new SoundLibrary(false); + await library.setMix(mix); + await play(library); + expect(library.busLevels().map(({ name, level }) => [name, level])).toEqual([ + ['mix', 0.5], + ['mix:a', 1], + ['mix:b', 0.25], + ]); + expect(gains.filter((gain) => gain.glides.length > 0)).toEqual([]); + // One there already glides to a new level, as before. + await library.setMix([bus('mix', 0.25, null), mix[1]!, mix[2]!]); + expect(library.busLevels()[0]).toMatchObject({ gain: 0.25, level: 0.5 }); + expect(gains.some((gain) => gain.glides.includes(0.25))).toBe(true); + }); + it('keeps the mix where the first thing heard is a tab of a song, which names its bus before there is a context', async () => { const library = new SoundLibrary(false); await library.setMix(mix);