summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Mayberry <mojofunk@gmail.com>2015-05-17 20:55:04 +1000
committerTim Mayberry <mojofunk@gmail.com>2015-07-31 09:59:54 +1000
commite258c827e243d029f824f98d9ee4de9fbaf3f207 (patch)
tree95d4cbed3eaf6e63885e11d56b8982ecdbb39962
parentb12f865a4ac161c2d9e08379a83842342975090c (diff)
WinMME based midi input/output for portaudio backend
TODO: Use MMCSS to elevate thread priorities Enable/test and fix SYSEX related code
-rw-r--r--libs/backends/portaudio/cycle_timer.h103
-rw-r--r--libs/backends/portaudio/debug.h13
-rw-r--r--libs/backends/portaudio/midi_util.cc53
-rw-r--r--libs/backends/portaudio/midi_util.h38
-rw-r--r--libs/backends/portaudio/portaudio_backend.cc192
-rw-r--r--libs/backends/portaudio/portaudio_backend.h11
-rw-r--r--libs/backends/portaudio/portaudio_io.cc101
-rw-r--r--libs/backends/portaudio/win_utils.cc99
-rw-r--r--libs/backends/portaudio/win_utils.h34
-rw-r--r--libs/backends/portaudio/winmmemidi_input_device.cc366
-rw-r--r--libs/backends/portaudio/winmmemidi_input_device.h105
-rw-r--r--libs/backends/portaudio/winmmemidi_io.cc294
-rw-r--r--libs/backends/portaudio/winmmemidi_io.h124
-rw-r--r--libs/backends/portaudio/winmmemidi_output_device.cc494
-rw-r--r--libs/backends/portaudio/winmmemidi_output_device.h103
-rw-r--r--libs/backends/portaudio/wscript6
-rw-r--r--libs/pbd/debug.cc5
-rw-r--r--libs/pbd/pbd/debug.h5
18 files changed, 2074 insertions, 72 deletions
diff --git a/libs/backends/portaudio/cycle_timer.h b/libs/backends/portaudio/cycle_timer.h
new file mode 100644
index 0000000000..95811ae1ca
--- /dev/null
+++ b/libs/backends/portaudio/cycle_timer.h
@@ -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
diff --git a/libs/backends/portaudio/debug.h b/libs/backends/portaudio/debug.h
new file mode 100644
index 0000000000..0270efa2a6
--- /dev/null
+++ b/libs/backends/portaudio/debug.h
@@ -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
diff --git a/libs/backends/portaudio/midi_util.cc b/libs/backends/portaudio/midi_util.cc
new file mode 100644
index 0000000000..0542dfc5b2
--- /dev/null
+++ b/libs/backends/portaudio/midi_util.cc
@@ -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;
+}
diff --git a/libs/backends/portaudio/midi_util.h b/libs/backends/portaudio/midi_util.h
new file mode 100644
index 0000000000..7b6366a82e
--- /dev/null
+++ b/libs/backends/portaudio/midi_util.h
@@ -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
diff --git a/libs/backends/portaudio/portaudio_backend.cc b/libs/backends/portaudio/portaudio_backend.cc
index 71986cd376..a1fa2c96d1 100644
--- a/libs/backends/portaudio/portaudio_backend.cc
+++ b/libs/backends/portaudio/portaudio_backend.cc
@@ -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;
diff --git a/libs/backends/portaudio/portaudio_backend.h b/libs/backends/portaudio/portaudio_backend.h
index d3def25fb1..9831d035d2 100644
--- a/libs/backends/portaudio/portaudio_backend.h
+++ b/libs/backends/portaudio/portaudio_backend.h
@@ -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;
diff --git a/libs/backends/portaudio/portaudio_io.cc b/libs/backends/portaudio/portaudio_io.cc
index b7ea02ff22..4f967b370c 100644
--- a/libs/backends/portaudio/portaudio_io.cc
+++ b/libs/backends/portaudio/portaudio_io.cc
@@ -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;
}
diff --git a/libs/backends/portaudio/win_utils.cc b/libs/backends/portaudio/win_utils.cc
new file mode 100644
index 0000000000..7bda04ccf1
--- /dev/null
+++ b/libs/backends/portaudio/win_utils.cc
@@ -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
diff --git a/libs/backends/portaudio/win_utils.h b/libs/backends/portaudio/win_utils.h
new file mode 100644
index 0000000000..50576b9c5d
--- /dev/null
+++ b/libs/backends/portaudio/win_utils.h
@@ -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
diff --git a/libs/backends/portaudio/winmmemidi_input_device.cc b/libs/backends/portaudio/winmmemidi_input_device.cc
new file mode 100644
index 0000000000..1067b46743
--- /dev/null
+++ b/libs/backends/portaudio/winmmemidi_input_device.cc
@@ -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
diff --git a/libs/backends/portaudio/winmmemidi_input_device.h b/libs/backends/portaudio/winmmemidi_input_device.h
new file mode 100644
index 0000000000..dab8dbfd53
--- /dev/null
+++ b/libs/backends/portaudio/winmmemidi_input_device.h
@@ -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
diff --git a/libs/backends/portaudio/winmmemidi_io.cc b/libs/backends/portaudio/winmmemidi_io.cc
new file mode 100644
index 0000000000..710456da0b
--- /dev/null
+++ b/libs/backends/portaudio/winmmemidi_io.cc
@@ -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);
+}
diff --git a/libs/backends/portaudio/winmmemidi_io.h b/libs/backends/portaudio/winmmemidi_io.h
new file mode 100644
index 0000000000..a2868241d8
--- /dev/null
+++ b/libs/backends/portaudio/winmmemidi_io.h
@@ -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
+
diff --git a/libs/backends/portaudio/winmmemidi_output_device.cc b/libs/backends/portaudio/winmmemidi_output_device.cc
new file mode 100644
index 0000000000..6b31488b00
--- /dev/null
+++ b/libs/backends/portaudio/winmmemidi_output_device.cc
@@ -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
diff --git a/libs/backends/portaudio/winmmemidi_output_device.h b/libs/backends/portaudio/winmmemidi_output_device.h
new file mode 100644
index 0000000000..ead85d7169
--- /dev/null
+++ b/libs/backends/portaudio/winmmemidi_output_device.h
@@ -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
diff --git a/libs/backends/portaudio/wscript b/libs/backends/portaudio/wscript
index 4b77f9670b..8bcfa5fa46 100644
--- a/libs/backends/portaudio/wscript
+++ b/libs/backends/portaudio/wscript
@@ -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'
diff --git a/libs/pbd/debug.cc b/libs/pbd/debug.cc
index 9150cd6883..b42e7f047e 100644
--- a/libs/pbd/debug.cc
+++ b/libs/pbd/debug.cc
@@ -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");
diff --git a/libs/pbd/pbd/debug.h b/libs/pbd/pbd/debug.h
index 8beed56d41..cc3fbdcf55 100644
--- a/libs/pbd/pbd/debug.h
+++ b/libs/pbd/pbd/debug.h
@@ -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;