From b19f0436b325b02d4986b69467d149fc1d018c8f Mon Sep 17 00:00:00 2001 From: Steven Atkinson Date: Sat, 28 Jan 2023 18:33:32 -0800 Subject: [PATCH] Better error messages for IR loader failures (#48) * More error codes related for loading WAV files * Better error messages * Add enum of return codes and capture IR loading in IR state * Error messages for failed IR loads. --- NeuralAmpModeler/NeuralAmpModeler.cpp | 76 ++++++++++++++++++++---- NeuralAmpModeler/NeuralAmpModeler.h | 4 +- NeuralAmpModeler/dsp/ImpulseResponse.cpp | 18 +++--- NeuralAmpModeler/dsp/ImpulseResponse.h | 5 ++ NeuralAmpModeler/dsp/wav.cpp | 45 +++++++++----- NeuralAmpModeler/dsp/wav.h | 23 ++++--- 6 files changed, 128 insertions(+), 43 deletions(-) diff --git a/NeuralAmpModeler/NeuralAmpModeler.cpp b/NeuralAmpModeler/NeuralAmpModeler.cpp index 8315eb2b..f0a5b505 100644 --- a/NeuralAmpModeler/NeuralAmpModeler.cpp +++ b/NeuralAmpModeler/NeuralAmpModeler.cpp @@ -206,7 +206,47 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info) pGraphics->PromptForFile(fileName, path); if (fileName.GetLength()) { this->mIRPath = path; - this->_GetIR(fileName); + const dsp::wav::LoadReturnCode retCode = this->_GetIR(fileName); + if (retCode != dsp::wav::LoadReturnCode::SUCCESS) { + std::stringstream message; + message << "Failed to load IR file " << fileName.Get() << ":\n"; + switch (retCode) { + case (dsp::wav::LoadReturnCode::ERROR_OPENING): + message << "Failed to open file (is it being used by another program?)"; + break; + case (dsp::wav::LoadReturnCode::ERROR_NOT_RIFF): + message << "File is not a WAV file."; + break; + case (dsp::wav::LoadReturnCode::ERROR_NOT_WAVE): + message << "File is not a WAV file."; + break; + case (dsp::wav::LoadReturnCode::ERROR_MISSING_FMT): + message << "File is missing expected format chunk."; + break; + case (dsp::wav::LoadReturnCode::ERROR_INVALID_FILE): + message << "WAV file contents are invalid."; + break; + case (dsp::wav::LoadReturnCode::ERROR_UNSUPPORTED_FORMAT_IEEE_FLOAT): + message << "Unsupported file format \"IEEE float\""; + break; + case (dsp::wav::LoadReturnCode::ERROR_UNSUPPORTED_FORMAT_ALAW): + message << "Unsupported file format \"A-law\""; + break; + case (dsp::wav::LoadReturnCode::ERROR_UNSUPPORTED_FORMAT_MULAW): + message << "Unsupported file format \"mu-law\""; + break; + case (dsp::wav::LoadReturnCode::ERROR_UNSUPPORTED_FORMAT_EXTENSIBLE): + message << "Unsupported file format \"extensible\""; + break; + case (dsp::wav::LoadReturnCode::ERROR_NOT_MONO): + message << "File is not mono."; + break; + case (dsp::wav::LoadReturnCode::ERROR_OTHER): + message << "???"; + break; + } + pGraphics->ShowMessageBox(message.str().c_str(), "Failed to load IR!", kMB_OK); + } } }; @@ -489,26 +529,36 @@ void NeuralAmpModeler::_GetDSP(const WDL_String& modelPath) } } -void NeuralAmpModeler::_GetIR(const WDL_String& irFileName) +dsp::wav::LoadReturnCode NeuralAmpModeler::_GetIR(const WDL_String& irFileName) { WDL_String previousIRFileName; + + previousIRFileName = this->mIRFileName; + const double sampleRate = this->GetSampleRate(); + dsp::wav::LoadReturnCode wavState = dsp::wav::LoadReturnCode::ERROR_OTHER; try { - previousIRFileName = this->mIRFileName; - const double sampleRate = this->GetSampleRate(); this->mStagedIR = std::make_unique(irFileName, sampleRate); - this->_SetIRMsg(irFileName); + wavState = this->mStagedIR->GetWavState(); } catch (std::exception& e) { - std::stringstream ss; - ss << "FAILED to load IR"; - SendControlMsgFromDelegate(kCtrlTagIRName, 0, int(strlen(ss.str().c_str())), ss.str().c_str()); - if (this->mStagedIR != nullptr) { - this->mStagedIR = nullptr; - } - this->mIRFileName = previousIRFileName; - std::cerr << "Failed to read IR" << std::endl; + wavState = dsp::wav::LoadReturnCode::ERROR_OTHER; + std::cerr << "Caught unhandled exception while attempting to load IR:" << std::endl; std::cerr << e.what() << std::endl; } + + if (wavState == dsp::wav::LoadReturnCode::SUCCESS) + this->_SetIRMsg(irFileName); + else { + if (this->mStagedIR != nullptr) { + this->mStagedIR = nullptr; + } + this->mIRFileName = previousIRFileName; + std::stringstream ss; + ss << "FAILED to load IR"; + SendControlMsgFromDelegate(kCtrlTagIRName, 0, int(strlen(ss.str().c_str())), ss.str().c_str()); + } + + return wavState; } size_t NeuralAmpModeler::_GetBufferNumChannels() const diff --git a/NeuralAmpModeler/NeuralAmpModeler.h b/NeuralAmpModeler/NeuralAmpModeler.h index 68a3f361..4ff0d5a8 100644 --- a/NeuralAmpModeler/NeuralAmpModeler.h +++ b/NeuralAmpModeler/NeuralAmpModeler.h @@ -10,6 +10,8 @@ #include "ISender.h" +#include "dsp/wav.h" + const int kNumPresets = 1; enum EParams @@ -68,7 +70,7 @@ class NeuralAmpModeler final : public iplug::Plugin // Gets a new DSP object and stores it to mStagedDSP void _GetDSP(const WDL_String& dspPath); // Gets the IR and stores to mStagedIR - void _GetIR(const WDL_String& irFileName); + dsp::wav::LoadReturnCode _GetIR(const WDL_String& irFileName); // Update the message about which model is loaded. void _SetModelMsg(const WDL_String& dspPath); bool _HaveModel() const { diff --git a/NeuralAmpModeler/dsp/ImpulseResponse.cpp b/NeuralAmpModeler/dsp/ImpulseResponse.cpp index d91ec7df..1f5be73b 100644 --- a/NeuralAmpModeler/dsp/ImpulseResponse.cpp +++ b/NeuralAmpModeler/dsp/ImpulseResponse.cpp @@ -11,16 +11,18 @@ #include "ImpulseResponse.h" dsp::ImpulseResponse::ImpulseResponse(const WDL_String& fileName, - const double sampleRate) + const double sampleRate): + mWavState(dsp::wav::LoadReturnCode::ERROR_OTHER) { // Try to load the WAV - if (dsp::wav::Load(fileName, this->mRawAudio, this->mRawAudioSampleRate) != dsp::wav::RET_SUCCESS) { - std::stringstream ss; - ss << "Failed to load IR at " << fileName.Get() << std::endl; - throw std::runtime_error(ss.str()); - } - // Set the weights based on the raw audio. - this->_SetWeights(sampleRate); + this->mWavState = dsp::wav::Load(fileName, this->mRawAudio, this->mRawAudioSampleRate); + if (this->mWavState != dsp::wav::LoadReturnCode::SUCCESS) { + std::stringstream ss; + ss << "Failed to load IR at " << fileName.Get() << std::endl; + } + else + // Set the weights based on the raw audio. + this->_SetWeights(sampleRate); } iplug::sample** dsp::ImpulseResponse::Process(iplug::sample** inputs, diff --git a/NeuralAmpModeler/dsp/ImpulseResponse.h b/NeuralAmpModeler/dsp/ImpulseResponse.h index c3aa861a..0d1b4f90 100644 --- a/NeuralAmpModeler/dsp/ImpulseResponse.h +++ b/NeuralAmpModeler/dsp/ImpulseResponse.h @@ -16,6 +16,7 @@ #include "wdlstring.h" // WDL_String #include "IPlugConstants.h" // sample #include "dsp.h" +#include "wav.h" namespace dsp { class ImpulseResponse : public History { @@ -24,10 +25,14 @@ namespace dsp { iplug::sample** Process(iplug::sample** inputs, const size_t numChannels, const size_t numFrames) override; + // TODO states for the IR class + dsp::wav::LoadReturnCode GetWavState() const { return this->mWavState; }; private: // Set the weights, given that the plugin is running at the provided sample rate. void _SetWeights(const double sampleRate); + // State of audio + dsp::wav::LoadReturnCode mWavState; // Keep a copy of the raw audio that was loaded so that it can be resampled std::vector mRawAudio; double mRawAudioSampleRate; diff --git a/NeuralAmpModeler/dsp/wav.cpp b/NeuralAmpModeler/dsp/wav.cpp index 8985d26b..ec8c0ea3 100644 --- a/NeuralAmpModeler/dsp/wav.cpp +++ b/NeuralAmpModeler/dsp/wav.cpp @@ -34,7 +34,7 @@ void ReadChunkAndSkipJunk(std::ifstream& file, char* chunkID) throw std::runtime_error("Found more than 1 junk chunk"); } -int dsp::wav::Load(const WDL_String &fileName, std::vector &audio, double &sampleRate) +dsp::wav::LoadReturnCode dsp::wav::Load(const WDL_String &fileName, std::vector &audio, double &sampleRate) { // FYI: https://www.mmsp.ece.mcgill.ca/Documents/AudioFormats/WAVE/WAVE.html // Open the WAV file for reading @@ -43,7 +43,7 @@ int dsp::wav::Load(const WDL_String &fileName, std::vector &audio, double // Check if the file was opened successfully if (!wavFile.is_open()) { std::cerr << "Error opening WAV file" << std::endl; - return dsp::wav::RET_ERROR_OPENING; + return dsp::wav::LoadReturnCode::ERROR_OPENING; } // WAV file has 3 "chunks": RIFF ("RIFF"), format ("fmt ") and data ("data"). @@ -51,8 +51,8 @@ int dsp::wav::Load(const WDL_String &fileName, std::vector &audio, double char chunkId[4]; ReadChunkAndSkipJunk(wavFile, chunkId); if (strncmp(chunkId, "RIFF", 4) != 0) { - std::cerr << "Error: Not a WAV file" << std::endl; - return dsp::wav::RET_ERROR_NOT_WAV; + 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; @@ -61,16 +61,16 @@ int dsp::wav::Load(const WDL_String &fileName, std::vector &audio, double char format[4]; wavFile.read(format, 4); if (strncmp(format, "WAVE", 4) != 0) { - std::cerr << "Error: Not a WAV file" << std::endl; - return dsp::wav::RET_ERROR_NOT_WAV; + 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]; ReadChunkAndSkipJunk(wavFile, subchunk1Id); if (strncmp(subchunk1Id, "fmt ", 4) != 0) { - std::cerr << "Error: Invalid WAV file" << std::endl; - return dsp::wav::RET_ERROR_INVALID_WAV; + std::cerr << "Error: Invalid WAV file missing expected fmt section; got " << subchunk1Id << " instead." << std::endl; + return dsp::wav::LoadReturnCode::ERROR_MISSING_FMT; } int subchunk1Size; @@ -79,8 +79,25 @@ int dsp::wav::Load(const WDL_String &fileName, std::vector &audio, double unsigned short audioFormat; wavFile.read(reinterpret_cast(&audioFormat), 2); if (audioFormat != 1) { - std::cerr << "Error: Only PCM format is supported" << std::endl; - return dsp::wav::RET_ERROR_NOT_PCM; + std::cerr << "Error: Only PCM format is supported."; + switch (audioFormat) + { + case 3: + std::cerr << "(Got: IEEE float)" << std::endl; + return dsp::wav::LoadReturnCode::ERROR_UNSUPPORTED_FORMAT_IEEE_FLOAT; + 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; @@ -88,7 +105,7 @@ int dsp::wav::Load(const WDL_String &fileName, std::vector &audio, double // HACK if (numChannels != 1) { std::cerr << "Require mono (using for IR loading)" << std::endl; - return dsp::wav::RET_ERROR_INVALID_WAV; + return dsp::wav::LoadReturnCode::ERROR_NOT_MONO; } int iSampleRate; @@ -110,7 +127,7 @@ int dsp::wav::Load(const WDL_String &fileName, std::vector &audio, double ReadChunkAndSkipJunk(wavFile, subchunk2Id); if (strncmp(subchunk2Id, "data", 4) != 0) { std::cerr << "Error: Invalid WAV file" << std::endl; - return dsp::wav::RET_ERROR_INVALID_WAV; + return dsp::wav::LoadReturnCode::ERROR_INVALID_FILE; } // Size of the data chunk, in bits. @@ -125,7 +142,7 @@ int dsp::wav::Load(const WDL_String &fileName, std::vector &audio, double dsp::wav::_LoadSamples32(wavFile, subchunk2Size, audio); else { std::cerr << "Error: Unsupported bits per sample: " << bitsPerSample << std::endl; - return 1; + return dsp::wav::LoadReturnCode::ERROR_UNSUPPORTED_BITS_PER_SAMPLE; } // Close the WAV file @@ -134,7 +151,7 @@ int dsp::wav::Load(const WDL_String &fileName, std::vector &audio, double // Print the number of samples // std::cout << "Number of samples: " << samples.size() << std::endl; - return dsp::wav::RET_SUCCESS; + return dsp::wav::LoadReturnCode::SUCCESS; } void dsp::wav::_LoadSamples16(std::ifstream &wavFile, diff --git a/NeuralAmpModeler/dsp/wav.h b/NeuralAmpModeler/dsp/wav.h index c3265820..fb620567 100644 --- a/NeuralAmpModeler/dsp/wav.h +++ b/NeuralAmpModeler/dsp/wav.h @@ -12,17 +12,26 @@ namespace dsp { namespace wav { - // Return cases - const int RET_SUCCESS = 0; - const int RET_ERROR_OPENING = 1; - const int RET_ERROR_NOT_WAV = 2; - const int RET_ERROR_INVALID_WAV = 3; - const int RET_ERROR_NOT_PCM = 4; + enum class LoadReturnCode { + SUCCESS = 0, + ERROR_OPENING, + ERROR_NOT_RIFF, + ERROR_NOT_WAVE, + ERROR_MISSING_FMT, + ERROR_INVALID_FILE, + ERROR_UNSUPPORTED_FORMAT_IEEE_FLOAT, + ERROR_UNSUPPORTED_FORMAT_ALAW, + ERROR_UNSUPPORTED_FORMAT_MULAW, + ERROR_UNSUPPORTED_FORMAT_EXTENSIBLE, + ERROR_UNSUPPORTED_BITS_PER_SAMPLE, + ERROR_NOT_MONO, + ERROR_OTHER + }; // Load a WAV file into a provided array of doubles, // And note the sample rate. // // Returns: as per return cases above - int Load(const WDL_String& fileName, std::vector &audio, double& sampleRate); + LoadReturnCode Load(const WDL_String& fileName, std::vector &audio, double& sampleRate); // Load samples, 16-bit void _LoadSamples16(std::ifstream &wavFile, const int chunkSize, std::vector& samples);