Sitelet https://github.com/86Box/86Box/pull/7941
Skip to content

GUS: Fix the ADC sample rate divisor missing the +2 bias - #7941

Merged
OBattler merged 1 commit into
86Box:masterfrom
MaxFreedomPollard:fix/gus-adc-srate
Sep 12, 2026
Merged

OBattler merged 1 commit into
86Box:masterfrom
MaxFreedomPollard:fix/gus-adc-srate

Conversation

@MaxFreedomPollard

Copy link
Copy Markdown
Contributor

Summary

Writing the GF1 recording sample rate register (index 48h) programs the wrong timer interval, and one value divides by zero.

src/sound/snd_gus.c:760 computed the rate as 617400 / gus->adc_srate. The Ultrasound SDK gives it as rate = 9878400 / (16 * (FREQ + 2)) in section 2.6.1.6, and the comment on that same line carries the inverse form, 9878400 / (freq * 16) - 2, so the same bias was already documented and only the code dropped it. Writing 12, the documented value for 44100 Hz, therefore armed the record sample timer at 19.44 us instead of 22.68 us, so gus_input_poll() paced the recording DMA about 16.7% faster than the guest asked for and raised the terminal count IRQ at src/sound/snd_gus.c:387 about 14.3% early in wall time. 22050 Hz was 7.7% fast and 11025 Hz 3.7% fast.

Two smaller problems came out of the same line. The quotient was stored into uint16_t gus->adc_freq (src/sound/snd_gus.c:247) before it was range checked, so a write of 4 wrapped 154350 down to 23278. And temp at src/sound/snd_gus.c:761 was taken from that value before the 4000/44100 clamp ran, while temp alone fed gus->inputlatch, so the clamp only ever reached the debug log string and never the timer. A write of 0 divided by zero.

The change computes the rate the way the SDK documents it, in a uint32_t that cannot wrap, and derives inputlatch from it. The + 2 removes the division by zero on its own, so no extra guard is needed.

The 44.1 kHz ceiling stays, since that is the maximum the SDK documents and it stops a write of 0, which asks for 308700 Hz, from arming a 3.2 us timer. The 4000 Hz floor is dropped. Now that the clamped value actually reaches the timer, keeping that floor would be a real behavior change rather than dead code, and the wrong one: FREQ = 255 is a genuine 2402 Hz on hardware, and the SDK states no minimum anywhere. For every register value that asks for a rate at or below the ceiling, the new interval is within 0.04% of the hardware formula.

One thing this does not fix: the GUS ADC is still a stub, since gus_input_poll() at src/sound/snd_gus.c:364 DMAs constant filler bytes rather than sampling anything. So this corrects the pacing of the recording DMA and the IRQ that ends it, not the content. The program that path exists for is MegaEM 3.x, named in the commit that added this handler.

I checked this by rebuilding the arithmetic of both versions in a standalone program. Value 12 gives a 19.44 us latch before and 22.68 us after, and value 26 gives 23746 Hz before and 22050 Hz after. No new warnings under the project's own flags; with -Wextra added, the same nine pre-existing -Wmissing-field-initializers in the device config tables appear before and after. I have not run this inside a built emulator, so the numbers are the arithmetic only.

Checklist

  • I have tested my changes locally and validated that the functionality works as intended

References

Ultrasound Software Development Kit 2.10, section 2.6.1.6 "Sampling Frequency - (48)", which states rate = 9878400/(16*(FREQ+2)), and the feature list stating "Playback and recording rates up to 44.1 kHz": http://archives.oldskool.org/pub/drivers/Gravis/UltraSound/ULT/programming/sdk2.10/ULTRADOC.TXT

The commit that added this register handler, for context on the MegaEM 3.x case: c6f67b979

Register 48h holds FREQ, and the SDK gives the rate as 9878400 / (16 *
(FREQ + 2)). The divisor used 617400 / FREQ, so writing 12 for 44100 Hz
armed the record sample timer at 19.44 us instead of 22.68 us and paced
the record DMA about 16.7% faster than the guest asked for. The quotient
was also stored into a uint16_t before it was range checked, so a write
of 4 wrapped 154350 down to 23278, and inputlatch was taken from the
unclamped value, so the clamp only ever reached the log string.

Compute the rate in a uint32_t that cannot wrap, keep the 44.1 kHz
ceiling the SDK documents, and derive inputlatch from that value. The
+ 2 removes the division by zero on a write of 0 without a separate
guard. The 4000 Hz floor is dropped rather than applied to the timer:
the SDK states no minimum, and FREQ = 255 is a real 2402 Hz that the
floor would have forced up to 4000 Hz.
@OBattler
OBattler merged commit 4183d49 into 86Box:master Sep 12, 2026
44 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants