@@ -1,5 +1,6 @@ | |||||
/* | /* | ||||
Copyright (C) 2009 Grame | Copyright (C) 2009 Grame | ||||
Copyright (C) 2011 Devin Anderson | |||||
This program is free software; you can redistribute it and/or modify | 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 | 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 "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; | return 0; | ||||
} | } | ||||
} // end of namespace | |||||
#ifdef __cplusplus | #ifdef __cplusplus | ||||
extern "C" | extern "C" | ||||
{ | { | ||||
@@ -1,5 +1,6 @@ | |||||
/* | /* | ||||
Copyright (C) 2009 Grame | Copyright (C) 2009 Grame | ||||
Copyright (C) 2011 Devin Anderson | |||||
This program is free software; you can redistribute it and/or modify | 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 | 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__ | #define __JackWinMMEDriver__ | ||||
#include "JackMidiDriver.h" | #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 | #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 |