2 * MIDI driver for macOS (unixlib)
4 * Copyright 1994 Martin Ayotte
5 * Copyright 1998 Luiz Otavio L. Zorzella
6 * Copyright 1998, 1999 Eric POUECH
7 * Copyright 2005, 2006 Emmanuel Maillard
8 * Copyright 2021 Huw Davies
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
30 #define ULONG __carbon_ULONG
31 #define E_INVALIDARG __carbon_E_INVALIDARG
32 #define E_OUTOFMEMORY __carbon_E_OUTOFMEMORY
33 #define E_HANDLE __carbon_E_HANDLE
34 #define E_ACCESSDENIED __carbon_E_ACCESSDENIED
35 #define E_UNEXPECTED __carbon_E_UNEXPECTED
36 #define E_FAIL __carbon_E_FAIL
37 #define E_ABORT __carbon_E_ABORT
38 #define E_POINTER __carbon_E_POINTER
39 #define E_NOINTERFACE __carbon_E_NOINTERFACE
40 #define E_NOTIMPL __carbon_E_NOTIMPL
41 #define S_FALSE __carbon_S_FALSE
42 #define S_OK __carbon_S_OK
43 #define HRESULT_FACILITY __carbon_HRESULT_FACILITY
44 #define IS_ERROR __carbon_IS_ERROR
45 #define FAILED __carbon_FAILED
46 #define SUCCEEDED __carbon_SUCCEEDED
47 #define MAKE_HRESULT __carbon_MAKE_HRESULT
48 #define HRESULT __carbon_HRESULT
49 #define STDMETHODCALLTYPE __carbon_STDMETHODCALLT
50 #include <mach/mach_time.h>
51 #include <CoreMIDI/CoreMIDI.h>
52 #include <AudioUnit/AudioUnit.h>
53 #include <AudioToolbox/AudioToolbox.h>
67 #undef HRESULT_FACILITY
73 #undef STDMETHODCALLTYPE
76 #define WIN32_NO_STATUS
83 #include "mmdeviceapi.h"
84 #include "audioclient.h"
85 #include "wine/debug.h"
86 #include "wine/unicode.h"
87 #include "wine/unixlib.h"
89 #include "coreaudio.h"
93 WINE_DEFAULT_DEBUG_CHANNEL(midi
);
95 static MIDIClientRef midi_client
;
96 static MIDIPortRef midi_out_port
, midi_in_port
;
97 static UINT num_dests
, num_srcs
;
98 static struct midi_dest
*dests
;
99 static struct midi_src
*srcs
;
100 static CFStringRef midi_in_thread_port_name
;
103 * CoreMIDI IO threaded callback,
104 * we can't call Wine debug channels, critical section or anything using NtCurrentTeb here.
106 static void midi_in_read_proc(const MIDIPacketList
*pktlist
, void *refCon
, void *connRefCon
)
108 CFMessagePortRef msg_port
= CFMessagePortCreateRemote(kCFAllocatorDefault
, midi_in_thread_port_name
);
109 MIDIPacket
*packet
= (MIDIPacket
*)pktlist
->packet
;
110 CFMutableDataRef data
;
114 for (i
= 0; i
< pktlist
->numPackets
; ++i
)
116 msg
.devID
= *(UInt16
*)connRefCon
;
117 msg
.length
= packet
->length
;
118 data
= CFDataCreateMutable(kCFAllocatorDefault
, sizeof(msg
) + packet
->length
);
121 CFDataAppendBytes(data
, (UInt8
*)&msg
, sizeof(msg
));
122 CFDataAppendBytes(data
, packet
->data
, packet
->length
);
123 CFMessagePortSendRequest(msg_port
, 0, data
, 0.0, 0.0, NULL
, NULL
);
126 packet
= MIDIPacketNext(packet
);
131 NTSTATUS
midi_init(void *args
)
133 CFStringRef name
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("wineMIDIClient.%d"), getpid());
134 struct midi_init_params
*params
= args
;
138 sc
= MIDIClientCreate(name
, NULL
/* FIXME use notify proc */, NULL
, &midi_client
);
142 ERR("can't create MIDI Client\n");
143 *params
->err
= DRV_FAILURE
;
144 return STATUS_SUCCESS
;
147 num_dests
= MAX_MIDI_SYNTHS
+ MIDIGetNumberOfDestinations();
148 num_srcs
= MIDIGetNumberOfSources();
150 TRACE("num_dests %d num_srcs %d\n", num_dests
, num_srcs
);
152 dests
= calloc(num_dests
, sizeof(*dests
));
153 srcs
= calloc(num_srcs
, sizeof(*srcs
));
157 midi_in_thread_port_name
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("MIDIInThreadPortName.%u"), getpid());
158 name
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("WineInputPort.%u"), getpid());
159 MIDIInputPortCreate(midi_client
, name
, midi_in_read_proc
, NULL
, &midi_in_port
);
163 if (num_dests
> MAX_MIDI_SYNTHS
)
165 name
= CFStringCreateWithFormat(kCFAllocatorDefault
, NULL
, CFSTR("WineOutputPort.%u"), getpid());
166 MIDIOutputPortCreate(midi_client
, name
, &midi_out_port
);
170 /* initialize sources */
171 for (i
= 0; i
< num_srcs
; i
++)
174 srcs
[i
].source
= MIDIGetSource(i
);
176 sc
= MIDIObjectGetStringProperty(srcs
[i
].source
, kMIDIPropertyName
, &name
);
179 int len
= min(CFStringGetLength(name
), ARRAY_SIZE(srcs
[i
].caps
.szPname
) - 1);
180 CFStringGetCharacters(name
, CFRangeMake(0, len
), srcs
[i
].caps
.szPname
);
181 srcs
[i
].caps
.szPname
[len
] = '\0';
183 MIDIPortConnectSource(midi_in_port
, srcs
[i
].source
, &srcs
[i
].wDevID
);
187 srcs
[i
].caps
.wMid
= 0x00FF; /* Manufac ID */
188 srcs
[i
].caps
.wPid
= 0x0001; /* Product ID */
189 srcs
[i
].caps
.vDriverVersion
= 0x0001;
190 srcs
[i
].caps
.dwSupport
= 0;
193 /* initialise MIDI synths */
194 for (i
= 0; i
< MAX_MIDI_SYNTHS
; i
++)
196 static const WCHAR synth_name
[] = {'C','o','r','e','A','u','d','i','o',' ','M','I','D','I',' ','S','y','n','t','h',' '};
198 C_ASSERT(MAX_MIDI_SYNTHS
< 10);
199 memcpy(dests
[i
].caps
.szPname
, synth_name
, sizeof(synth_name
));
200 dests
[i
].caps
.szPname
[ARRAY_SIZE(synth_name
)] = '1' + i
;
201 dests
[i
].caps
.szPname
[ARRAY_SIZE(synth_name
) + 1] = '\0';
203 dests
[i
].caps
.wTechnology
= MOD_SYNTH
;
204 dests
[i
].caps
.wChannelMask
= 0xFFFF;
206 dests
[i
].caps
.wMid
= 0x00FF; /* Manufac ID */
207 dests
[i
].caps
.wPid
= 0x0001; /* Product ID */
208 dests
[i
].caps
.vDriverVersion
= 0x0001;
209 dests
[i
].caps
.dwSupport
= MIDICAPS_VOLUME
;
210 dests
[i
].caps
.wVoices
= 16;
211 dests
[i
].caps
.wNotes
= 16;
213 /* initialise available destinations */
214 for (i
= MAX_MIDI_SYNTHS
; i
< num_dests
; i
++)
216 dests
[i
].dest
= MIDIGetDestination(i
- MAX_MIDI_SYNTHS
);
218 sc
= MIDIObjectGetStringProperty(dests
[i
].dest
, kMIDIPropertyName
, &name
);
221 int len
= min(CFStringGetLength(name
), ARRAY_SIZE(dests
[i
].caps
.szPname
) - 1);
222 CFStringGetCharacters(name
, CFRangeMake(0, len
), dests
[i
].caps
.szPname
);
223 dests
[i
].caps
.szPname
[len
] = '\0';
226 dests
[i
].caps
.wTechnology
= MOD_MIDIPORT
;
227 dests
[i
].caps
.wChannelMask
= 0xFFFF;
229 dests
[i
].caps
.wMid
= 0x00FF; /* Manufac ID */
230 dests
[i
].caps
.wPid
= 0x0001;
231 dests
[i
].caps
.vDriverVersion
= 0x0001;
232 dests
[i
].caps
.dwSupport
= 0;
233 dests
[i
].caps
.wVoices
= 0;
234 dests
[i
].caps
.wNotes
= 0;
237 params
->num_dests
= num_dests
;
238 params
->num_srcs
= num_srcs
;
239 params
->dests
= dests
;
241 params
->midi_out_port
= (void *)midi_out_port
;
242 params
->midi_in_port
= (void *)midi_in_port
;
244 *params
->err
= DRV_SUCCESS
;
245 return STATUS_SUCCESS
;
248 NTSTATUS
midi_release(void *args
)
250 CFMessagePortRef msg_port
;
254 /* Stop CFRunLoop in MIDIIn_MessageThread */
255 msg_port
= CFMessagePortCreateRemote(kCFAllocatorDefault
, midi_in_thread_port_name
);
256 CFMessagePortSendRequest(msg_port
, 1, NULL
, 0.0, 0.0, NULL
, NULL
);
260 if (midi_client
) MIDIClientDispose(midi_client
); /* MIDIClientDispose will close all ports */
265 return STATUS_SUCCESS
;
271 static BOOL
synth_unit_create_default(AUGraph
*graph
, AudioUnit
*synth
)
273 AudioComponentDescription desc
;
278 sc
= NewAUGraph(graph
);
281 ERR("NewAUGraph return %s\n", wine_dbgstr_fourcc(sc
));
285 desc
.componentManufacturer
= kAudioUnitManufacturer_Apple
;
286 desc
.componentFlags
= 0;
287 desc
.componentFlagsMask
= 0;
289 /* create synth node */
290 desc
.componentType
= kAudioUnitType_MusicDevice
;
291 desc
.componentSubType
= kAudioUnitSubType_DLSSynth
;
293 sc
= AUGraphAddNode(*graph
, &desc
, &synth_node
);
296 ERR("AUGraphAddNode cannot create synthNode : %s\n", wine_dbgstr_fourcc(sc
));
300 /* create out node */
301 desc
.componentType
= kAudioUnitType_Output
;
302 desc
.componentSubType
= kAudioUnitSubType_DefaultOutput
;
304 sc
= AUGraphAddNode(*graph
, &desc
, &out_node
);
307 ERR("AUGraphAddNode cannot create outNode %s\n", wine_dbgstr_fourcc(sc
));
311 sc
= AUGraphOpen(*graph
);
314 ERR("AUGraphOpen returns %s\n", wine_dbgstr_fourcc(sc
));
318 /* connecting the nodes */
319 sc
= AUGraphConnectNodeInput(*graph
, synth_node
, 0, out_node
, 0);
322 ERR("AUGraphConnectNodeInput cannot connect synthNode to outNode : %s\n",
323 wine_dbgstr_fourcc(sc
));
327 /* Get the synth unit */
328 sc
= AUGraphNodeInfo(*graph
, synth_node
, 0, synth
);
331 ERR("AUGraphNodeInfo return %s\n", wine_dbgstr_fourcc(sc
));
338 static BOOL
synth_unit_init(AudioUnit synth
, AUGraph graph
)
342 sc
= AUGraphInitialize(graph
);
345 ERR("AUGraphInitialize(%p) returns %s\n", graph
, wine_dbgstr_fourcc(sc
));
349 sc
= AUGraphStart(graph
);
352 ERR("AUGraphStart(%p) returns %s\n", graph
, wine_dbgstr_fourcc(sc
));
359 static BOOL
synth_unit_close(AUGraph graph
)
363 sc
= AUGraphStop(graph
);
366 ERR("AUGraphStop(%p) returns %s\n", graph
, wine_dbgstr_fourcc(sc
));
370 sc
= DisposeAUGraph(graph
);
373 ERR("DisposeAUGraph(%p) returns %s\n", graph
, wine_dbgstr_fourcc(sc
));
380 static void set_out_notify(struct notify_context
*notify
, struct midi_dest
*dest
, WORD dev_id
, WORD msg
,
381 DWORD_PTR param_1
, DWORD_PTR param_2
)
383 notify
->send_notify
= TRUE
;
384 notify
->dev_id
= dev_id
;
386 notify
->param_1
= param_1
;
387 notify
->param_2
= param_2
;
388 notify
->callback
= dest
->midiDesc
.dwCallback
;
389 notify
->flags
= dest
->wFlags
;
390 notify
->device
= dest
->midiDesc
.hMidi
;
391 notify
->instance
= dest
->midiDesc
.dwInstance
;
394 static DWORD
midi_out_open(WORD dev_id
, MIDIOPENDESC
*midi_desc
, DWORD flags
, struct notify_context
*notify
)
396 struct midi_dest
*dest
;
398 TRACE("dev_id = %d desc = %p flags = %08x\n", dev_id
, midi_desc
, flags
);
400 if (!midi_desc
) return MMSYSERR_INVALPARAM
;
402 if (dev_id
>= num_dests
)
404 WARN("bad device ID : %d\n", dev_id
);
405 return MMSYSERR_BADDEVICEID
;
407 if (dests
[dev_id
].midiDesc
.hMidi
!= 0)
409 WARN("device already open!\n");
410 return MMSYSERR_ALLOCATED
;
412 if ((flags
& ~CALLBACK_TYPEMASK
) != 0)
415 return MMSYSERR_INVALFLAG
;
418 dest
= dests
+ dev_id
;
419 if (dest
->caps
.wTechnology
== MOD_SYNTH
)
421 if (!synth_unit_create_default(&dest
->graph
, &dest
->synth
))
423 ERR("SynthUnit_CreateDefaultSynthUnit dest=%p failed\n", dest
);
424 return MMSYSERR_ERROR
;
426 if (!synth_unit_init(dest
->synth
, dest
->graph
))
428 ERR("SynthUnit_Initialise dest=%p failed\n", dest
);
429 return MMSYSERR_ERROR
;
432 dest
->wFlags
= HIWORD(flags
& CALLBACK_TYPEMASK
);
433 dest
->midiDesc
= *midi_desc
;
435 set_out_notify(notify
, dest
, dev_id
, MOM_OPEN
, 0, 0);
437 return MMSYSERR_NOERROR
;
440 static DWORD
midi_out_close(WORD dev_id
, struct notify_context
*notify
)
442 struct midi_dest
*dest
;
444 TRACE("dev_id = %d\n", dev_id
);
446 if (dev_id
>= num_dests
)
448 WARN("bad device ID : %d\n", dev_id
);
449 return MMSYSERR_BADDEVICEID
;
452 dest
= dests
+ dev_id
;
454 if (dest
->caps
.wTechnology
== MOD_SYNTH
)
455 synth_unit_close(dest
->graph
);
459 set_out_notify(notify
, dest
, dev_id
, MOM_CLOSE
, 0, 0);
461 dest
->midiDesc
.hMidi
= 0;
463 return MMSYSERR_NOERROR
;
466 static void midi_send(MIDIPortRef port
, MIDIEndpointRef dest
, UInt8
*buffer
, unsigned len
)
468 Byte packet_buf
[512];
469 MIDIPacketList
*packet_list
= (MIDIPacketList
*)packet_buf
;
470 MIDIPacket
*packet
= MIDIPacketListInit(packet_list
);
472 packet
= MIDIPacketListAdd(packet_list
, sizeof(packet_buf
), packet
, mach_absolute_time(), len
, buffer
);
473 if (packet
) MIDISend(port
, dest
, packet_list
);
476 static DWORD
midi_out_data(WORD dev_id
, DWORD data
)
478 struct midi_dest
*dest
;
482 TRACE("dev_id = %d data = %08x\n", dev_id
, data
);
484 if (dev_id
>= num_dests
)
486 WARN("bad device ID : %d\n", dev_id
);
487 return MMSYSERR_BADDEVICEID
;
490 bytes
[0] = data
& 0xff;
491 bytes
[1] = (data
>> 8) & 0xff;
492 bytes
[2] = (data
>> 16) & 0xff;
494 dest
= dests
+ dev_id
;
495 if (dest
->caps
.wTechnology
== MOD_SYNTH
)
497 sc
= MusicDeviceMIDIEvent(dest
->synth
, bytes
[0], bytes
[1], bytes
[2], 0);
500 ERR("MusicDeviceMIDIEvent returns %s\n", wine_dbgstr_fourcc(sc
));
501 return MMSYSERR_ERROR
;
506 midi_send(midi_out_port
, dest
->dest
, bytes
, sizeof(bytes
));
509 return MMSYSERR_NOERROR
;
512 static DWORD
midi_out_long_data(WORD dev_id
, MIDIHDR
*hdr
, DWORD hdr_size
, struct notify_context
*notify
)
514 struct midi_dest
*dest
;
517 TRACE("dev_id = %d midi_hdr = %p hdr_size = %d\n", dev_id
, hdr
, hdr_size
);
519 if (dev_id
>= num_dests
)
521 WARN("bad device ID : %d\n", dev_id
);
522 return MMSYSERR_BADDEVICEID
;
526 WARN("Invalid Parameter\n");
527 return MMSYSERR_INVALPARAM
;
529 if (!hdr
->lpData
|| !(hdr
->dwFlags
& MHDR_PREPARED
))
530 return MIDIERR_UNPREPARED
;
532 if (hdr
->dwFlags
& MHDR_INQUEUE
)
533 return MIDIERR_STILLPLAYING
;
535 hdr
->dwFlags
&= ~MHDR_DONE
;
536 hdr
->dwFlags
|= MHDR_INQUEUE
;
538 if ((UInt8
)hdr
->lpData
[0] != 0xf0)
539 /* System Exclusive */
540 ERR("Add missing 0xf0 marker at the beginning of system exclusive byte stream\n");
542 if ((UInt8
)hdr
->lpData
[hdr
->dwBufferLength
- 1] != 0xF7)
543 /* Send end of System Exclusive */
544 ERR("Add missing 0xf7 marker at the end of system exclusive byte stream\n");
546 dest
= dests
+ dev_id
;
548 if (dest
->caps
.wTechnology
== MOD_SYNTH
) /* FIXME */
550 sc
= MusicDeviceSysEx(dest
->synth
, (const UInt8
*)hdr
->lpData
, hdr
->dwBufferLength
);
553 ERR("MusicDeviceSysEx returns %s\n", wine_dbgstr_fourcc(sc
));
554 return MMSYSERR_ERROR
;
557 else if (dest
->caps
.wTechnology
== MOD_MIDIPORT
)
558 midi_send(midi_out_port
, dest
->dest
, (UInt8
*)hdr
->lpData
, hdr
->dwBufferLength
);
560 hdr
->dwFlags
&= ~MHDR_INQUEUE
;
561 hdr
->dwFlags
|= MHDR_DONE
;
563 set_out_notify(notify
, dest
, dev_id
, MOM_DONE
, (DWORD_PTR
)hdr
, 0);
565 return MMSYSERR_NOERROR
;
568 static DWORD
midi_out_prepare(WORD dev_id
, MIDIHDR
*hdr
, DWORD hdr_size
)
570 TRACE("dev_id = %d midi_hdr = %p hdr_size = %d\n", dev_id
, hdr
, hdr_size
);
572 if (hdr_size
< offsetof(MIDIHDR
, dwOffset
) || !hdr
|| !hdr
->lpData
)
573 return MMSYSERR_INVALPARAM
;
574 if (hdr
->dwFlags
& MHDR_PREPARED
)
575 return MMSYSERR_NOERROR
;
578 hdr
->dwFlags
|= MHDR_PREPARED
;
579 hdr
->dwFlags
&= ~(MHDR_DONE
| MHDR_INQUEUE
);
580 return MMSYSERR_NOERROR
;
583 static DWORD
midi_out_unprepare(WORD dev_id
, MIDIHDR
*hdr
, DWORD hdr_size
)
585 TRACE("dev_id = %d midi_hdr = %p hdr_size = %d\n", dev_id
, hdr
, hdr_size
);
587 if (hdr_size
< offsetof(MIDIHDR
, dwOffset
) || !hdr
|| !hdr
->lpData
)
588 return MMSYSERR_INVALPARAM
;
589 if (!(hdr
->dwFlags
& MHDR_PREPARED
))
590 return MMSYSERR_NOERROR
;
591 if (hdr
->dwFlags
& MHDR_INQUEUE
)
592 return MIDIERR_STILLPLAYING
;
594 hdr
->dwFlags
&= ~MHDR_PREPARED
;
595 return MMSYSERR_NOERROR
;
598 static DWORD
midi_out_get_devcaps(WORD dev_id
, MIDIOUTCAPSW
*caps
, DWORD size
)
600 TRACE("dev_id = %d caps = %p size = %d\n", dev_id
, caps
, size
);
604 WARN("Invalid Parameter\n");
605 return MMSYSERR_INVALPARAM
;
608 if (dev_id
>= num_dests
)
610 WARN("bad device ID : %d\n", dev_id
);
611 return MMSYSERR_BADDEVICEID
;
613 memcpy(caps
, &dests
[dev_id
].caps
, min(size
, sizeof(*caps
)));
614 return MMSYSERR_NOERROR
;
617 NTSTATUS
midi_out_message(void *args
)
619 struct midi_out_message_params
*params
= args
;
621 params
->notify
->send_notify
= FALSE
;
629 *params
->err
= MMSYSERR_NOERROR
;
632 *params
->err
= midi_out_open(params
->dev_id
, (MIDIOPENDESC
*)params
->param_1
, params
->param_2
, params
->notify
);
635 *params
->err
= midi_out_close(params
->dev_id
, params
->notify
);
638 *params
->err
= midi_out_data(params
->dev_id
, params
->param_1
);
641 *params
->err
= midi_out_long_data(params
->dev_id
, (MIDIHDR
*)params
->param_1
, params
->param_2
, params
->notify
);
644 *params
->err
= midi_out_prepare(params
->dev_id
, (MIDIHDR
*)params
->param_1
, params
->param_2
);
647 *params
->err
= midi_out_unprepare(params
->dev_id
, (MIDIHDR
*)params
->param_1
, params
->param_2
);
649 case MODM_GETDEVCAPS
:
650 *params
->err
= midi_out_get_devcaps(params
->dev_id
, (MIDIOUTCAPSW
*)params
->param_1
, params
->param_2
);
653 TRACE("Unsupported message\n");
654 *params
->err
= MMSYSERR_NOTSUPPORTED
;
657 return STATUS_SUCCESS
;