How to Load an Audio-to-Audio Alignment (Listen Here!)
ListenHereLoader, dense audio-to-audio alignment matrix, columnar MatchClaimField, complete pairwise topology, get_matchstamp_at across all recordings
How to Load an Audio-to-Audio Alignment (Listen Here!)
Listen Here! aligns several recordings of one work to each other and exports the result as a single JSON file. Each recording is warped onto a shared, equidistant reference grid: per recording, the file stores a times array whose i-th entry is that recording’s clock-time (in seconds) at reference-grid column i. Because every recording is sampled against the same grid, the arrays are parallel and equal length — together they form a dense alignment matrix of shape (recordings × grid columns). This guide loads such an export into a single audio-to-audio with ListenHereLoader.
This is a different shape of data from the score↔︎performance loaders. There, each file relates one score to one performance, and a only emerges once several such bundles are combined. A Listen Here! file instead encodes the alignment of all recordings against one another directly — one file already carries the whole graph. The natural reading is a complete pairwise topology: at every grid column, every unordered pair of recordings is related by a synchronous instant .
We load an existing alignment; nothing here runs an aligner. The warp was computed once, offline, and written to disk; the loader reads it faithfully.
The arc:
Load the export in one call and read the reference recording.
Reach the columnar through the uniform get_field API, and inspect a single claim.
Build the .
The headline: place a point on one recording and read it on all the others.
A note on scale — why the claims live in a column, not a million objects.
Setup
The example data is a real Listen Here! alignment export of six recordings of the whole of Beethoven’s Eroica Variations, Op. 35 — six readings of the entire work warped onto one reference grid. The file is large (about 14 MB of parallel onset arrays), so it is read directly from its place on disk rather than fetched through the test-data helper. The cell below walks up from the notebook to the directory that holds the export.
from __future__ import annotationsimport osfrom pathlib import Pathfrom timetoalign.alignment.claims import MatchClaimfrom timetoalign.loader.alignment import ListenHereLoader# Locate the local Listen Here! alignment export by walking up the directory# tree until an ancestor contains it. This reads a local export file directly;# it is not a packaged test corpus._EXPORT_REL = Path("beethoven_eroica_variations_op35/variation_14/mdw/alignment.json")_search_roots = [Path.cwd(), *Path.cwd().parents]if"__file__"inglobals(): _search_roots +=list(Path(__file__).resolve().parents)_alignment_json =next( candidate for base in _search_roots if (candidate := base / _EXPORT_REL).exists())
/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
1. Load the export in one call
ListenHereLoader parses the single alignment JSON file: it reads each recording’s times array, checks that they all index the same reference grid (equal length), and assembles the complete pairwise claim set. from_file() is the one-line form of the standard two-phase loader pattern.
The loader names the reference recording — the recording whose clock defines the grid origin. Reading the reference matters because it is the anchor against which every other recording was warped; it is, however, just another recording in the bundle, not a privileged hub. Every recording is related to every other directly, so the bundle can be read from any of them.
The alignment is reached through the uniform field API: loader.get_field(MatchClaim) returns a — a columnar, PyArrow-backed store that holds the entire set of pairwise s as Arrow columns and materialises individual MatchClaim objects only on demand. Its length is the complete topology: for R recordings and N grid columns, every unordered pair at every column, i.e. C(R, 2) × N claims. Six recordings give C(6, 2) = 15 pairs, and this 25-minute work is sampled at 76 376 grid columns — so the field holds well over a million claims:
The field is held columnar precisely so that this many claims need not be a million Python objects. Indexing it materialises one on demand — an ordinary pairwise claim relating two recordings at one grid column, carrying the shared provenance the export recorded (Listen Here!’s chroma-feature DTW). We look at a single claim to see the shape; we do not iterate or materialise the whole field.
field[0]
MatchClaimsynchronous, instant
Timeline A
mdw-Chang-Gyu-LEE:cpt1
@-0.125019
Timeline B
mdw-Hyo-Eun-PARK:cpt1
@0
Metadata
agent=Listen Here! v0.20.0
Try: claim.get_matchstamp()
3. Build the bundle
create_bundle() assembles the : one seconds per recording, each in its own group, and the complete pairwise claim set tying every recording to every other. The recordings carry no symbolic events — each timeline holds only a length (the recording’s stored duration) and a unit; all the alignment lives in the cross-group claim field, which the bundle keeps columnar rather than exploding into a million claim objects.
The bundle’s diagram confirms the shape: six single-timeline groups and the full claim count, read straight from the columnar field (no claim is materialised to count it):
4. Place a point on one recording, read it on all the others
Here is the promise that makes audio-to-audio alignment compelling: place a point on one recording, and it is instantly placed on all the others.get_matchstamp_at takes a coordinate on any one recording’s timeline and returns the corresponding coordinate on every recording connected to it.
The query coordinate must land on an exact grid column carried by the field, so we take a real one from the data rather than inventing a value: a claim from roughly the middle of the work, read off the reference recording’s clock.
Read across that : the same musical instant the reference reaches at this second falls at a slightly different second in each of the other five recordings — a quicker reading reaches it earlier, a steadier one later. One coordinate, placed once, located in all six recordings in a single query:
{"timelines in the stamp": stamp.n_timelines,"seconds per recording": { tl_id: round(stamp.get_coordinate(tl_id).value, 2)for tl_id insorted(stamp.coordinates) },}
{'timelines in the stamp': 6,
'seconds per recording': {'mdw-Chang-Gyu-LEE:cpt1': 762.07,
'mdw-Hyo-Eun-PARK:cpt1': 742.49,
'mdw-Martin-NÖBAUER:cpt1': 704.98,
'mdw-Seika-ISHIDA:cpt1': 765.0,
'mdw-Wataru-MASHIMO:cpt1': 763.76,
'mdw-Yau-Nam-NG:cpt1': 722.07}}
Because the topology is complete, the same query works from any recording as the anchor, not only the reference — every pair is directly related, so no recording is a required hub.
5. A note on scale
This single file holds more than a million pairwise claims: six recordings give C(6, 2) = 15 pairs, and a 25-minute work sampled at 50 Hz runs to tens of thousands of grid columns. A whole-work export with more recordings would be larger still.
This is exactly why the claims live in a rather than as a million individual MatchClaim objects. The field stores the whole set as Arrow columns (the two timeline ids dictionary-encoded, the two coordinates as float64) and the loader builds it vectorised — never constructing a Python claim per row. A MatchClaim is materialised only when a single row is indexed. get_matchstamp_at likewise filters the column vectorised and materialises only the handful of claims at the queried coordinate. The columnar store is what lets one AlignmentBundle hold a whole-work, every-pair audio-to-audio graph without strain.
Recap
What the bundle expresses
How
One recording per group
<stem>:cpt1 seconds timeline, no events, length = the recording’s duration
The whole pairwise alignment
a columnar via get_field(MatchClaim)
One claim, materialised
field[i] → a synchronous instant
The reference recording
loader.reference — the grid origin, but just another recording
A point placed everywhere
bundle.get_matchstamp_at(coord, "<stem>:cpt1") → every recording at once
A single file encodes an entire audio-to-audio — every recording of one work warped onto a shared reference grid, every pair directly related. ListenHereLoader reads it into one in which a coordinate placed on any one recording resolves, in a single query, across all the others; and the dense claim set that makes that possible is held in a column, not a million objects.