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

pluginEditor.cpp (19730B)


      1 #include "pluginEditor.h"
      2 
      3 #include "pluginProcessor.h"
      4 #include "skin.h"
      5 
      6 #include "baseLib/filesystem.h"
      7 
      8 #include "jucePluginLib/clipboard.h"
      9 #include "jucePluginLib/filetype.h"
     10 #include "jucePluginLib/parameterbinding.h"
     11 #include "jucePluginLib/tools.h"
     12 
     13 #include "juceUiLib/messageBox.h"
     14 
     15 #include "synthLib/os.h"
     16 #include "synthLib/sysexToMidi.h"
     17 
     18 #include "patchmanager/patchmanager.h"
     19 #include "patchmanager/savepatchdesc.h"
     20 
     21 namespace jucePluginEditorLib
     22 {
     23 	Editor::Editor(Processor& _processor, pluginLib::ParameterBinding& _binding, Skin _skin)
     24 		: genericUI::Editor(static_cast<EditorInterface&>(*this))
     25 		, m_processor(_processor)
     26 		, m_binding(_binding)
     27 		, m_skin(std::move(_skin))
     28 		, m_overlays(*this, _binding)
     29 	{
     30 		showDisclaimer();
     31 	}
     32 
     33 	Editor::~Editor()
     34 	{
     35 		for (const auto& file : m_dragAndDropFiles)
     36 			file.deleteFile();
     37 	}
     38 
     39 	void Editor::create()
     40 	{
     41 		genericUI::Editor::create(m_skin.jsonFilename);
     42 	}
     43 
     44 	const char* Editor::findResourceByFilename(const std::string& _filename, uint32_t& _size) const
     45 	{
     46 		const auto res = m_processor.findResource(_filename);
     47 		if(!res)
     48 			return nullptr;
     49 		_size = res->second;
     50 		return res->first;
     51 	}
     52 
     53 	void Editor::loadPreset(const std::function<void(const juce::File&)>& _callback)
     54 	{
     55 		const auto path = m_processor.getConfig().getValue("load_path", "");
     56 
     57 		m_fileChooser = std::make_unique<juce::FileChooser>(
     58 			"Choose syx/midi banks to import",
     59 			path.isEmpty() ? juce::File::getSpecialLocation(juce::File::currentApplicationFile).getParentDirectory() : path,
     60 			"*.syx,*.mid,*.midi,*.vstpreset,*.fxb,*.cpr", true);
     61 
     62 		constexpr auto flags = juce::FileBrowserComponent::openMode | juce::FileBrowserComponent::FileChooserFlags::canSelectFiles;
     63 
     64 		const std::function onFileChosen = [this, _callback](const juce::FileChooser& _chooser)
     65 		{
     66 			if (_chooser.getResults().isEmpty())
     67 				return;
     68 
     69 			const auto result = _chooser.getResult();
     70 
     71 			m_processor.getConfig().setValue("load_path", result.getParentDirectory().getFullPathName());
     72 
     73 			_callback(result);
     74 		};
     75 		m_fileChooser->launchAsync(flags, onFileChosen);
     76 	}
     77 
     78 	void Editor::savePreset(const pluginLib::FileType& _fileType, const std::function<void(const juce::File&)>& _callback)
     79 	{
     80 #if !SYNTHLIB_DEMO_MODE
     81 		const auto path = m_processor.getConfig().getValue("save_path", "");
     82 
     83 		m_fileChooser = std::make_unique<juce::FileChooser>(
     84 			"Save preset(s) as " + _fileType.type(),
     85 			path.isEmpty() ? juce::File::getSpecialLocation(juce::File::currentApplicationFile).getParentDirectory() : path,
     86 			"*." + _fileType.type(), true);
     87 
     88 		constexpr auto flags = juce::FileBrowserComponent::saveMode | juce::FileBrowserComponent::FileChooserFlags::canSelectFiles;
     89 
     90 		auto onFileChosen = [this, _callback](const juce::FileChooser& _chooser)
     91 		{
     92 			if (_chooser.getResults().isEmpty())
     93 				return;
     94 
     95 			const auto result = _chooser.getResult();
     96 			m_processor.getConfig().setValue("save_path", result.getParentDirectory().getFullPathName());
     97 
     98 			if (!result.existsAsFile())
     99 			{
    100 				_callback(result);
    101 			}
    102 			else
    103 			{
    104 				genericUI::MessageBox::showYesNo(juce::MessageBoxIconType::WarningIcon, "File exists", "Do you want to overwrite the existing file?", 
    105 					[this, _callback, result](const genericUI::MessageBox::Result _result)
    106 				{
    107 					if (_result == genericUI::MessageBox::Result::Yes)
    108 						_callback(result);
    109 				});
    110 			}
    111 		};
    112 		m_fileChooser->launchAsync(flags, onFileChosen);
    113 #else
    114 		showDemoRestrictionMessageBox();
    115 #endif
    116 	}
    117 
    118 #if !SYNTHLIB_DEMO_MODE
    119 	bool Editor::savePresets(const pluginLib::FileType& _type, const std::string& _pathName, const std::vector<std::vector<uint8_t>>& _presets)
    120 	{
    121 		if (_presets.empty())
    122 			return false;
    123 
    124 		if (_type == pluginLib::FileType::Mid)
    125 			return synthLib::SysexToMidi::write(_pathName.c_str(), _presets);
    126 
    127 		FILE* hFile = fopen(_pathName.c_str(), "wb");
    128 
    129 		if (!hFile)
    130 			return false;
    131 
    132 		for (const auto& message : _presets)
    133 		{
    134 			const auto written = fwrite(&message.front(), 1, message.size(), hFile);
    135 
    136 			if (written != message.size())
    137 			{
    138 				fclose(hFile);
    139 				return false;
    140 			}
    141 		}
    142 		fclose(hFile);
    143 		return true;
    144 	}
    145 #endif
    146 
    147 	std::string Editor::createValidFilename(pluginLib::FileType& _type, const juce::File& _file)
    148 	{
    149 		const auto ext = _file.getFileExtension();
    150 		auto file = _file.getFullPathName().toStdString();
    151 
    152 		if (ext.endsWithIgnoreCase(_type.type()))
    153 			return file;
    154 		
    155 		if (ext.endsWithIgnoreCase("mid"))
    156 			_type = pluginLib::FileType::Mid;
    157 		else if (ext.endsWithIgnoreCase("syx"))
    158 			_type = pluginLib::FileType::Syx;
    159 		else
    160 			file += "." + _type.type();
    161 		return file;
    162 	}
    163 
    164 	void Editor::showDemoRestrictionMessageBox() const
    165 	{
    166 		const auto &[title, msg] = getDemoRestrictionText();
    167 		genericUI::MessageBox::showOk(juce::AlertWindow::WarningIcon, title, msg);
    168 	}
    169 
    170 	void Editor::setPatchManager(patchManager::PatchManager* _patchManager)
    171 	{
    172 		m_patchManager.reset(_patchManager);
    173 
    174 		if(_patchManager && !m_patchManagerConfig.empty())
    175 			m_patchManager->setPerInstanceConfig(m_patchManagerConfig);
    176 	}
    177 
    178 	void Editor::setPerInstanceConfig(const std::vector<uint8_t>& _data)
    179 	{
    180 		{
    181 			// test if its an old version that didn't use chunks yet
    182 			pluginLib::PluginStream oldStream(_data);
    183 			const auto version = oldStream.read<uint32_t>();
    184 
    185 			if(version == 1)
    186 			{
    187 				m_patchManagerConfig = _data;
    188 				if(m_patchManager)
    189 					m_patchManager->setPerInstanceConfig(_data);
    190 				return;
    191 			}
    192 		}
    193 
    194 		baseLib::BinaryStream s(_data);
    195 		baseLib::ChunkReader cr(s);
    196 		loadChunkData(cr);
    197 		cr.read();
    198 	}
    199 
    200 	void Editor::loadChunkData(baseLib::ChunkReader& _cr)
    201 	{
    202 		_cr.add("pmDt", 2, [this](baseLib::BinaryStream& _s, uint32_t/* _version*/)
    203 		{
    204 			m_patchManagerConfig.clear();
    205 			_s.read(m_patchManagerConfig);
    206 			if(m_patchManager)
    207 				m_patchManager->setPerInstanceConfig(m_patchManagerConfig);
    208 		});
    209 	}
    210 
    211 	void Editor::getPerInstanceConfig(std::vector<uint8_t>& _data)
    212 	{
    213 		baseLib::BinaryStream s;
    214 		saveChunkData(s);
    215 		s.toVector(_data);
    216 	}
    217 
    218 	void Editor::saveChunkData(baseLib::BinaryStream& _s)
    219 	{
    220 		if(m_patchManager)
    221 		{
    222 			m_patchManagerConfig.clear();
    223 			m_patchManager->getPerInstanceConfig(m_patchManagerConfig);
    224 		}
    225 		baseLib::ChunkWriter cw(_s, "pmDt", 2);
    226 		_s.write(m_patchManagerConfig);
    227 	}
    228 
    229 	void Editor::setCurrentPart(const uint8_t _part)
    230 	{
    231 		genericUI::Editor::setCurrentPart(_part);
    232 
    233 		if(m_patchManager)
    234 			m_patchManager->setCurrentPart(_part);
    235 	}
    236 
    237 	void Editor::showDisclaimer() const
    238 	{
    239 		if(pluginLib::Tools::isHeadless())
    240 			return;
    241 
    242 		if(!m_processor.getConfig().getBoolValue("disclaimerSeen", false))
    243 		{
    244 			const juce::MessageBoxOptions options = juce::MessageBoxOptions::makeOptionsOk(juce::MessageBoxIconType::WarningIcon, m_processor.getProperties().name,
    245 	           "It is the sole responsibility of the user to operate this emulator within the bounds of all applicable laws.\n\n"
    246 
    247 				"Usage of emulators in conjunction with ROM images you are not legally entitled to own is forbidden by copyright law.\n\n"
    248 
    249 				"If you are not legally entitled to use this emulator please discontinue usage immediately.\n\n", 
    250 
    251 				"I Agree"
    252 			);
    253 
    254 			juce::NativeMessageBox::showAsync(options, [this](int)
    255 			{
    256 				m_processor.getConfig().setValue("disclaimerSeen", true);
    257 				onDisclaimerFinished();
    258 			});
    259 		}
    260 		else
    261 		{
    262 			onDisclaimerFinished();
    263 		}
    264 	}
    265 
    266 	bool Editor::shouldDropFilesWhenDraggedExternally(const juce::DragAndDropTarget::SourceDetails& sourceDetails, juce::StringArray& files, bool& canMoveFiles)
    267 	{
    268 		const auto* ddObject = DragAndDropObject::fromDragSource(sourceDetails);
    269 
    270 		if(!ddObject || !ddObject->canDropExternally())
    271 			return false;
    272 
    273 		// try to create human-readable filename first
    274 		const auto patchFileName = ddObject->getExportFileName(m_processor);
    275 		const auto pathName = juce::File::getSpecialLocation(juce::File::tempDirectory).getFullPathName().toStdString() + "/" + patchFileName;
    276 
    277 		auto file = juce::File(pathName);
    278 
    279 		if(file.hasWriteAccess())
    280 		{
    281 			m_dragAndDropFiles.emplace_back(file);
    282 		}
    283 		else
    284 		{
    285 			// failed, create temp file
    286 			const auto& tempFile = m_dragAndDropTempFiles.emplace_back(std::make_shared<juce::TemporaryFile>(patchFileName));
    287 			file = tempFile->getFile();
    288 		}
    289 
    290 		if(!ddObject->writeToFile(file))
    291 			return false;
    292 
    293 		files.add(file.getFullPathName());
    294 
    295 		canMoveFiles = true;
    296 		return true;
    297 	}
    298 
    299 	void Editor::copyCurrentPatchToClipboard() const
    300 	{
    301 		// copy patch of current part to Clipboard
    302 		if(!m_patchManager)
    303 			return;
    304 
    305 		const auto p = m_patchManager->requestPatchForPart(m_patchManager->getCurrentPart());
    306 
    307 		if(!p)
    308 			return;
    309 
    310 		const auto patchAsString = m_patchManager->toString(p, pluginLib::FileType::Empty, pluginLib::ExportType::Clipboard);
    311 
    312 		if(!patchAsString.empty())
    313 			juce::SystemClipboard::copyTextToClipboard(patchAsString);
    314 	}
    315 
    316 	bool Editor::replaceCurrentPatchFromClipboard() const
    317 	{
    318 		if(!m_patchManager)
    319 			return false;
    320 		return m_patchManager->activatePatchFromClipboard();
    321 	}
    322 
    323 	void Editor::openMenu(juce::MouseEvent* _event)
    324 	{
    325 		onOpenMenu(this, _event);
    326 	}
    327 
    328 	bool Editor::openContextMenuForParameter(const juce::MouseEvent* _event)
    329 	{
    330 		if(!_event || !_event->originalComponent)
    331 			return false;
    332 
    333 		const auto* param = m_binding.getBoundParameter(_event->originalComponent);
    334 		if(!param)
    335 			return false;
    336 
    337 		auto& controller = m_processor.getController();
    338 
    339 		const auto& regions = controller.getParameterDescriptions().getRegions();
    340 		const auto paramRegionIds = controller.getRegionIdsForParameter(param);
    341 
    342 		if(paramRegionIds.empty())
    343 			return false;
    344 
    345 		const auto part = param->getPart();
    346 
    347 		juce::PopupMenu menu;
    348 
    349 		// Lock / Unlock
    350 
    351 		for (const auto& regionId : paramRegionIds)
    352 		{
    353 			const auto& regionName = regions.find(regionId)->second.getName();
    354 
    355 			const auto isLocked = controller.getParameterLocking().isRegionLocked(part, regionId);
    356 
    357 			menu.addItem(std::string(isLocked ? "Unlock" : "Lock") + std::string(" region '") + regionName + "'", [this, regionId, isLocked, part]
    358 			{
    359 				auto& locking = m_processor.getController().getParameterLocking();
    360 				if(isLocked)
    361 					locking.unlockRegion(part, regionId);
    362 				else
    363 					locking.lockRegion(part, regionId);
    364 			});
    365 		}
    366 
    367 		// Copy to clipboard
    368 
    369 		menu.addSeparator();
    370 
    371 		for (const auto& regionId : paramRegionIds)
    372 		{
    373 			const auto& regionName = regions.find(regionId)->second.getName();
    374 
    375 			menu.addItem(std::string("Copy region '") + regionName + "'", [this, regionId]
    376 			{
    377 				copyRegionToClipboard(regionId);
    378 			});
    379 		}
    380 
    381 		// Paste from clipboard
    382 
    383 		const auto data = pluginLib::Clipboard::getDataFromString(m_processor, juce::SystemClipboard::getTextFromClipboard().toStdString());
    384 
    385 		if(!data.parameterValuesByRegion.empty())
    386 		{
    387 			bool haveSeparator = false;
    388 
    389 			for (const auto& paramRegionId : paramRegionIds)
    390 			{
    391 				const auto it = data.parameterValuesByRegion.find(paramRegionId);
    392 
    393 				if(it == data.parameterValuesByRegion.end())
    394 					continue;
    395 
    396 				// if region is not fully covered, skip it
    397 				const auto& region = regions.find(it->first)->second;
    398 				if(it->second.size() < region.getParams().size())
    399 					continue;
    400 
    401 				const auto& parameterValues = it->second;
    402 
    403 				if(!haveSeparator)
    404 				{
    405 					menu.addSeparator();
    406 					haveSeparator = true;
    407 				}
    408 
    409 				const auto& regionName = regions.find(paramRegionId)->second.getName();
    410 
    411 				menu.addItem("Paste region '" + regionName + "'", [this, parameterValues]
    412 				{
    413 					setParameters(parameterValues);
    414 				});
    415 			}
    416 
    417 			menu.addSeparator();
    418 
    419 			const auto& desc = param->getDescription();
    420 			const auto& paramName = desc.name;
    421 
    422 			const auto itParam = data.parameterValues.find(paramName);
    423 
    424 			if(itParam != data.parameterValues.end())
    425 			{
    426 				const auto& paramValue = itParam->second;
    427 
    428 				const auto& valueText = desc.valueList.valueToText(paramValue);
    429 
    430 				menu.addItem("Paste value '" + valueText + "' for parameter '" + desc.displayName + "'", [this, paramName, paramValue]
    431 				{
    432 					pluginLib::Clipboard::Data::ParameterValues params;
    433 					params.insert({paramName, paramValue});
    434 					setParameters(params);
    435 				});
    436 			}
    437 		}
    438 
    439 		// Parameter links
    440 
    441 		juce::PopupMenu linkMenu;
    442 
    443 		menu.addSeparator();
    444 
    445 		for (const auto& regionId : paramRegionIds)
    446 		{
    447 			juce::PopupMenu regionMenu;
    448 
    449 			const auto currentPart = controller.getCurrentPart();
    450 
    451 			for(uint8_t p=0; p<controller.getPartCount(); ++p)
    452 			{
    453 				if(p == currentPart)
    454 					continue;
    455 
    456 				const auto isLinked = controller.getParameterLinks().isRegionLinked(regionId, currentPart, p);
    457 
    458 				regionMenu.addItem(std::string("Link Part ") + std::to_string(p+1), true, isLinked, [this, regionId, isLinked, currentPart, p]
    459 				{
    460 					auto& links = m_processor.getController().getParameterLinks();
    461 
    462 					if(isLinked)
    463 						links.unlinkRegion(regionId, currentPart, p);
    464 					else
    465 						links.linkRegion(regionId, currentPart, p, true);
    466 				});
    467 			}
    468 
    469 			const auto& regionName = regions.find(regionId)->second.getName();
    470 			linkMenu.addSubMenu("Region '" + regionName + "'", regionMenu);
    471 		}
    472 
    473 		menu.addSubMenu("Parameter Links", linkMenu);
    474 
    475 		auto& midiPackets = m_processor.getController().getParameterDescriptions().getMidiPackets();
    476 		for (const auto& mp : midiPackets)
    477 		{
    478 			auto defIndices = mp.second.getDefinitionIndicesForParameterName(param->getDescription().name);
    479 			if (defIndices.empty())
    480 				continue;
    481 
    482 			const auto expectedValue = param->getUnnormalizedValue();
    483 
    484 			menu.addSeparator();
    485 
    486 			auto findSimilar = [this, defIndices, packet = &mp.second, expectedValue](const int _offsetMin, const int _offsetMax)
    487 			{
    488 				pluginLib::patchDB::SearchRequest sr;
    489 
    490 				sr.customCompareFunc = [packet, expectedValue, defIndices, _offsetMin, _offsetMax](const pluginLib::patchDB::Patch& _patch) -> bool
    491 				{
    492 					if (_patch.sysex.empty())
    493 						return false;
    494 					const auto v = packet->getParameterValue(_patch.sysex, defIndices);
    495 					if (v >= expectedValue + _offsetMin && v <= expectedValue + _offsetMax)
    496 						return true;
    497 					return false;
    498 				};
    499 
    500 				const auto sh = getPatchManager()->search(std::move(sr));
    501 
    502 				if (sh != pluginLib::patchDB::g_invalidSearchHandle)
    503 				{
    504 					getPatchManager()->setCustomSearch(sh);
    505 					getPatchManager()->bringToFront();
    506 				}
    507 			};
    508 
    509 			juce::PopupMenu subMenu;
    510 			subMenu.addItem("Exact Match (Value " + param->getCurrentValueAsText() + ")", [this, findSimilar]{ findSimilar(0, 0); });
    511 			subMenu.addItem("-/+ 4", [this, findSimilar]{ findSimilar(-4, 4); });
    512 			subMenu.addItem("-/+ 12", [this, findSimilar]{ findSimilar(-12, 12); });
    513 			subMenu.addItem("-/+ 24", [this, findSimilar]{ findSimilar(-24, 24); });
    514 
    515 			menu.addSubMenu("Find similar Patches for parameter " + param->getDescription().displayName, subMenu);
    516 
    517 			break;
    518 		}
    519 		menu.showMenuAsync({});
    520 
    521 		return true;
    522 	}
    523 
    524 	bool Editor::copyRegionToClipboard(const std::string& _regionId) const
    525 	{
    526 		const auto& regions = m_processor.getController().getParameterDescriptions().getRegions();
    527 		const auto it = regions.find(_regionId);
    528 		if(it == regions.end())
    529 			return false;
    530 
    531 		const auto& region = it->second;
    532 
    533 		const auto& params = region.getParams();
    534 
    535 		std::vector<std::string> paramsList;
    536 		paramsList.reserve(params.size());
    537 
    538 		for (const auto& p : params)
    539 			paramsList.push_back(p.first);
    540 
    541 		return copyParametersToClipboard(paramsList, _regionId);
    542 	}
    543 
    544 	bool Editor::copyParametersToClipboard(const std::vector<std::string>& _params, const std::string& _regionId) const
    545 	{
    546 		const auto result = pluginLib::Clipboard::parametersToString(m_processor, _params, _regionId);
    547 
    548 		if(result.empty())
    549 			return false;
    550 
    551 		juce::SystemClipboard::copyTextToClipboard(result);
    552 
    553 		return true;
    554 	}
    555 
    556 	bool Editor::setParameters(const std::map<std::string, pluginLib::ParamValue>& _paramValues) const
    557 	{
    558 		if(_paramValues.empty())
    559 			return false;
    560 
    561 		return getProcessor().getController().setParameters(_paramValues, m_processor.getController().getCurrentPart(), pluginLib::Parameter::Origin::Ui);
    562 	}
    563 
    564 	void Editor::parentHierarchyChanged()
    565 	{
    566 		genericUI::Editor::parentHierarchyChanged();
    567 
    568 		if(isShowing())
    569 			m_overlays.refreshAll();
    570 	}
    571 
    572 	juce::PopupMenu Editor::createExportFileTypeMenu(const std::function<void(pluginLib::FileType)>& _func) const
    573 	{
    574 		juce::PopupMenu menu;
    575 		createExportFileTypeMenu(menu, _func);
    576 		return menu;
    577 	}
    578 
    579 	void Editor::createExportFileTypeMenu(juce::PopupMenu& _menu, const std::function<void(pluginLib::FileType)>& _func) const
    580 	{
    581 		_menu.addItem(".syx", [this, _func]{_func(pluginLib::FileType::Syx);});
    582 		_menu.addItem(".mid", [this, _func]{_func(pluginLib::FileType::Mid);});
    583 	}
    584 
    585 	bool Editor::keyPressed(const juce::KeyPress& _key)
    586 	{
    587 		if(_key.getModifiers().isCommandDown())
    588 		{
    589 			switch(_key.getKeyCode())
    590 			{
    591 			case 'c':
    592 			case 'C':
    593 				copyCurrentPatchToClipboard();
    594 				return true;
    595 			case 'v':
    596 			case 'V':
    597 				if(replaceCurrentPatchFromClipboard())
    598 					return true;
    599 				break;
    600 			default:
    601 				return genericUI::Editor::keyPressed(_key);
    602 			}
    603 		}
    604 		return genericUI::Editor::keyPressed(_key);
    605 	}
    606 
    607 	void Editor::onDisclaimerFinished() const
    608 	{
    609 		if(!synthLib::isRunningUnderRosetta())
    610 			return;
    611 
    612 		const auto& name = m_processor.getProperties().name;
    613 
    614 		genericUI::MessageBox::showOk(juce::MessageBoxIconType::WarningIcon,
    615 			name + " - Rosetta detected", 
    616 			name + " appears to be running in Rosetta mode.\n"
    617 			"\n"
    618 			"The DSP emulation core will perform much worse when being executed under Rosetta. We strongly recommend to run your DAW as a native Apple Silicon application");
    619 	}
    620 
    621 	const char* Editor::getResourceByFilename(const std::string& _name, uint32_t& _dataSize)
    622 	{
    623 		if(!m_skin.folder.empty())
    624 		{
    625 			auto readFromCache = [this, &_name, &_dataSize]()
    626 			{
    627 				const auto it = m_fileCache.find(_name);
    628 				if(it == m_fileCache.end())
    629 				{
    630 					_dataSize = 0;
    631 					return static_cast<char*>(nullptr);
    632 				}
    633 				_dataSize = static_cast<uint32_t>(it->second.size());
    634 				return &it->second.front();
    635 			};
    636 
    637 			const auto* res = readFromCache();
    638 
    639 			if(res)
    640 				return res;
    641 
    642 			const auto modulePath = synthLib::getModulePath();
    643 			const auto publicDataPath = m_processor.getDataFolder();
    644 			const auto folder = baseLib::filesystem::validatePath(m_skin.folder.find(modulePath) == 0 || m_skin.folder.find(publicDataPath) == 0 ? m_skin.folder : modulePath + m_skin.folder);
    645 
    646 			// try to load from disk first
    647 			FILE* hFile = fopen((folder + _name).c_str(), "rb");
    648 			if(hFile)
    649 			{
    650 				fseek(hFile, 0, SEEK_END);
    651 				_dataSize = ftell(hFile);
    652 				fseek(hFile, 0, SEEK_SET);
    653 
    654 				std::vector<char> data;
    655 				data.resize(_dataSize);
    656 				const auto readCount = fread(&data.front(), 1, _dataSize, hFile);
    657 				fclose(hFile);
    658 
    659 				if(readCount == _dataSize)
    660 					m_fileCache.insert(std::make_pair(_name, std::move(data)));
    661 
    662 				res = readFromCache();
    663 
    664 				if(res)
    665 					return res;
    666 			}
    667 		}
    668 
    669 		uint32_t size = 0;
    670 		const auto res = findResourceByFilename(_name, size);
    671 		if(!res)
    672 			throw std::runtime_error("Failed to find file named " + _name);
    673 		_dataSize = size;
    674 		return res;
    675 	}
    676 
    677 	int Editor::getParameterIndexByName(const std::string& _name)
    678 	{
    679 		return static_cast<int>(m_processor.getController().getParameterIndexByName(_name));
    680 	}
    681 
    682 	bool Editor::bindParameter(juce::Button& _target, int _parameterIndex)
    683 	{
    684 		m_binding.bind(_target, _parameterIndex);
    685 		return true;
    686 	}
    687 
    688 	bool Editor::bindParameter(juce::ComboBox& _target, int _parameterIndex)
    689 	{
    690 		m_binding.bind(_target, _parameterIndex);
    691 		return true;
    692 	}
    693 
    694 	bool Editor::bindParameter(juce::Slider& _target, int _parameterIndex)
    695 	{
    696 		m_binding.bind(_target, _parameterIndex);
    697 		return true;
    698 	}
    699 
    700 	bool Editor::bindParameter(juce::Label& _target, int _parameterIndex)
    701 	{
    702 		m_binding.bind(_target, _parameterIndex);
    703 		return true;
    704 	}
    705 
    706 	juce::Value* Editor::getParameterValue(int _parameterIndex, uint8_t _part)
    707 	{
    708 		return m_processor.getController().getParamValueObject(_parameterIndex, _part);
    709 	}
    710 }