Quickstart
Music exists simultaneously as audio, notation, and images — each with its own coordinate system. Time To Align! provides a single framework for connecting them.
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, TimelineGroup
from timetoalign.loader.midi.performance import PerformanceMidiLoader
from timetoalign.loader.score.partitura import PartituraLoader
from timetoalign.loader.score.tsv import TSVLoader
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 Time To Align! 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 = TSVLoader.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.get_timestamps().head(5 )
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" )
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
score
20 quarters
axis
perf
5560 ticks
child
measures
11.75 measures
cmap
ticks
9600 ticks
cmap
quarters
19.5 quarters
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_alignment_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.