|
1 | | -# saf |
| 1 | +# SAF — Simple Audio Format |
| 2 | + |
| 3 | +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. |
| 4 | + |
| 5 | +## Philosophy |
| 6 | + |
| 7 | +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. |
| 8 | + |
| 9 | +## Format Specification |
| 10 | + |
| 11 | +| Offset | Size | Type | Description | |
| 12 | +| ------ | ---- | ------ | --------------------------- | |
| 13 | +| 0 | 4 | uint32 | Sample rate (little-endian) | |
| 14 | +| 4 | 1 | uint8 | Channel count | |
| 15 | +| 5 | 1 | uint8 | Bit depth | |
| 16 | +| 6 | ... | raw | PCM samples (interleaved) | |
| 17 | + |
| 18 | +**Total header size: 6 bytes.** |
| 19 | + |
| 20 | +Samples are raw interleaved PCM. Stereo audio is stored LRLRLR... No padding, no alignment, no checksums. File length determines data length. |
| 21 | + |
| 22 | +Supported bit depths: 8, 16, 32 (determined by your data — SAF doesn't enforce this). |
| 23 | + |
| 24 | +## Usage |
| 25 | + |
| 26 | +SAF is header-only. Copy `saf.h` into your project. |
| 27 | + |
| 28 | +In **exactly one** `.c` file, define `SAF_IMPL` before including: |
| 29 | + |
| 30 | +```c |
| 31 | +#define SAF_IMPL |
| 32 | +#include "saf.h" |
| 33 | +``` |
| 34 | + |
| 35 | +All other files just include normally: |
| 36 | + |
| 37 | +```c |
| 38 | +#include "saf.h" |
| 39 | +``` |
| 40 | + |
| 41 | +### Writing |
| 42 | + |
| 43 | +```c |
| 44 | +SAF_Header header = { |
| 45 | + .sample_rate = 44100, |
| 46 | + .channels = 1, |
| 47 | + .bit_depth = 16, |
| 48 | +}; |
| 49 | + |
| 50 | +int16_t *samples = /* your audio data */; |
| 51 | +size_t count = /* number of samples */; |
| 52 | + |
| 53 | +SAF_RetCode ret = saf_write("audio.saf", header, samples, count); |
| 54 | +if (ret != SAF_OK) |
| 55 | + fprintf(stderr, "Error: %s\n", saf_strerror(ret)); |
| 56 | +``` |
| 57 | +
|
| 58 | +### Reading |
| 59 | +
|
| 60 | +```c |
| 61 | +SAF_Header header; |
| 62 | +void *samples = NULL; |
| 63 | +size_t sample_count = 0; |
| 64 | +
|
| 65 | +SAF_RetCode ret = saf_read("audio.saf", &header, &samples, &sample_count); |
| 66 | +if (ret != SAF_OK) |
| 67 | + fprintf(stderr, "Error: %s\n", saf_strerror(ret)); |
| 68 | +
|
| 69 | +int16_t *pcm = (int16_t *)samples; |
| 70 | +/* use pcm... */ |
| 71 | +
|
| 72 | +saf_free(&samples); |
| 73 | +``` |
| 74 | + |
| 75 | +The library allocates the sample buffer. Always free with `saf_free` — never `free()` directly, in case the implementation changes. |
| 76 | + |
| 77 | +## API |
| 78 | + |
| 79 | +```c |
| 80 | +// Write audio data to a .saf file |
| 81 | +SAF_RetCode saf_write(const char *filename, SAF_Header header, const void *samples, size_t sample_count); |
| 82 | + |
| 83 | +// Read a .saf file — allocates sample buffer, caller must saf_free() |
| 84 | +SAF_RetCode saf_read(const char *filename, SAF_Header *header, void **samples, size_t *sample_count); |
| 85 | + |
| 86 | +// Free a buffer allocated by saf_read — nulls the pointer |
| 87 | +void saf_free(void **samples); |
| 88 | + |
| 89 | +// Return human-readable string for a return code |
| 90 | +const char *saf_strerror(SAF_RetCode code); |
| 91 | +``` |
| 92 | +
|
| 93 | +### Return Codes |
| 94 | +
|
| 95 | +| Code | Meaning | |
| 96 | +| --------------- | ------------------------ | |
| 97 | +| `SAF_OK` | Success | |
| 98 | +| `SAF_ERR_OPEN` | Failed to open file | |
| 99 | +| `SAF_ERR_WRITE` | Failed to write file | |
| 100 | +| `SAF_ERR_READ` | Failed to read file | |
| 101 | +| `SAF_ERR_MEM` | Memory allocation failed | |
| 102 | +
|
| 103 | +## Building the Test |
| 104 | +
|
| 105 | +```bash |
| 106 | +make # build and run test |
| 107 | +make clean # clean artifacts |
| 108 | +``` |
| 109 | + |
| 110 | +Requires GCC and `libm`. |
| 111 | + |
| 112 | +## Portability |
| 113 | + |
| 114 | +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. |
| 115 | + |
| 116 | +## License |
| 117 | + |
| 118 | +MIT — see [LICENSE](LICENSE). |
0 commit comments