Skip to content

Commit 733cbcf

Browse files
committed
winmidi: implement virtual port support. Fixes #194
1 parent d3769f7 commit 733cbcf

File tree

5 files changed

+238
-24
lines changed

5 files changed

+238
-24
lines changed

cmake/libremidi.examples.cmake

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ add_example(qmidiin)
2929
add_example(sysextest)
3030
add_example(minimal)
3131
add_example(midi2_echo)
32+
add_example(midi2_virtual)
3233
add_example(rawmidiin)
3334

3435
if(LIBREMIDI_HAS_STD_FLAT_SET AND LIBREMIDI_HAS_STD_PRINTLN)

examples/midi2_virtual.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
#include <libremidi/backends.hpp>
2+
#include <libremidi/libremidi.hpp>
3+
4+
#if defined(_WIN32) && __has_include(<winrt/base.h>)
5+
#include <winrt/base.h>
6+
#endif
7+
8+
#include <iostream>
9+
#include <mutex>
10+
11+
int main()
12+
try
13+
{
14+
#if defined(_WIN32) && __has_include(<winrt/base.h>)
15+
// Necessary for using WinUWP and WinMIDI, must be done as early as possible in your main()
16+
winrt::init_apartment();
17+
#endif
18+
19+
using namespace libremidi;
20+
namespace lm2 = libremidi::midi2;
21+
22+
// Create a midi out
23+
midi_out midiout{{}, lm2::out_default_configuration()};
24+
if (auto err = midiout.open_virtual_port("my-midiout"); err != stdx::error{})
25+
err.throw_exception();
26+
27+
// Create a midi in
28+
auto on_ump = [&](const libremidi::ump& message) {
29+
if (midiout.is_port_connected())
30+
midiout.send_ump(message);
31+
};
32+
midi_in midiin{{.on_message = on_ump}, lm2::in_default_configuration()};
33+
34+
if (auto err = midiin.open_virtual_port("my-midiin"); err != stdx::error{})
35+
err.throw_exception();
36+
37+
// Wait until we exit
38+
char input;
39+
std::cin.get(input);
40+
}
41+
catch (const std::exception& error)
42+
{
43+
std::cerr << error.what() << std::endl;
44+
return EXIT_FAILURE;
45+
}

include/libremidi/backends/winmidi/helpers.hpp

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414
#include <winrt/Windows.Foundation.Collections.h>
1515
#include <winrt/Windows.Devices.Enumeration.h>
1616
#include <winrt/Microsoft.Windows.Devices.Midi2.h>
17+
#if __has_include(<winrt/Microsoft.Windows.Devices.Midi2.Endpoints.Virtual.h>)
18+
#define LIBREMIDI_WINMIDI_HAS_VIRTUAL_DEVICE 1
19+
#include <winrt/Microsoft.Windows.Devices.Midi2.Endpoints.Virtual.h>
20+
#endif
1721
#include <libremidi/cmidi2.hpp>
1822

1923
#if __has_include(<WindowsMidiServicesAppSdkComExtensions.h>)
@@ -215,4 +219,48 @@ struct winmidi_shared_data
215219
}
216220
};
217221

