code:
/*
 * Lv2Proc.cpp - Lv2 processor class
 *
 * Copyright (c) 2019-2020 Johannes Lorenz <jlsf2013$users.sourceforge.net, $=@>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "Lv2Proc.h"

#ifdef LMMS_HAVE_LV2

#include <cmath>
#include <lv2/lv2plug.in/ns/ext/midi/midi.h>
#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
#include <lv2/lv2plug.in/ns/ext/resize-port/resize-port.h>
#include <QDebug>
#include <QtGlobal>

#include "AutomatableModel.h"
#include "ComboBoxModel.h"
#include "Engine.h"
#include "Lv2Features.h"
#include "Lv2Manager.h"
#include "Lv2Ports.h"
#include "Lv2Evbuf.h"
#include "MidiEventToByteSeq.h"
#include "Mixer.h"

// container for everything required to store MIDI events going to the plugin
struct MidiInputEvent
{
	MidiEvent ev;
	TimePos time;
	f_cnt_t offset;
};

Plugin::PluginTypes Lv2Proc::check(const LilvPlugin *plugin,
	std::vector<PluginIssue>& issues)
{
	unsigned maxPorts = lilv_plugin_get_num_ports(plugin);
	enum { inCount, outCount, maxCount };
	unsigned audioChannels[maxCount] = { 0, 0 }; // audio input and output count
	unsigned midiChannels[maxCount] = { 0, 0 }; // MIDI input and output count

	const char* pluginUri = lilv_node_as_uri(lilv_plugin_get_uri(plugin));
	//qDebug() << "Checking plugin" << pluginUri << "...";

	// TODO: manage a global blacklist outside of the code
	//       for now, this will help
	//       this is only a fix for the meantime
	const auto& pluginBlacklist = Lv2Manager::getPluginBlacklist();
	if (!Engine::ignorePluginBlacklist() &&
		pluginBlacklist.find(pluginUri) != pluginBlacklist.end())
	{
		issues.emplace_back(blacklisted);
	}

	for (unsigned portNum = 0; portNum < maxPorts; ++portNum)
	{
		Lv2Ports::Meta meta;
		// does all port checks:
		std::vector<PluginIssue> tmp = meta.get(plugin, portNum);
		std::move(tmp.begin(), tmp.end(), std::back_inserter(issues));

		bool portMustBeUsed =
			!portIsSideChain(plugin,
							lilv_plugin_get_port_by_index(plugin, portNum)) &&
			!meta.m_optional;
		if (meta.m_type == Lv2Ports::Type::Audio && portMustBeUsed)
		{
			++audioChannels[meta.m_flow == Lv2Ports::Flow::Output
				? outCount : inCount];
		}
		else if(meta.m_type == Lv2Ports::Type::AtomSeq && portMustBeUsed)
		{
			++midiChannels[meta.m_flow == Lv2Ports::Flow::Output
				? outCount : inCount];
		}
	}

	if (audioChannels[inCount] > 2)
		issues.emplace_back(tooManyInputChannels,
			std::to_string(audioChannels[inCount]));
	if (audioChannels[outCount] == 0)
		issues.emplace_back(noOutputChannel);
	else if (audioChannels[outCount] > 2)
		issues.emplace_back(tooManyOutputChannels,
			std::to_string(audioChannels[outCount]));

	if (midiChannels[inCount] > 1)
		issues.emplace_back(tooManyMidiInputChannels,
			std::to_string(midiChannels[inCount]));
	if (midiChannels[outCount] > 1)
		issues.emplace_back(tooManyMidiOutputChannels,
			std::to_string(midiChannels[outCount]));

	AutoLilvNodes reqFeats(lilv_plugin_get_required_features(plugin));
	LILV_FOREACH (nodes, itr, reqFeats.get())
	{
		const char* reqFeatName = lilv_node_as_string(
								lilv_nodes_get(reqFeats.get(), itr));
		if(!Lv2Features::isFeatureSupported(reqFeatName))
		{
			issues.emplace_back(featureNotSupported, reqFeatName);
		}
	}

	Lv2Manager* mgr = Engine::getLv2Manager();
	AutoLilvNode requiredOptionNode(mgr->uri(LV2_OPTIONS__requiredOption));
	AutoLilvNodes requiredOptions = mgr->findNodes(lilv_plugin_get_uri (plugin), requiredOptionNode.get(), nullptr);
	if (requiredOptions)
	{
		LILV_FOREACH(nodes, i, requiredOptions.get())
		{
			const char* ro = lilv_node_as_uri (lilv_nodes_get (requiredOptions.get(), i));
			if (!Lv2Options::isOptionSupported(mgr->uridMap().map(ro)))
			{
				// yes, this is not a Lv2 feature,
				// but it's a feature in abstract sense
				issues.emplace_back(featureNotSupported, ro);
			}
		}
	}

	return (audioChannels[inCount] > 2 || audioChannels[outCount] > 2)
		? Plugin::Undefined
		: (audioChannels[inCount] > 0)
			? Plugin::Effect
			: Plugin::Instrument;
}

Lv2Proc::Lv2Proc(const LilvPlugin *plugin, Model* parent) :
	LinkedModelGroup(parent),
	m_plugin(plugin),
	m_midiInputBuf(m_maxMidiInputEvents),
	m_midiInputReader(m_midiInputBuf)
{
	initPlugin();
}

Lv2Proc::~Lv2Proc() { shutdownPlugin(); }

void Lv2Proc::dumpPorts()
{
	std::size_t num = 0;
	for (const std::unique_ptr<Lv2Ports::PortBase>& port: m_ports)
	{
		(void)port;
		dumpPort(num++);
	}
}

void Lv2Proc::copyModelsFromCore()
{
	struct FloatFromModelVisitor : public ConstModelVisitor
	{
		const std::vector<float>* m_scalePointMap; // in
		float m_res; // out
		void visit(const FloatModel& m) override { m_res = m.value(); }
		void visit(const IntModel& m) override {
			m_res = static_cast<float>(m.value()); }
		void visit(const BoolModel& m) override {
			m_res = static_cast<float>(m.value()); }
		void visit(const ComboBoxModel& m) override {
			m_res = (*m_scalePointMap)[static_cast<std::size_t>(m.value())]; }
	};

	struct Copy : public Lv2Ports::Visitor
	{
		void visit(Lv2Ports::Control& ctrl) override
		{
			FloatFromModelVisitor ffm;
			ffm.m_scalePointMap = &ctrl.m_scalePointMap;
			ctrl.m_connectedModel->accept(ffm);
			ctrl.m_val = ffm.m_res;
		}
		void visit(Lv2Ports::Cv& cv) override
		{
			FloatFromModelVisitor ffm;
			ffm.m_scalePointMap = &cv.m_scalePointMap;
			cv.m_connectedModel->accept(ffm);
			// dirty fix, needs better interpolation
			std::fill(cv.m_buffer.begin(), cv.m_buffer.end(), ffm.m_res);
		}
		void visit(Lv2Ports::AtomSeq& atomPort) override
		{
			lv2_evbuf_reset(atomPort.m_buf.get(), true);
		}
	} copy;

	// feed each input port with the respective data from the LMMS core
	for (const std::unique_ptr<Lv2Ports::PortBase>& port : m_ports)
	{
		if (port->m_flow == Lv2Ports::Flow::Input)
		{
			port->accept(copy);
		}
	}

	// send pending MIDI events to atom port
	if(m_midiIn)
	{
		LV2_Evbuf_Iterator iter = lv2_evbuf_begin(m_midiIn->m_buf.get());
		// MIDI events waiting to go to the plugin?
		while(m_midiInputReader.read_space() > 0)
		{
			const MidiInputEvent ev = m_midiInputReader.read(1)[0];
			uint32_t atomStamp =
				ev.time.frames(Engine::framesPerTick()) + ev.offset;
			uint32_t type = Engine::getLv2Manager()->
				uridCache()[Lv2UridCache::Id::midi_MidiEvent];
			uint8_t buf[4];
			std::size_t bufsize = writeToByteSeq(ev.ev, buf, sizeof(buf));
			if(bufsize)
			{
				lv2_evbuf_write(&iter, atomStamp, type, bufsize, buf);
			}
		}
	}
}

void Lv2Proc::copyModelsToCore()
{
	struct Copy : public Lv2Ports::Visitor
	{
		void visit(Lv2Ports::AtomSeq& atomPort) override
		{
			// we currently don't copy anything, but we need to clear the buffer
			// for the plugin to write again
			lv2_evbuf_reset(atomPort.m_buf.get(), false);
		}
	} copy;

	// fetch data from each output port and bring it to the LMMS core
	for (const std::unique_ptr<Lv2Ports::PortBase>& port : m_ports)
	{
		if (port->m_flow == Lv2Ports::Flow::Output)
		{
			port->accept(copy);
		}
	}
}

void Lv2Proc::copyBuffersFromCore(const sampleFrame *buf,
									unsigned firstChan, unsigned num,
									fpp_t frames)
{
	inPorts().m_left->copyBuffersFromCore(buf, firstChan, frames);
	if (num > 1)
	{
		// if the caller requests to take input from two channels, but we only
		// have one input channel... take medium of left and right for
		// mono input
		// (this happens if we have two outputs and only one input)
		if (inPorts().m_right)
		{
			inPorts().m_right->copyBuffersFromCore(buf, firstChan + 1, frames);
		}
		else
		{
			inPorts().m_left->averageWithBuffersFromCore(buf, firstChan + 1, frames);
		}
	}
}

void Lv2Proc::copyBuffersToCore(sampleFrame* buf,
								unsigned firstChan, unsigned num,
								fpp_t frames) const
{
	outPorts().m_left->copyBuffersToCore(buf, firstChan + 0, frames);
	if (num > 1)
	{
		// if the caller requests to copy into two channels, but we only have
		// one output channel, duplicate our output
		// (this happens if we have two inputs and only one output)
		Lv2Ports::Audio* ap = outPorts().m_right
			? outPorts().m_right : outPorts().m_left;
		ap->copyBuffersToCore(buf, firstChan + 1, frames);
	}
}

void Lv2Proc::run(fpp_t frames)
{
	lilv_instance_run(m_instance, static_cast<uint32_t>(frames));
}

// in case there will be a PR which removes this callback and instead adds a
// `ringbuffer_t<MidiEvent + time info>` to `class Instrument`, this
// function (and the ringbuffer and its reader in `Lv2Proc`) will simply vanish
void Lv2Proc::handleMidiInputEvent(const MidiEvent &event, const TimePos &time, f_cnt_t offset)
{
	if(m_midiIn)
	{
		// ringbuffer allows only one writer at a time
		// however, this function can be called by multiple threads
		// (different RT and non-RT!) at the same time
		// for now, a spinlock looks like the most safe/easy compromise

		// source: https://en.cppreference.com/w/cpp/atomic/atomic_flag
		while (m_ringLock.test_and_set(std::memory_order_acquire))  // acquire lock
			 ; // spin

		MidiInputEvent ev { event, time, offset };
		std::size_t written = m_midiInputBuf.write(&ev, 1);
		if(written != 1)
		{
			qWarning("MIDI ringbuffer is too small! Discarding MIDI event.");
		}

		m_ringLock.clear(std::memory_order_release);
	}
	else
	{
		qWarning() << "Warning: Caught MIDI event for an Lv2 instrument"
					<< "that can not hande MIDI... Ignoring";
	}
}

AutomatableModel *Lv2Proc::modelAtPort(const QString &uri)
{
	// unused currently
	AutomatableModel *mod;
	auto itr = m_connectedModels.find(uri.toUtf8().data());
	if (itr != m_connectedModels.end()) { mod = itr->second; }
	else { mod = nullptr; }
	return mod;
}

void Lv2Proc::initPlugin()
{
	m_features.initCommon();
	initPluginSpecificFeatures();
	m_features.createFeatureVectors();

	createPorts();

	m_instance = lilv_plugin_instantiate(m_plugin,
		Engine::mixer()->processingSampleRate(),
		m_features.featurePointers());

	if (m_instance)
	{
		for (std::size_t portNum = 0; portNum < m_ports.size(); ++portNum)
			connectPort(portNum);
		lilv_instance_activate(m_instance);
	}
	else
	{
		qCritical() << "Failed to create an instance of"
			<< qStringFromPluginNode(m_plugin, lilv_plugin_get_name)
			<< "(URI:"
			<< lilv_node_as_uri(lilv_plugin_get_uri(m_plugin))
			<< ")";
		m_valid = false;
	}
}

void Lv2Proc::shutdownPlugin()
{
	if (m_valid)
	{
		lilv_instance_deactivate(m_instance);
		lilv_instance_free(m_instance);
		m_instance = nullptr;
	}
}

bool Lv2Proc::hasNoteInput() const
{
	return m_midiIn;
	// we could additionally check for
	// http://lv2plug.in/ns/lv2core#InstrumentPlugin
	// however, jalv does not do that, too
	// so, if there's any MIDI input, we just assume we can send notes there
}

void Lv2Proc::initMOptions()
{
	/*
		sampleRate:
		LMMS can in theory inform plugins of a new sample rate.
		However, Lv2 plugins seem to not allow sample rate changes
		(not even through LV2_Options_Interface) - it's assumed to be
		fixed after being passed via LV2_Descriptor::instantiate.
		So, if the sampleRate would change, the plugin will need to
		re-initialize, and this code section will be
		executed again, creating a new option vector.
	*/
	float sampleRate = Engine::mixer()->processingSampleRate();
	int32_t blockLength = Engine::mixer()->framesPerPeriod();
	int32_t sequenceSize = defaultEvbufSize();

	using Id = Lv2UridCache::Id;
	m_options.initOption<float>(Id::param_sampleRate, sampleRate);
	m_options.initOption<int32_t>(Id::bufsz_maxBlockLength, blockLength);
	m_options.initOption<int32_t>(Id::bufsz_minBlockLength, blockLength);
	m_options.initOption<int32_t>(Id::bufsz_nominalBlockLength, blockLength);
	m_options.initOption<int32_t>(Id::bufsz_sequenceSize, sequenceSize);
	m_options.createOptionVectors();
}

