OXYGENHUB
◆ main openmsx / src / serial / MidiInCoreMIDI.cc
7.7 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
#ifdef __APPLE__
2
3
#include "MidiInCoreMIDI.hh"
4
#include "MidiInConnector.hh"
5
#include "PluggingController.hh"
6
#include "PlugException.hh"
7
#include "EventDistributor.hh"
8
#include "Scheduler.hh"
9
#include "serialize.hh"
10
#include "StringOp.hh"
11
#include "xrange.hh"
12
#include <mach/mach_time.h>
13
#include <memory>
14
15
16
namespace openmsx {
17
18
// MidiInCoreMIDI ===========================================================
19
20
void MidiInCoreMIDI::registerAll(EventDistributor& eventDistributor,
21
                                 Scheduler& scheduler,
22
                                 PluggingController& controller)
23
{
24
	for (auto i : xrange(MIDIGetNumberOfSources())) {
25
		if (MIDIEndpointRef endpoint = MIDIGetSource(i)) {
26
			controller.registerPluggable(std::make_unique<MidiInCoreMIDI>(
27
					eventDistributor, scheduler, endpoint));
28
		}
29
	}
30
}
31
32
MidiInCoreMIDI::MidiInCoreMIDI(EventDistributor& eventDistributor_,
33
                               Scheduler& scheduler_, MIDIEndpointRef endpoint_)
34
	: eventDistributor(eventDistributor_)
35
	, scheduler(scheduler_)
36
	, endpoint(endpoint_)
37
{
38
	// Get a user-presentable name for the endpoint.
39
	CFStringRef midiDeviceName;
40
	OSStatus status = MIDIObjectGetStringProperty(
41
						endpoint, kMIDIPropertyDisplayName, &midiDeviceName);
42
	if (status) {
43
		status = MIDIObjectGetStringProperty(
44
						endpoint, kMIDIPropertyName, &midiDeviceName);
45
	}
46
	if (status) {
47
		name = "Nameless endpoint";
48
	} else {
49
		name = strCat(StringOp::fromCFString(midiDeviceName), " IN");
50
		CFRelease(midiDeviceName);
51
	}
52
53
	eventDistributor.registerEventListener(
54
			EventType::MIDI_IN_COREMIDI, *this);
55
}
56
57
MidiInCoreMIDI::~MidiInCoreMIDI()
58
{
59
	eventDistributor.unregisterEventListener(
60
			EventType::MIDI_IN_COREMIDI, *this);
61
}
62
63
void MidiInCoreMIDI::plugHelper(Connector& /*connector*/, EmuTime /*time*/)
64
{
65
	// Create client.
66
	if (OSStatus status = MIDIClientCreate(
67
			CFSTR("openMSX"), nullptr, nullptr, &client)) {
68
		throw PlugException("Failed to create MIDI client (", status, ')');
69
	}
70
	// Create input port.
71
	if (OSStatus status = MIDIInputPortCreate(
72
			client, CFSTR("Input"), sendPacketList, this, &port)) {
73
		MIDIClientDispose(client);
74
		client = 0;
75
		throw PlugException("Failed to create MIDI port (", status, ')');
76
	}
77
78
	MIDIPortConnectSource(port, endpoint, nullptr);
79
}
80
81
void MidiInCoreMIDI::unplugHelper(EmuTime /*time*/)
82
{
83
	// Dispose of the client; this automatically disposes of the port as well.
84
	if (OSStatus status = MIDIClientDispose(client)) {
85
		fprintf(stderr, "Failed to dispose of MIDI client (%d)\n", int(status));
86
	}
87
	port = 0;
88
	client = 0;
89
}
90
91
zstring_view MidiInCoreMIDI::getName() const
92
{
93
	return name;
94
}
95
96
zstring_view MidiInCoreMIDI::getDescription() const
97
{
98
	return "Receives MIDI events from an existing CoreMIDI source.";
99
}
100
101
void MidiInCoreMIDI::sendPacketList(const MIDIPacketList *packetList,
102
                                    void *readProcRefCon, void *srcConnRefCon)
103
{
104
	static_cast<MidiInCoreMIDI*>(readProcRefCon)
105
			->sendPacketList(packetList, srcConnRefCon);
106
}
107
108
void MidiInCoreMIDI::sendPacketList(const MIDIPacketList *packetList,
109
                                    void * /*srcConnRefCon*/) {
110
	{
111
		std::scoped_lock lock(mutex);
112
		const MIDIPacket* packet = &packetList->packet[0];
113
		repeat(packetList->numPackets, [&] {
114
			for (auto j : xrange(packet->length)) {
115
				queue.push_back(packet->data[j]);
116
			}
117
			packet = MIDIPacketNext(packet);
118
		});
119
	}
120
	eventDistributor.distributeEvent(MidiInCoreMidiEvent());
121
}
122
123
// MidiInDevice
124
void MidiInCoreMIDI::signal(EmuTime time)
125
{
126
	auto connector = static_cast<MidiInConnector*>(getConnector());
127
	if (!connector->acceptsData()) {
128
		std::scoped_lock lock(mutex);
129
		queue.clear();
130
		return;
131
	}
132
	if (!connector->ready()) {
133
		return;
134
	}
135
136
	uint8_t data;
137
	{
138
		std::scoped_lock lock(mutex);
139
		if (queue.empty()) return;
140
		data = queue.pop_front();
141
	}
142
	connector->recvByte(data, time);
143
}
144
145
// EventListener
146
bool MidiInCoreMIDI::signalEvent(const Event& /*event*/)
147
{
148
	if (isPluggedIn()) {
149
		signal(scheduler.getCurrentTime());
150
	} else {
151
		std::scoped_lock lock(mutex);
152
		queue.clear();
153
	}
154
	return false;
155
}
156
157
template<typename Archive>
158
void MidiInCoreMIDI::serialize(Archive& /*ar*/, unsigned /*version*/)
159
{
160
}
161
INSTANTIATE_SERIALIZE_METHODS(MidiInCoreMIDI);
162
REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, MidiInCoreMIDI, "MidiInCoreMIDI");
163
164
165
// MidiInCoreMIDIVirtual ====================================================
166
167
MidiInCoreMIDIVirtual::MidiInCoreMIDIVirtual(EventDistributor& eventDistributor_,
168
                                             Scheduler& scheduler_)
