Quickstart

What the library is for, in five minutes: one section per tutorial that follows

Quickstart

One piece of music can exist at once as a recording, as notation, and as images on a page. A says which kind of representation an axis measures: physical, logical, or graphical. A continuous axis permits positions between its marks; a axis uses numbered steps such as samples, ticks, or pixels.

Domain Continuous Discrete
Physical: recording seconds samples
Logical: notation quarters ticks
Graphical: page image centimetres pixels

TimeToAlign! represents these axes and the relationships between them. A keeps a position together with the unit of its axis, so the library can distinguish 12.5 seconds from 12.5 quarters.

What you will build

In five minutes, you will connect a 30-second recording axis to a small loaded score and inspect one exact cross-representation position. Each section gives a short preview and points to the tutorial that develops that idea.

Before you start

Use Python 3.11 or later in an environment with TimeToAlign! and its tutorial dependencies installed; see Getting Started.

from fractions import Fraction

from timetoalign import MatchClaim, PartituraLoader
from timetoalign.core import EnharmonicPitch, SpecificPitch
from timetoalign.testdata import ensure_data
from timetoalign.timelines import (
    ContinuousPhysicalTimeline,
    TimelineGroup,
)

vienna = ensure_data("vienna_1x22")

Timelines and Coordinates

A is a measured axis for one representation of the music. A Coordinate places a value on that axis and keeps its unit attached.

audio = ContinuousPhysicalTimeline(length=Fraction(30), uid="audio")
audio_coordinate = audio.make_coordinate(Fraction(25, 2))
audio_coordinate
Coordinate(Fraction(25, 2), seconds)

The result is the exact rational position 25/2 seconds on a 30-second audio axis, displayed with its unit. Open Timelines and Coordinates to build and inspect timelines carefully.

Nesting and Timestamps

Nested timelines are objects. A reads one position across the parent and every child present there.

left_channel = ContinuousPhysicalTimeline(length=Fraction(30), uid="left-channel")
right_channel = ContinuousPhysicalTimeline(length=Fraction(30), uid="right-channel")
audio.add_child(left_channel, offset=Fraction(0))
audio.add_child(right_channel, offset=Fraction(0))
audio_timestamp = audio.get_timestamp(audio_coordinate)
timestamp_coordinates = {
    "audio parent": audio_timestamp.get_coordinate(audio.id),
    "left channel": audio_timestamp.get_coordinate(left_channel.id),
    "right channel": audio_timestamp.get_coordinate(right_channel.id),
}
timestamp_coordinates
{'audio parent': IdCoordinate(12.5, seconds, 'audio'),
 'left channel': IdCoordinate(12.5, seconds, 'left-channel'),
 'right channel': IdCoordinate(12.5, seconds, 'right-channel')}

The three named values show the parent and both children at the same exact 25/2-second position. The timestamp itself is the cross-section used to retrieve them. Open Nesting and Timestamps to learn how nested coordinate systems work.

Events on a Timeline

An records something that happens at an instant or over an interval. Here three labelled cues become part of the audio timeline.

audio.add_events(
    [
        {
            "id": "opening",
            "event_type": "Cue",
            "temporal_type": "interval",
            "start": 0.0,
            "end": 2.0,
        },
        {
            "id": "theme",
            "event_type": "Cue",
            "temporal_type": "interval",
            "start": 8.0,
            "end": 14.0,
        },
        {
            "id": "coda",
            "event_type": "Cue",
            "temporal_type": "interval",
            "start": 25.0,
            "end": 30.0,
        },
    ]
)
audio_events = audio.get_events(event_type="Cue")
audio_events

EventData

Events 3
Unit seconds
Number type float
Fields (none)
Try get_field(<Scalar>), get_pitch_field(), get_raw('<col>')

The event store contains the three non-empty cue intervals in seconds. Open Events on a Timeline to add, filter, and retrieve musical events.

Loading Real Data

Loaders turn common research formats into the same kind of timeline. This preview reads a small MusicXML score prepared in the setup, then shows what the loader populated.

score_loader = PartituraLoader.from_file(vienna / "Chopin_op10_no3.musicxml")
score = score_loader.create_timeline(uid="score")
score_children = score.list_children()
score_notes = score.get_events(event_type="Note")
score_measures = score.get_events(event_type="Measure")
note_frame = score_notes.to_dataframe(coordinates=True)
measure_frame = score_measures.to_dataframe(coordinates=True)
preview_columns = ["name", "start", "end"]
note_preview = note_frame.loc[0, preview_columns].to_dict()
measure_preview = measure_frame.loc[0, preview_columns].to_dict()
loaded_score_preview = {
    "children": score_children,
    "first note": note_preview,
    "first measure": measure_preview,
}
loaded_score_preview
{'children': ['notes', 'measures', 'controls', 'annotations'],
 'first note': {'name': 'B3',
  'start': Coordinate(Fraction(0, 1), quarters),
  'end': Coordinate(Fraction(1, 2), quarters)},
 'first measure': {'name': '1',
  'start': Coordinate(Fraction(0, 1), quarters),
  'end': Coordinate(Fraction(1, 2), quarters)}}

