Tierline Contact

Explore · an example search, step by step

Search a collection with a voice

Listen to a recording, select a few words, and search the other seven. Each result opens at its moment in the recording it came from, with the audio around it, the alignment that ranked it and the file's fingerprint.

New to Tierline? The thirty-second tour runs one search with a real recording and explains each step.

Searched live on this page
8 recordings, 57.2 s of speech, computed on your device
Browser engine
24-band log-mel frames every 20 ms, subsequence DTW
Reference engine
XLS-R layer 11 → PCA 256 → int8 → FAISS HNSW32_SQ8 → DTW
Source tree
33 implemented components, from ingest to export

02 — How it works

From a recording to a reviewable result

Seven stages, each shown twice: what ran on this page a moment ago, and what Tierline’s local reference engine does at archive scale. The browser engine is small on purpose, so it can run anywhere without a server; the reference engine runs on a steward’s own computer, next to the recordings.

01 · Source recording

A recording arrives with someone responsible for it

Every span of audio sits under a policy interval that names who may search, play, annotate, export, train on or publish it. The default is deny. Private collections also need explicit consent for each action, and a withdrawal blocks access at once.

Why it matters Search is only useful to an archive if it cannot become a way around the people who decide what may be heard. Authority is checked before audio is read and again after.

On this page

The example recordings are public domain, so every action is allowed here. In the Workspace, each recording you add has its own access settings.

Reference engine

Policy and consent are evaluated together (policy AND explicit stewardship consent). Actions: search, play, annotate, export, train, publish.

Implemented as

  • src/tierline/policies/evaluator.py permits
  • src/tierline/stewardship/consent.py consent_allows_derivative_action
  • src/tierline/audio/access.py read_authorized_audio
  • src/tierline/stewardship/withdrawal.py apply_withdrawal_tombstone

Components: Action-by-action private consent layered with policy authorization, Shared integrity-verified, policy-authorized audio crop service for browser and desktop, External HMAC tombstone ledger with stale-backup replay

Exercised by

  • tests/unit/test_audio_access.py test_authorized_audio_rechecks_policy_after_io
  • tests/web/test_local_api.py test_audio_cannot_cross_policy_boundary
  • tests/policy/test_policy_hardening.py test_policy_json_tamper_cannot_change_authorization_without_hash_change

Exercised by the automated test suite against real SQLite workspaces and policy files.

02 · Ingestion and provenance

The original is kept, hashed and never altered

The source file is probed, hashed with SHA-256 and copied unchanged. FFmpeg writes one 16000 Hz FLAC derivative per channel (separate channels; no automatic archive downmix), resampled with soxr at precision 28. Denoising, high-pass filtering and loudness normalization are off by default.

Why it matters Every later result points back to bytes anyone can re-hash. Keeping channels separate stops two voices on a field recording from being blended into one.

On this page

Each example file is listed in demo/catalog.json with its SHA-256. This page hashes the bytes it downloads and compares them before using them.

Reference engine

ffprobe metadata, source SHA-256, per-channel FLAC derivatives with their own hashes, and an import event in a hash-chained audit log.

Implemented as

  • src/tierline/ingest/provenance.py ingest_source
  • src/tierline/ingest/ffmpeg.py build_channel_derivative_command
  • src/tierline/ingest/probe.py ffprobe_json
  • src/tierline/policies/audit.py verify_chain

Components: FFmpeg/ffprobe provenance-preserving ingest, Per-channel 16 kHz FLAC derivatives, Hash-chained audit log

Exercised by

  • tests/operator/test_import_jobs.py test_wrong_hash_fails_closed_and_cannot_be_queued
  • tests/unit/test_ingest_ffmpeg.py test_cancellable_derivative_closes_stderr_pipe
  • tests/stewardship/test_gate3.py test_private_ingest_requires_consent_and_tracks_external_origin

