gearmulator

Emulation of classic VA synths of the late 90s/2000s that are based on Motorola 56300 family DSPs
Log | Files | Refs | Submodules | README | LICENSE

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 }