Tim Mayberry
d10913e2de
This issue is not always reproducible but when it does occur it happens somewhat consistently on both 32bit and 64bit builds(Tested on Windows 7). The midiOutReset call does not return (or it takes so long that it might as well be indefinite) and as it is not strictly necessary just remove it. Resolves: http://tracker.ardour.org/view.php?id=7095
513 lines
13 KiB
C++
513 lines
13 KiB
C++
/*
|
|
* Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.com>
|
|
*
|
|
* 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 "winmmemidi_output_device.h"
|
|
|
|
#include <glibmm.h>
|
|
|
|
#include "pbd/debug.h"
|
|
#include "pbd/compose.h"
|
|
#include "pbd/windows_timer_utils.h"
|
|
#include "pbd/windows_mmcss.h"
|
|
|
|
#include "rt_thread.h"
|
|
#include "midi_util.h"
|
|
|
|
#include "debug.h"
|
|
|
|
// remove dup with input_device
|
|
static const uint32_t MIDI_BUFFER_SIZE = 32768;
|
|
static const uint32_t MAX_MIDI_MSG_SIZE = 256; // fix this for sysex
|
|
static const uint32_t MAX_QUEUE_SIZE = 4096;
|
|
|
|
namespace ARDOUR {
|
|
|
|
WinMMEMidiOutputDevice::WinMMEMidiOutputDevice (int index)
|
|
: m_handle(0)
|
|
, m_queue_semaphore(0)
|
|
, m_sysex_semaphore(0)
|
|
, m_timer(0)
|
|
, m_started(false)
|
|
, m_enabled(false)
|
|
, m_thread_running(false)
|
|
, m_thread_quit(false)
|
|
, m_midi_buffer(new RingBuffer<uint8_t>(MIDI_BUFFER_SIZE))
|
|
{
|
|
DEBUG_MIDI (string_compose ("Creating midi output device index: %1\n", index));
|
|
|
|
std::string error_msg;
|
|
|
|
if (!open (index, error_msg)) {
|
|
DEBUG_MIDI (error_msg);
|
|
throw std::runtime_error (error_msg);
|
|
}
|
|
|
|
set_device_name (index);
|
|
}
|
|
|
|
WinMMEMidiOutputDevice::~WinMMEMidiOutputDevice ()
|
|
{
|
|
std::string error_msg;
|
|
if (!close (error_msg)) {
|
|
DEBUG_MIDI (error_msg);
|
|
}
|
|
}
|
|
|
|
bool
|
|
WinMMEMidiOutputDevice::enqueue_midi_event (uint64_t timestamp,
|
|
const uint8_t* data,
|
|
size_t size)
|
|
{
|
|
const uint32_t total_bytes = sizeof(MidiEventHeader) + size;
|
|
if (m_midi_buffer->write_space () < total_bytes) {
|
|
DEBUG_MIDI ("WinMMEMidiOutput: ring buffer overflow\n");
|
|
return false;
|
|
}
|
|
|
|
MidiEventHeader h (timestamp, size);
|
|
m_midi_buffer->write ((uint8_t*)&h, sizeof(MidiEventHeader));
|
|
m_midi_buffer->write (data, size);
|
|
|
|
signal (m_queue_semaphore);
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
WinMMEMidiOutputDevice::open (UINT index, std::string& error_msg)
|
|
{
|
|
MMRESULT result = midiOutOpen (&m_handle,
|
|
index,
|
|
(DWORD_PTR)winmm_output_callback,
|
|
(DWORD_PTR) this,
|
|
CALLBACK_FUNCTION);
|
|
if (result != MMSYSERR_NOERROR) {
|
|
error_msg = get_error_string (result);
|
|
return false;
|
|
}
|
|
|
|
m_queue_semaphore = CreateSemaphore (NULL, 0, MAX_QUEUE_SIZE, NULL);
|
|
if (m_queue_semaphore == NULL) {
|
|
DEBUG_MIDI ("WinMMEMidiOutput: Unable to create queue semaphore\n");
|
|
return false;
|
|
}
|
|
m_sysex_semaphore = CreateSemaphore (NULL, 0, 1, NULL);
|
|
if (m_sysex_semaphore == NULL) {
|
|
DEBUG_MIDI ("WinMMEMidiOutput: Unable to create sysex semaphore\n");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
WinMMEMidiOutputDevice::close (std::string& error_msg)
|
|
{
|
|
// return error message for first error encountered?
|
|
bool success = true;
|
|
MMRESULT result = midiOutClose (m_handle);
|
|
if (result != MMSYSERR_NOERROR) {
|
|
error_msg = get_error_string (result);
|
|
DEBUG_MIDI (error_msg);
|
|
success = false;
|
|
}
|
|
|
|
if (m_sysex_semaphore) {
|
|
if (!CloseHandle (m_sysex_semaphore)) {
|
|
DEBUG_MIDI ("WinMMEMidiOut Unable to close sysex semaphore\n");
|
|
success = false;
|
|
} else {
|
|
m_sysex_semaphore = 0;
|
|
}
|
|
}
|
|
if (m_queue_semaphore) {
|
|
if (!CloseHandle (m_queue_semaphore)) {
|
|
DEBUG_MIDI ("WinMMEMidiOut Unable to close queue semaphore\n");
|
|
success = false;
|
|
} else {
|
|
m_queue_semaphore = 0;
|
|
}
|
|
}
|
|
|
|
m_handle = 0;
|
|
return success;
|
|
}
|
|
|
|
bool
|
|
WinMMEMidiOutputDevice::set_device_name (UINT index)
|
|
{
|
|
MIDIOUTCAPS capabilities;
|
|
MMRESULT result =
|
|
midiOutGetDevCaps (index, &capabilities, sizeof(capabilities));
|
|
|
|
if (result != MMSYSERR_NOERROR) {
|
|
DEBUG_MIDI (get_error_string (result));
|
|
m_name = "Unknown Midi Output Device";
|
|
return false;
|
|
} else {
|
|
m_name = capabilities.szPname;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
std::string
|
|
WinMMEMidiOutputDevice::get_error_string (MMRESULT error_code)
|
|
{
|
|
char error_msg[MAXERRORLENGTH];
|
|
MMRESULT result = midiOutGetErrorText (error_code, error_msg, MAXERRORLENGTH);
|
|
if (result != MMSYSERR_NOERROR) {
|
|
return error_msg;
|
|
}
|
|
return "WinMMEMidiOutput: Unknown Error code";
|
|
}
|
|
|
|
bool
|
|
WinMMEMidiOutputDevice::start ()
|
|
{
|
|
if (m_thread_running) {
|
|
DEBUG_MIDI (
|
|
string_compose ("WinMMEMidiOutput: device %1 already started\n", m_name));
|
|
return true;
|
|
}
|
|
|
|
m_timer = CreateWaitableTimer (NULL, FALSE, NULL);
|
|
|
|
if (!m_timer) {
|
|
DEBUG_MIDI ("WinMMEMidiOutput: unable to create waitable timer\n");
|
|
return false;
|
|
}
|
|
|
|
if (!start_midi_output_thread ()) {
|
|
DEBUG_MIDI ("WinMMEMidiOutput: Failed to start MIDI output thread\n");
|
|
|
|
if (!CloseHandle (m_timer)) {
|
|
DEBUG_MIDI ("WinMMEMidiOutput: unable to close waitable timer\n");
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
WinMMEMidiOutputDevice::stop ()
|
|
{
|
|
if (!m_thread_running) {
|
|
DEBUG_MIDI ("WinMMEMidiOutputDevice: device already stopped\n");
|
|
return true;
|
|
}
|
|
|
|
if (!stop_midi_output_thread ()) {
|
|
DEBUG_MIDI ("WinMMEMidiOutput: Failed to stop MIDI output thread\n");
|
|
return false;
|
|
}
|
|
|
|
if (!CloseHandle (m_timer)) {
|
|
DEBUG_MIDI ("WinMMEMidiOutput: unable to close waitable timer\n");
|
|
return false;
|
|
}
|
|
m_timer = 0;
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
WinMMEMidiOutputDevice::start_midi_output_thread ()
|
|
{
|
|
m_thread_quit = false;
|
|
|
|
//pthread_attr_t attr;
|
|
size_t stacksize = 100000;
|
|
|
|
// TODO Use native threads
|
|
if (_realtime_pthread_create (SCHED_FIFO, -21, stacksize,
|
|
&m_output_thread_handle, midi_output_thread, this)) {
|
|
return false;
|
|
}
|
|
|
|
int timeout = 5000;
|
|
while (!m_thread_running && --timeout > 0) { Glib::usleep (1000); }
|
|
if (timeout == 0 || !m_thread_running) {
|
|
DEBUG_MIDI (string_compose ("Unable to start midi output device thread: %1\n",
|
|
m_name));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
WinMMEMidiOutputDevice::stop_midi_output_thread ()
|
|
{
|
|
int timeout = 5000;
|
|
m_thread_quit = true;
|
|
signal (m_queue_semaphore);
|
|
|
|
while (m_thread_running && --timeout > 0) { Glib::usleep (1000); }
|
|
if (timeout == 0 || m_thread_running) {
|
|
DEBUG_MIDI (string_compose ("Unable to stop midi output device thread: %1\n",
|
|
m_name));
|
|
return false;
|
|
}
|
|
|
|
void *status;
|
|
if (pthread_join (m_output_thread_handle, &status)) {
|
|
DEBUG_MIDI (string_compose ("Unable to join midi output device thread: %1\n",
|
|
m_name));
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool
|
|
WinMMEMidiOutputDevice::signal (HANDLE semaphore)
|
|
{
|
|
bool result = (bool)ReleaseSemaphore (semaphore, 1, NULL);
|
|
if (!result) {
|
|
DEBUG_MIDI ("WinMMEMidiOutDevice: Cannot release semaphore\n");
|
|
}
|
|
return result;
|
|
}
|
|
|
|
bool
|
|
WinMMEMidiOutputDevice::wait (HANDLE semaphore)
|
|
{
|
|
DWORD result = WaitForSingleObject (semaphore, INFINITE);
|
|
switch (result) {
|
|
case WAIT_FAILED:
|
|
DEBUG_MIDI ("WinMMEMidiOutDevice: WaitForSingleObject Failed\n");
|
|
break;
|
|
case WAIT_OBJECT_0:
|
|
return true;
|
|
default:
|
|
DEBUG_MIDI ("WinMMEMidiOutDevice: Unexpected result from WaitForSingleObject\n");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void CALLBACK
|
|
WinMMEMidiOutputDevice::winmm_output_callback (HMIDIOUT handle,
|
|
UINT msg,
|
|
DWORD_PTR instance,
|
|
DWORD_PTR midi_data,
|
|
DWORD_PTR timestamp)
|
|
{
|
|
((WinMMEMidiOutputDevice*)instance)
|
|
->midi_output_callback (msg, midi_data, timestamp);
|
|
}
|
|
|
|
void
|
|
WinMMEMidiOutputDevice::midi_output_callback (UINT message,
|
|
DWORD_PTR midi_data,
|
|
DWORD_PTR timestamp)
|
|
{
|
|
switch (message) {
|
|
case MOM_CLOSE:
|
|
DEBUG_MIDI ("WinMMEMidiOutput - MIDI device closed\n");
|
|
break;
|
|
case MOM_DONE:
|
|
signal (m_sysex_semaphore);
|
|
break;
|
|
case MOM_OPEN:
|
|
DEBUG_MIDI ("WinMMEMidiOutput - MIDI device opened\n");
|
|
break;
|
|
case MOM_POSITIONCB:
|
|
LPMIDIHDR header = (LPMIDIHDR)midi_data;
|
|
DEBUG_MIDI (string_compose ("WinMMEMidiOut - %1 bytes out of %2 bytes of "
|
|
"the current sysex message have been sent.\n",
|
|
header->dwOffset,
|
|
header->dwBytesRecorded));
|
|
}
|
|
}
|
|
|
|
void*
|
|
WinMMEMidiOutputDevice::midi_output_thread (void *arg)
|
|
{
|
|
WinMMEMidiOutputDevice* output_device = reinterpret_cast<WinMMEMidiOutputDevice*> (arg);
|
|
output_device->midi_output_thread ();
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
WinMMEMidiOutputDevice::midi_output_thread ()
|
|
{
|
|
m_thread_running = true;
|
|
|
|
DEBUG_MIDI ("WinMMEMidiOut: MIDI output thread started\n");
|
|
|
|
#ifdef USE_MMCSS_THREAD_PRIORITIES
|
|
HANDLE task_handle;
|
|
|
|
PBD::MMCSS::set_thread_characteristics ("Pro Audio", &task_handle);
|
|
PBD::MMCSS::set_thread_priority (task_handle, PBD::MMCSS::AVRT_PRIORITY_HIGH);
|
|
#endif
|
|
|
|
while (!m_thread_quit) {
|
|
if (!wait (m_queue_semaphore)) {
|
|
DEBUG_MIDI ("WinMMEMidiOut: output thread waiting for semaphore failed\n");
|
|
break;
|
|
}
|
|
|
|
DEBUG_MIDI ("WinMMEMidiOut: output thread woken by semaphore\n");
|
|
|
|
MidiEventHeader h (0, 0);
|
|
uint8_t data[MAX_MIDI_MSG_SIZE];
|
|
|
|
const uint32_t read_space = m_midi_buffer->read_space ();
|
|
|
|
DEBUG_MIDI (string_compose ("WinMMEMidiOut: total readable MIDI data %1\n", read_space));
|
|
|
|
if (read_space > sizeof(MidiEventHeader)) {
|
|
if (m_midi_buffer->read ((uint8_t*)&h, sizeof(MidiEventHeader)) !=
|
|
sizeof(MidiEventHeader)) {
|
|
DEBUG_MIDI ("WinMMEMidiOut: Garbled MIDI EVENT HEADER!!\n");
|
|
break;
|
|
}
|
|
assert (read_space >= h.size);
|
|
|
|
if (h.size > MAX_MIDI_MSG_SIZE) {
|
|
m_midi_buffer->increment_read_idx (h.size);
|
|
DEBUG_MIDI ("WinMMEMidiOut: MIDI event too large!\n");
|
|
continue;
|
|
}
|
|
if (m_midi_buffer->read (&data[0], h.size) != h.size) {
|
|
DEBUG_MIDI ("WinMMEMidiOut: Garbled MIDI EVENT DATA!!\n");
|
|
break;
|
|
}
|
|
} else {
|
|
// error/assert?
|
|
DEBUG_MIDI ("WinMMEMidiOut: MIDI buffer underrun, shouldn't occur\n");
|
|
continue;
|
|
}
|
|
uint64_t current_time = PBD::get_microseconds ();
|
|
|
|
DEBUG_TIMING (string_compose (
|
|
"WinMMEMidiOut: h.time = %1, current_time = %2\n", h.time, current_time));
|
|
|
|
if (h.time > current_time) {
|
|
|
|
DEBUG_TIMING (string_compose ("WinMMEMidiOut: waiting at %1 for %2 "
|
|
"milliseconds before sending message\n",
|
|
((double)current_time) / 1000.0,
|
|
((double)(h.time - current_time)) / 1000.0));
|
|
|
|
if (!wait_for_microseconds (h.time - current_time))
|
|
{
|
|
DEBUG_MIDI ("WinMMEMidiOut: Error waiting for timer\n");
|
|
break;
|
|
}
|
|
|
|
uint64_t wakeup_time = PBD::get_microseconds ();
|
|
DEBUG_TIMING (string_compose ("WinMMEMidiOut: woke up at %1(ms)\n",
|
|
((double)wakeup_time) / 1000.0));
|
|
if (wakeup_time > h.time) {
|
|
DEBUG_TIMING (string_compose ("WinMMEMidiOut: overslept by %1(ms)\n",
|
|
((double)(wakeup_time - h.time)) / 1000.0));
|
|
} else if (wakeup_time < h.time) {
|
|
DEBUG_TIMING (string_compose ("WinMMEMidiOut: woke up %1(ms) too early\n",
|
|
((double)(h.time - wakeup_time)) / 1000.0));
|
|
}
|
|
|
|
} else if (h.time < current_time) {
|
|
DEBUG_TIMING (string_compose (
|
|
"WinMMEMidiOut: MIDI event at sent to driver %1(ms) late\n",
|
|
((double)(current_time - h.time)) / 1000.0));
|
|
}
|
|
|
|
DEBUG_MIDI (string_compose ("WinMMEMidiOut: MIDI event size: %1 time %2 now %3\n", h.size, h.time, current_time));
|
|
|
|
DWORD message = 0;
|
|
MMRESULT result;
|
|
switch (h.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 (m_handle, message);
|
|
if (result != MMSYSERR_NOERROR) {
|
|
DEBUG_MIDI (
|
|
string_compose ("WinMMEMidiOutput: %1\n", get_error_string (result)));
|
|
}
|
|
continue;
|
|
}
|
|
|
|
MIDIHDR header;
|
|
header.dwBufferLength = h.size;
|
|
header.dwFlags = 0;
|
|
header.lpData = (LPSTR)data;
|
|
|
|
result = midiOutPrepareHeader (m_handle, &header, sizeof(MIDIHDR));
|
|
if (result != MMSYSERR_NOERROR) {
|
|
DEBUG_MIDI (string_compose ("WinMMEMidiOutput: midiOutPrepareHeader %1\n",
|
|
get_error_string (result)));
|
|
continue;
|
|
}
|
|
|
|
result = midiOutLongMsg (m_handle, &header, sizeof(MIDIHDR));
|
|
if (result != MMSYSERR_NOERROR) {
|
|
DEBUG_MIDI (string_compose ("WinMMEMidiOutput: midiOutLongMsg %1\n",
|
|
get_error_string (result)));
|
|
continue;
|
|
}
|
|
|
|
// Sysex 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.
|
|
|
|
DEBUG_MIDI ("WinMMEMidiOut: wait for sysex semaphore\n");
|
|
|
|
if (!wait (m_sysex_semaphore)) {
|
|
DEBUG_MIDI ("WinMMEMidiOut: wait for sysex semaphore - failed!\n");
|
|
break;
|
|
}
|
|
|
|
result = midiOutUnprepareHeader (m_handle, &header, sizeof(MIDIHDR));
|
|
if (result != MMSYSERR_NOERROR) {
|
|
DEBUG_MIDI (string_compose ("WinMMEMidiOutput: midiOutUnprepareHeader %1\n",
|
|
get_error_string (result)));
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef USE_MMCSS_THREAD_PRIORITIES
|
|
PBD::MMCSS::revert_thread_characteristics (task_handle);
|
|
#endif
|
|
|
|
m_thread_running = false;
|
|
}
|
|
|
|
bool
|
|
WinMMEMidiOutputDevice::wait_for_microseconds (int64_t wait_us)
|
|
{
|
|
LARGE_INTEGER due_time;
|
|
|
|
// 100 ns resolution
|
|
due_time.QuadPart = -((LONGLONG)(wait_us * 10));
|
|
if (!SetWaitableTimer (m_timer, &due_time, 0, NULL, NULL, 0)) {
|
|
DEBUG_MIDI ("WinMMEMidiOut: Error waiting for timer\n");
|
|
return false;
|
|
}
|
|
|
|
if (!wait (m_timer)) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace ARDOUR
|