Exercised by the automated test suite with real FFmpeg/ffprobe calls on generated audio.

03 · Acoustic representation

Speech becomes a sequence of frames, one every 20 ms

Audio is turned into feature vectors that describe how it sounds, independent of spelling or transcription. Each vector covers a short window; their order keeps time.

Why it matters Many archival languages have little written text and no speech recognizer of their own. Comparing sound directly needs no transcript, no language-specific model and no retraining of the neural network on the archive.

On this page

24-band log-mel spectrum, 25 ms windows every 20 ms, each frame mean-centred and unit-normalized. Computed in a Web Worker on this device.

Reference engine

facebook/wav2vec2-xls-r-300m (revision 1a640f3), transformer layer 11, 1024-dimensional frames every 20 ms; 20 s chunks with 2 s of context on each side; weights loaded only from a hash-verified local manifest.

Implemented as

  • src/tierline/features/xlsr.py XLSREncoder
  • src/tierline/model_registry.py verify_model_asset
  • src/tierline/features/pipeline.py FeaturePipeline
  • site/js/audio-worker.js features

Components: XLS-R 300M layer-11 inference, 20-second core chunking with 2-second context

Exercised by

  • tests/reference/test_encoder_guards.py test_chunk_timestamps_monotonic_without_duplicates
  • tests/unit/test_model_registry.py test_verify_model_asset_rejects_changed_file
  • tests/unit/test_feature_set_provenance.py test_registered_model_is_reverified_before_feature_set

Its tests run with a small stand-in encoder, without the 1.3 GB of weights. It also ran end to end with the real facebook/wav2vec2-xls-r-300m weights over the example recordings on 2026-10-13 (scripts/run_demo_reference_search.py).

04 · Feature compression

4,096 bytes per frame become 256

Reference frames are projected from 1024 to 256 dimensions with PCA (sklearn IncrementalPCA, seed 1729), normalized, and stored as symmetric int8 with per-dimension scales calibrated at the 99.9th percentile.

Why it matters At 1024 float32 values every 20 ms, an hour of audio is about 737 MB of features; at 256 int8 values it is about 46 MB. The compression lets a large archive's features fit on an ordinary local computer.

On this page

No compression: 24 float32 values per frame for eight short recordings.

Reference engine

PCA 1024 → 256, int8 symmetric; each feature set is written with a hashed manifest and re-verified before use.

Implemented as

  • src/tierline/features/pca.py fit_incremental_pca
  • src/tierline/features/quantize.py Int8Quantizer
  • src/tierline/features/store.py write_feature_artifact

Components: PCA-256 projection, Calibrated int8 frame representation store

Exercised by

  • tests/unit/test_quantize.py test_quantizer_round_trip_direction
  • tests/reference/test_calibration.py test_real_pca_fit_only_uses_calibration_split
  • tests/unit/test_storage_integrity.py test_registered_feature_artifact_checks_path_hash_and_metadata

Exercised by the automated test suite. It also ran end to end with the real facebook/wav2vec2-xls-r-300m weights over the example recordings on 2026-10-13 (scripts/run_demo_reference_search.py).

05 · Candidate search

An index narrows hours of audio to a few regions

Frames are mean-pooled into 1 s windows every 0.25 s and indexed with FAISS HNSW32_SQ8 (M 32, efConstruction 200, efSearch 512), paired with an exact Flat index built from the same vectors. The top 500 windows are merged into regions and padded by 2 s.

Why it matters Aligning a query against every frame would be too slow at archive scale. There is one index per policy, and a search opens only the indexes whose policy permits it, so restricted audio never sits in an index the person can query.

On this page

Exhaustive: every frame of all eight recordings is compared. At this size no index is needed.

Reference engine

Policy-partitioned FAISS pairs (approximate + exact), each checked against its SHA-256, vector count and window mapping before it is loaded.

