openMSX
Midi_w32.cc
Go to the documentation of this file.
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#include "MSXException.hh"
31#include "MemBuffer.hh"
32#include "cstdiop.hh"
33#include "one_of.hh"
34#include "xrange.hh"
35
36#include <cstring>
37#include <cstdlib>
38#include <sstream>
39#include <cassert>
40
41#define MAXPATHLEN MAX_PATH
42
43namespace openmsx {
44
45/*
46 * MIDI I/O helper functions for Win32.
47 */
48// MIDI SYSTEM MESSAGES max length is not defined...
49// TODO: support variable length.
50#define OPENMSX_W32_MIDI_SYSMES_MAXLEN 4096
51
52
53struct vfn_midi {
54 unsigned idx;
55 unsigned devid;
56 HMIDI handle;
57 char vfname[MAXPATHLEN + 1];
58 char devname[MAXPNAMELEN];
59};
60
61struct outbuf {
62 MIDIHDR header;
63};
64
65static MemBuffer<vfn_midi> vfnt_midiout;
66static MemBuffer<vfn_midi> vfnt_midiin;
67static unsigned vfnt_midiout_num, vfnt_midiin_num;
68
69static MemBuffer<int> state_out;
70static MemBuffer<outbuf> buf_out;
71
72static MIDIHDR inhdr;
73static char inlongmes[OPENMSX_W32_MIDI_SYSMES_MAXLEN];
74
75
76static void w32_midiDevNameConv(char *dst, char *src)
77{
78 size_t len = strlen(src);
79 size_t i = 0;
80 for (; i < len; ++i) {
81 if ((src[i] < '0') || (src[i] > 'z') ||
82 ((src[i] > '9') && (src[i] < 'A')) ||
83 ((src[i] > 'Z') && (src[i] < 'a'))) {
84 dst[i] = '_';
85 } else {
86 dst[i] = src[i];
87 }
88 }
89 dst[i] = '\0';
90}
91
92
93// MIDI-OUT
94static int w32_midiOutFindDev(unsigned *idx, unsigned *dev, const char *vfn)
95{
96 for (auto i : xrange(vfnt_midiout_num)) {
97 if (!strcmp(vfnt_midiout[i].vfname, vfn)) {
98 *idx = i;
99 *dev = vfnt_midiout[i].devid;
100 return 0;
101 }
102 }
103 return -1;
104}
105
106
107int w32_midiOutInit()
108{
109 vfnt_midiout_num = 0;
110 unsigned num = midiOutGetNumDevs();
111 if (!num) return 0;
112
113 state_out.resize(num + 1);
114 memset(state_out.data(), 0, (num + 1) * sizeof(int));
115
116 buf_out.resize(num + 1);
117 memset(buf_out.data(), 0, (num + 1) * sizeof(outbuf));
118
119 vfnt_midiout.resize(num + 1);
120
121 // MIDI_MAPPER is #define's as ((UINT)-1)
122 UINT OPENMSX_MIDI_MAPPER = static_cast<UINT>(-1);
123 MIDIOUTCAPSA cap;
124 if (midiOutGetDevCapsA(OPENMSX_MIDI_MAPPER, &cap, sizeof(cap)) != MMSYSERR_NOERROR) {
125 return 2;
126 }
127 vfnt_midiout[0].devid = OPENMSX_MIDI_MAPPER;
128 w32_midiDevNameConv(vfnt_midiout[0].devname, cap.szPname);
129 strncpy(vfnt_midiout[0].vfname, "midi-out", MAXPATHLEN + 1);
130 vfnt_midiout_num ++;
131
132 for (auto i : xrange(num)) {
133 if (midiOutGetDevCapsA(i, &cap, sizeof(cap)) != MMSYSERR_NOERROR) {
134 return 0; // atleast MIDI-MAPPER is available...
135 }
136 vfnt_midiout[i + 1].devid = i;
137 w32_midiDevNameConv(vfnt_midiout[i + 1].devname, cap.szPname);
138 snprintf(vfnt_midiout[i + 1].vfname, MAXPATHLEN + 1, "midi-out-%u", i);
139 vfnt_midiout_num++;
140 }
141 return 0;
142}
143
144void w32_midiOutClean()
145{
146 vfnt_midiout_num = 0;
147}
148
149
150unsigned w32_midiOutGetVFNsNum()
151{
152 return vfnt_midiout_num;
153}
154
155std::string w32_midiOutGetVFN(unsigned nmb)
156{
157 assert(nmb < vfnt_midiout_num);
158 return vfnt_midiout[nmb].vfname;
159}
160
161std::string w32_midiOutGetRDN(unsigned nmb)
162{
163 assert(nmb < vfnt_midiout_num);
164 return vfnt_midiout[nmb].devname;
165}
166
167
168unsigned w32_midiOutOpen(const char *vfn)
169{
170 unsigned idx, devid;
171 if (w32_midiOutFindDev(&idx, &devid,vfn)) {
172 return unsigned(-1);
173 }
174 if (midiOutOpen(reinterpret_cast<HMIDIOUT*>(&vfnt_midiout[idx].handle), devid, 0, 0 ,0) != MMSYSERR_NOERROR) {
175 return unsigned(-1);
176 }
177 return idx;
178}
179
180int w32_midiOutClose(unsigned idx)
181{
182 midiOutReset(reinterpret_cast<HMIDIOUT>(vfnt_midiout[idx].handle));
183 if (midiOutClose(reinterpret_cast<HMIDIOUT>(vfnt_midiout[idx].handle)) == MMSYSERR_NOERROR) {
184 return 0;
185 } else {
186 return -1;
187 }
188}
189
190
191static int w32_midiOutFlushExclusiveMsg(unsigned idx)
192{
193 int i;
194 //buf_out[idx].header.lpData = buf_out[idx].longmes;
195 //buf_out[idx].header.dwBufferLength = buf_out[idx].longmes_cnt;
196 buf_out[idx].header.dwFlags = 0;
197 if ((i = midiOutPrepareHeader(reinterpret_cast<HMIDIOUT>(vfnt_midiout[idx].handle), &buf_out[idx].header, sizeof(buf_out[idx].header))) != MMSYSERR_NOERROR) {
198 throw FatalError("midiOutPrepareHeader() returned ", i);
199 }
200 if ((i = midiOutLongMsg(reinterpret_cast<HMIDIOUT>(vfnt_midiout[idx].handle), &buf_out[idx].header, sizeof(buf_out[idx].header))) != MMSYSERR_NOERROR) {
201 throw FatalError("midiOutLongMsg() returned ", i);
202 }
203 // Wait sending in driver.
204 // This may take long...
205 while (!(buf_out[idx].header.dwFlags & MHDR_DONE)) {
206 Sleep(1);
207 }
208 // Sending Exclusive done.
209 if ((i = midiOutUnprepareHeader(reinterpret_cast<HMIDIOUT>(vfnt_midiout[idx].handle), &buf_out[idx].header, sizeof(buf_out[idx].header))) != MMSYSERR_NOERROR) {
210 throw FatalError("midiOutUnPrepareHeader() returned ", i);
211 }
212 return 0;
213}
214
215int w32_midiOutMsg(size_t size, const uint8_t* data, unsigned idx)
216{
217 if (size == 0) return 0;
218
219 HMIDIOUT hMidiOut = reinterpret_cast<HMIDIOUT>(vfnt_midiout[idx].handle);
220 if (data[0] == one_of(0xF0, 0xF7)) { // SysEx
221 if (size > OPENMSX_W32_MIDI_SYSMES_MAXLEN) {
222 return -1;
223 }
224 auto& buf = buf_out[idx];
225 // Note: We have to be careful with the const_cast here.
226 // Even though Windows doesn't write to the buffer, it fails if you don't have
227 // write access to the respective memory page.
228 buf.header.lpData = const_cast<LPSTR>(reinterpret_cast<LPCSTR>(data));
229 buf.header.dwBufferLength = unsigned(size);
230 w32_midiOutFlushExclusiveMsg(idx);
231 } else {
232 DWORD midiMsg = 0x000000;
233 for (unsigned i = 0; i < size && i < 4; i++) {
234 midiMsg |= data[i] << (8 * i);
235 }
236 midiOutShortMsg(hMidiOut, midiMsg);
237 }
238 return 0;
239}
240
241
242// MIDI-IN
243static int w32_midiInFindDev(unsigned *idx, unsigned *dev, const char *vfn)
244{
245 for (auto i : xrange(vfnt_midiin_num)) {
246 if (!strcmp(vfnt_midiin[i].vfname, vfn)) {
247 *idx = i;
248 *dev = vfnt_midiin[i].devid;
249 return 0;
250 }
251 }
252 return -1;
253}
254
255
256int w32_midiInInit()
257{
258 vfnt_midiin_num = 0;
259 unsigned num = midiInGetNumDevs();
260 if (!num) return 0;
261
262 vfnt_midiin.resize(num + 1);
263 for (auto i : xrange(num)) {
264 MIDIINCAPSA cap;
265 if (midiInGetDevCapsA(i, &cap, sizeof(cap)) != MMSYSERR_NOERROR) {
266 return 1;
267 }
268 vfnt_midiin[i].devid = i;
269 w32_midiDevNameConv(vfnt_midiin[i].devname, cap.szPname);
270 snprintf(vfnt_midiin[i].vfname, MAXPATHLEN + 1, "midi-in-%u", i);
271 vfnt_midiin_num++;
272 }
273 return 0;
274}
275
276void w32_midiInClean()
277{
278 vfnt_midiin_num = 0;
279}
280
281
282unsigned w32_midiInGetVFNsNum()
283{
284 return vfnt_midiin_num;
285}
286
287std::string w32_midiInGetVFN(unsigned nmb)
288{
289 assert(nmb < vfnt_midiin_num);
290 return vfnt_midiin[nmb].vfname;
291}
292
293std::string w32_midiInGetRDN(unsigned nmb)
294{
295 assert(nmb < vfnt_midiin_num);
296 return vfnt_midiin[nmb].devname;
297}
298
299unsigned w32_midiInOpen(const char *vfn, DWORD thrdid)
300{
301 unsigned idx, devid;
302 if (w32_midiInFindDev(&idx, &devid, vfn)) {
303 return unsigned(-1);
304 }
305 if (midiInOpen(reinterpret_cast<HMIDIIN*>(&vfnt_midiin[idx].handle), devid, thrdid, 0, CALLBACK_THREAD) != MMSYSERR_NOERROR) {
306 return unsigned(-1);
307 }
308 memset(&inhdr, 0, sizeof(inhdr));
309 inhdr.lpData = inlongmes;
310 inhdr.dwBufferLength = OPENMSX_W32_MIDI_SYSMES_MAXLEN;
311 if (midiInPrepareHeader(reinterpret_cast<HMIDIIN>(vfnt_midiin[idx].handle),
312 static_cast<LPMIDIHDR>(&inhdr), sizeof(inhdr)) != MMSYSERR_NOERROR) {
313 return unsigned(-1);
314 }
315 if (midiInAddBuffer(reinterpret_cast<HMIDIIN>(vfnt_midiin[idx].handle), static_cast<LPMIDIHDR>(&inhdr), sizeof(inhdr)) != MMSYSERR_NOERROR) {
316 return unsigned(-1);
317 }
318 if (midiInStart(reinterpret_cast<HMIDIIN>(vfnt_midiin[idx].handle)) != MMSYSERR_NOERROR) {
319 return unsigned(-1);
320 }
321 return idx;
322}
323
324int w32_midiInClose(unsigned idx)
325{
326 midiInStop(reinterpret_cast<HMIDIIN>(vfnt_midiin[idx].handle));
327 midiInReset(reinterpret_cast<HMIDIIN>(vfnt_midiin[idx].handle));
328 midiInUnprepareHeader(reinterpret_cast<HMIDIIN>(vfnt_midiin[idx].handle),
329 static_cast<LPMIDIHDR>(&inhdr), sizeof(inhdr));
330 if (midiInClose(reinterpret_cast<HMIDIIN>(vfnt_midiin[idx].handle)) == MMSYSERR_NOERROR) {
331 return 0;
332 } else {
333 return -1;
334 }
335}
336
337} // namespace openmsx
338
339#endif // _WIN32
#define MAXPATHLEN
Definition: one_of.hh:7
This file implemented 3 utility functions:
Definition: Autofire.cc:9
size_t size(std::string_view utf8)
constexpr auto xrange(T e)
Definition: xrange.hh:132