Reverse/adlib gold driver#76
Conversation
5b6dd71 to
ffdae88
Compare
| } catch (Exception ex) { | ||
| Status = $"Load failed: {ex.Message}"; | ||
| Logger.Error(ex, "Load failed"); | ||
| return false; | ||
| } |
| } catch (Exception ex) { | ||
| Status = $"Load failed: {ex.Message}"; | ||
| Logger.Error(ex, "Load failed"); | ||
| return false; | ||
| } |
| } catch (Exception ex) { | ||
| Status = $"Play failed: {ex.Message}"; | ||
| Logger.Error(ex, "Play failed"); | ||
| RefreshTransportState(); | ||
| } |
| } catch (Exception ex) { | ||
| Log.Fatal(ex, "Unhandled exception"); | ||
| } finally { |
|
@copilot Réimplantation of the adgPlayer Music decoding and playback logic from the overriden driver code. To drive it and test it yourself implement a full mcp server |
Done in 350b885.
Engine source files are linked (not copied) from
Tools exposed:
To register it with Claude Desktop or the Spice86 MCP config, add: "cryogenic-adg": {
"command": "dotnet",
"args": ["run", "--project", "src/Cryogenic.AdgPlayer.Mcp"]
}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:
If you need me to access, download, or install something from one of these locations, you can either:
|
| private static readonly ILogger Logger = Log.ForContext<AdgPlayerTools>(); | ||
|
|
||
| private static readonly string[] DefaultSearchDirs = [ | ||
| Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", "..", "doc", "DUNECDVF", "C", "DUNECD", "DUNE.DAT_"), |
| foreach (string dir in searchDirs) { | ||
| string expanded = dir; | ||
| if (!Directory.Exists(expanded)) { | ||
| continue; | ||
| } | ||
| foreach (string file in Directory.GetFiles(expanded, "*", SearchOption.TopDirectoryOnly)) { | ||
| string ext = Path.GetExtension(file).ToUpperInvariant(); | ||
| if (ext is ".HSQ" or ".ADG" or ".AGD") { | ||
| found.Add(file); | ||
| } | ||
| } | ||
| } |
| } catch (Exception ex) { | ||
| Log.Fatal(ex, "ADG MCP server terminated unexpectedly"); | ||
| } finally { |
| } catch (Exception ex) { | ||
| HsqLogger.Warning(ex, "HSQ decompression failed, using raw data"); | ||
| return null; | ||
| } |
| } catch (Exception ex) { | ||
| Logger.Warning(ex, "Failed to extract header info for playlist item {FileName}", item.FileName); | ||
| item.Tooltip = $"Error loading header for {item.FileName}"; | ||
| } |
There was a problem hiding this comment.
Pull request overview
This PR introduces a new standalone AdLib Gold/OPL3 playback surface for Cryogenic by adding a dedicated Cryogenic.AdgPlayer app, an MCP server for headless inspection/control, and supporting reverse-engineering assets/scripts, while also wiring ADG override registration into the main emulator codebase.
Changes:
- Adds
Cryogenic.AdgPlayer, an Avalonia-based standalone ADG/HSQ player with playback UI, waveform/volume visualization, playlist support, and an OPL3/AdLib Gold synthesis pipeline. - Adds
Cryogenic.AdgPlayer.Mcp, a stdio MCP server that exposes load/play/tick/state/log tools on top of the same ADG engine. - Adds ADG reverse-engineering support files: bundled DNADG/music assets, comparison/debug scripts, and guidance docs/prompts for runtime analysis.
Reviewed changes
Copilot reviewed 40 out of 46 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
src/Cryogenic/Overrides/Overrides.cs |
Registers the new ADG driver override scaffold alongside existing override groups. |
src/Cryogenic/Overrides/AdpDriverCode.cs |
Disables ADP C# function replacement by default. |
src/Cryogenic/5F30AEB84D67CF2E053A83C09C2890F010F2E25EE877EBEC58EA15C5B30CFFF9/spice86dumpCpuRegisters.json |
Updates captured CPU-register dump data. |
src/Cryogenic.sln |
Adds the new ADG player and MCP projects to the solution. |
src/Cryogenic.AdgPlayer/Views/WaveformControl.cs |
Adds custom stereo waveform rendering control for the standalone player UI. |
src/Cryogenic.AdgPlayer/Views/VolumeFeedbackControl.cs |
Adds custom VU/history visualization control. |
src/Cryogenic.AdgPlayer/Views/MainWindow.axaml.cs |
Connects view controls to the view model. |
src/Cryogenic.AdgPlayer/Views/MainWindow.axaml |
Defines the standalone player window layout and bindings. |
src/Cryogenic.AdgPlayer/ViewModels/ViewModelBase.cs |
Adds the base MVVM type. |
src/Cryogenic.AdgPlayer/ViewModels/PlaylistItem.cs |
Adds playlist item view-model data structure. |
src/Cryogenic.AdgPlayer/ViewModels/OplWriteItem.cs |
Adds UI model for displayed OPL register writes. |
src/Cryogenic.AdgPlayer/ViewModels/MainWindowViewModel.cs |
Implements player UI behavior, playlist flow, logging, and engine integration. |
src/Cryogenic.AdgPlayer/ViewModels/LogDisplayItem.cs |
Adds log-row display model. |
src/Cryogenic.AdgPlayer/ViewModels/ChannelEventItem.cs |
Adds channel-event display model. |
src/Cryogenic.AdgPlayer/Services/OplSynthesizer.cs |
Adds the standalone OPL3/AdLib Gold audio synthesis and mixer pipeline. |
src/Cryogenic.AdgPlayer/Services/DuneAdgPlayerEngine.cs |
Adds the core ADG playback engine, state model, and public API. |
src/Cryogenic.AdgPlayer/Services/DuneAdgPlayerEngine.Opl.cs |
Adds routed OPL3 Gold register/instrument/surround handling. |
src/Cryogenic.AdgPlayer/Services/DuneAdgPlayerEngine.Hsq.cs |
Adds HSQ decompression for ADG assets. |
src/Cryogenic.AdgPlayer/Program.cs |
Adds standalone app startup and logging bootstrap. |
src/Cryogenic.AdgPlayer/Logging/ObservableSerilogSink.cs |
Adds a UI-consumable Serilog sink. |
src/Cryogenic.AdgPlayer/Cryogenic.AdgPlayer.csproj |
Defines the standalone player project and dependencies. |
src/Cryogenic.AdgPlayer/Behaviors/ScrollToEndBehavior.cs |
Adds reusable auto-scroll behavior for list panels. |
src/Cryogenic.AdgPlayer/App.axaml.cs |
Wires app startup, main window creation, and VM disposal. |
src/Cryogenic.AdgPlayer/App.axaml |
Defines application theme/style root. |
src/Cryogenic.AdgPlayer.Mcp/Program.cs |
Adds MCP server bootstrap and logging configuration. |
src/Cryogenic.AdgPlayer.Mcp/Cryogenic.AdgPlayer.Mcp.csproj |
Defines the MCP server project and linked engine sources. |
src/Cryogenic.AdgPlayer.Mcp/AdgPlayerTools.cs |
Adds MCP tool surface for loading, playback, ticking, and inspection. |
src/Cryogenic.AdgPlayer.Mcp/AdgMcpState.cs |
Adds shared engine/session state and captured trace buffers for MCP tools. |
scripts/adg/Wait-AdgDriverLoaded.ps1 |
Adds automation for detecting live DNADG load state and capturing evidence. |
scripts/adg/Start-AdgMcpCompare.ps1 |
Adds helper script to launch baseline vs override comparison sessions. |
scripts/adg/README.md |
Documents the ADG comparison-script workflow. |
scripts/adg/Get-Spice86CfgSnapshot.ps1 |
Adds MCP snapshot capture for CFG/status/function data. |
scripts/adg/Compare-Spice86Cfg.ps1 |
Adds CFG diff tooling for baseline/override comparisons. |
doc/DUNECDVF/C/DUNECD/DUNE.DAT_/MORNING.HSQ |
Adds bundled ADG music test asset. |
doc/DUNECDVF/C/DUNECD/DUNE.DAT_/DNADG.UNHSQ |
Adds bundled uncompressed DNADG driver binary asset. |
doc/DUNECDVF/C/DUNECD/DUNE.DAT_/ARRAKIS_AGD.HSQ |
Adds bundled ADG music test asset. |
.github/skills/spice86-mcp/SKILL.md |
Expands MCP usage guidance with verified transport and DNADG segment details. |
.github/prompts/plan-adgOpl3RuntimeReverse.prompt.md |
Adds an ADG reverse-engineering implementation plan. |
.github/agents/adg-opl3-runtime-reverse.agent.md |
Adds an ADG runtime reverse-engineering agent definition. |
| public sealed partial class MainWindowViewModel : ViewModelBase, IDisposable { | ||
| private static readonly ILogger Logger = Log.ForContext<MainWindowViewModel>(); | ||
|
|
||
| private static readonly string[] DefaultSongCandidates = [ |
| /// <summary> | ||
| /// Represents one playlist entry in the ADP player. |
|
|
||
|
|
||
| /// <summary> | ||
| /// Writes a register/value pair to the OPL synthesizer. |
| _opl.WriteRegister(0x01, 0x20); | ||
| // Primary chip: rhythm mode off | ||
| _opl.WriteRegister(0xBD, 0x00); | ||
| // Primary chip: CSM/keyboard split off | ||
| _opl.WriteRegister(0x08, 0x00); | ||
|
|
||
| // Secondary chip: OPL3 mode enable (reg 0x105) | ||
| _opl.WriteRegister(0x105, 0x01); | ||
| // Secondary chip: waveform select enable | ||
| _opl.WriteRegister(0x101, 0x20); | ||
| // Secondary chip: rhythm mode off | ||
| _opl.WriteRegister(0x1BD, 0x00); | ||
| _opl.InitializeGoldHardware(); |
| // Try HSQ decompression using a temporary instance | ||
| DuneAdgPlayerEngine tempEngine = new DuneAdgPlayerEngine(); |
| /// <summary> | ||
| /// Set to true when this is the currently playing track. | ||
| /// </summary> | ||
| public bool IsCurrentTrack { get; set; } |
|
@copilot apply changes based on the comments in this thread
Actually fully reimplement the player based on audio MCP driven tests, and the driver overrides as source of truth. I hear nothing. No music. The UI tends to not answer. Etc... This needs a new approach, not just 'fixes'. |
Done in 78f29c0. Here is what changed: Audio silence (root cause): UI not responding (root cause): Also fixed:
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:
If you need me to access, download, or install something from one of these locations, you can either:
|
- AdgPitchBendFractionsTable / AdgPortamentoFractionsTable now expose DefaultEntries + CreateDefault() built from live-captured driver bytes at segment 0x5BAE, offsets 0x01C7 and 0x01D4 respectively (read_memory via Spice86 MCP). The DNADG driver fills these tables from zero at runtime init; the captured sequences let us skip emulating that init. - AdgPlayerSessionViewModel now constructs a routing table, frequency lookup, bend fractions, and portamento fractions in its constructor, binds them to the engine via the existing SetRoutingTable / SetFrequencyLookupTable / SetPitchBendBody hooks, and assembles DefaultAdgPitchBendBody around the same references plus the engine's per-channel state slots. Captured bytes (validated against the canonical CFG): pitch-bend (13B): 13 15 15 17 19 1A 1B 1D 1F 21 23 24 25 portamento (10B): 00 05 0A 0F 14 00 06 0C 12 18 495 + 87 tests pass.
…mit-msg.txt, mcp-tools.json)
Headless MCP server exposing the full ADG audio pipeline over the wire: DuneAdgPlayerEngine + AdgOplSynthesizer (NukedOPL3Sharp + Spice86 SoftwareMixer + master volume / filters), so MCP clients hear/see exactly what speakers receive. Components: - AdgMcpSession: wires engine to a FanOutOplBus that mirrors writes into both the synth (real audio out) and an OplWriteRecorder (ring of 65536 OPL writes for inspection). Subscribes to AdgOplSynthesizer.AudioSamplesRendered and accumulates samples into an AudioSampleRingBuffer (~1 second @ 49716 Hz). - FanOutOplBus / OplWriteRecorder / AudioSampleRingBuffer helpers. - AdgMcpTools (McpServerToolType): list_songs, load_song, play, stop, tick, get_state, get_opl_writes, get_audio_samples, get_audio_levels (cheap peak/RMS probe). - Program.cs: stdio host with AddMcpServer + WithTools. References ModelContextProtocol 1.3.0 + Microsoft.Extensions.Hosting 9.0.0. Project added to Cryogenic.sln.
…bility and maintainability - Updated namespace declarations to remove unnecessary whitespace. - Reformatted code for consistency in indentation and spacing. - Added a new method `DrainSince` in `AudioSampleRingBuffer` to retrieve frames since a specific index. - Ensured thread safety in methods by using locks appropriately. - Enhanced XML documentation for clarity on method functionalities. - Updated project file to maintain consistent formatting. - Refactored `FanOutOplBus`, `OplWriteRecorder`, and `AdgEnvelopeSetupEmitter` classes for improved readability. - Added unit tests for `AdgEnvelopeSetupEmitter` to ensure correct functionality.
Per user request, the AdgPlayer.Ui is now a 1:1 port of AdpPlayer's polished player UI: file/playlist browse, transport, waveform, volume feedback, header info, channel snapshots, OPL writes panel, Serilog log panel. Namespace and DuneAdpPlayerEngine class renamed to Cryogenic.AdgPlayer.Ui / DuneAdgPlayerEngine. The engine here is the AdpPlayer engine carried over (it already loads ADG/HSQ files); the in-progress reverse-engineered Cryogenic.AdgPlayer driver and its MCP server remain untouched for ongoing RE work. - src/Cryogenic.AdgPlayer.Ui: wipe browser/grid/spectrum tree, copy Views/ViewModels/Behaviors/Logging/Services/App.axaml*/Program.cs from Cryogenic.AdpPlayer. - src/Cryogenic.AdgPlayer.Ui.csproj: add Spice86.Core, drop project reference to Cryogenic.AdgPlayer. - src/Cryogenic.AdgPlayer.Ui.Tests: removed (tested deleted code). - src/Cryogenic.sln: drop Cryogenic.AdgPlayer.Ui.Tests entry. fix(adg/mcp): repair dump_wav streaming and ResolvePath braces Brace mismatch in AdgMcpSession.Dispose and AdgMcpTools (orphaned ResolvePath fragment + missing JsonOpts arg on dump_wav response). Streamed dump_wav now produces correct duration WAV (5 s ≈ 971 KB, was capped at 1 s ≈ 194 KB by the ring buffer). tools: scripts/adg/mcp-smoke.ps1 sequential JSON-RPC stdio client for MCP smoke tests (load_song / play / tick / get_state / get_opl_writes / get_audio_levels / dump_wav).
…traction and analysis
- Updated OPL chip initialization to explicitly enable OPL3 mode for AdLib Gold, logging each step for better traceability. - Introduced DuneDatReader for in-memory reading of DUNE.DAT archives, eliminating the need for disk extraction. - Modified MainWindowViewModel to load music entries directly from DUNE.DAT, improving performance and user experience. - Enhanced PlaylistItem to support in-memory byte payloads for songs, allowing for seamless playback without filesystem access. - Updated UI elements in MainWindow.axaml for better user guidance on file paths. - Implemented throttling in VolumeFeedbackControl and WaveformControl to maintain UI responsiveness during high-frequency updates. - Integrated AdlibGold post-processing in both AdgOplSynthesizer and OplSynthesizer for consistent audio output.
…eFeedbackControl and WaveformControl for thread safety
…in VolumeFeedbackControl and WaveformControl for thread safety" This reverts commit 49aee8a.
This reverts commit c27466c.
…time" This reverts commit 36bede6.
- Strip ADP from user-visible strings: driverIdentityText, file picker filter labels (Dune AdLib Gold Music), liveProofText now surfaces OPL3 dual-bank + Ym7128B surround status. - Drop *.ADP / *.adp from file pickers (UI is ADG-only; ADP project remains separate). - Rename ResolveAdpCompatiblePath -> ResolveHsqPreferredPath (4 sites). - ScrollToEndBehavior + PlaylistItem doc comments rebranded ADG. - OplWriteItem: decode AdLib Gold dual-bank OPL3 address (Chip / Reg properties); add chip-1 special-register names (0x04 4OpMask, 0x05 OPL3Enable); Display now shows 'cN' chip prefix. - NO audio pipeline / engine changes. Restored audio by reverting commit 36bede6 (AdlibGold post-processor insertion).
- Remove unused 'using System.IO;' from PlaylistItem.cs (call site uses fully qualified System.IO.Path). - Remove unused 'using System;' from DuneAdgPlayerEngine.Opl.cs. - Rewrite stale top-of-file summaries that called the engine 'DNADP (AdLib Pro)' to reflect what it IS today (ADG / AdLib Gold) while keeping the lineage citation to AdpDriverCode.cs as a 'Lineage:' note. - Rename log strings 'ADP engine created/disposed' -> 'ADG engine created/disposed' (3 sites in DuneAdgPlayerEngine.cs). - Fix factually wrong startup log: 'ChannelCountConst=18, AdgOplSynthesizer dual-bank writes (chip0/chip1)' was a leftover from the reverted engine swap. Engine actually uses ChannelCount=9 via OplSynthesizer (chip 0 only). Corrected log line.
…nt management - Consolidated OPL register write operations to mirror ADG driver methods. - Introduced frequency word table for channel frequency management. - Updated note on/off methods to utilize new frequency handling. - Enhanced event dispatching for OPL register writes and channel events. - Removed redundant channel frequency array in favor of frequency word table. - Improved wait value reading logic for better overflow handling. - Updated MainWindowViewModel to wire new engine events and manage audio samples.
…ve event handling in MainWindowViewModel - Updated DuneAdgPlayerEngine to handle 18 logical channels, adjusting channel-related arrays and constants accordingly. - Enhanced channel event processing by implementing concurrent queues for OPL writes, channel events, and log messages to reduce UI thread load. - Modified MainWindowViewModel to drain event queues on the UI timer, ensuring responsive UI during high-frequency events. - Updated logging and status messages to reflect changes in channel handling and engine capabilities. - Improved performance by capping the size of event queues and logs to prevent excessive memory usage.
…workflow for AdgDriverCode modifications
… remove Wait-AdgDriverActive and Wait-AdgDriverLoaded scripts
80997a0 to
7b0fb0b
Compare
…ation - Integrated Adlib Gold functionality into the OPL synthesizer, allowing for control register writes and volume adjustments. - Updated the OPL synthesizer constructor to accept an audio engine parameter. - Implemented effective master volume application for Adlib Gold channels. - Enhanced UI display to reflect the new Adlib Gold capabilities. - Updated OplWriteItem to decode Adlib Gold control registers. - Improved driver load toolbox with new constants for driver entry points and segment tracking. - Added functionality to record the loaded driver segment for DNADG (AdLib Gold) during runtime. - Enhanced MT-32 driver code to log driver type information and handle driver-specific logic.
| al = 0; | ||
| ushort axDiv = Make16(al, ah); | ||
| al = (byte)(axDiv / bh); | ||
| ah = (byte)(axDiv % bh); |
| ah = (byte)(axDiv % bh); | ||
|
|
||
| ushort mul1 = (ushort)(al * dl); | ||
| al = Lo8(mul1); |
| al = 0; | ||
| axDiv = Make16(al, ah); | ||
| al = (byte)(axDiv / bh); | ||
| ah = (byte)(axDiv % bh); |
| ushort ax = Make16(Lo8(input), 0); | ||
| ushort cx = Make16(note, 0); | ||
| ushort temp = cx; | ||
| cx = ax; |
| if (DriverLoadToolbox.ActualAdgSegment != 0) { | ||
| _adgSegment = DriverLoadToolbox.ActualAdgSegment; | ||
| } else { | ||
| _adgSegment = AdgDefaultSegment; | ||
| } |
| } catch (Exception ex) { | ||
| Log.Error(ex, "Headless trace capture failed"); | ||
| exitCode = 1; | ||
| } |
| } catch (Exception ex) { | ||
| Log.Fatal(ex, "Unhandled exception"); | ||
| } finally { |

Description of Changes
Rationale behind Changes
Suggested Testing Steps