@@ -1,5 +1,6 @@ | |||
/* | |||
Copyright (C) 2009 Grame | |||
Copyright (C) 2011 Devin Anderson | |||
This program is free software; you can redistribute it and/or modify | |||
it under the terms of the GNU General Public License as published by | |||
@@ -17,401 +18,285 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |||
*/ | |||
#include "JackWinMMEDriver.h" | |||
#include "JackGraphManager.h" | |||
#include "JackEngineControl.h" | |||
#include "JackDriverLoader.h" | |||
#include <assert.h> | |||
#include <iostream> | |||
#include <sstream> | |||
#include <string> | |||
#include <windows.h> | |||
#include <windowsx.h> | |||
#include <mmsystem.h> | |||
#include "JackWinMMEDriver.h" | |||
namespace Jack | |||
{ | |||
using Jack::JackWinMMEDriver; | |||
static bool InitHeaders(MidiSlot* slot) | |||
JackWinMMEDriver::JackWinMMEDriver(const char *name, const char *alias, | |||
JackLockedEngine *engine, | |||
JackSynchro *table): | |||
JackMidiDriver(name, alias, engine, table) | |||
{ | |||
slot->fHeader = (LPMIDIHDR)GlobalAllocPtr(GMEM_MOVEABLE|GMEM_SHARE|GMEM_ZEROINIT, sizeof(MIDIHDR) + kBuffSize); | |||
if (!slot->fHeader) | |||
return false; | |||
slot->fHeader->lpData = (LPSTR)((LPBYTE)slot->fHeader + sizeof(MIDIHDR)); | |||
slot->fHeader->dwBufferLength = kBuffSize; | |||
slot->fHeader->dwFlags = 0; | |||
slot->fHeader->dwUser = 0; | |||
slot->fHeader->lpNext = 0; | |||
slot->fHeader->dwBytesRecorded = 0; | |||
return true; | |||
fCaptureChannels = 0; | |||
fPlaybackChannels = 0; | |||
input_ports = 0; | |||
output_ports = 0; | |||
} | |||
void CALLBACK JackWinMMEDriver::MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD userData, DWORD dwParam1, DWORD dwParam2) | |||
JackWinMMEDriver::~JackWinMMEDriver() | |||
{ | |||
jack_ringbuffer_t* ringbuffer = (jack_ringbuffer_t*)userData; | |||
//jack_info("JackWinMMEDriver::MidiInProc 0\n"); | |||
switch (wMsg) { | |||
case MIM_OPEN: | |||
break; | |||
case MIM_ERROR: | |||
case MIM_DATA: { | |||
//jack_info("JackWinMMEDriver::MidiInProc"); | |||
// One event | |||
unsigned int num_packet = 1; | |||
jack_ringbuffer_write(ringbuffer, (char*)&num_packet, sizeof(unsigned int)); | |||
// Write event actual data | |||
jack_ringbuffer_write(ringbuffer, (char*)&dwParam1, 3); | |||
break; | |||
} | |||
case MIM_LONGERROR: | |||
case MIM_LONGDATA: | |||
/* | |||
Nothing for now | |||
*/ | |||
break; | |||
} | |||
Stop(); | |||
Close(); | |||
} | |||
JackWinMMEDriver::JackWinMMEDriver(const char* name, const char* alias, JackLockedEngine* engine, JackSynchro* table) | |||
: JackMidiDriver(name, alias, engine, table), | |||
fRealCaptureChannels(0), | |||
fRealPlaybackChannels(0), | |||
fMidiSource(NULL), | |||
fMidiDestination(NULL) | |||
{} | |||
JackWinMMEDriver::~JackWinMMEDriver() | |||
{} | |||
int JackWinMMEDriver::Open(bool capturing, | |||
bool playing, | |||
int inchannels, | |||
int outchannels, | |||
bool monitor, | |||
const char* capture_driver_name, | |||
const char* playback_driver_name, | |||
jack_nframes_t capture_latency, | |||
jack_nframes_t playback_latency) | |||
int | |||
JackWinMMEDriver::Attach() | |||
{ | |||
jack_nframes_t buffer_size = fEngineControl->fBufferSize; | |||
jack_port_id_t index; | |||
jack_nframes_t latency = buffer_size; | |||
jack_latency_range_t latency_range; | |||
const char *name; | |||
JackPort *port; | |||
latency_range.max = latency; | |||
latency_range.min = latency; | |||
// Inputs | |||
for (int i = 0; i < fCaptureChannels; i++) { | |||
JackWinMMEInputPort *input_port = input_ports[i]; | |||
name = input_port->GetName(); | |||
index = fGraphManager->AllocatePort(fClientControl.fRefNum, name, | |||
JACK_DEFAULT_MIDI_TYPE, | |||
CaptureDriverFlags, buffer_size); | |||
if (index == NO_PORT) { | |||
jack_error("JackWinMMEDriver::Attach - cannot register input port " | |||
"with name '%s'.", name); | |||
// X: Do we need to deallocate ports? | |||
return -1; | |||
} | |||
port = fGraphManager->GetPort(index); | |||
port->SetAlias(input_port->GetAlias()); | |||
port->SetLatencyRange(JackCaptureLatency, &latency_range); | |||
fCapturePortList[i] = index; | |||
} | |||
jack_log("JackWinMMEDriver::Open"); | |||
fRealCaptureChannels = midiInGetNumDevs(); | |||
fRealPlaybackChannels = midiOutGetNumDevs(); | |||
// Generic JackMidiDriver Open | |||
if (JackMidiDriver::Open(capturing, playing, fRealCaptureChannels, fRealPlaybackChannels, monitor, capture_driver_name, playback_driver_name, capture_latency, playback_latency) != 0) | |||
return -1; | |||
fMidiDestination = new MidiSlot[fRealCaptureChannels]; | |||
assert(fMidiDestination); | |||
if (! fEngineControl->fSyncMode) { | |||
latency += buffer_size; | |||
latency_range.max = latency; | |||
latency_range.min = latency; | |||
} | |||
// Real input | |||
int devindex = 0; | |||
for (int i = 0; i < fRealCaptureChannels; i++) { | |||
// Outputs | |||
for (int i = 0; i < fPlaybackChannels; i++) { | |||
JackWinMMEOutputPort *output_port = output_ports[i]; | |||
name = output_port->GetName(); | |||
index = fGraphManager->AllocatePort(fClientControl.fRefNum, name, | |||
JACK_DEFAULT_MIDI_TYPE, | |||
PlaybackDriverFlags, buffer_size); | |||
if (index == NO_PORT) { | |||
jack_error("JackWinMMEDriver::Attach - cannot register output " | |||
"port with name '%s'.", name); | |||
// X: Do we need to deallocate ports? | |||
return -1; | |||
} | |||
port = fGraphManager->GetPort(index); | |||
port->SetAlias(output_port->GetAlias()); | |||
port->SetLatencyRange(JackPlaybackLatency, &latency_range); | |||
fPlaybackPortList[i] = index; | |||
} | |||
HMIDIIN handle; | |||
fMidiDestination[devindex].fIndex = i; | |||
MMRESULT ret = midiInOpen(&handle, fMidiDestination[devindex].fIndex, (DWORD)MidiInProc, (DWORD)fRingBuffer[devindex], CALLBACK_FUNCTION); | |||
return 0; | |||
} | |||
if (ret == MMSYSERR_NOERROR) { | |||
fMidiDestination[devindex].fHandle = handle; | |||
if (!InitHeaders(&fMidiDestination[devindex])) { | |||
jack_error("memory allocation failed"); | |||
midiInClose(handle); | |||
continue; | |||
} | |||
ret = midiInPrepareHeader(handle, fMidiDestination[devindex].fHeader, sizeof(MIDIHDR)); | |||
if (ret == MMSYSERR_NOERROR) { | |||
fMidiDestination[devindex].fHeader->dwUser = 1; | |||
ret = midiInAddBuffer(handle, fMidiDestination[devindex].fHeader, sizeof(MIDIHDR)); | |||
if (ret == MMSYSERR_NOERROR) { | |||
ret = midiInStart(handle); | |||
if (ret != MMSYSERR_NOERROR) { | |||
jack_error("midiInStart error"); | |||
CloseInput(&fMidiDestination[devindex]); | |||
continue; | |||
} | |||
} else { | |||
jack_error ("midiInAddBuffer error"); | |||
CloseInput(&fMidiDestination[devindex]); | |||
continue; | |||
} | |||
} else { | |||
jack_error("midiInPrepareHeader error"); | |||
midiInClose(handle); | |||
continue; | |||
} | |||
} else { | |||
jack_error ("midiInOpen error"); | |||
continue; | |||
int | |||
JackWinMMEDriver::Close() | |||
{ | |||
int result = JackMidiDriver::Close(); | |||
if (input_ports) { | |||
for (int i = 0; i < fCaptureChannels; i++) { | |||
delete input_ports[i]; | |||
} | |||
devindex += 1; | |||
delete[] input_ports; | |||
input_ports = 0; | |||
} | |||
fRealCaptureChannels = devindex; | |||
fCaptureChannels = devindex; | |||
fMidiSource = new MidiSlot[fRealPlaybackChannels]; | |||
assert(fMidiSource); | |||
// Real output | |||
devindex = 0; | |||
for (int i = 0; i < fRealPlaybackChannels; i++) { | |||
MMRESULT res; | |||
HMIDIOUT handle; | |||
fMidiSource[devindex].fIndex = i; | |||
UINT ret = midiOutOpen(&handle, fMidiSource[devindex].fIndex, 0L, 0L, CALLBACK_NULL); | |||
if (ret == MMSYSERR_NOERROR) { | |||
fMidiSource[devindex].fHandle = handle; | |||
if (!InitHeaders(&fMidiSource[devindex])) { | |||
jack_error("memory allocation failed"); | |||
midiOutClose(handle); | |||
continue; | |||
} | |||
res = midiOutPrepareHeader(handle, fMidiSource[devindex].fHeader, sizeof(MIDIHDR)); | |||
if (res != MMSYSERR_NOERROR) { | |||
jack_error("midiOutPrepareHeader error %d %d %d", i, handle, res); | |||
continue; | |||
} else { | |||
fMidiSource[devindex].fHeader->dwUser = 1; | |||
} | |||
} else { | |||
jack_error("midiOutOpen error"); | |||
continue; | |||
if (output_ports) { | |||
for (int i = 0; i < fPlaybackChannels; i++) { | |||
delete output_ports[i]; | |||
} | |||
devindex += 1; | |||
delete[] output_ports; | |||
output_ports = 0; | |||
} | |||
fRealPlaybackChannels = devindex; | |||
fPlaybackChannels = devindex; | |||
return 0; | |||
return result; | |||
} | |||
void JackWinMMEDriver::CloseInput(MidiSlot* slot) | |||
int | |||
JackWinMMEDriver::Open(bool capturing, bool playing, int in_channels, | |||
int out_channels, bool monitor, | |||
const char* capture_driver_name, | |||
const char* playback_driver_name, | |||
jack_nframes_t capture_latency, | |||
jack_nframes_t playback_latency) | |||
{ | |||
MMRESULT res; | |||
int retry = 0; | |||
if (slot->fHandle == 0) | |||
return; | |||
HMIDIIN handle = (HMIDIIN)slot->fHandle; | |||
slot->fHeader->dwUser = 0; | |||
res = midiInStop(handle); | |||
if (res != MMSYSERR_NOERROR) { | |||
jack_error("midiInStop error"); | |||
const char *client_name = fClientControl.fName; | |||
int input_count = 0; | |||
int num_potential_inputs = midiInGetNumDevs(); | |||
int num_potential_outputs = midiOutGetNumDevs(); | |||
int output_count = 0; | |||
if (num_potential_inputs) { | |||
try { | |||
input_ports = new JackWinMMEInputPort *[num_potential_inputs]; | |||
} catch (std::exception e) { | |||
jack_error("JackWinMMEDriver::Open - while creating input port " | |||
"array: %s", e.what()); | |||
return -1; | |||
} | |||
for (int i = 0; i < num_potential_inputs; i++) { | |||
try { | |||
input_ports[input_count] = | |||
new JackWinMMEInputPort(fAliasName, client_name, | |||
capture_driver_name, i); | |||
} catch (std::exception e) { | |||
jack_error("JackWinMMEDriver::Open - while creating input " | |||
"port: %s", e.what()); | |||
continue; | |||
} | |||
input_count++; | |||
} | |||
} | |||
res = midiInReset(handle); | |||
if (res != MMSYSERR_NOERROR) { | |||
jack_error("midiInReset error"); | |||
if (num_potential_outputs) { | |||
try { | |||
output_ports = new JackWinMMEOutputPort *[num_potential_outputs]; | |||
} catch (std::exception e) { | |||
jack_error("JackWinMMEDriver::Open - while creating output port " | |||
"array: %s", e.what()); | |||
goto destroy_input_ports; | |||
} | |||
for (int i = 0; i < num_potential_outputs; i++) { | |||
try { | |||
output_ports[output_count] = | |||
new JackWinMMEOutputPort(fAliasName, client_name, | |||
playback_driver_name, i); | |||
} catch (std::exception e) { | |||
jack_error("JackWinMMEDriver::Open - while creating output " | |||
"port: %s", e.what()); | |||
continue; | |||
} | |||
output_count++; | |||
} | |||
} | |||
res = midiInUnprepareHeader(handle, slot->fHeader, sizeof(MIDIHDR)); | |||
if (res != MMSYSERR_NOERROR) { | |||
jack_error("midiInUnprepareHeader error"); | |||
if (! (input_count || output_count)) { | |||
jack_error("JackWinMMEDriver::Open - no WinMME inputs or outputs " | |||
"allocated."); | |||
} else if (! JackMidiDriver::Open(capturing, playing, input_count, | |||
output_count, monitor, | |||
capture_driver_name, | |||
playback_driver_name, capture_latency, | |||
playback_latency)) { | |||
return 0; | |||
} | |||
do { | |||
res = midiInClose(handle); | |||
if (res != MMSYSERR_NOERROR) { | |||
jack_error("midiInClose error"); | |||
destroy_input_ports: | |||
if (input_ports) { | |||
for (int i = 0; i < input_count; i++) { | |||
delete input_ports[i]; | |||
} | |||
if (res == MIDIERR_STILLPLAYING) | |||
midiInReset(handle); | |||
Sleep (10); | |||
retry++; | |||
} while ((res == MIDIERR_STILLPLAYING) && (retry < 10)); | |||
if (slot->fHeader) { | |||
GlobalFreePtr(slot->fHeader); | |||
delete[] input_ports; | |||
input_ports = 0; | |||
} | |||
return -1; | |||
} | |||
void JackWinMMEDriver::CloseOutput(MidiSlot* slot) | |||
int | |||
JackWinMMEDriver::Read() | |||
{ | |||
MMRESULT res; | |||
int retry = 0; | |||
if (slot->fHandle == 0) | |||
return; | |||
HMIDIOUT handle = (HMIDIOUT)slot->fHandle; | |||
res = midiOutReset(handle); | |||
if (res != MMSYSERR_NOERROR) | |||
jack_error("midiOutReset error"); | |||
midiOutUnprepareHeader(handle, slot->fHeader, sizeof(MIDIHDR)); | |||
do { | |||
res = midiOutClose(handle); | |||
if (res != MMSYSERR_NOERROR) | |||
jack_error("midiOutClose error"); | |||
Sleep(10); | |||
retry++; | |||
} while ((res == MIDIERR_STILLPLAYING) && (retry < 10)); | |||
if (slot->fHeader) { | |||
GlobalFreePtr(slot->fHeader); | |||
jack_nframes_t buffer_size = fEngineControl->fBufferSize; | |||
for (int i = 0; i < fCaptureChannels; i++) { | |||
input_ports[i]->ProcessJack(GetInputBuffer(i), buffer_size); | |||
} | |||
return 0; | |||
} | |||
int JackWinMMEDriver::Close() | |||
int | |||
JackWinMMEDriver::Start() | |||
{ | |||
jack_log("JackWinMMEDriver::Close"); | |||
jack_info("JackWinMMEDriver::Start - Starting driver."); | |||
JackMidiDriver::Start(); | |||
int input_count = 0; | |||
int output_count = 0; | |||
// Generic midi driver close | |||
int res = JackMidiDriver::Close(); | |||
jack_info("JackWinMMEDriver::Start - Enabling input ports."); | |||
// Close input | |||
if (fMidiDestination) { | |||
for (int i = 0; i < fRealCaptureChannels; i++) { | |||
CloseInput(&fMidiDestination[i]); | |||
for (; input_count < fCaptureChannels; input_count++) { | |||
if (input_ports[input_count]->Start() < 0) { | |||
jack_error("JackWinMMEDriver::Start - Failed to enable input " | |||
"port."); | |||
goto stop_input_ports; | |||
} | |||
delete[] fMidiDestination; | |||
} | |||
// Close output | |||
if (fMidiSource) { | |||
for (int i = 0; i < fRealPlaybackChannels; i++) { | |||
CloseOutput(&fMidiSource[i]); | |||
jack_info("JackWinMMEDriver::Start - Enabling output ports."); | |||
for (; output_count < fPlaybackChannels; output_count++) { | |||
if (output_ports[output_count]->Start() < 0) { | |||
jack_error("JackWinMMEDriver::Start - Failed to enable output " | |||
"port."); | |||
goto stop_output_ports; | |||
} | |||
delete[] fMidiSource; | |||
} | |||
return res; | |||
} | |||
jack_info("JackWinMMEDriver::Start - Driver started."); | |||
int JackWinMMEDriver::Attach() | |||
{ | |||
JackPort* port; | |||
jack_port_id_t port_index; | |||
char name[JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE]; | |||
char alias[JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE]; | |||
MMRESULT res; | |||
int i; | |||
jack_log("JackMidiDriver::Attach fBufferSize = %ld fSampleRate = %ld", fEngineControl->fBufferSize, fEngineControl->fSampleRate); | |||
for (i = 0; i < fCaptureChannels; i++) { | |||
MIDIINCAPS caps; | |||
res = midiInGetDevCaps(fMidiDestination[i].fIndex, &caps, sizeof(caps)); | |||
if (res == MMSYSERR_NOERROR) { | |||
snprintf(alias, sizeof(alias) - 1, "%s:%s:out%d", fAliasName, caps.szPname, i + 1); | |||
} else { | |||
snprintf(alias, sizeof(alias) - 1, "%s:%s:out%d", fAliasName, fCaptureDriverName, i + 1); | |||
} | |||
snprintf(name, sizeof(name) - 1, "%s:capture_%d", fClientControl.fName, i + 1); | |||
if ((port_index = fGraphManager->AllocatePort(fClientControl.fRefNum, name, JACK_DEFAULT_MIDI_TYPE, CaptureDriverFlags, fEngineControl->fBufferSize)) == NO_PORT) { | |||
jack_error("driver: cannot register port for %s", name); | |||
return -1; | |||
return 0; | |||
stop_output_ports: | |||
for (int i = 0; i < output_count; i++) { | |||
if (output_ports[i]->Stop() < 0) { | |||
jack_error("JackWinMMEDriver::Start - Failed to disable output " | |||
"port."); | |||
} | |||
port = fGraphManager->GetPort(port_index); | |||
port->SetAlias(alias); | |||
fCapturePortList[i] = port_index; | |||
jack_log("JackMidiDriver::Attach fCapturePortList[i] port_index = %ld", port_index); | |||
} | |||
for (i = 0; i < fPlaybackChannels; i++) { | |||
MIDIOUTCAPS caps; | |||
res = midiOutGetDevCaps(fMidiSource[i].fIndex, &caps, sizeof(caps)); | |||
if (res == MMSYSERR_NOERROR) { | |||
snprintf(alias, sizeof(alias) - 1, "%s:%s:out%d", fAliasName, caps.szPname, i + 1); | |||
} else { | |||
snprintf(alias, sizeof(alias) - 1, "%s:%s:out%d", fAliasName, fPlaybackDriverName, i + 1); | |||
} | |||
snprintf(name, sizeof(name) - 1, "%s:playback_%d", fClientControl.fName, i + 1); | |||
if ((port_index = fGraphManager->AllocatePort(fClientControl.fRefNum, name, JACK_DEFAULT_MIDI_TYPE, PlaybackDriverFlags, fEngineControl->fBufferSize)) == NO_PORT) { | |||
jack_error("driver: cannot register port for %s", name); | |||
return -1; | |||
stop_input_ports: | |||
for (int i = 0; i < input_count; i++) { | |||
if (input_ports[i]->Stop() < 0) { | |||
jack_error("JackWinMMEDriver::Start - Failed to disable input " | |||
"port."); | |||
} | |||
port = fGraphManager->GetPort(port_index); | |||
port->SetAlias(alias); | |||
fPlaybackPortList[i] = port_index; | |||
jack_log("JackMidiDriver::Attach fPlaybackPortList[i] port_index = %ld", port_index); | |||
} | |||
return 0; | |||
return -1; | |||
} | |||
int JackWinMMEDriver::Read() | |||
int | |||
JackWinMMEDriver::Stop() | |||
{ | |||
size_t size; | |||
int result = 0; | |||
for (int chan = 0; chan < fCaptureChannels; chan++) { | |||
jack_info("JackWinMMEDriver::Stop - disabling input ports."); | |||
if (fGraphManager->GetConnectionsNum(fCapturePortList[chan]) > 0) { | |||
JackMidiBuffer* midi_buffer = GetInputBuffer(chan); | |||
if (jack_ringbuffer_read_space (fRingBuffer[chan]) == 0) { | |||
// Reset buffer | |||
midi_buffer->Reset(midi_buffer->nframes); | |||
} else { | |||
while ((size = jack_ringbuffer_read_space (fRingBuffer[chan])) > 0) { | |||
for (int i = 0; i < fCaptureChannels; i++) { | |||
if (input_ports[i]->Stop() < 0) { | |||
jack_error("JackWinMMEDriver::Stop - Failed to disable input " | |||
"port."); | |||
result = -1; | |||
} | |||
} | |||
//jack_info("jack_ringbuffer_read_space %d", size); | |||
int ev_count = 0; | |||
jack_ringbuffer_read(fRingBuffer[chan], (char*)&ev_count, sizeof(int)); | |||
jack_info("JackWinMMEDriver::Stop - disabling output ports."); | |||
if (ev_count > 0) { | |||
for (int j = 0; j < ev_count; j++) { | |||
unsigned int event_len = 3; | |||
// Read event actual data | |||
jack_midi_data_t* dest = midi_buffer->ReserveEvent(0, event_len); | |||
jack_ringbuffer_read(fRingBuffer[chan], (char*)dest, event_len); | |||
} | |||
} | |||
} | |||
} | |||
} else { | |||
//jack_info("Consume ring buffer"); | |||
jack_ringbuffer_read_advance(fRingBuffer[chan], jack_ringbuffer_read_space(fRingBuffer[chan])); | |||
for (int i = 0; i < fPlaybackChannels; i++) { | |||
if (output_ports[i]->Stop() < 0) { | |||
jack_error("JackWinMMEDriver::Stop - Failed to disable output " | |||
"port."); | |||
result = -1; | |||
} | |||
} | |||
return 0; | |||
return result; | |||
} | |||
int JackWinMMEDriver::Write() | |||
int | |||
JackWinMMEDriver::Write() | |||
{ | |||
for (int chan = 0; chan < fPlaybackChannels; chan++) { | |||
if (fGraphManager->GetConnectionsNum(fPlaybackPortList[chan]) > 0) { | |||
JackMidiBuffer* midi_buffer = GetOutputBuffer(chan); | |||
// TODO : use timestamp | |||
for (unsigned int j = 0; j < midi_buffer->event_count; j++) { | |||
JackMidiEvent* ev = &midi_buffer->events[j]; | |||
if (ev->size <= 3) { | |||
jack_midi_data_t *d = ev->GetData(midi_buffer); | |||
DWORD winev = 0; | |||
if (ev->size > 0) winev |= d[0]; | |||
if (ev->size > 1) winev |= (d[1] << 8); | |||
if (ev->size > 2) winev |= (d[2] << 16); | |||
MMRESULT res = midiOutShortMsg((HMIDIOUT)fMidiSource[chan].fHandle, winev); | |||
if (res != MMSYSERR_NOERROR) | |||
jack_error ("midiOutShortMsg error res %d", res); | |||
} else { | |||
} | |||
} | |||
} | |||
jack_nframes_t buffer_size = fEngineControl->fBufferSize; | |||
for (int i = 0; i < fPlaybackChannels; i++) { | |||
output_ports[i]->ProcessJack(GetOutputBuffer(i), buffer_size); | |||
} | |||
return 0; | |||
} | |||
} // end of namespace | |||
#ifdef __cplusplus | |||
extern "C" | |||
{ | |||
@@ -1,5 +1,6 @@ | |||
/* | |||
Copyright (C) 2009 Grame | |||
Copyright (C) 2011 Devin Anderson | |||
This program is free software; you can redistribute it and/or modify | |||
it under the terms of the GNU General Public License as published by | |||
@@ -21,67 +22,51 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |||
#define __JackWinMMEDriver__ | |||
#include "JackMidiDriver.h" | |||
#include "JackTime.h" | |||
#include "JackWinMMEInputPort.h" | |||
#include "JackWinMMEOutputPort.h" | |||
namespace Jack | |||
{ | |||
namespace Jack { | |||
/*! | |||
\brief The WinMME driver. | |||
*/ | |||
#define kBuffSize 512 | |||
struct MidiSlot { | |||
LPVOID fHandle; // MMSystem handler | |||
short fIndex; // MMSystem dev index | |||
LPMIDIHDR fHeader; // for long msg output | |||
MidiSlot():fHandle(0),fIndex(0) | |||
{} | |||
class JackWinMMEDriver : public JackMidiDriver { | |||
}; | |||
private: | |||
class JackWinMMEDriver : public JackMidiDriver | |||
{ | |||
JackWinMMEInputPort **input_ports; | |||
JackWinMMEOutputPort **output_ports; | |||
private: | |||
public: | |||
int fRealCaptureChannels; | |||
int fRealPlaybackChannels; | |||
JackWinMMEDriver(const char* name, const char* alias, | |||
JackLockedEngine* engine, JackSynchro* table); | |||
MidiSlot* fMidiSource; | |||
MidiSlot* fMidiDestination; | |||
~JackWinMMEDriver(); | |||
void CloseInput(MidiSlot* slot); | |||
void CloseOutput(MidiSlot* slot); | |||
int | |||
Attach(); | |||
static void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2); | |||
int | |||
Close(); | |||
public: | |||
int | |||
Open(bool capturing, bool playing, int num_inputs, int num_outputs, | |||
bool monitor, const char* capture_driver_name, | |||
const char* playback_driver_name, jack_nframes_t capture_latency, | |||
jack_nframes_t playback_latency); | |||
JackWinMMEDriver(const char* name, const char* alias, JackLockedEngine* engine, JackSynchro* table); | |||
virtual ~JackWinMMEDriver(); | |||
int | |||
Read(); | |||
int Open(bool capturing, | |||
bool playing, | |||
int chan_in, | |||
int chan_out, | |||
bool monitor, | |||
const char* capture_driver_name, | |||
const char* playback_driver_name, | |||
jack_nframes_t capture_latency, | |||
jack_nframes_t playback_latency); | |||
int Close(); | |||
int | |||
Start(); | |||
int Attach(); | |||
int | |||
Stop(); | |||
int Read(); | |||
int Write(); | |||
int | |||
Write(); | |||
}; | |||
}; | |||
} // end of namespace | |||
} | |||
#endif |
@@ -0,0 +1,287 @@ | |||
/* | |||
Copyright (C) 2011 Devin Anderson | |||
This program is free software; you can redistribute it and/or modify | |||
it under the terms of the GNU General Public License as published by | |||
the Free Software Foundation; either version 2 of the License, or | |||
(at your option) any later version. | |||
This program 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 General Public License for more details. | |||
You should have received a copy of the GNU General Public License | |||
along with this program; if not, write to the Free Software | |||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |||
*/ | |||
#include <cassert> | |||
#include <memory> | |||
#include <stdexcept> | |||
#include "JackError.h" | |||
#include "JackMidiUtil.h" | |||
#include "JackWinMMEInputPort.h" | |||
using Jack::JackWinMMEInputPort; | |||
/////////////////////////////////////////////////////////////////////////////// | |||
// Static callbacks | |||
/////////////////////////////////////////////////////////////////////////////// | |||
void CALLBACK | |||
JackWinMMEInputPort::HandleMidiInputEvent(HMIDIIN handle, UINT message, | |||
DWORD port, DWORD param1, | |||
DWORD param2) | |||
{ | |||
((JackWinMMEInputPort *) port)->ProcessWinMME(message, param1, param2); | |||
} | |||
/////////////////////////////////////////////////////////////////////////////// | |||
// Class | |||
/////////////////////////////////////////////////////////////////////////////// | |||
JackWinMMEInputPort::JackWinMMEInputPort(const char *alias_name, | |||
const char *client_name, | |||
const char *driver_name, UINT index, | |||
size_t max_bytes, size_t max_messages) | |||
{ | |||
thread_queue = new JackMidiAsyncQueue(max_bytes, max_messages); | |||
std::auto_ptr<JackMidiAsyncQueue> thread_queue_ptr(thread_queue); | |||
write_queue = new JackMidiBufferWriteQueue(); | |||
std::auto_ptr<JackMidiBufferWriteQueue> write_queue_ptr(write_queue); | |||
sysex_buffer = new jack_midi_data_t[max_bytes]; | |||
char error_message[MAXERRORLENGTH]; | |||
MMRESULT result = midiInOpen(&handle, index, HandleMidiInputEvent, this, | |||
CALLBACK_FUNCTION); | |||
if (result != MMSYSERR_NOERROR) { | |||
GetErrorString(result, error_message); | |||
goto delete_sysex_buffer; | |||
} | |||
sysex_header.dwBufferLength = max_bytes; | |||
sysex_header.dwBytesRecorded = 0; | |||
sysex_header.dwFlags = 0; | |||
sysex_header.dwUser = 0; | |||
sysex_header.lpData = (((LPBYTE) sysex_header) + sizeof(MIDIHDR)); | |||
sysex_header.lpNext = 0; | |||
result = midiInPrepareHeader(handle, &sysex_header, sizeof(MIDIHDR)); | |||
if (result != MMSYSERR_NOERROR) { | |||
GetErrorString(result, error_message); | |||
goto close_handle; | |||
} | |||
result = midiInAddBuffer(handle, &sysex_header, sizeof(MIDIHDR)); | |||
if (result != MMSYSERR_NOERROR) { | |||
GetErrorString(result, error_message); | |||
goto unprepare_header; | |||
} | |||
jack_event = 0; | |||
started = false; | |||
write_queue_ptr.release(); | |||
thread_queue_ptr.release(); | |||
return; | |||
unprepare_header: | |||
result = midiInUnprepareHeader(handle, sysex_header, sizeof(MIDIHDR)); | |||
if (result != MMSYSERR_NOERROR) { | |||
WriteError("JackWinMMEInputPort [constructor]", | |||
"midiInUnprepareHeader", result); | |||
} | |||
close_handle: | |||
result = midiInClose(handle); | |||
if (result != MMSYSERR_NOERROR) { | |||
WriteError("JackWinMMEInputPort [constructor]", "midiInClose", result); | |||
} | |||
delete_sysex_buffer: | |||
delete[] sysex_buffer; | |||
throw std::runtime_error(error_message); | |||
} | |||
JackWinMMEInputPort::~JackWinMMEInputPort() | |||
{ | |||
Stop(); | |||
MMRESULT result = midiInReset(handle); | |||
if (result != MMSYSERR_NOERROR) { | |||
WriteError("JackWinMMEInputPort [destructor]", "midiInReset", result); | |||
} | |||
result = midiInUnprepareHeader(handle, sysex_header, sizeof(MIDIHDR)); | |||
if (result != MMSYSERR_NOERROR) { | |||
WriteError("JackWinMMEInputPort [destructor]", "midiInUnprepareHeader", | |||
result); | |||
} | |||
result = midiInClose(handle); | |||
if (result != MMSYSERR_NOERROR) { | |||
WriteError("JackWinMMEInputPort [destructor]", "midiInClose", result); | |||
} | |||
delete[] sysex_buffer; | |||
delete thread_queue; | |||
delete write_queue; | |||
} | |||
void | |||
JackWinMMEInputPort::EnqueueMessage(jack_nframes_t time, size_t length, | |||
jack_midi_data_t *data) | |||
{ | |||
switch (thread_queue->EnqueueEvent(time, length, data)) { | |||
case JackMidiWriteQueue::BUFFER_FULL: | |||
jack_error("JackWinMMEInputPort::EnqueueMessage - The thread queue " | |||
"cannot currently accept a %d-byte event. Dropping event.", | |||
size); | |||
break; | |||
case JackMidiWriteQueue::BUFFER_TOO_SMALL: | |||
jack_error("JackWinMMEInputPort::EnqueueMessage - The thread queue " | |||
"buffer is too small to enqueue a %d-byte event. Dropping " | |||
"event.", size); | |||
break; | |||
default: | |||
; | |||
} | |||
} | |||
const char * | |||
JackWinMMEInputPort::GetAlias() | |||
{ | |||
return alias; | |||
} | |||
void | |||
JackWinMMEInputPort::GetErrorString(MMRESULT error, LPTSTR text) | |||
{ | |||
MMRESULT result = midiInGetErrorText(error, text, MAXERRORLENGTH); | |||
if (result != MMSYSERR_NOERROR) { | |||
snprintf(text, MAXERRORLENGTH, "Unknown error code '%d'", error); | |||
} | |||
} | |||
const char * | |||
JackWinMMEInputPort::GetName() | |||
{ | |||
return name; | |||
} | |||
void | |||
JackWinMMEInputPort::ProcessJack() | |||
{ | |||
write_queue->ResetMidiBuffer(port_buffer, frames); | |||
if (! jack_event) { | |||
jack_event = thread_queue->DequeueEvent(); | |||
} | |||
for (; jack_event; jack_event = thread_queue->DequeueEvent()) { | |||
switch (write_queue->EnqueueEvent(event)) { | |||
case BUFFER_TOO_SMALL: | |||
jack_error("JackWinMMEMidiInputPort::Process - The buffer write " | |||
"queue couldn't enqueue a %d-byte event. Dropping " | |||
"event.", event->size); | |||
// Fallthrough on purpose | |||
case OK: | |||
continue; | |||
} | |||
break; | |||
} | |||
} | |||
void | |||
JackWinMMEInputPort::ProcessWinMME(UINT message, DWORD param1, DWORD param2) | |||
{ | |||
jack_nframes_t current_frame = GetCurrentFrame(); | |||
switch (message) { | |||
case MIM_CLOSE: | |||
jack_info("JackWinMMEInputPort::ProcessWinMME - MIDI device closed."); | |||
break; | |||
case MIM_MOREDATA: | |||
jack_info("JackWinMMEInputPort::ProcessWinMME - The MIDI input device " | |||
"driver thinks that JACK is not processing messages fast " | |||
"enough."); | |||
// Fallthrough on purpose. | |||
case MIM_DATA: | |||
jack_midi_data_t message_buffer[3]; | |||
jack_midi_data_t status = param1 & 0xff; | |||
int length = GetMessageLength(status); | |||
switch (length) { | |||
case 3: | |||
message_buffer[2] = param1 & 0xff0000; | |||
// Fallthrough on purpose. | |||
case 2: | |||
message_buffer[1] = param1 & 0xff00; | |||
// Fallthrough on purpose. | |||
case 1: | |||
message_buffer[0] = status; | |||
break; | |||
case 0: | |||
jack_error("JackWinMMEInputPort::ProcessWinMME - **BUG** MIDI " | |||
"input driver sent an MIM_DATA message with a sysex " | |||
"status byte."); | |||
return; | |||
case -1: | |||
jack_error("JackWinMMEInputPort::ProcessWinMME - **BUG** MIDI " | |||
"input driver sent an MIM_DATA message with an invalid " | |||
"status byte."); | |||
return; | |||
} | |||
EnqueueMessage(current_frame, (size_t) length, message_buffer); | |||
break; | |||
case MIM_LONGDATA: | |||
LPMIDIHDR header = (LPMIDIHDR) dwParam1; | |||
jack_midi_data_t *data = (jack_midi_data_t *) header->lpData; | |||
size_t length = header->dwBytesRecorded; | |||
if ((data[0] != 0xf0) || (data[length - 1] != 0xf7)) { | |||
jack_error("JackWinMMEInputPort::ProcessWinMME - Discarding " | |||
"%d-byte sysex chunk.", length); | |||
} else { | |||
EnqueueMessage(current_frame, length, data); | |||
} | |||
// Is this realtime-safe? This function isn't run in the JACK thread, | |||
// but we still want it to perform as quickly as possible. Even if | |||
// this isn't realtime safe, it may not be avoidable. | |||
MMRESULT result = midiInAddBuffer(handle, &sysex_header, | |||
sizeof(MIDIHDR)); | |||
if (result != MMSYSERR_NOERROR) { | |||
WriteError("JackWinMMEInputPort::ProcessWinMME", "midiInAddBuffer", | |||
result); | |||
} | |||
break; | |||
case MIM_LONGERROR: | |||
jack_error("JackWinMMEInputPort::ProcessWinMME - Invalid or " | |||
"incomplete sysex message received."); | |||
break; | |||
case MIM_OPEN: | |||
jack_info("JackWinMMEInputPort::ProcessWinMME - MIDI device opened."); | |||
} | |||
} | |||
bool | |||
JackWinMMEInputPort::Start() | |||
{ | |||
if (! started) { | |||
MMRESULT result = midiInStart(handle); | |||
started = result == MMSYSERR_NOERROR; | |||
if (! started) { | |||
WriteError("JackWinMMEInputPort::Start", "midiInStart", result); | |||
} | |||
} | |||
return started; | |||
} | |||
bool | |||
JackWinMMEInputPort::Stop() | |||
{ | |||
if (started) { | |||
MMRESULT result = midiInStop(handle); | |||
started = result != MMSYSERR_NOERROR; | |||
if (started) { | |||
WriteError("JackWinMMEInputPort::Stop", "midiInStop", result); | |||
} | |||
} | |||
return ! started; | |||
} | |||
void | |||
JackWinMMEInputPort::WriteError(const char *jack_func, const char *mm_func, | |||
MMRESULT result) | |||
{ | |||
const char error_message[MAXERRORLENGTH]; | |||
GetErrorString(result, error_message); | |||
jack_error("%s - %s: %s", jack_func, mm_func, error_message); | |||
} |
@@ -0,0 +1,89 @@ | |||
/* | |||
Copyright (C) 2011 Devin Anderson | |||
This program is free software; you can redistribute it and/or modify | |||
it under the terms of the GNU General Public License as published by | |||
the Free Software Foundation; either version 2 of the License, or | |||
(at your option) any later version. | |||
This program 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 General Public License for more details. | |||
You should have received a copy of the GNU General Public License | |||
along with this program; if not, write to the Free Software | |||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |||
*/ | |||
#ifndef __JackWinMMEInputPort__ | |||
#define __JackWinMMEInputPort__ | |||
#include <mmsystem.h> | |||
#include "JackMidiAsyncQueue.h" | |||
#include "JackMidiBufferWriteQueue.h" | |||
namespace Jack { | |||
class JackWinMMEInputPort { | |||
private: | |||
static void CALLBACK | |||
HandleMidiInputEvent(HMIDIIN handle, UINT message, DWORD port, | |||
DWORD param1, DWORD param2); | |||
void | |||
EnqueueMessage(jack_nframes_t time, size_t length, | |||
jack_midi_data_t *data); | |||
void | |||
GetErrorString(MMRESULT error, LPTSTR text); | |||
void | |||
ProcessWinMME(UINT message, DWORD param1, DWORD param2); | |||
void | |||
WriteError(const char *jack_func, const char *mm_func, | |||
MMRESULT result); | |||
char alias[JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE]; | |||
HMIDIIN handle; | |||
jack_midi_event_t *jack_event; | |||
char name[JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE]; | |||
bool started; | |||
jack_midi_data_t *sysex_buffer; | |||
MIDIHDR sysex_header | |||
JackMidiAsyncQueue *thread_queue; | |||
JackMidiBufferWriteQueue *write_queue; | |||
public: | |||
JackWinMMEInputPort(const char *alias_name, const char *client_name, | |||
const char *driver_name, UINT index, | |||
size_t max_bytes=4096, size_t max_messages=1024); | |||
~JackWinMMEInputPort(); | |||
const char * | |||
GetAlias(); | |||
const char * | |||
GetName(); | |||
void | |||
ProcessJack(); | |||
bool | |||
Start(); | |||
bool | |||
Stop(); | |||
}; | |||
} | |||
#endif |
@@ -0,0 +1,352 @@ | |||
/* | |||
Copyright (C) 2011 Devin Anderson | |||
This program is free software; you can redistribute it and/or modify | |||
it under the terms of the GNU General Public License as published by | |||
the Free Software Foundation; either version 2 of the License, or | |||
(at your option) any later version. | |||
This program 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 General Public License for more details. | |||
You should have received a copy of the GNU General Public License | |||
along with this program; if not, write to the Free Software | |||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |||
*/ | |||
#include <memory> | |||
#include <stdexcept> | |||
#include "JackMidiUtil.h" | |||
#include "JackTime.h" | |||
#include "JackWinMMEOutputPort.h" | |||
using Jack::JackWinMMEOutputPort; | |||
/////////////////////////////////////////////////////////////////////////////// | |||
// Static callbacks | |||
/////////////////////////////////////////////////////////////////////////////// | |||
void CALLBACK | |||
JackWinMMEOutputPort::HandleMessageEvent(HMIDIOUT handle, UINT message, | |||
DWORD_PTR port, DWORD_PTR param1, | |||
DWORD_PTR param2) | |||
{ | |||
((JackWinMMEOutputPort *) port)->HandleMessage(message, param1, param2); | |||
} | |||
/////////////////////////////////////////////////////////////////////////////// | |||
// Class | |||
/////////////////////////////////////////////////////////////////////////////// | |||
JackWinMMEOutputPort::JackWinMMEOutputPort(const char *alias_name, | |||
const char *client_name, | |||
const char *driver_name, UINT index, | |||
size_t max_bytes, | |||
size_t max_messages) | |||
{ | |||
read_queue = new JackMidiBufferReadQueue(); | |||
std::auto_ptr<JackMidiBufferReadQueue> read_queue_ptr(read_queue); | |||
thread_queue = new JackMidiAsyncQueue(max_bytes, max_messages); | |||
std::auto_ptr<JackMidiAsyncQueue> thread_queue_ptr(thread_queue); | |||
char error_message[MAXERRORLENGTH]; | |||
MMRESULT result = midiOutOpen(&handle, index, HandleMessageEvent, this, | |||
CALLBACK_FUNCTION); | |||
if (result != MMSYSERR_NOERROR) { | |||
GetErrorString(result, error_message); | |||
goto raise_exception; | |||
} | |||
thread_queue_semaphore = CreateSemaphore(NULL, 0, max_messages, NULL); | |||
if (thread_queue_semaphore == NULL) { | |||
GetOSErrorString(error_message); | |||
goto close_handle; | |||
} | |||
sysex_semaphore = CreateSemaphore(NULL, 0, 1, NULL); | |||
if (sysex_semaphore == NULL) { | |||
GetOSErrorString(error_message); | |||
goto destroy_thread_queue_semaphore; | |||
} | |||
MIDIINCAPS capabilities; | |||
char *name; | |||
result = midiOutGetDevCaps(index, &capabilities, sizeof(capabilities)); | |||
if (result != MMSYSERR_NOERROR) { | |||
WriteMMError("JackWinMMEOutputPort [constructor]", "midiOutGetDevCaps", | |||
result); | |||
name = driver_name; | |||
} else { | |||
name = capabilities.szPname; | |||
} | |||
snprintf(alias, sizeof(alias) - 1, "%s:%s:out%d", alias_name, driver_name, | |||
index + 1); | |||
snprintf(name, sizeof(name) - 1, "%s:playback_%d", client_name, index + 1); | |||
write_queue_ptr.release(); | |||
thread_queue_ptr.release(); | |||
return; | |||
destroy_thread_queue_semaphore: | |||
if (! CloseHandle(thread_queue_semaphore)) { | |||
WriteOSError("JackWinMMEOutputPort [constructor]", "CloseHandle"); | |||
} | |||
close_handle: | |||
result = midiOutClose(handle); | |||
if (result != MMSYSERR_NOERROR) { | |||
WriteMMError("JackWinMMEOutputPort [constructor]", "midiOutClose", | |||
result); | |||
} | |||
raise_exception: | |||
throw std::runtime_error(error_message); | |||
} | |||
JackWinMMEOutputPort::~JackWinMMEOutputPort() | |||
{ | |||
Stop(); | |||
MMRESULT result = midiOutReset(handle); | |||
if (result != MMSYSERR_NOERROR) { | |||
WriteMMError("JackWinMMEOutputPort [destructor]", "midiOutReset", | |||
result); | |||
} | |||
result = midiOutClose(handle); | |||
if (result != MMSYSERR_NOERROR) { | |||
WriteMMError("JackWinMMEOutputPort [destructor]", "midiOutClose", | |||
result); | |||
} | |||
if (! CloseHandle(sysex_semaphore)) { | |||
WriteOSError("JackWinMMEOutputPort [destructor]", "CloseHandle"); | |||
} | |||
if (! CloseHandle(thread_queue_semaphore)) { | |||
WriteOSError("JackWinMMEOutputPort [destructor]", "CloseHandle"); | |||
} | |||
delete read_queue; | |||
delete thread_queue; | |||
} | |||
bool | |||
JackWinMMEOutputPort::Execute() | |||
{ | |||
for (;;) { | |||
if (! Wait(thread_queue_semaphore)) { | |||
break; | |||
} | |||
jack_midi_event_t *event = thread_queue->DequeueEvent(); | |||
if (! event) { | |||
break; | |||
} | |||
jack_time_t frame_time = GetTimeFromFrames(event->time); | |||
for (jack_time_t current_time = GetMicroSeconds(); | |||
frame_time > current_time; current_time = GetMicroSeconds()) { | |||
jack_time_t sleep_time = frame_time - current_time; | |||
// Windows has a millisecond sleep resolution for its Sleep calls. | |||
// This is unfortunate, as MIDI timing often requires a higher | |||
// resolution. For now, we attempt to compensate by letting an | |||
// event be sent if we're less than 500 microseconds from sending | |||
// the event. We assume that it's better to let an event go out | |||
// 499 microseconds early than let an event go out 501 microseconds | |||
// late. Of course, that's assuming optimal sleep times, which is | |||
// a whole different Windows issue ... | |||
if (sleep_time < 500) { | |||
break; | |||
} | |||
if (sleep_time < 1000) { | |||
sleep_time = 1000; | |||
} | |||
JackSleep(sleep_time); | |||
} | |||
jack_midi_data_t *data = event->buffer; | |||
DWORD message = 0; | |||
MMRESULT result; | |||
size_t size = event->size; | |||
switch (size) { | |||
case 3: | |||
message |= (((DWORD) data[2]) << 16); | |||
// Fallthrough on purpose. | |||
case 2: | |||
message |= (((DWORD) data[1]) << 8); | |||
// Fallthrough on purpose. | |||
case 1: | |||
message |= (DWORD) data[0]; | |||
result = midiOutShortMsg(handle, message); | |||
if (result != MMSYSERR_NOERROR) { | |||
WriteMMError("JackWinMMEOutputPort::Execute", | |||
"midiOutShortMsg", result); | |||
} | |||
continue; | |||
} | |||
MIDIHDR header; | |||
header.dwFlags = 0; | |||
header.dwLength = size; | |||
header.lpData = data; | |||
result = midiOutPrepareHeader(handle, &header, sizeof(MIDIHDR)); | |||
if (result != MMSYSERR_NOERROR) { | |||
WriteMMError("JackWinMMEOutputPort::Execute", | |||
"midiOutPrepareHeader", result); | |||
continue; | |||
} | |||
result = midiOutLongMsg(handle, &header, sizeof(MIDIHDR)); | |||
if (result != MMSYSERR_NOERROR) { | |||
WriteMMError("JackWinMMEOutputPort::Execute", "midiOutLongMsg", | |||
result); | |||
continue; | |||
} | |||
// System exclusive messages may be sent synchronously or | |||
// asynchronously. The choice is up to the WinMME driver. So, we wait | |||
// until the message is sent, regardless of the driver's choice. | |||
if (! Wait(sysex_semaphore)) { | |||
break; | |||
} | |||
result = midiOutUnprepareHeader(handle, &header, sizeof(MIDIHDR)); | |||
if (result != MMSYSERR_NOERROR) { | |||
WriteMMError("JackWinMMEOutputPort::Execute", | |||
"midiOutUnprepareHeader", result); | |||
break; | |||
} | |||
} | |||
stop_execution: | |||
return false; | |||
} | |||
void | |||
JackWinMMEOutputPort::GetMMErrorString(MMRESULT error, LPTSTR text) | |||
{ | |||
MMRESULT result = midiOutGetErrorText(error, text, MAXERRORLENGTH); | |||
if (result != MMSYSERR_NOERROR) { | |||
snprintf(text, MAXERRORLENGTH, "Unknown MM error code '%d'", error); | |||
} | |||
} | |||
void | |||
JackWinMMEOutputPort::GetOSErrorString(LPTSTR text) | |||
{ | |||
DWORD error = GetLastError(); | |||
if (! FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, error, | |||
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), &text, | |||
MAXERRORLENGTH, NULL)) { | |||
snprintf(text, MAXERRORLENGTH, "Unknown OS error code '%d'", error); | |||
} | |||
} | |||
void | |||
JackWinMMEOutputPort::HandleMessage(UINT message, DWORD_PTR param1, | |||
DWORD_PTR param2) | |||
{ | |||
switch (message) { | |||
case MOM_CLOSE: | |||
jack_info("JackWinMMEOutputPort::HandleMessage - MIDI device closed."); | |||
break; | |||
case MOM_DONE: | |||
if (! ReleaseSemaphore(sysex_semaphore, 1, NULL)) { | |||
WriteOSError("JackWinMMEOutputPort::HandleMessage", | |||
"ReleaseSemaphore"); | |||
} | |||
break; | |||
case MOM_OPEN: | |||
jack_info("JackWinMMEOutputPort::HandleMessage - MIDI device opened."); | |||
} | |||
} | |||
bool | |||
JackWinMMEOutputPort::Init() | |||
{ | |||
set_threaded_log_function(); | |||
// XX: Can more be done? Ideally, this thread should have the JACK server | |||
// thread priority + 1. | |||
if (thread->AcquireSelfRealTime()) { | |||
jack_error("JackWinMMEOutputPort::Init - could not acquire realtime " | |||
"scheduling. Continuing anyway."); | |||
} | |||
return true; | |||
} | |||
void | |||
JackWinMMEOutputPort::ProcessJack(JackMidiBuffer *port_buffer, | |||
jack_nframes_t frames) | |||
{ | |||
read_queue->ResetMidiBuffer(port_buffer); | |||
for (jack_midi_event_t *event = read_queue->DequeueEvent(); event; | |||
event = read_queue->DequeueEvent()) { | |||
switch (thread_queue->EnqueueEvent(event, frames)) { | |||
case JackMidiWriteQueue::BUFFER_FULL: | |||
jack_error("JackWinMMEOutputPort::ProcessJack - The thread queue " | |||
"buffer is full. Dropping event."); | |||
break; | |||
case JackMidiWriteQueue::BUFFER_TOO_SMALL: | |||
jack_error("JackWinMMEOutputPort::ProcessJack - The thread queue " | |||
"couldn't enqueue a %d-byte event. Dropping event.", | |||
event->size); | |||
break; | |||
default: | |||
if (! ReleaseSemaphore(thread_queue_semaphore, 1, NULL)) { | |||
WriteOSError("JackWinMMEOutputPort::ProcessJack", | |||
"ReleaseSemaphore"); | |||
} | |||
} | |||
} | |||
} | |||
bool | |||
JackWinMMEOutputPort::Start() | |||
{ | |||
bool result = thread->GetStatus() != JackThread::kIdle; | |||
if (! result) { | |||
result = ! thread->StartSync(); | |||
if (! result) { | |||
jack_error("JackWinMMEOutputPort::Start - failed to start MIDI " | |||
"processing thread."); | |||
} | |||
} | |||
return result; | |||
} | |||
bool | |||
JackWinMMEOutputPort::Stop() | |||
{ | |||
bool result = thread->GetStatus() == JackThread::kIdle; | |||
if (! result) { | |||
result = ! thread->Kill(); | |||
if (! result) { | |||
jack_error("JackWinMMEOutputPort::Stop - failed to stop MIDI " | |||
"processing thread."); | |||
} | |||
} | |||
return result; | |||
} | |||
bool | |||
JackWinMMEOutputPort::Wait(Handle semaphore) | |||
{ | |||
DWORD result = WaitForSingleObject(semaphore, INFINITE); | |||
switch (result) { | |||
case WAIT_FAILED: | |||
WriteOSError("JackWinMMEOutputPort::Wait", "WaitForSingleObject"); | |||
break; | |||
case WAIT_OBJECT_0: | |||
return true; | |||
default: | |||
jack_error("JackWinMMEOutputPort::Wait - unexpected result from " | |||
"'WaitForSingleObject'."); | |||
} | |||
return false; | |||
} | |||
void | |||
JackWinMMEOutputPort::WriteMMError(const char *jack_func, const char *mm_func, | |||
MMRESULT result) | |||
{ | |||
const char error_message[MAXERRORLENGTH]; | |||
GetMMErrorString(result, error_message); | |||
jack_error("%s - %s: %s", jack_func, mm_func, error_message); | |||
} | |||
void | |||
JackWinMMEOutputPort::WriteOSError(const char *jack_func, const char *os_func) | |||
{ | |||
const char error_message[MAXERRORLENGTH]; | |||
GetOSErrorString(result, error_message); | |||
jack_error("%s - %s: %s", jack_func, os_func, error_message); | |||
} |
@@ -0,0 +1,93 @@ | |||
/* | |||
Copyright (C) 2011 Devin Anderson | |||
This program is free software; you can redistribute it and/or modify | |||
it under the terms of the GNU General Public License as published by | |||
the Free Software Foundation; either version 2 of the License, or | |||
(at your option) any later version. | |||
This program 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 General Public License for more details. | |||
You should have received a copy of the GNU General Public License | |||
along with this program; if not, write to the Free Software | |||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | |||
*/ | |||
#ifndef __JackWinMMEOutputPort__ | |||
#define __JackWinMMEOutputPort__ | |||
#include <mmsystem.h> | |||
#include <windows.h> | |||
#include "JackMidiAsyncQueue.h" | |||
#include "JackMidiBufferReadQueue.h" | |||
namespace Jack { | |||
class JackWinMMEOutputPort { | |||
private: | |||
static void CALLBACK | |||
HandleMessageEvent(HMIDIOUT handle, UINT message, DWORD_PTR port, | |||
DWORD_PTR param1, DWORD_PTR param2); | |||
void | |||
GetMMErrorString(MMRESULT error, LPTSTR text); | |||
void | |||
GetOSErrorString(LPTSTR text); | |||
void | |||
HandleMessage(UINT message, DWORD_PTR param1, DWORD_PTR param2); | |||
bool | |||
Wait(Handle semaphore); | |||
void | |||
WriteMMError(const char *jack_func, const char *mm_func, | |||
MMRESULT result); | |||
void | |||
WriteOSError(const char *jack_func, const char *os_func); | |||
char alias[JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE]; | |||
HMIDIOUT handle; | |||
char name[JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE]; | |||
JackMidiBufferReadQueue *read_queue; | |||
HANDLE sysex_semaphore; | |||
JackMidiAsyncQueue *thread_queue; | |||
HANDLE thread_queue_semaphore; | |||
public: | |||
JackWinMMEOutputPort(const char *alias_name, const char *client_name, | |||
const char *driver_name, UINT index, | |||
size_t max_bytes=4096, size_t max_messages=1024); | |||
~JackWinMMEOutputPort(); | |||
bool | |||
Execute(); | |||
bool | |||
Init(); | |||
void | |||
ProcessJack(JackMidiBuffer *port_buffer, jack_nframes_t frames); | |||
bool | |||
Start(); | |||
bool | |||
Stop(); | |||
}; | |||
} | |||
#endif |