Quickstart

A five-minute tour of Timelines

Quickstart

Music exists simultaneously as audio, notation, and images — each with its own coordinate system. TimeToAlign! provides a single framework for connecting them.

Domain Description Examples
Physical Wall-clock time Seconds, samples, frames
Logical Symbolic/musical time Beats, quarters, ticks
Graphical Visual coordinates Pixels, centimetres

This whirlwind tour covers the core concepts in under five minutes. Each section links to a full tutorial.

from pathlib import Path

from timetoalign import BeatGrid, MatchfileLoader, Ms3Loader, TimelineGroup
from timetoalign.loader.midi.performance import PerformanceMidiLoader
from timetoalign.loader.score.partitura import PartituraLoader
from timetoalign.maps import TicksToQuarters

DATA_DIR = Path(".").resolve().parents[1] / "tests" / "data"
/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

Same content, different formats

Before the tour, a quick demonstration of what TimeToAlign! is for. We load Chopin’s Etude Op. 10 No. 3 from both MusicXML and a TSV note list, and check that both loaders see the same musical content.

pt_loader = PartituraLoader()
pt_loader.load(DATA_DIR / "vienna_1x22" / "Chopin_op10_no3.musicxml")
tsv_loader = Ms3Loader.from_file(
    DATA_DIR / "vienna_1x22" / "ms3" / "chopin_op10_no3.notes.tsv"
)

{
    "Partitura (MusicXML)": len(pt_loader.store.notes),
    "TSV (MS3 export)": len(tsv_loader.store.notes),
}
{'Partitura (MusicXML)': 498, 'TSV (MS3 export)': 498}

Both loaders find 498 notes — different formats, same musical content. From here on we use the Partitura loader as the source for the tour.

1. Timelines & Events

Promote the loaded score into a timeline with nested children. (Full tutorial)

score = pt_loader.create_timeline(uid="score")
score
ContinuousLogicalTimeline[score] (547 events, 4 children, 3 cmaps)
                      0 ________________________________ 41.5 quarters
  ├─ notes            0 ________________________________ 41.5 (498 events)
  ├─ measures         0 ________________________________ 41.5 (22 events)
  ├─ controls         0 ______________________________   40 (22 events)
  └─ annotations      0 _________________________        32.5 (5 events)

2. Timestamps

A cross-section showing coordinates in the root and all children. (Full tutorial)

score.to_dataframe().head(5)
axis (quarters) score (quarters) notes (quarters) measures (quarters) controls (quarters) annotations (quarters) quarters_to_ticks (ticks) quarters_to_measures (floating_measures) raw_quarters (quarters)
id
notes:note:000001 0 0 0 0 0 0 0 1 -1/2
notes:note:000002 1/2 1/2 1/2 1/2 1/2 1/2 240 2 0
notes:note:000006 3/4 3/4 3/4 3/4 3/4 3/4 360 17/8 1/4
notes:note:000008 1 1 1 1 1 1 480 9/4 1/2
notes:note:000010 5/4 5/4 5/4 5/4 5/4 5/4 600 19/8 3/4

3. Conversion Maps

Translate between units (e.g., quarters to MIDI ticks). (Full tutorial)

q2t = TicksToQuarters(ppq=480).inverse()
score.add_conversion_map(q2t)
score.convert_to(score.make_coordinate(8), target_unit="ticks")
Coordinate(3840, ticks)

4. Timeline Groups

Link timelines for coordinate transfer. (Full tutorial)

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

group = TimelineGroup(id="demo")
group.add_timeline(score)
group.add_timeline(perf)

ts = group.get_timestamp_at(20.0, "score")
ts
TimeStamp interpolated
ID Coordinate Type
score 20 quarters axis
perf 5560 ticks child
score:ticks 9600 ticks cmap
floating_measures 11.75 floating_measures cmap
quarters 19.5 quarters cmap
score:ticks 9600 ticks cmap
Try: ts.get(<tl_id>), ts.get_unit(<unit>), ts[<cmap>]

5. Alignment Bundles

Load 22 performances from .match files in one go. (Full tutorial)

match_files = sorted((DATA_DIR / "vienna_1x22").glob("*.match"))
bundle = MatchfileLoader().load(*match_files).create_bundle()

{"timelines": len(bundle.timeline_ids), "groups": len(bundle.group_ids)}
{'timelines': 23, 'groups': 1}

6. Beat Grids

Rapid measure/beat queries for metrical structure. (Full tutorial)

grid = BeatGrid.from_tempo(tempo_bpm=120, beats_per_measure=4, length_seconds=30)
{"quarters": 6.0, "measure": grid.measure_at(6.0), "beat": grid.beat_at(6.0)}
{'quarters': 6.0, 'measure': 2, 'beat': Fraction(3, 1)}

That’s it. The tutorials that follow unpack each topic in detail; the How-To Guides show real-world workflows.