@@ -293,6 +293,33 @@ describe("setupRhythmActions", () => {
293293
294294 expect ( value ) . toBe ( 0 ) ;
295295 } ) ;
296+ it ( "respects getNoteValue priority hierarchy" , ( ) => {
297+ // Simulate active note block
298+ targetTurtle . singer . inNoteBlock = [ 7 ] ;
299+
300+ // Provide ALL possible sources
301+ targetTurtle . singer . noteValue = { 7 : 0.25 } ; // highest priority
302+ targetTurtle . singer . lastNotePlayed = [ null , 8 ] ; // second priority
303+ targetTurtle . singer . notePitches = { 7 : [ "C" ] } ; // third priority
304+ targetTurtle . singer . noteBeat = { 7 : 4 } ;
305+
306+ const value = Singer . RhythmActions . getNoteValue ( 0 ) ;
307+
308+ // noteValue = 0.25 -> internally inverted twice -> returns 0.25
309+ expect ( value ) . toBe ( 0.25 ) ;
310+ } ) ;
311+ it ( "falls back to noteBeat when noteValue and lastNotePlayed are absent" , ( ) => {
312+ targetTurtle . singer . inNoteBlock = [ 5 ] ;
313+
314+ targetTurtle . singer . noteValue = { } ;
315+ targetTurtle . singer . lastNotePlayed = null ;
316+ targetTurtle . singer . notePitches = { 5 : [ "C" ] } ;
317+ targetTurtle . singer . noteBeat = { 5 : 4 } ;
318+
319+ const value = Singer . RhythmActions . getNoteValue ( 0 ) ;
320+
321+ expect ( value ) . toBe ( 0.25 ) ; // 1 / 4
322+ } ) ;
296323
297324 describe ( "doTie" , ( ) => {
298325 // Store original implementations to restore after tests
@@ -463,4 +490,65 @@ describe("setupRhythmActions", () => {
463490 ) ;
464491 } ) ;
465492 } ) ;
493+
494+ it ( "restores beatFactor after dot and multiply lifecycle" , ( ) => {
495+ let dotListener , multiplyListener ;
496+
497+ activity . logo . setTurtleListener = jest . fn ( ( _ , name , cb ) => {
498+ if ( name . includes ( "_dot_" ) ) dotListener = cb ;
499+ if ( name . includes ( "_multiplybeat_" ) ) multiplyListener = cb ;
500+ } ) ;
501+
502+ targetTurtle . singer . beatFactor = 1 ;
503+ targetTurtle . singer . dotCount = 0 ;
504+
505+ Singer . RhythmActions . doRhythmicDot ( 1 , 0 , 1 ) ;
506+ Singer . RhythmActions . multiplyNoteValue ( 2 , 0 , 1 ) ;
507+
508+ const mutated = targetTurtle . singer . beatFactor ;
509+ expect ( mutated ) . not . toBe ( 1 ) ;
510+
511+ multiplyListener ( ) ;
512+ dotListener ( ) ;
513+
514+ expect ( targetTurtle . singer . beatFactor ) . toBeCloseTo ( 1 ) ;
515+ } ) ;
516+ it ( "maintains stable beatFactor after repeated dots" , ( ) => {
517+ targetTurtle . singer . beatFactor = 1 ;
518+ targetTurtle . singer . dotCount = 0 ;
519+
520+ Singer . RhythmActions . doRhythmicDot ( 1 , 0 , 1 ) ;
521+ Singer . RhythmActions . doRhythmicDot ( 1 , 0 , 1 ) ;
522+
523+ expect ( targetTurtle . singer . dotCount ) . toBe ( 2 ) ;
524+ expect ( targetTurtle . singer . beatFactor ) . toBeGreaterThan ( 0 ) ;
525+ } ) ;
526+ it ( "treats osctime differently from note duration" , ( ) => {
527+ const enqueue = jest . fn ( ) ;
528+ Singer . processNote . mockClear ( ) ;
529+
530+ Singer . RhythmActions . playNote ( 2 , "note" , 0 , 1 , enqueue ) ;
531+ let listener = activity . logo . setTurtleListener . mock . calls [ 0 ] [ 2 ] ;
532+ listener ( ) ;
533+
534+ const noteCall = Singer . processNote . mock . calls [ 0 ] ;
535+
536+ Singer . processNote . mockClear ( ) ;
537+
538+ Singer . RhythmActions . playNote ( 500 , "osctime" , 0 , 1 , enqueue ) ;
539+ listener = activity . logo . setTurtleListener . mock . calls [ 1 ] [ 2 ] ;
540+ listener ( ) ;
541+
542+ const oscCall = Singer . processNote . mock . calls [ 0 ] ;
543+
544+ expect ( noteCall [ 2 ] ) . toBe ( false ) ;
545+ expect ( oscCall [ 2 ] ) . toBe ( true ) ;
546+ } ) ;
547+ it ( "activates multipleVoices for nested notes" , ( ) => {
548+ targetTurtle . singer . inNoteBlock = [ 10 ] ;
549+
550+ Singer . RhythmActions . playNote ( 1 , "note" , 0 , 1 , jest . fn ( ) ) ;
551+
552+ expect ( targetTurtle . singer . multipleVoices ) . toBe ( true ) ;
553+ } ) ;
466554} ) ;
0 commit comments