This tutorial demystifies rec_vis_tuner.c
, a compact SDL2 application that:
.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.
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
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
.
CHUNK
frames:#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.
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.
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.
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.
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.
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.
After recording, the console lists *.wav
files in the current directory. Choose one to hear it back through:
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.
yin
or libaubio
for better stability.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.