Linux MIDI support?


#1

Jules, do you have an idea when you might add MIDI support for the linux port? It’s just that I’m adding MIDI to my Mouse to OSC program, and I’m going to need it working on my linux machine at uni.

It’s relatively urgent (I had actually thought MIDI was already implemented, having noticed the juce_linux_Midi.cpp file - it was only recently that I realised that file is essentially empty :oops:), so if it’s low down on your list of priorities, I’ll have a go at implementing it myself, but I don’t want to end up replicating lines of code if you’re already working on it.

  • Niall.

#2

Hi there

No immediate plans, and I’m a bit booked up, but if you wanted to have a shot I’d be happy to advise and help out. I had a quick look at it when I was doing ALSA and it seemed fairly straightforward.


#3

Okay, here’s my juce_linux_midi.cpp, using ALSA’s sequencer API, so you can do inter-app connections :D. It’s not by any means complete - it only handles MIDI input, and I left out anything I don’t actually need (timestamps, sysex etc.), but it works well:

[code]/*

This file is part of the JUCE library - "Jules’ Utility Class Extensions"
Copyright 2004-6 by Raw Material Software ltd.


JUCE can be redistributed and/or modified 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.

JUCE 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 JUCE; if not, visit www.gnu.org/licenses or write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA


If you’d like to release a closed-source product which uses JUCE, commercial
licenses are also available: visit www.rawmaterialsoftware.com/juce for
more information.

==============================================================================
*/

#include “juce_Config.h”
#if JUCE_BUILD_GUI_CLASSES

#include “…/…/…/src/juce_core/basics/juce_StandardHeader.h”

#include “alsa/asoundlib.h”
#include

using namespace std;

BEGIN_JUCE_NAMESPACE

#include “…/…/…/src/juce_appframework/audio/devices/juce_MidiOutput.h”
#include “…/…/…/src/juce_appframework/audio/devices/juce_MidiInput.h”
#include “…/…/…/src/juce_core/threads/juce_Thread.h”

//==============================================================================
const StringArray MidiOutput::getDevices()
{
StringArray s;
return s;
}

int MidiOutput::getDefaultDeviceIndex()
{
return 0;
}

MidiOutput* MidiOutput::openDevice (int deviceIndex)
{
return 0;
}

MidiOutput::MidiOutput()
{
}

MidiOutput::~MidiOutput()
{
}

void MidiOutput::reset()
{
}

bool MidiOutput::getVolume (float& leftVol, float& rightVol)
{
return false;
}

void MidiOutput::setVolume (float leftVol, float rightVol)
{
}

void MidiOutput::sendMessageNow (const MidiMessage& message)
{
}

//==============================================================================

//------------------------------------------------------------------------------
/// Thread used to handle all MIDI input.
class MidiInputThread : public Thread
{
public:
/// Constructor.
/*!
\param input Pointer to the MidiInput object in charge of the MIDI
stuff.
\param handle Handle to the sequencer object used to receive MIDI events
from.
\param inputCallback Pointer to the callback object to be called when
a MIDI event is received.
*/
MidiInputThread(MidiInput *input,
snd_seq_t *handle,
MidiInputCallback * const inputCallback):
Thread(T(“JUCE MIDI Input Thread”)),
midiInput(input),
seqHandle(handle),
callback(inputCallback)
{

};
///	Destructor.
~MidiInputThread()
{
	if(seqHandle)
	{
		//Do I need to do anything else?
		snd_seq_close(seqHandle);
	}
};

///	Where the main thread activity takes place.
void run()
{
	int npfd = -1;
	struct pollfd *pfd = 0;
	snd_seq_event_t *event = 0;
	unsigned char tempBytes[32];
	snd_midi_event_t *midiParser;
	int byteCount;

	if(snd_midi_event_new(32, &midiParser) != 0)
	{
		cout << "Could not create MIDI parser." << endl;
		midiParser = 0;
	}

	//Polling code taken from:
	//http://www.suse.de/~mana/seqdemo.c
	if(seqHandle)
	{
		npfd = snd_seq_poll_descriptors_count(seqHandle, POLLIN);
		pfd = (struct pollfd *)alloca(npfd * sizeof(struct pollfd));
		snd_seq_poll_descriptors(seqHandle, pfd, npfd, POLLIN);

		while(!threadShouldExit())
		{
			if(poll(pfd, npfd, 200) > 0)
			{
				while(snd_seq_event_input(seqHandle, &event) >= 0)
				{
					if(!midiParser)
						continue;

					byteCount = snd_midi_event_decode(midiParser,
													  tempBytes,
													  32,
													  event);
					snd_midi_event_reset_decode(midiParser);

					//Ignore timestamp for now (not sure what to do with
					//it?).
					const MidiMessage message((const uint8 * const)tempBytes,
											  byteCount);

					callback->handleIncomingMidiMessage(midiInput, message);
				}
				//Should this be inside the above loop?
				snd_seq_free_event(event);
			}
		}
	}

	if(midiParser)
		snd_midi_event_free(midiParser);
};

juce_UseDebuggingNewOperator

private:
/// The MidiInput object associated with this thread.
MidiInput *midiInput;
/// The sequencer handle for this client.
snd_seq_t *seqHandle;
/// The callback to call when we receive a MIDI event.
MidiInputCallback *callback;
};

