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

n2xstate.cpp (18335B)


      1 #include "n2xstate.h"
      2 
      3 #include <cassert>
      4 
      5 #include "n2xhardware.h"
      6 #include "synthLib/midiToSysex.h"
      7 #include "synthLib/midiTranslator.h"
      8 #include "synthLib/midiTypes.h"
      9 
     10 namespace n2x
     11 {
     12 	static constexpr uint8_t g_singleDefault[] =
     13 	{
     14 		72,		// O2Pitch
     15 		67,		// O2PitchFine
     16 		64,		// Mix
     17 		100,	// Cutoff
     18 		10,		// Resonance
     19 		0,		// FilterEnvAmount
     20 		0,		// PW
     21 		0,		// FmDepth
     22 		0,		// FilterEnvA
     23 		10,		// FilterEnvD
     24 		100,	// FilterEnvS
     25 		10,		// FilterEnvR
     26 		0,		// AmpEnvA
     27 		10,		// AmpEnvD
     28 		100,	// AmpEnvS
     29 		0,		// AmpEnvR
     30 		0,		// Portamento
     31 		127,	// Gain
     32 		0,		// ModEnvA
     33 		0,		// ModEnvD
     34 		0,		// ModEnvLevel
     35 		10,		// Lfo1Rate
     36 		0,		// Lfo1Level
     37 		10,		// Lfo2Rate
     38 		0,		// ArpRange
     39 		0,		// O2PitchSens
     40 		0,		// O2PitchFineSens
     41 		0,		// MixSens
     42 		0,		// CutoffSens
     43 		0,		// ResonanceSens
     44 		0,		// FilterEnvAmountSens
     45 		0,		// PWSens
     46 		0,		// FmDepthSens
     47 		0,		// FilterEnvASens
     48 		0,		// FilterEnvDSens
     49 		0,		// FilterEnvSSens
     50 		0,		// FilterEnvRSens
     51 		0,		// AmpEnvASens
     52 		0,		// AmpEnvDSens
     53 		0,		// AmpEnvSSens
     54 		0,		// AmpEnvRSens
     55 		0,		// PortamentoSens
     56 		0,		// GainSens
     57 		0,		// ModEnvASens
     58 		0,		// ModEnvDSens
     59 		0,		// ModEnvLevelSens
     60 		0,		// Lfo1RateSens
     61 		0,		// Lfo1LevelSens
     62 		0,		// Lfo2RateSens
     63 		0,		// ArpRangeSens
     64 		0,		// O1Waveform
     65 		0,		// O2Waveform
     66 		0,		// Sync/RM/Dist
     67 		0,		// FilterType
     68 		1,		// O2Keytrack
     69 		0,		// FilterKeytrack
     70 		0,		// Lfo1Waveform
     71 		0,		// Lfo1Dest
     72 		0,		// VoiceMode
     73 		0,		// ModWheelDest
     74 		0,		// Unison
     75 		0,		// ModEnvDest
     76 		0,		// Auto
     77 		0,		// FilterVelocity
     78 		2,		// OctaveShift
     79 		0,		// Lfo2Dest
     80 	};
     81 
     82 	static_assert(std::size(g_singleDefault) == g_singleDataSize/2);
     83 
     84 	using MultiDefaultData = std::array<uint8_t, ((g_multiDataSize - g_singleDataSize * 4)>>1)>;
     85 
     86 	const std::unordered_map<ControlChange, SingleParam> g_controllerMap =
     87 	{
     88 		{ControlChange::CCGain					, SingleParam::Gain				},
     89 		{ControlChange::CCOctaveShift			, SingleParam::OctaveShift		},
     90 		{ControlChange::CCModWheelDest			, SingleParam::ModWheelDest		},
     91 		{ControlChange::CCUnison				, SingleParam::Unison			},
     92 		{ControlChange::CCVoiceMode				, SingleParam::VoiceMode		},
     93 		{ControlChange::CCAuto					, SingleParam::Auto				},
     94 		{ControlChange::CCPortamento			, SingleParam::Portamento		},
     95 		{ControlChange::CCLfo1Rate				, SingleParam::Lfo1Rate			},
     96 		{ControlChange::CCLfo1Waveform			, SingleParam::Lfo1Waveform		},
     97 		{ControlChange::CCLfo1Dest				, SingleParam::Lfo1Dest			},
     98 		{ControlChange::CCLfo1Level				, SingleParam::Lfo1Level		},
     99 		{ControlChange::CCLfo2Rate				, SingleParam::Lfo2Rate			},
    100 		{ControlChange::CCLfo2Dest				, SingleParam::Lfo2Dest			},
    101 		{ControlChange::CCArpRange				, SingleParam::ArpRange			},
    102 		{ControlChange::CCModEnvA				, SingleParam::ModEnvA			},
    103 		{ControlChange::CCModEnvD				, SingleParam::ModEnvD			},
    104 		{ControlChange::CCModEnvDest			, SingleParam::ModEnvDest		},
    105 		{ControlChange::CCModEnvLevel			, SingleParam::ModEnvLevel		},
    106 		{ControlChange::CCO1Waveform			, SingleParam::O1Waveform		},
    107 		{ControlChange::CCO2Waveform			, SingleParam::O2Waveform		},
    108 		{ControlChange::CCO2Pitch				, SingleParam::O2Pitch			},
    109 		{ControlChange::CCO2PitchFine			, SingleParam::O2PitchFine		},
    110 		{ControlChange::CCFmDepth				, SingleParam::FmDepth			},
    111 		{ControlChange::CCO2Keytrack			, SingleParam::O2Keytrack		},
    112 		{ControlChange::CCPW					, SingleParam::PW				},
    113 		{ControlChange::CCSync					, SingleParam::Sync				},
    114 		{ControlChange::CCMix					, SingleParam::Mix				},
    115 		{ControlChange::CCAmpEnvA				, SingleParam::AmpEnvA			},
    116 		{ControlChange::CCAmpEnvD				, SingleParam::AmpEnvD			},
    117 		{ControlChange::CCAmpEnvS				, SingleParam::AmpEnvS			},
    118 		{ControlChange::CCAmpEnvR				, SingleParam::AmpEnvR			},
    119 		{ControlChange::CCFilterEnvA			, SingleParam::FilterEnvA		},
    120 		{ControlChange::CCFilterEnvD			, SingleParam::FilterEnvD		},
    121 		{ControlChange::CCFilterEnvS			, SingleParam::FilterEnvS		},
    122 		{ControlChange::CCFilterEnvR			, SingleParam::FilterEnvR		},
    123 		{ControlChange::CCFilterType			, SingleParam::FilterType		},
    124 		{ControlChange::CCCutoff				, SingleParam::Cutoff			},
    125 		{ControlChange::CCResonance				, SingleParam::Resonance		},
    126 		{ControlChange::CCFilterEnvAmount		, SingleParam::FilterEnvAmount	},
    127 		{ControlChange::CCFilterVelocity		, SingleParam::FilterVelocity	},
    128 		{ControlChange::CCFilterKeytrack		, SingleParam::FilterKeytrack	},
    129 		{ControlChange::CCDistortion			, SingleParam::Distortion		}
    130 	};
    131 
    132 	MultiDefaultData createMultiDefaultData()
    133 	{
    134 		uint32_t i=0;
    135 
    136 		MultiDefaultData multi{};
    137 
    138 		auto set4 = [&](const uint8_t _a, const uint8_t _b, const uint8_t _c, const uint8_t _d)
    139 		{
    140 			multi[i++] = _a; multi[i++] = _b; multi[i++] = _c; multi[i++] = _d;
    141 		};
    142 
    143 		set4( 0, 1, 2, 3);	// MIDI channel
    144 		set4( 0, 0, 0, 0);	// LFO 1 Sync
    145 		set4( 0, 0, 0, 0);	// LFO 2 Sync
    146 		set4( 0, 0, 0, 0);	// Filter Env Trigger
    147 		set4( 0, 1, 2, 3);	// Filter Env Trigger Midi Channel
    148 		set4(23,23,23,23);	// Filter Env Trigger Note Number
    149 		set4( 0, 0, 0, 0);	// Amp Env Trigger
    150 		set4( 0, 1, 2, 3);	// Amp Env Trigger Midi Channel
    151 		set4(23,23,23,23);	// Amp Env Trigger Note Number
    152 		set4( 0, 0, 0, 0);	// Morph Trigger
    153 		set4( 0, 1, 2, 3);	// Morph Trigger Midi Channel
    154 		set4(23,23,23,23);	// Morph Trigger Note Number
    155 		multi[i++] = 2;		// Bend Range
    156 		multi[i++] = 2;		// Unison Detune
    157 		multi[i++] = 0;		// Out Mode A&B (lower nibble) and C&D (upper nibble)
    158 		multi[i++] = 15;	// Global Midi Channel
    159 		multi[i++] = 0;		// Program Change Enable
    160 		multi[i++] = 1;		// Midi Control
    161 		multi[i++] = 0;		// Master Tune
    162 		multi[i++] = 0;		// Pedal Type
    163 		multi[i++] = 1;		// Local Control
    164 		multi[i++] = 2;		// Keyboard Octave Shift
    165 		multi[i++] = 0;		// Selected Channel
    166 		multi[i++] = 0;		// Arp Midi Out
    167 		set4(1,0,0,0);		// Channel Active
    168 		set4(0,0,0,0);		// Program Select
    169 		set4(0,0,0,0);		// Bank Select
    170 		set4(7,7,7,7);		// Channel Pressure Amount
    171 		set4(0,0,0,0);		// Channel Pressure Dest
    172 		set4(7,7,7,7);		// Expression Pedal Amount
    173 		set4(0,0,0,0);		// Expression Pedal Dest
    174 		multi[i++] = 0;		// Keyboard Split
    175 		multi[i++] = 64;	// Split Point
    176 
    177 		assert(i == multi.size());
    178 
    179 		return multi;
    180 	}
    181 
    182 	static const MultiDefaultData g_multiDefault = createMultiDefaultData();
    183 
    184 	State::State(Hardware* _hardware, synthLib::MidiTranslator* _midiTranslator) : m_hardware(_hardware), m_midiTranslator(_midiTranslator)
    185 	{
    186 		for(uint8_t i=0; i<static_cast<uint8_t>(m_singles.size()); ++i)
    187 			createDefaultSingle(m_singles[i], i);
    188 		createDefaultMulti(m_multi);
    189 
    190 		receive(m_multi);
    191 		for (const auto& single : m_singles)
    192 			receive(single);
    193 	}
    194 
    195 	bool State::getState(std::vector<uint8_t>& _state)
    196 	{
    197 		updateMultiFromSingles();
    198 		if(m_multi[g_multiDumpSize - 1] == 0xf7)
    199 			_state.insert(_state.end(), m_multi.begin(), m_multi.end() - g_nameLength);
    200 		else
    201 			_state.insert(_state.end(), m_multi.begin(), m_multi.end());
    202 		for (const auto it : m_knobStates)
    203 		{
    204 			auto knobSysex = createKnobSysex(it.first, it.second);
    205 			_state.insert(_state.end(), knobSysex.begin(), knobSysex.end());
    206 		}
    207 		return true;
    208 	}
    209 
    210 	bool State::setState(const std::vector<uint8_t>& _state)
    211 	{
    212 		std::vector<std::vector<uint8_t>> msgs;
    213 		synthLib::MidiToSysex::splitMultipleSysex(msgs, _state);
    214 
    215 		for (auto& msg : msgs)
    216 			receive(msg, synthLib::MidiEventSource::Host);
    217 
    218 		return false;
    219 	}
    220 
    221 	bool State::receive(std::vector<synthLib::SMidiEvent>& _responses, const synthLib::SMidiEvent& _ev)
    222 	{
    223 		if(_ev.sysex.empty())
    224 		{
    225 			return receiveNonSysex(_ev);
    226 		}
    227 
    228 		auto& sysex = _ev.sysex;
    229 
    230 		if(sysex.size() <= SysexIndex::IdxMsgSpec)
    231 			return false;
    232 
    233 		const auto bank = sysex[SysexIndex::IdxMsgType];
    234 		const auto prog = sysex[SysexIndex::IdxMsgSpec];
    235 
    236 		if(isSingleDump(sysex))
    237 		{
    238 			if(bank != SysexByte::SingleDumpBankEditBuffer)
    239 				return false;
    240 			if(prog > m_singles.size())
    241 				return false;
    242 			m_singles[prog].fill(0);
    243 			std::copy(sysex.begin(), sysex.end(), m_singles[prog].begin());
    244 			send(_ev);
    245 			return true;
    246 		}
    247 
    248 		if(isMultiDump(sysex))
    249 		{
    250 			if(bank != SysexByte::MultiDumpBankEditBuffer)
    251 				return false;
    252 			if(prog != 0)
    253 				return false;
    254 			m_multi.fill(0);
    255 			std::copy(sysex.begin(), sysex.end(), m_multi.begin());
    256 
    257 			if (m_midiTranslator)
    258 			{
    259 				// As we need support for individual midi channels for the editor to adjus each part separately but
    260 				// knobs can only be modified via midi CC, we tell the device that parts 0-3 are on midi channels 0-3 even if
    261 				// they are not. The midi translator will translate regular midi messages and the editor uses messages that are
    262 				// not translated
    263 
    264 				m_midiTranslator->clear();
    265 
    266 				MultiDump dump = m_multi;
    267 				for (uint8_t i = 0; i < 4; ++i)
    268 				{
    269 					const auto ch = getPartMidiChannel(dump, i);
    270 					setPartMidiChannel(dump, i, i);
    271 					m_midiTranslator->addTargetChannel(ch, i);
    272 				}
    273 
    274 				synthLib::SMidiEvent e;
    275 				e.sysex.assign(dump.begin(), dump.end());
    276 				send(e);
    277 			}
    278 			else
    279 			{
    280 				send(_ev);
    281 			}
    282 
    283 			return true;
    284 		}
    285 
    286 		if (bank == SysexByte::MultiRequestBankEditBuffer)
    287 		{
    288 			_responses.emplace_back(synthLib::MidiEventSource::Internal);
    289 			_responses.back().sysex.assign(m_multi.begin(), m_multi.end());
    290 			_responses.back().sysex = validateDump(_responses.back().sysex);
    291 			return true;
    292 		}
    293 
    294 		if(bank == n2x::SysexByte::EmuSetPotPosition)
    295 		{
    296 			KnobType knobType;
    297 			uint8_t knobValue;
    298 
    299 			if(parseKnobSysex(knobType, knobValue, sysex))
    300 			{
    301 				if(m_hardware)
    302 					m_hardware->setKnobPosition(knobType, knobValue);
    303 				m_knobStates[knobType] = knobValue;
    304 				return true;
    305 			}
    306 		}
    307 		else if(bank == SysexByte::EmuGetPotsPosition)
    308 		{
    309 			for (const auto it : m_knobStates)
    310 			{
    311 				_responses.emplace_back(synthLib::MidiEventSource::Internal);
    312 				_responses.back().sysex = createKnobSysex(it.first, it.second);
    313 			}
    314 			return true;
    315 		}
    316 		else if (bank == SysexByte::EmuSetPartCC)
    317 		{
    318 			synthLib::SMidiEvent e;
    319 			auto part = sysex[5];
    320 			e.a = sysex[6];
    321 			e.b = sysex[7];
    322 			e.c = sysex[8];
    323 			e.source = _ev.source;
    324 			e.offset = _ev.offset;
    325 			changeSingleParameter(part, e);
    326 		}
    327 
    328 		return false;
    329 	}
    330 
    331 	bool State::receive(const std::vector<uint8_t>& _data, synthLib::MidiEventSource _source)
    332 	{
    333 		synthLib::SMidiEvent e;
    334 		e.sysex = _data;
    335 		e.source = _source;
    336 		return receive(e);
    337 	}
    338 
    339 	bool State::receiveNonSysex(const synthLib::SMidiEvent& _ev)
    340 	{
    341 		switch (_ev.a & 0xf0)
    342 		{
    343 		case synthLib::M_CONTROLCHANGE:
    344 			{
    345 				const auto parts = getPartsForMidiChannel(_ev);
    346 				if(parts.empty())
    347 					return false;
    348 				for (const auto part : parts)
    349 				{
    350 					if (!changeSingleParameter(part, _ev))
    351 						return false;
    352 				}
    353 				return true;
    354 			}
    355 		default:
    356 			return false;
    357 		}
    358 	}
    359 
    360 	bool State::changeSingleParameter(const uint8_t _part, const synthLib::SMidiEvent& _ev)
    361 	{
    362 		const auto cc = static_cast<ControlChange>(_ev.b);
    363 		const auto it = g_controllerMap.find(cc);
    364 		if(it == g_controllerMap.end())
    365 			return false;
    366 		const SingleParam param = it->second;
    367 		const auto offset = getOffsetInSingleDump(param);
    368 		switch (param)
    369 		{
    370 		case SingleParam::Sync:
    371 			// this can either be sync or distortion, they end up in the same midi byte
    372 			switch(cc)
    373 			{
    374 			case ControlChange::CCSync:
    375 				{
    376 					auto v = unpackNibbles(m_singles[_part], offset);
    377 					v &= ~0x3;
    378 					v |= _ev.c & 0x3;
    379 					packNibbles(m_singles[_part], offset, v);
    380 				}
    381 				return true;
    382 			case ControlChange::CCDistortion:
    383 				{
    384 					auto v = unpackNibbles(m_singles[_part], offset);
    385 					v &= ~(1<<4);
    386 					v |= _ev.c << 4;
    387 					packNibbles(m_singles[_part], offset, v);
    388 				}
    389 				return true;
    390 			default:
    391 				assert(false && "unexpected control change type");
    392 				return false;
    393 			}
    394 			break;
    395 		default:
    396 			packNibbles(m_singles[_part], offset, _ev.c);
    397 			return true;
    398 		}
    399 	}
    400 
    401 	bool State::changeSingleParameter(const uint8_t _part, const SingleParam _parameter, const uint8_t _value)
    402 	{
    403 		if(_part >= m_singles.size())
    404 			return false;
    405 		return changeSingleParameter(m_singles[_part], _parameter, _value);
    406 	}
    407 
    408 	bool State::changeMultiParameter(const MultiParam _parameter, const uint8_t _value)
    409 	{
    410 		return changeDumpParameter(m_multi, getOffsetInMultiDump(_parameter), _value);
    411 	}
    412 
    413 	bool State::changeSingleParameter(SingleDump& _dump, const SingleParam _param, const uint8_t _value)
    414 	{
    415 		return changeDumpParameter(_dump, getOffsetInSingleDump(_param), _value);
    416 	}
    417 
    418 	void State::updateMultiFromSingles()
    419 	{
    420 		for(uint8_t i=0; i<static_cast<uint8_t>(m_singles.size()); ++i)
    421 			copySingleToMulti(m_multi, m_singles[i], i);
    422 	}
    423 
    424 	void State::createDefaultSingle(SingleDump& _single, const uint8_t _program, const uint8_t _bank/* = n2x::SingleDumpBankEditBuffer*/)
    425 	{
    426 		createHeader(_single, _bank, _program);
    427 		_single[g_singleDumpSize-1] = 0xf7;
    428 
    429 		uint32_t o = IdxMsgSpec + 1;
    430 
    431 		for (const auto b : g_singleDefault)
    432 		{
    433 			_single[o++] = b & 0xf;
    434 			_single[o++] = (b>>4) & 0xf;
    435 		}
    436 	}
    437 
    438 	// ReSharper disable once CppParameterMayBeConstPtrOrRef
    439 	void State::copySingleToMulti(MultiDump& _multi, const SingleDump& _single, const uint8_t _index)
    440 	{
    441 		uint32_t i = SysexIndex::IdxMsgSpec + 1;
    442 		i += g_singleDataSize * _index;
    443 		std::copy_n(_single.begin() + g_sysexHeaderSize, g_singleDataSize, _multi.begin() + i);
    444 	}
    445 
    446 	void State::extractSingleFromMulti(SingleDump& _single, const MultiDump& _multi, uint8_t _index)
    447 	{
    448 		uint32_t i = SysexIndex::IdxMsgSpec + 1;
    449 		i += g_singleDataSize * _index;
    450 		std::copy_n(_multi.begin() + i, g_singleDataSize, _single.begin() + g_sysexHeaderSize);
    451 	}
    452 
    453 	void State::createDefaultMulti(MultiDump& _multi, const uint8_t _bank/* = SysexByte::MultiDumpBankEditBuffer*/)
    454 	{
    455 		createHeader(_multi, _bank, 0);
    456 		_multi[g_multiDumpSize-1] = 0xf7;
    457 
    458 		SingleDump single;
    459 		createDefaultSingle(single, 0);
    460 
    461 		copySingleToMulti(_multi, single, 0);
    462 		copySingleToMulti(_multi, single, 1);
    463 		copySingleToMulti(_multi, single, 2);
    464 		copySingleToMulti(_multi, single, 3);
    465 
    466 		uint32_t i = SysexIndex::IdxMsgSpec + 1 + 4 * g_singleDataSize;
    467 		assert(i == 264 * 2 + g_sysexHeaderSize);
    468 
    469 		for (const auto b : g_multiDefault)
    470 		{
    471 			_multi[i++] = b & 0xf;
    472 			_multi[i++] = (b>>4) & 0xf;
    473 		}
    474 		assert(i + g_sysexFooterSize == g_multiDumpSize);
    475 	}
    476 
    477 	uint32_t State::getOffsetInSingleDump(const SingleParam _param)
    478 	{
    479 		return g_sysexHeaderSize + (_param<<1);
    480 	}
    481 
    482 	uint32_t State::getOffsetInMultiDump(const MultiParam _param)
    483 	{
    484 		return g_sysexHeaderSize + (_param<<1);
    485 	}
    486 
    487 	std::vector<uint8_t> State::getPartsForMidiChannel(const synthLib::SMidiEvent& _ev) const
    488 	{
    489 		const auto ch = _ev.a & 0xf;
    490 		return getPartsForMidiChannel(ch);
    491 	}
    492 
    493 	std::vector<uint8_t> State::getPartsForMidiChannel(const uint8_t _channel) const
    494 	{
    495 		std::vector<uint8_t> res;
    496 
    497 		for(uint8_t i=0; i<static_cast<uint8_t>(m_singles.size()); ++i)
    498 		{
    499 			if(getPartMidiChannel(i) == _channel)
    500 				res.push_back(i);
    501 		}
    502 		return res;
    503 	}
    504 
    505 	std::vector<uint8_t> State::createKnobSysex(KnobType _type, uint8_t _value)
    506 	{
    507 		return {0xf0, IdClavia, DefaultDeviceId, IdN2X,
    508 			EmuSetPotPosition,
    509 			static_cast<uint8_t>(_type),
    510 			static_cast<uint8_t>(_value >> 4),
    511 			static_cast<uint8_t>(_value & 0x0f),
    512 			0xf7
    513 		};
    514 	}
    515 
    516 	bool State::parseKnobSysex(KnobType& _type, uint8_t& _value, const std::vector<uint8_t>& _sysex)
    517 	{
    518 		if(_sysex.size() <= SysexIndex::IdxKnobPosL)
    519 			return false;
    520 		if(_sysex[SysexIndex::IdxMsgType] != SysexByte::EmuSetPotPosition)
    521 			return false;
    522 
    523 		_type = static_cast<KnobType>(_sysex[SysexIndex::IdxMsgSpec]);
    524 		_value = static_cast<uint8_t>((_sysex[SysexIndex::IdxKnobPosH] << 4) | _sysex[SysexIndex::IdxKnobPosL]);
    525 
    526 		return true;
    527 	}
    528 
    529 	bool State::getKnobState(uint8_t& _result, const KnobType _type) const
    530 	{
    531 		const auto it = m_knobStates.find(_type);
    532 		if(it == m_knobStates.end())
    533 			return false;
    534 		_result = it->second;
    535 		return true;
    536 	}
    537 
    538 	bool State::isSingleDump(const std::vector<uint8_t>& _dump)
    539 	{
    540 		return _dump.size() == g_singleDumpSize || _dump.size() == g_singleDumpWithNameSize;
    541 	}
    542 
    543 	bool State::isMultiDump(const std::vector<uint8_t>& _dump)
    544 	{
    545 		return _dump.size() == g_multiDumpSize || _dump.size() == g_multiDumpWithNameSize;
    546 	}
    547 
    548 	std::string State::extractPatchName(const std::vector<uint8_t>& _dump)
    549 	{
    550 		if(!isDumpWithPatchName(_dump))
    551 			return {};
    552 		auto* begin = &_dump[_dump.size() - g_nameLength - 1];
    553 		if(*begin == 0xf7)
    554 			return {};
    555 		std::string name(reinterpret_cast<const char*>(begin), g_nameLength);
    556 		return name;
    557 	}
    558 
    559 	bool State::isDumpWithPatchName(const std::vector<uint8_t>& _dump)
    560 	{
    561 		return _dump.size() == g_singleDumpWithNameSize || _dump.size() == g_multiDumpWithNameSize;
    562 	}
    563 
    564 	std::vector<uint8_t> State::stripPatchName(const std::vector<uint8_t>& _dump)
    565 	{
    566 		if(!isDumpWithPatchName(_dump))
    567 			return _dump;
    568 		auto d = _dump;
    569 		d.erase(d.end() - g_nameLength - 1, d.end() - 1);
    570 		assert(d.size() == g_singleDumpSize || d.size() == g_multiDumpSize);
    571 		d.back() = 0xf7;
    572 		return d;
    573 	}
    574 
    575 	bool State::isValidPatchName(const std::vector<uint8_t>& _dump)
    576 	{
    577 		if(!isDumpWithPatchName(_dump))
    578 			return false;
    579 
    580 		if(_dump.back() != 0xf7)
    581 			return false;
    582 
    583 		const auto nameStart = _dump.size() - g_nameLength - 1;
    584 
    585 		for(size_t i=nameStart; i<nameStart+g_nameLength; ++i)
    586 		{
    587 			if(_dump[i] < 32 || _dump[i] >= 128)
    588 				return false;
    589 		}
    590 		return true;
    591 	}
    592 
    593 	std::vector<uint8_t> State::validateDump(const std::vector<uint8_t>& _dump)
    594 	{
    595 		if(!isValidPatchName(_dump))
    596 			return stripPatchName(_dump);
    597 		return _dump;
    598 	}
    599 
    600 	synthLib::SMidiEvent& State::createPartCC(uint8_t _part, synthLib::SMidiEvent& _ccEvent)
    601 	{
    602 		_ccEvent.sysex = { 0xf0, IdClavia, SysexByte::DefaultDeviceId, IdN2X,
    603 			SysexByte::EmuSetPartCC,
    604 			_part,
    605 			_ccEvent.a,
    606 			_ccEvent.b,
    607 			_ccEvent.c,
    608 			0xf7
    609 		};
    610 		return _ccEvent;
    611 	}
    612 
    613 	void State::send(const synthLib::SMidiEvent& _e) const
    614 	{
    615 		if(_e.source == synthLib::MidiEventSource::Plugin)
    616 			return;
    617 
    618 		if(m_hardware)
    619 		{
    620 			const auto& sysex = _e.sysex;
    621 
    622 			if(isDumpWithPatchName(sysex))
    623 			{
    624 				auto e = _e;
    625 				e.sysex = stripPatchName(sysex);
    626 				m_hardware->sendMidi(e);
    627 			}
    628 			else
    629 			{
    630 				m_hardware->sendMidi(_e);
    631 			}
    632 		}
    633 	}
    634 
    635 	template<size_t Size>
    636 	void State::createHeader(std::array<uint8_t, Size>& _buffer, uint8_t _msgType, uint8_t _msgSpec)
    637 	{
    638 		_buffer.fill(0);
    639 
    640 		_buffer[0] = 0xf0;
    641 
    642 		_buffer[SysexIndex::IdxClavia] = SysexByte::IdClavia;
    643 		_buffer[SysexIndex::IdxDevice] = SysexByte::DefaultDeviceId;
    644 		_buffer[SysexIndex::IdxN2x] = SysexByte::IdN2X;
    645 		_buffer[SysexIndex::IdxMsgType] = _msgType;
    646 		_buffer[SysexIndex::IdxMsgSpec] = _msgSpec;
    647 
    648 		_buffer.back() = 0xf7;
    649 	}
    650 }