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