WinMME based midi input/output for portaudio backend

TODO:

Use MMCSS to elevate thread priorities
Enable/test and fix SYSEX related code
This commit is contained in:
Tim Mayberry 2015-05-17 20:55:04 +10:00
parent b12f865a4a
commit e258c827e2
18 changed files with 2074 additions and 72 deletions

View File

@ -0,0 +1,103 @@
/*
* 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.
*/
#ifndef CYCLE_TIMER_H
#define CYCLE_TIMER_H
#include <stdint.h>
#include <cmath>
#include <algorithm>
// Could call it FrameTimer and make it more generic
// Could be an interface and or include clock source
// include sample count/processed frames in iteration?
class CycleTimer {
public:
CycleTimer ()
: m_cycle_start (0)
, m_samplerate (0)
, m_samples_per_cycle (0)
{
}
void set_samplerate (double samplerate) { m_samplerate = samplerate; }
double get_samplerate () const { return m_samplerate; }
double get_sample_length_us () const { return 1e6 / m_samplerate; }
double get_length_us () const
{
return get_sample_length_us () * m_samples_per_cycle;
}
void set_samples_per_cycle (uint32_t samples)
{
m_samples_per_cycle = samples;
}
uint32_t get_samples_per_cycle () const { return m_samples_per_cycle; }
// rint?? that may round to sample outside of cycle?
// max(0, value)?
uint32_t samples_since_cycle_start (uint64_t timer_val) const
{
if (timer_val < m_cycle_start) {
return 0;
}
return std::max((double)0, (timer_val - get_start ()) / get_sample_length_us ());
}
uint64_t timestamp_from_sample_offset (uint32_t sample_offset)
{
return m_cycle_start + get_sample_length_us () * sample_offset;
}
bool valid () const { return m_samples_per_cycle && m_samplerate; }
bool in_cycle (uint64_t timer_value_us) const
{ return (timer_value_us >= get_start()) && (timer_value_us < get_next_start());
}
void reset_start (uint64_t timestamp) { m_cycle_start = timestamp; }
uint64_t get_start () const { return m_cycle_start; }
uint64_t microseconds_since_start (uint64_t timestamp) const
{
return timestamp - m_cycle_start;
}
uint64_t microseconds_since_start (uint32_t samples) const
{
return m_cycle_start + samples * get_sample_length_us ();
}
uint64_t get_next_start () const
{
return m_cycle_start + rint (get_length_us ());
}
private:
uint64_t m_cycle_start;
uint32_t m_samplerate;
uint32_t m_samples_per_cycle;
};
#endif // CYCLE_TIMER_H

View File

@ -0,0 +1,13 @@
#ifndef PORTAUDIO_BACKEND_DEBUG_H
#define PORTAUDIO_BACKEND_DEBUG_H
#include "pbd/debug.h"
using namespace PBD;
#define DEBUG_AUDIO(msg) DEBUG_TRACE (DEBUG::BackendAudio, msg);
#define DEBUG_MIDI(msg) DEBUG_TRACE (DEBUG::BackendMIDI, msg);
#define DEBUG_TIMING(msg) DEBUG_TRACE (DEBUG::BackendTiming, msg);
#define DEBUG_THREADS(msg) DEBUG_TRACE (DEBUG::BackendThreads, msg);
#endif // PORTAUDIO_BACKEND_DEBUG_H

View File

@ -0,0 +1,53 @@
/*
* 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 "midi_util.h"
int get_midi_msg_length (uint8_t status_byte)
{
// define these with meaningful names
switch (status_byte & 0xf0) {
case 0x80:
case 0x90:
case 0xa0:
case 0xb0:
case 0xe0:
return 3;
case 0xc0:
case 0xd0:
return 2;
case 0xf0:
switch (status_byte) {
case 0xf0:
return 0;
case 0xf1:
case 0xf3:
return 2;
case 0xf2:
return 3;
case 0xf4:
case 0xf5:
case 0xf7:
case 0xfd:
break;
default:
return 1;
}
}
return -1;
}

View File

@ -0,0 +1,38 @@
/*
* 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.
*/
#ifndef MIDI_UTIL_H
#define MIDI_UTIL_H
#include <stdint.h>
struct MidiEventHeader {
uint64_t time;
size_t size;
MidiEventHeader (const uint64_t t, const size_t s)
: time (t)
, size (s)
{
}
};
// rename to get_midi_message_size?
// @return -1 to indicate error
int get_midi_msg_length (uint8_t status_byte);
#endif

View File

