]> git.bts.cx Git - benzene.git/blob - src/bz/audio/wav.c
Sprites
[benzene.git] / src / bz / audio / wav.c
1 #include <bz/audio/wav.h>
2
3 #include <bz/memory/allocator.h>
4 #include <bz/resources/resource.h>
5 #include <bz/types/identifier_internal.h>
6
7 #include <assert.h> // Static assertions
8
9 // https://www.aelius.com/njh/wavemetatools/doc/riffmci.pdf
10
11 #define PACKED_STRUCT __attribute__((__packed__))
12
13 typedef uint16_t RIFF_WORD;
14 typedef uint32_t RIFF_DWORD;
15 typedef uint8_t RIFF_BYTE;
16
17 typedef RIFF_DWORD RIFF_FOURCC; // Four-character code
18
19 typedef RIFF_FOURCC RIFF_CKID; // Four-character-code chunk identifier
20 typedef RIFF_DWORD RIFF_CKSIZE; // 32-bit unsigned size value
21
22 struct PACKED_STRUCT RIFF_CK {
23 RIFF_CKID ckID;
24 RIFF_CKSIZE ckSize;
25 //RIFF_BYTE ckData[];
26 };
27 typedef struct RIFF_CK RIFF_CK;
28 static_assert(sizeof(RIFF_CK) == 8, "CK is wrong size");
29
30 #define CHUNK_ID(a,b,c,d) (((a) << 0) | ((b) << 8) | ((c) << 16) | ((d) << 24))
31
32 struct PACKED_STRUCT RIFF_WAVEFormatCK {
33 RIFF_WORD wFormatTag; // Format category
34 RIFF_WORD wChannels; // Number of channels
35 RIFF_DWORD dwSamplesPerSec; // Sampling rate
36 RIFF_DWORD dwAvgBytesPerSec; // For buffer estimation
37 RIFF_WORD wBlockAlign; // Data block size
38 };
39 typedef struct RIFF_WAVEFormatCK RIFF_WAVEFormatCK;
40
41 struct PACKED_STRUCT RIFF_PCMFormatCK {
42 RIFF_WORD wBitsPerSample; // Sample size
43 };
44 typedef struct RIFF_PCMFormatCK RIFF_PCMFormatCK;
45
46 enum {
47 RIFF_WAVE_FORMAT_PCM = 0x0001, // Microsoft Pulse Code Modulation (PCM) format
48 RIFF_IBM_FORMAT_MULAW = 0x0101, // IBM mu-law format
49 RIFF_IBM_FORMAT_ALAW = 0x0102, // IBM a-law format
50 RIFF_IBM_FORMAT_ADPCM = 0x0103 // IBM AVC Adaptive Differential Pulse Code Modulation format
51 };
52
53 void *bzAudioLoadWAV(size_t *sizeOut, BZMemoryArenaID arena, uint32_t *samplingRateOut, uint16_t *channelsOut, uint16_t *bitsPerSampleOut, const char *identifierFmt, ...) {
54 bzMakeIdentifier(identifier, identifierFmt);
55
56 BZResourceID handle = bzResourcesOpenResource("sounds", "assets/sounds/%s.wav", identifier);
57
58 void *wavData = NULL;
59
60 for (;;) {
61 RIFF_CK chunk;
62 size_t readLength = bzResourcesReadBytes(handle, &chunk, sizeof(chunk));
63 if (readLength != sizeof(chunk)) {
64 break;
65 }
66
67 size_t chunkDataStartPosition = bzResourcesTell(handle);
68
69 switch (chunk.ckID) {
70 case CHUNK_ID('R','I','F','F'): {
71 RIFF_CKID fileType;
72 bzResourcesReadBytes(handle, &fileType, sizeof(fileType));
73 bzAssertMessage(fileType == CHUNK_ID('W','A','V','E'), "Not a WAV file");
74 break;
75 }
76
77 case CHUNK_ID('f','m','t',' '): {
78 RIFF_WAVEFormatCK format;
79 bzResourcesReadBytes(handle, &format, sizeof(format));
80 bzAssertMessage(format.wFormatTag == RIFF_WAVE_FORMAT_PCM, "Not a PCM file");
81 RIFF_PCMFormatCK pcmFormat;
82 bzResourcesReadBytes(handle, &pcmFormat, sizeof(pcmFormat));
83
84 *samplingRateOut = format.dwSamplesPerSec;
85 *channelsOut = format.wChannels;
86 *bitsPerSampleOut = pcmFormat.wBitsPerSample;
87 }
88
89 case CHUNK_ID('d','a','t','a'): {
90 wavData = bzMemoryAlloc(arena, chunk.ckSize);
91 *sizeOut = chunk.ckSize;
92 bzResourcesReadBytes(handle, wavData, chunk.ckSize);
93 }
94
95 default:
96 // Skip this chunk...
97 bzResourcesSeek(handle, chunkDataStartPosition + chunk.ckSize);
98 break;
99 }
100 }
101
102 bzResourcesCloseResource(handle);
103
104 bzLog("Loaded WAV '%s', %d, %d, %d", identifier, *samplingRateOut, *channelsOut, *bitsPerSampleOut);
105
106 return wavData;
107 }