aboutsummaryrefslogtreecommitdiff
path: root/portmidi/pm_win/pmwinmm.c
diff options
context:
space:
mode:
authorMitja Felicijan <mitja.felicijan@gmail.com>2024-10-07 19:30:56 +0200
committerMitja Felicijan <mitja.felicijan@gmail.com>2024-10-07 19:30:56 +0200
commit40a899bd6ee536eae093337bf2d0dcc8db4e46f1 (patch)
tree485ace3e6fd28b91f394efd277732651e10824d8 /portmidi/pm_win/pmwinmm.c
parent6fc4bddfdf8e056469f316c1a0fe488efbb4253a (diff)
downloadttdaw-40a899bd6ee536eae093337bf2d0dcc8db4e46f1.tar.gz
Moved example code examples folder
Diffstat (limited to 'portmidi/pm_win/pmwinmm.c')
-rwxr-xr-xportmidi/pm_win/pmwinmm.c1196
1 files changed, 0 insertions, 1196 deletions
diff --git a/portmidi/pm_win/pmwinmm.c b/portmidi/pm_win/pmwinmm.c
deleted file mode 100755
index 6f4b6f3..0000000
--- a/portmidi/pm_win/pmwinmm.c
+++ /dev/null
@@ -1,1196 +0,0 @@
1/* pmwinmm.c -- system specific definitions */
2
3#ifndef _WIN32_WINNT
4 /* without this define, InitializeCriticalSectionAndSpinCount is
5 * undefined. This version level means "Windows 2000 and higher"
6 */
7 #define _WIN32_WINNT 0x0500
8#endif
9
10#define UNICODE 1
11#include <wchar.h>
12#include "windows.h"
13#include "mmsystem.h"
14#include "portmidi.h"
15#include "pmutil.h"
16#include "pminternal.h"
17#include "pmwinmm.h"
18#include <string.h>
19#include "porttime.h"
20#ifndef UNICODE
21#error Expected UNICODE to be defined
22#endif
23
24
25/* asserts used to verify portMidi code logic is sound; later may want
26 something more graceful */
27#include <assert.h>
28#ifdef MMDEBUG
29/* this printf stuff really important for debugging client app w/host errors.
30 probably want to do something else besides read/write from/to console
31 for portability, however */
32#define STRING_MAX 80
33#include "stdio.h"
34#endif
35
36#define streql(x, y) (strcmp(x, y) == 0)
37
38#define MIDI_SYSEX 0xf0
39#define MIDI_EOX 0xf7
40
41/* callback routines */
42static void CALLBACK winmm_in_callback(HMIDIIN hMidiIn,
43 UINT wMsg, DWORD_PTR dwInstance,
44 DWORD_PTR dwParam1, DWORD_PTR dwParam2);
45static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg,
46 DWORD_PTR dwInstance,
47 DWORD_PTR dwParam1,
48 DWORD_PTR dwParam2);
49
50extern pm_fns_node pm_winmm_in_dictionary;
51extern pm_fns_node pm_winmm_out_dictionary;
52
53static void winmm_out_delete(PmInternal *midi); /* forward reference */
54
55/*
56A note about buffers: WinMM seems to hold onto buffers longer than
57one would expect, e.g. when I tried using 2 small buffers to send
58long sysex messages, at some point WinMM held both buffers. This problem
59was fixed by making buffers bigger. Therefore, it seems that there should
60be enough buffer space to hold a whole sysex message.
61
62The bufferSize passed into Pm_OpenInput (passed into here as buffer_len)
63will be used to estimate the largest sysex message (= buffer_len * 4 bytes).
64Call that the max_sysex_len = buffer_len * 4.
65
66For simple midi output (latency == 0), allocate 3 buffers, each with half
67the size of max_sysex_len, but each at least 256 bytes.
68
69For stream output, there will already be enough space in very short
70buffers, so use them, but make sure there are at least 16.
71
72For input, use many small buffers rather than 2 large ones so that when
73there are short sysex messages arriving frequently (as in control surfaces)
74there will be more free buffers to fill. Use max_sysex_len / 64 buffers,
75but at least 16, of size 64 bytes each.
76
77The following constants help to represent these design parameters:
78*/
79#define NUM_SIMPLE_SYSEX_BUFFERS 3
80#define MIN_SIMPLE_SYSEX_LEN 256
81
82#define MIN_STREAM_BUFFERS 16
83#define STREAM_BUFFER_LEN 24
84
85#define INPUT_SYSEX_LEN 64
86#define MIN_INPUT_BUFFERS 16
87
88/* if we run out of space for output (assume this is due to a sysex msg,
89 expand by up to NUM_EXPANSION_BUFFERS in increments of EXPANSION_BUFFER_LEN
90 */
91#define NUM_EXPANSION_BUFFERS 128
92#define EXPANSION_BUFFER_LEN 1024
93
94/* A sysex buffer has 3 DWORDS as a header plus the actual message size */
95#define MIDIHDR_SYSEX_BUFFER_LENGTH(x) ((x) + sizeof(long)*3)
96/* A MIDIHDR with a sysex message is the buffer length plus the header size */
97#define MIDIHDR_SYSEX_SIZE(x) (MIDIHDR_SYSEX_BUFFER_LENGTH(x) + sizeof(MIDIHDR))
98
99/*
100==============================================================================
101win32 mmedia system specific structure passed to midi callbacks
102==============================================================================
103*/
104
105/* global winmm device info */
106MIDIINCAPS *midi_in_caps = NULL;
107MIDIINCAPS midi_in_mapper_caps;
108UINT midi_num_inputs = 0;
109MIDIOUTCAPS *midi_out_caps = NULL;
110MIDIOUTCAPS midi_out_mapper_caps;
111UINT midi_num_outputs = 0;
112
113/* per device info */
114typedef struct winmm_info_struct {
115 union {
116 HMIDISTRM stream; /* windows handle for stream */
117 HMIDIOUT out; /* windows handle for out calls */
118 HMIDIIN in; /* windows handle for in calls */
119 } handle;
120
121 /* midi output messages are sent in these buffers, which are allocated
122 * in a round-robin fashion, using next_buffer as an index
123 */
124 LPMIDIHDR *buffers; /* pool of buffers for midi in or out data */
125 int max_buffers; /* length of buffers array */
126 int buffers_expanded; /* buffers array expanded for extra msgs? */
127 int num_buffers; /* how many buffers allocated in buffers array */
128 int next_buffer; /* index of next buffer to send */
129 HANDLE buffer_signal; /* used to wait for buffer to become free */
130 unsigned long last_time; /* last output time */
131 int first_message; /* flag: treat first message differently */
132 int sysex_mode; /* middle of sending sysex */
133 unsigned long sysex_word; /* accumulate data when receiving sysex */
134 unsigned int sysex_byte_count; /* count how many received */
135 LPMIDIHDR hdr; /* the message accumulating sysex to send */
136 unsigned long sync_time; /* when did we last determine delta? */
137 long delta; /* difference between stream time and
138 real time */
139 CRITICAL_SECTION lock; /* prevents reentrant callbacks (input only) */
140} winmm_info_node, *winmm_info_type;
141
142
143/*
144=============================================================================
145general MIDI device queries
146=============================================================================
147*/
148
149/* add a device after converting device (product) name to UTF-8 */
150static void pm_add_device_w(char *api, WCHAR *device_name, int is_input,
151 int is_virtual, void *descriptor, pm_fns_type dictionary)
152{
153 char utf8name[4 * MAXPNAMELEN];
154 WideCharToMultiByte(CP_UTF8, 0, device_name, -1,
155 utf8name, 4 * MAXPNAMELEN - 1, NULL, NULL);
156 /* ignore errors here -- if pm_descriptor_max is exceeded,
157 some devices will not be accessible. */
158 pm_add_device(api, utf8name, is_input, is_virtual, descriptor, dictionary);
159}
160
161
162static void pm_winmm_general_inputs()
163{
164 UINT i;
165 WORD wRtn;
166 midi_num_inputs = midiInGetNumDevs();
167 midi_in_caps = (MIDIINCAPS *) pm_alloc(sizeof(MIDIINCAPS) *
168 midi_num_inputs);
169 if (midi_in_caps == NULL) {
170 /* if you can't open a particular system-level midi interface
171 * (such as winmm), we just consider that system or API to be
172 * unavailable and move on without reporting an error.
173 */
174 return;
175 }
176
177 for (i = 0; i < midi_num_inputs; i++) {
178 wRtn = midiInGetDevCaps(i, (LPMIDIINCAPS) & midi_in_caps[i],
179 sizeof(MIDIINCAPS));
180 if (wRtn == MMSYSERR_NOERROR) {
181 pm_add_device_w("MMSystem", midi_in_caps[i].szPname, TRUE, FALSE,
182 (void *) (intptr_t) i, &pm_winmm_in_dictionary);
183 }
184 }
185}
186
187
188static void pm_winmm_mapper_input()
189{
190 WORD wRtn;
191 /* Note: if MIDIMAPPER opened as input (documentation implies you
192 can, but current system fails to retrieve input mapper
193 capabilities) then you still should retrieve some form of
194 setup info. */
195 wRtn = midiInGetDevCaps((UINT) MIDIMAPPER,
196 (LPMIDIINCAPS) & midi_in_mapper_caps,
197 sizeof(MIDIINCAPS));
198 if (wRtn == MMSYSERR_NOERROR) {
199 pm_add_device_w("MMSystem", midi_in_mapper_caps.szPname, TRUE, FALSE,
200 (void *) (intptr_t) MIDIMAPPER,
201 &pm_winmm_in_dictionary);
202 }
203}
204
205
206static void pm_winmm_general_outputs()
207{
208 UINT i;
209 DWORD wRtn;
210 midi_num_outputs = midiOutGetNumDevs();
211 midi_out_caps = pm_alloc(sizeof(MIDIOUTCAPS) * midi_num_outputs);
212
213 if (midi_out_caps == NULL) {
214 /* no error is reported -- see pm_winmm_general_inputs */
215 return ;
216 }
217
218 for (i = 0; i < midi_num_outputs; i++) {
219 wRtn = midiOutGetDevCaps(i, (LPMIDIOUTCAPS) & midi_out_caps[i],
220 sizeof(MIDIOUTCAPS));
221 if (wRtn == MMSYSERR_NOERROR) {
222 pm_add_device_w("MMSystem", midi_out_caps[i].szPname, FALSE, FALSE,
223 (void *) (intptr_t) i, &pm_winmm_out_dictionary);
224 }
225 }
226}
227
228
229static void pm_winmm_mapper_output()
230{
231 WORD wRtn;
232 /* Note: if MIDIMAPPER opened as output (pseudo MIDI device
233 maps device independent messages into device dependant ones,
234 via NT midimapper program) you still should get some setup info */
235 wRtn = midiOutGetDevCaps((UINT) MIDIMAPPER, (LPMIDIOUTCAPS)
236 & midi_out_mapper_caps, sizeof(MIDIOUTCAPS));
237 if (wRtn == MMSYSERR_NOERROR) {
238 pm_add_device_w("MMSystem", midi_out_mapper_caps.szPname, FALSE, FALSE,
239 (void *) (intptr_t) MIDIMAPPER,
240 &pm_winmm_out_dictionary);
241 }
242}
243
244
245/*
246============================================================================
247host error handling
248============================================================================
249*/
250
251static unsigned int winmm_check_host_error(PmInternal *midi)
252{
253 return FALSE;
254}
255
256
257/*
258=============================================================================
259buffer handling
260=============================================================================
261*/
262static MIDIHDR *allocate_buffer(long data_size)
263{
264 LPMIDIHDR hdr = (LPMIDIHDR) pm_alloc(MIDIHDR_SYSEX_SIZE(data_size));
265 MIDIEVENT *evt;
266 if (!hdr) return NULL;
267 evt = (MIDIEVENT *) (hdr + 1); /* place MIDIEVENT after header */
268 hdr->lpData = (LPSTR) evt;
269 hdr->dwBufferLength = MIDIHDR_SYSEX_BUFFER_LENGTH(data_size);
270 hdr->dwBytesRecorded = 0;
271 hdr->dwFlags = 0;
272 hdr->dwUser = hdr->dwBufferLength;
273 return hdr;
274}
275
276
277static PmError allocate_buffers(winmm_info_type info, long data_size,
278 long count)
279{
280 int i;
281 /* buffers is an array of count pointers to MIDIHDR/MIDIEVENT struct */
282 info->num_buffers = 0; /* in case no memory can be allocated */
283 info->buffers = (LPMIDIHDR *) pm_alloc(sizeof(LPMIDIHDR) * count);
284 if (!info->buffers) return pmInsufficientMemory;
285 info->max_buffers = count;
286 for (i = 0; i < count; i++) {
287 LPMIDIHDR hdr = allocate_buffer(data_size);
288 if (!hdr) { /* free everything allocated so far and return */
289 for (i = i - 1; i >= 0; i--) pm_free(info->buffers[i]);
290 pm_free(info->buffers);
291 info->max_buffers = 0;
292 return pmInsufficientMemory;
293 }
294 info->buffers[i] = hdr; /* this may be NULL if allocation fails */
295 }
296 info->num_buffers = count;
297 return pmNoError;
298}
299
300
301static LPMIDIHDR get_free_output_buffer(PmInternal *midi)
302{
303 LPMIDIHDR r = NULL;
304 winmm_info_type info = (winmm_info_type) midi->api_info;
305 while (TRUE) {
306 int i;
307 for (i = 0; i < info->num_buffers; i++) {
308 /* cycle through buffers, modulo info->num_buffers */
309 info->next_buffer++;
310 if (info->next_buffer >= info->num_buffers) info->next_buffer = 0;
311 r = info->buffers[info->next_buffer];
312 if ((r->dwFlags & MHDR_PREPARED) == 0) goto found_buffer;
313 }
314 /* after scanning every buffer and not finding anything, block */
315 if (WaitForSingleObject(info->buffer_signal, 1000) == WAIT_TIMEOUT) {
316#ifdef MMDEBUG
317 printf("PortMidi warning: get_free_output_buffer() "
318 "wait timed out after 1000ms\n");
319#endif
320 /* if we're trying to send a sysex message, maybe the
321 * message is too big and we need more message buffers.
322 * Expand the buffer pool by 128KB using 1024-byte buffers.
323 */
324 /* first, expand the buffers array if necessary */
325 if (!info->buffers_expanded) {
326 LPMIDIHDR *new_buffers = (LPMIDIHDR *) pm_alloc(
327 (info->num_buffers + NUM_EXPANSION_BUFFERS) *
328 sizeof(LPMIDIHDR));
329 /* if no memory, we could return a no-memory error, but user
330 * probably will be unprepared to deal with it. Maybe the
331 * MIDI driver is temporarily hung so we should just wait.
332 * I don't know the right answer, but waiting is easier.
333 */
334 if (!new_buffers) continue;
335 /* copy buffers to new_buffers and replace buffers */
336 memcpy(new_buffers, info->buffers,
337 info->num_buffers * sizeof(LPMIDIHDR));
338 pm_free(info->buffers);
339 info->buffers = new_buffers;
340 info->max_buffers = info->num_buffers + NUM_EXPANSION_BUFFERS;
341 info->buffers_expanded = TRUE;
342 }
343 /* next, add one buffer and return it */
344 if (info->num_buffers < info->max_buffers) {
345 r = allocate_buffer(EXPANSION_BUFFER_LEN);
346 /* again, if there's no memory, we may not really be
347 * dead -- maybe the system is temporarily hung and
348 * we can just wait longer for a message buffer */
349 if (!r) continue;
350 info->buffers[info->num_buffers++] = r;
351 goto found_buffer; /* break out of 2 loops */
352 }
353 /* else, we've allocated all NUM_EXPANSION_BUFFERS buffers,
354 * and we have no free buffers to send. We'll just keep
355 * polling to see if any buffers show up.
356 */
357 }
358 }
359found_buffer:
360 r->dwBytesRecorded = 0;
361 /* actual buffer length is saved in dwUser field */
362 r->dwBufferLength = (DWORD) r->dwUser;
363 return r;
364}
365
366/*
367============================================================================
368begin midi input implementation
369============================================================================
370*/
371
372
373static unsigned int allocate_input_buffer(HMIDIIN h, long buffer_len)
374{
375 LPMIDIHDR hdr = allocate_buffer(buffer_len);
376 if (!hdr) return pmInsufficientMemory;
377 /* note: pm_hosterror is normally a boolean, but here, we store Win
378 * error code. The caller must test the value for nonzero, set
379 * pm_hosterror_text, and then set pm_hosterror to TRUE */
380 pm_hosterror = midiInPrepareHeader(h, hdr, sizeof(MIDIHDR));
381 if (pm_hosterror) {
382 pm_free(hdr);
383 return pm_hosterror;
384 }
385 pm_hosterror = midiInAddBuffer(h, hdr, sizeof(MIDIHDR));
386 return pm_hosterror;
387}
388
389
390static winmm_info_type winmm_info_create()
391{
392 winmm_info_type info = (winmm_info_type) pm_alloc(sizeof(winmm_info_node));
393 info->handle.in = NULL;
394 info->handle.out = NULL;
395 info->buffers = NULL; /* not used for input */
396 info->num_buffers = 0; /* not used for input */
397 info->max_buffers = 0; /* not used for input */
398 info->buffers_expanded = FALSE; /* not used for input */
399 info->next_buffer = 0; /* not used for input */
400 info->buffer_signal = 0; /* not used for input */
401 info->last_time = 0;
402 info->first_message = TRUE; /* not used for input */
403 info->sysex_mode = FALSE;
404 info->sysex_word = 0;
405 info->sysex_byte_count = 0;
406 info->hdr = NULL; /* not used for input */
407 info->sync_time = 0;
408 info->delta = 0;
409 return info;
410}
411
412
413static void report_hosterror(LPWCH error_msg)
414{
415 WideCharToMultiByte(CP_UTF8, 0, error_msg, -1, pm_hosterror_text,
416 sizeof(pm_hosterror_text), NULL, NULL);
417 if (pm_hosterror == MMSYSERR_NOMEM) {
418 /* add explanation to Window's confusing error message */
419 /* if there's room: */
420 if (PM_HOST_ERROR_MSG_LEN - strlen(pm_hosterror_text) > 60) {
421 strcat_s(pm_hosterror_text, PM_HOST_ERROR_MSG_LEN,
422 " Probably this MIDI device is open "
423 "in another application.");
424 }
425 }
426 pm_hosterror = TRUE;
427}
428
429
430static void report_hosterror_in()
431{
432 WCHAR error_msg[PM_HOST_ERROR_MSG_LEN];
433 int err = midiInGetErrorText(pm_hosterror, error_msg,
434 PM_HOST_ERROR_MSG_LEN);
435 assert(err == MMSYSERR_NOERROR);
436 report_hosterror(error_msg);
437}
438
439
440static void report_hosterror_out()
441{
442 WCHAR error_msg[PM_HOST_ERROR_MSG_LEN];
443 int err = midiOutGetErrorText(pm_hosterror, error_msg,
444 PM_HOST_ERROR_MSG_LEN);
445 assert(err == MMSYSERR_NOERROR);
446 report_hosterror(error_msg);
447}
448
449
450static PmError winmm_in_open(PmInternal *midi, void *driverInfo)
451{
452 DWORD dwDevice;
453 int i = midi->device_id;
454 int max_sysex_len = midi->buffer_len * 4;
455 int num_input_buffers = max_sysex_len / INPUT_SYSEX_LEN;
456 winmm_info_type info;
457
458 dwDevice = (DWORD) (intptr_t) pm_descriptors[i].descriptor;
459
460 /* create system dependent device data */
461 info = winmm_info_create();
462 midi->api_info = info;
463 if (!info) goto no_memory;
464 /* 4000 is based on Windows documentation -- that's the value used
465 in the memory manager. It's small enough that it should not
466 hurt performance even if it's not optimal.
467 */
468 InitializeCriticalSectionAndSpinCount(&info->lock, 4000);
469 /* open device */
470 pm_hosterror = midiInOpen(
471 &(info->handle.in), /* input device handle */
472 dwDevice, /* device ID */
473 (DWORD_PTR) winmm_in_callback, /* callback address */
474 (DWORD_PTR) midi, /* callback instance data */
475 CALLBACK_FUNCTION); /* callback is a procedure */
476 if (pm_hosterror) goto free_descriptor;
477
478 if (num_input_buffers < MIN_INPUT_BUFFERS)
479 num_input_buffers = MIN_INPUT_BUFFERS;
480 for (i = 0; i < num_input_buffers; i++) {
481 if (allocate_input_buffer(info->handle.in, INPUT_SYSEX_LEN)) {
482 /* either pm_hosterror was set, or the proper return code
483 is pmInsufficientMemory */
484 goto close_device;
485 }
486 }
487 /* start device */
488 pm_hosterror = midiInStart(info->handle.in);
489 if (!pm_hosterror) {
490 return pmNoError;
491 }
492
493 /* undo steps leading up to the detected error */
494
495 /* ignore return code (we already have an error to report) */
496 midiInReset(info->handle.in);
497close_device:
498 midiInClose(info->handle.in); /* ignore return code */
499free_descriptor:
500 midi->api_info = NULL;
501 pm_free(info);
502no_memory:
503 if (pm_hosterror) {
504 report_hosterror_in();
505 return pmHostError;
506 }
507 /* if !pm_hosterror, then the error must be pmInsufficientMemory */
508 return pmInsufficientMemory;
509 /* note: if we return an error code, the device will be
510 closed and memory will be freed. It's up to the caller
511 to free the parameter midi */
512}
513
514
515/* winmm_in_close -- close an open midi input device */
516/*
517 * assume midi is non-null (checked by caller)
518 */
519static PmError winmm_in_close(PmInternal *midi)
520{
521 winmm_info_type info = (winmm_info_type) midi->api_info;
522 if (!info) return pmBadPtr;
523 /* device to close */
524 if ((pm_hosterror = midiInStop(info->handle.in))) {
525 midiInReset(info->handle.in); /* try to reset and close port */
526 midiInClose(info->handle.in);
527 } else if ((pm_hosterror = midiInReset(info->handle.in))) {
528 midiInClose(info->handle.in); /* best effort to close midi port */
529 } else {
530 pm_hosterror = midiInClose(info->handle.in);
531 }
532 midi->api_info = NULL;
533 DeleteCriticalSection(&info->lock);
534 pm_free(info); /* delete */
535 if (pm_hosterror) {
536 report_hosterror_in();
537 return pmHostError;
538 }
539 return pmNoError;
540}
541
542
543/* Callback function executed via midiInput SW interrupt (via midiInOpen). */
544static void FAR PASCAL winmm_in_callback(
545 HMIDIIN hMidiIn, /* midiInput device Handle */
546 UINT wMsg, /* midi msg */
547 DWORD_PTR dwInstance, /* application data */
548 DWORD_PTR dwParam1, /* MIDI data */
549 DWORD_PTR dwParam2) /* device timestamp (wrt most recent midiInStart) */
550{
551 PmInternal *midi = (PmInternal *) dwInstance;
552 winmm_info_type info = (winmm_info_type) midi->api_info;
553
554 /* NOTE: we do not just EnterCriticalSection() here because an
555 * MIM_CLOSE message arrives when the port is closed, but then
556 * the info->lock has been destroyed.
557 */
558
559 switch (wMsg) {
560 case MIM_DATA: {
561 /* if this callback is reentered with data, we're in trouble.
562 * It's hard to imagine that Microsoft would allow callbacks
563 * to be reentrant -- isn't the model that this is like a
564 * hardware interrupt? -- but I've seen reentrant behavior
565 * using a debugger, so it happens.
566 */
567 EnterCriticalSection(&info->lock);
568
569 /* dwParam1 is MIDI data received, packed into DWORD w/ 1st byte of
570 message LOB;
571 dwParam2 is time message received by input device driver, specified
572 in [ms] from when midiInStart called.
573 each message is expanded to include the status byte */
574
575 if ((dwParam1 & 0x80) == 0) {
576 /* not a status byte -- ignore it. This happened running the
577 sysex.c test under Win2K with MidiMan USB 1x1 interface,
578 but I can't reproduce it. -RBD
579 */
580 /* printf("non-status byte found\n"); */
581 } else { /* data to process */
582 PmEvent event;
583 if (midi->time_proc)
584 dwParam2 = (*midi->time_proc)(midi->time_info);
585 event.timestamp = (PmTimestamp)dwParam2;
586 event.message = (PmMessage)dwParam1;
587 pm_read_short(midi, &event);
588 }
589 LeaveCriticalSection(&info->lock);
590 break;
591 }
592 case MIM_LONGDATA: {
593 MIDIHDR *lpMidiHdr = (MIDIHDR *) dwParam1;
594 unsigned char *data = (unsigned char *) lpMidiHdr->lpData;
595 unsigned int processed = 0;
596 int remaining = lpMidiHdr->dwBytesRecorded;
597
598 EnterCriticalSection(&info->lock);
599 /* printf("midi_in_callback -- lpMidiHdr %x, %d bytes, %2x...\n",
600 lpMidiHdr, lpMidiHdr->dwBytesRecorded, *data); */
601 if (midi->time_proc)
602 dwParam2 = (*midi->time_proc)(midi->time_info);
603 /* can there be more than one message in one buffer? */
604 /* assume yes and iterate through them */
605 pm_read_bytes(midi, data + processed, remaining, (PmTimestamp)dwParam2);
606
607 /* when a device is closed, the pending MIM_LONGDATA buffers are
608 returned to this callback with dwBytesRecorded == 0. In this
609 case, we do not want to send them back to the interface (if
610 we do, the interface will not close, and Windows OS may hang). */
611 if (lpMidiHdr->dwBytesRecorded > 0) {
612 MMRESULT rslt;
613 lpMidiHdr->dwBytesRecorded = 0;
614 lpMidiHdr->dwFlags = 0;
615
616 /* note: no error checking -- can this actually fail? */
617 rslt = midiInPrepareHeader(hMidiIn, lpMidiHdr, sizeof(MIDIHDR));
618 assert(rslt == MMSYSERR_NOERROR);
619 /* note: I don't think this can fail except possibly for
620 * MMSYSERR_NOMEM, but the pain of reporting this
621 * unlikely but probably catastrophic error does not seem
622 * worth it.
623 */
624 rslt = midiInAddBuffer(hMidiIn, lpMidiHdr, sizeof(MIDIHDR));
625 assert(rslt == MMSYSERR_NOERROR);
626 LeaveCriticalSection(&info->lock);
627 } else {
628 midiInUnprepareHeader(hMidiIn,lpMidiHdr,sizeof(MIDIHDR));
629 LeaveCriticalSection(&info->lock);
630 pm_free(lpMidiHdr);
631 }
632 break;
633 }
634 case MIM_OPEN:
635 break;
636 case MIM_CLOSE:
637 break;
638 case MIM_ERROR:
639 /* printf("MIM_ERROR\n"); */
640 break;
641 case MIM_LONGERROR:
642 /* printf("MIM_LONGERROR\n"); */
643 break;
644 default:
645 break;
646 }
647}
648
649/*
650===========================================================================
651begin midi output implementation
652===========================================================================
653*/
654
655/* begin helper routines used by midiOutStream interface */
656
657/* add_to_buffer -- adds timestamped short msg to buffer, returns fullp */
658static int add_to_buffer(winmm_info_type m, LPMIDIHDR hdr,
659 unsigned long delta, unsigned long msg)
660{
661 unsigned long *ptr = (unsigned long *)
662 (hdr->lpData + hdr->dwBytesRecorded);
663 *ptr++ = delta; /* dwDeltaTime */
664 *ptr++ = 0; /* dwStream */
665 *ptr++ = msg; /* dwEvent */
666 hdr->dwBytesRecorded += 3 * sizeof(long);
667 /* if the addition of three more words (a message) would extend beyond
668 the buffer length, then return TRUE (full)
669 */
670 return hdr->dwBytesRecorded + 3 * sizeof(long) > hdr->dwBufferLength;
671}
672
673
674static PmTimestamp pm_time_get(winmm_info_type info)
675{
676 MMTIME mmtime;
677 MMRESULT wRtn;
678 mmtime.wType = TIME_TICKS;
679 mmtime.u.ticks = 0;
680 wRtn = midiStreamPosition(info->handle.stream, &mmtime, sizeof(mmtime));
681 assert(wRtn == MMSYSERR_NOERROR);
682 return mmtime.u.ticks;
683}
684
685
686/* end helper routines used by midiOutStream interface */
687
688
689static PmError winmm_out_open(PmInternal *midi, void *driverInfo)
690{
691 DWORD dwDevice;
692 int i = midi->device_id;
693 winmm_info_type info;
694 MIDIPROPTEMPO propdata;
695 MIDIPROPTIMEDIV divdata;
696 int max_sysex_len = midi->buffer_len * 4;
697 int output_buffer_len;
698 int num_buffers;
699 dwDevice = (DWORD) (intptr_t) pm_descriptors[i].descriptor;
700
701 /* create system dependent device data */
702 info = winmm_info_create();
703 midi->api_info = info;
704 if (!info) goto no_memory;
705 /* create a signal */
706 info->buffer_signal = CreateEvent(NULL, FALSE, FALSE, NULL);
707 /* this should only fail when there are very serious problems */
708 assert(info->buffer_signal);
709 /* open device */
710 if (midi->latency == 0) {
711 /* use simple midi out calls */
712 pm_hosterror = midiOutOpen(
713 (LPHMIDIOUT) & info->handle.out, /* device Handle */
714 dwDevice, /* device ID */
715 /* note: same callback fn as for StreamOpen: */
716 (DWORD_PTR) winmm_streamout_callback, /* callback fn */
717 (DWORD_PTR) midi, /* callback instance data */
718 CALLBACK_FUNCTION); /* callback type */
719 } else {
720 /* use stream-based midi output (schedulable in future) */
721 pm_hosterror = midiStreamOpen(
722 &info->handle.stream, /* device Handle */
723 (LPUINT) & dwDevice, /* device ID pointer */
724 1, /* reserved, must be 1 */
725 (DWORD_PTR) winmm_streamout_callback,
726 (DWORD_PTR) midi, /* callback instance data */
727 CALLBACK_FUNCTION);
728 }
729 if (pm_hosterror != MMSYSERR_NOERROR) {
730 goto free_descriptor;
731 }
732
733 if (midi->latency == 0) {
734 num_buffers = NUM_SIMPLE_SYSEX_BUFFERS;
735 output_buffer_len = max_sysex_len / num_buffers;
736 if (output_buffer_len < MIN_SIMPLE_SYSEX_LEN)
737 output_buffer_len = MIN_SIMPLE_SYSEX_LEN;
738 } else {
739 num_buffers = max(midi->buffer_len, midi->latency / 2);
740 if (num_buffers < MIN_STREAM_BUFFERS)
741 num_buffers = MIN_STREAM_BUFFERS;
742 output_buffer_len = STREAM_BUFFER_LEN;
743
744 propdata.cbStruct = sizeof(MIDIPROPTEMPO);
745 propdata.dwTempo = 480000; /* microseconds per quarter */
746 pm_hosterror = midiStreamProperty(info->handle.stream,
747 (LPBYTE) & propdata,
748 MIDIPROP_SET | MIDIPROP_TEMPO);
749 if (pm_hosterror) goto close_device;
750
751 divdata.cbStruct = sizeof(MIDIPROPTEMPO);
752 divdata.dwTimeDiv = 480; /* divisions per quarter */
753 pm_hosterror = midiStreamProperty(info->handle.stream,
754 (LPBYTE) & divdata,
755 MIDIPROP_SET | MIDIPROP_TIMEDIV);
756 if (pm_hosterror) goto close_device;
757 }
758 /* allocate buffers */
759 if (allocate_buffers(info, output_buffer_len, num_buffers))
760 goto free_buffers;
761 /* start device */
762 if (midi->latency != 0) {
763 pm_hosterror = midiStreamRestart(info->handle.stream);
764 if (pm_hosterror != MMSYSERR_NOERROR) goto free_buffers;
765 }
766 return pmNoError;
767
768free_buffers:
769 /* buffers are freed below by winmm_out_delete */
770close_device:
771 midiOutClose(info->handle.out);
772free_descriptor:
773 midi->api_info = NULL;
774 winmm_out_delete(midi); /* frees buffers and m */
775no_memory:
776 if (pm_hosterror) {
777 report_hosterror_out();
778 return pmHostError;
779 }
780 return pmInsufficientMemory;
781}
782
783
784/* winmm_out_delete -- carefully free data associated with midi */
785/**/
786static void winmm_out_delete(PmInternal *midi)
787{
788 int i;
789 /* delete system dependent device data */
790 winmm_info_type info = (winmm_info_type) midi->api_info;
791 if (info) {
792 if (info->buffer_signal) {
793 /* don't report errors -- better not to stop cleanup */
794 CloseHandle(info->buffer_signal);
795 }
796 /* if using stream output, free buffers */
797 for (i = 0; i < info->num_buffers; i++) {
798 if (info->buffers[i]) pm_free(info->buffers[i]);
799 }
800 info->num_buffers = 0;
801 pm_free(info->buffers);
802 info->max_buffers = 0;
803 }
804 midi->api_info = NULL;
805 pm_free(info); /* delete */
806}
807
808
809/* see comments for winmm_in_close */
810static PmError winmm_out_close(PmInternal *midi)
811{
812 winmm_info_type info = (winmm_info_type) midi->api_info;
813 if (info->handle.out) {
814 /* device to close */
815 if (midi->latency == 0) {
816 pm_hosterror = midiOutClose(info->handle.out);
817 } else {
818 pm_hosterror = midiStreamClose(info->handle.stream);
819 }
820 /* regardless of outcome, free memory */
821 winmm_out_delete(midi);
822 }
823 if (pm_hosterror) {
824 report_hosterror_out();
825 return pmHostError;
826 }
827 return pmNoError;
828}
829
830
831static PmError winmm_out_abort(PmInternal *midi)
832{
833 winmm_info_type info = (winmm_info_type) midi->api_info;
834
835 /* only stop output streams */
836 if (midi->latency > 0) {
837 pm_hosterror = midiStreamStop(info->handle.stream);
838 if (pm_hosterror) {
839 report_hosterror_out();
840 return pmHostError;
841 }
842 }
843 return pmNoError;
844}
845
846
847static PmError winmm_write_flush(PmInternal *midi, PmTimestamp timestamp)
848{
849 winmm_info_type info = (winmm_info_type) midi->api_info;
850 assert(info);
851 if (info->hdr) {
852 pm_hosterror = midiOutPrepareHeader(info->handle.out, info->hdr,
853 sizeof(MIDIHDR));
854 if (pm_hosterror) {
855 /* do not send message */
856 } else if (midi->latency == 0) {
857 /* As pointed out by Nigel Brown, 20Sep06, dwBytesRecorded
858 * should be zero. This is set in get_free_sysex_buffer().
859 * The msg length goes in dwBufferLength in spite of what
860 * Microsoft documentation says (or doesn't say). */
861 info->hdr->dwBufferLength = info->hdr->dwBytesRecorded;
862 info->hdr->dwBytesRecorded = 0;
863 pm_hosterror = midiOutLongMsg(info->handle.out, info->hdr,
864 sizeof(MIDIHDR));
865 } else {
866 pm_hosterror = midiStreamOut(info->handle.stream, info->hdr,
867 sizeof(MIDIHDR));
868 }
869 midi->fill_base = NULL;
870 info->hdr = NULL;
871 if (pm_hosterror) {
872 report_hosterror_out();
873 return pmHostError;
874 }
875 }
876 return pmNoError;
877}
878
879
880static PmError winmm_write_short(PmInternal *midi, PmEvent *event)
881{
882 winmm_info_type info = (winmm_info_type) midi->api_info;
883 PmError rslt = pmNoError;
884 assert(info);
885
886 if (midi->latency == 0) { /* use midiOut interface, ignore timestamps */
887 pm_hosterror = midiOutShortMsg(info->handle.out, event->message);
888 if (pm_hosterror) {
889 if (info->hdr) { /* device disconnect may delete hdr */
890 info->hdr->dwFlags = 0; /* release the buffer */
891 }
892 report_hosterror_out();
893 return pmHostError;
894 }
895 } else { /* use midiStream interface -- pass data through buffers */
896 unsigned long when = event->timestamp;
897 unsigned long delta;
898 int full;
899 if (when == 0) when = midi->now;
900 /* when is in real_time; translate to intended stream time */
901 when = when + info->delta + midi->latency;
902 /* make sure we don't go backward in time */
903 if (when < info->last_time) when = info->last_time;
904 delta = when - info->last_time;
905 info->last_time = when;
906 /* before we insert any data, we must have a buffer */
907 if (info->hdr == NULL) {
908 /* stream interface: buffers allocated when stream is opened */
909 info->hdr = get_free_output_buffer(midi);
910 }
911 full = add_to_buffer(info, info->hdr, delta, event->message);
912 /* note: winmm_write_flush sets pm_hosterror etc. on host error */
913 if (full) rslt = winmm_write_flush(midi, when);
914 }
915 return rslt;
916}
917
918#define winmm_begin_sysex winmm_write_flush
919#ifndef winmm_begin_sysex
920static PmError winmm_begin_sysex(PmInternal *midi, PmTimestamp timestamp)
921{
922 winmm_info_type m = (winmm_info_type) midi->api_info;
923 PmError rslt = pmNoError;
924
925 if (midi->latency == 0) {
926 /* do nothing -- it's handled in winmm_write_byte */
927 } else {
928 /* sysex expects an empty sysex buffer, so send whatever is here */
929 rslt = winmm_write_flush(midi);
930 }
931 return rslt;
932}
933#endif
934
935static PmError winmm_end_sysex(PmInternal *midi, PmTimestamp timestamp)
936{
937 /* could check for callback_error here, but I haven't checked
938 * what happens if we exit early and don't finish the sysex msg
939 * and clean up
940 */
941 winmm_info_type info = (winmm_info_type) midi->api_info;
942 PmError rslt = pmNoError;
943 LPMIDIHDR hdr = info->hdr;
944 if (!hdr) return rslt; /* something bad happened earlier,
945 do not report an error because it would have been
946 reported (at least) once already */
947 /* a(n old) version of MIDI YOKE requires a zero byte after
948 * the sysex message, but do not increment dwBytesRecorded: */
949 hdr->lpData[hdr->dwBytesRecorded] = 0;
950 if (midi->latency == 0) {
951#ifdef DEBUG_PRINT_BEFORE_SENDING_SYSEX
952 /* DEBUG CODE: */
953 { int i; int len = info->hdr->dwBufferLength;
954 printf("OutLongMsg %d ", len);
955 for (i = 0; i < len; i++) {
956 printf("%2x ", (unsigned char) (info->hdr->lpData[i]));
957 }
958 }
959#endif
960 } else {
961 /* Using stream interface. There are accumulated bytes in info->hdr
962 to send using midiStreamOut
963 */
964 /* add bytes recorded to MIDIEVENT length, but don't
965 count the MIDIEVENT data (3 longs) */
966 MIDIEVENT *evt = (MIDIEVENT *) (hdr->lpData);
967 evt->dwEvent += hdr->dwBytesRecorded - 3 * sizeof(long);
968 /* round up BytesRecorded to multiple of 4 */
969 hdr->dwBytesRecorded = (hdr->dwBytesRecorded + 3) & ~3;
970 }
971 rslt = winmm_write_flush(midi, timestamp);
972 return rslt;
973}
974
975
976static PmError winmm_write_byte(PmInternal *midi, unsigned char byte,
977 PmTimestamp timestamp)
978{
979 /* write a sysex byte */
980 PmError rslt = pmNoError;
981 winmm_info_type info = (winmm_info_type) midi->api_info;
982 LPMIDIHDR hdr = info->hdr;
983 unsigned char *msg_buffer;
984 assert(info);
985 if (!hdr) {
986 info->hdr = hdr = get_free_output_buffer(midi);
987 assert(hdr);
988 midi->fill_base = (unsigned char *) info->hdr->lpData;
989 midi->fill_offset_ptr = (uint32_t *) &(hdr->dwBytesRecorded);
990 /* when buffer fills, Pm_WriteSysEx will revert to calling
991 * pmwin_write_byte, which expect to have space, so leave
992 * one byte free for pmwin_write_byte. Leave another byte
993 * of space for zero after message to make early version of
994 * MIDI YOKE driver happy -- therefore dwBufferLength - 2 */
995 midi->fill_length = hdr->dwBufferLength - 2;
996 if (midi->latency != 0) {
997 unsigned long when = (unsigned long) timestamp;
998 unsigned long delta;
999 unsigned long *ptr;
1000 if (when == 0) when = midi->now;
1001 /* when is in real_time; translate to intended stream time */
1002 when = when + info->delta + midi->latency;
1003 /* make sure we don't go backward in time */
1004 if (when < info->last_time) when = info->last_time;
1005 delta = when - info->last_time;
1006 info->last_time = when;
1007
1008 ptr = (unsigned long *) hdr->lpData;
1009 *ptr++ = delta;
1010 *ptr++ = 0;
1011 *ptr = MEVT_F_LONG;
1012 hdr->dwBytesRecorded = 3 * sizeof(long);
1013 /* data will be added at an offset of dwBytesRecorded ... */
1014 }
1015 }
1016 /* add the data byte */
1017 msg_buffer = (unsigned char *) (hdr->lpData);
1018 msg_buffer[hdr->dwBytesRecorded++] = byte;
1019
1020 /* see if buffer is full, leave one byte extra for pad */
1021 if (hdr->dwBytesRecorded >= hdr->dwBufferLength - 1) {
1022 /* write what we've got and continue */
1023 rslt = winmm_end_sysex(midi, timestamp);
1024 }
1025 return rslt;
1026}
1027
1028
1029static PmTimestamp winmm_synchronize(PmInternal *midi)
1030{
1031 winmm_info_type info;
1032 unsigned long pm_stream_time_2;
1033 unsigned long real_time;
1034 unsigned long pm_stream_time;
1035
1036 /* only synchronize if we are using stream interface */
1037 if (midi->latency == 0) return 0;
1038
1039 /* figure out the time */
1040 info = (winmm_info_type) midi->api_info;
1041 pm_stream_time_2 = pm_time_get(info);
1042
1043 do {
1044 /* read real_time between two reads of stream time */
1045 pm_stream_time = pm_stream_time_2;
1046 real_time = (*midi->time_proc)(midi->time_info);
1047 pm_stream_time_2 = pm_time_get(info);
1048 /* repeat if more than 1ms elapsed */
1049 } while (pm_stream_time_2 > pm_stream_time + 1);
1050 info->delta = pm_stream_time - real_time;
1051 info->sync_time = real_time;
1052 return real_time;
1053}
1054
1055
1056/* winmm_streamout_callback -- unprepare (free) buffer header */
1057static void CALLBACK winmm_streamout_callback(HMIDIOUT hmo, UINT wMsg,
1058 DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
1059{
1060 PmInternal *midi = (PmInternal *) dwInstance;
1061 winmm_info_type info = (winmm_info_type) midi->api_info;
1062 LPMIDIHDR hdr = (LPMIDIHDR) dwParam1;
1063 int err;
1064
1065 /* Even if an error is pending, I think we should unprepare msgs and
1066 signal their arrival
1067 */
1068 /* printf("streamout_callback: hdr %x, wMsg %x, MOM_DONE %x\n",
1069 hdr, wMsg, MOM_DONE); */
1070 if (wMsg == MOM_DONE) {
1071 MMRESULT ret = midiOutUnprepareHeader(info->handle.out, hdr,
1072 sizeof(MIDIHDR));
1073 assert(ret == MMSYSERR_NOERROR);
1074 } else if (wMsg == MOM_CLOSE) {
1075 /* The streaming API gets a callback when the device is closed.
1076 * The non-streaming API gets a callback when the device is
1077 * removed or closed. It is misleading to set is_removed when
1078 * the device is closed normally, but in that case, midi itself
1079 * will be freed immediately, so there should be no way to
1080 * observe is_removed == TRUE. On the other hand, if the device
1081 * is removed, setting is_removed will cause PortMidi to return
1082 * the pmDeviceRemoved error on attempts to output to the device.
1083 * In the case of normal closing, due to midiOutClose(),
1084 * the call below is reentrant (!), but for some reason this does
1085 * not cause an error or infinite recursion, so we are not taking
1086 * any precautions to flag midi as "in the process of closing."
1087 */
1088 midi->is_removed = TRUE;
1089 midiOutClose(info->handle.out);
1090 }
1091 /* signal client in case it is blocked waiting for buffer */
1092 err = SetEvent(info->buffer_signal);
1093 assert(err); /* false -> error */
1094}
1095
1096
1097/*
1098===========================================================================
1099begin exported functions
1100===========================================================================
1101*/
1102
1103#define winmm_in_abort pm_fail_fn
1104pm_fns_node pm_winmm_in_dictionary = {
1105 none_write_short,
1106 none_sysex,
1107 none_sysex,
1108 none_write_byte,
1109 none_write_short,
1110 none_write_flush,
1111 winmm_synchronize,
1112 winmm_in_open,
1113 winmm_in_abort,
1114 winmm_in_close,
1115 success_poll,
1116 winmm_check_host_error
1117 };
1118
1119pm_fns_node pm_winmm_out_dictionary = {
1120 winmm_write_short,
1121 winmm_begin_sysex,
1122 winmm_end_sysex,
1123 winmm_write_byte,
1124 /* short realtime message: */ winmm_write_short,
1125 winmm_write_flush,
1126 winmm_synchronize,
1127 winmm_out_open,
1128 winmm_out_abort,
1129 winmm_out_close,
1130 none_poll,
1131 winmm_check_host_error
1132 };
1133
1134
1135/* initialize winmm interface. Note that if there is something wrong
1136 with winmm (e.g. it is not supported or installed), it is not an
1137 error. We should simply return without having added any devices to
1138 the table. Hence, no error code is returned. Furthermore, this init
1139 code is called along with every other supported interface, so the
1140 user would have a very hard time figuring out what hardware and API
1141 generated the error. Finally, it would add complexity to pmwin.c to
1142 remember where the error code came from in order to convert to text.
1143 */
1144void pm_winmm_init( void )
1145{
1146 pm_winmm_mapper_input();
1147 pm_winmm_mapper_output();
1148 pm_winmm_general_inputs();
1149 pm_winmm_general_outputs();
1150}
1151
1152
1153/* no error codes are returned, even if errors are encountered, because
1154 there is probably nothing the user could do (e.g. it would be an error
1155 to retry.
1156 */
1157void pm_winmm_term( void )
1158{
1159 int i;
1160#ifdef MMDEBUG
1161 int doneAny = 0;
1162 printf("pm_winmm_term called\n");
1163#endif
1164 for (i = 0; i < pm_descriptor_len; i++) {
1165 PmInternal *midi = pm_descriptors[i].pm_internal;
1166 if (midi) {
1167 winmm_info_type info = (winmm_info_type) midi->api_info;
1168 if (info->handle.out) {
1169 /* close next open device*/
1170#ifdef MMDEBUG
1171 if (doneAny == 0) {
1172 printf("begin closing open devices...\n");
1173 doneAny = 1;
1174 }
1175#endif
1176 /* close all open ports */
1177 (*midi->dictionary->close)(midi);
1178 }
1179 }
1180 }
1181 if (midi_in_caps) {
1182 pm_free(midi_in_caps);
1183 midi_in_caps = NULL;
1184 }
1185 if (midi_out_caps) {
1186 pm_free(midi_out_caps);
1187 midi_out_caps = NULL;
1188 }
1189#ifdef MMDEBUG
1190 if (doneAny) {
1191 printf("warning: devices were left open. They have been closed.\n");
1192 }
1193 printf("pm_winmm_term exiting\n");
1194#endif
1195 pm_descriptor_len = 0;
1196}