const StringArray MidiInput::getDevices()
{
StringArray s;

snd_seq_t *seqHandle;
snd_seq_system_info_t *info;
snd_seq_client_info_t *info2;
snd_seq_port_info_t *portInfo;

if(snd_seq_open(&seqHandle, "default", SND_SEQ_OPEN_INPUT, 0) < 0)
{
	cout << "Could not open ALSA seq client." << endl;
	return s;
}

if(!snd_seq_system_info_malloc(&info))
{
	if(!snd_seq_system_info(seqHandle, info))
	{
		if(!snd_seq_client_info_malloc(&info2))
		{
			int tempint = snd_seq_system_info_get_cur_clients(info);
			for(int i=0;i<tempint;++i)
			{
				if(!snd_seq_query_next_client(seqHandle, info2))
				{

					if(!snd_seq_port_info_malloc(&portInfo))
					{
						int tempint2;
						int tempint3 = snd_seq_client_info_get_num_ports(info2);
						tempint2 = snd_seq_client_info_get_client(info2);
						snd_seq_port_info_set_client(portInfo, tempint2);
						snd_seq_port_info_set_port(portInfo, -1);
						for(int j=0;j<tempint3;++j)
						{
							if(!snd_seq_query_next_port(seqHandle, portInfo))
							{
								unsigned int caps;

								caps = snd_seq_port_info_get_capability(portInfo);
								if(caps&SND_SEQ_PORT_CAP_READ)
								{
									s.add(snd_seq_client_info_get_name(info2));
								}
							}
						}
						snd_seq_port_info_free(portInfo);
					}
				}
				else
					cout << "Could not get client info " << i << endl;
			}
			if(!snd_seq_get_client_info(seqHandle, info2))
			{
				cout << "Got current client info." << endl;
				cout << "Client ID: ";
				cout << snd_seq_client_info_get_client(info2) << endl;
				cout << "Client name: ";
				cout << snd_seq_client_info_get_name(info2) << endl;
			}
			snd_seq_client_info_free(info2);
		}
	}
	else
		cout << "Could not get ALSA seq system info." << endl;

	snd_seq_system_info_free(info);
}
else
	cout << "Could not allocate system info struct." << endl;

snd_seq_close(seqHandle);

return s;

}

int MidiInput::getDefaultDeviceIndex()
{
return 0;
}

