How to Align Multimodal Data (Beethoven)

AlignmentBundle, FlowMap, OMR, 16+ timelines across 3 domains

How to Align Multimodal Data (Beethoven)

Figure 3 acid test for TimeToAlign! — 16+ timelines across all 3 domains (Physical, Logical, Graphical) in 5 TimelineGroups within one AlignmentBundle.

Structure: 1. Part I: Build 3 recording groups (Groups 1-3) — 15 DPTs 2. Part II: Build Score group (Group 4) + align with recordings 3. Part III: Build Emerson group (Group 5) + cross-group coordinate transfer

0. Gold Standard Reference Values

ID Description Samples Rate Grp
DPT1-5 Normal 11,753,638 / 11,195 / 22,389 / 45,844 / 63,965 44.1k / 42 / 84 / 172 / 240 1
DPT6-10 Mechanical 12,426,696 / 11,836 / 23,671 / 48,469 / 67,628 same rates 2
DPT11-15 Exaggerated 8,197,748 / 7,808 / 15,616 / 31,975 / 44,614 same rates 3
Recording Notes Matched Unmatched EEP Unmatched ABC
Normal 4,026 3,740 16 23
Mechanical 4,026 3,743 13 20
Exaggerated 2,820 2,650 4 1,113

1. Setup


import numpy as np
import pandas as pd
from PIL import Image

from timetoalign import (
    ContinuousPhysicalTimeline,
    Coordinate,
    DiscreteGraphicalTimeline,
    IdCoordinate,
    Ms3Loader,
    NumberType,
    RepoVizzLoader,
    TableMap,
    TimelineGroup,
    TimeUnit,
)
from timetoalign.alignment import (
    Agent,
    AlignmentAnchor,
    AlignmentBundle,
    MatchClaim,
    MatchLine,
    MatchMetadata,
    WarpMap,
)
from timetoalign.alignment.matching import (
    match_notes_by_attributes,
    prepare_abc_notes_for_matching,
    prepare_eep_notes_for_matching,
)
from timetoalign.core import AgentType
from timetoalign.core.enums import FlowMode
from timetoalign.testdata import ensure_data
from timetoalign.timelines.flow import create_unfolded_timeline
from timetoalign.timelines.types import SegmentLine

DATA_DIR = ensure_data("score") / "beethoven_op18-4iv_multimodal"

# XML manifest paths — the loader reads metadata from these files
NORMAL_XML = DATA_DIR / "StringQuartetEEP_I_Normal" / "StringQuartetEEP_I_Normal.xml"
MECHANICAL_XML = (
    DATA_DIR / "StringQuartetEEP_I_Mechanical" / "StringQuartetEEP_I_Mechanical.xml"
)
EXAGGERATED_XML = (
    DATA_DIR / "StringQuartetEEP_I_Exaggerated" / "StringQuartetEEP_I_Exaggerated.xml"
)

# Audio sources and instruments
AUDIO_SOURCES = [
    "mono",
    "binaural",
    "pickup_vln1",
    "pickup_vln2",
    "pickup_vla",
    "pickup_cello",
]
INSTRUMENTS = ["vln1", "vln2", "vla", "cello"]

Each EEP recording directory contains 5 modalities (audio, 3 feature types, MoCap) plus .notes files with annotated note events. The function below builds a TimelineGroup from one such directory via the XML manifest.

Structure (per manuscript): - 5 parent physical timelines, each with a SamplesToSeconds c-map - Audio, Tonal, LowLevel, Rhythm parents: 6 children each (mono, binaural, 4 pickups) - MoCap parent: 4 children (one per instrument: vln1, vln2, vla, cello)

def build_recording_group(xml_path, group_id, group_name, dpt_base):
    """Build a TimelineGroup from one EEP recording directory via XML manifest.

    Args:
        xml_path: Path to the recording's XML manifest file.
        group_id: ID for the TimelineGroup.
        group_name: Human-readable name for the group.
        dpt_base: Starting DPT number (e.g. 1 for dpt1-dpt5).

    Returns:
        TimelineGroup with 5 hierarchical DPTs (parent + children).
    """
    rv = RepoVizzLoader.from_file(xml_path)
    n = dpt_base

    # 1. Audio (mono as parent, 6 sources as children)
    audio = rv.create_timeline("mono", uid=f"dpt{n}", name="Audio")
    for src in AUDIO_SOURCES:
        audio.add_child(rv.create_timeline(src, uid=src), offset=0)

    # 2-4. Essentia descriptors (tonal, lowlevel, rhythm)
    desc_cfgs = [
        ("tonal", "ChordsStrength", 1),
        ("lowlevel", "Dissonance", 2),
        ("rhythm", "BeatsLoudness", 3),
    ]
    descriptors = []
    for desc_type, desc_name, offset in desc_cfgs:
        parent = rv.create_timeline(
            f"{desc_type}.{desc_name}.mono",
            uid=f"dpt{n + offset}",
            name=desc_type.title(),
        )
        for src in AUDIO_SOURCES:
            parent.add_child(
                rv.create_timeline(
                    f"{desc_type}.{desc_name}.{src}", uid=f"{src}_{desc_type}"
                ),
                offset=0,
            )
        descriptors.append(parent)

    # 5. MoCap bb_angle (from the DescriptorGroup section of the XML)
    mocap = rv.create_timeline(
        rv.find_descriptor("bb_angle", "vln1"),
        uid=f"dpt{n + 4}",
        name="MoCap",
    )
    for inst in INSTRUMENTS:
        child = rv.create_timeline(
            rv.find_descriptor("bb_angle", inst),
            uid=f"{inst}_mocap",
        )
        mocap.add_child(child, offset=0)

    # Add notes to pickup children
    for inst in INSTRUMENTS:
        notes = rv.store.notes_for_instrument(inst)
        if notes and (pickup := audio.get_child(f"pickup_{inst}")):
            pickup.add_events(notes.to_dataframe().to_dict("records"))

    return TimelineGroup(
        id=group_id,
        name=group_name,
        timelines=[audio, *descriptors, mocap],
    )

Part I: Three Recording Groups (Groups 1-3)

Each EEP recording = 5 DPTs (audio + 3 feature types + MoCap) at different sampling rates, all sharing the same physical duration. Note events live as a child of the audio DPT.

2. Group 1: Normal Recording (DPT1-DPT5)

normal_group = build_recording_group(
    NORMAL_XML, "normal", "Normal Recording", dpt_base=1
)
normal_group
TimelineGroup[normal] (5 timelines, 2 timestamps)
┌────────────────────────────────────────────────────────────────────┐
│ DiscretePhysicalTimeline[dpt1] (6 children, 1 cmaps)               │
│                          0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11753638 samples │
│   ├─ Cardioid ...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11753638         │
│   ├─ Binaural ...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11753638         │
│   ├─ Piezo pic...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11753638         │
│   ├─ Piezo pic...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11753638         │
│   ├─ Piezo pic...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11753638         │
│   └─ Piezo pic...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11753638         │
│                                                                    │
│ DiscretePhysicalTimeline[dpt2] (6 children, 1 cmaps)               │
│                       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11195 samples │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11195         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11195         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11195         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11195         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11195         │
│   └─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11195         │
│                                                                    │
│ DiscretePhysicalTimeline[dpt3] (6 children, 1 cmaps)               │
│                       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 22389 samples │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 22389         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 22389         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 22389         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 22389         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 22389         │
│   └─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 22389         │
│                                                                    │
│ DiscretePhysicalTimeline[dpt4] (6 children, 1 cmaps)               │
│                       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 45844 samples │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 45844         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 45844         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 45844         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 45844         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 45844         │
│   └─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 45844         │
│                                                                    │
│ DiscretePhysicalTimeline[dpt5] (4 children, 1 cmaps)               │
│                       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 63965 samples │
│   ├─ Bow Angle        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 63965         │
│   ├─ Bow Angle        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 63965         │
│   ├─ Bow Angle        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 63965         │
│   └─ Bow Angle        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 63965         │
└────────────────────────────────────────────────────────────────────┘
Timestamps: 2

The audio timeline now carries the note annotations as a child:

normal_group.get_timeline("dpt1")
DiscretePhysicalTimeline[dpt1] (6 children, 1 cmaps)
                         0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11753638 samples
  ├─ Cardioid ...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11753638
  ├─ Binaural ...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11753638
  ├─ Piezo pic...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11753638
  ├─ Piezo pic...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11753638
  ├─ Piezo pic...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11753638
  └─ Piezo pic...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11753638

3. Group 2: Mechanical Recording (DPT6-DPT10)

mechanical_group = build_recording_group(
    MECHANICAL_XML, "mechanical", "Mechanical Recording", dpt_base=6
)
mechanical_group
TimelineGroup[mechanical] (5 timelines, 2 timestamps)
┌────────────────────────────────────────────────────────────────────┐
│ DiscretePhysicalTimeline[dpt6] (6 children, 1 cmaps)               │
│                          0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 12426696 samples │
│   ├─ Cardioid ...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 12426696         │
│   ├─ Binaural ...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 12426696         │
│   ├─ Piezo pic...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 12426696         │
│   ├─ Piezo pic...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 12426696         │
│   ├─ Piezo pic...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 12426696         │
│   └─ Piezo pic...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 12426696         │
│                                                                    │
│ DiscretePhysicalTimeline[dpt7] (6 children, 1 cmaps)               │
│                       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11836 samples │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11836         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11836         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11836         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11836         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11836         │
│   └─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11836         │
│                                                                    │
│ DiscretePhysicalTimeline[dpt8] (6 children, 1 cmaps)               │
│                       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 23671 samples │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 23671         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 23671         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 23671         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 23671         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 23671         │
│   └─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 23671         │
│                                                                    │
│ DiscretePhysicalTimeline[dpt9] (6 children, 1 cmaps)               │
│                       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 48469 samples │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 48469         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 48469         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 48469         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 48469         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 48469         │
│   └─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 48469         │
│                                                                    │
│ DiscretePhysicalTimeline[dpt10] (4 children, 1 cmaps)              │
│                       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 67628 samples │
│   ├─ Bow Angle        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 67628         │
│   ├─ Bow Angle        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 67628         │
│   ├─ Bow Angle        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 67628         │
│   └─ Bow Angle        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 67628         │
└────────────────────────────────────────────────────────────────────┘
Timestamps: 2

4. Group 3: Exaggerated Recording (DPT11-DPT15)

Shorter recording (~186s) — stops after measure 131.

exaggerated_group = build_recording_group(
    EXAGGERATED_XML,
    "exaggerated",
    "Exaggerated Recording",
    dpt_base=11,
)
exaggerated_group
TimelineGroup[exaggerated] (5 timelines, 2 timestamps)
┌────────────────────────────────────────────────────────────────────┐
│ DiscretePhysicalTimeline[dpt11] (6 children, 1 cmaps)              │
│                         0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 8197748 samples │
│   ├─ Cardioid ...       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 8197748         │
│   ├─ Binaural ...       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 8197748         │
│   ├─ Piezo pic...       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 8197748         │
│   ├─ Piezo pic...       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 8197748         │
│   ├─ Piezo pic...       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 8197748         │
│   └─ Piezo pic...       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 8197748         │
│                                                                    │
│ DiscretePhysicalTimeline[dpt12] (6 children, 1 cmaps)              │
│                       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 7809 samples │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 7809         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 7809         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 7809         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 7809         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 7809         │
│   └─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 7809         │
│                                                                    │
│ DiscretePhysicalTimeline[dpt13] (6 children, 1 cmaps)              │
│                       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 15616 samples │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 15616         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 15616         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 15616         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 15616         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 15616         │
│   └─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 15616         │
│                                                                    │
│ DiscretePhysicalTimeline[dpt14] (6 children, 1 cmaps)              │
│                       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 31975 samples │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 31975         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 31975         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 31975         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 31975         │
│   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 31975         │
│   └─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 31975         │
│                                                                    │
│ DiscretePhysicalTimeline[dpt15] (4 children, 1 cmaps)              │
│                       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 44614 samples │
│   ├─ Bow Angle        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 44614         │
│   ├─ Bow Angle        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 44614         │
│   ├─ Bow Angle        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 44614         │
│   └─ Bow Angle        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 44614         │
└────────────────────────────────────────────────────────────────────┘
Timestamps: 2

