Timeline Groups and Commensurability

TimelineGroup, coordinate transfer, partial alignment

Timeline Groups and Commensurability

Two timelines become commensurable — meaning coordinates can be transferred between them — once they share a TimelineGroup.

from timetoalign import TimelineGroup
from timetoalign.loader.midi.performance import PerformanceMidiLoader
from timetoalign.loader.score.partitura import PartituraLoader
from timetoalign.testdata import ensure_data

DATA_DIR = ensure_data("midi")
/home/laser/miniconda3/envs/timetoalign/lib/python3.11/site-packages/partitura/__init__.py:9: UserWarning: pkg_resources is deprecated as an API. See https://setuptools.pypa.io/en/latest/pkg_resources.html. The pkg_resources package is slated for removal as early as 2025-11-30. Refrain from using this package or pin to Setuptools<81.
  import pkg_resources

Load a Score and a Performance

_pt = PartituraLoader()
_pt.load(DATA_DIR / "score" / "rachmaninoff_piano.mid")
score_tl = _pt.create_timeline(uid="score")

perf_tl = PerformanceMidiLoader.from_file(
    DATA_DIR / "performance" / "rachmaninoff_perf.mid"
).create_timeline(uid="performance")

{
    "score": f"{score_tl.length} {score_tl.unit.name}",
    "performance": f"{perf_tl.length} {perf_tl.unit.name}",
}
/home/laser/miniconda3/envs/timetoalign/lib/python3.11/site-packages/partitura/io/importmidi.py:576: UserWarning: pitch spelling
  warnings.warn("pitch spelling")
/home/laser/miniconda3/envs/timetoalign/lib/python3.11/site-packages/partitura/io/importmidi.py:661: UserWarning: create_part
  part = create_part(
/home/laser/miniconda3/envs/timetoalign/lib/python3.11/site-packages/partitura/io/importmidi.py:661: UserWarning: add notes
  part = create_part(
/home/laser/miniconda3/envs/timetoalign/lib/python3.11/site-packages/partitura/io/importmidi.py:661: UserWarning: add time sigs and measures
  part = create_part(
/home/laser/miniconda3/envs/timetoalign/lib/python3.11/site-packages/partitura/io/importmidi.py:661: UserWarning: tie notes
  part = create_part(
/home/laser/miniconda3/envs/timetoalign/lib/python3.11/site-packages/partitura/io/importmidi.py:661: UserWarning: find tuplets
  part = create_part(
/home/laser/miniconda3/envs/timetoalign/lib/python3.11/site-packages/partitura/io/importmidi.py:661: UserWarning: done create_part
  part = create_part(
{'score': '25.985417 quarters quarters', 'performance': '11537 ticks ticks'}

Create a Group

Adding both timelines to the same group establishes a linear mapping between their full extents.

group = TimelineGroup(id="rachmaninoff")
group.add_timeline(score_tl)
group.add_timeline(perf_tl)
group
TimelineGroup[rachmaninoff] (2 timelines, 2 timestamps)
┌────────────────────────────────────────────────────────────────────────┐
│ ContinuousLogicalTimeline[score] (122 events, 3 children, 2 cmaps)     │
│                       0 ____________________________ 26.0 quarters     │
│   ├─ notes            0 ____________________________ 26.0 (111 events) │
│   ├─ measures         0 ____________________________ 26.0 (7 events)   │
│   └─ controls         0 _                            0 (4 events)      │
│                                                                        │
│ DiscreteLogicalTimeline[performance] (111 events)                      │
│                       0 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 11537 ticks     │
└────────────────────────────────────────────────────────────────────────┘
Timestamps: 2

Transfer Coordinates

# Get timestamp at score position 20.0 - shows ALL peer timelines
ts = group.get_timestamp_at(20.0, "score")
ts
TimeStamp interpolated
ID Coordinate Type
score 20 quarters axis
performance 8880 ticks child
ticks 9600 ticks cmap
floating_measures 6 floating_measures cmap
Try: ts.get(<tl_id>), ts.get_unit(<unit>)
# Transfer back: get timestamp at performance position 50.0 (ticks are DISCRETE)
ts_back = group.get_timestamp_at(50, "performance")
ts_back
TimeStamp interpolated
ID Coordinate Type
performance 50 ticks axis
score 0.112618 quarters child
ticks 54 ticks cmap
floating_measures 1.028154 floating_measures cmap
Try: ts.get(<tl_id>), ts.get_unit(<unit>)

Partial Alignment

If the performance only covers part of the score (say, quarter-beat positions 8 to 20), specify start and end boundaries.

partial = TimelineGroup(id="partial")
partial.add_timeline(score_tl)
partial.add_timeline(
    perf_tl,
    start=score_tl.make_coordinate(8.0).with_timeline("score"),
    end=score_tl.make_coordinate(20.0).with_timeline("score"),
)
partial
TimelineGroup[partial] (2 timelines, 4 timestamps)
┌────────────────────────────────────────────────────────────────────────┐
│ ContinuousLogicalTimeline[score] (122 events, 3 children, 2 cmaps)     │
│                       0 ____________________________ 26.0 quarters     │
│   ├─ notes            0 ____________________________ 26.0 (111 events) │
│   ├─ measures         0 ____________________________ 26.0 (7 events)   │
│   └─ controls         0 _                            0 (4 events)      │
│                                                                        │
│ DiscreteLogicalTimeline[performance] (111 events)                      │
│                       0 ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 11537 ticks     │
└────────────────────────────────────────────────────────────────────────┘
Timestamps: 4

Next: Alignment Bundles