MidiInput* MidiInput::openDevice (int deviceIndex, MidiInputCallback* callback)
{
snd_seq_t *seqHandle;
snd_seq_system_info_t *info;
snd_seq_client_info_t *info2;
snd_seq_port_info_t *portInfo;
int portId;
int sourcePort = -1;
int currentIndex = -1;
MidiInput *retval = 0;
MidiInputThread *thread;

if(snd_seq_open(&seqHandle, "default", SND_SEQ_OPEN_INPUT, 0) < 0)
	return 0;

if(!snd_seq_system_info_malloc(&info))
{
	if(!snd_seq_system_info(seqHandle, info))
	{
		if(!snd_seq_client_info_malloc(&info2))
		{
			int tempint = snd_seq_system_info_get_cur_clients(info);
			for(int i=0;i<tempint;++i)
			{
				if(!snd_seq_query_next_client(seqHandle, info2))
				{
					if(!snd_seq_port_info_malloc(&portInfo))
					{
						int tempint2;
						int tempint3 = snd_seq_client_info_get_num_ports(info2);
						tempint2 = snd_seq_client_info_get_client(info2);
						snd_seq_port_info_set_client(portInfo, tempint2);
						snd_seq_port_info_set_port(portInfo, -1);
						for(int j=0;j<tempint3;++j)
						{
							if(!snd_seq_query_next_port(seqHandle, portInfo))
							{
								unsigned int caps;

								sourcePort = snd_seq_port_info_get_port(portInfo);

								caps = snd_seq_port_info_get_capability(portInfo);
								if(caps&SND_SEQ_PORT_CAP_READ)
								{
									++currentIndex;
									break;
								}
							}
						}
						if((currentIndex == deviceIndex) &&
						   (sourcePort != -1))
						{
							String tempstr;
							int sourceClient;

							//Get client name, put into tempstr.
							tempstr << snd_seq_client_info_get_name(info2);

							//Set name for our client.
							snd_seq_set_client_name(seqHandle,
													"JUCE Midi Input");

							//Open port for seqHandle (do I need to save the ID?).
							portId = snd_seq_create_simple_port(seqHandle,
													   			"JUCE Midi In Port",
													   			SND_SEQ_PORT_CAP_WRITE,
													   			SND_SEQ_PORT_TYPE_MIDI_GENERIC);

							//Subscribe to relevant client port.
							sourceClient = snd_seq_client_info_get_client(info2);
							snd_seq_connect_from(seqHandle,
												 portId,
												 sourceClient,
												 sourcePort);

							retval = new MidiInput(tempstr);
							thread = new MidiInputThread(retval,
														 seqHandle,
														 callback);
							retval->internal = thread;

							snd_seq_port_info_free(portInfo);
							break;
						}

						snd_seq_port_info_free(portInfo);
					}
				}
			}
			snd_seq_client_info_free(info2);
		}
	}

	snd_seq_system_info_free(info);
}

if(!retval)
	snd_seq_close(seqHandle);

return retval;

}

MidiInput::~MidiInput()
{
MidiInputThread *thread;

if(internal)
{
	thread = (MidiInputThread *)internal;
	delete thread;
}

}

MidiInput::MidiInput(const String &name_):
internal(0),
name(name_)
{

}

void MidiInput::start()
{
((MidiInputThread *)internal)->startThread();
}

void MidiInput::stop()
{
((MidiInputThread *)internal)->stopThread(250);
}

END_JUCE_NAMESPACE

#endif
[/code]

  • Niall.

#4

Nice job! Do you mind if I take that, hack it around, and put it into the official build?


#5

:smiley: Of course! I’m happy to be able to contribute to such a great library.

  • Niall.

#6

ok, well I zoomed through and did a bit of tidying-up… Here’s my version (bears very little resemblence to the original!). If you could try it out I’d appreciate it, because I don’t have any midi on my linux set-up (using VMWare).

One thing I noticed is that it won’t handle large sysexes. I’ve increased the buffer size to 16K but they could be megabytes in size. Will leave that for the future, I think…

[code]/*

This file is part of the JUCE library - "Jules’ Utility Class Extensions"
Copyright 2004-6 by Raw Material Software ltd.


JUCE can be redistributed and/or modified 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.

JUCE 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 JUCE; if not, visit www.gnu.org/licenses or write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA


If you’d like to release a closed-source product which uses JUCE, commercial
licenses are also available: visit www.rawmaterialsoftware.com/juce for
more information.

==============================================================================
*/

#include “juce_Config.h”

#if JUCE_BUILD_GUI_CLASSES

#include “…/…/…/src/juce_core/basics/juce_StandardHeader.h”

#include <alsa/asoundlib.h>

BEGIN_JUCE_NAMESPACE