5. Part I Summary

3 groups, 15 timelines. Each audio DPT carries note events as a child timeline, making them accessible for matching in Part II.

Next: Part II builds the Score group and aligns each recording via note matching.


Part II: Score Group + Alignment to Recordings (Group 4)

The score group brings together three representations of the same music:

  • CLT1: ABC v2.6 score (notes, measures, harmonies) — ContinuousLogicalTimeline
  • DGT1: OMR ground truth (3,190 note heads across 22 pages) — DiscreteGraphicalTimeline
  • OpenScore: OpenScore String Quartet edition (4th movement) — ContinuousLogicalTimeline

All three go into one TimelineGroup. Cross-domain coordinate transfer (pixels ↔︎ quarters ↔︎ seconds) works automatically via linear interpolation.

6. CLT1: ABC v2.6 Score

ABC_DIR = DATA_DIR / "ABC"
abc_loader = Ms3Loader.from_file(
    ABC_DIR / "n04op18-4_04.notes.tsv",
    ABC_DIR / "n04op18-4_04.measures.tsv",
    ABC_DIR / "n04op18-4_04.harmonies.tsv",
)
clt1 = abc_loader.create_timeline(uid="clt1")
clt1
ContinuousLogicalTimeline[clt1] (3768 events, 3 children, 2 cmaps)
                      0 _______________________________ 878.5 quarters
  ├─ notes            0 ______________________________  872.5 (3156 events)
  ├─ measures         0 _______________________________ 878.5 (226 events)
  └─ annotations      0 _______________________________ 878.5 (386 events)

6.1 ABC Flow Control: Repeat Structure

The ABC score has repeats and volta brackets. The loader’s create_flow_controller() derives the repeat structure from the measure data and computes the default flow (all repeats taken). This is the same flow control machinery used later for CLT2 (the recordings edition) in Part III.

abc_controller = abc_loader.create_flow_controller()
abc_flow = abc_controller.compute_flow(FlowMode.default)
abc_flow
Flow(default): 226 folded → 291 unfolded (×1.29), 11 sections

   #  MCs          Sections     Reason
  ──  ───────────  ────────     ──────────────
   1 [1, 10)      A            start
   2 [1, 19)      A;B          repeat → 1
   3 [10, 28)     B;C          repeat → 10
   4 [19, 45)     C;D;D1       repeat → 19
   5 [28, 44)     D            repeat → 28
   6 [45, 85)     D2;E         skip → 45
   7 [78, 94)     E;F;F1       repeat → 78
   8 [85, 93)     F            repeat → 85
   9 [94, 103)    F2;G;G1      skip → 94
  10 [95, 102)    G            repeat → 95
  11 [103, 227)   G2           skip → 103

Sequence: A A B B C C D D1 D D2 E E F F1 F F2 G G1 G G2

The flow controller and flow will be used in §9.2 to unfold the entire score group at once — not just CLT1, but all timelines.

7. DGT1: OMR Ground Truth

The OMR data contains 3,190 note head bounding boxes across 22 score pages. Each page has 2 systems (except the last which has 1), giving 43 system segments in reading order. Note events use Left (start) and Width (duration) as pixel coordinates. Each system’s onset_beats values provide a c-map from pixels to quarters.

Architecture: SegmentLine[SegmentLine[DiscreteGraphicalTimeline]] → 22 page SegmentLine[DiscreteGraphicalTimeline] segments → 2 system sub-segments each.

OMR_CSV = DATA_DIR / "OMR_groundtruth" / "OMR_xml_by_score" / "omr_note_heads.csv"
OMR_IMAGES = DATA_DIR / "OMR_groundtruth" / "Images"
omr_df = pd.read_csv(OMR_CSV)
IMAGE_WIDTH = Image.open(next(OMR_IMAGES.glob("*.png"))).size[0]

Build the DGT1 bottom-up: system segments → page SegmentLine[DiscreteGraphicalTimeline] → top-level SegmentLine[SegmentLine[DiscreteGraphicalTimeline]]. Events and c-maps must be added before a timeline is locked as a child.

noteheads = pd.DataFrame(
    {
        "start": omr_df["Nodes.Node.Left"].astype(int),
        "end": (omr_df["Nodes.Node.Left"] + omr_df["Nodes.Node.Width"]).astype(int),
        "onset_beats": omr_df["onset_beats"].astype(float),
        "pitch": omr_df["pitch"],
        "staff_id": omr_df["staff_id"].astype(int),
        "midi_pitch": omr_df["midi_pitch_code"].astype(int),
        "top": omr_df["Nodes.Node.Top"].astype(int),
        "page": omr_df["@pageIndex"],
        "spacing_run_id": omr_df["spacing_run_id"],
    }
)

dgt1 = SegmentLine[SegmentLine[DiscreteGraphicalTimeline]](
    length=0,
    unit=TimeUnit.pixels,
    number_type=NumberType.int,
    uid="dgt1",
)

for page_idx, page_data in noteheads.groupby("page", sort=True):
    # Systems ordered by vertical position (top first = reading order)
    sys_top = page_data.groupby("spacing_run_id")["top"].min()
    sys_order = sys_top.sort_values().index

    page = SegmentLine[DiscreteGraphicalTimeline](
        length=0,
        unit=TimeUnit.pixels,
        number_type=NumberType.int,
    )

    for sys_rank, sys_id in enumerate(sys_order):
        sys_data = page_data[page_data["spacing_run_id"] == sys_id]

        system = DiscreteGraphicalTimeline(
            length=IMAGE_WIDTH,
            uid=f"p{page_idx}_s{sys_rank}",
            name=f"Page {page_idx + 1}, System {sys_rank + 1}",
        )

        events = sys_data.drop(columns=["page", "spacing_run_id"])
        system.add_events(events.assign(event_type="Notehead").to_dict("records"))

        # C-map: pixels → quarters (deduplicated for chords at the same x)
        pairs = (
            events[["start", "onset_beats"]]
            .drop_duplicates("start")
            .sort_values("start")
        )
        if len(pairs) >= 2:
            system.add_conversion_map(
                TableMap(
                    x_values=pairs["start"].tolist(),
                    y_values=pairs["onset_beats"].tolist(),
                    source_unit="pixels",
                    target_unit="quarters",
                    uid=f"p{page_idx}_s{sys_rank}_px_to_qb",
                )
            )

        page.append_segment(system)

    dgt1.append_segment(page, name=f"page_{page_idx}")

dgt1.diagram(depth=True)
SegmentLine[SegmentLine[DiscreteGraphicalTimeline]][dgt1] (22 children)
                          0 ∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶ 106425 pixels
  ├─ page_0               0 ∶                            4950
  │  ├─ Page 1, S...      0 ∶                            2475 (66 events)
  │  └─ Page 1, S...   2475 ∶                            4950 (68 events)
  ├─ page_1            4950  ∶                           9900
  │  ├─ Page 2, S...   4950  ∶                           7425 (76 events)
  │  └─ Page 2, S...   7425  ∶                           9900 (60 events)
  ├─ page_2            9900   ∶                          14850
  │  ├─ Page 3, S...   9900   ∶                          12375 (57 events)
  │  └─ Page 3, S...  12375    ∶                         14850 (71 events)
  │  ... (16 more children)
  ├─ page_19          94050                         ∶∶   99000
  │  ├─ Page 20, ...  94050                         ∶    96525 (81 events)
  │  └─ Page 20, ...  96525                          ∶   99000 (64 events)
  ├─ page_20          99000                           ∶  103950
  │  ├─ Page 21, ...  99000                           ∶  101475 (80 events)
  │  └─ Page 21, ... 101475                           ∶  103950 (53 events)
  └─ page_21         103950                            ∶ 106425
     └─ Page 22, ... 103950                            ∶ 106425 (4 events)

8. OpenScore (4th Movement Only)

The OpenScore edition covers all 4 movements. We use the flow controller to identify section breaks (movement boundaries) and extract the 4th movement as a child timeline.

OPENSCORE_DIR = DATA_DIR / "OpenScoreSQ"
os_loader = Ms3Loader.from_file(
    OPENSCORE_DIR / "sq8913219.notes.tsv",
    OPENSCORE_DIR / "sq8913219.measures.tsv",
)
os_full = os_loader.create_timeline(uid="openscore_full")
os_full
ContinuousLogicalTimeline[openscore_full] (12707 events, 2 children, 2 cmaps)
                      0 ________________________________ 2447 quarters
  ├─ notes            0 _______________________________  2441 (11898 events)
  └─ measures         0 ________________________________ 2447 (809 events)

The loader’s create_flow_controller() derives section boundaries from the score’s flow control markup. Splitting at those coordinates creates one region per movement.

os_flow_controller = os_loader.create_flow_controller()
boundaries = os_flow_controller.get_section_boundary_coordinates()
os_full.create_regions_from_boundaries(
    [0, *[float(b) for b in boundaries], float(os_full.length.value)], prefix="movement"
)
openscore = os_full.create_child_from_region("movement_4", uid="openscore")
openscore
ContinuousLogicalTimeline[openscore] (3382 events)
                      0 _______________________________ 878.5 quarters

The four movement regions and the extracted child timeline:

os_full.diagram(show={"regions", "children"}, depth=1)
ContinuousLogicalTimeline[openscore_full] (16089 events, 3 children, 4 regions, 2 cmaps)
                      0 ________________________________ 2447 quarters
  ├─ notes            0 _______________________________  2441 (11898 events)
  ├─ measures         0 ________________________________ 2447 (809 events)
  └─ movement_4   1568.5                     ____________ 2447 (3382 events)
  ┄ movement_1       0 ▐═════════▌                      880
  ┄ movement_2     880            ▐═══▌                 1271.5
  ┄ movement_3   1271.5                 ▐══▌             1568.5
  ┄ movement_4   1568.5                     ▐══════════▌ 2447

9. Score Group (Group 4)

All three score representations in one TimelineGroup. Cross-domain coordinate transfer (pixels ↔︎ quarters) works via linear interpolation.