void Lv2Proc::initPluginSpecificFeatures()
{
	initMOptions();
	m_features[LV2_OPTIONS__options] = const_cast<LV2_Options_Option*>(m_options.feature());
}

void Lv2Proc::loadFileInternal(const QString &file)
{
	(void)file;
}

void Lv2Proc::createPort(std::size_t portNum)
{
	Lv2Ports::Meta meta;
	meta.get(m_plugin, portNum);

	const LilvPort* lilvPort = lilv_plugin_get_port_by_index(m_plugin,
								static_cast<uint32_t>(portNum));
	Lv2Ports::PortBase* port;

	switch (meta.m_type)
	{
		case Lv2Ports::Type::Control:
		{
			Lv2Ports::Control* ctrl = new Lv2Ports::Control;
			if (meta.m_flow == Lv2Ports::Flow::Input)
			{
				AutoLilvNode node(lilv_port_get_name(m_plugin, lilvPort));
				QString dispName = lilv_node_as_string(node.get());
				sample_rate_t sr = Engine::mixer()->processingSampleRate();
				if(meta.def() < meta.min(sr) || meta.def() > meta.max(sr))
				{
					qWarning()	<< "Warning: Plugin"
								<< qStringFromPluginNode(m_plugin, lilv_plugin_get_name)
								<< "(URI:"
								<< lilv_node_as_uri(lilv_plugin_get_uri(m_plugin))
								<< ") has a default value for port"
								<< dispName
								<< "which is not in range [min, max].";
				}
				switch (meta.m_vis)
				{
					case Lv2Ports::Vis::Generic:
					{
						// allow ~1000 steps
						float stepSize = (meta.max(sr) - meta.min(sr)) / 1000.0f;

						// make multiples of 0.01 (or 0.1 for larger values)
						float minStep = (stepSize >= 1.0f) ? 0.1f : 0.01f;
						stepSize -= fmodf(stepSize, minStep);
						stepSize = std::max(stepSize, minStep);

						ctrl->m_connectedModel.reset(
							new FloatModel(meta.def(), meta.min(sr), meta.max(sr),
											stepSize, nullptr, dispName));
						break;
					}
					case Lv2Ports::Vis::Integer:
						ctrl->m_connectedModel.reset(
							new IntModel(static_cast<int>(meta.def()),
											static_cast<int>(meta.min(sr)),
											static_cast<int>(meta.max(sr)),
											nullptr, dispName));
						break;
					case Lv2Ports::Vis::Enumeration:
					{
						ComboBoxModel* comboModel
							= new ComboBoxModel(
								nullptr, dispName);
						LilvScalePoints* sps =
							lilv_port_get_scale_points(m_plugin, lilvPort);
						LILV_FOREACH(scale_points, i, sps)
						{
							const LilvScalePoint* sp = lilv_scale_points_get(sps, i);
							ctrl->m_scalePointMap.push_back(lilv_node_as_float(
											lilv_scale_point_get_value(sp)));
							comboModel->addItem(
								lilv_node_as_string(
									lilv_scale_point_get_label(sp)));
						}
						lilv_scale_points_free(sps);
						ctrl->m_connectedModel.reset(comboModel);
						// TODO: use default value on comboModel, too?
						break;
					}
					case Lv2Ports::Vis::Toggled:
						ctrl->m_connectedModel.reset(
							new BoolModel(static_cast<bool>(meta.def()),
											nullptr, dispName));
						break;
				}
				if(meta.m_logarithmic)
				{
					ctrl->m_connectedModel->setScaleLogarithmic();
				}

			} // if m_flow == Input
			port = ctrl;
			break;
		}
		case Lv2Ports::Type::Audio:
		{
			Lv2Ports::Audio* audio =
				new Lv2Ports::Audio(
						static_cast<std::size_t>(
							Engine::mixer()->framesPerPeriod()),
						portIsSideChain(m_plugin, lilvPort)
					);
			port = audio;
			break;
		}
		case Lv2Ports::Type::AtomSeq:
		{
			Lv2Ports::AtomSeq* atomPort = new Lv2Ports::AtomSeq;

			{
				AutoLilvNode uriAtomSupports(Engine::getLv2Manager()->uri(LV2_ATOM__supports));
				AutoLilvNodes atomSupports(lilv_port_get_value(m_plugin, lilvPort, uriAtomSupports.get()));
				AutoLilvNode uriMidiEvent(Engine::getLv2Manager()->uri(LV2_MIDI__MidiEvent));

				LILV_FOREACH (nodes, itr, atomSupports.get())
				{
					if(lilv_node_equals(lilv_nodes_get(atomSupports.get(), itr), uriMidiEvent.get()))
					{
						atomPort->flags |= Lv2Ports::AtomSeq::FlagType::Midi;
					}
				}
			}

			int minimumSize = defaultEvbufSize();

			Lv2Manager* mgr = Engine::getLv2Manager();

			// check for alternative minimum size
			{
				AutoLilvNode rszMinimumSize = mgr->uri(LV2_RESIZE_PORT__minimumSize);
				AutoLilvNodes minSizeV(lilv_port_get_value(m_plugin, lilvPort, rszMinimumSize.get()));
				LilvNode* minSize = minSizeV ? lilv_nodes_get_first(minSizeV.get()) : nullptr;
				if (minSize && lilv_node_is_int(minSize)) {
					minimumSize = std::max(minimumSize, lilv_node_as_int(minSize));
				}
			}

			atomPort->m_buf.reset(
				lv2_evbuf_new(static_cast<uint32_t>(minimumSize),
								mgr->uridMap().map(LV2_ATOM__Chunk),
								mgr->uridMap().map(LV2_ATOM__Sequence)));

			port = atomPort;
			break;
		}
		default:
			port = new Lv2Ports::Unknown;
	}

	// `meta` is of class `Lv2Ports::Meta` and `port` is of a child class
	// we can now assign the `Lv2Ports::Meta` part of meta to ports, leaving
	// the additional members of `port` unchanged
	*static_cast<Lv2Ports::Meta*>(port) = meta;
	port->m_port = lilvPort;
	port->m_plugin = m_plugin;

	m_ports[portNum].reset(port);
}

void Lv2Proc::createPorts()
{
	// register ports at the processor after creation,
	// i.e. link their data or count them
	struct RegisterPort : public Lv2Ports::Visitor
	{
		Lv2Proc* m_proc;

		void visit(Lv2Ports::Control& ctrl) override
		{
			if (ctrl.m_flow == Lv2Ports::Flow::Input)
			{
				AutomatableModel* amo = ctrl.m_connectedModel.get();
				m_proc->m_connectedModels.emplace(
					lilv_node_as_string(lilv_port_get_symbol(
										m_proc->m_plugin, ctrl.m_port)),
					amo);
				m_proc->addModel(amo, ctrl.uri());
			}
		}

		void visit(Lv2Ports::Audio& audio) override
		{
			if (audio.mustBeUsed())
			{
				StereoPortRef dummy;
				StereoPortRef* portRef = &dummy;
				switch (audio.m_flow)
				{
					case Lv2Ports::Flow::Input:
						portRef = &m_proc->m_inPorts;
						break;
					case Lv2Ports::Flow::Output:
						portRef = &m_proc->m_outPorts;
						break;
					case Lv2Ports::Flow::Unknown:
						break;
				}
				// in Lv2, leftPort is defined to be the first port
				if (!portRef->m_left) { portRef->m_left = &audio; }
				else if (!portRef->m_right) { portRef->m_right = &audio; }
			}
		}

		void visit(Lv2Ports::AtomSeq& atomPort) override
		{
			if(atomPort.m_flow == Lv2Ports::Flow::Input)
			{
				if(atomPort.flags & Lv2Ports::AtomSeq::FlagType::Midi)
				{
					// take any MIDI input, prefer mandatory MIDI input
					// (Lv2Proc::check() assures there are <=1 mandatory MIDI
					// input ports)
					if(!m_proc->m_midiIn || !atomPort.m_optional)
						m_proc->m_midiIn = &atomPort;
				}
			}
			else if(atomPort.m_flow == Lv2Ports::Flow::Output)
			{
				if(atomPort.flags & Lv2Ports::AtomSeq::FlagType::Midi)
				{
					// take any MIDI output, prefer mandatory MIDI output
					// (Lv2Proc::check() assures there are <=1 mandatory MIDI
					// output ports)
					if(!m_proc->m_midiOut || !atomPort.m_optional)
						m_proc->m_midiOut = &atomPort;
				}
			}
			else { Q_ASSERT(false); }
		}
	};

	std::size_t maxPorts = lilv_plugin_get_num_ports(m_plugin);
	m_ports.resize(maxPorts);

	for (std::size_t portNum = 0; portNum < maxPorts; ++portNum)
	{
		createPort(portNum);
		RegisterPort registerPort;
		registerPort.m_proc = this;
		m_ports[portNum]->accept(registerPort);
	}

	// initially assign model values to port values
	copyModelsFromCore();

	// debugging:
	//dumpPorts();
}

struct ConnectPortVisitor : public Lv2Ports::Visitor
{
	std::size_t m_num;
	LilvInstance* m_instance;
	void connectPort(void* location)
	{
		lilv_instance_connect_port(m_instance,
			static_cast<uint32_t>(m_num), location);
	}
	void visit(Lv2Ports::AtomSeq& atomSeq) override
	{
		connectPort(lv2_evbuf_get_buffer(atomSeq.m_buf.get()));
	}
	void visit(Lv2Ports::Control& ctrl) override { connectPort(&ctrl.m_val); }
	void visit(Lv2Ports::Audio& audio) override
	{
		connectPort((audio.mustBeUsed()) ? audio.m_buffer.data() : nullptr);
	}
	void visit(Lv2Ports::Unknown&) override { connectPort(nullptr); }
	~ConnectPortVisitor() override;
};

ConnectPortVisitor::~ConnectPortVisitor() {}

// !This function must be realtime safe!
// use createPort to create any port before connecting
void Lv2Proc::connectPort(std::size_t num)
{
	ConnectPortVisitor connect;
	connect.m_num = num;
	connect.m_instance = m_instance;
	m_ports[num]->accept(connect);
}

void Lv2Proc::dumpPort(std::size_t num)
{
	struct DumpPortDetail : public Lv2Ports::ConstVisitor
	{
		void visit(const Lv2Ports::Control& ctrl) override {
			qDebug() << "  control port";
			// output ports may be uninitialized yet, only print inputs
			if (ctrl.m_flow == Lv2Ports::Flow::Input)
			{
				qDebug() << "    value:" << ctrl.m_val;
			}
		}
		void visit(const Lv2Ports::Audio& audio) override {
			qDebug() << (audio.isSideChain()	? "  audio port (sidechain)"
												: "  audio port");
			qDebug() << "    buffer size:" << audio.bufferSize();
		}
	};

	const Lv2Ports::PortBase& port = *m_ports[num];
	qDebug().nospace() << "port " << num << ":";
	qDebug() << "  name:"
		<< qStringFromPortName(m_plugin, port.m_port);
	qDebug() << "  flow: " << Lv2Ports::toStr(port.m_flow);
	qDebug() << "  type: " << Lv2Ports::toStr(port.m_type);
	qDebug() << "  visualization: " << Lv2Ports::toStr(port.m_vis);
	if (port.m_type == Lv2Ports::Type::Control || port.m_type == Lv2Ports::Type::Cv)
	{
		sample_rate_t sr = Engine::mixer()->processingSampleRate();
		qDebug() << "  default:" << port.def();
		qDebug() << "  min:" << port.min(sr);
		qDebug() << "  max:" << port.max(sr);
	}
	qDebug() << "  optional: " << port.m_optional;
	qDebug() << "  => USED: " << port.m_used;

	DumpPortDetail dumper;
	port.accept(dumper);
}

bool Lv2Proc::portIsSideChain(const LilvPlugin *plugin, const LilvPort *port)
{
	return	lilv_port_has_property(plugin, port,
									uri(LV2_CORE_PREFIX "isSidechain").get());
}

bool Lv2Proc::portIsOptional(const LilvPlugin *plugin, const LilvPort *port)
{
	return	lilv_port_has_property(plugin, port,
									uri(LV2_CORE__connectionOptional).get());
}

AutoLilvNode Lv2Proc::uri(const char *uriStr)
{
	return Engine::getLv2Manager()->uri(uriStr);
}

#endif // LMMS_HAVE_LV2


