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

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 }