score_group = TimelineGroup(
    id="score",
    name="Score (ABC + OMR + OpenScore)",
    timelines=[clt1, dgt1, openscore],
)
score_group
TimelineGroup[score] (3 timelines, 2 timestamps)
┌─────────────────────────────────────────────────────────────────────────┐
│ ContinuousLogicalTimeline[clt1] (3768 events, 3 children, 2 cmaps)      │
│                       0 ___________________________ 878.5 quarters      │
│   ├─ notes            0 __________________________  872.5 (3156 events) │
│   ├─ measures         0 ___________________________ 878.5 (226 events)  │
│   └─ annotations      0 ___________________________ 878.5 (386 events)  │
│                                                                         │
│ SegmentLine[SegmentLine[DiscreteGraphicalTimeline]][dgt1] (22 children) │
│                           0 ∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶ 106425 pixels      │
│   ├─ page_0               0 ∶                        4950               │
│   │  ├─ Page 1, S...      0 ∶                        2475 (66 events)   │
│   │  └─ Page 1, S...   2475 ∶                        4950 (68 events)   │
│   ├─ page_1            4950  ∶                       9900               │
│   │  ├─ Page 2, S...   4950  ∶                       7425 (76 events)   │
│   │  └─ Page 2, S...   7425  ∶                       9900 (60 events)   │
│   ├─ page_2            9900   ∶                      14850              │
│   │  ├─ Page 3, S...   9900   ∶                      12375 (57 events)  │
│   │  └─ Page 3, S...  12375   ∶                      14850 (71 events)  │
│   │  ... (16 more children)                                             │
│   ├─ page_19          94050                      ∶   99000              │
│   │  ├─ Page 20, ...  94050                      ∶   96525 (81 events)  │
│   │  └─ Page 20, ...  96525                      ∶   99000 (64 events)  │
│   ├─ page_20          99000                       ∶  103950             │
│   │  ├─ Page 21, ...  99000                       ∶  101475 (80 events) │
│   │  └─ Page 21, ... 101475                       ∶  103950 (53 events) │
│   └─ page_21         103950                        ∶ 106425             │
│      └─ Page 22, ... 103950                        ∶ 106425 (4 events)  │
│                                                                         │
│ ContinuousLogicalTimeline[openscore] (3382 events)                      │
│                       0 ___________________________ 878.5 quarters      │
└─────────────────────────────────────────────────────────────────────────┘
Timestamps: 2

9.1 Cross-Domain Section Boundaries (Quarters → Pixels → Pages)

The playthrough section boundaries (from §6.1) can now be mapped through the score group to DGT1 pixel coordinates. This demonstrates cross-domain coordinate transfer within a TimelineGroup: the InterpolationMap between CLT1 (quarters) and DGT1 (pixels) uses each system’s pixel-to-quarter TableMap as its C-map anchor.

# Build a page-boundary lookup from DGT1's segment structure
_page_bounds = []
for _seg_id in dgt1.list_segments():
    _off = dgt1.get_child_offset(_seg_id)
    _seg = dgt1.get_child(_seg_id)
    _page_bounds.append(
        (float(_off.value), float(_off.value) + float(_seg.length.value))
    )

_section_rows = []
for _sid, _qb in abc_controller.get_atomic_section_coordinates(flow=abc_flow).items():
    _ts = score_group.get_timestamp_at(float(_qb), "clt1")
    _px = (
        _ts.get_coordinate_for("dgt1", format="int")
        if "dgt1" in _ts.present_timelines
        else None
    )
    _page = next(
        (
            i + 1
            for i, (s, e) in enumerate(_page_bounds)
            if _px is not None and s <= _px < e
        ),
        "-",
    )
    _section_rows.append(
        {"section": _sid, "quarters": float(_qb), "dgt1_pixels": _px, "page": _page}
    )
section_boundary_table = pd.DataFrame(_section_rows).set_index("section")
section_boundary_table
quarters dgt1_pixels page
section
A 0.0 0 1
B 64.0 7753 2
C 128.0 15506 4
D 192.0 23260 5
D1 253.0 30649 7
D2 317.0 38403 8
E 448.5 54333 11
F 496.5 60148 13
F1 525.0 63601 13
F2 557.0 67477 14
G 561.0 67962 14
G1 589.0 71354 15
G2 621.0 75230 16

Each atomic section’s start coordinate is located precisely on a specific page of the OMR score image. The pixel column gives the linearised x-coordinate across all 22 pages; the page column tells which score image to open.

9.2 Unfolding the Entire Score Group

The score has repeats and volta brackets. Rather than unfolding each timeline individually, TimelineGroup.apply_flow() does it in one call: the flow controller’s section boundaries are resolved via the group’s interpolation maps, so every timeline — regardless of domain — is sliced and reassembled in playthrough order.

score_group_unfolded = score_group.apply_flow(
    abc_flow, abc_controller, reference_timeline_id="clt1"
)
score_group_unfolded
TimelineGroup[score_unfolded] (3 timelines, 2 timestamps)
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ ContinuousLogicalTimeline[clt1] (11 children, 11 regions)                           │
│                       0 ____________________________ 1116 quarters                  │
│   ├─ A                0 _                            32                             │
│   │  ├─ tl:24         0 _                            32 (116 events)                │
│   │  ├─ tl:25         0 _                            32 (9 events)                  │
│   │  └─ tl:26         0 _                            32 (14 events)                 │
│   ├─ A+B             32 __                           96                             │
│   │  ├─ tl:28        32 __                           96 (224 events)                │
│   │  ├─ tl:29        32 __                           96 (18 events)                 │
│   │  └─ tl:30        32 __                           96 (32 events)                 │
│   ├─ B+C             96   __                         160                            │
│   │  ├─ tl:32        96   __                         160 (176 events)               │
│   │  ├─ tl:33        96   __                         160 (18 events)                │
│   │  └─ tl:34        96   __                         160 (35 events)                │
│   │  ... (5 more children)                                                          │
│   ├─ F2+G+G1        557              _               593                            │
│   │  ├─ tl:56       557              _               593 (124 events)               │
│   │  ├─ tl:57       557              _               593 (9 events)                 │
│   │  └─ tl:58       557              _               593 (16 events)                │
│   ├─ G              593               _              621                            │
│   │  ├─ tl:60       593               _              621 (104 events)               │
│   │  ├─ tl:61       593               _              621 (7 events)                 │
│   │  └─ tl:62       593               _              621 (14 events)                │
│   └─ G2             621                _____________ 1116                           │
│      ├─ tl:64       621                ____________  1110 (1674 events)             │
│      ├─ tl:65       621                _____________ 1116 (124 events)              │
│      └─ tl:66       621                _____________ 1116 (196 events)              │
│                                                                                     │
│ SegmentLine[SegmentLine[DiscreteGraphicalTimeline]][dgt1] (11 children, 11 regions) │
│                        0 ∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶ 135198 pixels                  │
│   ├─ A                 0 ∶                           3877                           │
│   │  └─ tl:68          0 ∶                           3877                           │
│   │     ├─ tl:69       0 ∶                           2475 (66 events)               │
│   │     └─ tl:70    2475 ∶                           3877 (33 events)               │
│   ├─ A+B            3877 ∶∶                          11630                          │
│   │  ├─ tl:72       3877 ∶                           8827                           │
│   │  │  ├─ tl:73    3877 ∶                           6352 (66 events)               │
│   │  │  └─ tl:74    6352  ∶                          8827 (68 events)               │
│   │  └─ tl:75       8827  ∶                          11630                          │
│   │     ├─ tl:76    8827  ∶                          11302 (76 events)              │
│   │     └─ tl:77   11302   ∶                         11630                          │
│   ├─ B+C           11630   ∶                         19383                          │
│   │  ├─ tl:79      11630   ∶                         12703                          │
│   │  │  └─ tl:80   11630   ∶                         12703 (35 events)              │
│   │  ├─ tl:81      12703   ∶                         17653                          │
│   │  │  ├─ tl:82   12703   ∶                         15178 (76 events)              │
│   │  │  └─ tl:83   15178    ∶                        17653 (60 events)              │
│   │  └─ tl:84      17653    ∶                        19383                          │
│   │     └─ tl:85   17653    ∶                        19383 (42 events)              │
│   │  ... (5 more children)                                                          │
│   ├─ F2+G+G1       67479              ∶              71840                          │
│   │  ├─ tl:130     67479              ∶              69931                          │
│   │  │  └─ tl:131  67479              ∶              69931 (66 events)              │
│   │  └─ tl:132     69931              ∶              71840                          │
│   │     └─ tl:133  69931              ∶              71840 (59 events)              │
│   ├─ G             71840               ∶             75232                          │
│   │  ├─ tl:135     71840               ∶             73808                          │
│   │  │  └─ tl:136  71840               ∶             73808 (66 events)              │
│   │  └─ tl:137     73808               ∶             75232                          │
│   │     └─ tl:138  73808               ∶             75232 (33 events)              │
│   └─ G2            75232                ∶∶∶∶∶∶∶∶∶∶∶∶ 135198                         │
│      ├─ tl:140     75232                ∶            78273                          │
│      │  ├─ tl:141  75232                ∶            75798 (11 events)              │
│      │  └─ tl:142  75798                ∶            78273 (74 events)              │
│      ├─ tl:143     78273                ∶            83223                          │
│      │  ├─ tl:144  78273                ∶            80748 (79 events)              │
│      │  └─ tl:145  80748                 ∶           83223 (94 events)              │
│      ├─ tl:146     83223                 ∶           88173                          │
│      │  ├─ tl:147  83223                 ∶           85698 (73 events)              │
│      │  └─ tl:148  85698                  ∶          88173 (74 events)              │
│      │  ... (7 more children)                                                       │
│      ├─ tl:170    122823                         ∶   127773                         │
│      │  ├─ tl:171 122823                         ∶   125298 (81 events)             │
│      │  └─ tl:172 125298                          ∶  127773 (64 events)             │
│      ├─ tl:173    127773                          ∶  132723                         │
│      │  ├─ tl:174 127773                          ∶  130248 (80 events)             │
│      │  └─ tl:175 130248                           ∶ 132723 (53 events)             │
│      └─ tl:176    132723                           ∶ 135198                         │
│         └─ tl:177 132723                           ∶ 135198 (4 events)              │
│                                                                                     │
│ ContinuousLogicalTimeline[openscore] (4159 events, 11 children, 11 regions)         │
│                       0 ____________________________ 1116 quarters                  │
│   ├─ A                0 _                            32 (125 events)                │
│   ├─ A+B             32 __                           96 (242 events)                │
│   ├─ B+C             96   __                         160 (194 events)               │
│   │  ... (5 more children)                                                          │
│   ├─ F2+G+G1        557              _               593 (133 events)               │
│   ├─ G              593               _              621 (111 events)               │
│   └─ G2             621                _____________ 1116 (1799 events)             │
└─────────────────────────────────────────────────────────────────────────────────────┘
Timestamps: 2

The unfolded CLT1 carries all note events in playthrough order. Extract them for note matching:

clt1_unfolded = score_group_unfolded.get_timeline("clt1")
abc_notes_df = clt1_unfolded.get_events(
    event_type="Note", include_children=True
).to_dataframe()

# Cast types restored from string (EventData stores extra columns as strings)
abc_notes_df["staff"] = pd.to_numeric(abc_notes_df["staff"], errors="coerce").astype(
    "Int64"
)
abc_notes_df["tied"] = pd.to_numeric(abc_notes_df["tied"], errors="coerce")
abc_notes_df.loc[abc_notes_df["tied"] == 0, "tied"] = np.nan
abc_notes_df["quarterbeats_playthrough"] = abc_notes_df["start"]

abc_prepared = prepare_abc_notes_for_matching(abc_notes_df)
len(abc_prepared)  # note onsets after dropping tied notes
3763

10. Aligning Recordings with the Score via Note Matching

Each EEP recording’s note events (seconds, pitch, staff) are matched against the ABC unfolded score notes (quarterbeats, pitch, staff) prepared in §9.2 using greedy sequential matching. The result: MatchClaim objects that connect recording coordinates to score coordinates. No pre-computed TSV is needed — the unfolded CLT1 carries all the notes.

Match each recording against the score. The source_timeline_id and target_timeline_id are the audio DPT and CLT1 respectively — these appear in the resulting MatchClaim anchors.