code:
/*
 * Lv2SubPluginFeatures.cpp - derivation from
 *                            Plugin::Descriptor::SubPluginFeatures for
 *                            hosting LV2 plugins
 *
 * Copyright (c) 2018-2020 Johannes Lorenz <jlsf2013$users.sourceforge.net, $=@>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "Lv2SubPluginFeatures.h"

#ifdef LMMS_HAVE_LV2

#include <QDebug>
#include <QHBoxLayout>
#include <QLabel>

#include "Engine.h"
#include "Lv2Basics.h"
#include "Lv2Manager.h"

const LilvPlugin *Lv2SubPluginFeatures::getPlugin(
	const Plugin::Descriptor::SubPluginFeatures::Key &k)
{
	const LilvPlugin* result = Engine::getLv2Manager()->
		getPlugin(k.attributes["uri"]);
	Q_ASSERT(result);
	return result;
}

QString Lv2SubPluginFeatures::pluginName(const LilvPlugin *plug)
{
	return qStringFromPluginNode(plug, lilv_plugin_get_name);
}

Lv2SubPluginFeatures::Lv2SubPluginFeatures(Plugin::PluginTypes type) :
	SubPluginFeatures(type)
{
}

void Lv2SubPluginFeatures::fillDescriptionWidget(QWidget *parent,
													const Key *k) const
{
	const LilvPlugin *plug = getPlugin(*k);

	QLabel *label = new QLabel(parent);
	label->setText(QWidget::tr("Name: ") + pluginName(plug));

	QLabel *label2 = new QLabel(parent);
	label2->setText(QWidget::tr("URI: ") +
		lilv_node_as_uri(lilv_plugin_get_uri(plug)));

	QWidget *maker = new QWidget(parent);
	QHBoxLayout *l = new QHBoxLayout(maker);
	l->setMargin(0);
	l->setSpacing(0);

	QLabel *maker_label = new QLabel(maker);
	maker_label->setText(QWidget::tr("Maker: "));
	maker_label->setAlignment(Qt::AlignTop);

	QLabel *maker_content = new QLabel(maker);
	maker_content->setText(
		qStringFromPluginNode(plug, lilv_plugin_get_author_name));
	maker_content->setWordWrap(true);

	l->addWidget(maker_label);
	l->addWidget(maker_content, 1);

	QWidget *copyright = new QWidget(parent);
	l = new QHBoxLayout(copyright);
	l->setMargin(0);
	l->setSpacing(0);
	copyright->setMinimumWidth(parent->minimumWidth());

	QLabel *copyright_label = new QLabel(copyright);
	copyright_label->setText(QWidget::tr("Copyright: "));
	copyright_label->setAlignment(Qt::AlignTop);

	QLabel *copyright_content = new QLabel(copyright);
	copyright_content->setText("<unknown>");
	copyright_content->setWordWrap(true);
	l->addWidget(copyright_label);
	l->addWidget(copyright_content, 1);

	AutoLilvNodes extensions(lilv_plugin_get_extension_data(plug));
	(void)extensions;
	// possibly TODO: version, project, plugin type, number of channels
}

QString Lv2SubPluginFeatures::additionalFileExtensions(
	const Plugin::Descriptor::SubPluginFeatures::Key &k) const
{
	(void)k;
	// lv2 only loads .lv2 files
	// maybe add conversions later, e.g. for loading xmz
	return QString();
}

QString Lv2SubPluginFeatures::displayName(
	const Plugin::Descriptor::SubPluginFeatures::Key &k) const
{
	return pluginName(getPlugin(k));
}

QString Lv2SubPluginFeatures::description(
	const Plugin::Descriptor::SubPluginFeatures::Key &k) const
{
	(void)k;
	return QString::fromUtf8("description not implemented yet"); // TODO
}

const PixmapLoader *Lv2SubPluginFeatures::logo(
	const Plugin::Descriptor::SubPluginFeatures::Key &k) const
{
	(void)k; // TODO
	return nullptr;
}

void Lv2SubPluginFeatures::listSubPluginKeys(const Plugin::Descriptor *desc,
												KeyList &kl) const
{
	Lv2Manager *lv2Mgr = Engine::getLv2Manager();
	for (const auto &uriInfoPair : *lv2Mgr)
	{
		if (uriInfoPair.second.type() == m_type && uriInfoPair.second.isValid())
		{
			using KeyType =
				Plugin::Descriptor::SubPluginFeatures::Key;
			KeyType::AttributeMap atm;
			atm["uri"] = QString::fromUtf8(uriInfoPair.first.c_str());
			const LilvPlugin* plug = uriInfoPair.second.plugin();

			kl.push_back(KeyType(desc, pluginName(plug), atm));
			//qDebug() << "Found LV2 sub plugin key of type" <<
			//	m_type << ":" << pr.first.c_str();
		}
	}
}

#endif // LMMS_HAVE_LV2

repo_name: CYBERDEViL/LMMS

license: mit


code:
/*
 * Lv2UridCache.cpp - Lv2UridCache implementation
 *
 * Copyright (c) 2020-2020 Johannes Lorenz <jlsf2013$users.sourceforge.net, $=@>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "Lv2UridCache.h"

#ifdef LMMS_HAVE_LV2

#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
#include <lv2/lv2plug.in/ns/ext/buf-size/buf-size.h>
#include <lv2/lv2plug.in/ns/ext/midi/midi.h>
#include <lv2/lv2plug.in/ns/ext/parameters/parameters.h>
#include <QtGlobal>

#include "Lv2UridMap.h"

// support newer URIs on old systems
#ifndef LV2_BUF_SIZE__nominalBlockLength
#define LV2_BUF_SIZE__nominalBlockLength LV2_BUF_SIZE_PREFIX "nominalBlockLength"
#endif

uint32_t Lv2UridCache::operator[](Lv2UridCache::Id id) const
{
	Q_ASSERT(id != Id::size);
	return m_cache[static_cast<std::size_t>(id)];
}

Lv2UridCache::Lv2UridCache(UridMap &mapper)
{
	const uint32_t noIdYet = 0;
	std::fill_n(m_cache, static_cast<std::size_t>(Id::size), noIdYet);

	auto init = [this, &mapper](Id id, const char* uridStr)
	{
		m_cache[static_cast<std::size_t>(id)] = mapper.map(uridStr);
	};

	init(Id::atom_Float, LV2_ATOM__Float);
	init(Id::atom_Int, LV2_ATOM__Int);
	init(Id::bufsz_minBlockLength, LV2_BUF_SIZE__minBlockLength);
	init(Id::bufsz_maxBlockLength, LV2_BUF_SIZE__maxBlockLength);
	init(Id::bufsz_nominalBlockLength, LV2_BUF_SIZE__nominalBlockLength);
	init(Id::bufsz_sequenceSize, LV2_BUF_SIZE__sequenceSize);
	init(Id::midi_MidiEvent, LV2_MIDI__MidiEvent);
	init(Id::param_sampleRate, LV2_PARAMETERS__sampleRate);

	for(uint32_t urid : m_cache) { Q_ASSERT(urid != noIdYet); }
}

#endif // LMMS_HAVE_LV2


code:
/*
 * Lv2UridMap.cpp - Lv2UridMap implementation
 *
 * Copyright (c) 2019 Johannes Lorenz <j.git$$$lorenz-ho.me, $$$=@>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "Lv2UridMap.h"

#ifdef LMMS_HAVE_LV2

static LV2_URID staticMap(LV2_URID_Map_Handle handle, const char* uri)
{
	UridMap* map = static_cast<UridMap*>(handle);
	return map->map(uri);
}

static const char* staticUnmap(LV2_URID_Unmap_Handle handle, LV2_URID urid)
{
	UridMap* map = static_cast<UridMap*>(handle);
	return map->unmap(urid);
}

UridMap::UridMap()
{
	m_mapFeature.handle = static_cast<LV2_URID_Map_Handle>(this);
	m_mapFeature.map = staticMap;
	m_unmapFeature.handle = static_cast<LV2_URID_Unmap_Handle>(this);
	m_unmapFeature.unmap = staticUnmap;
}

LV2_URID UridMap::map(const char *uri)
{
	LV2_URID result = 0u;

	// the Lv2 docs say that 0 should be returned in any case
	// where creating an ID for the given URI fails
	try
	{
		// TODO:
		// when using C++14, we can get around any string allocation
		// in the case the URI is already inside the map:
		// * use `m_map.find(uri)` instead of `m_map.find(uriStr)`
		// * to avoid temporary string construction in the `find` call, create
		//   m_map like this:
		//   std::unordered_map<std::string, LV2_URID,
		//     std::hash<std::string>, std::equal<>> m_map;
		// * move the try block inside the case where the URI is not in the map
		const std::string uriStr = uri;

		std::lock_guard<std::mutex> guard (m_MapMutex);

		auto itr = m_map.find(uriStr);
		if (itr == m_map.end())
		{
			// 1 is the first free URID
			std::size_t index = 1u + m_unMap.size();
			auto pr = m_map.emplace(std::move(uriStr), index);
			if (pr.second)
			{
				m_unMap.emplace_back(pr.first->first.c_str());
				result = static_cast<LV2_URID>(index);
			}
		}
		else { result = itr->second; }
	}
	catch(...) { /* result variable is already 0 */ }

	return result;
}

const char *UridMap::unmap(LV2_URID urid)
{
	std::size_t idx = static_cast<std::size_t>(urid) - 1;

	std::lock_guard<std::mutex> guard (m_MapMutex);
	return (idx < m_unMap.size()) ? m_unMap[idx] : nullptr;
}

#endif // LMMS_HAVE_LV2


code:
/*
 * main.cpp - just main.cpp which is starting up app...
 *
 * Copyright (c) 2004-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 * Copyright (c) 2012-2013 Paul Giblock    <p/at/pgiblock.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "lmmsconfig.h"
#include "lmmsversion.h"
#include "versioninfo.h"

#include "denormals.h"

#include <QDebug>
#include <QFileInfo>
#include <QLocale>
#include <QTimer>
#include <QTranslator>
#include <QApplication>
#include <QMessageBox>
#include <QPushButton>
#include <QTextStream>

#ifdef LMMS_BUILD_WIN32
#include <windows.h>
#endif

#ifdef LMMS_HAVE_SCHED_H
#include "sched.h"
#endif

#ifdef LMMS_HAVE_PROCESS_H
#include <process.h>
#endif

#ifdef LMMS_HAVE_UNISTD_H
#include <unistd.h>
#endif

#include <signal.h>

#include "MainApplication.h"
#include "ConfigManager.h"
#include "DataFile.h"
#include "NotePlayHandle.h"
#include "embed.h"
#include "Engine.h"
#include "GuiApplication.h"
#include "ImportFilter.h"
#include "MainWindow.h"
#include "MixHelpers.h"
#include "OutputSettings.h"
#include "ProjectRenderer.h"
#include "RenderManager.h"
#include "Song.h"
#include "SetupDialog.h"

#ifdef LMMS_DEBUG_FPE
#include <fenv.h> // For feenableexcept
#include <execinfo.h> // For backtrace and backtrace_symbols_fd
#include <unistd.h> // For STDERR_FILENO
#include <csignal> // To register the signal handler
#endif

#ifdef LMMS_DEBUG_FPE
void signalHandler( int signum ) {

	// Get a back trace
	void *array[10];
	size_t size;

	// get void*'s for all entries on the stack
	size = backtrace(array, 10);

	backtrace_symbols_fd(array, size, STDERR_FILENO);

	// cleanup and close up stuff here
	// terminate program

	exit(signum);
}
#endif

static inline QString baseName( const QString & file )
{
	return QFileInfo( file ).absolutePath() + "/" +
			QFileInfo( file ).completeBaseName();
}

#ifdef LMMS_BUILD_WIN32
// Workaround for old MinGW
#ifdef __MINGW32__
extern "C" _CRTIMP errno_t __cdecl freopen_s(FILE** _File,
	const char *_Filename, const char *_Mode, FILE *_Stream);
#endif

// For qInstallMessageHandler
void consoleMessageHandler(QtMsgType type,
	const QMessageLogContext &context, const QString &msg)
{
    QByteArray localMsg = msg.toLocal8Bit();
    fprintf(stderr, "%s\n", localMsg.constData());
}
#endif

inline void loadTranslation( const QString & tname,
	const QString & dir = ConfigManager::inst()->localeDir() )
{
	QTranslator * t = new QTranslator( QCoreApplication::instance() );
	QString name = tname + ".qm";

	if (t->load(name, dir))
	{
		QCoreApplication::instance()->installTranslator(t);
	}
}

void printVersion( char *executableName )
{
	printf( "LMMS %s\n(%s %s, Qt %s, %s)\n\n"
		"Copyright (c) %s\n\n"
		"This program is free software; you can redistribute it and/or\n"
		"modify it under the terms of the GNU General Public\n"
		"License as published by the Free Software Foundation; either\n"
		"version 2 of the License, or (at your option) any later version.\n\n"
		"Try \"%s --help\" for more information.\n\n", LMMS_VERSION,
		PLATFORM, MACHINE, QT_VERSION_STR, COMPILER_VERSION,
		LMMS_PROJECT_COPYRIGHT, executableName );
}

void printHelp()
{
	printf( "LMMS %s\n"
		"Copyright (c) %s\n\n"
		"Usage: lmms [global options...] [<action> [action parameters...]]\n\n"
		"Actions:\n"
		"  <no action> [options...] [<project>]  Start LMMS in normal GUI mode\n"
		"  dump <in>                             Dump XML of compressed file <in>\n"
		"  compress <in>                         Compress file <in>\n"
		"  render <project> [options...]         Render given project file\n"
		"  rendertracks <project> [options...]   Render each track to a different file\n"
		"  upgrade <in> [out]                    Upgrade file <in> and save as <out>\n"
		"                                        Standard out is used if no output file\n"
		"                                        is specified\n"
		"\nGlobal options:\n"
		"      --allowroot                Bypass root user startup check (use with\n"
		"          caution).\n"
		"  -c, --config <configfile>      Get the configuration from <configfile>\n"
		"  -h, --help                     Show this usage information and exit.\n"
		"  -v, --version                  Show version information and exit.\n"
		"\nOptions if no action is given:\n"
		"      --geometry <geometry>      Specify the size and position of\n"
		"          the main window\n"
		"          geometry is <xsizexysize+xoffset+yoffsety>.\n"
		"      --import <in> [-e]         Import MIDI or Hydrogen file <in>.\n"
		"          If -e is specified lmms exits after importing the file.\n"
		"\nOptions for \"render\" and \"rendertracks\":\n"
		"  -a, --float                    Use 32bit float bit depth\n"
		"  -b, --bitrate <bitrate>        Specify output bitrate in KBit/s\n"
		"          Default: 160.\n"
		"  -f, --format <format>         Specify format of render-output where\n"
		"          Format is either 'wav', 'flac', 'ogg' or 'mp3'.\n"
		"  -i, --interpolation <method>   Specify interpolation method\n"
		"          Possible values:\n"
		"            - linear\n"
		"            - sincfastest (default)\n"
		"            - sincmedium\n"
		"            - sincbest\n"
		"  -l, --loop                     Render as a loop\n"
		"  -m, --mode                     Stereo mode used for MP3 export\n"
		"          Possible values: s, j, m\n"
		"            s: Stereo\n"
		"            j: Joint Stereo\n"
		"            m: Mono\n"
		"          Default: j\n"
		"  -o, --output <path>            Render into <path>\n"
		"          For \"render\", provide a file path\n"
		"          For \"rendertracks\", provide a directory path\n"
		"          If not specified, render will overwrite the input file\n"
		"          For \"rendertracks\", this might be required\n"
		"  -p, --profile <out>            Dump profiling information to file <out>\n"
		"  -s, --samplerate <samplerate>  Specify output samplerate in Hz\n"
		"          Range: 44100 (default) to 192000\n"
		"  -x, --oversampling <value>     Specify oversampling\n"
		"          Possible values: 1, 2, 4, 8\n"
		"          Default: 2\n\n",
		LMMS_VERSION, LMMS_PROJECT_COPYRIGHT );
}

void fileCheck( QString &file )
{
	QFileInfo fileToCheck( file );

	if( !fileToCheck.size() )
	{
		printf( "The file %s does not have any content.\n",
				file.toUtf8().constData() );
		exit( EXIT_FAILURE );
	}
	else if( fileToCheck.isDir() )
	{
		printf( "%s is a directory.\n",
				file.toUtf8().constData() );
		exit( EXIT_FAILURE );
	}
}

int usageError(const QString& message)
{
	qCritical().noquote() << QString("\n%1.\n\nTry \"%2 --help\" for more information.\n\n")
				   .arg( message ).arg( qApp->arguments()[0] );
	return EXIT_FAILURE;
}

int noInputFileError()
{
	return usageError( "No input file specified" );
}

int main( int argc, char * * argv )
{
#ifdef LMMS_DEBUG_FPE
	// Enable exceptions for certain floating point results
	// FE_UNDERFLOW is disabled for the time being
	feenableexcept( FE_INVALID   |
			FE_DIVBYZERO |
			FE_OVERFLOW  /*|
			FE_UNDERFLOW*/);

	// Install the trap handler
	// register signal SIGFPE and signal handler
	signal(SIGFPE, signalHandler);
