VirusEditor.cpp (15583B)
1 #include "VirusEditor.h" 2 3 #include "ArpUserPattern.h" 4 #include "PartButton.h" 5 6 #include "ParameterNames.h" 7 #include "VirusProcessor.h" 8 #include "VirusController.h" 9 10 #include "jucePluginLib/filetype.h" 11 #include "jucePluginLib/parameterbinding.h" 12 #include "jucePluginLib/pluginVersion.h" 13 14 #include "jucePluginEditorLib/patchmanager/savepatchdesc.h" 15 16 #include "juceUiLib/messageBox.h" 17 18 namespace genericVirusUI 19 { 20 VirusEditor::VirusEditor(pluginLib::ParameterBinding& _binding, virus::VirusProcessor& _processorRef, const jucePluginEditorLib::Skin& _skin) : 21 Editor(_processorRef, _binding, _skin), 22 m_processor(_processorRef), 23 m_parameterBinding(_binding), 24 m_romChangedListener(_processorRef.evRomChanged) 25 { 26 create(); 27 28 m_parts.reset(new Parts(*this)); 29 m_leds.reset(new Leds(*this, _processorRef)); 30 31 // be backwards compatible with old skins 32 if(getTabGroupCount() == 0) 33 m_tabs.reset(new Tabs(*this)); 34 35 // be backwards compatible with old skins 36 if(getControllerLinkCountRecursive() == 0) 37 m_controllerLinks.reset(new ControllerLinks(*this)); 38 39 m_midiPorts.reset(new jucePluginEditorLib::MidiPorts(*this, getProcessor())); 40 41 // be backwards compatible with old skins 42 if(!getConditionCountRecursive()) 43 m_fxPage.reset(new FxPage(*this)); 44 45 { 46 auto pmParent = findComponent("ContainerPatchManager", false); 47 if(!pmParent) 48 pmParent = findComponent("page_presets", false); 49 if(!pmParent) 50 pmParent = findComponent("page_2_browser"); 51 setPatchManager(new PatchManager(*this, pmParent)); 52 } 53 54 m_presetName = findComponentT<juce::Label>("PatchName"); 55 56 m_focusedParameter.reset(new jucePluginEditorLib::FocusedParameter(getController(), m_parameterBinding, *this)); 57 58 m_romSelector = findComponentT<juce::ComboBox>("RomSelector"); 59 60 m_playModeSingle = findComponentT<juce::Button>("PlayModeSingle", false); 61 m_playModeMulti = findComponentT<juce::Button>("PlayModeMulti", false); 62 63 if(m_playModeSingle && m_playModeMulti) 64 { 65 m_playModeSingle->onClick = [this]{ if(m_playModeSingle->getToggleState()) setPlayMode(virusLib::PlayMode::PlayModeSingle); }; 66 m_playModeMulti->onClick = [this]{ if(m_playModeMulti->getToggleState()) setPlayMode(virusLib::PlayMode::PlayModeMulti); }; 67 } 68 else 69 { 70 m_playModeToggle = findComponentT<juce::Button>("PlayModeToggle"); 71 m_playModeToggle->onClick = [this]{ setPlayMode(m_playModeToggle->getToggleState() ? virusLib::PlayMode::PlayModeMulti : virusLib::PlayMode::PlayModeSingle); }; 72 } 73 74 if(m_romSelector) 75 { 76 const auto roms = m_processor.getRoms(); 77 78 if(roms.empty()) 79 { 80 m_romSelector->addItem("<No ROM found>", 1); 81 } 82 else 83 { 84 int id = 1; 85 86 for (const auto& rom : roms) 87 { 88 const auto name = juce::File(rom.getFilename()).getFileNameWithoutExtension(); 89 m_romSelector->addItem(name + " (" + rom.getModelName() + ')', id++); 90 } 91 } 92 93 m_romSelector->setSelectedId(static_cast<int>(m_processor.getSelectedRomIndex()) + 1, juce::dontSendNotification); 94 95 m_romSelector->onChange = [this] 96 { 97 const auto oldIndex = m_processor.getSelectedRomIndex(); 98 const auto newIndex = m_romSelector->getSelectedId() - 1; 99 if(!m_processor.setSelectedRom(newIndex)) 100 m_romSelector->setSelectedId(static_cast<int>(oldIndex) + 1); 101 }; 102 } 103 104 getController().onProgramChange = [this](int _part) { onProgramChange(_part); }; 105 106 addMouseListener(this, true); 107 108 if(auto* versionInfo = findComponentT<juce::Label>("VersionInfo", false)) 109 { 110 const std::string message = "DSP 56300 Emulator Version " + pluginLib::Version::getVersionString() + " - " + pluginLib::Version::getVersionDateTime(); 111 versionInfo->setText(message, juce::dontSendNotification); 112 } 113 114 if(auto* versionNumber = findComponentT<juce::Label>("VersionNumber", false)) 115 { 116 versionNumber->setText(pluginLib::Version::getVersionString(), juce::dontSendNotification); 117 } 118 119 m_deviceModel = findComponentT<juce::Label>("DeviceModel", false); 120 121 auto* presetSave = findComponentT<juce::Button>("PresetSave", false); 122 if(presetSave) 123 presetSave->onClick = [this] { savePreset(); }; 124 125 auto* presetLoad = findComponentT<juce::Button>("PresetLoad", false); 126 if(presetLoad) 127 presetLoad->onClick = [this] { loadPreset(); }; 128 129 m_presetName->setEditable(false, true, true); 130 m_presetName->onTextChange = [this]() 131 { 132 const auto text = m_presetName->getText(); 133 if (text.trim().length() > 0) 134 { 135 getController().setSinglePresetName(getController().getCurrentPart(), text); 136 onProgramChange(getController().getCurrentPart()); 137 } 138 }; 139 140 m_presetNameMouseListener = new PartMouseListener(pluginLib::MidiPacket::AnyPart, [this](const juce::MouseEvent&, int) 141 { 142 startDragging(new jucePluginEditorLib::patchManager::SavePatchDesc(*getPatchManager(), getController().getCurrentPart()), m_presetName); 143 }); 144 145 m_presetName->addMouseListener(m_presetNameMouseListener, false); 146 147 auto* menuButton = findComponentT<juce::Button>("Menu", false); 148 149 if(menuButton) 150 { 151 menuButton->onClick = [this]() 152 { 153 openMenu(nullptr); 154 }; 155 } 156 157 updatePresetName(); 158 updatePlayModeButtons(); 159 160 m_romChangedListener = [this](auto) 161 { 162 updateDeviceModel(); 163 updateKeyValueConditions("deviceModel", virusLib::getModelName(m_processor.getModel())); 164 m_parts->onPlayModeChanged(); 165 }; 166 } 167 168 VirusEditor::~VirusEditor() 169 { 170 m_presetName->removeMouseListener(m_presetNameMouseListener); 171 delete m_presetNameMouseListener; 172 m_presetNameMouseListener = nullptr; 173 174 m_focusedParameter.reset(); 175 176 m_parameterBinding.clearBindings(); 177 178 getController().onProgramChange = nullptr; 179 } 180 181 virus::Controller& VirusEditor::getController() const 182 { 183 return static_cast<virus::Controller&>(m_processor.getController()); 184 } 185 186 std::pair<std::string, std::string> VirusEditor::getDemoRestrictionText() const 187 { 188 return { 189 m_processor.getProperties().name + " - Demo Mode", 190 m_processor.getProperties().name + " runs in demo mode, the following restrictions apply:\n" 191 "\n" 192 "* The plugin state is not preserved\n" 193 "* Preset saving is disabled"}; 194 } 195 196 genericUI::Button<juce::TextButton>* VirusEditor::createJuceComponent(genericUI::Button<juce::TextButton>* _button, genericUI::UiObject& _object) 197 { 198 if(_object.getName() == "PresetName") 199 return new PartButton(*this); 200 201 return Editor::createJuceComponent(_button, _object); 202 } 203 204 juce::Component* VirusEditor::createJuceComponent(juce::Component* _component, genericUI::UiObject& _object) 205 { 206 if(_object.getName() == "ArpUserGraphics") 207 { 208 assert(m_arpUserPattern == nullptr); 209 m_arpUserPattern = new ArpUserPattern(*this); 210 return m_arpUserPattern; 211 } 212 213 return Editor::createJuceComponent(_component, _object); 214 } 215 216 void VirusEditor::onProgramChange(int _part) 217 { 218 m_parts->onProgramChange(); 219 updatePresetName(); 220 updatePlayModeButtons(); 221 if(getPatchManager()) 222 getPatchManager()->onProgramChanged(_part); 223 } 224 225 void VirusEditor::onPlayModeChanged() 226 { 227 m_parts->onPlayModeChanged(); 228 updatePresetName(); 229 updatePlayModeButtons(); 230 } 231 232 void VirusEditor::onCurrentPartChanged() 233 { 234 m_parts->onCurrentPartChanged(); 235 if(m_arpUserPattern) 236 m_arpUserPattern->onCurrentPartChanged(); 237 updatePresetName(); 238 } 239 240 void VirusEditor::mouseEnter(const juce::MouseEvent& event) 241 { 242 m_focusedParameter->onMouseEnter(event); 243 } 244 245 void VirusEditor::updatePresetName() const 246 { 247 m_presetName->setText(getController().getCurrentPartPresetName(getController().getCurrentPart()), juce::dontSendNotification); 248 } 249 250 void VirusEditor::updatePlayModeButtons() const 251 { 252 if(m_playModeSingle) 253 m_playModeSingle->setToggleState(!getController().isMultiMode(), juce::dontSendNotification); 254 if(m_playModeMulti) 255 m_playModeMulti->setToggleState(getController().isMultiMode(), juce::dontSendNotification); 256 if(m_playModeToggle) 257 m_playModeToggle->setToggleState(getController().isMultiMode(), juce::dontSendNotification); 258 } 259 260 void VirusEditor::updateDeviceModel() 261 { 262 if(!m_deviceModel) 263 return; 264 265 std::string m; 266 267 switch(m_processor.getModel()) 268 { 269 case virusLib::DeviceModel::Invalid: 270 return; 271 case virusLib::DeviceModel::ABC: 272 { 273 auto* rom = m_processor.getSelectedRom(); 274 if(!rom) 275 return; 276 277 virusLib::ROMFile::TPreset data; 278 if(!rom->getSingle(0, 0, data)) 279 return; 280 281 switch(virusLib::Microcontroller::getPresetVersion(data.front())) 282 { 283 case virusLib::A: m = "A"; break; 284 case virusLib::B: m = "B"; break; 285 case virusLib::C: m = "C"; break; 286 case virusLib::D: m = "TI"; break; 287 case virusLib::D2: m = "TI2"; break; 288 default: m = "?"; break; 289 } 290 } 291 break; 292 case virusLib::DeviceModel::Snow: m = "Snow"; break; 293 case virusLib::DeviceModel::TI: m = "TI"; break; 294 case virusLib::DeviceModel::TI2: m = "TI2"; break; 295 } 296 297 m_deviceModel->setText(m, juce::dontSendNotification); 298 } 299 300 void VirusEditor::savePreset() 301 { 302 juce::PopupMenu menu; 303 304 const auto countAdded = getPatchManager()->createSaveMenuEntries(menu); 305 306 if(countAdded) 307 menu.addSeparator(); 308 309 auto addEntry = [&](juce::PopupMenu& _menu, const std::string& _name, const std::function<void(pluginLib::FileType)>& _callback) 310 { 311 juce::PopupMenu subMenu; 312 313 subMenu.addItem(".syx", [_callback](){_callback(pluginLib::FileType::Syx); }); 314 subMenu.addItem(".mid", [_callback](){_callback(pluginLib::FileType::Mid); }); 315 316 _menu.addSubMenu(_name, subMenu); 317 }; 318 319 addEntry(menu, "Export Current Single (Edit Buffer)", [this](const pluginLib::FileType& _type) 320 { 321 savePresets(SaveType::CurrentSingle, _type); 322 }); 323 324 if(getController().isMultiMode()) 325 { 326 addEntry(menu, "Export Arrangement (Multi + 16 Singles)", [this](const pluginLib::FileType& _type) 327 { 328 savePresets(SaveType::Arrangement, _type); 329 }); 330 } 331 332 juce::PopupMenu banksMenu; 333 for(uint8_t b=0; b<static_cast<uint8_t>(getController().getBankCount()); ++b) 334 { 335 addEntry(banksMenu, getController().getBankName(b), [this, b](const pluginLib::FileType& _type) 336 { 337 savePresets(SaveType::Bank, _type, b); 338 }); 339 } 340 341 menu.addSubMenu("Export Bank", banksMenu); 342 343 menu.showMenuAsync(juce::PopupMenu::Options()); 344 } 345 346 void VirusEditor::loadPreset() 347 { 348 Editor::loadPreset([this](const juce::File& _result) 349 { 350 pluginLib::patchDB::DataList results; 351 352 if(!getPatchManager()->loadFile(results, _result.getFullPathName().toStdString())) 353 return; 354 355 auto& c = getController(); 356 357 // we attempt to convert all results as some of them might not be valid preset data 358 for(size_t i=0; i<results.size();) 359 { 360 // convert to load to edit buffer of current part 361 const auto data = c.modifySingleDump(results[i], virusLib::BankNumber::EditBuffer, c.isMultiMode() ? c.getCurrentPart() : virusLib::SINGLE); 362 if(data.empty()) 363 results.erase(results.begin() + i); 364 else 365 results[i++] = data; 366 } 367 368 if (results.size() == 1) 369 { 370 c.activatePatch(results.front()); 371 } 372 else if(results.size() > 1) 373 { 374 // check if this is one multi and 16 singles and load them as arrangement dump. Ask user for confirmation first 375 if(results.size() == 17) 376 { 377 uint32_t multiCount = 0; 378 379 pluginLib::patchDB::DataList singles; 380 pluginLib::patchDB::Data multi; 381 382 for (const auto& result : results) 383 { 384 if(result.size() < 256) 385 continue; 386 387 const auto cmd = result[6]; 388 389 if(cmd == virusLib::SysexMessageType::DUMP_MULTI) 390 { 391 multi = result; 392 ++multiCount; 393 } 394 else if(cmd == virusLib::SysexMessageType::DUMP_SINGLE) 395 { 396 singles.push_back(result); 397 } 398 } 399 400 if(multiCount == 1 && singles.size() == 16) 401 { 402 const auto title = m_processor.getProductName(true) + " - Load Arrangement Dump?"; 403 const auto message = "This file contains an arrangement dump, i.e. one Multi and 16 Singles.\nDo you want to replace the current state by this dump?"; 404 405 genericUI::MessageBox::showYesNo(juce::MessageBoxIconType::QuestionIcon, title, message, [this, multi, singles](const genericUI::MessageBox::Result _result) 406 { 407 if (_result != genericUI::MessageBox::Result::Yes) 408 return; 409 410 auto& c = getController(); 411 412 setPlayMode(virusLib::PlayMode::PlayModeMulti); 413 c.sendSysEx(multi); 414 415 for(uint8_t i=0; i<static_cast<uint8_t>(singles.size()); ++i) 416 { 417 const auto& single = singles[i]; 418 c.modifySingleDump(single, virusLib::BankNumber::EditBuffer, i); 419 c.activatePatch(single, i); 420 } 421 422 c.requestArrangement(); 423 }); 424 425 return; 426 } 427 } 428 genericUI::MessageBox::showOk(juce::AlertWindow::InfoIcon, "Information", 429 "The selected file contains more than one patch. Please add this file as a data source in the Patch Manager instead.\n\n" 430 "Go to the Patch Manager, right click the 'Data Sources' node and select 'Add File...' to import it." 431 ); 432 } 433 }); 434 } 435 436 void VirusEditor::setPlayMode(uint8_t _playMode) 437 { 438 const auto playMode = getController().getParameterIndexByName(virus::g_paramPlayMode); 439 440 auto* param = getController().getParameter(playMode); 441 param->setUnnormalizedValueNotifyingHost(_playMode, pluginLib::Parameter::Origin::Ui); 442 443 // we send this directly here as we request a new arrangement below, we don't want to wait on juce to inform the knob to have changed 444 getController().sendParameterChange(*param, _playMode); 445 446 if (_playMode == virusLib::PlayModeSingle && getController().getCurrentPart() != 0) 447 setPart(0); 448 449 onPlayModeChanged(); 450 451 getController().requestArrangement(); 452 } 453 454 void VirusEditor::savePresets(SaveType _saveType, const pluginLib::FileType& _fileType, uint8_t _bankNumber/* = 0*/) 455 { 456 Editor::savePreset(_fileType, [this, _saveType, _bankNumber, _fileType](const juce::File& _result) 457 { 458 pluginLib::FileType fileType = _fileType; 459 const auto file = createValidFilename(fileType, _result); 460 savePresets(file, _saveType, fileType, _bankNumber); 461 }); 462 } 463 464 bool VirusEditor::savePresets(const std::string& _pathName, SaveType _saveType, const pluginLib::FileType& _fileType, uint8_t _bankNumber/* = 0*/) const 465 { 466 #if SYNTHLIB_DEMO_MODE 467 return false; 468 #else 469 std::vector< std::vector<uint8_t> > messages; 470 471 switch (_saveType) 472 { 473 case SaveType::CurrentSingle: 474 { 475 const auto dump = getController().createSingleDump(getController().getCurrentPart(), toMidiByte(virusLib::BankNumber::A), 0); 476 messages.push_back(dump); 477 } 478 break; 479 case SaveType::Bank: 480 { 481 const auto& presets = getController().getSinglePresets(); 482 if(_bankNumber < presets.size()) 483 { 484 const auto& bankPresets = presets[_bankNumber]; 485 for (const auto& bankPreset : bankPresets) 486 messages.push_back(bankPreset.data); 487 } 488 } 489 break; 490 case SaveType::Arrangement: 491 { 492 getController().onMultiReceived = [this, _fileType, _pathName] 493 { 494 std::vector< std::vector<uint8_t> > messages; 495 messages.push_back(getController().getMultiEditBuffer().data); 496 497 for(uint8_t i=0; i<16; ++i) 498 { 499 const auto dump = getController().createSingleDump(i, toMidiByte(virusLib::BankNumber::EditBuffer), i); 500 messages.push_back(dump); 501 Editor::savePresets(_fileType, _pathName, messages); 502 } 503 504 getController().onMultiReceived = {}; 505 }; 506 getController().requestMulti(0, 0); 507 } 508 return true; 509 default: 510 return false; 511 } 512 513 return Editor::savePresets(_fileType, _pathName, messages); 514 #endif 515 } 516 517 void VirusEditor::setPart(size_t _part) 518 { 519 m_parameterBinding.setPart(static_cast<uint8_t>(_part)); 520 onCurrentPartChanged(); 521 setCurrentPart(static_cast<uint8_t>(_part)); 522 } 523 }