View Issue Details

IDProjectCategoryView StatusLast Update
0000843OpenMPTPlugins / VSTpublic2017-07-24 11:37
ReporterSaga Musix Assigned ToSaga Musix  
PriorityhighSeveritycrashReproducibilityrandom
Status resolvedResolutionfixed 
Product VersionOpenMPT 1.26.03.00 / libopenmpt 0.2-beta18 (upgrade first) 
Target VersionOpenMPT 1.27.01.00 / libopenmpt 0.3.1 (upgrade first)Fixed in VersionOpenMPT 1.27.01.00 / libopenmpt 0.3.1 (upgrade first) 
Summary0000843: Sporadic deadlocks in MIDI I/O
Description

I keep experiencing random deadlocks in midiStreamClose when using the restart playback button in the main toolbar while the MIDI I/O plugin is in use. This could mean that threading is not implemented correctly on our or PortMidi's side, or my MIDI interface driver is buggy. Devices are opened and closed in the main thread when stopping or resuming playback, but the relevant code is guarded by a mutex.

TagsNo tags attached.
Attached Files
rtmidi.patch (20,752 bytes)   
Index: mptrack/mptrack_10.vcxproj
===================================================================
--- mptrack/mptrack_10.vcxproj	(revision 7100)
+++ mptrack/mptrack_10.vcxproj	(working copy)
@@ -750,6 +750,7 @@
     <ClCompile Include="..\common\serialization_utils.cpp" />
     <ClCompile Include="..\common\typedefs.cpp" />
     <ClCompile Include="..\common\version.cpp" />
+    <ClCompile Include="..\include\rtmidi\RtMidi.cpp" />
     <ClCompile Include="..\pluginBridge\BridgeWrapper.cpp" />
     <ClCompile Include="..\plugins\MidiInOut\MidiInOut.cpp" />
     <ClCompile Include="..\plugins\MidiInOut\MidiInOutEditor.cpp" />
@@ -1010,6 +1011,7 @@
     <ClInclude Include="..\common\version.h" />
     <ClInclude Include="..\common\versionNumber.h" />
     <ClInclude Include="..\common\WriteMemoryDump.h" />
+    <ClInclude Include="..\include\rtmidi\RtMidi.h" />
     <ClInclude Include="..\pluginBridge\AEffectWrapper.h" />
     <ClInclude Include="..\pluginBridge\BridgeCommon.h" />
     <ClInclude Include="..\pluginBridge\BridgeWrapper.h" />
Index: mptrack/mptrack_10.vcxproj.filters
===================================================================
--- mptrack/mptrack_10.vcxproj.filters	(revision 7100)
+++ mptrack/mptrack_10.vcxproj.filters	(working copy)
@@ -652,6 +652,9 @@
     <ClCompile Include="BuildVariants.cpp">
       <Filter>Source Files\mptrack</Filter>
     </ClCompile>
+    <ClCompile Include="..\include\rtmidi\RtMidi.cpp">
+      <Filter>Source Files</Filter>
+    </ClCompile>
   </ItemGroup>
   <ItemGroup>
     <ClInclude Include="..\soundlib\Loaders.h">
@@ -1281,6 +1284,9 @@
     <ClInclude Include="BuildVariants.h">
       <Filter>Header Files\mptrack</Filter>
     </ClInclude>
+    <ClInclude Include="..\include\rtmidi\RtMidi.h">
+      <Filter>Header Files</Filter>
+    </ClInclude>
   </ItemGroup>
   <ItemGroup>
     <None Include="res\bitmap1.bmp">
Index: plugins/MidiInOut/MidiInOut.cpp
===================================================================
--- plugins/MidiInOut/MidiInOut.cpp	(revision 7100)
+++ plugins/MidiInOut/MidiInOut.cpp	(working copy)
@@ -20,12 +20,16 @@
 OPENMPT_NAMESPACE_BEGIN
 
 
-int MidiInOut::numInstances = 0;
-
 IMixPlugin* MidiInOut::Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
 //------------------------------------------------------------------------------------------------
 {
-	return new (std::nothrow) MidiInOut(factory, sndFile, mixStruct);
+	try
+	{
+		return new (std::nothrow) MidiInOut(factory, sndFile, mixStruct);
+	} catch(RtMidiError)
+	{
+		return nullptr;
+	}
 }
 
 
@@ -33,14 +37,10 @@
 	: IMidiPlugin(factory, sndFile, mixStruct)
 	, latencyCompensation(true)
 	, programName(_T("Default"))
+	, inputDevice(midiIn)
+	, outputDevice(midiOut)
 //---------------------------------------------------------------------------------------
 {
-	if(!numInstances++)
-	{
-		Pt_Start(1, nullptr, nullptr);
-		Pm_Initialize();
-	}
-
 	m_mixBuffer.Initialize(2, 2);
 	InsertIntoFactoryList();
 }
@@ -50,13 +50,6 @@
 //---------------------
 {
 	Suspend();
-
-	if(--numInstances == 0)
-	{
-		// This terminates MIDI output for all instances of the plugin, so only ever do it if this was the only instance left.
-		Pm_Terminate();
-		Pt_Stop();
-	}
 }
 
 
@@ -127,9 +120,9 @@
 		return;
 
 	uint32 nameStrSize = file.ReadUint32LE();
-	PmDeviceID inID = file.ReadUint32LE();
+	int inID = file.ReadInt32LE();
 	uint32 inStrSize = file.ReadUint32LE();
-	PmDeviceID outID = file.ReadUint32LE();
+	int outID = file.ReadInt32LE();
 	uint32 outStrSize = file.ReadUint32LE();
 	latencyCompensation = file.ReadUint32LE() != 0;
 
@@ -139,16 +132,21 @@
 
 	file.ReadString<mpt::String::maybeNullTerminated>(s, inStrSize);
 	s = mpt::ToCharset(mpt::CharsetLocale, mpt::CharsetUTF8, s);
-	if(s != GetDeviceName(inID))
+	if(s != inputDevice.GetPortName(inID))
 	{
 		// Stored name differs from actual device name - try finding another device with the same name.
-		const PmDeviceInfo *device;
-		for(PmDeviceID i = 0; (device = Pm_GetDeviceInfo(i)) != nullptr; i++)
+		unsigned int ports = midiIn.getPortCount();
+		for(unsigned int i = 0; i < ports; i++)
 		{
-			if(device->input && s == device->name)
+			try
 			{
-				inID = i;
-				break;
+				if(s == inputDevice.GetPortName(i))
+				{
+					inID = i;
+					break;
+				}
+			} catch (RtMidiError &)
+			{
 			}
 		}
 	}
@@ -155,16 +153,21 @@
 
 	file.ReadString<mpt::String::maybeNullTerminated>(s, outStrSize);
 	s = mpt::ToCharset(mpt::CharsetLocale, mpt::CharsetUTF8, s);
-	if(s != GetDeviceName(outID))
+	if(s != outputDevice.GetPortName(outID))
 	{
 		// Stored name differs from actual device name - try finding another device with the same name.
-		const PmDeviceInfo *device;
-		for(PmDeviceID i = 0; (device = Pm_GetDeviceInfo(i)) != nullptr; i++)
+		unsigned int ports = midiOut.getPortCount();
+		for(unsigned int i = 0; i < ports; i++)
 		{
-			if(device->output && s == device->name)
+			try
 			{
-				outID = i;
-				break;
+				if(s == outputDevice.GetPortName(i))
+				{
+					outID = i;
+					break;
+				}
+			} catch (RtMidiError &)
+			{
 			}
 		}
 	}
