---
title: "Align a transcript to audio in C#"
url: https://recursiveai.org/onespeech/cookbook/forced-alignment/
description: "Align an existing transcript to audio in C# and print word timestamps with OneSpeech. Includes a working example, sample audio and null-timing guidance."
version: "1.0.0"
---

# Align a transcript to audio in C#

**OneSpeech** is a free, open-source speech recognition library for .NET from Recursive AI. MIT or Apache-2.0, no native dependencies.

[NuGet](https://www.nuget.org/packages/RecursiveAI.OneSpeech) · [GitHub](https://github.com/recursive-ai-oss/onespeech-csharp) · [Docs](https://recursiveai.org/onespeech/docs/) · [Download example (.zip)](https://recursiveai.org/onespeech/cookbook/forced-alignment/forced-alignment.zip)

Time supplied words against audio (up to 60 seconds). First use downloads a roughly 1.3 GB model.

```shell
dotnet add package RecursiveAI.OneSpeech
```

Requires [.NET 10](https://dotnet.microsoft.com/download/dotnet/10.0) (net10.0); .NET 9 and earlier aren’t supported.

```csharp
using OneSpeech;
var transcript = Speech.Align("speech.flac",
    "Stuff it into you his belly counselled him",
    "en", "facebook/omniASR-CTC-300M");
foreach (var word in transcript.Words)
    Console.WriteLine($"{word.Text}: " +
        $"{word.Start?.TotalSeconds:0.00}–" +
        $"{word.End?.TotalSeconds:0.00}");
```

The zip includes `speech.flac`; the output shows word boundaries in seconds.

Unzip [the example](https://recursiveai.org/onespeech/cookbook/forced-alignment/forced-alignment.zip), then `dotnet run`. It prints:

```text
Stuff: 0.46–0.72
it: 0.78–0.86
into: 0.92–1.12
you: 1.18–1.28
his: 1.60–1.72
belly: 1.82–2.06
counselled: 2.22–2.60
him: 2.64–2.74
```


## Full program

speech.flac is the unmodified LibriSpeech test-clean recording 1089-134686-0001, renamed for this example. It contains:

> Stuff it into you, his belly counselled him.

[LibriSpeech](https://www.openslr.org/12), Vassil Panayotov, Guoguo Chen, Daniel Povey and Sanjeev Khudanpur (2015); [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). The zip includes attribution. The sample is short so the download stays small.

The program supplies the written words rather than recognizing them again. Unalignable words keep null times, displayed as empty fields here; audio longer than 60 seconds is refused. The example selects the CTC aligner explicitly.

```csharp
using OneSpeech;

var transcript = Speech.Align(
    "speech.flac",
    "Stuff it into you his belly counselled him",
    language: "en", model: "facebook/omniASR-CTC-300M");
foreach (var word in transcript.Words)
    Console.WriteLine($"{word.Text}: {word.Start?.TotalSeconds:0.00}–{word.End?.TotalSeconds:0.00}");
```

Output from `dotnet run`:

```text
Stuff: 0.46–0.72
it: 0.78–0.86
into: 0.92–1.12
you: 1.18–1.28
his: 1.60–1.72
belly: 1.82–2.06
counselled: 2.22–2.60
him: 2.64–2.74
```

## Variations

**Read encoded audio streams instead of paths.** This replaces the full program and prints the same output above. The stream stays open until the `using` declaration disposes it.

```csharp
using OneSpeech;
using var stream = File.OpenRead("speech.flac");
var transcript = await Speech.AlignAsync(stream,
    "Stuff it into you his belly counselled him",
    language: "en", model: "facebook/omniASR-CTC-300M");
foreach (var word in transcript.Words)
    Console.WriteLine($"{word.Text}: {word.Start?.TotalSeconds:0.00}–{word.End?.TotalSeconds:0.00}");
```

## Related recipes

- [Transcribe long audio](https://recursiveai.org/onespeech/cookbook/transcribe-long-audio/)
- [Compare two speaker recordings](https://recursiveai.org/onespeech/cookbook/speaker-verification/)

---

## The short answer

Call `Speech.Align(audioPath, text, ...)`, then read each returned word’s nullable `Start` and `End`. This attaches times to supplied text; it does not discover the text. The example explicitly selects `facebook/omniASR-CTC-300M`, so the model choice does not depend on which ordered fallback is available. See [word timing](https://recursiveai.org/onespeech/docs/word-timing/) and the [CTC adapter](https://recursiveai.org/onespeech/docs/ctc-alignment/) for the timing contract.

## What is forced alignment in speech recognition?

Recognition asks which words were spoken. Forced alignment starts with written words and tries to place them on the audio timeline. That distinction matters when you already have an edited transcript: running recognition again can introduce different wording instead of timing the words you supplied.

The CTC aligner finds a path through frame probabilities for a fixed target. Its adapter maps that path back to written words. In this adapter, casing and punctuation remain in the result, while the spoken target is normalized for matching. The printed boundaries describe that alignment, not a recording device’s exact sample-level truth.

A successful alignment is not proof that every supplied word was actually spoken. An inaccurate transcript can still lead to a plausible-looking path. Use text that belongs to the audio and review uncertain regions. If your task requires recognizing an unknown recording, transcribe it first; alignment alone cannot replace recognition.

The helper also accepts an existing `Transcript`. That overload retains its written words and non-timing fields rather than splitting the text again. This is useful after correcting a document without wanting to replace its other information. Re-aligning is still a new timing operation; previous boundaries do not establish where words fall in different audio.

## How do I get word timestamps?

In the program above, enumerate `transcript.Words` and read each word’s `Start` and `End`. They are `TimeSpan` values, so `TotalSeconds` gives a convenient display unit. The two decimal places are output formatting, not a promise of that much accuracy. Keep nullable values in your application’s data model instead of converting a missing boundary to zero.

The standalone helper accepts at most 60 seconds. For a longer recording, split audio and its corresponding text into matching requests. Offset each returned boundary by the chunk’s start time in the original recording. Otherwise every chunk appears to start at zero and the assembled transcript has an incorrect clock. A timestamp from the middle of a clip must not be presented as a timestamp from the start of the original file.

The stream variation consumes encoded audio from its current position and leaves the stream open. It can spool the input to temporary storage; asynchronous I/O does not mean CPU model execution becomes asynchronous. Allow for both temporary disk usage and model memory.

## Do you need timestamps in a transcript?

Use them when a reader must jump from a word to its audio, or when another stage needs time-bounded text. Plain searchable text may not need them. Do not infer word confidence from the presence of a boundary: confidence, timing method and nullable boundaries are separate fields.

For evidence, subtitles or an editor, preserve missing times and review the output against the recording. The library’s alignment is not sample-accurate ground truth. Its model files and first-use conversion also need deployment planning; a small audio input does not imply a small model download. OneSpeech is unaffiliated with other products or companies named OneSpeech.
