Skip to content

feat(deck): read wave from the language, and accept the harmonics form - #27

Merged
spacedevin merged 4 commits into
mainfrom
feat/wave-harmonics
Sep 1, 2026
Merged

feat(deck): read wave from the language, and accept the harmonics form#27
spacedevin merged 4 commits into
mainfrom
feat/wave-harmonics

Conversation

@spacedevin

Copy link
Copy Markdown
Member

Reads named wave tables out of the .deck language and bakes them, accepting both spellings —
the 32-hex-digit form and the harmonics <a1> ... form, which the parser resolves to the same 32
levels.

Commits:

  • feat: read wavefrom the language, and accept theharmonics form
  • chore(deps): pin deckfile 1.5.3
  • style: satisfy rustfmt and clippy in the wave bake

Note for review

While building a cross-implementation audio conformance corpus, two divergences turned up between
this bake and @spacedevin/deck-player's Web Audio voices. One is fixed on the JS side in
spacedevin/deck#21 (an unrecognised waveform was a sine in the browser and a pulse here). The
other is on this side and is not addressed by this PR:

pcm_table has no "square" arm, so square falls through to the _ catch-all — which is a pulse
that honours duty. The browser treats square as a fixed 50% wave that ignores duty. So
gen waveform square duty 25 is 50% in the browser and 25% in the ROM, and square is a documented
value in the chuggie.dev deck docs. Worth an explicit arm here.

`wave <name> <32 hex nibbles>` originated here, as a fork of the parser, and
survived as a registered dialect extension after the fork was undone. It was
never GBA-specific though — deck-player registered its own copy too — so it has
been adopted into the language proper (spacedevin/deck#16).

That deletes the hex validation and nibble unpacking from this crate. The parser
resolves both spellings to 32 levels, and all that is left here is packing two
levels per byte for wave RAM.

The additive sum lands at bake time, which is the only place it can: the device
is no_std on ARM7TDMI with no FPU and no libm, so there is no sin() on the far
side. Nothing about the ROM changes — still 16 bytes per table, copied verbatim
into WAVE_RAM, so a harmonics table costs exactly what a hex one did.

The corpus digest is unchanged across all 54 decks, and a new test pins the part
that would be invisible otherwise: a harmonics table and the hex literal it
resolves to pack to byte-identical wave RAM.

Blocked on the deckfile 1.5.0 release; the pin is bumped ahead of it, so CI will
not go green until that publishes.
The released crate, in place of the version pinned ahead of it. `wave` is core
grammar there, so this is what actually makes the branch build.

All 11 tests pass against the published crate, and the corpus digest across all
54 decks is byte-identical to the pre-change baseline.
CI runs `cargo fmt --check` on every crate and `cargo clippy --all-targets -D
warnings` on this one; the wave changes passed neither.

Formatting was mechanical. Clippy found two needless range loops and one real
problem: `assert!(b >> 4 <= 15 && (b & 0x0f) <= 15)` is always true, because a
nibble of a u8 cannot exceed 15. The assertion proved nothing.

Replaced with the invariant actually worth pinning — that packing is lossless
and high-nibble-first. Getting that order wrong still yields 16 plausible bytes,
so nothing downstream would catch it; the ROM would just play a scrambled
waveform.

Tests still 11/11 and the corpus digest is unchanged across all 54 decks.
@spacedevin
spacedevin merged commit 097715a into main Sep 1, 2026
97 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.

1 participant