Developers’ Reference
This page is the canonical reference for contributing to Time To Align!. The conceptual model lives in Concepts and the per-class API lives under API Reference; everything you need to build, test, document, and release the library is here.
The package is named timetoalign for machines (imports, PyPI) and written Time To Align! for humans (prose, headings, comments).
1. Repository Layout
The git repository is a multi-project workspace; the library itself lives in timetoalign/.
tta/ # Repository root
├── timetoalign/ # The Python library — the focus of this doc
│ ├── timetoalign/ # Importable package
│ ├── tests/ # pytest suite (mirrors the package layout)
│ ├── docs/ # Documentation source
│ │ ├── page/ # Quarto site (this site)
│ │ ├── tuto-notebooks/ # Tutorial notebooks (jupytext .py + paired .ipynb)
│ │ └── howto-notebooks/ # How-to notebooks (jupytext .py + paired .ipynb)
│ ├── pyproject.toml # PEP 621 project metadata + ruff/coverage/pytest config
│ ├── tox.ini # Build/test/lint/publish orchestrator
│ ├── .pre-commit-config.yaml # Pinned formatter/linter versions
│ ├── .github/workflows/ # CI (release-please)
│ └── CHANGELOG.md # Auto-generated by release-please — DO NOT edit by hand
├── tta_article/ # TISMIR manuscript (LaTeX, submitted) — out of scope here
├── pyMeasureMap/ # Sibling project; standalone
└── dashboard/ # Sibling project; standalone
Sibling projects (tta_article/, pyMeasureMap/, dashboard/) have their own build infrastructure and are not governed by the rules below.
2. Architecture Overview
2.1 Subpackages
The importable package timetoalign/ is organised into seven subpackages, each with a single responsibility:
| Subpackage | Responsibility |
|---|---|
core/ |
Foundational types: enums (Domain, TimeUnit, …), Coordinate / IdCoordinate, TimeStamp, IdGenerator. |
fields/ |
SemanticField definitions (Layer 2 of the EventData stack). |
display/ |
ASCII rendering helpers for timelines, groups, bundles, flows. |
maps/ |
ConversionMap family (LinearMap, TableMap, ChainMap, PiecewiseMap, …). |
timelines/ |
Timeline classes for all six domain × modality combinations, regions, flow control, beat grids. |
alignment/ |
MatchClaim, AlignmentAnchor, TimelineGroup, AlignmentBundle, MatchGraph, MatchLine, WarpMap. |
loader/ |
File-format-specific ingestion + the PyArrow-backed EventData / EventStore storage primitives. |
2.2 Import Direction (MANDATORY)
Subpackages are arranged in strict layers. Imports may only flow downward, from a higher-numbered layer to a lower-numbered one. This is a hard rule: violations create circular-import landmines and are refused at review.
┌──────────────────────────────┐
Layer 3 ──▶ │ loader │
└──────────────┬───────────────┘
│ may import from layers 0–2
┌──────────────▼───────────────┐
Layer 2 ──▶ │ timelines alignment │
└──────────────┬───────────────┘
│ may import from layers 0–1
┌──────────────▼───────────────┐
Layer 1 ──▶ │ maps │
└──────────────┬───────────────┘
│ may import from layer 0
┌──────────────▼───────────────┐
Layer 0 ──▶ │ core fields display │
└──────────────────────────────┘
(no internal dependencies)
Equivalently:
| Subpackage | May import from |
|---|---|
core/ |
nothing internal |
fields/ |
nothing internal |
display/ |
nothing internal at runtime (TYPE_CHECKING only) |
maps/ |
core |
timelines/ |
core, maps (+ documented exception below) |
alignment/ |
core, maps |
loader/ |
core, maps, timelines, alignment |
One documented exception: timelines ↔︎ loader
Timeline instances own an EventData (PyArrow-backed event storage). For historical reasons, EventData and EventStore live under loader/ because loaders are their primary producer. As a result timelines/base.py performs one runtime import from loader/:
from timetoalign.loader import EventDataAll other timelines → loader references are deferred (function-local) or guarded by if TYPE_CHECKING: to avoid the circular import at module load. Do not introduce any new runtime timelines → loader imports. If you need additional types from the loader namespace inside a timeline module, either:
- Put the import inside the function that needs it (deferred), or
- Guard it with
if TYPE_CHECKING:and use a string annotation.
2.3 Stable Architectural Invariants
These invariants are tested at the unit level and enforced at review. If you find yourself wanting to break one, raise it as a discussion before coding.
TimeStampis the one and only return type for coordinate resolution. No parallel “GroupTimestamp” / “BundleTimestamp” / etc. classes.CoordinateandIdCoordinateare the canonical input types for every method that takes a coordinate value. Raw numbers are accepted for ergonomics; raw numbers are not acceptable as return types — every getter returns aCoordinate.ConversionMapproperties belong to a singleTimeline. They are not graph edges between timelines.MatchGraphis on-demand and analytical. It is built from claims when needed, not maintained as a system-wide structure.- Loaders use a strict two-phase contract.
loader.load(*sources)ingests files;loader.create_*()produces domain objects and never takes file paths. Thefrom_file()classmethod is sugar for the common case. - All enums live in
core/enums.pyand subclassFancyStrEnum. Member names are lowercase. The single documented exception isNumberType, whose values are Pythontypeobjects. - Timeline IDs are systematic:
clt/dlt/cpt/dpt/cgt/dgtprefixes for the six domain × modality combinations, with optional role prefixes (score:clt1,perf:dlt1).
For the full conceptual rationale see Concepts and the Glossary.
3. Environment & Installation
3.1 Python
- Supported: Python 3.11, 3.12, 3.13 (CI matrix).
- Required:
>=3.11perpyproject.toml.
3.2 Install
From the timetoalign/ directory:
pip install -e ".[dev]"dev is the convenience extra that pulls in everything: all loaders, plotting, the tutorial Jupyter stack, the docs build chain, the test stack, and the formatter/linter tooling. See pyproject.toml for the full extras hierarchy (midi, partitura, music21, ms3, audio, graphical, plot, scores, loaders, tutorial, docs, testing, all, dev).
3.3 Pre-commit Hook
After installing, register the git hook once per clone:
pre-commit installThe pinned hook versions in .pre-commit-config.yaml are:
| Hook | Version | Settings |
|---|---|---|
black |
26.1.0 | language_version: python3.11 |
isort |
7.0.0 | --profile black --filter-files |
flake8 |
7.3.0 | max-line-length=120, __init__.py:F401 ignored, docs/howto-notebooks/*.py:E402 ignored |
seed-isort-config |
2.2.0 | |
pre-commit-hooks |
6.0.0 | trailing-whitespace, check-ast/json/xml/yaml, end-of-file-fixer, mixed-line-ending (auto), debug-statements, requirements-txt-fixer |
These are pinned deliberately — bump them in a dedicated chore: commit so the diff is isolated.
4. Build, Test, Publish (tox)
tox is the canonical entry point for everything CI-relevant. It uses tox-uv for fast environment creation. All commands run from timetoalign/.
| Command | Effect |
|---|---|
tox |
Runs the full pytest suite under py311, py312, py313 (skips missing interpreters). |
tox -e lint |
pre-commit run --all-files --show-diff-on-failure. |
tox -e build |
PEP 517 sdist + wheel into ./dist/ via python -m build. |
tox -e clean |
Removes ./build/ and ./dist/. |
tox -e publish |
twine check dist/* then twine upload dist/* (testpypi by default). |
To publish to production PyPI explicitly:
tox -e publish -- --repository pypiThe publish env passes TWINE_USERNAME / TWINE_PASSWORD / TWINE_REPOSITORY / TWINE_REPOSITORY_URL through from the environment.
There is no tox -e docs env; documentation is built directly with quartodoc + quarto (see §6).
4.1 Direct pytest for Iteration
Inside the active venv, plain pytest is faster than tox for tight edit-test loops:
pytest # full suite, parallel via pytest-xdist (-n auto)
pytest -n 0 # serial — debugging only
pytest tests/timelines # one subtree
pytest tests/timelines/test_base.py::test_lockCoverage is on by default (configured in pyproject.toml [tool.pytest.ini_options]).
4.2 Test Discipline (MANDATORY)
- Parallel-safe. No global mutable state, no inter-test ordering, use
tmp_pathnot hardcoded/tmp/paths, no port collisions. - Exact assertions. Use exact gold-standard counts, never
>=, never “approximate”. If floating-point tolerance is required, document the mathematical reason in the test and the test data README. - Cover happy path + edge cases. Boundaries, empty inputs, type mismatches.
- Property tests for math. ConversionMap-style code uses
hypothesisto verify invariants such asinverse(forward(x)) == x. - Test data needs a README. Every directory under
tests/data/documents provenance, validation logic, and known discrepancies between loaders.
5. Coding Standards
5.1 Style and Formatting
- Line length 120 (enforced by black + flake8).
from __future__ import annotationsis the first non-comment line.- Imports grouped: standard library, third-party, local — handled by isort with the black profile.
- Module logger:
module_logger = logging.getLogger(__name__)immediately after imports. - Use
# region <name>/# endregion <name>to group related class / function definitions inside a module.
5.2 Class Member Order
Inside a class, declare members in this order:
- Class variables.
@classmethod @propertyaccessors.@classmethodfactories / helpers.- Nested classes (especially
Schema). __init__.- Magic methods (
__eq__,__hash__,__repr__,__str__). @propertyaccessors.- Public and private methods.
5.3 Typing
- Every public function signature and class attribute is type-hinted.
- Use modern generics (
list[str],dict[str, int],X | None) — the__future__ annotationsimport makes these free at runtime on 3.11+. - Use
typing_extensions.Selffor methods returning the instance type. - Break circular dependencies with
if TYPE_CHECKING:blocks and string annotations, not by collapsing modules.
5.4 Docstrings (Google Style)
Every public module, class, function, and method needs a Google-style docstring. Quartodoc renders them into the API reference site.
def lookup(self, coord: CoordinateSpec) -> TimeStamp:
"""Return the timestamp at ``coord``.
Args:
coord: A raw value, ``Coordinate``, or ``IdCoordinate``.
Returns:
A `timetoalign.TimeStamp` populated with all synchronous child
coordinates and conversion-map results.
Raises:
ValueError: If ``coord`` is outside the timeline length.
"""Cross-linking
- In docstrings: wrap fully-qualified names in backticks (
`timetoalign.ConversionMap`); theinterlinksfilter resolves them. - In
.qmdpages and notebook markdown: every model term goes through the glossary shortcode:. Bare uses without the shortcode are documentation bugs. - When you add a new concept: update
glossary.ymlandglossary.qmdin the same commit as the implementation.
5.5 Error Handling
- Never use
assertfor runtime validation — it disappears under-O. ValueErrorfor invalid arguments.TypeErrorfor unit / type mismatches.RuntimeError(or a custom subclass) for invalid state, e.g. mutating a locked timeline.
5.6 Pitch Spelling
Normalise all input to canonical Unicode characters:
- Sharp:
♯(U+266F) - Flat:
♭(U+266D)
#, b, -, etc. are converted at the boundary.
6. Documentation Site
The docs site is built with Quarto + quartodoc and lives at https://timetoalign.github.io/.
6.1 Building Locally
cd docs/page
quartodoc build # regenerate the API reference pages from docstrings
quarto render # build the full site into _site/The Quarto pre-render hook automatically runs sync_notebooks.py (see §6.3) to refresh tutorial and how-to notebooks before rendering.
6.2 Site Structure (Diátaxis)
The site follows the Diátaxis framework:
- Tutorials (
tutorials.qmd+tutorials/): learning-oriented, step-by-step. - How-to Guides (
howto.qmd+howto/): task-oriented, focused recipes. - Explanation (
concepts.qmd,glossary.qmd): understanding-oriented. - API Reference (
reference.qmd+ auto-generatedreference/): information-oriented; this Developers’ Reference lives here too.
The navigation (top navbar + per-section sidebar) is configured in _quarto.yml.
6.3 Notebook Integration Pipeline
Tutorial and how-to notebooks live as jupytext py:percent files in docs/tuto-notebooks/ and docs/howto-notebooks/. The paired .ipynb files are regenerated mechanically; the .py is the source of truth in git.
The pipeline is driven by docs/page/notebooks.csv, with one row per notebook:
| Column | Meaning |
|---|---|
section |
Either tutorials or howto. Selects the source directory and target subdirectory of the rendered site. |
source |
Filename of the .py jupytext source. |
slug |
Stem used for the rendered .ipynb (without extension). |
title |
Title injected as Quarto YAML front matter. |
description |
Short description used in listing pages. |
docs/page/sync_notebooks.py (run as the Quarto pre-render step) does three things per row:
- Runs
jupytext --sync --executeon the.pysource, regenerating the paired.ipynbwith fresh outputs. - Copies the resulting
.ipynbintodocs/page/{tutorials,howto}/<slug>.ipynb. - Replaces the notebook metadata with a Quarto-friendly YAML cell built from the CSV row.
A SHA-256 cache (docs/page/.sync_state.json) skips notebooks whose .py source has not changed.
Adding a new how-to / tutorial notebook
- Drop the new
.py(jupytextpy:percent) intodocs/tuto-notebooks/ordocs/howto-notebooks/. - Add a row to
docs/page/notebooks.csv. - Add a sidebar entry under the appropriate section in
docs/page/_quarto.yml. - If the notebook introduces a new model term, also update
glossary.ymlandglossary.qmd. - Run
cd docs/page && python sync_notebooks.py --verboseonce locally to confirm it executes cleanly. - Run
quartodoc build && quarto renderto confirm it renders.
Notebook style rules
- Markdown cells use the
shortcode for every model term. A bare “Timeline” / “ConversionMap” / etc. is a documentation bug. - Use
Loader.from_file()rather than the two-phaseload()+create_*()pattern in tutorials. The two-phase form is library-internal. - Never demonstrate
AlignmentBundle.transfer(). The user-facing answer to “how do I convert coordinates?” is always aTimeStamporMatchStamp.
6.4 Jupytext and Quarto: Upgrade-Only
Jupytext and Quarto versions may only ever be upgraded, never downgraded.
Both tools rewrite notebook structure on save; downgrading silently introduces incompatible changes that break the diff history and may corrupt outputs across the entire notebook corpus. If a contributor’s local install is older than the version that last touched the notebooks, they must upgrade before running sync_notebooks.py.
This rule applies equally to CI environments — pin versions upward only.
6.5 quartodoc API Reference
The API reference under docs/page/reference/ is fully generated from the live docstrings. Do not hand-edit those files; they are overwritten by quartodoc build. To add a class to the reference:
- Make sure the class is exported from
timetoalign/__init__.py(or list it with an explicitname:/package:entry). - Add it to the appropriate
contents:list underquartodoc:indocs/page/_quarto.yml. quartodoc buildregenerates the page.
7. Conventional Commits & Releases
The library is released by release-please, configured in .github/workflows/release-please.yml with release-type: python and package-name: timetoalign. The action runs on every push to main and:
- Reads commit messages since the last release tag.
- Computes the next semantic version from those messages.
- Opens (or updates) a release PR that bumps
pyproject.toml’sversionand rewritesCHANGELOG.md. - When the release PR is merged, tags the commit and creates a GitHub release.
Because the version bump is computed from commit messages, every commit that lands on main must follow Conventional Commits.
7.1 Commit Types
| Prefix | Effect on version | Use for |
|---|---|---|
feat: |
minor bump | New user-facing capability. |
fix: |
patch bump | Bug fix. |
perf: |
patch bump | Performance improvement (no behaviour change). |
docs: |
none | Documentation only. |
style: |
none | Formatting / whitespace. |
refactor: |
none | Restructuring with no behaviour change. |
test: |
none | Adding or correcting tests. |
build: |
none | Build system or dependency changes. |
ci: |
none | CI configuration changes. |
chore: |
none | Repo housekeeping with no src/test impact. |
revert: |
depends on reverted | Revert of a previous commit. |
Optional scopes are encouraged for clarity: feat(loader): …, fix(maps): ….
7.2 Breaking Changes (MAJOR Bump)
A breaking change triggers a major version bump. Mark it both ways:
- Add
!after the type, e.g.refactor!:,feat!:. - Include a
BREAKING CHANGE:footer describing the migration.
refactor!: replace TimelineGroup.get_timestamp_at return type
BREAKING CHANGE: get_timestamp_at now returns TimeStamp instead of the
removed GroupTimestamp class. Callers using `.coordinates` should switch
to subscript access (`ts["clt1"]`) or `ts.get(timeline_id)`.
The ! is what release-please reads to compute the bump; the BREAKING CHANGE: footer is what readers of the changelog rely on. Use both.
7.3 Manual Edits Are Forbidden
- Do not edit
pyproject.toml’sversionby hand. - Do not edit
CHANGELOG.mdby hand.
Both are owned by release-please. Manual edits will be overwritten or, worse, will fight the next release PR.
8. Branching and Pull Requests
mainis the release branch; release-please runs against it.- Feature work happens on topic branches; squash-merge into
mainwith a Conventional Commit message that becomes the merge commit. - The PR title also follows Conventional Commits — release-please reads the merge commit, but a consistent title makes the PR list scannable.
- Run
tox -e lintandtox(or at least targetedpytest) locally before opening a PR.
9. IDE
PyCharm is the preferred IDE. The repo is set up to work cleanly with it out of the box:
- Import the
timetoalign/directory as a PyCharm project (not the repo root — sibling projects have their own settings). - Configure the project interpreter to a venv with
pip install -e ".[dev]"applied. - Enable the Black and isort integrations and point them at the project tools (PyCharm reads
.pre-commit-config.yamlfor the pinned versions; mirror them in Settings → Tools → Black and Settings → Editor → Code Style → Python → Imports). - Enable Settings → Editor → Inspections → Python → Type checker with strict mode if you want the same level of typing the codebase already enforces.
- The flake8 line length is 120; set Settings → Editor → Code Style → Python → Hard wrap at to 120 to match.
VS Code, Neovim, and other editors all work — the formatters and linters are externally driven by pre-commit and tox, so editor choice is not load-bearing — but PyCharm is what the maintainers use day-to-day.
10. Where to Look Next
- The conceptual model: Concepts.
- Term definitions: Glossary.
- Per-class API: API Reference.
- Worked examples: Tutorials and How-To Guides.