diff --git a/NeuralAmpModeler/NeuralAmpModeler.cpp b/NeuralAmpModeler/NeuralAmpModeler.cpp index 4a08266ac..811d89837 100644 --- a/NeuralAmpModeler/NeuralAmpModeler.cpp +++ b/NeuralAmpModeler/NeuralAmpModeler.cpp @@ -2,6 +2,7 @@ #include // pow #include #include +#include #include #include "Colors.h" @@ -69,6 +70,23 @@ EMsgBoxResult _ShowMessageBox(iplug::igraphics::IGraphics* pGraphics, const char #endif } +std::string _GetModelDisplayName(const nam::dspData& modelData) +{ + if (!modelData.metadata.is_object()) + { + return ""; + } + + auto it = modelData.metadata.find("name"); + if (it == modelData.metadata.end() || !it->is_string()) + { + return ""; + } + + const std::string name = it->get(); + return name.empty() ? "" : name; +} + const std::string kCalibrateInputParamName = "CalibrateInput"; const bool kDefaultCalibrateInput = false; const std::string kInputCalibrationLevelParamName = "InputCalibrationLevel"; @@ -486,7 +504,7 @@ void NeuralAmpModeler::OnUIOpen() if (mNAMPath.GetLength()) { - SendControlMsgFromDelegate(kCtrlTagModelFileBrowser, kMsgTagLoadedModel, mNAMPath.GetLength(), mNAMPath.Get()); + _SendLoadedModelMessage(); // If it's not loaded yet, then mark as failed. // If it's yet to be loaded, then the completion handler will set us straight once it runs. if (mModel == nullptr && mStagedModel == nullptr) @@ -599,6 +617,7 @@ void NeuralAmpModeler::_ApplyDSPStaging() { mModel = nullptr; mNAMPath.Set(""); + mNAMDisplayName.Set(""); mShouldRemoveModel = false; mModelCleared = true; _UpdateLatency(); @@ -742,13 +761,30 @@ void NeuralAmpModeler::_ApplySlimParamToLoadedNAMs() apply(mStagedModel.get()); } +void NeuralAmpModeler::_SendLoadedModelMessage() +{ + std::string payload(mNAMPath.Get(), mNAMPath.GetLength()); + payload.push_back('\0'); + + if (mNAMDisplayName.GetLength()) + { + payload.append(mNAMDisplayName.Get(), mNAMDisplayName.GetLength()); + payload.push_back('\0'); + } + + SendControlMsgFromDelegate(kCtrlTagModelFileBrowser, kMsgTagLoadedModel, static_cast(payload.size()), + payload.data()); +} + std::string NeuralAmpModeler::_StageModel(const WDL_String& modelPath) { WDL_String previousNAMPath = mNAMPath; + WDL_String previousNAMDisplayName = mNAMDisplayName; try { auto dspPath = std::filesystem::u8path(modelPath.Get()); - std::unique_ptr model = nam::get_dsp(dspPath); + nam::dspData modelData; + std::unique_ptr model = nam::get_dsp(dspPath, modelData); // Check that the model has 1 input and 1 output channel if (model->NumInputChannels() != 1) @@ -769,7 +805,8 @@ std::string NeuralAmpModeler::_StageModel(const WDL_String& modelPath) } mStagedModel = std::move(temp); mNAMPath = modelPath; - SendControlMsgFromDelegate(kCtrlTagModelFileBrowser, kMsgTagLoadedModel, mNAMPath.GetLength(), mNAMPath.Get()); + mNAMDisplayName.Set(_GetModelDisplayName(modelData).c_str()); + _SendLoadedModelMessage(); } catch (std::runtime_error& e) { @@ -780,6 +817,7 @@ std::string NeuralAmpModeler::_StageModel(const WDL_String& modelPath) mStagedModel = nullptr; } mNAMPath = previousNAMPath; + mNAMDisplayName = previousNAMDisplayName; std::cerr << "Failed to read DSP module" << std::endl; std::cerr << e.what() << std::endl; return e.what(); diff --git a/NeuralAmpModeler/NeuralAmpModeler.h b/NeuralAmpModeler/NeuralAmpModeler.h index f5dae839d..0e52437e8 100644 --- a/NeuralAmpModeler/NeuralAmpModeler.h +++ b/NeuralAmpModeler/NeuralAmpModeler.h @@ -256,6 +256,7 @@ class NeuralAmpModeler final : public iplug::Plugin void _SetInputGain(); void _SetOutputGain(); void _ApplySlimParamToLoadedNAMs(); + void _SendLoadedModelMessage(); // See: Unserialization.cpp void _UnserializeApplyConfig(nlohmann::json& config); @@ -316,6 +317,8 @@ class NeuralAmpModeler final : public iplug::Plugin // Path to model's config.json or model.nam WDL_String mNAMPath; + // Name from the model metadata, if present. + WDL_String mNAMDisplayName; // Path to IR (.wav file) WDL_String mIRPath; diff --git a/NeuralAmpModeler/NeuralAmpModelerControls.h b/NeuralAmpModeler/NeuralAmpModelerControls.h index cd6e5b07b..3b1a71706 100644 --- a/NeuralAmpModeler/NeuralAmpModelerControls.h +++ b/NeuralAmpModeler/NeuralAmpModelerControls.h @@ -1,12 +1,18 @@ #pragma once +#include // std::find #include // std::round #include // FILE, fclose +#include // std::ifstream #include // std::stringstream +#include // std::string +#include // std::string_view #include // std::unordered_map #include "IControls.h" #include "IPlugPaths.h" +#include "../NeuralAmpModelerCore/Dependencies/nlohmann/json.hpp" + #ifdef OS_WIN #include #include @@ -269,7 +275,6 @@ class NAMFileBrowserControl : public IDirBrowseControlBase const ISVG& clearSVG, const ISVG& leftSVG, const ISVG& rightSVG, const IBitmap& bitmap, const ISVG& globeSVG, const char* getButtonLabel, const char* getButtonURL) : IDirBrowseControlBase(bounds, fileExtension, false, false) - , mClearMsgTag(clearMsgTag) , mDefaultLabelStr(labelStr) , mCompletionHandlerFunc(ch) , mStyle(style.WithColor(kFG, COLOR_TRANSPARENT).WithDrawFrame(false)) @@ -279,6 +284,8 @@ class NAMFileBrowserControl : public IDirBrowseControlBase , mLeftSVG(leftSVG) , mRightSVG(rightSVG) , mGlobeSVG(globeSVG) + , mClearMsgTag(clearMsgTag) + , mUseModelMetadataNames(std::string_view(fileExtension) == "nam") , mGetButtonLabel(getButtonLabel) , mGetButtonURL(getButtonURL) , mBrowserState(NAMBrowserState::Empty) @@ -338,7 +345,7 @@ class NAMFileBrowserControl : public IDirBrowseControlBase { ClearPathList(); AddPath(path.Get(), ""); - SetupMenu(); + SetupMenuWithDisplayNames(); SelectFirstFile(); LoadFileAtCurrentIndex(); } @@ -350,7 +357,7 @@ class NAMFileBrowserControl : public IDirBrowseControlBase { ClearPathList(); AddPath(path.Get(), ""); - SetupMenu(); + SetupMenuWithDisplayNames(); SetSelectedFile(fileName.Get()); LoadFileAtCurrentIndex(); } @@ -425,6 +432,21 @@ class NAMFileBrowserControl : public IDirBrowseControlBase void OnMsgFromDelegate(int msgTag, int dataSize, const void* pData) override { + auto ReadPayloadString = [](const void* payload, int size, int& offset) { + if (payload == nullptr || offset >= size) + { + return std::string(); + } + + const char* data = reinterpret_cast(payload); + const char* begin = data + offset; + const char* end = data + size; + const char* terminator = std::find(begin, end, '\0'); + std::string value(begin, terminator); + offset = terminator == end ? size : static_cast(terminator - data) + 1; + return value; + }; + switch (msgTag) { case kMsgTagLoadFailed: @@ -438,16 +460,26 @@ class NAMFileBrowserControl : public IDirBrowseControlBase case kMsgTagLoadedModel: case kMsgTagLoadedIR: { + int offset = 0; + const std::string pathString = ReadPayloadString(pData, dataSize, offset); + const std::string displayName = msgTag == kMsgTagLoadedModel ? ReadPayloadString(pData, dataSize, offset) : ""; WDL_String fileName, directory; - fileName.Set(reinterpret_cast(pData)); - directory.Set(reinterpret_cast(pData)); + fileName.Set(pathString.c_str()); + directory.Set(pathString.c_str()); directory.remove_filepart(true); ClearPathList(); AddPath(directory.Get(), ""); - SetupMenu(); + SetupMenuWithDisplayNames(); SetSelectedFile(fileName.Get()); - mFileNameControl->SetLabelAndTooltipEllipsizing(fileName); + if (displayName.empty()) + { + mFileNameControl->SetLabelAndTooltipEllipsizing(fileName); + } + else + { + mFileNameControl->SetLabelAndTooltip(displayName.c_str()); + } SetBrowserState(NAMBrowserState::Loaded); } break; @@ -458,6 +490,75 @@ class NAMFileBrowserControl : public IDirBrowseControlBase private: void SelectFirstFile() { mSelectedItemIndex = mFiles.GetSize() ? 0 : -1; } + void SetupMenuWithDisplayNames() + { + SetupMenu(); + UpdateModelMenuDisplayNames(); + } + + std::string GetCachedModelMetadataName(const char* filePath) + { + const std::string key(filePath); + auto cached = mModelMetadataNames.find(key); + if (cached != mModelMetadataNames.end()) + { + return cached->second; + } + + std::ifstream file(filePath); + if (!file.is_open()) + { + mModelMetadataNames[key] = ""; + return ""; + } + + try + { + nlohmann::json modelJson; + file >> modelJson; + const auto metadata = modelJson.find("metadata"); + if (metadata != modelJson.end() && metadata->is_object()) + { + const auto name = metadata->find("name"); + if (name != metadata->end() && name->is_string()) + { + mModelMetadataNames[key] = name->get(); + return mModelMetadataNames[key]; + } + } + } + catch (const nlohmann::json::exception&) + { + } + + mModelMetadataNames[key] = ""; + return ""; + } + + void UpdateModelMenuDisplayNames() + { + if (!mUseModelMetadataNames) + { + return; + } + + for (int itemIdx = 0; itemIdx < mItems.GetSize(); itemIdx++) + { + IPopupMenu::Item* pItem = mItems.Get(itemIdx); + const int fileIdx = pItem->GetTag(); + if (fileIdx < 0 || fileIdx >= mFiles.GetSize()) + { + continue; + } + + const std::string displayName = GetCachedModelMetadataName(mFiles.Get(fileIdx)->Get()); + if (!displayName.empty()) + { + pItem->SetText(displayName.c_str()); + } + } + } + void GetSelectedFileDirectory(WDL_String& path) { GetSelectedFile(path); @@ -490,6 +591,8 @@ class NAMFileBrowserControl : public IDirBrowseControlBase IBitmap mBitmap; ISVG mLoadSVG, mClearSVG, mLeftSVG, mRightSVG, mGlobeSVG; int mClearMsgTag; + bool mUseModelMetadataNames; + std::unordered_map mModelMetadataNames; // new members for the "Get" button const char* mGetButtonLabel;