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

xtState.cpp (37058B)


      1 #include "xtState.h"
      2 
      3 #include <cassert>
      4 #include <map>
      5 #include <set>
      6 #include <algorithm>
      7 
      8 #include "xtMidiTypes.h"
      9 #include "xt.h"
     10 #include "xtWavePreview.h"
     11 
     12 #include "synthLib/midiToSysex.h"
     13 #include "synthLib/midiBufferParser.h"
     14 
     15 #include "dsp56kEmu/logging.h"
     16 
     17 namespace xt
     18 {
     19 	static_assert(std::size(State::Dumps) == static_cast<uint32_t>(State::DumpType::Count), "data definition missing");
     20 
     21 	State::State(Xt& _xt, WavePreview& _wavePreview) : m_xt(_xt), m_wavePreview(_wavePreview)
     22 	{
     23 	}
     24 
     25 	bool State::loadState(const SysEx& _sysex)
     26 	{
     27 		std::vector<std::vector<uint8_t>> messages;
     28 		synthLib::MidiToSysex::splitMultipleSysex(messages, _sysex);
     29 
     30 		if(messages.empty())
     31 			return false;
     32 
     33 		Responses nop;
     34 
     35 		for (const auto& message : messages)
     36 			receive(nop, message, Origin::External);
     37 
     38 		return true;
     39 	}
     40 
     41 	bool State::getState(std::vector<uint8_t>& _state, synthLib::StateType _type) const
     42 	{
     43 		append(_state, m_mode, ~0u);
     44 		append(_state, m_global, wLib::IdxCommand);
     45 
     46 		// add all waves and tables that are used by the singles
     47 		std::set<TableId> tableIds;
     48 
     49 		auto addTableId = [&](TableId id)
     50 		{
     51 			if(wave::isReadOnly(id))
     52 				return false;
     53 
     54 			const auto idx = id.rawId();
     55 			if(idx >= m_tables.size())
     56 				return false;
     57 
     58 			if(!isValid(m_tables[idx]))
     59 				return false;
     60 			tableIds.insert(id);
     61 			return true;
     62 		};
     63 
     64 		for (const auto& s  : m_currentInstrumentSingles)
     65 		{
     66 			const auto table = getWavetableFromSingleDump({s.begin(), s.end()});
     67 			addTableId(table);
     68 		}
     69 		for (const auto& s  : m_currentMultiSingles)
     70 		{
     71 			const auto table = getWavetableFromSingleDump({s.begin(), s.end()});
     72 			addTableId(table);
     73 		}
     74 
     75 		std::set<WaveId> waveIds;
     76 		for (const TableId id : tableIds)
     77 		{
     78 			if(wave::isReadOnly(id))
     79 				continue;
     80 
     81 			const auto idx = id.rawId();
     82 			if(idx >= m_tables.size())
     83 				continue;
     84 
     85 			const auto& t = m_tables[idx];
     86 			if(!isValid(t))
     87 				continue;
     88 
     89 			TableData table;
     90 			parseTableData(table, {t.begin(), t.end()});
     91 
     92 			auto tableWaves = getWavesForTable(table);
     93 
     94 			for (const auto& waveId : tableWaves)
     95 			{
     96 				if(wave::isReadOnly(waveId))
     97 					continue;
     98 				const auto waveIdx = waveId.rawId();
     99 				if(waveIdx >= m_waves.size())
    100 					continue;
    101 				const auto& wave = m_waves[waveIdx];
    102 				if(!isValid(wave))
    103 					continue;
    104 				waveIds.insert(waveId);
    105 			}
    106 		}
    107 
    108 		for (const auto& waveId : waveIds)
    109 			append(_state, m_waves[waveId.rawId()], 7);
    110 
    111 		for (const auto& tableId : tableIds)
    112 			append(_state, m_tables[tableId.rawId()], 7);
    113 
    114 		const auto multiMode = isMultiMode();
    115 
    116 		// if we are in multimode, write multis last, otherwise, write singles last
    117 		// This causes the relevant things to be activated last when loading
    118 		if(multiMode)
    119 		{
    120 			for (const auto& s: m_currentInstrumentSingles)
    121 				append(_state, s, IdxSingleChecksumStart);
    122 			append(_state, m_currentMulti, IdxMultiChecksumStart);
    123 			for (const auto& s: m_currentMultiSingles)
    124 				append(_state, s, IdxSingleChecksumStart);
    125 		}
    126 		else
    127 		{
    128 			append(_state, m_currentMulti, IdxMultiChecksumStart);
    129 			for (const auto& s: m_currentMultiSingles)
    130 				append(_state, s, IdxSingleChecksumStart);
    131 			for (const auto& s: m_currentInstrumentSingles)
    132 				append(_state, s, IdxSingleChecksumStart);
    133 		}
    134 
    135 		return !_state.empty();
    136 	}
    137 
    138 	bool State::receive(Responses& _responses, const synthLib::SMidiEvent& _data, Origin _sender)
    139 	{
    140 		if(!_data.sysex.empty())
    141 		{
    142 			return receive(_responses, _data.sysex, _sender);
    143 		}
    144 
    145 		if (_sender == Origin::Device)
    146 			LOG("Recv: " << HEXN(_data.a, 2) << ' ' << HEXN(_data.b, 2) << ' ' << HEXN(_data.c, 2));
    147 
    148 		switch(_data.a & 0xf0)
    149 		{
    150 		case synthLib::M_CONTROLCHANGE:
    151 			switch(_data.b)
    152 			{
    153 			case synthLib::MC_BANKSELECTMSB:
    154 				m_lastBankSelectMSB = _data;
    155 				break;
    156 			case synthLib::MC_BANKSELECTLSB:
    157 				m_lastBankSelectLSB = _data;
    158 				break;
    159 			default:
    160 				return false;
    161 			}
    162 			break;
    163 		case synthLib::M_PROGRAMCHANGE:
    164 			/*
    165 			switch(static_cast<BankSelectLSB>(m_lastBankSelectLSB.c))
    166 			{
    167 			case BankSelectLSB::BsDeprecatedSingleBankA:
    168 			case BankSelectLSB::BsDeprecatedSingleBankB:
    169 			case BankSelectLSB::BsDeprecatedSingleBankC:
    170 			case BankSelectLSB::BsSingleBankA:
    171 			case BankSelectLSB::BsSingleBankB:
    172 			case BankSelectLSB::BsSingleBankC:
    173 				if(getGlobalParameter(GlobalParameter::SingleMultiMode) == 0)
    174 					requestSingle(LocationH::SingleEditBufferSingleMode, MidiSoundLocation::EditBufferCurrentSingle);
    175 				break;
    176 			case BankSelectLSB::BsMultiBank:
    177 				if(getGlobalParameter(GlobalParameter::SingleMultiMode) != 0)
    178 					requestMulti(LocationH::MultiEditBuffer, 0);
    179 				break;
    180 			default:
    181 				return false;
    182 			}
    183 			*/
    184 			break;
    185 		default:
    186 			return false;
    187 		}
    188 		return false;
    189 	}
    190 
    191 	bool State::receive(Responses& _responses, const SysEx& _data, const Origin _sender)
    192 	{
    193 		if(_data.size() == Mw1::g_singleDumpLength)
    194 		{
    195 			m_sender = _sender;
    196 			forwardToDevice(_data);
    197 
    198 			// MW1 dump doesn't contain any information about which part or bank it is loaded into, the first
    199 			// part is always the target
    200 			// Invalidate the currently cached single. We cannot do the conversion here, the hardware has to.
    201 			// The editor needs to request the single after sending a MW1 dump, which will fill our cache again
    202 			if(isMultiMode())
    203 				m_currentMultiSingles[0].fill(0);
    204 			else
    205 				m_currentInstrumentSingles.front().fill(0);
    206 			return true;
    207 		}
    208 
    209 		const auto cmd = getCommand(_data);
    210 
    211 		if(cmd == SysexCommand::Invalid)
    212 			return false;
    213 
    214 		m_sender = _sender;
    215 		m_isEditBuffer = false;
    216 
    217 		switch (cmd)
    218 		{
    219 		case SysexCommand::SingleRequest:			return getDump(DumpType::Single, _responses, _data);
    220 		case SysexCommand::MultiRequest:			return getDump(DumpType::Multi,_responses, _data);
    221 		case SysexCommand::GlobalRequest:			return getDump(DumpType::Global, _responses, _data);
    222 		case SysexCommand::ModeRequest:				return getDump(DumpType::Mode, _responses, _data);
    223 		case SysexCommand::WaveRequest:				return getDump(DumpType::Wave, _responses, _data);
    224 		case SysexCommand::WaveCtlRequest:			return getDump(DumpType::Table, _responses, _data);
    225 
    226 		case SysexCommand::SingleDump:				return parseDump(DumpType::Single, _data);
    227 		case SysexCommand::MultiDump:				return parseDump(DumpType::Multi, _data);
    228 		case SysexCommand::GlobalDump:				return parseDump(DumpType::Global, _data);
    229 		case SysexCommand::ModeDump:				return parseDump(DumpType::Mode, _data);
    230 		case SysexCommand::WaveDump:				return parseDump(DumpType::Wave, _data);
    231 		case SysexCommand::WaveCtlDump:				return parseDump(DumpType::Table, _data);
    232 
    233 		case SysexCommand::SingleParameterChange:	return modifyDump(DumpType::Single, _data);
    234 		case SysexCommand::MultiParameterChange:	return modifyDump(DumpType::Multi, _data);
    235 		case SysexCommand::GlobalParameterChange:	return modifyDump(DumpType::Global, _data);
    236 		case SysexCommand::ModeParameterChange:		return modifyDump(DumpType::Mode, _data);
    237 
    238 		case SysexCommand::WaveDumpP:				return m_wavePreview.receiveWave(_data);
    239 		case SysexCommand::WaveCtlDumpP:			return m_wavePreview.receiveWaveControlTable(_data);
    240 		case SysexCommand::WavePreviewMode:			return m_wavePreview.receiveWavePreviewMode(_data);
    241 
    242 /*		case SysexCommand::EmuLCD:
    243 		case SysexCommand::EmuLEDs:
    244 		case SysexCommand::EmuButtons:
    245 		case SysexCommand::EmuRotaries:
    246 			return false;
    247 */		default:
    248 			return false;
    249 		}
    250 	}
    251 
    252 	void State::createInitState()
    253 	{
    254 		// request global settings and wait for them. Once they are valid, send init state
    255 		requestGlobal();
    256 		requestMode();
    257 
    258 		synthLib::MidiBufferParser parser;
    259 		Responses unused;
    260 		std::vector<uint8_t> midi;
    261 		std::vector<synthLib::SMidiEvent> events;
    262 
    263 		while(!isValid(m_global) || !isValid(m_mode))
    264 		{
    265 			m_xt.process(8);
    266 			midi.clear();
    267 			m_xt.receiveMidi(midi);
    268 			parser.write(midi);
    269 
    270 			events.clear();
    271 			parser.getEvents(events);
    272 
    273 			for (const auto & event : events)
    274 			{
    275 				if(!event.sysex.empty())
    276 				{
    277 					if(!receive(unused, event.sysex, Origin::Device))
    278 						assert(false);
    279 				}
    280 			}
    281 		}
    282 
    283 		auto setParam = [&](const GlobalParameter _param, const uint8_t _value)
    284 		{
    285 			sendGlobalParameter(_param, _value);
    286 		};
    287 
    288 		setParam(GlobalParameter::StartupSoundbank, 0);			// First bank
    289 		setParam(GlobalParameter::StartupSoundNum, 0);			// First sound
    290 		setParam(GlobalParameter::StartupMultiNumber, 0);		// First Multi
    291 
    292 		setParam(GlobalParameter::ProgramChangeMode, 0);		// single
    293 		setParam(GlobalParameter::MasterTune, 64);				// 440 Hz
    294 		setParam(GlobalParameter::Transpose, 64);				// +/- 0
    295 		setParam(GlobalParameter::ParameterSend, 2);			// SysEx
    296 		setParam(GlobalParameter::ParameterReceive, 1);			// on
    297 		setParam(GlobalParameter::ArpNoteOutChannel, 0);		// off
    298 		setParam(GlobalParameter::MidiClockOutput, 0);			// off
    299 		setParam(GlobalParameter::MidiChannel, 1);				// omni
    300 		setParam(GlobalParameter::DeviceId, 0);					// 0
    301 		setParam(GlobalParameter::InputGain, 3);				// 4
    302 
    303 		receive(unused, convertTo(m_global), Origin::External);
    304 	}
    305 
    306 	bool State::setState(const std::vector<uint8_t>& _state, synthLib::StateType _type)
    307 	{
    308 		return loadState(_state);
    309 	}
    310 
    311 	void State::process(const uint32_t _numSamples)
    312 	{
    313 		for (auto it = m_delayedCalls.begin(); it != m_delayedCalls.end();)
    314 		{
    315 			auto& delay = it->first;
    316 			auto& call = it->second;
    317 
    318 			delay -= static_cast<int32_t>(_numSamples);
    319 
    320 			if (delay <= 0)
    321 			{
    322 				call();
    323 				it = m_delayedCalls.erase(it);
    324 			}
    325 			else
    326 			{
    327 				++it;
    328 			}
    329 		}
    330 	}
    331 
    332 	bool State::setSingleName(std::vector<uint8_t>& _sysex, const std::string& _name)
    333 	{
    334 		if (_sysex.size() != std::tuple_size_v<Single>)
    335 			return false;
    336 
    337 		if (getCommand(_sysex) != SysexCommand::SingleDump)
    338 			return false;
    339 
    340 		for (size_t i=0; i<mw2::g_singleNameLength; ++i)
    341 			_sysex[i + mw2::g_singleNamePosition] = i >= _name.size() ? ' ' : _name[i];
    342 		return true;
    343 	}
    344 
    345 	TableId State::getWavetableFromSingleDump(const SysEx& _single)
    346 	{
    347 		constexpr auto wavetableIndex = IdxSingleParamFirst + static_cast<uint32_t>(SingleParameter::Wavetable);
    348 
    349 		if(wavetableIndex >= _single.size())
    350 			return TableId::invalid();
    351 
    352 		return TableId(_single[wavetableIndex]);
    353 	}
    354 
    355 	bool State::parseSingleDump(const SysEx& _data)
    356 	{
    357 		Single single;
    358 
    359 		if(!convertTo(single, _data))
    360 			return false;
    361 
    362 		const auto buf = static_cast<LocationH>(_data[wLib::IdxBuffer]);
    363 		const auto loc = _data[wLib::IdxLocation];
    364 
    365 		Single* dst = getSingle(buf, loc);
    366 
    367 		if(!dst)
    368 			return false;
    369 		*dst = single;
    370 		return true;
    371 	}
    372 
    373 	bool State::parseMultiDump(const SysEx& _data)
    374 	{
    375 		Multi multi;
    376 
    377 		if(!convertTo(multi, _data))
    378 			return false;
    379 
    380 		const auto buf = static_cast<LocationH>(_data[wLib::IdxBuffer]);
    381 		const auto loc = _data[wLib::IdxLocation];
    382 
    383 		auto* m = getMulti(buf, loc);
    384 		if(!m)
    385 			return false;
    386 		*m = multi;
    387 		return true;
    388 	}
    389 
    390 	bool State::parseGlobalDump(const SysEx& _data)
    391 	{
    392 		return convertTo(m_global, _data);
    393 	}
    394 
    395 	bool State::parseModeDump(const SysEx& _data)
    396 	{
    397 		if(!convertTo(m_mode, _data))
    398 			return false;
    399 		onPlayModeChanged();
    400 		return true;
    401 	}
    402 
    403 	bool State::parseWaveDump(const SysEx& _data)
    404 	{
    405 		const auto idx = getWaveId(_data).rawId();
    406 
    407 		if(idx >= m_waves.size())
    408 			return false;
    409 
    410 		const auto old = m_waves[idx];
    411 
    412 		if(!convertTo(m_waves[idx], _data))
    413 			return false;
    414 
    415 		if (m_waves[idx] == old)
    416 			return true;
    417 
    418 		forwardToDevice(_data);
    419 
    420 		return true;
    421 	}
    422 
    423 	bool State::parseTableDump(const SysEx& _data)
    424 	{
    425 		const auto idx = getTableId(_data).rawId();
    426 
    427 		if(idx >= m_tables.size())
    428 			return false;
    429 
    430 		if(!convertTo(m_tables[idx], _data))
    431 			return false;
    432 
    433 		return true;
    434 	}
    435 
    436 	bool State::modifySingle(const SysEx& _data)
    437 	{
    438 		auto* p = getSingleParameter(_data);
    439 		if(!p)
    440 			return false;
    441 		*p = _data[IdxSingleParamValue];
    442 		return true;
    443 	}
    444 
    445 	bool State::modifyMulti(const SysEx& _data)
    446 	{
    447 		auto* p = getMultiParameter(_data);
    448 		if(!p)
    449 			return false;
    450 
    451 		*p = _data[IdxMultiParamValue];
    452 		return true;
    453 	}
    454 
    455 	bool State::modifyGlobal(const SysEx& _data)
    456 	{
    457 		auto* p = getGlobalParameter(_data);
    458 		if(!p)
    459 			return false;
    460 
    461 		if(*p == _data[IdxGlobalParamValue])
    462 			return true;
    463 
    464 		*p = _data[IdxGlobalParamValue];
    465 
    466 		return true;
    467 	}
    468 
    469 	bool State::modifyMode(const SysEx& _data)
    470 	{
    471 		auto* p = getModeParameter(_data);
    472 		if(!p)
    473 			return false;
    474 
    475 		*p = _data[IdxModeParamValue];
    476 
    477 		onPlayModeChanged();
    478 
    479 		return true;
    480 	}
    481 
    482 	namespace
    483 	{
    484 		template<size_t Size>
    485 		uint8_t* getParameter(std::array<uint8_t, Size>& _dump, const SysEx& _data, State::DumpType _type)
    486 		{
    487 			const auto& dump = State::Dumps[static_cast<uint32_t>(_type)];
    488 
    489 			if(dump.idxParamIndexH >= _data.size() || dump.idxParamIndexL >= _data.size())
    490 				return nullptr;
    491 
    492 			auto i = dump.firstParamIndex;
    493 			if (dump.idxParamIndexH != dump.idxParamIndexL)
    494 				i += static_cast<uint32_t>(_data[dump.idxParamIndexH]) << 7;
    495 			i += static_cast<uint32_t>(_data[dump.idxParamIndexL]);
    496 
    497 			if(i > _dump.size())
    498 				return nullptr;
    499 			return &_dump[i];
    500 		}
    501 	}
    502 
    503 	uint8_t* State::getSingleParameter(const SysEx& _data)
    504 	{
    505 		const auto loc = _data[wLib::IdxBuffer];
    506 
    507 		Single* s = getSingle(isMultiMode() ? LocationH::SingleEditBufferMultiMode : LocationH::SingleEditBufferSingleMode, loc);
    508 		if(!s)
    509 			return nullptr;
    510 		return getParameter(*s, _data, DumpType::Single);
    511 	}
    512 
    513 	uint8_t* State::getMultiParameter(const SysEx& _data)
    514 	{
    515 		const auto& dump = Dumps[static_cast<uint8_t>(DumpType::Multi)];
    516 
    517 		const auto idxH = _data[dump.idxParamIndexH];
    518 		const auto idxL = _data[dump.idxParamIndexL];
    519 //		const auto val = _data[dump.idxParamValue];
    520 
    521 		if(idxH == 0x20)
    522 			return &m_currentMulti[dump.firstParamIndex + idxL];
    523 
    524 		constexpr auto inst0 = static_cast<uint8_t>(MultiParameter::Inst0First);
    525 		constexpr auto inst1 = static_cast<uint8_t>(MultiParameter::Inst1First);
    526 
    527 		const auto idx = dump.firstParamIndex + inst0 + idxH * (inst1 - inst0) + idxL;
    528 
    529 		return &m_currentMulti[idx];
    530 	}
    531 
    532 	uint8_t* State::getGlobalParameter(const SysEx& _data)
    533 	{
    534 		return getParameter(m_global, _data, DumpType::Global);
    535 	}
    536 
    537 	uint8_t* State::getModeParameter(const SysEx& _data)
    538 	{
    539 		return getParameter(m_mode, _data, DumpType::Mode);
    540 	}
    541 
    542 	bool State::getSingle(Responses& _responses, const SysEx& _data)
    543 	{
    544 		const auto buf = static_cast<LocationH>(_data[wLib::IdxBuffer]);
    545 		const auto loc = _data[wLib::IdxLocation];
    546 
    547 		const auto* s = getSingle(buf, loc);
    548 		if(!s || !isValid(*s))
    549 			return false;
    550 		_responses.push_back(convertTo(*s));
    551 		return true;
    552 	}
    553 
    554 	State::Single* State::getSingle(LocationH _buf, uint8_t _loc)
    555 	{
    556 		switch (_buf)
    557 		{
    558 		case LocationH::SingleBankA:
    559 			if(_loc >= 128)
    560 				return nullptr;
    561 			return &m_romSingles[_loc];
    562 		case LocationH::SingleBankB:
    563 			if(_loc >= 128)
    564 				return nullptr;
    565 			return &m_romSingles[_loc + 100];
    566 		case LocationH::SingleEditBufferSingleMode:
    567 			m_isEditBuffer = true;
    568 			return m_currentInstrumentSingles.data();
    569 		case LocationH::SingleEditBufferMultiMode:
    570 			{
    571 				m_isEditBuffer = true;
    572 				if(_loc >= m_currentMultiSingles.size())
    573 					return nullptr;
    574 				return &m_currentMultiSingles[_loc];
    575 		}
    576 		default:
    577 			return nullptr;
    578 		}
    579 	}
    580 
    581 	bool State::getMulti(Responses& _responses, const SysEx& _data)
    582 	{
    583 		const auto buf = static_cast<LocationH>(_data[wLib::IdxBuffer]);
    584 		const auto loc = _data[wLib::IdxLocation];
    585 
    586 		const auto* m = getMulti(buf, loc);
    587 		if(!m || !isValid(*m))
    588 			return false;
    589 		_responses.push_back(convertTo(*m));
    590 		return true;
    591 	}
    592 
    593 	State::Multi* State::getMulti(LocationH buf, uint8_t loc)
    594 	{
    595 		switch (buf)
    596 		{
    597 		case LocationH::MultiDumpMultiEditBuffer:
    598 			m_isEditBuffer = true;
    599 			return &m_currentMulti;
    600 		case LocationH::MultiBankA:
    601 			if(loc >= m_romMultis.size())
    602 				return nullptr;
    603 			return &m_romMultis[loc];
    604 		default:
    605 			return nullptr;
    606 		}
    607 	}
    608 
    609 	bool State::getGlobal(Responses& _responses)
    610 	{
    611 		const auto* g = getGlobal();
    612 		if(g == nullptr)
    613 			return false;
    614 		_responses.push_back(convertTo(*g));
    615 		return true;
    616 	}
    617 
    618 	State::Global* State::getGlobal()
    619 	{
    620 		if(isValid(m_global))
    621 		{
    622 			m_isEditBuffer = true;
    623 			return &m_global;
    624 		}
    625 		return nullptr;
    626 	}
    627 
    628 	bool State::getMode(Responses& _responses)
    629 	{
    630 		const auto* m = getMode();
    631 		if(m == nullptr)
    632 			return false;
    633 		_responses.push_back(convertTo(*m));
    634 		return true;
    635 	}
    636 
    637 	State::Mode* State::getMode()
    638 	{
    639 		if(isValid(m_mode))
    640 		{
    641 			m_isEditBuffer = true;
    642 			return &m_mode;
    643 		}
    644 		return nullptr;
    645 	}
    646 
    647 	bool State::getWave(Responses& _responses, const SysEx& _data)
    648 	{
    649 		const auto idx = getWaveId(_data);
    650 
    651 		auto* w = getWave(idx);
    652 		if(!w || !isValid(*w))
    653 			return false;
    654 		_responses.emplace_back(w->begin(), w->end());
    655 		return true;
    656 	}
    657 
    658 	State::Wave* State::getWave(const WaveId _id)
    659 	{
    660 		const auto idx = _id.rawId();
    661 		if(idx >= m_waves.size())
    662 			return nullptr;
    663 		return &m_waves[idx];
    664 	}
    665 
    666 	bool State::getTable(Responses& _responses, const SysEx& _data)
    667 	{
    668 		const auto idx = getTableId(_data);
    669 
    670 		auto* t = getTable(idx);
    671 		if(!t || !isValid(*t))
    672 			return false;
    673 		_responses.emplace_back(t->begin(), t->end());
    674 		return true;
    675 	}
    676 
    677 	State::Table* State::getTable(const TableId _id)
    678 	{
    679 		const auto idx = _id.rawId();
    680 		if(idx >= m_tables.size())
    681 			return nullptr;
    682 		return &m_tables[idx];
    683 	}
    684 
    685 	bool State::getDump(const DumpType _type, Responses& _responses, const SysEx& _data)
    686 	{
    687 		bool res;
    688 
    689 		switch (_type)
    690 		{
    691 		case DumpType::Single: res = getSingle(_responses, _data); break;
    692 		case DumpType::Multi: res = getMulti(_responses, _data); break;
    693 		case DumpType::Global: res = getGlobal(_responses); break;
    694 		case DumpType::Mode: res = getMode(_responses); break;
    695 		case DumpType::Wave: res = getWave(_responses, _data); break;
    696 		case DumpType::Table: res = getTable(_responses, _data); break;
    697 		default:
    698 			return false;
    699 		}
    700 
    701 		if(!res)
    702 			forwardToDevice(_data);
    703 		return true;
    704 	}
    705 
    706 	bool State::parseDump(DumpType _type, const SysEx& _data)
    707 	{
    708 		bool res;
    709 		switch (_type)
    710 		{
    711 		case DumpType::Single: res = parseSingleDump(_data); break;
    712 		case DumpType::Multi: res = parseMultiDump(_data); break;
    713 		case DumpType::Global: res = parseGlobalDump(_data); break;
    714 		case DumpType::Mode: res = parseModeDump(_data); break;
    715 		case DumpType::Wave: res = parseWaveDump(_data); break;
    716 		case DumpType::Table: res = parseTableDump(_data); break;
    717 		default:
    718 			return false;
    719 		}
    720 
    721 		if(res && _type != DumpType::Wave)
    722 			forwardToDevice(_data);
    723 		return res;
    724 	}
    725 
    726 	bool State::modifyDump(DumpType _type, const SysEx& _data)
    727 	{
    728 		bool res;
    729 		switch (_type)
    730 		{
    731 		case DumpType::Single: res = modifySingle(_data); break;
    732 		case DumpType::Multi: res = modifyMulti(_data); break;
    733 		case DumpType::Global: res = modifyGlobal(_data); break;
    734 		case DumpType::Mode: res = modifyMode(_data); break;
    735 		default:
    736 			return false;
    737 		}
    738 		if(res)
    739 			forwardToDevice(_data);
    740 		return res;
    741 	}
    742 
    743 	uint8_t State::getGlobalParameter(const GlobalParameter _parameter) const
    744 	{
    745 		return m_global[static_cast<uint32_t>(_parameter) + IdxGlobalParamFirst];
    746 	}
    747 
    748 	void State::setGlobalParameter(GlobalParameter _parameter, uint8_t _value)
    749 	{
    750 		m_global[static_cast<uint32_t>(_parameter) + IdxGlobalParamFirst] = _value;
    751 	}
    752 
    753 	uint8_t State::getModeParameter(const ModeParameter _parameter) const
    754 	{
    755 		return m_mode[static_cast<uint32_t>(_parameter) + IdxModeParamFirst];
    756 	}
    757 
    758 	SysexCommand State::getCommand(const SysEx& _data)
    759 	{
    760 		if (_data.size() < 5)
    761 			return SysexCommand::Invalid;
    762 
    763 		if (_data.front() != 0xf0 || _data.back() != 0xf7)
    764 			return SysexCommand::Invalid;
    765 
    766 		if (_data[wLib::IdxIdWaldorf] != wLib::IdWaldorf || _data[wLib::IdxIdMachine] != IdMw2)
    767 			return SysexCommand::Invalid;
    768 
    769 		return static_cast<SysexCommand>(_data[wLib::IdxCommand]);
    770 	}
    771 
    772 	TableId State::getTableId(const SysEx& _data)
    773 	{
    774 		if (_data.size() == Mw1::g_tableDumpLength)
    775 			return TableId(_data[5] + wave::g_firstRamTableIndex - Mw1::g_firstRamTableIndex);
    776 		return TableId(static_cast<uint16_t>((_data[IdxWaveIndexH] << 7) | _data[IdxWaveIndexL]));
    777 	}
    778 
    779 	WaveId State::getWaveId(const SysEx& _data)
    780 	{
    781 		if (_data.size() == Mw1::g_waveDumpLength)
    782 		{
    783 			const uint16_t id = 
    784 				static_cast<uint16_t>(_data[5] << 12) | 
    785 				static_cast<uint16_t>(_data[6] << 8) | 
    786 				static_cast<uint16_t>(_data[7] << 4) | 
    787 				static_cast<uint16_t>(_data[8]);
    788 
    789 			return WaveId(static_cast<uint16_t>(id + wave::g_firstRamWaveIndex - Mw1::g_firstRamWaveIndex));
    790 		}
    791 		return WaveId(static_cast<uint16_t>((_data[IdxWaveIndexH] << 7) | _data[IdxWaveIndexL]));
    792 	}
    793 
    794 	bool State::isSpeech(const TableData& _table)
    795 	{
    796 		return _table[0].rawId() == 0xdead && _table[1].rawId() == 0xbeef;
    797 	}
    798 
    799 	bool State::isUpaw(const TableData& _table)
    800 	{
    801 		return _table[0].rawId() == 0x12de && _table[1].rawId() == 0xc0de;
    802 	}
    803 
    804 	void State::forwardToDevice(const SysEx& _data)
    805 	{
    806 		if(m_sender != Origin::External)
    807 			return;
    808 
    809 		sendSysex(_data);
    810 
    811 		switch (getCommand(_data))
    812 		{
    813 			case SysexCommand::WaveDump:
    814 				// there is an annoying bug in the XT
    815 				// A wave that is edited that is part of a table that is used in one of the current singles is not updated
    816 				// The workaround is to send a table dump with different waves and then the one with the correct waves
    817 				{
    818 					const auto waveId = getWaveId(_data);
    819 
    820 					std::set<TableId> dirtyTables;
    821 
    822 					auto checkTable = [&](const TableId _id)
    823 					{
    824 						if (wave::isReadOnly(_id))
    825 							return false;
    826 						const auto idx = _id.rawId();
    827 						if (idx >= m_tables.size())
    828 							return false;
    829 						if (dirtyTables.find(_id) != dirtyTables.end())
    830 							return true;
    831 						const auto& t = m_tables[idx];
    832 						if (!isValid(t))
    833 							return false;
    834 						TableData table;
    835 						if (!parseTableData(table, { t.begin(), t.end() }))
    836 							return false;
    837 						auto waves = getWavesForTable(table);
    838 						const auto it = std::find(waves.begin(), waves.end(), waveId);
    839 						if (it == waves.end())
    840 							return false;
    841 						dirtyTables.insert(_id);
    842 						return true;
    843 					};
    844 
    845 					if (isMultiMode())
    846 					{
    847 						for (auto& s : m_currentMultiSingles)
    848 						{
    849 							const auto tableId = getWavetableFromSingleDump({ s.begin(), s.end() });
    850 							checkTable(tableId);
    851 						}
    852 					}
    853 					else
    854 					{
    855 						const auto tableId = getWavetableFromSingleDump({m_currentInstrumentSingles.front().begin(), m_currentInstrumentSingles.front().end()});
    856 						checkTable(tableId);
    857 					}
    858 
    859 					for (const auto& tableId : dirtyTables)
    860 					{
    861 						const auto& originalTable = m_tables[tableId.rawId()];
    862 						SysEx originalTableSysex = SysEx(originalTable.begin(), originalTable.end());
    863 
    864 						TableData table;
    865 						parseTableData(table, originalTableSysex);
    866 						auto waves = getWavesForTable(table);
    867 
    868 						// modify table to use a different wave
    869 						for (auto& idx : waves)
    870 						{
    871 							if (idx == waveId)
    872 								idx = WaveId(waveId.rawId() > 1000 ? 1000 : 1001);
    873 						}
    874 
    875 						// send the modified table to the device
    876 						auto modifiedTableSysex = createTableData(table, tableId.rawId(), false);
    877 
    878 						sendSysex(std::move(modifiedTableSysex));
    879 
    880 						// after a delay, send the original table again
    881 						constexpr auto delaySamples = static_cast<uint32_t>(40000 * 0.8f);
    882 
    883 						m_delayedCalls.emplace_back(delaySamples, [this, tableId]
    884 						{
    885 							const auto& t = m_tables[tableId.rawId()];
    886 							SysEx s = SysEx(t.begin(), t.end());
    887 							sendSysex(std::move(s));
    888 						});
    889 					}
    890 				}
    891 				break;
    892 		default:;
    893 		}
    894 	}
    895 
    896 	void State::requestGlobal() const
    897 	{
    898 		sendSysex({0xf0, wLib::IdWaldorf, IdMw2, wLib::IdDeviceOmni, static_cast<uint8_t>(SysexCommand::GlobalRequest), 0xf7});
    899 	}
    900 
    901 	void State::requestMode() const
    902 	{
    903 		sendSysex({0xf0, wLib::IdWaldorf, IdMw2, wLib::IdDeviceOmni, static_cast<uint8_t>(SysexCommand::ModeRequest), 0xf7});
    904 	}
    905 
    906 	void State::requestSingle(LocationH _buf, uint8_t _location) const
    907 	{
    908 		sendSysex({0xf0, wLib::IdWaldorf, IdMw2, wLib::IdDeviceOmni, static_cast<uint8_t>(SysexCommand::SingleRequest), static_cast<uint8_t>(_buf), static_cast<uint8_t>(_location), 0xf7});
    909 	}
    910 
    911 	void State::requestMulti(LocationH _buf, uint8_t _location) const
    912 	{
    913 		sendSysex({0xf0, wLib::IdWaldorf, IdMw2, wLib::IdDeviceOmni, static_cast<uint8_t>(SysexCommand::MultiRequest), static_cast<uint8_t>(_buf), _location, 0xf7});
    914 	}
    915 
    916 	void State::sendMulti(const std::vector<uint8_t>& _multiData) const
    917 	{
    918 		std::vector<uint8_t> data = { 0xf0, wLib::IdWaldorf, IdMw2, wLib::IdDeviceOmni, static_cast<uint8_t>(SysexCommand::MultiDump), static_cast<uint8_t>(LocationH::MultiBankA), 0};
    919 		data.insert(data.end(), _multiData.begin(), _multiData.end());
    920 		data.push_back(0x00);
    921 		data.push_back(0xf7);
    922 		updateChecksum(data, IdxMultiChecksumStart);
    923 		sendSysex(std::move(data));
    924 	}
    925 
    926 	void State::sendGlobalParameter(GlobalParameter _param, uint8_t _value)
    927 	{
    928 		setGlobalParameter(_param, _value);
    929 
    930 		const auto p = static_cast<uint8_t>(_param);
    931 
    932 		sendSysex({0xf0, wLib::IdWaldorf, IdMw2, wLib::IdDeviceOmni, static_cast<uint8_t>(SysexCommand::GlobalParameterChange),
    933 			static_cast<uint8_t>(p >> 7), static_cast<uint8_t>(p & 0x7f), _value, 0xf7});
    934 	}
    935 
    936 	void State::sendMultiParameter(const uint8_t _instrument, MultiParameter _param, const uint8_t _value)
    937 	{
    938 		const SysEx sysex{0xf0, wLib::IdWaldorf, IdMw2, wLib::IdDeviceOmni, static_cast<uint8_t>(SysexCommand::MultiParameterChange),
    939 			_instrument, static_cast<uint8_t>(static_cast<uint8_t>(_param) - static_cast<uint8_t>(MultiParameter::Inst0First)), _value, 0xf7};
    940 
    941 		Responses responses;
    942 		receive(responses, sysex, Origin::External);
    943 	}
    944 
    945 	void State::sendSysex(const std::initializer_list<uint8_t>& _data) const
    946 	{
    947 		synthLib::SMidiEvent e(synthLib::MidiEventSource::Internal);
    948 		e.sysex = _data;
    949 		m_xt.sendMidiEvent(e);
    950 	}
    951 
    952 	void State::sendSysex(const SysEx& _data) const
    953 	{
    954 		synthLib::SMidiEvent e(synthLib::MidiEventSource::Internal);
    955 		e.sysex = _data;
    956 		m_xt.sendMidiEvent(e);
    957 	}
    958 
    959 	void State::sendSysex(SysEx&& _data) const
    960 	{
    961 		synthLib::SMidiEvent e(synthLib::MidiEventSource::Internal);
    962 		e.sysex = std::move(_data);
    963 		m_xt.sendMidiEvent(e);
    964 	}
    965 
    966 	void State::createSequencerMultiData(std::vector<uint8_t>& _data)
    967 	{
    968 		assert(false);
    969 		/*
    970 		static_assert(
    971 			(static_cast<uint32_t>(MultiParameter::Inst15) - static_cast<uint32_t>(MultiParameter::Inst0)) ==
    972 			(static_cast<uint32_t>(MultiParameter::Inst1) - static_cast<uint32_t>(MultiParameter::Inst0)) * 15,
    973 			"we need a consecutive offset");
    974 
    975 		_data.assign(static_cast<uint32_t>(xt::MultiParameter::Count), 0);
    976 
    977 		constexpr char name[] = "From TUS with <3";
    978 		static_assert(std::size(name) == 17, "wrong name length");
    979 		memcpy(&_data[static_cast<uint32_t>(MultiParameter::Name00)], name, sizeof(name) - 1);
    980 
    981 		auto setParam = [&](MultiParameter _param, const uint8_t _value)
    982 		{
    983 			_data[static_cast<uint32_t>(_param)] = _value;
    984 		};
    985 
    986 		auto setInstParam = [&](const uint8_t _instIndex, const MultiParameter _param, const uint8_t _value)
    987 		{
    988 			auto index = static_cast<uint32_t>(MultiParameter::Inst0) + (static_cast<uint32_t>(MultiParameter::Inst1) - static_cast<uint32_t>(MultiParameter::Inst0)) * _instIndex;
    989 			index += static_cast<uint32_t>(_param) - static_cast<uint32_t>(MultiParameter::Inst0);
    990 			_data[index] = _value;
    991 		};
    992 
    993 		setParam(MultiParameter::Volume, 127);						// max volume
    994 
    995 		setParam(MultiParameter::ControlW, 121);					// global
    996 		setParam(MultiParameter::ControlX, 121);					// global
    997 		setParam(MultiParameter::ControlY, 121);					// global
    998 		setParam(MultiParameter::ControlZ, 121);					// global
    999 
   1000 		for (uint8_t i = 0; i < 16; ++i)
   1001 		{
   1002 			setInstParam(i, MultiParameter::Inst0SoundBank, 0);	    // bank A
   1003 			setInstParam(i, MultiParameter::Inst0SoundNumber, i);	// sound number i
   1004 			setInstParam(i, MultiParameter::Inst0MidiChannel, 2+i);	// midi channel i
   1005 			setInstParam(i, MultiParameter::Inst0Volume, 127);		// max volume
   1006 			setInstParam(i, MultiParameter::Inst0Transpose, 64);	// no transpose
   1007 			setInstParam(i, MultiParameter::Inst0Detune, 64);		// no detune
   1008 			setInstParam(i, MultiParameter::Inst0Output, 0);		// main out
   1009 			setInstParam(i, MultiParameter::Inst0Flags, 3);			// RX = Local+MIDI / TX = off / Engine = Play
   1010 			setInstParam(i, MultiParameter::Inst0Pan, 64);			// center
   1011 			setInstParam(i, MultiParameter::Inst0Pattern, 0);		// no pattern
   1012 			setInstParam(i, MultiParameter::Inst0VeloLow, 1);		// full velocity range
   1013 			setInstParam(i, MultiParameter::Inst0VeloHigh, 127);
   1014 			setInstParam(i, MultiParameter::Inst0KeyLow, 0);		// full key range
   1015 			setInstParam(i, MultiParameter::Inst0KeyHigh, 127);
   1016 			setInstParam(i, MultiParameter::Inst0MidiRxFlags, 63);	// enable Pitchbend, Modwheel, Aftertouch, Sustain, Button 1/2, Program Change
   1017 		}
   1018 		*/
   1019 	}
   1020 
   1021 	namespace
   1022 	{
   1023 		void extractWaveDataFromSysEx(WaveData& _wave, const SysEx& _sysex, const uint32_t _off)
   1024 		{
   1025 			/*
   1026 				mw2_sysex.pdf:
   1027 
   1028 				"A Wave consists of 128 eight Bit samples, but only the first 64 of them are
   1029 				stored/transmitted, the second half is same as first except the values are
   1030 				negated and the order is reversed:
   1031 
   1032 				Wave[64+n] = -Wave[63-n] for n=0..63
   1033 
   1034 				Note that samples are not two's complement format, to get a signed byte,
   1035 				the most significant bit must be flipped:
   1036 
   1037 				signed char s = Wave[n] ^ 0x80"
   1038 			*/
   1039 
   1040 			for(uint32_t i=0; i<_wave.size()>>1; ++i)
   1041 			{
   1042 				const auto idx = _off + (i<<1);
   1043 				auto sample = (_sysex[idx]) << 4 | _sysex[idx+1];
   1044 				sample = sample ^ 0x80;
   1045 
   1046 				_wave[i] = static_cast<int8_t>(sample);
   1047 				_wave[127-i] = static_cast<int8_t>(-sample);
   1048 			}
   1049 		}
   1050 	}
   1051 
   1052 	bool State::parseWaveData(WaveData& _wave, const SysEx& _sysex)
   1053 	{
   1054 		if(_sysex.size() != std::tuple_size_v<Wave>)
   1055 			return parseMw1WaveData(_wave, _sysex);
   1056 
   1057 		if(_sysex.front() != 0xf0 || _sysex[1] != wLib::IdWaldorf || _sysex[2] != IdMw2)
   1058 			return false;
   1059 
   1060 		if(_sysex[4] != static_cast<uint8_t>(SysexCommand::WaveDump) && _sysex[4] != static_cast<uint8_t>(SysexCommand::WaveDumpP))
   1061 			return false;
   1062 
   1063 		constexpr auto off = 7;
   1064 
   1065 		extractWaveDataFromSysEx(_wave, _sysex, off);
   1066 
   1067 		return true;
   1068 	}
   1069 
   1070 	bool State::parseMw1WaveData(WaveData& _wave, const SysEx& _sysex)
   1071 	{
   1072 		if (_sysex.size() != Mw1::g_waveDumpLength)
   1073 			return false;
   1074 
   1075 		if(_sysex.front() != 0xf0 || _sysex[1] != wLib::IdWaldorf || _sysex[2] != IdMw1)
   1076 			return false;
   1077 
   1078 		if(_sysex[4] != Mw1::g_idmWave)
   1079 			return false;
   1080 		
   1081 		constexpr auto off = 9;
   1082 
   1083 		extractWaveDataFromSysEx(_wave, _sysex, off);
   1084 
   1085 		return true;
   1086 	}
   1087 
   1088 	SysEx State::createWaveData(const WaveData& _wave, const uint16_t _waveIndex, const bool _preview)
   1089 	{
   1090 		const auto hh = static_cast<uint8_t>(_waveIndex >> 7);
   1091 		const auto ll = static_cast<uint8_t>(_waveIndex & 0x7f);
   1092 
   1093 		const std::initializer_list<uint8_t> header{0xf0, wLib::IdWaldorf, IdMw2, wLib::IdDeviceOmni, static_cast<uint8_t>(_preview ? SysexCommand::WaveDumpP : SysexCommand::WaveDump), hh, ll};
   1094 		SysEx sysex{header};
   1095 		sysex.reserve(sysex.size() + _wave.size());
   1096 
   1097 		for(uint32_t i=0; i<_wave.size()>>1; ++i)
   1098 		{
   1099 			const int sample = static_cast<uint8_t>(_wave[i] ^ 0x80);
   1100 
   1101 			sysex.push_back(static_cast<uint8_t>(sample >> 4));
   1102 			sysex.push_back(static_cast<uint8_t>(sample & 0xf));
   1103 		}
   1104 
   1105 		sysex.push_back(0);
   1106 		sysex.push_back(0xf7);
   1107 
   1108 		updateChecksum(sysex, static_cast<uint32_t>(std::size(header)));
   1109 
   1110 		return sysex;
   1111 	}
   1112 
   1113 	WaveData State::createinterpolatedTable(const WaveData& _a, const WaveData& _b, uint16_t _indexA, uint16_t _indexB, uint16_t _indexTarget)
   1114 	{
   1115 		assert(_indexB > _indexA);
   1116 		assert(_indexTarget >= _indexA && _indexTarget <= _indexB);
   1117 
   1118 		xt::WaveData result;
   1119 
   1120 		const auto indexDelta = _indexB - _indexA;
   1121 		const auto targetDelta = _indexTarget - _indexA;
   1122 
   1123 		for(size_t i=0; i<_a.size(); ++i)
   1124 		{
   1125 			auto d = _b[i] - _a[i];
   1126 			d *= targetDelta;
   1127 			d /= indexDelta;
   1128 			d += _a[i];
   1129 			result[i] = static_cast<int8_t>(d);
   1130 		}
   1131 		return result;
   1132 	}
   1133 
   1134 	namespace
   1135 	{
   1136 		void extractTableDataFromSysEx(TableData& _table, const SysEx& _sysex, const uint32_t _off)
   1137 		{
   1138 			for(uint32_t i=0; i<_table.size(); ++i)
   1139 			{
   1140 				const auto i4 = i<<2;
   1141 
   1142 				auto waveIdx = _sysex[i4+_off] << 12;
   1143 				waveIdx |= _sysex[i4+_off+1] << 8;
   1144 				waveIdx |= _sysex[i4+_off+2] << 4;
   1145 				waveIdx |= _sysex[i4+_off+3];
   1146 
   1147 				_table[i] = WaveId(static_cast<uint16_t>(waveIdx));
   1148 			}
   1149 		}
   1150 	}
   1151 
   1152 	bool State::parseTableData(TableData& _table, const SysEx& _sysex)
   1153 	{
   1154 		if(_sysex.size() != std::tuple_size_v<Table>)
   1155 			return parseMw1TableData(_table, _sysex);
   1156 
   1157 		if(_sysex[0] != 0xf0 || _sysex[1] != wLib::IdWaldorf || _sysex[2] != IdMw2)
   1158 			return false;
   1159 
   1160 		if(_sysex[4] != static_cast<uint8_t>(SysexCommand::WaveCtlDump) && _sysex[4] != static_cast<uint8_t>(SysexCommand::WaveCtlDumpP))
   1161 			return false;
   1162 
   1163 		constexpr uint32_t off = 7;
   1164 
   1165 		extractTableDataFromSysEx(_table, _sysex, off);
   1166 
   1167 		return true;
   1168 	}
   1169 
   1170 	bool State::parseMw1TableData(TableData& _table, const SysEx& _sysex)
   1171 	{
   1172 		if (_sysex.size() != Mw1::g_tableDumpLength)
   1173 			return false;
   1174 
   1175 		if(_sysex[0] != 0xf0 || _sysex[1] != wLib::IdWaldorf || _sysex[2] != IdMw1)
   1176 			return false;
   1177 
   1178 		extractTableDataFromSysEx(_table, _sysex, 6);
   1179 
   1180 		if (isSpeech(_table))
   1181 		{
   1182 			_table[2] = WaveId(_table[2].rawId() + wave::g_firstRamWaveIndex - Mw1::g_firstRamWaveIndex);
   1183 		}
   1184 		else
   1185 		{
   1186 			for(auto & t : _table)
   1187 				t = WaveId(t.rawId() + wave::g_firstRamWaveIndex - Mw1::g_firstRamWaveIndex);
   1188 		}
   1189 		return true;
   1190 	}
   1191 
   1192 	std::vector<WaveId> State::getWavesForTable(const TableData& _table)
   1193 	{
   1194 		std::vector<WaveId> waves;
   1195 
   1196 		if (isSpeech(_table))
   1197 		{
   1198 			const auto startWaveId = _table[2];
   1199 
   1200 			waves.reserve(8);
   1201 			for (uint16_t i=0; i<8; ++i)
   1202 				waves.emplace_back(startWaveId.rawId() + i);
   1203 		}
   1204 		else if (isUpaw(_table))
   1205 		{
   1206 			assert(false && "add support");
   1207 		}
   1208 		else
   1209 		{
   1210 			waves.reserve(_table.size());
   1211 			for (const auto& w : _table)
   1212 				waves.push_back(w);
   1213 		}
   1214 
   1215 		return waves;
   1216 	}
   1217 
   1218 	SysEx State::createTableData(const TableData& _table, const uint32_t _tableIndex, const bool _preview)
   1219 	{
   1220 		const auto hh = static_cast<uint8_t>(_tableIndex >> 7);
   1221 		const auto ll = static_cast<uint8_t>(_tableIndex & 0x7f);
   1222 
   1223 		const std::initializer_list<uint8_t> header{0xf0, wLib::IdWaldorf, IdMw2, wLib::IdDeviceOmni, static_cast<uint8_t>(_preview ? SysexCommand::WaveCtlDumpP : SysexCommand::WaveCtlDump), hh, ll};
   1224 		SysEx sysex{header};
   1225 		sysex.reserve(sysex.size() + _table.size() * 4 + 2);
   1226 
   1227 		for (const auto& e : _table)
   1228 		{
   1229 			const auto waveId = e.rawId();
   1230 
   1231 			sysex.push_back((waveId >> 12) & 0xf);
   1232 			sysex.push_back((waveId >> 8 ) & 0xf);
   1233 			sysex.push_back((waveId >> 4 ) & 0xf);
   1234 			sysex.push_back((waveId      ) & 0xf);
   1235 		}
   1236 
   1237 		sysex.push_back(0);
   1238 		sysex.push_back(0xf7);
   1239 
   1240 		updateChecksum(sysex, static_cast<uint32_t>(std::size(header)));
   1241 
   1242 		return sysex;
   1243 	}
   1244 
   1245 	bool State::splitCombinedPatch(std::vector<SysEx>& _dumps, const SysEx& _combinedSingle)
   1246 	{
   1247 		if(getCommand(_combinedSingle) != SysexCommand::SingleDump)
   1248 			return false;
   1249 
   1250 		constexpr auto singleSize = std::tuple_size_v<Single>;
   1251 		constexpr auto tableSize = std::tuple_size_v<Table>;
   1252 		constexpr auto waveSize = std::tuple_size_v<Wave>;
   1253 
   1254 		if(_combinedSingle.size() == singleSize)
   1255 			return false;
   1256 
   1257 		if(_combinedSingle.size() < singleSize + tableSize - 2)
   1258 			return false;
   1259 
   1260 		auto& single = _dumps.emplace_back();
   1261 		size_t offBegin = 0;
   1262 		size_t offEnd = offBegin + singleSize - 1;
   1263 		single.assign(_combinedSingle.begin() + static_cast<ptrdiff_t>(offBegin), _combinedSingle.begin() + static_cast<ptrdiff_t>(offEnd));
   1264 		single.push_back(0xf7);
   1265 
   1266 		auto& table = _dumps.emplace_back();
   1267 
   1268 		offBegin = offEnd;
   1269 		offEnd += tableSize - 2;
   1270 		table.push_back(0xf0);
   1271 		table.insert(table.end(), _combinedSingle.begin() + static_cast<ptrdiff_t>(offBegin), _combinedSingle.begin() + static_cast<ptrdiff_t>(offEnd));
   1272 		table.push_back(0xf7);
   1273 
   1274 		while(_combinedSingle.size() - offEnd >= waveSize - 2)
   1275 		{
   1276 			offBegin = offEnd;
   1277 			offEnd += waveSize - 2;
   1278 
   1279 			auto& wave = _dumps.emplace_back();
   1280 			wave.push_back(0xf0);
   1281 			wave.insert(wave.end(), _combinedSingle.begin() + static_cast<ptrdiff_t>(offBegin), _combinedSingle.begin() + static_cast<ptrdiff_t>(offEnd));
   1282 			wave.push_back(0xf7);
   1283 		}
   1284 
   1285 		return true;
   1286 	}
   1287 
   1288 	SysEx State::createCombinedPatch(const std::vector<SysEx>& _dumps)
   1289 	{
   1290 		uint32_t singleCount = 0;
   1291 		uint32_t tableCount = 0;
   1292 
   1293 		std::vector<SysEx> waves;
   1294 
   1295 		SysEx single;
   1296 		SysEx table;
   1297 
   1298 		for (auto& dump : _dumps)
   1299 		{
   1300 			switch(getCommand(dump))
   1301 			{
   1302 			case SysexCommand::SingleDump:
   1303 				++singleCount;
   1304 				single = dump;
   1305 				break;
   1306 			case SysexCommand::WaveCtlDump:
   1307 				++tableCount;
   1308 				table = dump;
   1309 				break;
   1310 			case SysexCommand::WaveDump:
   1311 				waves.push_back(dump);
   1312 				break;
   1313 			default:;
   1314 			}
   1315 		}
   1316 
   1317 		if(!tableCount && waves.empty())
   1318 			return single;
   1319 
   1320 		if(tableCount > 1)
   1321 			return {};
   1322 
   1323 		// a combined single is a single dump + a table dump + an arbitrary number of wave dumps in one sysex, i.e. f0/f7 are stripped from the individual dumps
   1324 		single.pop_back();
   1325 
   1326 		single.insert(single.end(), table.begin()+1, table.end()-1);
   1327 
   1328 		for (const auto& wave : waves)
   1329 			single.insert(single.end(), wave.begin()+1, wave.end()-1);
   1330 
   1331 		single.push_back(0xf7);
   1332 
   1333 		return single;
   1334 	}
   1335 
   1336 	void State::onPlayModeChanged()
   1337 	{
   1338 		// if the play mode is changed, force a re-request of the edit buffer for the first single again, because on the device, that edit buffer is shared between multi & single
   1339 		m_currentMultiSingles[0][0] = 0;
   1340 		m_currentInstrumentSingles[0][0] = 0;
   1341 
   1342 		// also, as the multi is not valid if the machine is not in multi mode, invalidate the existing data to force a re-request from the device
   1343 		if(isMultiMode())
   1344 			m_currentMulti[0] = 0;
   1345 	}
   1346 }