| @@ -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 | |||||