I'm writing a .wav PCM file header in C, but one part I'm still stumped on handling is the bits per sample format parameter of a file. Right now I've only added support for 8-bit and 16-bit PCMs (storing audio sample data in a int16_t*
or int8_t*
byteArray
), but it would be nice to be able to read the sample size of a file before loading its data into memory, and writing a bunch of functions to handle every single byte size (writeToWav8()
, writeToWav16()
, writeToWav32()
) feels incredibly naive.
The only solutions to this I could think of were
Is there a way to more cleanly handle this issue?
The struct looks like this right now:
typedef struct RiffHeader {
// basic formatting info for the file, not relevant
} RIFF_CHUNK;
typedef struct FmtHeader {
uint32_t Subchunk1ID;
// all of these determine how to read the audio
uint32_t Subchunk1Size;
uint16_t AudioFormat;
uint16_t NumChannels;
uint32_t SampleRate;
uint32_t ByteRate;
uint16_t BlockAlign;
uint16_t BitsPerSample; // bits per sample, determines how large each bit is in the data array
} FMT_CHUNK;
typedef struct DataChunk8 {
uint32_t Subchunk2ID;
uint32_t Subchunk2Size;
int8_t *byteArray; // pointer to start of data
// data size is equal to subchunk2size
} DATA_CHUNK_8;
typedef struct DataChunk16 {
uint32_t Subchunk2ID;
uint32_t Subchunk2Size;
int16_t *byteArray; // pointer to start of data
// data size is equal to subchunk2size
} DATA_CHUNK_16;
typedef struct WaveFile8 {
RIFF_CHUNK RIFF;
FMT_CHUNK FMT;
DATA_CHUNK_8 DATA;
} WAV_FILE_8;
typedef struct WaveFile_16 {
RIFF_CHUNK RIFF;
FMT_CHUNK FMT;
DATA_CHUNK_16 DATA;
} WAV_FILE_16;
followed by a bunch of helper functions that input and/or output WAV_FILE_8*
or WAV_FILE_16*
*Additionally, I'm kind of stumped on file reading, since I'm currently using two functions:
WAV_FILE_8* readFromFile8(const char* path)
WAV_FILE_16* readFromFile16(const char* path)
which are intended to do what you'd expect, but I'm not sure how to determine and decide to return the appropriately-sized WAV (and use the appropriate function) when reading from an unknown file.
You can simply use flexible array member for the data part. For the remaining part there's a lot of duplication in your code so just put them in a tagged union
typedef struct DataChunk {
uint32_t Subchunk2ID;
uint32_t Subchunk2Size;
union {
int8_t byteArray[];
int16_t shortArray[];
};
} DATA_CHUNK;
typedef struct WaveFile {
RIFF_CHUNK RIFF;
FMT_CHUNK FMT;
DATA_CHUNK DATA;
} WAV_FILE;
This feature was common before it even entered the C standard. For example in GCC it's array with zero length at the end of the struct, and in MSVC you can't have array with zero length so they used array with size 1 instead. I've also made use the anonymous struct feature to avoid another unnecessary field reference
When allocating the struct you'll need to specify enough memory for the whole header plus data
// Allocate memory for the header
WAV_FILE *wav = malloc(sizeof(*wav));
// Read the header
if (fread(wav, sizeof(WAV_FILE), 1, inputfile) == 1) {
// Resize the struct to make room for the data
WAV_FILE *newWav = realloc(sizeof(*wav) + wav->DATA.Subchunk2Size);
if (newWav) {
wav = newWav;
// Do something with the data
}
}
I'm not a language lawyer so I'm not sure if the flexible array member is in a union or nested struct is allowed or not. But if it's not then you can easily regroup the structs to move the array to the end of the outermost struct, for example like this
typedef struct DataChunk {
uint32_t Subchunk2ID;
uint32_t Subchunk2Size;
} DATA_CHUNK_HEADER;
typedef struct WaveFile {
RIFF_CHUNK RIFF;
FMT_CHUNK FMT;
DATA_CHUNK_HEADER DATA_HEADER;
union {
int8_t byteArray[];
int16_t shortArray[];
};
} WAV_FILE;
// or
typedef union WaveFile2 {
struct {
RIFF_CHUNK RIFF8;
FMT_CHUNK FMT8;
DATA_CHUNK_HEADER DATA_HEADER8;
int8_t byteArray[];
};
struct {
RIFF_CHUNK RIFF16;
FMT_CHUNK FMT16;
DATA_CHUNK_HEADER DATA_HEADER16;
int16_t shortArray[];
};
} WAV_FILE2;
Alternatively you can remove the int8_t *byteArray;
completely and use only int16_t *shortArray;
which can be cast to char*
or int8_t*
when the file is a 8-bit one without violating the aliasing rule
Of course you also need to take care of the endianness as nielsen commented