ModernLrc 1.2.0
dotnet add package ModernLrc --version 1.2.0
NuGet\Install-Package ModernLrc -Version 1.2.0
<PackageReference Include="ModernLrc" Version="1.2.0" />
<PackageVersion Include="ModernLrc" Version="1.2.0" />
<PackageReference Include="ModernLrc" />
paket add ModernLrc --version 1.2.0
#r "nuget: ModernLrc, 1.2.0"
#:package ModernLrc@1.2.0
#addin nuget:?package=ModernLrc&version=1.2.0
#tool nuget:?package=ModernLrc&version=1.2.0
ModernLrc
Modern, performance-first LRC (lyrics) parser and writer for .NET 10. AOT-compatible, trim-safe, zero non-BCL runtime dependencies.
π Full documentation, API reference, and topical guides: https://anthonyy232.github.io/ModernLrcParser
(also browseable in the docs/ folder).
Features
- Full LRC support β simple
[mm:ss.xx]text, multi-timestamp[t1][t2]β¦, Enhanced LRC word timing<β¦>, Walaoke voice markers (M:/F:/D:) with state propagation. - Tolerant by default with rich diagnostics; opt-in
LrcStrictness.Strictfor fail-fast behaviour. - Span-first hand-rolled scanner β no regex, no backtracking, zero per-token transient allocations.
- Every input shape β
string,ReadOnlySpan<char>,ReadOnlyMemory<char>,TextReader,ReadOnlySpan<byte>,byte[],Stream, file paths; sync and async. - Zero-allocation writer via
IBufferWriter<char>and UTF-8 byte sinks; theWrite β string,TextWriter,TryWrite, andStreamsinks pre-size their staging buffer to the document's estimated size so large renders don't accumulate copy-on-grow waste; atomic file write via temp + rename. - Encoding pipeline β BOM detection (UTF-8, UTF-16 LE/BE) β caller override β UTF-8 validation β caller-supplied fallback.
- Diagnostics catalogue β every recoverable concern surfaces as a stable diagnostic code (
LRC0001βLRC0099) with line/column location.
Install
dotnet add package ModernLrc
Requirements
- .NET 10 runtime (consumers).
- .NET 10 SDK (pinned via
global.json) only required to build from source.
Quick start
using ModernLrc;
using ModernLrc.Model;
// Parse
var result = LrcParser.Parse("[ti:Demo]\n[00:01.00]hello\n");
Console.WriteLine(result.Document.Metadata.Title); // Demo
// Author + write
var doc = new LrcDocumentBuilder()
.WithTitle("My Song")
.AddLine("00:12.00", "first line")
.AddLine("00:14.20", "second line", LrcVoice.Female)
.Build();
string lrc = LrcWriter.Write(doc);
Design notes
Offset model
Documents store the [offset:N] tag verbatim in Metadata.Offset. Line timestamps are NOT mutated at parse time. Apply the offset on demand:
var doc = LrcParser.Parse(text).Document;
TimeSpan effective = doc.GetEffectiveTime(line.Timestamp);
LrcLine? current = doc.FindLineAt(currentPlayhead); // already factors offset in
This preserves round-trip fidelity β re-writing the document emits the original [offset:N] tag rather than collapsing it into mutated timestamps.
Round-trip fidelity applies to supported LRC syntax. If authored plain lyric text
contains valid-looking LRC markers such as <00:01.00>, a later parse will treat
those markers as syntax.
Encoding model
Byte/stream input uses this priority:
- BOM detection (UTF-8
EF BB BF, UTF-16 LEFF FE, UTF-16 BEFE FF). - Caller-supplied
LrcParseOptions.Encodingif set. - UTF-8 validation (rejects malformed sequences).
LrcParseOptions.FallbackEncoding(defaultEncoding.UTF8); emitsLRC0010at Error severity if it kicks in. SetFallbackEncoding = nullto fail loudly instead.
For non-Unicode encodings without a BOM (Shift-JIS, GBK, Big5), provide the encoding explicitly β statistical detection is intentionally out of scope.
Whitespace and empty lines
Lyric text is preserved verbatim, including whitespace-only lines and trailing whitespace. Apply display filters at the UI layer:
foreach (var line in doc.Lines)
{
if (line is LrcPlainLine p && string.IsNullOrWhiteSpace(p.Text)) continue;
Render(line);
}
Tolerantly accepted variants
Beyond the canonical [mm:ss.xx], the parser accepts these in tolerant mode (the default), each producing an informational diagnostic:
| Variant | Diagnostic | Example |
|---|---|---|
Three-digit fraction mm:ss.fff |
LRC0030 |
[00:01.500] |
Colon fraction separator mm:ss:ff |
LRC0030 |
[00:01:50] |
No fraction mm:ss |
LRC0030 |
[00:01] |
Hours notation h:mm:ss.ff |
LRC0030 |
[1:02:33.45] |
Comma decimal mm:ss,ff |
LRC0030 |
[01:23,45] |
ID3 language prefix xxx\|\|... |
LRC0092 |
eng\|\|[ti:...] |
Set LrcParseOptions.Strictness = LrcStrictness.Strict to throw LrcParseException on the first error-severity diagnostic. Tolerant mode collects everything for inspection on LrcParseResult.Diagnostics.
Comparison
ModernLrc compared to other .NET LRC parsers as of 2026-04-28.
Performance
Throughput on synthetic basic-LRC inputs (Small β 20 lines, Medium β 200 lines, Large β 2,000 lines). Lower is better. Ratio is relative to ModernLrc.
Parse
| Library | Small (Β΅s) | Medium (Β΅s) | Large (Β΅s) | Large alloc |
|---|---|---|---|---|
| ModernLrc 1.1.0 | 1.89 (1.00Γ) | 15.3 (1.00Γ) | 154 (1.00Γ) | 324 KB |
| Opportunity.LrcParser 1.0.4 | 1.93 (1.02Γ) | 12.4 (0.81Γ) | 121 (0.78Γ) | 317 KB |
| Kfstorm.LrcParser 1.0.3 | 12.5 (6.63Γ) | 106 (6.92Γ) | 1,066 (6.91Γ) | 2,156 KB |
| LrcParser 2025.623.0 (karaoke-dev) | 22.4 (11.9Γ) | 231 (15.1Γ) | 2,381 (15.4Γ) | 5,434 KB |
| SharpLyrics 1.0.0.2 (archived) | 192 (101Γ) | 277 (18.1Γ) | 1,152 (7.47Γ) | 2,252 KB |
Write
| Library | Small (Β΅s) | Medium (Β΅s) | Large (Β΅s) | Large alloc |
|---|---|---|---|---|
| ModernLrc 1.1.0 | 2.76 (1.00Γ) | 23.1 (1.00Γ) | 367 (1.00Γ) | 399 KB |
| Opportunity.LrcParser 1.0.4 | 3.22 (1.17Γ) | 29.2 (1.26Γ) | 423 (1.15Γ) | 648 KB |
| LrcParser 2025.623.0 (karaoke-dev) | 5.27 (1.91Γ) | 49.6 (2.14Γ) | 635 (1.73Γ) | 2,013 KB |
| Kfstorm.LrcParser 1.0.3 | β | β | β | (no write API) |
| SharpLyrics 1.0.0.2 (archived) | β | β | β | (no write API) |
ModernLrc edges Opportunity.LrcParser on small inputs and trails by 19β27% on larger inputs; the gap is the cost of its broader feature surface (recovery, structured diagnostics, voice tracking, encoding detection, word-level timing, stable sort with reorder detection). Allocation is within 2% across all sizes. ModernLrc leads on write across every input size β 13β26% faster than Opportunity.LrcParser and ~1.7β2.1Γ faster than karaoke-dev/LrcParser β while still emitting consecutive same-text lines as a collapsed [t1][t2]text group by default for round-trip fidelity.
Features
| Capability | ModernLrc | Opportunity | karaoke-dev | Kfstorm | SharpLyrics |
|---|---|---|---|---|---|
Basic LRC [mm:ss.xx]text |
β | β | β | β | β |
Enhanced LRC (word-level <β¦>) |
β | β | β | β | partialΒΉ |
Walaoke voice markers (M:/F:/D:) |
β | partialΒ² | β | β | β |
ID3 metadata tags (ti, ar, al, β¦) |
β | β | β | β | β |
[offset:N] semantics |
β | β | β | β | β |
ID3-style language prefix (eng\|\|β¦) |
β | β | β | β | β |
| Error recovery (continue past bad input) | β | partialΒ³ | β | β | β |
| Structured diagnostics with line/column | β | β | β | β | β |
| Strict / lenient parse modes | β | β | β | β | β |
| Round-trip parse β write β parse | β | β | β | β | β |
| Sync write API | β | β | β | β | β |
| Async parse / write | β | β | β | β | β |
Byte / Stream input + encoding detect |
β | β | β | β | β |
IBufferWriter<byte/char> output |
β | β | β | β | β |
TryParse / TryWrite non-throwing |
β | β | β | β | β |
| Span-first parser (no regex) | β | β | β | β | β |
| Native AOT compatible | β | β | β | β | β |
| Trim-safe | β | β | β | β | β |
| Nullable reference types | β | β | β | β | β |
| Target framework | net10.0 | netstandard1.0 | netstandard2.1 | portable PCL | netstandard2.0 |
| Last release | 2026-04 | 2018-05 | 2025-06 | 2015-06 | 2023-11 (archived) |
ΒΉ SharpLyrics has internal word-time extraction, but the public API surfaces only single-timestamp lines from a file path β no string input.
Β² Opportunity.LrcParser exposes LineWithSpeaker for Speaker: text patterns, not the Walaoke M:/F:/D: voice protocol with state propagation.
Β³ Opportunity.LrcParser collects parse exceptions in a list, but they are unstructured (no diagnostic codes or severity).
Methodology
- Workload: synthetic basic-LRC inputs (
[mm:ss.xx]text+ ID3 metadata header),Smallβ 20 lines,Mediumβ 200 lines,Largeβ 2,000 lines. Inputs are stored asstringconstants β no file I/O on the timed path. SharpLyrics requires a file path, so its corpus is staged to a temp file in[GlobalSetup](one-time cost outside the timed region). - Each parser called via the smallest possible idiomatic public entry point β no tuning.
- Write benchmarks start from each library's own parsed model (parsed once in
[GlobalSetup]) so the timed region measures serialisation only, not cross-model conversion. - Environment: BenchmarkDotNet 0.15.8, .NET 10.0.7 (SDK 10.0.202), Windows 11, AMD Ryzen 5 5600 (6 cores, 12 threads).
- Numbers will drift across machines and SDK versions β treat ratios as the reliable signal, absolute numbers as a snapshot of one machine on one day.
Tools
Development hooks
pre-commit install
pre-commit run --all-files
The hook set runs common file hygiene checks and dotnet format.
Sample CLI
dotnet run --project samples/ModernLrc.Samples.Console -- parse path/to/song.lrc
dotnet run --project samples/ModernLrc.Samples.Console -- shift path/to/song.lrc 500
Benchmarks
dotnet run -c Release --project bench/ModernLrc.Benchmarks -- --filter '*'
Uses BenchmarkDotNet. Filter to a benchmark group with e.g. --filter ParseBenchmarks*.
License
MIT β see LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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. |
-
net10.0
- No dependencies.
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories (1)
Showing the top 1 popular GitHub repositories that depend on ModernLrc:
| Repository | Stars |
|---|---|
|
Anthonyy232/Nagi
Rediscover your local music collection with Nagi, a music player focused on speed, simplicity, and privacy. Nagi is built with C# and WinUI 3 to offer a clean, native Fluent experience. It's beautiful, efficient, and respects your privacy.
|