OXYGENHUB
◆ main openmsx / src / serial / Midi_w32.cc
9.1 KiB · C++ · last changed chore(): initial commit 1 month ago source blame history raw
e8555c89 Andy McCall 1
/*
2
 * Win32 MIDI utility routines for openMSX.
3
 *
4
 * Copyright (c) 2003 Reikan.  All rights reserved.
5
 *
6
 * Redistribution and use in source and binary forms, with or without
7
 * modification, are permitted provided that the following conditions
8
 * are met:
9
 * 1. Redistributions of source code must retain the above copyright
10
 *    notice, this list of conditions and the following disclaimer.
11
 * 2. Redistributions in binary form must reproduce the above copyright
12
 *    notice, this list of conditions and the following disclaimer in the
13
 *    documentation and/or other materials provided with the distribution.
14
 *
15
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
16
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25
 */
26
27
#ifdef _WIN32
28
29
#include "Midi_w32.hh"
30
31
#include "MSXException.hh"
32
33
#include "MemBuffer.hh"
34
#include "cstdiop.hh"
35
#include "one_of.hh"
36
#include "xrange.hh"
37
38
#include <array>
39
#include <bit>
40
#include <cassert>
41
#include <cstdlib>
42
#include <cstring>
43
44
#define MAXPATHLEN MAX_PATH
45
46
namespace openmsx {
47
48
/*
49
 * MIDI I/O helper functions for Win32.
50
 */
51
// MIDI SYSTEM MESSAGES max length is not defined...
52
// TODO: support variable length.
53
static constexpr size_t OPENMSX_W32_MIDI_SYSMES_MAXLEN = 4096;
54
55
56
struct vfn_midi {
57
	unsigned idx;
58
	unsigned devid;
59
	HMIDI    handle;
60
	std::array<char, MAXPATHLEN + 1> vfname;
61
	std::array<char, MAXPNAMELEN> devname;
62
};
63
64
struct outbuf {
65
	MIDIHDR  header;
66
};
67
68
static MemBuffer<vfn_midi> vfnt_midiout;
69
static MemBuffer<vfn_midi> vfnt_midiin;
70
static unsigned vfnt_midiout_num, vfnt_midiin_num;
71
72
static MemBuffer<int> state_out;
73
static MemBuffer<outbuf> buf_out;
74
75
static MIDIHDR inhdr;
76
static std::array<char, OPENMSX_W32_MIDI_SYSMES_MAXLEN> inlongmes;
77
78
79
static void w32_midiDevNameConv(char *dst, const char *src)
80
{
81
	size_t len = strlen(src);
82
	size_t i = 0;
83
	for (/**/; i < len; ++i) {
84
		if ((src[i] < '0') || (src[i] > 'z') ||
85
		    ((src[i] > '9') && (src[i] < 'A')) ||
86
		    ((src[i] > 'Z') && (src[i] < 'a'))) {
87
			dst[i] = '_';
88
		} else {
89
			dst[i] = src[i];
90
		}
91
	}
92
	dst[i] = '\0';
93
}
94
95
96
// MIDI-OUT
97
static int w32_midiOutFindDev(unsigned *idx, unsigned *dev, const char *vfn)
98
{
99
	for (auto i : xrange(vfnt_midiout_num)) {
100
		if (!strcmp(vfnt_midiout[i].vfname.data(), vfn)) {
101
			*idx = i;
102
			*dev = vfnt_midiout[i].devid;
103
			return 0;
104
		}
105
	}
106
	return -1;
107
}
108
109
110
int w32_midiOutInit()
111
{
112
	vfnt_midiout_num = 0;
113
	unsigned num = midiOutGetNumDevs();
114
	if (!num) return 0;
115
116
	state_out.resize(num + 1);
117
	memset(state_out.data(), 0, (num + 1) * sizeof(int));
118
119
	buf_out.resize(num + 1);
120
	memset(buf_out.data(), 0, (num + 1) * sizeof(outbuf));
121
122
	vfnt_midiout.resize(num + 1);
123
124
	// MIDI_MAPPER is #define's as ((UINT)-1)
125
	auto OPENMSX_MIDI_MAPPER = static_cast<UINT>(-1);
126
	MIDIOUTCAPSA cap;
127
	if (midiOutGetDevCapsA(OPENMSX_MIDI_MAPPER, &cap, sizeof(cap)) != MMSYSERR_NOERROR) {
128
		return 2;
129
	}
130
	vfnt_midiout[0].devid = OPENMSX_MIDI_MAPPER;
131
	w32_midiDevNameConv(vfnt_midiout[0].devname.data(), cap.szPname);
132
	strncpy(vfnt_midiout[0].vfname.data(), "midi-out", MAXPATHLEN);
133
	vfnt_midiout[0].vfname[MAXPATHLEN] = '\0';
134
	vfnt_midiout_num++;
135
136
	for (auto i : xrange(num)) {
137
		if (midiOutGetDevCapsA(i, &cap, sizeof(cap)) != MMSYSERR_NOERROR) {
138
			return 0; // atleast MIDI-MAPPER is available...
139
		}
140
		vfnt_midiout[i + 1].devid = i;
141
		w32_midiDevNameConv(vfnt_midiout[i + 1].devname.data(), cap.szPname);
142
		snprintf(vfnt_midiout[i + 1].vfname.data(), MAXPATHLEN + 1, "midi-out-%u", i);
143
		vfnt_midiout_num++;
144
	}
145
	return 0;
146
}
147
148
void w32_midiOutClean()
149
{
150
	vfnt_midiout_num = 0;
151
}
152
153
154
unsigned w32_midiOutGetVFNsNum()
155
{
156
	return vfnt_midiout_num;
157
}
158
159
std::string w32_midiOutGetVFN(unsigned nmb)
160
{
161
	assert(nmb < vfnt_midiout_num);
162
	return vfnt_midiout[nmb].vfname.data();
163
}
164
165
std::string w32_midiOutGetRDN(unsigned nmb)
166
{
167
	assert(nmb < vfnt_midiout_num);
168
	return vfnt_midiout[nmb].devname.data();
169
}
170
171
172
unsigned w32_midiOutOpen(const char *vfn)
173
{
174
	unsigned idx, devid;
175
	if (w32_midiOutFindDev(&idx, &devid,vfn)) {
176
		return unsigned(-1);
177
	}
178
	if (midiOutOpen(std::bit_cast<HMIDIOUT*>(&vfnt_midiout[idx].handle), devid, 0, 0 ,0) != MMSYSERR_NOERROR) {
179
		return unsigned(-1);
180
	}
181
	return idx;
182
}
183
184
int w32_midiOutClose(unsigned idx)
185
{
186
	midiOutReset(reinterpret_cast<HMIDIOUT>(vfnt_midiout[idx].handle));
187
	if (midiOutClose(reinterpret_cast<HMIDIOUT>(vfnt_midiout[idx].handle)) == MMSYSERR_NOERROR) {
188
		return 0;
189
	} else {
190
		return -1;
191
	}
192
}
193
194
195
static int w32_midiOutFlushExclusiveMsg(unsigned idx)
196
{
197
	int i;
198
	//buf_out[idx].header.lpData = buf_out[idx].longmes;
199
	//buf_out[idx].header.dwBufferLength = buf_out[idx].longmes_cnt;
200
	buf_out[idx].header.dwFlags = 0;
201
	if ((i = midiOutPrepareHeader(reinterpret_cast<HMIDIOUT>(vfnt_midiout[idx].handle), &buf_out[idx].header, sizeof(buf_out[idx].header))) != MMSYSERR_NOERROR) {
202
		throw FatalError("midiOutPrepareHeader() returned ", i);
203
	}
204
	if ((i = midiOutLongMsg(reinterpret_cast<HMIDIOUT>(vfnt_midiout[idx].handle), &buf_out[idx].header, sizeof(buf_out[idx].header))) != MMSYSERR_NOERROR) {
205
		throw FatalError("midiOutLongMsg() returned ", i);
206
	}
207
	// Wait sending in driver.
208
	// This may take long...
209
	while (!(buf_out[idx].header.dwFlags & MHDR_DONE)) {
210
		Sleep(1);
211
	}
212
	// Sending Exclusive done.
213
	if ((i = midiOutUnprepareHeader(reinterpret_cast<HMIDIOUT>(vfnt_midiout[idx].handle), &buf_out[idx].header, sizeof(buf_out[idx].header))) != MMSYSERR_NOERROR) {
214
		throw FatalError("midiOutUnPrepareHeader() returned ", i);
215
	}
216
	return 0;
217
}
218
219
int w32_midiOutMsg(size_t size, const uint8_t* data, unsigned idx)
220
{
221
	if (size == 0) return 0;
222
223
	auto hMidiOut = reinterpret_cast<HMIDIOUT>(vfnt_midiout[idx].handle);
224
	if (data[0] == one_of(0xF0, 0xF7)) { // SysEx
225
		if (size > OPENMSX_W32_MIDI_SYSMES_MAXLEN) {
226
			return -1;
227
		}
228
		auto& buf = buf_out[idx];
229
		// Note: We have to be careful with the const_cast here.
230
		// Even though Windows doesn't write to the buffer, it fails if you don't have
231
		// write access to the respective memory page.
232
		buf.header.lpData = const_cast<LPSTR>(reinterpret_cast<LPCSTR>(data));
233
		buf.header.dwBufferLength = unsigned(size);
234
		w32_midiOutFlushExclusiveMsg(idx);
235
	} else {
236
		DWORD midiMsg = 0x000000;
237
		for (unsigned i = 0; i < size && i < 4; i++) {
238
			midiMsg |= data[i] << (8 * i);
239
		}
240
		midiOutShortMsg(hMidiOut, midiMsg);
241
	}
242
	return 0;
243
}
244
245
246
// MIDI-IN
247
static int w32_midiInFindDev(unsigned *idx, unsigned *dev, const char *vfn)
248
{
249
	for (auto i : xrange(vfnt_midiin_num)) {
250
		if (!strcmp(vfnt_midiin[i].vfname.data(), vfn)) {
251
			*idx = i;
252
			*dev = vfnt_midiin[i].devid;
253
			return 0;
254
		}
255
	}
256
	return -1;
257
}
258
259
260
int w32_midiInInit()
261
{
262
	vfnt_midiin_num = 0;
263
	unsigned num = midiInGetNumDevs();
264
	if (!num) return 0;
265
266
	vfnt_midiin.resize(num + 1);
267
	for (auto i : xrange(num)) {
268
		MIDIINCAPSA cap;
269
		if (midiInGetDevCapsA(i, &cap, sizeof(cap)) != MMSYSERR_NOERROR) {
270
			return 1;
271
		}
272
		vfnt_midiin[i].devid = i;
273
		w32_midiDevNameConv(vfnt_midiin[i].devname.data(), cap.szPname);
274
		snprintf(vfnt_midiin[i].vfname.data(), MAXPATHLEN + 1, "midi-in-%u", i);
275
		vfnt_midiin_num++;
276
	}
277
	return 0;
278
}
279
280
void w32_midiInClean()
281
{
282
	vfnt_midiin_num = 0;
283
}
284
285
286
unsigned w32_midiInGetVFNsNum()
287
{
288
	return vfnt_midiin_num;
289
}
290
291
std::string w32_midiInGetVFN(unsigned nmb)
292
{
293
	assert(nmb < vfnt_midiin_num);
294
	return vfnt_midiin[nmb].vfname.data();
295
}
296
297
std::string w32_midiInGetRDN(unsigned nmb)
298
{
299
	assert(nmb < vfnt_midiin_num);
300
	return vfnt_midiin[nmb].devname.data();
301
}
302
303
unsigned w32_midiInOpen(const char *vfn, DWORD thrdid)
304
{
305
	unsigned idx, devid;
306
	if (w32_midiInFindDev(&idx, &devid, vfn)) {
307
		return unsigned(-1);
308
	}
309
	if (midiInOpen(std::bit_cast<HMIDIIN*>(&vfnt_midiin[idx].handle), devid, thrdid, 0, CALLBACK_THREAD) != MMSYSERR_NOERROR) {
310
		return unsigned(-1);
311
	}
312
	memset(&inhdr, 0, sizeof(inhdr));
313
	inhdr.lpData = inlongmes.data();
314
	inhdr.dwBufferLength = OPENMSX_W32_MIDI_SYSMES_MAXLEN;
315
	if (midiInPrepareHeader(reinterpret_cast<HMIDIIN>(vfnt_midiin[idx].handle),
316
		                &inhdr, sizeof(inhdr)) != MMSYSERR_NOERROR) {
317
		return unsigned(-1);
318
	}
319
	if (midiInAddBuffer(reinterpret_cast<HMIDIIN>(vfnt_midiin[idx].handle), &inhdr, sizeof(inhdr)) != MMSYSERR_NOERROR) {
320
		return unsigned(-1);
321
	}
322
	if (midiInStart(reinterpret_cast<HMIDIIN>(vfnt_midiin[idx].handle)) != MMSYSERR_NOERROR) {
323
		return unsigned(-1);
324
	}
325
	return idx;
326
}
327
328
int w32_midiInClose(unsigned idx)
329
{
330
	midiInStop(reinterpret_cast<HMIDIIN>(vfnt_midiin[idx].handle));
331
	midiInReset(reinterpret_cast<HMIDIIN>(vfnt_midiin[idx].handle));
332
	midiInUnprepareHeader(reinterpret_cast<HMIDIIN>(vfnt_midiin[idx].handle),
333
	                      &inhdr, sizeof(inhdr));
334
	if (midiInClose(reinterpret_cast<HMIDIIN>(vfnt_midiin[idx].handle)) == MMSYSERR_NOERROR) {
335
		return 0;
336
	} else {
337
		return -1;
338
	}
339
}
340
341
} // namespace openmsx
342
343
#endif // _WIN32