#include "wav_io.h" #include #include #include #include #include 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; }