ElBruno.S1Mini 0.1.2

dotnet add package ElBruno.S1Mini --version 0.1.2
                    
NuGet\Install-Package ElBruno.S1Mini -Version 0.1.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="ElBruno.S1Mini" Version="0.1.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="ElBruno.S1Mini" Version="0.1.2" />
                    
Directory.Packages.props
<PackageReference Include="ElBruno.S1Mini" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add ElBruno.S1Mini --version 0.1.2
                    
#r "nuget: ElBruno.S1Mini, 0.1.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package ElBruno.S1Mini@0.1.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=ElBruno.S1Mini&version=0.1.2
                    
Install as a Cake Addin
#tool nuget:?package=ElBruno.S1Mini&version=0.1.2
                    
Install as a Cake Tool

ElBruno.S1Mini

NuGet NuGet Downloads CI Build Publish to NuGet License: MIT HuggingFace .NET GitHub stars

Local ASR transcript normalizer for .NET 🧹

Clean raw speech-to-text transcripts into well-formed written text in .NET. Powered by superwhisper/s1-mini (a 0.6B Qwen3 fine-tune) running locally via ONNX Runtime GenAI. Auto-downloads the INT4 ONNX model from HuggingFace.

⚠️ s1-mini is not a chat model. It performs exactly one task β€” normalizing an ASR transcript. Give it anything else and you get unpredictable output.

Packages

Package NuGet Downloads Description
ElBruno.S1Mini NuGet NuGet Downloads Self-contained transcript normalizer with S1MiniClient (IChatClient) and TranscriptNormalizer.

Features

  • 🧹 Filler removal + punctuation + capitalization in one call
  • πŸ” Self-correction resolution β€” "nine am no sorry ten thirty" becomes 10:30
  • πŸ”’ Written form for spoken numbers, dates, times, currency, emails
  • 🎚️ Control-line settings β€” Styling (formal / semi-formal / casual) and Context (general / email) with empirically verified behavior
  • πŸ“¦ Auto-download β€” INT4 ONNX model fetched from HuggingFace on first use
  • 🧡 IChatClient compatible β€” S1MiniClient plugs into Microsoft.Extensions.AI
  • πŸ’‰ DI-friendly β€” AddTranscriptNormalizer() for ASP.NET Core
  • πŸͺ Chunking helper β€” NormalizeChunkedAsync for transcripts longer than the ~1,000-token recommended input
  • πŸ›‘ Empty-in / empty-out β€” pure filler returns string.Empty, as the model card documents
  • 🦺 Temperature-0 safe β€” the ORT-GenAI native divide-by-zero trap is guarded at the runtime layer so callers can always use greedy decoding

Installation

dotnet add package ElBruno.S1Mini

Quick Start

using ElBruno.S1Mini.Normalization;

// Downloads elbruno/s1-mini-onnx (int4) on first run.
using var normalizer = await TranscriptNormalizer.CreateAsync();

var cleaned = await normalizer.NormalizeAsync(
    "so um i need to like send the the report by uh friday no wait make that thursday");

Console.WriteLine(cleaned);
// So I need to send the report by Thursday.

What it actually changes

Three short console samples. Each is a complete program β€” the only difference is the input.

1 Β· Collapses stutters and repeated words

using ElBruno.S1Mini.Normalization;

using var normalizer = await TranscriptNormalizer.CreateAsync();

Console.WriteLine(await normalizer.NormalizeAsync(
    "you don't have any any any any change at all?"));
You don't have any change at all.

2 Β· Resolves self-corrections to what the speaker landed on

using ElBruno.S1Mini.Normalization;

using var normalizer = await TranscriptNormalizer.CreateAsync();

Console.WriteLine(await normalizer.NormalizeAsync(
    "i think we should uh go with with option b i mean option c"));
I think we should go with option C.

Note it keeps option C β€” the choice the speaker corrected to, not the one they abandoned.

3 Β· Writes spoken numbers, times, and dates in written form

using ElBruno.S1Mini.Normalization;

using var normalizer = await TranscriptNormalizer.CreateAsync();

Console.WriteLine(await normalizer.NormalizeAsync(
    "lets meet at uh three thirty on on tuesday the the tenth"));
Let's meet at 3:30 on Tuesday the 10th.

