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

VirusController.cpp (27618B)


      1 #include "VirusController.h"
      2 
      3 #include <fstream>
      4 
      5 #include "ParameterNames.h"
      6 #include "VirusProcessor.h"
      7 
      8 #include "virusLib/microcontrollerTypes.h"
      9 
     10 #include "VirusEditor.h"
     11 
     12 using MessageType = virusLib::SysexMessageType;
     13 
     14 namespace virus
     15 {
     16     constexpr const char* g_midiPacketNames[] =
     17     {
     18 	    "requestsingle",
     19 	    "requestmulti",
     20 	    "requestsinglebank",
     21 	    "requestmultibank",
     22 	    "requestarrangement",
     23 	    "requestglobal",
     24 	    "requesttotal",
     25 	    "requestcontrollerdump",
     26 	    "parameterchange",
     27 	    "singledump",
     28 	    "multidump",
     29 	    "singledump_C",
     30     };
     31 
     32     static_assert(std::size(g_midiPacketNames) == static_cast<size_t>(Controller::MidiPacketType::Count));
     33 
     34     const char* midiPacketName(Controller::MidiPacketType _type)
     35     {
     36 	    return g_midiPacketNames[static_cast<uint32_t>(_type)];
     37     }
     38 
     39     Controller::Controller(VirusProcessor& p, const virusLib::DeviceModel _defaultModel, unsigned char deviceId)
     40 		: pluginLib::Controller(p, virusLib::isTIFamily(_defaultModel) ? "parameterDescriptions_TI.json" : "parameterDescriptions_C.json")
     41 		, m_processor(p)
     42 		, m_defaultModel(_defaultModel)
     43 		, m_deviceId(deviceId)
     44 		, m_onRomChanged(p.evRomChanged)
     45     {
     46      	registerParams(p);
     47 
     48 		// add lambda to enforce updating patches when virus switches from/to multi/single.
     49         const auto paramIdx = getParameterIndexByName(g_paramPlayMode);
     50 		auto* parameter = getParameter(paramIdx);
     51         if(parameter)
     52 		{
     53 			parameter->onValueChanged.addListener([this](pluginLib::Parameter*)
     54 			{
     55 				const uint8_t prg = isMultiMode() ? 0x0 : virusLib::SINGLE;
     56 				requestSingle(0, prg);
     57                 requestMulti(0, prg);
     58 
     59 				if (onMsgDone)
     60 				{
     61 					onMsgDone();
     62 				}
     63 			});
     64 		}
     65 
     66 		requestTotal();
     67 		requestArrangement();
     68 
     69 		// ABC models have different factory presets depending on the used ROM, but for the TI we have all presets from all models loaded anyway so no need to replace them at runtime
     70 		if(isTIFamily(m_processor.getModel()))
     71 		{
     72 			requestRomBanks();
     73 		}
     74 		else
     75 		{
     76 			m_onRomChanged = [this](const virusLib::ROMFile*)
     77 			{
     78 				requestRomBanks();
     79 			};
     80 		}
     81 	}
     82 
     83     Controller::~Controller() = default;
     84 
     85     bool Controller::parseSysexMessage(const pluginLib::SysEx& _msg, synthLib::MidiEventSource _source)
     86 	{
     87         std::string name;
     88     	pluginLib::MidiPacket::Data data;
     89         pluginLib::MidiPacket::ParamValues parameterValues;
     90 
     91 		if(_msg.size() > 6 && _msg[6] == virusLib::DUMP_EMU_SYNTHSTATE)
     92 		{
     93 			if(!m_frontpanelState.fromMidiEvent(_msg))
     94 				return false;
     95 			onFrontPanelStateChanged(m_frontpanelState);
     96 			return true;
     97 		}
     98 
     99         if(parseMidiPacket(name,  data, parameterValues, _msg))
    100         {
    101             const auto deviceId = data[pluginLib::MidiDataType::DeviceId];
    102 
    103             if(deviceId != m_deviceId && deviceId != virusLib::OMNI_DEVICE_ID)
    104                 return false; // not intended to this device!
    105 
    106             if(name == midiPacketName(MidiPacketType::SingleDump) || name == midiPacketName(MidiPacketType::SingleDump_C))
    107                 parseSingle(_msg, data, parameterValues);
    108             else if(name == midiPacketName(MidiPacketType::MultiDump))
    109                 parseMulti(_msg, data, parameterValues);
    110             else if(name == midiPacketName(MidiPacketType::ParameterChange))
    111             {
    112 				// TI DSP sends parameter changes back as sysex, unsure why. Ignore them as it stops
    113 				// host automation because the host thinks we "edit" the parameter
    114 				if(_source != synthLib::MidiEventSource::Plugin)
    115 	                parseParamChange(data);
    116             }
    117             else
    118             {
    119 		        LOG("Controller: Begin unhandled SysEx! --");
    120 		        printMessage(_msg);
    121 		        LOG("Controller: End unhandled SysEx! --");
    122 				return false;
    123             }
    124 			return true;
    125         }
    126 
    127         LOG("Controller: Begin unknown SysEx! --");
    128         printMessage(_msg);
    129         LOG("Controller: End unknown SysEx! --");
    130 		return false;
    131     }
    132 
    133     bool Controller::parseControllerMessage(const synthLib::SMidiEvent& e)
    134     {
    135 		return parseControllerDump(e);
    136     }
    137 
    138     void Controller::parseParamChange(const pluginLib::MidiPacket::Data& _data)
    139     {
    140     	const auto page  = _data.find(pluginLib::MidiDataType::Page)->second;
    141 		const auto part  = _data.find(pluginLib::MidiDataType::Part)->second;
    142 		const auto index = _data.find(pluginLib::MidiDataType::ParameterIndex)->second;
    143 		const auto value = _data.find(pluginLib::MidiDataType::ParameterValue)->second;
    144 
    145         const auto& partParams = findSynthParam(part == virusLib::SINGLE ? 0 : part, page, index);
    146 
    147     	if (partParams.empty() && part != 0 && part != virusLib::SINGLE)
    148 		{
    149             // ensure it's not global
    150 			const auto& globalParams = findSynthParam(0, page, index);
    151 			if (globalParams.empty())
    152 			{
    153                 jassertfalse;
    154                 return;
    155             }
    156             for (const auto& param : globalParams)
    157             {
    158 				if (!param->getDescription().isNonPartSensitive())
    159 				{
    160 					jassertfalse;
    161 					return;
    162 				}
    163             }
    164 			for (const auto& param : globalParams)
    165 				param->setValueFromSynth(value, pluginLib::Parameter::Origin::Midi);
    166 		}
    167 		for (const auto& param : partParams)
    168 			param->setValueFromSynth(value, pluginLib::Parameter::Origin::Midi);
    169 		// TODO:
    170         /**
    171          If a
    172         global  parameter  or  a  Multi  parameter  is  ac-
    173         cessed,  which  is  not  part-sensitive  (e.g.  Input
    174         Boost  or  Multi  Delay  Time),  the  part  number  is
    175         ignored
    176          */
    177     }
    178 	
    179     juce::StringArray Controller::getSinglePresetNames(virusLib::BankNumber _bank) const
    180     {
    181 		if (_bank == virusLib::BankNumber::EditBuffer)
    182 		{
    183 			jassertfalse;
    184 			return {};
    185 		}
    186 
    187 		const auto bank = virusLib::toArrayIndex(_bank);
    188 
    189         if (bank >= m_singles.size() || bank < 0)
    190         {
    191             jassertfalse;
    192             return {};
    193         }
    194 
    195         juce::StringArray bankNames;
    196         for (auto i = 0; i < 128; i++)
    197             bankNames.add(m_singles[bank][i].name);
    198         return bankNames;
    199     }
    200 
    201     std::string Controller::getSinglePresetName(const pluginLib::MidiPacket::ParamValues& _values) const
    202     {
    203         return getPresetName("SingleName", _values);
    204     }
    205 
    206     std::string Controller::getSinglePresetName(const pluginLib::MidiPacket::AnyPartParamValues& _values) const
    207     {
    208         return getPresetName("SingleName", _values);
    209     }
    210 
    211     std::string Controller::getMultiPresetName(const pluginLib::MidiPacket::ParamValues& _values) const
    212     {
    213         return getPresetName("MultiName", _values);
    214     }
    215 
    216     std::string Controller::getPresetName(const std::string& _paramNamePrefix, const pluginLib::MidiPacket::ParamValues& _values) const
    217     {
    218         std::string name;
    219         for(uint32_t i=0; i<kNameLength; ++i)
    220         {
    221 	        const std::string paramName = _paramNamePrefix + std::to_string(i);
    222             const auto idx = getParameterIndexByName(paramName);
    223             if(idx == InvalidParameterIndex)
    224                 break;
    225 
    226             const auto it = _values.find(std::make_pair(pluginLib::MidiPacket::AnyPart, idx));
    227             if(it == _values.end())
    228                 break;
    229 
    230             name += static_cast<char>(it->second);
    231         }
    232         return name;
    233     }
    234 
    235     std::string Controller::getPresetName(const std::string& _paramNamePrefix, const pluginLib::MidiPacket::AnyPartParamValues& _values) const
    236     {
    237         std::string name;
    238         for(uint32_t i=0; i<kNameLength; ++i)
    239         {
    240 	        const std::string paramName = _paramNamePrefix + std::to_string(i);
    241             const auto idx = getParameterIndexByName(paramName);
    242             if(idx == InvalidParameterIndex)
    243                 break;
    244 
    245             const auto it = _values[idx];
    246             if(!it)
    247                 break;
    248 
    249             name += static_cast<char>(*it);
    250         }
    251         return name;
    252     }
    253 
    254     void Controller::setSinglePresetName(const uint8_t _part, const juce::String& _name) const
    255     {
    256 		for (int i=0; i<kNameLength; i++)
    257 		{
    258 	        const std::string paramName = "SingleName" + std::to_string(i);
    259             const auto idx = getParameterIndexByName(paramName);
    260             if(idx == InvalidParameterIndex)
    261                 break;
    262 
    263             auto* param = getParameter(idx, _part);
    264             if(!param)
    265                 break;
    266             auto& v = param->getValueObject();
    267             if(i >= _name.length())
    268 				v.setValue(static_cast<uint8_t>(' '));
    269             else
    270 				v.setValue(static_cast<uint8_t>(_name[i]));
    271 		}
    272 	}
    273 
    274     void Controller::setSinglePresetName(pluginLib::MidiPacket::AnyPartParamValues& _values, const std::string& _name) const
    275     {
    276         for(uint32_t i=0; i<kNameLength; ++i)
    277         {
    278 	        const std::string paramName = "SingleName" + std::to_string(i);
    279             const auto idx = getParameterIndexByName(paramName);
    280             if(idx == InvalidParameterIndex)
    281                 break;
    282 
    283             _values[idx] = (i < _name.size()) ? _name[i] : ' ';
    284         }
    285     }
    286 
    287     bool Controller::isMultiMode() const
    288 	{
    289 		return getParameter(g_paramPlayMode, 0)->getUnnormalizedValue();
    290 	}
    291 
    292 	juce::String Controller::getCurrentPartPresetName(const uint8_t _part) const
    293 	{
    294         std::string name;
    295 		for (int i=0; i<kNameLength; i++)
    296 		{
    297 	        const std::string paramName = "SingleName" + std::to_string(i);
    298             const auto idx = getParameterIndexByName(paramName);
    299             if(idx == InvalidParameterIndex)
    300                 break;
    301 
    302             auto* param = getParameter(idx, _part);
    303             if(!param)
    304                 break;
    305             const auto v = param->getUnnormalizedValue();
    306 			name += static_cast<char>(v);
    307 		}
    308         return name;
    309 	}
    310 
    311 	void Controller::setCurrentPartPreset(uint8_t _part, const virusLib::BankNumber _bank, uint8_t _prg)
    312 	{
    313     	if(_bank == virusLib::BankNumber::EditBuffer || _prg > m_singles[0].size())
    314     	{
    315 			jassertfalse;
    316 			return;
    317     	}
    318 
    319     	const auto bank = virusLib::toArrayIndex(_bank);
    320 
    321 		if (bank >= m_singles.size())
    322 		{
    323 			jassertfalse;
    324 			return;
    325 		}
    326 
    327 		const uint8_t pt = isMultiMode() ? _part : virusLib::SINGLE;
    328 
    329 		sendParameterChange(MessageType::PARAM_CHANGE_C, pt, virusLib::PART_BANK_SELECT, virusLib::toMidiByte(_bank));
    330 		sendParameterChange(MessageType::PARAM_CHANGE_C, pt, virusLib::PART_PROGRAM_CHANGE, _prg);
    331 
    332 		requestSingle(toMidiByte(virusLib::BankNumber::EditBuffer), pt);
    333 
    334 		m_currentBank[_part] = _bank;
    335 		m_currentProgram[_part] = _prg;
    336         m_currentPresetSource[_part] = PresetSource::Rom;
    337 	}
    338 
    339 	void Controller::setCurrentPartPresetSource(uint8_t _part, PresetSource _source)
    340 	{
    341         m_currentPresetSource[_part] = _source;
    342 	}
    343 
    344 	virusLib::BankNumber Controller::getCurrentPartBank(const uint8_t _part) const
    345     {
    346 	    return m_currentBank[_part];
    347     }
    348 
    349 	uint8_t Controller::getCurrentPartProgram(const uint8_t _part) const
    350     {
    351 	    return m_currentProgram[_part];
    352     }
    353 
    354 	Controller::PresetSource Controller::getCurrentPartPresetSource(uint8_t _part) const
    355 	{
    356         return m_currentPresetSource[_part];
    357 	}
    358 
    359 	bool Controller::parseSingle(pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _parameterValues, const pluginLib::SysEx& _msg) const
    360 	{
    361         MidiPacketType unused;
    362         return parseSingle(_data, _parameterValues, _msg, unused);
    363 	}
    364 
    365 	bool Controller::parseSingle(pluginLib::MidiPacket::Data& _data, pluginLib::MidiPacket::AnyPartParamValues& _parameterValues, const pluginLib::SysEx& _msg, MidiPacketType& usedPacketType) const
    366 	{
    367         const auto packetName = midiPacketName(MidiPacketType::SingleDump);
    368 
    369     	auto* m = getMidiPacket(packetName);
    370 
    371     	if(!m)
    372             return false;
    373 
    374     	usedPacketType = MidiPacketType::SingleDump;
    375 
    376         if(_msg.size() > m->size())
    377         {
    378 	        pluginLib::SysEx temp;
    379             temp.insert(temp.begin(), _msg.begin(), _msg.begin() + (m->size()-1));
    380             temp.push_back(0xf7);
    381 	    	return parseMidiPacket(*m, _data, _parameterValues, temp);
    382         }
    383 
    384 		if(_msg.size() < m->size())
    385         {
    386 			const auto* mc = getMidiPacket(midiPacketName(MidiPacketType::SingleDump_C));
    387 	    	if(!mc)
    388 	            return false;
    389             usedPacketType = MidiPacketType::SingleDump_C;
    390 	    	return parseMidiPacket(*mc, _data, _parameterValues, _msg);
    391         }
    392 
    393     	return parseMidiPacket(*m, _data, _parameterValues, _msg);
    394     }
    395 
    396 	void Controller::parseSingle(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _parameterValues)
    397 	{
    398         SinglePatch patch;
    399 
    400         patch.bankNumber = virusLib::fromMidiByte(_data.find(pluginLib::MidiDataType::Bank)->second);
    401         patch.progNumber = _data.find(pluginLib::MidiDataType::Program)->second;
    402 
    403         patch.name = getSinglePresetName(_parameterValues);
    404 
    405         patch.data = _msg;
    406 
    407 		if (patch.bankNumber == virusLib::BankNumber::EditBuffer)
    408 		{
    409             if(patch.progNumber == virusLib::SINGLE)
    410                 m_singleEditBuffer = patch;
    411             else
    412                 m_singleEditBuffers[patch.progNumber] = patch;
    413 
    414 			// virus sends also the single buffer not matter what's the mode. (?? no, both is requested, so both is sent)
    415 			// instead of keeping both, we 'encapsulate' this into first channel.
    416 			// the logic to maintain this is done by listening the global single/multi param.
    417 			if (isMultiMode() && patch.progNumber == virusLib::SINGLE)
    418 				return;
    419 			if (!isMultiMode() && patch.progNumber == 0x0)
    420 				return;
    421 
    422 			const uint8_t ch = patch.progNumber == virusLib::SINGLE ? 0 : patch.progNumber;
    423 
    424             const auto locked = m_locking.getLockedParameterNames(ch);
    425 
    426 			// if there are locked parameters and the current values in the received preset do not match
    427 			// the values of the parameters that are locked, create a new preset that contains all
    428 			// locked parameter values and send it back to the device
    429 			std::unordered_set<const pluginLib::Parameter*> lockedParamsToApply;
    430 
    431             for (const auto& parameterValue : _parameterValues)
    432             {
    433 	            auto* p = getParameter(parameterValue.first.second, ch);
    434 
    435 				// if a parameter is not locked, apply it
    436                 if(locked.find(p->getDescription().name) == locked.end())
    437 					p->setValueFromSynth(parameterValue.second, pluginLib::Parameter::Origin::PresetChange);
    438 				// otherwise, remember the locked parameter if the locked value doesn't match the preset value
    439 				else if (parameterValue.second != p->getUnnormalizedValue())
    440 					lockedParamsToApply.insert(p);
    441             }
    442 
    443 			// if we have any locked parameters that need to be applied, create a new preset and send it to the device
    444 			if (!lockedParamsToApply.empty())
    445 			{
    446 				auto newDump = createSingleDump(patch.progNumber == virusLib::SINGLE ? 0 : patch.progNumber, virusLib::toMidiByte(virusLib::BankNumber::EditBuffer), patch.progNumber);
    447 				sendSysEx(newDump);
    448 			}
    449 
    450             getProcessor().updateHostDisplay(juce::AudioProcessorListener::ChangeDetails().withProgramChanged(true));
    451 
    452             if(m_currentPresetSource[ch] != PresetSource::Browser)
    453             {
    454 	            bool found = false;
    455 	            for(size_t b=0; b<m_singles.size() && !found; ++b)
    456 	            {
    457 		            const auto& singlePatches = m_singles[b];
    458 
    459 	                for(size_t s=0; s<singlePatches.size(); ++s)
    460 	                {
    461 	                    const auto& singlePatch = singlePatches[s];
    462 
    463 	                    if(singlePatch.name == patch.name)
    464 	                    {
    465 	                        m_currentBank[ch] = virusLib::fromArrayIndex(static_cast<uint8_t>(b));
    466 	                        m_currentProgram[ch] = static_cast<uint8_t>(s);
    467 	                        m_currentPresetSource[ch] = PresetSource::Rom;
    468 	                        found = true;
    469 	                        break;
    470 	                    }
    471 	                }
    472 	            }
    473 
    474 	            if(!found)
    475 	            {
    476 		            m_currentProgram[ch] = 0;
    477 	                m_currentBank[ch] = virusLib::BankNumber::EditBuffer;
    478 	                m_currentPresetSource[ch] = PresetSource::Unknown;
    479 	            }
    480             }
    481             else
    482             {
    483 	            m_currentProgram[ch] = 0;
    484                 m_currentBank[ch] = virusLib::BankNumber::EditBuffer;
    485             }
    486 
    487 			if (onProgramChange)
    488 				onProgramChange(patch.progNumber);
    489 		}
    490 		else
    491 		{
    492             const auto bank = toArrayIndex(patch.bankNumber);
    493             const auto program = patch.progNumber;
    494 
    495 			m_singles[bank][program] = patch;
    496 
    497             if(onRomPatchReceived)
    498 				onRomPatchReceived(patch.bankNumber, program);
    499 		}
    500 	}
    501 
    502 	void Controller::parseMulti(const pluginLib::SysEx& _msg, const pluginLib::MidiPacket::Data& _data, const pluginLib::MidiPacket::ParamValues& _parameterValues)
    503     {
    504         const auto bankNumber = _data.find(pluginLib::MidiDataType::Bank)->second;
    505 
    506 		/* If it's a multi edit buffer, set the part page C parameters to their multi equivalents */
    507 		if (bankNumber == 0)
    508         {
    509 	        m_multiEditBuffer.progNumber = _data.find(pluginLib::MidiDataType::Program)->second;
    510 	        m_multiEditBuffer.name = getMultiPresetName(_parameterValues);
    511 	        m_multiEditBuffer.data = _msg;
    512 
    513 			for (const auto & paramValue : _parameterValues)
    514 			{
    515                 const auto part = paramValue.first.first;
    516                 const auto index = paramValue.first.second;
    517                 const auto value = paramValue.second;
    518 
    519                 auto* param = getParameter(index, part);
    520                 if(!param)
    521                     continue;
    522 
    523                 const auto& desc = param->getDescription();
    524 
    525                 if(desc.page != virusLib::PAGE_C)
    526                     continue;
    527 
    528                 param->setValueFromSynth(value, pluginLib::Parameter::Origin::PresetChange);
    529 			}
    530 
    531 			getProcessor().updateHostDisplay(juce::AudioProcessorListener::ChangeDetails().withProgramChanged(true));
    532 
    533 			if(onMultiReceived)
    534 				onMultiReceived();
    535 		}
    536     }
    537 
    538 	bool Controller::parseControllerDump(const synthLib::SMidiEvent& m) const
    539 	{
    540 		const uint8_t status = m.a & 0xf0;
    541     	const uint8_t part = m.a & 0x0f;
    542 
    543 		uint8_t page;
    544 
    545 		if (status == synthLib::M_CONTROLCHANGE)
    546 		{
    547 			page = virusLib::PAGE_A;
    548 		}
    549 		else if (status == synthLib::M_POLYPRESSURE)
    550 		{
    551 			// device decides if PP is enabled and will echo any parameter change to us. Reject any other source
    552 			if(m.source != synthLib::MidiEventSource::Plugin)
    553 				return false;
    554 			page = virusLib::PAGE_B;
    555 		}
    556 		else if(status == synthLib::M_PROGRAMCHANGE)
    557 		{
    558 			if(isMultiMode())
    559 			{
    560 				for(uint8_t p=0; p<getPartCount(); ++p)
    561 				{
    562 					const auto idx = getParameterIndexByName("Part Midi Channel");
    563 					if(idx == pluginLib::Controller::InvalidParameterIndex)
    564 						continue;
    565 
    566 					const auto v = getParameter(idx, p);
    567 					if(v->getUnnormalizedValue() == part)
    568 						requestSingle(toMidiByte(virusLib::BankNumber::EditBuffer), p);
    569 				}
    570 			}
    571 			else
    572 			{
    573 				requestSingle(toMidiByte(virusLib::BankNumber::EditBuffer), virusLib::SINGLE);
    574 			}
    575 			return true;
    576 		}
    577 		else
    578 		{
    579 			return false;
    580 		}
    581 
    582 		const auto& params = findSynthParam(part, page, m.b);
    583 		for (const auto & p : params)
    584 			p->setValueFromSynth(m.c, pluginLib::Parameter::Origin::Midi);
    585 
    586 		return true;
    587 	}
    588 
    589     void Controller::printMessage(const pluginLib::SysEx &msg)
    590     {
    591 		std::stringstream ss;
    592         ss << "[size " << msg.size() << "] ";
    593         for(size_t i=0; i<msg.size(); ++i)
    594         {
    595             ss << HEXN(static_cast<int>(msg[i]), 2);
    596             if(i < msg.size()-1)
    597                 ss << ',';
    598         }
    599         const auto s(ss.str());
    600 		LOG(s);
    601     }
    602 
    603     void Controller::onStateLoaded()
    604     {
    605 		requestTotal();
    606 		requestArrangement();
    607 	}
    608 
    609     uint8_t Controller::getPartCount() const
    610     {
    611 	    return m_processor.getModel() == virusLib::DeviceModel::Snow ? 4 : 16;
    612     }
    613 
    614     bool Controller::requestProgram(uint8_t _bank, uint8_t _program, bool _multi) const
    615     {
    616         std::map<pluginLib::MidiDataType, uint8_t> data;
    617 
    618         data.insert(std::make_pair(pluginLib::MidiDataType::Bank, _bank));
    619         data.insert(std::make_pair(pluginLib::MidiDataType::Program, _program));
    620 
    621 		return sendSysEx(_multi ? MidiPacketType::RequestMulti : MidiPacketType::RequestSingle, data);
    622     }
    623 
    624     bool Controller::requestSingle(uint8_t _bank, uint8_t _program) const
    625     {
    626         return requestProgram(_bank, _program, false);
    627     }
    628 
    629     bool Controller::requestMulti(uint8_t _bank, uint8_t _program) const
    630     {
    631         return requestProgram(_bank, _program, true);
    632     }
    633 
    634     bool Controller::requestSingleBank(uint8_t _bank) const
    635     {
    636         std::map<pluginLib::MidiDataType, uint8_t> data;
    637         data.insert(std::make_pair(pluginLib::MidiDataType::Bank, _bank));
    638 
    639         return sendSysEx(MidiPacketType::RequestSingleBank, data);
    640     }
    641 
    642     bool Controller::requestTotal() const
    643     {
    644         return sendSysEx(MidiPacketType::RequestTotal);
    645     }
    646 
    647     bool Controller::requestArrangement() const
    648     {
    649         return sendSysEx(MidiPacketType::RequestArrangement);
    650     }
    651 
    652     void Controller::requestRomBanks()
    653     {
    654 		switch(m_processor.getModel())
    655 		{
    656         default:
    657         case virusLib::DeviceModel::A:
    658         case virusLib::DeviceModel::B:
    659         case virusLib::DeviceModel::C:
    660 			m_singles.resize(8);
    661 			break;
    662         case virusLib::DeviceModel::Snow:
    663         case virusLib::DeviceModel::TI:
    664         case virusLib::DeviceModel::TI2:
    665         	m_singles.resize(
    666                 virusLib::ROMFile::getRomBankCount(virusLib::DeviceModel::TI) +
    667                 virusLib::ROMFile::getRomBankCount(virusLib::DeviceModel::TI2) +
    668                 virusLib::ROMFile::getRomBankCount(virusLib::DeviceModel::Snow) +
    669                 2
    670             );
    671 			break;
    672         }
    673 
    674     	for(uint8_t i=3; i<=getBankCount(); ++i)
    675 			requestSingleBank(i);
    676     }
    677 
    678     bool Controller::sendSysEx(MidiPacketType _type) const
    679     {
    680 	    std::map<pluginLib::MidiDataType, uint8_t> params;
    681         return sendSysEx(_type, params);
    682     }
    683 
    684     bool Controller::sendSysEx(MidiPacketType _type, std::map<pluginLib::MidiDataType, uint8_t>& _params) const
    685     {
    686         _params.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, m_deviceId));
    687         return pluginLib::Controller::sendSysEx(midiPacketName(_type), _params);
    688     }
    689 
    690     void Controller::sendParameterChange(const pluginLib::Parameter& _parameter, const pluginLib::ParamValue _value)
    691     {
    692         const auto& desc = _parameter.getDescription();
    693 
    694         sendParameterChange(desc.page, _parameter.getPart(), desc.index, static_cast<uint8_t>(_value));
    695     }
    696 
    697     bool Controller::sendParameterChange(uint8_t _page, uint8_t _part, uint8_t _index, uint8_t _value) const
    698     {
    699         std::map<pluginLib::MidiDataType, uint8_t> data;
    700 
    701         data.insert(std::make_pair(pluginLib::MidiDataType::Page, _page));
    702         data.insert(std::make_pair(pluginLib::MidiDataType::Part, _part));
    703         data.insert(std::make_pair(pluginLib::MidiDataType::ParameterIndex, _index));
    704         data.insert(std::make_pair(pluginLib::MidiDataType::ParameterValue, _value));
    705 
    706     	return sendSysEx(MidiPacketType::ParameterChange, data);
    707     }
    708 
    709     std::vector<uint8_t> Controller::createSingleDump(uint8_t _part, uint8_t _bank, uint8_t _program)
    710     {
    711 	    pluginLib::MidiPacket::Data data;
    712 
    713         data.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, m_deviceId));
    714         data.insert(std::make_pair(pluginLib::MidiDataType::Bank, _bank));
    715         data.insert(std::make_pair(pluginLib::MidiDataType::Program, _program));
    716 
    717         std::vector<uint8_t> dst;
    718 
    719     	if(!createMidiDataFromPacket(dst, midiPacketName(MidiPacketType::SingleDump), data, _part))
    720             return {};
    721 
    722         return dst;
    723     }
    724 
    725     std::vector<uint8_t> Controller::createSingleDump(MidiPacketType _packet, uint8_t _bank, uint8_t _program, const pluginLib::MidiPacket::AnyPartParamValues& _paramValues)
    726     {
    727         const auto* m = getMidiPacket(midiPacketName(_packet));
    728 		assert(m && "midi packet not found");
    729 
    730     	if(!m)
    731 			return {};
    732 
    733 		pluginLib::MidiPacket::Data data;
    734 		pluginLib::MidiPacket::NamedParamValues paramValues;
    735 
    736         data.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, m_deviceId));
    737         data.insert(std::make_pair(pluginLib::MidiDataType::Bank, _bank));
    738         data.insert(std::make_pair(pluginLib::MidiDataType::Program, _program));
    739 
    740         if(!createNamedParamValues(paramValues, _paramValues))
    741             return {};
    742 
    743         pluginLib::MidiPacket::Sysex dst;
    744         if(!m->create(dst, data, paramValues))
    745             return {};
    746         return dst;
    747     }
    748 
    749     std::vector<uint8_t> Controller::modifySingleDump(const std::vector<uint8_t>& _sysex, const virusLib::BankNumber _newBank, const uint8_t _newProgram) const
    750     {
    751         auto* m = getMidiPacket(midiPacketName(MidiPacketType::SingleDump));
    752         assert(m);
    753 
    754         const auto idxBank = m->getByteIndexForType(pluginLib::MidiDataType::Bank);
    755         const auto idxProgram = m->getByteIndexForType(pluginLib::MidiDataType::Program);
    756 
    757         assert(idxBank != pluginLib::MidiPacket::InvalidIndex);
    758         assert(idxProgram != pluginLib::MidiPacket::InvalidIndex);
    759 
    760         auto data = _sysex;
    761 
    762         data[idxBank] = toMidiByte(_newBank);
    763         data[idxProgram] = _newProgram;
    764 
    765         return data;
    766     }
    767 
    768     void Controller::selectPrevPreset(const uint8_t _part)
    769     {
    770 		if(getCurrentPartProgram(_part) > 0)
    771 		{
    772             setCurrentPartPreset(_part, getCurrentPartBank(_part), getCurrentPartProgram(_part) - 1);
    773 		}
    774     }
    775 
    776     void Controller::selectNextPreset(uint8_t _part)
    777     {
    778 		if(getCurrentPartProgram(_part) < m_singles[0].size())
    779 		{
    780             setCurrentPartPreset(_part, getCurrentPartBank(_part), getCurrentPartProgram(_part) + 1);
    781 		}
    782     }
    783 
    784     std::string Controller::getBankName(uint32_t _index) const
    785     {
    786         char temp[32]{0};
    787 
    788         if(getBankCount() <= 26)
    789         {
    790 	        snprintf(temp, sizeof(temp), "Bank %c", 'A' + _index);
    791         }
    792         else if(_index < 2)
    793         {
    794 	        snprintf(temp, sizeof(temp), "RAM Bank %c", 'A' + _index);
    795         }
    796         else
    797         {
    798             _index -= 2;
    799 
    800             const auto countTI      = virusLib::ROMFile::getRomBankCount(virusLib::DeviceModel::TI);
    801             const auto countTI2     = virusLib::ROMFile::getRomBankCount(virusLib::DeviceModel::TI2);
    802 
    803             if(_index < countTI)	                sprintf(temp, "TI Rom %c", 'A' + _index);
    804             else if(_index < countTI + countTI2)	sprintf(temp, "TI2 Rom %c", 'A' + (_index - countTI));
    805             else			    		            sprintf(temp, "Snow Rom %c", 'A' + (_index - countTI - countTI2));
    806         }
    807         return temp;
    808     }
    809 
    810     bool Controller::activatePatch(const std::vector<unsigned char>& _sysex)
    811     {
    812 		return activatePatch(_sysex, isMultiMode() ? getCurrentPart() : static_cast<uint8_t>(virusLib::ProgramType::SINGLE));
    813     }
    814 
    815     bool Controller::activatePatch(const std::vector<unsigned char>& _sysex, uint32_t _part)
    816     {
    817         if(_part == virusLib::ProgramType::SINGLE)
    818         {
    819             if(isMultiMode())
    820 	            _part = 0;
    821         }
    822         else if(_part >= 16)
    823         {
    824             return false;
    825         }
    826         else if(!isMultiMode() && _part == 0)
    827         {
    828             _part = virusLib::ProgramType::SINGLE;
    829         }
    830 
    831         const auto program = static_cast<uint8_t>(_part);
    832 
    833 		// re-pack, force to edit buffer
    834     	const auto msg = modifySingleDump(_sysex, virusLib::BankNumber::EditBuffer, program);
    835 
    836 		if(msg.empty())
    837 			return false;
    838 
    839 		sendSysEx(msg);
    840 
    841 		requestSingle(toMidiByte(virusLib::BankNumber::EditBuffer), program);
    842 
    843 		setCurrentPartPresetSource(program == virusLib::ProgramType::SINGLE ? 0 : program, PresetSource::Browser);
    844 
    845 		return true;
    846     }
    847 }; // namespace Virus