We use rv.store.notes to access the EEP notes from the XML manifest’s score section — no direct EepNotesLoader import needed.

match_results = {}
for xml_path, dpt_id in [
    (NORMAL_XML, "dpt1"),
    (MECHANICAL_XML, "dpt6"),
    (EXAGGERATED_XML, "dpt11"),
]:
    rv = RepoVizzLoader.from_file(xml_path)
    eep_events = rv.store.notes.to_dataframe()
    eep_prepared = prepare_eep_notes_for_matching(eep_events)
    match_results[dpt_id] = match_notes_by_attributes(
        eep_prepared,
        abc_prepared,
        match_columns=["pitch", "staff"],
        source_coord_column="start",
        target_coord_column="quarterbeats_playthrough",
        source_timeline_id=dpt_id,
        target_timeline_id="clt1",
        source_unit=TimeUnit.seconds,
        target_unit=TimeUnit.quarters,
    )

normal_match = match_results["dpt1"]
mechanical_match = match_results["dpt6"]
exaggerated_match = match_results["dpt11"]
{
    "Normal": normal_match.summary(),
    "Mechanical": mechanical_match.summary(),
    "Exaggerated": exaggerated_match.summary(),
}
{'Normal': {'matched': 3740,
  'unmatched_source': 16,
  'unmatched_target': 23,
  'match_claims': 3740},
 'Mechanical': {'matched': 3743,
  'unmatched_source': 13,
  'unmatched_target': 20,
  'match_claims': 3743},
 'Exaggerated': {'matched': 2650,
  'unmatched_source': 4,
  'unmatched_target': 1113,
  'match_claims': 2650}}

Part II Summary

The score group unites 3 score representations across 2 domains (Logical + Graphical). Note matching produced MatchClaims connecting each recording group’s audio timeline to CLT1:

Recording Matched Unmatched EEP Unmatched ABC
Normal 3,740 16 23
Mechanical 3,743 13 20
Exaggerated 2,650 4 1,113

Next: Part III adds the Emerson group and demonstrates cross-group coordinate transfer using an AlignmentBundle.


Part III: Emerson Recording + Cascading Alignment (Group 5)

The Emerson group connects a commercial recording to a second score edition via segment-level alignment. Unlike the EEP groups (per-note alignment), the Emerson recording is aligned at the level of 10 structural sections (alpha through kappa), derived from the score’s repeat structure.

The central payoff of this notebook is cascading alignment: by adding the recordings edition’s unfolded score (CLT2) to the same group as CLT1, coordinate transfer chains automatically from the EEP recordings through both score editions to the Emerson recording.

  • CLT2: ABC v1.0 (“recordings edition”) score — ContinuousLogicalTimeline
  • DPT16: Emerson String Quartet recording (DG 1997) — ContinuousPhysicalTimeline

11. Building the Emerson Recording Components

11.1 CLT2: Recordings Edition Score

The recordings edition uses the same measure/repeat structure as CLT1 but was encoded independently (ABC v1.0). We load it via Ms3Loader and use its flow controller to compute the traversal map.

REC_DIR = DATA_DIR / "recordings"
rec_loader = Ms3Loader.from_file(
    REC_DIR / "Beethoven_Op018No4-04.notes.tsv",
    REC_DIR / "Beethoven_Op018No4-04.measures.tsv",
    REC_DIR / "Beethoven_Op018No4-04.harmonies.tsv",
)
clt2 = rec_loader.create_timeline(uid="clt2")
clt2
ContinuousLogicalTimeline[clt2] (3765 events, 3 children, 2 cmaps)
                      0 _________________________________ 876 quarters
  ├─ notes            0 ________________________________  870 (3153 events)
  ├─ measures         0 _________________________________ 876 (226 events)
  └─ annotations      0 _________________________________ 876 (386 events)

11.2 Flow Control: Inspect the Score’s Repeat Structure

The loader’s create_flow_controller() identifies atomic sections and flow control events (repeats, voltas) from the measure data.

rec_controller = rec_loader.create_flow_controller()
rec_controller
ScoreFlowController (226 MCs, 13 atomic sections, 18 flow events)
    ├─A──┤├─B──┤├─C──┤├─D──┤├─E──┤┌1─E1─┌2─E2─├─F──┤├─G──┤├─H──┤├─I──┤┌1─I1─┌2─I2─
     1-9  10-18 19-27   28  29-43   44  45-77 78-84 85-93   94  95-10  102  103-2
    ║:  :║║:  :║║:  :║      ║:        :║      ║:  :║║:  :║      ║:        :║

Flow control:
  MC   1: repeat_start (section A)
  MC   9: repeat_end → MC 1
  MC  10: repeat_start (section B)
  MC  18: repeat_end → MC 10
  MC  19: repeat_start (section C)
  MC  27: repeat_end → MC 19
  MC  29: repeat_start (section E)
  MC  44: repeat_end → MC 29; volta 1 (section E1)
  MC  45: volta 2 (section E2)
  MC  78: repeat_start (section F)
  MC  84: repeat_end → MC 78
  MC  85: repeat_start (section G)
  MC  93: repeat_end → MC 85
  MC  95: repeat_start (section I)
  MC 102: repeat_end → MC 95; volta 1 (section I1)
  MC 103: volta 2 (section I2)

Section transitions:
  A → [A, B]    B → [B, C]    C → [C, D]    D → [E]
  E → [E1, E2]    E1 → [E]    E2 → [F]    F → [F, G]
  G → [G, H]    H → [I]    I → [I1, I2]    I1 → [I]
  I2 → []

Atomic flow (default):
  A → A → B → B → C → C → D → E → E1 → E → E2 → F → F → G → G → H → I → I1 → I → I2

Compute the default flow (all repeats taken) and a single-pass flow (no repeats, last volta only) for comparison:

default_flow = rec_controller.compute_flow(FlowMode.default)
default_flow
Flow(default): 226 folded → 291 unfolded (×1.29), 10 sections

   #  MCs          Sections     Reason
  ──  ───────────  ────────     ──────────────
   1 [1, 10)      A            start
   2 [1, 19)      A;B          repeat → 1
   3 [10, 28)     B;C          repeat → 10
   4 [19, 45)     C;D;E;E1     repeat → 19
   5 [29, 44)     E            repeat → 29
   6 [45, 85)     E2;F         skip → 45
   7 [78, 94)     F;G          repeat → 78
   8 [85, 103)    G;H;I;I1     repeat → 85
   9 [95, 102)    I            repeat → 95
  10 [103, 227)   I2           skip → 103

Sequence: A A B B C C D E E1 E E2 F F G G H I I1 I I2
single_flow = rec_controller.compute_flow(FlowMode.single)
single_flow
Flow(single): 226 folded → 224 unfolded (×0.99), 3 sections

   #  MCs          Sections     Reason
  ──  ───────────  ────────     ──────────────
   1 [1, 44)      A;B;C;D;E    start
   2 [45, 102)    E2;F;G;H;I   skip → 45
   3 [103, 227)   I2           skip → 103

Sequence: A B C D E E2 F G H I I2

11.3 Unfolding CLT2

The recordings edition has the same repeat structure as CLT1. We unfold it via the standalone create_unfolded_timeline() function, passing the default flow (all repeats taken). The result is a flat timeline with all sections concatenated in playthrough order — coordinates in quarter-beats, suitable for matching against the Emerson CSV’s unfolded floating-measure boundaries.

clt2_unfolded = create_unfolded_timeline(
    clt2, default_flow, flow_controller=rec_controller, uid="clt2_unfolded"
)
clt2_unfolded
ContinuousLogicalTimeline[clt2_unfolded] (10 children, 10 regions)
                      0 ________________________________ 1116 quarters
  ├─ A                0 _                                32
  │  ├─ tl:190        0 _                                32 (116 events)
  │  ├─ tl:191        0 _                                32 (9 events)
  │  └─ tl:192        0 _                                32 (14 events)
  ├─ A+B             32 __                               96
  │  ├─ tl:194       32 __                               96 (224 events)
  │  ├─ tl:195       32 __                               96 (18 events)
  │  └─ tl:196       32 __                               96 (32 events)
  ├─ B+C             96   __                             160
  │  ├─ tl:198       96   __                             160 (176 events)
  │  ├─ tl:199       96   __                             160 (18 events)
  │  └─ tl:200       96   __                             160 (35 events)
  │  ... (4 more children)
  ├─ G+H+I+I1       528                __                593
  │  ├─ tl:218      528                __                593 (225 events)
  │  ├─ tl:219      528                __                593 (18 events)
  │  └─ tl:220      528                __                593 (34 events)
  ├─ I              593                  _               621
  │  ├─ tl:222      593                  _               621 (104 events)
  │  ├─ tl:223      593                  _               621 (7 events)
  │  └─ tl:224      593                  _               621 (14 events)
  └─ I2             621                  _______________ 1116
     ├─ tl:226      621                  ______________  1110 (1679 events)
     ├─ tl:227      621                  _______________ 1116 (124 events)
     └─ tl:228      621                  _______________ 1116 (196 events)

11.4 DPT16: Emerson Recording

The measureMapAudio.csv provides a 10-segment alignment between the unfolded score (floating measures) and the Emerson recording (seconds). Each segment is labelled with a Greek letter (alpha through kappa).

ema_df = pd.read_csv(
    REC_DIR / "Beethoven_Op018No4-04_EmersonStringQuartet_DG_measureMapAudio.csv",
    sep="\t",
    index_col=0,
)
ema_df
measure_score_start measure_score_end measure_unfold_start measure_unfold_end seconds_start seconds_end
α 0.75 8.750 0.75 8.750 0.567007 7.381043
β 0.75 16.750 8.75 24.750 7.381043 21.823855
γ 8.75 24.750 24.75 40.750 21.823855 37.495283
δ 16.75 40.999 40.75 64.999 37.495283 59.309161
ε 25.00 39.999 65.00 79.999 59.309161 72.699388
ζ 41.00 79.750 80.00 118.750 72.699388 106.863129
η 73.75 87.750 118.75 132.750 106.863129 118.964393
θ 79.75 95.999 132.75 148.999 118.964393 132.918685
ι 88.00 94.999 149.00 155.999 132.918685 138.739229
κ 96.00 218.250 156.00 278.250 138.739229 241.823152

Create DPT16 as a ContinuousPhysicalTimeline in seconds. Unlike the EEP recordings (per-note alignment), the Emerson alignment operates at the level of section boundaries — the coordinates in ema_df will become MatchClaims in §11.5 rather than a C-map.

dpt16_duration = float(ema_df["seconds_end"].iloc[-1])
dpt16 = ContinuousPhysicalTimeline(length=dpt16_duration, uid="dpt16")
dpt16
ContinuousPhysicalTimeline[dpt16]
                      0 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 241.8 seconds

11.5 Emerson MatchClaims (alpha through kappa)

Each row in the measure-map CSV defines a section boundary: a correspondence between an unfolded floating-measure coordinate on CLT2 and a seconds coordinate on DPT16. We create one MatchClaim per boundary, plus the final end boundary.

These cross-group claims are the key connection between the Emerson recording and the score group. AlignmentAnchor stores unit-bearing Coordinate values, so the units are explicit at the claim boundary even though the source data is numeric.

