Browse Source

Overhaul WinMME driver to use MIDI queue system. WARNING: I don't have a Windows development environment, and haven't compiled/tested this code.

tags/1.9.8
Devin Anderson 14 years ago
parent
commit
20831f45ea
6 changed files with 1069 additions and 378 deletions
  1. +217
    -332
      windows/winmme/JackWinMMEDriver.cpp
  2. +31
    -46
      windows/winmme/JackWinMMEDriver.h
  3. +287
    -0
      windows/winmme/JackWinMMEInputPort.cpp
  4. +89
    -0
      windows/winmme/JackWinMMEInputPort.h
  5. +352
    -0
      windows/winmme/JackWinMMEOutputPort.cpp
  6. +93
    -0
      windows/winmme/JackWinMMEOutputPort.h

+ 217
- 332
windows/winmme/JackWinMMEDriver.cpp View File

@@ -1,5 +1,6 @@
/*
Copyright (C) 2009 Grame
Copyright (C) 2011 Devin Anderson

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -17,401 +18,285 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

*/

#include "JackWinMMEDriver.h"
#include "JackGraphManager.h"
#include "JackEngineControl.h"
#include "JackDriverLoader.h"

#include <assert.h>
#include <iostream>
#include <sstream>
#include <string>

#include <windows.h>
#include <windowsx.h>
#include <mmsystem.h>
#include "JackWinMMEDriver.h"

namespace Jack
{
using Jack::JackWinMMEDriver;

static bool InitHeaders(MidiSlot* slot)
JackWinMMEDriver::JackWinMMEDriver(const char *name, const char *alias,
JackLockedEngine *engine,
JackSynchro *table):
JackMidiDriver(name, alias, engine, table)
{
slot->fHeader = (LPMIDIHDR)GlobalAllocPtr(GMEM_MOVEABLE|GMEM_SHARE|GMEM_ZEROINIT, sizeof(MIDIHDR) + kBuffSize);
if (!slot->fHeader)
return false;

slot->fHeader->lpData = (LPSTR)((LPBYTE)slot->fHeader + sizeof(MIDIHDR));
slot->fHeader->dwBufferLength = kBuffSize;
slot->fHeader->dwFlags = 0;
slot->fHeader->dwUser = 0;
slot->fHeader->lpNext = 0;
slot->fHeader->dwBytesRecorded = 0;
return true;
fCaptureChannels = 0;
fPlaybackChannels = 0;
input_ports = 0;
output_ports = 0;
}

void CALLBACK JackWinMMEDriver::MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD userData, DWORD dwParam1, DWORD dwParam2)
JackWinMMEDriver::~JackWinMMEDriver()
{
jack_ringbuffer_t* ringbuffer = (jack_ringbuffer_t*)userData;
//jack_info("JackWinMMEDriver::MidiInProc 0\n");

switch (wMsg) {
case MIM_OPEN:
break;

case MIM_ERROR:
case MIM_DATA: {

//jack_info("JackWinMMEDriver::MidiInProc");

// One event
unsigned int num_packet = 1;
jack_ringbuffer_write(ringbuffer, (char*)&num_packet, sizeof(unsigned int));

// Write event actual data
jack_ringbuffer_write(ringbuffer, (char*)&dwParam1, 3);
break;
}

case MIM_LONGERROR:
case MIM_LONGDATA:
/*
Nothing for now
*/
break;
}
Stop();
Close();
}

JackWinMMEDriver::JackWinMMEDriver(const char* name, const char* alias, JackLockedEngine* engine, JackSynchro* table)
: JackMidiDriver(name, alias, engine, table),
fRealCaptureChannels(0),
fRealPlaybackChannels(0),
fMidiSource(NULL),
fMidiDestination(NULL)
{}

JackWinMMEDriver::~JackWinMMEDriver()
{}

