@@ -1512,6 +1512,77 @@ describe("Utility Functions (logic-only)", () => {
15121512 } ) ;
15131513 } ) ;
15141514
1515+ describe ( "_performNotes glide/portamento setNote routing " , ( ) => {
1516+ it ( "should call setNote (continuous glide) instead of triggerAttackRelease when doPortamento and setNote are true" , async ( ) => {
1517+ const mockSynth = {
1518+ oscillator : { } ,
1519+ toDestination : jest . fn ( ) . mockReturnThis ( ) ,
1520+ triggerAttackRelease : jest . fn ( ) ,
1521+ setNote : jest . fn ( ) ,
1522+ chain : jest . fn ( ) . mockReturnThis ( ) ,
1523+ disconnect : jest . fn ( ) ,
1524+ connect : jest . fn ( )
1525+ } ;
1526+ Synth . inTemperament = "equal" ;
1527+
1528+ const paramsEffects = {
1529+ doPortamento : true ,
1530+ portamento : 0.05
1531+ } ;
1532+
1533+ await _performNotes . call ( Synth , mockSynth , "D4" , 0.5 , paramsEffects , null , true , 0 ) ;
1534+
1535+ // This is the regression check: glide notes must continue the same
1536+ // voice via setNote, not retrigger a fresh attack+release.
1537+ expect ( mockSynth . setNote ) . toHaveBeenCalledWith ( "D4" ) ;
1538+ expect ( mockSynth . triggerAttackRelease ) . not . toHaveBeenCalled ( ) ;
1539+ } ) ;
1540+
1541+ it ( "should still call triggerAttackRelease for a portamento note when setNote is false" , async ( ) => {
1542+ const mockSynth = {
1543+ oscillator : { } ,
1544+ toDestination : jest . fn ( ) . mockReturnThis ( ) ,
1545+ triggerAttackRelease : jest . fn ( ) ,
1546+ setNote : jest . fn ( ) ,
1547+ chain : jest . fn ( ) . mockReturnThis ( ) ,
1548+ disconnect : jest . fn ( ) ,
1549+ connect : jest . fn ( )
1550+ } ;
1551+ Synth . inTemperament = "equal" ;
1552+
1553+ const paramsEffects = {
1554+ doPortamento : true ,
1555+ portamento : 0.05
1556+ } ;
1557+
1558+ await _performNotes . call ( Synth , mockSynth , "C4" , 0.5 , paramsEffects , null , false , 0 ) ;
1559+
1560+ // Non-continuation notes (setNote=false) should still play normally.
1561+ expect ( mockSynth . triggerAttackRelease ) . toHaveBeenCalled ( ) ;
1562+ expect ( mockSynth . setNote ) . not . toHaveBeenCalled ( ) ;
1563+ } ) ;
1564+
1565+ it ( "should route plain (non-portamento) notes through the fast path unaffected" , async ( ) => {
1566+ const mockSynth = {
1567+ toDestination : jest . fn ( ) . mockReturnThis ( ) ,
1568+ triggerAttackRelease : jest . fn ( ) ,
1569+ setNote : jest . fn ( )
1570+ } ;
1571+ Synth . inTemperament = "equal" ;
1572+
1573+ const paramsEffects = {
1574+ doPartials : true ,
1575+ partials : [ 1 ]
1576+ } ;
1577+
1578+ await _performNotes . call ( Synth , mockSynth , "E4" , 0.5 , paramsEffects , null , true , 0 ) ;
1579+
1580+ // No portamento involved — fast path is fine here, no regression expected.
1581+ expect ( mockSynth . triggerAttackRelease ) . toHaveBeenCalled ( ) ;
1582+ expect ( mockSynth . setNote ) . not . toHaveBeenCalled ( ) ;
1583+ } ) ;
1584+ } ) ;
1585+
15151586 describe ( "_performNotes effects routing and cleanup" , ( ) => {
15161587 it ( "should reconnect synth to destination and disconnect old routing when effects complete" , async ( ) => {
15171588 jest . useFakeTimers ( ) ;
0 commit comments