Skip to content

Optional native 44.1 kHz engine rate (GameSampleRate=44100) - #648

Merged
mdias merged 7 commits into
mdias:masterfrom
L0FKA:feat/native-game-sample-rate
Oct 26, 2026
Merged

mdias merged 7 commits into
mdias:masterfrom
L0FKA:feat/native-game-sample-rate

Conversation

@L0FKA

@L0FKA L0FKA commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Optional native 44.1 kHz engine rate

Some interfaces cannot run at 48 kHz (hardware locked to 44.1 kHz, or a driver that refuses 48000).
This adds an opt-in GameSampleRate=44100 under [Config] that makes the game's audio engine run
natively at 44.1 kHz, so a 44.1 kHz interface needs no external sample-rate conversion.

What it does

  • Patches the game's 48000 Hz constants on the September 2022 (d1b38fcb) and December 2024 Learn & Play
    (6ea6d1ba) builds:
    • the approved-rate check;
    • the output rate;
    • the capture rate, which is a double;
    • six input-side integer sites.
      All 9 sites are byte-verified before anything is written. If any site doesn't match, nothing is patched
      and the game keeps 48 kHz. A memory-protection or write failure after that check is logged and stops further
      patching; earlier sites, and possibly the failing one, may already have changed. There is no rollback.
      The third supported build logs "not supported for this game version" and is left alone.
  • At 44.1 kHz the convolution amp cabinet would reject its 48 kHz impulse responses, which silently drops
    cabinet coloring. A hook in the convolution engine's init resamples each IR to 44.1 kHz with a
    windowed-sinc filter inside the engine's init when the cabinet media loads (about 40 ms per cabinet).
    It runs once per cabinet load, not on every audio block. The hook's stolen bytes are
    verified first; on a mismatch the cabinet is left unhooked and the rate patch still applies.
    The media header is bounds-checked against the media size before any allocation or read (partition count,
    table entries of 1..128 bins, 64-bit payload total, plausible authored rate); anything else is left untouched.
  • Blank, 0 or 48000 keeps the existing 48 kHz audio behaviour; only startup log lines are added. Other values
    are rejected with a log line.
  • If a driver refuses 48000 Hz but accepts 44100 Hz, the log now suggests the option once.

Why 44.1 kHz only: the note detector's analysis window is a fixed number of samples. At 96 kHz the
window is half as long, which raises the lowest detectable pitch to about 47 Hz, so a bass low E
(41.2 Hz) could no longer be detected.

Testing

September 2022 build, MOTU M Series, Int32LSB, 128 frames. A 110 Hz test tone was fed into the input,
and the in-game tuner was read:

Setting Log Tuner
default (48 kHz) rate patch skipped A, +6 cents
GameSampleRate=44100 9 sites patched, driver switched to 44100, both clients at 44100 A, +5 cents

If the capture rate did not match the engine rate, the tone would read about 147 cents sharp, so the two
readings agreeing within 1 cent shows the input path runs at the engine rate. The cabinet log showed the
IR resampled 48000 -> 44100 (31 partitions, about 40 ms, at load), and the cabinet effect was active.
In-game pitch and tone checked by ear.

An offline test fed the cabinet parser 11 malformed and valid headers, inside buffers with canary bytes after the
media. Every malformed header was refused, nothing was read or written past the media, and no exception escaped;
both valid cases behaved correctly. With the new partition-count bound removed, a 4 GiB partition-count case escapes
as vector too long, so the test does catch the bug.

Not tested:

  • The Learn & Play build has been checked statically Reports from Learn & Play users would be very welcome.

L0FKA added 6 commits October 24, 2026 10:37
Interfaces that are hardware-locked to 44.1 kHz cannot run the game, which
only accepts 48 kHz. [Config] GameSampleRate=44100 makes the game's audio
engine itself run at 44.1 kHz, so no resampling is needed: nine sites per
supported build (Sept 2022 and Dec 2024 Learn & Play) are verified as a set
and written only if all of them match. Unset, 0 or 48000 changes nothing.

Known limitation: at 44.1 kHz the amp-cabinet impulse-response effect fails
to initialise, so tones lack cabinet colouring; a follow-up addresses it.
At 44.1 kHz Wwise's partitioned convolution engine refuses the game's
48 kHz cabinet impulse responses, so tones lose their cabinet colouring.
With GameSampleRate=44100, hook CAkPartitionedConvolutionEngine::Init right
after its media fetch and resample each IR to the engine rate before the
engine parses it (windowed-sinc, at media load, never in the audio callback).
The five stolen bytes are verified per build (Sept 2022 and Dec 2024 LP);
on mismatch the cabinet stays unhooked and the rate patch is unaffected.
…erfaces, and why only 44.1

- dist/RS_ASIO.ini lists GameSampleRate under [Config], blank by default.
- When the driver refuses 48000 Hz but accepts 44100 Hz, log once that
  GameSampleRate=44100 is available. No behaviour change.
- README: higher rates shorten the note detector's sample-sized window, so
  96 kHz cannot detect a bass low E.
The direct-DFT branch used for tapered partitions (e.g. 58 bins -> 116-point
transform) skipped the 1/n inverse scaling the radix-2 path applies, so those
partitions were decoded n times too loud before resampling. Only the few
cabinets with variable partition sizes take this branch.

Also tone down two claims: README no longer says 'without resampling' (the
cabinet IRs are resampled), and the 48 kHz log hint says the driver could not
use 48000 Hz rather than implying a hardware limit.
A malformed header could size allocations and reads from unchecked fields:
partCount = 0x80000000 wraps the x86 payload offset and requests a 4 GiB
table. Before any allocation or read, check:
- partCount against the media size;
- that the padded table fits;
- each table entry is 1..128 bins (the supported transform size);
- the payload total in 64-bit;
- the authored rate is plausible.
An offline test with canary-guarded buffers covers these, and fails
without the partCount bound.

README: the December 2024 build is byte-checked only, and cabinet
resampling depends on the hook installing (the log reports both).
Comments drop dated session history, say a cache hash hit is not a proof
of equality, and describe the FFT's direct-DFT fallback.
@mdias

mdias commented Oct 26, 2026

Copy link
Copy Markdown
Owner

This looks pretty good, but I would change one small thing.
Instead of:

[Config]
GameSampleRate=44100

I would rather have:

[Config]
GameSampleRate44100=true

Since if I understand correctly that's the only real alternative sample rate we support.
Do you agree?

@L0FKA

L0FKA commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor Author

Yes, going up in 96 kHz territory is moot, going 44.1 kHz improves bass detection range. I'll drop the rename in a sec.

@mdias
mdias merged commit 69d8b11 into mdias:master Oct 26, 2026
1 check 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