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 }