1 /* -*- tab-width: 8; c-basic-offset: 4 -*- */
4 * Sample MIDI Wine Driver for ALSA (basically Linux)
6 * Copyright 1994 Martin Ayotte
7 * Copyright 1998 Luiz Otavio L. Zorzella (init procedures)
8 * Copyright 1998/1999 Eric POUECH :
9 * 98/7 changes for making this MIDI driver work on OSS
10 * current support is limited to MIDI ports of OSS systems
11 * 98/9 rewriting MCI code for MIDI
12 * 98/11 splitted in midi.c and mcimidi.c
13 * Copyright 2003 Christian Costa :
16 * This library is free software; you can redistribute it and/or
17 * modify it under the terms of the GNU Lesser General Public
18 * License as published by the Free Software Foundation; either
19 * version 2.1 of the License, or (at your option) any later version.
21 * This library is distributed in the hope that it will be useful,
22 * but WITHOUT ANY WARRANTY; without even the implied warranty of
23 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
24 * Lesser General Public License for more details.
26 * You should have received a copy of the GNU Lesser General Public
27 * License along with this library; if not, write to the Free Software
28 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
30 * TODO: Finish midi record
52 #include "wine/debug.h"
54 WINE_DEFAULT_DEBUG_CHANNEL(midi);
59 int state; /* -1 disabled, 0 is no recording started, 1 in recording, bit 2 set if in sys exclusive recording */
61 MIDIOPENDESC midiDesc;
65 unsigned char incoming[3];
66 unsigned char incPrev;
76 MIDIOPENDESC midiDesc;
80 void* lpExtra; /* according to port type (MIDI, FM...), extra data when needed */
85 static WINE_MIDIIN MidiInDev [MAX_MIDIINDRV ];
86 static WINE_MIDIOUT MidiOutDev[MAX_MIDIOUTDRV];
88 /* this is the total number of MIDI out devices found (synth and port) */
89 static int MODM_NumDevs = 0;
90 /* this is the total number of MIDI out devices found */
91 static int MIDM_NumDevs = 0;
93 static snd_seq_t* midiSeq = NULL;
94 static int numOpenMidiSeq = 0;
95 static int numStartedMidiIn = 0;
100 static CRITICAL_SECTION crit_sect; /* protects all MidiIn buffer queues */
101 static CRITICAL_SECTION_DEBUG critsect_debug =
104 { &critsect_debug.ProcessLocksList, &critsect_debug.ProcessLocksList },
105 0, 0, { 0, (DWORD)(__FILE__ ": crit_sect") }
107 static CRITICAL_SECTION crit_sect = { &critsect_debug, -1, 0, 0, 0, 0 };
109 static int end_thread;
110 static HANDLE hThread;
112 /*======================================================================*
113 * Low level MIDI implementation *
114 *======================================================================*/
116 static int midiOpenSeq(int);
117 static int midiCloseSeq(void);
119 #if 0 /* Debug Purpose */
120 static void error_handler(const char* file, int line, const char* function, int err, const char* fmt, ...)
126 fprintf(stderr, "ALSA lib %s:%i:(%s) ", file, line, function);
127 vfprintf(stderr, fmt, arg);
129 fprintf(stderr, ": %s", snd_strerror(err));
135 /**************************************************************************
136 * MIDI_unixToWindowsDeviceType [internal]
138 * return the Windows equivalent to a Unix Device Type
141 static int MIDI_AlsaToWindowsDeviceType(int type)
143 /* MOD_MIDIPORT output port
144 * MOD_SYNTH generic internal synth
145 * MOD_SQSYNTH square wave internal synth
146 * MOD_FMSYNTH FM internal synth
147 * MOD_MAPPER MIDI mapper
148 * MOD_WAVETABLE hardware watetable internal synth
149 * MOD_SWSYNTH software internal synth
152 /* FIXME Is this really the correct equivalence from ALSA to
153 Windows Sound type */
155 if (type & SND_SEQ_PORT_TYPE_SYNTH)
158 if (type & (SND_SEQ_PORT_TYPE_DIRECT_SAMPLE|SND_SEQ_PORT_TYPE_SAMPLE))
161 if (type & SND_SEQ_PORT_TYPE_MIDI_GENERIC)
164 ERR("Cannot determine the type of this midi device. Assuming FM Synth\n");
168 /**************************************************************************
169 * MIDI_NotifyClient [internal]
171 static DWORD MIDI_NotifyClient(UINT wDevID, WORD wMsg,
172 DWORD dwParam1, DWORD dwParam2)
179 TRACE("wDevID = %04X wMsg = %d dwParm1 = %04lX dwParam2 = %04lX\n",
180 wDevID, wMsg, dwParam1, dwParam2);
187 if (wDevID > MODM_NumDevs)
188 return MMSYSERR_BADDEVICEID;
190 dwCallBack = MidiOutDev[wDevID].midiDesc.dwCallback;
191 uFlags = MidiOutDev[wDevID].wFlags;
192 hDev = MidiOutDev[wDevID].midiDesc.hMidi;
193 dwInstance = MidiOutDev[wDevID].midiDesc.dwInstance;
203 if (wDevID > MIDM_NumDevs)
204 return MMSYSERR_BADDEVICEID;
206 dwCallBack = MidiInDev[wDevID].midiDesc.dwCallback;
207 uFlags = MidiInDev[wDevID].wFlags;
208 hDev = MidiInDev[wDevID].midiDesc.hMidi;
209 dwInstance = MidiInDev[wDevID].midiDesc.dwInstance;
212 WARN("Unsupported MSW-MIDI message %u\n", wMsg);
213 return MMSYSERR_ERROR;
216 return DriverCallback(dwCallBack, uFlags, hDev, wMsg, dwInstance, dwParam1, dwParam2) ?
220 static int midi_warn = 1;
221 /**************************************************************************
222 * midiOpenSeq [internal]
224 static int midiOpenSeq(int create_client)
226 if (numOpenMidiSeq == 0) {
227 if (snd_seq_open(&midiSeq, "default", SND_SEQ_OPEN_DUPLEX, 0) < 0)
231 WARN("Error opening ALSA sequencer.\n");
238 /* Setting the client name is the only init to do */
239 snd_seq_set_client_name(midiSeq, "WINE midi driver");
241 #if 0 /* FIXME: Is it possible to use a port for READ & WRITE ops */
242 port_in = port_out = snd_seq_create_simple_port(midiSeq, "WINE ALSA Input/Output", SND_SEQ_PORT_CAP_READ|SND_SEQ_PORT_CAP_WRITE,
243 SND_SEQ_PORT_TYPE_APPLICATION);
245 TRACE("Unable to create output port\n");
247 TRACE("Outport port created successfully (%d)\n", port_out);
249 port_out = snd_seq_create_simple_port(midiSeq, "WINE ALSA Output", SND_SEQ_PORT_CAP_READ,
250 SND_SEQ_PORT_TYPE_APPLICATION);
252 TRACE("Unable to create output port\n");
254 TRACE("Outport port created successfully (%d)\n", port_out);
256 port_in = snd_seq_create_simple_port(midiSeq, "WINE ALSA Input", SND_SEQ_PORT_CAP_WRITE,
257 SND_SEQ_PORT_TYPE_APPLICATION);
259 TRACE("Unable to create input port\n");
261 TRACE("Input port created successfully (%d)\n", port_in);
269 /**************************************************************************
270 * midiCloseSeq [internal]
272 static int midiCloseSeq(void)
274 if (--numOpenMidiSeq == 0) {
275 snd_seq_delete_simple_port(midiSeq, port_out);
276 snd_seq_delete_simple_port(midiSeq, port_in);
277 snd_seq_close(midiSeq);
283 static DWORD WINAPI midRecThread(LPVOID arg)
288 TRACE("Thread startup\n");
291 TRACE("Thread loop\n");
292 npfd = snd_seq_poll_descriptors_count(midiSeq, POLLIN);
293 pfd = (struct pollfd *)HeapAlloc(GetProcessHeap(), 0, npfd * sizeof(struct pollfd));
294 snd_seq_poll_descriptors(midiSeq, pfd, npfd, POLLIN);
296 /* Check if a event is present */
297 if (poll(pfd, npfd, 250) < 0) {
298 HeapFree(GetProcessHeap(), 0, pfd);
302 /* Note: This definitely does not work.
303 * while(snd_seq_event_input_pending(midiSeq, 0) > 0) {
305 snd_seq_event_input(midiSeq, &ev);
307 snd_seq_free_event(ev);
313 snd_seq_event_input(midiSeq, &ev);
314 /* Find the target device */
315 for (wDevID = 0; wDevID < MIDM_NumDevs; wDevID++)
316 if ( (ev->source.client == MidiInDev[wDevID].addr.client) && (ev->source.port == MidiInDev[wDevID].addr.port) )
318 if ((wDevID == MIDM_NumDevs) || (MidiInDev[wDevID].state != 1))
319 FIXME("Unexpected event received, type = %x from %d:%d\n", ev->type, ev->source.client, ev->source.port);
321 DWORD dwTime, toSend = 0;
322 /* FIXME: Should use ev->time instead for better accuracy */
323 dwTime = GetTickCount() - MidiInDev[wDevID].startTime;
324 TRACE("Event received, type = %x, device = %d\n", ev->type, wDevID);
327 case SND_SEQ_EVENT_NOTEOFF:
328 toSend = (ev->data.note.velocity << 16) | (ev->data.note.note << 8) | MIDI_CMD_NOTE_OFF | ev->data.control.channel;
330 case SND_SEQ_EVENT_NOTEON:
331 toSend = (ev->data.note.velocity << 16) | (ev->data.note.note << 8) | MIDI_CMD_NOTE_ON | ev->data.control.channel;
333 case SND_SEQ_EVENT_KEYPRESS:
334 toSend = (ev->data.note.velocity << 16) | (ev->data.note.note << 8) | MIDI_CMD_NOTE_PRESSURE | ev->data.control.channel;
336 case SND_SEQ_EVENT_CONTROLLER:
337 toSend = (ev->data.control.value << 16) | (ev->data.control.param << 8) | MIDI_CMD_CONTROL | ev->data.control.channel;
339 case SND_SEQ_EVENT_PITCHBEND:
340 toSend = (ev->data.control.value << 16) | (ev->data.control.param << 8) | MIDI_CMD_BENDER | ev->data.control.channel;
342 case SND_SEQ_EVENT_PGMCHANGE:
343 toSend = (ev->data.control.value << 16) | (ev->data.control.param << 8) | MIDI_CMD_PGM_CHANGE | ev->data.control.channel;
345 case SND_SEQ_EVENT_CHANPRESS:
346 toSend = (ev->data.control.value << 16) | (ev->data.control.param << 8) | MIDI_CMD_CHANNEL_PRESSURE | ev->data.control.channel;
348 case SND_SEQ_EVENT_SYSEX:
350 int len = ev->data.ext.len;
351 LPBYTE ptr = (BYTE*) ev->data.ext.ptr;
354 /* FIXME: Should handle sysex greater that a single buffer */
355 EnterCriticalSection(&crit_sect);
356 if ((lpMidiHdr = MidiInDev[wDevID].lpQueueHdr) != NULL) {
357 if (len <= lpMidiHdr->dwBufferLength) {
358 lpMidiHdr->dwBytesRecorded = len;
359 memcpy(lpMidiHdr->lpData, ptr, len);
360 lpMidiHdr->dwFlags &= ~MHDR_INQUEUE;
361 lpMidiHdr->dwFlags |= MHDR_DONE;
362 MidiInDev[wDevID].lpQueueHdr = (LPMIDIHDR)lpMidiHdr->lpNext;
363 if (MIDI_NotifyClient(wDevID, MIM_LONGDATA, (DWORD)lpMidiHdr, dwTime) != MMSYSERR_NOERROR)
364 WARN("Couldn't notify client\n");
366 FIXME("No enough space in the buffer to store sysex!\n");
368 FIXME("Sysex received but no buffer to store it!\n");
369 LeaveCriticalSection(&crit_sect);
372 case SND_SEQ_EVENT_SENSING:
376 FIXME("Unhandled event received, type = %x\n", ev->type);
380 TRACE("Sending event %08lx (from %d %d)\n", toSend, ev->source.client, ev->source.port);
381 if (MIDI_NotifyClient(wDevID, MIM_DATA, toSend, dwTime) != MMSYSERR_NOERROR) {
382 WARN("Couldn't notify client\n");
386 snd_seq_free_event(ev);
387 } while(snd_seq_event_input_pending(midiSeq, 0) > 0);
389 HeapFree(GetProcessHeap(), 0, pfd);
394 /**************************************************************************
395 * midGetDevCaps [internal]
397 static DWORD midGetDevCaps(WORD wDevID, LPMIDIINCAPSW lpCaps, DWORD dwSize)
399 TRACE("(%04X, %p, %08lX);\n", wDevID, lpCaps, dwSize);
401 if (wDevID >= MIDM_NumDevs) return MMSYSERR_BADDEVICEID;
402 if (lpCaps == NULL) return MMSYSERR_INVALPARAM;
404 memcpy(lpCaps, &MidiInDev[wDevID].caps, min(dwSize, sizeof(*lpCaps)));
406 return MMSYSERR_NOERROR;
410 /**************************************************************************
413 static DWORD midOpen(WORD wDevID, LPMIDIOPENDESC lpDesc, DWORD dwFlags)
415 TRACE("(%04X, %p, %08lX);\n", wDevID, lpDesc, dwFlags);
417 if (lpDesc == NULL) {
418 WARN("Invalid Parameter !\n");
419 return MMSYSERR_INVALPARAM;
423 * how to check that content of lpDesc is correct ?
425 if (wDevID >= MIDM_NumDevs) {
426 WARN("wDevID too large (%u) !\n", wDevID);
427 return MMSYSERR_BADDEVICEID;
429 if (MidiInDev[wDevID].state == -1) {
430 WARN("device disabled\n");
431 return MIDIERR_NODEVICE;
433 if (MidiInDev[wDevID].midiDesc.hMidi != 0) {
434 WARN("device already open !\n");
435 return MMSYSERR_ALLOCATED;
437 if ((dwFlags & MIDI_IO_STATUS) != 0) {
438 WARN("No support for MIDI_IO_STATUS in dwFlags yet, ignoring it\n");
439 dwFlags &= ~MIDI_IO_STATUS;
441 if ((dwFlags & ~CALLBACK_TYPEMASK) != 0) {
442 FIXME("Bad dwFlags\n");
443 return MMSYSERR_INVALFLAG;
446 if (midiOpenSeq(1) < 0) {
447 return MMSYSERR_ERROR;
450 /* Connect our app port to the device port */
451 if (snd_seq_connect_from(midiSeq, port_in, MidiInDev[wDevID].addr.client, MidiInDev[wDevID].addr.port) < 0)
452 return MMSYSERR_NOTENABLED;
454 TRACE("input port connected %d %d %d\n",port_in,MidiInDev[wDevID].addr.client,MidiInDev[wDevID].addr.port);
456 if (numStartedMidiIn++ == 0) {
458 hThread = CreateThread(NULL, 0, midRecThread, NULL, 0, NULL);
460 numStartedMidiIn = 0;
461 WARN("Couldn't create thread for midi-in\n");
463 return MMSYSERR_ERROR;
465 TRACE("Created thread for midi-in\n");
468 MidiInDev[wDevID].wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK);
470 MidiInDev[wDevID].lpQueueHdr = NULL;
471 MidiInDev[wDevID].dwTotalPlayed = 0;
472 MidiInDev[wDevID].bufsize = 0x3FFF;
473 MidiInDev[wDevID].midiDesc = *lpDesc;
474 MidiInDev[wDevID].state = 0;
475 MidiInDev[wDevID].incLen = 0;
476 MidiInDev[wDevID].startTime = 0;
478 if (MIDI_NotifyClient(wDevID, MIM_OPEN, 0L, 0L) != MMSYSERR_NOERROR) {
479 WARN("can't notify client !\n");
480 return MMSYSERR_INVALPARAM;
482 return MMSYSERR_NOERROR;
485 /**************************************************************************
486 * midClose [internal]
488 static DWORD midClose(WORD wDevID)
490 int ret = MMSYSERR_NOERROR;
492 TRACE("(%04X);\n", wDevID);
494 if (wDevID >= MIDM_NumDevs) {
495 WARN("wDevID too big (%u) !\n", wDevID);
496 return MMSYSERR_BADDEVICEID;
498 if (MidiInDev[wDevID].midiDesc.hMidi == 0) {
499 WARN("device not opened !\n");
500 return MMSYSERR_ERROR;
502 if (MidiInDev[wDevID].lpQueueHdr != 0) {
503 return MIDIERR_STILLPLAYING;
506 if (midiSeq == NULL) {
508 return MMSYSERR_ERROR;
510 if (--numStartedMidiIn == 0) {
511 TRACE("Stopping thread for midi-in\n");
513 if (WaitForSingleObject(hThread, 5000) != WAIT_OBJECT_0) {
514 WARN("Thread end not signaled, force termination\n");
515 TerminateThread(hThread, 0);
517 TRACE("Stopped thread for midi-in\n");
520 snd_seq_disconnect_from(midiSeq, port_in, MidiInDev[wDevID].addr.client, MidiInDev[wDevID].addr.port);
523 MidiInDev[wDevID].bufsize = 0;
524 if (MIDI_NotifyClient(wDevID, MIM_CLOSE, 0L, 0L) != MMSYSERR_NOERROR) {
525 WARN("can't notify client !\n");
526 ret = MMSYSERR_INVALPARAM;
528 MidiInDev[wDevID].midiDesc.hMidi = 0;
534 /**************************************************************************
535 * midAddBuffer [internal]
537 static DWORD midAddBuffer(WORD wDevID, LPMIDIHDR lpMidiHdr, DWORD dwSize)
539 TRACE("(%04X, %p, %08lX);\n", wDevID, lpMidiHdr, dwSize);
541 if (wDevID >= MIDM_NumDevs) return MMSYSERR_BADDEVICEID;
542 if (MidiInDev[wDevID].state == -1) return MIDIERR_NODEVICE;
544 if (lpMidiHdr == NULL) return MMSYSERR_INVALPARAM;
545 if (sizeof(MIDIHDR) > dwSize) return MMSYSERR_INVALPARAM;
546 if (lpMidiHdr->dwBufferLength == 0) return MMSYSERR_INVALPARAM;
547 if (lpMidiHdr->dwFlags & MHDR_INQUEUE) return MIDIERR_STILLPLAYING;
548 if (!(lpMidiHdr->dwFlags & MHDR_PREPARED)) return MIDIERR_UNPREPARED;
550 EnterCriticalSection(&crit_sect);
551 if (MidiInDev[wDevID].lpQueueHdr == 0) {
552 MidiInDev[wDevID].lpQueueHdr = lpMidiHdr;
556 for (ptr = MidiInDev[wDevID].lpQueueHdr;
558 ptr = (LPMIDIHDR)ptr->lpNext);
559 ptr->lpNext = (struct midihdr_tag*)lpMidiHdr;
561 LeaveCriticalSection(&crit_sect);
563 return MMSYSERR_NOERROR;
566 /**************************************************************************
567 * midPrepare [internal]
569 static DWORD midPrepare(WORD wDevID, LPMIDIHDR lpMidiHdr, DWORD dwSize)
571 TRACE("(%04X, %p, %08lX);\n", wDevID, lpMidiHdr, dwSize);
573 if (dwSize < sizeof(MIDIHDR) || lpMidiHdr == 0 ||
574 lpMidiHdr->lpData == 0 || (lpMidiHdr->dwFlags & MHDR_INQUEUE) != 0 ||
575 lpMidiHdr->dwBufferLength >= 0x10000ul)
576 return MMSYSERR_INVALPARAM;
578 lpMidiHdr->lpNext = 0;
579 lpMidiHdr->dwFlags |= MHDR_PREPARED;
580 lpMidiHdr->dwBytesRecorded = 0;
582 return MMSYSERR_NOERROR;
585 /**************************************************************************
586 * midUnprepare [internal]
588 static DWORD midUnprepare(WORD wDevID, LPMIDIHDR lpMidiHdr, DWORD dwSize)
590 TRACE("(%04X, %p, %08lX);\n", wDevID, lpMidiHdr, dwSize);
592 if (wDevID >= MIDM_NumDevs) return MMSYSERR_BADDEVICEID;
593 if (MidiInDev[wDevID].state == -1) return MIDIERR_NODEVICE;
595 if (dwSize < sizeof(MIDIHDR) || lpMidiHdr == 0 ||
596 lpMidiHdr->lpData == 0 || lpMidiHdr->dwBufferLength >= 0x10000ul)
597 return MMSYSERR_INVALPARAM;
599 if (!(lpMidiHdr->dwFlags & MHDR_PREPARED)) return MIDIERR_UNPREPARED;
600 if (lpMidiHdr->dwFlags & MHDR_INQUEUE) return MIDIERR_STILLPLAYING;
602 lpMidiHdr->dwFlags &= ~MHDR_PREPARED;
604 return MMSYSERR_NOERROR;
607 /**************************************************************************
608 * midReset [internal]
610 static DWORD midReset(WORD wDevID)
612 DWORD dwTime = GetTickCount();
614 TRACE("(%04X);\n", wDevID);
616 if (wDevID >= MIDM_NumDevs) return MMSYSERR_BADDEVICEID;
617 if (MidiInDev[wDevID].state == -1) return MIDIERR_NODEVICE;
619 EnterCriticalSection(&crit_sect);
620 while (MidiInDev[wDevID].lpQueueHdr) {
621 MidiInDev[wDevID].lpQueueHdr->dwFlags &= ~MHDR_INQUEUE;
622 MidiInDev[wDevID].lpQueueHdr->dwFlags |= MHDR_DONE;
623 /* FIXME: when called from 16 bit, lpQueueHdr needs to be a segmented ptr */
624 if (MIDI_NotifyClient(wDevID, MIM_LONGDATA,
625 (DWORD)MidiInDev[wDevID].lpQueueHdr, dwTime) != MMSYSERR_NOERROR) {
626 WARN("Couldn't notify client\n");
628 MidiInDev[wDevID].lpQueueHdr = (LPMIDIHDR)MidiInDev[wDevID].lpQueueHdr->lpNext;
630 LeaveCriticalSection(&crit_sect);
632 return MMSYSERR_NOERROR;
635 /**************************************************************************
636 * midStart [internal]
638 static DWORD midStart(WORD wDevID)
640 TRACE("(%04X);\n", wDevID);
642 if (wDevID >= MIDM_NumDevs) return MMSYSERR_BADDEVICEID;
643 if (MidiInDev[wDevID].state == -1) return MIDIERR_NODEVICE;
645 MidiInDev[wDevID].state = 1;
646 MidiInDev[wDevID].startTime = GetTickCount();
647 return MMSYSERR_NOERROR;
650 /**************************************************************************
653 static DWORD midStop(WORD wDevID)
655 TRACE("(%04X);\n", wDevID);
657 if (wDevID >= MIDM_NumDevs) return MMSYSERR_BADDEVICEID;
658 if (MidiInDev[wDevID].state == -1) return MIDIERR_NODEVICE;
660 MidiInDev[wDevID].state = 0;
661 return MMSYSERR_NOERROR;
664 /**************************************************************************
665 * modGetDevCaps [internal]
667 static DWORD modGetDevCaps(WORD wDevID, LPMIDIOUTCAPSW lpCaps, DWORD dwSize)
669 TRACE("(%04X, %p, %08lX);\n", wDevID, lpCaps, dwSize);
671 if (wDevID >= MODM_NumDevs) return MMSYSERR_BADDEVICEID;
672 if (lpCaps == NULL) return MMSYSERR_INVALPARAM;
674 memcpy(lpCaps, &MidiOutDev[wDevID].caps, min(dwSize, sizeof(*lpCaps)));
676 return MMSYSERR_NOERROR;
679 /**************************************************************************
682 static DWORD modOpen(WORD wDevID, LPMIDIOPENDESC lpDesc, DWORD dwFlags)
684 TRACE("(%04X, %p, %08lX);\n", wDevID, lpDesc, dwFlags);
685 if (lpDesc == NULL) {
686 WARN("Invalid Parameter !\n");
687 return MMSYSERR_INVALPARAM;
689 if (wDevID >= MODM_NumDevs) {
690 TRACE("MAX_MIDIOUTDRV reached !\n");
691 return MMSYSERR_BADDEVICEID;
693 if (MidiOutDev[wDevID].midiDesc.hMidi != 0) {
694 WARN("device already open !\n");
695 return MMSYSERR_ALLOCATED;
697 if (!MidiOutDev[wDevID].bEnabled) {
698 WARN("device disabled !\n");
699 return MIDIERR_NODEVICE;
701 if ((dwFlags & ~CALLBACK_TYPEMASK) != 0) {
702 WARN("bad dwFlags\n");
703 return MMSYSERR_INVALFLAG;
705 if (!MidiOutDev[wDevID].bEnabled) {
706 TRACE("disabled wDevID\n");
707 return MMSYSERR_NOTENABLED;
710 MidiOutDev[wDevID].lpExtra = 0;
712 switch (MidiOutDev[wDevID].caps.wTechnology) {
716 if (midiOpenSeq(1) < 0) {
717 return MMSYSERR_ALLOCATED;
721 WARN("Technology not supported (yet) %d !\n",
722 MidiOutDev[wDevID].caps.wTechnology);
723 return MMSYSERR_NOTENABLED;
726 MidiOutDev[wDevID].wFlags = HIWORD(dwFlags & CALLBACK_TYPEMASK);
728 MidiOutDev[wDevID].lpQueueHdr = NULL;
729 MidiOutDev[wDevID].dwTotalPlayed = 0;
730 MidiOutDev[wDevID].bufsize = 0x3FFF;
731 MidiOutDev[wDevID].midiDesc = *lpDesc;
733 /* Connect our app port to the device port */
734 if (snd_seq_connect_to(midiSeq, port_out, MidiOutDev[wDevID].addr.client, MidiOutDev[wDevID].addr.port) < 0)
735 return MMSYSERR_NOTENABLED;
737 if (MIDI_NotifyClient(wDevID, MOM_OPEN, 0L, 0L) != MMSYSERR_NOERROR) {
738 WARN("can't notify client !\n");
739 return MMSYSERR_INVALPARAM;
741 TRACE("Successful !\n");
742 return MMSYSERR_NOERROR;
746 /**************************************************************************
747 * modClose [internal]
749 static DWORD modClose(WORD wDevID)
751 int ret = MMSYSERR_NOERROR;
753 TRACE("(%04X);\n", wDevID);
755 if (MidiOutDev[wDevID].midiDesc.hMidi == 0) {
756 WARN("device not opened !\n");
757 return MMSYSERR_ERROR;
759 /* FIXME: should test that no pending buffer is still in the queue for
762 if (midiSeq == NULL) {
763 WARN("can't close !\n");
764 return MMSYSERR_ERROR;
767 switch (MidiOutDev[wDevID].caps.wTechnology) {
771 snd_seq_disconnect_to(midiSeq, port_out, MidiOutDev[wDevID].addr.client, MidiOutDev[wDevID].addr.port);
775 WARN("Technology not supported (yet) %d !\n",
776 MidiOutDev[wDevID].caps.wTechnology);
777 return MMSYSERR_NOTENABLED;
780 HeapFree(GetProcessHeap(), 0, MidiOutDev[wDevID].lpExtra);
781 MidiOutDev[wDevID].lpExtra = 0;
783 MidiOutDev[wDevID].bufsize = 0;
784 if (MIDI_NotifyClient(wDevID, MOM_CLOSE, 0L, 0L) != MMSYSERR_NOERROR) {
785 WARN("can't notify client !\n");
786 ret = MMSYSERR_INVALPARAM;
788 MidiOutDev[wDevID].midiDesc.hMidi = 0;
792 /**************************************************************************
795 static DWORD modData(WORD wDevID, DWORD dwParam)
797 BYTE evt = LOBYTE(LOWORD(dwParam));
798 BYTE d1 = HIBYTE(LOWORD(dwParam));
799 BYTE d2 = LOBYTE(HIWORD(dwParam));
801 TRACE("(%04X, %08lX);\n", wDevID, dwParam);
803 if (wDevID >= MODM_NumDevs) return MMSYSERR_BADDEVICEID;
804 if (!MidiOutDev[wDevID].bEnabled) return MIDIERR_NODEVICE;
806 if (midiSeq == NULL) {
807 WARN("can't play !\n");
808 return MIDIERR_NODEVICE;
810 switch (MidiOutDev[wDevID].caps.wTechnology) {
814 int handled = 1; /* Assume event is handled */
815 snd_seq_event_t event;
816 snd_seq_ev_clear(&event);
817 snd_seq_ev_set_direct(&event);
818 snd_seq_ev_set_source(&event, port_out);
819 snd_seq_ev_set_dest(&event, MidiOutDev[wDevID].addr.client, MidiOutDev[wDevID].addr.port);
821 switch (evt & 0xF0) {
822 case MIDI_CMD_NOTE_OFF:
823 snd_seq_ev_set_noteoff(&event, evt&0x0F, d1, d2);
825 case MIDI_CMD_NOTE_ON:
826 snd_seq_ev_set_noteon(&event, evt&0x0F, d1, d2);
828 case MIDI_CMD_NOTE_PRESSURE:
829 snd_seq_ev_set_keypress(&event, evt&0x0F, d1, d2);
831 case MIDI_CMD_CONTROL:
832 snd_seq_ev_set_controller(&event, evt&0x0F, d1, d2);
834 case MIDI_CMD_BENDER:
835 snd_seq_ev_set_pitchbend(&event, evt&0x0F, ((WORD)d1 << 7) | (WORD)d2);
837 case MIDI_CMD_PGM_CHANGE:
838 snd_seq_ev_set_pgmchange(&event, evt&0x0F, d1);
840 case MIDI_CMD_CHANNEL_PRESSURE:
841 snd_seq_ev_set_chanpress(&event, evt&0x0F, d1);
843 case MIDI_CMD_COMMON_SYSEX:
844 switch (evt & 0x0F) {
845 case 0x00: /* System Exclusive, don't do it on modData,
846 * should require modLongData*/
847 case 0x01: /* Undefined */
848 case 0x04: /* Undefined. */
849 case 0x05: /* Undefined. */
850 case 0x07: /* End of Exclusive. */
851 case 0x09: /* Undefined. */
852 case 0x0D: /* Undefined. */
855 case 0x06: /* Tune Request */
856 case 0x08: /* Timing Clock. */
857 case 0x0A: /* Start. */
858 case 0x0B: /* Continue */
859 case 0x0C: /* Stop */
860 case 0x0E: /* Active Sensing. */
861 /* FIXME: Is this function suitable for these purposes
862 (and also Song Select and Song Position Pointer) */
863 snd_seq_ev_set_sysex(&event, 1, &evt);
865 case 0x0F: /* Reset */
866 /* snd_seq_ev_set_sysex(&event, 1, &evt);
867 this other way may be better */
869 BYTE reset_sysex_seq[] = {MIDI_CMD_COMMON_SYSEX, 0x7e, 0x7f, 0x09, 0x01, 0xf7};
870 snd_seq_ev_set_sysex(&event, sizeof(reset_sysex_seq), reset_sysex_seq);
873 case 0x03: /* Song Select. */
878 snd_seq_ev_set_sysex(&event, sizeof(buf), buf);
881 case 0x02: /* Song Position Pointer. */
887 snd_seq_ev_set_sysex(&event, sizeof(buf), buf);
894 snd_seq_event_output_direct(midiSeq, &event);
898 WARN("Technology not supported (yet) %d !\n",
899 MidiOutDev[wDevID].caps.wTechnology);
900 return MMSYSERR_NOTENABLED;
903 return MMSYSERR_NOERROR;
906 /**************************************************************************
907 * modLongData [internal]
909 static DWORD modLongData(WORD wDevID, LPMIDIHDR lpMidiHdr, DWORD dwSize)
912 LPBYTE lpData, lpNewData = NULL;
913 snd_seq_event_t event;
915 TRACE("(%04X, %p, %08lX);\n", wDevID, lpMidiHdr, dwSize);
917 /* Note: MS doc does not say much about the dwBytesRecorded member of the MIDIHDR structure
918 * but it seems to be used only for midi input.
919 * Taking a look at the WAVEHDR structure (which is quite similar) confirms this assumption.
922 if (wDevID >= MODM_NumDevs) return MMSYSERR_BADDEVICEID;
923 if (!MidiOutDev[wDevID].bEnabled) return MIDIERR_NODEVICE;
925 if (midiSeq == NULL) {
926 WARN("can't play !\n");
927 return MIDIERR_NODEVICE;
930 lpData = lpMidiHdr->lpData;
933 return MIDIERR_UNPREPARED;
934 if (!(lpMidiHdr->dwFlags & MHDR_PREPARED))
935 return MIDIERR_UNPREPARED;
936 if (lpMidiHdr->dwFlags & MHDR_INQUEUE)
937 return MIDIERR_STILLPLAYING;
938 lpMidiHdr->dwFlags &= ~MHDR_DONE;
939 lpMidiHdr->dwFlags |= MHDR_INQUEUE;
941 /* FIXME: MS doc is not 100% clear. Will lpData only contain system exclusive
942 * data, or can it also contain raw MIDI data, to be split up and sent to
944 * If the latest is true, then the following WARNing will fire up
946 if (lpData[0] != 0xF0 || lpData[lpMidiHdr->dwBufferLength - 1] != 0xF7) {
947 WARN("Alledged system exclusive buffer is not correct\n\tPlease report with MIDI file\n");
948 lpNewData = HeapAlloc(GetProcessHeap(), 0, lpMidiHdr->dwBufferLength + 2);
951 TRACE("dwBufferLength=%lu !\n", lpMidiHdr->dwBufferLength);
952 TRACE(" %02X %02X %02X ... %02X %02X %02X\n",
953 lpData[0], lpData[1], lpData[2], lpData[lpMidiHdr->dwBufferLength-3],
954 lpData[lpMidiHdr->dwBufferLength-2], lpData[lpMidiHdr->dwBufferLength-1]);
956 switch (MidiOutDev[wDevID].caps.wTechnology) {
958 /* FIXME: I don't think there is much to do here */
961 if (lpData[0] != 0xF0) {
962 /* Send start of System Exclusive */
965 memcpy(lpNewData, lpData, lpMidiHdr->dwBufferLength);
966 WARN("Adding missing 0xF0 marker at the beginning of "
967 "system exclusive byte stream\n");
969 if (lpData[lpMidiHdr->dwBufferLength-1] != 0xF7) {
970 /* Send end of System Exclusive */
971 memcpy(lpData + len_add, lpData, lpMidiHdr->dwBufferLength);
972 lpNewData[lpMidiHdr->dwBufferLength + len_add - 1] = 0xF0;
974 WARN("Adding missing 0xF7 marker at the end of "
975 "system exclusive byte stream\n");
977 snd_seq_ev_clear(&event);
978 snd_seq_ev_set_direct(&event);
979 snd_seq_ev_set_source(&event, port_out);
980 snd_seq_ev_set_dest(&event, MidiOutDev[wDevID].addr.client, MidiOutDev[wDevID].addr.port);
981 TRACE("client = %d port = %d\n", MidiOutDev[wDevID].addr.client, MidiOutDev[wDevID].addr.port);
982 snd_seq_ev_set_sysex(&event, lpMidiHdr->dwBufferLength + len_add, lpNewData ? lpNewData : lpData);
983 snd_seq_event_output_direct(midiSeq, &event);
985 HeapFree(GetProcessHeap(), 0, lpData);
988 WARN("Technology not supported (yet) %d !\n",
989 MidiOutDev[wDevID].caps.wTechnology);
990 return MMSYSERR_NOTENABLED;
993 lpMidiHdr->dwFlags &= ~MHDR_INQUEUE;
994 lpMidiHdr->dwFlags |= MHDR_DONE;
995 if (MIDI_NotifyClient(wDevID, MOM_DONE, (DWORD)lpMidiHdr, 0L) != MMSYSERR_NOERROR) {
996 WARN("can't notify client !\n");
997 return MMSYSERR_INVALPARAM;
999 return MMSYSERR_NOERROR;
1002 /**************************************************************************
1003 * modPrepare [internal]
1005 static DWORD modPrepare(WORD wDevID, LPMIDIHDR lpMidiHdr, DWORD dwSize)
1007 TRACE("(%04X, %p, %08lX);\n", wDevID, lpMidiHdr, dwSize);
1009 if (midiSeq == NULL) {
1010 WARN("can't prepare !\n");
1011 return MMSYSERR_NOTENABLED;
1014 /* MS doc says that dwFlags must be set to zero, but (kinda funny) MS mciseq drivers
1015 * asks to prepare MIDIHDR which dwFlags != 0.
1016 * So at least check for the inqueue flag
1018 if (dwSize < sizeof(MIDIHDR) || lpMidiHdr == 0 ||
1019 lpMidiHdr->lpData == 0 || (lpMidiHdr->dwFlags & MHDR_INQUEUE) != 0 ||
1020 lpMidiHdr->dwBufferLength >= 0x10000ul) {
1021 WARN("%p %p %08lx %d/%ld\n", lpMidiHdr, lpMidiHdr->lpData,
1022 lpMidiHdr->dwFlags, sizeof(MIDIHDR), dwSize);
1023 return MMSYSERR_INVALPARAM;
1026 lpMidiHdr->lpNext = 0;
1027 lpMidiHdr->dwFlags |= MHDR_PREPARED;
1028 lpMidiHdr->dwFlags &= ~MHDR_DONE;
1029 return MMSYSERR_NOERROR;
1032 /**************************************************************************
1033 * modUnprepare [internal]
1035 static DWORD modUnprepare(WORD wDevID, LPMIDIHDR lpMidiHdr, DWORD dwSize)
1037 TRACE("(%04X, %p, %08lX);\n", wDevID, lpMidiHdr, dwSize);
1039 if (midiSeq == NULL) {
1040 WARN("can't unprepare !\n");
1041 return MMSYSERR_NOTENABLED;
1044 if (dwSize < sizeof(MIDIHDR) || lpMidiHdr == 0)
1045 return MMSYSERR_INVALPARAM;
1046 if (lpMidiHdr->dwFlags & MHDR_INQUEUE)
1047 return MIDIERR_STILLPLAYING;
1048 lpMidiHdr->dwFlags &= ~MHDR_PREPARED;
1049 return MMSYSERR_NOERROR;
1052 /**************************************************************************
1053 * modReset [internal]
1055 static DWORD modReset(WORD wDevID)
1059 TRACE("(%04X);\n", wDevID);
1061 if (wDevID >= MODM_NumDevs) return MMSYSERR_BADDEVICEID;
1062 if (!MidiOutDev[wDevID].bEnabled) return MIDIERR_NODEVICE;
1064 /* stop all notes */
1065 /* FIXME: check if 0x78B0 is channel dependent or not. I coded it so that
1066 * it's channel dependent...
1068 for (chn = 0; chn < 16; chn++) {
1069 /* turn off every note */
1070 modData(wDevID, 0x7800 | MIDI_CMD_CONTROL | chn);
1071 /* remove sustain on all channels */
1072 modData(wDevID, (MIDI_CTL_SUSTAIN << 8) | MIDI_CMD_CONTROL | chn);
1074 /* FIXME: the LongData buffers must also be returned to the app */
1075 return MMSYSERR_NOERROR;
1079 /**************************************************************************
1080 * ALSA_AddMidiPort [internal]
1082 * Helper for ALSA_MidiInit
1084 static void ALSA_AddMidiPort(snd_seq_client_info_t* cinfo, snd_seq_port_info_t* pinfo, int cap, int type)
1086 if (cap & SND_SEQ_PORT_CAP_WRITE) {
1087 TRACE("OUT (%d:%s:%s:%d:%s:%x)\n",snd_seq_client_info_get_client(cinfo),
1088 snd_seq_client_info_get_name(cinfo),
1089 snd_seq_client_info_get_type(cinfo) == SND_SEQ_USER_CLIENT ? "user" : "kernel",
1090 snd_seq_port_info_get_port(pinfo),
1091 snd_seq_port_info_get_name(pinfo),
1094 if (MODM_NumDevs >= MAX_MIDIOUTDRV)
1099 memcpy(&MidiOutDev[MODM_NumDevs].addr, snd_seq_port_info_get_addr(pinfo), sizeof(snd_seq_addr_t));
1101 /* Manufac ID. We do not have access to this with soundcard.h
1102 * Does not seem to be a problem, because in mmsystem.h only
1103 * Microsoft's ID is listed.
1105 MidiOutDev[MODM_NumDevs].caps.wMid = 0x00FF;
1106 MidiOutDev[MODM_NumDevs].caps.wPid = 0x0001; /* FIXME Product ID */
1107 /* Product Version. We simply say "1" */
1108 MidiOutDev[MODM_NumDevs].caps.vDriverVersion = 0x001;
1109 MidiOutDev[MODM_NumDevs].caps.wChannelMask = 0xFFFF;
1111 /* FIXME Do we have this information?
1112 * Assuming the soundcards can handle
1113 * MIDICAPS_VOLUME and MIDICAPS_LRVOLUME but
1114 * not MIDICAPS_CACHE.
1116 MidiOutDev[MODM_NumDevs].caps.dwSupport = MIDICAPS_VOLUME|MIDICAPS_LRVOLUME;
1117 MultiByteToWideChar(CP_ACP, 0, snd_seq_client_info_get_name(cinfo), -1,
1118 MidiOutDev[MODM_NumDevs].caps.szPname,
1119 sizeof(MidiOutDev[MODM_NumDevs].caps.szPname) / sizeof(WCHAR));
1121 MidiOutDev[MODM_NumDevs].caps.wTechnology = MIDI_AlsaToWindowsDeviceType(type);
1122 MidiOutDev[MODM_NumDevs].caps.wVoices = 16;
1124 /* FIXME Is it possible to know the maximum
1125 * number of simultaneous notes of a soundcard ?
1126 * I believe we don't have this information, but
1127 * it's probably equal or more than wVoices
1129 MidiOutDev[MODM_NumDevs].caps.wNotes = 16;
1130 MidiOutDev[MODM_NumDevs].bEnabled = TRUE;
1132 TRACE("MidiOut[%d]\tname='%s' techn=%d voices=%d notes=%d chnMsk=%04x support=%ld\n"
1133 "\tALSA info: midi dev-type=%lx, capa=%lx\n",
1134 MODM_NumDevs, wine_dbgstr_w(MidiOutDev[MODM_NumDevs].caps.szPname),
1135 MidiOutDev[MODM_NumDevs].caps.wTechnology,
1136 MidiOutDev[MODM_NumDevs].caps.wVoices, MidiOutDev[MODM_NumDevs].caps.wNotes,
1137 MidiOutDev[MODM_NumDevs].caps.wChannelMask, MidiOutDev[MODM_NumDevs].caps.dwSupport,
1138 (long)type, (long)0);
1142 if (cap & SND_SEQ_PORT_CAP_READ) {
1143 TRACE("IN (%d:%s:%s:%d:%s:%x)\n",snd_seq_client_info_get_client(cinfo),
1144 snd_seq_client_info_get_name(cinfo),
1145 snd_seq_client_info_get_type(cinfo) == SND_SEQ_USER_CLIENT ? "user" : "kernel",
1146 snd_seq_port_info_get_port(pinfo),
1147 snd_seq_port_info_get_name(pinfo),
1150 if (MIDM_NumDevs >= MAX_MIDIINDRV)
1155 memcpy(&MidiInDev[MIDM_NumDevs].addr, snd_seq_port_info_get_addr(pinfo), sizeof(snd_seq_addr_t));
1157 /* Manufac ID. We do not have access to this with soundcard.h
1158 * Does not seem to be a problem, because in mmsystem.h only
1159 * Microsoft's ID is listed.
1161 MidiInDev[MIDM_NumDevs].caps.wMid = 0x00FF;
1162 MidiInDev[MIDM_NumDevs].caps.wPid = 0x0001; /* FIXME Product ID */
1163 /* Product Version. We simply say "1" */
1164 MidiInDev[MIDM_NumDevs].caps.vDriverVersion = 0x001;
1166 /* FIXME Do we have this information?
1167 * Assuming the soundcards can handle
1168 * MIDICAPS_VOLUME and MIDICAPS_LRVOLUME but
1169 * not MIDICAPS_CACHE.
1171 MidiInDev[MIDM_NumDevs].caps.dwSupport = MIDICAPS_VOLUME|MIDICAPS_LRVOLUME;
1172 MultiByteToWideChar(CP_ACP, 0, snd_seq_client_info_get_name(cinfo), -1,
1173 MidiInDev[MIDM_NumDevs].caps.szPname,
1174 sizeof(MidiInDev[MIDM_NumDevs].caps.szPname) / sizeof(WCHAR));
1175 MidiInDev[MIDM_NumDevs].state = 0;
1177 TRACE("MidiIn [%d]\tname='%s' support=%ld\n"
1178 "\tALSA info: midi dev-type=%lx, capa=%lx\n",
1179 MIDM_NumDevs, wine_dbgstr_w(MidiInDev[MIDM_NumDevs].caps.szPname),
1180 MidiInDev[MIDM_NumDevs].caps.dwSupport,
1181 (long)type, (long)0);
1187 #endif /* HAVE_ALSA */
1190 /*======================================================================*
1191 * MIDI entry points *
1192 *======================================================================*/
1194 /**************************************************************************
1195 * ALSA_MidiInit [internal]
1197 * Initializes the MIDI devices information variables
1199 LONG ALSA_MidiInit(void)
1202 static BOOL bInitDone = FALSE;
1203 snd_seq_client_info_t *cinfo;
1204 snd_seq_port_info_t *pinfo;
1209 TRACE("Initializing the MIDI variables.\n");
1212 /* try to open device */
1213 if (midiOpenSeq(0) == -1) {
1217 #if 0 /* Debug purpose */
1218 snd_lib_error_set_handler(error_handler);
1221 snd_seq_client_info_alloca(&cinfo);
1222 snd_seq_port_info_alloca(&pinfo);
1224 /* First, search for all internal midi devices */
1225 snd_seq_client_info_set_client(cinfo, -1);
1226 while(snd_seq_query_next_client(midiSeq, cinfo) >= 0) {
1227 snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo));
1228 snd_seq_port_info_set_port(pinfo, -1);
1229 while (snd_seq_query_next_port(midiSeq, pinfo) >= 0) {
1230 int cap = snd_seq_port_info_get_capability(pinfo);
1231 int type = snd_seq_port_info_get_type(pinfo);
1232 if (type != SND_SEQ_PORT_TYPE_MIDI_GENERIC)
1233 ALSA_AddMidiPort(cinfo, pinfo, cap, type);
1237 /* Second, search for all external ports */
1238 snd_seq_client_info_set_client(cinfo, -1);
1239 while(snd_seq_query_next_client(midiSeq, cinfo) >= 0) {
1240 snd_seq_port_info_set_client(pinfo, snd_seq_client_info_get_client(cinfo));
1241 snd_seq_port_info_set_port(pinfo, -1);
1242 while (snd_seq_query_next_port(midiSeq, pinfo) >= 0) {
1243 int cap = snd_seq_port_info_get_capability(pinfo);
1244 int type = snd_seq_port_info_get_type(pinfo);
1245 if (type == SND_SEQ_PORT_TYPE_MIDI_GENERIC)
1246 ALSA_AddMidiPort(cinfo, pinfo, cap, type);
1250 /* close file and exit */
1258 /**************************************************************************
1259 * midMessage (WINEOSS.4)
1261 DWORD WINAPI ALSA_midMessage(UINT wDevID, UINT wMsg, DWORD dwUser,
1262 DWORD dwParam1, DWORD dwParam2)
1264 TRACE("(%04X, %04X, %08lX, %08lX, %08lX);\n",
1265 wDevID, wMsg, dwUser, dwParam1, dwParam2);
1272 /* FIXME: Pretend this is supported */
1275 return midOpen(wDevID, (LPMIDIOPENDESC)dwParam1, dwParam2);
1277 return midClose(wDevID);
1278 case MIDM_ADDBUFFER:
1279 return midAddBuffer(wDevID, (LPMIDIHDR)dwParam1, dwParam2);
1281 return midPrepare(wDevID, (LPMIDIHDR)dwParam1, dwParam2);
1282 case MIDM_UNPREPARE:
1283 return midUnprepare(wDevID, (LPMIDIHDR)dwParam1, dwParam2);
1284 case MIDM_GETDEVCAPS:
1285 return midGetDevCaps(wDevID, (LPMIDIINCAPSW)dwParam1,dwParam2);
1286 case MIDM_GETNUMDEVS:
1287 return MIDM_NumDevs;
1289 return midReset(wDevID);
1291 return midStart(wDevID);
1293 return midStop(wDevID);
1296 TRACE("Unsupported message\n");
1298 return MMSYSERR_NOTSUPPORTED;
1301 /**************************************************************************
1302 * modMessage (WINEOSS.5)
1304 DWORD WINAPI ALSA_modMessage(UINT wDevID, UINT wMsg, DWORD dwUser,
1305 DWORD dwParam1, DWORD dwParam2)
1307 TRACE("(%04X, %04X, %08lX, %08lX, %08lX);\n",
1308 wDevID, wMsg, dwUser, dwParam1, dwParam2);
1316 /* FIXME: Pretend this is supported */
1319 return modOpen(wDevID, (LPMIDIOPENDESC)dwParam1, dwParam2);
1321 return modClose(wDevID);
1323 return modData(wDevID, dwParam1);
1325 return modLongData(wDevID, (LPMIDIHDR)dwParam1, dwParam2);
1327 return modPrepare(wDevID, (LPMIDIHDR)dwParam1, dwParam2);
1328 case MODM_UNPREPARE:
1329 return modUnprepare(wDevID, (LPMIDIHDR)dwParam1, dwParam2);
1330 case MODM_GETDEVCAPS:
1331 return modGetDevCaps(wDevID, (LPMIDIOUTCAPSW)dwParam1, dwParam2);
1332 case MODM_GETNUMDEVS:
1333 return MODM_NumDevs;
1334 case MODM_GETVOLUME:
1336 case MODM_SETVOLUME:
1339 return modReset(wDevID);
1342 TRACE("Unsupported message\n");
1344 return MMSYSERR_NOTSUPPORTED;
1347 /*-----------------------------------------------------------------------*/