n2xController.cpp (14672B)
1 #include "n2xController.h" 2 3 #include <fstream> 4 5 #include "n2xPatchManager.h" 6 #include "n2xPluginProcessor.h" 7 8 #include "dsp56kEmu/logging.h" 9 10 #include "n2xLib/n2xmiditypes.h" 11 12 #include "synthLib/midiTranslator.h" 13 14 namespace 15 { 16 constexpr const char* g_midiPacketNames[] = 17 { 18 "requestdump", 19 "singledump", 20 "multidump" 21 }; 22 23 static_assert(std::size(g_midiPacketNames) == static_cast<size_t>(n2xJucePlugin::Controller::MidiPacketType::Count)); 24 25 const char* midiPacketName(n2xJucePlugin::Controller::MidiPacketType _type) 26 { 27 return g_midiPacketNames[static_cast<uint32_t>(_type)]; 28 } 29 30 constexpr uint32_t g_multiPage = 10; 31 } 32 33 namespace n2xJucePlugin 34 { 35 Controller::Controller(AudioPluginAudioProcessor& _p) : pluginLib::Controller(_p, "parameterDescriptions_n2x.json"), m_state(nullptr, nullptr) 36 { 37 registerParams(_p, [](const uint8_t _part, const bool _isNonPartExclusive) 38 { 39 if(_isNonPartExclusive) 40 return juce::String(); 41 char temp[2] = {static_cast<char>('A' + _part),0}; 42 return juce::String(temp); 43 }); 44 45 Controller::onStateLoaded(); 46 47 m_currentPartChanged.set(onCurrentPartChanged, [this](const uint8_t& _part) 48 { 49 setMultiParameter(n2x::SelectedChannel, _part); 50 }); 51 } 52 53 Controller::~Controller() = default; 54 55 void Controller::onStateLoaded() 56 { 57 requestDump(n2x::SysexByte::SingleRequestBankEditBuffer, 0); // single edit buffers A-D 58 requestDump(n2x::SysexByte::SingleRequestBankEditBuffer, 1); 59 requestDump(n2x::SysexByte::SingleRequestBankEditBuffer, 2); 60 requestDump(n2x::SysexByte::SingleRequestBankEditBuffer, 3); 61 62 requestDump(n2x::SysexByte::MultiRequestBankEditBuffer, 0); // performance edit buffer 63 64 requestDump(n2x::SysexByte::EmuGetPotsPosition, 0); 65 } 66 67 bool Controller::parseSysexMessage(const pluginLib::SysEx& _msg, synthLib::MidiEventSource _source) 68 { 69 if(n2x::State::isSingleDump(_msg)) 70 { 71 return parseSingleDump(_msg); 72 } 73 if(n2x::State::isMultiDump(_msg)) 74 { 75 return parseMultiDump(_msg); 76 } 77 78 n2x::KnobType knobType; 79 uint8_t knobValue; 80 81 if(n2x::State::parseKnobSysex(knobType, knobValue, _msg)) 82 { 83 if(m_state.receive(_msg, _source)) 84 { 85 onKnobChanged(knobType, knobValue); 86 return true; 87 } 88 } 89 return false; 90 } 91 92 bool Controller::parseSingleDump(const pluginLib::SysEx& _msg) 93 { 94 pluginLib::MidiPacket::Data data; 95 pluginLib::MidiPacket::ParamValues params; 96 97 if(!parseMidiPacket(midiPacketName(MidiPacketType::SingleDump), data, params, _msg)) 98 return false; 99 100 // the read parameters are in range 0-255 but the synth has a range of -128 to 127 (signed byte) 101 for (auto& param : params) 102 param.second = static_cast<int8_t>(param.second); // NOLINT(bugprone-signed-char-misuse) 103 104 const auto bank = data[pluginLib::MidiDataType::Bank]; 105 const auto program = data[pluginLib::MidiDataType::Program]; 106 107 if(bank == n2x::SysexByte::SingleDumpBankEditBuffer && program < getPartCount()) 108 { 109 m_state.receive(_msg, synthLib::MidiEventSource::Plugin); 110 applyPatchParameters(params, program); 111 onProgramChanged(); 112 return true; 113 } 114 115 assert(false && "receiving a single for a non-edit-buffer is unexpected"); 116 return false; 117 } 118 119 bool Controller::parseMultiDump(const pluginLib::SysEx& _msg) 120 { 121 pluginLib::MidiPacket::Data data; 122 pluginLib::MidiPacket::ParamValues params; 123 124 if(!parseMidiPacket(midiPacketName(MidiPacketType::MultiDump), data, params, _msg)) 125 return false; 126 127 // the read parameters are in range 0-255 but the synth has a range of -128 to 127 (signed byte) 128 for (auto& param : params) 129 param.second = static_cast<int8_t>(param.second); // NOLINT(bugprone-signed-char-misuse) 130 131 const auto bank = data[pluginLib::MidiDataType::Bank]; 132 133 if(bank != n2x::SysexByte::MultiDumpBankEditBuffer) 134 return false; 135 136 m_state.receive(_msg, synthLib::MidiEventSource::Plugin); 137 138 applyPatchParameters(params, 0); 139 140 onProgramChanged(); 141 142 const auto part = m_state.getMultiParam(n2x::SelectedChannel, 0); 143 if(part < getPartCount()) // if have seen dumps that have invalid stuff in here 144 { 145 // we ignore this for now, is annoying if the selected part changes whenever we load a multi 146 // setCurrentPart(part); 147 } 148 149 return true; 150 } 151 152 bool Controller::parseControllerMessage(const synthLib::SMidiEvent& _e) 153 { 154 const auto& cm = getParameterDescriptions().getControllerMap(); 155 const auto paramIndices = cm.getControlledParameters(_e); 156 157 if(paramIndices.empty()) 158 return false; 159 160 const auto origin = midiEventSourceToParameterOrigin(_e.source); 161 162 m_state.receive(_e); 163 164 const auto parts = getPartsForMidiEvent(_e); 165 166 for (const uint8_t part : parts) 167 { 168 if(_e.b == n2x::ControlChange::CCSync) 169 { 170 // this controls both Sync and RingMod 171 // Sync = bit 0 172 // RingMod = bit 1 173 auto* paramSync = getParameter("Sync", part); 174 auto* paramRingMod = getParameter("RingMod", part); 175 paramSync->setValueFromSynth(_e.c & 1, origin); 176 paramRingMod->setValueFromSynth((_e.c>>1) & 1, origin); 177 } 178 else 179 { 180 for (const auto paramIndex : paramIndices) 181 { 182 auto* param = getParameter(paramIndex, part); 183 assert(param && "parameter not found for control change"); 184 param->setValueFromSynth(_e.c, origin); 185 } 186 } 187 } 188 189 return true; 190 } 191 192 void Controller::sendParameterChange(const pluginLib::Parameter& _parameter, pluginLib::ParamValue _value) 193 { 194 if(_parameter.getDescription().page >= g_multiPage) 195 { 196 sendMultiParameter(_parameter, static_cast<uint8_t>(_value)); 197 return; 198 } 199 200 constexpr uint32_t sysexRateLimitMs = 150; 201 202 pluginLib::Parameter& nonConstParam = const_cast<pluginLib::Parameter&>(_parameter); 203 204 const auto singleParam = static_cast<n2x::SingleParam>(_parameter.getDescription().index); 205 const uint8_t part = _parameter.getPart(); 206 207 const auto& controllerMap = getParameterDescriptions().getControllerMap(); 208 209 uint32_t descIndex; 210 if(!getParameterDescriptions().getIndexByName(descIndex, _parameter.getDescription().name)) 211 assert(false && "parameter not found"); 212 213 const auto& ccs = controllerMap.getControlChanges(synthLib::M_CONTROLCHANGE, descIndex); 214 if(ccs.empty()) 215 { 216 nonConstParam.setRateLimitMilliseconds(sysexRateLimitMs); 217 setSingleParameter(part, singleParam, static_cast<uint8_t>(_value)); 218 return; 219 } 220 221 const auto cc = ccs.front(); 222 223 if(cc == n2x::ControlChange::CCSync) 224 { 225 // sync and ringmod have the same CC, combine them 226 const auto v = combineSyncRingModDistortion(part, 0, false); 227 _value = v & 3; // strip Distortion, it has its own CC 228 } 229 230 const auto ch = m_state.getPartMidiChannel(part); 231 232 const auto parts = m_state.getPartsForMidiChannel(ch); 233 234 auto ev = synthLib::SMidiEvent{synthLib::MidiEventSource::Editor, static_cast<uint8_t>(synthLib::M_CONTROLCHANGE + part), cc, static_cast<uint8_t>(_value)}; 235 236 nonConstParam.setRateLimitMilliseconds(0); 237 m_state.changeSingleParameter(part, ev); 238 sendMidiEvent(n2x::State::createPartCC(part, ev)); 239 } 240 241 void Controller::setSingleParameter(uint8_t _part, n2x::SingleParam _sp, uint8_t _value) 242 { 243 if(!m_state.changeSingleParameter(_part, _sp, _value)) 244 return; 245 246 const auto& single = m_state.getSingle(_part); 247 auto sysex = pluginLib::SysEx{single.begin(), single.end()}; 248 sysex = n2x::State::validateDump(sysex); 249 pluginLib::Controller::sendSysEx(sysex); 250 } 251 252 void Controller::setMultiParameter(n2x::MultiParam _mp, uint8_t _value) 253 { 254 if(!m_state.changeMultiParameter(_mp, _value)) 255 return; 256 const auto& multi = m_state.updateAndGetMulti(); 257 auto sysex = pluginLib::SysEx{multi.begin(), multi.end()}; 258 sysex = n2x::State::validateDump(sysex); 259 pluginLib::Controller::sendSysEx(sysex); 260 } 261 262 uint8_t Controller::getMultiParameter(const n2x::MultiParam _param) const 263 { 264 return m_state.getMultiParam(_param, 0); 265 } 266 267 void Controller::sendMultiParameter(const pluginLib::Parameter& _parameter, const uint8_t _value) 268 { 269 const auto& desc = _parameter.getDescription(); 270 271 const auto mp = static_cast<n2x::MultiParam>(desc.index + (desc.page - g_multiPage) * 128); 272 273 setMultiParameter(mp, _value); 274 } 275 276 bool Controller::sendSysEx(MidiPacketType _packet, const std::map<pluginLib::MidiDataType, uint8_t>& _params) const 277 { 278 return pluginLib::Controller::sendSysEx(midiPacketName(_packet), _params); 279 } 280 281 void Controller::requestDump(const uint8_t _bank, const uint8_t _patch) const 282 { 283 std::map<pluginLib::MidiDataType, uint8_t> params; 284 285 params[pluginLib::MidiDataType::DeviceId] = n2x::SysexByte::DefaultDeviceId; 286 params[pluginLib::MidiDataType::Bank] = static_cast<uint8_t>(_bank); 287 params[pluginLib::MidiDataType::Program] = _patch; 288 289 sendSysEx(MidiPacketType::RequestDump, params); 290 } 291 292 std::vector<uint8_t> Controller::createSingleDump(uint8_t _bank, uint8_t _program, uint8_t _part) const 293 { 294 pluginLib::MidiPacket::Data data; 295 296 data.insert(std::make_pair(pluginLib::MidiDataType::DeviceId, n2x::SysexByte::DefaultDeviceId)); 297 data.insert(std::make_pair(pluginLib::MidiDataType::Bank, _bank)); 298 data.insert(std::make_pair(pluginLib::MidiDataType::Program, _program)); 299 300 std::vector<uint8_t> dst; 301 302 if (!createMidiDataFromPacket(dst, midiPacketName(MidiPacketType::SingleDump), data, _part)) 303 return {}; 304 305 return dst; 306 } 307 308 std::vector<uint8_t> Controller::createMultiDump(const n2x::SysexByte _bank, const uint8_t _program) 309 { 310 const auto multi = m_state.updateAndGetMulti(); 311 312 std::vector<uint8_t> result(multi.begin(), multi.end()); 313 result = n2x::State::validateDump(result); 314 315 result[n2x::SysexIndex::IdxMsgType] = _bank; 316 result[n2x::SysexIndex::IdxMsgSpec] = _program; 317 318 return result; 319 } 320 321 bool Controller::activatePatch(const std::vector<uint8_t>& _sysex, const uint32_t _part) 322 { 323 if(_part >= getPartCount()) 324 return false; 325 326 const auto part = static_cast<uint8_t>(_part); 327 328 const auto isSingle = n2x::State::isSingleDump(_sysex); 329 const auto isMulti = n2x::State::isMultiDump(_sysex); 330 331 if(!isSingle && !isMulti) 332 return false; 333 334 auto d = _sysex; 335 336 d[n2x::SysexIndex::IdxMsgType] = isSingle ? n2x::SysexByte::SingleDumpBankEditBuffer : n2x::SysexByte::MultiDumpBankEditBuffer; 337 d[n2x::SysexIndex::IdxMsgSpec] = static_cast<uint8_t>(isMulti ? 0 : _part); 338 d[n2x::SysexIndex::IdxDevice] = n2x::DefaultDeviceId; 339 340 auto applyLockedParamsToSingle = [&](n2x::State::SingleDump& _dump, const uint8_t _singlePart) 341 { 342 const auto& lockedParameters = getParameterLocking().getLockedParameters(_singlePart); 343 344 for (auto& lockedParam : lockedParameters) 345 { 346 const auto& name = lockedParam->getDescription().name; 347 348 if(name == "Sync" || name == "RingMod" || name == "Distortion") 349 { 350 const auto current = n2x::State::getSingleParam(_dump, n2x::Sync, 0); 351 const auto value = combineSyncRingModDistortion(_singlePart, current, true); 352 n2x::State::changeSingleParameter(_dump, n2x::Sync, value); 353 } 354 else 355 { 356 const auto singleParam = static_cast<n2x::SingleParam>(lockedParam->getDescription().index); 357 const auto val = lockedParam->getUnnormalizedValue(); 358 n2x::State::changeSingleParameter(_dump, singleParam, static_cast<uint8_t>(val)); 359 } 360 } 361 }; 362 363 if(isSingle) 364 { 365 if(!getParameterLocking().getLockedParameters(part).empty()) 366 { 367 n2x::State::SingleDump dump; 368 std::copy_n(d.begin(), d.size(), dump.begin()); 369 applyLockedParamsToSingle(dump, part); 370 std::copy_n(dump.begin(), d.size(), d.begin()); 371 } 372 } 373 else 374 { 375 n2x::State::MultiDump multi; 376 std::copy_n(d.begin(), d.size(), multi.begin()); 377 for(uint8_t i=0; i<4; ++i) 378 { 379 n2x::State::SingleDump single; 380 381 for(uint8_t p=0; p<4; ++p) 382 { 383 n2x::State::extractSingleFromMulti(single, multi, p); 384 applyLockedParamsToSingle(single, p); 385 n2x::State::copySingleToMulti(multi, single, p); 386 } 387 } 388 std::copy_n(multi.begin(), d.size(), d.begin()); 389 } 390 391 pluginLib::Controller::sendSysEx(n2x::State::validateDump(d)); 392 393 if(isSingle) 394 { 395 requestDump(n2x::SysexByte::SingleRequestBankEditBuffer, static_cast<uint8_t>(_part)); 396 } 397 else 398 { 399 requestDump(n2x::SysexByte::MultiRequestBankEditBuffer, 0); 400 for(uint8_t i=0; i<getPartCount(); ++i) 401 requestDump(n2x::SysexByte::SingleRequestBankEditBuffer, i); 402 } 403 404 return true; 405 } 406 407 bool Controller::isDerivedParameter(pluginLib::Parameter& _derived, pluginLib::Parameter& _base) const 408 { 409 if(_base.getDescription().isNonPartSensitive() || _derived.getDescription().isNonPartSensitive()) 410 return false; 411 412 if(_derived.getParameterIndex() != _base.getParameterIndex()) 413 return false; 414 415 const auto& packetName = midiPacketName(MidiPacketType::SingleDump); 416 const auto* packet = getMidiPacket(packetName); 417 418 if (!packet) 419 { 420 LOG("Failed to find midi packet " << packetName); 421 return true; 422 } 423 424 const auto* defA = packet->getDefinitionByParameterName(_derived.getDescription().name); 425 const auto* defB = packet->getDefinitionByParameterName(_base.getDescription().name); 426 427 if (!defA || !defB) 428 return true; 429 430 return defA->doMasksOverlap(*defB); 431 } 432 433 std::string Controller::getSingleName(const uint8_t _part) const 434 { 435 const auto& single = m_state.getSingle(_part); 436 return PatchManager::getPatchName({single.begin(), single.end()}); 437 } 438 439 std::string Controller::getPatchName(const uint8_t _part) const 440 { 441 const auto& multi = m_state.getMulti(); 442 443 const auto bank = multi[n2x::SysexIndex::IdxMsgType]; 444 if(bank >= n2x::SysexByte::MultiDumpBankA) 445 return PatchManager::getPatchName({multi.begin(), multi.end()}); 446 return getSingleName(_part); 447 } 448 449 bool Controller::getKnobState(uint8_t& _result, const n2x::KnobType _type) const 450 { 451 return m_state.getKnobState(_result, _type); 452 } 453 454 std::vector<uint8_t> Controller::getPartsForMidiChannel(const uint8_t _channel) 455 { 456 return m_state.getPartsForMidiChannel(_channel); 457 } 458 459 uint8_t Controller::combineSyncRingModDistortion(const uint8_t _part, const uint8_t _currentCombinedValue, bool _lockedOnly) 460 { 461 // this controls both Sync and RingMod 462 // Sync = bit 0 463 // RingMod = bit 1 464 // Distortion = bit 4 465 const auto* paramSync = getParameter("Sync", _part); 466 const auto* paramRingMod = getParameter("RingMod", _part); 467 const auto* paramDistortion = getParameter("Distortion", _part); 468 469 auto v = _currentCombinedValue; 470 471 if(!_lockedOnly || getParameterLocking().isParameterLocked(_part, "Sync")) 472 { 473 v &= ~0x01; 474 v |= paramSync->getUnnormalizedValue() & 1; 475 } 476 477 if(!_lockedOnly || getParameterLocking().isParameterLocked(_part, "RingMod")) 478 { 479 v &= ~0x02; 480 v |= (paramRingMod->getUnnormalizedValue() & 1) << 1; 481 } 482 483 if(!_lockedOnly || getParameterLocking().isParameterLocked(_part, "Distortion")) 484 { 485 v &= ~0x10; 486 v |= (paramDistortion->getUnnormalizedValue() & 1) << 4; 487 } 488 489 return v; 490 } 491 }