mirror of
https://github.com/Ardour/ardour.git
synced 2025-12-08 07:45:00 +01:00
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
18 changed files with 2074 additions and 72 deletions
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 "ardour/port_manager.h"
|
||||||
#include "i18n.h"
|
#include "i18n.h"
|
||||||
|
|
||||||
|
#include "win_utils.h"
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
using namespace ARDOUR;
|
using namespace ARDOUR;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
const char * const winmme_driver_name = X_("WinMME");
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
static std::string s_instance_name;
|
static std::string s_instance_name;
|
||||||
size_t PortAudioBackend::_max_buffer_size = 8192;
|
size_t PortAudioBackend::_max_buffer_size = 8192;
|
||||||
std::vector<std::string> PortAudioBackend::_midi_options;
|
std::vector<std::string> PortAudioBackend::_midi_options;
|
||||||
|
|
@ -51,7 +60,9 @@ PortAudioBackend::PortAudioBackend (AudioEngine& e, AudioBackendInfo& info)
|
||||||
, _active (false)
|
, _active (false)
|
||||||
, _freewheel (false)
|
, _freewheel (false)
|
||||||
, _measure_latency (false)
|
, _measure_latency (false)
|
||||||
, _last_process_start (0)
|
, m_cycle_count(0)
|
||||||
|
, m_total_deviation_us(0)
|
||||||
|
, m_max_deviation_us(0)
|
||||||
, _input_audio_device("")
|
, _input_audio_device("")
|
||||||
, _output_audio_device("")
|
, _output_audio_device("")
|
||||||
, _midi_driver_option(_("None"))
|
, _midi_driver_option(_("None"))
|
||||||
|
|
@ -69,11 +80,13 @@ PortAudioBackend::PortAudioBackend (AudioEngine& e, AudioBackendInfo& info)
|
||||||
pthread_mutex_init (&_port_callback_mutex, 0);
|
pthread_mutex_init (&_port_callback_mutex, 0);
|
||||||
|
|
||||||
_pcmio = new PortAudioIO ();
|
_pcmio = new PortAudioIO ();
|
||||||
|
_midiio = new WinMMEMidiIO ();
|
||||||
}
|
}
|
||||||
|
|
||||||
PortAudioBackend::~PortAudioBackend ()
|
PortAudioBackend::~PortAudioBackend ()
|
||||||
{
|
{
|
||||||
delete _pcmio; _pcmio = 0;
|
delete _pcmio; _pcmio = 0;
|
||||||
|
delete _midiio; _midiio = 0;
|
||||||
pthread_mutex_destroy (&_port_callback_mutex);
|
pthread_mutex_destroy (&_port_callback_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -343,7 +356,7 @@ std::vector<std::string>
|
||||||
PortAudioBackend::enumerate_midi_options () const
|
PortAudioBackend::enumerate_midi_options () const
|
||||||
{
|
{
|
||||||
if (_midi_options.empty()) {
|
if (_midi_options.empty()) {
|
||||||
//_midi_options.push_back (_("PortMidi"));
|
_midi_options.push_back (winmme_driver_name);
|
||||||
_midi_options.push_back (_("None"));
|
_midi_options.push_back (_("None"));
|
||||||
}
|
}
|
||||||
return _midi_options;
|
return _midi_options;
|
||||||
|
|
@ -352,9 +365,10 @@ PortAudioBackend::enumerate_midi_options () const
|
||||||
int
|
int
|
||||||
PortAudioBackend::set_midi_option (const std::string& opt)
|
PortAudioBackend::set_midi_option (const std::string& opt)
|
||||||
{
|
{
|
||||||
if (opt != _("None") /* && opt != _("PortMidi")*/) {
|
if (opt != _("None") && opt != winmme_driver_name) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
DEBUG_MIDI (string_compose ("Setting midi option to %1\n", opt));
|
||||||
_midi_driver_option = opt;
|
_midi_driver_option = opt;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
@ -401,7 +415,6 @@ PortAudioBackend::_start (bool for_latency_measurement)
|
||||||
_dsp_load = 0;
|
_dsp_load = 0;
|
||||||
_freewheeling = false;
|
_freewheeling = false;
|
||||||
_freewheel = false;
|
_freewheel = false;
|
||||||
_last_process_start = 0;
|
|
||||||
|
|
||||||
_pcmio->pcm_setup (name_to_id(_input_audio_device), name_to_id(_output_audio_device), _samplerate, _samples_per_period);
|
_pcmio->pcm_setup (name_to_id(_input_audio_device), name_to_id(_output_audio_device), _samplerate, _samples_per_period);
|
||||||
|
|
||||||
|
|
@ -441,10 +454,28 @@ PortAudioBackend::_start (bool for_latency_measurement)
|
||||||
_run = true;
|
_run = true;
|
||||||
_port_change_flag = false;
|
_port_change_flag = false;
|
||||||
|
|
||||||
// TODO MIDI
|
if (_midi_driver_option == winmme_driver_name) {
|
||||||
|
_midiio->set_enabled(true);
|
||||||
|
//_midiio->set_port_changed_callback(midi_port_change, this);
|
||||||
|
_midiio->start(); // triggers port discovery, callback coremidi_rediscover()
|
||||||
|
}
|
||||||
|
|
||||||
|
m_cycle_timer.set_samplerate(_samplerate);
|
||||||
|
m_cycle_timer.set_samples_per_cycle(_samples_per_period);
|
||||||
|
|
||||||
|
DEBUG_MIDI ("Registering MIDI ports\n");
|
||||||
|
|
||||||
|
if (register_system_midi_ports () != 0) {
|
||||||
|
PBD::error << _ ("PortAudioBackend: failed to register system midi ports.")
|
||||||
|
<< endmsg;
|
||||||
|
_run = false;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
DEBUG_AUDIO ("Registering Audio ports\n");
|
||||||
|
|
||||||
if (register_system_audio_ports()) {
|
if (register_system_audio_ports()) {
|
||||||
PBD::error << _("PortAudioBackend: failed to register system ports.") << endmsg;
|
PBD::error << _("PortAudioBackend: failed to register system audio ports.") << endmsg;
|
||||||
_run = false;
|
_run = false;
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
@ -557,12 +588,11 @@ PortAudioBackend::samples_since_cycle_start ()
|
||||||
if (!_active || !_run || _freewheeling || _freewheel) {
|
if (!_active || !_run || _freewheeling || _freewheel) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
if (_last_process_start == 0) {
|
if (!m_cycle_timer.valid()) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int64_t elapsed_time_us = g_get_monotonic_time() - _last_process_start;
|
return m_cycle_timer.samples_since_cycle_start (utils::get_microseconds());
|
||||||
return std::max((pframes_t)0, (pframes_t)rint(1e-6 * elapsed_time_us * _samplerate));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int
|
int
|
||||||
|
|
@ -846,6 +876,52 @@ PortAudioBackend::register_system_audio_ports()
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
PortAudioBackend::register_system_midi_ports()
|
||||||
|
{
|
||||||
|
if (_midi_driver_option == _("None")) {
|
||||||
|
DEBUG_MIDI ("No MIDI backend selected, not system midi ports available\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
LatencyRange lr;
|
||||||
|
lr.min = lr.max = _samples_per_period;
|
||||||
|
|
||||||
|
const std::vector<WinMMEMidiInputDevice*> inputs = _midiio->get_inputs();
|
||||||
|
|
||||||
|
for (std::vector<WinMMEMidiInputDevice*>::const_iterator i = inputs.begin ();
|
||||||
|
i != inputs.end ();
|
||||||
|
++i) {
|
||||||
|
std::string port_name = "system_midi:" + (*i)->name() + " capture";
|
||||||
|
PortHandle p =
|
||||||
|
add_port (port_name,
|
||||||
|
DataType::MIDI,
|
||||||
|
static_cast<PortFlags>(IsOutput | IsPhysical | IsTerminal));
|
||||||
|
if (!p) return -1;
|
||||||
|
set_latency_range (p, false, lr);
|
||||||
|
_system_midi_in.push_back (static_cast<PortMidiPort*>(p));
|
||||||
|
DEBUG_MIDI (string_compose ("Registered MIDI input port: %1\n", port_name));
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<WinMMEMidiOutputDevice*> outputs = _midiio->get_outputs();
|
||||||
|
|
||||||
|
for (std::vector<WinMMEMidiOutputDevice*>::const_iterator i = outputs.begin ();
|
||||||
|
i != outputs.end ();
|
||||||
|
++i) {
|
||||||
|
std::string port_name = "system_midi:" + (*i)->name() + " playback";
|
||||||
|
PortHandle p =
|
||||||
|
add_port (port_name,
|
||||||
|
DataType::MIDI,
|
||||||
|
static_cast<PortFlags>(IsInput | IsPhysical | IsTerminal));
|
||||||
|
if (!p) return -1;
|
||||||
|
set_latency_range (p, false, lr);
|
||||||
|
static_cast<PortMidiPort*>(p)->set_n_periods(2);
|
||||||
|
_system_midi_out.push_back (static_cast<PortMidiPort*>(p));
|
||||||
|
DEBUG_MIDI (string_compose ("Registered MIDI output port: %1\n", port_name));
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
PortAudioBackend::unregister_ports (bool system_only)
|
PortAudioBackend::unregister_ports (bool system_only)
|
||||||
{
|
{
|
||||||
|
|
@ -1015,11 +1091,10 @@ PortAudioBackend::midi_event_put (
|
||||||
if (!buffer || !port_buffer) return -1;
|
if (!buffer || !port_buffer) return -1;
|
||||||
PortMidiBuffer& dst = * static_cast<PortMidiBuffer*>(port_buffer);
|
PortMidiBuffer& dst = * static_cast<PortMidiBuffer*>(port_buffer);
|
||||||
if (dst.size () && (pframes_t)dst.back ()->timestamp () > timestamp) {
|
if (dst.size () && (pframes_t)dst.back ()->timestamp () > timestamp) {
|
||||||
#ifndef NDEBUG
|
|
||||||
// nevermind, ::get_buffer() sorts events
|
// nevermind, ::get_buffer() sorts events
|
||||||
fprintf (stderr, "PortMidiBuffer: unordered event: %d > %d\n",
|
DEBUG_MIDI (string_compose ("PortMidiBuffer: unordered event: %1 > %2\n",
|
||||||
(pframes_t)dst.back ()->timestamp (), timestamp);
|
(pframes_t)dst.back ()->timestamp (),
|
||||||
#endif
|
timestamp));
|
||||||
}
|
}
|
||||||
dst.push_back (boost::shared_ptr<PortMidiEvent>(new PortMidiEvent (timestamp, buffer, size)));
|
dst.push_back (boost::shared_ptr<PortMidiEvent>(new PortMidiEvent (timestamp, buffer, size)));
|
||||||
return 0;
|
return 0;
|
||||||
|
|
@ -1200,7 +1275,10 @@ PortAudioBackend::main_process_thread ()
|
||||||
_processed_samples = 0;
|
_processed_samples = 0;
|
||||||
|
|
||||||
uint64_t clock1, clock2;
|
uint64_t clock1, clock2;
|
||||||
|
int64_t min_elapsed_us = 1000000;
|
||||||
|
int64_t max_elapsed_us = 0;
|
||||||
const int64_t nomial_time = 1e6 * _samples_per_period / _samplerate;
|
const int64_t nomial_time = 1e6 * _samples_per_period / _samplerate;
|
||||||
|
// const int64_t nomial_time = m_cycle_timer.get_length_us();
|
||||||
|
|
||||||
manager.registration_callback();
|
manager.registration_callback();
|
||||||
manager.graph_order_callback();
|
manager.graph_order_callback();
|
||||||
|
|
@ -1224,9 +1302,7 @@ PortAudioBackend::main_process_thread ()
|
||||||
case 0: // OK
|
case 0: // OK
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
#ifndef NDEBUG
|
DEBUG_AUDIO ("PortAudio: Xrun\n");
|
||||||
printf("PortAudio: Xrun\n");
|
|
||||||
#endif
|
|
||||||
engine.Xrun ();
|
engine.Xrun ();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
@ -1235,7 +1311,7 @@ PortAudioBackend::main_process_thread ()
|
||||||
}
|
}
|
||||||
|
|
||||||
uint32_t i = 0;
|
uint32_t i = 0;
|
||||||
clock1 = g_get_monotonic_time();
|
clock1 = utils::get_microseconds ();
|
||||||
|
|
||||||
/* get audio */
|
/* get audio */
|
||||||
i = 0;
|
i = 0;
|
||||||
|
|
@ -1248,6 +1324,26 @@ PortAudioBackend::main_process_thread ()
|
||||||
for (std::vector<PamPort*>::const_iterator it = _system_midi_in.begin (); it != _system_midi_in.end (); ++it, ++i) {
|
for (std::vector<PamPort*>::const_iterator it = _system_midi_in.begin (); it != _system_midi_in.end (); ++it, ++i) {
|
||||||
PortMidiBuffer* mbuf = static_cast<PortMidiBuffer*>((*it)->get_buffer(0));
|
PortMidiBuffer* mbuf = static_cast<PortMidiBuffer*>((*it)->get_buffer(0));
|
||||||
mbuf->clear();
|
mbuf->clear();
|
||||||
|
uint64_t timestamp;
|
||||||
|
pframes_t sample_offset;
|
||||||
|
uint8_t data[256];
|
||||||
|
size_t size = sizeof(data);
|
||||||
|
while (_midiio->dequeue_input_event (i,
|
||||||
|
m_cycle_timer.get_start (),
|
||||||
|
m_cycle_timer.get_next_start (),
|
||||||
|
timestamp,
|
||||||
|
data,
|
||||||
|
size)) {
|
||||||
|
sample_offset = m_cycle_timer.samples_since_cycle_start (timestamp);
|
||||||
|
midi_event_put (mbuf, sample_offset, data, size);
|
||||||
|
DEBUG_MIDI (string_compose ("Dequeuing incoming MIDI data for device: %1 "
|
||||||
|
"sample_offset: %2 timestamp: %3, size: %4\n",
|
||||||
|
_midiio->get_inputs ()[i]->name (),
|
||||||
|
sample_offset,
|
||||||
|
timestamp,
|
||||||
|
size));
|
||||||
|
size = sizeof(data);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* clear output buffers */
|
/* clear output buffers */
|
||||||
|
|
@ -1255,25 +1351,61 @@ PortAudioBackend::main_process_thread ()
|
||||||
memset ((*it)->get_buffer (_samples_per_period), 0, _samples_per_period * sizeof (Sample));
|
memset ((*it)->get_buffer (_samples_per_period), 0, _samples_per_period * sizeof (Sample));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_last_cycle_start = m_cycle_timer.get_start ();
|
||||||
|
m_cycle_timer.reset_start(utils::get_microseconds());
|
||||||
|
m_cycle_count++;
|
||||||
|
|
||||||
|
uint64_t cycle_diff_us = (m_cycle_timer.get_start () - m_last_cycle_start);
|
||||||
|
int64_t deviation_us = (cycle_diff_us - m_cycle_timer.get_length_us());
|
||||||
|
m_total_deviation_us += std::abs(deviation_us);
|
||||||
|
m_max_deviation_us =
|
||||||
|
std::max (m_max_deviation_us, (uint64_t)std::abs (deviation_us));
|
||||||
|
|
||||||
|
if ((m_cycle_count % 1000) == 0) {
|
||||||
|
uint64_t mean_deviation_us = m_total_deviation_us / m_cycle_count;
|
||||||
|
DEBUG_TIMING (
|
||||||
|
string_compose ("Mean avg cycle deviation: %1(ms), max %2(ms)\n",
|
||||||
|
mean_deviation_us * 1e-3,
|
||||||
|
m_max_deviation_us * 1e-3));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::abs(deviation_us) > m_cycle_timer.get_length_us()) {
|
||||||
|
DEBUG_TIMING (string_compose (
|
||||||
|
"time between process(ms): %1, Est(ms): %2, Dev(ms): %3\n",
|
||||||
|
cycle_diff_us * 1e-3,
|
||||||
|
m_cycle_timer.get_length_us () * 1e-3,
|
||||||
|
deviation_us * 1e-3));
|
||||||
|
}
|
||||||
|
|
||||||
/* call engine process callback */
|
/* call engine process callback */
|
||||||
_last_process_start = g_get_monotonic_time();
|
|
||||||
if (engine.process_callback (_samples_per_period)) {
|
if (engine.process_callback (_samples_per_period)) {
|
||||||
_pcmio->pcm_stop ();
|
_pcmio->pcm_stop ();
|
||||||
_active = false;
|
_active = false;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
#if 0
|
|
||||||
/* mixdown midi */
|
/* mixdown midi */
|
||||||
for (std::vector<PamPort*>::iterator it = _system_midi_out.begin (); it != _system_midi_out.end (); ++it) {
|
for (std::vector<PamPort*>::iterator it = _system_midi_out.begin (); it != _system_midi_out.end (); ++it) {
|
||||||
static_cast<PortBackendMidiPort*>(*it)->next_period();
|
static_cast<PortMidiPort*>(*it)->next_period();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* queue outgoing midi */
|
/* queue outgoing midi */
|
||||||
i = 0;
|
i = 0;
|
||||||
for (std::vector<PamPort*>::const_iterator it = _system_midi_out.begin (); it != _system_midi_out.end (); ++it, ++i) {
|
for (std::vector<PamPort*>::const_iterator it = _system_midi_out.begin (); it != _system_midi_out.end (); ++it, ++i) {
|
||||||
// TODO
|
const PortMidiBuffer* src = static_cast<const PortMidiPort*>(*it)->const_buffer();
|
||||||
|
|
||||||
|
for (PortMidiBuffer::const_iterator mit = src->begin (); mit != src->end (); ++mit) {
|
||||||
|
uint64_t timestamp =
|
||||||
|
m_cycle_timer.timestamp_from_sample_offset ((*mit)->timestamp ());
|
||||||
|
DEBUG_MIDI (
|
||||||
|
string_compose ("Queuing outgoing MIDI data for device: "
|
||||||
|
"%1 sample_offset: %2 timestamp: %3, size: %4\n",
|
||||||
|
_midiio->get_outputs ()[i]->name (),
|
||||||
|
(*mit)->timestamp (),
|
||||||
|
timestamp,
|
||||||
|
(*mit)->size ()));
|
||||||
|
_midiio->enqueue_output_event (
|
||||||
|
i, timestamp, (*mit)->data (), (*mit)->size ());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
|
|
||||||
/* write back audio */
|
/* write back audio */
|
||||||
i = 0;
|
i = 0;
|
||||||
|
|
@ -1284,10 +1416,19 @@ PortAudioBackend::main_process_thread ()
|
||||||
_processed_samples += _samples_per_period;
|
_processed_samples += _samples_per_period;
|
||||||
|
|
||||||
/* calculate DSP load */
|
/* calculate DSP load */
|
||||||
clock2 = g_get_monotonic_time();
|
clock2 = utils::get_microseconds ();
|
||||||
const int64_t elapsed_time = clock2 - clock1;
|
const int64_t elapsed_time = clock2 - clock1;
|
||||||
_dsp_load = elapsed_time / (float) nomial_time;
|
_dsp_load = elapsed_time / (float) nomial_time;
|
||||||
|
|
||||||
|
max_elapsed_us = std::max (elapsed_time, max_elapsed_us);
|
||||||
|
min_elapsed_us = std::min (elapsed_time, min_elapsed_us);
|
||||||
|
if ((m_cycle_count % 1000) == 0) {
|
||||||
|
DEBUG_TIMING (
|
||||||
|
string_compose ("Elapsed process time(usecs) max: %1, min: %2\n",
|
||||||
|
max_elapsed_us,
|
||||||
|
min_elapsed_us));
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Freewheelin'
|
// Freewheelin'
|
||||||
|
|
||||||
|
|
@ -1296,11 +1437,10 @@ PortAudioBackend::main_process_thread ()
|
||||||
memset ((*it)->get_buffer (_samples_per_period), 0, _samples_per_period * sizeof (Sample));
|
memset ((*it)->get_buffer (_samples_per_period), 0, _samples_per_period * sizeof (Sample));
|
||||||
}
|
}
|
||||||
|
|
||||||
clock1 = g_get_monotonic_time();
|
clock1 = utils::get_microseconds ();
|
||||||
|
|
||||||
// TODO clear midi or stop midi recv when entering fwheelin'
|
// TODO clear midi or stop midi recv when entering fwheelin'
|
||||||
|
|
||||||
_last_process_start = 0;
|
|
||||||
if (engine.process_callback (_samples_per_period)) {
|
if (engine.process_callback (_samples_per_period)) {
|
||||||
_pcmio->pcm_stop ();
|
_pcmio->pcm_stop ();
|
||||||
_active = false;
|
_active = false;
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,8 @@
|
||||||
#include "ardour/types.h"
|
#include "ardour/types.h"
|
||||||
|
|
||||||
#include "portaudio_io.h"
|
#include "portaudio_io.h"
|
||||||
|
#include "winmmemidi_io.h"
|
||||||
|
#include "cycle_timer.h"
|
||||||
|
|
||||||
namespace ARDOUR {
|
namespace ARDOUR {
|
||||||
|
|
||||||
|
|
@ -312,6 +314,7 @@ class PortAudioBackend : public AudioBackend {
|
||||||
private:
|
private:
|
||||||
std::string _instance_name;
|
std::string _instance_name;
|
||||||
PortAudioIO *_pcmio;
|
PortAudioIO *_pcmio;
|
||||||
|
WinMMEMidiIO *_midiio;
|
||||||
|
|
||||||
bool _run; /* keep going or stop, ardour thread */
|
bool _run; /* keep going or stop, ardour thread */
|
||||||
bool _active; /* is running, process thread */
|
bool _active; /* is running, process thread */
|
||||||
|
|
@ -319,7 +322,12 @@ class PortAudioBackend : public AudioBackend {
|
||||||
bool _freewheeling;
|
bool _freewheeling;
|
||||||
bool _measure_latency;
|
bool _measure_latency;
|
||||||
|
|
||||||
uint64_t _last_process_start;
|
uint64_t m_cycle_count;
|
||||||
|
uint64_t m_total_deviation_us;
|
||||||
|
uint64_t m_max_deviation_us;
|
||||||
|
|
||||||
|
CycleTimer m_cycle_timer;
|
||||||
|
uint64_t m_last_cycle_start;
|
||||||
|
|
||||||
static std::vector<std::string> _midi_options;
|
static std::vector<std::string> _midi_options;
|
||||||
static std::vector<AudioBackend::DeviceStatus> _input_audio_device_status;
|
static std::vector<AudioBackend::DeviceStatus> _input_audio_device_status;
|
||||||
|
|
@ -365,6 +373,7 @@ class PortAudioBackend : public AudioBackend {
|
||||||
/* port engine */
|
/* port engine */
|
||||||
PortHandle add_port (const std::string& shortname, ARDOUR::DataType, ARDOUR::PortFlags);
|
PortHandle add_port (const std::string& shortname, ARDOUR::DataType, ARDOUR::PortFlags);
|
||||||
int register_system_audio_ports ();
|
int register_system_audio_ports ();
|
||||||
|
int register_system_midi_ports ();
|
||||||
void unregister_ports (bool system_only = false);
|
void unregister_ports (bool system_only = false);
|
||||||
|
|
||||||
std::vector<PamPort *> _ports;
|
std::vector<PamPort *> _ports;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2015 Robin Gareus <robin@gareus.org>
|
* Copyright (C) 2015 Robin Gareus <robin@gareus.org>
|
||||||
|
* Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.com>
|
||||||
*
|
*
|
||||||
* This program is free software; you can redistribute it and/or modify
|
* This program is free software; you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
|
@ -23,6 +24,10 @@
|
||||||
#include <glibmm.h>
|
#include <glibmm.h>
|
||||||
#include "portaudio_io.h"
|
#include "portaudio_io.h"
|
||||||
|
|
||||||
|
#include "pbd/compose.h"
|
||||||
|
|
||||||
|
#include "debug.h"
|
||||||
|
|
||||||
#define INTERLEAVED_INPUT
|
#define INTERLEAVED_INPUT
|
||||||
#define INTERLEAVED_OUTPUT
|
#define INTERLEAVED_OUTPUT
|
||||||
|
|
||||||
|
|
@ -69,9 +74,9 @@ PortAudioIO::available_sample_rates(int device_id, std::vector<float>& sampleRat
|
||||||
if (device_id == -1) {
|
if (device_id == -1) {
|
||||||
device_id = get_default_input_device ();
|
device_id = get_default_input_device ();
|
||||||
}
|
}
|
||||||
#ifndef NDEBUG
|
|
||||||
printf("PortAudio: Querying Samplerates for device %d\n", device_id);
|
DEBUG_AUDIO (
|
||||||
#endif
|
string_compose ("Querying Samplerates for device %1\n", device_id));
|
||||||
|
|
||||||
sampleRates.clear();
|
sampleRates.clear();
|
||||||
const PaDeviceInfo* nfo = Pa_GetDeviceInfo(device_id);
|
const PaDeviceInfo* nfo = Pa_GetDeviceInfo(device_id);
|
||||||
|
|
@ -181,7 +186,7 @@ PortAudioIO::set_host_api (const std::string& host_api_name)
|
||||||
_host_api_index = get_host_api_index_from_name (host_api_name);
|
_host_api_index = get_host_api_index_from_name (host_api_name);
|
||||||
|
|
||||||
if (_host_api_index < 0) {
|
if (_host_api_index < 0) {
|
||||||
fprintf(stderr, "Error setting host API\n");
|
DEBUG_AUDIO ("Error setting host API\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -192,7 +197,10 @@ PortAudioIO::get_host_api_index_from_name (const std::string& name)
|
||||||
|
|
||||||
PaHostApiIndex count = Pa_GetHostApiCount();
|
PaHostApiIndex count = Pa_GetHostApiCount();
|
||||||
|
|
||||||
if (count < 0) return -1;
|
if (count < 0) {
|
||||||
|
DEBUG_AUDIO ("Host API count < 0\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = 0; i < count; ++i) {
|
for (int i = 0; i < count; ++i) {
|
||||||
const PaHostApiInfo* info = Pa_GetHostApiInfo (i);
|
const PaHostApiInfo* info = Pa_GetHostApiInfo (i);
|
||||||
|
|
@ -200,6 +208,8 @@ PortAudioIO::get_host_api_index_from_name (const std::string& name)
|
||||||
if (name == info->name) return i;
|
if (name == info->name) return i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
DEBUG_AUDIO (string_compose ("Unable to get host API from name: %1\n", name));
|
||||||
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -262,26 +272,28 @@ PortAudioIO::add_devices ()
|
||||||
if (info == NULL) return;
|
if (info == NULL) return;
|
||||||
|
|
||||||
int n_devices = Pa_GetDeviceCount();
|
int n_devices = Pa_GetDeviceCount();
|
||||||
#ifndef NDEBUG
|
|
||||||
printf("PortAudio %d devices found:\n", n_devices);
|
DEBUG_AUDIO (string_compose ("PortAudio found %1 devices\n", n_devices));
|
||||||
#endif
|
|
||||||
|
|
||||||
for (int i = 0 ; i < n_devices; ++i) {
|
for (int i = 0 ; i < n_devices; ++i) {
|
||||||
const PaDeviceInfo* nfo = Pa_GetDeviceInfo(i);
|
const PaDeviceInfo* nfo = Pa_GetDeviceInfo(i);
|
||||||
|
|
||||||
if (!nfo) continue;
|
if (!nfo) continue;
|
||||||
if (nfo->hostApi != _host_api_index) continue;
|
if (nfo->hostApi != _host_api_index) continue;
|
||||||
#ifndef NDEBUG
|
|
||||||
printf(" (%d) '%s' '%s' in: %d (lat: %.1f .. %.1f) out: %d (lat: %.1f .. %.1f) sr:%.2f\n",
|
DEBUG_AUDIO (string_compose (" (%1) '%2' '%3' in: %4 (lat: %5 .. %6) out: %7 "
|
||||||
i, info->name, nfo->name,
|
"(lat: %8 .. %9) sr:%10\n",
|
||||||
|
i,
|
||||||
|
info->name,
|
||||||
|
nfo->name,
|
||||||
nfo->maxInputChannels,
|
nfo->maxInputChannels,
|
||||||
nfo->defaultLowInputLatency * 1e3,
|
nfo->defaultLowInputLatency * 1e3,
|
||||||
nfo->defaultHighInputLatency * 1e3,
|
nfo->defaultHighInputLatency * 1e3,
|
||||||
nfo->maxOutputChannels,
|
nfo->maxOutputChannels,
|
||||||
nfo->defaultLowOutputLatency * 1e3,
|
nfo->defaultLowOutputLatency * 1e3,
|
||||||
nfo->defaultHighOutputLatency * 1e3,
|
nfo->defaultHighOutputLatency * 1e3,
|
||||||
nfo->defaultSampleRate);
|
nfo->defaultSampleRate));
|
||||||
#endif
|
|
||||||
if ( nfo->maxInputChannels == 0 && nfo->maxOutputChannels == 0) {
|
if ( nfo->maxInputChannels == 0 && nfo->maxOutputChannels == 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
@ -364,15 +376,13 @@ PortAudioIO::pcm_setup (
|
||||||
{
|
{
|
||||||
_state = -2;
|
_state = -2;
|
||||||
|
|
||||||
// TODO error reporting sans fprintf()
|
|
||||||
|
|
||||||
PaError err = paNoError;
|
PaError err = paNoError;
|
||||||
const PaDeviceInfo *nfo_in;
|
const PaDeviceInfo *nfo_in;
|
||||||
const PaDeviceInfo *nfo_out;
|
const PaDeviceInfo *nfo_out;
|
||||||
const PaStreamInfo *nfo_s;
|
const PaStreamInfo *nfo_s;
|
||||||
|
|
||||||
if (!initialize_pa()) {
|
if (!initialize_pa()) {
|
||||||
fprintf(stderr, "PortAudio Initialization Failed\n");
|
DEBUG_AUDIO ("PortAudio Initialization Failed\n");
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -389,15 +399,14 @@ PortAudioIO::pcm_setup (
|
||||||
_cur_input_latency = 0;
|
_cur_input_latency = 0;
|
||||||
_cur_output_latency = 0;
|
_cur_output_latency = 0;
|
||||||
|
|
||||||
#ifndef NDEBUG
|
DEBUG_AUDIO (string_compose (
|
||||||
printf("PortAudio Device IDs: i:%d o:%d\n", device_input, device_output);
|
"PortAudio Device IDs: i:%1 o:%2\n", device_input, device_output));
|
||||||
#endif
|
|
||||||
|
|
||||||
nfo_in = Pa_GetDeviceInfo(device_input);
|
nfo_in = Pa_GetDeviceInfo(device_input);
|
||||||
nfo_out = Pa_GetDeviceInfo(device_output);
|
nfo_out = Pa_GetDeviceInfo(device_output);
|
||||||
|
|
||||||
if (!nfo_in && ! nfo_out) {
|
if (!nfo_in && ! nfo_out) {
|
||||||
fprintf(stderr, "PortAudio Cannot Query Device Info\n");
|
DEBUG_AUDIO ("PortAudio Cannot Query Device Info\n");
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -409,29 +418,29 @@ PortAudioIO::pcm_setup (
|
||||||
}
|
}
|
||||||
|
|
||||||
if(_capture_channels == 0 && _playback_channels == 0) {
|
if(_capture_channels == 0 && _playback_channels == 0) {
|
||||||
fprintf(stderr, "PortAudio no Input and no output channels.\n");
|
DEBUG_AUDIO ("PortAudio no input or output channels.\n");
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
// pa_mac_core_blocking.c pa_stable_v19_20140130
|
// pa_mac_core_blocking.c pa_stable_v19_20140130
|
||||||
// BUG: ringbuffer alloc requires power-of-two chn count.
|
// BUG: ringbuffer alloc requires power-of-two chn count.
|
||||||
if ((_capture_channels & (_capture_channels - 1)) != 0) {
|
if ((_capture_channels & (_capture_channels - 1)) != 0) {
|
||||||
printf("Adjusted capture channes to power of two (portaudio rb bug)\n");
|
DEBUG_AUDIO (
|
||||||
|
"Adjusted capture channels to power of two (portaudio rb bug)\n");
|
||||||
_capture_channels = lower_power_of_two (_capture_channels);
|
_capture_channels = lower_power_of_two (_capture_channels);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((_playback_channels & (_playback_channels - 1)) != 0) {
|
if ((_playback_channels & (_playback_channels - 1)) != 0) {
|
||||||
printf("Adjusted capture channes to power of two (portaudio rb bug)\n");
|
DEBUG_AUDIO (
|
||||||
|
"Adjusted capture channels to power of two (portaudio rb bug)\n");
|
||||||
_playback_channels = lower_power_of_two (_playback_channels);
|
_playback_channels = lower_power_of_two (_playback_channels);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifndef NDEBUG
|
DEBUG_AUDIO (string_compose ("PortAudio Channels: in:%1 out:%2\n",
|
||||||
printf("PortAudio Channels: in:%d out:%d\n",
|
_capture_channels,
|
||||||
_capture_channels, _playback_channels);
|
_playback_channels));
|
||||||
#endif
|
|
||||||
|
|
||||||
PaStreamParameters inputParam;
|
PaStreamParameters inputParam;
|
||||||
PaStreamParameters outputParam;
|
PaStreamParameters outputParam;
|
||||||
|
|
@ -471,13 +480,13 @@ PortAudioIO::pcm_setup (
|
||||||
NULL, NULL);
|
NULL, NULL);
|
||||||
|
|
||||||
if (err != paNoError) {
|
if (err != paNoError) {
|
||||||
fprintf(stderr, "PortAudio failed to start stream.\n");
|
DEBUG_AUDIO ("PortAudio failed to start stream.\n");
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
||||||
nfo_s = Pa_GetStreamInfo (_stream);
|
nfo_s = Pa_GetStreamInfo (_stream);
|
||||||
if (!nfo_s) {
|
if (!nfo_s) {
|
||||||
fprintf(stderr, "PortAudio failed to query stream information.\n");
|
DEBUG_AUDIO ("PortAudio failed to query stream information.\n");
|
||||||
pcm_stop();
|
pcm_stop();
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
@ -486,18 +495,22 @@ PortAudioIO::pcm_setup (
|
||||||
_cur_input_latency = nfo_s->inputLatency * _cur_sample_rate;
|
_cur_input_latency = nfo_s->inputLatency * _cur_sample_rate;
|
||||||
_cur_output_latency = nfo_s->outputLatency * _cur_sample_rate;
|
_cur_output_latency = nfo_s->outputLatency * _cur_sample_rate;
|
||||||
|
|
||||||
#ifndef NDEBUG
|
DEBUG_AUDIO (string_compose ("PA Sample Rate %1 SPS\n", _cur_sample_rate));
|
||||||
printf("PA Sample Rate %.1f SPS\n", _cur_sample_rate);
|
|
||||||
printf("PA Input Latency %.1fms %d spl\n", 1e3 * nfo_s->inputLatency, _cur_input_latency);
|
DEBUG_AUDIO (string_compose ("PA Input Latency %1ms, %2 spl\n",
|
||||||
printf("PA Output Latency %.1fms %d spl\n", 1e3 * nfo_s->outputLatency, _cur_output_latency);
|
1e3 * nfo_s->inputLatency,
|
||||||
#endif
|
_cur_input_latency));
|
||||||
|
|
||||||
|
DEBUG_AUDIO (string_compose ("PA Output Latency %1ms, %2 spl\n",
|
||||||
|
1e3 * nfo_s->outputLatency,
|
||||||
|
_cur_output_latency));
|
||||||
|
|
||||||
_state = 0;
|
_state = 0;
|
||||||
|
|
||||||
if (_capture_channels > 0) {
|
if (_capture_channels > 0) {
|
||||||
_input_buffer = (float*) malloc (samples_per_period * _capture_channels * sizeof(float));
|
_input_buffer = (float*) malloc (samples_per_period * _capture_channels * sizeof(float));
|
||||||
if (!_input_buffer) {
|
if (!_input_buffer) {
|
||||||
fprintf(stderr, "PortAudio failed to allocate input buffer.\n");
|
DEBUG_AUDIO ("PortAudio failed to allocate input buffer.\n");
|
||||||
pcm_stop();
|
pcm_stop();
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
@ -506,7 +519,7 @@ PortAudioIO::pcm_setup (
|
||||||
if (_playback_channels > 0) {
|
if (_playback_channels > 0) {
|
||||||
_output_buffer = (float*) calloc (samples_per_period * _playback_channels, sizeof(float));
|
_output_buffer = (float*) calloc (samples_per_period * _playback_channels, sizeof(float));
|
||||||
if (!_output_buffer) {
|
if (!_output_buffer) {
|
||||||
fprintf(stderr, "PortAudio failed to allocate output buffer.\n");
|
DEBUG_AUDIO ("PortAudio failed to allocate output buffer.\n");
|
||||||
pcm_stop();
|
pcm_stop();
|
||||||
goto error;
|
goto error;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
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 = bld(features = 'cxx cxxshlib')
|
||||||
obj.source = [ 'portaudio_backend.cc',
|
obj.source = [ 'portaudio_backend.cc',
|
||||||
'portaudio_io.cc',
|
'portaudio_io.cc',
|
||||||
# 'portmidi_io.cc'
|
'winmmemidi_io.cc',
|
||||||
|
'winmmemidi_input_device.cc',
|
||||||
|
'winmmemidi_output_device.cc',
|
||||||
|
'win_utils.cc',
|
||||||
|
'midi_util.cc'
|
||||||
]
|
]
|
||||||
obj.includes = ['.']
|
obj.includes = ['.']
|
||||||
obj.name = 'portaudio_backend'
|
obj.name = 'portaudio_backend'
|
||||||
|
|
|
||||||
|
|
@ -63,6 +63,11 @@ DebugBits PBD::DEBUG::Configuration = PBD::new_debug_bit ("configuration");
|
||||||
from dynamically loaded code, for use in command line parsing, is way above the pay grade
|
from dynamically loaded code, for use in command line parsing, is way above the pay grade
|
||||||
of this debug tracing scheme.
|
of this debug tracing scheme.
|
||||||
*/
|
*/
|
||||||
|
DebugBits PBD::DEBUG::BackendMIDI = PBD::new_debug_bit ("BackendMIDI");
|
||||||
|
DebugBits PBD::DEBUG::BackendAudio = PBD::new_debug_bit ("BackendAudio");
|
||||||
|
DebugBits PBD::DEBUG::BackendTiming = PBD::new_debug_bit ("BackendTiming");
|
||||||
|
DebugBits PBD::DEBUG::BackendThreads = PBD::new_debug_bit ("BackendThreads");
|
||||||
|
|
||||||
|
|
||||||
DebugBits PBD::DEBUG::WavesMIDI = PBD::new_debug_bit ("WavesMIDI");
|
DebugBits PBD::DEBUG::WavesMIDI = PBD::new_debug_bit ("WavesMIDI");
|
||||||
DebugBits PBD::DEBUG::WavesAudio = PBD::new_debug_bit ("WavesAudio");
|
DebugBits PBD::DEBUG::WavesAudio = PBD::new_debug_bit ("WavesAudio");
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,11 @@ namespace PBD {
|
||||||
LIBPBD_API extern DebugBits Configuration;
|
LIBPBD_API extern DebugBits Configuration;
|
||||||
LIBPBD_API extern DebugBits FileUtils;
|
LIBPBD_API extern DebugBits FileUtils;
|
||||||
|
|
||||||
|
LIBPBD_API extern DebugBits BackendMIDI;
|
||||||
|
LIBPBD_API extern DebugBits BackendAudio;
|
||||||
|
LIBPBD_API extern DebugBits BackendTiming;
|
||||||
|
LIBPBD_API extern DebugBits BackendThreads;
|
||||||
|
|
||||||
/* See notes in ../debug.cc on why these are defined here */
|
/* See notes in ../debug.cc on why these are defined here */
|
||||||
|
|
||||||
LIBPBD_API extern DebugBits WavesMIDI;
|
LIBPBD_API extern DebugBits WavesMIDI;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue