Overview

SDL2’s low‑level audio subsystem gives you raw PCM buffers, letting you implement custom effects ranging from simple volume envelopes to real‑time convolution reverb. This guide walks through device setup, the callback model and several effect recipes.

Device Setup & Callback Basics

1. Initialise SDL Audio


if (SDL_Init(SDL_INIT_AUDIO) != 0) {
    SDL_Log("Init error %s", SDL_GetError());
    return ‑1;
}
      

2. Desired vs Obtained spec


SDL_AudioSpec want = {0}, have;
want.freq     = 48000;
want.format   = AUDIO_F32SYS;  /* 32‑bit float */
want.channels = 2;
want.samples  = 1024;
want.callback = audioCB;
want.userdata = &fxState;

SDL_AudioDeviceID dev = SDL_OpenAudioDevice(
        NULL,               /* default output  */
        0,                  /* is capture?     */
        &want, &have,
        SDL_AUDIO_ALLOW_FORMAT_CHANGE);
      

3. Start ⇄ Pause Streaming


SDL_PauseAudioDevice(dev, 0);   /* 0 = start, 1 = pause */
      

Shared Effect State

Create a state‑struct so multiple effects can share parameters:


typedef struct {
    float vol      ;  /* 0 – 1 */
    float pan      ;  /* ‑1 = L … +1 = R */
    float delayBuf[48000]; /* 1 s @ 48 kHz mono */
    size_t delayPos;
    float delayMix ;  /* wet/dry */
    float pitchSemis;
} FxState;
      

Pass &fxState via want.userdata.

Volume & Amplitude Envelopes

Using SDL_MixAudioFormat


SDL_MixAudioFormat(stream, stream,
                   AUDIO_F32SYS,
                   len,
                   (int)(fxState.vol * SDL_MIX_MAXVOLUME));
      

Manual Per‑Sample Gain (for envelopes)


float *f = (float*)stream;
size_t frames = len / sizeof(float);
for (size_t i=0; i < frames; i++)
    f[i] *= currentGain(i);  /* fade‑in / fade‑out */
      

Note: keep envelopes in float domain to avoid clipping artefacts.

Stereo Panning


float L = (fxState.pan < 0) ? 1.0f       : 1.0f ‑ fxState.pan;
float R = (fxState.pan > 0) ? 1.0f       : 1.0f + fxState.pan;
for (size_t i=0; i < frames; i+=2) {
    f[i  ] *= L;
    f[i+1] *= R;
}
      

Equal‑power panning uses sqrt() for perceptual balance.

Delay / Echo


for (size_t i=0; i < frames; ++i) {
    float dry = f[i];
    float wet = fxState.delayBuf[fxState.delayPos];
    fxState.delayBuf[fxState.delayPos] = dry;
    f[i] = dry * (1.0f ‑ fxState.delayMix) + wet * fxState.delayMix;
    if (++fxState.delayPos >= 48000) fxState.delayPos = 0;
}
      

Increase depth: add a feedback term before writing to delayBuf.

Pitch Shift (Granular)

SDL2 has no built‑in resampler, but you can pitch‑shift by buffer‑skipping (naïve, cheap) or with granular overlap‑add. A lightweight approach uses SDL’s SDL_AudioStream:


SDL_AudioStream *rs =
    SDL_NewAudioStream(AUDIO_F32SYS, 2, 48000,
                       AUDIO_F32SYS, 2,
                       48000 * powf(2.f, fxState.pitchSemis / 12.f));
/* write original PCM into rs, then SDL_AudioStreamGet() resampled PCM */
      

For smoother artefact‑free shifts, consider third‑party libs (Rubber Band, SoundTouch) and push resampled data into the callback.

Filters (Low‑pass & High‑pass)


typedef struct { float z1, a0, b1; } OnePole;
static inline float lp(OnePole *s, float x) {
    float y = s->a0 * x + s->b1 * s->z1;
    return s->z1 = y;
}
/* … inside callback … */
for (size_t i=0;i<frames;++i)
    f[i] = lp(&lowPass, f[i]);
      

Set coefficients with omega = 2πf / Fs and a0 = 1‑exp(‑omega), b1 = 1‑a0.

Using SDL_mixer for Chainable DSP

SDL_mixer 2.8+ supports Mix_RegisterEffect for per‑channel DSP.


int effectCB(int chan, void *stream, int len, void *udata) {
    (void)chan;
    myCoolEffect((float*)stream, len/sizeof(float), udata);
    return 0;
}
Mix_RegisterEffect(MIX_CHANNEL_POST, effectCB, NULL, &fxState);
      

Advantages: automatic mixing, channel grouping, run‑time chaining without writing your own callback.  Downside: tied to SDL_mixer’s internal format and thread.

Performance & Best Practices

Further Reading

• SDL Wiki – Audio Subsystem
• “Designing Audio Effect Plug‑ins in C++” – Will Pirkle
SoundTouch & Rubber Band libraries for pitch/tempo
• Game Audio Coding Patterns – GDC Vault talks