emerson_claims = []
for _, row in ema_df.iterrows():
    anchor = AlignmentAnchor(
        timeline_a_id="clt2_unfolded",
        coordinate_a=Coordinate(float(row["measure_unfold_start"]), TimeUnit.quarters),
        timeline_b_id="dpt16",
        coordinate_b=Coordinate(float(row["seconds_start"]), TimeUnit.seconds),
    )
    emerson_claims.append(
        MatchClaim(
            timeline_a_id="clt2_unfolded",
            timeline_b_id="dpt16",
            start_anchor=anchor,
            metadata=MatchMetadata(
                agent=Agent(
                    name="dataset",
                    type=AgentType.software,
                    identifier="measure_map_audio",
                ),
            ),
        )
    )

# Final end boundary
final_anchor = AlignmentAnchor(
    timeline_a_id="clt2_unfolded",
    coordinate_a=Coordinate(
        float(ema_df["measure_unfold_end"].iloc[-1]), TimeUnit.quarters
    ),
    timeline_b_id="dpt16",
    coordinate_b=Coordinate(float(ema_df["seconds_end"].iloc[-1]), TimeUnit.seconds),
)
emerson_claims.append(
    MatchClaim(
        timeline_a_id="clt2_unfolded",
        timeline_b_id="dpt16",
        start_anchor=final_anchor,
        metadata=MatchMetadata(
            agent=Agent(
                name="dataset",
                type=AgentType.software,
                identifier="measure_map_audio",
            ),
        ),
    )
)

len(emerson_claims)
11

12. Bridging the Two AlignmentBundles

12.1 The Key Move: Adding CLT2_unfolded to the Unfolded Score Group

The Unfolded Score Group and the Emerson Group are currently independent: neither shares a timeline with the other, and no MatchClaims connect them. The Emerson MatchClaims (§11.5) link CLT2_unfolded to DPT16 — but CLT2_unfolded is not yet in any group that the bundle’s existing WarpMaps can reach.

The insight: CLT1_unfolded and CLT2_unfolded encode the same music from different editions. By adding CLT2_unfolded to the Unfolded Score Group, any coordinate on CLT1_unfolded can be transferred to CLT2_unfolded via within-group interpolation, and from there to DPT16 via the Emerson MatchLine’s WarpMap. The cascading path:

DPT1 -> (WarpMap) -> CLT1 -> (interpolation) -> CLT2_unfolded -> (WarpMap) -> DPT16

A single additional group membership retroactively enriches every timeline in both groups.

clt1_unfolded = score_group_unfolded.get_timeline("clt1")
score_group_unfolded.add_timeline(
    clt2_unfolded,
    start=IdCoordinate(0.0, TimeUnit.quarters, "clt1"),
    end=IdCoordinate(float(clt1_unfolded.length.value), TimeUnit.quarters, "clt1"),
)
score_group_unfolded
TimelineGroup[score_unfolded] (4 timelines, 2 timestamps)
┌─────────────────────────────────────────────────────────────────────────────────────┐
│ ContinuousLogicalTimeline[clt1] (11 children, 11 regions)                           │
│                       0 ____________________________ 1116 quarters                  │
│   ├─ A                0 _                            32                             │
│   │  ├─ tl:24         0 _                            32 (116 events)                │
│   │  ├─ tl:25         0 _                            32 (9 events)                  │
│   │  └─ tl:26         0 _                            32 (14 events)                 │
│   ├─ A+B             32 __                           96                             │
│   │  ├─ tl:28        32 __                           96 (224 events)                │
│   │  ├─ tl:29        32 __                           96 (18 events)                 │
│   │  └─ tl:30        32 __                           96 (32 events)                 │
│   ├─ B+C             96   __                         160                            │
│   │  ├─ tl:32        96   __                         160 (176 events)               │
│   │  ├─ tl:33        96   __                         160 (18 events)                │
│   │  └─ tl:34        96   __                         160 (35 events)                │
│   │  ... (5 more children)                                                          │
│   ├─ F2+G+G1        557              _               593                            │
│   │  ├─ tl:56       557              _               593 (124 events)               │
│   │  ├─ tl:57       557              _               593 (9 events)                 │
│   │  └─ tl:58       557              _               593 (16 events)                │
│   ├─ G              593               _              621                            │
│   │  ├─ tl:60       593               _              621 (104 events)               │
│   │  ├─ tl:61       593               _              621 (7 events)                 │
│   │  └─ tl:62       593               _              621 (14 events)                │
│   └─ G2             621                _____________ 1116                           │
│      ├─ tl:64       621                ____________  1110 (1674 events)             │
│      ├─ tl:65       621                _____________ 1116 (124 events)              │
│      └─ tl:66       621                _____________ 1116 (196 events)              │
│                                                                                     │
│ SegmentLine[SegmentLine[DiscreteGraphicalTimeline]][dgt1] (11 children, 11 regions) │
│                        0 ∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶ 135198 pixels                  │
│   ├─ A                 0 ∶                           3877                           │
│   │  └─ tl:68          0 ∶                           3877                           │
│   │     ├─ tl:69       0 ∶                           2475 (66 events)               │
│   │     └─ tl:70    2475 ∶                           3877 (33 events)               │
│   ├─ A+B            3877 ∶∶                          11630                          │
│   │  ├─ tl:72       3877 ∶                           8827                           │
│   │  │  ├─ tl:73    3877 ∶                           6352 (66 events)               │
│   │  │  └─ tl:74    6352  ∶                          8827 (68 events)               │
│   │  └─ tl:75       8827  ∶                          11630                          │
│   │     ├─ tl:76    8827  ∶                          11302 (76 events)              │
│   │     └─ tl:77   11302   ∶                         11630                          │
│   ├─ B+C           11630   ∶                         19383                          │
│   │  ├─ tl:79      11630   ∶                         12703                          │
│   │  │  └─ tl:80   11630   ∶                         12703 (35 events)              │
│   │  ├─ tl:81      12703   ∶                         17653                          │
│   │  │  ├─ tl:82   12703   ∶                         15178 (76 events)              │
│   │  │  └─ tl:83   15178    ∶                        17653 (60 events)              │
│   │  └─ tl:84      17653    ∶                        19383                          │
│   │     └─ tl:85   17653    ∶                        19383 (42 events)              │
│   │  ... (5 more children)                                                          │
│   ├─ F2+G+G1       67479              ∶              71840                          │
│   │  ├─ tl:130     67479              ∶              69931                          │
│   │  │  └─ tl:131  67479              ∶              69931 (66 events)              │
│   │  └─ tl:132     69931              ∶              71840                          │
│   │     └─ tl:133  69931              ∶              71840 (59 events)              │
│   ├─ G             71840               ∶             75232                          │
│   │  ├─ tl:135     71840               ∶             73808                          │
│   │  │  └─ tl:136  71840               ∶             73808 (66 events)              │
│   │  └─ tl:137     73808               ∶             75232                          │
│   │     └─ tl:138  73808               ∶             75232 (33 events)              │
│   └─ G2            75232                ∶∶∶∶∶∶∶∶∶∶∶∶ 135198                         │
│      ├─ tl:140     75232                ∶            78273                          │
│      │  ├─ tl:141  75232                ∶            75798 (11 events)              │
│      │  └─ tl:142  75798                ∶            78273 (74 events)              │
│      ├─ tl:143     78273                ∶            83223                          │
│      │  ├─ tl:144  78273                ∶            80748 (79 events)              │
│      │  └─ tl:145  80748                 ∶           83223 (94 events)              │
│      ├─ tl:146     83223                 ∶           88173                          │
│      │  ├─ tl:147  83223                 ∶           85698 (73 events)              │
│      │  └─ tl:148  85698                  ∶          88173 (74 events)              │
│      │  ... (7 more children)                                                       │
│      ├─ tl:170    122823                         ∶   127773                         │
│      │  ├─ tl:171 122823                         ∶   125298 (81 events)             │
│      │  └─ tl:172 125298                          ∶  127773 (64 events)             │
│      ├─ tl:173    127773                          ∶  132723                         │
│      │  ├─ tl:174 127773                          ∶  130248 (80 events)             │
│      │  └─ tl:175 130248                           ∶ 132723 (53 events)             │
│      └─ tl:176    132723                           ∶ 135198                         │
│         └─ tl:177 132723                           ∶ 135198 (4 events)              │
│                                                                                     │
│ ContinuousLogicalTimeline[openscore] (4159 events, 11 children, 11 regions)         │
│                       0 ____________________________ 1116 quarters                  │
│   ├─ A                0 _                            32 (125 events)                │
│   ├─ A+B             32 __                           96 (242 events)                │
│   ├─ B+C             96   __                         160 (194 events)               │
│   │  ... (5 more children)                                                          │
│   ├─ F2+G+G1        557              _               593 (133 events)               │
│   ├─ G              593               _              621 (111 events)               │
│   └─ G2             621                _____________ 1116 (1799 events)             │
│                                                                                     │
│ ContinuousLogicalTimeline[clt2_unfolded] (10 children, 10 regions)                  │
│                       0 ____________________________ 1116 quarters                  │
│   ├─ A                0 _                            32                             │
│   │  ├─ tl:190        0 _                            32 (116 events)                │
│   │  ├─ tl:191        0 _                            32 (9 events)                  │
│   │  └─ tl:192        0 _                            32 (14 events)                 │
│   ├─ A+B             32 __                           96                             │
│   │  ├─ tl:194       32 __                           96 (224 events)                │
│   │  ├─ tl:195       32 __                           96 (18 events)                 │
│   │  └─ tl:196       32 __                           96 (32 events)                 │
│   ├─ B+C             96   __                         160                            │
│   │  ├─ tl:198       96   __                         160 (176 events)               │
│   │  ├─ tl:199       96   __                         160 (18 events)                │
│   │  └─ tl:200       96   __                         160 (35 events)                │
│   │  ... (4 more children)                                                          │
│   ├─ G+H+I+I1       528              _               593                            │
│   │  ├─ tl:218      528              _               593 (225 events)               │
│   │  ├─ tl:219      528              _               593 (18 events)                │
│   │  └─ tl:220      528              _               593 (34 events)                │
│   ├─ I              593               _              621                            │
│   │  ├─ tl:222      593               _              621 (104 events)               │
│   │  ├─ tl:223      593               _              621 (7 events)                 │
│   │  └─ tl:224      593               _              621 (14 events)                │
│   └─ I2             621                _____________ 1116                           │
│      ├─ tl:226      621                ____________  1110 (1679 events)             │
│      ├─ tl:227      621                _____________ 1116 (124 events)              │
│      └─ tl:228      621                _____________ 1116 (196 events)              │
└─────────────────────────────────────────────────────────────────────────────────────┘
Timestamps: 2

CLT2_unfolded now appears alongside CLT1, DGT1, and OpenScore in the unfolded score group. The group’s interpolation maps link all four timelines pairwise, bridging quarter-beats and floating measures.

12.2 The Emerson Group

The Emerson group contains only DPT16 — the recording timeline. CLT2_unfolded lives in the score group, and the Emerson MatchClaims connect the two groups via cross-group claims.

emerson_group = TimelineGroup(
    id="emerson",
    name="Emerson Recording (DG 1997)",
    timelines=[dpt16],
)
emerson_group
TimelineGroup[emerson] (1 timelines, 2 timestamps)
┌────────────────────────────────────────────────────────────────────┐
│ ContinuousPhysicalTimeline[dpt16]                                  │
│                       0 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 241.8 seconds │
└────────────────────────────────────────────────────────────────────┘
Timestamps: 2

13. The AlignmentBundle

The bundle collects all 5 groups and connects them via MatchClaims. Within each group, coordinate transfer uses linear interpolation. Between groups, WarpMaps (built from MatchClaims) enable cross-domain transfer.

