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

parameterdescriptions.cpp (22353B)


      1 #include "parameterdescriptions.h"
      2 
      3 #include <cassert>
      4 
      5 #include "dsp56kEmu/logging.h"
      6 
      7 #include "synthLib/midiTypes.h"
      8 
      9 namespace pluginLib
     10 {
     11 	ParameterDescriptions::ParameterDescriptions(const std::string& _jsonString)
     12 	{
     13 		m_errors = loadJson(_jsonString);
     14 	}
     15 
     16 	const MidiPacket* ParameterDescriptions::getMidiPacket(const std::string& _name) const
     17 	{
     18 		const auto it = m_midiPackets.find(_name);
     19 		return it == m_midiPackets.end() ? nullptr : &it->second;
     20 	}
     21 
     22 	std::string ParameterDescriptions::removeComments(std::string _json)
     23 	{
     24 		auto removeBlock = [&](const std::string& _begin, const std::string& _end)
     25 		{
     26 			const auto pos = _json.find(_begin);
     27 
     28 			if (pos == std::string::npos)
     29 				return false;
     30 
     31 			const auto end = _json.find(_end, pos + 1);
     32 
     33 			if (end != std::string::npos)
     34 				_json.erase(pos, end - pos + _end.size());
     35 			else
     36 				_json.erase(pos);
     37 
     38 			return true;
     39 		};
     40 
     41 		while (removeBlock("//", "\n") || removeBlock("/*", "*/"))
     42 		{
     43 		}
     44 
     45 		return _json;
     46 	}
     47 
     48 	bool ParameterDescriptions::getIndexByName(uint32_t& _index, const std::string& _name) const
     49 	{
     50 		const auto it = m_nameToIndex.find(_name);
     51 		if(it == m_nameToIndex.end())
     52 			return false;
     53 		_index = it->second;
     54 		return true;
     55 	}
     56 
     57 	const ValueList* ParameterDescriptions::getValueList(const std::string& _key) const
     58 	{
     59 		const auto it = m_valueLists.find(_key);
     60 		if(it == m_valueLists.end())
     61 			return nullptr;
     62 		return &it->second;
     63 	}
     64 
     65 	std::string ParameterDescriptions::loadJson(const std::string& _jsonString)
     66 	{
     67 		// juce' JSON parser doesn't like JSON5-style comments
     68 		const auto jsonString = removeComments(_jsonString);
     69 
     70 		juce::var json;
     71 
     72 		const auto error = juce::JSON::parse(juce::String(jsonString), json);
     73 
     74 		if (error.failed())
     75 			return std::string("Failed to parse JSON: ") + std::string(error.getErrorMessage().toUTF8());
     76 
     77 		const auto paramDescDefaults = json["parameterdescriptiondefaults"].getDynamicObject();
     78 		const auto defaultProperties = paramDescDefaults ? paramDescDefaults->getProperties() : juce::NamedValueSet();
     79 		const auto paramDescs = json["parameterdescriptions"];
     80 
     81 		const auto descsArray = paramDescs.getArray();
     82 
     83 		if (descsArray == nullptr)
     84 			return "Parameter descriptions are empty";
     85 
     86 		std::stringstream errors;
     87 
     88 		{
     89 			const auto& valueLists = json["valuelists"];
     90 
     91 			auto* entries = valueLists.getDynamicObject();
     92 
     93 			if (!entries)
     94 				return "value lists are not defined";
     95 
     96 			auto entryProps = entries->getProperties();
     97 
     98 			for(int i=0; i<entryProps.size(); ++i)
     99 			{
    100 				const auto key = std::string(entryProps.getName(i).toString().toUTF8());
    101 				const auto& values = entryProps.getValueAt(i);
    102 
    103 				const auto result = parseValueList(key, values);
    104 
    105 				if(!result.empty())
    106 					errors << "Failed to parse value list " << key << ": " << result << '\n';
    107 			}
    108 		}
    109 
    110 		const auto& descs = *descsArray;
    111 
    112 		for (int i = 0; i < descs.size(); ++i)
    113 		{
    114 			const auto& desc = descs[i].getDynamicObject();
    115 			const auto props = desc->getProperties();
    116 
    117 			if (props.isEmpty())
    118 			{
    119 				errors << "Parameter desc " << i << " has no properties defined" << std::endl;
    120 				continue;
    121 			}
    122 
    123 			const auto name = props["name"].toString();
    124 			const auto displayName = props["displayName"].toString().toStdString();
    125 
    126 			if (name.isEmpty())
    127 			{
    128 				errors << "Parameter desc " << i << " doesn't have a name" << std::endl;
    129 				continue;
    130 			}
    131 
    132 			auto readProperty = [&](const char* _key, bool _faiIfNotExisting = true)
    133 			{
    134 				auto result = props[_key];
    135 				if (!result.isVoid())
    136 					return result;
    137 				result = defaultProperties[_key];
    138 				if (_faiIfNotExisting && result.isVoid())
    139 					errors << "Property " << _key << " not found for parameter description " << name << " and no default provided" << std::endl;
    140 				return result;
    141 			};
    142 
    143 			auto readPropertyString = [&](const char* _key)
    144 			{
    145 				const auto res = readProperty(_key);
    146 
    147 				if(!res.isString())
    148 					errors << "Property " << _key << " of parameter desc " << name << " is not of type string" << std::endl;
    149 
    150 				return std::string(res.toString().toUTF8());
    151 			};
    152 
    153 			auto readPropertyInt = [&](const char* _key)
    154 			{
    155 				const auto res = readProperty(_key);
    156 
    157 				if (!res.isInt())
    158 					errors << "Property " << _key << " of parameter desc " << name << " is not of type int " << std::endl;
    159 
    160 				return static_cast<int>(res);
    161 			};
    162 
    163 			auto readPropertyIntWithDefault = [&](const char* _key, const int _defaultValue)
    164 			{
    165 				const auto res = readProperty(_key, false);
    166 
    167 				if (!res.isInt())
    168 					return _defaultValue;
    169 
    170 				return static_cast<int>(res);
    171 			};
    172 
    173 			auto readPropertyBool = [&](const char* _key)
    174 			{
    175 				const auto res = readProperty(_key);
    176 
    177 				if (res.isInt())
    178 					return static_cast<int>(res) != 0;
    179 				if (res.isBool())
    180 					return static_cast<bool>(res);
    181 
    182 				errors << "Property " << _key << " of parameter desc " << name << " is not of type bool " << std::endl;
    183 
    184 				return static_cast<bool>(res);
    185 			};
    186 
    187 			const auto minValue = readPropertyInt("min");
    188 			const auto maxValue = readPropertyInt("max");
    189 			const auto defaultValue = readPropertyIntWithDefault("default", Description::NoDefaultValue);
    190 
    191 			if (maxValue < minValue)
    192 			{
    193 				errors << name << ": max value must be larger than min value but min is " << minValue << ", max is " << maxValue << std::endl;
    194 				continue;
    195 			}
    196 
    197 			if(defaultValue != Description::NoDefaultValue && (defaultValue < minValue || defaultValue > maxValue))
    198 			{
    199 				errors << name << ": default value must be within parameter range " << minValue << " to " << maxValue << " but default value is specified as " << defaultValue << std::endl;
    200 				continue;
    201 			}
    202 
    203 			const auto valueList = readPropertyString("toText");
    204 
    205 			const auto it = m_valueLists.find(valueList);
    206 			if(it == m_valueLists.end())
    207 			{
    208 				errors << name << ": Value list " << valueList << " not found" << std::endl;
    209 				continue;
    210 			}
    211 
    212 			const auto& list = *it;
    213 
    214 			if((maxValue - minValue + 1) > static_cast<int>(list.second.texts.size()))
    215 			{
    216 				errors << name << ": value list " << valueList << " contains only " << list.second.texts.size() << " entries but parameter range is " << minValue << "-" << maxValue << std::endl;
    217 			}
    218 
    219 			Description d;
    220 
    221 			d.name = name.toStdString();
    222 			d.displayName = displayName.empty() ? d.name : displayName;
    223 
    224 			d.isPublic = readPropertyBool("isPublic");
    225 			d.isDiscrete = readPropertyBool("isDiscrete");
    226 			d.isBool = readPropertyBool("isBool");
    227 			d.isBipolar = readPropertyBool("isBipolar");
    228 			d.step = readPropertyIntWithDefault("step", 0);
    229 			d.softKnobTargetSelect = readProperty("softknobTargetSelect", false).toString().toStdString();
    230 			d.softKnobTargetList = readProperty("softknobTargetList", false).toString().toStdString();
    231 
    232 			d.toText = valueList;
    233 
    234 			d.page = static_cast<uint8_t>(readPropertyInt("page"));
    235 
    236 			auto index = readPropertyInt("index");
    237 
    238 			while(index >= 128)
    239 			{
    240 				index -= 128;
    241 				++d.page;
    242 			}
    243 			d.index = static_cast<uint8_t>(index);
    244 
    245 			d.range.setStart(minValue);
    246 			d.range.setEnd(maxValue);
    247 
    248 			d.defaultValue = defaultValue;
    249 
    250 			if(defaultValue == Description::NoDefaultValue)
    251 			{
    252 				if(d.isBipolar)
    253 					d.defaultValue = juce::roundToInt((minValue + maxValue) * 0.5f);
    254 				else
    255 					d.defaultValue = minValue;
    256 			}
    257 
    258 			d.valueList = it->second;
    259 
    260 			{
    261 				d.classFlags = 0;
    262 
    263 				const auto classFlags = readPropertyString("class");
    264 
    265 				if(!classFlags.empty())
    266 				{
    267 					std::vector<std::string> flags;
    268 					size_t off = 0;
    269 
    270 					while (true)
    271 					{
    272 						const auto pos = classFlags.find('|', off);
    273 
    274 						if(pos == std::string::npos)
    275 						{
    276 							flags.push_back(classFlags.substr(off));
    277 							break;
    278 						}
    279 
    280 						flags.push_back(classFlags.substr(off, pos - off));
    281 						off = pos + 1;
    282 					}
    283 
    284 					for (const auto & flag : flags)
    285 					{
    286 						if (flag == "Global")
    287 							d.classFlags |= static_cast<int>(ParameterClass::Global);
    288 						else if (flag == "MultiOrSingle")
    289 							d.classFlags |= static_cast<int>(ParameterClass::MultiOrSingle);
    290 						else if (flag == "NonPartSensitive")
    291 							d.classFlags |= static_cast<int>(ParameterClass::NonPartSensitive);
    292 						else
    293 							errors << "Class " << flag << " is unknown" << std::endl;
    294 					}
    295 				}
    296 			}
    297 
    298 			m_descriptions.emplace_back(std::move(d));
    299 		}
    300 
    301 		for (size_t i=0; i<m_descriptions.size(); ++i)
    302 		{
    303 			const auto& d = m_descriptions[i];
    304 
    305 			if(m_nameToIndex.find(d.name) != m_nameToIndex.end())
    306 			{
    307 				errors << "Parameter named " << d.name << " is already defined" << std::endl;
    308 				continue;
    309 			}
    310 			m_nameToIndex.insert(std::make_pair(d.name, static_cast<uint32_t>(i)));
    311 		}
    312 
    313 		// verify soft knob parameters
    314 		for (auto& desc : m_descriptions)
    315 		{
    316 			if(desc.softKnobTargetSelect.empty())
    317 				continue;
    318 
    319 			const auto it = m_nameToIndex.find(desc.softKnobTargetSelect);
    320 
    321 			if(it == m_nameToIndex.end())
    322 			{
    323 				errors << desc.name << ": soft knob target parameter " << desc.softKnobTargetSelect << " not found" << '\n';
    324 				continue;
    325 			}
    326 
    327 			if(desc.softKnobTargetList.empty())
    328 			{
    329 				errors << desc.name << ": soft knob target list not specified\n";
    330 				continue;
    331 			}
    332 
    333 			const auto& targetParam = m_descriptions[it->second];
    334 			auto itList = m_valueLists.find(desc.softKnobTargetList);
    335 			if(itList == m_valueLists.end())
    336 			{
    337 				errors << desc.name << ": soft knob target list '" << desc.softKnobTargetList << "' not found\n";
    338 				continue;
    339 			}
    340 
    341 			const auto& sourceParamNames = itList->second.texts;
    342 			if(static_cast<int>(sourceParamNames.size()) != (targetParam.range.getLength() + 1))
    343 			{
    344 				errors << desc.name << ": soft knob target list " << desc.softKnobTargetList << " has " << sourceParamNames.size() << " entries but target select parameter " << targetParam.name << " has a range of " << (targetParam.range.getLength()+1) << '\n';
    345 				continue;
    346 			}
    347 
    348 			for (const auto& paramName : sourceParamNames)
    349 			{
    350 				if(paramName.empty())
    351 					continue;
    352 
    353 				const auto itsourceParam = m_nameToIndex.find(paramName);
    354 
    355 				if(itsourceParam == m_nameToIndex.end())
    356 				{
    357 					errors << desc.name << " - " << targetParam.name << ": soft knob source parameter " << paramName << " not found" << '\n';
    358 					continue;
    359 				}
    360 			}
    361 		}
    362 
    363 		const auto midipackets = json["midipackets"].getDynamicObject();
    364 		parseMidiPackets(errors, midipackets);
    365 
    366 		const auto regions = json["regions"].getArray();
    367 		parseParameterRegions(errors, regions);
    368 
    369 		const auto controllers = json["controllerMap"].getArray();
    370 		parseControllerMap(errors, controllers);
    371 
    372 		auto res = errors.str();
    373 
    374 		if(!res.empty())
    375 		{
    376 			LOG("ParameterDescription parsing issues:\n" << res);
    377 			assert(false && "failed to parse parameter descriptions");
    378 		}
    379 
    380 		return res;
    381 	}
    382 
    383 
    384 	std::string ParameterDescriptions::parseValueList(const std::string& _key, const juce::var& _values)
    385 	{
    386 		auto valuesArray = _values.getArray();
    387 
    388 		if(m_valueLists.find(_key) != m_valueLists.end())
    389 			return "value list " + _key + " is defined twice";
    390 
    391 		ValueList vl;
    392 
    393 		if(valuesArray)
    394 		{
    395 			if(valuesArray->isEmpty())
    396 				return std::string("value list ") + _key + " is not a valid array of strings";
    397 
    398 			vl.texts.reserve(valuesArray->size());
    399 
    400 			for (auto&& value : *valuesArray)
    401 			{
    402 				const auto text = static_cast<std::string>(value.toString().toUTF8());
    403 				vl.texts.push_back(text);
    404 			}
    405 
    406 			for(uint32_t i=0; i<vl.texts.size(); ++i)
    407 				vl.order.push_back(i);
    408 		}
    409 		else
    410 		{
    411 			const auto valueMap = _values.getDynamicObject();
    412 			if(!valueMap)
    413 			{
    414 				return std::string("value list ") + _key + " neither contains an array of strings nor a map of values => strings";
    415 			}
    416 
    417 			std::set<uint32_t> knownValues;
    418 
    419 			for (const auto& it : valueMap->getProperties())
    420 			{
    421 				const auto keyName = it.name.toString().toStdString();
    422 				const auto& value = it.value;
    423 
    424 				const auto key = std::strtol(keyName.c_str(), nullptr, 10);
    425 
    426 				if(key < 0)
    427 				{
    428 					return std::string("Invalid parameter index '") + keyName + " in value list '" + _key + "', values must be >= 0";
    429 				}
    430 				if(knownValues.find(key) != knownValues.end())
    431 				{
    432 					return std::string("Parameter index '") + keyName + " in value list '" + _key + " has been specified twice";
    433 				}
    434 				knownValues.insert(key);
    435 
    436 				const auto requiredLength = key+1;
    437 
    438 				if(vl.texts.size() < requiredLength)
    439 					vl.texts.resize(requiredLength);
    440 
    441 				vl.texts[key] = value.toString().toStdString();
    442 				vl.order.push_back(key);
    443 			}
    444 		}
    445 
    446 		for(uint32_t i=0; i<vl.texts.size(); ++i)
    447 		{
    448 			const auto& text = vl.texts[i];
    449 
    450 			if (vl.textToValueMap.find(text) == vl.textToValueMap.end())
    451 				vl.textToValueMap.insert(std::make_pair(text, i));
    452 		}
    453 
    454 		m_valueLists.insert(std::make_pair(_key, vl));
    455 
    456 		return {};
    457 	}
    458 
    459 	void ParameterDescriptions::parseMidiPackets(std::stringstream& _errors, juce::DynamicObject* _packets)
    460 	{
    461 		if(!_packets)
    462 			return;
    463 
    464 		const auto entryProps = _packets->getProperties();
    465 
    466 		for(int i=0; i<entryProps.size(); ++i)
    467 		{
    468 			const auto key = std::string(entryProps.getName(i).toString().toUTF8());
    469 			const auto& value = entryProps.getValueAt(i);
    470 
    471 			parseMidiPacket(_errors, key, value);
    472 		}
    473 	}
    474 
    475 	void ParameterDescriptions::parseMidiPacket(std::stringstream& _errors, const std::string& _key, const juce::var& _value)
    476 	{
    477 		if(_key.empty())
    478 		{
    479 			_errors << "midi packet name must not be empty" << std::endl;
    480 			return;
    481 		}
    482 
    483 		if(m_midiPackets.find(_key) != m_midiPackets.end())
    484 		{
    485 			_errors << "midi packet with name " << _key << " is already defined" << std::endl;
    486 			return;
    487 		}
    488 
    489 		const auto arr = _value.getArray();
    490 
    491 		if(!arr)
    492 		{
    493 			_errors << "midi packet " << _key << " is empty" << std::endl;
    494 			return;
    495 		}
    496 
    497 		std::vector<MidiPacket::MidiDataDefinition> bytes;
    498 
    499 		for(auto i=0; i<arr->size(); ++i)
    500 		{
    501 			auto entry = (*arr)[i];
    502 
    503 			auto type = entry["type"].toString().toStdString();
    504 
    505 			MidiPacket::MidiDataDefinition byte;
    506 
    507 			if(type == "byte")
    508 			{
    509 				auto value = entry["value"].toString().toStdString();
    510 
    511 				if(value.empty())
    512 				{
    513 					_errors << "no value specified for type byte, midi packet " << _key << ", index " << i << std::endl;
    514 					return;
    515 				}
    516 
    517 				const auto v = ::strtol(value.c_str(), nullptr, 16);
    518 
    519 				if(v < 0 || v > 0xff)
    520 				{
    521 					_errors << "Midi byte must be in range 0-255" << std::endl;
    522 					return;
    523 				}
    524 
    525 				byte.type = MidiDataType::Byte;
    526 				byte.byte = static_cast<uint8_t>(v);
    527 			}
    528 			else if(type == "param")
    529 			{
    530 				byte.paramName = entry["name"].toString().toStdString();
    531 
    532 				if(byte.paramName.empty())
    533 				{
    534 					_errors << "no parameter name specified for type param, midi packet " << _key << ", index " << i << std::endl;
    535 					return;
    536 				}
    537 
    538 				const auto hasMask = entry.hasProperty("mask");
    539 				const auto hasRightShift = entry.hasProperty("shift");
    540 				const auto hasLeftShift = entry.hasProperty("shiftL");
    541 				const auto hasPart = entry.hasProperty("part");
    542 
    543 				if(hasMask)
    544 				{
    545 					const int mask = strtol(entry["mask"].toString().toStdString().c_str(), nullptr, 16);
    546 					if(mask < 0 || mask > 0xff)
    547 					{
    548 						_errors << "mask needs to be between 00 and ff but got " << std::hex << mask << std::endl;
    549 						return;
    550 					}
    551 					byte.paramMask = static_cast<uint8_t>(mask);
    552 				}
    553 
    554 				if(hasRightShift)
    555 				{
    556 					const int shift = entry["shift"];
    557 					if(shift < 0 || shift > 7)
    558 					{
    559 						_errors << "right shift value needs to be between 0 and 7 but got " << shift << std::endl;
    560 						return;
    561 					}
    562 					byte.paramShiftRight = static_cast<uint8_t>(shift);
    563 				}
    564 
    565 				if(hasLeftShift)
    566 				{
    567 					const int shift = entry["shiftL"];
    568 					if(shift < 0 || shift > 7)
    569 					{
    570 						_errors << "left shift value needs to be between 0 and 7 but got " << shift << std::endl;
    571 						return;
    572 					}
    573 					byte.paramShiftLeft = static_cast<uint8_t>(shift);
    574 				}
    575 
    576 				if(hasPart)
    577 				{
    578 					const int part= entry["part"];
    579 					if(part < 0 || part > 15)
    580 					{
    581 						_errors << "part needs to be between 0 and 15 but got " << part << std::endl;
    582 						return;
    583 					}
    584 					byte.paramPart = static_cast<uint8_t>(part);
    585 				}
    586 
    587 				byte.type = MidiDataType::Parameter;
    588 			}
    589 			else if(type == "checksum")
    590 			{
    591 				const int first = entry["first"];
    592 				const int last = entry["last"];
    593 				const int init = entry["init"];
    594 
    595 				if(first < 0 || last < 0 || last < first)
    596 				{
    597 					_errors << "specified checksum range " << first << "-" << last << " is not valid, midi packet " << _key << ", index " << i << std::endl;
    598 					return;
    599 				}
    600 
    601 				byte.type = MidiDataType::Checksum;
    602 				byte.checksumFirstIndex = first;
    603 				byte.checksumLastIndex = last;
    604 				byte.checksumInitValue = static_cast<uint8_t>(init);
    605 			}
    606 			else if(type == "bank")				byte.type = MidiDataType::Bank;
    607 			else if(type == "program")			byte.type = MidiDataType::Program;
    608 			else if(type == "deviceid")			byte.type = MidiDataType::DeviceId;
    609 			else if(type == "page")				byte.type = MidiDataType::Page;
    610 			else if(type == "part")				byte.type = MidiDataType::Part;
    611 			else if(type == "paramindex")		byte.type = MidiDataType::ParameterIndex;
    612 			else if(type == "paramvalue")		byte.type = MidiDataType::ParameterValue;
    613 			else if(type == "null")				byte.type = MidiDataType::Null;
    614 			else
    615 			{
    616 				_errors << "Unknown midi packet data type '" << type << "', midi packet " << _key << ", index " << i << std::endl;
    617 				return;
    618 			}
    619 
    620 			bytes.push_back(byte);
    621 		}
    622 
    623 		MidiPacket packet(_key, std::move(bytes));
    624 
    625 		bool hasErrors = false;
    626 
    627 		// post-read validation
    628 		for(size_t i=0; i<packet.definitions().size(); ++i)
    629 		{
    630 			const auto& p = packet.definitions()[i];
    631 
    632 			if(p.type == MidiDataType::Checksum)
    633 			{
    634 				if(p.checksumFirstIndex >= (packet.size()-1) || p.checksumLastIndex >= (packet.size()-1))
    635 				{
    636 					_errors << "specified checksum range " << p.checksumFirstIndex << "-" << p.checksumLastIndex << " is out of range 0-" << packet.size() << std::endl;
    637 					return;
    638 				}
    639 			}
    640 			else if(p.type == MidiDataType::Parameter)
    641 			{
    642 				uint32_t index;
    643 
    644 				if(!getIndexByName(index, p.paramName))
    645 				{
    646 					hasErrors = true;
    647 					_errors << "specified parameter " << p.paramName << " does not exist" << std::endl;
    648 				}
    649 			}
    650 		}
    651 
    652 		if(!hasErrors)
    653 			m_midiPackets.insert(std::make_pair(_key, packet));
    654 	}
    655 
    656 	void ParameterDescriptions::parseParameterRegions(std::stringstream& _errors, const juce::Array<juce::var>* _regions)
    657 	{
    658 		if(!_regions)
    659 			return;
    660 
    661 		for (const auto& _region : *_regions)
    662 			parseParameterRegion(_errors, _region);
    663 	}
    664 
    665 	void ParameterDescriptions::parseParameterRegion(std::stringstream& _errors, const juce::var& _value)
    666 	{
    667 		const auto id = _value["id"].toString().toStdString();
    668 		const auto name = _value["name"].toString().toStdString();
    669 		const auto parameters = _value["parameters"].getArray();
    670 		const auto regions = _value["regions"].getArray();
    671 
    672 		if(id.empty())
    673 		{
    674 			_errors << "region needs to have an id\n";
    675 			return;
    676 		}
    677 
    678 		if(m_regions.find(id) != m_regions.end())
    679 		{
    680 			_errors << "region with id '" << id << "' already exists\n";
    681 			return;
    682 		}
    683 
    684 		if(name.empty())
    685 		{
    686 			_errors << "region with id " << id << " needs to have a name\n";
    687 			return;
    688 		}
    689 
    690 		if(!parameters && !regions)
    691 		{
    692 			_errors << "region with id " << id << " needs to at least one parameter or region\n";
    693 			return;
    694 		}
    695 
    696 		std::unordered_map<std::string, const Description*> paramMap;
    697 
    698 		if(parameters)
    699 		{
    700 			const auto& params = *parameters;
    701 
    702 			for (const auto& i : params)
    703 			{
    704 				const auto& param = i.toString().toStdString();
    705 
    706 				if(param.empty())
    707 				{
    708 					_errors << "Empty parameter name in parameter list for region " << id << '\n';
    709 					return;
    710 				}
    711 
    712 				uint32_t idx = 0;
    713 
    714 				if(!getIndexByName(idx, param))
    715 				{
    716 					_errors << "Parameter with name '" << param << "' not found for region " << id << '\n';
    717 					return;
    718 				}
    719 
    720 				const auto* desc = &m_descriptions[idx];
    721 
    722 				if(paramMap.find(param) != paramMap.end())
    723 				{
    724 					_errors << "Parameter with name '" << param << "' has been specified more than once for region " << id << '\n';
    725 					return;
    726 				}
    727 
    728 				paramMap.insert({param, desc});
    729 			}
    730 		}
    731 
    732 		if(regions)
    733 		{
    734 			const auto& regs = *regions;
    735 
    736 			for (const auto& i : regs)
    737 			{
    738 				const auto& reg = i.toString().toStdString();
    739 
    740 				if(reg.empty())
    741 				{
    742 					_errors << "Empty region specified in region '" << id << "'\n";
    743 					return;
    744 				}
    745 
    746 				const auto it = m_regions.find(reg);
    747 
    748 				if(it == m_regions.end())
    749 				{
    750 					_errors << "Region with id '" << reg << "' not found for region '" << id << "'\n";
    751 					return;
    752 				}
    753 
    754 				const auto& region = it->second;
    755 
    756 				const auto& regParams = region.getParams();
    757 
    758 				for (const auto& itParam : regParams)
    759 				{
    760 					if(paramMap.find(itParam.first) == paramMap.end())
    761 						paramMap.insert(itParam);
    762 				}
    763 			}
    764 		}
    765 
    766 		m_regions.insert({id, ParameterRegion(id, name, std::move(paramMap))});
    767 	}
    768 
    769 	void ParameterDescriptions::parseControllerMap(std::stringstream& _errors, const juce::Array<juce::var>* _controllers)
    770 	{
    771 		if(!_controllers)
    772 			return;
    773 
    774 		for (const auto& controller : *_controllers)
    775 			parseController(_errors, controller);
    776 	}
    777 
    778 	void ParameterDescriptions::parseController(std::stringstream& _errors, const juce::var& _value)
    779 	{
    780 		const auto ccStr = _value["cc"].toString().toStdString();
    781 		const auto ppStr = _value["pp"].toString().toStdString();
    782 		const auto paramName = _value["param"].toString().toStdString();
    783 
    784 		if(ccStr.empty() && ppStr.empty())
    785 		{
    786 			_errors << "Controller needs to define control change (cc) or poly pressure (pp) parameter\n";
    787 			return;
    788 		}
    789 
    790 		static constexpr uint8_t Invalid = 0xff;
    791 
    792 		uint8_t cc = Invalid;
    793 		uint8_t pp = Invalid;
    794 
    795 		if(!ccStr.empty())
    796 		{
    797 			cc = static_cast<uint8_t>(::strtol(ccStr.c_str(), nullptr, 10));
    798 			if(cc < 0 || cc > 127)
    799 			{
    800 				_errors << "Controller needs to be in range 0-127, param " << paramName << '\n';
    801 				return;
    802 			}
    803 		}
    804 
    805 		if(!ppStr.empty())
    806 		{
    807 			pp = static_cast<uint8_t>(::strtol(ppStr.c_str(), nullptr, 10));
    808 			if(pp < 0 || pp > 127)
    809 			{
    810 				_errors << "Poly Pressure parameter needs to be in range 0-127, param " << paramName << '\n';
    811 				return;
    812 			}
    813 		}
    814 
    815 		if(paramName.empty())
    816 		{
    817 			_errors << "Target parameter name 'param' must not be empty\n";
    818 			return;
    819 		}
    820 
    821 		uint32_t paramIndex = 0;
    822 
    823 		if(!getIndexByName(paramIndex, paramName))
    824 		{
    825 			_errors << "Parameter with name " << paramName << " not found\n";
    826 			return;
    827 		}
    828 
    829 		if(cc != Invalid)
    830 			m_controllerMap.add(synthLib::M_CONTROLCHANGE, cc, paramIndex);
    831 
    832 		if(pp != Invalid)
    833 			m_controllerMap.add(synthLib::M_POLYPRESSURE, pp, paramIndex);
    834 	}
    835 }