@@ -177,7 +180,7 @@
 void MidiInOut::SetParameter(PlugParamIndex index, PlugParamValue value)
 //----------------------------------------------------------------------
 {
-	PmDeviceID newDevice = ParameterToDeviceID(value);
+	int newDevice = ParameterToDeviceID(value);
 	OpenDevice(newDevice, (index == kInputParameter));
 
 	// Update selection in editor
@@ -237,12 +240,13 @@
 {
 	MPT_LOCK_GUARD<mpt::mutex> lock(mutex);
 
-	if(outputDevice.stream != nullptr)
+	if(midiOut.isPortOpen())
 	{
 		// Send MIDI clock
 		if(nextClock < 1)
 		{
-			Pm_WriteShort(outputDevice.stream, Now(), 0xF8);
+			std::vector<unsigned char> message(1, 0xF8);
+			midiOut.sendMessage(&message);
 			double bpm = m_SndFile.GetCurrentBPM();
 			if(bpm != 0.0)
 			{
@@ -253,26 +257,20 @@
 	}
 
 	// We don't do any audio processing here, but we process incoming MIDI events.
-	if(inputDevice.stream == nullptr)
+	if(!midiOut.isPortOpen())
 		return;
 
-	while(Pm_Poll(inputDevice.stream))
+	std::vector<unsigned char> message;
+	while(midiIn.getMessage(&message), !message.empty())
 	{
-		// Read incoming MIDI events.
-		PmEvent buffer;
-		Pm_Read(inputDevice.stream, &buffer, 1);
-
 		// Discard events if bypassed
 		if(IsBypassed())
 			continue;
 
-		mpt::byte message[sizeof(buffer.message)];
-		memcpy(message, &buffer.message, sizeof(message));
-
 		if(!bufferedMessage.empty())
 		{
-			bufferedMessage.push_back(buffer.message);
-			if(buffer.message & 0x80808080)
+			bufferedMessage.insert(bufferedMessage.end(), message.begin(), message.end());
+			if(message.back() == 0xF7)
 			{
 				// End of message found!
 				ReceiveSysex(bufferedMessage.data(), bufferedMessage.size() * sizeof(bufferedMessage[0]));
@@ -279,17 +277,19 @@
 				bufferedMessage.clear();
 			}
 			continue;
-		} else if(message[0] == 0xF0)
+		} else if(message.front() == 0xF0)
 		{
 			// Start of SysEx message...
-			if(message[1] != 0xF7 && message[2] != 0xF7 && message[3] != 0xF7)
-				bufferedMessage.push_back(buffer.message);	// ...but not the end!
+			if(message.back() != 0xF7)
+				bufferedMessage.insert(bufferedMessage.end(), message.begin(), message.end());	// ...but not the end!
 			else
-				ReceiveSysex(message, sizeof(message));
+				ReceiveSysex(&message[0], message.size() * sizeof(message[0]));
 			continue;
 		}
 
-		ReceiveMidi(buffer.message);
+		uint32 msg = 0;
+		memcpy(&msg, &message[0], std::min(message.size(), sizeof(msg)));
+		ReceiveMidi(msg);
 	}
 }
 
@@ -303,9 +303,10 @@
 	nextClock = 0;
 	OpenDevice(inputDevice.index, true);
 	OpenDevice(outputDevice.index, false);
-	if(outputDevice.stream != nullptr)
+	if(midiOut.isPortOpen())
 	{
-		Pm_WriteShort(outputDevice.stream, Now(), 0xFA);	// Start
+		std::vector<unsigned char> message(1, 0xFA);	// Start
+		midiOut.sendMessage(&message);
 	}
 }
 
@@ -315,9 +316,10 @@
 //-----------------------
 {
 	// Suspend MIDI I/O
-	if(outputDevice.stream != nullptr)
+	if(midiOut.isPortOpen())
 	{
-		Pm_WriteShort(outputDevice.stream, Now(), 0xFC);	// Stop
+		std::vector<unsigned char> message(1, 0xFC);	// Stop
+		midiOut.sendMessage(&message);
 	}
 	CloseDevice(inputDevice);
 	CloseDevice(outputDevice);
@@ -329,11 +331,12 @@
 void MidiInOut::PositionChanged()
 //-------------------------------
 {
-	if(outputDevice.stream != nullptr)
+	if(midiOut.isPortOpen())
 	{
-		const PtTimestamp now = Now();
-		Pm_WriteShort(outputDevice.stream, now, 0xFC);	// Stop
-		Pm_WriteShort(outputDevice.stream, now, 0xFA);	// Start
+		std::vector<unsigned char> message;
+		message.push_back(0xFC);	// Stop
+		message.push_back(0xFA);	// Start
+		midiOut.sendMessage(&message);
 	}
 }
 
@@ -342,27 +345,30 @@
 bool MidiInOut::MidiSend(uint32 midiCode)
 //---------------------------------------
 {
-	if(outputDevice.stream == nullptr || IsBypassed())
+	if(!midiOut.isPortOpen() || IsBypassed())
 	{
 		// We need an output device to send MIDI messages to.
 		return true;
 	}
 
-	Pm_WriteShort(outputDevice.stream, Now(), midiCode);
+	std::vector<unsigned char> message(3, 0);
+	memcpy(&message[0], &midiCode, 3);
+	midiOut.sendMessage(&message);
 	return true;
 }
 
 
-bool MidiInOut::MidiSysexSend(const void *message, uint32 /*length*/)
-//-------------------------------------------------------------------
+bool MidiInOut::MidiSysexSend(const void *sysex, uint32 length)
+//-------------------------------------------------------------
 {
-	if(outputDevice.stream == nullptr || IsBypassed())
+	if(!midiOut.isPortOpen() || IsBypassed())
 	{
 		// We need an output device to send MIDI messages to.
 		return true;
 	}
 
-	Pm_WriteSysEx(outputDevice.stream, Now(), const_cast<unsigned char *>(static_cast<const unsigned char *>(message)));
+	std::vector<unsigned char> message(static_cast<const unsigned char *>(sysex), static_cast<const unsigned char *>(sysex) + length);
+	midiOut.sendMessage(&message);
 	return true;
 }
 
@@ -376,7 +382,7 @@
 		Resume();
 	}
 
-	for(uint8 mc = 0; mc < CountOf(m_MidiCh); mc++)		//all midi chans
+	for(auto mc = 0; mc < CountOf(m_MidiCh); mc++)		//all midi chans
 	{
 		PlugInstrChannel &channel = m_MidiCh[mc];
 		channel.ResetProgram();
@@ -384,7 +390,7 @@
 		MidiPitchBend(mc, EncodePitchBendParam(MIDIEvents::pitchBendCentre));		// centre pitch bend
 		MidiSend(MIDIEvents::CC(MIDIEvents::MIDICC_AllSoundOff, mc, 0));			// all sounds off
 
-		for(size_t i = 0; i < CountOf(channel.noteOnMap); i++)	//all notes
+		for(auto i = 0; i < CountOf(channel.noteOnMap); i++)	//all notes
 		{
 			for(CHANNELINDEX c = 0; c < CountOf(channel.noteOnMap[i]); c++)
 			{
@@ -404,20 +410,13 @@
 }
 
 
-static PmTimestamp PtTimeWrapper(void* /*time_info*/)
-//---------------------------------------------------
-{
-	return Pt_Time();
-}
-
-
 // Open a device for input or output.
-void MidiInOut::OpenDevice(PmDeviceID newDevice, bool asInputDevice)
-//------------------------------------------------------------------
+void MidiInOut::OpenDevice(int newDevice, bool asInputDevice)
+//-----------------------------------------------------------
 {
 	MidiDevice &device = asInputDevice ? inputDevice : outputDevice;
 
-	if(device.index == newDevice && device.stream != nullptr)
+	if(device.index == newDevice && device.stream.isPortOpen())
 	{
 		// No need to re-open this device.
 		return;
@@ -426,15 +425,16 @@
 	CloseDevice(device);
 
 	device.index = newDevice;
+	device.stream.closePort();
 
 	if(device.index == kNoDevice)
 	{
 		// Dummy device
-		device = MidiDevice();
+		device.name = "<none>";
 		return;
 	}
 
-	PmError result = pmNoError;
+	device.name = device.GetPortName(newDevice);
 	if(m_isResumed)
 	{
 		// Don't open MIDI devices if we're not processing.
@@ -441,31 +441,22 @@
 		// This has to be done since we receive MIDI events in processReplacing(),
 		// so if no processing is happening, some irrelevant events might be queued until the next processing happens...
 		MPT_LOCK_GUARD<mpt::mutex> lock(mutex);
-		if(asInputDevice)
+
+		try
 		{
-			result = Pm_OpenInput(&device.stream, newDevice, nullptr, 0, nullptr, nullptr);
-		} else
+			device.stream.openPort(newDevice);
+		} catch(RtMidiError &error)
 		{
-			if(latencyCompensation)
+			device.name = "Unavailable";
+			MidiInOutEditor *editor = dynamic_cast<MidiInOutEditor *>(GetEditor());
+			if(editor != nullptr)
 			{
-				// buffer of 10000 events
-				result = Pm_OpenOutput(&device.stream, newDevice, nullptr, 10000, PtTimeWrapper, nullptr, Util::Round<PtTimestamp>(1000.0 * GetOutputLatency()));
-			} else
-			{
-				result = Pm_OpenOutput(&device.stream, newDevice, nullptr, 0, nullptr, nullptr, 0);
+				// Display a warning if the editor is open.
+				Reporting::Error("MIDI device cannot be opened:" + error.getMessage(), "MIDI Input / Output", editor);
 			}
+
 		}
 	}
-
-	// Update current device name
-	device.name = GetDeviceName(device.index);
-
-	MidiInOutEditor *editor = dynamic_cast<MidiInOutEditor *>(GetEditor());
-	if(result != pmNoError && editor != nullptr)
-	{
-		// Display a warning if the editor is open.
-		Reporting::Error("MIDI device cannot be opened!", "MIDI Input / Output", editor);
-	}
 }
 
 
@@ -473,33 +464,25 @@
 void MidiInOut::CloseDevice(MidiDevice &device)
 //---------------------------------------------
 {
-	if(device.stream != nullptr)
+	if(device.stream.isPortOpen())
 	{
 		MPT_LOCK_GUARD<mpt::mutex> lock(mutex);
-		Pm_Close(device.stream);
-		device.stream = nullptr;
+		device.stream.closePort();
 	}
 }
 
 
-// Get a device name
-const char *MidiInOut::GetDeviceName(PmDeviceID index) const
-//-----------------------------------------------------------
+std::string MidiDevice::GetPortName(int port)
+//-------------------------------------------
 {
-	const PmDeviceInfo *deviceInfo = Pm_GetDeviceInfo(index);
-
-	if(deviceInfo != nullptr)
-		return deviceInfo->name;
-	else
-		return "Unavailable";
+	std::string portName = stream.getPortName(port);
+#if MPT_OS_WINDOWS
+	// Remove auto-appended port number
+	if(portName.length() >= 2)
+		return portName.substr(0, portName.find_last_of(' '));
+#endif
+	return portName;
 }
 
 
-PtTimestamp MidiInOut::Now() const
-//--------------------------------
-{
-	return (latencyCompensation ? Pt_Time() : 0);
-}
-
-
 OPENMPT_NAMESPACE_END
Index: plugins/MidiInOut/MidiInOut.h
===================================================================
--- plugins/MidiInOut/MidiInOut.h	(revision 7100)
+++ plugins/MidiInOut/MidiInOut.h	(working copy)
@@ -12,10 +12,10 @@
 
 #include "../../common/mptMutex.h"
 #include "../../soundlib/plugins/PlugInterface.h"
-#include <portmidi/pm_common/portmidi.h>
-#include <portmidi/porttime/porttime.h>
+#include "../../include/rtmidi/RtMidi.h"
 
 
+
 OPENMPT_NAMESPACE_BEGIN
 
 
@@ -24,16 +24,18 @@
 //==============
 {
 public:
-	PmDeviceID index;
-	PortMidiStream *stream;
+	RtMidi &stream;
 	std::string name;
+	int index;
 
 public:
-	MidiDevice()
-		: index(-1)	// MidiInOut::kNoDevice
-		, stream(nullptr)
+	MidiDevice(RtMidi &stream)
+		: stream(stream)
 		, name("<none>")
+		, index(-1)	// MidiInOut::kNoDevice
 	{ }
+
+	std::string GetPortName(int port);
 };
 
 
@@ -62,12 +64,13 @@
 	double nextClock;
 
 	// I/O device settings
+	RtMidiIn midiIn;
+	RtMidiOut midiOut;
 	MidiDevice inputDevice;
 	MidiDevice outputDevice;
 	bool latencyCompensation;
 
 	CString programName;
-	static int numInstances;
 
 public:
 	static IMixPlugin* Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
@@ -75,13 +78,13 @@
 	~MidiInOut();
 
 	// Translate a VST parameter to a PortMidi device ID
-	static PmDeviceID ParameterToDeviceID(float value)
+	static int ParameterToDeviceID(float value)
 	{
-		return static_cast<PmDeviceID>(value * static_cast<float>(kMaxDevices)) - 1;
+		return static_cast<int>(value * static_cast<float>(kMaxDevices)) - 1;
 	}
 
 	// Translate a PortMidi device ID to a VST parameter
-	static float DeviceIDToParameter(PmDeviceID index)
+	static float DeviceIDToParameter(int index)
 	{
 		return static_cast<float>(index + 1) / static_cast<float>(kMaxDevices);
 	}
@@ -154,14 +157,9 @@
 
 protected:
 	// Open a device for input or output.
-	void OpenDevice(PmDeviceID newDevice, bool asInputDevice);
+	void OpenDevice(int newDevice, bool asInputDevice);
 	// Close an active device.
 	void CloseDevice(MidiDevice &device);
-	// Get a device name
-	const char *GetDeviceName(PmDeviceID index) const;
-
-	// Get current timestamp for sending
-	PtTimestamp Now() const;
 };
 
 
Index: plugins/MidiInOut/MidiInOutEditor.cpp
===================================================================
--- plugins/MidiInOut/MidiInOutEditor.cpp	(revision 7100)
+++ plugins/MidiInOut/MidiInOutEditor.cpp	(working copy)
@@ -13,6 +13,7 @@
 #include "MidiInOut.h"
 #include "MidiInOutEditor.h"
 #include "../../mptrack/resource.h"
+#include "../../include/rtmidi/RtMidi.h"
 
 
 OPENMPT_NAMESPACE_BEGIN
@@ -74,32 +75,37 @@
 	int selectOutputItem = 0;
 	MidiInOut &plugin = static_cast<MidiInOut &>(m_VstPlugin);
 
-	const PmDeviceInfo *device;
-
-	// Go through all PortMidi devices
-	for(PmDeviceID i = 0; (device = Pm_GetDeviceInfo(i)) != nullptr; i++)
+	// Go through all RtMidi devices
+	unsigned int ports = plugin.midiIn.getPortCount();
+	std::string portName;
+	for(unsigned int i = 0; i < ports; i++)
 	{
-		std::string deviceName = std::string(device->name) + std::string(" [") + std::string(device->interf) + std::string("]");
-		// We could make use of Pm_GetDefaultInputDeviceID / Pm_GetDefaultOutputDeviceID here, but is it useful to show those actually?
-
-		if(device->input)
+		try
 		{
-			// We can actually receive MIDI data on this device.
-			int result = m_inputCombo.AddString(deviceName.c_str());
+			portName = plugin.inputDevice.GetPortName(i);
+			int result = m_inputCombo.AddString(portName.c_str());
 			m_inputCombo.SetItemData(result, i);
 
 			if(result != CB_ERR && i == plugin.inputDevice.index)
 				selectInputItem = result;
+		} catch(RtMidiError &error)
+		{
 		}
+	}
 
-		if(device->output)
+	ports = plugin.midiOut.getPortCount();
+	for(unsigned int i = 0; i < ports; i++)
+	{
+		try
 		{
-			// We can actually output MIDI data on this device.
-			int result = m_outputCombo.AddString(deviceName.c_str());
+			portName = plugin.outputDevice.GetPortName(i);
+			int result = m_outputCombo.AddString(portName.c_str());
 			m_outputCombo.SetItemData(result, i);
 
 			if(result != CB_ERR && i == plugin.outputDevice.index)
 				selectOutputItem = result;
+		} catch(RtMidiError &error)
+		{
 		}
 	}
 
@@ -112,13 +118,13 @@
 
 
 // Refresh current input / output device in GUI
-void MidiInOutEditor::SetCurrentDevice(CComboBox &combo, PmDeviceID device)
-//-------------------------------------------------------------------------
+void MidiInOutEditor::SetCurrentDevice(CComboBox &combo, int device)
+//------------------------------------------------------------------
 {
 	int items = combo.GetCount();
 	for(int i = 0; i < items; i++)
 	{
-		if(static_cast<PmDeviceID>(combo.GetItemData(i)) == device)
+		if(static_cast<int>(combo.GetItemData(i)) == device)
 		{
 			combo.SetCurSel(i);
 			break;
@@ -131,7 +137,7 @@
 //------------------------------------------------------------------------------
 {
 	// Update device ID and notify plugin.
-	PmDeviceID newDevice = static_cast<PmDeviceID>(combo.GetItemData(combo.GetCurSel()));
+	int newDevice = static_cast<int>(combo.GetItemData(combo.GetCurSel()));
 	plugin.SetParameter(param, MidiInOut::DeviceIDToParameter(newDevice));
 	plugin.AutomateParameter(param);
 }
Index: plugins/MidiInOut/MidiInOutEditor.h
===================================================================
--- plugins/MidiInOut/MidiInOutEditor.h	(revision 7100)
+++ plugins/MidiInOut/MidiInOutEditor.h	(working copy)
@@ -13,7 +13,6 @@
 #ifdef MODPLUG_TRACKER
 
 #include "../../mptrack/AbstractVstEditor.h"
-#include <portmidi/pm_common/portmidi.h>
 
 OPENMPT_NAMESPACE_BEGIN
 
@@ -32,7 +31,7 @@
 	MidiInOutEditor(MidiInOut &plugin);
 
 	// Refresh current input / output device in GUI
-	void SetCurrentDevice(bool asInputDevice, PmDeviceID device)
+	void SetCurrentDevice(bool asInputDevice, int device)
 	{
 		CComboBox &combo = asInputDevice ? m_inputCombo : m_outputCombo;
 		SetCurrentDevice(combo, device);
@@ -47,7 +46,7 @@
 	// Update lists of available input / output devices
 	void PopulateLists();
 	// Refresh current input / output device in GUI
-	void SetCurrentDevice(CComboBox &combo, PmDeviceID device);
+	void SetCurrentDevice(CComboBox &combo, int device);
 
 	virtual void DoDataExchange(CDataExchange* pDX);
 
rtmidi.patch (20,752 bytes)   
RtMidi-2.patch (24,162 bytes)   
Index: build/premake/ext-rtmidi.lua
===================================================================
--- build/premake/ext-rtmidi.lua	(nonexistent)
+++ build/premake/ext-rtmidi.lua	(working copy)
@@ -0,0 +1,26 @@
+ 
+ project "rtmidi"
+  uuid "05BBD03D-0985-4D76-8DDD-534DA631C3A8"
+  language "C++"
+  location ( "../../build/" .. mpt_projectpathname .. "/ext" )
+  mpt_projectname = "rtmidi"
+  dofile "../../build/premake/premake-defaults-LIBorDLL.lua"
+  dofile "../../build/premake/premake-defaults.lua"
+  dofile "../../build/premake/premake-defaults-winver.lua"
+  targetname "openmpt-rtmidi"
+	filter {}
+	filter { "action:vs*" }
+		characterset "Unicode"
+	filter {}
+  files {
+   "../../include/rtmidi/RtMidi.cpp"
+  }
+  files {
+   "../../include/rtmidi/RtMidi.h"
+  }
+  defines {
+   "__WINDOWS_MM__"
+  }
+  links {
+   "winmm"
+  }

Property changes on: build/premake/ext-rtmidi.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/x-lua
\ No newline at end of property
Index: build/premake/mpt-OpenMPT-VSTi.lua
===================================================================
--- build/premake/mpt-OpenMPT-VSTi.lua	(revision 7555)
+++ build/premake/mpt-OpenMPT-VSTi.lua	(working copy)
@@ -87,8 +87,8 @@
    "opus",
    "opusfile",
    "portaudio",
-   "portmidi",
    "r8brain",
+   "rtmidi",
    "soundtouch",
    "vorbis",
   }
Index: build/premake/mpt-OpenMPT.lua
===================================================================
--- build/premake/mpt-OpenMPT.lua	(revision 7555)
+++ build/premake/mpt-OpenMPT.lua	(working copy)
@@ -122,8 +122,8 @@
    "opus",
    "opusfile",
    "portaudio",
-   "portmidi",
    "r8brain",
+   "rtmidi",
    "soundtouch",
    "vorbis",
   }
Index: build/premake/premake.lua
===================================================================
--- build/premake/premake.lua	(revision 7555)
+++ build/premake/premake.lua	(working copy)
@@ -376,8 +376,8 @@
  dofile "../../build/premake/ext-opus.lua"
  dofile "../../build/premake/ext-opusfile.lua"
  dofile "../../build/premake/ext-portaudio.lua"
- dofile "../../build/premake/ext-portmidi.lua"
  dofile "../../build/premake/ext-r8brain.lua"
+ dofile "../../build/premake/ext-rtmidi.lua"
  dofile "../../build/premake/ext-smbPitchShift.lua"
  dofile "../../build/premake/ext-soundtouch.lua"
  dofile "../../build/premake/ext-UnRAR.lua"
@@ -405,8 +405,8 @@
  dofile "../../build/premake/ext-opus.lua"
  dofile "../../build/premake/ext-opusfile.lua"
  dofile "../../build/premake/ext-portaudio.lua"
- dofile "../../build/premake/ext-portmidi.lua"
  dofile "../../build/premake/ext-r8brain.lua"
+ dofile "../../build/premake/ext-rtmidi.lua"
  dofile "../../build/premake/ext-smbPitchShift.lua"
  dofile "../../build/premake/ext-soundtouch.lua"
  dofile "../../build/premake/ext-UnRAR.lua"
@@ -429,8 +429,8 @@
  dofile "../../build/premake/ext-opus.lua"
  dofile "../../build/premake/ext-opusfile.lua"
  dofile "../../build/premake/ext-portaudio.lua"
- dofile "../../build/premake/ext-portmidi.lua"
  dofile "../../build/premake/ext-r8brain.lua"
+ dofile "../../build/premake/ext-rtmidi.lua"
  dofile "../../build/premake/ext-smbPitchShift.lua"
  dofile "../../build/premake/ext-soundtouch.lua"
  dofile "../../build/premake/ext-UnRAR.lua"
@@ -455,8 +455,8 @@
  dofile "../../build/premake/ext-opus.lua"
  dofile "../../build/premake/ext-opusfile.lua"
  dofile "../../build/premake/ext-portaudio.lua"
- dofile "../../build/premake/ext-portmidi.lua"
  dofile "../../build/premake/ext-r8brain.lua"
+ dofile "../../build/premake/ext-rtmidi.lua"
  dofile "../../build/premake/ext-smbPitchShift.lua"
  dofile "../../build/premake/ext-soundtouch.lua"
  dofile "../../build/premake/ext-UnRAR.lua"
@@ -481,8 +481,8 @@
  dofile "../../build/premake/ext-opus.lua"
  dofile "../../build/premake/ext-opusfile.lua"
  dofile "../../build/premake/ext-portaudio.lua"
- dofile "../../build/premake/ext-portmidi.lua"
  dofile "../../build/premake/ext-pugixml.lua"
+ dofile "../../build/premake/ext-rtmidi.lua"
  dofile "../../build/premake/ext-r8brain.lua"
  dofile "../../build/premake/ext-smbPitchShift.lua"
  dofile "../../build/premake/ext-soundtouch.lua"
Index: plugins/MidiInOut/MidiInOut.cpp
===================================================================
--- plugins/MidiInOut/MidiInOut.cpp	(revision 7555)
+++ plugins/MidiInOut/MidiInOut.cpp	(working copy)
@@ -20,12 +20,16 @@
 OPENMPT_NAMESPACE_BEGIN
 
 
-int MidiInOut::numInstances = 0;
-
 IMixPlugin* MidiInOut::Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
 //------------------------------------------------------------------------------------------------
 {
-	return new (std::nothrow) MidiInOut(factory, sndFile, mixStruct);
+	try
+	{
+		return new (std::nothrow) MidiInOut(factory, sndFile, mixStruct);
+	} catch(RtMidiError &)
+	{
+		return nullptr;
+	}
 }
 
 
@@ -33,14 +37,10 @@
 	: IMidiPlugin(factory, sndFile, mixStruct)
 	, latencyCompensation(true)
 	, programName(_T("Default"))
+	, inputDevice(midiIn)
+	, outputDevice(midiOut)
 //---------------------------------------------------------------------------------------
 {
-	if(!numInstances++)
-	{
-		Pt_Start(1, nullptr, nullptr);
-		Pm_Initialize();
-	}
-
 	m_mixBuffer.Initialize(2, 2);
 	InsertIntoFactoryList();
 }
@@ -50,13 +50,6 @@
 //---------------------
 {
 	Suspend();
-
-	if(--numInstances == 0)
-	{
-		// This terminates MIDI output for all instances of the plugin, so only ever do it if this was the only instance left.
-		Pm_Terminate();
-		Pt_Stop();
-	}
 }
 
 
@@ -127,9 +120,9 @@
 		return;
 
 	uint32 nameStrSize = file.ReadUint32LE();
-	PmDeviceID inID = file.ReadUint32LE();
+	MidiDevice::ID inID = file.ReadUint32LE();
 	uint32 inStrSize = file.ReadUint32LE();
-	PmDeviceID outID = file.ReadUint32LE();
+	MidiDevice::ID outID = file.ReadUint32LE();
 	uint32 outStrSize = file.ReadUint32LE();
 	latencyCompensation = file.ReadUint32LE() != 0;
 
@@ -139,16 +132,21 @@
 
 	file.ReadString<mpt::String::maybeNullTerminated>(s, inStrSize);
 	s = mpt::ToCharset(mpt::CharsetLocale, mpt::CharsetUTF8, s);
-	if(s != GetDeviceName(inID) || !IsInputDevice(inID))
+	if(s != inputDevice.GetPortName(inID))
 	{
 		// Stored name differs from actual device name - try finding another device with the same name.
-		const PmDeviceInfo *device;
-		for(PmDeviceID i = 0; (device = Pm_GetDeviceInfo(i)) != nullptr; i++)
+		unsigned int ports = midiIn.getPortCount();
+		for(unsigned int i = 0; i < ports; i++)
 		{
-			if(device->input && s == device->name)
+			try
 			{
-				inID = i;
-				break;
+				if(s == inputDevice.GetPortName(i))
+				{
+					inID = i;
+					break;
+				}
+			} catch(RtMidiError &)
+			{
 			}
 		}
 	}
@@ -155,16 +153,21 @@
 
 	file.ReadString<mpt::String::maybeNullTerminated>(s, outStrSize);
 	s = mpt::ToCharset(mpt::CharsetLocale, mpt::CharsetUTF8, s);
-	if(s != GetDeviceName(outID) || !IsOutputDevice(outID))
+	if(s != outputDevice.GetPortName(outID))
 	{
 		// Stored name differs from actual device name - try finding another device with the same name.
-		const PmDeviceInfo *device;
-		for(PmDeviceID i = 0; (device = Pm_GetDeviceInfo(i)) != nullptr; i++)
+		unsigned int ports = midiOut.getPortCount();
+		for(unsigned int i = 0; i < ports; i++)
 		{
-			if(device->output && s == device->name)
+			try
 			{
-				outID = i;
-				break;
+				if(s == outputDevice.GetPortName(i))
+				{
+					outID = i;
+					break;
+				}
+			} catch(RtMidiError &)
+			{
 			}
 		}
 	}
@@ -177,7 +180,7 @@
 void MidiInOut::SetParameter(PlugParamIndex index, PlugParamValue value)
 //----------------------------------------------------------------------
 {
-	PmDeviceID newDevice = ParameterToDeviceID(value);
+	MidiDevice::ID newDevice = ParameterToDeviceID(value);
 	OpenDevice(newDevice, (index == kInputParameter));
 
 	// Update selection in editor
@@ -238,12 +241,13 @@
 {
 	MPT_LOCK_GUARD<mpt::mutex> lock(mutex);
 
-	if(outputDevice.stream != nullptr)
+	if(midiOut.isPortOpen())
 	{
 		// Send MIDI clock
 		if(nextClock < 1)
 		{
-			Pm_WriteShort(outputDevice.stream, Now(), 0xF8);
+			std::vector<unsigned char> message(1, 0xF8);
+			midiOut.sendMessage(&message);
 			double bpm = m_SndFile.GetCurrentBPM();
 			if(bpm != 0.0)
 			{
@@ -254,26 +258,22 @@
 	}
 
 	// We don't do any audio processing here, but we process incoming MIDI events.
-	if(inputDevice.stream == nullptr)
+	if(!midiOut.isPortOpen())
 		return;
 
-	while(Pm_Poll(inputDevice.stream))
+	std::vector<unsigned char> message;
+	while(midiIn.getMessage(&message), !message.empty())
 	{
 		// Read incoming MIDI events.
-		PmEvent buffer;
-		Pm_Read(inputDevice.stream, &buffer, 1);
 
 		// Discard events if bypassed
 		if(IsBypassed())
 			continue;
 
-		mpt::byte message[sizeof(buffer.message)];
-		memcpy(message, &buffer.message, sizeof(message));
-
 		if(!bufferedMessage.empty())
 		{
-			bufferedMessage.push_back(buffer.message);
-			if(buffer.message & 0x80808080)
+			bufferedMessage.insert(bufferedMessage.end(), message.begin(), message.end());
+			if(message.back() == 0xF7)
 			{
 				// End of message found!
 				ReceiveSysex(bufferedMessage.data(), bufferedMessage.size() * sizeof(bufferedMessage[0]));
@@ -280,17 +280,19 @@
 				bufferedMessage.clear();
 			}
 			continue;
-		} else if(message[0] == 0xF0)
+		} else if(message.front() == 0xF0)
 		{
 			// Start of SysEx message...
-			if(message[1] != 0xF7 && message[2] != 0xF7 && message[3] != 0xF7)
-				bufferedMessage.push_back(buffer.message);	// ...but not the end!
+			if(message.back() != 0xF7)
+				bufferedMessage.insert(bufferedMessage.end(), message.begin(), message.end());	// ...but not the end!
 			else
-				ReceiveSysex(message, sizeof(message));
+				ReceiveSysex(&message[0], message.size() * sizeof(message[0]));
 			continue;
 		}
 
-		ReceiveMidi(buffer.message);
+		uint32 msg = 0;
+		memcpy(&msg, &message[0], std::min(message.size(), sizeof(msg)));
+		ReceiveMidi(msg);
 	}
 }
 
@@ -304,9 +306,10 @@
 	nextClock = 0;
 	OpenDevice(inputDevice.index, true);
 	OpenDevice(outputDevice.index, false);
-	if(outputDevice.stream != nullptr)
+	if(midiOut.isPortOpen())
 	{
-		Pm_WriteShort(outputDevice.stream, Now(), 0xFA);	// Start
+		std::vector<unsigned char> message(1, 0xFA);	// Start
+		midiOut.sendMessage(&message);
 	}
 }
 
@@ -316,9 +319,10 @@
 //-----------------------
 {
 	// Suspend MIDI I/O
-	if(outputDevice.stream != nullptr)
+	if(midiOut.isPortOpen())
 	{
-		Pm_WriteShort(outputDevice.stream, Now(), 0xFC);	// Stop
+		std::vector<unsigned char> message(1, 0xFC);	// Stop
+		midiOut.sendMessage(&message);
 	}
 	CloseDevice(inputDevice);
 	CloseDevice(outputDevice);
@@ -330,11 +334,12 @@
 void MidiInOut::PositionChanged()
 //-------------------------------
 {
-	if(outputDevice.stream != nullptr)
+	if(midiOut.isPortOpen())
 	{
-		const PtTimestamp now = Now();
-		Pm_WriteShort(outputDevice.stream, now, 0xFC);	// Stop
-		Pm_WriteShort(outputDevice.stream, now, 0xFA);	// Start
+		std::vector<unsigned char> message;
+		message.push_back(0xFC);	// Stop
+		message.push_back(0xFA);	// Start
+		midiOut.sendMessage(&message);
 	}
 }
 
@@ -343,27 +348,30 @@
 bool MidiInOut::MidiSend(uint32 midiCode)
 //---------------------------------------
 {
-	if(outputDevice.stream == nullptr || IsBypassed())
+	if(!midiOut.isPortOpen() || IsBypassed())
 	{
 		// We need an output device to send MIDI messages to.
 		return true;
 	}
 
-	Pm_WriteShort(outputDevice.stream, Now(), midiCode);
+	std::vector<unsigned char> message(3, 0);
+	memcpy(&message[0], &midiCode, 3);
+	midiOut.sendMessage(&message);
 	return true;
 }
 
 
-bool MidiInOut::MidiSysexSend(const void *message, uint32 /*length*/)
-//-------------------------------------------------------------------
+bool MidiInOut::MidiSysexSend(const void *sysex, uint32 length)
+//-------------------------------------------------------------
 {
-	if(outputDevice.stream == nullptr || IsBypassed())
+	if(!midiOut.isPortOpen() || IsBypassed())
 	{
 		// We need an output device to send MIDI messages to.
 		return true;
 	}
 
-	Pm_WriteSysEx(outputDevice.stream, Now(), const_cast<unsigned char *>(static_cast<const unsigned char *>(message)));
+	std::vector<unsigned char> message(static_cast<const unsigned char *>(sysex), static_cast<const unsigned char *>(sysex) + length);
+	midiOut.sendMessage(&message);
 	return true;
 }
 
@@ -405,20 +413,13 @@
 }
 
 
-static PmTimestamp PtTimeWrapper(void* /*time_info*/)
-//---------------------------------------------------
-{
-	return Pt_Time();
-}
-
-
 // Open a device for input or output.
-void MidiInOut::OpenDevice(PmDeviceID newDevice, bool asInputDevice)
-//------------------------------------------------------------------
+void MidiInOut::OpenDevice(MidiDevice::ID newDevice, bool asInputDevice)
+//----------------------------------------------------------------------
 {
 	MidiDevice &device = asInputDevice ? inputDevice : outputDevice;
 
-	if(device.index == newDevice && device.stream != nullptr)
+	if(device.index == newDevice && device.stream.isPortOpen())
 	{
 		// No need to re-open this device.
 		return;
@@ -427,15 +428,16 @@
 	CloseDevice(device);
 
 	device.index = newDevice;
+	device.stream.closePort();
 
 	if(device.index == kNoDevice)
 	{
 		// Dummy device
-		device = MidiDevice();
+		device.name = "<none>";
 		return;
 	}
 
-	PmError result = pmNoError;
+	device.name = device.GetPortName(newDevice);
 	if(m_isResumed)
 	{
 		// Don't open MIDI devices if we're not processing.
@@ -442,31 +444,20 @@
 		// This has to be done since we receive MIDI events in processReplacing(),
 		// so if no processing is happening, some irrelevant events might be queued until the next processing happens...
 		MPT_LOCK_GUARD<mpt::mutex> lock(mutex);
-		if(asInputDevice)
+		
+		try
 		{
-			result = Pm_OpenInput(&device.stream, newDevice, nullptr, 0, nullptr, nullptr);
-		} else
+			device.stream.openPort(newDevice);
+		} catch(RtMidiError &error)
 		{
-			if(latencyCompensation)
+			device.name = "Unavailable";
+			MidiInOutEditor *editor = dynamic_cast<MidiInOutEditor *>(GetEditor());
+			if(editor != nullptr)
 			{
-				// buffer of 10000 events
-				result = Pm_OpenOutput(&device.stream, newDevice, nullptr, 10000, PtTimeWrapper, nullptr, Util::Round<PtTimestamp>(1000.0 * GetOutputLatency()));
-			} else
-			{
-				result = Pm_OpenOutput(&device.stream, newDevice, nullptr, 0, nullptr, nullptr, 0);
+				Reporting::Error("MIDI device cannot be opened:" + error.getMessage(), "MIDI Input / Output", editor);
 			}
 		}
 	}
-
-	// Update current device name
-	device.name = GetDeviceName(device.index);
-
-	MidiInOutEditor *editor = dynamic_cast<MidiInOutEditor *>(GetEditor());
-	if(result != pmNoError && editor != nullptr)
-	{
-		// Display a warning if the editor is open.
-		Reporting::Error("MIDI device cannot be opened!", "MIDI Input / Output", editor);
-	}
 }
 
 
@@ -474,60 +465,26 @@
 void MidiInOut::CloseDevice(MidiDevice &device)
 //---------------------------------------------
 {
-	if(device.stream != nullptr)
+	if(device.stream.isPortOpen())
 	{
 		MPT_LOCK_GUARD<mpt::mutex> lock(mutex);
-		Pm_Close(device.stream);
-		device.stream = nullptr;
+		device.stream.closePort();
 	}
 }
 
 
 // Get a device name
-const char *MidiInOut::GetDeviceName(PmDeviceID index) const
-//-----------------------------------------------------------
+std::string MidiDevice::GetPortName(MidiDevice::ID port)
+//------------------------------------------------------
 {
-	const PmDeviceInfo *deviceInfo = Pm_GetDeviceInfo(index);
-	if(deviceInfo != nullptr)
-		return deviceInfo->name;
-	else
-		return "Unavailable";
+	std::string portName = stream.getPortName(port);
+#if MPT_OS_WINDOWS
+	// Remove auto-appended port number
+	if(portName.length() >= 2)
+		return portName.substr(0, portName.find_last_of(' '));
+#endif
+	return portName;
 }
 
 
-bool MidiInOut::IsInputDevice(PmDeviceID index) const
-//----------------------------------------------------
-{
-	if(index == kNoDevice)
-		return true;
-
-	const PmDeviceInfo *deviceInfo = Pm_GetDeviceInfo(index);
-	if(deviceInfo != nullptr)
-		return deviceInfo->input != 0;
-	else
-		return false;
-}
-
-
-bool MidiInOut::IsOutputDevice(PmDeviceID index) const
-//----------------------------------------------------
-{
-	if(index == kNoDevice)
-		return true;
-
-	const PmDeviceInfo *deviceInfo = Pm_GetDeviceInfo(index);
-	if(deviceInfo != nullptr)
-		return deviceInfo->output != 0;
-	else
-		return false;
-}
-
-
-PtTimestamp MidiInOut::Now() const
-//--------------------------------
-{
-	return (latencyCompensation ? Pt_Time() : 0);
-}
-
-
 OPENMPT_NAMESPACE_END
Index: plugins/MidiInOut/MidiInOut.h
===================================================================
--- plugins/MidiInOut/MidiInOut.h	(revision 7555)
+++ plugins/MidiInOut/MidiInOut.h	(working copy)
@@ -12,8 +12,7 @@
 
 #include "../../common/mptMutex.h"
 #include "../../soundlib/plugins/PlugInterface.h"
-#include <portmidi/pm_common/portmidi.h>
-#include <portmidi/porttime/porttime.h>
+#include "../../include/rtmidi/RtMidi.h"
 
 
 OPENMPT_NAMESPACE_BEGIN
@@ -24,16 +23,20 @@
 //==============
 {
 public:
-	PmDeviceID index;
-	PortMidiStream *stream;
+	typedef unsigned int ID;
+
+	RtMidi &stream;
 	std::string name;
+	ID index;
 
 public:
-	MidiDevice()
-		: index(-1)	// MidiInOut::kNoDevice
-		, stream(nullptr)
+	MidiDevice(RtMidi &stream)
+		: stream(stream)
 		, name("<none>")
+		, index(ID(-1))	// MidiInOut::kNoDevice
 	{ }
+
+	std::string GetPortName(ID port);
 };
 
 
@@ -62,12 +65,13 @@
 	double nextClock;
 
 	// I/O device settings
+	RtMidiIn midiIn;
+	RtMidiOut midiOut;
 	MidiDevice inputDevice;
 	MidiDevice outputDevice;
 	bool latencyCompensation;
 
 	CString programName;
-	static int numInstances;
 
 public:
 	static IMixPlugin* Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
@@ -75,13 +79,13 @@
 	~MidiInOut();
 
 	// Translate a VST parameter to a PortMidi device ID
-	static PmDeviceID ParameterToDeviceID(float value)
+	static MidiDevice::ID ParameterToDeviceID(float value)
 	{
-		return static_cast<PmDeviceID>(value * static_cast<float>(kMaxDevices)) - 1;
+		return static_cast<MidiDevice::ID>(value * static_cast<float>(kMaxDevices)) - 1;
 	}
 
 	// Translate a PortMidi device ID to a VST parameter
-	static float DeviceIDToParameter(PmDeviceID index)
+	static float DeviceIDToParameter(MidiDevice::ID index)
 	{
 		return static_cast<float>(index + 1) / static_cast<float>(kMaxDevices);
 	}
@@ -125,10 +129,6 @@
 #ifdef MODPLUG_TRACKER
 	virtual CString GetDefaultEffectName() { return _T("MIDI Input / Output"); }
 
-	// Cache a range of names, in case one-by-one retrieval would be slow (e.g. when using plugin bridge)
-	virtual void CacheProgramNames(int32, int32) { }
-	virtual void CacheParameterNames(int32, int32) { }
-
 	virtual CString GetParamName(PlugParamIndex param);
 	virtual CString GetParamLabel(PlugParamIndex) { return CString(); }
 	virtual CString GetParamDisplay(PlugParamIndex param);
@@ -155,18 +155,9 @@
 
 protected:
 	// Open a device for input or output.
-	void OpenDevice(PmDeviceID newDevice, bool asInputDevice);
+	void OpenDevice(MidiDevice::ID newDevice, bool asInputDevice);
 	// Close an active device.
 	void CloseDevice(MidiDevice &device);
-	// Get a device name
-	const char *GetDeviceName(PmDeviceID index) const;
-	// Check if the given device is an input device
-	bool IsInputDevice(PmDeviceID index) const;
-	// Check if the given device is an output device
-	bool IsOutputDevice(PmDeviceID index) const;
-
-	// Get current timestamp for sending
-	PtTimestamp Now() const;
 };
 
 
Index: plugins/MidiInOut/MidiInOutEditor.cpp
===================================================================
--- plugins/MidiInOut/MidiInOutEditor.cpp	(revision 7555)
+++ plugins/MidiInOut/MidiInOutEditor.cpp	(working copy)
@@ -14,6 +14,7 @@
 #include "MidiInOutEditor.h"
 #include "../../mptrack/Mptrack.h"
 #include "../../mptrack/resource.h"
+#include "../../include/rtmidi/RtMidi.h"
 
 
 OPENMPT_NAMESPACE_BEGIN
@@ -75,32 +76,38 @@
 	int selectOutputItem = 0;
 	MidiInOut &plugin = static_cast<MidiInOut &>(m_VstPlugin);
 
-	const PmDeviceInfo *device;
-	CString deviceName;
-
-	// Go through all PortMidi devices
-	for(PmDeviceID i = 0; (device = Pm_GetDeviceInfo(i)) != nullptr; i++)
+	// Go through all RtMidi devices
+	unsigned int ports = plugin.midiIn.getPortCount();
+	std::string portName;
+	for(unsigned int i = 0; i < ports; i++)
 	{
-		if(device->input)
+		try
 		{
-			// We can actually receive MIDI data on this device.
-			deviceName = theApp.GetFriendlyMIDIPortName(device->name, true) + _T(" [") + CString(device->interf) + _T("]");
-			int result = m_inputCombo.AddString(deviceName);
+			portName = theApp.GetFriendlyMIDIPortName(plugin.inputDevice.GetPortName(i).c_str(), true);
+			int result = m_inputCombo.AddString(portName.c_str());
 			m_inputCombo.SetItemData(result, i);
 
 			if(result != CB_ERR && i == plugin.inputDevice.index)
 				selectInputItem = result;
 		}
+		catch(RtMidiError &)
+		{
+		}
+	}
 
-		if(device->output)
+	ports = plugin.midiOut.getPortCount();
+	for(unsigned int i = 0; i < ports; i++)
+	{
+		try
 		{
-			// We can actually output MIDI data on this device.
-			deviceName = theApp.GetFriendlyMIDIPortName(device->name, false) + _T(" [") + CString(device->interf) + _T("]");
-			int result = m_outputCombo.AddString(deviceName);
+			portName = theApp.GetFriendlyMIDIPortName(plugin.outputDevice.GetPortName(i).c_str(), false);
+			int result = m_outputCombo.AddString(portName.c_str());
 			m_outputCombo.SetItemData(result, i);
 
 			if(result != CB_ERR && i == plugin.outputDevice.index)
 				selectOutputItem = result;
+		} catch(RtMidiError &)
+		{
 		}
 	}
 
@@ -113,13 +120,13 @@
 
 
 // Refresh current input / output device in GUI
-void MidiInOutEditor::SetCurrentDevice(CComboBox &combo, PmDeviceID device)
-//-------------------------------------------------------------------------
+void MidiInOutEditor::SetCurrentDevice(CComboBox &combo, MidiDevice::ID device)
+//-----------------------------------------------------------------------------
 {
 	int items = combo.GetCount();
 	for(int i = 0; i < items; i++)
 	{
-		if(static_cast<PmDeviceID>(combo.GetItemData(i)) == device)
+		if(static_cast<MidiDevice::ID>(combo.GetItemData(i)) == device)
 		{
 			combo.SetCurSel(i);
 			break;
@@ -132,7 +139,7 @@
 //------------------------------------------------------------------------------
 {
 	// Update device ID and notify plugin.
-	PmDeviceID newDevice = static_cast<PmDeviceID>(combo.GetItemData(combo.GetCurSel()));
+	MidiDevice::ID newDevice = static_cast<MidiDevice::ID>(combo.GetItemData(combo.GetCurSel()));
 	plugin.SetParameter(param, MidiInOut::DeviceIDToParameter(newDevice));
 	plugin.AutomateParameter(param);
 }
Index: plugins/MidiInOut/MidiInOutEditor.h
===================================================================
--- plugins/MidiInOut/MidiInOutEditor.h	(revision 7555)
+++ plugins/MidiInOut/MidiInOutEditor.h	(working copy)
@@ -13,7 +13,6 @@
 #ifdef MODPLUG_TRACKER
 
 #include "../../mptrack/AbstractVstEditor.h"
-#include <portmidi/pm_common/portmidi.h>
 
 OPENMPT_NAMESPACE_BEGIN
 
@@ -32,7 +31,7 @@
 	MidiInOutEditor(MidiInOut &plugin);
 
 	// Refresh current input / output device in GUI
-	void SetCurrentDevice(bool asInputDevice, PmDeviceID device)
+	void SetCurrentDevice(bool asInputDevice, MidiDevice::ID device)
 	{
 		CComboBox &combo = asInputDevice ? m_inputCombo : m_outputCombo;
 		SetCurrentDevice(combo, device);
@@ -47,7 +46,7 @@
 	// Update lists of available input / output devices
 	void PopulateLists();
 	// Refresh current input / output device in GUI
-	void SetCurrentDevice(CComboBox &combo, PmDeviceID device);
+	void SetCurrentDevice(CComboBox &combo, MidiDevice::ID device);
 
 	virtual void DoDataExchange(CDataExchange* pDX);
 
RtMidi-2.patch (24,162 bytes)   
Has the bug occurred in previous versions?Yes
Tested code revision (in case you know it)

Relationships

has duplicate 0000994 closed Crash when trying to mute a MIDI output 

Activities

Saga Musix

Saga Musix

2016-09-11 14:39

administrator   ~0002656

Last edited: 2016-09-11 14:40

So far, running my own builds with RtMidi (instead of PortMidi) did not yield any deadlocks for a long while. There might be a problem in PortMidi. If we wanted to switch, we would have to re-implement PortMidi's latency compensation.

Quick-and-dirty RtMidi replacement patch has been added to the issue.

Saga Musix

Saga Musix

2017-02-05 01:40

administrator   ~0002862

PortMidi also randomly crashes e.g. in winmm_write_flush, which I tried working around previously, but there are apparently more ways it can crash: https://forum.openmpt.org/index.php?topic=5784.0

Saga Musix

Saga Musix

2017-02-06 22:01

administrator   ~0002864

Updated patch: Moved Midi In to callback function - might be more desirable as per https://github.com/thestk/rtmidi/issues/51#issuecomment-138185164
And fixed SysEx reception

RtMidi-3.patch (29,131 bytes)   
Index: build/premake/ext-rtmidi.lua
===================================================================
--- build/premake/ext-rtmidi.lua	(nonexistent)
+++ build/premake/ext-rtmidi.lua	(working copy)
@@ -0,0 +1,26 @@
+ 
+ project "rtmidi"
+  uuid "05BBD03D-0985-4D76-8DDD-534DA631C3A8"
+  language "C++"
+  location ( "../../build/" .. mpt_projectpathname .. "/ext" )
+  mpt_projectname = "rtmidi"
+  dofile "../../build/premake/premake-defaults-LIBorDLL.lua"
+  dofile "../../build/premake/premake-defaults.lua"
+  dofile "../../build/premake/premake-defaults-winver.lua"
+  targetname "openmpt-rtmidi"
+	filter {}
+	filter { "action:vs*" }
+		characterset "Unicode"
+	filter {}
+  files {
+   "../../include/rtmidi/RtMidi.cpp"
+  }
+  files {
+   "../../include/rtmidi/RtMidi.h"
+  }
+  defines {
+   "__WINDOWS_MM__"
+  }
+  links {
+   "winmm"
+  }

Property changes on: build/premake/ext-rtmidi.lua
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Added: svn:executable
## -0,0 +1 ##
+*
\ No newline at end of property
Added: svn:mime-type
## -0,0 +1 ##
+text/x-lua
\ No newline at end of property
Index: build/premake/mpt-OpenMPT-VSTi.lua
===================================================================
--- build/premake/mpt-OpenMPT-VSTi.lua	(revision 7555)
+++ build/premake/mpt-OpenMPT-VSTi.lua	(working copy)
@@ -87,8 +87,8 @@
    "opus",
    "opusfile",
    "portaudio",
-   "portmidi",
    "r8brain",
+   "rtmidi",
    "soundtouch",
    "vorbis",
   }
Index: build/premake/mpt-OpenMPT.lua
===================================================================
--- build/premake/mpt-OpenMPT.lua	(revision 7555)
+++ build/premake/mpt-OpenMPT.lua	(working copy)
@@ -122,8 +122,8 @@
    "opus",
    "opusfile",
    "portaudio",
-   "portmidi",
    "r8brain",
+   "rtmidi",
    "soundtouch",
    "vorbis",
   }
Index: build/premake/premake.lua
===================================================================
--- build/premake/premake.lua	(revision 7555)
+++ build/premake/premake.lua	(working copy)
@@ -376,8 +376,8 @@
  dofile "../../build/premake/ext-opus.lua"
  dofile "../../build/premake/ext-opusfile.lua"
  dofile "../../build/premake/ext-portaudio.lua"
- dofile "../../build/premake/ext-portmidi.lua"
  dofile "../../build/premake/ext-r8brain.lua"
+ dofile "../../build/premake/ext-rtmidi.lua"
  dofile "../../build/premake/ext-smbPitchShift.lua"
  dofile "../../build/premake/ext-soundtouch.lua"
  dofile "../../build/premake/ext-UnRAR.lua"
@@ -405,8 +405,8 @@
  dofile "../../build/premake/ext-opus.lua"
  dofile "../../build/premake/ext-opusfile.lua"
  dofile "../../build/premake/ext-portaudio.lua"
- dofile "../../build/premake/ext-portmidi.lua"
  dofile "../../build/premake/ext-r8brain.lua"
+ dofile "../../build/premake/ext-rtmidi.lua"
  dofile "../../build/premake/ext-smbPitchShift.lua"
  dofile "../../build/premake/ext-soundtouch.lua"
  dofile "../../build/premake/ext-UnRAR.lua"
@@ -429,8 +429,8 @@
  dofile "../../build/premake/ext-opus.lua"
  dofile "../../build/premake/ext-opusfile.lua"
  dofile "../../build/premake/ext-portaudio.lua"
- dofile "../../build/premake/ext-portmidi.lua"
  dofile "../../build/premake/ext-r8brain.lua"
+ dofile "../../build/premake/ext-rtmidi.lua"
  dofile "../../build/premake/ext-smbPitchShift.lua"
  dofile "../../build/premake/ext-soundtouch.lua"
  dofile "../../build/premake/ext-UnRAR.lua"
@@ -455,8 +455,8 @@
  dofile "../../build/premake/ext-opus.lua"
  dofile "../../build/premake/ext-opusfile.lua"
  dofile "../../build/premake/ext-portaudio.lua"
- dofile "../../build/premake/ext-portmidi.lua"
  dofile "../../build/premake/ext-r8brain.lua"
+ dofile "../../build/premake/ext-rtmidi.lua"
  dofile "../../build/premake/ext-smbPitchShift.lua"
  dofile "../../build/premake/ext-soundtouch.lua"
  dofile "../../build/premake/ext-UnRAR.lua"
@@ -481,8 +481,8 @@
  dofile "../../build/premake/ext-opus.lua"
  dofile "../../build/premake/ext-opusfile.lua"
  dofile "../../build/premake/ext-portaudio.lua"
- dofile "../../build/premake/ext-portmidi.lua"
  dofile "../../build/premake/ext-pugixml.lua"
+ dofile "../../build/premake/ext-rtmidi.lua"
  dofile "../../build/premake/ext-r8brain.lua"
  dofile "../../build/premake/ext-smbPitchShift.lua"
  dofile "../../build/premake/ext-soundtouch.lua"
Index: plugins/MidiInOut/MidiInOut.cpp
===================================================================
--- plugins/MidiInOut/MidiInOut.cpp	(revision 7555)
+++ plugins/MidiInOut/MidiInOut.cpp	(working copy)
@@ -20,27 +20,27 @@
 OPENMPT_NAMESPACE_BEGIN
 
 
-int MidiInOut::numInstances = 0;
-
 IMixPlugin* MidiInOut::Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
 //------------------------------------------------------------------------------------------------
 {
-	return new (std::nothrow) MidiInOut(factory, sndFile, mixStruct);
+	try
+	{
+		return new (std::nothrow) MidiInOut(factory, sndFile, mixStruct);
+	} catch(RtMidiError &)
+	{
+		return nullptr;
+	}
 }
 
 
 MidiInOut::MidiInOut(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct)
 	: IMidiPlugin(factory, sndFile, mixStruct)
-	, latencyCompensation(true)
-	, programName(_T("Default"))
+	, m_latencyCompensation(true)
+	, m_programName(_T("Default"))
+	, inputDevice(midiIn)
+	, outputDevice(midiOut)
 //---------------------------------------------------------------------------------------
 {
-	if(!numInstances++)
-	{
-		Pt_Start(1, nullptr, nullptr);
-		Pm_Initialize();
-	}
-
 	m_mixBuffer.Initialize(2, 2);
 	InsertIntoFactoryList();
 }
@@ -50,13 +50,6 @@
 //---------------------
 {
 	Suspend();
-
-	if(--numInstances == 0)
-	{
-		// This terminates MIDI output for all instances of the plugin, so only ever do it if this was the only instance left.
-		Pm_Terminate();
-		Pt_Stop();
-	}
 }
 
 
@@ -93,7 +86,7 @@
 size_t MidiInOut::GetChunk(char *(&chunk), bool /*isBank*/)
 //---------------------------------------------------------
 {
-	const std::string programName8 = mpt::ToCharset(mpt::CharsetUTF8, programName);
+	const std::string programName8 = mpt::ToCharset(mpt::CharsetUTF8, m_programName);
 	const std::string inputName8 = mpt::ToCharset(mpt::CharsetUTF8, mpt::CharsetLocale, inputDevice.name);
 	const std::string outputName8 = mpt::ToCharset(mpt::CharsetUTF8, mpt::CharsetLocale, outputDevice.name);
 
@@ -106,13 +99,13 @@
 	mpt::IO::WriteIntLE<uint32>(s, static_cast<uint32>(inputDevice.name.size()));
 	mpt::IO::WriteIntLE<uint32>(s, outputDevice.index);
 	mpt::IO::WriteIntLE<uint32>(s, static_cast<uint32>(outputDevice.name.size()));
-	mpt::IO::WriteIntLE<uint32>(s, latencyCompensation);
+	mpt::IO::WriteIntLE<uint32>(s, m_latencyCompensation);
 	mpt::IO::WriteRaw(s, programName8.c_str(), programName8.size());
 	mpt::IO::WriteRaw(s, inputName8.c_str(), inputName8.size());
 	mpt::IO::WriteRaw(s, outputName8.c_str(), outputName8.size());
-	chunkData = s.str();
-	chunk = const_cast<char *>(chunkData.c_str());
-	return chunkData.size();
+	m_chunkData = s.str();
+	chunk = const_cast<char *>(m_chunkData.c_str());
+	return m_chunkData.size();
 }
 
 
@@ -127,28 +120,33 @@
 		return;
 
 	uint32 nameStrSize = file.ReadUint32LE();
-	PmDeviceID inID = file.ReadUint32LE();
+	MidiDevice::ID inID = file.ReadUint32LE();
 	uint32 inStrSize = file.ReadUint32LE();
-	PmDeviceID outID = file.ReadUint32LE();
+	MidiDevice::ID outID = file.ReadUint32LE();
 	uint32 outStrSize = file.ReadUint32LE();
-	latencyCompensation = file.ReadUint32LE() != 0;
+	m_latencyCompensation = file.ReadUint32LE() != 0;
 
 	std::string s;
 	file.ReadString<mpt::String::maybeNullTerminated>(s, nameStrSize);
-	programName = mpt::ToCString(mpt::CharsetUTF8, s);
+	m_programName = mpt::ToCString(mpt::CharsetUTF8, s);
 
 	file.ReadString<mpt::String::maybeNullTerminated>(s, inStrSize);
 	s = mpt::ToCharset(mpt::CharsetLocale, mpt::CharsetUTF8, s);
-	if(s != GetDeviceName(inID) || !IsInputDevice(inID))
+	if(s != inputDevice.GetPortName(inID))
 	{
 		// Stored name differs from actual device name - try finding another device with the same name.
-		const PmDeviceInfo *device;
-		for(PmDeviceID i = 0; (device = Pm_GetDeviceInfo(i)) != nullptr; i++)
+		unsigned int ports = midiIn.getPortCount();
+		for(unsigned int i = 0; i < ports; i++)
 		{
-			if(device->input && s == device->name)
+			try
 			{
-				inID = i;
-				break;
+				if(s == inputDevice.GetPortName(i))
+				{
+					inID = i;
+					break;
+				}
+			} catch(RtMidiError &)
+			{
 			}
 		}
 	}
@@ -155,16 +153,21 @@
 
 	file.ReadString<mpt::String::maybeNullTerminated>(s, outStrSize);
 	s = mpt::ToCharset(mpt::CharsetLocale, mpt::CharsetUTF8, s);
-	if(s != GetDeviceName(outID) || !IsOutputDevice(outID))
+	if(s != outputDevice.GetPortName(outID))
 	{
 		// Stored name differs from actual device name - try finding another device with the same name.
-		const PmDeviceInfo *device;
-		for(PmDeviceID i = 0; (device = Pm_GetDeviceInfo(i)) != nullptr; i++)
+		unsigned int ports = midiOut.getPortCount();
+		for(unsigned int i = 0; i < ports; i++)
 		{
-			if(device->output && s == device->name)
+			try
 			{
-				outID = i;
-				break;
+				if(s == outputDevice.GetPortName(i))
+				{
+					outID = i;
+					break;
+				}
+			} catch(RtMidiError &)
+			{
 			}
 		}
 	}
@@ -177,7 +180,7 @@
 void MidiInOut::SetParameter(PlugParamIndex index, PlugParamValue value)
 //----------------------------------------------------------------------
 {
-	PmDeviceID newDevice = ParameterToDeviceID(value);
+	MidiDevice::ID newDevice = ParameterToDeviceID(value);
 	OpenDevice(newDevice, (index == kInputParameter));
 
 	// Update selection in editor
@@ -236,61 +239,59 @@
 void MidiInOut::Process(float *, float *, uint32 numFrames)
 //---------------------------------------------------------
 {
-	MPT_LOCK_GUARD<mpt::mutex> lock(mutex);
+	MPT_LOCK_GUARD<mpt::mutex> lock(m_mutex);
 
-	if(outputDevice.stream != nullptr)
+	if(midiOut.isPortOpen())
 	{
 		// Send MIDI clock
-		if(nextClock < 1)
+		if(m_nextClock < 1)
 		{
-			Pm_WriteShort(outputDevice.stream, Now(), 0xF8);
+			std::vector<unsigned char> message(1, 0xF8);
+			midiOut.sendMessage(&message);
 			double bpm = m_SndFile.GetCurrentBPM();
 			if(bpm != 0.0)
 			{
-				nextClock += 2.5 * m_SndFile.GetSampleRate() / bpm;
+				m_nextClock += 2.5 * m_SndFile.GetSampleRate() / bpm;
 			}
 		}
-		nextClock -= numFrames;
+		m_nextClock -= numFrames;
 	}
+}
 
-	// We don't do any audio processing here, but we process incoming MIDI events.
-	if(inputDevice.stream == nullptr)
-		return;
 
-	while(Pm_Poll(inputDevice.stream))
+void MidiInOut::InputCallback(double /*deltatime*/, std::vector<unsigned char> &message)
+//--------------------------------------------------------------------------------------
+{
+	// We will check the bypass status before passing on the message, and not before entering the function,
+	// because otherwise we might read garbage if we toggle bypass status in the middle of a SysEx message.
+	bool isBypassed = IsBypassed();
+	if(message.empty())
 	{
-		// Read incoming MIDI events.
-		PmEvent buffer;
-		Pm_Read(inputDevice.stream, &buffer, 1);
-
-		// Discard events if bypassed
-		if(IsBypassed())
-			continue;
-
-		mpt::byte message[sizeof(buffer.message)];
-		memcpy(message, &buffer.message, sizeof(message));
-
-		if(!bufferedMessage.empty())
+		return;
+	} else if(!m_bufferedMessage.empty())
+	{
+		// SysEx message (continued)
+		m_bufferedMessage.insert(m_bufferedMessage.end(), message.begin(), message.end());
+		if(message.back() == 0xF7)
 		{
-			bufferedMessage.push_back(buffer.message);
-			if(buffer.message & 0x80808080)
-			{
-				// End of message found!
-				ReceiveSysex(bufferedMessage.data(), bufferedMessage.size() * sizeof(bufferedMessage[0]));
-				bufferedMessage.clear();
-			}
-			continue;
-		} else if(message[0] == 0xF0)
-		{
-			// Start of SysEx message...
-			if(message[1] != 0xF7 && message[2] != 0xF7 && message[3] != 0xF7)
-				bufferedMessage.push_back(buffer.message);	// ...but not the end!
-			else
-				ReceiveSysex(message, sizeof(message));
-			continue;
+			// End of message found!
+			if(!isBypassed)
+				ReceiveSysex(m_bufferedMessage.data(), m_bufferedMessage.size());
+			m_bufferedMessage.clear();
 		}
-
-		ReceiveMidi(buffer.message);
+	} else if(message.front() == 0xF0)
+	{
+		// Start of SysEx message...
+		if(message.back() != 0xF7)
+			m_bufferedMessage.insert(m_bufferedMessage.end(), message.begin(), message.end());	// ...but not the end!
+		else if(!isBypassed)
+			ReceiveSysex(message.data(), message.size());
+	} else if(!isBypassed)
+	{
+		// Regular message
+		uint32 msg = 0;
+		memcpy(&msg, message.data(), std::min(message.size(), sizeof(msg)));
+		ReceiveMidi(msg);
 	}
 }
 
@@ -301,12 +302,13 @@
 {
 	// Resume MIDI I/O
 	m_isResumed = true;
-	nextClock = 0;
+	m_nextClock = 0;
 	OpenDevice(inputDevice.index, true);
 	OpenDevice(outputDevice.index, false);
-	if(outputDevice.stream != nullptr)
+	if(midiOut.isPortOpen())
 	{
-		Pm_WriteShort(outputDevice.stream, Now(), 0xFA);	// Start
+		std::vector<unsigned char> message(1, 0xFA);	// Start
+		midiOut.sendMessage(&message);
 	}
 }
 
@@ -316,9 +318,10 @@
 //-----------------------
 {
 	// Suspend MIDI I/O
-	if(outputDevice.stream != nullptr)
+	if(midiOut.isPortOpen())
 	{
-		Pm_WriteShort(outputDevice.stream, Now(), 0xFC);	// Stop
+		std::vector<unsigned char> message(1, 0xFC);	// Stop
+		midiOut.sendMessage(&message);
 	}
 	CloseDevice(inputDevice);
 	CloseDevice(outputDevice);
@@ -330,11 +333,12 @@
 void MidiInOut::PositionChanged()
 //-------------------------------
 {
-	if(outputDevice.stream != nullptr)
+	if(midiOut.isPortOpen())
 	{
-		const PtTimestamp now = Now();
-		Pm_WriteShort(outputDevice.stream, now, 0xFC);	// Stop
-		Pm_WriteShort(outputDevice.stream, now, 0xFA);	// Start
+		std::vector<unsigned char> message;
+		message.push_back(0xFC);	// Stop
+		message.push_back(0xFA);	// Start
+		midiOut.sendMessage(&message);
 	}
 }
 
@@ -343,27 +347,30 @@
 bool MidiInOut::MidiSend(uint32 midiCode)
 //---------------------------------------
 {
-	if(outputDevice.stream == nullptr || IsBypassed())
+	if(!midiOut.isPortOpen() || IsBypassed())
 	{
 		// We need an output device to send MIDI messages to.
 		return true;
 	}
 
-	Pm_WriteShort(outputDevice.stream, Now(), midiCode);
+	std::vector<unsigned char> message(3, 0);
+	memcpy(&message[0], &midiCode, 3);
+	midiOut.sendMessage(&message);
 	return true;
 }
 
 
-bool MidiInOut::MidiSysexSend(const void *message, uint32 /*length*/)
-//-------------------------------------------------------------------
+bool MidiInOut::MidiSysexSend(const void *sysex, uint32 length)
+//-------------------------------------------------------------
 {
-	if(outputDevice.stream == nullptr || IsBypassed())
+	if(!midiOut.isPortOpen() || IsBypassed())
 	{
 		// We need an output device to send MIDI messages to.
 		return true;
 	}
 
-	Pm_WriteSysEx(outputDevice.stream, Now(), const_cast<unsigned char *>(static_cast<const unsigned char *>(message)));
+	std::vector<unsigned char> message(static_cast<const unsigned char *>(sysex), static_cast<const unsigned char *>(sysex) + length);
+	midiOut.sendMessage(&message);
 	return true;
 }
 
@@ -405,20 +412,13 @@
 }
 
 
-static PmTimestamp PtTimeWrapper(void* /*time_info*/)
-//---------------------------------------------------
-{
-	return Pt_Time();
-}
-
-
 // Open a device for input or output.
-void MidiInOut::OpenDevice(PmDeviceID newDevice, bool asInputDevice)
-//------------------------------------------------------------------
+void MidiInOut::OpenDevice(MidiDevice::ID newDevice, bool asInputDevice)
+//----------------------------------------------------------------------
 {
 	MidiDevice &device = asInputDevice ? inputDevice : outputDevice;
 
-	if(device.index == newDevice && device.stream != nullptr)
+	if(device.index == newDevice && device.stream.isPortOpen())
 	{
 		// No need to re-open this device.
 		return;
@@ -427,46 +427,41 @@
 	CloseDevice(device);
 
 	device.index = newDevice;
+	device.stream.closePort();
 
 	if(device.index == kNoDevice)
 	{
 		// Dummy device
-		device = MidiDevice();
+		device.name = "<none>";
 		return;
 	}
 
-	PmError result = pmNoError;
-	if(m_isResumed)
+	device.name = device.GetPortName(newDevice);
+	//if(m_isResumed)
 	{
 		// Don't open MIDI devices if we're not processing.
 		// This has to be done since we receive MIDI events in processReplacing(),
 		// so if no processing is happening, some irrelevant events might be queued until the next processing happens...
-		MPT_LOCK_GUARD<mpt::mutex> lock(mutex);
-		if(asInputDevice)
+		MPT_LOCK_GUARD<mpt::mutex> lock(m_mutex);
+		
+		try
 		{
-			result = Pm_OpenInput(&device.stream, newDevice, nullptr, 0, nullptr, nullptr);
-		} else
+			device.stream.openPort(newDevice);
+			if(asInputDevice)
+			{
+				midiIn.setCallback(InputCallback, this);
+				midiIn.ignoreTypes(false, true, true);
+			}
+		} catch(RtMidiError &error)
 		{
-			if(latencyCompensation)
+			device.name = "Unavailable";
+			MidiInOutEditor *editor = dynamic_cast<MidiInOutEditor *>(GetEditor());
+			if(editor != nullptr)
 			{
-				// buffer of 10000 events
-				result = Pm_OpenOutput(&device.stream, newDevice, nullptr, 10000, PtTimeWrapper, nullptr, Util::Round<PtTimestamp>(1000.0 * GetOutputLatency()));
-			} else
-			{
-				result = Pm_OpenOutput(&device.stream, newDevice, nullptr, 0, nullptr, nullptr, 0);
+				Reporting::Error("MIDI device cannot be opened:" + error.getMessage(), "MIDI Input / Output", editor);
 			}
 		}
 	}
-
-	// Update current device name
-	device.name = GetDeviceName(device.index);
-
-	MidiInOutEditor *editor = dynamic_cast<MidiInOutEditor *>(GetEditor());
-	if(result != pmNoError && editor != nullptr)
-	{
-		// Display a warning if the editor is open.
-		Reporting::Error("MIDI device cannot be opened!", "MIDI Input / Output", editor);
-	}
 }
 
 
@@ -474,60 +469,26 @@
 void MidiInOut::CloseDevice(MidiDevice &device)
 //---------------------------------------------
 {
-	if(device.stream != nullptr)
+	if(device.stream.isPortOpen())
 	{
-		MPT_LOCK_GUARD<mpt::mutex> lock(mutex);
-		Pm_Close(device.stream);
-		device.stream = nullptr;
+		MPT_LOCK_GUARD<mpt::mutex> lock(m_mutex);
+		device.stream.closePort();
 	}
 }
 
 
 // Get a device name
-const char *MidiInOut::GetDeviceName(PmDeviceID index) const
-//-----------------------------------------------------------
+std::string MidiDevice::GetPortName(MidiDevice::ID port)
+//------------------------------------------------------
 {
-	const PmDeviceInfo *deviceInfo = Pm_GetDeviceInfo(index);
-	if(deviceInfo != nullptr)
-		return deviceInfo->name;
-	else
-		return "Unavailable";
+	std::string portName = stream.getPortName(port);
+#if MPT_OS_WINDOWS
+	// Remove auto-appended port number
+	if(portName.length() >= 2)
+		return portName.substr(0, portName.find_last_of(' '));
+#endif
+	return portName;
 }
 
 
-bool MidiInOut::IsInputDevice(PmDeviceID index) const
-//----------------------------------------------------
-{
-	if(index == kNoDevice)
-		return true;
-
-	const PmDeviceInfo *deviceInfo = Pm_GetDeviceInfo(index);
-	if(deviceInfo != nullptr)
-		return deviceInfo->input != 0;
-	else
-		return false;
-}
-
-
-bool MidiInOut::IsOutputDevice(PmDeviceID index) const
-//----------------------------------------------------
-{
-	if(index == kNoDevice)
-		return true;
-
-	const PmDeviceInfo *deviceInfo = Pm_GetDeviceInfo(index);
-	if(deviceInfo != nullptr)
-		return deviceInfo->output != 0;
-	else
-		return false;
-}
-
-
-PtTimestamp MidiInOut::Now() const
-//--------------------------------
-{
-	return (latencyCompensation ? Pt_Time() : 0);
-}
-
-
 OPENMPT_NAMESPACE_END
Index: plugins/MidiInOut/MidiInOut.h
===================================================================
--- plugins/MidiInOut/MidiInOut.h	(revision 7555)
+++ plugins/MidiInOut/MidiInOut.h	(working copy)
@@ -12,8 +12,7 @@
 
 #include "../../common/mptMutex.h"
 #include "../../soundlib/plugins/PlugInterface.h"
-#include <portmidi/pm_common/portmidi.h>
-#include <portmidi/porttime/porttime.h>
+#include "../../include/rtmidi/RtMidi.h"
 
 
 OPENMPT_NAMESPACE_BEGIN
@@ -24,16 +23,20 @@
 //==============
 {
 public:
-	PmDeviceID index;
-	PortMidiStream *stream;
+	typedef unsigned int ID;
+
+	RtMidi &stream;
 	std::string name;
+	ID index;
 
 public:
-	MidiDevice()
-		: index(-1)	// MidiInOut::kNoDevice
-		, stream(nullptr)
+	MidiDevice(RtMidi &stream)
+		: stream(stream)
 		, name("<none>")
+		, index(ID(-1))	// MidiInOut::kNoDevice
 	{ }
+
+	std::string GetPortName(ID port);
 };
 
 
@@ -56,18 +59,21 @@
 		kMaxDevices = 65536,		// Should be a power of 2 to avoid rounding errors.
 	};
 
-	std::string chunkData;					// Storage for GetChunk
-	std::vector<uint32> bufferedMessage;	// For receiving SysEx messages
-	mpt::mutex mutex;
-	double nextClock;
+	std::string m_chunkData;						// Storage for GetChunk
+	std::vector<unsigned char> m_bufferedMessage;	// For receiving SysEx messages
+	mpt::mutex m_mutex;
+	double m_nextClock;
 
 	// I/O device settings
+	RtMidiIn midiIn;
+	RtMidiOut midiOut;
 	MidiDevice inputDevice;
 	MidiDevice outputDevice;
-	bool latencyCompensation;
+	bool m_latencyCompensation;
 
-	CString programName;
-	static int numInstances;
+#ifdef MODPLUG_TRACKER
+	CString m_programName;
+#endif
 
 public:
 	static IMixPlugin* Create(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN *mixStruct);
@@ -75,13 +81,13 @@
 	~MidiInOut();
 
 	// Translate a VST parameter to a PortMidi device ID
-	static PmDeviceID ParameterToDeviceID(float value)
+	static MidiDevice::ID ParameterToDeviceID(float value)
 	{
-		return static_cast<PmDeviceID>(value * static_cast<float>(kMaxDevices)) - 1;
+		return static_cast<MidiDevice::ID>(value * static_cast<float>(kMaxDevices)) - 1;
 	}
 
 	// Translate a PortMidi device ID to a VST parameter
-	static float DeviceIDToParameter(PmDeviceID index)
+	static float DeviceIDToParameter(MidiDevice::ID index)
 	{
 		return static_cast<float>(index + 1) / static_cast<float>(kMaxDevices);
 	}
@@ -125,16 +131,12 @@
 #ifdef MODPLUG_TRACKER
 	virtual CString GetDefaultEffectName() { return _T("MIDI Input / Output"); }
 
-	// Cache a range of names, in case one-by-one retrieval would be slow (e.g. when using plugin bridge)
-	virtual void CacheProgramNames(int32, int32) { }
-	virtual void CacheParameterNames(int32, int32) { }
-
 	virtual CString GetParamName(PlugParamIndex param);
 	virtual CString GetParamLabel(PlugParamIndex) { return CString(); }
 	virtual CString GetParamDisplay(PlugParamIndex param);
-	virtual CString GetCurrentProgramName() { return programName; }
-	virtual void SetCurrentProgramName(const CString &name) { programName = name; }
-	virtual CString GetProgramName(int32) { return programName; }
+	virtual CString GetCurrentProgramName() { return m_programName; }
+	virtual void SetCurrentProgramName(const CString &name) { m_programName = name; }
+	virtual CString GetProgramName(int32) { return m_programName; }
 	virtual CString GetPluginVendor() { return _T("OpenMPT Project"); }
 
 	virtual bool HasEditor() const { return true; }
@@ -155,18 +157,12 @@
 
 protected:
 	// Open a device for input or output.
-	void OpenDevice(PmDeviceID newDevice, bool asInputDevice);
+	void OpenDevice(MidiDevice::ID newDevice, bool asInputDevice);
 	// Close an active device.
 	void CloseDevice(MidiDevice &device);
-	// Get a device name
-	const char *GetDeviceName(PmDeviceID index) const;
-	// Check if the given device is an input device
-	bool IsInputDevice(PmDeviceID index) const;
-	// Check if the given device is an output device
-	bool IsOutputDevice(PmDeviceID index) const;
 
-	// Get current timestamp for sending
-	PtTimestamp Now() const;
+	static void InputCallback(double deltatime, std::vector<unsigned char> *message, void *userData) { static_cast<MidiInOut *>(userData)->InputCallback(deltatime, *message); }
+	void InputCallback(double deltatime, std::vector<unsigned char> &message);
 };
 
 
Index: plugins/MidiInOut/MidiInOutEditor.cpp
===================================================================
--- plugins/MidiInOut/MidiInOutEditor.cpp	(revision 7555)
+++ plugins/MidiInOut/MidiInOutEditor.cpp	(working copy)
@@ -14,6 +14,7 @@
 #include "MidiInOutEditor.h"
 #include "../../mptrack/Mptrack.h"
 #include "../../mptrack/resource.h"
+#include "../../include/rtmidi/RtMidi.h"
 
 
 OPENMPT_NAMESPACE_BEGIN
@@ -51,7 +52,7 @@
 //--------------------------------------------
 {
 	Create(IDD_MIDI_IO_PLUGIN, parent);
-	m_latencyCheck.SetCheck(static_cast<MidiInOut &>(m_VstPlugin).latencyCompensation ? BST_CHECKED : BST_UNCHECKED);
+	m_latencyCheck.SetCheck(static_cast<MidiInOut &>(m_VstPlugin).m_latencyCompensation ? BST_CHECKED : BST_UNCHECKED);
 	PopulateLists();
 	return CAbstractVstEditor::OpenEditor(parent);
 }
@@ -75,32 +76,38 @@
 	int selectOutputItem = 0;
 	MidiInOut &plugin = static_cast<MidiInOut &>(m_VstPlugin);
 
-	const PmDeviceInfo *device;
-	CString deviceName;
-
-	// Go through all PortMidi devices
-	for(PmDeviceID i = 0; (device = Pm_GetDeviceInfo(i)) != nullptr; i++)
+	// Go through all RtMidi devices
+	unsigned int ports = plugin.midiIn.getPortCount();
+	std::string portName;
+	for(unsigned int i = 0; i < ports; i++)
 	{
-		if(device->input)
+		try
 		{
-			// We can actually receive MIDI data on this device.
-			deviceName = theApp.GetFriendlyMIDIPortName(device->name, true) + _T(" [") + CString(device->interf) + _T("]");
-			int result = m_inputCombo.AddString(deviceName);
+			portName = theApp.GetFriendlyMIDIPortName(plugin.inputDevice.GetPortName(i).c_str(), true);
+			int result = m_inputCombo.AddString(portName.c_str());
 			m_inputCombo.SetItemData(result, i);
 
 			if(result != CB_ERR && i == plugin.inputDevice.index)
 				selectInputItem = result;
 		}
+		catch(RtMidiError &)
+		{
+		}
+	}
 
-		if(device->output)
+	ports = plugin.midiOut.getPortCount();
+	for(unsigned int i = 0; i < ports; i++)
+	{
+		try
 		{
-			// We can actually output MIDI data on this device.
-			deviceName = theApp.GetFriendlyMIDIPortName(device->name, false) + _T(" [") + CString(device->interf) + _T("]");
-			int result = m_outputCombo.AddString(deviceName);
+			portName = theApp.GetFriendlyMIDIPortName(plugin.outputDevice.GetPortName(i).c_str(), false);
+			int result = m_outputCombo.AddString(portName.c_str());
 			m_outputCombo.SetItemData(result, i);
 
 			if(result != CB_ERR && i == plugin.outputDevice.index)
 				selectOutputItem = result;
+		} catch(RtMidiError &)
+		{
 		}
 	}
 
@@ -113,13 +120,13 @@
 
 
 // Refresh current input / output device in GUI
-void MidiInOutEditor::SetCurrentDevice(CComboBox &combo, PmDeviceID device)
-//-------------------------------------------------------------------------
+void MidiInOutEditor::SetCurrentDevice(CComboBox &combo, MidiDevice::ID device)
+//-----------------------------------------------------------------------------
 {
 	int items = combo.GetCount();
 	for(int i = 0; i < items; i++)
 	{
-		if(static_cast<PmDeviceID>(combo.GetItemData(i)) == device)
+		if(static_cast<MidiDevice::ID>(combo.GetItemData(i)) == device)
 		{
 			combo.SetCurSel(i);
 			break;
@@ -132,7 +139,7 @@
 //------------------------------------------------------------------------------
 {
 	// Update device ID and notify plugin.
-	PmDeviceID newDevice = static_cast<PmDeviceID>(combo.GetItemData(combo.GetCurSel()));
+	MidiDevice::ID newDevice = static_cast<MidiDevice::ID>(combo.GetItemData(combo.GetCurSel()));
 	plugin.SetParameter(param, MidiInOut::DeviceIDToParameter(newDevice));
 	plugin.AutomateParameter(param);
 }
@@ -157,7 +164,7 @@
 {
 	MidiInOut &plugin = static_cast<MidiInOut &>(m_VstPlugin);
 	plugin.CloseDevice(plugin.outputDevice);
-	plugin.latencyCompensation = (m_latencyCheck.GetCheck() != BST_UNCHECKED);
+	plugin.m_latencyCompensation = (m_latencyCheck.GetCheck() != BST_UNCHECKED);
 	plugin.OpenDevice(plugin.outputDevice.index, false);
 }
 
Index: plugins/MidiInOut/MidiInOutEditor.h
===================================================================
--- plugins/MidiInOut/MidiInOutEditor.h	(revision 7555)
+++ plugins/MidiInOut/MidiInOutEditor.h	(working copy)
@@ -13,7 +13,6 @@
 #ifdef MODPLUG_TRACKER
 
 #include "../../mptrack/AbstractVstEditor.h"
-#include <portmidi/pm_common/portmidi.h>
 
 OPENMPT_NAMESPACE_BEGIN
 
@@ -32,7 +31,7 @@
 	MidiInOutEditor(MidiInOut &plugin);
 
 	// Refresh current input / output device in GUI
-	void SetCurrentDevice(bool asInputDevice, PmDeviceID device)
+	void SetCurrentDevice(bool asInputDevice, MidiDevice::ID device)
 	{
 		CComboBox &combo = asInputDevice ? m_inputCombo : m_outputCombo;
 		SetCurrentDevice(combo, device);
@@ -47,7 +46,7 @@
 	// Update lists of available input / output devices
 	void PopulateLists();
 	// Refresh current input / output device in GUI
-	void SetCurrentDevice(CComboBox &combo, PmDeviceID device);
+	void SetCurrentDevice(CComboBox &combo, MidiDevice::ID device);
 
 	virtual void DoDataExchange(CDataExchange* pDX);
 
RtMidi-3.patch (29,131 bytes)   
Saga Musix

Saga Musix

2017-02-09 01:19

administrator   ~0002870

r7573 switches from PortMidi to RtMidi.

Issue History

Date Modified Username Field Change
2016-07-31 22:49 Saga Musix New Issue
2016-07-31 22:52 Saga Musix Description Updated
2016-07-31 22:53 Saga Musix Description Updated
2016-09-11 14:39 Saga Musix Note Added: 0002656
2016-09-11 14:40 Saga Musix File Added: rtmidi.patch
2016-09-11 14:40 Saga Musix Note Edited: 0002656
2016-11-20 17:48 Saga Musix Category VST => Plugins (VST)
2016-11-20 17:48 Saga Musix Category Plugins (VST) => Plugins / VST
2017-02-05 01:40 Saga Musix Note Added: 0002862
2017-02-05 01:43 Saga Musix Priority normal => high
2017-02-05 01:43 Saga Musix Severity minor => crash
2017-02-06 00:23 Saga Musix File Added: RtMidi-2.patch
2017-02-06 00:24 Saga Musix Assigned To => Saga Musix
2017-02-06 00:24 Saga Musix Status new => assigned
2017-02-06 00:24 Saga Musix Target Version => OpenMPT 1.27.01.00 / libopenmpt 0.3.1 (upgrade first)
2017-02-06 22:01 Saga Musix File Added: RtMidi-3.patch
2017-02-06 22:01 Saga Musix Note Added: 0002864
2017-02-09 01:19 Saga Musix Status assigned => resolved
2017-02-09 01:19 Saga Musix Resolution open => fixed
2017-02-09 01:19 Saga Musix Fixed in Version => OpenMPT 1.27.01.00 / libopenmpt 0.3.1 (upgrade first)
2017-02-09 01:19 Saga Musix Note Added: 0002870
2017-07-24 11:37 Saga Musix Relationship added has duplicate 0000994