#endif

#ifdef LMMS_BUILD_WIN32
	// Don't touch redirected streams here
	// GetStdHandle should be called before AttachConsole
	HANDLE hStdIn = GetStdHandle(STD_INPUT_HANDLE);
	HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
	HANDLE hStdErr = GetStdHandle(STD_ERROR_HANDLE);
	FILE *fIn, *fOut, *fErr;
	// Enable console output if available
	if (AttachConsole(ATTACH_PARENT_PROCESS))
	{
		if (!hStdIn)
		{
			freopen_s(&fIn, "CONIN$", "r", stdin);
		}
		if (!hStdOut)
		{
			freopen_s(&fOut, "CONOUT$", "w", stdout);
		}
		if (!hStdErr)
		{
			freopen_s(&fErr, "CONOUT$", "w", stderr);
		}
	}
	// Make Qt's debug message handlers work
	qInstallMessageHandler(consoleMessageHandler);
#endif

	// initialize memory managers
	NotePlayHandleManager::init();

	// intialize RNG
	srand( getpid() + time( 0 ) );

	disable_denormals();

	bool coreOnly = false;
	bool fullscreen = true;
	bool exitAfterImport = false;
	bool allowRoot = false;
	bool renderLoop = false;
	bool renderTracks = false;
	QString fileToLoad, fileToImport, renderOut, profilerOutputFile, configFile;

	// first of two command-line parsing stages
	for( int i = 1; i < argc; ++i )
	{
		QString arg = argv[i];

		if( arg == "--help"    || arg == "-h" ||
		    arg == "--version" || arg == "-v" ||
		    arg == "render" || arg == "--render" || arg == "-r" )
		{
			coreOnly = true;
		}
		else if( arg == "rendertracks" || arg == "--rendertracks" )
		{
			coreOnly = true;
			renderTracks = true;
		}
		else if( arg == "--allowroot" )
		{
			allowRoot = true;
		}
		else if( arg == "--geometry" || arg == "-geometry")
		{
			if( arg == "--geometry" )
			{
				// Delete the first "-" so Qt recognize the option
				strcpy(argv[i], "-geometry");
			}
			// option -geometry is filtered by Qt later,
			// so we need to check its presence now to
			// determine, if the application should run in
			// fullscreen mode (default, no -geometry given).
			fullscreen = false;
		}
	}

#if !defined(LMMS_BUILD_WIN32) && !defined(LMMS_BUILD_HAIKU)
	if ( ( getuid() == 0 || geteuid() == 0 ) && !allowRoot )
	{
		printf( "LMMS cannot be run as root.\nUse \"--allowroot\" to override.\n\n" );
		return EXIT_FAILURE;
	}
#endif
#ifdef LMMS_BUILD_LINUX
	// don't let OS steal the menu bar. FIXME: only effective on Qt4
	QCoreApplication::setAttribute( Qt::AA_DontUseNativeMenuBar );
#endif
#if QT_VERSION >= QT_VERSION_CHECK(5, 6, 0)
	QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
#endif
	QCoreApplication * app = coreOnly ?
			new QCoreApplication( argc, argv ) :
					new MainApplication( argc, argv );

	Mixer::qualitySettings qs( Mixer::qualitySettings::Mode_HighQuality );
	OutputSettings os( 44100, OutputSettings::BitRateSettings(160, false), OutputSettings::Depth_16Bit, OutputSettings::StereoMode_JointStereo );
	ProjectRenderer::ExportFileFormats eff = ProjectRenderer::WaveFile;

	// second of two command-line parsing stages
	for( int i = 1; i < argc; ++i )
	{
		QString arg = argv[i];

		if( arg == "--version" || arg == "-v" )
		{
			printVersion( argv[0] );
			return EXIT_SUCCESS;
		}
		else if( arg == "--help" || arg  == "-h" )
		{
			printHelp();
			return EXIT_SUCCESS;
		}
		else if( arg == "upgrade" || arg == "--upgrade" || arg  == "-u")
		{
			++i;

			if( i == argc )
			{
				return noInputFileError();
			}

			DataFile dataFile( QString::fromLocal8Bit( argv[i] ) );

			if( argc > i+1 ) // output file specified
			{
				dataFile.writeFile( QString::fromLocal8Bit( argv[i+1] ) );
			}
			else // no output file specified; use stdout
			{
				QTextStream ts( stdout );
				dataFile.write( ts );
				fflush( stdout );
			}

			return EXIT_SUCCESS;
		}
		else if( arg == "--allowroot" )
		{
			// Ignore, processed earlier
#ifdef LMMS_BUILD_WIN32
			if( allowRoot )
			{
				printf( "\nOption \"--allowroot\" will be ignored on this platform.\n\n" );
			}
#endif

		}
		else if( arg == "dump" || arg == "--dump" || arg  == "-d" )
		{
			++i;

			if( i == argc )
			{
				return noInputFileError();
			}

			QFile f( QString::fromLocal8Bit( argv[i] ) );
			f.open( QIODevice::ReadOnly );
			QString d = qUncompress( f.readAll() );
			printf( "%s\n", d.toUtf8().constData() );

			return EXIT_SUCCESS;
		}
		else if( arg == "compress" || arg == "--compress" )
		{
			++i;

			if( i == argc )
			{
				return noInputFileError();
			}

			QFile f( QString::fromLocal8Bit( argv[i] ) );
			f.open( QIODevice::ReadOnly );
			QByteArray d = qCompress( f.readAll() ) ;
			fwrite( d.constData(), sizeof(char), d.size(), stdout );

			return EXIT_SUCCESS;
		}
		else if( arg == "render" || arg == "--render" || arg == "-r" ||
			arg == "rendertracks" || arg == "--rendertracks" )
		{
			++i;

			if( i == argc )
			{
				return noInputFileError();
			}

			fileToLoad = QString::fromLocal8Bit( argv[i] );
			renderOut = fileToLoad;
		}
		else if( arg == "--loop" || arg == "-l" )
		{
			renderLoop = true;
		}
		else if( arg == "--output" || arg == "-o" )
		{
			++i;

			if( i == argc )
			{
				return usageError( "No output file specified" );
			}

			renderOut = QString::fromLocal8Bit( argv[i] );
		}
		else if( arg == "--format" || arg == "-f" )
		{
			++i;

			if( i == argc )
			{
				return usageError( "No output format specified" );
			}

			const QString ext = QString( argv[i] );

			if( ext == "wav" )
			{
				eff = ProjectRenderer::WaveFile;
			}
#ifdef LMMS_HAVE_OGGVORBIS
			else if( ext == "ogg" )
			{
				eff = ProjectRenderer::OggFile;
			}
#endif
#ifdef LMMS_HAVE_MP3LAME
			else if( ext == "mp3" )
			{
				eff = ProjectRenderer::MP3File;
			}
#endif
			else if (ext == "flac")
			{
				eff = ProjectRenderer::FlacFile;
			}
			else
			{
				return usageError( QString( "Invalid output format %1" ).arg( argv[i] ) );
			}
		}
		else if( arg == "--samplerate" || arg == "-s" )
		{
			++i;

			if( i == argc )
			{
				return usageError( "No samplerate specified" );
			}

			sample_rate_t sr = QString( argv[i] ).toUInt();
			if( sr >= 44100 && sr <= 192000 )
			{
				os.setSampleRate(sr);
			}
			else
			{
				return usageError( QString( "Invalid samplerate %1" ).arg( argv[i] ) );
			}
		}
		else if( arg == "--bitrate" || arg == "-b" )
		{
			++i;

			if( i == argc )
			{
				return usageError( "No bitrate specified" );
			}

			int br = QString( argv[i] ).toUInt();

			if( br >= 64 && br <= 384 )
			{
				OutputSettings::BitRateSettings bitRateSettings = os.getBitRateSettings();
				bitRateSettings.setBitRate(br);
				os.setBitRateSettings(bitRateSettings);
			}
			else
			{
				return usageError( QString( "Invalid bitrate %1" ).arg( argv[i] ) );
			}
		}
		else if( arg == "--mode" || arg == "-m" )
		{
			++i;

			if( i == argc )
			{
				return usageError( "No stereo mode specified" );
			}

			QString const mode( argv[i] );

			if( mode == "s" )
			{
				os.setStereoMode(OutputSettings::StereoMode_Stereo);
			}
			else if( mode == "j" )
			{
				os.setStereoMode(OutputSettings::StereoMode_JointStereo);
			}
			else if( mode == "m" )
			{
				os.setStereoMode(OutputSettings::StereoMode_Mono);
			}
			else
			{
				return usageError( QString( "Invalid stereo mode %1" ).arg( argv[i] ) );
			}
		}
		else if( arg =="--float" || arg == "-a" )
		{
			os.setBitDepth(OutputSettings::Depth_32Bit);
		}
		else if( arg == "--interpolation" || arg == "-i" )
		{
			++i;

			if( i == argc )
			{
				return usageError( "No interpolation method specified" );
			}

			const QString ip = QString( argv[i] );

			if( ip == "linear" )
			{
		qs.interpolation = Mixer::qualitySettings::Interpolation_Linear;
			}
			else if( ip == "sincfastest" )
			{
		qs.interpolation = Mixer::qualitySettings::Interpolation_SincFastest;
			}
			else if( ip == "sincmedium" )
			{
		qs.interpolation = Mixer::qualitySettings::Interpolation_SincMedium;
			}
			else if( ip == "sincbest" )
			{
		qs.interpolation = Mixer::qualitySettings::Interpolation_SincBest;
			}
			else
			{
				return usageError( QString( "Invalid interpolation method %1" ).arg( argv[i] ) );
			}
		}
		else if( arg == "--oversampling" || arg == "-x" )
		{
			++i;

			if( i == argc )
			{
				return usageError( "No oversampling specified" );
			}

			int o = QString( argv[i] ).toUInt();

			switch( o )
			{
				case 1:
		qs.oversampling = Mixer::qualitySettings::Oversampling_None;
		break;
				case 2:
		qs.oversampling = Mixer::qualitySettings::Oversampling_2x;
		break;
				case 4:
		qs.oversampling = Mixer::qualitySettings::Oversampling_4x;
		break;
				case 8:
		qs.oversampling = Mixer::qualitySettings::Oversampling_8x;
		break;
				default:
				return usageError( QString( "Invalid oversampling %1" ).arg( argv[i] ) );
			}
		}
		else if( arg == "--import" )
		{
			++i;

			if( i == argc )
			{
				return usageError( "No file specified for importing" );
			}

			fileToImport = QString::fromLocal8Bit( argv[i] );

			// exit after import? (only for debugging)
			if( QString( argv[i + 1] ) == "-e" )
			{
				exitAfterImport = true;
				++i;
			}
		}
		else if( arg == "--profile" || arg == "-p" )
		{
			++i;

			if( i == argc )
			{
				return usageError( "No profile specified" );
			}

			profilerOutputFile = QString::fromLocal8Bit( argv[i] );
		}
		else if( arg == "--config" || arg == "-c" )
		{
			++i;

			if( i == argc )
			{
				return usageError( "No configuration file specified" );
			}

			configFile = QString::fromLocal8Bit( argv[i] );
		}
		else
		{
			if( argv[i][0] == '-' )
			{
				return usageError( QString( "Invalid option %1" ).arg( argv[i] ) );
			}
			fileToLoad = QString::fromLocal8Bit( argv[i] );
		}
	}

	// Test file argument before continuing
	if( !fileToLoad.isEmpty() )
	{
		fileCheck( fileToLoad );
	}
	else if( !fileToImport.isEmpty() )
	{
		fileCheck( fileToImport );
	}

	ConfigManager::inst()->loadConfigFile(configFile);

	// Hidden settings
	MixHelpers::setNaNHandler( ConfigManager::inst()->value( "app",
						"nanhandler", "1" ).toInt() );

	// set language
	QString pos = ConfigManager::inst()->value( "app", "language" );
	if( pos.isEmpty() )
	{
		pos = QLocale::system().name().left( 2 );
	}

	// load actual translation for LMMS
	loadTranslation( pos );

	// load translation for Qt-widgets/-dialogs