int JackWinMMEDriver::Open(bool capturing,
bool playing,
int inchannels,
int outchannels,
bool monitor,
const char* capture_driver_name,
const char* playback_driver_name,
jack_nframes_t capture_latency,
jack_nframes_t playback_latency)
int
JackWinMMEDriver::Attach()
{
jack_nframes_t buffer_size = fEngineControl->fBufferSize;
jack_port_id_t index;
jack_nframes_t latency = buffer_size;
jack_latency_range_t latency_range;
const char *name;
JackPort *port;
latency_range.max = latency;
latency_range.min = latency;

// Inputs
for (int i = 0; i < fCaptureChannels; i++) {
JackWinMMEInputPort *input_port = input_ports[i];
name = input_port->GetName();
index = fGraphManager->AllocatePort(fClientControl.fRefNum, name,
JACK_DEFAULT_MIDI_TYPE,
CaptureDriverFlags, buffer_size);
if (index == NO_PORT) {
jack_error("JackWinMMEDriver::Attach - cannot register input port "
"with name '%s'.", name);
// X: Do we need to deallocate ports?
return -1;
}
port = fGraphManager->GetPort(index);
port->SetAlias(input_port->GetAlias());
port->SetLatencyRange(JackCaptureLatency, &latency_range);
fCapturePortList[i] = index;
}

jack_log("JackWinMMEDriver::Open");

fRealCaptureChannels = midiInGetNumDevs();
fRealPlaybackChannels = midiOutGetNumDevs();

// Generic JackMidiDriver Open
if (JackMidiDriver::Open(capturing, playing, fRealCaptureChannels, fRealPlaybackChannels, monitor, capture_driver_name, playback_driver_name, capture_latency, playback_latency) != 0)
return -1;

fMidiDestination = new MidiSlot[fRealCaptureChannels];
assert(fMidiDestination);
if (! fEngineControl->fSyncMode) {
latency += buffer_size;
latency_range.max = latency;
latency_range.min = latency;
}

// Real input
int devindex = 0;
for (int i = 0; i < fRealCaptureChannels; i++) {
// Outputs
for (int i = 0; i < fPlaybackChannels; i++) {
JackWinMMEOutputPort *output_port = output_ports[i];
name = output_port->GetName();
index = fGraphManager->AllocatePort(fClientControl.fRefNum, name,
JACK_DEFAULT_MIDI_TYPE,
PlaybackDriverFlags, buffer_size);
if (index == NO_PORT) {
jack_error("JackWinMMEDriver::Attach - cannot register output "
"port with name '%s'.", name);
// X: Do we need to deallocate ports?
return -1;
}
port = fGraphManager->GetPort(index);
port->SetAlias(output_port->GetAlias());
port->SetLatencyRange(JackPlaybackLatency, &latency_range);
fPlaybackPortList[i] = index;
}

HMIDIIN handle;
fMidiDestination[devindex].fIndex = i;
MMRESULT ret = midiInOpen(&handle, fMidiDestination[devindex].fIndex, (DWORD)MidiInProc, (DWORD)fRingBuffer[devindex], CALLBACK_FUNCTION);
return 0;
}

if (ret == MMSYSERR_NOERROR) {
fMidiDestination[devindex].fHandle = handle;
if (!InitHeaders(&fMidiDestination[devindex])) {
jack_error("memory allocation failed");
midiInClose(handle);
continue;
}
ret = midiInPrepareHeader(handle, fMidiDestination[devindex].fHeader, sizeof(MIDIHDR));

if (ret == MMSYSERR_NOERROR) {
fMidiDestination[devindex].fHeader->dwUser = 1;
ret = midiInAddBuffer(handle, fMidiDestination[devindex].fHeader, sizeof(MIDIHDR));
if (ret == MMSYSERR_NOERROR) {
ret = midiInStart(handle);
if (ret != MMSYSERR_NOERROR) {
jack_error("midiInStart error");
CloseInput(&fMidiDestination[devindex]);
continue;
}
} else {
jack_error ("midiInAddBuffer error");
CloseInput(&fMidiDestination[devindex]);
continue;
}
} else {
jack_error("midiInPrepareHeader error");
midiInClose(handle);
continue;
}
} else {
jack_error ("midiInOpen error");
continue;
int
JackWinMMEDriver::Close()
{
int result = JackMidiDriver::Close();
if (input_ports) {
for (int i = 0; i < fCaptureChannels; i++) {
delete input_ports[i];
}
devindex += 1;
delete[] input_ports;
input_ports = 0;
}
fRealCaptureChannels = devindex;
fCaptureChannels = devindex;

fMidiSource = new MidiSlot[fRealPlaybackChannels];
assert(fMidiSource);

// Real output
devindex = 0;
for (int i = 0; i < fRealPlaybackChannels; i++) {
MMRESULT res;
HMIDIOUT handle;
fMidiSource[devindex].fIndex = i;
UINT ret = midiOutOpen(&handle, fMidiSource[devindex].fIndex, 0L, 0L, CALLBACK_NULL);
if (ret == MMSYSERR_NOERROR) {
fMidiSource[devindex].fHandle = handle;
if (!InitHeaders(&fMidiSource[devindex])) {
jack_error("memory allocation failed");
midiOutClose(handle);
continue;
}
res = midiOutPrepareHeader(handle, fMidiSource[devindex].fHeader, sizeof(MIDIHDR));
if (res != MMSYSERR_NOERROR) {
jack_error("midiOutPrepareHeader error %d %d %d", i, handle, res);
continue;
} else {
fMidiSource[devindex].fHeader->dwUser = 1;
}
} else {
jack_error("midiOutOpen error");
continue;
if (output_ports) {
for (int i = 0; i < fPlaybackChannels; i++) {
delete output_ports[i];
}
devindex += 1;
delete[] output_ports;
output_ports = 0;
}
fRealPlaybackChannels = devindex;
fPlaybackChannels = devindex;
return 0;
return result;
}

void JackWinMMEDriver::CloseInput(MidiSlot* slot)
int
JackWinMMEDriver::Open(bool capturing, bool playing, int in_channels,
int out_channels, bool monitor,
const char* capture_driver_name,
const char* playback_driver_name,
jack_nframes_t capture_latency,
jack_nframes_t playback_latency)
{
MMRESULT res;
int retry = 0;

if (slot->fHandle == 0)
return;

HMIDIIN handle = (HMIDIIN)slot->fHandle;
slot->fHeader->dwUser = 0;
res = midiInStop(handle);
if (res != MMSYSERR_NOERROR) {
jack_error("midiInStop error");
const char *client_name = fClientControl.fName;
int input_count = 0;
int num_potential_inputs = midiInGetNumDevs();
int num_potential_outputs = midiOutGetNumDevs();
int output_count = 0;
if (num_potential_inputs) {
try {
input_ports = new JackWinMMEInputPort *[num_potential_inputs];
} catch (std::exception e) {
jack_error("JackWinMMEDriver::Open - while creating input port "
"array: %s", e.what());
return -1;
}
for (int i = 0; i < num_potential_inputs; i++) {
try {
input_ports[input_count] =
new JackWinMMEInputPort(fAliasName, client_name,
capture_driver_name, i);
} catch (std::exception e) {
jack_error("JackWinMMEDriver::Open - while creating input "
"port: %s", e.what());
continue;
}
input_count++;
}
}
res = midiInReset(handle);
if (res != MMSYSERR_NOERROR) {
jack_error("midiInReset error");
if (num_potential_outputs) {
try {
output_ports = new JackWinMMEOutputPort *[num_potential_outputs];
} catch (std::exception e) {
jack_error("JackWinMMEDriver::Open - while creating output port "
"array: %s", e.what());
goto destroy_input_ports;
}
for (int i = 0; i < num_potential_outputs; i++) {
try {
output_ports[output_count] =
new JackWinMMEOutputPort(fAliasName, client_name,
playback_driver_name, i);
} catch (std::exception e) {
jack_error("JackWinMMEDriver::Open - while creating output "
"port: %s", e.what());
continue;
}
output_count++;
}
}
res = midiInUnprepareHeader(handle, slot->fHeader, sizeof(MIDIHDR));
if (res != MMSYSERR_NOERROR) {
jack_error("midiInUnprepareHeader error");
if (! (input_count || output_count)) {
jack_error("JackWinMMEDriver::Open - no WinMME inputs or outputs "
"allocated.");
} else if (! JackMidiDriver::Open(capturing, playing, input_count,
output_count, monitor,
capture_driver_name,
playback_driver_name, capture_latency,
playback_latency)) {
return 0;
}
do {
res = midiInClose(handle);
if (res != MMSYSERR_NOERROR) {
jack_error("midiInClose error");

destroy_input_ports:
if (input_ports) {
for (int i = 0; i < input_count; i++) {
delete input_ports[i];
}
if (res == MIDIERR_STILLPLAYING)
midiInReset(handle);
Sleep (10);
retry++;
} while ((res == MIDIERR_STILLPLAYING) && (retry < 10));

if (slot->fHeader) {
GlobalFreePtr(slot->fHeader);
delete[] input_ports;
input_ports = 0;
}
return -1;
}

void JackWinMMEDriver::CloseOutput(MidiSlot* slot)
int
JackWinMMEDriver::Read()
{
MMRESULT res;
int retry = 0;

if (slot->fHandle == 0)
return;

HMIDIOUT handle = (HMIDIOUT)slot->fHandle;
res = midiOutReset(handle);
if (res != MMSYSERR_NOERROR)
jack_error("midiOutReset error");
midiOutUnprepareHeader(handle, slot->fHeader, sizeof(MIDIHDR));
do {
res = midiOutClose(handle);
if (res != MMSYSERR_NOERROR)
jack_error("midiOutClose error");
Sleep(10);
retry++;
} while ((res == MIDIERR_STILLPLAYING) && (retry < 10));

if (slot->fHeader) {
GlobalFreePtr(slot->fHeader);
jack_nframes_t buffer_size = fEngineControl->fBufferSize;
for (int i = 0; i < fCaptureChannels; i++) {
input_ports[i]->ProcessJack(GetInputBuffer(i), buffer_size);
}
return 0;
}

int JackWinMMEDriver::Close()
int
JackWinMMEDriver::Start()
{
jack_log("JackWinMMEDriver::Close");
jack_info("JackWinMMEDriver::Start - Starting driver.");

JackMidiDriver::Start();

int input_count = 0;
int output_count = 0;

// Generic midi driver close
int res = JackMidiDriver::Close();
jack_info("JackWinMMEDriver::Start - Enabling input ports.");

// Close input
if (fMidiDestination) {
for (int i = 0; i < fRealCaptureChannels; i++) {
CloseInput(&fMidiDestination[i]);
for (; input_count < fCaptureChannels; input_count++) {
if (input_ports[input_count]->Start() < 0) {
jack_error("JackWinMMEDriver::Start - Failed to enable input "
"port.");
goto stop_input_ports;
}
delete[] fMidiDestination;
}

// Close output
if (fMidiSource) {
for (int i = 0; i < fRealPlaybackChannels; i++) {
CloseOutput(&fMidiSource[i]);
jack_info("JackWinMMEDriver::Start - Enabling output ports.");

for (; output_count < fPlaybackChannels; output_count++) {
if (output_ports[output_count]->Start() < 0) {
jack_error("JackWinMMEDriver::Start - Failed to enable output "
"port.");
goto stop_output_ports;
}
delete[] fMidiSource;
}

return res;
}
jack_info("JackWinMMEDriver::Start - Driver started.");

int JackWinMMEDriver::Attach()
{
JackPort* port;
jack_port_id_t port_index;
char name[JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE];
char alias[JACK_CLIENT_NAME_SIZE + JACK_PORT_NAME_SIZE];
MMRESULT res;
int i;

jack_log("JackMidiDriver::Attach fBufferSize = %ld fSampleRate = %ld", fEngineControl->fBufferSize, fEngineControl->fSampleRate);

for (i = 0; i < fCaptureChannels; i++) {
MIDIINCAPS caps;
res = midiInGetDevCaps(fMidiDestination[i].fIndex, &caps, sizeof(caps));
if (res == MMSYSERR_NOERROR) {
snprintf(alias, sizeof(alias) - 1, "%s:%s:out%d", fAliasName, caps.szPname, i + 1);
} else {
snprintf(alias, sizeof(alias) - 1, "%s:%s:out%d", fAliasName, fCaptureDriverName, i + 1);
}
snprintf(name, sizeof(name) - 1, "%s:capture_%d", fClientControl.fName, i + 1);

if ((port_index = fGraphManager->AllocatePort(fClientControl.fRefNum, name, JACK_DEFAULT_MIDI_TYPE, CaptureDriverFlags, fEngineControl->fBufferSize)) == NO_PORT) {
jack_error("driver: cannot register port for %s", name);
return -1;
return 0;

stop_output_ports:
for (int i = 0; i < output_count; i++) {
if (output_ports[i]->Stop() < 0) {
jack_error("JackWinMMEDriver::Start - Failed to disable output "
"port.");
}
port = fGraphManager->GetPort(port_index);
port->SetAlias(alias);
fCapturePortList[i] = port_index;
jack_log("JackMidiDriver::Attach fCapturePortList[i] port_index = %ld", port_index);
}

for (i = 0; i < fPlaybackChannels; i++) {
MIDIOUTCAPS caps;
res = midiOutGetDevCaps(fMidiSource[i].fIndex, &caps, sizeof(caps));
if (res == MMSYSERR_NOERROR) {
snprintf(alias, sizeof(alias) - 1, "%s:%s:out%d", fAliasName, caps.szPname, i + 1);
} else {
snprintf(alias, sizeof(alias) - 1, "%s:%s:out%d", fAliasName, fPlaybackDriverName, i + 1);
}
snprintf(name, sizeof(name) - 1, "%s:playback_%d", fClientControl.fName, i + 1);

if ((port_index = fGraphManager->AllocatePort(fClientControl.fRefNum, name, JACK_DEFAULT_MIDI_TYPE, PlaybackDriverFlags, fEngineControl->fBufferSize)) == NO_PORT) {
jack_error("driver: cannot register port for %s", name);
return -1;
stop_input_ports:
for (int i = 0; i < input_count; i++) {
if (input_ports[i]->Stop() < 0) {
jack_error("JackWinMMEDriver::Start - Failed to disable input "
"port.");
}
port = fGraphManager->GetPort(port_index);
port->SetAlias(alias);
fPlaybackPortList[i] = port_index;
jack_log("JackMidiDriver::Attach fPlaybackPortList[i] port_index = %ld", port_index);
}

return 0;
return -1;
}

int JackWinMMEDriver::Read()
int
JackWinMMEDriver::Stop()
{
size_t size;
int result = 0;

for (int chan = 0; chan < fCaptureChannels; chan++) {
jack_info("JackWinMMEDriver::Stop - disabling input ports.");

if (fGraphManager->GetConnectionsNum(fCapturePortList[chan]) > 0) {

JackMidiBuffer* midi_buffer = GetInputBuffer(chan);

if (jack_ringbuffer_read_space (fRingBuffer[chan]) == 0) {
// Reset buffer
midi_buffer->Reset(midi_buffer->nframes);
} else {

while ((size = jack_ringbuffer_read_space (fRingBuffer[chan])) > 0) {
for (int i = 0; i < fCaptureChannels; i++) {
if (input_ports[i]->Stop() < 0) {
jack_error("JackWinMMEDriver::Stop - Failed to disable input "
"port.");
result = -1;
}
}

//jack_info("jack_ringbuffer_read_space %d", size);
int ev_count = 0;
jack_ringbuffer_read(fRingBuffer[chan], (char*)&ev_count, sizeof(int));
jack_info("JackWinMMEDriver::Stop - disabling output ports.");

if (ev_count > 0) {
for (int j = 0; j < ev_count; j++) {
unsigned int event_len = 3;
// Read event actual data
jack_midi_data_t* dest = midi_buffer->ReserveEvent(0, event_len);
jack_ringbuffer_read(fRingBuffer[chan], (char*)dest, event_len);
}
}
}
}
} else {
//jack_info("Consume ring buffer");
jack_ringbuffer_read_advance(fRingBuffer[chan], jack_ringbuffer_read_space(fRingBuffer[chan]));
for (int i = 0; i < fPlaybackChannels; i++) {
if (output_ports[i]->Stop() < 0) {
jack_error("JackWinMMEDriver::Stop - Failed to disable output "
"port.");
result = -1;
}
}
return 0;

return result;
}

int JackWinMMEDriver::Write()
int
JackWinMMEDriver::Write()
{
for (int chan = 0; chan < fPlaybackChannels; chan++) {

if (fGraphManager->GetConnectionsNum(fPlaybackPortList[chan]) > 0) {

JackMidiBuffer* midi_buffer = GetOutputBuffer(chan);

// TODO : use timestamp

for (unsigned int j = 0; j < midi_buffer->event_count; j++) {
JackMidiEvent* ev = &midi_buffer->events[j];
if (ev->size <= 3) {
jack_midi_data_t *d = ev->GetData(midi_buffer);
DWORD winev = 0;
if (ev->size > 0) winev |= d[0];
if (ev->size > 1) winev |= (d[1] << 8);
if (ev->size > 2) winev |= (d[2] << 16);
MMRESULT res = midiOutShortMsg((HMIDIOUT)fMidiSource[chan].fHandle, winev);
if (res != MMSYSERR_NOERROR)
jack_error ("midiOutShortMsg error res %d", res);
} else {

}
}
}
jack_nframes_t buffer_size = fEngineControl->fBufferSize;
for (int i = 0; i < fPlaybackChannels; i++) {
output_ports[i]->ProcessJack(GetOutputBuffer(i), buffer_size);
}

return 0;
}

} // end of namespace

#ifdef __cplusplus
extern "C"
{


+ 31
- 46
windows/winmme/JackWinMMEDriver.h View File

@@ -1,5 +1,6 @@
/*
Copyright (C) 2009 Grame
Copyright (C) 2011 Devin Anderson

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -21,67 +22,51 @@ Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#define __JackWinMMEDriver__

#include "JackMidiDriver.h"
#include "JackTime.h"
#include "JackWinMMEInputPort.h"
#include "JackWinMMEOutputPort.h"

namespace Jack
{
namespace Jack {

/*!
\brief The WinMME driver.
*/

#define kBuffSize 512

struct MidiSlot {

LPVOID fHandle; // MMSystem handler
short fIndex; // MMSystem dev index
LPMIDIHDR fHeader; // for long msg output

MidiSlot():fHandle(0),fIndex(0)
{}
class JackWinMMEDriver : public JackMidiDriver {

};
private:

class JackWinMMEDriver : public JackMidiDriver
{
JackWinMMEInputPort **input_ports;
JackWinMMEOutputPort **output_ports;

private:
public:

int fRealCaptureChannels;
int fRealPlaybackChannels;
JackWinMMEDriver(const char* name, const char* alias,
JackLockedEngine* engine, JackSynchro* table);

MidiSlot* fMidiSource;
MidiSlot* fMidiDestination;
~JackWinMMEDriver();

void CloseInput(MidiSlot* slot);
void CloseOutput(MidiSlot* slot);
int
Attach();

static void CALLBACK MidiInProc(HMIDIIN hMidiIn, UINT wMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2);
int
Close();

public:
int
Open(bool capturing, bool playing, int num_inputs, int num_outputs,
bool monitor, const char* capture_driver_name,
const char* playback_driver_name, jack_nframes_t capture_latency,
jack_nframes_t playback_latency);

JackWinMMEDriver(const char* name, const char* alias, JackLockedEngine* engine, JackSynchro* table);
virtual ~JackWinMMEDriver();
int
Read();

int Open(bool capturing,
bool playing,
int chan_in,
int chan_out,
bool monitor,
const char* capture_driver_name,
const char* playback_driver_name,
jack_nframes_t capture_latency,
jack_nframes_t playback_latency);
int Close();
int
Start();

int Attach();
int
Stop();

int Read();
int Write();
int
Write();

};
};

} // end of namespace
}

#endif

+ 287
- 0
windows/winmme/JackWinMMEInputPort.cpp View File

@@ -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);
}

+ 89
- 0
windows/winmme/JackWinMMEInputPort.h View File

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

+ 352
- 0
windows/winmme/JackWinMMEOutputPort.cpp View File

@@ -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);
}

+ 93
- 0
windows/winmme/JackWinMMEOutputPort.h View File

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

Loading…
Cancel
Save