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 }