#ifdef QT_TRANSLATIONS_DIR
	// load from the original path first
	loadTranslation(QString("qt_") + pos, QT_TRANSLATIONS_DIR);
#endif
	// override it with bundled/custom one, if exists
	loadTranslation(QString("qt_") + pos, ConfigManager::inst()->localeDir());

	// try to set realtime priority
#if defined(LMMS_BUILD_LINUX) || defined(LMMS_BUILD_FREEBSD)
#ifdef LMMS_HAVE_SCHED_H
#ifndef __OpenBSD__
	struct sched_param sparam;
	sparam.sched_priority = ( sched_get_priority_max( SCHED_FIFO ) +
				sched_get_priority_min( SCHED_FIFO ) ) / 2;
	if( sched_setscheduler( 0, SCHED_FIFO, &sparam ) == -1 )
	{
		printf( "Notice: could not set realtime priority.\n" );
	}
#endif
#endif
#endif

#ifdef LMMS_BUILD_WIN32
	if( !SetPriorityClass( GetCurrentProcess(), HIGH_PRIORITY_CLASS ) )
	{
		printf( "Notice: could not set high priority.\n" );
	}
#endif

#if _POSIX_C_SOURCE >= 1 || _XOPEN_SOURCE || _POSIX_SOURCE
	struct sigaction sa;
	sa.sa_handler = SIG_IGN;
	sa.sa_flags = SA_SIGINFO;
	if ( sigemptyset( &sa.sa_mask ) )
	{
		fprintf( stderr, "Signal initialization failed.\n" );
	}
	if ( sigaction( SIGPIPE, &sa, NULL ) )
	{
		fprintf( stderr, "Signal initialization failed.\n" );
	}
#endif

	bool destroyEngine = false;

	// if we have an output file for rendering, just render the song
	// without starting the GUI
	if( !renderOut.isEmpty() )
	{
		Engine::init( true );
		destroyEngine = true;

		printf( "Loading project...\n" );
		Engine::getSong()->loadProject( fileToLoad );
		if( Engine::getSong()->isEmpty() )
		{
			printf("The project %s is empty, aborting!\n", fileToLoad.toUtf8().constData() );
			exit( EXIT_FAILURE );
		}
		printf( "Done\n" );

		Engine::getSong()->setExportLoop( renderLoop );

		// when rendering multiple tracks, renderOut is a directory
		// otherwise, it is a file, so we need to append the file extension
		if ( !renderTracks )
		{
			renderOut = baseName( renderOut ) +
				ProjectRenderer::getFileExtensionFromFormat(eff);
		}

		// create renderer
		RenderManager * r = new RenderManager( qs, os, eff, renderOut );
		QCoreApplication::instance()->connect( r,
				SIGNAL( finished() ), SLOT( quit() ) );

		// timer for progress-updates
		QTimer * t = new QTimer( r );
		r->connect( t, SIGNAL( timeout() ),
				SLOT( updateConsoleProgress() ) );
		t->start( 200 );

		if( profilerOutputFile.isEmpty() == false )
		{
			Engine::mixer()->profiler().setOutputFile( profilerOutputFile );
		}

		// start now!
		if ( renderTracks )
		{
			r->renderTracks();
		}
		else
		{
			r->renderProject();
		}
	}
	else // otherwise, start the GUI
	{
		new GuiApplication();

		// re-intialize RNG - shared libraries might have srand() or
		// srandom() calls in their init procedure
		srand( getpid() + time( 0 ) );

		// recover a file?
		QString recoveryFile = ConfigManager::inst()->recoveryFile();

		bool recoveryFilePresent = QFileInfo( recoveryFile ).exists() &&
				QFileInfo( recoveryFile ).isFile();
		bool autoSaveEnabled =
			ConfigManager::inst()->value( "ui", "enableautosave" ).toInt();
		if( recoveryFilePresent )
		{
			QMessageBox mb;
			mb.setWindowTitle( MainWindow::tr( "Project recovery" ) );
			mb.setText( QString(
				"<html>"
				"<p style=\"margin-left:6\">%1</p>"
				"<table cellpadding=\"3\">"
				"  <tr>"
				"    <td><b>%2</b></td>"
				"    <td>%3</td>"
				"  </tr>"
				"  <tr>"
				"    <td><b>%4</b></td>"
				"    <td>%5</td>"
				"  </tr>"
				"</table>"
				"</html>" ).arg(
				MainWindow::tr( "There is a recovery file present. "
					"It looks like the last session did not end "
					"properly or another instance of LMMS is "
					"already running. Do you want to recover the "
					"project of this session?" ),
				MainWindow::tr( "Recover" ),
				MainWindow::tr( "Recover the file. Please don't run "
					"multiple instances of LMMS when you do this." ),
				MainWindow::tr( "Discard" ),
				MainWindow::tr( "Launch a default session and delete "
					"the restored files. This is not reversible." )
							) );

			mb.setIcon( QMessageBox::Warning );
			mb.setWindowIcon( embed::getIconPixmap( "icon_small" ) );
			mb.setWindowFlags( Qt::WindowCloseButtonHint );

			QPushButton * recover;
			QPushButton * discard;
			QPushButton * exit;

			// setting all buttons to the same roles allows us
			// to have a custom layout
			discard = mb.addButton( MainWindow::tr( "Discard" ),
								QMessageBox::AcceptRole );
			recover = mb.addButton( MainWindow::tr( "Recover" ),
								QMessageBox::AcceptRole );

			// have a hidden exit button
			exit = mb.addButton( "", QMessageBox::RejectRole);
			exit->setVisible(false);

			// set icons
			recover->setIcon( embed::getIconPixmap( "recover" ) );
			discard->setIcon( embed::getIconPixmap( "discard" ) );

			mb.setDefaultButton( recover );
			mb.setEscapeButton( exit );

			mb.exec();
			if( mb.clickedButton() == discard )
			{
				gui->mainWindow()->sessionCleanup();
			}
			else if( mb.clickedButton() == recover ) // Recover
			{
				fileToLoad = recoveryFile;
				gui->mainWindow()->setSession( MainWindow::SessionState::Recover );
			}
			else // Exit
			{
				return EXIT_SUCCESS;
			}
		}

		// first show the Main Window and then try to load given file

		// [Settel] workaround: showMaximized() doesn't work with
		// FVWM2 unless the window is already visible -> show() first
		gui->mainWindow()->show();
		if( fullscreen )
		{
			gui->mainWindow()->showMaximized();
		}

		// Handle macOS-style FileOpen QEvents
		QString queuedFile = static_cast<MainApplication *>( app )->queuedFile();
		if ( !queuedFile.isEmpty() ) {
			fileToLoad = queuedFile;
		}

		if( !fileToLoad.isEmpty() )
		{
			if( fileToLoad == recoveryFile )
			{
				Engine::getSong()->createNewProjectFromTemplate( fileToLoad );
			}
			else
			{
				Engine::getSong()->loadProject( fileToLoad );
			}
		}
		else if( !fileToImport.isEmpty() )
		{
			ImportFilter::import( fileToImport, Engine::getSong() );
			if( exitAfterImport )
			{
				return EXIT_SUCCESS;
			}
		}
		// If enabled, open last project if there is one. Else, create
		// a new one.
		else if( ConfigManager::inst()->
				value( "app", "openlastproject" ).toInt() &&
			!ConfigManager::inst()->
				recentlyOpenedProjects().isEmpty() &&
				!recoveryFilePresent )
		{
			QString f = ConfigManager::inst()->
					recentlyOpenedProjects().first();
			QFileInfo recentFile( f );

			if ( recentFile.exists() &&
				recentFile.suffix().toLower() != "mpt" )
			{
				Engine::getSong()->loadProject( f );
			}
			else
			{
				Engine::getSong()->createNewProject();
			}
		}
		else
		{
			Engine::getSong()->createNewProject();
		}

		// Finally we start the auto save timer and also trigger the
		// autosave one time as recover.mmp is a signal to possible other
		// instances of LMMS.
		if( autoSaveEnabled )
		{
			gui->mainWindow()->autoSaveTimerReset();
		}
	}

	const int ret = app->exec();
	delete app;

	if( destroyEngine )
	{
		Engine::destroy();
	}

	// ProjectRenderer::updateConsoleProgress() doesn't return line after render
	if( coreOnly )
	{
		printf( "\n" );
	}

#ifdef LMMS_BUILD_WIN32
	// Cleanup console
	HWND hConsole = GetConsoleWindow();
	if (hConsole)
	{
		SendMessage(hConsole, WM_CHAR, (WPARAM)VK_RETURN, (LPARAM)0);
		FreeConsole();
	}
#endif

	NotePlayHandleManager::free();

	return ret;
}


code:
/*
 * MidiAlsaRaw.cpp - MIDI client for RawMIDI via ALSA
 *
 * Copyright (c) 2005-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "MidiAlsaRaw.h"
#include "ConfigManager.h"
#include "gui_templates.h"

#ifdef LMMS_HAVE_ALSA

MidiAlsaRaw::MidiAlsaRaw() :
	MidiClientRaw(),
	m_inputp( &m_input ),
	m_outputp( &m_output ),
	m_quit( false )
{
	int err;
	if( ( err = snd_rawmidi_open( m_inputp, m_outputp,
					probeDevice().toLatin1().constData(),
								0 ) ) < 0 )
	{
		printf( "cannot open MIDI-device: %s\n", snd_strerror( err ) );
		return;
	}

	snd_rawmidi_read( m_input, NULL, 0 );

	snd_rawmidi_nonblock( m_input, 1 );
	m_npfds = snd_rawmidi_poll_descriptors_count( m_input );
	m_pfds = new pollfd[m_npfds];
	snd_rawmidi_poll_descriptors( m_input, m_pfds, m_npfds );

	start( QThread::LowPriority );
}

MidiAlsaRaw::~MidiAlsaRaw()
{
	if( isRunning() )
	{
		m_quit = true;
		wait( 1000 );
		terminate();

		snd_rawmidi_close( m_input );
		snd_rawmidi_close( m_output );
		delete[] m_pfds;
	}
}

QString MidiAlsaRaw::probeDevice()
{
	QString dev = ConfigManager::inst()->value( "MidiAlsaRaw", "device" );
	if( dev == "" )
	{
		if( getenv( "MIDIDEV" ) != NULL )
		{
			return getenv( "MIDIDEV" );
		}
		return "default";
	}
	return dev;
}

void MidiAlsaRaw::sendByte( unsigned char c )
{
	snd_rawmidi_write( m_output, &c, sizeof( c ) );
}

void MidiAlsaRaw::run()
{
	unsigned char buf[128];
	//int cnt = 0;
	while( m_quit == false )
	{
		msleep( 5 );	// must do that, otherwise this thread takes
				// too much CPU-time, even with LowPriority...
		int err = poll( m_pfds, m_npfds, 10000 );
		if( err < 0 && errno == EINTR )
		{
			printf( "MidiAlsaRaw::run(): Got EINTR while "
				"polling. Will stop polling MIDI-events from "
				"MIDI-port.\n" );
			break;
		}
		if( err < 0 )
		{
			printf( "poll failed: %s\nWill stop polling "
				"MIDI-events from MIDI-port.\n",
							strerror( errno ) );
			break;
		}
		if( err == 0 )
		{
			//printf( "there seems to be no active MIDI-device %d\n", ++cnt );
			continue;
		}
		unsigned short revents;
		if( ( err = snd_rawmidi_poll_descriptors_revents(
				m_input, m_pfds, m_npfds, &revents ) ) < 0 )
		{
			printf( "cannot get poll events: %s\nWill stop polling "
				"MIDI-events from MIDI-port.\n",
							snd_strerror( errno ) );
			break;
		}
		if( revents & ( POLLERR | POLLHUP ) )
		{
			printf( "POLLERR or POLLHUP\n" );
			break;
		}
		if( !( revents & POLLIN ) )
		{
			continue;
		}
		err = snd_rawmidi_read( m_input, buf, sizeof( buf ) );
		if( err == -EAGAIN )
		{
			continue;
		}
		if( err < 0 )
		{
			printf( "cannot read from port \"%s\": %s\nWill stop "
				"polling MIDI-events from MIDI-port.\n",
				/*port_name*/"default", snd_strerror( err ) );
			break;
		}
		if( err == 0 )
		{
			continue;
		}
		for( int i = 0; i < err; ++i )
		{
			parseData( buf[i] );
		}
	}

}