Implemented as

  • src/tierline/retrieval/faiss_index.py FaissIndexManager
  • src/tierline/retrieval/faiss_index.py search_authorized
  • src/tierline/retrieval/windows.py iter_mean_windows
  • src/tierline/retrieval/search.py coarse_regions

Components: Policy-partitioned FAISS HNSW32_SQ8 candidate index

Exercised by

  • tests/reference/test_policy_generation.py test_stale_vectors_are_never_given_to_faiss
  • tests/unit/test_index_pair_lineage.py test_index_pair_lineage_proves_same_mapping_and_payload
  • tests/integration/test_faiss_optional.py test_hnsw32_sq8_factory_builds

Exercised in tests with a stand-in index and, in an integration test, the real FAISS factory. It also ran end to end with the real facebook/wav2vec2-xls-r-300m weights over the example recordings on 2026-10-13 (scripts/run_demo_reference_search.py).

06 · Temporal alignment

Subsequence DTW finds where the passage lines up

Each candidate region is compared frame by frame with cosine distance. Subsequence dynamic time warping finds the cheapest monotone path that uses the whole query, allowing the match to be 0.67–1.5× the query’s length. The score is the mean cost along that path.

Why it matters People repeat a phrase faster or slower each time. Warping absorbs tempo; the duration limits stop a whole sentence from collapsing onto one syllable.

On this page

The same algorithm and the same 0.67–1.5× limits over log-mel frames. The evidence panel above draws its actual cost matrix and path.

Reference engine

Then temporal non-maximum suppression at IoU 0.5, permission re-checked before and after alignment, 50 results kept, 20 shown per page.

Implemented as

  • src/tierline/retrieval/dtw.py subsequence_dtw
  • src/tierline/retrieval/nms.py temporal_nms
  • src/tierline/retrieval/search.py search_frames
  • site/js/audio-worker.js alignment

Components: Subsequence-DTW reranking, Temporal-IoU non-maximum suppression

Exercised by

  • tests/unit/test_dtw.py test_identical_query_found_inside_candidate
  • tests/reference/test_dtw_bounds.py test_exact_duration_constraint_is_enforced
  • tests/unit/test_search_policy_bounds.py test_search_rechecks_match_after_reranking

Exercised by the automated test suite and by every search on this page. It also ran end to end with the real facebook/wav2vec2-xls-r-300m weights over the example recordings on 2026-10-13 (scripts/run_demo_reference_search.py).

07 · Human review

A person decides what a match means

A reviewer marks each result relevant, uncertain or not relevant and adds a note. Reviewed results export to ELAN EAF and Praat TextGrid, each export carrying a manifest, a lineage record and an audit event.

Why it matters Acoustic similarity is evidence for a person to weigh, not an interpretation. Export is a separate permission, checked for every result before anything is written.

On this page

Decision and note stay in this browser. Export a JSON record, or a Praat TextGrid laid out like the reference engine’s.

Reference engine

Review history in the local workspace; policy-checked EAF/TextGrid export into a managed directory; downloading is a further explicit step.

Implemented as

  • src/tierline/review/service.py set_review
  • src/tierline/review/service.py export_reviewed_search
  • src/tierline/export/praat.py write_textgrid
  • src/tierline/export/elan.py write_eaf

Components: ELAN EAF export, Praat TextGrid export, Loopback browser API with current-policy playback, context and search adapter

Exercised by

  • tests/policy/test_review_export.py test_review_and_policy_checked_export
  • tests/export_roundtrip/test_exports.py test_textgrid_contains_interval
  • tests/web/test_operator_api.py test_waveform_review_history_and_managed_export

Exercised by the automated test suite and by the review panel on this page.

Paths are relative to the Tierline source tree. The browser engine is linked because this site serves it; the rest of the source tree is described on About.

The full configuration, every component and its interactive explorer are on How it works.

03 — The same passage, reference engine

Run through real XLS-R features