These outputs are measured against the real INT4 model with default options, and are pinned by regression tests in TranscriptNormalizerModelTests so they cannot silently drift.

It normalizes rather than rewrites: casual phrasing survives. the the total was like twenty five dollars and uh fifty cents becomes The total was like $25.50. β€” the numerals are fixed, but the colloquial "like" stays.

Control-line options

using ElBruno.S1Mini.Normalization;

var cleaned = await normalizer.NormalizeAsync(
    transcript,
    new TranscriptNormalizerOptions
    {
        Styling = TranscriptStyling.Formal,
        Context = TranscriptContext.Email,
    });

Empirically verified against the real INT4 model:

  • Styling: SemiFormal (default), Formal, Casual β€” all three produce distinct output.
  • Structure: Prose (default), Lists β€” caveat: Lists did not reliably produce Markdown bullets in testing; output stayed prose. Kept as a model-card value.
  • Context: General (default), Email β€” distinct. Message and Notes are accepted but empirically behave identically to General; kept for API completeness.

See docs/transcript-normalization.md for the full behavior table and before/after examples.

Dependency Injection

using ElBruno.S1Mini;

builder.Services.AddTranscriptNormalizer(options =>
{
    options.CacheDirectory = @"C:\models";
});

TranscriptNormalizer is registered as its own service type β€” not as IChatClient. s1-mini is not a chat model, and exposing it as one would mislead consumers expecting chat semantics.

Compose with any IChatClient

TranscriptNormalizer is a control-line + prompt-builder layer over any IChatClient. Supply your own client (as long as it is really pointed at s1-mini):

using ElBruno.S1Mini;
using ElBruno.S1Mini.Normalization;

using var chatClient = await S1MiniClient.CreateAsync();
using var normalizer = new TranscriptNormalizer(chatClient);

S1MiniClient also implements IChatClient directly, so you can plug it into any Microsoft.Extensions.AI pipeline β€” with the caveat that it only handles the exact prompt shape s1-mini was fine-tuned on.

Chunking long transcripts

var cleaned = await normalizer.NormalizeChunkedAsync(longTranscript, maxCharsPerChunk: 3500);

Each chunk is normalized statelessly at sentence boundaries; for tighter control on transcripts with context spanning boundaries, chunk manually at a natural pause instead.

FP16 is currently broken on CPU

elbruno/s1-mini-onnx also has an fp16/ subfolder, but that variant fails at inference on CPU with onnxruntime-genai 0.15.1 (upstream ORT GQA repeat_kv Reshape shape-mismatch bug). Use INT4 (the default). This library will not switch to FP16 automatically.

Model license

superwhisper/s1-mini is Apache-2.0 with a naming clause. The converted ONNX artifacts (elbruno/s1-mini-onnx) are an explicitly unofficial, unaffiliated, non-endorsed derivative. ElBruno.S1Mini's C# code is MIT; the downloaded model weights remain under the upstream Apache-2.0 license. Vendor quality claim: 94.8% token accuracy on 7,519 held-out English cases β€” Superwhisper's measurement on their official GGUF Q4_K_M build, not on the INT4 ONNX weights this library downloads, whose accuracy has not been separately measured. English only, v1.

Superwhisper also publishes official GGUF builds for llama.cpp, Ollama, and LM Studio. This library targets ONNX Runtime GenAI, which is what .NET can consume directly.

Building from Source

git clone https://github.com/elbruno/ElBruno.S1Mini
cd ElBruno.S1Mini
dotnet build ElBruno.S1Mini.slnx
dotnet test ElBruno.S1Mini.slnx --framework net8.0

What's New

  • πŸ“š v0.1.2 β€” documentation release: the package page now shows before/after examples, corrects the upstream accuracy attribution, and refreshes the author links. No library code changes.
  • 🦺 ORT-GenAI temperature-0 crash guard β€” the native temperature=0 divide-by-zero trap is guarded at the runtime layer; greedy decoding is safe for every call.
  • πŸŽ™οΈ LiveMicTranscription sample β€” microphone β†’ Silero VAD β†’ Whisper β†’ s1-mini, fully on-device, with --save-audio / --wav replay for reproducible testing.
  • 🧡 S1MiniClient β€” self-contained IChatClient implementation with automatic HuggingFace download of elbruno/s1-mini-onnx (int4).
  • πŸ§ͺ Qwen3 non-thinking prompt format β€” ported verbatim from the model's own chat_template.jinja, verified byte-for-byte against the real model.