#include “…/…/…/src/juce_appframework/audio/devices/juce_MidiOutput.h”
#include “…/…/…/src/juce_appframework/audio/devices/juce_MidiInput.h”
#include “…/…/…/src/juce_core/threads/juce_Thread.h”
#include “…/…/…/src/juce_core/basics/juce_Time.h”

//==============================================================================
const StringArray MidiOutput::getDevices()
{
StringArray s;
return s;
}

int MidiOutput::getDefaultDeviceIndex()
{
return 0;
}

MidiOutput* MidiOutput::openDevice (int deviceIndex)
{
return 0;
}

MidiOutput::MidiOutput()
{
}

MidiOutput::~MidiOutput()
{
}

void MidiOutput::reset()
{
}

bool MidiOutput::getVolume (float& leftVol, float& rightVol)
{
return false;
}

void MidiOutput::setVolume (float leftVol, float rightVol)
{
}

void MidiOutput::sendMessageNow (const MidiMessage& message)
{
}

//==============================================================================
class MidiInputThread : public Thread
{
public:
MidiInputThread (MidiInput* const midiInput_,
snd_seq_t* const seqHandle_,
MidiInputCallback* const callback_)
: Thread (T(“Juce MIDI Input”)),
midiInput (midiInput_),
seqHandle (seqHandle_),
callback (callback_)
{
jassert (seqHandle != 0 && callback != 0 && midiInput != 0);
}

~MidiInputThread()
{
    snd_seq_close (seqHandle);
}

void run()
{
    const int maxEventSize = 16 * 1024;
    snd_midi_event_t* midiParser;

    if (snd_midi_event_new (maxEventSize, &midiParser) >= 0)
    {
        uint8* const buffer = (uint8*) juce_malloc (maxEventSize);

        const int numPfds = snd_seq_poll_descriptors_count (seqHandle, POLLIN);
        struct pollfd* const pfd = (struct pollfd*) alloca (numPfds * sizeof (struct pollfd));

        snd_seq_poll_descriptors (seqHandle, pfd, numPfds, POLLIN);

        while (! threadShouldExit())
        {
            if (poll (pfd, numPfds, 200) > 0)
            {
                snd_seq_event_t* inputEvent = 0;

                while (snd_seq_event_input (seqHandle, &inputEvent) >= 0)
                {
                    // xxx what about SYSEXes that are too big for the buffer?
                    const int numBytes = snd_midi_event_decode (midiParser, buffer, maxEventSize, inputEvent);

                    snd_midi_event_reset_decode (midiParser);

                    const MidiMessage message ((const uint8*) buffer, 
                                               numBytes,
                                               Time::getMillisecondCounter() * 0.001);

                    callback->handleIncomingMidiMessage (midiInput, message);
                }

                snd_seq_free_event (inputEvent);
            }
        }

        snd_midi_event_free (midiParser);
        juce_free (buffer);
    }
};

juce_UseDebuggingNewOperator

private:
MidiInput* const midiInput;
snd_seq_t* const seqHandle;
MidiInputCallback* const callback;
};

//==============================================================================
MidiInput::MidiInput (const String& name_)
: internal (0),
name (name_)
{
}

MidiInput::~MidiInput()
{
stop();
MidiInputThread* const thread = (MidiInputThread*) internal;
delete thread;
}

void MidiInput::start()
{
((MidiInputThread*) internal)->startThread();
}

void MidiInput::stop()
{
((MidiInputThread*) internal)->stopThread (2000);
}

int MidiInput::getDefaultDeviceIndex()
{
return 0;
}