@ -36,8 +36,17 @@
#include "ardour/port_manager.h"
#include "i18n.h"
#include "win_utils.h"
#include "debug.h"
using namespace ARDOUR;
namespace {
const char * const winmme_driver_name = X_("WinMME");
}
static std::string s_instance_name;
size_t PortAudioBackend::_max_buffer_size = 8192;
std::vector<std::string> PortAudioBackend::_midi_options;
@ -51,7 +60,9 @@ PortAudioBackend::PortAudioBackend (AudioEngine& e, AudioBackendInfo& info)
, _active (false)
, _freewheel (false)
, _measure_latency (false)
, _last_process_start (0)
, m_cycle_count(0)
, m_total_deviation_us(0)
, m_max_deviation_us(0)
, _input_audio_device("")
, _output_audio_device("")
, _midi_driver_option(_("None"))
@ -69,11 +80,13 @@ PortAudioBackend::PortAudioBackend (AudioEngine& e, AudioBackendInfo& info)
pthread_mutex_init (&_port_callback_mutex, 0);
_pcmio = new PortAudioIO ();
_midiio = new WinMMEMidiIO ();
}
PortAudioBackend::~PortAudioBackend ()
{
delete _pcmio; _pcmio = 0;
delete _midiio; _midiio = 0;
pthread_mutex_destroy (&_port_callback_mutex);
}
@ -343,7 +356,7 @@ std::vector<std::string>
PortAudioBackend::enumerate_midi_options () const
{
if (_midi_options.empty()) {
//_midi_options.push_back (_("PortMidi"));
_midi_options.push_back (winmme_driver_name);
_midi_options.push_back (_("None"));
}
return _midi_options;
@ -352,9 +365,10 @@ PortAudioBackend::enumerate_midi_options () const
int
PortAudioBackend::set_midi_option (const std::string& opt)
{
if (opt != _("None") /* && opt != _("PortMidi")*/) {
if (opt != _("None") && opt != winmme_driver_name) {
return -1;
}
DEBUG_MIDI (string_compose ("Setting midi option to %1\n", opt));
_midi_driver_option = opt;
return 0;
}
@ -401,7 +415,6 @@ PortAudioBackend::_start (bool for_latency_measurement)
_dsp_load = 0;
_freewheeling = false;
_freewheel = false;
_last_process_start = 0;
_pcmio->pcm_setup (name_to_id(_input_audio_device), name_to_id(_output_audio_device), _samplerate, _samples_per_period);
@ -441,10 +454,28 @@ PortAudioBackend::_start (bool for_latency_measurement)
_run = true;
_port_change_flag = false;
// TODO MIDI
if (_midi_driver_option == winmme_driver_name) {
_midiio->set_enabled(true);
//_midiio->set_port_changed_callback(midi_port_change, this);
_midiio->start(); // triggers port discovery, callback coremidi_rediscover()
}
m_cycle_timer.set_samplerate(_samplerate);
m_cycle_timer.set_samples_per_cycle(_samples_per_period);
DEBUG_MIDI ("Registering MIDI ports\n");
if (register_system_midi_ports () != 0) {
PBD::error << _ ("PortAudioBackend: failed to register system midi ports.")
<< endmsg;
_run = false;
return -1;
}
DEBUG_AUDIO ("Registering Audio ports\n");
if (register_system_audio_ports()) {
PBD::error << _("PortAudioBackend: failed to register system ports.") << endmsg;
PBD::error << _("PortAudioBackend: failed to register system audio ports.") << endmsg;
_run = false;
return -1;
}
@ -557,12 +588,11 @@ PortAudioBackend::samples_since_cycle_start ()
if (!_active || !_run || _freewheeling || _freewheel) {
return 0;
}
if (_last_process_start == 0) {
if (!m_cycle_timer.valid()) {
return 0;
}
const int64_t elapsed_time_us = g_get_monotonic_time() - _last_process_start;
return std::max((pframes_t)0, (pframes_t)rint(1e-6 * elapsed_time_us * _samplerate));
return m_cycle_timer.samples_since_cycle_start (utils::get_microseconds());
}
int
@ -846,6 +876,52 @@ PortAudioBackend::register_system_audio_ports()
return 0;
}
int
PortAudioBackend::register_system_midi_ports()
{
if (_midi_driver_option == _("None")) {
DEBUG_MIDI ("No MIDI backend selected, not system midi ports available\n");
return 0;
}
LatencyRange lr;
lr.min = lr.max = _samples_per_period;
const std::vector<WinMMEMidiInputDevice*> inputs = _midiio->get_inputs();
for (std::vector<WinMMEMidiInputDevice*>::const_iterator i = inputs.begin ();
i != inputs.end ();
++i) {
std::string port_name = "system_midi:" + (*i)->name() + " capture";
PortHandle p =
add_port (port_name,
DataType::MIDI,
static_cast<PortFlags>(IsOutput | IsPhysical | IsTerminal));
if (!p) return -1;
set_latency_range (p, false, lr);
_system_midi_in.push_back (static_cast<PortMidiPort*>(p));
DEBUG_MIDI (string_compose ("Registered MIDI input port: %1\n", port_name));
}
const std::vector<WinMMEMidiOutputDevice*> outputs = _midiio->get_outputs();
for (std::vector<WinMMEMidiOutputDevice*>::const_iterator i = outputs.begin ();
i != outputs.end ();
++i) {
std::string port_name = "system_midi:" + (*i)->name() + " playback";
PortHandle p =
add_port (port_name,
DataType::MIDI,
static_cast<PortFlags>(IsInput | IsPhysical | IsTerminal));
if (!p) return -1;
set_latency_range (p, false, lr);
static_cast<PortMidiPort*>(p)->set_n_periods(2);
_system_midi_out.push_back (static_cast<PortMidiPort*>(p));
DEBUG_MIDI (string_compose ("Registered MIDI output port: %1\n", port_name));
}
return 0;
}
void
PortAudioBackend::unregister_ports (bool system_only)
{
@ -1015,11 +1091,10 @@ PortAudioBackend::midi_event_put (
if (!buffer || !port_buffer) return -1;
PortMidiBuffer& dst = * static_cast<PortMidiBuffer*>(port_buffer);
if (dst.size () && (pframes_t)dst.back ()->timestamp () > timestamp) {
#ifndef NDEBUG
// nevermind, ::get_buffer() sorts events
fprintf (stderr, "PortMidiBuffer: unordered event: %d > %d\n",
(pframes_t)dst.back ()->timestamp (), timestamp);
#endif
DEBUG_MIDI (string_compose ("PortMidiBuffer: unordered event: %1 > %2\n",
(pframes_t)dst.back ()->timestamp (),
timestamp));
}
dst.push_back (boost::shared_ptr<PortMidiEvent>(new PortMidiEvent (timestamp, buffer, size)));
return 0;
@ -1200,7 +1275,10 @@ PortAudioBackend::main_process_thread ()
_processed_samples = 0;
uint64_t clock1, clock2;
int64_t min_elapsed_us = 1000000;
int64_t max_elapsed_us = 0;
const int64_t nomial_time = 1e6 * _samples_per_period / _samplerate;
// const int64_t nomial_time = m_cycle_timer.get_length_us();
manager.registration_callback();
manager.graph_order_callback();
@ -1224,9 +1302,7 @@ PortAudioBackend::main_process_thread ()
case 0: // OK
break;
case 1:
#ifndef NDEBUG
printf("PortAudio: Xrun\n");
#endif
DEBUG_AUDIO ("PortAudio: Xrun\n");
engine.Xrun ();
break;
default:
@ -1235,7 +1311,7 @@ PortAudioBackend::main_process_thread ()
}
uint32_t i = 0;
clock1 = g_get_monotonic_time();
clock1 = utils::get_microseconds ();
/* get audio */
i = 0;
@ -1248,6 +1324,26 @@ PortAudioBackend::main_process_thread ()
for (std::vector<PamPort*>::const_iterator it = _system_midi_in.begin (); it != _system_midi_in.end (); ++it, ++i) {
PortMidiBuffer* mbuf = static_cast<PortMidiBuffer*>((*it)->get_buffer(0));
mbuf->clear();
uint64_t timestamp;
pframes_t sample_offset;
uint8_t data[256];
size_t size = sizeof(data);
while (_midiio->dequeue_input_event (i,
m_cycle_timer.get_start (),
m_cycle_timer.get_next_start (),
timestamp,
data,
size)) {
sample_offset = m_cycle_timer.samples_since_cycle_start (timestamp);
midi_event_put (mbuf, sample_offset, data, size);
DEBUG_MIDI (string_compose ("Dequeuing incoming MIDI data for device: %1 "
"sample_offset: %2 timestamp: %3, size: %4\n",
_midiio->get_inputs ()[i]->name (),
sample_offset,
timestamp,
size));
size = sizeof(data);
}
}
/* clear output buffers */
@ -1255,25 +1351,61 @@ PortAudioBackend::main_process_thread ()
memset ((*it)->get_buffer (_samples_per_period), 0, _samples_per_period * sizeof (Sample));
}
m_last_cycle_start = m_cycle_timer.get_start ();
m_cycle_timer.reset_start(utils::get_microseconds());
m_cycle_count++;
uint64_t cycle_diff_us = (m_cycle_timer.get_start () - m_last_cycle_start);
int64_t deviation_us = (cycle_diff_us - m_cycle_timer.get_length_us());
m_total_deviation_us += std::abs(deviation_us);
m_max_deviation_us =
std::max (m_max_deviation_us, (uint64_t)std::abs (deviation_us));
if ((m_cycle_count % 1000) == 0) {
uint64_t mean_deviation_us = m_total_deviation_us / m_cycle_count;
DEBUG_TIMING (
string_compose ("Mean avg cycle deviation: %1(ms), max %2(ms)\n",
mean_deviation_us * 1e-3,
m_max_deviation_us * 1e-3));
}
if (std::abs(deviation_us) > m_cycle_timer.get_length_us()) {
DEBUG_TIMING (string_compose (
"time between process(ms): %1, Est(ms): %2, Dev(ms): %3\n",
cycle_diff_us * 1e-3,
m_cycle_timer.get_length_us () * 1e-3,
deviation_us * 1e-3));
}
/* call engine process callback */
_last_process_start = g_get_monotonic_time();
if (engine.process_callback (_samples_per_period)) {
_pcmio->pcm_stop ();
_active = false;
return 0;
}
#if 0
/* mixdown midi */
for (std::vector<PamPort*>::iterator it = _system_midi_out.begin (); it != _system_midi_out.end (); ++it) {
static_cast<PortBackendMidiPort*>(*it)->next_period();
static_cast<PortMidiPort*>(*it)->next_period();
}
/* queue outgoing midi */
i = 0;
for (std::vector<PamPort*>::const_iterator it = _system_midi_out.begin (); it != _system_midi_out.end (); ++it, ++i) {
// TODO
const PortMidiBuffer* src = static_cast<const PortMidiPort*>(*it)->const_buffer();
for (PortMidiBuffer::const_iterator mit = src->begin (); mit != src->end (); ++mit) {
uint64_t timestamp =
m_cycle_timer.timestamp_from_sample_offset ((*mit)->timestamp ());
DEBUG_MIDI (
string_compose ("Queuing outgoing MIDI data for device: "
"%1 sample_offset: %2 timestamp: %3, size: %4\n",
_midiio->get_outputs ()[i]->name (),
(*mit)->timestamp (),
timestamp,
(*mit)->size ()));
_midiio->enqueue_output_event (
i, timestamp, (*mit)->data (), (*mit)->size ());
}
}
#endif
/* write back audio */
i = 0;
@ -1284,10 +1416,19 @@ PortAudioBackend::main_process_thread ()
_processed_samples += _samples_per_period;
/* calculate DSP load */
clock2 = g_get_monotonic_time();
clock2 = utils::get_microseconds ();
const int64_t elapsed_time = clock2 - clock1;
_dsp_load = elapsed_time / (float) nomial_time;
max_elapsed_us = std::max (elapsed_time, max_elapsed_us);
min_elapsed_us = std::min (elapsed_time, min_elapsed_us);
if ((m_cycle_count % 1000) == 0) {
DEBUG_TIMING (
string_compose ("Elapsed process time(usecs) max: %1, min: %2\n",
max_elapsed_us,
min_elapsed_us));
}
} else {
// Freewheelin'
@ -1296,11 +1437,10 @@ PortAudioBackend::main_process_thread ()
memset ((*it)->get_buffer (_samples_per_period), 0, _samples_per_period * sizeof (Sample));
}
clock1 = g_get_monotonic_time();
clock1 = utils::get_microseconds ();
// TODO clear midi or stop midi recv when entering fwheelin'
_last_process_start = 0;
if (engine.process_callback (_samples_per_period)) {
_pcmio->pcm_stop ();
_active = false;

View File

@ -33,6 +33,8 @@
#include "ardour/types.h"
#include "portaudio_io.h"
#include "winmmemidi_io.h"
#include "cycle_timer.h"
namespace ARDOUR {
@ -312,6 +314,7 @@ class PortAudioBackend : public AudioBackend {
private:
std::string _instance_name;
PortAudioIO *_pcmio;
WinMMEMidiIO *_midiio;
bool _run; /* keep going or stop, ardour thread */
bool _active; /* is running, process thread */
@ -319,7 +322,12 @@ class PortAudioBackend : public AudioBackend {
bool _freewheeling;
bool _measure_latency;
uint64_t _last_process_start;
uint64_t m_cycle_count;
uint64_t m_total_deviation_us;
uint64_t m_max_deviation_us;
CycleTimer m_cycle_timer;
uint64_t m_last_cycle_start;
static std::vector<std::string> _midi_options;
static std::vector<AudioBackend::DeviceStatus> _input_audio_device_status;
@ -365,6 +373,7 @@ class PortAudioBackend : public AudioBackend {
/* port engine */
PortHandle add_port (const std::string& shortname, ARDOUR::DataType, ARDOUR::PortFlags);
int register_system_audio_ports ();
int register_system_midi_ports ();
void unregister_ports (bool system_only = false);
std::vector<PamPort *> _ports;

View File

@ -1,5 +1,6 @@
/*
* Copyright (C) 2015 Robin Gareus <robin@gareus.org>
* 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
@ -23,6 +24,10 @@
#include <glibmm.h>
#include "portaudio_io.h"
#include "pbd/compose.h"
#include "debug.h"
#define INTERLEAVED_INPUT
#define INTERLEAVED_OUTPUT
@ -69,9 +74,9 @@ PortAudioIO::available_sample_rates(int device_id, std::vector<float>& sampleRat
if (device_id == -1) {
device_id = get_default_input_device ();
}
#ifndef NDEBUG
printf("PortAudio: Querying Samplerates for device %d\n", device_id);
#endif
DEBUG_AUDIO (
string_compose ("Querying Samplerates for device %1\n", device_id));
sampleRates.clear();
const PaDeviceInfo* nfo = Pa_GetDeviceInfo(device_id);
@ -181,7 +186,7 @@ PortAudioIO::set_host_api (const std::string& host_api_name)
_host_api_index = get_host_api_index_from_name (host_api_name);
if (_host_api_index < 0) {
fprintf(stderr, "Error setting host API\n");
DEBUG_AUDIO ("Error setting host API\n");
}
}
@ -192,7 +197,10 @@ PortAudioIO::get_host_api_index_from_name (const std::string& name)
PaHostApiIndex count = Pa_GetHostApiCount();
if (count < 0) return -1;
if (count < 0) {
DEBUG_AUDIO ("Host API count < 0\n");
return -1;
}
for (int i = 0; i < count; ++i) {
const PaHostApiInfo* info = Pa_GetHostApiInfo (i);
@ -200,6 +208,8 @@ PortAudioIO::get_host_api_index_from_name (const std::string& name)
if (name == info->name) return i;
}
}
DEBUG_AUDIO (string_compose ("Unable to get host API from name: %1\n", name));
return -1;
}
@ -262,26 +272,28 @@ PortAudioIO::add_devices ()
if (info == NULL) return;
int n_devices = Pa_GetDeviceCount();
#ifndef NDEBUG
printf("PortAudio %d devices found:\n", n_devices);
#endif
DEBUG_AUDIO (string_compose ("PortAudio found %1 devices\n", n_devices));
for (int i = 0 ; i < n_devices; ++i) {
const PaDeviceInfo* nfo = Pa_GetDeviceInfo(i);
if (!nfo) continue;
if (nfo->hostApi != _host_api_index) continue;
#ifndef NDEBUG
printf(" (%d) '%s' '%s' in: %d (lat: %.1f .. %.1f) out: %d (lat: %.1f .. %.1f) sr:%.2f\n",
i, info->name, nfo->name,
nfo->maxInputChannels,
nfo->defaultLowInputLatency * 1e3,
nfo->defaultHighInputLatency * 1e3,
nfo->maxOutputChannels,
nfo->defaultLowOutputLatency * 1e3,
nfo->defaultHighOutputLatency * 1e3,
nfo->defaultSampleRate);
#endif
DEBUG_AUDIO (string_compose (" (%1) '%2' '%3' in: %4 (lat: %5 .. %6) out: %7 "
"(lat: %8 .. %9) sr:%10\n",
i,
info->name,
nfo->name,
nfo->maxInputChannels,
nfo->defaultLowInputLatency * 1e3,
nfo->defaultHighInputLatency * 1e3,
nfo->maxOutputChannels,
nfo->defaultLowOutputLatency * 1e3,
nfo->defaultHighOutputLatency * 1e3,
nfo->defaultSampleRate));
if ( nfo->maxInputChannels == 0 && nfo->maxOutputChannels == 0) {
continue;
}
@ -364,15 +376,13 @@ PortAudioIO::pcm_setup (
{
_state = -2;
// TODO error reporting sans fprintf()
PaError err = paNoError;
const PaDeviceInfo *nfo_in;
const PaDeviceInfo *nfo_out;
const PaStreamInfo *nfo_s;
if (!initialize_pa()) {
fprintf(stderr, "PortAudio Initialization Failed\n");
DEBUG_AUDIO ("PortAudio Initialization Failed\n");
goto error;
}
@ -389,15 +399,14 @@ PortAudioIO::pcm_setup (
_cur_input_latency = 0;
_cur_output_latency = 0;
#ifndef NDEBUG
printf("PortAudio Device IDs: i:%d o:%d\n", device_input, device_output);
#endif
DEBUG_AUDIO (string_compose (
"PortAudio Device IDs: i:%1 o:%2\n", device_input, device_output));
nfo_in = Pa_GetDeviceInfo(device_input);
nfo_out = Pa_GetDeviceInfo(device_output);
if (!nfo_in && ! nfo_out) {
fprintf(stderr, "PortAudio Cannot Query Device Info\n");
DEBUG_AUDIO ("PortAudio Cannot Query Device Info\n");
goto error;
}
@ -409,29 +418,29 @@ PortAudioIO::pcm_setup (
}
if(_capture_channels == 0 && _playback_channels == 0) {
fprintf(stderr, "PortAudio no Input and no output channels.\n");
DEBUG_AUDIO ("PortAudio no input or output channels.\n");
goto error;
}
#ifdef __APPLE__
// pa_mac_core_blocking.c pa_stable_v19_20140130
// BUG: ringbuffer alloc requires power-of-two chn count.
if ((_capture_channels & (_capture_channels - 1)) != 0) {
printf("Adjusted capture channes to power of two (portaudio rb bug)\n");
DEBUG_AUDIO (
"Adjusted capture channels to power of two (portaudio rb bug)\n");
_capture_channels = lower_power_of_two (_capture_channels);
}
if ((_playback_channels & (_playback_channels - 1)) != 0) {
printf("Adjusted capture channes to power of two (portaudio rb bug)\n");
DEBUG_AUDIO (
"Adjusted capture channels to power of two (portaudio rb bug)\n");
_playback_channels = lower_power_of_two (_playback_channels);
}
#endif
#ifndef NDEBUG
printf("PortAudio Channels: in:%d out:%d\n",
_capture_channels, _playback_channels);
#endif
DEBUG_AUDIO (string_compose ("PortAudio Channels: in:%1 out:%2\n",
_capture_channels,
_playback_channels));
PaStreamParameters inputParam;
PaStreamParameters outputParam;
@ -471,13 +480,13 @@ PortAudioIO::pcm_setup (
NULL, NULL);
if (err != paNoError) {
fprintf(stderr, "PortAudio failed to start stream.\n");
DEBUG_AUDIO ("PortAudio failed to start stream.\n");
goto error;
}
nfo_s = Pa_GetStreamInfo (_stream);
if (!nfo_s) {
fprintf(stderr, "PortAudio failed to query stream information.\n");
DEBUG_AUDIO ("PortAudio failed to query stream information.\n");
pcm_stop();
goto error;
}
@ -486,18 +495,22 @@ PortAudioIO::pcm_setup (
_cur_input_latency = nfo_s->inputLatency * _cur_sample_rate;
_cur_output_latency = nfo_s->outputLatency * _cur_sample_rate;
#ifndef NDEBUG
printf("PA Sample Rate %.1f SPS\n", _cur_sample_rate);
printf("PA Input Latency %.1fms %d spl\n", 1e3 * nfo_s->inputLatency, _cur_input_latency);
printf("PA Output Latency %.1fms %d spl\n", 1e3 * nfo_s->outputLatency, _cur_output_latency);
#endif
DEBUG_AUDIO (string_compose ("PA Sample Rate %1 SPS\n", _cur_sample_rate));
DEBUG_AUDIO (string_compose ("PA Input Latency %1ms, %2 spl\n",
1e3 * nfo_s->inputLatency,
_cur_input_latency));
DEBUG_AUDIO (string_compose ("PA Output Latency %1ms, %2 spl\n",
1e3 * nfo_s->outputLatency,
_cur_output_latency));
_state = 0;
if (_capture_channels > 0) {
_input_buffer = (float*) malloc (samples_per_period * _capture_channels * sizeof(float));
if (!_input_buffer) {
fprintf(stderr, "PortAudio failed to allocate input buffer.\n");
DEBUG_AUDIO ("PortAudio failed to allocate input buffer.\n");
pcm_stop();
goto error;
}
@ -506,7 +519,7 @@ PortAudioIO::pcm_setup (
if (_playback_channels > 0) {
_output_buffer = (float*) calloc (samples_per_period * _playback_channels, sizeof(float));
if (!_output_buffer) {
fprintf(stderr, "PortAudio failed to allocate output buffer.\n");
DEBUG_AUDIO ("PortAudio failed to allocate output buffer.\n");
pcm_stop();
goto error;
}

View File

@ -0,0 +1,99 @@
/*
* 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 "win_utils.h"
#include <windows.h>
#include "pbd/compose.h"
#include "debug.h"
namespace {
LARGE_INTEGER
get_frequency ()
{
LARGE_INTEGER freq;
QueryPerformanceFrequency(&freq);
return freq;
}
UINT&
old_timer_resolution ()
{
static UINT timer_res_ms = 0;
return timer_res_ms;
}
} // anon namespace
namespace utils {
bool
set_min_timer_resolution ()
{
TIMECAPS caps;
if (timeGetDevCaps (&caps, sizeof(TIMECAPS)) != TIMERR_NOERROR) {
DEBUG_TIMING ("Could not get timer device capabilities.\n");
return false;
} else {
old_timer_resolution () = caps.wPeriodMin;
if (timeBeginPeriod (caps.wPeriodMin) != TIMERR_NOERROR) {
DEBUG_TIMING (string_compose (
"Could not set minimum timer resolution to %1(ms)\n", caps.wPeriodMin));
return false;
}
}
DEBUG_TIMING (string_compose ("Multimedia timer resolution set to %1(ms)\n",
caps.wPeriodMin));
return true;
}
bool
reset_timer_resolution ()
{
if (old_timer_resolution ()) {
if (timeEndPeriod (old_timer_resolution ()) != TIMERR_NOERROR) {
DEBUG_TIMING ("Could not reset timer resolution.\n");
return false;
}
}
DEBUG_TIMING (string_compose ("Multimedia timer resolution set to %1(ms)\n",
old_timer_resolution ()));
return true;
}
uint64_t get_microseconds ()
{
static LARGE_INTEGER frequency = get_frequency ();
LARGE_INTEGER current_val;
QueryPerformanceCounter (&current_val);
return (uint64_t)(((double)current_val.QuadPart) /
((double)frequency.QuadPart) * 1000000.0);
}
} // namespace utils

View File

@ -0,0 +1,34 @@
/*
* 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.
*/
#ifndef WIN_UTILS_H
#define WIN_UTILS_H
#include "stdint.h"
namespace utils {
bool set_min_timer_resolution ();
bool reset_timer_resolution ();
uint64_t get_microseconds ();
}
#endif // WIN_UTILS_H

View File

@ -0,0 +1,366 @@
/*
* 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_input_device.h"
#include <stdexcept>
#include <cmath>
#include "pbd/compose.h"
#include "win_utils.h"
#include "midi_util.h"
#include "debug.h"
static const uint32_t MIDI_BUFFER_SIZE = 32768;
static const uint32_t SYSEX_BUFFER_SIZE = 32768;
namespace ARDOUR {
WinMMEMidiInputDevice::WinMMEMidiInputDevice (int index)
: m_handle(0)
, m_midi_buffer(new RingBuffer<uint8_t>(MIDI_BUFFER_SIZE))
, m_sysex_buffer(new uint8_t[SYSEX_BUFFER_SIZE])
{
DEBUG_MIDI (string_compose ("Creating midi input device index: %1\n", index));
std::string error_msg;
if (!open (index, error_msg)) {
DEBUG_MIDI (error_msg);
throw std::runtime_error (error_msg);
}
// perhaps this should be called in open
if (!add_sysex_buffer (error_msg)) {
DEBUG_MIDI (error_msg);
std::string close_error;
if (!close (close_error)) {
DEBUG_MIDI (close_error);
}
throw std::runtime_error (error_msg);
}
set_device_name (index);
}
WinMMEMidiInputDevice::~WinMMEMidiInputDevice ()
{
std::string error_msg;
if (!close (error_msg)) {
DEBUG_MIDI (error_msg);
}
}
bool
WinMMEMidiInputDevice::open (UINT index, std::string& error_msg)
{
MMRESULT result = midiInOpen (&m_handle,
index,
(DWORD_PTR) winmm_input_callback,
(DWORD_PTR) this,
CALLBACK_FUNCTION | MIDI_IO_STATUS);
if (result != MMSYSERR_NOERROR) {
error_msg = get_error_string (result);
return false;
}
DEBUG_MIDI (string_compose ("Opened MIDI device index %1\n", index));
return true;
}
bool
WinMMEMidiInputDevice::close (std::string& error_msg)
{
// return error message for first error encountered?
bool success = true;
MMRESULT result = midiInReset (m_handle);
if (result != MMSYSERR_NOERROR) {
error_msg = get_error_string (result);
DEBUG_MIDI (error_msg);
success = false;
}
result = midiInUnprepareHeader (m_handle, &m_sysex_header, sizeof(MIDIHDR));
if (result != MMSYSERR_NOERROR) {
error_msg = get_error_string (result);
DEBUG_MIDI (error_msg);
success = false;
}
result = midiInClose (m_handle);
if (result != MMSYSERR_NOERROR) {
error_msg = get_error_string (result);
DEBUG_MIDI (error_msg);
success = false;
}
m_handle = 0;
if (success) {
DEBUG_MIDI (string_compose ("Closed MIDI device: %1\n", name ()));
} else {
DEBUG_MIDI (string_compose ("Unable to Close MIDI device: %1\n", name ()));
}
return success;
}
bool
WinMMEMidiInputDevice::add_sysex_buffer (std::string& error_msg)
{
m_sysex_header.dwBufferLength = SYSEX_BUFFER_SIZE;
m_sysex_header.dwFlags = 0;
m_sysex_header.lpData = (LPSTR)m_sysex_buffer.get ();
MMRESULT result = midiInPrepareHeader (m_handle, &m_sysex_header, sizeof(MIDIHDR));
if (result != MMSYSERR_NOERROR) {
error_msg = get_error_string (result);
DEBUG_MIDI (error_msg);
return false;
}
result = midiInAddBuffer (m_handle, &m_sysex_header, sizeof(MIDIHDR));
if (result != MMSYSERR_NOERROR) {
error_msg = get_error_string (result);
DEBUG_MIDI (error_msg);
return false;
}
return true;
}
bool
WinMMEMidiInputDevice::set_device_name (UINT index)
{
MIDIINCAPS capabilities;
MMRESULT result = midiInGetDevCaps (index, &capabilities, sizeof(capabilities));
if (result != MMSYSERR_NOERROR) {
DEBUG_MIDI (get_error_string (result));
m_name = "Unknown Midi Input Device";
return false;
} else {
m_name = capabilities.szPname;
}
return true;
}
std::string
WinMMEMidiInputDevice::get_error_string (MMRESULT error_code)
{
char error_msg[MAXERRORLENGTH];
MMRESULT result = midiInGetErrorText (error_code, error_msg, MAXERRORLENGTH);
if (result != MMSYSERR_NOERROR) {
return error_msg;
}
return "WinMMEMidiInput: Unknown Error code";
}
void CALLBACK
WinMMEMidiInputDevice::winmm_input_callback(HMIDIIN handle,
UINT msg,
DWORD_PTR instance,
DWORD_PTR midi_msg,
DWORD timestamp)
{
WinMMEMidiInputDevice* midi_input = (WinMMEMidiInputDevice*)instance;
switch (msg) {
case MIM_OPEN:
case MIM_CLOSE:
// devices_changed_callback
break;
case MIM_MOREDATA:
// passing MIDI_IO_STATUS to midiInOpen means that MIM_MOREDATA
// will be sent when the callback isn't processing MIM_DATA messages
// fast enough to keep up with messages arriving at input device
// driver. I'm not sure what could be done differently if that occurs
// so just handle MIM_DATA as per normal
case MIM_DATA:
midi_input->handle_short_msg ((const uint8_t*)&midi_msg, (uint32_t)timestamp);
break;
case MIM_LONGDATA:
midi_input->handle_sysex_msg ((MIDIHDR*)&midi_msg, (uint32_t)timestamp);
break;
case MIM_ERROR:
DEBUG_MIDI ("WinMME: Driver sent an invalid MIDI message\n");
break;
case MIM_LONGERROR:
DEBUG_MIDI ("WinMME: Driver sent an invalid or incomplete SYSEX message\n");
break;
}
}
void
WinMMEMidiInputDevice::handle_short_msg (const uint8_t* midi_data,
uint32_t timestamp)
{
int length = get_midi_msg_length (midi_data[0]);
if (length == 0 || length == -1) {
DEBUG_MIDI ("ERROR: midi input driver sent an invalid midi message\n");
return;
}
enqueue_midi_msg (midi_data, length, timestamp);
}
void
WinMMEMidiInputDevice::handle_sysex_msg (MIDIHDR* const midi_header,
uint32_t timestamp)
{
#ifdef ENABLE_SYSEX
LPMIDIHDR header = (LPMIDIHDR)midi_header;
size_t byte_count = header->dwBytesRecorded;
if (!byte_count) {
DEBUG_MIDI (
"ERROR: WinMME driver has returned sysex header to us with no bytes\n");
return;
}
uint8_t* data = (uint8_t*)header->lpData;
if ((data[0] != 0xf0) || (data[byte_count - 1] != 0xf7)) {
DEBUG_MIDI (string_compose ("Discarding %1 byte sysex chunk\n", byte_count));
} else {
enqueue_midi_msg (data, byte_count, timestamp);
}
MMRESULT result = midiInAddBuffer (m_handle, &m_sysex_header, sizeof(MIDIHDR));
if (result != MMSYSERR_NOERROR) {
DEBUG_MIDI (get_error_string (result));
}
#endif
}
// fix param order
bool
WinMMEMidiInputDevice::dequeue_midi_event (uint64_t timestamp_start,
uint64_t timestamp_end,
uint64_t& timestamp,
uint8_t* midi_data,
size_t& data_size)
{
const uint32_t read_space = m_midi_buffer->read_space();
struct MidiEventHeader h(0,0);
if (read_space <= sizeof(MidiEventHeader)) {
return false;
}
RingBuffer<uint8_t>::rw_vector vector;
m_midi_buffer->get_read_vector (&vector);
if (vector.len[0] >= sizeof(MidiEventHeader)) {
memcpy ((uint8_t*)&h, vector.buf[0], sizeof(MidiEventHeader));
} else {
if (vector.len[0] > 0) {
memcpy ((uint8_t*)&h, vector.buf[0], vector.len[0]);
}
assert (vector.buf[1] || vector.len[0] == sizeof(MidiEventHeader));
memcpy (((uint8_t*)&h) + vector.len[0],
vector.buf[1],
sizeof(MidiEventHeader) - vector.len[0]);
}
if (h.time >= timestamp_end) {
DEBUG_TIMING (string_compose ("WinMMEMidiInput EVENT %1(ms) early\n",
(h.time - timestamp_end) * 1e-3));
return false;
} else if (h.time < timestamp_start) {
DEBUG_TIMING (string_compose ("WinMMEMidiInput EVENT %1(ms) late\n",
(timestamp_start - h.time) * 1e-3));
}
m_midi_buffer->increment_read_idx (sizeof(MidiEventHeader));
assert (h.size > 0);
if (h.size > data_size) {
DEBUG_MIDI ("WinMMEMidiInput::dequeue_event MIDI event too large!\n");
m_midi_buffer->increment_read_idx (h.size);
return false;
}
if (m_midi_buffer->read (&midi_data[0], h.size) != h.size) {
DEBUG_MIDI ("WinMMEMidiInput::dequeue_event Garbled MIDI EVENT DATA!!\n");
return false;
}
timestamp = h.time;
data_size = h.size;
return true;
}
bool
WinMMEMidiInputDevice::enqueue_midi_msg (const uint8_t* midi_data,
size_t data_size,
uint32_t timestamp)
{
const uint32_t total_size = sizeof(MidiEventHeader) + data_size;
if (data_size == 0) {
DEBUG_MIDI ("ERROR: zero length midi data\n");
return false;
}
if (m_midi_buffer->write_space () < total_size) {
DEBUG_MIDI ("WinMMEMidiInput: ring buffer overflow\n");
return false;
}
// don't use winmme timestamps for now
uint64_t ts = utils::get_microseconds ();
DEBUG_TIMING (string_compose (
"Enqueing MIDI data device: %1 with timestamp: %2 and size %3\n",
name (),
ts,
data_size));
struct MidiEventHeader h (ts, data_size);
m_midi_buffer->write ((uint8_t*)&h, sizeof(MidiEventHeader));
m_midi_buffer->write (midi_data, data_size);
return true;
}
bool
WinMMEMidiInputDevice::start ()
{
if (!m_started) {
MMRESULT result = midiInStart (m_handle);
m_started = (result == MMSYSERR_NOERROR);
if (!m_started) {
DEBUG_MIDI (get_error_string (result));
} else {
DEBUG_MIDI (
string_compose ("WinMMEMidiInput: device %1 started\n", name ()));
}
}
return m_started;
}
bool
WinMMEMidiInputDevice::stop ()
{
if (m_started) {
MMRESULT result = midiInStop (m_handle);
m_started = (result != MMSYSERR_NOERROR);
if (m_started) {
DEBUG_MIDI (get_error_string (result));
} else {
DEBUG_MIDI (
string_compose ("WinMMEMidiInput: device %1 stopped\n", name ()));
}
}
return !m_started;
}
} // namespace ARDOUR

View File

@ -0,0 +1,105 @@
/*
* 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.
*/
#ifndef WINMME_MIDI_INPUT_DEVICE_H
#define WINMME_MIDI_INPUT_DEVICE_H
#include <windows.h>
#include <mmsystem.h>
#include <stdint.h>
#include <string>
#include <boost/scoped_array.hpp>
#include <boost/scoped_ptr.hpp>
#include <pbd/ringbuffer.h>
namespace ARDOUR {
class WinMMEMidiInputDevice {
public: // ctors
WinMMEMidiInputDevice (int index);
~WinMMEMidiInputDevice ();
public: // methods
/**
* Dequeue events that have accumulated in winmm_input_callback.
*
* This is called by the audio processing thread/callback to transfer events
* into midi ports before processing.
*/
bool dequeue_midi_event (uint64_t timestamp_start,
uint64_t timestamp_end,
uint64_t& timestamp,
uint8_t* data,
size_t& size);
bool start ();
bool stop ();
void set_enabled (bool enable);
bool get_enabled ();
/**
* @return Name of midi device
*/
std::string name () const { return m_name; }
private: // methods
bool open (UINT index, std::string& error_msg);
bool close (std::string& error_msg);
bool add_sysex_buffer (std::string& error_msg);
bool set_device_name (UINT index);
std::string get_error_string (MMRESULT error_code);
static void CALLBACK winmm_input_callback (HMIDIIN handle,
UINT msg,
DWORD_PTR instance,
DWORD_PTR midi_msg,
DWORD timestamp);
void handle_short_msg (const uint8_t* midi_data, uint32_t timestamp);
void handle_sysex_msg (MIDIHDR* const midi_header, uint32_t timestamp);
bool enqueue_midi_msg (const uint8_t* midi_data, size_t size, uint32_t timestamp);
private: // data
HMIDIIN m_handle;
MIDIHDR m_sysex_header;
bool m_started;
bool m_enabled;
std::string m_name;
// can't use unique_ptr yet
boost::scoped_ptr<RingBuffer<uint8_t> > m_midi_buffer;
boost::scoped_array<uint8_t> m_sysex_buffer;
};
}
#endif // WINMME_MIDI_INPUT_DEVICE_H

View File

@ -0,0 +1,294 @@
/*
* 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 <windows.h>
#include <mmsystem.h>
#include <sstream>
#include "pbd/error.h"
#include "pbd/compose.h"
#include "winmmemidi_io.h"
#include "win_utils.h"
#include "debug.h"
#include "i18n.h"
using namespace ARDOUR;
using namespace utils;
WinMMEMidiIO::WinMMEMidiIO()
: m_active (false)
, m_enabled (true)
, m_run (false)
, m_changed_callback (0)
, m_changed_arg (0)
{
pthread_mutex_init (&m_device_lock, 0);
}
WinMMEMidiIO::~WinMMEMidiIO()
{
pthread_mutex_lock (&m_device_lock);
cleanup();
pthread_mutex_unlock (&m_device_lock);
pthread_mutex_destroy (&m_device_lock);
}
void
WinMMEMidiIO::cleanup()
{
DEBUG_MIDI ("MIDI cleanup\n");
m_active = false;
destroy_input_devices ();
destroy_output_devices ();
}
bool
WinMMEMidiIO::dequeue_input_event (uint32_t port,
uint64_t timestamp_start,
uint64_t timestamp_end,
uint64_t &timestamp,
uint8_t *d,
size_t &s)
{
if (!m_active) {
return false;
}
assert(port < m_inputs.size());
// m_inputs access should be protected by trylock
return m_inputs[port]->dequeue_midi_event (
timestamp_start, timestamp_end, timestamp, d, s);
}
bool
WinMMEMidiIO::enqueue_output_event (uint32_t port,
uint64_t timestamp,
const uint8_t *d,
const size_t s)
{
if (!m_active) {
return false;
}
assert(port < m_outputs.size());
// m_outputs access should be protected by trylock
return m_outputs[port]->enqueue_midi_event (timestamp, d, s);
}
std::string
WinMMEMidiIO::port_id (uint32_t port, bool input)
{
std::stringstream ss;
if (input) {
ss << "system:midi_capture_";
ss << port;
} else {
ss << "system:midi_playback_";
ss << port;
}
return ss.str();
}
std::string
WinMMEMidiIO::port_name (uint32_t port, bool input)
{
if (input) {
if (port < m_inputs.size ()) {
return m_inputs[port]->name ();
}
} else {
if (port < m_outputs.size ()) {
return m_outputs[port]->name ();
}
}
return "";
}
void
WinMMEMidiIO::start ()
{
if (m_run) {
DEBUG_MIDI ("MIDI driver already started\n");
return;
}
m_run = true;
DEBUG_MIDI ("Starting MIDI driver\n");
set_min_timer_resolution();
discover();
start_devices ();
}
void
WinMMEMidiIO::stop ()
{
DEBUG_MIDI ("Stopping MIDI driver\n");
m_run = false;
stop_devices ();
pthread_mutex_lock (&m_device_lock);
cleanup ();
pthread_mutex_unlock (&m_device_lock);
reset_timer_resolution();
}
void
WinMMEMidiIO::start_devices ()
{
for (std::vector<WinMMEMidiInputDevice*>::iterator i = m_inputs.begin ();
i < m_inputs.end();
++i) {
if (!(*i)->start ()) {
PBD::error << string_compose (_("Unable to start MIDI input device %1\n"),
(*i)->name ()) << endmsg;
}
}
for (std::vector<WinMMEMidiOutputDevice*>::iterator i = m_outputs.begin ();
i < m_outputs.end();
++i) {
if (!(*i)->start ()) {
PBD::error << string_compose (_ ("Unable to start MIDI output device %1\n"),
(*i)->name ()) << endmsg;
}
}
}
void
WinMMEMidiIO::stop_devices ()
{
for (std::vector<WinMMEMidiInputDevice*>::iterator i = m_inputs.begin ();
i < m_inputs.end();
++i) {
if (!(*i)->stop ()) {
PBD::error << string_compose (_ ("Unable to stop MIDI input device %1\n"),
(*i)->name ()) << endmsg;
}
}
for (std::vector<WinMMEMidiOutputDevice*>::iterator i = m_outputs.begin ();
i < m_outputs.end();
++i) {
if (!(*i)->stop ()) {
PBD::error << string_compose (_ ("Unable to stop MIDI output device %1\n"),
(*i)->name ()) << endmsg;
}
}
}
void
WinMMEMidiIO::create_input_devices ()
{
int srcCount = midiInGetNumDevs ();
DEBUG_MIDI (string_compose ("MidiIn count: %1\n", srcCount));
for (int i = 0; i < srcCount; ++i) {
try {
WinMMEMidiInputDevice* midi_input = new WinMMEMidiInputDevice (i);
if (midi_input) {
m_inputs.push_back (midi_input);
}
}
catch (...) {
DEBUG_MIDI ("Unable to create MIDI input\n");
continue;
}
}
}
void
WinMMEMidiIO::create_output_devices ()
{
int dstCount = midiOutGetNumDevs ();
DEBUG_MIDI (string_compose ("MidiOut count: %1\n", dstCount));
for (int i = 0; i < dstCount; ++i) {
try {
WinMMEMidiOutputDevice* midi_output = new WinMMEMidiOutputDevice(i);
if (midi_output) {
m_outputs.push_back(midi_output);
}
} catch(...) {
DEBUG_MIDI ("Unable to create MIDI output\n");
continue;
}
}
}
void
WinMMEMidiIO::destroy_input_devices ()
{
while (!m_inputs.empty ()) {
WinMMEMidiInputDevice* midi_input = m_inputs.back ();
// assert(midi_input->stopped ());
m_inputs.pop_back ();
delete midi_input;
}
}
void
WinMMEMidiIO::destroy_output_devices ()
{
while (!m_outputs.empty ()) {
WinMMEMidiOutputDevice* midi_output = m_outputs.back ();
// assert(midi_output->stopped ());
m_outputs.pop_back ();
delete midi_output;
}
}
void
WinMMEMidiIO::discover()
{
if (!m_run) {
return;
}
if (pthread_mutex_trylock (&m_device_lock)) {
return;
}
cleanup ();
create_input_devices ();
create_output_devices ();
if (!(m_inputs.size () || m_outputs.size ())) {
DEBUG_MIDI ("No midi inputs or outputs\n");
pthread_mutex_unlock (&m_device_lock);
return;
}
DEBUG_MIDI (string_compose ("Discovered %1 inputs and %2 outputs\n",
m_inputs.size (),
m_outputs.size ()));
if (m_changed_callback) {
m_changed_callback(m_changed_arg);
}
m_active = true;
pthread_mutex_unlock (&m_device_lock);
}

View File

@ -0,0 +1,124 @@
/*
* 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.
*/
#ifndef WINMME_MIDI_IO_H
#define WINMME_MIDI_IO_H
#include <map>
#include <vector>
#include <string>
#include <stdint.h>
#include <boost/shared_ptr.hpp>
#include "pbd/ringbuffer.h"
#include "winmmemidi_input_device.h"
#include "winmmemidi_output_device.h"
namespace ARDOUR {
struct WinMMEMIDIPacket {
#if 0
WinMMEMIDIPacket (const WinMMEMIDIPacket& other)
: timeStamp (other.timeStamp)
, length (other.length)
{
if (length > 0) {
memcpy (data, other.data, length);
}
}
#endif
// MIDITimeStamp timeStamp;
uint16_t length;
uint8_t data[256];
};
typedef std::vector<boost::shared_ptr<WinMMEMIDIPacket> > WinMMEMIDIQueue;
class WinMMEMidiIO {
public:
WinMMEMidiIO ();
~WinMMEMidiIO ();
void start ();
void stop ();
bool dequeue_input_event (uint32_t port,
uint64_t timestamp_start,
uint64_t timestamp_end,
uint64_t& timestamp,
uint8_t* data,
size_t& size);
bool enqueue_output_event (uint32_t port,
uint64_t timestamp,
const uint8_t* data,
const size_t size);
uint32_t n_midi_inputs (void) const { return m_inputs.size(); }
uint32_t n_midi_outputs (void) const { return m_outputs.size(); }
std::vector<WinMMEMidiInputDevice*> get_inputs () { return m_inputs; }
std::vector<WinMMEMidiOutputDevice*> get_outputs () { return m_outputs; }
std::string port_id (uint32_t, bool input);
std::string port_name (uint32_t, bool input);
void set_enabled (bool yn = true) { m_enabled = yn; }
bool enabled (void) const { return m_active && m_enabled; }
void set_port_changed_callback (void (changed_callback (void*)), void *arg) {
m_changed_callback = changed_callback;
m_changed_arg = arg;
}
private: // Methods
void discover ();
void cleanup ();
void create_input_devices ();
void create_output_devices ();
void destroy_input_devices ();
void destroy_output_devices ();
void start_devices ();
void stop_devices ();
private: // Data
std::vector<WinMMEMidiInputDevice*> m_inputs;
std::vector<WinMMEMidiOutputDevice*> m_outputs;
bool m_active;
bool m_enabled;
bool m_run;
void (* m_changed_callback) (void*);
void * m_changed_arg;
// protects access to m_inputs and m_outputs
pthread_mutex_t m_device_lock;
};
} // namespace
#endif // WINMME_MIDI_IO_H

View File

@ -0,0 +1,494 @@
/*
* 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 "rt_thread.h"
#include "win_utils.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 = midiOutReset (m_handle);
if (result != MMSYSERR_NOERROR) {
error_msg = get_error_string (result);
DEBUG_MIDI (error_msg);
success = false;
}
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 start 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;
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;
while (!m_thread_quit) {
if (!wait (m_queue_semaphore)) {
break;
}
MidiEventHeader h (0, 0);
uint8_t data[MAX_MIDI_MSG_SIZE];
const uint32_t read_space = m_midi_buffer->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 = utils::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 = utils::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));
}
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;
}
#if ENABLE_SYSEX
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.
if (!wait (m_sysex_semaphore)) {
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;
}
#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

View File

@ -0,0 +1,103 @@
/*
* 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.
*/
#ifndef WINMME_MIDI_OUTPUT_DEVICE_H
#define WINMME_MIDI_OUTPUT_DEVICE_H
#include <windows.h>
#include <mmsystem.h>
#include <stdint.h>
#include <string>
#include <boost/scoped_ptr.hpp>
#include <pbd/ringbuffer.h>
namespace ARDOUR {
class WinMMEMidiOutputDevice {
public:
WinMMEMidiOutputDevice (int index);
~WinMMEMidiOutputDevice ();
bool enqueue_midi_event (uint64_t rel_event_time_us,
const uint8_t* data,
const size_t size);
bool start ();
bool stop ();
void set_enabled (bool enable);
bool get_enabled ();
std::string name () const { return m_name; }
private: // Methods
bool open (UINT index, std::string& error_msg);
bool close (std::string& error_msg);
bool set_device_name (UINT index);
std::string get_error_string (MMRESULT error_code);
bool start_midi_output_thread ();
bool stop_midi_output_thread ();
bool signal (HANDLE semaphore);
bool wait (HANDLE semaphore);
static void* midi_output_thread (void*);
void midi_output_thread ();
bool wait_for_microseconds (int64_t us);
static void CALLBACK winmm_output_callback (HMIDIOUT handle,
UINT msg,
DWORD_PTR instance,
DWORD_PTR midi_data,
DWORD_PTR timestamp);
void midi_output_callback (UINT msg, DWORD_PTR data, DWORD_PTR timestamp);
private: // Data
HMIDIOUT m_handle;
HANDLE m_queue_semaphore;
HANDLE m_sysex_semaphore;
HANDLE m_timer;
bool m_started;
bool m_enabled;
std::string m_name;
pthread_t m_output_thread_handle;
bool m_thread_running;
bool m_thread_quit;
boost::scoped_ptr<RingBuffer<uint8_t> > m_midi_buffer;
};
} // namespace ARDOUR
#endif // WINMME_MIDI_OUTPUT_DEVICE_H

View File

@ -22,7 +22,11 @@ def build(bld):
obj = bld(features = 'cxx cxxshlib')
obj.source = [ 'portaudio_backend.cc',
'portaudio_io.cc',
# 'portmidi_io.cc'
'winmmemidi_io.cc',
'winmmemidi_input_device.cc',
'winmmemidi_output_device.cc',
'win_utils.cc',
'midi_util.cc'
]
obj.includes = ['.']
obj.name = 'portaudio_backend'

View File

@ -63,6 +63,11 @@ DebugBits PBD::DEBUG::Configuration = PBD::new_debug_bit ("configuration");
from dynamically loaded code, for use in command line parsing, is way above the pay grade
of this debug tracing scheme.
*/
DebugBits PBD::DEBUG::BackendMIDI = PBD::new_debug_bit ("BackendMIDI");
DebugBits PBD::DEBUG::BackendAudio = PBD::new_debug_bit ("BackendAudio");
DebugBits PBD::DEBUG::BackendTiming = PBD::new_debug_bit ("BackendTiming");
DebugBits PBD::DEBUG::BackendThreads = PBD::new_debug_bit ("BackendThreads");
DebugBits PBD::DEBUG::WavesMIDI = PBD::new_debug_bit ("WavesMIDI");
DebugBits PBD::DEBUG::WavesAudio = PBD::new_debug_bit ("WavesAudio");

View File

@ -56,6 +56,11 @@ namespace PBD {
LIBPBD_API extern DebugBits Configuration;
LIBPBD_API extern DebugBits FileUtils;
LIBPBD_API extern DebugBits BackendMIDI;
LIBPBD_API extern DebugBits BackendAudio;
LIBPBD_API extern DebugBits BackendTiming;
LIBPBD_API extern DebugBits BackendThreads;
/* See notes in ../debug.cc on why these are defined here */
LIBPBD_API extern DebugBits WavesMIDI;