nyra-forced-aligner 0.2.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. nyra_forced_aligner-0.2.1/LICENSE +26 -0
  2. nyra_forced_aligner-0.2.1/PKG-INFO +290 -0
  3. nyra_forced_aligner-0.2.1/README.md +254 -0
  4. nyra_forced_aligner-0.2.1/nyra_align/__init__.py +18 -0
  5. nyra_forced_aligner-0.2.1/nyra_align/aligner.py +257 -0
  6. nyra_forced_aligner-0.2.1/nyra_align/cli.py +96 -0
  7. nyra_forced_aligner-0.2.1/nyra_align/decoder.py +193 -0
  8. nyra_forced_aligner-0.2.1/nyra_align/decoder_gpu.py +165 -0
  9. nyra_forced_aligner-0.2.1/nyra_align/decoder_jit.py +166 -0
  10. nyra_forced_aligner-0.2.1/nyra_align/export.py +233 -0
  11. nyra_forced_aligner-0.2.1/nyra_align/features.py +190 -0
  12. nyra_forced_aligner-0.2.1/nyra_align/g2p.py +216 -0
  13. nyra_forced_aligner-0.2.1/nyra_align/graph.py +251 -0
  14. nyra_forced_aligner-0.2.1/nyra_align/kaldi_text.py +301 -0
  15. nyra_forced_aligner-0.2.1/nyra_align/model.py +131 -0
  16. nyra_forced_aligner-0.2.1/nyra_align/outputs.py +117 -0
  17. nyra_forced_aligner-0.2.1/nyra_align/version.py +1 -0
  18. nyra_forced_aligner-0.2.1/nyra_forced_aligner.egg-info/PKG-INFO +290 -0
  19. nyra_forced_aligner-0.2.1/nyra_forced_aligner.egg-info/SOURCES.txt +24 -0
  20. nyra_forced_aligner-0.2.1/nyra_forced_aligner.egg-info/dependency_links.txt +1 -0
  21. nyra_forced_aligner-0.2.1/nyra_forced_aligner.egg-info/entry_points.txt +4 -0
  22. nyra_forced_aligner-0.2.1/nyra_forced_aligner.egg-info/requires.txt +12 -0
  23. nyra_forced_aligner-0.2.1/nyra_forced_aligner.egg-info/top_level.txt +1 -0
  24. nyra_forced_aligner-0.2.1/pyproject.toml +59 -0
  25. nyra_forced_aligner-0.2.1/setup.cfg +4 -0
  26. nyra_forced_aligner-0.2.1/tests/test_core.py +165 -0
