A minimal, portable, header-only C library for a dead-simple audio format. No magic numbers, no validation overhead, no dependencies. You own the data, you own the responsibility.
Most audio formats carry significant overhead — chunk negotiation, codec detection, metadata sprawl. SAF doesn't. The format assumes the user knows what they're doing. If you give it a file, it trusts you. This makes it fast, simple, and easy to embed anywhere.
| Offset | Size | Type | Description |
|---|---|---|---|
| 0 | 4 | uint32 | Sample rate (little-endian) |
| 4 | 1 | uint8 | Channel count |
| 5 | 1 | uint8 | Bit depth |
| 6 | ... | raw | PCM samples (interleaved) |
Total header size: 6 bytes.
Samples are raw interleaved PCM. Stereo audio is stored LRLRLR... No padding, no alignment, no checksums. File length determines data length.
Supported bit depths: 8, 16, 32 (determined by your data — SAF doesn't enforce this).
SAF is header-only. Copy saf.h into your project.
In exactly one .c file, define SAF_IMPL before including:
#define SAF_IMPL
#include "saf.h"All other files just include normally:
#include "saf.h"SAF_Header header = {
.sample_rate = 44100,
.channels = 1,
.bit_depth = 16,
};
int16_t *samples = /* your audio data */;
size_t count = /* number of samples */;
SAF_RetCode ret = saf_write("audio.saf", header, samples, count);
if (ret != SAF_OK)
fprintf(stderr, "Error: %s\n", saf_strerror(ret));SAF_Header header;
void *samples = NULL;
size_t sample_count = 0;
SAF_RetCode ret = saf_read("audio.saf", &header, &samples, &sample_count);
if (ret != SAF_OK)
fprintf(stderr, "Error: %s\n", saf_strerror(ret));
int16_t *pcm = (int16_t *)samples;
/* use pcm... */
saf_free(&samples);The library allocates the sample buffer. Always free with saf_free — never free() directly, in case the implementation changes.
// Write audio data to a .saf file
SAF_RetCode saf_write(const char *filename, SAF_Header header, const void *samples, size_t sample_count);
// Read a .saf file — allocates sample buffer, caller must saf_free()
SAF_RetCode saf_read(const char *filename, SAF_Header *header, void **samples, size_t *sample_count);
// Free a buffer allocated by saf_read — nulls the pointer
void saf_free(void **samples);
// Return human-readable string for a return code
const char *saf_strerror(SAF_RetCode code);| Code | Meaning |
|---|---|
SAF_OK |
Success |
SAF_ERR_OPEN |
Failed to open file |
SAF_ERR_WRITE |
Failed to write file |
SAF_ERR_READ |
Failed to read file |
SAF_ERR_MEM |
Memory allocation failed |
make # build and run test
make clean # clean artifactsRequires GCC and libm.
SAF manually serializes header fields byte-by-byte, so it is endian-safe and makes no assumptions about struct layout or compiler padding. Tested on Linux (GCC, Clang). Should work on any C99-compliant compiler.
MIT — see LICENSE.