fluidsynth/src/drivers/fluid_winmidi.c
derselbst 47c4a5b1c7 remove various redundant audio / midi driver declarations
by moving already existing function declarations to fluid_adriver.h and
fluid_mdriver.h resp.
2018-07-21 10:37:43 +02:00

393 lines
10 KiB
C

/* FluidSynth - A Software Synthesizer
*
* Copyright (C) 2003 Peter Hanappe and others.
*
* 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 Street, Fifth Floor, Boston, MA
* 02110-1301, USA
*/
/* fluid_winmidi.c
*
* Driver for Windows MIDI
*
* NOTE: Unfortunately midiInAddBuffer(), for SYSEX data, should not be called
* from within the MIDI input callback, despite many examples contrary to that
* on the Internet. Some MIDI devices will deadlock. Therefore we add MIDIHDR
* pointers to a queue and re-add them in a separate thread. Lame-o API! :(
*/
#include "fluidsynth_priv.h"
#if WINMIDI_SUPPORT
#include "fluid_midi.h"
#include "fluid_mdriver.h"
#include "fluid_settings.h"
#define MIDI_SYSEX_MAX_SIZE 512
#define MIDI_SYSEX_BUF_COUNT 16
typedef struct
{
fluid_midi_driver_t driver;
HMIDIIN hmidiin;
/* MIDI HDR for SYSEX buffer */
MIDIHDR sysExHdrs[MIDI_SYSEX_BUF_COUNT];
/* Thread for SYSEX re-add thread */
HANDLE hThread;
DWORD dwThread;
/* Sysex data buffer */
unsigned char sysExBuf[MIDI_SYSEX_BUF_COUNT * MIDI_SYSEX_MAX_SIZE];
} fluid_winmidi_driver_t;
static char fluid_winmidi_error_buffer[256];
#define msg_type(_m) ((unsigned char)(_m & 0xf0))
#define msg_chan(_m) ((unsigned char)(_m & 0x0f))
#define msg_p1(_m) ((_m >> 8) & 0x7f)
#define msg_p2(_m) ((_m >> 16) & 0x7f)
void CALLBACK fluid_winmidi_callback(HMIDIIN hmi, UINT wMsg, DWORD_PTR dwInstance,
DWORD_PTR msg, DWORD_PTR extra);
static char *fluid_winmidi_input_error(MMRESULT no);
void fluid_winmidi_midi_driver_settings(fluid_settings_t *settings)
{
MMRESULT res;
MIDIINCAPS in_caps;
UINT i, num;
fluid_settings_register_str(settings, "midi.winmidi.device", "default", 0);
num = midiInGetNumDevs();
if(num > 0)
{
fluid_settings_add_option(settings, "midi.winmidi.device", "default");
for(i = 0; i < num; i++)
{
res = midiInGetDevCaps(i, &in_caps, sizeof(MIDIINCAPS));
if(res == MMSYSERR_NOERROR)
{
fluid_settings_add_option(settings, "midi.winmidi.device", in_caps.szPname);
}
}
}
}
/* Thread for re-adding SYSEX buffers */
static DWORD WINAPI fluid_winmidi_add_sysex_thread(void *data)
{
fluid_winmidi_driver_t *dev = (fluid_winmidi_driver_t *)data;
MSG msg;
int code;
for(;;)
{
code = GetMessage(&msg, NULL, 0, 0);
if(code < 0)
{
FLUID_LOG(FLUID_ERR, "fluid_winmidi_add_sysex_thread: GetMessage() failed.");
break;
}
if(msg.message == WM_CLOSE)
{
break;
}
switch(msg.message)
{
case MM_MIM_LONGDATA:
midiInAddBuffer(dev->hmidiin, (LPMIDIHDR)msg.lParam, sizeof(MIDIHDR));
break;
}
}
return 0;
}
/*
* new_fluid_winmidi_driver
*/
fluid_midi_driver_t *
new_fluid_winmidi_driver(fluid_settings_t *settings,
handle_midi_event_func_t handler, void *data)
{
fluid_winmidi_driver_t *dev;
MIDIHDR *hdr;
MMRESULT res;
UINT i, num, midi_num = 0;
MIDIINCAPS in_caps;
char *devname = NULL;
/* not much use doing anything */
if(handler == NULL)
{
FLUID_LOG(FLUID_ERR, "Invalid argument");
return NULL;
}
dev = FLUID_MALLOC(sizeof(fluid_winmidi_driver_t));
if(dev == NULL)
{
return NULL;
}
FLUID_MEMSET(dev, 0, sizeof(fluid_winmidi_driver_t));
dev->hmidiin = NULL;
dev->driver.handler = handler;
dev->driver.data = data;
/* get the device name. if none is specified, use the default device. */
if(fluid_settings_dupstr(settings, "midi.winmidi.device", &devname) != FLUID_OK || !devname)
{
devname = FLUID_STRDUP("default");
if(!devname)
{
FLUID_LOG(FLUID_ERR, "Out of memory");
goto error_recovery;
}
}
/* check if there any midi devices installed */
num = midiInGetNumDevs();
if(num == 0)
{
FLUID_LOG(FLUID_ERR, "no MIDI in devices found");
goto error_recovery;
}
/* find the device */
if(FLUID_STRCASECMP("default", devname) != 0)
{
for(i = 0; i < num; i++)
{
res = midiInGetDevCaps(i, &in_caps, sizeof(MIDIINCAPS));
if(res == MMSYSERR_NOERROR)
{
FLUID_LOG(FLUID_DBG, "Testing midi device: %s\n", in_caps.szPname);
if(FLUID_STRCASECMP(devname, in_caps.szPname) == 0)
{
FLUID_LOG(FLUID_DBG, "Selected midi device number: %d\n", i);
midi_num = i;
break;
}
}
}
if(midi_num != i)
{
FLUID_LOG(FLUID_ERR, "Device <%s> does not exists", devname);
goto error_recovery;
}
}
/* try opening the device */
res = midiInOpen(&dev->hmidiin, midi_num,
(DWORD_PTR) fluid_winmidi_callback,
(DWORD_PTR) dev, CALLBACK_FUNCTION);
if(res != MMSYSERR_NOERROR)
{
FLUID_LOG(FLUID_ERR, "Couldn't open MIDI input: %s (error %d)",
fluid_winmidi_input_error(res), res);
goto error_recovery;
}
/* Prepare and add SYSEX buffers */
for(i = 0; i < MIDI_SYSEX_BUF_COUNT; i++)
{
hdr = &dev->sysExHdrs[i];
hdr->lpData = (LPSTR)&dev->sysExBuf[i * MIDI_SYSEX_MAX_SIZE];
hdr->dwBufferLength = MIDI_SYSEX_MAX_SIZE;
/* Prepare a buffer for SYSEX data and add it */
res = midiInPrepareHeader(dev->hmidiin, hdr, sizeof(MIDIHDR));
if(res == MMSYSERR_NOERROR)
{
res = midiInAddBuffer(dev->hmidiin, hdr, sizeof(MIDIHDR));
if(res != MMSYSERR_NOERROR)
{
FLUID_LOG(FLUID_WARN, "Failed to prepare MIDI SYSEX buffer: %s (error %d)",
fluid_winmidi_input_error(res), res);
midiInUnprepareHeader(dev->hmidiin, hdr, sizeof(MIDIHDR));
}
}
else
FLUID_LOG(FLUID_WARN, "Failed to prepare MIDI SYSEX buffer: %s (error %d)",
fluid_winmidi_input_error(res), res);
}
/* Create thread which processes re-adding SYSEX buffers */
dev->hThread = CreateThread(
NULL,
0,
(LPTHREAD_START_ROUTINE)
fluid_winmidi_add_sysex_thread,
dev,
0,
&dev->dwThread);
if(dev->hThread == NULL)
{
FLUID_LOG(FLUID_ERR, "Failed to create SYSEX buffer processing thread");
goto error_recovery;
}
/* Start the MIDI input interface */
if(midiInStart(dev->hmidiin) != MMSYSERR_NOERROR)
{
FLUID_LOG(FLUID_ERR, "Failed to start the MIDI input. MIDI input not available.");
goto error_recovery;
}
if(devname)
{
FLUID_FREE(devname); /* -- free device name */
}
return (fluid_midi_driver_t *) dev;
error_recovery:
if(devname)
{
FLUID_FREE(devname); /* -- free device name */
}
delete_fluid_winmidi_driver((fluid_midi_driver_t *) dev);
return NULL;
}
/*
* delete_fluid_winmidi_driver
*/
void
delete_fluid_winmidi_driver(fluid_midi_driver_t *p)
{
fluid_winmidi_driver_t *dev = (fluid_winmidi_driver_t *) p;
fluid_return_if_fail(dev != NULL);
if(dev->hThread != NULL)
{
PostThreadMessage(dev->dwThread, WM_CLOSE, 0, 0);
WaitForSingleObject(dev->hThread, INFINITE);
dev->hThread = NULL;
}
if(dev->hmidiin != NULL)
{
midiInStop(dev->hmidiin);
midiInReset(dev->hmidiin);
midiInClose(dev->hmidiin);
}
FLUID_FREE(dev);
}
void CALLBACK
fluid_winmidi_callback(HMIDIIN hmi, UINT wMsg, DWORD_PTR dwInstance,
DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
fluid_winmidi_driver_t *dev = (fluid_winmidi_driver_t *) dwInstance;
fluid_midi_event_t event;
LPMIDIHDR pMidiHdr;
unsigned char *data;
unsigned int msg_param = (unsigned int) dwParam1;
switch(wMsg)
{
case MIM_OPEN:
break;
case MIM_CLOSE:
break;
case MIM_DATA:
event.type = msg_type(msg_param);
event.channel = msg_chan(msg_param);
if(event.type != PITCH_BEND)
{
event.param1 = msg_p1(msg_param);
event.param2 = msg_p2(msg_param);
}
else /* Pitch bend is a 14 bit value */
{
event.param1 = (msg_p2(msg_param) << 7) | msg_p1(msg_param);
event.param2 = 0;
}
(*dev->driver.handler)(dev->driver.data, &event);
break;
case MIM_LONGDATA: /* SYSEX data */
if(dev->hThread == NULL)
{
break;
}
pMidiHdr = (LPMIDIHDR)dwParam1;
data = (unsigned char *)(pMidiHdr->lpData);
/* We only process complete SYSEX messages (discard those that are too small or too large) */
if(pMidiHdr->dwBytesRecorded > 2 && data[0] == 0xF0
&& data[pMidiHdr->dwBytesRecorded - 1] == 0xF7)
{
fluid_midi_event_set_sysex(&event, pMidiHdr->lpData + 1,
pMidiHdr->dwBytesRecorded - 2, FALSE);
(*dev->driver.handler)(dev->driver.data, &event);
}
PostThreadMessage(dev->dwThread, MM_MIM_LONGDATA, 0, dwParam1);
break;
case MIM_ERROR:
break;
case MIM_LONGERROR:
break;
case MIM_MOREDATA:
break;
}
}
static char *
fluid_winmidi_input_error(MMRESULT no)
{
midiInGetErrorText(no, fluid_winmidi_error_buffer, 256);
return fluid_winmidi_error_buffer;
}
#endif /* WINMIDI_SUPPORT */