@@ -226,7 +226,15 @@ describe("processLilypondNotes", () => {
226226 processLilypondNotes ( lilypond , logo , turtle ) ;
227227 expect ( logo . notationNotes [ turtle ] ) . toContain ( "\\staccato " ) ;
228228 } ) ;
229-
229+ test ( "should process custom key modes (freygish) correctly" , ( ) => {
230+ getScaleAndHalfSteps . mockReturnValueOnce ( [
231+ [ "C" , "D" , "E" , "F" , "G" , "A" , "B" ] ,
232+ [ "" , "♭" , "" , "" , "" , "♭" , "" ]
233+ ] ) ;
234+ logo . notation . notationStaging [ turtle ] = [ "key" , "C" , "myCustomMode" ] ;
235+ processLilypondNotes ( lilypond , logo , turtle ) ;
236+ expect ( logo . notationNotes [ turtle ] ) . toContain ( "\\myCustomMode" ) ;
237+ } ) ;
230238 test ( "should insert newline after 8 notes" , ( ) => {
231239 const notesArray = [ ] ;
232240 for ( let i = 0 ; i < 9 ; i ++ ) {
@@ -242,6 +250,107 @@ describe("processLilypondNotes", () => {
242250 processLilypondNotes ( lilypond , logo , turtle ) ;
243251 expect ( logo . notationNotes [ turtle ] ) . toContain ( "\\tuplet" ) ;
244252 } ) ;
253+ test ( "should handle tuplet with non-integer fraction" , ( ) => {
254+ toFraction . mockReturnValueOnce ( [ 3.5 , 2 ] ) . mockReturnValueOnce ( [ 7 , 4 ] ) ;
255+
256+ logo . notation . notationStaging [ turtle ] = [ [ [ "G4" ] , 4 , 0 , [ 3 , 2 ] , 0 , - 1 , false ] ] ;
257+ processLilypondNotes ( lilypond , logo , turtle ) ;
258+ expect ( logo . notationNotes [ turtle ] ) . toContain ( "\\tuplet 7/4" ) ;
259+ } ) ;
260+ test ( "should process chord with multiple notes correctly" , ( ) => {
261+ logo . notation . notationStaging [ turtle ] = [ [ [ "G4" , "B4" , "D5" ] , 4 , 0 , null , 0 , - 1 , false ] ] ;
262+ processLilypondNotes ( lilypond , logo , turtle ) ;
263+ expect ( logo . notationNotes [ turtle ] ) . toContain ( "<" ) ;
264+ expect ( logo . notationNotes [ turtle ] ) . toContain ( ">" ) ;
265+ expect ( logo . notationNotes [ turtle ] ) . toContain ( "g'" ) ;
266+ expect ( logo . notationNotes [ turtle ] ) . toContain ( "b'" ) ;
267+ } ) ;
268+ test ( "should handle tuplet with notes of varying durations" , ( ) => {
269+ logo . notation . notationStaging [ turtle ] = [
270+ [ [ "G4" ] , 8 , 4 , [ 3 , 2 ] , 0 , - 1 , false ] ,
271+ [ [ "A4" ] , 4 , 2 , [ 3 , 2 ] , 0 , - 1 , false ] ,
272+ [ [ "B4" ] , 8 , 4 , [ 3 , 2 ] , 0 , - 1 , false ]
273+ ] ;
274+ processLilypondNotes ( lilypond , logo , turtle ) ;
275+ expect ( logo . notationNotes [ turtle ] ) . toContain ( "\\tuplet" ) ;
276+ } ) ;
277+
278+ test ( "should handle tuplet with varying tuplet factors" , ( ) => {
279+ logo . notation . notationStaging [ turtle ] = [
280+ [ [ "G4" ] , 4 , 2 , [ 3 , 4 ] , 0 , - 1 , false ] ,
281+ [ [ "A4" ] , 4 , 2 , [ 3 , 2 ] , 0 , - 1 , false ]
282+ ] ;
283+ processLilypondNotes ( lilypond , logo , turtle ) ;
284+ expect ( logo . notationNotes [ turtle ] ) . toContain ( "\\tuplet" ) ;
285+ } ) ;
286+
287+ test ( "should handle tie command within tuplet" , ( ) => {
288+ logo . notation . notationStaging [ turtle ] = [
289+ [ [ "G4" ] , 4 , 2 , [ 3 , 2 ] , 0 , - 1 , false ] ,
290+ "tie" ,
291+ [ [ "A4" ] , 4 , 2 , [ 3 , 2 ] , 0 , - 1 , false ]
292+ ] ;
293+ processLilypondNotes ( lilypond , logo , turtle ) ;
294+ expect ( logo . notationNotes [ turtle ] ) . toContain ( "~" ) ;
295+ expect ( logo . notationNotes [ turtle ] ) . toContain ( "\\tuplet" ) ;
296+ } ) ;
297+
298+ test ( "should break tuplet when reaching POW2 duration (0.5)" , ( ) => {
299+ logo . notation . notationStaging [ turtle ] = [
300+ [ [ "G4" ] , 4 , 2 , [ 3 , 2 ] , 0 , - 1 , false ] ,
301+ [ [ "A4" ] , 4 , 2 , [ 3 , 2 ] , 0 , - 1 , false ] ,
302+ [ [ "B4" ] , 4 , 2 , [ 3 , 2 ] , 0 , - 1 , false ] ,
303+ [ [ "C4" ] , 4 , 0 , null , 0 , - 1 , false ]
304+ ] ;
305+ processLilypondNotes ( lilypond , logo , turtle ) ;
306+ expect ( logo . notationNotes [ turtle ] ) . toContain ( "\\tuplet" ) ;
307+ } ) ;
308+ test ( "should handle null tuplet value interrupting tuplet" , ( ) => {
309+ logo . notation . notationStaging [ turtle ] = [
310+ [ [ "G4" ] , 4 , 2 , [ 3 , 2 ] , 0 , - 1 , false ] ,
311+ [ [ "A4" ] , 4 , 0 , null , 0 , - 1 , false ] ,
312+ [ [ "B4" ] , 4 , 2 , [ 3 , 2 ] , 0 , - 1 , false ]
313+ ] ;
314+ processLilypondNotes ( lilypond , logo , turtle ) ;
315+ expect ( logo . notationNotes [ turtle ] ) . toContain ( "\\tuplet" ) ;
316+ } ) ;
317+ test ( "should break tuplet when tuplet factor changes" , ( ) => {
318+ logo . notation . notationStaging [ turtle ] = [
319+ [ [ "G4" ] , 4 , 2 , [ 3 , 2 ] , 0 , - 1 , false ] ,
320+ [ [ "A4" ] , 4 , 2 , [ 5 , 2 ] , 0 , - 1 , false ]
321+ ] ;
322+ processLilypondNotes ( lilypond , logo , turtle ) ;
323+ expect ( logo . notationNotes [ turtle ] ) . toContain ( "\\tuplet" ) ;
324+ } ) ;
325+ test ( "should handle tuplet with mixed NOTATIONROUNDDOWN values (greater)" , ( ) => {
326+ logo . notation . notationStaging [ turtle ] = [
327+ [ [ "G4" ] , 4 , 2 , [ 3 , 2 ] , 0 , - 1 , false ] ,
328+ [ [ "A4" ] , 8 , 4 , [ 3 , 2 ] , 0 , - 1 , false ] ,
329+ [ [ "B4" ] , 4 , 2 , [ 3 , 2 ] , 0 , - 1 , false ]
330+ ] ;
331+ processLilypondNotes ( lilypond , logo , turtle ) ;
332+ expect ( logo . notationNotes [ turtle ] ) . toContain ( "\\tuplet" ) ;
333+ } ) ;
334+
335+ test ( "should handle tuplet with mixed NOTATIONROUNDDOWN values (less)" , ( ) => {
336+ logo . notation . notationStaging [ turtle ] = [
337+ [ [ "G4" ] , 4 , 4 , [ 3 , 2 ] , 0 , - 1 , false ] ,
338+ [ [ "A4" ] , 8 , 2 , [ 3 , 2 ] , 0 , - 1 , false ] ,
339+ [ [ "B4" ] , 4 , 4 , [ 3 , 2 ] , 0 , - 1 , false ]
340+ ] ;
341+ processLilypondNotes ( lilypond , logo , turtle ) ;
342+ expect ( logo . notationNotes [ turtle ] ) . toContain ( "\\tuplet" ) ;
343+ } ) ;
344+
345+ test ( "should handle tuplet with equal NOTATIONROUNDDOWN values" , ( ) => {
346+ logo . notation . notationStaging [ turtle ] = [
347+ [ [ "G4" ] , 4 , 2 , [ 3 , 2 ] , 0 , - 1 , false ] ,
348+ [ [ "A4" ] , 4 , 2 , [ 3 , 2 ] , 0 , - 1 , false ] ,
349+ [ [ "B4" ] , 4 , 2 , [ 3 , 2 ] , 0 , - 1 , false ]
350+ ] ;
351+ processLilypondNotes ( lilypond , logo , turtle ) ;
352+ expect ( logo . notationNotes [ turtle ] ) . toContain ( "\\tuplet" ) ;
353+ } ) ;
245354} ) ;
246355
247356describe ( "saveLilypondOutput" , ( ) => {
@@ -435,4 +544,102 @@ describe("saveLilypondOutput", () => {
435544 const result = saveLilypondOutput ( activity ) ;
436545 expect ( result ) . toContain ( "% MIDI Output" ) ;
437546 } ) ;
547+ test ( "should handle instrument short name collisions (no spaces)" , ( ) => {
548+ activity . turtles . turtleList = {
549+ "0" : { name : "Trumpet" } ,
550+ "1" : { name : "Trombone" } ,
551+ "2" : { name : "Tuba" }
552+ } ;
553+ activity . logo . notation . notationStaging = {
554+ "0" : [ "note" ] ,
555+ "1" : [ "note" ] ,
556+ "2" : [ "note" ]
557+ } ;
558+ frequencyToPitch . mockReturnValue ( [ "C" , 4 ] ) ;
559+
560+ const result = saveLilypondOutput ( activity ) ;
561+ expect ( result ) . toContain ( 'shortInstrumentName = "Tr"' ) ;
562+ expect ( result ) . toContain ( 'instrumentName = "Trombone"' ) ;
563+ } ) ;
564+
565+ test ( "should handle instrument short name collisions (with spaces)" , ( ) => {
566+ activity . turtles . turtleList = {
567+ "0" : { name : "Violin One" } ,
568+ "1" : { name : "Violin Two" }
569+ } ;
570+ activity . logo . notation . notationStaging = { "0" : [ "note" ] , "1" : [ "note" ] } ;
571+ const result = saveLilypondOutput ( activity ) ;
572+ expect ( result ) . toContain ( 'shortInstrumentName = "Vi"' ) ;
573+ expect ( result ) . toContain ( 'shortInstrumentName = "Vio"' ) ;
574+ } ) ;
575+ test ( "should map special turtle names (start, numeric) to Rodent names" , ( ) => {
576+ activity . turtles . turtleList = {
577+ "0" : { name : "start" } ,
578+ "1" : { name : "start drum" } ,
579+ "2" : { name : "2" }
580+ } ;
581+
582+ activity . logo . notation . notationStaging = {
583+ "0" : [ "note" ] ,
584+ "1" : [ "note" ] ,
585+ "2" : [ "note" ]
586+ } ;
587+
588+ const result = saveLilypondOutput ( activity ) ;
589+ expect ( result ) . toContain ( 'instrumentName = "mouse"' ) ;
590+ expect ( result ) . toContain ( 'instrumentName = "mole"' ) ;
591+ } ) ;
592+ test ( "should select bass_8 clef for very low notes" , ( ) => {
593+ frequencyToPitch . mockReturnValue ( [ "C" , 1 ] ) ;
594+
595+ activity . logo . notation . notationStaging = {
596+ "0" : [ [ [ "C1" ] , 4 , 0 , null , 0 , - 1 , false ] ]
597+ } ;
598+ const result = saveLilypondOutput ( activity ) ;
599+ expect ( result ) . toContain ( '\\clef "bass_8"' ) ;
600+ } ) ;
601+ test ( "should select bass clef for mid-low notes" , ( ) => {
602+ frequencyToPitch . mockReturnValue ( [ "C" , 3 ] ) ;
603+ activity . logo . notation . notationStaging = {
604+ "0" : [ [ [ "C3" ] , 4 , 0 , null , 0 , - 1 , false ] ]
605+ } ;
606+
607+ const result = saveLilypondOutput ( activity ) ;
608+ expect ( result ) . toContain ( '\\clef "bass"' ) ;
609+ } ) ;
610+
611+ test ( "should handle rest notes in octave calculation" , ( ) => {
612+ activity . logo . notation . notationStaging = {
613+ "0" : [ [ [ "R" ] , 4 , 0 , null , 0 , - 1 , false ] ]
614+ } ;
615+ const result = saveLilypondOutput ( activity ) ;
616+ expect ( result ) . toBeDefined ( ) ;
617+ } ) ;
618+
619+ test ( "should handle numeric frequency in octave calculation" , ( ) => {
620+ frequencyToPitch . mockReturnValue ( [ "A" , 4 ] ) ;
621+ activity . logo . notation . notationStaging = {
622+ "0" : [ [ [ 440 ] , 4 , 0 , null , 0 , - 1 , false ] ]
623+ } ;
624+ const result = saveLilypondOutput ( activity ) ;
625+ expect ( result ) . toBeDefined ( ) ;
626+ expect ( frequencyToPitch ) . toHaveBeenCalledWith ( 440 ) ;
627+ } ) ;
628+ test ( "should handle short name collision for names without spaces (longer loop)" , ( ) => {
629+ activity . turtles . turtleList = {
630+ "0" : { name : "Trumpet" } ,
631+ "1" : { name : "Trombone" } ,
632+ "2" : { name : "Triangle" }
633+ } ;
634+ activity . logo . notation . notationStaging = {
635+ "0" : [ "note" ] ,
636+ "1" : [ "note" ] ,
637+ "2" : [ "note" ]
638+ } ;
639+ const result = saveLilypondOutput ( activity ) ;
640+
641+ expect ( result ) . toContain ( 'shortInstrumentName = "Tr"' ) ;
642+ expect ( result ) . toContain ( 'shortInstrumentName = "Tro"' ) ;
643+ expect ( result ) . toContain ( 'shortInstrumentName = "Tri"' ) ;
644+ } ) ;
438645} ) ;
0 commit comments