Skip to content

Gravis UltraSound emulation - #2310

Open
maximilien-noal with Copilot wants to merge 7 commits into
masterfrom
copilot/feature-gravis-ultrasound-emulation
Open

Gravis UltraSound emulation#2310
maximilien-noal with Copilot wants to merge 7 commits into
masterfrom
copilot/feature-gravis-ultrasound-emulation

Conversation

Copilot AI commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Description of Changes

Adds Gravis Ultrasound sound card emulation to spice86.

Rationale Behind Changes

The Gravis UltraSound (GUS) sound card is used by real-mode DOS games and demos. Spice86 had no GUS presence at all, causing programs that probe for it to fail or crash.

Suggested Testing Steps

  • Run a DOS program or demo that auto-detects GUS (e.g. sets ULTRASND=240,7,7,5,5 and probes port 0x240). Confirm it no longer crashes or throws an unhandled I/O port exception.
  • Confirm SoundBlaster and PC speaker audio are unaffected.

Copilot AI linked an issue Aug 13, 2026 that may be closed by this pull request
Copilot AI changed the title [WIP] Add Gravis Ultrasound sound card emulation Add Gravis UltraSound stub: register I/O port handlers for GUS detection Aug 13, 2026
Copilot AI requested a review from maximilien-noal August 13, 2026 17:01
@maximilien-noal

Copy link
Copy Markdown
Member

@copilot I want the full gravis ultrasound, not a skeleton.

https://github.com/dosbox-staging/dosbox-staging see gus.cpp and related files

@maximilien-noal
maximilien-noal marked this pull request as ready for review August 13, 2026 17:24
Copilot AI lite review requested due to automatic review settings August 13, 2026 17:24

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot wasn't able to review any files in this pull request.


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@maximilien-noal maximilien-noal added Music Music emulation related. Can be any form of Music (FM Synth, MT-32, or General MIDI, ...) hardware hardware device (can be virtual) emulation compatibility Emulator compatibility with DOS apps labels Aug 13, 2026

Copilot AI commented Aug 13, 2026

Copy link
Copy Markdown
Contributor Author

@copilot I want the full gravis ultrasound, not a skeleton.

https://github.com/dosbox-staging/dosbox-staging see gus.cpp and related files

Implemented the full GUS GF1 emulation in commit feat: implement full Gravis UltraSound GF1 emulation based on dosbox-staging reference, faithfully porting the dosbox-staging gus.cpp/gus.h logic to C#. Key changes:

Register protocol – port 0x304 byte-write now latches the low byte only; port 0x305 latches the high byte and commits the 16-bit register_data to the selected register, matching the real GF1 two-byte write sequence.

All GF1 registers – correct byte ordering (control/vol/pan use the high byte), volume start/end in EEEEMMMM format, wave address MSW/LSW with proper 13-bit masking, GetDmaOffset/UpdateDmaAddr for 16-bit DMA channel translation, and missing reads for registers 0x41, 0x42, 0x45, 0x4C, 0x82/83, 0x8A/8B.

Reset register (0x4C) – full GF1 reset when is_running=0; rendering is gated on is_running && is_dac_enabled.

Mix control / IRQlatches_enabled (bit 3) gates IRQ activation; irq_control_selected (bit 6) steers port 0x20B between IRQ/DMA address selection using the GUS SDK lookup tables.

Voice IRQCheckVoiceIrqs sets irq_status bits 0x20/0x40 and advances voice_irq.Status to the next pending voice; register 0x8F returns the correct format with inverted vol/wave bits.

Timers – corrected base delays (80 µs / 320 µs), mask bit handling, ShouldRaiseIrq per timer, and precomputed delay on value write.

Lookup tables – vol scalars use the DELTA_DB = 0.002709201 constant-dB formula; pan scalars use the asymmetric normalisation that puts position 7 exactly at centre.

GusVoiceReadCtrlState now checks the per-voice irqMask (not the fixed 0x80); RenderFrames skips only when both wave AND vol controls are simultaneously disabled.

@maximilien-noal maximilien-noal changed the title Add Gravis UltraSound stub: register I/O port handlers for GUS detection Gravis UltraSound emulation Aug 13, 2026
@maximilien-noal
maximilien-noal requested a lite review from Copilot August 13, 2026 19:04
Comment thread src/Spice86.Core/Emulator/Devices/Sound/GravisUltraSound.cs Fixed
Comment thread src/Spice86.Core/Emulator/Devices/Sound/GravisUltraSound.cs Fixed
Comment thread src/Spice86.Core/Emulator/Devices/Sound/GravisUltraSound.cs Fixed
Comment thread src/Spice86.Core/Emulator/Devices/Sound/GusVoice.cs Fixed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 8 out of 8 changed files in this pull request and generated 1 comment.

Suppressed comments (3)

