winecoreaudio: Move the midi input event processing to the unixlib.

Notifications of incoming data are now directly added to a notify
ring-buffer by macOS's midi read callback.  The ring-buffer is
implemented with an unused sentinel directly before the read ptr to
distinguish between the full vs empty state.  Notifications are
processed by the client's notify thread via the midi_notify_wait
syscall.

Note that the read callback thread is not a Win32 thread, so the
Win32 api cannot be used in anything that it calls.

Signed-off-by: Huw Davies <huw@codeweavers.com>
Signed-off-by: Andrew Eikum <aeikum@codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
Huw Davies 2021-11-30 07:56:30 +00:00 committed by Alexandre Julliard
parent 06bb1bdb14
commit 92f7c86f68
5 changed files with 140 additions and 321 deletions

View File

@ -1634,6 +1634,4 @@ unixlib_entry_t __wine_unix_call_funcs[] =
midi_out_message,
midi_in_message,
midi_notify_wait,
midi_in_lock, /* temporary */
};

View File

@ -89,7 +89,6 @@
#include "wine/unixlib.h"
#include "coreaudio.h"
#include "coremidi.h"
#include "unixlib.h"
WINE_DEFAULT_DEBUG_CHANNEL(midi);
@ -107,12 +106,24 @@ struct midi_dest
WORD wFlags;
};
struct midi_src
{
MIDIEndpointRef source;
WORD wDevID;
int state; /* 0 is no recording started, 1 in recording, bit 2 set if in sys exclusive recording */
MIDIINCAPSW caps;
MIDIOPENDESC midiDesc;
LPMIDIHDR lpQueueHdr;
WORD wFlags;
DWORD startTime;
};
static MIDIClientRef midi_client;
static MIDIPortRef midi_out_port, midi_in_port;
static UINT num_dests, num_srcs;
static struct midi_dest *dests;
static struct midi_src *srcs;
static CFStringRef midi_in_thread_port_name;
static pthread_mutex_t midi_in_mutex = PTHREAD_MUTEX_INITIALIZER;
@ -123,6 +134,8 @@ static BOOL notify_quit;
static struct notify_context notify_buffer[NOTIFY_BUFFER_SIZE];
static struct notify_context *notify_read, *notify_write;
#define MAX_MIDI_SYNTHS 1
NTSTATUS midi_in_lock(void *args)
{
BOOL lock = !!args;
@ -146,11 +159,49 @@ static void set_in_notify(struct notify_context *notify, struct midi_src *src, W
notify->instance = src->midiDesc.dwInstance;
}
/*
* notify buffer: The notification ring buffer is implemented so that
* there is always at least one unused sentinel before the current
* read position in order to allow detection of the full vs empty
* state.
*/
static struct notify_context *notify_buffer_next(struct notify_context *notify)
{
if (++notify >= notify_buffer + ARRAY_SIZE(notify_buffer))
notify = notify_buffer;
return notify;
}
static void notify_buffer_add(struct notify_context *notify)
{
struct notify_context *next = notify_buffer_next(notify_write);
if (next == notify_read) /* buffer is full - we can't issue a WARN() in a non-Win32 thread */
notify_read = notify_buffer_next(notify_read); /* drop the oldest notification */
*notify_write = *notify;
notify_write = next;
}
static BOOL notify_buffer_empty(void)
{
return notify_read == notify_write;
}
static BOOL notify_buffer_remove(struct notify_context *notify)
{
if (notify_buffer_empty()) return FALSE;
*notify = *notify_read;
notify_read = notify_buffer_next(notify_read);
return TRUE;
}
static void notify_post(struct notify_context *notify)
{
pthread_mutex_lock(&notify_mutex);
if (notify) FIXME("Not yet handled\n");
if (notify) notify_buffer_add(notify);
else notify_quit = TRUE;
pthread_cond_signal(&notify_cond);
@ -169,29 +220,94 @@ static uint64_t get_time_ms(void)
return mach_absolute_time() / 1000000 * timebase.numer / timebase.denom;
}
static void process_sysex_packet(struct midi_src *src, MIDIPacket *packet)
{
unsigned int pos = 0, len = packet->length, copy_len;
DWORD current_time = get_time_ms() - src->startTime;
struct notify_context notify;
src->state |= 2;
midi_in_lock((void *)TRUE);
while (len)
{
MIDIHDR *hdr = src->lpQueueHdr;
if (!hdr) break;
copy_len = min(len, hdr->dwBufferLength - hdr->dwBytesRecorded);
memcpy(hdr->lpData + hdr->dwBytesRecorded, packet->data + pos, copy_len);
hdr->dwBytesRecorded += copy_len;
len -= copy_len;
pos += copy_len;
if ((hdr->dwBytesRecorded == hdr->dwBufferLength) ||
(*(BYTE*)(hdr->lpData + hdr->dwBytesRecorded - 1) == 0xf7))
{ /* buffer full or end of sysex message */
src->lpQueueHdr = hdr->lpNext;
hdr->dwFlags &= ~MHDR_INQUEUE;
hdr->dwFlags |= MHDR_DONE;
set_in_notify(&notify, src, src->wDevID, MIM_LONGDATA, (DWORD_PTR)hdr, current_time);
notify_post(&notify);
src->state &= ~2;
}
}
midi_in_lock((void *)FALSE);
}
static void process_small_packet(struct midi_src *src, MIDIPacket *packet)
{
DWORD current_time = get_time_ms() - src->startTime, data;
struct notify_context notify;
unsigned int pos = 0;
while (pos < packet->length)
{
data = 0;
switch (packet->data[pos] & 0xf0)
{
case 0xf0:
data = packet->data[pos];
pos++;
break;
case 0xc0:
case 0xd0:
data = (packet->data[pos + 1] << 8) | packet->data[pos];
pos += 2;
break;
default:
data = (packet->data[pos + 2] << 16) | (packet->data[pos + 1] << 8) |
packet->data[pos];
pos += 3;
break;
}
set_in_notify(&notify, src, src->wDevID, MIM_DATA, data, current_time);
notify_post(&notify);
}
}
static void midi_in_read_proc(const MIDIPacketList *pktlist, void *refCon, void *connRefCon)
{
CFMessagePortRef msg_port = CFMessagePortCreateRemote(kCFAllocatorDefault, midi_in_thread_port_name);
MIDIPacket *packet = (MIDIPacket *)pktlist->packet;
CFMutableDataRef data;
MIDIMessage msg;
WORD dev_id = *(WORD *)connRefCon;
struct midi_src *src;
unsigned int i;
if (dev_id >= num_srcs) return;
src = srcs + dev_id;
if (src->state < 1) /* input not started */
return;
for (i = 0; i < pktlist->numPackets; ++i)
{
msg.devID = *(UInt16 *)connRefCon;
msg.length = packet->length;
data = CFDataCreateMutable(kCFAllocatorDefault, sizeof(msg) + packet->length);
if (data)
{
CFDataAppendBytes(data, (UInt8 *)&msg, sizeof(msg));
CFDataAppendBytes(data, packet->data, packet->length);
CFMessagePortSendRequest(msg_port, 0, data, 0.0, 0.0, NULL, NULL);
CFRelease(data);
}
if (packet->data[0] == 0xf0 || src->state & 2)
process_sysex_packet(src, packet);
else
process_small_packet(src, packet);
packet = MIDIPacketNext(packet);
}
CFRelease(msg_port);
}
NTSTATUS midi_init(void *args)
@ -225,7 +341,6 @@ NTSTATUS midi_init(void *args)
if (num_srcs > 0)
{
midi_in_thread_port_name = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("MIDIInThreadPortName.%u"), getpid());
name = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("WineInputPort.%u"), getpid());
MIDIInputPortCreate(midi_client, name, midi_in_read_proc, NULL, &midi_in_port);
CFRelease(name);
@ -306,8 +421,6 @@ NTSTATUS midi_init(void *args)
}
params->num_srcs = num_srcs;
params->srcs = srcs;
params->midi_in_port = (void *)midi_in_port;
*params->err = DRV_SUCCESS;
return STATUS_SUCCESS;
@ -315,15 +428,8 @@ NTSTATUS midi_init(void *args)
NTSTATUS midi_release(void *args)
{
CFMessagePortRef msg_port;
if (num_srcs)
{
/* Stop CFRunLoop in MIDIIn_MessageThread */
msg_port = CFMessagePortCreateRemote(kCFAllocatorDefault, midi_in_thread_port_name);
CFMessagePortSendRequest(msg_port, 1, NULL, 0.0, 0.0, NULL, NULL);
CFRelease(msg_port);
/* stop the notify_wait thread */
notify_post(NULL);
}
@ -1114,10 +1220,11 @@ NTSTATUS midi_notify_wait(void *args)
pthread_mutex_lock(&notify_mutex);
while (!notify_quit)
while (!notify_quit && notify_buffer_empty())
pthread_cond_wait(&notify_cond, &notify_mutex);
*params->quit = notify_quit;
if (!notify_quit) notify_buffer_remove(params->notify);
pthread_mutex_unlock(&notify_mutex);

View File

@ -1,86 +0,0 @@
/*
* Wine Midi driver for Mac OS X
*
* Copyright 2006 Emmanuel Maillard
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
#ifndef WINE_COREMIDI_H
#define WINE_COREMIDI_H
#include <CoreFoundation/CoreFoundation.h>
#define MAX_MIDI_SYNTHS 1
#ifdef WINE_DEFINITIONS
/*
* Due to CoreMIDI headers conflict redefine some types for Wine
*/
typedef void *MIDIClientRef;
typedef void *MIDIEndpointRef;
typedef void *MIDIPortRef;
typedef void *MIDIObjectRef;
typedef struct MIDIPacketList MIDIPacketList;
/*
* functions
*/
extern OSStatus MIDIClientDispose(MIDIClientRef client);
extern unsigned MIDIGetNumberOfDestinations(void);
extern MIDIEndpointRef MIDIGetDestination(unsigned i);
extern unsigned MIDIGetNumberOfSources(void);
extern MIDIEndpointRef MIDIGetSource(unsigned i);
extern OSStatus MIDIOutputPortCreate(MIDIClientRef client, CFStringRef portName, MIDIPortRef *outPort);
typedef void (*MIDIReadProc)(const MIDIPacketList *pktlist, void *readProcRefCon, void *srcConnRefCon);
extern OSStatus MIDIInputPortCreate(MIDIClientRef client, CFStringRef portName, MIDIReadProc readProc, void *refCon, MIDIPortRef *outPort);
extern OSStatus MIDIObjectGetProperties(MIDIObjectRef obj, CFPropertyListRef *outProperties, Boolean deep);
extern OSStatus MIDIPortConnectSource(MIDIPortRef port, MIDIEndpointRef source, void *connRefCon);
/*
* Due to AudioUnit headers conflict redefine some types.
*/
typedef void *AudioUnit;
typedef void *AUGraph;
extern OSStatus MusicDeviceMIDIEvent(AudioUnit au, UInt32 inStatus, UInt32 inData1, UInt32 inData2, UInt32 inOffsetSampleFrame);
extern OSStatus MusicDeviceSysEx(AudioUnit au, const UInt8 *inData, UInt32 inLength);
#endif
/* midi.c */
typedef struct midi_src
{
MIDIEndpointRef source;
WORD wDevID;
int state; /* 0 is no recording started, 1 in recording, bit 2 set if in sys exclusive recording */
MIDIINCAPSW caps;
MIDIOPENDESC midiDesc;
LPMIDIHDR lpQueueHdr;
WORD wFlags;
DWORD startTime;
} MIDISource;
typedef struct {
UInt16 devID;
UInt16 length;
Byte data[];
} MIDIMessage;
#endif

View File

@ -45,31 +45,8 @@
WINE_DEFAULT_DEBUG_CHANNEL(midi);
#include <mach/mach_time.h>
#include <CoreAudio/CoreAudio.h>
#define WINE_DEFINITIONS
#include "coremidi.h"
static DWORD MIDIIn_NumDevs = 0;
static CFStringRef MIDIInThreadPortName;
static DWORD WINAPI MIDIIn_MessageThread(LPVOID p);
static MIDIPortRef MIDIInPort = NULL;
MIDISource *sources;
static uint64_t get_time_ms(void)
{
static mach_timebase_info_data_t timebase;
if (!timebase.denom) mach_timebase_info(&timebase);
return mach_absolute_time() / 1000000 * timebase.numer / timebase.denom;
}
static void notify_client(struct notify_context *notify)
{
TRACE("dev_id=%d msg=%d param1=%04lX param2=%04lX\n", notify->dev_id, notify->msg, notify->param_1, notify->param_2);
@ -81,14 +58,17 @@ static void notify_client(struct notify_context *notify)
static DWORD WINAPI notify_thread(void *p)
{
struct midi_notify_wait_params params;
struct notify_context notify;
BOOL quit;
params.notify = &notify;
params.quit = &quit;
while (1)
{
UNIX_CALL(midi_notify_wait, &params);
if (quit) break;
if (notify.send_notify) notify_client(&notify);
}
return 0;
}
@ -108,15 +88,10 @@ static LONG CoreAudio_MIDIInit(void)
}
MIDIIn_NumDevs = params.num_srcs;
sources = params.srcs;
MIDIInPort = params.midi_in_port;
if (MIDIIn_NumDevs > 0)
{
MIDIInThreadPortName = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("MIDIInThreadPortName.%u"), getpid());
CloseHandle( CreateThread(NULL, 0, MIDIIn_MessageThread, NULL, 0, NULL));
CloseHandle(CreateThread(NULL, 0, notify_thread, NULL, 0, NULL));
}
return err;
}
@ -125,181 +100,10 @@ static LONG CoreAudio_MIDIRelease(void)
TRACE("\n");
UNIX_CALL(midi_release, NULL);
sources = NULL;
return DRV_SUCCESS;
}
/**************************************************************************
* MIDI_NotifyClient [internal]
*/
static void MIDI_NotifyClient(UINT wDevID, WORD wMsg, DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
DWORD_PTR dwCallBack;
UINT uFlags;
HANDLE hDev;
DWORD_PTR dwInstance;
TRACE("wDevID=%d wMsg=%d dwParm1=%04lX dwParam2=%04lX\n", wDevID, wMsg, dwParam1, dwParam2);
switch (wMsg) {
case MIM_DATA:
case MIM_LONGDATA:
case MIM_ERROR:
case MIM_LONGERROR:
case MIM_MOREDATA:
dwCallBack = sources[wDevID].midiDesc.dwCallback;
uFlags = sources[wDevID].wFlags;
hDev = sources[wDevID].midiDesc.hMidi;
dwInstance = sources[wDevID].midiDesc.dwInstance;
break;
default:
ERR("Unsupported MSW-MIDI message %u\n", wMsg);
return;
}
DriverCallback(dwCallBack, uFlags, hDev, wMsg, dwInstance, dwParam1, dwParam2);
}
static void midi_lock( BOOL lock )
{
UNIX_CALL(midi_in_lock, (void *)lock);
}
/*
* MIDI In Mach message handling
*/
static CFDataRef MIDIIn_MessageHandler(CFMessagePortRef local, SInt32 msgid, CFDataRef data, void *info)
{
MIDIMessage *msg = NULL;
int i = 0;
MIDISource *src = NULL;
DWORD sendData = 0;
int pos = 0;
DWORD currentTime;
BOOL sysexStart;
switch (msgid)
{
case 0:
msg = (MIDIMessage *) CFDataGetBytePtr(data);
TRACE("devID=%d\n", msg->devID);
for (i = 0; i < msg->length; ++i) {
TRACE("%02X ", msg->data[i]);
}
TRACE("\n");
src = &sources[msg->devID];
if (src->state < 1)
{
TRACE("input not started, thrown away\n");
return NULL;
}
sysexStart = (msg->data[0] == 0xF0);
if (sysexStart || src->state & 2) {
int pos = 0;
int len = msg->length;
if (sysexStart) {
TRACE("Receiving sysex message\n");
src->state |= 2;
}
midi_lock( TRUE );
currentTime = get_time_ms() - src->startTime;
while (len) {
LPMIDIHDR lpMidiHdr = src->lpQueueHdr;
if (lpMidiHdr != NULL) {
int copylen = min(len, lpMidiHdr->dwBufferLength - lpMidiHdr->dwBytesRecorded);
memcpy(lpMidiHdr->lpData + lpMidiHdr->dwBytesRecorded, msg->data + pos, copylen);
lpMidiHdr->dwBytesRecorded += copylen;
len -= copylen;
pos += copylen;
TRACE("Copied %d bytes of sysex message\n", copylen);
if ((lpMidiHdr->dwBytesRecorded == lpMidiHdr->dwBufferLength) ||
(*(BYTE*)(lpMidiHdr->lpData + lpMidiHdr->dwBytesRecorded - 1) == 0xF7)) {
TRACE("Sysex message complete (or buffer limit reached), dispatching %d bytes\n", lpMidiHdr->dwBytesRecorded);
src->lpQueueHdr = lpMidiHdr->lpNext;
lpMidiHdr->dwFlags &= ~MHDR_INQUEUE;
lpMidiHdr->dwFlags |= MHDR_DONE;
MIDI_NotifyClient(msg->devID, MIM_LONGDATA, (DWORD_PTR)lpMidiHdr, currentTime);
src->state &= ~2;
}
}
else {
FIXME("Sysex data received but no buffer to store it!\n");
break;
}
}
midi_lock( FALSE );
return NULL;
}
midi_lock( TRUE );
currentTime = get_time_ms() - src->startTime;
while (pos < msg->length)
{
sendData = 0;
switch (msg->data[pos] & 0xF0)
{
case 0xF0:
sendData = (msg->data[pos] << 0);
pos++;
break;
case 0xC0:
case 0xD0:
sendData = (msg->data[pos + 1] << 8) | (msg->data[pos] << 0);
pos += 2;
break;
default:
sendData = (msg->data[pos + 2] << 16) |
(msg->data[pos + 1] << 8) |
(msg->data[pos] << 0);
pos += 3;
break;
}
MIDI_NotifyClient(msg->devID, MIM_DATA, sendData, currentTime);
}
midi_lock( FALSE );
break;
default:
CFRunLoopStop(CFRunLoopGetCurrent());
break;
}
return NULL;
}
static DWORD WINAPI MIDIIn_MessageThread(LPVOID p)
{
CFMessagePortRef local;
CFRunLoopSourceRef source;
Boolean info;
local = CFMessagePortCreateLocal(kCFAllocatorDefault, MIDIInThreadPortName, &MIDIIn_MessageHandler, NULL, &info);
source = CFMessagePortCreateRunLoopSource(kCFAllocatorDefault, local, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
CFRunLoopRun();
CFRunLoopSourceInvalidate(source);
CFRelease(source);
CFRelease(local);
CFRelease(MIDIInThreadPortName);
MIDIInThreadPortName = NULL;
return 0;
}
/**************************************************************************
* modMessage
*/

View File

@ -187,8 +187,6 @@ struct midi_init_params
{
DWORD *err;
UINT num_srcs;
void *srcs;
void *midi_in_port;
};
struct notify_context
@ -228,6 +226,7 @@ struct midi_in_message_params
struct midi_notify_wait_params
{
struct notify_context *notify;
BOOL *quit;
};
@ -258,8 +257,6 @@ enum unix_funcs
unix_midi_out_message,
unix_midi_in_message,
unix_midi_notify_wait,
unix_midi_in_lock, /* temporary */
};
NTSTATUS midi_init( void * ) DECLSPEC_HIDDEN;
@ -267,7 +264,6 @@ NTSTATUS midi_release( void * ) DECLSPEC_HIDDEN;
NTSTATUS midi_out_message( void * ) DECLSPEC_HIDDEN;
NTSTATUS midi_in_message( void * ) DECLSPEC_HIDDEN;
NTSTATUS midi_notify_wait( void * ) DECLSPEC_HIDDEN;
NTSTATUS midi_in_lock( void * ) DECLSPEC_HIDDEN;
extern unixlib_handle_t coreaudio_handle;