#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include "main.h"
#include "wav.h"
// WAV header offsets
#define WAVHEADER_RIFF 0 // 32-bit - Always "RIFF"
#define WAVHEADER_RIFFSIZE 4 // 32-bit - Size of RIFF chunk
#define WAVHEADER_WAVE 8 // 32-bit - Always "WAVE"
#define WAVHEADER_FMT 12 // 32-bit - Always "fmt "
#define WAVHEADER_FMTSIZE 16 // 32-bit - Size of fmt chunk
#define WAVHEADER_ENCODING 20 // 16-bit - Encoding (1 = PCM)
#define WAVHEADER_NUMCHANNELS 22 // 16-bit - Number of channels
#define WAVHEADER_SAMPLERATE 24 // 32-bit - Samples per second
#define WAVHEADER_BYTESPERSEC 28 // 32-bit - Bytes per second
#define WAVHEADER_ALIGN 32 // 16-bit - Sample alignment
#define WAVHEADER_DEPTH 34 // 16-bit - Bits per sample
#define WAVHEADER_DATA 36 // 32-bit - Always "data"
#define WAVHEADER_DATASIZE 40 // 32-bit - Size of data chunk
#define SIZEOF_WAVHEADER 44 // Size of header
// Supported encoding formats
#define WAVENCODE_PCM 1 // Raw PCM
// Fixed values in the format
#define MAGIC(a,b,c,d) ((a)<<24 | (b)<<16 | (c)<<8 | (d))
#define MAGIC_RIFF MAGIC('R','I','F','F')
#define MAGIC_WAVE MAGIC('W','A','V','E')
#define MAGIC_FMT MAGIC('f','m','t',' ')
#define MAGIC_DATA MAGIC('d','a','t','a')
//***************************************************************************
// load_wav
// Parses and loads a WAV file into memory.
//---------------------------------------------------------------------------
// param filename ... file name to load from
//---------------------------------------------------------------------------
// return ........... pointer to waveform (NULL on failure)
//***************************************************************************
Waveform *load_wav(const char *filename)
{
Waveform *wave = NULL;
int errored = 0;
// Open the WAV file
FILE *file = fopen(filename, "rb");
if (file == NULL) {
fprintf(stderr, "Error: can't open WAV file \"%s\"\n", filename);
return NULL;
}
// Read WAV header
uint8_t header[SIZEOF_WAVHEADER];
if (fread(header, 1, SIZEOF_WAVHEADER, file) != SIZEOF_WAVHEADER) {
if (ferror(file))
goto read_error;
else
goto not_valid;
}
// Check format
// Note that since these are actually ASCII strings instead of 32-bit
// integers we read them as big endian (unlike the rest of the format)
uint32_t riff_text = header[WAVHEADER_RIFF+0] << 24 |
header[WAVHEADER_RIFF+1] << 16 |
header[WAVHEADER_RIFF+2] << 8 |
header[WAVHEADER_RIFF+3];
uint32_t wave_text = header[WAVHEADER_WAVE+0] << 24 |
header[WAVHEADER_WAVE+1] << 16 |
header[WAVHEADER_WAVE+2] << 8 |
header[WAVHEADER_WAVE+3];
uint32_t fmt_text = header[WAVHEADER_FMT+0] << 24 |
header[WAVHEADER_FMT+1] << 16 |
header[WAVHEADER_FMT+2] << 8 |
header[WAVHEADER_FMT+3];
uint32_t data_text = header[WAVHEADER_DATA+0] << 24 |
header[WAVHEADER_DATA+1] << 16 |
header[WAVHEADER_DATA+2] << 8 |
header[WAVHEADER_DATA+3];
if (riff_text != MAGIC_RIFF ||
wave_text != MAGIC_WAVE ||
fmt_text != MAGIC_FMT ||
data_text != MAGIC_DATA)
{
goto not_valid;
}
// Check encoding
uint16_t encoding = header[WAVHEADER_ENCODING+1] << 8 |
header[WAVHEADER_ENCODING+0];
if (encoding != WAVENCODE_PCM) {
fprintf(stderr, "Error[\"%s\"]: unsupported encoding type %u "
"(only PCM is supported)\n",
filename, (unsigned)(encoding));
fclose(file);
return NULL;
}
// Check number of channels
uint16_t num_channels = header[WAVHEADER_NUMCHANNELS+1] << 8 |
header[WAVHEADER_NUMCHANNELS+0];
if (num_channels != 1 && num_channels != 2) {
errored = 1;
fprintf(stderr, "Error[\"%s\"]: unsupported number of channels "
"(is %u, must be 1 or 2)\n",
filename, (unsigned)(num_channels));
}
// Check sample depth
uint16_t bit_depth = header[WAVHEADER_DEPTH+1] << 8 |
header[WAVHEADER_DEPTH+0];
if (bit_depth != 8 && bit_depth != 16) {
errored = 1;
fprintf(stderr, "Error[\"%s\"]: unsupported sample bit depth "
"(is %u, must be 8 or 16)\n",
filename, (unsigned)(bit_depth));
}
// Retrieve sample rate
uint32_t sample_rate = header[WAVHEADER_SAMPLERATE+3] << 24 |
header[WAVHEADER_SAMPLERATE+2] << 16 |
header[WAVHEADER_SAMPLERATE+1] << 8 |
header[WAVHEADER_SAMPLERATE+0];
if (sample_rate == 0) {
errored = 1;
fprintf(stderr, "Error[\"%s\"]: invalid sample rate (0Hz?)\n",
filename);
}
// If any of the above failed let's return now
// (we do it this way so all relevant messages are shown)
if (errored) {
fclose(file);
return NULL;
}
// Compute number of samples
uint32_t len = header[WAVHEADER_DATASIZE+3] << 24 |
header[WAVHEADER_DATASIZE+2] << 16 |
header[WAVHEADER_DATASIZE+1] << 8 |
header[WAVHEADER_DATASIZE+0];
len /= num_channels;
len /= bit_depth / 8;
// Allocate memory to hold the waveform
wave = malloc(sizeof(Waveform) + len);
if (wave == NULL) {
fprintf(stderr, "Error [\"%s\"]: out of memory!\n", filename);
fclose(file);
return NULL;
}
wave->len = len;
wave->rate = sample_rate;
// Read entire waveform into memory
// Convert it to 8-bit mono in the process
for (size_t pos = 0; pos < len; pos++) {
uint16_t sample_l;
uint16_t sample_r;
// Read left channel
if (bit_depth == 16) (void)(fgetc(file));
sample_l = fgetc(file);
// Read right channel, if any
if (num_channels == 2) {
if (bit_depth == 16) (void)(fgetc(file));
sample_r = fgetc(file);
} else {
sample_r = sample_l;
}
// 8-bit WAVs are unsigned but 16-bit are signed
// We want unsigned, so account for that now
if (bit_depth == 16) {
sample_l ^= 0x80;
sample_r ^= 0x80;
}
// Did anything go wrong while reading?
// (assume everything is bogus if so)
if (ferror(file) || feof(file))
goto read_error;
// Mix and store
wave->data[pos] = (sample_l + sample_r) / 2;
}
// We're done
fclose(file);
return wave;
// We reach here if something went wrong trying to read the file
// (though the most likely cause is that the file is truncated)
read_error:
fprintf(stderr, "Error[\"%s\"]: problem reading WAV file\n", filename);
fclose(file);
free(wave);
return NULL;
// We reach here if we determined it isn't a WAV file
not_valid:
fprintf(stderr, "Error[\"%s\"]: not a valid WAV file\n", filename);
fclose(file);
return NULL;
}