Skip to content

Feature: skip digital music silence based on RMS analysis (based on LibVLCSharp)#94

Merged
maximilien-noal merged 5 commits into
mainfrom
feature/improved_digital_music_driver
May 15, 2026
Merged

Feature: skip digital music silence based on RMS analysis (based on LibVLCSharp)#94
maximilien-noal merged 5 commits into
mainfrom
feature/improved_digital_music_driver

Conversation

@maximilien-noal
Copy link
Copy Markdown
Member

Description of Changes

feat: CD music: Add per-track silence skip analysis and caching

Introduce automatic startup silence detection for replacement audio tracks using RMS analysis via LibVLCSharp. Cache skip offsets in track-analysis-cache.json for fast startup and cache invalidation on file changes. Refactor MusicFolderPlayer to catalog tracks, apply skip offsets on playback and loop, and improve error handling and file resolution. Analysis is parallelized for efficiency.

Rationale behind Changes

Music is as dynamic as OPL FM or MT-32 music. Let's enjoy it without the silence.

Suggested Testing Steps

Already tested with DUNE CD.

@maximilien-noal maximilien-noal self-assigned this May 15, 2026
Copilot AI review requested due to automatic review settings May 15, 2026 09:04
@maximilien-noal maximilien-noal added the enhancement New feature or request label May 15, 2026
Comment thread src/Cryogenic/Services/MusicFolderPlayer.cs Fixed
Comment thread src/Cryogenic/Services/MusicFolderPlayer.cs Fixed
Comment thread src/Cryogenic/Services/MusicFolderPlayer.cs Fixed
Comment thread src/Cryogenic/Services/MusicFolderPlayer.cs Fixed
Comment thread src/Cryogenic/Services/MusicFolderPlayer.cs Fixed
Comment thread src/Cryogenic/Services/MusicFolderPlayer.cs Fixed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds automatic startup-silence detection for MusicFolder replacement tracks (LibVLC-decoded RMS analysis) and persists per-track skip offsets to a JSON cache for faster subsequent startups. This refactors MusicFolderPlayer to pre-catalog tracks, perform parallel analysis (with cache invalidation on file changes), and apply the computed skip on initial play and loop restarts.

Changes:

  • Introduce StartupTrackSilenceAnalyzer to compute a conservative “skip leading silence” offset per track.
  • Refactor MusicFolderPlayer to catalog replacement files up front, load/save track-analysis-cache.json, and apply skip offsets on play/loop.
  • Update Copilot instructions to avoid adding new silent fallback paths and prefer explicit failure/logging.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.

File Description
src/Cryogenic/Services/StartupTrackSilenceAnalyzer.cs New LibVLC-based RMS analyzer that detects leading silence and returns a skip offset + confidence.
src/Cryogenic/Services/MusicFolderPlayer.cs Adds track cataloging, analysis cache load/save, parallel startup analysis, and skip-on-play/loop behavior.
.github/copilot-instructions.md Adds guidance to avoid introducing silent fallback behavior and prefer fail-fast with explicit logging.
Comments suppressed due to low confidence (5)

src/Cryogenic/Services/MusicFolderPlayer.cs:189

  • OnEndReached runs on a ThreadPool work item and throws if restart fails. Unhandled exceptions on ThreadPool threads will typically terminate the process, so this should be handled inside the callback (catch/log and stop playback / set IsActive=false) instead of throwing.
			_mediaPlayer.Stop();
			bool restarted = _mediaPlayer.Play();
			if (!restarted) {
				throw new InvalidOperationException("Loop restart failed in MediaPlayer.");
			}

src/Cryogenic/Services/MusicFolderPlayer.cs:375

  • ApplyTrackSkip throws if the media never becomes seekable (or Length stays 0) within the timeout. A single problematic file would then crash playback (and if invoked from a background callback, could crash the whole process). Consider treating this as recoverable: log and continue without applying skip, and/or mark the cached skip as 0.
		if (!_mediaPlayer.IsSeekable || _mediaPlayer.Length <= 0) {
			throw new InvalidOperationException($"Track '{filePath}' is not seekable for fingerprint '{fingerprint}'.");
		}