169
	: eventDistributor(eventDistributor_)
170
	, scheduler(scheduler_)
171
	, client(0)
172
	, endpoint(0)
173
{
174
	eventDistributor.registerEventListener(
175
			EventType::MIDI_IN_COREMIDI_VIRTUAL, *this);
176
}
177
178
MidiInCoreMIDIVirtual::~MidiInCoreMIDIVirtual()
179
{
180
	eventDistributor.unregisterEventListener(
181
			EventType::MIDI_IN_COREMIDI_VIRTUAL, *this);
182
}
183
184
void MidiInCoreMIDIVirtual::plugHelper(Connector& /*connector*/,
185
                                       EmuTime /*time*/)
186
{
187
	// Create client.
188
	if (OSStatus status = MIDIClientCreate(CFSTR("openMSX"),
189
	                                       nullptr, nullptr, &client)) {
190
		throw PlugException("Failed to create MIDI client (", status, ')');
191
	}
192
	// Create endpoint.
193
	if (OSStatus status = MIDIDestinationCreate(client, CFSTR("openMSX"),
194
	                                            sendPacketList, this,
195
	                                            &endpoint)) {
196
		MIDIClientDispose(client);
197
		throw PlugException("Failed to create MIDI endpoint (", status, ')');
198
	}
199
}
200
201
void MidiInCoreMIDIVirtual::unplugHelper(EmuTime /*time*/)
202
{
203
	if (OSStatus status = MIDIEndpointDispose(endpoint)) {
204
		fprintf(stderr, "Failed to dispose of MIDI port (%d)\n", int(status));
205
	}
206
	endpoint = 0;
207
	if (OSStatus status = MIDIClientDispose(client)) {
208
		fprintf(stderr, "Failed to dispose of MIDI client (%d)\n", int(status));
209
	}
210
	client = 0;
211
}
212
213
zstring_view MidiInCoreMIDIVirtual::getName() const
214
{
215
	return "Virtual IN";
216
}
217
218
zstring_view MidiInCoreMIDIVirtual::getDescription() const
219
{
220
	return "Sends MIDI events from a newly created CoreMIDI virtual source.";
221
}
222
223
void MidiInCoreMIDIVirtual::sendPacketList(const MIDIPacketList *packetList,
224
                                           void *readProcRefCon,
225
                                           void *srcConnRefCon)
226
{
227
	static_cast<MidiInCoreMIDIVirtual*>(readProcRefCon)
228
			->sendPacketList(packetList, srcConnRefCon);
229
}
230
231
void MidiInCoreMIDIVirtual::sendPacketList(const MIDIPacketList *packetList,
232
                                           void * /*srcConnRefCon*/)
233
{
234
	{
235
		std::scoped_lock lock(mutex);
236
		const MIDIPacket* packet = &packetList->packet[0];
237
		repeat(packetList->numPackets, [&] {
238
			for (auto j : xrange(packet->length)) {
239
				queue.push_back(packet->data[j]);
240
			}
241
			packet = MIDIPacketNext(packet);
242
		});
243
	}
244
	eventDistributor.distributeEvent(MidiInCoreMidiVirtualEvent());
245
}
246
247
// MidiInDevice
248
void MidiInCoreMIDIVirtual::signal(EmuTime time)
249
{
250
	auto connector = static_cast<MidiInConnector*>(getConnector());
251
	if (!connector->acceptsData()) {
252
		std::scoped_lock lock(mutex);
253
		queue.clear();
254
		return;
255
	}
256
	if (!connector->ready()) {
257
		return;
258
	}
259
260
	uint8_t data;
261
	{
262
		std::scoped_lock lock(mutex);
263
		if (queue.empty()) return;
264
		data = queue.pop_front();
265
	}
266
	connector->recvByte(data, time);
267
}
268
269
// EventListener
270
bool MidiInCoreMIDIVirtual::signalEvent(const Event& /*event*/)
271
{
272
	if (isPluggedIn()) {
273
		signal(scheduler.getCurrentTime());
274
	} else {
275
		std::scoped_lock lock(mutex);
276
		queue.clear();
277
	}
278
	return false;
279
}
280
281
template<typename Archive>
282
void MidiInCoreMIDIVirtual::serialize(Archive& /*ar*/, unsigned /*version*/)
283
{
284
}
285
INSTANTIATE_SERIALIZE_METHODS(MidiInCoreMIDIVirtual);
286
REGISTER_POLYMORPHIC_INITIALIZER(Pluggable, MidiInCoreMIDIVirtual, "MidiInCoreMIDIVirtual");
287
288
} // namespace openmsx
289
290
#endif // defined(__APPLE__)