Commit Graph
2 Commits
Author SHA1 Message Date
AnachronautandClaude Opus 5 b0d06aa6e5 Make the vendored synth plain ASCII
soundThing's comments use em dashes and an arrow, and this repository is
plain ASCII throughout because the tooling around it does not do Unicode.
Tests/docs.sh caught it the first time it ran against the new file, which
is what that check is for.

Comments only; nothing the compiler sees has changed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-08-29 20:59:01 -04:00
AnachronautandClaude Opus 5 f58b0f93af Vendor the voice engine, with the level it always had and could not say
soundThing's synth.c and synth.h, and nothing else of it: the voice engine pulls in maths,
stdlib, stdint and stdio and knows nothing about Raylib, MIDI, patches or the interface, which
is what made it liftable. Copied rather than submoduled - two files against tying this build
to another repository's history is not a close call - so the header carries the whole of the
difference and is meant to stay current.

A VOICE'S LEVEL IS A ROUTING NOW. Envelope 0 was multiplied into the output unconditionally,
so routing it to a filter or an oscillator meant it shaped the volume as well whether that was
wanted or not, which is most of the trouble with making percussion. There was no flag to turn
that off and there never had been: Envelope is the only one of the four modulating things
without an active flag, where LFO, Filter and Oscillator all have one.

It could not take the filter's shape either. filterTick returning its input unchanged is the
right nothing-happens answer, but an envelope returning a constant would be useless as a mod
source - and being a mod source while NOT being the volume is exactly the thing wanted. So the
voice names the source that shapes its level, the way every other destination names its
source, and MOD_SOURCE_NONE means nothing shapes it. Two things fall out that were not
possible before: envelope 1 shaping the volume, and an LFO doing it, which is tremolo.

Envelope 0 still decides how long a voice lasts, because it still ticks. A voice with its
level unrouted will therefore cut rather than fade when the envelope reaches idle - fine or
wanted for percussion, a click if the oscillators are loud at that moment, and a one line
change to end on note-off instead if it turns out to matter.

NOISE COMES FROM A SEEDED GENERATOR, one to each thing that makes it. rand() is global state
shared with the whole process and varies between C libraries, so the same program sounded
different on different machines and no recorded result could have meant anything. One
generator each rather than one shared, because two noise sources drawing the same stream are
not two noise sources. Checked rather than assumed: two synths from the same start produce
identical samples, and 4,617 of 4,800 of them are non-zero, so it is noise and not silence.

AND A CHANNEL IS THE CHANNEL YOU ASKED FOR. synthNoteOn hunts for a free voice and steals
round-robin, which is what a keyboard wants and what the standalone synthesizer still does.
Channel two is channel two. Both old calls are untouched.

Nothing links it yet. It compiles clean and standalone under -std=c11 -pedantic, which is
what make strict already checks, and the 169 tests are unmoved because nothing calls it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E2JrLzFvuFX9fgi1LDRjrW
2026-08-29 20:26:35 -04:00