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:
parent
b12f865a4a
commit
e258c827e2
103
libs/backends/portaudio/cycle_timer.h
Normal file
103
libs/backends/portaudio/cycle_timer.h
Normal 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
|
13
libs/backends/portaudio/debug.h
Normal file
13
libs/backends/portaudio/debug.h
Normal 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
|
53
libs/backends/portaudio/midi_util.cc
Normal file
53
libs/backends/portaudio/midi_util.cc
Normal 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;
|
||||
}
|
38
libs/backends/portaudio/midi_util.h
Normal file
38
libs/backends/portaudio/midi_util.h
Normal 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
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
99
libs/backends/portaudio/win_utils.cc
Normal file
99
libs/backends/portaudio/win_utils.cc
Normal 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 (¤t_val);
|
||||
|
||||
return (uint64_t)(((double)current_val.QuadPart) /
|
||||
((double)frequency.QuadPart) * 1000000.0);
|
||||
}
|
||||
|
||||
} // namespace utils
|
34
libs/backends/portaudio/win_utils.h
Normal file
34
libs/backends/portaudio/win_utils.h
Normal 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
|
366
libs/backends/portaudio/winmmemidi_input_device.cc
Normal file
366
libs/backends/portaudio/winmmemidi_input_device.cc
Normal 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
|
105
libs/backends/portaudio/winmmemidi_input_device.h
Normal file
105
libs/backends/portaudio/winmmemidi_input_device.h
Normal 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
|
294
libs/backends/portaudio/winmmemidi_io.cc
Normal file
294
libs/backends/portaudio/winmmemidi_io.cc
Normal 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 ×tamp,
|
||||
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);
|
||||
}
|
124
libs/backends/portaudio/winmmemidi_io.h
Normal file
124
libs/backends/portaudio/winmmemidi_io.h
Normal 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
|
||||
|
494
libs/backends/portaudio/winmmemidi_output_device.cc
Normal file
494
libs/backends/portaudio/winmmemidi_output_device.cc
Normal 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
|
103
libs/backends/portaudio/winmmemidi_output_device.h
Normal file
103
libs/backends/portaudio/winmmemidi_output_device.h
Normal 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
|
|
@ -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'
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue
Block a user