#endif


code:
/*
 * MidiAlsaSeq.cpp - ALSA sequencer client
 *
 * Copyright (c) 2005-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "MidiAlsaSeq.h"
#include "ConfigManager.h"
#include "Engine.h"
#include "gui_templates.h"
#include "Song.h"
#include "MidiPort.h"
#include "Note.h"

#ifdef LMMS_HAVE_ALSA

const int EventPollTimeOut = 250;

// static helper functions
static QString portName( snd_seq_client_info_t * _cinfo,
								snd_seq_port_info_t * _pinfo )
{
	return QString( "%1:%2 %3:%4" ).
					arg( snd_seq_port_info_get_client( _pinfo ) ).
					arg( snd_seq_port_info_get_port( _pinfo ) ).
					arg( snd_seq_client_info_get_name( _cinfo ) ).
					arg( snd_seq_port_info_get_name( _pinfo ) );
}

static QString portName( snd_seq_t * _seq, const snd_seq_addr_t * _addr )
{
	snd_seq_client_info_t * cinfo;
	snd_seq_port_info_t * pinfo;

	snd_seq_client_info_malloc( &cinfo );
	snd_seq_port_info_malloc( &pinfo );

	snd_seq_get_any_port_info( _seq, _addr->client, _addr->port, pinfo );
	snd_seq_get_any_client_info( _seq, _addr->client, cinfo );

	const QString name = portName( cinfo, pinfo );

	snd_seq_client_info_free( cinfo );
	snd_seq_port_info_free( pinfo );

	return name;
}

MidiAlsaSeq::MidiAlsaSeq() :
	MidiClient(),
	m_seqMutex(),
	m_seqHandle( NULL ),
	m_queueID( -1 ),
	m_quit( false ),
	m_portListUpdateTimer( this )
{
	int err;
	if( ( err = snd_seq_open( &m_seqHandle,
					probeDevice().toLatin1().constData(),
						SND_SEQ_OPEN_DUPLEX, 0 ) ) < 0 )
	{
		fprintf( stderr, "cannot open sequencer: %s\n",
							snd_strerror( err ) );
		return;
	}
	snd_seq_set_client_name( m_seqHandle, "LMMS" );

	m_queueID = snd_seq_alloc_queue( m_seqHandle );
	snd_seq_queue_tempo_t * tempo;
	snd_seq_queue_tempo_malloc( &tempo );
	snd_seq_queue_tempo_set_tempo( tempo, 6000000 /
					Engine::getSong()->getTempo() );
	snd_seq_queue_tempo_set_ppq( tempo, 16 );
	snd_seq_set_queue_tempo( m_seqHandle, m_queueID, tempo );
	snd_seq_queue_tempo_free( tempo );

	snd_seq_start_queue( m_seqHandle, m_queueID, NULL );
	changeQueueTempo( Engine::getSong()->getTempo() );
	connect( Engine::getSong(), SIGNAL( tempoChanged( bpm_t ) ),
			this, SLOT( changeQueueTempo( bpm_t ) ), Qt::DirectConnection );

	// initial list-update
	updatePortList();

	connect( &m_portListUpdateTimer, SIGNAL( timeout() ),
					this, SLOT( updatePortList() ) );
	// we check for port-changes every second
	m_portListUpdateTimer.start( 1000 );

	// use a pipe to detect shutdown
	if( pipe( m_pipe ) == -1 )
	{
		perror( "MidiAlsaSeq: pipe" );
	}

	start( QThread::IdlePriority );
}

MidiAlsaSeq::~MidiAlsaSeq()
{
	if( isRunning() )
	{
		m_quit = true;
		wait( EventPollTimeOut*2 );

		m_seqMutex.lock();
		snd_seq_stop_queue( m_seqHandle, m_queueID, NULL );
		snd_seq_free_queue( m_seqHandle, m_queueID );
		snd_seq_close( m_seqHandle );
		m_seqMutex.unlock();
	}
}

QString MidiAlsaSeq::probeDevice()
{
	QString dev = ConfigManager::inst()->value( "Midialsaseq", "device" );
	if( dev.isEmpty() )
	{
		if( getenv( "MIDIDEV" ) != NULL )
		{
			return getenv( "MIDIDEV" );
		}
		return "default";
	}
	return dev;
}

void MidiAlsaSeq::processOutEvent( const MidiEvent& event, const TimePos& time, const MidiPort* port )
{
	// HACK!!! - need a better solution which isn't that easy since we
	// cannot store const-ptrs in our map because we need to call non-const
	// methods of MIDI-port - it's a mess...
	MidiPort* p = const_cast<MidiPort *>( port );

	snd_seq_event_t ev;
	snd_seq_ev_clear( &ev );
	snd_seq_ev_set_source( &ev, ( m_portIDs[p][1] != -1 ) ?
					m_portIDs[p][1] : m_portIDs[p][0] );
	snd_seq_ev_set_subs( &ev );
	snd_seq_ev_schedule_tick( &ev, m_queueID, 1, static_cast<int>( time ) );
	ev.queue =  m_queueID;
	switch( event.type() )
	{
		case MidiNoteOn:
			snd_seq_ev_set_noteon( &ev,
						event.channel(),
						event.key() + KeysPerOctave,
						event.velocity() );
			break;

		case MidiNoteOff:
			snd_seq_ev_set_noteoff( &ev,
						event.channel(),
						event.key() + KeysPerOctave,
						event.velocity() );
			break;

		case MidiKeyPressure:
			snd_seq_ev_set_keypress( &ev,
						event.channel(),
						event.key() + KeysPerOctave,
						event.velocity() );
			break;

		case MidiControlChange:
			snd_seq_ev_set_controller( &ev,
						event.channel(),
						event.controllerNumber(),
						event.controllerValue() );
			break;

		case MidiProgramChange:
			snd_seq_ev_set_pgmchange( &ev,
						event.channel(),
						event.program() );
			break;

		case MidiChannelPressure:
			snd_seq_ev_set_chanpress( &ev,
						event.channel(),
						event.channelPressure() );
			break;

		case MidiPitchBend:
			snd_seq_ev_set_pitchbend( &ev,
						event.channel(),
						event.param( 0 ) - 8192 );
			break;

		default:
			qWarning( "MidiAlsaSeq: unhandled output event %d\n", (int) event.type() );
			return;
	}

	m_seqMutex.lock();
	snd_seq_event_output( m_seqHandle, &ev );
	snd_seq_drain_output( m_seqHandle );
	m_seqMutex.unlock();

}

void MidiAlsaSeq::applyPortMode( MidiPort * _port )
{
	m_seqMutex.lock();

	// determine port-capabilities
	unsigned int caps[2] = { 0, 0 };

	switch( _port->mode() )
	{
		case MidiPort::Duplex:
			caps[1] |= SND_SEQ_PORT_CAP_READ |
						SND_SEQ_PORT_CAP_SUBS_READ;

		case MidiPort::Input:
			caps[0] |= SND_SEQ_PORT_CAP_WRITE |
						SND_SEQ_PORT_CAP_SUBS_WRITE;
			break;

		case MidiPort::Output:
			caps[1] |= SND_SEQ_PORT_CAP_READ |
						SND_SEQ_PORT_CAP_SUBS_READ;
			break;

		default:
			break;
	}

	for( int i = 0; i < 2; ++i )
	{
		if( caps[i] != 0 )
		{
			// no port there yet?
			if( m_portIDs[_port][i] == -1 )
			{
				// then create one;
				m_portIDs[_port][i] =
						snd_seq_create_simple_port(
							m_seqHandle,
				_port->displayName().toUtf8().constData(),
							caps[i],
						SND_SEQ_PORT_TYPE_MIDI_GENERIC |
						SND_SEQ_PORT_TYPE_APPLICATION );
				continue;
			}
			snd_seq_port_info_t * port_info;
			snd_seq_port_info_malloc( &port_info );
			snd_seq_get_port_info( m_seqHandle, m_portIDs[_port][i],
							port_info );
			snd_seq_port_info_set_capability( port_info, caps[i] );
			snd_seq_set_port_info( m_seqHandle, m_portIDs[_port][i],
							port_info );
			snd_seq_port_info_free( port_info );
		}
		// still a port there although no caps? ( = dummy port)
		else if( m_portIDs[_port][i] != -1 )
		{
			// then remove this port
			snd_seq_delete_simple_port( m_seqHandle,
							m_portIDs[_port][i] );
			m_portIDs[_port][i] = -1;
		}
	}

	m_seqMutex.unlock();
}

void MidiAlsaSeq::applyPortName( MidiPort * _port )
{
	m_seqMutex.lock();

	for( int i = 0; i < 2; ++i )
	{
		if( m_portIDs[_port][i] == -1 )
		{
			continue;
		}
		snd_seq_port_info_t * port_info;
		snd_seq_port_info_malloc( &port_info );
		snd_seq_get_port_info( m_seqHandle, m_portIDs[_port][i],
							port_info );
		snd_seq_port_info_set_name( port_info,
				_port->displayName().toUtf8().constData() );
		snd_seq_set_port_info( m_seqHandle, m_portIDs[_port][i],
							port_info );
		snd_seq_port_info_free( port_info );
	}

	m_seqMutex.unlock();
}

void MidiAlsaSeq::removePort( MidiPort * _port )
{
	if( m_portIDs.contains( _port ) )
	{
		m_seqMutex.lock();
		snd_seq_delete_simple_port( m_seqHandle, m_portIDs[_port][0] );
		snd_seq_delete_simple_port( m_seqHandle, m_portIDs[_port][1] );
		m_seqMutex.unlock();

		m_portIDs.remove( _port );
	}
	MidiClient::removePort( _port );
}

QString MidiAlsaSeq::sourcePortName( const MidiEvent & _event ) const
{
	if( _event.sourcePort() )
	{
		const snd_seq_addr_t * addr =
			static_cast<const snd_seq_addr_t *>( _event.sourcePort() );
		return portName( m_seqHandle, addr );
	}
	return MidiClient::sourcePortName( _event );
}

void MidiAlsaSeq::subscribeReadablePort( MidiPort * _port,
						const QString & _dest,
						bool _subscribe )
{
	if( !m_portIDs.contains( _port ) || m_portIDs[_port][0] < 0 )
	{
		return;
	}

	m_seqMutex.lock();

	snd_seq_addr_t sender;
	if( snd_seq_parse_address( m_seqHandle, &sender,
			_dest.section( ' ', 0, 0 ).toLatin1().constData() ) )
	{
		fprintf( stderr, "error parsing sender-address!\n" );

		m_seqMutex.unlock();
		return;
	}
	snd_seq_port_info_t * port_info;
	snd_seq_port_info_malloc( &port_info );
	snd_seq_get_port_info( m_seqHandle, m_portIDs[_port][0], port_info );
	const snd_seq_addr_t * dest = snd_seq_port_info_get_addr( port_info );
	snd_seq_port_subscribe_t * subs;
	snd_seq_port_subscribe_malloc( &subs );
	snd_seq_port_subscribe_set_sender( subs, &sender );
	snd_seq_port_subscribe_set_dest( subs, dest );
	if( _subscribe )
	{
		snd_seq_subscribe_port( m_seqHandle, subs );
	}
	else
	{
		snd_seq_unsubscribe_port( m_seqHandle, subs );
	}
	snd_seq_port_subscribe_free( subs );
	snd_seq_port_info_free( port_info );

	m_seqMutex.unlock();
}

void MidiAlsaSeq::subscribeWritablePort( MidiPort * _port,
						const QString & _dest,
						bool _subscribe )
{
	if( !m_portIDs.contains( _port ) )
	{
		return;
	}
	const int pid = m_portIDs[_port][1] < 0 ? m_portIDs[_port][0] :
							m_portIDs[_port][1];
	if( pid < 0 )
	{
		return;
	}

	m_seqMutex.lock();

	snd_seq_addr_t dest;
	if( snd_seq_parse_address( m_seqHandle, &dest,
			_dest.section( ' ', 0, 0 ).toLatin1().constData() ) )
	{
		fprintf( stderr, "error parsing dest-address!\n" );
		m_seqMutex.unlock();
		return;
	}
	snd_seq_port_info_t * port_info;
	snd_seq_port_info_malloc( &port_info );
	snd_seq_get_port_info( m_seqHandle, pid, port_info );
	const snd_seq_addr_t * sender = snd_seq_port_info_get_addr( port_info );
	snd_seq_port_subscribe_t * subs;
	snd_seq_port_subscribe_malloc( &subs );
	snd_seq_port_subscribe_set_sender( subs, sender );
	snd_seq_port_subscribe_set_dest( subs, &dest );
	if( _subscribe )
	{
		snd_seq_subscribe_port( m_seqHandle, subs );
	}
	else
	{
		snd_seq_unsubscribe_port( m_seqHandle, subs );
	}
	snd_seq_port_subscribe_free( subs );
	snd_seq_port_info_free( port_info );
	m_seqMutex.unlock();
}

void MidiAlsaSeq::run()
{
	// watch the pipe and sequencer input events
	int pollfd_count = snd_seq_poll_descriptors_count( m_seqHandle,
								POLLIN );
	struct pollfd * pollfd_set = new struct pollfd[pollfd_count + 1];
	snd_seq_poll_descriptors( m_seqHandle, pollfd_set + 1, pollfd_count,
								POLLIN );
	pollfd_set[0].fd = m_pipe[0];
	pollfd_set[0].events = POLLIN;
	++pollfd_count;

	while( m_quit == false )
	{
		int pollRet = poll( pollfd_set, pollfd_count, EventPollTimeOut );
		if( pollRet == 0 )
		{
			continue;
		}
		else if( pollRet == -1 )
		{
			// gdb may interrupt the poll
			if( errno == EINTR )
			{
				continue;
			}
			qCritical( "error while polling ALSA sequencer handle" );
			break;
		}
		// shutdown?
		if( m_quit )
		{
			break;
		}

		m_seqMutex.lock();

		// while event queue is not empty
		while( snd_seq_event_input_pending( m_seqHandle, true ) > 0 )
		{
			snd_seq_event_t * ev;
			if( snd_seq_event_input( m_seqHandle, &ev ) < 0 )
			{
				m_seqMutex.unlock();

				qCritical( "error while fetching MIDI event from sequencer" );
				break;
			}
			m_seqMutex.unlock();

			snd_seq_addr_t * source = NULL;
			MidiPort * dest = NULL;
			for( int i = 0; i < m_portIDs.size(); ++i )
			{
				if( m_portIDs.values()[i][0] == ev->dest.port )
				{
					dest = m_portIDs.keys()[i];
				}
				if( ( m_portIDs.values()[i][1] != -1 &&
						m_portIDs.values()[i][1] == ev->source.port ) ||
							m_portIDs.values()[i][0] == ev->source.port )
				{
					source = &ev->source;
				}
			}

			if( dest == NULL )
			{
				continue;
			}

			switch( ev->type )
			{
				case SND_SEQ_EVENT_NOTEON:
					dest->processInEvent( MidiEvent( MidiNoteOn,
								ev->data.note.channel,
								ev->data.note.note -
								KeysPerOctave,
								ev->data.note.velocity,
								source
								),
							TimePos( ev->time.tick ) );
					break;

				case SND_SEQ_EVENT_NOTEOFF:
					dest->processInEvent( MidiEvent( MidiNoteOff,
								ev->data.note.channel,
								ev->data.note.note -
								KeysPerOctave,
								ev->data.note.velocity,
								source
								),
							TimePos( ev->time.tick) );
					break;

				case SND_SEQ_EVENT_KEYPRESS:
					dest->processInEvent( MidiEvent(
									MidiKeyPressure,
								ev->data.note.channel,
								ev->data.note.note -
								KeysPerOctave,
								ev->data.note.velocity,
								source
								), TimePos() );
					break;

				case SND_SEQ_EVENT_CONTROLLER:
					dest->processInEvent( MidiEvent(
							MidiControlChange,
							ev->data.control.channel,
							ev->data.control.param,
							ev->data.control.value, source ),
									TimePos() );
					break;

				case SND_SEQ_EVENT_PGMCHANGE:
					dest->processInEvent( MidiEvent(
							MidiProgramChange,
							ev->data.control.channel,
							ev->data.control.value,	0,
							source ),
								TimePos() );
					break;

				case SND_SEQ_EVENT_CHANPRESS:
					dest->processInEvent( MidiEvent(
								MidiChannelPressure,
							ev->data.control.channel,
							ev->data.control.param,
							ev->data.control.value, source ),
									TimePos() );
					break;

				case SND_SEQ_EVENT_PITCHBEND:
					dest->processInEvent( MidiEvent( MidiPitchBend,
							ev->data.control.channel,
							ev->data.control.value + 8192, 0, source ),
									TimePos() );
					break;

				case SND_SEQ_EVENT_SENSING:
				case SND_SEQ_EVENT_CLOCK:
					break;

				default:
					fprintf( stderr,
						"ALSA-sequencer: unhandled input "
							"event %d\n", ev->type );
					break;
			}	// end switch

			m_seqMutex.lock();

		}	// end while

		m_seqMutex.unlock();

	}

	delete[] pollfd_set;
}

void MidiAlsaSeq::changeQueueTempo( bpm_t _bpm )
{
	m_seqMutex.lock();

	snd_seq_change_queue_tempo( m_seqHandle, m_queueID,
					60000000 / (int) _bpm, NULL );
	snd_seq_drain_output( m_seqHandle );

	m_seqMutex.unlock();
}

void MidiAlsaSeq::updatePortList()
{
	QStringList readablePorts;
	QStringList writablePorts;

	// get input- and output-ports
	snd_seq_client_info_t * cinfo;
	snd_seq_port_info_t * pinfo;

	snd_seq_client_info_malloc( &cinfo );
	snd_seq_port_info_malloc( &pinfo );

	snd_seq_client_info_set_client( cinfo, -1 );

	m_seqMutex.lock();

	while( snd_seq_query_next_client( m_seqHandle, cinfo ) >= 0 )
	{
		int client = snd_seq_client_info_get_client( cinfo );

		snd_seq_port_info_set_client( pinfo, client );
		snd_seq_port_info_set_port( pinfo, -1 );
		while( snd_seq_query_next_port( m_seqHandle, pinfo ) >= 0 )
		{
			// we need both READ and SUBS_READ
			if( ( snd_seq_port_info_get_capability( pinfo )
			     & ( SND_SEQ_PORT_CAP_READ |
					SND_SEQ_PORT_CAP_SUBS_READ ) ) ==
					( SND_SEQ_PORT_CAP_READ |
					  	SND_SEQ_PORT_CAP_SUBS_READ ) )
			{
				readablePorts.push_back( portName( cinfo, pinfo ) );
			}
			if( ( snd_seq_port_info_get_capability( pinfo )
			     & ( SND_SEQ_PORT_CAP_WRITE |
					SND_SEQ_PORT_CAP_SUBS_WRITE ) ) ==
					( SND_SEQ_PORT_CAP_WRITE |
					  	SND_SEQ_PORT_CAP_SUBS_WRITE ) )
			{
				writablePorts.push_back( portName( cinfo, pinfo ) );
			}
		}
	}

	m_seqMutex.unlock();

	snd_seq_client_info_free( cinfo );
	snd_seq_port_info_free( pinfo );

	if( m_readablePorts != readablePorts )
	{
		m_readablePorts = readablePorts;
		emit readablePortsChanged();
	}

	if( m_writablePorts != writablePorts )
	{
		m_writablePorts = writablePorts;
		emit writablePortsChanged();
	}
}

#endif


code:
/*
 * MidiApple.cpp - Apple MIDI client
 *
 * Copyright (c) 2005-2014 Tobias Doerffel <tobydox/at/users.sourceforge.net>
 * Copyright (c) 2015 Maurizio Lo Bosco (rageboge on github)
 *
 * This file is part of LMMS - https://lmms.io
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program (see COPYING); if not, write to the
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
 *
 */