222+
223+
inline winrt::Microsoft::Windows::Devices::Midi2::Endpoints::Virtual::MidiVirtualDeviceCreationConfig setup_virtualdevice_config(
224+
std::string_view manufacturer_name,
225+
std::string_view product_id,
226+
std::string_view port_name,
227+
MidiFunctionBlockDirection direction)
228+
{
229+
using namespace winrt::Microsoft::Windows::Devices::Midi2;
230+
using namespace winrt::Microsoft::Windows::Devices::Midi2::Endpoints::Virtual;
231+
232+
MidiDeclaredEndpointInfo endpointInfo;
233+
endpointInfo.HasStaticFunctionBlocks = true;
234+
endpointInfo.Name = to_hstring(port_name);
235+
endpointInfo.ProductInstanceId = to_hstring(product_id);
236+
endpointInfo.SupportsMidi10Protocol = true;
237+
endpointInfo.SupportsMidi20Protocol = true;
238+
endpointInfo.SupportsReceivingJitterReductionTimestamps = false;
239+
endpointInfo.SupportsSendingJitterReductionTimestamps = false;
240+
endpointInfo.SpecificationVersionMajor = 1;
241+
endpointInfo.SpecificationVersionMinor = 1;
242+
243+
// Create the virtual device configuration
244+
if(manufacturer_name.empty())
245+
manufacturer_name = "libremidi";
246+
247+
MidiVirtualDeviceCreationConfig creationConfig(
248+
to_hstring(port_name), // name
249+
to_hstring(std::string("Virtual input port: ") + std::string(port_name)), // description
250+
to_hstring(manufacturer_name), // manufacturer
251+
endpointInfo);
252+
253+
// Add a default function block for the virtual device
254+
MidiFunctionBlock block0;
255+
block0.Number(0);
256+
block0.IsActive(true);
257+
block0.Name(to_hstring(port_name));
258+
block0.FirstGroup(MidiGroup(static_cast<uint8_t>(0)));
259+
block0.GroupCount(16); // All 16 groups
260+
block0.Direction(direction);
261+
creationConfig.FunctionBlocks().Append(block0);
262+
263+
return creationConfig;
264+
265+
}
218266
}

include/libremidi/backends/winmidi/midi_in.hpp

Lines changed: 84 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -102,28 +102,88 @@ class midi_in_impl final
102102

103103
m_group_filter = port.port - 1;
104104

105-
// TODO use a MidiGroupEndpointListener for the filtering
106-
m_endpoint = m_session.CreateEndpointConnection(ep.EndpointDeviceId());
105+
try
106+
{
107+
// TODO use a MidiGroupEndpointListener for the filtering
108+
m_endpoint = m_session.CreateEndpointConnection(ep.EndpointDeviceId());
109+
if (!m_endpoint)
110+
return std::errc::device_or_resource_busy;
111+
112+
#if !LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS
113+
m_revoke_token = m_endpoint.MessageReceived(
114+
[this](
115+
const winrt::Microsoft::Windows::Devices::Midi2::IMidiMessageReceivedEventSource&,
116+
const winrt::Microsoft::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& args) {
117+
process_message(args);
118+
});
119+
#else
120+
m_endpoint.as(libremidi::IID_IMidiEndpointConnectionRaw, m_raw_endpoint.put_void());
121+
122+
m_raw_endpoint->SetMessagesReceivedCallback(
123+
&raw_callback
124+
);
125+
#endif
126+
127+
m_endpoint.Open();
128+
129+
return stdx::error{};
130+
}
131+
catch (...)
132+
{
133+
return std::errc::io_error;
134+
}
135+
}
136+
137+
#if LIBREMIDI_WINMIDI_HAS_VIRTUAL_DEVICE
138+
stdx::error open_virtual_port(std::string_view port_name) override
139+
{
140+
// Create endpoint information for the virtual device
141+
using namespace winrt::Microsoft::Windows::Devices::Midi2;
142+
using namespace winrt::Microsoft::Windows::Devices::Midi2::Endpoints::Virtual;
143+
144+
auto conf = setup_virtualdevice_config(configuration.client_name, port_name, port_name, MidiFunctionBlockDirection::BlockInput);
145+
146+
m_virtual = MidiVirtualDeviceManager::CreateVirtualDevice(conf);
147+
if (m_virtual == nullptr)
148+
return std::errc::device_or_resource_busy;
149+
150+
try
151+
{
152+
// Create a connection to the device-side endpoint
153+
m_endpoint = m_session.CreateEndpointConnection(m_virtual.DeviceEndpointDeviceId());
154+
if (!m_endpoint)
155+
return std::errc::device_or_resource_busy;
156+
157+
// Add the virtual device as a message processing plugin to receive messages
158+
m_endpoint.AddMessageProcessingPlugin(m_virtual);
107159

108160
#if !LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS
109-
m_revoke_token = m_endpoint.MessageReceived(
110-
[this](
111-
const winrt::Microsoft::Windows::Devices::Midi2::IMidiMessageReceivedEventSource&,
112-
const winrt::Microsoft::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& args) {
113-
process_message(args);
114-
});
161+
// Register message received event handler
162+
m_revoke_token = m_endpoint.MessageReceived(
163+
[this](
164+
const winrt::Microsoft::Windows::Devices::Midi2::IMidiMessageReceivedEventSource&,
165+
const winrt::Microsoft::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& args) {
166+
process_message(args);
167+
});
115168
#else
116-
m_endpoint.as(libremidi::IID_IMidiEndpointConnectionRaw, m_raw_endpoint.put_void());
117-
118-
m_raw_endpoint->SetMessagesReceivedCallback(
119-
&raw_callback
120-
);
169+
// Use COM interface for raw callback
170+
m_endpoint.as(libremidi::IID_IMidiEndpointConnectionRaw, m_raw_endpoint.put_void());
171+
if (m_raw_endpoint)
172+
{
173+
m_raw_endpoint->SetMessagesReceivedCallback(&raw_callback);
174+
}
121175
#endif
122176

123-
m_endpoint.Open();
177+
m_endpoint.Open();
124178

125-
return stdx::error{};
179+
return stdx::error{};
180+
}
181+
catch (...)
182+
{
183+
return std::errc::io_error;
184+
}
126185
}
186+
#endif
127187

