256 lines
8.2 KiB
C
256 lines
8.2 KiB
C
#include "wav_io.h"
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <pthread.h>
|
|
|
|
static pthread_mutex_t wav_io_mutex = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
// WAV file header structures (little-endian)
|
|
typedef struct {
|
|
char chunkID[4]; // "RIFF"
|
|
uint32_t chunkSize; // file size - 8
|
|
char format[4]; // "WAVE"
|
|
} WAVHeader;
|
|
|
|
typedef struct {
|
|
char subchunk1ID[4]; // "fmt "
|
|
uint32_t subchunk1Size; // 16 for PCM
|
|
uint16_t audioFormat; // 1 = PCM, 3 = IEEE float
|
|
uint16_t numChannels;
|
|
uint32_t sampleRate;
|
|
uint32_t byteRate;
|
|
uint16_t blockAlign;
|
|
uint16_t bitsPerSample;
|
|
} FMTSubchunk;
|
|
|
|
typedef struct {
|
|
char subchunk2ID[4]; // "data"
|
|
uint32_t subchunk2Size;
|
|
} DataSubchunk;
|
|
|
|
static void write_le16(FILE *f, uint16_t v) {
|
|
unsigned char buf[2];
|
|
buf[0] = v & 0xFF;
|
|
buf[1] = (v >> 8) & 0xFF;
|
|
fwrite(buf, 1, 2, f);
|
|
}
|
|
|
|
static void write_le32(FILE *f, uint32_t v) {
|
|
unsigned char buf[4];
|
|
buf[0] = v & 0xFF;
|
|
buf[1] = (v >> 8) & 0xFF;
|
|
buf[2] = (v >> 16) & 0xFF;
|
|
buf[3] = (v >> 24) & 0xFF;
|
|
fwrite(buf, 1, 4, f);
|
|
}
|
|
|
|
static uint16_t read_le16(const unsigned char *buf) {
|
|
return (uint16_t)buf[0] | ((uint16_t)buf[1] << 8);
|
|
}
|
|
|
|
static uint32_t read_le32(const unsigned char *buf) {
|
|
return (uint32_t)buf[0] | ((uint32_t)buf[1] << 8) |
|
|
((uint32_t)buf[2] << 16) | ((uint32_t)buf[3] << 24);
|
|
}
|
|
|
|
int save_wav_float(const char *filename, const float *buffer, size_t num_samples, unsigned int sample_rate) {
|
|
if (!filename || !buffer || num_samples == 0) return -1;
|
|
|
|
pthread_mutex_lock(&wav_io_mutex);
|
|
|
|
FILE *f = fopen(filename, "wb");
|
|
if (!f) {
|
|
pthread_mutex_unlock(&wav_io_mutex);
|
|
return -1;
|
|
}
|
|
|
|
// Calculate sizes
|
|
uint32_t data_size = (uint32_t)(num_samples * sizeof(float));
|
|
uint32_t chunk_size = 36 + data_size;
|
|
|
|
// Write RIFF header
|
|
fwrite("RIFF", 1, 4, f);
|
|
write_le32(f, chunk_size);
|
|
fwrite("WAVE", 1, 4, f);
|
|
|
|
// Write fmt subchunk
|
|
fwrite("fmt ", 1, 4, f);
|
|
write_le32(f, 16); // subchunk1Size
|
|
write_le16(f, 3); // audioFormat = IEEE float
|
|
write_le16(f, 1); // numChannels = mono
|
|
write_le32(f, sample_rate);
|
|
write_le32(f, sample_rate * sizeof(float)); // byteRate
|
|
write_le16(f, sizeof(float)); // blockAlign
|
|
write_le16(f, 32); // bitsPerSample
|
|
|
|
// Write data subchunk
|
|
fwrite("data", 1, 4, f);
|
|
write_le32(f, data_size);
|
|
|
|
// Write samples
|
|
fwrite(buffer, sizeof(float), num_samples, f);
|
|
|
|
fclose(f);
|
|
pthread_mutex_unlock(&wav_io_mutex);
|
|
return 0;
|
|
}
|
|
|
|
int load_wav_float(const char *filename, float **buffer, size_t *num_samples, unsigned int *sample_rate) {
|
|
if (!filename || !buffer || !num_samples || !sample_rate) return -1;
|
|
|
|
pthread_mutex_lock(&wav_io_mutex);
|
|
|
|
FILE *f = fopen(filename, "rb");
|
|
if (!f) {
|
|
pthread_mutex_unlock(&wav_io_mutex);
|
|
return -1;
|
|
}
|
|
|
|
// Read RIFF header
|
|
unsigned char header[12];
|
|
if (fread(header, 1, 12, f) != 12) {
|
|
fclose(f);
|
|
pthread_mutex_unlock(&wav_io_mutex);
|
|
return -1;
|
|
}
|
|
|
|
if (memcmp(header, "RIFF", 4) != 0 || memcmp(header + 8, "WAVE", 4) != 0) {
|
|
fclose(f);
|
|
pthread_mutex_unlock(&wav_io_mutex);
|
|
return -1;
|
|
}
|
|
|
|
// Read chunks until we find fmt and data
|
|
uint16_t audio_format = 0;
|
|
uint16_t num_channels = 0;
|
|
uint32_t sample_rate_val = 0;
|
|
uint16_t bits_per_sample = 0;
|
|
uint32_t data_size = 0;
|
|
float *data_buffer = NULL;
|
|
|
|
while (1) {
|
|
unsigned char chunk_header[8];
|
|
if (fread(chunk_header, 1, 8, f) != 8) break;
|
|
|
|
uint32_t chunk_size = read_le32(chunk_header + 4);
|
|
|
|
if (memcmp(chunk_header, "fmt ", 4) == 0) {
|
|
unsigned char fmt_data[16];
|
|
if (chunk_size < 16) {
|
|
fseek(f, chunk_size, SEEK_CUR);
|
|
continue;
|
|
}
|
|
if (fread(fmt_data, 1, 16, f) != 16) break;
|
|
|
|
audio_format = read_le16(fmt_data);
|
|
num_channels = read_le16(fmt_data + 2);
|
|
sample_rate_val = read_le32(fmt_data + 4);
|
|
bits_per_sample = read_le16(fmt_data + 14);
|
|
|
|
// Skip any extra fmt data
|
|
if (chunk_size > 16) fseek(f, chunk_size - 16, SEEK_CUR);
|
|
} else if (memcmp(chunk_header, "data", 4) == 0) {
|
|
data_size = chunk_size;
|
|
|
|
// Allocate buffer
|
|
size_t num_frames = data_size / (bits_per_sample / 8) / num_channels;
|
|
data_buffer = (float *)calloc(num_frames, sizeof(float));
|
|
if (!data_buffer) {
|
|
fclose(f);
|
|
pthread_mutex_unlock(&wav_io_mutex);
|
|
return -1;
|
|
}
|
|
|
|
if (audio_format == 3 && bits_per_sample == 32) {
|
|
// IEEE float
|
|
size_t read_size = num_frames * num_channels * sizeof(float);
|
|
if (read_size > data_size) read_size = data_size;
|
|
float *temp = (float *)malloc(read_size);
|
|
if (!temp) {
|
|
free(data_buffer);
|
|
fclose(f);
|
|
pthread_mutex_unlock(&wav_io_mutex);
|
|
return -1;
|
|
}
|
|
if (fread(temp, 1, read_size, f) != read_size) {
|
|
free(temp);
|
|
free(data_buffer);
|
|
fclose(f);
|
|
pthread_mutex_unlock(&wav_io_mutex);
|
|
return -1;
|
|
}
|
|
// Mix to mono if stereo
|
|
if (num_channels == 1) {
|
|
memcpy(data_buffer, temp, num_frames * sizeof(float));
|
|
} else {
|
|
for (size_t i = 0; i < num_frames; i++) {
|
|
float sum = 0.0f;
|
|
for (uint16_t ch = 0; ch < num_channels; ch++) {
|
|
sum += temp[i * num_channels + ch];
|
|
}
|
|
data_buffer[i] = sum / num_channels;
|
|
}
|
|
}
|
|
free(temp);
|
|
} else if (audio_format == 1 && bits_per_sample == 16) {
|
|
// 16-bit PCM
|
|
size_t read_size = num_frames * num_channels * sizeof(int16_t);
|
|
if (read_size > data_size) read_size = data_size;
|
|
int16_t *temp = (int16_t *)malloc(read_size);
|
|
if (!temp) {
|
|
free(data_buffer);
|
|
fclose(f);
|
|
pthread_mutex_unlock(&wav_io_mutex);
|
|
return -1;
|
|
}
|
|
if (fread(temp, 1, read_size, f) != read_size) {
|
|
free(temp);
|
|
free(data_buffer);
|
|
fclose(f);
|
|
pthread_mutex_unlock(&wav_io_mutex);
|
|
return -1;
|
|
}
|
|
// Convert to float and mix to mono
|
|
if (num_channels == 1) {
|
|
for (size_t i = 0; i < num_frames; i++) {
|
|
data_buffer[i] = (float)temp[i] / 32768.0f;
|
|
}
|
|
} else {
|
|
for (size_t i = 0; i < num_frames; i++) {
|
|
float sum = 0.0f;
|
|
for (uint16_t ch = 0; ch < num_channels; ch++) {
|
|
sum += (float)temp[i * num_channels + ch] / 32768.0f;
|
|
}
|
|
data_buffer[i] = sum / num_channels;
|
|
}
|
|
}
|
|
free(temp);
|
|
} else {
|
|
// Unsupported format
|
|
free(data_buffer);
|
|
fclose(f);
|
|
pthread_mutex_unlock(&wav_io_mutex);
|
|
return -1;
|
|
}
|
|
|
|
*buffer = data_buffer;
|
|
*num_samples = num_frames;
|
|
*sample_rate = sample_rate_val;
|
|
fclose(f);
|
|
pthread_mutex_unlock(&wav_io_mutex);
|
|
return 0;
|
|
} else {
|
|
// Skip unknown chunk
|
|
fseek(f, chunk_size, SEEK_CUR);
|
|
}
|
|
}
|
|
|
|
// If we get here, we didn't find data
|
|
if (data_buffer) free(data_buffer);
|
|
fclose(f);
|
|
pthread_mutex_unlock(&wav_io_mutex);
|
|
return -1;
|
|
}
|