Added mappings for a few messages.
[wine] / dlls / winmm / midimap / midimap.c
1 /* -*- tab-width: 8; c-basic-offset: 4 -*- */
2 /*                                 
3  * Wine Midi mapper driver
4  *
5  * Copyright    1999, 2000, 2001 Eric Pouech
6  *
7  * TODO:
8  *      notification has to be implemented
9  *      IDF file loading
10  */
11
12 #include <string.h>
13 #include <stdlib.h>
14 #include "windef.h"
15 #include "winbase.h"
16 #include "wingdi.h"
17 #include "winuser.h"
18 #include "mmddk.h"
19 #include "winreg.h"
20 #include "debugtools.h"
21
22 /*
23  * Here's how Windows stores the midiOut mapping information.
24  *
25  * Full form (in HKU) is:
26  *
27  * [Software\\Microsoft\\Windows\\CurrentVersion\\Multimedia\\MIDIMap] 988836060
28  * "AutoScheme"=dword:00000000
29  * "ConfigureCount"=dword:00000004
30  * "CurrentInstrument"="Wine OSS midi"
31  * "CurrentScheme"="epp"
32  * "DriverList"=""
33  * "UseScheme"=dword:00000000
34  *
35  * AutoScheme:          ?
36  * CurrentInstrument:   name of midiOut device to use when UseScheme is 0. Wine uses an extension
37  *                      of the form #n to link to n'th midiOut device of the system
38  * CurrentScheme:       when UseScheme is non null, it's the scheme to use (see below)
39  * DriverList:          ?
40  * UseScheme:           trigger for simple/complex mapping
41  *
42  * A scheme is defined (in HKLM) as:
43  *
44  * [System\\CurrentControlSet\\Control\\MediaProperties\\PrivateProperties\\Midi\\Schemes\\<nameScheme>]
45  * <nameScheme>:        one key for each defined scheme (system wide)
46  * under each one of these <nameScheme> keys, there's:
47  * [...\\<nameScheme>\\<idxDevice>]
48  * "Channels"="<bitMask>"
49  * (the default value of this key also refers to the name of the device).
50  *
51  * this defines, for each midiOut device (identified by its index in <idxDevice>), which 
52  * channels have to be mapped onto it. The <bitMask> defines the channels (from 0 to 15)
53  * will be mapped (mapping occurs for channel <ch> if bit <ch> is set in <bitMask>
54  *
55  * Further mapping information can also be defined in:
56  * [System\\CurrentControlSet\\Control\\MediaProperties\\PrivateProperties\\Midi\\Ports\\<nameDevice>\\Instruments\\<idx>]
57  * "Definition"="<.idf file>"
58  * "FriendlyName"="#for .idx file#"
59  * "Port"="<idxPort>"
60  *
61  * This last part isn't implemented (.idf file support).
62  */
63
64 DEFAULT_DEBUG_CHANNEL(msacm);
65
66 typedef struct tagMIDIOUTPORT 
67 {
68     char                name[MAXPNAMELEN];
69     int                 loaded;
70     HMIDI               hMidi;
71     unsigned short      uDevID;
72     LPBYTE              lpbPatch;
73     unsigned int        aChn[16];
74 } MIDIOUTPORT;
75
76 typedef struct tagMIDIMAPDATA 
77 {
78     struct tagMIDIMAPDATA*      self;
79     MIDIOUTPORT*        ChannelMap[16];
80 } MIDIMAPDATA;
81
82 static  MIDIOUTPORT*    midiOutPorts;
83 static  unsigned        numMidiOutPorts;
84
85 static  BOOL    MIDIMAP_IsBadData(MIDIMAPDATA* mm)
86 {
87     if (!IsBadReadPtr(mm, sizeof(MIDIMAPDATA)) && mm->self == mm)
88         return FALSE;
89     TRACE("Bad midimap data (%p)\n", mm);
90     return TRUE;
91 }
92
93 static BOOL     MIDIMAP_FindPort(const char* name, unsigned* dev)
94 {
95     for (*dev = 0; *dev < numMidiOutPorts; (*dev)++) 
96     {
97         TRACE("%s\n", midiOutPorts[*dev].name);
98         if (strcmp(midiOutPorts[*dev].name, name) == 0)
99             return TRUE;
100     }
101     /* try the form #nnn */
102     if (*name == '#' && isdigit(name[1])) 
103     {
104         *dev = atoi(name + 1);
105         if (*dev < numMidiOutPorts)
106             return TRUE;
107     }
108     return FALSE;
109 }
110
111 static BOOL     MIDIMAP_LoadSettingsDefault(MIDIMAPDATA* mom, const char* port)
112 {
113     unsigned i, dev = 0;
114
115     if (port != NULL && !MIDIMAP_FindPort(port, &dev)) 
116     {
117         ERR("Registry glitch: couldn't find midi out (%s)\n", port);
118         dev = 0;
119     }
120
121     /* this is necessary when no midi out ports are present */
122     if (dev >= numMidiOutPorts)
123         return FALSE;
124     /* sets default */
125     for (i = 0; i < 16; i++) mom->ChannelMap[i] = &midiOutPorts[dev];
126
127     return TRUE;
128 }
129
130 static BOOL     MIDIMAP_LoadSettingsScheme(MIDIMAPDATA* mom, const char* scheme)
131 {
132     HKEY        hSchemesKey, hKey, hPortKey;
133     unsigned    i, idx, dev;
134     char        buffer[256], port[256];
135     DWORD       type, size, mask;
136
137     for (i = 0; i < 16; i++)    mom->ChannelMap[i] = NULL;
138
139     if (RegOpenKeyA(HKEY_LOCAL_MACHINE, 
140                     "System\\CurrentControlSet\\Control\\MediaProperties\\PrivateProperties\\Midi\\Schemes", 
141                     &hSchemesKey)) 
142     {
143         return FALSE;
144     }
145     if (RegOpenKeyA(hSchemesKey, scheme, &hKey)) 
146     {
147         RegCloseKey(hSchemesKey);
148         return FALSE;
149     }
150
151     for (idx = 0; !RegEnumKeyA(hKey, idx, buffer, sizeof(buffer)); idx++) 
152     {
153         if (RegOpenKeyA(hKey, buffer, &hPortKey)) continue;
154
155         size = sizeof(port);
156         if (RegQueryValueExA(hPortKey, NULL, 0, &type, port, &size)) continue;
157
158         if (!MIDIMAP_FindPort(port, &dev)) continue;
159
160         size = sizeof(mask);
161         if (RegQueryValueExA(hPortKey, "Channels", 0, &type, (void*)&mask, &size))
162             continue;
163
164         for (i = 0; i < 16; i++) 
165         {
166             if (mask & (1 << i)) 
167             {
168                 if (mom->ChannelMap[i]) 
169                     ERR("Quirks in registry, channel %u is mapped twice\n", i);
170                 mom->ChannelMap[i] = &midiOutPorts[dev];
171             }
172         }
173     }
174
175     RegCloseKey(hSchemesKey);
176     RegCloseKey(hKey);
177     
178     return TRUE;
179 }
180
181 static BOOL     MIDIMAP_LoadSettings(MIDIMAPDATA* mom)
182 {
183     HKEY        hKey;
184     BOOL        ret;
185
186     if (RegOpenKeyA(HKEY_CURRENT_USER, 
187                     "Software\\Microsoft\\Windows\\CurrentVersion\\Multimedia\\MIDIMap", &hKey)) 
188     {
189         ret = MIDIMAP_LoadSettingsDefault(mom, NULL);
190     }
191     else 
192     {
193         DWORD   type, size, out;
194         char    buffer[256];
195     
196         ret = 2;
197         size = sizeof(out);
198         if (!RegQueryValueExA(hKey, "UseScheme", 0, &type, (void*)&out, &size) && out) 
199         {
200             size = sizeof(buffer);
201             if (!RegQueryValueExA(hKey, "CurrentScheme", 0, &type, buffer, &size)) 
202             {
203                 if (!(ret = MIDIMAP_LoadSettingsScheme(mom, buffer)))
204                     ret = MIDIMAP_LoadSettingsDefault(mom, NULL);
205             }
206             else 
207             {
208                 ERR("Wrong registry: UseScheme is active, but no CurrentScheme found\n");
209             }
210         }
211         if (ret == 2) 
212         {
213             size = sizeof(buffer);
214             if (!RegQueryValueExA(hKey, "CurrentInstrument", 0, &type, buffer, &size) && *buffer) 
215             {
216                 ret = MIDIMAP_LoadSettingsDefault(mom, buffer);
217             }
218             else 
219             {
220                 ret = MIDIMAP_LoadSettingsDefault(mom, NULL);
221             }
222         }
223     }
224     RegCloseKey(hKey);
225
226     if (ret && TRACE_ON(msacm)) 
227     {
228         unsigned        i;
229
230         for (i = 0; i < 16; i++) 
231         {
232             TRACE("chnMap[% 2d] => %d\n", 
233                   i, mom->ChannelMap[i] ? mom->ChannelMap[i]->uDevID : -1);
234         }
235     }
236     return ret;
237 }
238
239 static  DWORD   modOpen(LPDWORD lpdwUser, LPMIDIOPENDESC lpDesc, DWORD dwFlags)
240 {
241     MIDIMAPDATA*        mom = HeapAlloc(GetProcessHeap(), 0, sizeof(MIDIMAPDATA));
242
243     TRACE("(%p %p %08lx\n", lpdwUser, lpDesc, dwFlags);
244
245     if (!mom) return MMSYSERR_NOMEM;
246
247     if (MIDIMAP_LoadSettings(mom)) 
248     {
249         *lpdwUser = (DWORD)mom;
250         mom->self = mom;
251
252         return MMSYSERR_NOERROR;
253     }
254     HeapFree(GetProcessHeap(), 0, mom);
255     return MIDIERR_INVALIDSETUP;
256 }
257
258 static  DWORD   modClose(MIDIMAPDATA* mom)
259 {
260     UINT        i;
261     DWORD       ret = MMSYSERR_NOERROR;
262
263     if (MIDIMAP_IsBadData(mom))         return MMSYSERR_ERROR;
264
265     for (i = 0; i < 16; i++) 
266     {
267         DWORD   t;
268         if (mom->ChannelMap[i] && mom->ChannelMap[i]->loaded > 0) 
269         {
270             t = midiOutClose(mom->ChannelMap[i]->hMidi);
271             if (t == MMSYSERR_NOERROR) 
272             {
273                 mom->ChannelMap[i]->loaded = 0;
274                 mom->ChannelMap[i]->hMidi = 0;
275             }
276             else if (ret == MMSYSERR_NOERROR)
277                 ret = t;
278         }
279     }
280     if (ret == MMSYSERR_NOERROR)
281         HeapFree(GetProcessHeap(), 0, mom);
282     return ret;
283 }
284
285 static  DWORD   modLongData(MIDIMAPDATA* mom, LPMIDIHDR lpMidiHdr, DWORD dwParam2)
286 {
287     WORD        chn;
288     DWORD       ret = MMSYSERR_NOERROR;
289     MIDIHDR     mh;
290
291     if (MIDIMAP_IsBadData(mom))
292         return MMSYSERR_ERROR;
293
294     mh = *lpMidiHdr;
295     for (chn = 0; chn < 16; chn++) 
296     {
297         if (mom->ChannelMap[chn] && mom->ChannelMap[chn]->loaded > 0) 
298         {
299             mh.dwFlags = 0;
300             midiOutPrepareHeader(mom->ChannelMap[chn]->hMidi, &mh, sizeof(mh));
301             ret = midiOutLongMsg(mom->ChannelMap[chn]->hMidi, &mh, sizeof(mh));
302             midiOutUnprepareHeader(mom->ChannelMap[chn]->hMidi, &mh, sizeof(mh));
303             if (ret != MMSYSERR_NOERROR) break;
304         }
305     }
306     return ret;
307 }
308
309 static  DWORD   modData(MIDIMAPDATA* mom, DWORD dwParam)
310 {
311     BYTE        lb = LOBYTE(LOWORD(dwParam));
312     WORD        chn = lb & 0x0F;
313     DWORD       ret = MMSYSERR_NOERROR;
314
315     if (MIDIMAP_IsBadData(mom))
316         return MMSYSERR_ERROR;
317
318     if (!mom->ChannelMap[chn]) return MMSYSERR_NOERROR;
319
320     switch (lb & 0xF0) 
321     {
322     case 0x80:
323     case 0x90:
324     case 0xA0:
325     case 0xB0:
326     case 0xC0:
327     case 0xD0:
328     case 0xE0:
329         if (mom->ChannelMap[chn]->loaded == 0) 
330         {
331             if (midiOutOpen(&mom->ChannelMap[chn]->hMidi, mom->ChannelMap[chn]->uDevID,
332                             0L, 0L, CALLBACK_NULL) == MMSYSERR_NOERROR)
333                 mom->ChannelMap[chn]->loaded = 1;
334             else
335                 mom->ChannelMap[chn]->loaded = -1;
336             /* FIXME: should load here the IDF midi data... and allow channel and
337              * patch mappings 
338              */
339         }
340         if (mom->ChannelMap[chn]->loaded > 0) 
341         {
342             /* change channel */
343             dwParam &= ~0x0F;
344             dwParam |= mom->ChannelMap[chn]->aChn[chn];
345
346             if ((LOBYTE(LOWORD(dwParam)) & 0xF0) == 0xC0 /* program change */ &&
347                 mom->ChannelMap[chn]->lpbPatch) 
348             {
349                 BYTE patch = HIBYTE(LOWORD(dwParam));
350
351                 /* change patch */
352                 dwParam &= ~0x0000FF00;
353                 dwParam |= mom->ChannelMap[chn]->lpbPatch[patch];
354             }
355             ret = midiOutShortMsg(mom->ChannelMap[chn]->hMidi, dwParam);
356         }
357         break;
358     case 0xF0:
359         for (chn = 0; chn < 16; chn++) 
360         {
361             if (mom->ChannelMap[chn]->loaded > 0)
362                 ret = midiOutShortMsg(mom->ChannelMap[chn]->hMidi, dwParam);
363         }
364         break;
365     default:
366         FIXME("ooch %lu\n", dwParam);
367     }
368
369     return ret;
370 }
371
372 static  DWORD   modPrepare(MIDIMAPDATA* mom, LPMIDIHDR lpMidiHdr, DWORD dwParam2)
373 {
374     if (MIDIMAP_IsBadData(mom)) return MMSYSERR_ERROR;
375     if (lpMidiHdr->dwFlags & (MHDR_ISSTRM|MHDR_PREPARED))
376         return MMSYSERR_INVALPARAM;
377
378     lpMidiHdr->dwFlags |= MHDR_PREPARED;
379     return MMSYSERR_NOERROR;
380 }
381
382 static  DWORD   modUnprepare(MIDIMAPDATA* mom, LPMIDIHDR lpMidiHdr, DWORD dwParam2)
383 {
384     if (MIDIMAP_IsBadData(mom)) return MMSYSERR_ERROR;
385     if ((lpMidiHdr->dwFlags & MHDR_ISSTRM) || !(lpMidiHdr->dwFlags & MHDR_PREPARED))
386         return MMSYSERR_INVALPARAM;
387
388     lpMidiHdr->dwFlags &= ~MHDR_PREPARED;
389     return MMSYSERR_NOERROR;
390 }
391
392 static  DWORD   modGetDevCaps(UINT wDevID, MIDIMAPDATA* mom, LPMIDIOUTCAPSA lpMidiCaps, DWORD size)
393 {
394     lpMidiCaps->wMid = 0x00FF;
395     lpMidiCaps->wPid = 0x0001;
396     lpMidiCaps->vDriverVersion = 0x0100;
397     strcpy(lpMidiCaps->szPname, "Wine midi out mapper");
398     lpMidiCaps->wTechnology = MOD_MAPPER;
399     lpMidiCaps->wVoices = 0;
400     lpMidiCaps->wNotes = 0;
401     lpMidiCaps->wChannelMask = 0xFFFF;
402     lpMidiCaps->dwSupport = 0L;
403
404     return MMSYSERR_NOERROR;
405 }
406
407 static  DWORD   modReset(MIDIMAPDATA* mom)
408 {
409     WORD        chn;
410     DWORD       ret = MMSYSERR_NOERROR;
411
412     if (MIDIMAP_IsBadData(mom))
413         return MMSYSERR_ERROR;
414
415     for (chn = 0; chn < 16; chn++) 
416     {
417         if (mom->ChannelMap[chn] && mom->ChannelMap[chn]->loaded > 0) 
418         {
419             ret = midiOutReset(mom->ChannelMap[chn]->hMidi);
420             if (ret != MMSYSERR_NOERROR) break;
421         }
422     }
423     return ret;
424 }
425
426 /**************************************************************************
427  *                              modMessage (MIDIMAP.@)
428  */
429 DWORD WINAPI MIDIMAP_modMessage(UINT wDevID, UINT wMsg, DWORD dwUser, 
430                                 DWORD dwParam1, DWORD dwParam2)
431 {
432     TRACE("(%u, %04X, %08lX, %08lX, %08lX);\n",
433           wDevID, wMsg, dwUser, dwParam1, dwParam2);
434     
435     switch (wMsg) 
436     {
437     case DRVM_INIT:
438     case DRVM_EXIT:
439     case DRVM_ENABLE:
440     case DRVM_DISABLE:
441         /* FIXME: Pretend this is supported */
442         return 0;
443
444     case MODM_OPEN:             return modOpen          ((LPDWORD)dwUser,      (LPMIDIOPENDESC)dwParam1,dwParam2);
445     case MODM_CLOSE:            return modClose         ((MIDIMAPDATA*)dwUser);
446
447     case MODM_DATA:             return modData          ((MIDIMAPDATA*)dwUser, dwParam1);
448     case MODM_LONGDATA:         return modLongData      ((MIDIMAPDATA*)dwUser, (LPMIDIHDR)dwParam1,     dwParam2);
449     case MODM_PREPARE:          return modPrepare       ((MIDIMAPDATA*)dwUser, (LPMIDIHDR)dwParam1,     dwParam2);
450     case MODM_UNPREPARE:        return modUnprepare     ((MIDIMAPDATA*)dwUser, (LPMIDIHDR)dwParam1,     dwParam2);
451     case MODM_RESET:            return modReset         ((MIDIMAPDATA*)dwUser);
452
453     case MODM_GETDEVCAPS:       return modGetDevCaps    (wDevID, (MIDIMAPDATA*)dwUser, (LPMIDIOUTCAPSA)dwParam1,dwParam2);
454     case MODM_GETNUMDEVS:       return 1;
455     case MODM_GETVOLUME:        return MMSYSERR_NOTSUPPORTED;
456     case MODM_SETVOLUME:        return MMSYSERR_NOTSUPPORTED;
457     default:
458         FIXME("unknown message %d!\n", wMsg);
459     }
460     return MMSYSERR_NOTSUPPORTED;
461 }
462
463 /*======================================================================*
464  *                  Driver part                                         *
465  *======================================================================*/
466
467 /**************************************************************************
468  *                              MIDIMAP_drvOpen                 [internal]      
469  */
470 static  DWORD   MIDIMAP_drvOpen(LPSTR str)
471 {
472     MIDIOUTCAPSA        moc;
473     unsigned            dev, i;
474
475     if (midiOutPorts)
476         return 0;
477     
478     numMidiOutPorts = midiOutGetNumDevs();
479     midiOutPorts = HeapAlloc(GetProcessHeap(), 0, 
480                              numMidiOutPorts * sizeof(MIDIOUTPORT));
481     for (dev = 0; dev < numMidiOutPorts; dev++) 
482     {
483         if (midiOutGetDevCapsA((HMIDIOUT)dev, &moc, sizeof(moc)) == 0L) 
484         {
485             strcpy(midiOutPorts[dev].name, moc.szPname);
486             midiOutPorts[dev].loaded = 0;
487             midiOutPorts[dev].hMidi = 0;
488             midiOutPorts[dev].uDevID = 0;
489             midiOutPorts[dev].lpbPatch = NULL;
490             for (i = 0; i < 16; i++)
491                 midiOutPorts[dev].aChn[i] = i;
492         }
493         else 
494         {
495             midiOutPorts[dev].loaded = -1;
496         }
497     }
498
499     return 1;
500 }
501
502 /**************************************************************************
503  *                              MIDIMAP_drvClose                [internal]      
504  */
505 static  DWORD   MIDIMAP_drvClose(DWORD dwDevID)
506 {
507     if (midiOutPorts) 
508     {
509         HeapFree(GetProcessHeap(), 0, midiOutPorts);
510         midiOutPorts = NULL;
511         return 1;
512     }
513     return 0;
514 }
515
516 /**************************************************************************
517  *                              DriverProc (MIDIMAP.@)
518  */
519 LONG CALLBACK   MIDIMAP_DriverProc(DWORD dwDevID, HDRVR hDriv, DWORD wMsg, 
520                                    DWORD dwParam1, DWORD dwParam2)
521 {
522 /* EPP     TRACE("(%08lX, %04X, %08lX, %08lX, %08lX)\n",  */
523 /* EPP    dwDevID, hDriv, wMsg, dwParam1, dwParam2); */
524     
525     switch (wMsg) 
526     {
527     case DRV_LOAD:              return 1;
528     case DRV_FREE:              return 1;
529     case DRV_OPEN:              return MIDIMAP_drvOpen((LPSTR)dwParam1);
530     case DRV_CLOSE:             return MIDIMAP_drvClose(dwDevID);
531     case DRV_ENABLE:            return 1;
532     case DRV_DISABLE:           return 1;
533     case DRV_QUERYCONFIGURE:    return 1;
534     case DRV_CONFIGURE:         MessageBoxA(0, "MIDIMAP MultiMedia Driver !", "OSS Driver", MB_OK);     return 1;
535     case DRV_INSTALL:           return DRVCNF_RESTART;
536     case DRV_REMOVE:            return DRVCNF_RESTART;
537     default:
538         return DefDriverProc(dwDevID, hDriv, wMsg, dwParam1, dwParam2);
539     }
540 }
541
542