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

processor.cpp (23587B)


      1 #include "processor.h"
      2 
      3 #include <chrono>
      4 
      5 #include "dummydevice.h"
      6 #include "pluginVersion.h"
      7 #include "tools.h"
      8 #include "types.h"
      9 
     10 #include "baseLib/binarystream.h"
     11 #include "baseLib/filesystem.h"
     12 
     13 #include "bridgeLib/commands.h"
     14 
     15 #include "client/remoteDevice.h"
     16 
     17 #include "synthLib/deviceException.h"
     18 #include "synthLib/os.h"
     19 #include "synthLib/midiBufferParser.h"
     20 #include "synthLib/romLoader.h"
     21 
     22 #include "dsp56kEmu/fastmath.h"
     23 #include "dsp56kEmu/logging.h"
     24 
     25 #include "juceUiLib/messageBox.h"
     26 
     27 namespace synthLib
     28 {
     29 	class DeviceException;
     30 }
     31 
     32 namespace pluginLib
     33 {
     34 	constexpr char g_saveMagic[] = "DSP56300";
     35 	constexpr uint32_t g_saveVersion = 2;
     36 
     37 	bridgeLib::SessionId generateRemoteSessionId()
     38 	{
     39 		return std::chrono::duration_cast<std::chrono::nanoseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
     40 	}
     41 
     42 	Processor::Processor(const BusesProperties& _busesProperties, Properties _properties)
     43 		: juce::AudioProcessor(_busesProperties)
     44 		, m_properties(std::move(_properties))
     45 		, m_midiPorts(*this)
     46 		, m_remoteSessionId(generateRemoteSessionId())
     47 	{
     48 		juce::File(getPublicRomFolder()).createDirectory();
     49 
     50 		synthLib::RomLoader::addSearchPath(getPublicRomFolder());
     51 		synthLib::RomLoader::addSearchPath(synthLib::getModulePath(true));
     52 		synthLib::RomLoader::addSearchPath(synthLib::getModulePath(false));
     53 	}
     54 
     55 	Processor::~Processor()
     56 	{
     57 		destroyController();
     58 		m_plugin.reset();
     59 		m_device.reset();
     60 	}
     61 
     62 	void Processor::addMidiEvent(const synthLib::SMidiEvent& ev)
     63 	{
     64 		getPlugin().addMidiEvent(ev);
     65 	}
     66 
     67 	void Processor::handleIncomingMidiMessage(juce::MidiInput *_source, const juce::MidiMessage &_message)
     68 	{
     69 		synthLib::SMidiEvent sm(synthLib::MidiEventSource::PhysicalInput);
     70 
     71 		const auto* raw = _message.getSysExData();
     72 		if (raw)
     73 		{
     74 			const auto count = _message.getSysExDataSize();
     75 			auto syx = pluginLib::SysEx();
     76 			syx.push_back(0xf0);
     77 			for (int i = 0; i < count; i++)
     78 			{
     79 				syx.push_back(raw[i]);
     80 			}
     81 			syx.push_back(0xf7);
     82 			sm.sysex = std::move(syx);
     83 
     84 			getController().enqueueMidiMessages({sm});
     85 			addMidiEvent(sm);
     86 		}
     87 		else
     88 		{
     89 			const auto count = _message.getRawDataSize();
     90 			const auto* rawData = _message.getRawData();
     91 			if (count >= 1 && count <= 3)
     92 			{
     93 				sm.a = rawData[0];
     94 				sm.b = count > 1 ? rawData[1] : 0;
     95 				sm.c = count > 2 ? rawData[2] : 0;
     96 			}
     97 			else
     98 			{
     99 				auto syx = SysEx();
    100 				for (int i = 0; i < count; i++)
    101 					syx.push_back(rawData[i]);
    102 				sm.sysex = syx;
    103 			}
    104 
    105 			getController().enqueueMidiMessages({sm});
    106 			addMidiEvent(sm);
    107 		}
    108 	}
    109 
    110 	Controller& Processor::getController()
    111 	{
    112 	    if (m_controller == nullptr)
    113 	        m_controller.reset(createController());
    114 
    115 	    return *m_controller;
    116 	}
    117 
    118 	synthLib::Plugin& Processor::getPlugin()
    119 	{
    120 		if(m_plugin)
    121 			return *m_plugin;
    122 
    123 		try
    124 		{
    125 			m_device.reset(createDevice());
    126 			if(!m_device->isValid())
    127 				throw synthLib::DeviceException(synthLib::DeviceError::Unknown, "Device initialization failed");
    128 		}
    129 		catch(const synthLib::DeviceException& e)
    130 		{
    131 			LOG("Failed to create device: " << e.what());
    132 
    133 			// Juce loads the LV2/VST3 versions of the plugin as part of the build process, if we open a message box in this case, the build process gets stuck
    134 			const auto host = juce::PluginHostType::getHostPath();
    135 			if(!Tools::isHeadless())
    136 			{
    137 				std::string msg = e.what();
    138 
    139 				m_deviceError = e.errorCode();
    140 
    141 				if(e.errorCode() == synthLib::DeviceError::FirmwareMissing)
    142 				{
    143 					msg += "\n\n";
    144 					msg += "The firmware file needs to be copied to\n";
    145 					msg += baseLib::filesystem::validatePath(getPublicRomFolder()) + "\n";
    146 					msg += "\n";
    147 					msg += "The target folder will be opened once you click OK. Copy the firmware to this folder and reload the plugin.";
    148 #ifdef _DEBUG
    149 					msg += "\n\n" + std::string("[Debug] Host ") + host.toStdString() + "\n\n";
    150 #endif
    151 				}
    152 				juce::Timer::callAfterDelay(2000, [this, msg]
    153 				{
    154 					genericUI::MessageBox::showOk(juce::MessageBoxIconType::WarningIcon,
    155 						"Device Initialization failed", msg, 
    156 						[this]
    157 						{
    158 							const auto path = juce::File(getPublicRomFolder());
    159 							(void)path.createDirectory();
    160 							path.revealToUser();
    161 						}
    162 					);
    163 				});
    164 			}
    165 		}
    166 
    167 		if(!m_device)
    168 		{
    169 			m_device.reset(new DummyDevice({}));
    170 		}
    171 
    172 		m_device->setDspClockPercent(m_dspClockPercent);
    173 
    174 		m_plugin.reset(new synthLib::Plugin(m_device.get(), [this](synthLib::Device* _device)
    175 		{
    176 			return onDeviceInvalid(_device);
    177 		}));
    178 
    179 		return *m_plugin;
    180 	}
    181 
    182 	bridgeClient::RemoteDevice* Processor::createRemoteDevice(const synthLib::DeviceCreateParams& _params)
    183 	{
    184 		bridgeLib::PluginDesc desc;
    185 		getPluginDesc(desc);
    186 		return new bridgeClient::RemoteDevice(_params, std::move(desc), m_remoteHost, m_remotePort);
    187 	}
    188 
    189 	void Processor::getRemoteDeviceParams(synthLib::DeviceCreateParams& _params) const
    190 	{
    191 		_params.preferredSamplerate = getPreferredDeviceSamplerate();
    192 		_params.hostSamplerate = getHostSamplerate();
    193 	}
    194 
    195 	bridgeClient::RemoteDevice* Processor::createRemoteDevice()
    196 	{
    197 		synthLib::DeviceCreateParams params;
    198 		getRemoteDeviceParams(params);
    199 		return createRemoteDevice(params);
    200 	}
    201 
    202 	synthLib::Device* Processor::createDevice(const DeviceType _type)
    203 	{
    204 		switch (_type)
    205 		{
    206 		case DeviceType::Local:		return createDevice();
    207 		case DeviceType::Remote:	return createRemoteDevice();
    208 		case DeviceType::Dummy:		return new DummyDevice({});
    209 		}
    210 		return nullptr;
    211 	}
    212 
    213 	bool Processor::setLatencyBlocks(uint32_t _blocks)
    214 	{
    215 		if (!getPlugin().setLatencyBlocks(_blocks))
    216 			return false;
    217 		updateLatencySamples();
    218 		return true;
    219 	}
    220 
    221 	void Processor::updateLatencySamples()
    222 	{
    223 		if(getProperties().isSynth)
    224 			setLatencySamples(getPlugin().getLatencyMidiToOutput());
    225 		else
    226 			setLatencySamples(getPlugin().getLatencyInputToOutput());
    227 	}
    228 
    229 	void Processor::saveCustomData(std::vector<uint8_t>& _targetBuffer)
    230 	{
    231 		baseLib::BinaryStream s;
    232 		saveChunkData(s);
    233 		s.toVector(_targetBuffer, true);
    234 	}
    235 
    236 	void Processor::saveChunkData(baseLib::BinaryStream& s)
    237 	{
    238 		{
    239 			std::vector<uint8_t> buffer;
    240 			getPlugin().getState(buffer, synthLib::StateTypeGlobal);
    241 
    242 			baseLib::ChunkWriter cw(s, "MIDI", 1);
    243 			s.write(buffer);
    244 		}
    245 		{
    246 			baseLib::ChunkWriter cw(s, "GAIN", 1);
    247 			s.write<uint32_t>(1);	// version
    248 			s.write(m_inputGain);
    249 			s.write(m_outputGain);
    250 		}
    251 
    252 		if(m_dspClockPercent != 100)
    253 		{
    254 			baseLib::ChunkWriter cw(s, "DSPC", 1);
    255 			s.write(m_dspClockPercent);
    256 		}
    257 
    258 		if(m_preferredDeviceSamplerate > 0)
    259 		{
    260 			baseLib::ChunkWriter cw(s, "DSSR", 1);
    261 			s.write(m_preferredDeviceSamplerate);
    262 		}
    263 
    264 		m_midiPorts.saveChunkData(s);
    265 	}
    266 
    267 	bool Processor::loadCustomData(const std::vector<uint8_t>& _sourceBuffer)
    268 	{
    269 		if(_sourceBuffer.empty())
    270 			return true;
    271 
    272 		// In Vavra, the only data we had was the gain parameters
    273 		if(_sourceBuffer.size() == sizeof(float) * 2 + sizeof(uint32_t))
    274 		{
    275 			baseLib::BinaryStream ss(_sourceBuffer);
    276 			readGain(ss);
    277 			return true;
    278 		}
    279 
    280 		baseLib::BinaryStream s(_sourceBuffer);
    281 		baseLib::ChunkReader cr(s);
    282 
    283 		loadChunkData(cr);
    284 
    285 		return _sourceBuffer.empty() || (cr.tryRead() && cr.numRead() > 0);
    286 	}
    287 
    288 	void Processor::loadChunkData(baseLib::ChunkReader& _cr)
    289 	{
    290 		_cr.add("MIDI", 1, [this](baseLib::BinaryStream& _binaryStream, uint32_t _version)
    291 		{
    292 			std::vector<uint8_t> buffer;
    293 			_binaryStream.read(buffer);
    294 			getPlugin().setState(buffer);
    295 		});
    296 
    297 		_cr.add("GAIN", 1, [this](baseLib::BinaryStream& _binaryStream, uint32_t _version)
    298 		{
    299 			readGain(_binaryStream);
    300 		});
    301 
    302 		_cr.add("DSPC", 1, [this](baseLib::BinaryStream& _binaryStream, uint32_t _version)
    303 		{
    304 			auto p = _binaryStream.read<uint32_t>();
    305 			p = dsp56k::clamp<uint32_t>(p, 50, 200);
    306 			setDspClockPercent(p);
    307 		});
    308 
    309 		_cr.add("DSSR", 1, [this](baseLib::BinaryStream& _binaryStream, uint32_t _version)
    310 		{
    311 			const auto sr = _binaryStream.read<float>();
    312 			setPreferredDeviceSamplerate(sr);
    313 		});
    314 
    315 		m_midiPorts.loadChunkData(_cr);
    316 	}
    317 
    318 	void Processor::readGain(baseLib::BinaryStream& _s)
    319 	{
    320 		const auto version = _s.read<uint32_t>();
    321 		if (version != 1)
    322 			return;
    323 		m_inputGain = _s.read<float>();
    324 		m_outputGain = _s.read<float>();
    325 	}
    326 
    327 	bool Processor::setDspClockPercent(const uint32_t _percent)
    328 	{
    329 		if(!m_device)
    330 			return false;
    331 		if(!m_device->setDspClockPercent(_percent))
    332 			return false;
    333 		m_dspClockPercent = _percent;
    334 		return true;
    335 	}
    336 
    337 	uint32_t Processor::getDspClockPercent() const
    338 	{
    339 		if(!m_device)
    340 			return m_dspClockPercent;
    341 		return m_device->getDspClockPercent();
    342 	}
    343 
    344 	uint64_t Processor::getDspClockHz() const
    345 	{
    346 		if(!m_device)
    347 			return 0;
    348 		return m_device->getDspClockHz();
    349 	}
    350 
    351 	bool Processor::setPreferredDeviceSamplerate(const float _samplerate)
    352 	{
    353 		m_preferredDeviceSamplerate = _samplerate;
    354 
    355 		if(!m_device)
    356 			return false;
    357 
    358 		return getPlugin().setPreferredDeviceSamplerate(_samplerate);
    359 	}
    360 
    361 	float Processor::getPreferredDeviceSamplerate() const
    362 	{
    363 		return m_preferredDeviceSamplerate;
    364 	}
    365 
    366 	std::vector<float> Processor::getDeviceSupportedSamplerates() const
    367 	{
    368 		if(!m_device)
    369 			return {};
    370 		std::vector<float> result;
    371 		m_device->getSupportedSamplerates(result);
    372 		return result;
    373 	}
    374 
    375 	std::vector<float> Processor::getDevicePreferredSamplerates() const
    376 	{
    377 		if(!m_device)
    378 			return {};
    379 		std::vector<float> result;
    380 		m_device->getPreferredSamplerates(result);
    381 		return result;
    382 	}
    383 
    384 	std::optional<std::pair<const char*, uint32_t>> Processor::findResource(const std::string& _filename) const
    385 	{
    386 		const auto& bd = m_properties.binaryData;
    387 
    388 		for(uint32_t i=0; i<bd.listSize; ++i)
    389 		{
    390 			if (bd.originalFileNames[i] != _filename)
    391 				continue;
    392 
    393 			int size = 0;
    394 			const auto res = bd.getNamedResourceFunc(bd.namedResourceList[i], size);
    395 			return {std::make_pair(res, static_cast<uint32_t>(size))};
    396 		}
    397 		return {};
    398 	}
    399 
    400 	std::string Processor::getDataFolder(const bool _useFxFolder) const
    401 	{
    402 		return Tools::getPublicDataFolder(getProperties().vendor, getProductName(_useFxFolder));
    403 	}
    404 
    405 	std::string Processor::getPublicRomFolder() const
    406 	{
    407 		return baseLib::filesystem::validatePath(getDataFolder() + "roms/");
    408 	}
    409 
    410 	std::string Processor::getConfigFolder(const bool _useFxFolder) const
    411 	{
    412 		return baseLib::filesystem::validatePath(getDataFolder(_useFxFolder) + "config/");
    413 	}
    414 
    415 	std::string Processor::getPatchManagerDataFolder(bool _useFxFolder) const
    416 	{
    417 		return baseLib::filesystem::validatePath(getDataFolder(_useFxFolder) + "patchmanager/");
    418 	}
    419 
    420 	std::string Processor::getConfigFile(const bool _useFxFolder) const
    421 	{
    422 		return getConfigFolder(_useFxFolder) + getProductName(_useFxFolder) + ".xml";
    423 	}
    424 
    425 	std::string Processor::getProductName(const bool _useFxName) const
    426 	{
    427 		const auto& p = getProperties();
    428 		auto name = p.name;
    429 		if(!_useFxName && !p.isSynth && name.substr(name.size()-2, 2) == "FX")
    430 			return name.substr(0, name.size() - 2);
    431 		return name;
    432 	}
    433 
    434 	void Processor::getPluginDesc(bridgeLib::PluginDesc& _desc) const
    435 	{
    436 		_desc.plugin4CC = getProperties().plugin4CC;
    437 		_desc.pluginName = getProperties().name;
    438 		_desc.pluginVersion = Version::getVersionNumber();
    439 		_desc.sessionId = m_remoteSessionId;
    440 	}
    441 
    442 	void Processor::setDeviceType(const DeviceType _type, const bool _forceChange/* = false*/)
    443 	{
    444 		if(m_deviceType == _type && !_forceChange)
    445 			return;
    446 
    447 		try
    448 		{
    449 			if(auto* dev = createDevice(_type))
    450 			{
    451 				getPlugin().setDevice(dev);
    452 				(void)m_device.release();
    453 				m_device.reset(dev);
    454 				m_deviceType = _type;
    455 			}
    456 		}
    457 		catch(synthLib::DeviceException& e)
    458 		{
    459 			genericUI::MessageBox::showOk(juce::MessageBoxIconType::WarningIcon,
    460 				getName().toStdString() + " - Failed to switch device type",
    461 				std::string("Failed to create device:\n\n") + 
    462 				e.what() + "\n\n");
    463 		}
    464 
    465 		if(_type != DeviceType::Remote)
    466 			m_remoteSessionId = generateRemoteSessionId();
    467 	}
    468 
    469 	void Processor::setRemoteDevice(const std::string& _host, const uint32_t _port)
    470 	{
    471 		if(m_remotePort == _port && m_remoteHost == _host && m_deviceType == DeviceType::Remote)
    472 			return;
    473 
    474 		m_remoteHost = _host;
    475 		m_remotePort = _port;
    476 		setDeviceType(DeviceType::Remote, true);
    477 	}
    478 
    479 	void Processor::destroyController()
    480 	{
    481 		m_controller.reset();
    482 	}
    483 
    484 	//==============================================================================
    485 	void Processor::prepareToPlay(double sampleRate, int samplesPerBlock)
    486 	{
    487 		// Use this method as the place to do any pre-playback
    488 		// initialisation that you need
    489 		m_hostSamplerate = static_cast<float>(sampleRate);
    490 
    491 		getPlugin().setHostSamplerate(static_cast<float>(sampleRate), m_preferredDeviceSamplerate);
    492 		getPlugin().setBlockSize(samplesPerBlock);
    493 
    494 		updateLatencySamples();
    495 	}
    496 
    497 	void Processor::releaseResources()
    498 	{
    499 		// When playback stops, you can use this as an opportunity to free up any
    500 		// spare memory, etc.
    501 	}
    502 
    503 	bool Processor::isBusesLayoutSupported(const BusesLayout& _busesLayout) const
    504 	{
    505 	    // This is the place where you check if the layout is supported.
    506 	    // In this template code we only support mono or stereo.
    507 	    // Some plugin hosts, such as certain GarageBand versions, will only
    508 	    // load plugins that support stereo bus layouts.
    509 	    if (_busesLayout.getMainOutputChannelSet() != juce::AudioChannelSet::stereo())
    510 	        return false;
    511 
    512 	    // This checks if the input is stereo
    513 	    if (_busesLayout.getMainInputChannelSet() != juce::AudioChannelSet::stereo())
    514 	        return false;
    515 
    516 	    return true;
    517 	}
    518 
    519 	//==============================================================================
    520 	void Processor::getStateInformation (juce::MemoryBlock& destData)
    521 	{
    522 	    // You should use this method to store your parameters in the memory block.
    523 	    // You could do that either as raw data, or use the XML or ValueTree classes
    524 	    // as intermediaries to make it easy to save and load complex data.
    525 #if !SYNTHLIB_DEMO_MODE
    526 		PluginStream ss;
    527 		ss.write(g_saveMagic);
    528 		ss.write(g_saveVersion);
    529 		std::vector<uint8_t> buffer;
    530 		saveCustomData(buffer);
    531 		ss.write(buffer);
    532 
    533 		std::vector<uint8_t> buf;
    534 		ss.toVector(buf);
    535 
    536 		destData.append(buf.data(), buf.size());
    537 #endif
    538 	}
    539 
    540 	void Processor::setStateInformation (const void* _data, const int _sizeInBytes)
    541 	{
    542 #if !SYNTHLIB_DEMO_MODE
    543 		// You should use this method to restore your parameters from this memory block,
    544 	    // whose contents will have been created by the getStateInformation() call.
    545 		setState(_data, _sizeInBytes);
    546 #endif
    547 	}
    548 
    549 	void Processor::getCurrentProgramStateInformation(juce::MemoryBlock& destData)
    550 	{
    551 #if !SYNTHLIB_DEMO_MODE
    552 		std::vector<uint8_t> state;
    553 		getPlugin().getState(state, synthLib::StateTypeCurrentProgram);
    554 		destData.append(state.data(), state.size());
    555 #endif
    556 	}
    557 
    558 	void Processor::setCurrentProgramStateInformation(const void* data, int sizeInBytes)
    559 	{
    560 #if !SYNTHLIB_DEMO_MODE
    561 		setState(data, sizeInBytes);
    562 #endif
    563 	}
    564 		
    565 	const juce::String Processor::getName() const
    566 	{
    567 	    return getProperties().name;
    568 	}
    569 
    570 	bool Processor::acceptsMidi() const
    571 	{
    572 		return getProperties().wantsMidiInput;
    573 	}
    574 
    575 	bool Processor::producesMidi() const
    576 	{
    577 		return getProperties().producesMidiOut;
    578 	}
    579 
    580 	bool Processor::isMidiEffect() const
    581 	{
    582 		return getProperties().isMidiEffect;
    583 	}
    584 
    585 	void Processor::processBlock(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& midiMessages)
    586 	{
    587 	    juce::ScopedNoDenormals noDenormals;
    588 	    const auto totalNumInputChannels  = getTotalNumInputChannels();
    589 	    const auto totalNumOutputChannels = getTotalNumOutputChannels();
    590 
    591 	    const int numSamples = buffer.getNumSamples();
    592 
    593 	    // In case we have more outputs than inputs, this code clears any output
    594 	    // channels that didn't contain input data, (because these aren't
    595 	    // guaranteed to be empty - they may contain garbage).
    596 	    // This is here to avoid people getting screaming feedback
    597 	    // when they first compile a plugin, but obviously you don't need to keep
    598 	    // this code if your algorithm always overwrites all the output channels.
    599 	    for (auto i = totalNumInputChannels; i < totalNumOutputChannels; ++i)
    600 			buffer.clear (i, 0, numSamples);
    601 
    602 	    // This is the place where you'd normally do the guts of your plugin's
    603 	    // audio processing...
    604 	    // Make sure to reset the state if your inner loop is processing
    605 	    // the samples and the outer loop is handling the channels.
    606 	    // Alternatively, you can process the samples with the channels
    607 	    // interleaved by keeping the same state.
    608 
    609 	    synthLib::TAudioInputs inputs{};
    610 	    synthLib::TAudioOutputs outputs{};
    611 
    612 	    for (int channel = 0; channel < totalNumInputChannels; ++channel)
    613     		inputs[channel] = buffer.getReadPointer(channel);
    614 
    615 	    for (int channel = 0; channel < totalNumOutputChannels; ++channel)
    616     		outputs[channel] = buffer.getWritePointer(channel);
    617 
    618 		for(const auto metadata : midiMessages)
    619 		{
    620 			const auto message = metadata.getMessage();
    621 
    622 			synthLib::SMidiEvent ev(synthLib::MidiEventSource::Host);
    623 
    624 			if(message.isSysEx() || message.getRawDataSize() > 3)
    625 			{
    626 				ev.sysex.resize(message.getRawDataSize());
    627 				memcpy(ev.sysex.data(), message.getRawData(), ev.sysex.size());
    628 
    629 				// Juce bug? Or VSTHost bug? Juce inserts f0/f7 when converting VST3 midi packet to Juce packet, but it's already there
    630 				if(ev.sysex.size() > 1)
    631 				{
    632 					if(ev.sysex.front() == 0xf0 && ev.sysex[1] == 0xf0)
    633 						ev.sysex.erase(ev.sysex.begin());
    634 
    635 					if(ev.sysex.size() > 1)
    636 					{
    637 						if(ev.sysex[ev.sysex.size()-1] == 0xf7 && ev.sysex[ev.sysex.size()-2] == 0xf7)
    638 							ev.sysex.erase(ev.sysex.begin());
    639 					}
    640 				}
    641 			}
    642 			else
    643 			{
    644 				ev.a = message.getRawData()[0];
    645 				ev.b = message.getRawDataSize() > 0 ? message.getRawData()[1] : 0;
    646 				ev.c = message.getRawDataSize() > 1 ? message.getRawData()[2] : 0;
    647 
    648 				const auto status = ev.a & 0xf0;
    649 
    650 				if(status == synthLib::M_CONTROLCHANGE || status == synthLib::M_POLYPRESSURE || status == synthLib::M_PROGRAMCHANGE)
    651 				{
    652 					// forward to UI to react to control input changes that should move knobs
    653 					getController().enqueueMidiMessages({ev});
    654 				}
    655 			}
    656 
    657 			ev.offset = metadata.samplePosition;
    658 
    659 			getPlugin().addMidiEvent(ev);
    660 		}
    661 
    662 		midiMessages.clear();
    663 
    664 		bool isPlaying = true;
    665 		float bpm = 0.0f;
    666 		float ppqPos = 0.0f;
    667 
    668 	    if(const auto* playHead = getPlayHead())
    669 		{
    670 			if(auto pos = playHead->getPosition())
    671 			{
    672 				isPlaying = pos->getIsPlaying();
    673 
    674 				if(pos->getBpm())
    675 				{
    676 					bpm = static_cast<float>(*pos->getBpm());
    677 					processBpm(bpm);
    678 				}
    679 				if(pos->getPpqPosition())
    680 				{
    681 					ppqPos = static_cast<float>(*pos->getPpqPosition());
    682 				}
    683 			}
    684 		}
    685 
    686 		getPlugin().process(inputs, outputs, numSamples, bpm, ppqPos, isPlaying);
    687 
    688 		applyOutputGain(outputs, numSamples);
    689 
    690 		m_midiOut.clear();
    691 		getPlugin().getMidiOut(m_midiOut);
    692 
    693 	    if (!m_midiOut.empty())
    694 		{
    695 			getController().enqueueMidiMessages(m_midiOut);
    696 
    697 		    for (auto& e : m_midiOut)
    698 		    {
    699 			    if (e.source == synthLib::MidiEventSource::Editor || e.source == synthLib::MidiEventSource::Internal)
    700 					continue;
    701 
    702 				auto toJuceMidiMessage = [&e]()
    703 				{
    704 					if(!e.sysex.empty())
    705 					{
    706 						assert(e.sysex.front() == 0xf0);
    707 						assert(e.sysex.back() == 0xf7);
    708 
    709 						return juce::MidiMessage(e.sysex.data(), static_cast<int>(e.sysex.size()), 0.0);
    710 					}
    711 					const auto len = synthLib::MidiBufferParser::lengthFromStatusByte(e.a);
    712 					if(len == 1)
    713 						return juce::MidiMessage(e.a, 0.0);
    714 					if(len == 2)
    715 						return juce::MidiMessage(e.a, e.b, 0.0);
    716 					return juce::MidiMessage(e.a, e.b, e.c, 0.0);
    717 				};
    718 
    719 				const juce::MidiMessage message = toJuceMidiMessage();
    720 				midiMessages.addEvent(message, 0);
    721 
    722 				// additionally send to the midi output we've selected in the editor
    723 				if (auto* out = m_midiPorts.getMidiOutput())
    724 					out->sendMessageNow(message);
    725 		    }
    726 		}
    727 	}
    728 
    729 	void Processor::processBlockBypassed(juce::AudioBuffer<float>& _buffer, juce::MidiBuffer& _midiMessages)
    730 	{
    731 		if(getProperties().isSynth || getTotalNumInputChannels() <= 0)
    732 		{
    733 			_buffer.clear(0, _buffer.getNumSamples());
    734 			return;
    735 		}
    736 
    737 		const auto sampleCount = static_cast<uint32_t>(_buffer.getNumSamples());
    738 		const auto outCount = static_cast<uint32_t>(getTotalNumOutputChannels());
    739 		const auto inCount = static_cast<uint32_t>(getTotalNumInputChannels());
    740 
    741 		uint32_t inCh = 0;
    742 
    743 		for(uint32_t outCh=0; outCh<outCount; ++outCh)
    744 		{
    745 			auto* input = _buffer.getReadPointer(static_cast<int>(inCh));
    746 			auto* output = _buffer.getWritePointer(static_cast<int>(outCh));
    747 
    748 			m_bypassBuffer.write(input, outCh, sampleCount, getLatencySamples());
    749 			m_bypassBuffer.read(output, outCh, sampleCount);
    750 
    751 			++inCh;
    752 
    753 			if(inCh >= inCount)
    754 				inCh = 0;
    755 		}
    756 
    757 //		AudioProcessor::processBlockBypassed(_buffer, _midiMessages);
    758 	}
    759 
    760 #if !SYNTHLIB_DEMO_MODE
    761 	void Processor::setState(const void* _data, const size_t _sizeInBytes)
    762 	{
    763 		if(_sizeInBytes < 1)
    764 			return;
    765 
    766 		std::vector<uint8_t> state;
    767 		state.resize(_sizeInBytes);
    768 		memcpy(state.data(), _data, _sizeInBytes);
    769 
    770 		PluginStream ss(state);
    771 
    772 		if (ss.checkString(g_saveMagic))
    773 		{
    774 			try
    775 			{
    776 				const std::string magic = ss.readString();
    777 
    778 				if (magic != g_saveMagic)
    779 					return;
    780 
    781 				const auto version = ss.read<uint32_t>();
    782 
    783 				if (version > g_saveVersion)
    784 					return;
    785 
    786 				std::vector<uint8_t> buffer;
    787 
    788 				if(version == 1)
    789 				{
    790 					ss.read(buffer);
    791 					getPlugin().setState(buffer);
    792 				}
    793 
    794 				ss.read(buffer);
    795 
    796 				if(!buffer.empty())
    797 				{
    798 					try
    799 					{
    800 						loadCustomData(buffer);
    801 					}
    802 					catch (std::range_error&)
    803 					{
    804 					}
    805 				}
    806 			}
    807 			catch (std::range_error& e)
    808 			{
    809 				LOG("Failed to read state: " << e.what());
    810 				return;
    811 			}
    812 		}
    813 		else
    814 		{
    815 			getPlugin().setState(state);
    816 		}
    817 
    818 		if (hasController())
    819 			getController().onStateLoaded();
    820 	}
    821 #endif
    822 
    823 	//==============================================================================
    824 
    825 	int Processor::getNumPrograms()
    826 	{
    827 		return 1; // NB: some hosts don't cope very well if you tell them there are 0 programs,
    828 				  // so this should be at least 1, even if you're not really implementing programs.
    829 	}
    830 
    831 	int Processor::getCurrentProgram()
    832 	{
    833 		return 0;
    834 	}
    835 
    836 	void Processor::setCurrentProgram(int _index)
    837 	{
    838 		juce::ignoreUnused(_index);
    839 	}
    840 
    841 	const juce::String Processor::getProgramName(int _index)
    842 	{
    843 		juce::ignoreUnused(_index);
    844 		return "default";
    845 	}
    846 
    847 	void Processor::changeProgramName(int _index, const juce::String& _newName)
    848 	{
    849 		juce::ignoreUnused(_index, _newName);
    850 	}
    851 
    852 	double Processor::getTailLengthSeconds() const
    853 	{
    854 		return 0.0f;
    855 	}
    856 
    857 	synthLib::Device* Processor::onDeviceInvalid(synthLib::Device* _device)
    858 	{
    859 		if(dynamic_cast<bridgeClient::RemoteDevice*>(_device))
    860 		{
    861 			try
    862 			{
    863 				// attempt one reconnect
    864 				auto* newDevice = createRemoteDevice();
    865 				if(newDevice && newDevice->isValid())
    866 				{
    867 					m_device.reset(newDevice);
    868 					return newDevice;
    869 				}
    870 			}
    871 			catch (synthLib::DeviceException& e)
    872 			{
    873 				juce::MessageManager::callAsync([e]
    874 				{
    875 					genericUI::MessageBox::showOk(juce::MessageBoxIconType::WarningIcon,
    876 						"Device creation failed:",
    877 						std::string("The connection to the remote server has been lost and a reconnect failed. Processing mode has been switched to local processing\n\n") + 
    878 						e.what() + "\n\n");
    879 				});
    880 			}
    881 		}
    882 
    883 		setDeviceType(DeviceType::Local);
    884 
    885 		juce::MessageManager::callAsync([this]
    886 		{
    887 			getController().onStateLoaded();
    888 		});
    889 
    890 		return m_device.get();
    891 	}
    892 
    893 	bool Processor::rebootDevice()
    894 	{
    895 		try
    896 		{
    897 			synthLib::Device* device = createDevice();
    898 			getPlugin().setDevice(device);
    899 			(void)m_device.release();
    900 			m_device.reset(device);
    901 
    902 			return true;
    903 		}
    904 		catch(const synthLib::DeviceException& e)
    905 		{
    906 			genericUI::MessageBox::showOk(juce::MessageBoxIconType::WarningIcon,
    907 				"Device creation failed:",
    908 				std::string("Failed to create device:\n\n") + 
    909 				e.what() + "\n\n");
    910 			return false;
    911 		}
    912 	}
    913 }