Sysex handling


#1

Jules,

I start a new thread here to make it more transparent under the right head line.

I’ve reviewed your MidiInThread code to get sysex working under Windows again.

That code is not using handlePartialSysexMessage cause I feel that you are about to skip that anyway somewhen in the future
cause it makes it more complicated as it should be???
Question is if that method is really needed for MAC? From my little understanding it should be doable the same way as Windows
by only using handleIncomingMidiMessage.

Awaiting your statement :slight_smile:

class MidiInThread  : public Thread
{
public:

	MidiInThread (MidiInput* const input_,
				  MidiInputCallback* const callback_)
		: Thread ("Juce Midi"),
		  deviceHandle (0),
		  input (input_),
		  callback (callback_),
		  isStarted (false),
		  startTime (0),
		  msgPartLenght(0)
	{
		pending.ensureSize ((int) defaultBufferSize);
		sysexBuffer.ensureSize((int) defaultBufferSize, true);

		for (int i = (int) numInHeaders; --i >= 0;)
		{
			zeromem (&hdr[i], sizeof (MIDIHDR));
			hdr[i].lpData = inData[i];
			hdr[i].dwBufferLength = (int) inBufferSize;
		}
	};

	~MidiInThread()
	{
		stop();

		if (deviceHandle != 0)
		{
			int count = 5;
			while (--count >= 0)
			{
				if (midiInClose (deviceHandle) == MMSYSERR_NOERROR)
					break;

				Sleep (20);
			}
		}
	}

	void handle (const uint32 message, const uint32 timeStamp)
	{
		const int byte = message & 0xff;
		if (byte < 0x80)
			return;

		const int time = timeStampToMs (timeStamp);

		{
			const ScopedLock sl (lock);
			pending.addEvent (&message, 3, time);
		}

		notify();
	}

	void handleSysEx (MIDIHDR* const hdr, const uint32 timeStamp)
	{
		const int time = timeStampToMs (timeStamp);
		const int num = hdr->dwBytesRecorded;
		const unsigned char topByte = (unsigned char)(hdr->lpData[0]);
		const unsigned char tailByte = (unsigned char)(hdr->lpData[num - 1]);

		if (num > 0)
		{
			//complete sysex
			if (topByte == 0xf0 && tailByte == 0xf7)
			{
				const ScopedLock sl (lock);
				pending.addEvent (hdr->lpData, num, time);
			}
			//first part of a sysex message longer than inBufferSize
			else if (topByte == 0xf0 && tailByte != 0xf7)
			{
				sysexBuffer.copyFrom(hdr->lpData, 0, num);
				msgPartLenght = num;
			}
			//middle part of a sysex message longer than inBufferSize
			else if (topByte != 0xf0 && tailByte != 0xf7)
			{
				sysexBuffer.copyFrom(hdr->lpData, msgPartLenght, num);
				msgPartLenght += num;
			}
			//last part of a sysex message
			else if (topByte != 0xf0 && tailByte == 0xf7)
			{
				sysexBuffer.copyFrom(hdr->lpData, msgPartLenght, num);
				
				const ScopedLock sl (lock);
				pending.addEvent (sysexBuffer.getData(), msgPartLenght + num, time);
				msgPartLenght = 0;
			}

			notify();
		}
	}
	
	void writeBlock (const int i)
	{
		hdr[i].dwBytesRecorded = 0;
		MMRESULT res = midiInPrepareHeader (deviceHandle, &hdr[i], sizeof (MIDIHDR));
		jassert (res == MMSYSERR_NOERROR);
		res = midiInAddBuffer (deviceHandle, &hdr[i], sizeof (MIDIHDR));
		jassert (res == MMSYSERR_NOERROR);
	}

