Sound¶
examples/audio/beep.HC plays a short tune, a coin pickup and a "hurt" sound, then exits. It opens no window. The reusable part is examples/common/audio.HC, about 90 lines on top of a few SDL2 declarations.
Queueing instead of callbacks¶
SDL2 can get audio from a program in two ways. With a callback, SDL's audio thread calls your function whenever it needs more samples. With a queue, the program pushes blocks of samples with SDL_QueueAudio and SDL plays them in order. Callbacks mean handing a HolyC function pointer to C, which this guide hasn't tested. The queue avoids that entirely: leave callback as NULL when opening the device.
SdlAudioSpec want;
MemSet(&want, 0, sizeof(SdlAudioSpec));
want.freq = AUDIO_RATE; // 48000 samples per second
want.format = AUDIO_S16SYS; // signed 16-bit
want.channels = 1;
want.samples = 1024;
U32 device = SDL_OpenAudioDevice(NULL, 0, &want, NULL, 0);
SDL_PauseAudioDevice(device, 0); // devices open paused
Passing 0 for allowed_changes tells SDL to convert to whatever the hardware actually uses, so the program can always write 48 kHz mono samples.
SdlAudioSpec in sdl2.HC mirrors C's SDL_AudioSpec field for field. Its fields happen to need no padding, so the layout is the same whether or not a compiler aligns them.
A square wave¶
A square wave at freq Hz is freq full cycles per second, or 2 * freq half-cycles. Sample i falls in half-cycle i * freq * 2 / AUDIO_RATE. Even half-cycles are high and odd ones are low:
It's all integer math, and the volume is out of 32767. The default, 3000, is plenty: square waves are loud.
Jumping straight from silence to full volume, or back, makes a click. AudioBeep ramps the level over the first and last 4 ms of each beep.
Playing sounds from a game¶
AudioBeep(&audio, freq, ms) generates the samples, queues them and returns at once. Calling it from a frame loop doesn't stall the game. A frequency of 0 queues silence, which is how the tune gets its rests.
Sounds play in the order they were queued, so a burst of events queues a backlog that plays late. For effects that should cut off whatever is playing, clear the queue first:
AudioQueuedMs reports how much sound is still waiting. AudioWait blocks until the queue is empty, which only a program like beep.HC needs before it exits.
Without a sound card¶
CI runners have no audio hardware. SDL's dummy driver accepts and discards the samples at real-time speed, so the program behaves the same way:
To check what the program actually produced, the disk driver writes the mixed output to a raw file at real-time speed. Open it in Audacity with File → Import → Raw Data.
The layout depends on which SDL you have. SDL2 writes exactly what the program asked for: 16-bit little-endian mono at 48 kHz. sdl2-compat on SDL3 (what Homebrew and Arch install) writes stereo with the same samples in both channels, and prints the format it chose. Either way there's some silence before and after the sound, and its length changes from run to run.
scripts/check.sh uses this to check beep. It keeps one channel, drops the silence at both ends, and compares the SHA-256 of the samples with the beep.pcm line in scripts/checksums.txt. Any other format than 16-bit little-endian fails the check rather than being guessed at.