bundle = AlignmentBundle(name="Beethoven Op.18/4 — Multimodal Alignment")

bundle.add_group(score_group_unfolded)
bundle.add_group(normal_group)
bundle.add_group(mechanical_group)
bundle.add_group(exaggerated_group)
bundle.add_group(emerson_group)

# Add EEP recording <-> CLT1 match claims
for dpt_id in ["dpt1", "dpt6", "dpt11"]:
    bundle.add_match_claims(match_results[dpt_id].match_claims)

# Add Emerson section boundary claims (CLT2_unfolded <-> DPT16)
bundle.add_match_claims(emerson_claims)

bundle
AlignmentBundle[bundle:AlignmentBundle_1]

  TimelineGroup[score_unfolded] (4 timelines, 2 timestamps)
  ┌─────────────────────────────────────────────────────────────────────────────────────┐
  │ ContinuousLogicalTimeline[clt1] (11 children, 11 regions)                           │
  │                       0 ____________________________________ 1116 quarters          │
  │   ├─ A                0 _                                    32                     │
  │   │  ├─ tl:24         0 _                                    32 (116 events)        │
  │   │  ├─ tl:25         0 _                                    32 (9 events)          │
  │   │  └─ tl:26         0 _                                    32 (14 events)         │
  │   ├─ A+B             32  __                                  96                     │
  │   │  ├─ tl:28        32  __                                  96 (224 events)        │
  │   │  ├─ tl:29        32  __                                  96 (18 events)         │
  │   │  └─ tl:30        32  __                                  96 (32 events)         │
  │   ├─ B+C             96    __                                160                    │
  │   │  ├─ tl:32        96    __                                160 (176 events)       │
  │   │  ├─ tl:33        96    __                                160 (18 events)        │
  │   │  └─ tl:34        96    __                                160 (35 events)        │
  │   │  ... (5 more children)                                                          │
  │   ├─ F2+G+G1        557                  __                  593                    │
  │   │  ├─ tl:56       557                  __                  593 (124 events)       │
  │   │  ├─ tl:57       557                  __                  593 (9 events)         │
  │   │  └─ tl:58       557                  __                  593 (16 events)        │
  │   ├─ G              593                    _                 621                    │
  │   │  ├─ tl:60       593                    _                 621 (104 events)       │
  │   │  ├─ tl:61       593                    _                 621 (7 events)         │
  │   │  └─ tl:62       593                    _                 621 (14 events)        │
  │   └─ G2             621                     ________________ 1116                   │
  │      ├─ tl:64       621                     _______________  1110 (1674 events)     │
  │      ├─ tl:65       621                     ________________ 1116 (124 events)      │
  │      └─ tl:66       621                     ________________ 1116 (196 events)      │
  │                                                                                     │
  │ SegmentLine[SegmentLine[DiscreteGraphicalTimeline]][dgt1] (11 children, 11 regions) │
  │                        0 ∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶ 135198 pixels          │
  │   ├─ A                 0 ∶                                   3877                   │
  │   │  └─ tl:68          0 ∶                                   3877                   │
  │   │     ├─ tl:69       0 ∶                                   2475 (66 events)       │
  │   │     └─ tl:70    2475 ∶                                   3877 (33 events)       │
  │   ├─ A+B            3877  ∶∶                                 11630                  │
  │   │  ├─ tl:72       3877  ∶                                  8827                   │
  │   │  │  ├─ tl:73    3877  ∶                                  6352 (66 events)       │
  │   │  │  └─ tl:74    6352  ∶                                  8827 (68 events)       │
  │   │  └─ tl:75       8827   ∶                                 11630                  │
  │   │     ├─ tl:76    8827   ∶                                 11302 (76 events)      │
  │   │     └─ tl:77   11302   ∶                                 11630                  │
  │   ├─ B+C           11630    ∶∶                               19383                  │
  │   │  ├─ tl:79      11630    ∶                                12703                  │
  │   │  │  └─ tl:80   11630    ∶                                12703 (35 events)      │
  │   │  ├─ tl:81      12703    ∶                                17653                  │
  │   │  │  ├─ tl:82   12703    ∶                                15178 (76 events)      │
  │   │  │  └─ tl:83   15178    ∶                                17653 (60 events)      │
  │   │  └─ tl:84      17653     ∶                               19383                  │
  │   │     └─ tl:85   17653     ∶                               19383 (42 events)      │
  │   │  ... (5 more children)                                                          │
  │   ├─ F2+G+G1       67479                  ∶                  71840                  │
  │   │  ├─ tl:130     67479                  ∶                  69931                  │
  │   │  │  └─ tl:131  67479                  ∶                  69931 (66 events)      │
  │   │  └─ tl:132     69931                   ∶                 71840                  │
  │   │     └─ tl:133  69931                   ∶                 71840 (59 events)      │
  │   ├─ G             71840                   ∶                 75232                  │
  │   │  ├─ tl:135     71840                   ∶                 73808                  │
  │   │  │  └─ tl:136  71840                   ∶                 73808 (66 events)      │
  │   │  └─ tl:137     73808                    ∶                75232                  │
  │   │     └─ tl:138  73808                    ∶                75232 (33 events)      │
  │   └─ G2            75232                    ∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶∶ 135198                 │
  │      ├─ tl:140     75232                    ∶                78273                  │
  │      │  ├─ tl:141  75232                    ∶                75798 (11 events)      │
  │      │  └─ tl:142  75798                    ∶                78273 (74 events)      │
  │      ├─ tl:143     78273                     ∶               83223                  │
  │      │  ├─ tl:144  78273                     ∶               80748 (79 events)      │
  │      │  └─ tl:145  80748                     ∶               83223 (94 events)      │
  │      ├─ tl:146     83223                      ∶              88173                  │
  │      │  ├─ tl:147  83223                      ∶              85698 (73 events)      │
  │      │  └─ tl:148  85698                       ∶             88173 (74 events)      │
  │      │  ... (7 more children)                                                       │
  │      ├─ tl:170    122823                                ∶∶   127773                 │
  │      │  ├─ tl:171 122823                                ∶    125298 (81 events)     │
  │      │  └─ tl:172 125298                                 ∶   127773 (64 events)     │
  │      ├─ tl:173    127773                                  ∶  132723                 │
  │      │  ├─ tl:174 127773                                  ∶  130248 (80 events)     │
  │      │  └─ tl:175 130248                                  ∶  132723 (53 events)     │
  │      └─ tl:176    132723                                   ∶ 135198                 │
  │         └─ tl:177 132723                                   ∶ 135198 (4 events)      │
  │                                                                                     │
  │ ContinuousLogicalTimeline[openscore] (4159 events, 11 children, 11 regions)         │
  │                       0 ____________________________________ 1116 quarters          │
  │   ├─ A                0 _                                    32 (125 events)        │
  │   ├─ A+B             32  __                                  96 (242 events)        │
  │   ├─ B+C             96    __                                160 (194 events)       │
  │   │  ... (5 more children)                                                          │
  │   ├─ F2+G+G1        557                  __                  593 (133 events)       │
  │   ├─ G              593                    _                 621 (111 events)       │
  │   └─ G2             621                     ________________ 1116 (1799 events)     │
  │                                                                                     │
  │ ContinuousLogicalTimeline[clt2_unfolded] (10 children, 10 regions)                  │
  │                       0 ____________________________________ 1116 quarters          │
  │   ├─ A                0 _                                    32                     │
  │   │  ├─ tl:190        0 _                                    32 (116 events)        │
  │   │  ├─ tl:191        0 _                                    32 (9 events)          │
  │   │  └─ tl:192        0 _                                    32 (14 events)         │
  │   ├─ A+B             32  __                                  96                     │
  │   │  ├─ tl:194       32  __                                  96 (224 events)        │
  │   │  ├─ tl:195       32  __                                  96 (18 events)         │
  │   │  └─ tl:196       32  __                                  96 (32 events)         │
  │   ├─ B+C             96    __                                160                    │
  │   │  ├─ tl:198       96    __                                160 (176 events)       │
  │   │  ├─ tl:199       96    __                                160 (18 events)        │
  │   │  └─ tl:200       96    __                                160 (35 events)        │
  │   │  ... (4 more children)                                                          │
  │   ├─ G+H+I+I1       528                  __                  593                    │
  │   │  ├─ tl:218      528                  __                  593 (225 events)       │
  │   │  ├─ tl:219      528                  __                  593 (18 events)        │
  │   │  └─ tl:220      528                  __                  593 (34 events)        │
  │   ├─ I              593                    _                 621                    │
  │   │  ├─ tl:222      593                    _                 621 (104 events)       │
  │   │  ├─ tl:223      593                    _                 621 (7 events)         │
  │   │  └─ tl:224      593                    _                 621 (14 events)        │
  │   └─ I2             621                     ________________ 1116                   │
  │      ├─ tl:226      621                     _______________  1110 (1679 events)     │
  │      ├─ tl:227      621                     ________________ 1116 (124 events)      │
  │      └─ tl:228      621                     ________________ 1116 (196 events)      │
  └─────────────────────────────────────────────────────────────────────────────────────┘
  Timestamps: 2

  TimelineGroup[normal] (5 timelines, 2 timestamps)
  ┌────────────────────────────────────────────────────────────────────────────┐
  │ DiscretePhysicalTimeline[dpt1] (6 children, 1 cmaps)                       │
  │                          0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11753638 samples │
  │   ├─ Cardioid ...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11753638         │
  │   ├─ Binaural ...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11753638         │
  │   ├─ Piezo pic...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11753638         │
  │   ├─ Piezo pic...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11753638         │
  │   ├─ Piezo pic...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11753638         │
  │   └─ Piezo pic...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11753638         │
  │                                                                            │
  │ DiscretePhysicalTimeline[dpt2] (6 children, 1 cmaps)                       │
  │                       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11195 samples │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11195         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11195         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11195         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11195         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11195         │
  │   └─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11195         │
  │                                                                            │
  │ DiscretePhysicalTimeline[dpt3] (6 children, 1 cmaps)                       │
  │                       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 22389 samples │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 22389         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 22389         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 22389         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 22389         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 22389         │
  │   └─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 22389         │
  │                                                                            │
  │ DiscretePhysicalTimeline[dpt4] (6 children, 1 cmaps)                       │
  │                       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 45844 samples │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 45844         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 45844         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 45844         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 45844         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 45844         │
  │   └─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 45844         │
  │                                                                            │
  │ DiscretePhysicalTimeline[dpt5] (4 children, 1 cmaps)                       │
  │                       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 63965 samples │
  │   ├─ Bow Angle        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 63965         │
  │   ├─ Bow Angle        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 63965         │
  │   ├─ Bow Angle        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 63965         │
  │   └─ Bow Angle        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 63965         │
  └────────────────────────────────────────────────────────────────────────────┘
  Timestamps: 2

  TimelineGroup[mechanical] (5 timelines, 2 timestamps)
  ┌────────────────────────────────────────────────────────────────────────────┐
  │ DiscretePhysicalTimeline[dpt6] (6 children, 1 cmaps)                       │
  │                          0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 12426696 samples │
  │   ├─ Cardioid ...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 12426696         │
  │   ├─ Binaural ...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 12426696         │
  │   ├─ Piezo pic...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 12426696         │
  │   ├─ Piezo pic...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 12426696         │
  │   ├─ Piezo pic...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 12426696         │
  │   └─ Piezo pic...        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 12426696         │
  │                                                                            │
  │ DiscretePhysicalTimeline[dpt7] (6 children, 1 cmaps)                       │
  │                       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11836 samples │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11836         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11836         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11836         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11836         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11836         │
  │   └─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 11836         │
  │                                                                            │
  │ DiscretePhysicalTimeline[dpt8] (6 children, 1 cmaps)                       │
  │                       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 23671 samples │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 23671         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 23671         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 23671         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 23671         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 23671         │
  │   └─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 23671         │
  │                                                                            │
  │ DiscretePhysicalTimeline[dpt9] (6 children, 1 cmaps)                       │
  │                       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 48469 samples │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 48469         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 48469         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 48469         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 48469         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 48469         │
  │   └─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 48469         │
  │                                                                            │
  │ DiscretePhysicalTimeline[dpt10] (4 children, 1 cmaps)                      │
  │                       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 67628 samples │
  │   ├─ Bow Angle        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 67628         │
  │   ├─ Bow Angle        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 67628         │
  │   ├─ Bow Angle        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 67628         │
  │   └─ Bow Angle        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 67628         │
  └────────────────────────────────────────────────────────────────────────────┘
  Timestamps: 2

  TimelineGroup[exaggerated] (5 timelines, 2 timestamps)
  ┌────────────────────────────────────────────────────────────────────────────┐
  │ DiscretePhysicalTimeline[dpt11] (6 children, 1 cmaps)                      │
  │                         0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 8197748 samples │
  │   ├─ Cardioid ...       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 8197748         │
  │   ├─ Binaural ...       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 8197748         │
  │   ├─ Piezo pic...       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 8197748         │
  │   ├─ Piezo pic...       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 8197748         │
  │   ├─ Piezo pic...       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 8197748         │
  │   └─ Piezo pic...       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 8197748         │
  │                                                                            │
  │ DiscretePhysicalTimeline[dpt12] (6 children, 1 cmaps)                      │
  │                       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 7809 samples │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 7809         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 7809         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 7809         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 7809         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 7809         │
  │   └─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 7809         │
  │                                                                            │
  │ DiscretePhysicalTimeline[dpt13] (6 children, 1 cmaps)                      │
  │                       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 15616 samples │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 15616         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 15616         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 15616         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 15616         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 15616         │
  │   └─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 15616         │
  │                                                                            │
  │ DiscretePhysicalTimeline[dpt14] (6 children, 1 cmaps)                      │
  │                       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 31975 samples │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 31975         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 31975         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 31975         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 31975         │
  │   ├─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 31975         │
  │   └─ essentia....     0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 31975         │
  │                                                                            │
  │ DiscretePhysicalTimeline[dpt15] (4 children, 1 cmaps)                      │
  │                       0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 44614 samples │
  │   ├─ Bow Angle        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 44614         │
  │   ├─ Bow Angle        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 44614         │
  │   ├─ Bow Angle        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 44614         │
  │   └─ Bow Angle        0 ⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅⋅ 44614         │
  └────────────────────────────────────────────────────────────────────────────┘
  Timestamps: 2

  TimelineGroup[emerson] (1 timelines, 2 timestamps)
  ┌────────────────────────────────────────────────────────────────────────────┐
  │ ContinuousPhysicalTimeline[dpt16]                                          │
  │                       0 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 241.8 seconds │
  └────────────────────────────────────────────────────────────────────────────┘
  Timestamps: 2

  MatchClaims: 10144

