Program Overview

This tutorial demystifies rec_vis_tuner.c, a compact SDL2 application that:

  • Lists audio capture devices and records microphone input.
  • Visualises the live waveform while running a real‑time guitar‑style tuner.
  • Saves the session as a .wav file — then optionally replays any recording with DSP effects (echo, tremolo, low‑pass).

All processing is single‑threaded except audio I/O, handled by SDL’s internal callback.

Compiling & Dependencies

Linux

sudo apt-get install libsdl2-dev libsdl2-ttf-dev
gcc rec_vis_tuner.c -std=c11 $(sdl2-config --cflags --libs) -lSDL2_ttf -lm -o rec_vis_tuner

macOS (Homebrew)

brew install sdl2 sdl2_ttf
gcc rec_vis_tuner.c -std=c11 $(sdl2-config --cflags --libs) -lSDL2_ttf -lm -o rec_vis_tuner

Link order matters on some platforms: keep -lSDL2_ttf after sdl2-config --libs.

Architecture & Data Flow

Diagram
  1. Input Device Selection via SDL_GetNumAudioDevices.
  2. Audio Callback (capture_cb) fills a ring buffer.
  3. Main Loop pops CHUNK frames:
    • visualises waveform
    • runs detect_pitch
    • writes to WAV file.
  4. On exit, header sizes are patched and a CLI menu offers playback with effects.

Compile‑time Knobs

#define WIN_W      1024   /* window width  */
#define WIN_H       400   /* window height */
#define BUF_SAMPLES 8192   /* ring‑buffer power‑of‑two */
#define CHUNK       2048   /* analysis block size      */

Set CHUNK < BUF_SAMPLES. Larger CHUNK → better pitch resolution but higher latency.

Audio Capture & Callback

SDL_AudioSpec want = {0};
want.freq     = 48000;
want.format   = AUDIO_F32SYS;
want.channels = 1;
want.samples  = CHUNK;
want.callback = capture_cb;

The callback copies incoming float samples into a lock‑free ring buffer of BUF_SAMPLES.

capture_cb snippet
ring[w++ & (BUF_SAMPLES-1)] = in[i];

Bit‑wise & (BUF_SAMPLES‑1) is a fast modulo because BUF_SAMPLES is power‑of‑two.

Pitch Detection (Autocorrelation)

The function iterates over lags from 50 Hz → 1 kHz and searches for the highest correlation.

for(lag = minLag; lag <= maxLag; ++lag){
    float sum = 0;
    for(i = 0; i < n-lag; ++i)
        sum += buf[i] * buf[i+lag];
    if(sum > best){ best = sum; bestLag = lag; }
}

CPU‑friendly (~2 ms) due to small CHUNK. For pro use, an FFT‑based approach is faster.

Waveform Rendering

float xs = (float)WIN_W / CHUNK;
SDL_SetRenderDrawColor(ren, 0,91,150,255);
for(int i=0;i

Samples are mapped linearly to the X‑axis; Y‑scaling keeps ±1.0 amplitudes inside the view.

Tuner Overlay & Text Rendering

SDL_ttf renders the current frequency + nearest note:

char label[64];
snprintf(label, sizeof label, "%.1f Hz  (%s)", last_pitch, note_str);
SDL_Surface* surf = TTF_RenderUTF8_Blended(font, label, colour);
SDL_Texture* tex  = SDL_CreateTextureFromSurface(ren, surf);
SDL_RenderCopy(ren, tex, NULL, &dst);

All UTF‑8 capable; replace font path as needed on Windows.

Recording to WAV (32‑bit Float)

The app builds the WAV header manually so it can stream‑write samples, then patches sizes on exit.

// after capture loop
SDL_RWseek(wav, 4, RW_SEEK_SET); WR32(36 + bytes_total); // RIFF chunk size
SDL_RWseek(wav, data_size_pos, RW_SEEK_SET); WR32(bytes_total);

WAV format 3 = IEEE float. Compatible with Audacity/Reaper etc.

Playback & Real‑Time Effects

After recording, the console lists *.wav files in the current directory. Choose one to hear it back through:

  • Echo — 0.5 s circular buffer.
  • Tremolo — 5 Hz amplitude LFO.
  • Low‑pass — simple 1‑pole filter (alpha = 0.1).
float in = samples[i];
float out = in;
// switch(fx) { ... } // effect‑specific processing
samples[i] = out;

Processing is in‑place; assumes 32‑bit float format.

Where to Go Next

  • Add reverb using a convolution or comb‑filter chain.
  • Replace naive pitch detection with yin or libaubio for better stability.
  • Stream the waveform to a web client via WebSockets for remote visualisation.
  • Swap SDL_Renderer for OpenGL to draw thousands of samples more efficiently.

SDL2’s cross‑platform backend lets you port to Raspberry Pi or Windows with minimal tweaks.