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"
'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
scalar
mc
tied
...
gracenote
nominal_duration
staff
mn
volta
timesig
mn_onset
tpc
octave
voice
0
e000000
A3
interval
Event
0
1
1
1
1
NaN
...
NaN
1/4
2
0
NaN
2/4
1/4
3
3
1
1
e000001
E4
interval
Event
0
1
1
1
1
NaN
...
NaN
1/4
1
0
NaN
2/4
1/4
4
4
2
2
e000002
A4
interval
Event
0
1/2
1/2
1
1
NaN
...
NaN
1/8
1
0
NaN
2/4
1/4
3
4
1
3
e000003
C#5
interval
Event
0
1/2
1/2
1
1
NaN
...
NaN
1/8
1
0
NaN
2/4
1/4
7
5
1
4
e000004
E5
interval
Event
1/2
1
1/2
1
1
NaN
...
NaN
1/8
1
0
NaN
2/4
3/8
4
5
1
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
rect_coords_json
layer_order
text_content
graphical_element_id
image_filename
alignment_group_id
text_anchor_xy_json
0
annot_cue_001
NaN
interval
rectangle
0.0
5.00
5.00
{"x": 10, "y": 90, "width": 148, "height": 55}
NaN
NaN
rect_a
thoresen_2010_form-building-patterns_p90-91_pa...
NaN
NaN
1
annot_cue_002
NaN
interval
rectangle
1.5
5.50
4.00
{"x": 40, "y": 37, "width": 127, "height": 21}
NaN
NaN
rect_b
thoresen_2010_form-building-patterns_p90-91_pa...
NaN
NaN
2
annot_cue_003
NaN
interval
rectangle
3.5
5.50
2.00
{"x": 111, "y": 60, "width": 57, "height": 23}
NaN
NaN
rect_c
thoresen_2010_form-building-patterns_p90-91_pa...
NaN
NaN
3
annot_cue_004
NaN
interval
rectangle
34.6
39.80
5.20
{"x": 145, "y": 90, "width": 160, "height": 58}
NaN
NaN
rect_a2
thoresen_2010_form-building-patterns_p90-91_pa...
NaN
NaN
4
annot_cue_005
NaN
interval
rectangle
43.5
48.00
4.50
{"x": 385, "y": 46, "width": 139, "height": 20}
NaN
NaN
rect_h2
thoresen_2010_form-building-patterns_p90-91_pa...
NaN
NaN
5
annot_cue_006
NaN
interval
rectangle
71.0
75.75
4.75
{"x": 310, "y": 93, "width": 154, "height": 18}
NaN
NaN
rect_d3
thoresen_2010_form-building-patterns_p90-91_pa...
NaN
NaN
6
annot_cue_007
NaN
interval
rectangle
76.0
83.50
7.50
{"x": 456, "y": 69, "width": 229, "height": 18}
NaN
NaN
rect_b3
thoresen_2010_form-building-patterns_p90-91_pa...
NaN
NaN
7
annot_cue_008
NaN
interval
rectangle
90.5
94.50
4.00
{"x": 14, "y": 115, "width": 127, "height": 31}
NaN
NaN
rect_i4
thoresen_2010_form-building-patterns_p90-91_pa...
NaN
NaN
8
annot_cue_009
NaN
interval
rectangle
113.4
116.40
3.00
{"x": 663, "y": 82, "width": 97, "height": 23}
NaN
NaN
rect_a4
thoresen_2010_form-building-patterns_p90-91_pa...
NaN
NaN
9
annot_cue_010
NaN
interval
rectangle
121.0
128.50
7.50
{"x": 19, "y": 119, "width": 251, "height": 29}
NaN
NaN
rect_i5
thoresen_2010_form-building-patterns_p90-91_pa...
NaN
NaN
10
annot_cue_011
NaN
interval
rectangle
141.0
142.50
1.50
{"x": 595, "y": 45, "width": 64, "height": 21}
NaN
NaN
rect_f5
thoresen_2010_form-building-patterns_p90-91_pa...
NaN
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(),}
{'unit': 'pixels', 'coordinate_range': (10, 760)}
Two Coordinate Systems from One File
The same TSV file backs timelines in different coordinate systems — seconds from the time columns, pixels from the JSON column:
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.