The named children include notes and measures. The non-empty samples show one event from each of those children; their positions are exact, unit-bearing coordinates. Open Loading Real Data to choose a loader and understand what it creates.

Timeline Groups

A relates commensurable timelines so that a position on one can be transferred to another. Here their full extents are treated as corresponding.

group = TimelineGroup(id="quickstart", timelines=[audio, score])
score_coordinate = group.get_coordinate_at(
    timestamp_coordinates["audio parent"],
    timeline_id=score.id,
)
coordinate_pair = {
    "audio": timestamp_coordinates["audio parent"],
    "score": score_coordinate,
}
coordinate_pair
{'audio': IdCoordinate(12.5, seconds, 'audio'),
 'score': IdCoordinate(Fraction(4867171472288427, 281474976710656), quarters, 'score')}

The dictionary shows what convert returns directly: the exact 25/2 seconds maps to Fraction(415, 24) quarters, and both values remain unit-bearing coordinates. Open Timeline Groups to control these relationships.

Alignment Bundles

An collects broader alignment evidence; its basic link is a . A synchronous claim asserts that two unit-bearing positions correspond.

claim = MatchClaim.from_events(
    {"start": coordinate_pair["audio"].value},
    audio.id,
    {"start": coordinate_pair["score"].value},
    score.id,
    unit_a=audio.unit,
    unit_b=score.unit,
)
claim
MatchClaim ANCHOR
Timeline A audio @12.5 seconds
Timeline B score @4867171472288427/281474976710656 quarters
Try: claim.get_matchstamp()

The rendered claim shows the exact, unit-bearing positions @25/2 seconds and @415/24 quarters, with the ANCHOR tag marking a claim built from bare coordinates, as explained with other claim types in the alignment tutorial. A bundle can combine this claim with wider alignment evidence. Open Alignment Bundles to assemble and query larger alignments.

Repeat Unfolding

A can unfold a printed passage that is played more than once. Here the written opening from quarter 0 up to, but not including, quarter 4 is played twice.

opening_span = (Fraction(0), Fraction(4))
played_spans = [opening_span, opening_span]
repeat_map = score.create_flow_map(played_spans, id="quickstart-repeat")
folded_coordinate = score.make_coordinate(Fraction(2))
unfolded_values = repeat_map.unfold_coordinate(folded_coordinate.value)
unfolded_coordinates = [score.make_coordinate(value) for value in unfolded_values]
flow_preview = {"written": folded_coordinate, "played": unfolded_coordinates}
flow_preview
{'written': Coordinate(Fraction(2, 1), quarters),
 'played': [Coordinate(Fraction(2, 1), quarters),
  Coordinate(Fraction(6, 1), quarters)]}

The written position at two quarters appears at two and six quarters in the played order. This cell previews only repeat unfolding; the next tutorial introduces metrical grids separately. Open Flow and Grids to model complete playthroughs and metrical queries.

The Data Model

Arrow is the column-oriented table storage used underneath the library: it keeps many event values compact and consistent. Asking a typed column for its start field reconstructs the library object represented in that storage.

first_start = score_notes.get_field("start")[0]
first_start
Coordinate(Fraction(0, 1), quarters)

The loaded note’s first start value is an exact Coordinate in quarters, not a bare number. Open The Data Model to understand fields, scalars, and their tabular representation.

Pitch and Harmony

MIDI pitch can leave a black key’s spelling open, while notation can preserve the written accidental. TimeToAlign! keeps those meanings distinct.

enharmonic_pitch = EnharmonicPitch(61)
spelled_pitch = SpecificPitch.from_string("C♯4")
pitch_pair = {"enharmonic": enharmonic_pitch, "spelled": spelled_pitch}
pitch_pair
{'enharmonic': EP(C♯/D♭4), 'spelled': SP(C♯4)}

The first pitch allows C♯ or D♭; the second records C♯ specifically. Open Pitch and Harmony to work with pitch representations, spelling, and harmonic labels.

Series Map

Tutorial Promise
1. Timelines and Coordinates Build a measured axis and place a coordinate on it.
2. Nesting and Timestamps Read one position across nested timelines.
3. Events on a Timeline Store and retrieve musical observations.
4. Loading Real Data Turn a research file into timelines and events.
5. Timeline Groups Transfer positions between commensurable timelines.
6. Alignment Bundles Collect claims and query cross-timeline matches.
7. Flow and Grids Unfold score order and ask metrical questions.
8. The Data Model Recover typed library objects from columns.
9. Pitch and Harmony Preserve pitch meaning and written spelling.

What you learned

  • You can build a timeline and make a unit-bearing coordinate.
  • You can nest child timelines and read across them with a timestamp.
  • You can add events to a timeline and retrieve a non-empty selection.
  • You can load a score file into the same timeline model.
  • You can group timelines and transfer a coordinate between them.
  • You can express one match claim between exact, unit-bearing positions.
  • You can unfold one written position into repeated played positions.
  • You can retrieve a typed coordinate from an event-data field.
  • You can distinguish an enharmonic pitch from a specifically spelled pitch.

Next

Continue with Timelines and Coordinates.

Go deeper