@@ -18,20 +18,11 @@ function createMockAudioContext(currentTime = 100) {
1818 } ) ,
1919 _fireEnded : ( ) => endedListeners . forEach ( ( cb ) => cb ( ) ) ,
2020 } ;
21- // Every createGain call returns a distinct node. Sharing one hides graph
22- // errors because the solo stage can overwrite the authored-volume stage.
23- const gainNodes : Array < {
24- gain : { value : number } ;
25- connect : ReturnType < typeof vi . fn > ;
26- disconnect : ReturnType < typeof vi . fn > ;
27- } > = [ ] ;
28- const makeGain = ( ) => {
29- const node = { gain : { value : 1 } , connect : vi . fn ( ) , disconnect : vi . fn ( ) } ;
30- gainNodes . push ( node ) ;
31- return node ;
21+ const gainNode = {
22+ gain : { value : 1 } ,
23+ connect : vi . fn ( ) ,
24+ disconnect : vi . fn ( ) ,
3225 } ;
33- const gainNode = makeGain ( ) ;
34- let served = 0 ;
3526 const mediaElementSourceNode = {
3627 connect : vi . fn ( ) ,
3728 disconnect : vi . fn ( ) ,
@@ -46,11 +37,11 @@ function createMockAudioContext(currentTime = 100) {
4637 resume : vi . fn ( ) ,
4738 createBufferSource : vi . fn ( ( ) => sourceNode ) ,
4839 createMediaElementSource : vi . fn ( ( ) => mediaElementSourceNode ) ,
49- createGain : vi . fn ( ( ) => ( served ++ === 0 ? gainNode : makeGain ( ) ) ) ,
40+ createGain : vi . fn ( ( ) => gainNode ) ,
5041 destination : { } ,
5142 close : vi . fn ( ) ,
5243 } ;
53- return { ctx, sourceNode, mediaElementSourceNode, gainNode, gainNodes , masterGain, startFn } ;
44+ return { ctx, sourceNode, mediaElementSourceNode, gainNode, masterGain, startFn } ;
5445}
5546
5647function setupTransport ( currentTime = 100 ) {
@@ -121,9 +112,7 @@ describe("WebAudioTransport", () => {
121112 expect ( mock . ctx . createMediaElementSource ) . toHaveBeenCalledWith ( mockEl ) ;
122113 expect ( mock . ctx . createBufferSource ) . not . toHaveBeenCalled ( ) ;
123114 expect ( mock . mediaElementSourceNode . connect ) . toHaveBeenCalled ( ) ;
124- const [ volumeGain , soloGain ] = mock . gainNodes ;
125- expect ( volumeGain ?. connect ) . toHaveBeenCalledWith ( soloGain ) ;
126- expect ( soloGain ?. connect ) . toHaveBeenCalledWith ( mock . masterGain ) ;
115+ expect ( mock . gainNode . connect ) . toHaveBeenCalledWith ( mock . masterGain ) ;
127116 expect ( mockEl . muted ) . toBe ( false ) ;
128117 expect ( mockEl . volume ) . toBe ( 1 ) ;
129118 expect ( mock . gainNode . gain . value ) . toBe ( 0.8 ) ;
@@ -748,15 +737,15 @@ describe("WebAudioTransport", () => {
748737 document . body . innerHTML = "" ;
749738 } ) ;
750739
751- it ( "routes an ungrouped clip to master through its own solo stage " , async ( ) => {
740+ it ( "routes an ungrouped clip straight to master through its own gain " , async ( ) => {
752741 const { transport, mock, gen } = setupGroupTransport ( ) ;
753742
754743 await scheduleGrouped ( transport , gen , "lone" ) ;
755744
756- expect ( mock . gainNodes ) . toHaveLength ( 2 ) ;
757- const [ clipGain , soloGain ] = mock . gainNodes ;
758- expect ( clipGain ! . connect ) . toHaveBeenCalledWith ( soloGain ) ;
759- expect ( soloGain ! . connect ) . toHaveBeenCalledWith ( mock . masterGain ) ;
745+ // One gain per clip now that solo is gone — it goes straight to master.
746+ expect ( mock . gainNodes ) . toHaveLength ( 1 ) ;
747+ const [ clipGain ] = mock . gainNodes ;
748+ expect ( clipGain ! . connect ) . toHaveBeenCalledWith ( mock . masterGain ) ;
760749 } ) ;
761750
762751 // The media-element transport is the PRIMARY path for audio — the runtime
@@ -801,9 +790,7 @@ describe("WebAudioTransport", () => {
801790 await transport . scheduleMediaElementPlayback ( el , 0 , 0 , 0 , 1 , gen , 1 ) ;
802791
803792 const clipGain = mock . gainNodes [ 0 ] ! ;
804- const soloGain = mock . gainNodes [ 5 ] ! ;
805- expect ( clipGain . connect ) . toHaveBeenCalledWith ( soloGain ) ;
806- expect ( soloGain . connect ) . toHaveBeenCalledWith ( firstGroupInput ( mock ) ) ;
793+ expect ( clipGain . connect ) . toHaveBeenCalledWith ( firstGroupInput ( mock ) ) ;
807794 expect ( clipGain . connect ) . not . toHaveBeenCalledWith ( mock . masterGain ) ;
808795 } ) ;
809796
@@ -813,9 +800,8 @@ describe("WebAudioTransport", () => {
813800
814801 await transport . scheduleMediaElementPlayback ( el , 0 , 0 , 0 , 1 , gen , 1 ) ;
815802
816- expect ( mock . gainNodes ) . toHaveLength ( 2 ) ;
817- expect ( mock . gainNodes [ 0 ] ! . connect ) . toHaveBeenCalledWith ( mock . gainNodes [ 1 ] ) ;
818- expect ( mock . gainNodes [ 1 ] ! . connect ) . toHaveBeenCalledWith ( mock . masterGain ) ;
803+ expect ( mock . gainNodes ) . toHaveLength ( 1 ) ;
804+ expect ( mock . gainNodes [ 0 ] ! . connect ) . toHaveBeenCalledWith ( mock . masterGain ) ;
819805 } ) ;
820806
821807 it ( "two members of the same group land on ONE shared group gain, not master directly" , async ( ) => {
@@ -824,23 +810,20 @@ describe("WebAudioTransport", () => {
824810 await scheduleGrouped ( transport , gen , "a" , "vo" ) ;
825811 await scheduleGrouped ( transport , gen , "b" , "vo" ) ;
826812
827- // Creation order for a: gain(0), group bus(1–4), solo(5). Then b uses
828- // gain(6), solo(7); both solo stages feed the one shared group input.
829- expect ( mock . gainNodes . length ) . toBeGreaterThanOrEqual ( 8 ) ;
813+ // Creation order for a: a-gain(0), groupInput(1), groupOutput(2),
814+ // muteGain(3), fader(4) — the group bus is built lazily inside a's
815+ // schedule call. Then b: b-gain(5).
816+ expect ( mock . gainNodes . length ) . toBeGreaterThanOrEqual ( 6 ) ;
830817 const aGain = mock . gainNodes [ 0 ] ! ;
831- const aSolo = mock . gainNodes [ 5 ] ! ;
832818 const groupInput = firstGroupInput ( mock ) ;
833819 const groupOutput = mock . gainNodes [ 2 ] ! ;
834820 const muteGain = mock . gainNodes [ 3 ] ! ;
835821 const fader = mock . gainNodes [ 4 ] ! ;
836- const bGain = mock . gainNodes [ 6 ] ! ;
837- const bSolo = mock . gainNodes [ 7 ] ! ;
838-
839- // Both members feed their own solo stage, then the shared bus.
840- expect ( aGain . connect ) . toHaveBeenCalledWith ( aSolo ) ;
841- expect ( bGain . connect ) . toHaveBeenCalledWith ( bSolo ) ;
842- expect ( aSolo . connect ) . toHaveBeenCalledWith ( groupInput ) ;
843- expect ( bSolo . connect ) . toHaveBeenCalledWith ( groupInput ) ;
822+ const bGain = mock . gainNodes [ 5 ] ! ;
823+
824+ // Both members feed the shared bus — neither connects straight to master.
825+ expect ( aGain . connect ) . toHaveBeenCalledWith ( groupInput ) ;
826+ expect ( bGain . connect ) . toHaveBeenCalledWith ( groupInput ) ;
844827 expect ( aGain . connect ) . not . toHaveBeenCalledWith ( mock . masterGain ) ;
845828 expect ( bGain . connect ) . not . toHaveBeenCalledWith ( mock . masterGain ) ;
846829
@@ -860,11 +843,11 @@ describe("WebAudioTransport", () => {
860843 const { transport, mock, gen } = setupGroupTransport ( ) ;
861844
862845 await scheduleGrouped ( transport , gen , "a" , "vo" ) ;
863- const gainCountAfterFirst = mock . gainNodes . length ;
846+ const gainCountAfterFirst = mock . gainNodes . length ; // a-gain + group input/output/mute/fader
864847 await scheduleGrouped ( transport , gen , "b" , "vo" ) ;
865848
866- // Only b's volume and solo gains are new — no second group bus minted.
867- expect ( mock . gainNodes . length ) . toBe ( gainCountAfterFirst + 2 ) ;
849+ // Only b's own gain is new — no second group bus minted.
850+ expect ( mock . gainNodes . length ) . toBe ( gainCountAfterFirst + 1 ) ;
868851 } ) ;
869852
870853 it ( "a group id with no matching <hf-audio-group> element still gets a flat bus" , async ( ) => {
@@ -930,45 +913,6 @@ describe("WebAudioTransport", () => {
930913 expect ( mock . gainNodes . filter ( ( n ) => n === groupInput ) ) . toHaveLength ( 1 ) ;
931914 } ) ;
932915
933- describe ( 'solo — "Hear only this" compatibility bridge' , ( ) => {
934- it ( "silences non-soloed members without attenuating their shared group bus" , async ( ) => {
935- const { transport, mock, gen } = setupGroupTransport ( ) ;
936- await scheduleGrouped ( transport , gen , "a" , "vo" ) ;
937- await scheduleGrouped ( transport , gen , "b" , "vo" ) ;
938-
939- transport . setSolo ( new Set ( [ "a" ] ) ) ;
940-
941- expect ( mock . gainNodes [ 5 ] ! . gain . value ) . toBe ( 1 ) ;
942- expect ( mock . gainNodes [ 7 ] ! . gain . value ) . toBe ( 0 ) ;
943- expect ( firstGroupInput ( mock ) . gain . value ) . toBe ( 1 ) ;
944- } ) ;
945-
946- it ( "soloing a group keeps every member of that group audible" , async ( ) => {
947- const { transport, mock, gen } = setupGroupTransport ( ) ;
948- await scheduleGrouped ( transport , gen , "a" , "vo" ) ;
949- await scheduleGrouped ( transport , gen , "b" , "vo" ) ;
950-
951- transport . setSolo ( new Set ( [ "vo" ] ) ) ;
952-
953- expect ( mock . gainNodes [ 5 ] ! . gain . value ) . toBe ( 1 ) ;
954- expect ( mock . gainNodes [ 7 ] ! . gain . value ) . toBe ( 1 ) ;
955- } ) ;
956-
957- it ( "applies an existing solo to newly scheduled clips and restores them when cleared" , async ( ) => {
958- const { transport, mock, gen } = setupGroupTransport ( ) ;
959- transport . setSolo ( new Set ( [ "a" ] ) ) ;
960- await scheduleGrouped ( transport , gen , "a" ) ;
961- await scheduleGrouped ( transport , gen , "b" ) ;
962-
963- expect ( mock . gainNodes [ 1 ] ! . gain . value ) . toBe ( 1 ) ;
964- expect ( mock . gainNodes [ 3 ] ! . gain . value ) . toBe ( 0 ) ;
965-
966- transport . setSolo ( new Set ( ) ) ;
967- expect ( mock . gainNodes [ 1 ] ! . gain . value ) . toBe ( 1 ) ;
968- expect ( mock . gainNodes [ 3 ] ! . gain . value ) . toBe ( 1 ) ;
969- } ) ;
970- } ) ;
971-
972916 // Surviving stopAll() is the point of the bus — and the trap. Its envelopes
973917 // were booked against the FIRST pass's absolute context times, so a replay
974918 // or a seek left the fader holding that pass's last value: 0 after a
0 commit comments