High‑level purpose & runtime flow

This SDL2 / SDL2_ttf program records mono audio from any capture device, streams it to a WAV file, visualises the live waveform, and performs a lightweight autocorrelation‑based tuner that prints pitch (Hz) → musical note.

  1. Initialise SDL (audio + video) and SDL_ttf.
  2. Enumerate input devices and prompt user selection.
  3. Open the chosen device (SDL_OpenAudioDevice) with floating‑point 48000 Hz.
  4. Create an accelerated renderer & font for on‑screen text.
  5. Pre‑write a WAV header to capture.wav.
  6. Start capture – each callback fills a ring buffer.
  7. Main loop:
    • Drain 2048 samples at a time, write them to disk,
    • detect pitch (detect_pitch) & translate to note (freq_to_note),
    • render scrolling waveform + pitch label.
  8. On quit, finalise WAV sizes & release resources.

Compile‑time constants & globals

Settings block


#define WIN_W       1024   /* window width  */
#define WIN_H        400   /* window height */
#define BUF_SAMPLES  8192  /* ring‑buffer size (power‑of‑two) */
#define CHUNK        2048  /* processing hop size            */
#define WAV_NAME "capture.wav"
			

main() step‑by‑step

3.1 SDL / font init

SDL_Init(SDL_INIT_AUDIO | SDL_INIT_VIDEO) and TTF_Init() are mandatory before any I/O or rendering.

3.2 Device discovery

3.3 Opening the stream


SDL_AudioSpec want = {0};
want.freq     = 48000;          /* sample‑rate */
want.format   = AUDIO_F32SYS;   /* 32‑bit float */
want.channels = 1;              /* mono */
want.samples  = CHUNK;          /* callback buffer size */
want.callback = capture_cb;     /* recording handler */
			

Returned have describes the exact format SDL could provide; the code uses it later for WAV header values.

3.4 Renderer & font

An SDL_Window → SDL_Renderer pair is set up.
Font path tries Apple SF first, falls back to DejaVu.

3.5 WAV header priming

Because the data chunk size is unknown upfront, the code back‑fills RIFF chunk size and data chunk size after recording stops (see Section 6).

3.6 Capture loop

capture_cb() – audio callback

capture_cb(void *udata, Uint8 *stream, int len) is invoked by SDL on a real‑time thread.

⚠ Thread‑safety: Only wr is modified inside the callback; the main thread reads it without locks, relying on atomicity of 32‑bit writes.

detect_pitch() – autocorrelation


float detect_pitch(const float *buf, int n, int sr)
			

For each candidate lag between minLag (1000 Hz) and maxLag (50 Hz) it performs a straight dot‑product of the signal with itself, retaining the lag with maximum energy. Returned pitch = sr / bestLag, or 0 if silence.

In practice this O(n·lag) scan is < 2 ms for 2048×900 floating operations on modern CPUs.

freq_to_note() – Hz → MIDI note label


const char *freq_to_note(float f, char *out, size_t n)
			

Uses the well‑known formula
  midi = 69 + 12·log2(f / 440 Hz)
then maps midi % 12 to name array {C, C#, … B} and puts octave = (midi/12 – 1).

Graceful teardown & WAV footer

After the loop exits, the code seeks back to two offsets:

  1. Byte 4 (RIFF size) → 36 + bytes_total
  2. data_size_pos → bytes_total

This finalises the file so any DAW/player can open it.
Finally all SDL objects, the device, and TTF subsystems are dequeued.

Ideas for extension