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"Quickstart
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.
| 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.
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")
scoreContinuousLogicalTimeline[score] (547 events, 3 children, 3 cmaps)
0 ________________________________ 41.5 quarters
├─ notes 0 ________________________________ 41.5 (498 events)
├─ measures 0 ________________________________ 41.5 (22 events)
└─ controls 0 ______________________________ 40 (27 events)
2. Timestamps
A cross-section showing coordinates in the root and all children. (Full tutorial)
score.get_timestamps().head(5)| axis (quarters) | score (quarters) | notes (quarters) | measures (quarters) | controls (quarters) | quarters_to_ticks (ticks) | quarters_to_measures (measures) | raw_quarters (quarters) | |
|---|---|---|---|---|---|---|---|---|
| id | ||||||||
| notes:note:000001 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | -1/2 |
| notes:note:000002 | 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 | 360 | 17/8 | 1/4 |
| notes:note:000008 | 1 | 1 | 1 | 1 | 1 | 480 | 9/4 | 1/2 |
| notes:note:000010 | 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| ID | Coordinate | Type |
|---|---|---|
| score | 20 quarters | axis |
| perf | 5560 ticks | child |
| quarters | 19.5 quarters | cmap |
| ticks | 9600 ticks | cmap |
| measures | 11.75 measures | 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.