Match claims per connection:

pd.DataFrame(
    [
        {
            "recording": name,
            "source": dpt_id,
            "target": "clt1",
            "matched": match_results[dpt_id].n_matched,
            "unmatched_source": match_results[dpt_id].n_unmatched_source,
            "unmatched_target": match_results[dpt_id].n_unmatched_target,
        }
        for name, dpt_id in [
            ("Normal", "dpt1"),
            ("Mechanical", "dpt6"),
            ("Exaggerated", "dpt11"),
        ]
    ]
    + [
        {
            "recording": "Emerson",
            "source": "clt2_unfolded",
            "target": "dpt16",
            "matched": len(emerson_claims),
            "unmatched_source": 0,
            "unmatched_target": 0,
        }
    ]
).set_index("recording")
source target matched unmatched_source unmatched_target
recording
Normal dpt1 clt1 3740 16 23
Mechanical dpt6 clt1 3743 13 20
Exaggerated dpt11 clt1 2650 4 1113
Emerson clt2_unfolded dpt16 11 0 0

13.1 Explicit MatchLine and WarpMap

Before demonstrating bundle-level coordinate transfer, it is instructive to see the intermediate MatchLine and WarpMap that the bundle constructs internally. The MatchLine orders the 11 Emerson anchors by source coordinate; the WarpMap interpolates between them.

emerson_matchline = MatchLine.from_claims(
    emerson_claims, source_timeline_id="clt2_unfolded"
)
emerson_matchline
MatchLine(source='clt2_unfolded', stamps=11, targets=[dpt16])
emerson_warpmap = WarpMap.from_match_line(emerson_matchline, target_timeline_id="dpt16")
emerson_warpmap
WarpMap(source='clt2_unfolded', target='dpt16', n_anchors=11)

Verify the WarpMap manually: transfer a coordinate from CLT2_unfolded to DPT16 and compare with a known section boundary:

# The first section boundary from ema_df
first_fm = float(ema_df["measure_unfold_start"].iloc[0])
first_sec = float(ema_df["seconds_start"].iloc[0])
transferred = emerson_warpmap.get_coordinate_at(first_fm)
{
    "CLT2_unfolded (floating measures)": first_fm,
    "DPT16 expected (seconds)": first_sec,
    "DPT16 via WarpMap": transferred,
}
{'CLT2_unfolded (floating measures)': 0.75,
 'DPT16 expected (seconds)': 0.567006803,
 'DPT16 via WarpMap': IdCoordinate(0.567006803, seconds, 'dpt16')}

14. Cross-Group Coordinate Transfer

The bundle’s get_matchstamp_at() method is the primary interface for cross-domain coordinate transfer. Given a coordinate on any timeline, it returns a MatchStamp with corresponding coordinates on all connected timelines — regardless of domain. With CLT2_unfolded bridging the score group and the Emerson MatchClaims, the bundle now reaches all 5 groups.

14.1 Inspecting CLT1’s Harmony Annotations

Before transferring coordinates, let us see what harmonic events live on CLT1. The annotations child carries all harmony labels from the ABC score:

annotations_df = clt1.get_child("annotations").get_events().to_dataframe()
annotations_df[["start", "name"]].head(15)
start name
0 0 c.i
1 9 V65
2 10 i
3 11 V
4 12 i
5 13 V
6 17 i
7 21 v.iv
8 24 viio7/V
9 25 V(64)
10 26 It6
11 27 V(64)
12 28 V
13 29 i\\
14 33 i.V7/iv

14.2 USE CASE A — Transfer a Harmony Across All Groups

The V7 at quarterbeat 79 (m. 20) is a dominant seventh — one of the most recognisable sonorities. Where does this moment land across all 5 groups, in every domain? The nested format groups results by TimelineGroup:

stamp = bundle.get_matchstamp_at(79.0, "clt1")
stamp
MatchStamp 20 timelines, 19 edges
ID Coordinate Type
clt1 79 quarters inferred
dgt1 9570 pixels inferred
openscore 79 quarters inferred
clt2_unfolded 79 quarters inferred
dpt1 837936 samples inferred
dpt2 798 samples inferred
dpt3 1596 samples inferred
dpt4 3268 samples inferred
dpt5 4560 samples inferred
dpt6 910548 samples inferred
dpt7 867 samples inferred
dpt8 1734 samples inferred
dpt9 3551 samples inferred
dpt10 4955 samples inferred
dpt11 848192 samples inferred
dpt12 808 samples inferred
dpt13 1616 samples inferred
dpt14 3308 samples inferred
dpt15 4616 samples inferred
dpt16 71.8067059712 seconds inferred
Try: stamp.get_coordinate_for(<tl_id>), stamp.get_coordinates_for(<tl_ids>), stamp.get_unit(<unit>), stamp.get_conversion_for(<key>)

MatchStamp belongs to the same unified stamp family as TimeStamp and GroupTimestamp: get_coordinate() returns a unit-bearing coordinate, while is_interpolated reports whether resolution used interpolation. Those two are independent. This position was reached by interpolation and is still an exact Fraction, because the number type follows the quarters axis and never records how a value was obtained. If you want to know whether a reading was estimated, is_interpolated is the only thing that tells you.

{
    "CLT1 coordinate": stamp.get_coordinate("clt1"),
    "interpolated": stamp.is_interpolated,
}
{'CLT1 coordinate': IdCoordinate(Fraction(79, 1), quarters, 'clt1'),
 'interpolated': True}

to_dict() is serialization rather than a shortcut to numbers: every leaf is the wire entry {value, numerator, denominator, unit, number_type}. The float value is a mirror for consumers that only speak JSON numbers, the ratio members carry the exact value where the axis is fraction-canonical, and number_type names which side is authoritative. When you want a plain number instead, ask for one with get_coordinate_for(id, format="float").

