xtPatchManager.cpp (10879B)
1 #include "xtPatchManager.h" 2 3 #include "xtController.h" 4 #include "xtEditor.h" 5 #include "xtWaveEditor.h" 6 7 #include "jucePluginEditorLib/pluginProcessor.h" 8 9 #include "jucePluginLib/filetype.h" 10 11 #include "juceUiLib/messageBox.h" 12 13 #include "xtLib/xtMidiTypes.h" 14 15 namespace xtJucePlugin 16 { 17 static constexpr std::initializer_list<jucePluginEditorLib::patchManager::GroupType> g_groupTypes = 18 { 19 jucePluginEditorLib::patchManager::GroupType::Favourites, 20 jucePluginEditorLib::patchManager::GroupType::LocalStorage, 21 jucePluginEditorLib::patchManager::GroupType::DataSources, 22 }; 23 24 PatchManager::PatchManager(Editor& _editor, juce::Component* _root) 25 : jucePluginEditorLib::patchManager::PatchManager(_editor, _root, g_groupTypes) 26 , m_editor(_editor) 27 , m_controller(_editor.getXtController()) 28 { 29 setTagTypeName(pluginLib::patchDB::TagType::CustomA, "MW Model"); 30 jucePluginEditorLib::patchManager::PatchManager::startLoaderThread(); 31 addGroupTreeItemForTag(pluginLib::patchDB::TagType::CustomA); 32 } 33 34 PatchManager::~PatchManager() 35 { 36 stopLoaderThread(); 37 } 38 39 bool PatchManager::requestPatchForPart(pluginLib::patchDB::Data& _data, const uint32_t _part, uint64_t) 40 { 41 _data = m_controller.createSingleDump(xt::LocationH::SingleBankA, 0, static_cast<uint8_t>(_part)); 42 _data = createCombinedDump(_data); 43 return !_data.empty(); 44 } 45 46 bool PatchManager::loadRomData(pluginLib::patchDB::DataList& _results, uint32_t _bank, uint32_t _program) 47 { 48 return false; 49 } 50 51 pluginLib::patchDB::PatchPtr PatchManager::initializePatch(pluginLib::patchDB::Data&& _sysex, const std::string& _defaultPatchName) 52 { 53 if(_sysex.size() == xt::Mw1::g_singleDumpLength) 54 { 55 if(_sysex[1] == wLib::IdWaldorf && _sysex[2] == xt::IdMw1) 56 { 57 // MW1 single dump 58 auto p = std::make_shared<pluginLib::patchDB::Patch>(); 59 60 p->name.resize(xt::Mw1::g_singleNameLength, ' '); 61 memcpy(p->name.data(), &_sysex[xt::Mw1::g_singleNamePosition], xt::Mw1::g_singleNameLength); 62 while(p->name.back() == ' ') 63 p->name.pop_back(); 64 65 p->sysex = std::move(_sysex); 66 67 p->tags.add(pluginLib::patchDB::TagType::CustomA, "MW1"); 68 return p; 69 } 70 } 71 72 pluginLib::MidiPacket::Data data; 73 pluginLib::MidiPacket::AnyPartParamValues parameters; 74 75 bool hasUserTable = false; 76 bool hasUserWaves = false; 77 78 if(_sysex.size() > std::tuple_size_v<xt::State::Single>) 79 { 80 std::vector<xt::SysEx> dumps; 81 xt::State::splitCombinedPatch(dumps, _sysex); 82 83 if(dumps.empty()) 84 return {}; 85 86 if(!m_controller.parseSingle(data, parameters, dumps.front())) 87 return {}; 88 89 if(dumps.size() > 2) 90 hasUserWaves = true; 91 hasUserTable = true; 92 } 93 else if(!m_controller.parseSingle(data, parameters, _sysex)) 94 { 95 return {}; 96 } 97 98 auto p = std::make_shared<pluginLib::patchDB::Patch>(); 99 100 p->sysex = std::move(_sysex); 101 p->name = m_controller.getSingleName(parameters); 102 103 p->tags.add(pluginLib::patchDB::TagType::CustomA, "MW2"); 104 105 if(hasUserTable) 106 p->tags.add(pluginLib::patchDB::TagType::Tag, "UserTable"); 107 108 if(hasUserWaves) 109 p->tags.add(pluginLib::patchDB::TagType::Tag, "UserWave"); 110 111 return p; 112 } 113 114 pluginLib::patchDB::Data PatchManager::applyModifications(const pluginLib::patchDB::PatchPtr& _patch, const pluginLib::FileType& _fileType, pluginLib::ExportType _exportType) const 115 { 116 auto applyModifications = [&_patch](pluginLib::patchDB::Data& _result) -> bool 117 { 118 if (xt::State::getCommand(_result) != xt::SysexCommand::SingleDump) 119 return false; 120 121 const auto dumpSize = _result.size(); 122 123 if (dumpSize != std::tuple_size_v<xt::State::Single>) 124 return false; 125 126 // apply name 127 if (!_patch->getName().empty()) 128 xt::State::setSingleName(_result, _patch->getName()); 129 130 // apply program 131 uint32_t program = 0; 132 uint32_t bank = 0; 133 if(_patch->program != pluginLib::patchDB::g_invalidProgram) 134 { 135 program = std::clamp(_patch->program, 0u, 299u); 136 137 bank = program / 128; 138 program -= bank * 128; 139 } 140 141 _result[xt::SysexIndex::IdxSingleBank ] = static_cast<uint8_t>(bank); 142 _result[xt::SysexIndex::IdxSingleProgram] = static_cast<uint8_t>(program); 143 144 xt::State::updateChecksum(_result, xt::SysexIndex::IdxSingleChecksumStart); 145 146 return true; 147 }; 148 149 if (xt::State::getCommand(_patch->sysex) == xt::SysexCommand::SingleDump) 150 { 151 auto result = _patch->sysex; 152 153 if (applyModifications(result)) 154 return result; 155 156 std::vector<xt::SysEx> dumps; 157 158 if (xt::State::splitCombinedPatch(dumps, _patch->sysex)) 159 { 160 if (applyModifications(dumps[0])) 161 { 162 if (_exportType == pluginLib::ExportType::File) 163 { 164 // hardware compatibility: multiple sysex 165 xt::SysEx r; 166 167 for (auto& dump : dumps) 168 r.insert(r.end(), dump.begin(), dump.end()); 169 170 return r; 171 } 172 173 // emu compatibility: custom patch format, one sysex that includes everything 174 return xt::State::createCombinedPatch(dumps); 175 } 176 } 177 } 178 179 return _patch->sysex; 180 } 181 182 uint32_t PatchManager::getCurrentPart() const 183 { 184 return m_editor.getProcessor().getController().getCurrentPart(); 185 } 186 187 bool PatchManager::activatePatch(const pluginLib::patchDB::PatchPtr& _patch, const uint32_t _part) 188 { 189 if(!m_controller.sendSingle(applyModifications(_patch, pluginLib::FileType::Empty, pluginLib::ExportType::EmuHardware), static_cast<uint8_t>(_part))) 190 { 191 genericUI::MessageBox::showOk(juce::MessageBoxIconType::WarningIcon, 192 m_editor.getProcessor().getProperties().name + " - Unable to load patch", 193 "MW1 patches can only be loaded to the first part.\n" 194 "\n" 195 "If you want to load a MW1 patch to another part, first convert it by loading it to part 1, then save the loaded patch to a user bank."); 196 } 197 return true; 198 } 199 200 bool PatchManager::parseFileData(pluginLib::patchDB::DataList& _results, const pluginLib::patchDB::Data& _data) 201 { 202 if(!jucePluginEditorLib::patchManager::PatchManager::parseFileData(_results, _data)) 203 return false; 204 205 // check if there are MW1 bank dumps. A bank dump is one sysex with multiple patches. Split them into individual preset dumps 206 const int resultCount = static_cast<int>(_results.size()); 207 208 for(int r=0; r<resultCount; ++r) 209 { 210 auto& res = _results[r]; 211 212 if(res.size() < xt::Mw1::g_singleDumpLength) 213 continue; 214 215 if(res[0] != 0xf0 || res[1] != wLib::IdWaldorf || res[2] != xt::IdMw1) 216 continue; 217 218 auto createPreset = [](pluginLib::patchDB::DataList& _res, const std::vector<uint8_t>& _source, size_t _readPos) 219 { 220 pluginLib::patchDB::Data data; 221 222 constexpr uint8_t deviceNum = 0; 223 224 // create single dump preset header 225 data.reserve(xt::Mw1::g_singleDumpLength); 226 data.assign({0xf0, wLib::IdWaldorf, xt::IdMw1, deviceNum, xt::Mw1::g_idmPreset}); 227 228 // add data 229 uint8_t checksum = 0; 230 231 for(size_t j=0; j<xt::Mw1::g_singleLength; ++j, ++_readPos) 232 { 233 const auto d = _source[_readPos]; 234 checksum += d; 235 data.push_back(d); 236 } 237 238 // add checksum and EOX. FWIW the XT ignores the checksum anyway 239 data.push_back(checksum & 0x7f); 240 data.push_back(0xf7); 241 242 _res.push_back(std::move(data)); 243 return _readPos; 244 }; 245 246 if(res[4] == xt::Mw1::g_idmPresetBank) 247 { 248 // remove bank from results 249 const auto source = std::move(res); 250 _results.erase(_results.begin() + r); 251 --r; 252 253 const auto rawDataSize = source.size() - xt::Mw1::g_sysexHeaderSize - xt::Mw1::g_sysexFooterSize; 254 const auto presetCount = rawDataSize / xt::Mw1::g_singleLength; 255 256 size_t readPos = xt::Mw1::g_sysexHeaderSize; 257 258 _results.reserve(presetCount); 259 260 for(size_t i=0; i<presetCount; ++i) 261 readPos = createPreset(_results, source, readPos); 262 } 263 else if(res[4] == xt::Mw1::g_idmCartridgeBank) 264 { 265 // remove bank from results 266 const auto source = std::move(res); 267 _results.erase(_results.begin() + r); 268 --r; 269 270 size_t readPos = 5; 271 _results.reserve(64); 272 for(size_t p=0; p<64; ++p) 273 readPos = createPreset(_results, source, readPos); 274 } 275 } 276 277 createCombinedDumps(_results); 278 279 return true; 280 } 281 282 pluginLib::patchDB::Data PatchManager::createCombinedDump(const pluginLib::patchDB::Data& _data) const 283 { 284 // combine single dump with user wave table and user waves if applicable 285 if(auto* waveEditor = m_editor.getWaveEditor()) 286 { 287 std::vector<xt::SysEx> results; 288 waveEditor->getData().getWaveDataForSingle(results, _data); 289 if(!results.empty()) 290 { 291 results.insert(results.begin(), _data); 292 auto result = xt::State::createCombinedPatch(results); 293 if(!result.empty()) 294 return result; 295 } 296 } 297 return _data; 298 } 299 300 void PatchManager::createCombinedDumps(std::vector<pluginLib::patchDB::Data>& _messages) 301 { 302 // grab single dumps, waves and control tables and combine them into our custom single format that includes waves & tables if applicable 303 304 m_waves.clear(); 305 m_tables.clear(); 306 m_singles.clear(); 307 308 // grab waves & tables first, if there are none we can skip everything 309 for (auto& msg : _messages) 310 { 311 auto cmd = xt::State::getCommand(msg); 312 switch (cmd) 313 { 314 case xt::SysexCommand::WaveDump: 315 { 316 const auto id = xt::State::getWaveId(msg); 317 if (m_waves.find(id) == m_waves.end()) 318 { 319 m_waves.emplace(id, std::move(msg)); 320 msg.clear(); 321 } 322 } 323 break; 324 case xt::SysexCommand::WaveCtlDump: 325 { 326 const auto id = xt::State::getTableId(msg); 327 if (m_tables.find(id) == m_tables.end()) 328 { 329 m_tables.emplace(id, std::move(msg)); 330 msg.clear(); 331 } 332 } 333 break; 334 default:; 335 } 336 } 337 338 if (m_tables.empty()) 339 return; 340 341 for (auto& msg : _messages) 342 { 343 auto cmd = xt::State::getCommand(msg); 344 345 if (cmd != xt::SysexCommand::SingleDump) 346 continue; 347 348 auto table = xt::State::getWavetableFromSingleDump(msg); 349 350 if (xt::wave::isReadOnly(table)) 351 continue; 352 353 std::vector<xt::SysEx> results; 354 getWaveDataForSingle(results, msg); 355 if (results.empty()) 356 continue; 357 358 results.insert(results.begin(), msg); 359 auto newSingle = xt::State::createCombinedPatch(results); 360 msg.assign(newSingle.begin(), newSingle.end()); 361 } 362 } 363 364 void PatchManager::getWaveDataForSingle(std::vector<xt::SysEx>& _results, const xt::SysEx& _single) const 365 { 366 const auto tableId = xt::State::getWavetableFromSingleDump(_single); 367 368 if(xt::wave::isReadOnly(tableId)) 369 return; 370 371 auto itTable = m_tables.find(tableId); 372 if (itTable == m_tables.end()) 373 return; 374 375 xt::TableData table; 376 377 if (!xt::State::parseTableData(table, itTable->second)) 378 return; 379 380 const auto waves = xt::State::getWavesForTable(table); 381 382 for (const auto waveId : waves) 383 { 384 if(!xt::wave::isValidWaveIndex(waveId.rawId())) 385 continue; 386 387 if(xt::wave::isReadOnly(waveId)) 388 continue; 389 390 const auto itWave = m_waves.find(waveId); 391 if (itWave == m_waves.end()) 392 continue; 393 394 const auto wave = itWave->second; 395 396 _results.emplace_back(wave); 397 } 398 399 _results.emplace_back(itTable->second); 400 } 401 }