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 "ardour/port_manager.h"
#include "i18n.h" #include "i18n.h"
#include "win_utils.h"
#include "debug.h"
using namespace ARDOUR; using namespace ARDOUR;
namespace {
const char * const winmme_driver_name = X_("WinMME");
}
static std::string s_instance_name; static std::string s_instance_name;
size_t PortAudioBackend::_max_buffer_size = 8192; size_t PortAudioBackend::_max_buffer_size = 8192;
std::vector<std::string> PortAudioBackend::_midi_options; std::vector<std::string> PortAudioBackend::_midi_options;
@ -51,7 +60,9 @@ PortAudioBackend::PortAudioBackend (AudioEngine& e, AudioBackendInfo& info)
, _active (false) , _active (false)
, _freewheel (false) , _freewheel (false)
, _measure_latency (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("") , _input_audio_device("")
, _output_audio_device("") , _output_audio_device("")
, _midi_driver_option(_("None")) , _midi_driver_option(_("None"))
@ -69,11 +80,13 @@ PortAudioBackend::PortAudioBackend (AudioEngine& e, AudioBackendInfo& info)
pthread_mutex_init (&_port_callback_mutex, 0); pthread_mutex_init (&_port_callback_mutex, 0);
_pcmio = new PortAudioIO (); _pcmio = new PortAudioIO ();
_midiio = new WinMMEMidiIO ();
} }
PortAudioBackend::~PortAudioBackend () PortAudioBackend::~PortAudioBackend ()
{ {
delete _pcmio; _pcmio = 0; delete _pcmio; _pcmio = 0;
delete _midiio; _midiio = 0;
pthread_mutex_destroy (&_port_callback_mutex); pthread_mutex_destroy (&_port_callback_mutex);
} }
@ -343,7 +356,7 @@ std::vector<std::string>
PortAudioBackend::enumerate_midi_options () const PortAudioBackend::enumerate_midi_options () const
{ {
if (_midi_options.empty()) { if (_midi_options.empty()) {
//_midi_options.push_back (_("PortMidi")); _midi_options.push_back (winmme_driver_name);
_midi_options.push_back (_("None")); _midi_options.push_back (_("None"));
} }
return _midi_options; return _midi_options;
@ -352,9 +365,10 @@ PortAudioBackend::enumerate_midi_options () const
int int
PortAudioBackend::set_midi_option (const std::string& opt) PortAudioBackend::set_midi_option (const std::string& opt)
{ {
if (opt != _("None") /* && opt != _("PortMidi")*/) { if (opt != _("None") && opt != winmme_driver_name) {
return -1; return -1;
} }
DEBUG_MIDI (string_compose ("Setting midi option to %1\n", opt));
_midi_driver_option = opt; _midi_driver_option = opt;
return 0; return 0;
} }
@ -401,7 +415,6 @@ PortAudioBackend::_start (bool for_latency_measurement)
_dsp_load = 0; _dsp_load = 0;
_freewheeling = false; _freewheeling = false;
_freewheel = 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); _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; _run = true;
_port_change_flag = false; _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()) { 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; _run = false;
return -1; return -1;
} }
@ -557,12 +588,11 @@ PortAudioBackend::samples_since_cycle_start ()
if (!_active || !_run || _freewheeling || _freewheel) { if (!_active || !_run || _freewheeling || _freewheel) {
return 0; return 0;
} }
if (_last_process_start == 0) { if (!m_cycle_timer.valid()) {
return 0; return 0;
} }
const int64_t elapsed_time_us = g_get_monotonic_time() - _last_process_start; return m_cycle_timer.samples_since_cycle_start (utils::get_microseconds());
return std::max((pframes_t)0, (pframes_t)rint(1e-6 * elapsed_time_us * _samplerate));
} }
int int
@ -846,6 +876,52 @@ PortAudioBackend::register_system_audio_ports()
return 0; 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 void
PortAudioBackend::unregister_ports (bool system_only) PortAudioBackend::unregister_ports (bool system_only)
{ {
@ -1015,11 +1091,10 @@ PortAudioBackend::midi_event_put (
if (!buffer || !port_buffer) return -1; if (!buffer || !port_buffer) return -1;
PortMidiBuffer& dst = * static_cast<PortMidiBuffer*>(port_buffer); PortMidiBuffer& dst = * static_cast<PortMidiBuffer*>(port_buffer);
if (dst.size () && (pframes_t)dst.back ()->timestamp () > timestamp) { if (dst.size () && (pframes_t)dst.back ()->timestamp () > timestamp) {
#ifndef NDEBUG
// nevermind, ::get_buffer() sorts events // nevermind, ::get_buffer() sorts events
fprintf (stderr, "PortMidiBuffer: unordered event: %d > %d\n", DEBUG_MIDI (string_compose ("PortMidiBuffer: unordered event: %1 > %2\n",
(pframes_t)dst.back ()->timestamp (), timestamp); (pframes_t)dst.back ()->timestamp (),
#endif timestamp));
} }
dst.push_back (boost::shared_ptr<PortMidiEvent>(new PortMidiEvent (timestamp, buffer, size))); dst.push_back (boost::shared_ptr<PortMidiEvent>(new PortMidiEvent (timestamp, buffer, size)));
return 0; return 0;
@ -1200,7 +1275,10 @@ PortAudioBackend::main_process_thread ()
_processed_samples = 0; _processed_samples = 0;
uint64_t clock1, clock2; 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 = 1e6 * _samples_per_period / _samplerate;
// const int64_t nomial_time = m_cycle_timer.get_length_us();
manager.registration_callback(); manager.registration_callback();
manager.graph_order_callback(); manager.graph_order_callback();
@ -1224,9 +1302,7 @@ PortAudioBackend::main_process_thread ()
case 0: // OK case 0: // OK
break; break;
case 1: case 1:
#ifndef NDEBUG DEBUG_AUDIO ("PortAudio: Xrun\n");
printf("PortAudio: Xrun\n");
#endif
engine.Xrun (); engine.Xrun ();
break; break;
default: default:
@ -1235,7 +1311,7 @@ PortAudioBackend::main_process_thread ()
} }
uint32_t i = 0; uint32_t i = 0;
clock1 = g_get_monotonic_time(); clock1 = utils::get_microseconds ();
/* get audio */ /* get audio */
i = 0; 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) { 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)); PortMidiBuffer* mbuf = static_cast<PortMidiBuffer*>((*it)->get_buffer(0));
mbuf->clear(); 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 */ /* clear output buffers */
@ -1255,25 +1351,61 @@ PortAudioBackend::main_process_thread ()
memset ((*it)->get_buffer (_samples_per_period), 0, _samples_per_period * sizeof (Sample)); 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 */ /* call engine process callback */
_last_process_start = g_get_monotonic_time();
if (engine.process_callback (_samples_per_period)) { if (engine.process_callback (_samples_per_period)) {
_pcmio->pcm_stop (); _pcmio->pcm_stop ();
_active = false; _active = false;
return 0; return 0;
} }
#if 0
/* mixdown midi */ /* mixdown midi */
for (std::vector<PamPort*>::iterator it = _system_midi_out.begin (); it != _system_midi_out.end (); ++it) { 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 */ /* queue outgoing midi */
i = 0; i = 0;
for (std::vector<PamPort*>::const_iterator it = _system_midi_out.begin (); it != _system_midi_out.end (); ++it, ++i) { 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 */ /* write back audio */
i = 0; i = 0;
@ -1284,10 +1416,19 @@ PortAudioBackend::main_process_thread ()
_processed_samples += _samples_per_period; _processed_samples += _samples_per_period;
/* calculate DSP load */ /* calculate DSP load */
clock2 = g_get_monotonic_time(); clock2 = utils::get_microseconds ();
const int64_t elapsed_time = clock2 - clock1; const int64_t elapsed_time = clock2 - clock1;
_dsp_load = elapsed_time / (float) nomial_time; _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 { } else {
// Freewheelin' // Freewheelin'
@ -1296,11 +1437,10 @@ PortAudioBackend::main_process_thread ()
memset ((*it)->get_buffer (_samples_per_period), 0, _samples_per_period * sizeof (Sample)); 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' // TODO clear midi or stop midi recv when entering fwheelin'
_last_process_start = 0;
if (engine.process_callback (_samples_per_period)) { if (engine.process_callback (_samples_per_period)) {
_pcmio->pcm_stop (); _pcmio->pcm_stop ();
_active = false; _active = false;

View file

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

View file

@ -1,5 +1,6 @@
/* /*
* Copyright (C) 2015 Robin Gareus <robin@gareus.org> * 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 * This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -23,6 +24,10 @@
#include <glibmm.h> #include <glibmm.h>
#include "portaudio_io.h" #include "portaudio_io.h"
#include "pbd/compose.h"
#include "debug.h"
#define INTERLEAVED_INPUT #define INTERLEAVED_INPUT
#define INTERLEAVED_OUTPUT #define INTERLEAVED_OUTPUT
@ -69,9 +74,9 @@ PortAudioIO::available_sample_rates(int device_id, std::vector<float>& sampleRat
if (device_id == -1) { if (device_id == -1) {
device_id = get_default_input_device (); device_id = get_default_input_device ();
} }
#ifndef NDEBUG
printf("PortAudio: Querying Samplerates for device %d\n", device_id); DEBUG_AUDIO (
#endif string_compose ("Querying Samplerates for device %1\n", device_id));
sampleRates.clear(); sampleRates.clear();
const PaDeviceInfo* nfo = Pa_GetDeviceInfo(device_id); 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); _host_api_index = get_host_api_index_from_name (host_api_name);
if (_host_api_index < 0) { 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(); 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) { for (int i = 0; i < count; ++i) {
const PaHostApiInfo* info = Pa_GetHostApiInfo (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; if (name == info->name) return i;
} }
} }
DEBUG_AUDIO (string_compose ("Unable to get host API from name: %1\n", name));
return -1; return -1;
} }
@ -262,26 +272,28 @@ PortAudioIO::add_devices ()
if (info == NULL) return; if (info == NULL) return;
int n_devices = Pa_GetDeviceCount(); int n_devices = Pa_GetDeviceCount();
#ifndef NDEBUG
printf("PortAudio %d devices found:\n", n_devices); DEBUG_AUDIO (string_compose ("PortAudio found %1 devices\n", n_devices));
#endif
for (int i = 0 ; i < n_devices; ++i) { for (int i = 0 ; i < n_devices; ++i) {
const PaDeviceInfo* nfo = Pa_GetDeviceInfo(i); const PaDeviceInfo* nfo = Pa_GetDeviceInfo(i);
if (!nfo) continue; if (!nfo) continue;
if (nfo->hostApi != _host_api_index) 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", DEBUG_AUDIO (string_compose (" (%1) '%2' '%3' in: %4 (lat: %5 .. %6) out: %7 "
i, info->name, nfo->name, "(lat: %8 .. %9) sr:%10\n",
nfo->maxInputChannels, i,
nfo->defaultLowInputLatency * 1e3, info->name,
nfo->defaultHighInputLatency * 1e3, nfo->name,
nfo->maxOutputChannels, nfo->maxInputChannels,
nfo->defaultLowOutputLatency * 1e3, nfo->defaultLowInputLatency * 1e3,
nfo->defaultHighOutputLatency * 1e3, nfo->defaultHighInputLatency * 1e3,
nfo->defaultSampleRate); nfo->maxOutputChannels,
#endif nfo->defaultLowOutputLatency * 1e3,
nfo->defaultHighOutputLatency * 1e3,
nfo->defaultSampleRate));
if ( nfo->maxInputChannels == 0 && nfo->maxOutputChannels == 0) { if ( nfo->maxInputChannels == 0 && nfo->maxOutputChannels == 0) {
continue; continue;
} }
@ -364,15 +376,13 @@ PortAudioIO::pcm_setup (
{ {
_state = -2; _state = -2;
// TODO error reporting sans fprintf()
PaError err = paNoError; PaError err = paNoError;
const PaDeviceInfo *nfo_in; const PaDeviceInfo *nfo_in;
const PaDeviceInfo *nfo_out; const PaDeviceInfo *nfo_out;
const PaStreamInfo *nfo_s; const PaStreamInfo *nfo_s;
if (!initialize_pa()) { if (!initialize_pa()) {
fprintf(stderr, "PortAudio Initialization Failed\n"); DEBUG_AUDIO ("PortAudio Initialization Failed\n");
goto error; goto error;
} }
@ -389,15 +399,14 @@ PortAudioIO::pcm_setup (
_cur_input_latency = 0; _cur_input_latency = 0;
_cur_output_latency = 0; _cur_output_latency = 0;
#ifndef NDEBUG DEBUG_AUDIO (string_compose (
printf("PortAudio Device IDs: i:%d o:%d\n", device_input, device_output); "PortAudio Device IDs: i:%1 o:%2\n", device_input, device_output));
#endif
nfo_in = Pa_GetDeviceInfo(device_input); nfo_in = Pa_GetDeviceInfo(device_input);
nfo_out = Pa_GetDeviceInfo(device_output); nfo_out = Pa_GetDeviceInfo(device_output);
if (!nfo_in && ! nfo_out) { if (!nfo_in && ! nfo_out) {
fprintf(stderr, "PortAudio Cannot Query Device Info\n"); DEBUG_AUDIO ("PortAudio Cannot Query Device Info\n");
goto error; goto error;
} }
@ -409,29 +418,29 @@ PortAudioIO::pcm_setup (
} }
if(_capture_channels == 0 && _playback_channels == 0) { 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; goto error;
} }
#ifdef __APPLE__ #ifdef __APPLE__
// pa_mac_core_blocking.c pa_stable_v19_20140130 // pa_mac_core_blocking.c pa_stable_v19_20140130
// BUG: ringbuffer alloc requires power-of-two chn count. // BUG: ringbuffer alloc requires power-of-two chn count.
if ((_capture_channels & (_capture_channels - 1)) != 0) { 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); _capture_channels = lower_power_of_two (_capture_channels);
} }
if ((_playback_channels & (_playback_channels - 1)) != 0) { 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); _playback_channels = lower_power_of_two (_playback_channels);
} }
#endif #endif
#ifndef NDEBUG DEBUG_AUDIO (string_compose ("PortAudio Channels: in:%1 out:%2\n",
printf("PortAudio Channels: in:%d out:%d\n", _capture_channels,
_capture_channels, _playback_channels); _playback_channels));
#endif
PaStreamParameters inputParam; PaStreamParameters inputParam;
PaStreamParameters outputParam; PaStreamParameters outputParam;
@ -471,13 +480,13 @@ PortAudioIO::pcm_setup (
NULL, NULL); NULL, NULL);
if (err != paNoError) { if (err != paNoError) {
fprintf(stderr, "PortAudio failed to start stream.\n"); DEBUG_AUDIO ("PortAudio failed to start stream.\n");
goto error; goto error;
} }
nfo_s = Pa_GetStreamInfo (_stream); nfo_s = Pa_GetStreamInfo (_stream);
if (!nfo_s) { if (!nfo_s) {
fprintf(stderr, "PortAudio failed to query stream information.\n"); DEBUG_AUDIO ("PortAudio failed to query stream information.\n");
pcm_stop(); pcm_stop();
goto error; goto error;
} }
@ -486,18 +495,22 @@ PortAudioIO::pcm_setup (
_cur_input_latency = nfo_s->inputLatency * _cur_sample_rate; _cur_input_latency = nfo_s->inputLatency * _cur_sample_rate;
_cur_output_latency = nfo_s->outputLatency * _cur_sample_rate; _cur_output_latency = nfo_s->outputLatency * _cur_sample_rate;
#ifndef NDEBUG DEBUG_AUDIO (string_compose ("PA Sample Rate %1 SPS\n", _cur_sample_rate));
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); DEBUG_AUDIO (string_compose ("PA Input Latency %1ms, %2 spl\n",
printf("PA Output Latency %.1fms %d spl\n", 1e3 * nfo_s->outputLatency, _cur_output_latency); 1e3 * nfo_s->inputLatency,
#endif _cur_input_latency));
DEBUG_AUDIO (string_compose ("PA Output Latency %1ms, %2 spl\n",
1e3 * nfo_s->outputLatency,
_cur_output_latency));
_state = 0; _state = 0;
if (_capture_channels > 0) { if (_capture_channels > 0) {
_input_buffer = (float*) malloc (samples_per_period * _capture_channels * sizeof(float)); _input_buffer = (float*) malloc (samples_per_period * _capture_channels * sizeof(float));
if (!_input_buffer) { if (!_input_buffer) {
fprintf(stderr, "PortAudio failed to allocate input buffer.\n"); DEBUG_AUDIO ("PortAudio failed to allocate input buffer.\n");
pcm_stop(); pcm_stop();
goto error; goto error;
} }
@ -506,7 +519,7 @@ PortAudioIO::pcm_setup (
if (_playback_channels > 0) { if (_playback_channels > 0) {
_output_buffer = (float*) calloc (samples_per_period * _playback_channels, sizeof(float)); _output_buffer = (float*) calloc (samples_per_period * _playback_channels, sizeof(float));
if (!_output_buffer) { if (!_output_buffer) {
fprintf(stderr, "PortAudio failed to allocate output buffer.\n"); DEBUG_AUDIO ("PortAudio failed to allocate output buffer.\n");
pcm_stop(); pcm_stop();
goto error; 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 = bld(features = 'cxx cxxshlib')
obj.source = [ 'portaudio_backend.cc', obj.source = [ 'portaudio_backend.cc',
'portaudio_io.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.includes = ['.']
obj.name = 'portaudio_backend' 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 from dynamically loaded code, for use in command line parsing, is way above the pay grade
of this debug tracing scheme. 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::WavesMIDI = PBD::new_debug_bit ("WavesMIDI");
DebugBits PBD::DEBUG::WavesAudio = PBD::new_debug_bit ("WavesAudio"); 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 Configuration;
LIBPBD_API extern DebugBits FileUtils; 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 */ /* See notes in ../debug.cc on why these are defined here */
LIBPBD_API extern DebugBits WavesMIDI; LIBPBD_API extern DebugBits WavesMIDI;