#include "MidiApple.h"

#ifdef LMMS_BUILD_APPLE

#include <QtAlgorithms>
#include <algorithm>

#include "ConfigManager.h"
#include "MidiPort.h"
#include "Note.h"

#include <CoreMIDI/CoreMIDI.h>

const unsigned int SYSEX_LENGTH=1024;

MidiApple::MidiApple() :
	MidiClient(),
	m_inputDevices(),
	m_outputDevices(),
	m_inputSubs(),
	m_outputSubs()
{
	openDevices();
}

MidiApple::~MidiApple()
{
	closeDevices();
}

void MidiApple::processOutEvent( const MidiEvent& event, const TimePos& time, const MidiPort* port )
{
	qDebug("MidiApple:processOutEvent displayName:'%s'",port->displayName().toLatin1().constData());

	QStringList outDevs;
	for( SubMap::ConstIterator it = m_outputSubs.begin(); it != m_outputSubs.end(); ++it )
	{
		for( MidiPortList::ConstIterator jt = it.value().begin(); jt != it.value().end(); ++jt )
		{
			if( *jt == port )
			{
				outDevs += it.key();
				break;
			}
		}
	}

	for( QMap<QString, MIDIEndpointRef>::Iterator it = m_outputDevices.begin(); it != m_outputDevices.end(); ++it )
	{
		if( outDevs.contains( it.key() ) )
		{
			sendMidiOut( it.value(), event );
		}
	}

}

void MidiApple::sendMidiOut( MIDIEndpointRef &endPointRef, const MidiEvent& event )
{
	MIDIPacketList packetList=createMidiPacketList(event);
	MIDIPortRef port = m_sourcePortRef.value(endPointRef);
	MIDISend(port, endPointRef, &packetList);
}

MIDIPacketList MidiApple::createMidiPacketList( const MidiEvent& event )
{
	MIDIPacketList packetList;
	packetList.numPackets = 1;
	MIDIPacket* firstPacket = &packetList.packet[0];
	firstPacket->timeStamp = 0; // send immediately
	firstPacket->length = 3;
	firstPacket->data[0] = ( event.type() + event.channel() );
	firstPacket->data[1] = ( event.param( 0 ) & 0xff );
	firstPacket->data[2] = ( event.param( 1 ) & 0xff );
	return packetList;
}

void MidiApple::applyPortMode( MidiPort* port )
{
	qDebug("applyPortMode displayName:'%s'",port->displayName().toLatin1().constData());
	// make sure no subscriptions exist which are not possible with
	// current port-mode
	if( !port->isInputEnabled() )
	{
		for( SubMap::Iterator it = m_inputSubs.begin(); it != m_inputSubs.end(); ++it )
		{
			it.value().removeAll( port );
		}
	}

	if( !port->isOutputEnabled() )
	{
		for( SubMap::Iterator it = m_outputSubs.begin(); it != m_outputSubs.end(); ++it )
		{
			it.value().removeAll( port );
		}
	}
}

void MidiApple::removePort( MidiPort* port )
{
	qDebug("removePort displayName:'%s'",port->displayName().toLatin1().constData());
	for( SubMap::Iterator it = m_inputSubs.begin(); it != m_inputSubs.end(); ++it )
	{
		it.value().removeAll( port );
	}

	for( SubMap::Iterator it = m_outputSubs.begin(); it != m_outputSubs.end(); ++it )
	{
		it.value().removeAll( port );
	}

	MidiClient::removePort( port );
}

QString MidiApple::sourcePortName( const MidiEvent& event ) const
{
	qDebug("sourcePortName return '%s'?\n", event.sourcePort());
	/*
	if( event.sourcePort() )
	{
		return m_inputDevices.value( *static_cast<const HMIDIIN *>( event.sourcePort() ) );
	}
	*/
	return MidiClient::sourcePortName( event );
}

void MidiApple::subscribeReadablePort( MidiPort* port, const QString& dest, bool subscribe )
{
	qDebug("subscribeReadablePort %s subscribe=%d",dest.toLatin1().constData(),subscribe);
	if( subscribe && port->isInputEnabled() == false )
	{
		qWarning( "port %s can't be (un)subscribed!", port->displayName().toLatin1().constData() );
		return;
	}

	m_inputSubs[dest].removeAll( port );
	if( subscribe )
	{
		qDebug("Subscribing %s",dest.toLatin1().constData());
		m_inputSubs[dest].push_back( port );
	}
	else
	{
		MidiPortList list = m_inputSubs[dest];
		if(list.empty()){
			m_inputSubs.remove(dest);
		}
	}
}

void MidiApple::subscribeWritablePort( MidiPort* port, const QString& dest, bool subscribe )
{
	qDebug("subscribeWritablePort %s", port->displayName().toLatin1().constData());

	if( subscribe && port->isOutputEnabled() == false )
	{
		qWarning( "port %s can't be (un)subscribed!", port->displayName().toLatin1().constData() );
		return;
	}

	m_outputSubs[dest].removeAll( port );
	if( subscribe )
	{
		m_outputSubs[dest].push_back( port );
	}
	else
	{
		MidiPortList list = m_outputSubs[dest];
		if(list.empty()){
			m_outputSubs.remove(dest);
		}
	}
}

void MidiApple::ReadCallback( const MIDIPacketList *pktlist, void *readProcRefCon, void *srcConnRefCon )
{
	MidiApple *caller = static_cast<MidiApple*>(readProcRefCon);
	if (!caller)
	{
		qDebug("Error: !caller: MidiApple::ReadCallback");
		return;
	}
	caller->HandleReadCallback(pktlist,srcConnRefCon);
}

