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:
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