128188
void process_message(
129189
const winrt::Microsoft::Windows::Devices::Midi2::MidiMessageReceivedEventArgs& msg)
@@ -198,6 +258,12 @@ class midi_in_impl final
198258
}
199259
#endif
200260
m_session.DisconnectEndpointConnection(m_endpoint.ConnectionId());
261+
262+
if (m_virtual)
263+
{
264+
m_virtual.Cleanup();
265+
m_virtual = nullptr;
266+
}
201267
return stdx::error{};
202268
}
203269

@@ -209,6 +275,9 @@ class midi_in_impl final
209275
winrt::Microsoft::Windows::Devices::Midi2::MidiEndpointConnection m_endpoint{nullptr};
210276
#if LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS
211277
winrt::impl::com_ref<IMidiEndpointConnectionRaw> m_raw_endpoint{};
278+
#endif
279+
#if LIBREMIDI_WINMIDI_HAS_VIRTUAL_DEVICE
280+
winrt::Microsoft::Windows::Devices::Midi2::Endpoints::Virtual::MidiVirtualDevice m_virtual{nullptr};
212281
#endif
213282
midi2::input_state_machine m_processing{this->configuration};
214283
int m_group_filter = -1;

include/libremidi/backends/winmidi/midi_out.hpp

Lines changed: 60 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -46,21 +46,69 @@ class midi_out_impl final
4646
if (!ep || !gp)
4747
return std::errc::address_not_available;
4848

