@@ -552,3 +552,191 @@ def test_constructor_rejects_undefined_ratios():
552552 with pytest .raises (SyntaxError ):
553553 Pitch ({'ratios' : ratios2 })
554554
555+
556+ def test_latex_sargam_letter_basic ():
557+ """Test that latex_sargam_letter returns the same as sargam_letter."""
558+ # Test all sargam letters in both raised and lowered forms
559+ sargam_tests = [
560+ ({'swara' : 'sa' }, 'S' ),
561+ ({'swara' : 're' , 'raised' : False }, 'r' ),
562+ ({'swara' : 're' , 'raised' : True }, 'R' ),
563+ ({'swara' : 'ga' , 'raised' : False }, 'g' ),
564+ ({'swara' : 'ga' , 'raised' : True }, 'G' ),
565+ ({'swara' : 'ma' , 'raised' : False }, 'm' ),
566+ ({'swara' : 'ma' , 'raised' : True }, 'M' ),
567+ ({'swara' : 'pa' }, 'P' ),
568+ ({'swara' : 'dha' , 'raised' : False }, 'd' ),
569+ ({'swara' : 'dha' , 'raised' : True }, 'D' ),
570+ ({'swara' : 'ni' , 'raised' : False }, 'n' ),
571+ ({'swara' : 'ni' , 'raised' : True }, 'N' ),
572+ ]
573+
574+ for options , expected in sargam_tests :
575+ p = Pitch (options )
576+ assert p .latex_sargam_letter == expected
577+ assert p .latex_sargam_letter == p .sargam_letter
578+
579+
580+ def test_latex_octaved_sargam_letter_no_octave ():
581+ """Test LaTeX octaved sargam letter with no octave marking (oct=0)."""
582+ p = Pitch ({'swara' : 'sa' , 'oct' : 0 })
583+ assert p .latex_octaved_sargam_letter == 'S'
584+
585+ p = Pitch ({'swara' : 're' , 'raised' : False , 'oct' : 0 })
586+ assert p .latex_octaved_sargam_letter == 'r'
587+
588+
589+ def test_latex_octaved_sargam_letter_positive_octaves ():
590+ """Test LaTeX octaved sargam letter with positive octaves (dots above)."""
591+ # Test oct=1 (single dot above)
592+ p = Pitch ({'swara' : 'sa' , 'oct' : 1 })
593+ assert p .latex_octaved_sargam_letter == r'$\dot{\mathrm{S}}$'
594+
595+ p = Pitch ({'swara' : 're' , 'raised' : False , 'oct' : 1 })
596+ assert p .latex_octaved_sargam_letter == r'$\dot{\mathrm{r}}$'
597+
598+ p = Pitch ({'swara' : 'ga' , 'raised' : True , 'oct' : 1 })
599+ assert p .latex_octaved_sargam_letter == r'$\dot{\mathrm{G}}$'
600+
601+ # Test oct=2 (double dot above)
602+ p = Pitch ({'swara' : 'ma' , 'raised' : False , 'oct' : 2 })
603+ assert p .latex_octaved_sargam_letter == r'$\ddot{\mathrm{m}}$'
604+
605+ p = Pitch ({'swara' : 'pa' , 'oct' : 2 })
606+ assert p .latex_octaved_sargam_letter == r'$\ddot{\mathrm{P}}$'
607+
608+ # Test oct=3 (triple dot above)
609+ p = Pitch ({'swara' : 'dha' , 'raised' : True , 'oct' : 3 })
610+ assert p .latex_octaved_sargam_letter == r'$\dddot{\mathrm{D}}$'
611+
612+ p = Pitch ({'swara' : 'ni' , 'raised' : False , 'oct' : 3 })
613+ assert p .latex_octaved_sargam_letter == r'$\dddot{\mathrm{n}}$'
614+
615+
616+ def test_latex_octaved_sargam_letter_negative_octaves ():
617+ """Test LaTeX octaved sargam letter with negative octaves (dots below)."""
618+ # Test oct=-1 (single dot below)
619+ p = Pitch ({'swara' : 'sa' , 'oct' : - 1 })
620+ assert p .latex_octaved_sargam_letter == r'$\underset{\bullet}{\mathrm{S}}$'
621+
622+ p = Pitch ({'swara' : 're' , 'raised' : True , 'oct' : - 1 })
623+ assert p .latex_octaved_sargam_letter == r'$\underset{\bullet}{\mathrm{R}}$'
624+
625+ # Test oct=-2 (double dot below)
626+ p = Pitch ({'swara' : 'ga' , 'raised' : False , 'oct' : - 2 })
627+ assert p .latex_octaved_sargam_letter == r'$\underset{\bullet\bullet}{\mathrm{g}}$'
628+
629+ p = Pitch ({'swara' : 'ma' , 'raised' : True , 'oct' : - 2 })
630+ assert p .latex_octaved_sargam_letter == r'$\underset{\bullet\bullet}{\mathrm{M}}$'
631+
632+ # Test oct=-3 (triple dot below)
633+ p = Pitch ({'swara' : 'pa' , 'oct' : - 3 })
634+ assert p .latex_octaved_sargam_letter == r'$\underset{\bullet\bullet\bullet}{\mathrm{P}}$'
635+
636+ p = Pitch ({'swara' : 'dha' , 'raised' : False , 'oct' : - 3 })
637+ assert p .latex_octaved_sargam_letter == r'$\underset{\bullet\bullet\bullet}{\mathrm{d}}$'
638+
639+
640+ def test_latex_octaved_sargam_letter_all_sargam_all_octaves ():
641+ """Test all sargam letters across all octave levels."""
642+ sargam_letters = ['sa' , 're' , 'ga' , 'ma' , 'pa' , 'dha' , 'ni' ]
643+ octave_expected = {
644+ - 3 : r'\underset{\bullet\bullet\bullet}' ,
645+ - 2 : r'\underset{\bullet\bullet}' ,
646+ - 1 : r'\underset{\bullet}' ,
647+ 0 : '' ,
648+ 1 : r'\dot' ,
649+ 2 : r'\ddot' ,
650+ 3 : r'\dddot'
651+ }
652+
653+ for swara in sargam_letters :
654+ for raised in [True , False ]:
655+ # Skip invalid combinations (sa and pa are always raised)
656+ if swara in ['sa' , 'pa' ] and not raised :
657+ continue
658+
659+ p = Pitch ({'swara' : swara , 'raised' : raised })
660+ base_letter = p .sargam_letter
661+
662+ for oct in range (- 3 , 4 ):
663+ p_oct = Pitch ({'swara' : swara , 'raised' : raised , 'oct' : oct })
664+ expected_latex = octave_expected [oct ]
665+
666+ if oct == 0 :
667+ expected_result = base_letter
668+ elif expected_latex .startswith (r'\underset' ):
669+ expected_result = f'${ expected_latex } {{\\ mathrm{{{ base_letter } }}}}$'
670+ else :
671+ expected_result = f'${ expected_latex } {{\\ mathrm{{{ base_letter } }}}}$'
672+
673+ assert p_oct .latex_octaved_sargam_letter == expected_result
674+
675+
676+ def test_latex_properties_preserve_backward_compatibility ():
677+ """Test that existing properties are not affected by LaTeX additions."""
678+ test_cases = [
679+ {'swara' : 'sa' , 'oct' : 0 },
680+ {'swara' : 're' , 'raised' : False , 'oct' : 1 },
681+ {'swara' : 'ga' , 'raised' : True , 'oct' : - 1 },
682+ {'swara' : 'ma' , 'raised' : False , 'oct' : 2 },
683+ {'swara' : 'pa' , 'oct' : - 2 },
684+ {'swara' : 'dha' , 'raised' : True , 'oct' : 3 },
685+ {'swara' : 'ni' , 'raised' : False , 'oct' : - 3 },
686+ ]
687+
688+ for options in test_cases :
689+ p = Pitch (options )
690+
691+ # All existing properties should work exactly as before
692+ assert hasattr (p , 'sargam_letter' )
693+ assert hasattr (p , 'octaved_sargam_letter' )
694+ assert hasattr (p , 'frequency' )
695+ assert hasattr (p , 'numbered_pitch' )
696+ assert hasattr (p , 'chroma' )
697+
698+ # New LaTeX properties should be available
699+ assert hasattr (p , 'latex_sargam_letter' )
700+ assert hasattr (p , 'latex_octaved_sargam_letter' )
701+
702+ # latex_sargam_letter should match sargam_letter
703+ assert p .latex_sargam_letter == p .sargam_letter
704+
705+
706+ def test_latex_octave_diacritic_helper ():
707+ """Test the _octave_latex_diacritic helper method."""
708+ # Test all octave levels
709+ octave_mapping = {
710+ - 3 : r'\underset{\bullet\bullet\bullet}' ,
711+ - 2 : r'\underset{\bullet\bullet}' ,
712+ - 1 : r'\underset{\bullet}' ,
713+ 0 : '' ,
714+ 1 : r'\dot' ,
715+ 2 : r'\ddot' ,
716+ 3 : r'\dddot'
717+ }
718+
719+ for oct , expected in octave_mapping .items ():
720+ p = Pitch ({'swara' : 'sa' , 'oct' : oct })
721+ assert p ._octave_latex_diacritic () == expected
722+
723+
724+ def test_latex_properties_edge_cases ():
725+ """Test LaTeX properties with edge cases and various combinations."""
726+ # Test with log_offset (should not affect LaTeX output)
727+ p = Pitch ({'swara' : 'ga' , 'raised' : False , 'oct' : 1 , 'log_offset' : 0.1 })
728+ assert p .latex_octaved_sargam_letter == r'$\dot{\mathrm{g}}$'
729+
730+ # Test with different fundamentals (should not affect LaTeX output)
731+ p = Pitch ({'swara' : 'ma' , 'raised' : True , 'oct' : - 1 , 'fundamental' : 440.0 })
732+ assert p .latex_octaved_sargam_letter == r'$\underset{\bullet}{\mathrm{M}}$'
733+
734+ # Test serialization includes existing functionality
735+ p = Pitch ({'swara' : 'dha' , 'raised' : False , 'oct' : 2 })
736+ json_data = p .to_json ()
737+ p_restored = Pitch .from_json (json_data )
738+
739+ # LaTeX properties should work after deserialization
740+ assert p_restored .latex_sargam_letter == 'd'
741+ assert p_restored .latex_octaved_sargam_letter == r'$\ddot{\mathrm{d}}$'
742+
0 commit comments