Skip to content

fix(player): an unrecognised waveform is a pulse, not a sine - #21

Merged
spacedevin merged 1 commit into
mainfrom
fix/audio-conformance
Sep 1, 2026
Merged

fix(player): an unrecognised waveform is a pulse, not a sine#21
spacedevin merged 1 commit into
mainfrom
fix/audio-conformance

Conversation

@spacedevin

Copy link
Copy Markdown
Owner

gen waveform bogus produced a sine in the browser and a pulse in the ROM.

The JS table's catch-all branch in packages/player/src/generators/GbaDirectSound.tish was
Math.sin(...), while the GBA bake's catch-all arm in deckpack.rs pcm_table is a pulse.
pulse is already the documented default for an absent waveform on both sides, so the sine was
inconsistent with the language's own default as well as with the bake.

This adds an explicit sine arm first — the bake has one, and without it changing the else would
have silently removed sine support — and then makes the fallback a pulse.

Context

Found while building a cross-implementation audio conformance corpus. There are two remaining known
divergences between this player and the GBA bake:

  • square ignores duty here (a 50% wave, which is what the name means) but the bake has no
    square arm, so it falls through to pulse and does honour duty. gen waveform square duty 25
    is 50% here and 25% on hardware. That half belongs in the engine repo, not this one.
  • bitcrush 16bit bypasses quantisation here, but the bake always emits i8, so the ROM is 8-bit
    regardless. That one is genuinely irreconcilable and should be documented rather than "fixed".

No behaviour change for any waveform that was already spelled correctly. The existing
gbaDirectSound waveform tables test in packages/player/test/voices.mjs still passes — its
square is two-level assertion holds either way.

`gen waveform bogus` produced a sine in the browser and a pulse in the ROM: the JS
table's catch-all branch was `Math.sin`, while the GBA bake's catch-all arm in
deckpack.rs `pcm_table` is a pulse. `pulse` is already the default for an *absent*
waveform on both sides, so the sine was inconsistent with the language's own default
as well as with the bake.

Adds an explicit `sine` arm first, so `waveform sine` keeps working — the bake has
one, and without it this change would have silently removed sine support.

`square` still ignores `duty` here, which the bake does not; that half of the
divergence is fixed on the Rust side.
@spacedevin
spacedevin merged commit 6171ef2 into main Sep 1, 2026
5 checks passed
spacedevin added a commit to schlopai/chuggie that referenced this pull request Sep 1, 2026
`pcm_table` had no `square` arm, so `square` fell through to the `_` catch-all — a pulse
that reads `duty`. `gen waveform square duty 25` therefore baked a 25% pulse while
@spacedevin/deck-player played a fixed 50% square. Same source, two different ROMs' worth
of sound, and `square` is a documented waveform in the chuggie.dev deck docs.

`square` now means 50% here too, and ignores `duty` the way the browser does.

Found while diffing the two implementations for an audio conformance corpus. The other
divergence from that pass — an unrecognised `waveform` being a sine in the browser and a
pulse here — is fixed on the JS side in spacedevin/deck#21.
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.

1 participant