From db15422372da8e514679004cbff0e8c5e6300ab0 Mon Sep 17 00:00:00 2001 From: MelekTaus Date: Fri, 9 Oct 2026 13:09:41 +0200 Subject: [PATCH] Keep a bus's level when a tab plays the first sound of a page (#369) A bus of the mix played at full level, without its sound FX and straight into the master, until something on it changed. It happened when the first sound after loading came from the Song or Pattern tab. - `bus()` looked a bus up before it asked for the audio context. The context made then sets up the whole mix, this bus too, and `bus()` overwrote it with a fresh one at gain 1. - `setMix` does nothing for the same mix again, so the wrong bus stayed until the mix changed. - Since #368 the tabs play into the master, so it was the master's volume and FX that went missing on the first preview. - `bus()` now asks for the context first and looks the bus up after. Tests: - Unit sound-library, buses, master-routing: 139 green, 1 new. - The new test was red before the fix: mix:b at gain 1 into the master instead of 0.25 into mix:a. - Gate (melektaus-atx): CHECK EXIT 0, 274 files, 4231 tests. Co-Authored-By: Claude Opus 5.5 --- src/viewport/SoundLibrary.ts | 5 +++-- tests/viewport/sound-library.test.ts | 13 +++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/viewport/SoundLibrary.ts b/src/viewport/SoundLibrary.ts index a7d80d26..a42f982e 100644 --- a/src/viewport/SoundLibrary.ts +++ b/src/viewport/SoundLibrary.ts @@ -591,15 +591,16 @@ export class SoundLibrary implements AudioSink { /** * A bus by its name, made on its first use into `into`, or into the master; only the master is - * heard straight out (#325). It is not to go any more. + * heard straight out (#325). It is not to go any more. The context comes first: the one made now + * sets up the mix, whose bus this may be, and that one is to be kept (#369). */ private bus(name: string, into: string | null = null): Bus { + const context = this.audio(); const known = this.buses.get(name); if (known) { known.leaving = 0; return known; } - const context = this.audio(); const target = name === MASTER_MIX ? null : (into ?? MASTER_MIX); const made: Bus = { input: context.createGain(), diff --git a/tests/viewport/sound-library.test.ts b/tests/viewport/sound-library.test.ts index 475fb5ca..55aa121e 100644 --- a/tests/viewport/sound-library.test.ts +++ b/tests/viewport/sound-library.test.ts @@ -2261,6 +2261,19 @@ describe('the master and the buses of the project (#325)', () => { expect(wires.filter((wire) => wire.endsWith('> destination'))).toEqual(['panner > destination']); }); + 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); + await library.setBuses([{ name: 'music:1', gain: 1, pan: 0, into: 'mix:b' }]); + expect(levels(library)).toEqual([ + { name: 'mix', gain: 0.5, into: null }, + { name: 'mix:a', gain: 1, into: 'mix' }, + { name: 'mix:b', gain: 0.25, into: 'mix:a' }, + { name: 'music:1', gain: 1, into: 'mix:b' }, + ]); + expect(wires.filter((wire) => wire.endsWith('> destination'))).toEqual(['panner > destination']); + }); + it('plays a voice on the bus it is given, and a playing too', async () => { const library = new SoundLibrary(false); await library.setMix(mix);