@@ -0,0 +1,26 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 nyra health GmbH
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
23
+ This MIT License covers the inference code in this repository only. The
24
+ model files distributed on the Hugging Face Hub (nyralabs/nyra_forced_aligner_*)
25
+ and the Outputs they generate are licensed separately under the nyra health
26
+ Non-Commercial Research License; see the LICENSE.md in each model repository.
@@ -0,0 +1,290 @@
1
+ Metadata-Version: 2.4
2
+ Name: nyra-forced-aligner
3
+ Version: 0.2.1
4
+ Summary: Verbatim-aware, noise-robust forced alignment: precise word and phone timestamps for conversational speech, including fillers and vocal sounds
5
+ Author: nyra health GmbH
6
+ License: MIT
7
+ Project-URL: Homepage, https://nyra-labs.com/research/nyra-forced-aligner
8
+ Project-URL: Repository, https://github.com/nyrahealth/nyra-forced-aligner
9
+ Project-URL: Models, https://huggingface.co/nyralabs
10
+ Keywords: forced alignment,speech,word timestamps,phonetics,disfluency,wavlm
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Classifier: Topic :: Multimedia :: Sound/Audio :: Speech
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Requires-Dist: numpy>=1.24
25
+ Requires-Dist: torch>=2.0
26
+ Requires-Dist: soundfile>=0.12
27
+ Requires-Dist: soxr>=0.3
28
+ Requires-Dist: transformers>=4.40
29
+ Requires-Dist: huggingface_hub>=0.20
30
+ Requires-Dist: phonemizer>=3.2
31
+ Requires-Dist: numba>=0.58
32
+ Requires-Dist: tqdm
33
+ Provides-Extra: dev
34
+ Requires-Dist: pytest; extra == "dev"
35
+ Dynamic: license-file
36
+
37
+ # nyra-forced-aligner
38
+
39
+ [![Models](https://img.shields.io/badge/%F0%9F%A4%97%20models-nyralabs-yellow)](https://huggingface.co/nyralabs)
40
+
41
+ **The most accurate word timing you can get for conversational speech:
42
+ verbatim-aware, noise-robust, and fast.**
43
+
44
+ [Blog post](https://nyra-labs.com/research/nyra-forced-aligner) ·
45
+ [Paper](#) <!-- TODO: ICASSP 2026 link --> ·
46
+ [Models](https://huggingface.co/nyralabs) ·
47
+ [CrisperWhisper](https://github.com/nyrahealth/CrisperWhisper)
48
+
49
+ A forced aligner answers one question: given a recording and its transcript,
50
+ *when* was each word said? Most aligners were built for read speech and clean
51
+ audio, and they quietly fall apart on the material people actually need to
52
+ align: conversations full of `um`s, laughter, restarts and half-finished words,
53
+ recorded in rooms that are not studios.
54
+
55
+ `nyra-forced-aligner` was built for exactly that material.
56
+
57
+ - **Vocal sounds are alignable.** Fillers (`[UH]`, `[UM]`), laughter, breaths,
58
+ coughs and word cut-offs (`th-`) are units of the model like any phone,
59
+ so they get timestamps of their own instead of being smeared into the
60
+ neighbouring words.
61
+ - **Made for CrisperWhisper transcripts.** Trained on the verbatim
62
+ transcription convention of
63
+ [CrisperWhisper 2.0](https://github.com/nyrahealth/CrisperWhisper): feed
64
+ its output straight in and every filler, repetition and event token is
65
+ aligned.
66
+ - **Accurate where it matters.** 19.9 ms mean word-boundary error on
67
+ conversational English (Buckeye): 21% lower than the best configuration of
68
+ the Montreal Forced Aligner, half the error of the best ASR-with-timestamps
69
+ system, and the only system whose error tail drops below 1% of words before
70
+ 100 ms.
71
+ - **Noise-robust.** Every utterance stays aligned down to 0 dB SNR, where
72
+ classical aligners lose or mangle most of them. A Pro model goes further for
73
+ difficult recordings.
74
+ - **Fast and longform.** Batched WavLM features, GPU emissions and a compiled
75
+ beam Viterbi: eight minutes of audio align in about three seconds on one
76
+ GPU.
77
+
78
+ ## Install
79
+
80
+ ```bash
81
+ pip install nyra-forced-aligner
82
+
83
+ # espeak-ng is needed to pronounce words outside the built-in 41k-word lexicon:
84
+ # apt install espeak-ng (macOS: brew install espeak-ng)
85
+ ```
86
+
87
+ Runs on CPU, but a CUDA GPU is strongly recommended (WavLM-large front-end).
88
+ The model is downloaded from the Hugging Face Hub on first use.
89
+
90
+ ## Quickstart
91
+
92
+ ```python
93
+ from nyra_align import Aligner
94
+
95
+ aligner = Aligner() # nyralabs/nyra_forced_aligner_en
96
+ # aligner = Aligner(pro=True) # noise-robust model
97
+
98
+ result = aligner.align("interview.wav",
99
+ "so i [UM] i went there on on thursday [laughter]")
100
+
101
+ for w in result.words:
102
+ print(f"{w.start:7.3f} {w.end:7.3f} {w.word:<12s} {w.type}") # w / f(iller) / s(ound) / c(ut-off)
103
+
104
+ result.to_textgrid("interview.TextGrid") # Praat
105
+ result.to_json("interview.json")
106
+ ```
107
+
108
+ Any audio length works. For the best timings follow the
109
+ [transcript conventions](#transcript-conventions) below.
110
+
111
+ Command line, single file or a whole corpus of paired `.wav` + `.txt`/`.lab`
112
+ files (MFA-style layout):
113
+
114
+ ```bash
115
+ nyra-align interview.wav --text-file interview.txt --out interview.TextGrid
116
+ nyra-align corpus_dir/ --pro --out-dir aligned/ --format textgrid
117
+ ```
118
+
119
+ ### Transcript conventions
120
+
121
+ The aligner can only place what the transcript contains, so for the best
122
+ timings the transcript should be **verbatim: write what you hear.**
123
+
124
+ - **Cut-offs.** Interrupted words and word fragments are marked with a
125
+ trailing hyphen: `th-`, `w-`, `resched-`.
126
+ - **Fillers.** Filled pauses are bracketed and upper-cased: `[UH]`, `[UM]`.
127
+ - **Vocal sound events.** Non-speech sounds are bracketed: `[laughter]`,
128
+ `[cough]`, `[breath]`, `[sigh]`, `[sniff]`, `[lipsmack]`,
129
+ `[throatclearing]`, `[yawn]`, `[noise]`.
130
+ - **Numbers, dates, times and emails** are written out exactly as spoken
131
+ (`March third at nine thirty`), not as digits or symbols.
132
+ - **Everything else** is transcribed word for word. Normal casing and
133
+ punctuation are fine (the aligner ignores both); repetitions, false starts,
134
+ fillers, fragments and sounds are all left in place.
135
+
136
+ ```text
137
+ so we we need to, to reschedule the th- Thursday meeting to [UH] March third at nine thirty [laughter]
138
+ ```
139
+
140
+ What happens when a transcript deviates: a word that was spoken but is missing
141
+ from the transcript gets absorbed into its neighbours, and a word in the
142
+ transcript that was never spoken is squeezed into a few frames, so both shift
143
+ the surrounding boundaries. Event tags are matched in any casing; a tag the
144
+ model does not know (`[music]`) is left out and listed in `result.skipped`,
145
+ as is any token that cannot be pronounced.
146
+
147
+ ### With CrisperWhisper 2.0
148
+
149
+ [CrisperWhisper 2.0](https://github.com/nyrahealth/CrisperWhisper) produces
150
+ transcripts in exactly this convention, so its verbatim output can be aligned
151
+ as is:
152
+
153
+ ```python
154
+ from crisperwhisper import CrisperWhisperModel
155
+ from nyra_align import Aligner
156
+
157
+ audio = "interview.wav"
158
+ transcript = CrisperWhisperModel("large").transcribe(audio, language="en").text
159
+ result = Aligner().align(audio, transcript)
160
+
161
+ for w in result.words:
162
+ print(f"{w.start:7.3f} {w.end:7.3f} {w.word}")
163
+ result.to_textgrid("interview.TextGrid")
164
+ ```
165
+
166
+ ### Models
167
+
168
+ | Shorthand | Hugging Face ID | Use it for |
169
+ |-----------|-----------------|------------|
170
+ | `Aligner()` (default) | `nyralabs/nyra_forced_aligner_en` | Clean and lightly noisy audio; most accurate on clean speech |
171
+ | `Aligner(pro=True)` | `nyralabs/nyra_forced_aligner_en_pro` | **Pro**: roughly the same accuracy on clean speech, meaningfully more robust to noise and adverse acoustic conditions. Not publicly released; available on request |
172
+
173
+ Only English is available at the moment; `Aligner(language="de")` raises
174
+ `NotImplementedError`.
175
+
176
+ ## Performance
177
+
178
+ Mean absolute word-boundary error on **Buckeye** (conversational English,
179
+ 2,008 test recordings, 80,475 words), lower is better. Every system is scored
180
+ with the same protocol: DP word matching on canonicalized forms, laughter
181
+ tokens excluded, MAE = mean over words of (|onset error| + |offset error|)/2.
182
+
183
+ | # | System | Type | MAE (ms) | MedAE | ≤25 ms | ≤50 ms | ≤100 ms | mIoU |
184
+ |--:|--------|------|---------:|------:|-------:|-------:|--------:|-----:|
185
+ | 1 | **nyra_forced_aligner_en** | forced aligner | **19.9** | 13.9 | **79.5%** | **93.7%** | **98.3%** | **0.830** |
186
+ | 2 | Montreal Forced Aligner `english_us_arpa`, wide beam¹ | forced aligner | 25.3 | 13.0 | 76.7% | 92.2% | 97.3% | 0.826 |
187
+ | 3 | Montreal Forced Aligner `english_mfa`, wide beam¹ | forced aligner | 35.6 | 15.0 | 71.3% | 87.5% | 95.2% | 0.790 |
188
+ | 4 | CrisperWhisper 2.0 | ASR | 38.7 | 25.0 | 54.2% | 78.7% | 91.7% | 0.717 |
189
+ | 5 | Qwen3-ForcedAligner-0.6B | forced aligner | 39.5 | 25.0 | 52.8% | 85.9% | 96.6% | 0.711 |
190
+ | 6 | xAI Grok Speech-to-Text | ASR | 45.5 | 34.5 | 58.3% | 82.8% | 94.0% | 0.630 |
191
+ | 7 | Montreal Forced Aligner `english_us_arpa`, default beam | forced aligner | 48.0 | 13.5 | 75.2% | 90.4% | 95.6% | 0.810 |
192
+ | 8 | Montreal Forced Aligner `english_mfa`, default beam | forced aligner | 48.9 | 15.5 | 70.7% | 86.7% | 94.3% | 0.782 |
193
+ | 9 | MMS-FA (torchaudio) | forced aligner | 53.0 | 34.9 | 57.5% | 81.3% | 92.8% | 0.620 |
194
+ | 10 | ElevenLabs Scribe v2 | ASR | 57.8 | 50.0 | 47.6% | 75.8% | 94.8% | 0.540 |
195
+ | 11 | Charsiu | forced aligner | 70.3 | 28.0 | 51.8% | 68.5% | 85.1% | 0.649 |
196
+ | 12 | NeMo Forced Aligner | forced aligner | 72.6 | 62.5 | 26.8% | 49.8% | 79.3% | 0.443 |
197
+ | 13 | WhisperX (wav2vec2 alignment) | forced aligner | 80.9 | 61.5 | 35.1% | 68.1% | 94.1% | 0.456 |
198
+ | 14 | CTC-Segmentation | forced aligner | 81.0 | 36.0 | 41.5% | 69.5% | 89.3% | 0.635 |
199
+ | 15 | Deepgram Nova-3 | ASR | 86.9 | 71.0 | 18.1% | 36.4% | 68.9% | 0.496 |
200
+ | 16 | Cartesia Ink-Whisper | ASR | 124.8 | 46.3 | 36.0% | 60.4% | 82.8% | 0.598 |
201
+
202
+ <sub>**MAE** and **MedAE** are over the per-word error (|onset error| +
203
+ |offset error|)/2. The **≤ t ms** columns follow the convention of Rousso et
204
+ al. (2024): the share of words whose *end* timestamp is within t of the
205
+ reference (one boundary, not the per-word mean). **Forced aligners** are given
206
+ the reference words and only predict their timing; every word is scored. **ASR** systems produce their own words, which
207
+ are text-aligned to the reference, and only matched words are scored
208
+ (88–93% of words), so their timing is judged independently of their
209
+ transcription errors. ¹ MFA's shipped default beam (10) prunes the correct
210
+ path on conversational speech; the "wide beam" rows use `beam 400 / retry
211
+ 4000`, the best configuration we found for it.</sub>
212
+
213
+ The mean hides where the difference really is. Averaged over all words, most
214
+ systems are within a few tens of milliseconds; the gap is in the **tail**, the
215
+ words that are misaligned by 100 ms or more, which is what breaks downstream
216
+ use. Each curve shows the share of words whose per-word error (mean of the
217
+ onset and offset errors, the quantity behind MAE) is larger than *t*; note
218
+ this is not the single-boundary ≤ t ms column of the table:
219
+
220
+ ![Buckeye word-boundary error tails](https://huggingface.co/nyralabs/nyra_forced_aligner_en/resolve/main/docs/buckeye_ccdf.png)
221
+
222
+ ### Under noise
223
+
224
+ The same Buckeye recordings with added noise, at signal-to-noise ratios from
225
+ 20 dB down to 0 dB (MAE in ms). MUSAN is real noise recordings; Gaussian is
226
+ white noise, which the model never saw in training.
227
+
228
+ | System | MUSAN 20 | 10 | 5 | 0 dB | Gauss 20 | 10 | 5 | 0 dB |
229
+ |--------|---:|---:|---:|---:|---:|---:|---:|---:|
230
+ | **nyra_forced_aligner_en** | 20.0 | 22.2 | 26.9 | 56.3 | 20.2 | 25.3 | 38.9 | 110.9 |
231
+ | CrisperWhisper 2.0² | 38.7 | 42.4 | 48.0 | 55.7 | 40.1 | 50.9 | 67.3 | 103.4 |
232
+ | Qwen3-ForcedAligner-0.6B | 40.6 | 54.3 | 78.7 | 153.9 | 43.4 | 85.2 | 153.9 | 339.2 |
233
+ | MMS-FA (torchaudio) | 52.8 | 54.9 | 57.7 | 67.0 | 52.0 | 55.2 | 61.0 | 86.1 |
234
+ | Montreal Forced Aligner `english_us_arpa`, wide beam | 33.3 | 65.3 | 127.2 | 281.2 | 36.2 | 74.4 | 156.3 | 416.9 |
235
+ | WhisperX (wav2vec2 alignment) | 81.7 | 90.4 | 138.1 | 468.5 | 84.2 | 126.3 | 415.2 | 2720.6 |
236
+
237
+ <sub>² CrisperWhisper 2.0 is an ASR system: at 0 dB it still recognises only
238
+ 29% of the words, and only those are scored. All forced aligners above keep
239
+ 100% of words at every SNR.</sub>
240
+
241
+ `nyra_forced_aligner_en_pro` delivers roughly the same accuracy on clean speech
242
+ and is meaningfully more robust to noise and adverse acoustic conditions.
243
+
244
+ Results on read speech (TIMIT) and stuttered speech (FluencyBank), the full
245
+ set of ablations, and how the models are trained are in the paper.
246
+
247
+ ## What else is in the box
248
+
249
+ | Option | What it does |
250
+ |--------|--------------|
251
+ | `Aligner(pro=True)` | Noise-robust model |
252
+ | `Aligner(device="cuda" / "cpu")` | Device selection (auto by default) |
253
+ | `Aligner(precision="fp32")` | WavLM in fp32 instead of fp16; bit-exact parity with the reference implementation (fp16 moves ≤30 ms on ~0.5% of word boundaries) |
254
+ | `Aligner(wavlm_batch=16)` | Batch size for the 30 s WavLM windows on long audio (tune to VRAM) |
255
+ | `aligner.align_features(feats, text)` | Re-align a transcript against cached features (decode is ~6 ms per 3.5 s utterance) |
256
+ | `result.events` | Just the filler / laughter / cut-off tokens |
257
+ | `result.silences` | Silence segments between words |
258
+
259
+ ## How it works
260
+
261
+ The full story is in the deep-dive post:
262
+
263
+ - [The nyra forced aligner: precise word timing for real conversational speech](https://nyra-labs.com/research/nyra-forced-aligner).
264
+ Why classical aligners break on spontaneous and noisy speech, how frozen
265
+ WavLM features, vocal-event units and a noise-pooled projection fix it, and
266
+ how the model bootstraps itself from verbatim transcripts without any
267
+ manual boundary labels.
268
+
269
+ In short: WavLM-large hidden states are projected by a supervised LDA onto 40
270
+ dimensions and modelled by a triphone GMM-HMM whose inventory holds 15
271
+ vocal-event units next to the 39 English phones. At inference the package
272
+ builds the alignment graph for the transcript (optional silence between words,
273
+ full context across word boundaries) and runs a compiled beam Viterbi over the
274
+ model's likelihoods, with a beam ladder so that no utterance is ever silently
275
+ dropped. Words outside the built-in lexicon are phonemized with espeak through
276
+ the same mapping used at training time.
277
+
278
+ ## License
279
+
280
+ The model files are released under the
281
+ [nyra health Non-Commercial Research License](https://huggingface.co/nyralabs/nyra_forced_aligner_en/blob/main/LICENSE.md):
282
+ free for research and other non-commercial use. This license covers the
283
+ model **and the alignments it produces**: any commercial use of the
284
+ timestamps, and any use of them to train, fine-tune or otherwise improve
285
+ models intended for commercial use, requires a commercial license. The Pro
286
+ model is available under commercial license only. For commercial licensing
287
+ of either, [contact nyra health](mailto:licensing@nyra-labs.com).
288
+
289
+ The model repositories bundle Microsoft's WavLM-large weights, redistributed
290
+ unchanged under their upstream license.
@@ -0,0 +1,254 @@
1
+ # nyra-forced-aligner
2
+
3
+ [![Models](https://img.shields.io/badge/%F0%9F%A4%97%20models-nyralabs-yellow)](https://huggingface.co/nyralabs)
4
+
5
+ **The most accurate word timing you can get for conversational speech:
6
+ verbatim-aware, noise-robust, and fast.**
7
+
8
+ [Blog post](https://nyra-labs.com/research/nyra-forced-aligner) ·
9
+ [Paper](#) <!-- TODO: ICASSP 2026 link --> ·
10
+ [Models](https://huggingface.co/nyralabs) ·
11
+ [CrisperWhisper](https://github.com/nyrahealth/CrisperWhisper)
12
+
13
+ A forced aligner answers one question: given a recording and its transcript,
14
+ *when* was each word said? Most aligners were built for read speech and clean
15
+ audio, and they quietly fall apart on the material people actually need to
16
+ align: conversations full of `um`s, laughter, restarts and half-finished words,
17
+ recorded in rooms that are not studios.
18
+
19
+ `nyra-forced-aligner` was built for exactly that material.
20
+
21
+ - **Vocal sounds are alignable.** Fillers (`[UH]`, `[UM]`), laughter, breaths,
22
+ coughs and word cut-offs (`th-`) are units of the model like any phone,
23
+ so they get timestamps of their own instead of being smeared into the
24
+ neighbouring words.
25
+ - **Made for CrisperWhisper transcripts.** Trained on the verbatim
26
+ transcription convention of
27
+ [CrisperWhisper 2.0](https://github.com/nyrahealth/CrisperWhisper): feed
28
+ its output straight in and every filler, repetition and event token is
29
+ aligned.
30
+ - **Accurate where it matters.** 19.9 ms mean word-boundary error on
31
+ conversational English (Buckeye): 21% lower than the best configuration of
32
+ the Montreal Forced Aligner, half the error of the best ASR-with-timestamps
33
+ system, and the only system whose error tail drops below 1% of words before
34
+ 100 ms.
35
+ - **Noise-robust.** Every utterance stays aligned down to 0 dB SNR, where
36
+ classical aligners lose or mangle most of them. A Pro model goes further for
37
+ difficult recordings.
38
+ - **Fast and longform.** Batched WavLM features, GPU emissions and a compiled
39
+ beam Viterbi: eight minutes of audio align in about three seconds on one
40
+ GPU.
41
+
42
+ ## Install
43
+
44
+ ```bash
45
+ pip install nyra-forced-aligner
46
+
47
+ # espeak-ng is needed to pronounce words outside the built-in 41k-word lexicon:
48
+ # apt install espeak-ng (macOS: brew install espeak-ng)
49
+ ```
50
+
51
+ Runs on CPU, but a CUDA GPU is strongly recommended (WavLM-large front-end).
52
+ The model is downloaded from the Hugging Face Hub on first use.
53
+
54
+ ## Quickstart
55
+
56
+ ```python
57
+ from nyra_align import Aligner
58
+
59
+ aligner = Aligner() # nyralabs/nyra_forced_aligner_en
60
+ # aligner = Aligner(pro=True) # noise-robust model
61
+
62
+ result = aligner.align("interview.wav",
63
+ "so i [UM] i went there on on thursday [laughter]")
64
+
65
+ for w in result.words:
66
+ print(f"{w.start:7.3f} {w.end:7.3f} {w.word:<12s} {w.type}") # w / f(iller) / s(ound) / c(ut-off)
67
+
68
+ result.to_textgrid("interview.TextGrid") # Praat
69
+ result.to_json("interview.json")
70
+ ```
71
+
72
+ Any audio length works. For the best timings follow the
73
+ [transcript conventions](#transcript-conventions) below.
74
+
75
+ Command line, single file or a whole corpus of paired `.wav` + `.txt`/`.lab`
76
+ files (MFA-style layout):
77
+
78
+ ```bash
79
+ nyra-align interview.wav --text-file interview.txt --out interview.TextGrid
80
+ nyra-align corpus_dir/ --pro --out-dir aligned/ --format textgrid
81
+ ```
82
+
83
+ ### Transcript conventions
84
+
85
+ The aligner can only place what the transcript contains, so for the best
86
+ timings the transcript should be **verbatim: write what you hear.**
87
+
88
+ - **Cut-offs.** Interrupted words and word fragments are marked with a
89
+ trailing hyphen: `th-`, `w-`, `resched-`.
90
+ - **Fillers.** Filled pauses are bracketed and upper-cased: `[UH]`, `[UM]`.
91
+ - **Vocal sound events.** Non-speech sounds are bracketed: `[laughter]`,
92
+ `[cough]`, `[breath]`, `[sigh]`, `[sniff]`, `[lipsmack]`,
93
+ `[throatclearing]`, `[yawn]`, `[noise]`.
94
+ - **Numbers, dates, times and emails** are written out exactly as spoken
95
+ (`March third at nine thirty`), not as digits or symbols.
96
+ - **Everything else** is transcribed word for word. Normal casing and
97
+ punctuation are fine (the aligner ignores both); repetitions, false starts,
98
+ fillers, fragments and sounds are all left in place.
99
+
100
+ ```text
101
+ so we we need to, to reschedule the th- Thursday meeting to [UH] March third at nine thirty [laughter]
102
+ ```
103
+
104
+ What happens when a transcript deviates: a word that was spoken but is missing
105
+ from the transcript gets absorbed into its neighbours, and a word in the
106
+ transcript that was never spoken is squeezed into a few frames, so both shift
107
+ the surrounding boundaries. Event tags are matched in any casing; a tag the
108
+ model does not know (`[music]`) is left out and listed in `result.skipped`,
109
+ as is any token that cannot be pronounced.
110
+
111
+ ### With CrisperWhisper 2.0
112
+
113
+ [CrisperWhisper 2.0](https://github.com/nyrahealth/CrisperWhisper) produces
114
+ transcripts in exactly this convention, so its verbatim output can be aligned
115
+ as is:
116
+
117
+ ```python
118
+ from crisperwhisper import CrisperWhisperModel
119
+ from nyra_align import Aligner
120
+
121
+ audio = "interview.wav"
122
+ transcript = CrisperWhisperModel("large").transcribe(audio, language="en").text
123
+ result = Aligner().align(audio, transcript)
124
+
125
+ for w in result.words:
126
+ print(f"{w.start:7.3f} {w.end:7.3f} {w.word}")
127
+ result.to_textgrid("interview.TextGrid")
128
+ ```
129
+
130
+ ### Models
131
+
132
+ | Shorthand | Hugging Face ID | Use it for |
133
+ |-----------|-----------------|------------|
134
+ | `Aligner()` (default) | `nyralabs/nyra_forced_aligner_en` | Clean and lightly noisy audio; most accurate on clean speech |
135
+ | `Aligner(pro=True)` | `nyralabs/nyra_forced_aligner_en_pro` | **Pro**: roughly the same accuracy on clean speech, meaningfully more robust to noise and adverse acoustic conditions. Not publicly released; available on request |
136
+
137
+ Only English is available at the moment; `Aligner(language="de")` raises
138
+ `NotImplementedError`.
139
+
140
+ ## Performance
141
+
142
+ Mean absolute word-boundary error on **Buckeye** (conversational English,
143
+ 2,008 test recordings, 80,475 words), lower is better. Every system is scored
144
+ with the same protocol: DP word matching on canonicalized forms, laughter
145
+ tokens excluded, MAE = mean over words of (|onset error| + |offset error|)/2.
146
+
147
+ | # | System | Type | MAE (ms) | MedAE | ≤25 ms | ≤50 ms | ≤100 ms | mIoU |
148
+ |--:|--------|------|---------:|------:|-------:|-------:|--------:|-----:|
149
+ | 1 | **nyra_forced_aligner_en** | forced aligner | **19.9** | 13.9 | **79.5%** | **93.7%** | **98.3%** | **0.830** |
150
+ | 2 | Montreal Forced Aligner `english_us_arpa`, wide beam¹ | forced aligner | 25.3 | 13.0 | 76.7% | 92.2% | 97.3% | 0.826 |
151
+ | 3 | Montreal Forced Aligner `english_mfa`, wide beam¹ | forced aligner | 35.6 | 15.0 | 71.3% | 87.5% | 95.2% | 0.790 |
152
+ | 4 | CrisperWhisper 2.0 | ASR | 38.7 | 25.0 | 54.2% | 78.7% | 91.7% | 0.717 |
153
+ | 5 | Qwen3-ForcedAligner-0.6B | forced aligner | 39.5 | 25.0 | 52.8% | 85.9% | 96.6% | 0.711 |
154
+ | 6 | xAI Grok Speech-to-Text | ASR | 45.5 | 34.5 | 58.3% | 82.8% | 94.0% | 0.630 |
155
+ | 7 | Montreal Forced Aligner `english_us_arpa`, default beam | forced aligner | 48.0 | 13.5 | 75.2% | 90.4% | 95.6% | 0.810 |
156
+ | 8 | Montreal Forced Aligner `english_mfa`, default beam | forced aligner | 48.9 | 15.5 | 70.7% | 86.7% | 94.3% | 0.782 |
157
+ | 9 | MMS-FA (torchaudio) | forced aligner | 53.0 | 34.9 | 57.5% | 81.3% | 92.8% | 0.620 |
158
+ | 10 | ElevenLabs Scribe v2 | ASR | 57.8 | 50.0 | 47.6% | 75.8% | 94.8% | 0.540 |
159
+ | 11 | Charsiu | forced aligner | 70.3 | 28.0 | 51.8% | 68.5% | 85.1% | 0.649 |
160
+ | 12 | NeMo Forced Aligner | forced aligner | 72.6 | 62.5 | 26.8% | 49.8% | 79.3% | 0.443 |
161
+ | 13 | WhisperX (wav2vec2 alignment) | forced aligner | 80.9 | 61.5 | 35.1% | 68.1% | 94.1% | 0.456 |
162
+ | 14 | CTC-Segmentation | forced aligner | 81.0 | 36.0 | 41.5% | 69.5% | 89.3% | 0.635 |
163
+ | 15 | Deepgram Nova-3 | ASR | 86.9 | 71.0 | 18.1% | 36.4% | 68.9% | 0.496 |
164
+ | 16 | Cartesia Ink-Whisper | ASR | 124.8 | 46.3 | 36.0% | 60.4% | 82.8% | 0.598 |
165
+
166
+ <sub>**MAE** and **MedAE** are over the per-word error (|onset error| +
167
+ |offset error|)/2. The **≤ t ms** columns follow the convention of Rousso et
168
+ al. (2024): the share of words whose *end* timestamp is within t of the
169
+ reference (one boundary, not the per-word mean). **Forced aligners** are given
170
+ the reference words and only predict their timing; every word is scored. **ASR** systems produce their own words, which
171
+ are text-aligned to the reference, and only matched words are scored
172
+ (88–93% of words), so their timing is judged independently of their
173
+ transcription errors. ¹ MFA's shipped default beam (10) prunes the correct
174
+ path on conversational speech; the "wide beam" rows use `beam 400 / retry
175
+ 4000`, the best configuration we found for it.</sub>
176
+
177
+ The mean hides where the difference really is. Averaged over all words, most
178
+ systems are within a few tens of milliseconds; the gap is in the **tail**, the
179
+ words that are misaligned by 100 ms or more, which is what breaks downstream
180
+ use. Each curve shows the share of words whose per-word error (mean of the
181
+ onset and offset errors, the quantity behind MAE) is larger than *t*; note
182
+ this is not the single-boundary ≤ t ms column of the table:
183
+
184
+ ![Buckeye word-boundary error tails](https://huggingface.co/nyralabs/nyra_forced_aligner_en/resolve/main/docs/buckeye_ccdf.png)
185
+
186
+ ### Under noise
187
+
188
+ The same Buckeye recordings with added noise, at signal-to-noise ratios from
189
+ 20 dB down to 0 dB (MAE in ms). MUSAN is real noise recordings; Gaussian is
190
+ white noise, which the model never saw in training.
191
+
192
+ | System | MUSAN 20 | 10 | 5 | 0 dB | Gauss 20 | 10 | 5 | 0 dB |
193
+ |--------|---:|---:|---:|---:|---:|---:|---:|---:|
194
+ | **nyra_forced_aligner_en** | 20.0 | 22.2 | 26.9 | 56.3 | 20.2 | 25.3 | 38.9 | 110.9 |
195
+ | CrisperWhisper 2.0² | 38.7 | 42.4 | 48.0 | 55.7 | 40.1 | 50.9 | 67.3 | 103.4 |
196
+ | Qwen3-ForcedAligner-0.6B | 40.6 | 54.3 | 78.7 | 153.9 | 43.4 | 85.2 | 153.9 | 339.2 |
197
+ | MMS-FA (torchaudio) | 52.8 | 54.9 | 57.7 | 67.0 | 52.0 | 55.2 | 61.0 | 86.1 |
198
+ | Montreal Forced Aligner `english_us_arpa`, wide beam | 33.3 | 65.3 | 127.2 | 281.2 | 36.2 | 74.4 | 156.3 | 416.9 |
199
+ | WhisperX (wav2vec2 alignment) | 81.7 | 90.4 | 138.1 | 468.5 | 84.2 | 126.3 | 415.2 | 2720.6 |
200
+
201
+ <sub>² CrisperWhisper 2.0 is an ASR system: at 0 dB it still recognises only
202
+ 29% of the words, and only those are scored. All forced aligners above keep
203
+ 100% of words at every SNR.</sub>
204
+
205
+ `nyra_forced_aligner_en_pro` delivers roughly the same accuracy on clean speech
206
+ and is meaningfully more robust to noise and adverse acoustic conditions.
207
+
208
+ Results on read speech (TIMIT) and stuttered speech (FluencyBank), the full
209
+ set of ablations, and how the models are trained are in the paper.
210
+
211
+ ## What else is in the box
212
+
213
+ | Option | What it does |
214
+ |--------|--------------|
215
+ | `Aligner(pro=True)` | Noise-robust model |
216
+ | `Aligner(device="cuda" / "cpu")` | Device selection (auto by default) |
217
+ | `Aligner(precision="fp32")` | WavLM in fp32 instead of fp16; bit-exact parity with the reference implementation (fp16 moves ≤30 ms on ~0.5% of word boundaries) |
218
+ | `Aligner(wavlm_batch=16)` | Batch size for the 30 s WavLM windows on long audio (tune to VRAM) |
219
+ | `aligner.align_features(feats, text)` | Re-align a transcript against cached features (decode is ~6 ms per 3.5 s utterance) |
220
+ | `result.events` | Just the filler / laughter / cut-off tokens |
221
+ | `result.silences` | Silence segments between words |
222
+
223
+ ## How it works
224
+
225
+ The full story is in the deep-dive post:
226
+
227
+ - [The nyra forced aligner: precise word timing for real conversational speech](https://nyra-labs.com/research/nyra-forced-aligner).
228
+ Why classical aligners break on spontaneous and noisy speech, how frozen
229
+ WavLM features, vocal-event units and a noise-pooled projection fix it, and
230
+ how the model bootstraps itself from verbatim transcripts without any
231
+ manual boundary labels.
232
+
233
+ In short: WavLM-large hidden states are projected by a supervised LDA onto 40
234
+ dimensions and modelled by a triphone GMM-HMM whose inventory holds 15
235
+ vocal-event units next to the 39 English phones. At inference the package
236
+ builds the alignment graph for the transcript (optional silence between words,
237
+ full context across word boundaries) and runs a compiled beam Viterbi over the
238
+ model's likelihoods, with a beam ladder so that no utterance is ever silently
239
+ dropped. Words outside the built-in lexicon are phonemized with espeak through
240
+ the same mapping used at training time.
241
+
242
+ ## License
243
+
244
+ The model files are released under the
245
+ [nyra health Non-Commercial Research License](https://huggingface.co/nyralabs/nyra_forced_aligner_en/blob/main/LICENSE.md):
246
+ free for research and other non-commercial use. This license covers the
247
+ model **and the alignments it produces**: any commercial use of the
248
+ timestamps, and any use of them to train, fine-tune or otherwise improve
249
+ models intended for commercial use, requires a commercial license. The Pro
250
+ model is available under commercial license only. For commercial licensing
251
+ of either, [contact nyra health](mailto:licensing@nyra-labs.com).
252
+
253
+ The model repositories bundle Microsoft's WavLM-large weights, redistributed
254
+ unchanged under their upstream license.
@@ -0,0 +1,18 @@
1
+ """nyra-align: verbatim-aware, noise-robust forced alignment without Kaldi.
2
+
3
+ from nyra_align import Aligner
4
+ aligner = Aligner() # English, published model, device auto
5
+ aligner = Aligner(pro=True) # noise-robust variant
6
+ result = aligner.align("audio.wav", "so i uhm [laughter] went there")
7
+ result.words # Word(word, start, end, type, phones)
8
+ result.to_textgrid("out.TextGrid")
9
+ """
10
+
11
+ from .aligner import (DEFAULT_MODELS, SUPPORTED_LANGUAGES, Aligner,
12
+ resolve_model)
13
+ from .model import NyraModel
14
+ from .outputs import AlignmentResult, Phone, Word
15
+
16
+ from .version import __version__
17
+ __all__ = ["Aligner", "NyraModel", "AlignmentResult", "Word", "Phone",
18
+ "DEFAULT_MODELS", "SUPPORTED_LANGUAGES", "resolve_model"]