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