	void run()
	{
		MidiBuffer newEvents;
		newEvents.ensureSize ((int) defaultBufferSize);

		while (! threadShouldExit())
		{
			for (int i = 0; i < (int) numInHeaders; ++i)
			{
				if ((hdr[i].dwFlags & WHDR_DONE) != 0)
				{
					MMRESULT res = midiInUnprepareHeader (deviceHandle, &hdr[i], sizeof (MIDIHDR));
					(void) res;
					jassert (res == MMSYSERR_NOERROR);
					writeBlock (i);
				}
			}

			newEvents.clear(); // (resets it without freeing allocated storage)

			{
				const ScopedLock sl (lock);
				newEvents.swapWith (pending);
			}

			//xxx needs to figure out if blocks are broken up or not

			if (newEvents.isEmpty())
			{
				wait (500);
			}
			else
			{
				MidiMessage message (0xf4, 0.0);
				int time;

				for (MidiBuffer::Iterator i (newEvents); i.getNextEvent (message, time);)
				{
					message.setTimeStamp (time * 0.001);
					callback->handleIncomingMidiMessage (input, message);
				}
			}
		}
	}

	void start()
	{
		jassert (deviceHandle != 0);
		if (deviceHandle != 0 && ! isStarted)
		{
			stop();

			activeMidiThreads.addIfNotAlreadyThere (this);

			int i;
			for (i = 0; i < (int) numInHeaders; ++i)
				writeBlock (i);

			startTime = Time::getMillisecondCounter();
			MMRESULT res = midiInStart (deviceHandle);
			jassert (res == MMSYSERR_NOERROR);

			if (res == MMSYSERR_NOERROR)
			{
				isStarted = true;
				pending.clear();
				startThread (6);
			}
		}
	}

	void stop()
	{
		if (isStarted)
		{
			stopThread (5000);

			midiInReset (deviceHandle);
			midiInStop (deviceHandle);

			activeMidiThreads.removeValue (this);

			{ const ScopedLock sl (lock); }

			for (int i = (int) numInHeaders; --i >= 0;)
			{
				if ((hdr[i].dwFlags & WHDR_DONE) != 0)
				{
					int c = 10;
					while (--c >= 0 && midiInUnprepareHeader (deviceHandle, &hdr[i], sizeof (MIDIHDR)) == MIDIERR_STILLPLAYING)
						Sleep (20);

					jassert (c >= 0);
				}
			}

			isStarted = false;
			pending.clear();
		}
	}

	static void CALLBACK midiInCallback (HMIDIIN, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR midiMessage, DWORD_PTR timeStamp)
	{
		MidiInThread* const thread = reinterpret_cast <MidiInThread*> (dwInstance);

		if (thread != 0 && activeMidiThreads.contains (thread))
		{
			if (uMsg == MIM_DATA)
				thread->handle ((uint32) midiMessage, (uint32) timeStamp);
			else if (uMsg == MIM_LONGDATA)
				thread->handleSysEx ((MIDIHDR*) midiMessage, (uint32) timeStamp);
		}
	}

	juce_UseDebuggingNewOperator

	HMIDIIN deviceHandle;

private:
	static Array <void*, CriticalSection> activeMidiThreads;
	
	int msgPartLenght;
	MemoryBlock sysexBuffer;
	MidiInput* input;
	MidiInputCallback* callback;
	bool isStarted;
	uint32 startTime;
	CriticalSection lock;

	enum { defaultBufferSize = 8192,
		   numInHeaders = 32,
		   inBufferSize = 256 };

	MIDIHDR hdr [(int) numInHeaders];
	char inData [(int) numInHeaders] [(int) inBufferSize];

	MidiBuffer pending;

	int timeStampToMs (uint32 timeStamp)
	{
		timeStamp += startTime;

		const uint32 now = Time::getMillisecondCounter();
		if (timeStamp > now)
		{
			if (timeStamp > now + 2)
				--startTime;

			timeStamp = now;
		}

		return (int) timeStamp;
	}

	MidiInThread (const MidiInThread&);
	MidiInThread& operator= (const MidiInThread&);
};

#2

Joerg, please check your private messages!