@@ -566,6 +566,46 @@ def _calculate_proportional_level_duration(self, positions: List[int], cycle_num
566566
567567 return parent_duration / level_size
568568
569+ def _calculate_proportional_fractional_beat (self , positions : List [int ], cycle_number : int , real_time : float ) -> float :
570+ """Calculate fractional beat using proportional timing when pulse data is sparse."""
571+ # Calculate the start time of the current finest-level unit
572+ cycle_start_time = self .start_time + cycle_number * self .cycle_dur
573+
574+ # Calculate duration of finest-level unit (pulse duration)
575+ pulse_duration = self .cycle_dur / self ._pulses_per_cycle
576+
577+ # Find position within the finest-level unit using hierarchical positions
578+ cumulative_time = 0.0
579+ current_duration = self .cycle_dur # Start with full cycle duration
580+
581+ for level in range (len (positions )):
582+ level_size = self .hierarchy [level ]
583+ if isinstance (level_size , list ):
584+ level_size = sum (level_size )
585+
586+ # Duration of each unit at this level
587+ unit_duration = current_duration / level_size
588+
589+ # Add time offset for this level
590+ cumulative_time += positions [level ] * unit_duration
591+
592+ # Update duration for next level (duration of current unit)
593+ current_duration = unit_duration
594+
595+ # Start time of current finest-level unit
596+ current_unit_start_time = cycle_start_time + cumulative_time
597+
598+ # Calculate fractional position within this unit
599+ time_from_unit_start = real_time - current_unit_start_time
600+
601+ if current_duration <= 0 :
602+ return 0.0
603+
604+ fractional_position = time_from_unit_start / current_duration
605+
606+ # Clamp to [0, 1] range
607+ return max (0.0 , min (1.0 , fractional_position ))
608+
569609 def get_musical_time (self , real_time : float , reference_level : Optional [int ] = None ) -> Union ['MusicalTime' , Literal [False ]]:
570610 """
571611 Convert real time to musical time within this meter.
@@ -623,28 +663,38 @@ def get_musical_time(self, real_time: float, reference_level: Optional[int] = No
623663 # Step 4: Fractional beat calculation
624664 # ALWAYS calculate fractional_beat as position within finest level unit (between pulses)
625665 # This is independent of reference_level, which only affects hierarchical_position truncation
626- current_pulse_index = self ._hierarchical_position_to_pulse_index (positions , cycle_number )
627666
628- # Bounds checking
629- if current_pulse_index < 0 or current_pulse_index >= len (self .all_pulses ):
630- fractional_beat = 0.0
667+ # Check if we have sufficient pulse data for accurate calculation
668+ expected_pulses = self ._pulses_per_cycle * self .repetitions
669+ if len (self .all_pulses ) < expected_pulses * 0.5 : # Less than 50% of expected pulses
670+ # Fall back to proportional fractional beat calculation for sparse pulse data
671+ fractional_beat = self ._calculate_proportional_fractional_beat (positions , cycle_number , real_time )
631672 else :
632- current_pulse_time = self .all_pulses [current_pulse_index ].real_time
633-
634- # Handle next pulse
635- if current_pulse_index + 1 < len (self .all_pulses ):
636- next_pulse_time = self .all_pulses [current_pulse_index + 1 ].real_time
637- else :
638- # Last pulse - use next cycle start
639- next_cycle_start = self .start_time + (cycle_number + 1 ) * self .cycle_dur
640- next_pulse_time = next_cycle_start
673+ # Use pulse-based calculation when we have sufficient pulse data
674+ current_pulse_index = self ._hierarchical_position_to_pulse_index (positions , cycle_number )
641675
642- pulse_duration = next_pulse_time - current_pulse_time
643- if pulse_duration <= 0 :
644- fractional_beat = 0.0
676+ # Bounds checking
677+ if current_pulse_index < 0 or current_pulse_index >= len (self .all_pulses ):
678+ # Even with sufficient overall pulse data, this specific pulse might be missing
679+ # Fall back to proportional calculation
680+ fractional_beat = self ._calculate_proportional_fractional_beat (positions , cycle_number , real_time )
645681 else :
646- time_from_current_pulse = real_time - current_pulse_time
647- fractional_beat = time_from_current_pulse / pulse_duration
682+ current_pulse_time = self .all_pulses [current_pulse_index ].real_time
683+
684+ # Handle next pulse
685+ if current_pulse_index + 1 < len (self .all_pulses ):
686+ next_pulse_time = self .all_pulses [current_pulse_index + 1 ].real_time
687+ else :
688+ # Last pulse - use next cycle start
689+ next_cycle_start = self .start_time + (cycle_number + 1 ) * self .cycle_dur
690+ next_pulse_time = next_cycle_start
691+
692+ pulse_duration = next_pulse_time - current_pulse_time
693+ if pulse_duration <= 0 :
694+ fractional_beat = 0.0
695+ else :
696+ time_from_current_pulse = real_time - current_pulse_time
697+ fractional_beat = time_from_current_pulse / pulse_duration
648698
649699 # Clamp to [0, 1] range
650700 fractional_beat = max (0.0 , min (1.0 , fractional_beat ))
0 commit comments