void MidiApple::HandleReadCallback( const MIDIPacketList *pktlist, void *srcConnRefCon )
{
	const char * refName = (const char *) srcConnRefCon;

	MIDIEndpointRef endPointRef = m_inputDevices.value(refName);
	if( !m_inputSubs.contains( refName ) )
	{
//		qDebug("HandleReadCallback '%s' not subscribed",refName);
//		printQStringKeys("m_inputDevices", m_inputDevices);
		return;
	}
//	qDebug("HandleReadCallback '%s' subscribed",refName);
	bool continueSysEx = false;
	unsigned int nBytes;
	const MIDIPacket *packet = &pktlist->packet[0];
	unsigned char sysExMessage[SYSEX_LENGTH];
	unsigned int sysExLength = 0;

	for (uint32_t i=0; i<pktlist->numPackets; ++i)
	{
		nBytes = packet->length;
		// Check if this is the end of a continued SysEx message
		if (continueSysEx) {
			unsigned int lengthToCopy = qMin(nBytes, SYSEX_LENGTH - sysExLength);
			// Copy the message into our SysEx message buffer,
			// making sure not to overrun the buffer
			memcpy(sysExMessage + sysExLength, packet->data, lengthToCopy);
			sysExLength += lengthToCopy;
			// Check if the last byte is SysEx End.
			continueSysEx = (packet->data[nBytes - 1] == 0xF7);
			if (!continueSysEx || sysExLength == SYSEX_LENGTH) {
				// We would process the SysEx message here, as it is we're just ignoring it

				sysExLength = 0;
			}
		}
		else
		{
			UInt16 iByte, size;

			iByte = 0;
			while (iByte < nBytes)
			{
				size = 0;

				// First byte should be status
				unsigned char status = packet->data[iByte];
				if (status < 0xC0) {
					size = 3;
				}
				else if (status < 0xE0)
				{
					size = 2;
				}
				else if (status < 0xF0)
				{
					size = 3;
				}
				else if (status == 0xF0)
				{
					// MIDI SysEx then we copy the rest of the message into the SysEx message buffer
					unsigned int lengthLeftInMessage = nBytes - iByte;
					unsigned int lengthToCopy = qMin(lengthLeftInMessage, SYSEX_LENGTH);

					memcpy(sysExMessage + sysExLength, packet->data, lengthToCopy);
					sysExLength += lengthToCopy;

					size = 0;
					iByte = nBytes;

					// Check whether the message at the end is the end of the SysEx
					continueSysEx = (packet->data[nBytes - 1] != 0xF7);
				}
				else if (status < 0xF3)
				{
					size = 3;
				}
				else if (status == 0xF3)
				{
					size = 2;
				}
				else
				{
					size = 1;
				}

				unsigned char messageChannel = status & 0xF;
				const MidiEventTypes cmdtype = static_cast<MidiEventTypes>( status & 0xF0 );
				const int par1 = packet->data[iByte + 1];
				const int par2 = packet->data[iByte + 2];

				switch (cmdtype)
				{
					case MidiNoteOff: //0x80:
					case MidiNoteOn: //0x90:
					case MidiKeyPressure: //0xA0:
						notifyMidiPortList(m_inputSubs[refName],MidiEvent( cmdtype, messageChannel, par1 - KeysPerOctave, par2 & 0xff, &endPointRef ));
						break;

					case MidiControlChange: //0xB0:
					case MidiProgramChange: //0xC0:
					case MidiChannelPressure: //0xD0:
						notifyMidiPortList(m_inputSubs[refName],MidiEvent( cmdtype, messageChannel, par1, par2 & 0xff, &endPointRef ));
						break;

					case MidiPitchBend: //0xE0:
						notifyMidiPortList(m_inputSubs[refName],MidiEvent( cmdtype, messageChannel, par1 + par2 * 128, 0, &endPointRef ));
						break;
					case MidiActiveSensing: //0xF0
					case 0xF0:
						break;
					default:
						qDebug("endPointRef name='%s':Some other message %d", refName, cmdtype);
						break;
				}
				iByte += size;
			}
		}
		packet = MIDIPacketNext(packet);
	}
}

void MidiApple::updateDeviceList()
{
	closeDevices();
	openDevices();

	emit readablePortsChanged();
	emit writablePortsChanged();
}

void MidiApple::closeDevices()
{
	m_inputSubs.clear();
	m_outputSubs.clear();

	QMapIterator<QString, MIDIEndpointRef> i( m_inputDevices );
	while( i.hasNext() )
	{
		midiInClose( i.next().value() );
	}

	QMapIterator<QString, MIDIEndpointRef> o( m_outputDevices );
	while( o.hasNext() )
	{
		midiInClose( o.next().value() );
	}

	m_inputDevices.clear();
	m_outputDevices.clear();
}

void MidiApple::midiInClose( MIDIEndpointRef reference )
{
	qDebug("midiInClose '%s'", getFullName(reference));
	MIDIPortRef portRef = m_sourcePortRef[reference];
	MIDIPortDisconnectSource(portRef, reference);
}

char *getName( MIDIObjectRef &object )
{
	// Returns the name of a given MIDIObjectRef as char *
	CFStringRef name = nullptr;
	if (noErr != MIDIObjectGetStringProperty(object, kMIDIPropertyName, &name))
		return nullptr;
	int len = CFStringGetLength(name)+1;
	char *value = (char *) malloc(len);

	CFStringGetCString(name, value, len, 0);
	CFRelease(name);
	return value;
}

const void printQStringKeys( char const * mapName, const QMap<QString,MIDIEndpointRef> &inputMap )
{
	qDebug("%s:", mapName);
	QMapIterator<QString, MIDIEndpointRef> i( inputMap );
	while( i.hasNext() )
	{
		QString key = i.next().key();
		qDebug("  key='%s'",  key.toLatin1().constData());
	}
}

void MidiApple::openDevices()
{
	qDebug("openDevices");
	m_inputDevices.clear();
	// How many MIDI devices do we have?
	ItemCount deviceCount = MIDIGetNumberOfDevices();

	// Iterate through all MIDI devices
	for (ItemCount i = 0 ; i < deviceCount ; ++i)
	{
		// Grab a reference to current device
		MIDIDeviceRef device = MIDIGetDevice(i);
		char * deviceName = getName(device);
		QString qsDeviceName = QString::fromUtf8((char*)(deviceName));
		qDebug("Device name:%s",deviceName);

		// Is this device online? (Currently connected?)
		SInt32 isOffline = 0;
		MIDIObjectGetIntegerProperty(device, kMIDIPropertyOffline, &isOffline);
		qDebug(" is online: %s", (isOffline ? "No" : "Yes"));
		// How many entities do we have?
		ItemCount entityCount = MIDIDeviceGetNumberOfEntities(device);

		// Iterate through this device's entities
		for (ItemCount j = 0 ; j < entityCount ; ++j)
		{
			// Grab a reference to an entity
			MIDIEntityRef entity = MIDIDeviceGetEntity(device, j);
			qDebug("  Entity: %s", getName(entity));

			// Iterate through this device's source endpoints (MIDI In)
			ItemCount sourceCount = MIDIEntityGetNumberOfSources(entity);
			for ( ItemCount k = 0 ; k < sourceCount ; ++k )
			{
				// Grab a reference to a source endpoint
				MIDIEndpointRef source = MIDIEntityGetSource(entity, k);
				char * name = getName(source);
				qDebug("	Source: '%s'", name);
				QString sourceName = qsDeviceName + ":" + QString::fromUtf8((char*)(name));
				qDebug("	Source name: '%s'", sourceName.toLatin1().constData() );
				m_inputDevices.insert(sourceName, source);
				openMidiReference(source,sourceName,true);
			}

			// Iterate through this device's destination endpoints (MIDI Out)
			ItemCount destCount = MIDIEntityGetNumberOfDestinations(entity);
			for ( ItemCount k = 0 ; k < destCount ; ++k )
			{
				// Grab a reference to a destination endpoint
				MIDIEndpointRef dest = MIDIEntityGetDestination(entity, k);
				char * name = getName(dest);
				qDebug("	Destination: '%s'", name);
				QString destinationName = qsDeviceName + ":" + QString::fromUtf8((char*)(name));
				qDebug("	Destination name: '%s'", destinationName.toLatin1().constData() );
				m_outputDevices.insert(destinationName, dest);
				openMidiReference(dest,destinationName,false);
			}
		}
		qDebug("------");
	}
	printQStringKeys("m_inputDevices:",m_inputDevices);
	printQStringKeys("m_outputDevices:",m_outputDevices);
}

void MidiApple::openMidiReference( MIDIEndpointRef reference, QString refName, bool isIn )
{
	char * registeredName = (char*) malloc(refName.length()+1);
	sprintf(registeredName, "%s",refName.toLatin1().constData());
	qDebug("openMidiReference refName '%s'",refName.toLatin1().constData());

	MIDIClientRef mClient = getMidiClientRef();
	MIDIPortRef mPort = 0;

	CFStringRef inName = CFStringCreateWithCString(0, registeredName, kCFStringEncodingASCII);
	if(isIn)
	{
		MIDIInputPortCreate(mClient, inName, &MidiApple::ReadCallback, this, &mPort);
	}
	else
	{
		MIDIOutputPortCreate(mClient, inName, &mPort);
	}
	MIDIPortConnectSource(mPort, reference, (void *)registeredName);
	m_sourcePortRef.insert(reference, mPort);
	CFRelease(inName);
	qDebug("openMidiReference registeredName '%s'",registeredName);
}

MIDIClientRef MidiApple::getMidiClientRef()
{
	if(mClient==0)
	{
		CFStringRef deviceClientName = CFSTR("MIDI In Device Client");
		MIDIClientCreate(deviceClientName, NotifyCallback, this, &mClient);
		CFRelease(deviceClientName);
	}
	return mClient;
}

void MidiApple::notifyMidiPortList( MidiPortList l, MidiEvent event )
{
	for( MidiPortList::ConstIterator it = l.begin(); it != l.end(); ++it )
	{
		( *it )->processInEvent( event);
	}
}

void MidiApple::NotifyCallback( const MIDINotification *message, void *refCon )
{
	//refCon is a pointer to MidiApple class
	MidiApple *midiApple = (MidiApple *)refCon;
	qDebug("MidiApple::NotifyCallback '%d'",message->messageID);
	switch (message->messageID)
	{
		case kMIDIMsgObjectAdded:
		{
			MIDIObjectAddRemoveNotification* msg = (MIDIObjectAddRemoveNotification*)message ;
			MIDIEndpointRef endpoint_ref = (MIDIEndpointRef)msg->child ;
			char * fullName = midiApple->getFullName(endpoint_ref);

			if (msg->childType == kMIDIObjectType_Source) {
				qDebug("kMIDIMsgObjectAdded source '%s'",fullName);
				// Here your code to save the new source ref
				// and update your internal "clients" (eg
				// update a popup menu, a list, ...)
			}
			if (msg->childType == kMIDIObjectType_Destination) {
				qDebug("kMIDIMsgObjectAdded destination '%s'",fullName);
				// Here your code to save the new destination ref
				// and update your internal "clients"
			}
			break;
		}
		case kMIDIMsgObjectRemoved:
		{
			MIDIObjectAddRemoveNotification*
			msg = (MIDIObjectAddRemoveNotification*)message ;
			MIDIEndpointRef endpoint_ref = (MIDIEndpointRef)msg->child ;
			char * fullName = midiApple->getFullName(endpoint_ref);

			if (msg->childType == kMIDIObjectType_Source) {
				// Here your code to remove the source ref
				// and update your internal "clients"
				qDebug("kMIDIMsgObjectRemoved source '%s'",fullName);
			}
			if (msg->childType == kMIDIObjectType_Destination) {
				// Here your code to remove the destination ref
				// and update your internal "clients"
				qDebug("kMIDIMsgObjectRemoved destination '%s'",fullName);			}
			break;
		}
		case kMIDIMsgPropertyChanged:
			// Currently ignored
			qDebug("kMIDIMsgPropertyChanged");
			break;
		case kMIDIMsgThruConnectionsChanged:
			// Currently ignored
			qDebug("kMIDIMsgThruConnectionsChanged");
			break;
		case kMIDIMsgSerialPortOwnerChanged:
			// Currently ignored
			qDebug("kMIDIMsgSerialPortOwnerChanged");
			break;
		default:
			qDebug("unhandled message type");
			break;
	}
}

char * MidiApple::getFullName(MIDIEndpointRef &endpoint_ref)
{
	MIDIEntityRef entity = 0;
	MIDIEndpointGetEntity(endpoint_ref, &entity); //get the entity
	MIDIDeviceRef device = 0;
	MIDIEntityGetDevice(entity, &device);
	char * deviceName = getName(device);
	char * endPointName = getName(endpoint_ref);
	qDebug("device name='%s' endpoint name='%s'",deviceName,endPointName);
	size_t deviceNameLen = deviceName == nullptr ? 0 : strlen(deviceName);
	size_t endPointNameLen = endPointName == nullptr ? 0 : strlen(endPointName);
	char * fullName = (char *)malloc(deviceNameLen + endPointNameLen + 2);
	sprintf(fullName, "%s:%s", deviceName,endPointName);
	if (deviceName != nullptr) { free(deviceName); }
	if (endPointName != nullptr) { free(endPointName); }
	return fullName;
}

#endif