NeuralAmpModelerPlugin

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

Unserialization.cpp (8152B)


      1 // Unserialization
      2 //
      3 // This plugin is used in important places, so we need to be considerate when
      4 // attempting to unserialize. If the project was last saved with a legacy
      5 // version, then we need it to "update" to the current version is as
      6 // reasonable a way as possible.
      7 //
      8 // In order to handle older versions, the pattern is:
      9 // 1. Implement unserialization for every version into a version-specific
     10 //    struct (Let's use our friend nlohmann::json. Why not?)
     11 // 2. Implement an "update" from each struct to the next one.
     12 // 3. Implement assigning the data contained in the current struct to the
     13 //    current plugin configuration.
     14 //
     15 // This way, a constant amount of effort is required every time the
     16 // serialization changes instead of having to implement a current
     17 // unserialization for each past version.
     18 
     19 // Add new unserialization versions to the top, then add logic to the class method at the bottom.
     20 
     21 // Boilerplate
     22 
     23 void NeuralAmpModeler::_UnserializeApplyConfig(nlohmann::json& config)
     24 {
     25   auto getParamByName = [&](std::string& name) {
     26     // Could use a map but eh
     27     for (int i = 0; i < kNumParams; i++)
     28     {
     29       iplug::IParam* param = GetParam(i);
     30       if (strcmp(param->GetName(), name.c_str()) == 0)
     31       {
     32         return param;
     33       }
     34     }
     35     // else
     36     return (iplug::IParam*)nullptr;
     37   };
     38   TRACE
     39   ENTER_PARAMS_MUTEX
     40   for (auto it = config.begin(); it != config.end(); ++it)
     41   {
     42     std::string name = it.key();
     43     iplug::IParam* pParam = getParamByName(name);
     44     if (pParam != nullptr)
     45     {
     46       pParam->Set(*it);
     47       iplug::Trace(TRACELOC, "%s %f", pParam->GetName(), pParam->Value());
     48     }
     49     else
     50     {
     51       iplug::Trace(TRACELOC, "%s NOT-FOUND", name.c_str());
     52     }
     53   }
     54   OnParamReset(iplug::EParamSource::kPresetRecall);
     55   LEAVE_PARAMS_MUTEX
     56 
     57   mNAMPath.Set(static_cast<std::string>(config["NAMPath"]).c_str());
     58   mIRPath.Set(static_cast<std::string>(config["IRPath"]).c_str());
     59 
     60   if (mNAMPath.GetLength())
     61   {
     62     _StageModel(mNAMPath);
     63   }
     64   if (mIRPath.GetLength())
     65   {
     66     _StageIR(mIRPath);
     67   }
     68 }
     69 
     70 // Unserialize NAM Path, IR path, then named keys
     71 int _UnserializePathsAndExpectedKeys(const iplug::IByteChunk& chunk, int startPos, nlohmann::json& config,
     72                                      std::vector<std::string>& paramNames)
     73 {
     74   int pos = startPos;
     75   WDL_String path;
     76   pos = chunk.GetStr(path, pos);
     77   config["NAMPath"] = std::string(path.Get());
     78   pos = chunk.GetStr(path, pos);
     79   config["IRPath"] = std::string(path.Get());
     80 
     81   for (auto it = paramNames.begin(); it != paramNames.end(); ++it)
     82   {
     83     double v = 0.0;
     84     pos = chunk.Get(&v, pos);
     85     config[*it] = v;
     86   }
     87   return pos;
     88 }
     89 
     90 void _RenameKeys(nlohmann::json& j, std::unordered_map<std::string, std::string> newNames)
     91 {
     92   // Assumes no aliasing!
     93   for (auto it = newNames.begin(); it != newNames.end(); ++it)
     94   {
     95     j[it->second] = j[it->first];
     96     j.erase(it->first);
     97   }
     98 }
     99 
    100 // v0.7.12
    101 
    102 void _UpdateConfigFrom_0_7_12(nlohmann::json& config)
    103 {
    104   // Fill me in once something changes!
    105 }
    106 
    107 int _GetConfigFrom_0_7_12(const iplug::IByteChunk& chunk, int startPos, nlohmann::json& config)
    108 {
    109   std::vector<std::string> paramNames{"Input",
    110                                       "Threshold",
    111                                       "Bass",
    112                                       "Middle",
    113                                       "Treble",
    114                                       "Output",
    115                                       "NoiseGateActive",
    116                                       "ToneStack",
    117                                       "IRToggle",
    118                                       "CalibrateInput",
    119                                       "InputCalibrationLevel",
    120                                       "OutputMode"};
    121 
    122   int pos = _UnserializePathsAndExpectedKeys(chunk, startPos, config, paramNames);
    123   // Then update:
    124   _UpdateConfigFrom_0_7_12(config);
    125   return pos;
    126 }
    127 
    128 // 0.7.10
    129 
    130 void _UpdateConfigFrom_0_7_10(nlohmann::json& config)
    131 {
    132   // Note: "OutNorm" is Bool-like in v0.7.10, but "OutputMode" is enum.
    133   // This works because 0 is "Raw" (cf OutNorm false) and 1 is "Calibrated" (cf OutNorm true).
    134   std::unordered_map<std::string, std::string> newNames{{"OutNorm", "OutputMode"}};
    135   _RenameKeys(config, newNames);
    136   // There are new parameters. If they're not included, then 0.7.12 is ok, but future ones might not be.
    137   config[kCalibrateInputParamName] = (double)kDefaultCalibrateInput;
    138   config[kInputCalibrationLevelParamName] = kDefaultInputCalibrationLevel;
    139   _UpdateConfigFrom_0_7_12(config);
    140 }
    141 
    142 int _GetConfigFrom_0_7_10(const iplug::IByteChunk& chunk, int startPos, nlohmann::json& config)
    143 {
    144   std::vector<std::string> paramNames{
    145     "Input", "Threshold", "Bass", "Middle", "Treble", "Output", "NoiseGateActive", "ToneStack", "OutNorm", "IRToggle"};
    146   int pos = _UnserializePathsAndExpectedKeys(chunk, startPos, config, paramNames);
    147   // Then update:
    148   _UpdateConfigFrom_0_7_10(config);
    149   return pos;
    150 }
    151 
    152 // Earlier than 0.7.10 (Assumed to be 0.7.3-0.7.9)
    153 
    154 void _UpdateConfigFrom_Earlier(nlohmann::json& config)
    155 {
    156   std::unordered_map<std::string, std::string> newNames{{"Gate", "Threshold"}};
    157   _RenameKeys(config, newNames);
    158   _UpdateConfigFrom_0_7_10(config);
    159 }
    160 
    161 int _GetConfigFrom_Earlier(const iplug::IByteChunk& chunk, int startPos, nlohmann::json& config)
    162 {
    163   std::vector<std::string> paramNames{
    164     "Input", "Gate", "Bass", "Middle", "Treble", "Output", "NoiseGateActive", "ToneStack", "OutNorm", "IRToggle"};
    165 
    166   int pos = _UnserializePathsAndExpectedKeys(chunk, startPos, config, paramNames);
    167   // Then update:
    168   _UpdateConfigFrom_Earlier(config);
    169   return pos;
    170 }
    171 
    172 //==============================================================================
    173 
    174 class _Version
    175 {
    176 public:
    177   _Version(const int major, const int minor, const int patch)
    178   : mMajor(major)
    179   , mMinor(minor)
    180   , mPatch(patch) {};
    181   _Version(const std::string& versionStr)
    182   {
    183     std::istringstream stream(versionStr);
    184     std::string token;
    185     std::vector<int> parts;
    186 
    187     // Split the string by "."
    188     while (std::getline(stream, token, '.'))
    189     {
    190       parts.push_back(std::stoi(token)); // Convert to int and store
    191     }
    192 
    193     // Check if we have exactly 3 parts
    194     if (parts.size() != 3)
    195     {
    196       throw std::invalid_argument("Input string does not contain exactly 3 segments separated by '.'");
    197     }
    198 
    199     // Assign the parts to the provided int variables
    200     mMajor = parts[0];
    201     mMinor = parts[1];
    202     mPatch = parts[2];
    203   };
    204 
    205   bool operator>=(const _Version& other) const
    206   {
    207     // Compare on major version:
    208     if (GetMajor() > other.GetMajor())
    209     {
    210       return true;
    211     }
    212     if (GetMajor() < other.GetMajor())
    213     {
    214       return false;
    215     }
    216     // Compare on minor
    217     if (GetMinor() > other.GetMinor())
    218     {
    219       return true;
    220     }
    221     if (GetMinor() < other.GetMinor())
    222     {
    223       return false;
    224     }
    225     // Compare on patch
    226     return GetPatch() >= other.GetPatch();
    227   };
    228 
    229   int GetMajor() const { return mMajor; };
    230   int GetMinor() const { return mMinor; };
    231   int GetPatch() const { return mPatch; };
    232 
    233 private:
    234   int mMajor;
    235   int mMinor;
    236   int mPatch;
    237 };
    238 
    239 int NeuralAmpModeler::_UnserializeStateWithKnownVersion(const iplug::IByteChunk& chunk, int startPos)
    240 {
    241   // We already got through the header before calling this.
    242   int pos = startPos;
    243 
    244   // Get the version
    245   WDL_String wVersion;
    246   pos = chunk.GetStr(wVersion, pos);
    247   std::string versionStr(wVersion.Get());
    248   _Version version(versionStr);
    249   // Act accordingly
    250   nlohmann::json config;
    251   if (version >= _Version(0, 7, 12))
    252   {
    253     pos = _GetConfigFrom_0_7_12(chunk, pos, config);
    254   }
    255   else if (version >= _Version(0, 7, 10))
    256   {
    257     pos = _GetConfigFrom_0_7_10(chunk, pos, config);
    258   }
    259   else if (version >= _Version(0, 7, 9))
    260   {
    261     pos = _GetConfigFrom_Earlier(chunk, pos, config);
    262   }
    263   else
    264   {
    265     // You shouldn't be here...
    266     assert(false);
    267   }
    268   _UnserializeApplyConfig(config);
    269   return pos;
    270 }
    271 
    272 int NeuralAmpModeler::_UnserializeStateWithUnknownVersion(const iplug::IByteChunk& chunk, int startPos)
    273 {
    274   nlohmann::json config;
    275   int pos = _GetConfigFrom_Earlier(chunk, startPos, config);
    276   _UnserializeApplyConfig(config);
    277   return pos;
    278 }