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

xtPatchManager.cpp (10879B)


      1 #include "xtPatchManager.h"
      2 
      3 #include "xtController.h"
      4 #include "xtEditor.h"
      5 #include "xtWaveEditor.h"
      6 
      7 #include "jucePluginEditorLib/pluginProcessor.h"
      8 
      9 #include "jucePluginLib/filetype.h"
     10 
     11 #include "juceUiLib/messageBox.h"
     12 
     13 #include "xtLib/xtMidiTypes.h"
     14 
     15 namespace xtJucePlugin
     16 {
     17 	static constexpr std::initializer_list<jucePluginEditorLib::patchManager::GroupType> g_groupTypes =
     18 	{
     19 		jucePluginEditorLib::patchManager::GroupType::Favourites,
     20 		jucePluginEditorLib::patchManager::GroupType::LocalStorage,
     21 		jucePluginEditorLib::patchManager::GroupType::DataSources,
     22 	};
     23 
     24 	PatchManager::PatchManager(Editor& _editor, juce::Component* _root)
     25 		: jucePluginEditorLib::patchManager::PatchManager(_editor, _root, g_groupTypes)
     26 		, m_editor(_editor)
     27 		, m_controller(_editor.getXtController())
     28 	{
     29 		setTagTypeName(pluginLib::patchDB::TagType::CustomA, "MW Model");
     30 		jucePluginEditorLib::patchManager::PatchManager::startLoaderThread();
     31 		addGroupTreeItemForTag(pluginLib::patchDB::TagType::CustomA);
     32 	}
     33 
     34 	PatchManager::~PatchManager()
     35 	{
     36 		stopLoaderThread();
     37 	}
     38 
     39 	bool PatchManager::requestPatchForPart(pluginLib::patchDB::Data& _data, const uint32_t _part, uint64_t)
     40 	{
     41 		_data = m_controller.createSingleDump(xt::LocationH::SingleBankA, 0, static_cast<uint8_t>(_part));
     42 		_data = createCombinedDump(_data);
     43 		return !_data.empty();
     44 	}
     45 
     46 	bool PatchManager::loadRomData(pluginLib::patchDB::DataList& _results, uint32_t _bank, uint32_t _program)
     47 	{
     48 		return false;
     49 	}
     50 
     51 	pluginLib::patchDB::PatchPtr PatchManager::initializePatch(pluginLib::patchDB::Data&& _sysex, const std::string& _defaultPatchName)
     52 	{
     53 		if(_sysex.size() == xt::Mw1::g_singleDumpLength)
     54 		{
     55 			if(_sysex[1] == wLib::IdWaldorf && _sysex[2] == xt::IdMw1)
     56 			{
     57 				// MW1 single dump
     58 				auto p = std::make_shared<pluginLib::patchDB::Patch>();
     59 
     60 				p->name.resize(xt::Mw1::g_singleNameLength, ' ');
     61 				memcpy(p->name.data(), &_sysex[xt::Mw1::g_singleNamePosition], xt::Mw1::g_singleNameLength);
     62 				while(p->name.back() == ' ')
     63 					p->name.pop_back();
     64 
     65 				p->sysex = std::move(_sysex);
     66 
     67 				p->tags.add(pluginLib::patchDB::TagType::CustomA, "MW1");
     68 				return p;
     69 			}
     70 		}
     71 
     72 		pluginLib::MidiPacket::Data data;
     73 		pluginLib::MidiPacket::AnyPartParamValues parameters;
     74 
     75 		bool hasUserTable = false;
     76 		bool hasUserWaves = false;
     77 
     78 		if(_sysex.size() > std::tuple_size_v<xt::State::Single>)
     79 		{
     80 			std::vector<xt::SysEx> dumps;
     81 			xt::State::splitCombinedPatch(dumps, _sysex);
     82 
     83 			if(dumps.empty())
     84 				return {};
     85 
     86 			if(!m_controller.parseSingle(data, parameters, dumps.front()))
     87 				return {};
     88 
     89 			if(dumps.size() > 2)
     90 				hasUserWaves = true;
     91 			hasUserTable = true;
     92 		}
     93 		else if(!m_controller.parseSingle(data, parameters, _sysex))
     94 		{
     95 			return {};
     96 		}
     97 
     98 		auto p = std::make_shared<pluginLib::patchDB::Patch>();
     99 
    100 		p->sysex = std::move(_sysex);
    101 		p->name = m_controller.getSingleName(parameters);
    102 
    103 		p->tags.add(pluginLib::patchDB::TagType::CustomA, "MW2");
    104 
    105 		if(hasUserTable)
    106 			p->tags.add(pluginLib::patchDB::TagType::Tag, "UserTable");
    107 
    108 		if(hasUserWaves)
    109 			p->tags.add(pluginLib::patchDB::TagType::Tag, "UserWave");
    110 
    111 		return p;
    112 	}
    113 
    114 	pluginLib::patchDB::Data PatchManager::applyModifications(const pluginLib::patchDB::PatchPtr& _patch, const pluginLib::FileType& _fileType, pluginLib::ExportType _exportType) const
    115 	{
    116 		auto applyModifications = [&_patch](pluginLib::patchDB::Data& _result) -> bool
    117 		{
    118 			if (xt::State::getCommand(_result) != xt::SysexCommand::SingleDump)
    119 				return false;
    120 
    121 			const auto dumpSize = _result.size();
    122 
    123 			if (dumpSize != std::tuple_size_v<xt::State::Single>)
    124 				return false;
    125 
    126 			// apply name
    127 			if (!_patch->getName().empty())
    128 				xt::State::setSingleName(_result, _patch->getName());
    129 
    130 			// apply program
    131 			uint32_t program = 0;
    132 			uint32_t bank = 0;
    133 			if(_patch->program != pluginLib::patchDB::g_invalidProgram)
    134 			{
    135 				program = std::clamp(_patch->program, 0u, 299u);
    136 
    137 				bank = program / 128;
    138 				program -= bank * 128;
    139 			}
    140 
    141 			_result[xt::SysexIndex::IdxSingleBank   ] = static_cast<uint8_t>(bank);
    142 			_result[xt::SysexIndex::IdxSingleProgram] = static_cast<uint8_t>(program);
    143 
    144 			xt::State::updateChecksum(_result, xt::SysexIndex::IdxSingleChecksumStart);
    145 
    146 			return true;
    147 		};
    148 
    149 		if (xt::State::getCommand(_patch->sysex) == xt::SysexCommand::SingleDump)
    150 		{
    151 			auto result = _patch->sysex;
    152 
    153 			if (applyModifications(result))
    154 				return result;
    155 
    156 			std::vector<xt::SysEx> dumps;
    157 
    158 			if (xt::State::splitCombinedPatch(dumps, _patch->sysex))
    159 			{
    160 				if (applyModifications(dumps[0]))
    161 				{
    162 					if (_exportType == pluginLib::ExportType::File)
    163 					{
    164 						// hardware compatibility: multiple sysex
    165 						xt::SysEx r;
    166 
    167 						for (auto& dump : dumps)
    168 							r.insert(r.end(), dump.begin(), dump.end());
    169 
    170 						return r;
    171 					}
    172 
    173 					// emu compatibility: custom patch format, one sysex that includes everything
    174 					return xt::State::createCombinedPatch(dumps);
    175 				}
    176 			}
    177 		}
    178 
    179 		return _patch->sysex;
    180 	}
    181 
    182 	uint32_t PatchManager::getCurrentPart() const
    183 	{
    184 		return m_editor.getProcessor().getController().getCurrentPart();
    185 	}
    186 
    187 	bool PatchManager::activatePatch(const pluginLib::patchDB::PatchPtr& _patch, const uint32_t _part)
    188 	{
    189 		if(!m_controller.sendSingle(applyModifications(_patch, pluginLib::FileType::Empty, pluginLib::ExportType::EmuHardware), static_cast<uint8_t>(_part)))
    190 		{
    191 			genericUI::MessageBox::showOk(juce::MessageBoxIconType::WarningIcon, 
    192 				m_editor.getProcessor().getProperties().name + " - Unable to load patch",
    193 				"MW1 patches can only be loaded to the first part.\n"
    194 				"\n"
    195 				"If you want to load a MW1 patch to another part, first convert it by loading it to part 1, then save the loaded patch to a user bank.");
    196 		}
    197 		return true;
    198 	}
    199 
    200 	bool PatchManager::parseFileData(pluginLib::patchDB::DataList& _results, const pluginLib::patchDB::Data& _data)
    201 	{
    202 		if(!jucePluginEditorLib::patchManager::PatchManager::parseFileData(_results, _data))
    203 			return false;
    204 
    205 		// check if there are MW1 bank dumps. A bank dump is one sysex with multiple patches. Split them into individual preset dumps
    206 		const int resultCount = static_cast<int>(_results.size());
    207 
    208 		for(int r=0; r<resultCount; ++r)
    209 		{
    210 			auto& res = _results[r];
    211 
    212 			if(res.size() < xt::Mw1::g_singleDumpLength)
    213 				continue;
    214 
    215 			if(res[0] != 0xf0 || res[1] != wLib::IdWaldorf || res[2] != xt::IdMw1)
    216 				continue;
    217 
    218 			auto createPreset = [](pluginLib::patchDB::DataList& _res, const std::vector<uint8_t>& _source, size_t _readPos)
    219 			{
    220 				pluginLib::patchDB::Data data;
    221 
    222 				constexpr uint8_t deviceNum = 0;
    223 
    224 				// create single dump preset header
    225 				data.reserve(xt::Mw1::g_singleDumpLength);
    226 				data.assign({0xf0, wLib::IdWaldorf, xt::IdMw1, deviceNum, xt::Mw1::g_idmPreset});
    227 
    228 				// add data
    229 				uint8_t checksum = 0;
    230 
    231 				for(size_t j=0; j<xt::Mw1::g_singleLength; ++j, ++_readPos)
    232 				{
    233 					const auto d = _source[_readPos];
    234 					checksum += d;
    235 					data.push_back(d);
    236 				}
    237 
    238 				// add checksum and EOX. FWIW the XT ignores the checksum anyway
    239 				data.push_back(checksum & 0x7f);
    240 				data.push_back(0xf7);
    241 
    242 				_res.push_back(std::move(data));
    243 				return _readPos;
    244 			};
    245 
    246 			if(res[4] == xt::Mw1::g_idmPresetBank)
    247 			{
    248 				// remove bank from results
    249 				const auto source = std::move(res);
    250 				_results.erase(_results.begin() + r);
    251 				--r;
    252 
    253 				const auto rawDataSize = source.size() - xt::Mw1::g_sysexHeaderSize - xt::Mw1::g_sysexFooterSize;
    254 				const auto presetCount = rawDataSize / xt::Mw1::g_singleLength;
    255 
    256 				size_t readPos = xt::Mw1::g_sysexHeaderSize;
    257 
    258 				_results.reserve(presetCount);
    259 
    260 				for(size_t i=0; i<presetCount; ++i)
    261 					readPos = createPreset(_results, source, readPos);
    262 			}
    263 			else if(res[4] == xt::Mw1::g_idmCartridgeBank)
    264 			{
    265 				// remove bank from results
    266 				const auto source = std::move(res);
    267 				_results.erase(_results.begin() + r);
    268 				--r;
    269 
    270 				size_t readPos = 5;
    271 				_results.reserve(64);
    272 				for(size_t p=0; p<64; ++p)
    273 					readPos = createPreset(_results, source, readPos);
    274 			}
    275 		}
    276 
    277 		createCombinedDumps(_results);
    278 
    279 		return true;
    280 	}
    281 
    282 	pluginLib::patchDB::Data PatchManager::createCombinedDump(const pluginLib::patchDB::Data& _data) const
    283 	{
    284 		// combine single dump with user wave table and user waves if applicable
    285 		if(auto* waveEditor = m_editor.getWaveEditor())
    286 		{
    287 			std::vector<xt::SysEx> results;
    288 			waveEditor->getData().getWaveDataForSingle(results, _data);
    289 			if(!results.empty())
    290 			{
    291 				results.insert(results.begin(), _data);
    292 				auto result = xt::State::createCombinedPatch(results);
    293 				if(!result.empty())
    294 					return result;
    295 			}
    296 		}
    297 		return _data;
    298 	}
    299 
    300 	void PatchManager::createCombinedDumps(std::vector<pluginLib::patchDB::Data>& _messages)
    301 	{
    302 		// grab single dumps, waves and control tables and combine them into our custom single format that includes waves & tables if applicable
    303 
    304 		m_waves.clear();
    305 		m_tables.clear();
    306 		m_singles.clear();
    307 
    308 		// grab waves & tables first, if there are none we can skip everything
    309 		for (auto& msg : _messages)
    310 		{
    311 			auto cmd = xt::State::getCommand(msg);
    312 			switch (cmd)
    313 			{
    314 			case xt::SysexCommand::WaveDump:
    315 				{
    316 					const auto id = xt::State::getWaveId(msg);
    317 					if (m_waves.find(id) == m_waves.end())
    318 					{
    319 						m_waves.emplace(id, std::move(msg));
    320 						msg.clear();
    321 					}
    322 				}
    323 				break;
    324 			case xt::SysexCommand::WaveCtlDump:
    325 				{
    326 					const auto id = xt::State::getTableId(msg);
    327 					if (m_tables.find(id) == m_tables.end())
    328 					{
    329 						m_tables.emplace(id, std::move(msg));
    330 						msg.clear();
    331 					}
    332 				}
    333 				break;
    334 			default:;
    335 			}
    336 		}
    337 
    338 		if (m_tables.empty())
    339 			return;
    340 
    341 		for (auto& msg : _messages)
    342 		{
    343 			auto cmd = xt::State::getCommand(msg);
    344 
    345 			if (cmd != xt::SysexCommand::SingleDump)
    346 				continue;
    347 
    348 			auto table = xt::State::getWavetableFromSingleDump(msg);
    349 
    350 			if (xt::wave::isReadOnly(table))
    351 				continue;
    352 
    353 			std::vector<xt::SysEx> results;
    354 			getWaveDataForSingle(results, msg);
    355 			if (results.empty())
    356 				continue;
    357 
    358 			results.insert(results.begin(), msg);
    359 			auto newSingle = xt::State::createCombinedPatch(results);
    360 			msg.assign(newSingle.begin(), newSingle.end());
    361 		}
    362 	}
    363 
    364 	void PatchManager::getWaveDataForSingle(std::vector<xt::SysEx>& _results, const xt::SysEx& _single) const
    365 	{
    366 		const auto tableId = xt::State::getWavetableFromSingleDump(_single);
    367 
    368 		if(xt::wave::isReadOnly(tableId))
    369 			return;
    370 
    371 		auto itTable = m_tables.find(tableId);
    372 		if (itTable == m_tables.end())
    373 			return;
    374 
    375 		xt::TableData table;
    376 
    377 		if (!xt::State::parseTableData(table, itTable->second))
    378 			return;
    379 
    380 		const auto waves = xt::State::getWavesForTable(table);
    381 
    382 		for (const auto waveId : waves)
    383 		{
    384 			if(!xt::wave::isValidWaveIndex(waveId.rawId()))
    385 				continue;
    386 
    387 			if(xt::wave::isReadOnly(waveId))
    388 				continue;
    389 
    390 			const auto itWave = m_waves.find(waveId);
    391 			if (itWave == m_waves.end())
    392 				continue;
    393 
    394 			const auto wave = itWave->second;
    395 
    396 			_results.emplace_back(wave);
    397 		}
    398 
    399 		_results.emplace_back(itTable->second);
    400 	}
    401 }