Documentation

Samples

Sample Description
HelloS1Mini Console sample covering default normalization, Context.Email, Structure.Lists, and pure-filler input.
S1MiniWebSample Blazor Server web UI: textarea β†’ Normalize β†’ cleaned output with styling/structure/context selectors.
LiveMicTranscription Windows-only console sample: default microphone β†’ Silero VAD speech detection β†’ ElBruno.Whisper speech-to-text β†’ s1-mini cleanup, live and fully local. A Spectre.Console UI provides arrow-key model/style pickers, per-model download progress bars, a live input meter, and side-by-side raw vs. cleaned transcript panels, then offers to delete every downloaded model on exit. Supports --save-audio and --wav <file\|folder> for reproducible testing.

Reproducible testing with recordings

Live microphone testing is not repeatable β€” every attempt is a new performance. The sample can therefore record what it captures and replay it later through the identical pipeline:

# Capture: writes each detected utterance to ./recordings/*.wav
dotnet run --project src/samples/LiveMicTranscription -- --save-audio

# Replay: same VAD, same models, no microphone needed
dotnet run --project src/samples/LiveMicTranscription -- --wav recordings

--wav accepts a single file or a folder, and resamples/downmixes anything to 16 kHz mono, so a recording made with any tool works. This isolates model and setting changes from variation in the speaking itself.

Note on Whisper + s1-mini: Whisper does transcribe spoken fillers (um, uh) when they are actually captured, and s1-mini removes them. The hard part is capturing them: fillers are low-energy sounds that sit on the noise floor, so a simple energy-threshold gate discards exactly the words this library exists to clean up. The sample therefore uses Silero VAD (a neural speech detector) and cuts each utterance as one contiguous slice from the first to the last detected speech segment, plus padding β€” which preserves the quiet onsets. Measured on the same synthesized phrase:

Segmentation Whisper output
Energy threshold So, um, hello. (truncated at the first pause)
Silero VAD + contiguous slice So, um, hello. I have a, uh, question here. And I want to, um, see what I am going to do here.

s1-mini then returns So, hello. I have a question here. And I want to see what I am going to do here. The sample defaults to Whisper Tiny, which preserves fillers best; larger models tidy the transcript as they decode, which can make the cleanup step look like a no-op.

Utterance grouping matters too. A hesitation ("I think… ummm… we should") contains a pause often longer than a second. Ending the phrase there splits one sentence into fragments and strands the filler at a boundary, so the sample waits 1.5 s of silence before closing an utterance.

Known model limitation: s1-mini recognizes every common filler spelling (um, umm, ummm, uh, em, emm, eh, ehh, erm, hmm, er, ah) in ordinary sentences, but it passes greeting phrases containing a personal name through verbatim β€” "Hello, um, hi Kara." keeps the um, while "Hello, um, hi." and "Hello, um, this is a test." are both cleaned. Using Context.Email strips the filler in that case.

Testing

dotnet test ElBruno.S1Mini.slnx --framework net8.0

Tests use a fake IChatClient and a recording IGenerationSearchOptions seam β€” no model downloads, no network, no GPU required.

πŸ“„ License

MIT β€” see LICENSE. The downloaded s1-mini model weights are Apache-2.0 (upstream), not MIT.

πŸ™ Acknowledgments

πŸ‘‹ About the Author

Hi! I'm ElBruno 🧑, a passionate developer and content creator exploring AI, .NET, and modern development practices.

Made with ❀️ by ElBruno

If you like this project, consider following my work across platforms:

  • πŸ“» Podcast: No Tiene Nombre β€” Spanish-language episodes on AI, development, and tech culture
  • πŸ’» Blog: ElBruno.com β€” Deep dives on embeddings, RAG, .NET, and local AI
  • πŸ“Ί YouTube: youtube.com/elbruno β€” Demos, tutorials, and live coding
  • πŸ”— LinkedIn: @elbruno β€” Professional updates and insights
  • 𝕏 Twitter: @elbruno β€” Quick tips, releases, and tech news
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.2 115 8/21/2026
0.1.1 102 8/20/2026
0.1.0 106 8/20/2026