src/Spice86.Core/Emulator/Devices/Sound/GravisUltraSound.cs:701

  • CheckVoiceIrqs sets IrqVolStateBit/IrqWaveStateBit based on the full _voiceIrq bitmasks, but totalMask is computed using activeVoiceMask. If a program raises IRQ bits on an inactive voice index (>= _activeVoices), the status byte can incorrectly report a pending wave/vol IRQ. Mask the individual wave/vol bitmasks before setting the status bits.
        if (_voiceIrq.VolState != 0) {
            _irqStatus |= IrqVolStateBit;
        }
        if (_voiceIrq.WaveState != 0) {
            _irqStatus |= IrqWaveStateBit;

src/Spice86.Core/Emulator/Devices/Sound/GusVoice.cs:77

  • RenderFrames' early-return checks the intersection of CtrlDisabled bits across WaveState and VolState. If WaveState and VolState are both disabled but with different disabled bits set (e.g., Reset vs Stopped), rendering will incorrectly proceed. This should instead check each control state independently for any disabled bit.
    public void RenderFrames(byte[] ram, float[] volScalars, AudioFrame[] panScalars, AudioFrame[] frames, int count) {
        // Skip rendering only when BOTH wave AND vol controls are independently disabled,
        // matching the GF1 hardware behaviour documented in the UltraSound SDK.
        if ((WaveState & VolState & CtrlDisabled) != 0) {
            return;

src/Spice86.Core/Emulator/Devices/Sound/GravisUltraSound.cs:193

  • This PR adds a new hardware device with a non-trivial port map, IRQ, and DMA behavior, but there are no integration tests validating GUS detection/probing or basic IRQ/DMA flows. There is already a sound integration test suite (e.g., SoundIntegrationTests) for SoundBlaster; a similar test that runs a small COM fixture which probes 0x240 and/or checks ULTRASND-related behavior would help prevent regressions and ensure the original crash scenario is covered.
        InitPortHandlers(ioPortDispatcher, portBase);

        // Mixer setup
        mixer.RegisterQueueNotifier(this);
        mixer.LockMixerThread();

Comment thread src/Spice86.Core/Emulator/Devices/Sound/GravisUltraSound.cs Outdated
Comment thread src/Spice86.Core/Emulator/Devices/Sound/GravisUltraSound.cs Fixed
Copilot AI and others added 7 commits August 14, 2026 01:57
Co-authored-by: maximilien-noal <1087524+maximilien-noal@users.noreply.github.com>
…staging reference

- Fix register data accumulation: port 0x304 byte-write latches low byte only;
  port 0x305 triggers the write with accumulated 16-bit value (matching GF1 protocol)
- Fix all voice register byte ordering: control/rate/vol registers use high byte of
  accumulated register_data, matching dosbox-staging WriteToRegister() semantics
- Add volume register format: vol start/end use (data << 4) * VolumeIncScalar,
  vol pos uses (register_data >> 4) * VolumeIncScalar
- Fix wave address MSW/LSW helpers: proper 13-bit MSW masking and 16-bit LSW masking
- Add ResetRegister state (0x4C): full GF1 reset on is_running=0; render guard checks
  is_running && is_dac_enabled before synthesising voices
- Add MixControlRegister: latches_enabled gates IRQ activation; irq_control_selected
  steers port 0x20B between IRQ and DMA address selection
- Add port 0x20B IRQ/DMA address selection with GUS SDK lookup tables
- Fix CheckVoiceIrqs: sets irq_status bits 0x20/0x40 for wave/vol IRQs and advances
  voice_irq.Status to the next pending voice
- Fix GetVoiceIrqStatus (reg 0x8F): returns voice_irq.Status|0x20 with inverted vol/
  wave bits in the high byte, matching dosbox 0x8F read semantics
- Fix timer base delays: 80 µs (timer 1) and 320 µs (timer 2) per GUS SDK spec
- Fix timer control register (0x45): updates ShouldRaiseIrq per timer, clears IRQ
  status bits when IRQ is disabled
- Fix timer value writes (0x46/0x47): precompute total delay = (256-value)*baseDelay
- Fix OnTimerControl: handles 0x80 expired-flag reset and IsMasked bits
- Add missing register reads: 0x41 (DMA control with TC IRQ pending), 0x42 (DMA
  address), 0x45 (timer ctrl), 0x49 (DMA sample ctrl), 0x4C (reset register),
  0x82/0x83 (wave start MSW/LSW), 0x8A/0x8B (wave pos MSW/LSW)
- Fix active voices register (0x0E): use 1 + ((register_data>>8) & 31), high byte
- Fix DMA address registers (0x43/0x44): correct 20-bit DRAM address reconstruction
- Add GetDmaOffset/UpdateDmaAddr for 16-bit DMA channel address translation
- Fix vol scalars: constant-dB spacing using DELTA_DB=0.002709201 (dosbox formula)
- Fix pan scalars: asymmetric normalisation ensures position 7 is exactly at centre
- Fix _outputQueue initialisation: field-level init avoids NullReferenceException in
  NotifyLockMixer during mixer registration
- GusVoice: fix ReadCtrlState to check per-voice irqMask instead of fixed bit 0x80
- GusVoice: fix RenderFrames to skip only when both wave AND vol states are disabled
- GusVoice: simplify UpdateWaveState/UpdateVolState to directly compare before/after
  IRQ state, removing redundant helper methods

Co-authored-by: maximilien-noal <1087524+maximilien-noal@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…ts can be combined'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…eters'

Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
…arator comments; fix redundant cast

Co-authored-by: maximilien-noal <1087524+maximilien-noal@users.noreply.github.com>
@maximilien-noal
maximilien-noal force-pushed the copilot/feature-gravis-ultrasound-emulation branch from d6f007b to 8197da5 Compare August 14, 2026 00:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

compatibility Emulator compatibility with DOS apps hardware hardware device (can be virtual) emulation Music Music emulation related. Can be any form of Music (FM Synth, MT-32, or General MIDI, ...)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Feature request: Gravis Ultrasound sound card emulation

4 participants