The example passage of I would rather go by submarine (1.16–2.54 s), searched by the reference engine over the same eight recordings: facebook/wav2vec2-xls-r-300m layer 11, PCA, int8, FAISS and subsequence DTW. It ran on 2026-10-13, outside the browser; its complete output ships with this site as demo/reference-run.json.

Top results for bernatitus-01 1.16–2.54 s. Distances are on the reference engine’s own scale and are not comparable with the browser engine’s.
RankRecordingSpanWords in the spanDTW distance
1I would rather go by submarine · bernatitus-011.17–2.55 sgo by submarine,0.0000
2Gotten the message · bernatitus-031.65–2.65 ssubmarine0.5377
3Their own minefield · bernatitus-023.75–4.75 sget to the submarine.0.5616
4Taking us so long to get out there · bernatitus-0410.83–11.97 salready fallen.0.6203
5Home at almost one in the morning · pepys-011.21–2.19 salmost one o'clock in0.6635
6The slapping of the water · bernatitus-062.85–3.87 sof the water against,0.6925

How to read it The engine ranks passages by sound alone. The words beside each result come from the transcript’s estimated word times and are shown only so you can read what it found; the search never saw them. Rank 1 is the passage itself.

Scale With 202 one-second windows in the collection, fewer than the 500 candidates the index returns, every window was a candidate, so the run takes every stage of the pipeline end to end; retrieval accuracy is measured on Evaluation.

Model revision 1a640f32ac3e · weights SHA-256 d5e490574712… · torch 2.14.1+cpu · faiss 1.15.1 · PCA and int8 scales fitted on 2,048 frames of separate calibration recordings, none from this collection

04 — Where Tierline stands

Built, run, evaluated, in use

What Tierline has built, run on real recordings, measured on a test set, and put into use by the people it was built for. What comes next is on Progress.

  1. Implemented

    33

    components in the source tree, from provenance-preserving ingest to consent-aware export.

    Component list
  2. Run on real recordings

    8

    The reference engine ran end to end with the real facebook/wav2vec2-xls-r-300m weights on these 8 recordings (13 October 2026), and a second run returned the same results. Every stage, working on real speech.

    The run’s record
  3. Evaluated

    83.29%

    Hit@5 on 826 held-out positive queries in Tierline’s held-out evaluation, reported 13 October 2026.

    Evaluation
  4. In use

    4

    community-governed archive pilots in operation, each under a signed agreement; 82 people active in a measured 90-day period.

    Partners & archives

05 — Why it matters

Built for archives that belong to someone

Many Indigenous-language and oral-history collections exist mainly as sound: reel-to-reel and cassette interviews, songs, place names, lessons recorded across generations, often with little or no transcription. A speaker’s way of saying a word may survive on a single tape among hundreds of hours.

Search by example lets a language worker hold up a passage and ask where else it was said, then listen to each answer in the recording it came from, with whatever context the community has attached. It needs no transcript and no speech recognizer for the language.

Those recordings carry obligations. Some may be heard only by certain people, at certain times, for certain purposes; some families or nations may decide they should not be searchable at all. Tierline is built so that those decisions are enforced by the software, before anything is searched, played or exported, and stay with the people responsible for the archive.

06 — Check it yourself

The code this page runs

The browser matcher is js/audio-worker.js, served by this site exactly as it runs, and so are the workspace's js/workspace-worker.js and js/workspace-store.js. From a copy of the source, these are the commands its tests and its recorded run use.

Browser engine regression tests

node site/audio_baseline_test.js
node site/explore_worker_test.js

Reference engine

pip install -e ".[reference,web,dev]"
python -m pytest
python scripts/run_reference_smoke.py --work-root ~/tierline-ref --allow-model-download

Full local setup: Roadmap & local setup.

The film The same passage, as a film One passage cut from its recording, carried through the archive and returned to its context. Real-time, with an original score; its results are the reference engine’s recorded run on the same recordings. Watch