bundle.get_matchstamp_at(79.0, "clt1").to_dict(format="nested")
{'score_unfolded': {'clt1 (quarters)': {'value': 79.0,
   'numerator': 79,
   'denominator': 1,
   'unit': 'quarters',
   'number_type': 'fraction'},
  'dgt1 (pixels)': {'value': 9570.0,
   'numerator': None,
   'denominator': None,
   'unit': 'pixels',
   'number_type': 'int'},
  'openscore (quarters)': {'value': 79.0,
   'numerator': 79,
   'denominator': 1,
   'unit': 'quarters',
   'number_type': 'fraction'},
  'clt2_unfolded (quarters)': {'value': 79.0,
   'numerator': 79,
   'denominator': 1,
   'unit': 'quarters',
   'number_type': 'fraction'}},
 'normal': {'dpt1 (samples)': {'value': 837936.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'},
  'dpt2 (samples)': {'value': 798.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'},
  'dpt3 (samples)': {'value': 1596.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'},
  'dpt4 (samples)': {'value': 3268.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'},
  'dpt5 (samples)': {'value': 4560.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'}},
 'mechanical': {'dpt6 (samples)': {'value': 910548.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'},
  'dpt7 (samples)': {'value': 867.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'},
  'dpt8 (samples)': {'value': 1734.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'},
  'dpt9 (samples)': {'value': 3551.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'},
  'dpt10 (samples)': {'value': 4955.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'}},
 'exaggerated': {'dpt11 (samples)': {'value': 848192.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'},
  'dpt12 (samples)': {'value': 808.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'},
  'dpt13 (samples)': {'value': 1616.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'},
  'dpt14 (samples)': {'value': 3308.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'},
  'dpt15 (samples)': {'value': 4616.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'}},
 'emerson': {'dpt16 (seconds)': {'value': 71.8067059712,
   'numerator': None,
   'denominator': None,
   'unit': 'seconds',
   'number_type': 'float'}}}

Note that the emerson group now appears in the output: the cascading path CLT1 -> CLT2_unfolded -> DPT16 connects the Emerson recording to the rest of the bundle.

The flat format is useful for programmatic access:

bundle.get_matchstamp_at(79.0, "clt1").to_dict(format="flat")
{'clt1 (quarters)': {'value': 79.0,
  'numerator': 79,
  'denominator': 1,
  'unit': 'quarters',
  'number_type': 'fraction'},
 'dgt1 (pixels)': {'value': 9570.0,
  'numerator': None,
  'denominator': None,
  'unit': 'pixels',
  'number_type': 'int'},
 'openscore (quarters)': {'value': 79.0,
  'numerator': 79,
  'denominator': 1,
  'unit': 'quarters',
  'number_type': 'fraction'},
 'clt2_unfolded (quarters)': {'value': 79.0,
  'numerator': 79,
  'denominator': 1,
  'unit': 'quarters',
  'number_type': 'fraction'},
 'dpt1 (samples)': {'value': 837936.0,
  'numerator': None,
  'denominator': None,
  'unit': 'samples',
  'number_type': 'int'},
 'dpt2 (samples)': {'value': 798.0,
  'numerator': None,
  'denominator': None,
  'unit': 'samples',
  'number_type': 'int'},
 'dpt3 (samples)': {'value': 1596.0,
  'numerator': None,
  'denominator': None,
  'unit': 'samples',
  'number_type': 'int'},
 'dpt4 (samples)': {'value': 3268.0,
  'numerator': None,
  'denominator': None,
  'unit': 'samples',
  'number_type': 'int'},
 'dpt5 (samples)': {'value': 4560.0,
  'numerator': None,
  'denominator': None,
  'unit': 'samples',
  'number_type': 'int'},
 'dpt6 (samples)': {'value': 910548.0,
  'numerator': None,
  'denominator': None,
  'unit': 'samples',
  'number_type': 'int'},
 'dpt7 (samples)': {'value': 867.0,
  'numerator': None,
  'denominator': None,
  'unit': 'samples',
  'number_type': 'int'},
 'dpt8 (samples)': {'value': 1734.0,
  'numerator': None,
  'denominator': None,
  'unit': 'samples',
  'number_type': 'int'},
 'dpt9 (samples)': {'value': 3551.0,
  'numerator': None,
  'denominator': None,
  'unit': 'samples',
  'number_type': 'int'},
 'dpt10 (samples)': {'value': 4955.0,
  'numerator': None,
  'denominator': None,
  'unit': 'samples',
  'number_type': 'int'},
 'dpt11 (samples)': {'value': 848192.0,
  'numerator': None,
  'denominator': None,
  'unit': 'samples',
  'number_type': 'int'},
 'dpt12 (samples)': {'value': 808.0,
  'numerator': None,
  'denominator': None,
  'unit': 'samples',
  'number_type': 'int'},
 'dpt13 (samples)': {'value': 1616.0,
  'numerator': None,
  'denominator': None,
  'unit': 'samples',
  'number_type': 'int'},
 'dpt14 (samples)': {'value': 3308.0,
  'numerator': None,
  'denominator': None,
  'unit': 'samples',
  'number_type': 'int'},
 'dpt15 (samples)': {'value': 4616.0,
  'numerator': None,
  'denominator': None,
  'unit': 'samples',
  'number_type': 'int'},
 'dpt16 (seconds)': {'value': 71.8067059712,
  'numerator': None,
  'denominator': None,
  'unit': 'seconds',
  'number_type': 'float'}}

14.3 USE CASE B — Reverse Transfer: Emerson to All Groups

The cascading alignment is bidirectional. Starting from a seconds coordinate on DPT16 (the Emerson recording), we can reach every connected timeline — including the three EEP recording groups:

bundle.get_matchstamp_at(120.0, "dpt16").to_dict(format="nested")
{'emerson': {'dpt16 (seconds)': {'value': 120.0,
   'numerator': None,
   'denominator': None,
   'unit': 'seconds',
   'number_type': 'float'}},
 'score_unfolded': {'clt1 (quarters)': {'value': 133.95598075544436,
   'numerator': 4713157070423973,
   'denominator': 35184372088832,
   'unit': 'quarters',
   'number_type': 'fraction'},
  'dgt1 (pixels)': {'value': 16228.0,
   'numerator': None,
   'denominator': None,
   'unit': 'pixels',
   'number_type': 'int'},
  'openscore (quarters)': {'value': 133.95598075544436,
   'numerator': 4713157070423973,
   'denominator': 35184372088832,
   'unit': 'quarters',
   'number_type': 'fraction'},
  'clt2_unfolded (quarters)': {'value': 133.95598075544436,
   'numerator': 4713157070423973,
   'denominator': 35184372088832,
   'unit': 'quarters',
   'number_type': 'fraction'}},
 'normal': {'dpt1 (samples)': {'value': 1448818.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'},
  'dpt2 (samples)': {'value': 1380.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'},
  'dpt3 (samples)': {'value': 2760.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'},
  'dpt4 (samples)': {'value': 5651.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'},
  'dpt5 (samples)': {'value': 7885.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'}},
 'mechanical': {'dpt6 (samples)': {'value': 1579276.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'},
  'dpt7 (samples)': {'value': 1504.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'},
  'dpt8 (samples)': {'value': 3008.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'},
  'dpt9 (samples)': {'value': 6160.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'},
  'dpt10 (samples)': {'value': 8595.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'}},
 'exaggerated': {'dpt11 (samples)': {'value': 1466294.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'},
  'dpt12 (samples)': {'value': 1397.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'},
  'dpt13 (samples)': {'value': 2793.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'},
  'dpt14 (samples)': {'value': 5719.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'},
  'dpt15 (samples)': {'value': 7980.0,
   'numerator': None,
   'denominator': None,
   'unit': 'samples',
   'number_type': 'int'}}}

A coordinate at 120 seconds into the Emerson recording is mapped through the WarpMap to CLT2_unfolded, then via interpolation to CLT1, and from there via the per-note WarpMaps to DPT1, DPT6, and DPT11 — all in a single call.

14.4 USE CASE C — Section Boundaries Across All Groups

The score’s repeat structure defines atomic sections (A through M). The flow controller (from §6.1) computes each section’s unfolded quarterbeat start coordinate. With the Emerson group now connected, the boundary table includes DPT16:

section_coords = abc_controller.get_atomic_section_coordinates(flow=abc_flow)
section_coords
{'A': Fraction(0, 1),
 'B': Fraction(64, 1),
 'C': Fraction(128, 1),
 'D': Fraction(192, 1),
 'D1': Fraction(253, 1),
 'D2': Fraction(317, 1),
 'E': Fraction(897, 2),
 'F': Fraction(993, 2),
 'F1': Fraction(525, 1),
 'F2': Fraction(557, 1),
 'G': Fraction(561, 1),
 'G1': Fraction(589, 1),
 'G2': Fraction(621, 1)}
boundary_rows = []
for _qb in section_coords.values():
    _stamp = bundle.get_matchstamp_at(float(_qb), "clt1")
    _row = {}
    for _tl_id in _stamp.present_timelines:
        _coord = _stamp.get_coordinate_for(_tl_id)
        _row[f"{_tl_id} ({_coord.unit})"] = _coord.value
    boundary_rows.append(_row)
boundary_df = pd.DataFrame(
    boundary_rows,
    index=list(section_coords.keys()),
)
boundary_df.index.name = "section"
boundary_df
clt1 (quarters) dgt1 (pixels) openscore (quarters) clt2_unfolded (quarters) dpt1 (samples) dpt2 (samples) dpt3 (samples) dpt4 (samples) dpt5 (samples) dpt6 (samples) dpt7 (samples) dpt8 (samples) dpt9 (samples) dpt10 (samples) dpt11 (samples) dpt12 (samples) dpt13 (samples) dpt14 (samples) dpt15 (samples) dpt16 (seconds)
section
A 0 0 0 0 44100 42 84 172 240 44100 42 84 172 240 44100 42 84 172 240 NaN
B 64 7753 64 64 659662 628 1257 2573 3590 725796 691 1383 2831 3950 677580 645 1291 2643 3688 58.409620
C 128 15507 128 128 1388046 1322 2644 5414 7554 1509834 1438 2876 5889 8217 1406333 1340 2679 5485 7654 114.858607
D 192 23260 192 192 2055059 1957 3915 8016 11184 2232624 2126 4253 8708 12150 2063790 1966 3931 8050 11232 169.095231
D1 253 30650 253 253 2487974 2370 4739 9704 13540 2905988 2768 5535 11334 15815 2676016 2549 5098 10438 14563 220.531790
D2 317 38403 317 317 3368901 3209 6417 13140 18334 3625348 3453 6906 14140 19730 3334448 3176 6352 13006 18147 NaN
E 897/2 54334 897/2 897/2 4998366 4761 9521 19496 27202 5393668 5137 10274 21037 29353 4963792 4728 9456 19361 27014 NaN
F 993/2 60149 993/2 993/2 5252309 5003 10005 20486 28584 5669764 5400 10800 22114 30856 5208528 4962 9922 20316 28346 NaN
F1 525 63601 525 525 5529036 5266 10532 21566 30090 5969028 5685 11370 23282 32484 5477865 5218 10435 21366 29812 NaN
F2 557 67478 557 557 5861765 5583 11166 22863 31901 6325188 6025 12049 24671 34423 5803056 5528 11054 22635 31582 NaN
G 561 67962 561 561 5930346 5648 11296 23131 32274 6401064 6097 12193 24967 34836 5868885 5591 11180 22891 31940 NaN
G1 589 71354 589 589 6208176 5913 11826 24214 33786 6708070 6389 12778 26164 36506 6143257 5852 11702 23962 33433 NaN
G2 621 75231 621 621 6530473 6220 12440 25472 35540 7058436 6723 13445 27531 38413 6461812 6155 12309 25204 35167 NaN

Each row gives the exact coordinate of a section boundary in every timeline and domain — including the Emerson recording’s dpt16 column. Every column matches its own timeline’s native type, and that is the point worth pausing on: pixel and sample counts are integers because those axes are discrete, seconds are floats, and quarterbeats are exact fractions. One cross-section can therefore hold three different number types at once without any of them being a compromise. Nothing here was converted for display — each value is the canonical one its axis declares.

15. Summary & Key Takeaways

“Any two events in the bundle can be related with each other — regardless of whether they live on the same timeline, in the same group, or even in the same domain — as long as a path of MatchClaims or ConversionMaps connects them.”

The Cascading Alignment Pattern

The central demonstration of this notebook is that a single additional group membership retroactively enriches every timeline already present in the bundle. Adding CLT2_unfolded to the Unfolded Score Group bridges two independent alignment networks:

  • EEP recordings (per-note MatchClaims) connect DPT1-DPT15 to CLT1
  • Emerson recording (section-boundary MatchClaims) connects DPT16 to CLT2_unfolded
  • CLT2_unfolded in the score group bridges the two via within-group interpolation

Patterns Demonstrated

Pattern Example Section
build_recording_group() Reusable factory for EEP recordings 2-4
Ms3Loader.from_file() Load ABC score with notes, measures, annotations 6
create_flow_controller() Repeat structure + default flow 6.1
SegmentLine nesting OMR pages -> systems -> noteheads 7
Region extraction OpenScore 4-movement -> movement 4 child 8
Cross-domain timestamps Quarters -> pixels -> page number 9.1
TimelineGroup.apply_flow() Unfold entire group via one flow 9.2
match_notes_by_attributes() EEP <-> ABC note matching (from unfolded TL) 10
create_unfolded_timeline() Unfold a single timeline 11.3
MatchClaim + AlignmentAnchor Section-boundary alignment (alpha-kappa) 11.5
add_timeline() on a group Bridge independent alignment networks 12.1
MatchLine + WarpMap Explicit construction from MatchClaims 13.1
AlignmentBundle Multi-group cross-domain transfer 13
get_matchstamp_at() Universal coordinate resolution 14
Reverse transfer DPT16 -> all groups 14.3
Cascading alignment EEP <-> Score <-> Emerson via shared group 12-14

5 groups, 18+ timelines, 3 domains, 1 bundle.