The fastest way to get music data into TimeToAlign! is through tabular loaders. If your data is in CSV or TSV format, you’re just 3 lines of code away from analysis.
What you’ll learn: - Load music annotations from TSV/CSV files - Access event counts, coordinate ranges, and metadata - Create timelines from loaded data - Write a custom loader that maps your columns to event fields - Promote selected columns to typed fields with column_specs - Reach nested JSON columns with Field
Time: 15 minutes
This is the gentle introduction. For the full column-to-field mechanism — the Step-1 resolution chain, composite columns, and Step-2 field promotion — see the in-depth CSV/TSV how-to.
TL;DR
from timetoalign.loader.tabular import TsvLoaderloader = TsvLoader()loader.load("beethoven.notes.tsv")df = loader.events.to_dataframe() # Get as DataFrametimeline = loader.create_timeline() # Create Timeline
Setup
from timetoalign.testdata import ensure_dataBEETHOVEN = ensure_data("score") /"beethoven_woo71"THORESEN = ensure_data("thoresen")# Available files{"Beethoven files": [f.name for f in BEETHOVEN.glob("WoO71.*.tsv")],"Thoresen files": [f.name for f in THORESEN.glob("*.tsv")],}
TsvLoader is the generic starting point for a TSV whose columns you control. Declare the coordinate columns and promote the values you want as typed fields. For a standard ms3 score export, use the score Ms3Loader instead; it understands its notes, measures, chords, and harmonies facets.
Three lines of code:
from timetoalign.core import NumberType, TimeUnit # noqa: E402from timetoalign.loader.tabular import TsvLoader # noqa: E402class BeethovenNotesLoader(TsvLoader):"""Generic TSV configuration for the coordinate and pitch columns used here.""" start_column ="quarterbeats" duration_column ="duration_qb" _default_unit = TimeUnit.quarters coordinate_type = NumberType.fraction column_specs = {"midi": int, "staff": int, "voice": int}loader = BeethovenNotesLoader()loader.load(BEETHOVEN /"WoO71.notes.tsv")f"{len(loader.events):,} notes loaded"
/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
'4,753 notes loaded'
Converting to pandas
Use to_dataframe() to get a DataFrame with clean coordinate values:
loader.events.to_dataframe().head()
id
name
temporal_type
event_type
start
end
duration
staff
voice
tpc
...
quarterbeats_all_endings
midi
mc_onset
chord_id
scalar
gracenote
timesig
octave
volta
mn_onset
0
e000000
A3
interval
Event
0
1.0
1.0
2
1
3
...
0
57
0
3
1
None
2/4
3
NaN
1/4
1
e000001
E4
interval
Event
0
1.0
1.0
1
2
4
...
0
64
0
2
1
None
2/4
4
NaN
1/4
2
e000002
A4
interval
Event
0
0.5
0.5
1
1
3
...
0
69
0
0
1
None
2/4
4
NaN
1/4
3
e000003
C#5
interval
Event
0
0.5
0.5
1
1
7
...
0
73
0
0
1
None
2/4
5
NaN
1/4
4
e000004
E5
interval
Event
1/2
1.0
0.5
1
1
4
...
1/2
76
1/8
1
1
None
2/4
5
NaN
3/8
5 rows × 24 columns
Quick Statistics
The loader provides immediate access to summary information:
For files that don’t match the ms3 format, create a custom loader by subclassing TsvLoader or CsvLoader. You point the canonical column attributes (start_column, duration_column, …) at the names in your file.
Let’s load the Thoresen annotations file, which has a different column structure:
import pandas as pd # noqa: E402pd.read_csv(THORESEN /"thoresen_test.tsv", sep="\t", nrows=3)
event_id
alignment_group_id
start_time_sec
duration_sec
event_type
graphical_element_id
image_filename
rect_coords_json
text_content
text_anchor_xy_json
layer_order
description
0
annot_cue_001
NaN
0.0
5.0
rectangle
rect_a
thoresen_2010_form-building-patterns_p90-91_pa...
{"x": 10, "y": 90, "width": 148, "height": 55}
NaN
NaN
NaN
NaN
1
annot_cue_002
NaN
1.5
4.0
rectangle
rect_b
thoresen_2010_form-building-patterns_p90-91_pa...
{"x": 40, "y": 37, "width": 127, "height": 21}
NaN
NaN
NaN
NaN
2
annot_cue_003
NaN
3.5
2.0
rectangle
rect_c
thoresen_2010_form-building-patterns_p90-91_pa...
{"x": 111, "y": 60, "width": 57, "height": 23}
NaN
NaN
NaN
NaN
The Simplest Custom Loader
Map the core coordinate columns and nothing else. Every source column that you do not name survives as an opaque property column — carried alongside the event so you never lose data, but left untyped:
Promoting Columns to Typed Fields with column_specs
When a property column should become a typed field, name it in column_specs. The keys are source-column names; the values are anything the loader can resolve to a field — a bare Python type (int / float / str) is the simplest form. Here we give two extra columns explicit types:
class ThoresenTypedLoader(TsvLoader):"""Selected columns promoted to typed fields.""" id_column ="event_id" start_column ="start_time_sec" duration_column ="duration_sec" event_type_column ="event_type" name_column ="description" _default_unit = TimeUnit.seconds coordinate_type = NumberType.float# Promote these source columns to typed fields. column_specs = {"image_filename": str,"graphical_element_id": str, }typed = ThoresenTypedLoader()typed.load(THORESEN /"thoresen_test.tsv")typed.events.to_dataframe()
id
name
temporal_type
event_type
start
end
duration
text_content
layer_order
text_anchor_xy_json
rect_coords_json
graphical_element_id
image_filename
alignment_group_id
0
annot_cue_001
None
interval
rectangle
0.0
5.00
5.00
NaN
NaN
NaN
{"x": 10, "y": 90, "width": 148, "height": 55}
rect_a
thoresen_2010_form-building-patterns_p90-91_pa...
NaN
1
annot_cue_002
None
interval
rectangle
1.5
5.50
4.00
NaN
NaN
NaN
{"x": 40, "y": 37, "width": 127, "height": 21}
rect_b
thoresen_2010_form-building-patterns_p90-91_pa...
NaN
2
annot_cue_003
None
interval
rectangle
3.5
5.50
2.00
NaN
NaN
NaN
{"x": 111, "y": 60, "width": 57, "height": 23}
rect_c
thoresen_2010_form-building-patterns_p90-91_pa...
NaN
3
annot_cue_004
None
interval
rectangle
34.6
39.80
5.20
NaN
NaN
NaN
{"x": 145, "y": 90, "width": 160, "height": 58}
rect_a2
thoresen_2010_form-building-patterns_p90-91_pa...
NaN
4
annot_cue_005
None
interval
rectangle
43.5
48.00
4.50
NaN
NaN
NaN
{"x": 385, "y": 46, "width": 139, "height": 20}
rect_h2
thoresen_2010_form-building-patterns_p90-91_pa...
NaN
5
annot_cue_006
None
interval
rectangle
71.0
75.75
4.75
NaN
NaN
NaN
{"x": 310, "y": 93, "width": 154, "height": 18}
rect_d3
thoresen_2010_form-building-patterns_p90-91_pa...
NaN
6
annot_cue_007
None
interval
rectangle
76.0
83.50
7.50
NaN
NaN
NaN
{"x": 456, "y": 69, "width": 229, "height": 18}
rect_b3
thoresen_2010_form-building-patterns_p90-91_pa...
NaN
7
annot_cue_008
None
interval
rectangle
90.5
94.50
4.00
NaN
NaN
NaN
{"x": 14, "y": 115, "width": 127, "height": 31}
rect_i4
thoresen_2010_form-building-patterns_p90-91_pa...
NaN
8
annot_cue_009
None
interval
rectangle
113.4
116.40
3.00
NaN
NaN
NaN
{"x": 663, "y": 82, "width": 97, "height": 23}
rect_a4
thoresen_2010_form-building-patterns_p90-91_pa...
NaN
9
annot_cue_010
None
interval
rectangle
121.0
128.50
7.50
NaN
NaN
NaN
{"x": 19, "y": 119, "width": 251, "height": 29}
rect_i5
thoresen_2010_form-building-patterns_p90-91_pa...
NaN
10
annot_cue_011
None
interval
rectangle
141.0
142.50
1.50
NaN
NaN
NaN
{"x": 595, "y": 45, "width": 64, "height": 21}
rect_f5
thoresen_2010_form-building-patterns_p90-91_pa...
NaN
That is the entire idea: columns are a source artefact; fields are a TimeToAlign! artefact.column_specs is the bridge. A bare type is the gentlest entry — the in-depth CSV/TSV how-to covers the full resolution chain (composite columns, semantic pitch/id fields, and the Step-2 field_specs promotion stage) for richer formats.
Nested JSON Column Access with Field
The Thoresen data has a rect_coords_json column containing pixel coordinates as JSON:
{"x":10,"y":90,"width":148,"height":55}
Use Field("column", "nested_field") to point a coordinate attribute straight at a nested value. TimeToAlign! parses the JSON automatically. ComputedField lets you derive a coordinate from a small formula over those nested values:
from timetoalign.loader import ComputedField, Field # noqa: E402class ThoresenGraphicalLoader(TsvLoader):"""Loader using PIXEL coordinates from a nested JSON column."""# Nested fields are addressed directly; JSON is parsed automatically. start_column = Field("rect_coords_json", "x") end_column = ComputedField("end", formula="rect_coords_json.x + rect_coords_json.width" ) _default_unit = TimeUnit.pixels coordinate_type = NumberType.float default_event_type ="Rectangle"graphical = ThoresenGraphicalLoader()graphical.load(THORESEN /"thoresen_test.tsv"){"unit": str(graphical.unit),"coordinate_range": graphical.events.coordinate_range(),}
Note: Both timelines represent the same 11 events in different coordinate systems: - Physical:0 - 142.5 seconds (audio time) - Graphical:10 - 760 pixels (image coordinates)
TimeToAlign! uses these dual representations to align graphical annotations with audio.
Child Timelines from Column Values with group_by
When your data carries events from multiple sources (images, pages, tracks), pass group_by to create_timeline() to split the events into one child timeline per unique value. The Thoresen data has events from five different image files:
from timetoalign.timelines import create_timeline # noqa: E402grouped_tl = create_timeline(typed, group_by="image_filename")grouped_tl
# Each child timeline represents events from one image{"parent_id": grouped_tl.id,"n_children": grouped_tl.n_children,"children": { child.id: len(child._events) if child._events else0for _, child in grouped_tl.iter_children() },}
Key Takeaway: Tabular loaders map CSV/TSV columns to TimeToAlign! events declaratively. Unnamed columns ride along as property columns; column_specs promotes the ones you want as typed fields; Field reaches nested JSON. For the full column-to-field mechanism — composite columns, semantic field types, and Step-2 promotion — continue to the in-depth CSV/TSV how-to.