src/Cryogenic/Services/MusicFolderPlayer.cs:386

  • LoadRegistry now throws when fingerprints.json is missing. InitializeMusicFolderPlayer currently only checks that the folder exists, so this exception will propagate during startup and can prevent the game from launching. Either handle these exceptions at the call site (disable replacement with an explicit log) or make construction non-fatal while clearly logging the misconfiguration.
	private void LoadRegistry() {
		string registryPath = Path.Combine(_folderPath, "fingerprints.json");
		if (!File.Exists(registryPath)) {
			throw new FileNotFoundException($"fingerprints.json not found at '{registryPath}'.", registryPath);
		}

src/Cryogenic/Services/MusicFolderPlayer.cs:395

  • MergeRegistryFromJson throws if deserialization returns null. If the intent is to fail fast, consider including more context (e.g., JSON parse failure details) via a prior log entry so users know how to fix the file; otherwise, treat malformed fingerprints.json as a recoverable configuration error and disable replacement.
	private void MergeRegistryFromJson(string json, string source) {
		Dictionary<string, FingerprintEntry>? entries = JsonSerializer.Deserialize<Dictionary<string, FingerprintEntry>>(json);
		if (entries is null) {
			throw new InvalidOperationException($"fingerprints.json from '{source}' could not be parsed.");
		}

src/Cryogenic/Services/MusicFolderPlayer.cs:402

  • MergeRegistryFromJson throws when the registry ends up empty. As written this can prevent startup even though empty/partial registries could be handled by falling back to native MT-32 for unknown fingerprints. If you keep this fail-fast behavior, consider documenting it and/or providing a clearer log message that points to the expected fingerprints.json source/template.

		if (_registry.Count == 0) {
			throw new InvalidOperationException($"fingerprints.json from '{source}' is empty.");
		}

Comment thread src/Cryogenic/Services/MusicFolderPlayer.cs
Comment thread src/Cryogenic/Services/MusicFolderPlayer.cs Outdated
Comment thread src/Cryogenic/Services/MusicFolderPlayer.cs Outdated
@maximilien-noal maximilien-noal changed the title Feature/improved digital music driver Feature: skip digital music silence based on RMS analysis (based on LibVLCSharp) May 15, 2026
@maximilien-noal
Copy link
Copy Markdown
Member Author

@copilot apply changes based on the comments in this thread and CodeQL findings too

Copy link
Copy Markdown
Contributor

Copilot AI commented May 15, 2026

@copilot apply changes based on the comments in this thread and CodeQL findings too

Implemented in commit 7c3d2df. I addressed the review thread and CodeQL items in MusicFolderPlayer: removed the flagged Path.Combine usage for fixed filenames, replaced generic catches with specific exception handling, made analysis cache access concurrency-safe with ConcurrentDictionary, handled duplicate case-insensitive filenames deterministically, and replaced throw-based runtime playback failures with warning logs and safe fallback behavior.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • av-build-tel-api-v1.avaloniaui.net
    • Triggering command: /usr/share/dotnet/dotnet dotnet exec --runtimeconfig /home/REDACTED/.nuget/packages/avalonia.buildservices/11.3.2/tools/netstandard2.0/runtimeconfig.json /home/REDACTED/.nuget/packages/avalonia.buildservices/11.3.2/tools/netstandard2.0/Avalonia.BuildServices.Collector.dll (dns block)
    • Triggering command: /usr/share/dotnet/dotnet dotnet exec --runtimeconfig /home/REDACTED/.nuget/packages/avalonia.buildservices/11.3.2/tools/netstandard2.0/runtimeconfig.json /home/REDACTED/.nuget/packages/avalonia.buildservices/11.3.2/tools/netstandard2.0/Avalonia.BuildServices.Collector.dll -main/dist/indexgpg.program (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

maximilien-noal and others added 4 commits May 15, 2026 14:23
… on missing info

Added workflow rule to prohibit introducing new silent fallback or degraded behavior; require explicit logging and fast failure if evidence or functionality is missing. Existing fallback logic may be preserved unless change is requested.
Introduce automatic startup silence detection for replacement audio tracks using RMS analysis via LibVLCSharp. Cache skip offsets in track-analysis-cache.json for fast startup and cache invalidation on file changes. Refactor MusicFolderPlayer to catalog tracks, apply skip offsets on playback and loop, and improve error handling and file resolution. Analysis is parallelized for efficiency.
Switched from Thread.Sleep(10) to Thread.Yield() when waiting for the media player to become seekable and have a valid length. This reduces unnecessary delays and improves thread responsiveness during initialization.
Agent-Logs-Url: https://github.com/OpenRakis/Cryogenic/sessions/c3643e85-2be2-4ad8-b01c-e024d4c02f7b

Co-authored-by: maximilien-noal <1087524+maximilien-noal@users.noreply.github.com>
@maximilien-noal maximilien-noal force-pushed the feature/improved_digital_music_driver branch from 7c3d2df to 87fa45b Compare May 15, 2026 12:23
@maximilien-noal maximilien-noal merged commit 277c202 into main May 15, 2026
3 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants