1 /*
2  * midi-win32-drv.c - MIDI emulation for win32.
3  *
4  * Written by
5  *  Daniel Kahlin <daniel@kahlin.net>
6  *
7  * This file is part of VICE, the Versatile Commodore Emulator.
8  * See README for copyright notice.
9  *
10  *  This program is free software; you can redistribute it and/or modify
11  *  it under the terms of the GNU General Public License as published by
12  *  the Free Software Foundation; either version 2 of the License, or
13  *  (at your option) any later version.
14  *
15  *  This program is distributed in the hope that it will be useful,
16  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  *  GNU General Public License for more details.
19  *
20  *  You should have received a copy of the GNU General Public License
21  *  along with this program; if not, write to the Free Software
22  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
23  *  02111-1307  USA.
24  *
25  */
26 
27 #undef DEBUG
28 
29 #include "vice.h"
30 
31 #ifdef WIN32_COMPILE
32 
33 #ifdef HAVE_MIDI
34 
35 #include "types.h"
36 
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 #include <windows.h>
41 #include <mmsystem.h>
42 
43 #include "cmdline.h"
44 #include "log.h"
45 #include "mididrv.h"
46 
47 #include "resources.h"
48 
49 #if 0
50 #ifndef DWORD_PTR
51 #define DWORD_PTR unsigned long
52 #endif
53 #endif
54 
55 /* ------------------------------------------------------------------------- */
56 
57 static log_t mididrv_log = LOG_ERR;
58 
59 static HMIDIIN handle_in = 0;
60 static HMIDIOUT handle_out = 0;
61 
62 static void CALLBACK midi_callback(HMIDIIN handle, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2);
63 
64 /* ------------------------------------------------------------------------- */
65 
66 #define OUT_BUF_LEN 3
67 
68 static int out_index = 0;
69 static BYTE out_buf[OUT_BUF_LEN];
70 
71 /* ------------------------------------------------------------------------- */
72 
73 #define IN_BUF_LEN 1024
74 
75 static volatile unsigned int in_wi = 0;
76 static volatile unsigned int in_ri = 0;
77 static BYTE in_buf[IN_BUF_LEN];
78 
79 static int midi_in_dev = 0;
80 static int midi_out_dev = 0;
81 
set_midi_in_dev(int val,void * param)82 static int set_midi_in_dev(int val, void *param)
83 {
84     midi_in_dev = val;
85     return 0;
86 }
87 
set_midi_out_dev(int val,void * param)88 static int set_midi_out_dev(int val, void *param)
89 {
90     midi_out_dev = val;
91     return 0;
92 }
93 
94 static const resource_int_t resources_int[] = {
95     { "MIDIInDev", 0, RES_EVENT_NO, (resource_value_t)0,
96       &midi_in_dev, set_midi_in_dev, NULL },
97     { "MIDIOutDev", 0, RES_EVENT_NO, (resource_value_t)0,
98       &midi_out_dev, set_midi_out_dev, NULL },
99     RESOURCE_INT_LIST_END
100 };
101 
mididrv_resources_init(void)102 int mididrv_resources_init(void)
103 {
104     return resources_register_int(resources_int);
105 }
106 
mididrv_resources_shutdown(void)107 void mididrv_resources_shutdown(void)
108 {
109 }
110 
111 static const cmdline_option_t cmdline_options[] =
112 {
113     { "-midiin", SET_RESOURCE, CMDLINE_ATTRIB_NEED_ARGS,
114       NULL, NULL, "MIDIInDev", NULL,
115       "<number>", "Specify MIDI-In device" },
116     { "-midiout", SET_RESOURCE, CMDLINE_ATTRIB_NEED_ARGS,
117       NULL, NULL, "MIDIOutDev", NULL,
118       "<number>", "Specify MIDI-Out device" },
119     CMDLINE_LIST_END
120 };
121 
mididrv_cmdline_options_init(void)122 int mididrv_cmdline_options_init(void)
123 {
124     return cmdline_register_options(cmdline_options);
125 }
126 
reset_fifo(void)127 static void reset_fifo(void)
128 {
129     in_wi = 0;
130     in_ri = 0;
131 }
132 
write_fifo(BYTE data)133 static int write_fifo(BYTE data)
134 {
135     if (((in_wi - in_ri) % IN_BUF_LEN) == (IN_BUF_LEN - 1)) {
136         return 1;
137     }
138 
139     in_buf[in_wi] = data;
140     in_wi = (in_wi + 1) % IN_BUF_LEN;
141     return 0;
142 }
143 
read_fifo(BYTE * data)144 static int read_fifo(BYTE *data)
145 {
146     if (((in_wi - in_ri) % IN_BUF_LEN) != 0) {
147         *data = in_buf[in_ri];
148         in_ri = (in_ri + 1) % IN_BUF_LEN;
149         return 1;
150     }
151     return 0;
152 }
153 
154 /* ------------------------------------------------------------------------- */
155 
message_len(BYTE msg)156 static int message_len(BYTE msg)
157 {
158     int len = 0;
159 
160     switch (msg & 0xf0) {
161         case 0x80: /* Note Off */
162         case 0x90: /* Note On */
163         case 0xa0: /* Polyphonic Aftertouch */
164         case 0xb0: /* Control Change */
165         case 0xe0: /* Pitch Wheel */
166             len = 3;
167             break;
168         case 0xc0: /* Program Change */
169         case 0xd0: /* Channel Aftertouch */
170             len = 2;
171             break;
172         case 0xf0: /* Special */
173             switch (msg) {
174                 case 0xf0: /* Sysex Start (shouldn't happen here) */
175                 case 0xf7: /* Sysex End (shouldn't happen here) */
176                     len = -1;
177                     break;
178                 case 0xf2: /* Song Pointer */
179                     len = 3;
180                     break;
181                 case 0xf1: /* Quarter Frame */
182                 case 0xf3: /* Song Select */
183                 case 0xf9: /* Measure End */
184                     len = 2;
185                     break;
186                 case 0xf6: /* Tuning Request */
187                 case 0xf8: /* Timing Clock */
188                 case 0xfa: /* Start */
189                 case 0xfb: /* Continue */
190                 case 0xfc: /* Stop */
191                 case 0xfe: /* Active Sensing */
192                 case 0xff: /* Reset */
193                     len = 1;
194                     break;
195                 default:
196                     break;
197             }
198         default: /* running status */
199             len = 2;
200             break;
201     }
202     return len;
203 }
204 
205 /* ------------------------------------------------------------------------- */
206 
mididrv_init(void)207 void mididrv_init(void)
208 {
209     if (mididrv_log == LOG_ERR) {
210         mididrv_log = log_open("MIDIdrv");
211     }
212 }
213 
214 /* opens a MIDI-In device, returns handle */
mididrv_in_open(void)215 int mididrv_in_open(void)
216 {
217     MMRESULT ret;
218 
219     log_message(mididrv_log, "Opening MIDI-In device #%d", midi_in_dev);
220     if (handle_in) {
221         mididrv_in_close();
222     }
223 
224     if (midi_in_dev != -1) {
225         ret = midiInOpen(&handle_in, midi_in_dev, (DWORD_PTR)midi_callback, 0, CALLBACK_FUNCTION);
226         if (ret != MMSYSERR_NOERROR) {
227             log_error(mididrv_log, "Cannot open MIDI-In device #%d!", midi_in_dev);
228             handle_in = 0;
229             return -1;
230         }
231     } else {
232         handle_in = 0;
233         return -1;
234     }
235 
236     /* reset FIFO */
237     reset_fifo();
238 
239     /* can theoretically return MMSYSERR_INVALHANDLE */
240     ret = midiInStart(handle_in);
241 
242     return 0;
243 }
244 
245 /* opens a MIDI-Out device, returns handle */
mididrv_out_open(void)246 int mididrv_out_open(void)
247 {
248     MMRESULT ret;
249 
250     log_message(mididrv_log, "Opening MIDI-Out device #%d", midi_out_dev);
251     if (handle_out) {
252         mididrv_out_close();
253     }
254 
255     if (midi_out_dev != -1) {
256         ret = midiOutOpen(&handle_out, midi_out_dev, 0, 0, CALLBACK_NULL);
257         if (ret != MMSYSERR_NOERROR) {
258             log_error(mididrv_log, "Cannot open MIDI-Out device #%d!", midi_out_dev);
259             handle_out = 0;
260             return -1;
261         }
262     } else {
263         handle_out = 0;
264         return -1;
265     }
266 
267     /* reset buffer */
268     out_index = 0;
269 
270     return 0;
271 }
272 
273 /* closes the MIDI-In device*/
mididrv_in_close(void)274 void mididrv_in_close(void)
275 {
276     MMRESULT ret;
277 #ifdef DEBUG
278     log_message(mididrv_log, "in_close");
279 #endif
280     if (!handle_in) {
281         log_error(mididrv_log, "Attempt to close MIDI-In device that wasn't open!");
282         return;
283     }
284     /* can theoretically return MMSYSERR_INVALHANDLE */
285     ret = midiInReset(handle_in);
286 
287     ret = midiInClose(handle_in);
288     if (ret != MMSYSERR_NOERROR) {
289         log_error(mididrv_log, "Couldn't close MIDI-In device.");
290     }
291     handle_in = 0;
292 }
293 
294 /* closes the MIDI-Out device*/
mididrv_out_close(void)295 void mididrv_out_close(void)
296 {
297     MMRESULT ret;
298 #ifdef DEBUG
299     log_message(mididrv_log, "out_close");
300 #endif
301     if (!handle_out) {
302         log_error(mididrv_log, "Attempt to close MIDI-Out device that wasn't open!");
303         return;
304     }
305 
306     /* can theoretically return MMSYSERR_INVALHANDLE */
307     ret = midiOutReset(handle_out);
308 
309     ret = midiOutClose(handle_out);
310     if (ret != MMSYSERR_NOERROR) {
311         log_error(mididrv_log, "Couldn't close MIDI-Out device.");
312     }
313     handle_out = 0;
314 }
315 
316 /* sends a byte to MIDI-Out */
mididrv_out(uint8_t b)317 void mididrv_out(uint8_t b)
318 {
319     MMRESULT ret;
320     int thres;
321 
322 #ifdef DEBUG
323     log_message(mididrv_log, "out %02x", b);
324 #endif
325 
326     out_buf[out_index] = b;
327     out_index++;
328     if (out_index > OUT_BUF_LEN) {
329         out_index = 0;
330         log_error(mididrv_log, "MIDI-Out overrun.");
331     }
332 
333     thres = message_len(out_buf[0]);
334 
335     /* flush when enough bytes have been queued */
336     if (out_index >= thres) {
337         DWORD data;
338 
339         out_index = 0;
340         data = out_buf[0] | (out_buf[1] << 8) | (out_buf[2] << 16);
341 #ifdef DEBUG
342         log_message(mididrv_log, "flushing out %06x", data);
343 #endif
344         ret = midiOutShortMsg(handle_out, data);
345         if (ret != MMSYSERR_NOERROR) {
346             log_error(mididrv_log, "Failed to output data on MIDI-Out device.");
347         }
348     }
349 
350     return;
351 }
352 
midi_callback(HMIDIIN handle,UINT uMsg,DWORD dwInstance,DWORD dwParam1,DWORD dwParam2)353 static void CALLBACK midi_callback(HMIDIIN handle, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2)
354 {
355     int len;
356     int i;
357     switch (uMsg) {
358         case MIM_DATA:
359 #ifdef DEBUG
360             log_message(mididrv_log, "MIDI callback got %08x", dwParam1);
361 #endif
362             len = message_len((BYTE)(dwParam1 & 0xff));
363             for (i = 0; i < len; i++) {
364                 write_fifo((BYTE)(dwParam1 & 0xff));
365                 dwParam1 >>= 8;
366             }
367             break;
368         case MIM_LONGDATA:
369             break;
370         case MIM_OPEN:
371         case MIM_CLOSE:
372         case MIM_ERROR:
373         case MIM_LONGERROR:
374         case MIM_MOREDATA:
375             break;
376         default:
377             break;
378     }
379 }
380 
381 
382 /* gets a byte from MIDI-In, returns !=0 if byte received, byte in *b. */
mididrv_in(uint8_t * b)383 int mididrv_in(uint8_t *b)
384 {
385     if (!handle_in) {
386         log_error(mididrv_log, "Attempt to read from closed MIDI-In port!");
387         return -1;
388     }
389 
390     if (read_fifo(b)) {
391 #ifdef DEBUG
392         log_message(mididrv_log, "in got %02x", *b);
393 #endif
394         return 1;
395     }
396     return 0;
397 }
398 
399 #endif
400 #endif
401