Files
looper/docs/6-sampling-and-recording.md
Loic Coenen 51493d5cab docs: add WAV load/save documentation and update evaluation table
Co-authored-by: aider (deepseek/deepseek-reasoner) <aider@aider.chat>
2026-05-12 19:35:21 +00:00

2.9 KiB
Raw Blame History

Sampling and Recording (WAV Load/Save)

The looper supports loading a WAV file into channel 0 and saving the current loop of channel 0 as a WAV file. Both operations use the libsndfile library, ensuring correct handling of RIFF headers, chunk sizes, and sample format conversion.

Load Command

  • MIDI note 70 with the control key (note 64) triggers loading.
  • The file loop.wav (located in the working directory) is read by wav_read() in src/wav.c.
  • The function calls sf_open(path, SFM_READ, &info).
  • It accepts only mono PCM WAV files. If the file is not mono or has an invalid sample rate, it returns -1.
  • The number of frames read is capped at LOOP_BUF_SIZE (5 seconds at 48 kHz).
  • The data is stored in channels[0].loop_buffer and channels[0].loop_count is set atomically.
  • The state of channel 0 is set to STATE_LOOPING and prev_state is set to -1 to trigger the loop start in the next audio cycle.

Save Command

  • MIDI note 71 with the control key (note 64) triggers saving.
  • The looper must currently be in STATE_LOOPING and have a nonzero loop_count.
  • A ring buffer (RingBuf) is allocated with capacity 2 × loop_count samples.
  • The pointer to the ring buffer is published via atomic_store_explicit on channels[0].save_ring.
  • In each audio callback cycle, if the channel is looping and a save ring exists, the audio output data is written into the ring buffer.
  • A dedicated writer thread (writer_thread) is launched (detached) to consume the ring buffer.
  • The writer thread reads loop_count samples from the ring buffer, sleeping 10ms between empty reads.
  • Once all samples are collected, it writes them to save.wav using sf_writef_float().
  • After writing, the ring buffer is destroyed and freed, and the save ring pointer is set to NULL.

Dependencies

  • libsndfile must be installed (development headers). Add -lsndfile to your linker flags (already present in the provided makefile).

Implementation Files

  • src/wav.c contains wav_read() and wav_write() based on libsndfile.
  • src/looper.c contains the load/save command handling in looper_process_commands() and the writer thread function.
  • src/channel.h defines save_ring as _Atomic RingBuf *.

Testing

  • The integration test test_wav_load creates a short 440Hz WAV file, loads it via MIDI, and checks for ≥3 bursts of audio output.
  • The integration test test_wav_save records a beep, loops it, issues the save command, and verifies the resulting WAV file has nonzero data size.

Notes

  • The save operation is asynchronous: the writer thread runs in the background while the audio callback continues to fill the ring buffer. The test waits 2s for the file to be written before checking.
  • The load operation is synchronous: the callback sleeps 1s after the MIDI command to give the main loop time to process it.