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
Search the collection
Computed on this device from the audio files themselves; nothing is uploaded. The eight recordings are excerpts of a 1994 U.S. Navy oral-history interview with the nurse Ann Bernatitus, and of Samuel Pepys’s diary for June 1665 read by Nicole Lee for LibriVox; both are in the public domain. Hear the longer passages they come from in the Listening room.
The search itself needs JavaScript. The recordings below play without it; each is listed with its transcript and the SHA-256 of its file.
I would rather go by submarine
I would rather go by submarine, never dreaming that that was exactly what was going to happen to me.
SHA-256 56f1b18bfbc1a29b787d9dba2b5e7ea4277f9d9bae91783279a82b4f041ce7bf
Hear it in its passage, “Two PBYs came in first”, at 0:40Their own minefield
They had to go through their own minefield to get to the submarine.
SHA-256 44dd853684cf347977b8bd5f42b7dbb78fb219ed838b2856d90a6e3741d9d0f8
Hear it in its passage, “Leaving Corregidor by submarine”, at 0:57Gotten the message
And the submarine had gotten the message.
SHA-256 44355058c42c3dc24d22ee0dfab424ca4ed13705fd11e32765bca68f46968fbf
Hear it in its passage, “Leaving Corregidor by submarine”, at 1:04Taking us so long to get out there
Well, anyway, the submarine wasn't sure if it was taking us so long to get out there that they weren't sure that it hadn't already fallen.
SHA-256 16426739a63b51fe34b9a98370caf8f9e0bc676eb40b3a432935dd963eabd550
Hear it in its passage, “Leaving Corregidor by submarine”, at 1:15Tea and chocolate cake
You know, that's where we sat and they gave us tea and chocolate cake.
SHA-256 c5848c0bc426817c2833511ff5bb2879d0c1c4e232f856cdefc78d33782e24eb
The slapping of the water
And, you know, all you could hear was the slapping of the water against, between the two objects.
SHA-256 5b740851e8037d75ea888b536cf6f4842b858c2b83cdee86c3b242d217aadce3
Hear it in its passage, “Leaving Corregidor by submarine”, at 1:50Home at almost one in the morning
So home at past twelve, almost one o'clock in the morning, to my office till past two, and then home to supper and to bed.
SHA-256 328164dce7b1630bd05eef6c42ca91e1c0aca40ac745216179c93167a34e23f6
Hear it in its passage, “2 June 1665: home at almost one in the morning”, at 0:35Not very late, to supper and to bed
After dinner, to the office again till night, very busy, and so home, not very late, to supper and to bed.
SHA-256 a4bf15426806b35810878cc23cbc00f06e4afe6d1f3f2a16275e30270466b1bf
Hear it in its passage, “22 June 1665: sending my mother into the country”, at 1:01
Hear the source
Choose the passage
Drag across the waveform or move either edge. A search uses 0.5–2.0 s.
Transcript · the words in your passage are marked
Hear it in its passage, “Two PBYs came in first”, at 0:40 Hear it in its passage, “Leaving Corregidor by submarine”, at 0:57 Hear it in its passage, “Leaving Corregidor by submarine”, at 1:04 Hear it in its passage, “Leaving Corregidor by submarine”, at 1:15 Hear it in its passage, “Leaving Corregidor by submarine”, at 1:50 Hear it in its passage, “2 June 1665: home at almost one in the morning”, at 0:35 Hear it in its passage, “22 June 1665: sending my mother into the country”, at 1:01
Search the collection
Matches
Ranked by DTW distance: the mean cosine distance between aligned 20 ms frames. Lower is closer. It is a ranking measure, not a probability.
Evidence
How this match was obtained
Source record
Review
Decide what the match means. Your review stays in this browser until you export it.
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.
| Rank | Recording | Span | Words in the span | DTW distance |
|---|---|---|---|---|
| 1 | I would rather go by submarine · bernatitus-01 | 1.17–2.55 s | go by submarine, | 0.0000 |
| 2 | Gotten the message · bernatitus-03 | 1.65–2.65 s | submarine | 0.5377 |
| 3 | Their own minefield · bernatitus-02 | 3.75–4.75 s | get to the submarine. | 0.5616 |
| 4 | Taking us so long to get out there · bernatitus-04 | 10.83–11.97 s | already fallen. | 0.6203 |
| 5 | Home at almost one in the morning · pepys-01 | 1.21–2.19 s | almost one o'clock in | 0.6635 |
| 6 | The slapping of the water · bernatitus-06 | 2.85–3.87 s | of 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.
Implemented
33
components in the source tree, from provenance-preserving ingest to consent-aware export.
Component listRun 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 recordEvaluated
83.29%
Hit@5 on 826 held-out positive queries in Tierline’s held-out evaluation, reported 13 October 2026.
EvaluationIn 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.