From 00ef1f4101edbe11c978dd69e2e105638ef72b12 Mon Sep 17 00:00:00 2001 From: ignacio Date: Sun, 10 Nov 2024 15:53:50 -0300 Subject: [PATCH] Enable Direct Loading of Raw IR Data from Memory These modifications allow the IR data to be loaded from memory, enabling it to be embedded in the application and loaded at runtime instead of from a file. --- dsp/ImpulseResponse.cpp | 18 +++ dsp/ImpulseResponse.h | 1 + dsp/wav.cpp | 247 ++++++++++++++++++++++++++++++++++++++++ dsp/wav.h | 7 ++ 4 files changed, 273 insertions(+) diff --git a/dsp/ImpulseResponse.cpp b/dsp/ImpulseResponse.cpp index 6850f53..b66612c 100644 --- a/dsp/ImpulseResponse.cpp +++ b/dsp/ImpulseResponse.cpp @@ -35,6 +35,24 @@ dsp::ImpulseResponse::ImpulseResponse(const IRData& irData, const double sampleR this->_SetWeights(); } +dsp::ImpulseResponse::ImpulseResponse(const unsigned char* data, size_t dataSize, double sampleRate) +: mWavState(dsp::wav::LoadReturnCode::SUCCESS) +, mSampleRate(sampleRate) +{ + // Load the raw data from memory + this->mWavState = dsp::wav::Load(data, dataSize, this->mRawAudio, this->mRawAudioSampleRate); + + if (this->mWavState != dsp::wav::LoadReturnCode::SUCCESS) + { + std::stringstream ss; + ss << "Failed to load IR from embedded data array." << std::endl; + } + else + { + this->_SetWeights(); + } +} + double** dsp::ImpulseResponse::Process(double** inputs, const size_t numChannels, const size_t numFrames) { this->_PrepareBuffers(numChannels, numFrames); diff --git a/dsp/ImpulseResponse.h b/dsp/ImpulseResponse.h index fad1ee0..1c362ca 100644 --- a/dsp/ImpulseResponse.h +++ b/dsp/ImpulseResponse.h @@ -23,6 +23,7 @@ class ImpulseResponse : public History struct IRData; ImpulseResponse(const char* fileName, const double sampleRate); ImpulseResponse(const IRData& irData, const double sampleRate); + ImpulseResponse(const unsigned char* data, size_t dataSize, double sampleRate); double** Process(double** inputs, const size_t numChannels, const size_t numFrames) override; IRData GetData(); double GetSampleRate() const { return mSampleRate; }; diff --git a/dsp/wav.cpp b/dsp/wav.cpp index eca231e..635c859 100644 --- a/dsp/wav.cpp +++ b/dsp/wav.cpp @@ -39,6 +39,33 @@ bool ReadChunkAndSkipJunk(std::ifstream& file, char* chunkID) return file.good(); } +bool ReadChunkAndSkipJunk(std::istringstream& memoryStream, char* chunkID) +{ + // Read the first 4 bytes for the chunk ID + memoryStream.read(chunkID, 4); + + // Continue reading and skipping junk until we find valid data + while (!idIsNotJunk(chunkID) && memoryStream.good()) + { + int junkSize; + + // Read the junk size (4 bytes) + memoryStream.read(reinterpret_cast(&junkSize), 4); + + // Ignore the junk data + memoryStream.ignore(junkSize); + + // Unused byte if junkSize is odd + if ((junkSize % 2) == 1) + memoryStream.ignore(1); + + // Read the next chunk ID + memoryStream.read(chunkID, 4); + } + + return memoryStream.good(); +} + std::string dsp::wav::GetMsgForLoadReturnCode(LoadReturnCode retCode) { std::stringstream message; @@ -239,6 +266,164 @@ dsp::wav::LoadReturnCode dsp::wav::Load(const char* fileName, std::vector return dsp::wav::LoadReturnCode::SUCCESS; } +dsp::wav::LoadReturnCode dsp::wav::Load(const unsigned char* data, size_t dataSize, std::vector& audio, + double& sampleRate) +{ + std::istringstream memoryStream(std::string(reinterpret_cast(data), dataSize)); + + // WAV file has 3 "chunks": RIFF ("RIFF"), format ("fmt ") and data ("data"). + // Read the WAV file header + char chunkId[4]; + if (!ReadChunkAndSkipJunk(memoryStream, chunkId)) + { + std::cerr << "Error while reading for next chunk." << std::endl; + return dsp::wav::LoadReturnCode::ERROR_INVALID_FILE; + } + + if (strncmp(chunkId, "RIFF", 4) != 0) + { + std::cerr << "Error: File does not start with expected RIFF chunk. Got" << chunkId << " instead." << std::endl; + return dsp::wav::LoadReturnCode::ERROR_NOT_RIFF; + } + + int chunkSize; + memoryStream.read(reinterpret_cast(&chunkSize), 4); + + char format[4]; + memoryStream.read(format, 4); + if (strncmp(format, "WAVE", 4) != 0) + { + std::cerr << "Error: Files' second chunk (format) is not expected WAV. Got" << format << " instead." << std::endl; + return dsp::wav::LoadReturnCode::ERROR_NOT_WAVE; + } + + // Read the format chunk + char subchunk1Id[4]; + if (!ReadChunkAndSkipJunk(memoryStream, subchunk1Id)) + { + std::cerr << "Error while reading for next chunk." << std::endl; + return dsp::wav::LoadReturnCode::ERROR_INVALID_FILE; + } + if (strncmp(subchunk1Id, "fmt ", 4) != 0) + { + std::cerr << "Error: Invalid WAV file missing expected fmt section; got " << subchunk1Id << " instead." + << std::endl; + return dsp::wav::LoadReturnCode::ERROR_MISSING_FMT; + } + + int subchunk1Size; + memoryStream.read(reinterpret_cast(&subchunk1Size), 4); + if (subchunk1Size < 16) + { + std::cerr << "WAV chunk 1 size is " << subchunk1Size + << ", which is smaller than the requried 16 to fit the expected " + "information." + << std::endl; + return dsp::wav::LoadReturnCode::ERROR_INVALID_FILE; + } + + unsigned short audioFormat; + memoryStream.read(reinterpret_cast(&audioFormat), 2); + const short AUDIO_FORMAT_PCM = 1; + const short AUDIO_FORMAT_IEEE = 3; + std::unordered_set supportedFormats{AUDIO_FORMAT_PCM, AUDIO_FORMAT_IEEE}; + if (supportedFormats.find(audioFormat) == supportedFormats.end()) + { + std::cerr << "Error: Unsupported WAV format detected. "; + switch (audioFormat) + { + case 6: std::cerr << "(Got: A-law)" << std::endl; return dsp::wav::LoadReturnCode::ERROR_UNSUPPORTED_FORMAT_ALAW; + case 7: + std::cerr << "(Got: mu-law)" << std::endl; + return dsp::wav::LoadReturnCode::ERROR_UNSUPPORTED_FORMAT_MULAW; + case 65534: + std::cerr << "(Got: Extensible)" << std::endl; + return dsp::wav::LoadReturnCode::ERROR_UNSUPPORTED_FORMAT_EXTENSIBLE; + default: + std::cerr << "(Got unknown format " << audioFormat << ")" << std::endl; + return dsp::wav::LoadReturnCode::ERROR_INVALID_FILE; + } + } + + short numChannels; + memoryStream.read(reinterpret_cast(&numChannels), 2); + // HACK + if (numChannels != 1) + { + std::cerr << "Require mono (using for IR loading)" << std::endl; + return dsp::wav::LoadReturnCode::ERROR_NOT_MONO; + } + + int iSampleRate; + memoryStream.read(reinterpret_cast(&iSampleRate), 4); + // Store in format we assume (SR is double) + sampleRate = (double)iSampleRate; + + int byteRate; + memoryStream.read(reinterpret_cast(&byteRate), 4); + + short blockAlign; + memoryStream.read(reinterpret_cast(&blockAlign), 2); + + short bitsPerSample; + memoryStream.read(reinterpret_cast(&bitsPerSample), 2); + + // The default is for there to be 16 bytes in the fmt chunk, but sometimes + // it's different. + if (subchunk1Size > 16) + { + const int extraBytes = subchunk1Size - 16; + const int skipChars = extraBytes / 4 * 4; // truncate to dword size + memoryStream.ignore(skipChars); + const int remainder = extraBytes % 4; + memoryStream.read(reinterpret_cast(&byteRate), remainder); + } + + // Read the data chunk + char subchunk2Id[4]; + if (!ReadChunkAndSkipJunk(memoryStream, subchunk2Id)) + { + std::cerr << "Error while reading for next chunk." << std::endl; + return dsp::wav::LoadReturnCode::ERROR_INVALID_FILE; + } + if (strncmp(subchunk2Id, "data", 4) != 0) + { + std::cerr << "Error: Invalid WAV file" << std::endl; + return dsp::wav::LoadReturnCode::ERROR_INVALID_FILE; + } + + // Size of the data chunk, in bits. + int subchunk2Size; + memoryStream.read(reinterpret_cast(&subchunk2Size), 4); + + if (audioFormat == AUDIO_FORMAT_IEEE) + { + if (bitsPerSample == 32) + dsp::wav::_LoadSamples32(memoryStream, subchunk2Size, audio); + else + { + std::cerr << "Error: Unsupported bits per sample for IEEE files: " << bitsPerSample << std::endl; + return dsp::wav::LoadReturnCode::ERROR_UNSUPPORTED_BITS_PER_SAMPLE; + } + } + else if (audioFormat == AUDIO_FORMAT_PCM) + { + if (bitsPerSample == 16) + dsp::wav::_LoadSamples16(memoryStream, subchunk2Size, audio); + else if (bitsPerSample == 24) + dsp::wav::_LoadSamples24(memoryStream, subchunk2Size, audio); + else if (bitsPerSample == 32) + dsp::wav::_LoadSamples32(memoryStream, subchunk2Size, audio); + else + { + std::cerr << "Error: Unsupported bits per sample for PCM files: " << bitsPerSample << std::endl; + return dsp::wav::LoadReturnCode::ERROR_UNSUPPORTED_BITS_PER_SAMPLE; + } + } + + return dsp::wav::LoadReturnCode::SUCCESS; +} + void dsp::wav::_LoadSamples16(std::ifstream& wavFile, const int chunkSize, std::vector& samples) { // Allocate an array to hold the samples @@ -254,6 +439,21 @@ void dsp::wav::_LoadSamples16(std::ifstream& wavFile, const int chunkSize, std:: samples[i] = scale * ((float)tmp[i]); // 2^16 } +void dsp::wav::_LoadSamples16(std::istringstream& stream, const int chunkSize, std::vector& samples) +{ + // Allocate an array to hold the samples + std::vector tmp(chunkSize / 2); // 16 bits (2 bytes) per sample + + // Read the samples from the file into the array + stream.read(reinterpret_cast(tmp.data()), chunkSize); + + // Copy into the return array + const float scale = 1.0 / ((double)(1 << 15)); + samples.resize(tmp.size()); + for (auto i = 0; i < samples.size(); i++) + samples[i] = scale * ((float)tmp[i]); // 2^16 +} + void dsp::wav::_LoadSamples24(std::ifstream& wavFile, const int chunkSize, std::vector& samples) { // Allocate an array to hold the samples @@ -271,6 +471,23 @@ void dsp::wav::_LoadSamples24(std::ifstream& wavFile, const int chunkSize, std:: samples[i] = scale * ((float)tmp[i]); } +void dsp::wav::_LoadSamples24(std::istringstream& stream, const int chunkSize, std::vector& samples) +{ + // Allocate an array to hold the samples + std::vector tmp(chunkSize / 3); // 24 bits (3 bytes) per sample + // Read in and convert the samples + for (int& x : tmp) + { + x = dsp::wav::_ReadSigned24BitInt(stream); + } + + // Copy into the return array + const float scale = 1.0 / ((double)(1 << 23)); + samples.resize(tmp.size()); + for (auto i = 0; i < samples.size(); i++) + samples[i] = scale * ((float)tmp[i]); +} + int dsp::wav::_ReadSigned24BitInt(std::ifstream& stream) { // Read the three bytes of the 24-bit integer. @@ -293,6 +510,28 @@ int dsp::wav::_ReadSigned24BitInt(std::ifstream& stream) return value; } +int dsp::wav::_ReadSigned24BitInt(std::istringstream& stream) +{ + // Read the three bytes of the 24-bit integer. + std::uint8_t bytes[3]; + stream.read(reinterpret_cast(bytes), 3); + + // Combine the three bytes into a single integer using bit shifting and + // masking. This works by isolating each byte using a bit mask (0xff) and then + // shifting the byte to the correct position in the final integer. + int value = bytes[0] | (bytes[1] << 8) | (bytes[2] << 16); + + // The value is stored in two's complement format, so if the most significant + // bit (the 24th bit) is set, then the value is negative. In this case, we + // need to extend the sign bit to get the correct negative value. + if (value & (1 << 23)) + { + value |= ~((1 << 24) - 1); + } + + return value; +} + void dsp::wav::_LoadSamples32(std::ifstream& wavFile, const int chunkSize, std::vector& samples) { // NOTE: 32-bit is float. @@ -300,3 +539,11 @@ void dsp::wav::_LoadSamples32(std::ifstream& wavFile, const int chunkSize, std:: // Read the samples from the file into the array wavFile.read(reinterpret_cast(samples.data()), chunkSize); } + +void dsp::wav::_LoadSamples32(std::istringstream& data, const int chunkSize, std::vector& samples) +{ + // NOTE: 32-bit is float. + samples.resize(chunkSize / 4); // 32 bits (4 bytes) per sample + // Read the samples from the file into the array + data.read(reinterpret_cast(samples.data()), chunkSize); +} diff --git a/dsp/wav.h b/dsp/wav.h index 336eddf..1e1712e 100644 --- a/dsp/wav.h +++ b/dsp/wav.h @@ -39,15 +39,22 @@ std::string GetMsgForLoadReturnCode(LoadReturnCode rc); // // Returns: as per return cases above LoadReturnCode Load(const char* fileName, std::vector& audio, double& sampleRate); +LoadReturnCode Load(const unsigned char* data, size_t dataSize, std::vector& audio, double& sampleRate); // Load samples, 16-bit void _LoadSamples16(std::ifstream& wavFile, const int chunkSize, std::vector& samples); +void _LoadSamples16(std::istringstream& stream, const int chunkSize, std::vector& samples); // Load samples, 24-bit void _LoadSamples24(std::ifstream& wavFile, const int chunkSize, std::vector& samples); +void _LoadSamples24(std::istringstream& stream, const int chunkSize, std::vector& samples); // Load samples, 32-bit void _LoadSamples32(std::ifstream& wavFile, const int chunkSize, std::vector& samples); +void _LoadSamples32(std::istringstream& stream, const int chunkSize, std::vector& samples); + // Read in a 24-bit sample and convert it to an int int _ReadSigned24BitInt(std::ifstream& stream); +int _ReadSigned24BitInt(std::istringstream& stream); + }; // namespace wav }; // namespace dsp