Skip to content

Bug: fractional_beat incorrect for multi-cycle meters (hierarchical position calculation) #36

@jon-myers

Description

@jon-myers

Summary

While investigating ongoing fractional_beat issues in version 0.1.30, I found a second bug separate from the all_pulses issue that was fixed in PR #35.

The hierarchical position calculation in get_musical_time() sometimes calculates the wrong pulse index, leading to negative time_from_current_pulse values that get clamped to 0.0.

Root Cause Analysis

The Problem

The hierarchical position algorithm calculates positions that sometimes point to a pulse that comes AFTER the query time instead of before/at it. This causes:

time_from_current_pulse = real_time - current_pulse_time
# Becomes negative when current_pulse_time > real_time
fractional_beat = time_from_current_pulse / pulse_duration  # Negative!
fractional_beat = max(0.0, min(1.0, fractional_beat))      # Clamped to 0.0

Evidence

Testing with transcription 68a3a79fffd9b2d478ee11e8:

Debugging time 5.0:
  Calculated pulse index: 7
  Current pulse time: 5.060975  ← Pulse AFTER query time
  Query time: 5.0
  Time from current pulse: -0.060975  ← NEGATIVE!
  Raw fractional_beat: -0.441061
  Final fractional_beat: 0.000000  ← Clamped to 0

Debugging time 8.29:
  Calculated pulse index: 32
  Current pulse time: 8.349985  ← Pulse AFTER query time  
  Query time: 8.29
  Time from current pulse: -0.059985  ← NEGATIVE!
  Final fractional_beat: 0.000000  ← Clamped to 0

Working cases find pulses that come before the query time:

Debugging time 4.19:
  Current pulse time: 4.093249  ← Pulse BEFORE query time
  Query time: 4.19
  Time from current pulse: 0.096751  ← POSITIVE ✓
  Final fractional_beat: 0.699846  ← Correct!

Impact

  • Multi-cycle meters return fractional_beat=0.0 for many time points
  • Trajectory curve visualization shows clustering instead of smooth curves
  • Affects musical analysis that depends on sub-pulse positioning

Location

The bug is in the hierarchical position calculation logic in get_musical_time() around lines 600-670 in meter.py.

Reproduction

from idtap import SwaraClient, Piece

client = SwaraClient()
piece = Piece.from_json(client.get_piece("68a3a79fffd9b2d478ee11e8"))
meter = piece.meters[0]

# These should have non-zero fractional_beat but return 0.0:
result1 = meter.get_musical_time(5.0)    # Returns fractional_beat=0.0 (BUG)
result2 = meter.get_musical_time(8.29)   # Returns fractional_beat=0.0 (BUG)

# These work correctly:
result3 = meter.get_musical_time(4.19)   # Returns fractional_beat=0.699846 ✓
result4 = meter.get_musical_time(6.0)    # Returns fractional_beat=0.322064 ✓

Required Fix

The hierarchical position calculation needs to be corrected to always find the pulse that comes at or before the query time, not after it. This likely involves:

  1. Fixing the subdivision duration calculations
  2. Ensuring proper floor division vs ceiling division
  3. Adding comprehensive multi-cycle tests to catch this class of bug

Testing Needed

Current tests don't cover multi-cycle scenarios adequately. Need tests that:

  • Test fractional_beat across all cycles in a multi-cycle meter
  • Verify continuous fractional_beat progression within cycles
  • Test edge cases at cycle boundaries
  • Test various hierarchy structures across multiple cycles

This bug affects real-world usage where transcriptions span multiple meter cycles, making trajectory analysis unreliable.

🤖 Generated with Claude Code

Co-Authored-By: Claude noreply@anthropic.com

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions