NeuralAmpModelerPlugin

Plugin for Neural Amp Modeler
Log | Files | Refs | Submodules | README | LICENSE

commit bd850f17e78cd2039bcd593550fe674d20c42401
parent 261f4906ba8436837bf5c2e82a8ab565ef7616b1
Author: Oli Larkin <olilarkin@googlemail.com>
Date:   Sat,  6 May 2023 19:47:10 +0200

Introduce NAMFileBrowserControl & fix non-real-time safe code

Diffstat:
MNeuralAmpModeler/NeuralAmpModeler.cpp | 231++++++++++++++++++++++++-------------------------------------------------------
MNeuralAmpModeler/NeuralAmpModeler.h | 36++++++++++++++++++++----------------
MNeuralAmpModeler/NeuralAmpModelerControls.h | 210++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
MNeuralAmpModeler/resources/img/SkinEHeritage_ArrowLeft.svg | 12++----------
MNeuralAmpModeler/resources/img/SkinEHeritage_ArrowRight.svg | 12++----------
5 files changed, 292 insertions(+), 209 deletions(-)

diff --git a/NeuralAmpModeler/NeuralAmpModeler.cpp b/NeuralAmpModeler/NeuralAmpModeler.cpp @@ -60,8 +60,6 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info) , mStagedIR(nullptr) , mFlagRemoveNAM(false) , mFlagRemoveIR(false) -, mDefaultNAMString("Select model...") -, mDefaultIRString("Select IR...") , mToneBass() , mToneMid() , mToneTreble() @@ -114,7 +112,6 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info) const IRECT mainArea = b.GetPadded(-20); const auto content = mainArea.GetPadded(-10); const float titleHeight = 50.0f; - const auto titleLabel = content.GetFromTop(titleHeight); // Area for the Noise gate knob const float allKnobsHalfPad = 10.0f; @@ -139,7 +136,6 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info) const float toggleHeight = 40.0f; // Area for noise gate toggle const float ngAreaHeight = toggleHeight; - const float ngAreaHalfWidth = 0.5f * noiseGateArea.W(); const IRECT ngToggleArea = noiseGateArea.GetFromBottom(ngAreaHeight).GetTranslated(-10.f, ngAreaHeight + singleKnobPad - 14.f); // Area for EQ toggle @@ -161,17 +157,13 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info) const float irBypassToggleY = 343.f; const IRECT irBypassToggleArea = IRECT(irBypassToggleX, irBypassToggleY, irSwitchBitmap); - // Area for IR bypass toggle - const float dimPanelOpacity = 0.75f; - const IRECT irBypassDimPanel = IRECT(100.f, 344.f, 500.f, 364.f); // left, top, right, bottom - // Areas for model and IR const float fileWidth = 200.0f; const float fileHeight = 30.0f; - const float fileSpace = 10.0f; - const IRECT modelArea = - content.GetFromBottom(2.0f * fileHeight + fileSpace).GetFromTop(fileHeight).GetMidHPadded(fileWidth); - const IRECT irArea = content.GetFromBottom(fileHeight + 2.f).GetMidHPadded(fileWidth); + const float fileYSpace = 8.0f; + const float irYOffset = 38.0f; + const IRECT modelArea = content.GetFromBottom((2.0f * fileHeight) + fileYSpace).GetFromTop(fileHeight).GetMidHPadded(fileWidth).GetTranslated(0.0f, -1); + const IRECT irArea = modelArea.GetTranslated(0.0f, irYOffset); // Areas for meters const float meterHalfHeight = 0.5f * 385.0f; @@ -189,107 +181,48 @@ NeuralAmpModeler::NeuralAmpModeler(const InstanceInfo& info) ->SetBlend(IBlend(EBlend::Default, 1.0)); // Model loader button - auto loadNAM = [&, pGraphics](IControl* pCaller) { - WDL_String initFileName; - WDL_String initPath(this->mNAMPath.Get()); - initPath.remove_filepart(); - pGraphics->PromptForFile( - initFileName, initPath, EFileAction::Open, "nam", [&](const WDL_String& fileName, const WDL_String& path) { - if (fileName.GetLength()) - { - // Sets mNAMPath and mStagedNAM - const std::string msg = this->_GetNAM(fileName); - // TODO error messages like the IR loader. - if (msg.size()) - { - std::stringstream ss; - ss << "Failed to load NAM model. Message:\n\n" - << msg << "\n\n" - << "If the model is an old \"directory-style\" model, it " - "can be " - "converted using the utility at " - "https://github.com/sdatkinson/nam-model-utility"; - pGraphics->ShowMessageBox(ss.str().c_str(), "Failed to load model!", kMB_OK); - } - } - }); + auto loadModelCompletionHandler = [&](const WDL_String& fileName, const WDL_String& path) { + if (fileName.GetLength()) + { + // Sets mNAMPath and mStagedNAM + const std::string msg = this->_GetNAM(fileName); + // TODO error messages like the IR loader. + if (msg.size()) + { + std::stringstream ss; + ss << "Failed to load NAM model. Message:\n\n" + << msg << "\n\n" + << "If the model is an old \"directory-style\" model, it " + "can be " + "converted using the utility at " + "https://github.com/sdatkinson/nam-model-utility"; + GetUI()->ShowMessageBox(ss.str().c_str(), "Failed to load model!", kMB_OK); + } + std::cout << "Loaded: " << fileName.Get() << std::endl; + } }; + // IR loader button - auto loadIR = [&, pGraphics](IControl* pCaller) { - WDL_String initFileName; - WDL_String initPath(this->mIRPath.Get()); - initPath.remove_filepart(); - pGraphics->PromptForFile( - initFileName, initPath, EFileAction::Open, "wav", [&](const WDL_String& fileName, const WDL_String& path) { - if (fileName.GetLength()) - { - this->mIRPath = 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_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_UNSUPPORTED_BITS_PER_SAMPLE): - message << "Unsupported bits per sample"; - break; - case (dsp::wav::LoadReturnCode::ERROR_OTHER): message << "???"; break; - default: message << "???"; break; - } - pGraphics->ShowMessageBox(message.str().c_str(), "Failed to load IR!", kMB_OK); - } - } - }); + auto loadIRCompletionHandler = [&](const WDL_String& fileName, const WDL_String& path) { + if (fileName.GetLength()) + { + this->mIRPath = 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"; + message << dsp::wav::GetMsgForLoadReturnCode(retCode); + + GetUI()->ShowMessageBox(message.str().c_str(), "Failed to load IR!", kMB_OK); + } + } }; - // Model-clearing function - auto ClearNAM = [&, pGraphics](IControl* pCaller) { this->mFlagRemoveNAM = true; }; - // IR-clearing function - auto ClearIR = [&, pGraphics](IControl* pCaller) { this->mFlagRemoveIR = true; }; - - // Graphics objects for what NAM is loaded - const float iconWidth = fileHeight; // Square icon - pGraphics->AttachControl(new IRolloverSVGButtonControl( - modelArea.GetFromLeft(iconWidth).GetPadded(-6.f).GetTranslated(-1.f, 1.f), loadNAM, fileSVG)); - pGraphics->AttachControl( - new IRolloverSVGButtonControl(modelArea.GetFromRight(iconWidth).GetPadded(-8.f), ClearNAM, closeButtonSVG)); - pGraphics->AttachControl( - new IVUpdateableLabelControl( - modelArea.GetReducedFromLeft(iconWidth).GetReducedFromRight(iconWidth), this->mDefaultNAMString.Get(), - style.WithDrawFrame(false).WithValueText(style.valueText.WithSize(16.f).WithVAlign(EVAlign::Middle))), - kCtrlTagModelName); - // IR - pGraphics->AttachControl(new IRolloverSVGButtonControl( - irArea.GetFromLeft(iconWidth).GetPadded(-6.f).GetTranslated(-1.f, 1.f), loadIR, fileSVG), - -1, "IR_CONTROLS"); - pGraphics->AttachControl( - new IRolloverSVGButtonControl(irArea.GetFromRight(iconWidth).GetPadded(-8.f), ClearIR, closeButtonSVG), -1, - "IR_CONTROLS"); - pGraphics->AttachControl( - new IVUpdateableLabelControl( - irArea.GetReducedFromLeft(iconWidth).GetReducedFromRight(iconWidth), this->mDefaultIRString.Get(), - style.WithDrawFrame(false).WithValueText(style.valueText.WithSize(16.f).WithVAlign(EVAlign::Middle))), - kCtrlTagIRName, "IR_CONTROLS"); + + pGraphics->AttachControl(new NAMFileBrowserControl(modelArea, kMsgTagClearModel, "Select model...", "nam", loadModelCompletionHandler, style, + fileSVG, closeButtonSVG, leftArrowSVG, rightArrowSVG), kCtrlTagModelFileBrowser); + pGraphics->AttachControl(new NAMFileBrowserControl(irArea, kMsgTagClearIR, "Select IR...", "wav", loadIRCompletionHandler, style, + fileSVG, closeButtonSVG, leftArrowSVG, rightArrowSVG), kCtrlTagIRFileBrowser); // TODO all these magic numbers pGraphics->AttachControl(new NamSwitchControl( @@ -528,8 +461,8 @@ void NeuralAmpModeler::OnIdle() if (this->mNewNAMLoadedInDSP) { - if (GetUI()) - this->GetUI()->GetControlWithTag(kCtrlTagOutNorm)->SetDisabled(!this->mNAM->HasLoudness()); + if (auto* pGraphics = GetUI()) + pGraphics->GetControlWithTag(kCtrlTagOutNorm)->SetDisabled(!this->mNAM->HasLoudness()); this->mNewNAMLoadedInDSP = false; } @@ -562,10 +495,11 @@ int NeuralAmpModeler::UnserializeState(const IByteChunk& chunk, int startPos) void NeuralAmpModeler::OnUIOpen() { Plugin::OnUIOpen(); + if (this->mNAMPath.GetLength()) - this->_SetModelMsg(this->mNAMPath); + SendControlMsgFromDelegate(kCtrlTagModelFileBrowser, kMsgTagLoadedModel, this->mNAMPath.GetLength(), this->mNAMPath.Get()); if (this->mIRPath.GetLength()) - this->_SetIRMsg(this->mIRPath); + SendControlMsgFromDelegate(kCtrlTagIRFileBrowser, kMsgTagLoadedIR, this->mIRPath.GetLength(), this->mIRPath.Get()); if (this->mNAM != nullptr) this->GetUI()->GetControlWithTag(kCtrlTagOutNorm)->SetDisabled(!this->mNAM->HasLoudness()); } @@ -583,12 +517,27 @@ void NeuralAmpModeler::OnParamChangeUI(int paramIdx, EParamSource source) pGraphics->ForControlInGroup("EQ_KNOBS", [active](IControl* pControl) { pControl->SetDisabled(!active); }); break; case kIRToggle: - pGraphics->ForControlInGroup("IR_CONTROLS", [active](IControl* pControl) { pControl->SetDisabled(!active); }); + pGraphics->GetControlWithTag(kCtrlTagIRFileBrowser)->SetDisabled(!active); default: break; } } } +bool NeuralAmpModeler::OnMessage(int msgTag, int ctrlTag, int dataSize, const void* pData) +{ + switch (msgTag) + { + case kMsgTagClearModel: + mFlagRemoveNAM = true; + return true; + case kMsgTagClearIR: + mFlagRemoveIR = true; + return true; + default: + return false; + } +} + // Private methods ============================================================ void NeuralAmpModeler::_AllocateIOPointers(const size_t nChans) @@ -625,14 +574,12 @@ void NeuralAmpModeler::_ApplyDSPStaging() { this->mNAM = nullptr; this->mNAMPath.Set(""); - this->_UnsetModelMsg(); this->mFlagRemoveNAM = false; } if (this->mFlagRemoveIR) { this->mIR = nullptr; this->mIRPath.Set(""); - this->_UnsetIRMsg(); this->mFlagRemoveIR = false; } } @@ -670,14 +617,13 @@ std::string NeuralAmpModeler::_GetNAM(const WDL_String& modelPath) { auto dspPath = std::filesystem::u8path(modelPath.Get()); mStagedNAM = get_dsp(dspPath); - this->_SetModelMsg(modelPath); this->mNAMPath = modelPath; + SendControlMsgFromDelegate(kCtrlTagModelFileBrowser, kMsgTagLoadedModel, this->mNAMPath.GetLength(), this->mNAMPath.Get()); } catch (std::exception& e) { - std::stringstream ss; - ss << "FAILED to load model"; - SendControlMsgFromDelegate(kCtrlTagModelName, 0, int(strlen(ss.str().c_str())), ss.str().c_str()); + SendControlMsgFromDelegate(kCtrlTagModelFileBrowser, kMsgTagLoadFailed); + if (this->mStagedNAM != nullptr) { this->mStagedNAM = nullptr; @@ -712,8 +658,8 @@ dsp::wav::LoadReturnCode NeuralAmpModeler::_GetIR(const WDL_String& irPath) if (wavState == dsp::wav::LoadReturnCode::SUCCESS) { - this->_SetIRMsg(irPath); this->mIRPath = irPath; + SendControlMsgFromDelegate(kCtrlTagIRFileBrowser, kMsgTagLoadedIR, this->mIRPath.GetLength(), this->mIRPath.Get()); } else { @@ -722,9 +668,7 @@ dsp::wav::LoadReturnCode NeuralAmpModeler::_GetIR(const WDL_String& irPath) this->mStagedIR = nullptr; } this->mIRPath = previousIRPath; - std::stringstream ss; - ss << "FAILED to load IR"; - SendControlMsgFromDelegate(kCtrlTagIRName, 0, int(strlen(ss.str().c_str())), ss.str().c_str()); + SendControlMsgFromDelegate(kCtrlTagIRFileBrowser, kMsgTagLoadFailed); } return wavState; @@ -831,43 +775,6 @@ void NeuralAmpModeler::_ProcessOutput(iplug::sample** inputs, iplug::sample** ou #endif } -void NeuralAmpModeler::_SetModelMsg(const WDL_String& modelPath) -{ - auto dspPath = std::filesystem::path(modelPath.Get()); - std::stringstream ss; - // ss << "Loaded "; - if (dspPath.has_filename()) - ss << dspPath.filename().stem().string(); // /path/to/model.nam -> model - else - ss << dspPath.parent_path().filename().string(); // /path/to/model.nam -> model - SendControlMsgFromDelegate(kCtrlTagModelName, 0, int(strlen(ss.str().c_str())), ss.str().c_str()); -} - -void NeuralAmpModeler::_SetIRMsg(const WDL_String& irPath) -{ - this->mIRPath = irPath; // This might already be done elsewhere...need to dedup. - auto dspPath = std::filesystem::path(irPath.Get()); - std::stringstream ss; - // ss << "Loaded " << dspPath.filename().stem(); - ss << dspPath.filename().stem().string(); // /path/to/ir.wav -> ir; - SendControlMsgFromDelegate(kCtrlTagIRName, 0, int(strlen(ss.str().c_str())), ss.str().c_str()); -} - -void NeuralAmpModeler::_UnsetModelMsg() -{ - this->_UnsetMsg(kCtrlTagModelName, this->mDefaultNAMString); -} - -void NeuralAmpModeler::_UnsetIRMsg() -{ - this->_UnsetMsg(kCtrlTagIRName, this->mDefaultIRString); -} - -void NeuralAmpModeler::_UnsetMsg(const int tag, const WDL_String& msg) -{ - SendControlMsgFromDelegate(tag, 0, int(strlen(msg.Get())), msg.Get()); -} - void NeuralAmpModeler::_UpdateMeters(sample** inputPointer, sample** outputPointer, const size_t nFrames, const size_t nChansIn, const size_t nChansOut) { diff --git a/NeuralAmpModeler/NeuralAmpModeler.h b/NeuralAmpModeler/NeuralAmpModeler.h @@ -34,8 +34,8 @@ const int numKnobs = 6; enum ECtrlTags { - kCtrlTagModelName = 0, - kCtrlTagIRName, + kCtrlTagModelFileBrowser = 0, + kCtrlTagIRFileBrowser, kCtrlTagInputMeter, kCtrlTagOutputMeter, kCtrlTagAboutBox, @@ -43,6 +43,18 @@ enum ECtrlTags kNumCtrlTags }; +enum EMsgTags +{ + // These tags are used from UI -> DSP + kMsgTagClearModel = 0, + kMsgTagClearIR, + // The following tags are from DSP -> UI + kMsgTagLoadFailed, + kMsgTagLoadedModel, + kMsgTagLoadedIR, + kNumMsgTags +}; + class NeuralAmpModeler final : public iplug::Plugin { public: @@ -59,6 +71,7 @@ public: bool OnHostRequestingSupportedViewConfiguration(int width, int height) override { return true; } void OnParamChangeUI(int paramIdx, iplug::EParamSource source) override; + bool OnMessage(int msgTag, int ctrlTag, int dataSize, const void* pData) override; private: // Allocates mInputPointers and mOutputPointers @@ -82,8 +95,7 @@ private: // Return status code so that error messages can be relayed if // it wasn't successful. dsp::wav::LoadReturnCode _GetIR(const WDL_String& irPath); - // Update the message about which model is loaded. - void _SetModelMsg(const WDL_String& dspPath); + bool _HaveModel() const { return this->mNAM != nullptr; }; // Prepare the input & output buffers void _PrepareBuffers(const size_t numChannels, const size_t numFrames); @@ -98,14 +110,7 @@ private: // :param nChansOut: Out to external void _ProcessOutput(iplug::sample** inputs, iplug::sample** outputs, const size_t nFrames, const size_t nChansIn, const size_t nChansOut); - // Update the text in the IR area to say what's loaded. - void _SetIRMsg(const WDL_String& irPath); - // Disable Normalization toggle when no loudness data in model metadata - // Sometimes the UI isn't initialized, so we have to try again later. - // - void _UnsetModelMsg(); - void _UnsetIRMsg(); - void _UnsetMsg(const int tag, const WDL_String& msg); + // Update level meters // Called within ProcessBlock(). // Assume _ProcessInput() and _ProcessOutput() were run immediately before. @@ -136,10 +141,9 @@ private: std::unique_ptr<DSP> mStagedNAM; std::unique_ptr<dsp::ImpulseResponse> mStagedIR; // Flags to take away the modules at a safe time. - bool mFlagRemoveNAM; - bool mFlagRemoveIR; - const WDL_String mDefaultNAMString; - const WDL_String mDefaultIRString; + std::atomic<bool> mFlagRemoveNAM; + std::atomic<bool> mFlagRemoveIR; + std::atomic<bool> mNewNAMLoadedInDSP = false; // Tone stack modules diff --git a/NeuralAmpModeler/NeuralAmpModelerControls.h b/NeuralAmpModeler/NeuralAmpModelerControls.h @@ -39,17 +39,6 @@ public: } }; -class IVUpdateableLabelControl : public IVLabelControl -{ -public: - IVUpdateableLabelControl(const IRECT& bounds, const char* str, const IVStyle& style) - : IVLabelControl(bounds, str, style) - { - } - - void OnMsgFromDelegate(int msgTag, int dataSize, const void* pData) { SetStr(reinterpret_cast<const char*>(pData)); } -}; - class NamKnobControl : public IVKnobControl, public IBitmapBase { public: @@ -110,3 +99,202 @@ public: private: IBitmap mHandleBitmap; }; + +class NAMFileNameControl : public IVButtonControl +{ +public: + NAMFileNameControl(const IRECT& bounds, const char* label, const IVStyle& style) + : IVButtonControl(bounds, DefaultClickActionFunc, label, style) + { + } + + void SetLabelAndTooltip(const char* str) + { + SetLabelStr(str); + SetTooltip(str); + } + + void SetLabelAndTooltipEllipsizing(const WDL_String& fileName) + { + auto EllipsizeFilePath = [](const char* filePath, size_t prefixLength, size_t suffixLength, size_t maxLength) { + const std::string ellipses = "..."; + assert (maxLength <= (prefixLength + suffixLength + ellipses.size())); + std::string str {filePath}; + + if (str.length() <= maxLength) + { + return str; + } + else + { + return str.substr(0, prefixLength) + ellipses + str.substr(str.length() - suffixLength); + } + }; + + auto ellipsizedFileName = EllipsizeFilePath(fileName.get_filepart(), 22, 22, 45); + SetLabelStr(ellipsizedFileName.c_str()); + SetTooltip(fileName.get_filepart()); + } +}; + +class NAMFileBrowserControl : public IDirBrowseControlBase +{ +public: + NAMFileBrowserControl(const IRECT& bounds, int clearMsgTag, const char* labelStr, const char* fileExtension, + IFileDialogCompletionHandlerFunc ch, const IVStyle& style, + const ISVG& loadSVG, const ISVG& clearSVG, const ISVG& leftSVG, const ISVG& rightSVG) + : IDirBrowseControlBase(bounds, fileExtension, true) + , mClearMsgTag(clearMsgTag) + , mDefaultLabelStr(labelStr) + , mCompletionHandlerFunc(ch) + , mStyle(style.WithColor(kFG, COLOR_TRANSPARENT).WithDrawFrame(false)) + , mLoadSVG(loadSVG) + , mClearSVG(clearSVG) + , mLeftSVG(leftSVG) + , mRightSVG(rightSVG) + { + mIgnoreMouse = true; + mShowFileExtensions = false; + } + + void Draw(IGraphics& g) override { /* NO-OP */ } + + void OnPopupMenuSelection(IPopupMenu* pSelectedMenu, int valIdx) override + { + if (pSelectedMenu) + { + IPopupMenu::Item* pItem = pSelectedMenu->GetChosenItem(); + + if (pItem) + { + mSelectedIndex = mItems.Find(pItem); + LoadFileAtCurrentIndex(); + } + } + } + + void OnAttached() override + { + auto prevFileFunc = [&](IControl* pCaller) { + mSelectedIndex--; + + if (mSelectedIndex < 0) + mSelectedIndex = NItems() - 1; + + LoadFileAtCurrentIndex(); + }; + + auto nextFileFunc = [&](IControl* pCaller) { + mSelectedIndex++; + + if (mSelectedIndex >= NItems()) + mSelectedIndex = 0; + + LoadFileAtCurrentIndex(); + }; + + auto loadFileFunc = [&](IControl* pCaller) { + WDL_String fileName; + WDL_String path; + pCaller->GetUI()->PromptForFile(fileName, path, EFileAction::Open, mExtension.Get(), + [&](const WDL_String& fileName, const WDL_String& path) { + + if (fileName.GetLength()) + { + ClearPathList(); + AddPath(path.Get(), ""); + SetupMenu(); + SetSelectedFile(fileName.Get()); + LoadFileAtCurrentIndex(); + } + }); + }; + + auto clearFileFunc = [&](IControl* pCaller) { + pCaller->GetDelegate()->SendArbitraryMsgFromUI(mClearMsgTag); + mFileNameControl->SetLabelAndTooltip(mDefaultLabelStr.Get()); + pCaller->GetUI()->GetControlWithTag(kCtrlTagOutNorm)->SetDisabled(false); + }; + + auto chooseFileFunc = [&, loadFileFunc](IControl* pCaller) { + if (std::string_view(pCaller->As<IVButtonControl>()->GetLabelStr()) == mDefaultLabelStr.Get()) + { + loadFileFunc(pCaller); + } + else + { + CheckSelectedItem(); + pCaller->GetUI()->CreatePopupMenu(*this, mMainMenu, pCaller->GetRECT()); + } + }; + + IRECT padded = mRECT.GetPadded(-5.f); + const auto buttonWidth = padded.H(); + const auto loadFileButtonBounds = padded.ReduceFromLeft(buttonWidth); + const auto clearButtonBounds = padded.ReduceFromRight(buttonWidth); + const auto leftButtonBounds = padded.ReduceFromLeft(buttonWidth); + const auto rightButtonBounds = padded.ReduceFromLeft(buttonWidth); + const auto fileNameButtonBounds = padded; + + AddChildControl(new IRolloverSVGButtonControl(loadFileButtonBounds, DefaultClickActionFunc, mLoadSVG)) + ->SetAnimationEndActionFunction(loadFileFunc); + AddChildControl(new IRolloverSVGButtonControl(leftButtonBounds, DefaultClickActionFunc, mLeftSVG)) + ->SetAnimationEndActionFunction(prevFileFunc); + AddChildControl(new IRolloverSVGButtonControl(rightButtonBounds, DefaultClickActionFunc, mRightSVG)) + ->SetAnimationEndActionFunction(nextFileFunc); + AddChildControl(mFileNameControl = new NAMFileNameControl(fileNameButtonBounds, mDefaultLabelStr.Get(), mStyle)) + ->SetAnimationEndActionFunction(chooseFileFunc); + AddChildControl(new IRolloverSVGButtonControl(clearButtonBounds, DefaultClickActionFunc, mClearSVG)) + ->SetAnimationEndActionFunction(clearFileFunc); + + mFileNameControl->SetLabelAndTooltip(mDefaultLabelStr.Get()); + } + + void LoadFileAtCurrentIndex() + { + if (mSelectedIndex > -1 && mSelectedIndex < mItems.GetSize()) + { + WDL_String fileName, path; + GetSelectedFile(fileName); + mFileNameControl->SetLabelAndTooltipEllipsizing(fileName); + mCompletionHandlerFunc(fileName, path); + } + } + + void OnMsgFromDelegate(int msgTag, int dataSize, const void* pData) override + { + switch (msgTag) + { + case kMsgTagLoadFailed: + ClearPathList(); + SetupMenu(); + mFileNameControl->SetLabelAndTooltip("Load Failed"); + break; + case kMsgTagLoadedModel: + case kMsgTagLoadedIR: + { + WDL_String fileName; + fileName.Set(reinterpret_cast<const char*>(pData)); + ClearPathList(); + fileName.remove_filepart(true); + AddPath(fileName.Get(), ""); + SetupMenu(); + + //reset + fileName.Set(reinterpret_cast<const char*>(pData)); + SetSelectedFile(fileName.Get()); + mFileNameControl->SetLabelAndTooltipEllipsizing(fileName); + break; + } + default: + break; + } + } +private: + WDL_String mDefaultLabelStr; + IFileDialogCompletionHandlerFunc mCompletionHandlerFunc; + NAMFileNameControl* mFileNameControl = nullptr; + IVStyle mStyle; + ISVG mLoadSVG, mClearSVG, mLeftSVG, mRightSVG; + int mClearMsgTag; +}; diff --git a/NeuralAmpModeler/resources/img/SkinEHeritage_ArrowLeft.svg b/NeuralAmpModeler/resources/img/SkinEHeritage_ArrowLeft.svg @@ -1,9 +1 @@ -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools --> -<svg width="800px" height="800px" viewBox="0 0 1024 1024" class="icon" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#000000"> -<g id="SVGRepo_bgCarrier" stroke-width="0"/> -<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/> -<g id="SVGRepo_iconCarrier"> -<path d="M768 903.232l-50.432 56.768L256 512l461.568-448 50.432 56.768L364.928 512z" fill="#999999"/> -</g> -</svg> -\ No newline at end of file +<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 800 800" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g id="SVGRepo_iconCarrier"><path d="M540.098,614.105l-27.599,31.067l-252.597,-245.172l252.597,-245.172l27.599,31.067l-220.585,214.105l220.585,214.105Z" style="fill:#999;fill-rule:nonzero;"/></g></svg> +\ No newline at end of file diff --git a/NeuralAmpModeler/resources/img/SkinEHeritage_ArrowRight.svg b/NeuralAmpModeler/resources/img/SkinEHeritage_ArrowRight.svg @@ -1,9 +1 @@ -<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> -<!-- Uploaded to: SVG Repo, www.svgrepo.com, Transformed by: SVG Repo Mixer Tools --> -<svg width="800px" height="800px" viewBox="0 0 1024 1024" class="icon" version="1.1" xmlns="http://www.w3.org/2000/svg" fill="#000000"> -<g id="SVGRepo_bgCarrier" stroke-width="0"/> -<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"/> -<g id="SVGRepo_iconCarrier"> -<path d="M256 120.768L306.432 64 768 512l-461.568 448L256 903.232 659.072 512z" fill="#999999"/> -</g> -</svg> -\ No newline at end of file +<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 800 800" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><g id="SVGRepo_iconCarrier"><path d="M259.902,614.105l27.599,31.067l252.597,-245.172l-252.597,-245.172l-27.599,31.067l220.585,214.105l-220.585,214.105Z" style="fill:#999;fill-rule:nonzero;"/></g></svg> +\ No newline at end of file