46 lines
2.9 KiB
Markdown
46 lines
2.9 KiB
Markdown
# 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 non‑zero `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 10 ms 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 440 Hz 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 non‑zero 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 2 s for the file to be written before checking.
|
||
- The load operation is synchronous: the callback sleeps 1 s after the MIDI command to give the main loop time to process it.
|