Repository navigation
Add a General MIDI synthesizer: tecs.audio.synth over rustysynth - #8
Open
albanmgicquel wants to merge 2 commits into
Open
albanmgicquel wants to merge 2 commits into
albanmgicquel wants to merge 2 commits into
Conversation
albanmgicquel
force-pushed
the
audio-synth
branch
from
October 15, 2026 23:24
062f116 to
449d880
Compare
rustysynth (MIT) renders a SoundFont in the output callback as the input of a voice started by a PLAY carrying the new SYNTH flag. Events cross from the game thread through a bounded lock-free SPSC ring (4096) into a preallocated schedule (4096); a send that finds no room is refused and counted. The render splits at rustysynth's 8-frame block boundaries and applies each event at the first boundary at or after its frame, so the output is a deterministic function of the schedule. The clock is frames rendered over the rate. FFI: tecs_audio_load_soundfont, tecs_audio_release_soundfont, tecs_audio_synth_open/send/notes_off/reset/now/close. Tests cover the grid, ordering, reset discarding, queue bounds, and with TECS_TEST_SOUNDFONT a real SoundFont: sample-exact landing, determinism, voice gain, cuts and pause.
loadSoundFont(audio, path) and new(audio, font, {group, gain, polyphony,
effects}) with send, noteOn, noteOff, controlChange, programChange,
pitchBend, allNotesOff, reset, now, setGain and destroy. Sending methods
return whether the event was queued. The synth takes its voice slot the way
play does, through an internal per-mixer lender, so the voice ceiling, group
gain, mute and pause apply unchanged. The backend contract gains SynthPort and
optional loadSoundFont, releaseSoundFont and openSynth; a backend without them
and a mixer without an output get a silent port.
tecs.audio moves to audio/init.nupp for its child. README records why
synthesis is in-process and where a hardware MIDI output would plug in.
albanmgicquel
force-pushed
the
audio-synth
branch
from
October 16, 2026 02:17
449d880 to
609cbc5
Compare
This was referenced Oct 16, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds
tecs.audio.synth: General MIDI from a SoundFont (SF2), rendered in-process byrustysynth(MIT, no dependencies) as one voice of the existing mixer, so group gain/mute/pause, master gain andmaxVoicesapply unchanged.atis a time on the audio clock (now(), frames rendered over the rate). Events land on rustysynth's 8-frame block grid at or afterat(never early, at most ~0.15 ms late at 48 kHz), so a given schedule renders identically every time; a nil or pastatmeans the next boundary, in send order. Block 8 measured at 3.3% of one core with FluidR3, 48 notes held, effects on (1.9% at rustysynth's default 64).false(and the drop is counted) rather than blocking or growing.now()stays 0); a test backend can supplyopenSynthto log events.midir: CoreMIDI, WinMM, ALSA) would be a secondSynthPortwith the samesend/atAPI; documented, not implemented.tecs.audiomoves toaudio/init.nupp(to host the child module;require("tecs.audio")is unchanged); the backend contract gains optional synth fields; a README design record explains why General MIDI is synthesized in-process (desktop OS GM synths are software anyway; one sound on every platform; mixer integration; determinism). No SoundFont ships; THIRD_PARTY_NOTICES says a game ships its own font's licence.Tests: Rust scheduling/queue/reset/clock tests with a recording synth; SoundFont-gated rendering tests (
TECS_TEST_SOUNDFONT): a note scheduled at frame 4800 renders the same samples as one at 0 shifted by 4800, identical renders for the same schedule, gain, immediate cut vs release, reset discarding a scheduled note, pause freezing the clock; a Nupp suite (audiosynthtest) over a logging backend.cargo test -p tecs-audio, clippy-D warnings,cargo fmt --check,nupp check --strict,nupp task format-checkandnupp testpass. Used in a game with FluidR3_GM, music verified by ear.Not run here:
nupp task docs-check(this toolchain lacks thelunamarkrock) and the packaging step. Known: rustysynth's SF2 parser is not hardened against a crafted file (the docs say to load only fonts the game ships); in one early device run the clock appeared to stop at 2.24 s with no stream failure flagged, not reproduced in later runs.