static snd_seq_t* iterateDevices (StringArray& deviceNamesFound, const int deviceIndexToOpen)
{
snd_seq_t* returnedHandle = 0;

snd_seq_t* seqHandle;
if (snd_seq_open (&seqHandle, "default", SND_SEQ_OPEN_INPUT, 0) == 0)
{
    snd_seq_system_info_t* systemInfo;
    snd_seq_client_info_t* clientInfo;

    if (snd_seq_system_info_malloc (&systemInfo) == 0)
    {
        if (snd_seq_system_info (seqHandle, systemInfo) == 0
             && snd_seq_client_info_malloc (&clientInfo) == 0)
        {
            int numClients = snd_seq_system_info_get_cur_clients (systemInfo);

            while (--numClients >= 0 && returnedHandle == 0)
            {
                if (snd_seq_query_next_client (seqHandle, clientInfo) == 0)
                {
                    snd_seq_port_info_t* portInfo;
                    if (snd_seq_port_info_malloc (&portInfo) == 0)
                    {
                        int numPorts = snd_seq_client_info_get_num_ports (clientInfo);
                        const int client = snd_seq_client_info_get_client (clientInfo);

                        snd_seq_port_info_set_client (portInfo, client);
                        snd_seq_port_info_set_port (portInfo, -1);

                        while (--numPorts >= 0)
                        {
                            if (snd_seq_query_next_port (seqHandle, portInfo) == 0
                                 && (snd_seq_port_info_get_capability (portInfo) & SND_SEQ_PORT_CAP_READ) != 0)
                            {
                                deviceNamesFound.add (snd_seq_client_info_get_name (clientInfo));

                                if (deviceNamesFound.size() == deviceIndexToOpen + 1)
                                {
                                    const int sourcePort = snd_seq_port_info_get_port (portInfo);
                                    const int sourceClient = snd_seq_client_info_get_client (clientInfo);

                                    if (sourcePort != -1)
                                    {
                                        snd_seq_set_client_name (seqHandle, "Juce Midi Input");

                                        const int portId
                                            = snd_seq_create_simple_port (seqHandle, "Juce Midi In Port",
                                                                          SND_SEQ_PORT_CAP_WRITE,
                                                                          SND_SEQ_PORT_TYPE_MIDI_GENERIC);

                                        snd_seq_connect_from (seqHandle, portId, sourceClient, sourcePort);

                                        returnedHandle = seqHandle;
                                        break;
                                    }
                                }
                            }
                        }

                        snd_seq_port_info_free (portInfo);
                    }
                }
            }

            snd_seq_client_info_free (clientInfo);
        }

        snd_seq_system_info_free (systemInfo);
    }

    if (returnedHandle == 0)
        snd_seq_close (seqHandle);
}

return returnedHandle;

}

const StringArray MidiInput::getDevices()
{
StringArray devices;
iterateDevices (devices, -1);
return devices;
}

MidiInput* MidiInput::openDevice (int deviceIndex, MidiInputCallback* callback)
{
MidiInput* newDevice = 0;

StringArray devices;
snd_seq_t* const handle = iterateDevices (devices, deviceIndex);

if (handle != 0)
{
    newDevice = new MidiInput (devices [deviceIndex]);
    newDevice->internal = new MidiInputThread (newDevice, handle, callback);
}

return newDevice;

}

END_JUCE_NAMESPACE

#endif
[/code]


#7

Yep, that seems to work fine. I should point out that I’ve only got a hardware control surface here (no keyboard), so I’ve only been testing it with CCs so far, but I just had a go with the ALSA vkeybd virtual keyboard app and it caught all my mouse clicks/fake key presses (since ALSA’s sequencer api handles inter-app communication, you should be able to test it yourself too, even without a hardware input).

  • Niall.

#8

Cool. I’ll add it to the build then - thanks!


#9

Arghh… I was looking for ALSA midi I/O in juce as well and assumed it was already functional.

Anyway so the hard part (midi in) has already been implemented thanks to NiallM. I’ll be giving MIDI out a try, via ALSA. I’ve written midi i/o libraries on Windows before but I am only just starting with Linux so I’m not making any promises, but let’s see how far I get…


#10

Cool. Feel free to throw code in my direction for sanity checking and tidying-up!


#11

It shouldn’t be too hard, but make sure you use ALSA’s sequencer api, rather than the rawmidi one. The sequencer api allows for fancy inter-app connections, but IIRC rawmidi is a much more basic api that only handles connections to the hardware MIDI IO ports. I was going to post a link to the sequencer documentation, but it seems the ALSA site is down at the moment, so the best I can manage is this: http://www.alsa-project.org/documentation.php. Also, there’s a tutorial which uses the sequencer api here.

Good luck!

  • Niall.

#12