49-
m_endpoint = m_session.CreateEndpointConnection(ep.EndpointDeviceId());
50-
#if LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS
51-
m_endpoint.as(libremidi::IID_IMidiEndpointConnectionRaw, m_raw_endpoint.put_void());
49+
try
50+
{
51+
m_endpoint = m_session.CreateEndpointConnection(ep.EndpointDeviceId());
52+
if (!m_endpoint)
53+
return std::errc::device_or_resource_busy;
54+
#if LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS
55+
m_endpoint.as(libremidi::IID_IMidiEndpointConnectionRaw, m_raw_endpoint.put_void());
56+
#endif
57+
m_endpoint.Open();
58+
59+
return stdx::error{};
60+
}
61+
catch (...)
62+
{
63+
return std::errc::io_error;
64+
}
65+
}
66+
67+
#if LIBREMIDI_WINMIDI_HAS_VIRTUAL_DEVICE
68+
stdx::error open_virtual_port(std::string_view port_name) override
69+
{
70+
// Create endpoint information for the virtual device
71+
using namespace winrt::Microsoft::Windows::Devices::Midi2;
72+
using namespace winrt::Microsoft::Windows::Devices::Midi2::Endpoints::Virtual;
73+
74+
auto conf = setup_virtualdevice_config(configuration.client_name, port_name, port_name, MidiFunctionBlockDirection::BlockOutput);
75+
76+
m_virtual = MidiVirtualDeviceManager::CreateVirtualDevice(conf);
77+
if (m_virtual == nullptr)
78+
return std::errc::device_or_resource_busy;
79+
80+
try
81+
{
82+
m_endpoint = m_session.CreateEndpointConnection(m_virtual.DeviceEndpointDeviceId());
83+
if (!m_endpoint)
84+
return std::errc::device_or_resource_busy;
85+
86+
#if LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS
87+
m_endpoint.as(libremidi::IID_IMidiEndpointConnectionRaw, m_raw_endpoint.put_void());
5288
#endif
53-
m_endpoint.Open();
5489

55-
return stdx::error{};
90+
m_endpoint.Open();
91+
92+
return stdx::error{};
93+
}
94+
catch (...)
95+
{
96+
return std::errc::io_error;
97+
}
5698
}
99+
#endif
57100

58101
stdx::error close_port() override
59102
{
60103
if(!m_endpoint)
61104
return std::errc::not_connected;
62105

63106
m_session.DisconnectEndpointConnection(m_endpoint.ConnectionId());
107+
if (m_virtual)
108+
{
109+
m_virtual.Cleanup();
110+
m_virtual = nullptr;
111+
}
64112
return stdx::error{};
65113
}
66114

@@ -95,19 +143,19 @@ class midi_out_impl final
95143
switch (bytes / 4)
96144
{
97145
case 1:
98-
assert( m_raw_endpoint->ValidateBufferHasOnlyCompleteUmps(1, (UINT32*)ump));
146+
assert(m_raw_endpoint->ValidateBufferHasOnlyCompleteUmps(1, (UINT32*)ump));
99147
ret = m_raw_endpoint->SendMidiMessagesRaw(0, 1, (UINT32*)ump);
100148
break;
101149
case 2:
102-
assert( m_raw_endpoint->ValidateBufferHasOnlyCompleteUmps(2, (UINT32*)ump));
150+
assert(m_raw_endpoint->ValidateBufferHasOnlyCompleteUmps(2, (UINT32*)ump));
103151
ret = m_raw_endpoint->SendMidiMessagesRaw(0, 2, (UINT32*)ump);
104152
break;
105153
case 3:
106-
assert( m_raw_endpoint->ValidateBufferHasOnlyCompleteUmps(3, (UINT32*)ump));
154+
assert(m_raw_endpoint->ValidateBufferHasOnlyCompleteUmps(3, (UINT32*)ump));
107155
ret = m_raw_endpoint->SendMidiMessagesRaw(0, 3, (UINT32*)ump);
108156
break;
109157
case 4:
110-
assert( m_raw_endpoint->ValidateBufferHasOnlyCompleteUmps(4, (UINT32*)ump));
158+
assert(m_raw_endpoint->ValidateBufferHasOnlyCompleteUmps(4, (UINT32*)ump));
111159
ret = m_raw_endpoint->SendMidiMessagesRaw(0, 4, (UINT32*)ump);
112160
break;
113161
default:
@@ -128,6 +176,9 @@ class midi_out_impl final
128176
#if LIBREMIDI_WINMIDI_HAS_COM_EXTENSIONS
129177
winrt::impl::com_ref<IMidiEndpointConnectionRaw> m_raw_endpoint{};
130178
#endif
179+
#if LIBREMIDI_WINMIDI_HAS_VIRTUAL_DEVICE
180+
winrt::Microsoft::Windows::Devices::Midi2::Endpoints::Virtual::MidiVirtualDevice m_virtual{nullptr};
181+
#endif
131182
};
132183

133184
}

0 commit comments

Comments
 (0)