diff options
author | Tim Mayberry <mojofunk@gmail.com> | 2015-05-17 20:55:04 +1000 |
---|---|---|
committer | Tim Mayberry <mojofunk@gmail.com> | 2015-07-31 09:59:54 +1000 |
commit | e258c827e243d029f824f98d9ee4de9fbaf3f207 (patch) | |
tree | 95d4cbed3eaf6e63885e11d56b8982ecdbb39962 /libs/backends | |
parent | b12f865a4ac161c2d9e08379a83842342975090c (diff) |
WinMME based midi input/output for portaudio backend
TODO:
Use MMCSS to elevate thread priorities
Enable/test and fix SYSEX related code
Diffstat (limited to 'libs/backends')
-rw-r--r-- | libs/backends/portaudio/cycle_timer.h | 103 | ||||
-rw-r--r-- | libs/backends/portaudio/debug.h | 13 | ||||
-rw-r--r-- | libs/backends/portaudio/midi_util.cc | 53 | ||||
-rw-r--r-- | libs/backends/portaudio/midi_util.h | 38 | ||||
-rw-r--r-- | libs/backends/portaudio/portaudio_backend.cc | 192 | ||||
-rw-r--r-- | libs/backends/portaudio/portaudio_backend.h | 11 | ||||
-rw-r--r-- | libs/backends/portaudio/portaudio_io.cc | 101 | ||||
-rw-r--r-- | libs/backends/portaudio/win_utils.cc | 99 | ||||
-rw-r--r-- | libs/backends/portaudio/win_utils.h | 34 | ||||
-rw-r--r-- | libs/backends/portaudio/winmmemidi_input_device.cc | 366 | ||||
-rw-r--r-- | libs/backends/portaudio/winmmemidi_input_device.h | 105 | ||||
-rw-r--r-- | libs/backends/portaudio/winmmemidi_io.cc | 294 | ||||
-rw-r--r-- | libs/backends/portaudio/winmmemidi_io.h | 124 | ||||
-rw-r--r-- | libs/backends/portaudio/winmmemidi_output_device.cc | 494 | ||||
-rw-r--r-- | libs/backends/portaudio/winmmemidi_output_device.h | 103 | ||||
-rw-r--r-- | libs/backends/portaudio/wscript | 6 |
16 files changed, 2064 insertions, 72 deletions
diff --git a/libs/backends/portaudio/cycle_timer.h b/libs/backends/portaudio/cycle_timer.h new file mode 100644 index 0000000000..95811ae1ca --- /dev/null +++ b/libs/backends/portaudio/cycle_timer.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef CYCLE_TIMER_H +#define CYCLE_TIMER_H + +#include <stdint.h> +#include <cmath> +#include <algorithm> + +// Could call it FrameTimer and make it more generic +// Could be an interface and or include clock source +// include sample count/processed frames in iteration? +class CycleTimer { +public: + CycleTimer () + : m_cycle_start (0) + , m_samplerate (0) + , m_samples_per_cycle (0) + { + } + + void set_samplerate (double samplerate) { m_samplerate = samplerate; } + + double get_samplerate () const { return m_samplerate; } + + double get_sample_length_us () const { return 1e6 / m_samplerate; } + + double get_length_us () const + { + return get_sample_length_us () * m_samples_per_cycle; + } + + void set_samples_per_cycle (uint32_t samples) + { + m_samples_per_cycle = samples; + } + + uint32_t get_samples_per_cycle () const { return m_samples_per_cycle; } + + // rint?? that may round to sample outside of cycle? + // max(0, value)? + uint32_t samples_since_cycle_start (uint64_t timer_val) const + { + if (timer_val < m_cycle_start) { + return 0; + } + return std::max((double)0, (timer_val - get_start ()) / get_sample_length_us ()); + } + + uint64_t timestamp_from_sample_offset (uint32_t sample_offset) + { + return m_cycle_start + get_sample_length_us () * sample_offset; + } + + bool valid () const { return m_samples_per_cycle && m_samplerate; } + + bool in_cycle (uint64_t timer_value_us) const + { return (timer_value_us >= get_start()) && (timer_value_us < get_next_start()); + } + + void reset_start (uint64_t timestamp) { m_cycle_start = timestamp; } + + uint64_t get_start () const { return m_cycle_start; } + + uint64_t microseconds_since_start (uint64_t timestamp) const + { + return timestamp - m_cycle_start; + } + + uint64_t microseconds_since_start (uint32_t samples) const + { + return m_cycle_start + samples * get_sample_length_us (); + } + + uint64_t get_next_start () const + { + return m_cycle_start + rint (get_length_us ()); + } + +private: + uint64_t m_cycle_start; + + uint32_t m_samplerate; + uint32_t m_samples_per_cycle; +}; + +#endif // CYCLE_TIMER_H diff --git a/libs/backends/portaudio/debug.h b/libs/backends/portaudio/debug.h new file mode 100644 index 0000000000..0270efa2a6 --- /dev/null +++ b/libs/backends/portaudio/debug.h @@ -0,0 +1,13 @@ +#ifndef PORTAUDIO_BACKEND_DEBUG_H +#define PORTAUDIO_BACKEND_DEBUG_H + +#include "pbd/debug.h" + +using namespace PBD; + +#define DEBUG_AUDIO(msg) DEBUG_TRACE (DEBUG::BackendAudio, msg); +#define DEBUG_MIDI(msg) DEBUG_TRACE (DEBUG::BackendMIDI, msg); +#define DEBUG_TIMING(msg) DEBUG_TRACE (DEBUG::BackendTiming, msg); +#define DEBUG_THREADS(msg) DEBUG_TRACE (DEBUG::BackendThreads, msg); + +#endif // PORTAUDIO_BACKEND_DEBUG_H diff --git a/libs/backends/portaudio/midi_util.cc b/libs/backends/portaudio/midi_util.cc new file mode 100644 index 0000000000..0542dfc5b2 --- /dev/null +++ b/libs/backends/portaudio/midi_util.cc @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "midi_util.h" + +int get_midi_msg_length (uint8_t status_byte) +{ + // define these with meaningful names + switch (status_byte & 0xf0) { + case 0x80: + case 0x90: + case 0xa0: + case 0xb0: + case 0xe0: + return 3; + case 0xc0: + case 0xd0: + return 2; + case 0xf0: + switch (status_byte) { + case 0xf0: + return 0; + case 0xf1: + case 0xf3: + return 2; + case 0xf2: + return 3; + case 0xf4: + case 0xf5: + case 0xf7: + case 0xfd: + break; + default: + return 1; + } + } + return -1; +} diff --git a/libs/backends/portaudio/midi_util.h b/libs/backends/portaudio/midi_util.h new file mode 100644 index 0000000000..7b6366a82e --- /dev/null +++ b/libs/backends/portaudio/midi_util.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef MIDI_UTIL_H +#define MIDI_UTIL_H + +#include <stdint.h> + +struct MidiEventHeader { + uint64_t time; + size_t size; + MidiEventHeader (const uint64_t t, const size_t s) + : time (t) + , size (s) + { + } +}; + +// rename to get_midi_message_size? +// @return -1 to indicate error +int get_midi_msg_length (uint8_t status_byte); + +#endif diff --git a/libs/backends/portaudio/portaudio_backend.cc b/libs/backends/portaudio/portaudio_backend.cc index 71986cd376..a1fa2c96d1 100644 --- a/libs/backends/portaudio/portaudio_backend.cc +++ b/libs/backends/portaudio/portaudio_backend.cc @@ -36,8 +36,17 @@ #include "ardour/port_manager.h" #include "i18n.h" +#include "win_utils.h" +#include "debug.h" + using namespace ARDOUR; +namespace { + +const char * const winmme_driver_name = X_("WinMME"); + +} + static std::string s_instance_name; size_t PortAudioBackend::_max_buffer_size = 8192; std::vector<std::string> PortAudioBackend::_midi_options; @@ -51,7 +60,9 @@ PortAudioBackend::PortAudioBackend (AudioEngine& e, AudioBackendInfo& info) , _active (false) , _freewheel (false) , _measure_latency (false) - , _last_process_start (0) + , m_cycle_count(0) + , m_total_deviation_us(0) + , m_max_deviation_us(0) , _input_audio_device("") , _output_audio_device("") , _midi_driver_option(_("None")) @@ -69,11 +80,13 @@ PortAudioBackend::PortAudioBackend (AudioEngine& e, AudioBackendInfo& info) pthread_mutex_init (&_port_callback_mutex, 0); _pcmio = new PortAudioIO (); + _midiio = new WinMMEMidiIO (); } PortAudioBackend::~PortAudioBackend () { delete _pcmio; _pcmio = 0; + delete _midiio; _midiio = 0; pthread_mutex_destroy (&_port_callback_mutex); } @@ -343,7 +356,7 @@ std::vector<std::string> PortAudioBackend::enumerate_midi_options () const { if (_midi_options.empty()) { - //_midi_options.push_back (_("PortMidi")); + _midi_options.push_back (winmme_driver_name); _midi_options.push_back (_("None")); } return _midi_options; @@ -352,9 +365,10 @@ PortAudioBackend::enumerate_midi_options () const int PortAudioBackend::set_midi_option (const std::string& opt) { - if (opt != _("None") /* && opt != _("PortMidi")*/) { + if (opt != _("None") && opt != winmme_driver_name) { return -1; } + DEBUG_MIDI (string_compose ("Setting midi option to %1\n", opt)); _midi_driver_option = opt; return 0; } @@ -401,7 +415,6 @@ PortAudioBackend::_start (bool for_latency_measurement) _dsp_load = 0; _freewheeling = false; _freewheel = false; - _last_process_start = 0; _pcmio->pcm_setup (name_to_id(_input_audio_device), name_to_id(_output_audio_device), _samplerate, _samples_per_period); @@ -441,10 +454,28 @@ PortAudioBackend::_start (bool for_latency_measurement) _run = true; _port_change_flag = false; - // TODO MIDI + if (_midi_driver_option == winmme_driver_name) { + _midiio->set_enabled(true); + //_midiio->set_port_changed_callback(midi_port_change, this); + _midiio->start(); // triggers port discovery, callback coremidi_rediscover() + } + + m_cycle_timer.set_samplerate(_samplerate); + m_cycle_timer.set_samples_per_cycle(_samples_per_period); + + DEBUG_MIDI ("Registering MIDI ports\n"); + + if (register_system_midi_ports () != 0) { + PBD::error << _ ("PortAudioBackend: failed to register system midi ports.") + << endmsg; + _run = false; + return -1; + } + + DEBUG_AUDIO ("Registering Audio ports\n"); if (register_system_audio_ports()) { - PBD::error << _("PortAudioBackend: failed to register system ports.") << endmsg; + PBD::error << _("PortAudioBackend: failed to register system audio ports.") << endmsg; _run = false; return -1; } @@ -557,12 +588,11 @@ PortAudioBackend::samples_since_cycle_start () if (!_active || !_run || _freewheeling || _freewheel) { return 0; } - if (_last_process_start == 0) { + if (!m_cycle_timer.valid()) { return 0; } - const int64_t elapsed_time_us = g_get_monotonic_time() - _last_process_start; - return std::max((pframes_t)0, (pframes_t)rint(1e-6 * elapsed_time_us * _samplerate)); + return m_cycle_timer.samples_since_cycle_start (utils::get_microseconds()); } int @@ -846,6 +876,52 @@ PortAudioBackend::register_system_audio_ports() return 0; } +int +PortAudioBackend::register_system_midi_ports() +{ + if (_midi_driver_option == _("None")) { + DEBUG_MIDI ("No MIDI backend selected, not system midi ports available\n"); + return 0; + } + + LatencyRange lr; + lr.min = lr.max = _samples_per_period; + + const std::vector<WinMMEMidiInputDevice*> inputs = _midiio->get_inputs(); + + for (std::vector<WinMMEMidiInputDevice*>::const_iterator i = inputs.begin (); + i != inputs.end (); + ++i) { + std::string port_name = "system_midi:" + (*i)->name() + " capture"; + PortHandle p = + add_port (port_name, + DataType::MIDI, + static_cast<PortFlags>(IsOutput | IsPhysical | IsTerminal)); + if (!p) return -1; + set_latency_range (p, false, lr); + _system_midi_in.push_back (static_cast<PortMidiPort*>(p)); + DEBUG_MIDI (string_compose ("Registered MIDI input port: %1\n", port_name)); + } + + const std::vector<WinMMEMidiOutputDevice*> outputs = _midiio->get_outputs(); + + for (std::vector<WinMMEMidiOutputDevice*>::const_iterator i = outputs.begin (); + i != outputs.end (); + ++i) { + std::string port_name = "system_midi:" + (*i)->name() + " playback"; + PortHandle p = + add_port (port_name, + DataType::MIDI, + static_cast<PortFlags>(IsInput | IsPhysical | IsTerminal)); + if (!p) return -1; + set_latency_range (p, false, lr); + static_cast<PortMidiPort*>(p)->set_n_periods(2); + _system_midi_out.push_back (static_cast<PortMidiPort*>(p)); + DEBUG_MIDI (string_compose ("Registered MIDI output port: %1\n", port_name)); + } + return 0; +} + void PortAudioBackend::unregister_ports (bool system_only) { @@ -1015,11 +1091,10 @@ PortAudioBackend::midi_event_put ( if (!buffer || !port_buffer) return -1; PortMidiBuffer& dst = * static_cast<PortMidiBuffer*>(port_buffer); if (dst.size () && (pframes_t)dst.back ()->timestamp () > timestamp) { -#ifndef NDEBUG // nevermind, ::get_buffer() sorts events - fprintf (stderr, "PortMidiBuffer: unordered event: %d > %d\n", - (pframes_t)dst.back ()->timestamp (), timestamp); -#endif + DEBUG_MIDI (string_compose ("PortMidiBuffer: unordered event: %1 > %2\n", + (pframes_t)dst.back ()->timestamp (), + timestamp)); } dst.push_back (boost::shared_ptr<PortMidiEvent>(new PortMidiEvent (timestamp, buffer, size))); return 0; @@ -1200,7 +1275,10 @@ PortAudioBackend::main_process_thread () _processed_samples = 0; uint64_t clock1, clock2; + int64_t min_elapsed_us = 1000000; + int64_t max_elapsed_us = 0; const int64_t nomial_time = 1e6 * _samples_per_period / _samplerate; + // const int64_t nomial_time = m_cycle_timer.get_length_us(); manager.registration_callback(); manager.graph_order_callback(); @@ -1224,9 +1302,7 @@ PortAudioBackend::main_process_thread () case 0: // OK break; case 1: -#ifndef NDEBUG - printf("PortAudio: Xrun\n"); -#endif + DEBUG_AUDIO ("PortAudio: Xrun\n"); engine.Xrun (); break; default: @@ -1235,7 +1311,7 @@ PortAudioBackend::main_process_thread () } uint32_t i = 0; - clock1 = g_get_monotonic_time(); + clock1 = utils::get_microseconds (); /* get audio */ i = 0; @@ -1248,6 +1324,26 @@ PortAudioBackend::main_process_thread () for (std::vector<PamPort*>::const_iterator it = _system_midi_in.begin (); it != _system_midi_in.end (); ++it, ++i) { PortMidiBuffer* mbuf = static_cast<PortMidiBuffer*>((*it)->get_buffer(0)); mbuf->clear(); + uint64_t timestamp; + pframes_t sample_offset; + uint8_t data[256]; + size_t size = sizeof(data); + while (_midiio->dequeue_input_event (i, + m_cycle_timer.get_start (), + m_cycle_timer.get_next_start (), + timestamp, + data, + size)) { + sample_offset = m_cycle_timer.samples_since_cycle_start (timestamp); + midi_event_put (mbuf, sample_offset, data, size); + DEBUG_MIDI (string_compose ("Dequeuing incoming MIDI data for device: %1 " + "sample_offset: %2 timestamp: %3, size: %4\n", + _midiio->get_inputs ()[i]->name (), + sample_offset, + timestamp, + size)); + size = sizeof(data); + } } /* clear output buffers */ @@ -1255,25 +1351,61 @@ PortAudioBackend::main_process_thread () memset ((*it)->get_buffer (_samples_per_period), 0, _samples_per_period * sizeof (Sample)); } + m_last_cycle_start = m_cycle_timer.get_start (); + m_cycle_timer.reset_start(utils::get_microseconds()); + m_cycle_count++; + + uint64_t cycle_diff_us = (m_cycle_timer.get_start () - m_last_cycle_start); + int64_t deviation_us = (cycle_diff_us - m_cycle_timer.get_length_us()); + m_total_deviation_us += std::abs(deviation_us); + m_max_deviation_us = + std::max (m_max_deviation_us, (uint64_t)std::abs (deviation_us)); + + if ((m_cycle_count % 1000) == 0) { + uint64_t mean_deviation_us = m_total_deviation_us / m_cycle_count; + DEBUG_TIMING ( + string_compose ("Mean avg cycle deviation: %1(ms), max %2(ms)\n", + mean_deviation_us * 1e-3, + m_max_deviation_us * 1e-3)); + } + + if (std::abs(deviation_us) > m_cycle_timer.get_length_us()) { + DEBUG_TIMING (string_compose ( + "time between process(ms): %1, Est(ms): %2, Dev(ms): %3\n", + cycle_diff_us * 1e-3, + m_cycle_timer.get_length_us () * 1e-3, + deviation_us * 1e-3)); + } + /* call engine process callback */ - _last_process_start = g_get_monotonic_time(); if (engine.process_callback (_samples_per_period)) { _pcmio->pcm_stop (); _active = false; return 0; } -#if 0 /* mixdown midi */ for (std::vector<PamPort*>::iterator it = _system_midi_out.begin (); it != _system_midi_out.end (); ++it) { - static_cast<PortBackendMidiPort*>(*it)->next_period(); + static_cast<PortMidiPort*>(*it)->next_period(); } - /* queue outgoing midi */ i = 0; for (std::vector<PamPort*>::const_iterator it = _system_midi_out.begin (); it != _system_midi_out.end (); ++it, ++i) { - // TODO + const PortMidiBuffer* src = static_cast<const PortMidiPort*>(*it)->const_buffer(); + + for (PortMidiBuffer::const_iterator mit = src->begin (); mit != src->end (); ++mit) { + uint64_t timestamp = + m_cycle_timer.timestamp_from_sample_offset ((*mit)->timestamp ()); + DEBUG_MIDI ( + string_compose ("Queuing outgoing MIDI data for device: " + "%1 sample_offset: %2 timestamp: %3, size: %4\n", + _midiio->get_outputs ()[i]->name (), + (*mit)->timestamp (), + timestamp, + (*mit)->size ())); + _midiio->enqueue_output_event ( + i, timestamp, (*mit)->data (), (*mit)->size ()); + } } -#endif /* write back audio */ i = 0; @@ -1284,10 +1416,19 @@ PortAudioBackend::main_process_thread () _processed_samples += _samples_per_period; /* calculate DSP load */ - clock2 = g_get_monotonic_time(); + clock2 = utils::get_microseconds (); const int64_t elapsed_time = clock2 - clock1; _dsp_load = elapsed_time / (float) nomial_time; + max_elapsed_us = std::max (elapsed_time, max_elapsed_us); + min_elapsed_us = std::min (elapsed_time, min_elapsed_us); + if ((m_cycle_count % 1000) == 0) { + DEBUG_TIMING ( + string_compose ("Elapsed process time(usecs) max: %1, min: %2\n", + max_elapsed_us, + min_elapsed_us)); + } + } else { // Freewheelin' @@ -1296,11 +1437,10 @@ PortAudioBackend::main_process_thread () memset ((*it)->get_buffer (_samples_per_period), 0, _samples_per_period * sizeof (Sample)); } - clock1 = g_get_monotonic_time(); + clock1 = utils::get_microseconds (); // TODO clear midi or stop midi recv when entering fwheelin' - _last_process_start = 0; if (engine.process_callback (_samples_per_period)) { _pcmio->pcm_stop (); _active = false; diff --git a/libs/backends/portaudio/portaudio_backend.h b/libs/backends/portaudio/portaudio_backend.h index d3def25fb1..9831d035d2 100644 --- a/libs/backends/portaudio/portaudio_backend.h +++ b/libs/backends/portaudio/portaudio_backend.h @@ -33,6 +33,8 @@ #include "ardour/types.h" #include "portaudio_io.h" +#include "winmmemidi_io.h" +#include "cycle_timer.h" namespace ARDOUR { @@ -312,6 +314,7 @@ class PortAudioBackend : public AudioBackend { private: std::string _instance_name; PortAudioIO *_pcmio; + WinMMEMidiIO *_midiio; bool _run; /* keep going or stop, ardour thread */ bool _active; /* is running, process thread */ @@ -319,7 +322,12 @@ class PortAudioBackend : public AudioBackend { bool _freewheeling; bool _measure_latency; - uint64_t _last_process_start; + uint64_t m_cycle_count; + uint64_t m_total_deviation_us; + uint64_t m_max_deviation_us; + + CycleTimer m_cycle_timer; + uint64_t m_last_cycle_start; static std::vector<std::string> _midi_options; static std::vector<AudioBackend::DeviceStatus> _input_audio_device_status; @@ -365,6 +373,7 @@ class PortAudioBackend : public AudioBackend { /* port engine */ PortHandle add_port (const std::string& shortname, ARDOUR::DataType, ARDOUR::PortFlags); int register_system_audio_ports (); + int register_system_midi_ports (); void unregister_ports (bool system_only = false); std::vector<PamPort *> _ports; diff --git a/libs/backends/portaudio/portaudio_io.cc b/libs/backends/portaudio/portaudio_io.cc index b7ea02ff22..4f967b370c 100644 --- a/libs/backends/portaudio/portaudio_io.cc +++ b/libs/backends/portaudio/portaudio_io.cc @@ -1,5 +1,6 @@ /* * Copyright (C) 2015 Robin Gareus <robin@gareus.org> + * Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,6 +24,10 @@ #include <glibmm.h> #include "portaudio_io.h" +#include "pbd/compose.h" + +#include "debug.h" + #define INTERLEAVED_INPUT #define INTERLEAVED_OUTPUT @@ -69,9 +74,9 @@ PortAudioIO::available_sample_rates(int device_id, std::vector<float>& sampleRat if (device_id == -1) { device_id = get_default_input_device (); } -#ifndef NDEBUG - printf("PortAudio: Querying Samplerates for device %d\n", device_id); -#endif + + DEBUG_AUDIO ( + string_compose ("Querying Samplerates for device %1\n", device_id)); sampleRates.clear(); const PaDeviceInfo* nfo = Pa_GetDeviceInfo(device_id); @@ -181,7 +186,7 @@ PortAudioIO::set_host_api (const std::string& host_api_name) _host_api_index = get_host_api_index_from_name (host_api_name); if (_host_api_index < 0) { - fprintf(stderr, "Error setting host API\n"); + DEBUG_AUDIO ("Error setting host API\n"); } } @@ -192,7 +197,10 @@ PortAudioIO::get_host_api_index_from_name (const std::string& name) PaHostApiIndex count = Pa_GetHostApiCount(); - if (count < 0) return -1; + if (count < 0) { + DEBUG_AUDIO ("Host API count < 0\n"); + return -1; + } for (int i = 0; i < count; ++i) { const PaHostApiInfo* info = Pa_GetHostApiInfo (i); @@ -200,6 +208,8 @@ PortAudioIO::get_host_api_index_from_name (const std::string& name) if (name == info->name) return i; } } + DEBUG_AUDIO (string_compose ("Unable to get host API from name: %1\n", name)); + return -1; } @@ -262,26 +272,28 @@ PortAudioIO::add_devices () if (info == NULL) return; int n_devices = Pa_GetDeviceCount(); -#ifndef NDEBUG - printf("PortAudio %d devices found:\n", n_devices); -#endif + + DEBUG_AUDIO (string_compose ("PortAudio found %1 devices\n", n_devices)); for (int i = 0 ; i < n_devices; ++i) { const PaDeviceInfo* nfo = Pa_GetDeviceInfo(i); if (!nfo) continue; if (nfo->hostApi != _host_api_index) continue; -#ifndef NDEBUG - printf(" (%d) '%s' '%s' in: %d (lat: %.1f .. %.1f) out: %d (lat: %.1f .. %.1f) sr:%.2f\n", - i, info->name, nfo->name, - nfo->maxInputChannels, - nfo->defaultLowInputLatency * 1e3, - nfo->defaultHighInputLatency * 1e3, - nfo->maxOutputChannels, - nfo->defaultLowOutputLatency * 1e3, - nfo->defaultHighOutputLatency * 1e3, - nfo->defaultSampleRate); -#endif + + DEBUG_AUDIO (string_compose (" (%1) '%2' '%3' in: %4 (lat: %5 .. %6) out: %7 " + "(lat: %8 .. %9) sr:%10\n", + i, + info->name, + nfo->name, + nfo->maxInputChannels, + nfo->defaultLowInputLatency * 1e3, + nfo->defaultHighInputLatency * 1e3, + nfo->maxOutputChannels, + nfo->defaultLowOutputLatency * 1e3, + nfo->defaultHighOutputLatency * 1e3, + nfo->defaultSampleRate)); + if ( nfo->maxInputChannels == 0 && nfo->maxOutputChannels == 0) { continue; } @@ -364,15 +376,13 @@ PortAudioIO::pcm_setup ( { _state = -2; - // TODO error reporting sans fprintf() - PaError err = paNoError; const PaDeviceInfo *nfo_in; const PaDeviceInfo *nfo_out; const PaStreamInfo *nfo_s; if (!initialize_pa()) { - fprintf(stderr, "PortAudio Initialization Failed\n"); + DEBUG_AUDIO ("PortAudio Initialization Failed\n"); goto error; } @@ -389,15 +399,14 @@ PortAudioIO::pcm_setup ( _cur_input_latency = 0; _cur_output_latency = 0; -#ifndef NDEBUG - printf("PortAudio Device IDs: i:%d o:%d\n", device_input, device_output); -#endif + DEBUG_AUDIO (string_compose ( + "PortAudio Device IDs: i:%1 o:%2\n", device_input, device_output)); nfo_in = Pa_GetDeviceInfo(device_input); nfo_out = Pa_GetDeviceInfo(device_output); if (!nfo_in && ! nfo_out) { - fprintf(stderr, "PortAudio Cannot Query Device Info\n"); + DEBUG_AUDIO ("PortAudio Cannot Query Device Info\n"); goto error; } @@ -409,29 +418,29 @@ PortAudioIO::pcm_setup ( } if(_capture_channels == 0 && _playback_channels == 0) { - fprintf(stderr, "PortAudio no Input and no output channels.\n"); + DEBUG_AUDIO ("PortAudio no input or output channels.\n"); goto error; } - #ifdef __APPLE__ // pa_mac_core_blocking.c pa_stable_v19_20140130 // BUG: ringbuffer alloc requires power-of-two chn count. if ((_capture_channels & (_capture_channels - 1)) != 0) { - printf("Adjusted capture channes to power of two (portaudio rb bug)\n"); + DEBUG_AUDIO ( + "Adjusted capture channels to power of two (portaudio rb bug)\n"); _capture_channels = lower_power_of_two (_capture_channels); } if ((_playback_channels & (_playback_channels - 1)) != 0) { - printf("Adjusted capture channes to power of two (portaudio rb bug)\n"); + DEBUG_AUDIO ( + "Adjusted capture channels to power of two (portaudio rb bug)\n"); _playback_channels = lower_power_of_two (_playback_channels); } #endif - -#ifndef NDEBUG - printf("PortAudio Channels: in:%d out:%d\n", - _capture_channels, _playback_channels); -#endif + + DEBUG_AUDIO (string_compose ("PortAudio Channels: in:%1 out:%2\n", + _capture_channels, + _playback_channels)); PaStreamParameters inputParam; PaStreamParameters outputParam; @@ -471,13 +480,13 @@ PortAudioIO::pcm_setup ( NULL, NULL); if (err != paNoError) { - fprintf(stderr, "PortAudio failed to start stream.\n"); + DEBUG_AUDIO ("PortAudio failed to start stream.\n"); goto error; } nfo_s = Pa_GetStreamInfo (_stream); if (!nfo_s) { - fprintf(stderr, "PortAudio failed to query stream information.\n"); + DEBUG_AUDIO ("PortAudio failed to query stream information.\n"); pcm_stop(); goto error; } @@ -486,18 +495,22 @@ PortAudioIO::pcm_setup ( _cur_input_latency = nfo_s->inputLatency * _cur_sample_rate; _cur_output_latency = nfo_s->outputLatency * _cur_sample_rate; -#ifndef NDEBUG - printf("PA Sample Rate %.1f SPS\n", _cur_sample_rate); - printf("PA Input Latency %.1fms %d spl\n", 1e3 * nfo_s->inputLatency, _cur_input_latency); - printf("PA Output Latency %.1fms %d spl\n", 1e3 * nfo_s->outputLatency, _cur_output_latency); -#endif + DEBUG_AUDIO (string_compose ("PA Sample Rate %1 SPS\n", _cur_sample_rate)); + + DEBUG_AUDIO (string_compose ("PA Input Latency %1ms, %2 spl\n", + 1e3 * nfo_s->inputLatency, + _cur_input_latency)); + + DEBUG_AUDIO (string_compose ("PA Output Latency %1ms, %2 spl\n", + 1e3 * nfo_s->outputLatency, + _cur_output_latency)); _state = 0; if (_capture_channels > 0) { _input_buffer = (float*) malloc (samples_per_period * _capture_channels * sizeof(float)); if (!_input_buffer) { - fprintf(stderr, "PortAudio failed to allocate input buffer.\n"); + DEBUG_AUDIO ("PortAudio failed to allocate input buffer.\n"); pcm_stop(); goto error; } @@ -506,7 +519,7 @@ PortAudioIO::pcm_setup ( if (_playback_channels > 0) { _output_buffer = (float*) calloc (samples_per_period * _playback_channels, sizeof(float)); if (!_output_buffer) { - fprintf(stderr, "PortAudio failed to allocate output buffer.\n"); + DEBUG_AUDIO ("PortAudio failed to allocate output buffer.\n"); pcm_stop(); goto error; } diff --git a/libs/backends/portaudio/win_utils.cc b/libs/backends/portaudio/win_utils.cc new file mode 100644 index 0000000000..7bda04ccf1 --- /dev/null +++ b/libs/backends/portaudio/win_utils.cc @@ -0,0 +1,99 @@ +/* + * Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "win_utils.h" + +#include <windows.h> + +#include "pbd/compose.h" + +#include "debug.h" + +namespace { + +LARGE_INTEGER +get_frequency () +{ + LARGE_INTEGER freq; + QueryPerformanceFrequency(&freq); + return freq; +} + + +UINT& +old_timer_resolution () +{ + static UINT timer_res_ms = 0; + return timer_res_ms; +} + +} // anon namespace + +namespace utils { + +bool +set_min_timer_resolution () +{ + TIMECAPS caps; + + if (timeGetDevCaps (&caps, sizeof(TIMECAPS)) != TIMERR_NOERROR) { + DEBUG_TIMING ("Could not get timer device capabilities.\n"); + return false; + } else { + old_timer_resolution () = caps.wPeriodMin; + if (timeBeginPeriod (caps.wPeriodMin) != TIMERR_NOERROR) { + DEBUG_TIMING (string_compose ( + "Could not set minimum timer resolution to %1(ms)\n", caps.wPeriodMin)); + return false; + } + } + + DEBUG_TIMING (string_compose ("Multimedia timer resolution set to %1(ms)\n", + caps.wPeriodMin)); + + return true; +} + +bool +reset_timer_resolution () +{ + if (old_timer_resolution ()) { + if (timeEndPeriod (old_timer_resolution ()) != TIMERR_NOERROR) { + DEBUG_TIMING ("Could not reset timer resolution.\n"); + return false; + } + } + + DEBUG_TIMING (string_compose ("Multimedia timer resolution set to %1(ms)\n", + old_timer_resolution ())); + + return true; +} + +uint64_t get_microseconds () +{ + static LARGE_INTEGER frequency = get_frequency (); + LARGE_INTEGER current_val; + + QueryPerformanceCounter (¤t_val); + + return (uint64_t)(((double)current_val.QuadPart) / + ((double)frequency.QuadPart) * 1000000.0); +} + +} // namespace utils diff --git a/libs/backends/portaudio/win_utils.h b/libs/backends/portaudio/win_utils.h new file mode 100644 index 0000000000..50576b9c5d --- /dev/null +++ b/libs/backends/portaudio/win_utils.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef WIN_UTILS_H +#define WIN_UTILS_H + +#include "stdint.h" + +namespace utils { + +bool set_min_timer_resolution (); + +bool reset_timer_resolution (); + +uint64_t get_microseconds (); + +} + +#endif // WIN_UTILS_H diff --git a/libs/backends/portaudio/winmmemidi_input_device.cc b/libs/backends/portaudio/winmmemidi_input_device.cc new file mode 100644 index 0000000000..1067b46743 --- /dev/null +++ b/libs/backends/portaudio/winmmemidi_input_device.cc @@ -0,0 +1,366 @@ +/* + * Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "winmmemidi_input_device.h" + +#include <stdexcept> +#include <cmath> + +#include "pbd/compose.h" + +#include "win_utils.h" +#include "midi_util.h" + +#include "debug.h" + +static const uint32_t MIDI_BUFFER_SIZE = 32768; +static const uint32_t SYSEX_BUFFER_SIZE = 32768; + +namespace ARDOUR { + +WinMMEMidiInputDevice::WinMMEMidiInputDevice (int index) + : m_handle(0) + , m_midi_buffer(new RingBuffer<uint8_t>(MIDI_BUFFER_SIZE)) + , m_sysex_buffer(new uint8_t[SYSEX_BUFFER_SIZE]) +{ + DEBUG_MIDI (string_compose ("Creating midi input device index: %1\n", index)); + + std::string error_msg; + + if (!open (index, error_msg)) { + DEBUG_MIDI (error_msg); + throw std::runtime_error (error_msg); + } + + // perhaps this should be called in open + if (!add_sysex_buffer (error_msg)) { + DEBUG_MIDI (error_msg); + std::string close_error; + if (!close (close_error)) { + DEBUG_MIDI (close_error); + } + throw std::runtime_error (error_msg); + } + + set_device_name (index); +} + +WinMMEMidiInputDevice::~WinMMEMidiInputDevice () +{ + std::string error_msg; + if (!close (error_msg)) { + DEBUG_MIDI (error_msg); + } +} + +bool +WinMMEMidiInputDevice::open (UINT index, std::string& error_msg) +{ + MMRESULT result = midiInOpen (&m_handle, + index, + (DWORD_PTR) winmm_input_callback, + (DWORD_PTR) this, + CALLBACK_FUNCTION | MIDI_IO_STATUS); + if (result != MMSYSERR_NOERROR) { + error_msg = get_error_string (result); + return false; + } + DEBUG_MIDI (string_compose ("Opened MIDI device index %1\n", index)); + return true; +} + +bool +WinMMEMidiInputDevice::close (std::string& error_msg) +{ + // return error message for first error encountered? + bool success = true; + + MMRESULT result = midiInReset (m_handle); + if (result != MMSYSERR_NOERROR) { + error_msg = get_error_string (result); + DEBUG_MIDI (error_msg); + success = false; + } + result = midiInUnprepareHeader (m_handle, &m_sysex_header, sizeof(MIDIHDR)); + if (result != MMSYSERR_NOERROR) { + error_msg = get_error_string (result); + DEBUG_MIDI (error_msg); + success = false; + } + result = midiInClose (m_handle); + if (result != MMSYSERR_NOERROR) { + error_msg = get_error_string (result); + DEBUG_MIDI (error_msg); + success = false; + } + m_handle = 0; + if (success) { + DEBUG_MIDI (string_compose ("Closed MIDI device: %1\n", name ())); + } else { + DEBUG_MIDI (string_compose ("Unable to Close MIDI device: %1\n", name ())); + } + return success; +} + +bool +WinMMEMidiInputDevice::add_sysex_buffer (std::string& error_msg) +{ + m_sysex_header.dwBufferLength = SYSEX_BUFFER_SIZE; + m_sysex_header.dwFlags = 0; + m_sysex_header.lpData = (LPSTR)m_sysex_buffer.get (); + + MMRESULT result = midiInPrepareHeader (m_handle, &m_sysex_header, sizeof(MIDIHDR)); + + if (result != MMSYSERR_NOERROR) { + error_msg = get_error_string (result); + DEBUG_MIDI (error_msg); + return false; + } + result = midiInAddBuffer (m_handle, &m_sysex_header, sizeof(MIDIHDR)); + if (result != MMSYSERR_NOERROR) { + error_msg = get_error_string (result); + DEBUG_MIDI (error_msg); + return false; + } + return true; +} + +bool +WinMMEMidiInputDevice::set_device_name (UINT index) +{ + MIDIINCAPS capabilities; + MMRESULT result = midiInGetDevCaps (index, &capabilities, sizeof(capabilities)); + if (result != MMSYSERR_NOERROR) { + DEBUG_MIDI (get_error_string (result)); + m_name = "Unknown Midi Input Device"; + return false; + } else { + m_name = capabilities.szPname; + } + return true; +} + +std::string +WinMMEMidiInputDevice::get_error_string (MMRESULT error_code) +{ + char error_msg[MAXERRORLENGTH]; + MMRESULT result = midiInGetErrorText (error_code, error_msg, MAXERRORLENGTH); + if (result != MMSYSERR_NOERROR) { + return error_msg; + } + return "WinMMEMidiInput: Unknown Error code"; +} + +void CALLBACK +WinMMEMidiInputDevice::winmm_input_callback(HMIDIIN handle, + UINT msg, + DWORD_PTR instance, + DWORD_PTR midi_msg, + DWORD timestamp) +{ + WinMMEMidiInputDevice* midi_input = (WinMMEMidiInputDevice*)instance; + + switch (msg) { + case MIM_OPEN: + case MIM_CLOSE: + // devices_changed_callback + break; + case MIM_MOREDATA: + // passing MIDI_IO_STATUS to midiInOpen means that MIM_MOREDATA + // will be sent when the callback isn't processing MIM_DATA messages + // fast enough to keep up with messages arriving at input device + // driver. I'm not sure what could be done differently if that occurs + // so just handle MIM_DATA as per normal + case MIM_DATA: + midi_input->handle_short_msg ((const uint8_t*)&midi_msg, (uint32_t)timestamp); + break; + case MIM_LONGDATA: + midi_input->handle_sysex_msg ((MIDIHDR*)&midi_msg, (uint32_t)timestamp); + break; + case MIM_ERROR: + DEBUG_MIDI ("WinMME: Driver sent an invalid MIDI message\n"); + break; + case MIM_LONGERROR: + DEBUG_MIDI ("WinMME: Driver sent an invalid or incomplete SYSEX message\n"); + break; + } +} + +void +WinMMEMidiInputDevice::handle_short_msg (const uint8_t* midi_data, + uint32_t timestamp) +{ + int length = get_midi_msg_length (midi_data[0]); + + if (length == 0 || length == -1) { + DEBUG_MIDI ("ERROR: midi input driver sent an invalid midi message\n"); + return; + } + + enqueue_midi_msg (midi_data, length, timestamp); +} + +void +WinMMEMidiInputDevice::handle_sysex_msg (MIDIHDR* const midi_header, + uint32_t timestamp) +{ +#ifdef ENABLE_SYSEX + LPMIDIHDR header = (LPMIDIHDR)midi_header; + size_t byte_count = header->dwBytesRecorded; + + if (!byte_count) { + DEBUG_MIDI ( + "ERROR: WinMME driver has returned sysex header to us with no bytes\n"); + return; + } + + uint8_t* data = (uint8_t*)header->lpData; + + if ((data[0] != 0xf0) || (data[byte_count - 1] != 0xf7)) { + DEBUG_MIDI (string_compose ("Discarding %1 byte sysex chunk\n", byte_count)); + } else { + enqueue_midi_msg (data, byte_count, timestamp); + } + + MMRESULT result = midiInAddBuffer (m_handle, &m_sysex_header, sizeof(MIDIHDR)); + if (result != MMSYSERR_NOERROR) { + DEBUG_MIDI (get_error_string (result)); + } +#endif +} + +// fix param order +bool +WinMMEMidiInputDevice::dequeue_midi_event (uint64_t timestamp_start, + uint64_t timestamp_end, + uint64_t& timestamp, + uint8_t* midi_data, + size_t& data_size) +{ + const uint32_t read_space = m_midi_buffer->read_space(); + struct MidiEventHeader h(0,0); + + if (read_space <= sizeof(MidiEventHeader)) { + return false; + } + + RingBuffer<uint8_t>::rw_vector vector; + m_midi_buffer->get_read_vector (&vector); + if (vector.len[0] >= sizeof(MidiEventHeader)) { + memcpy ((uint8_t*)&h, vector.buf[0], sizeof(MidiEventHeader)); + } else { + if (vector.len[0] > 0) { + memcpy ((uint8_t*)&h, vector.buf[0], vector.len[0]); + } + assert (vector.buf[1] || vector.len[0] == sizeof(MidiEventHeader)); + memcpy (((uint8_t*)&h) + vector.len[0], + vector.buf[1], + sizeof(MidiEventHeader) - vector.len[0]); + } + + if (h.time >= timestamp_end) { + DEBUG_TIMING (string_compose ("WinMMEMidiInput EVENT %1(ms) early\n", + (h.time - timestamp_end) * 1e-3)); + return false; + } else if (h.time < timestamp_start) { + DEBUG_TIMING (string_compose ("WinMMEMidiInput EVENT %1(ms) late\n", + (timestamp_start - h.time) * 1e-3)); + } + + m_midi_buffer->increment_read_idx (sizeof(MidiEventHeader)); + + assert (h.size > 0); + if (h.size > data_size) { + DEBUG_MIDI ("WinMMEMidiInput::dequeue_event MIDI event too large!\n"); + m_midi_buffer->increment_read_idx (h.size); + return false; + } + if (m_midi_buffer->read (&midi_data[0], h.size) != h.size) { + DEBUG_MIDI ("WinMMEMidiInput::dequeue_event Garbled MIDI EVENT DATA!!\n"); + return false; + } + timestamp = h.time; + data_size = h.size; + return true; +} + +bool +WinMMEMidiInputDevice::enqueue_midi_msg (const uint8_t* midi_data, + size_t data_size, + uint32_t timestamp) +{ + const uint32_t total_size = sizeof(MidiEventHeader) + data_size; + + if (data_size == 0) { + DEBUG_MIDI ("ERROR: zero length midi data\n"); + return false; + } + + if (m_midi_buffer->write_space () < total_size) { + DEBUG_MIDI ("WinMMEMidiInput: ring buffer overflow\n"); + return false; + } + + // don't use winmme timestamps for now + uint64_t ts = utils::get_microseconds (); + + DEBUG_TIMING (string_compose ( + "Enqueing MIDI data device: %1 with timestamp: %2 and size %3\n", + name (), + ts, + data_size)); + + struct MidiEventHeader h (ts, data_size); + m_midi_buffer->write ((uint8_t*)&h, sizeof(MidiEventHeader)); + m_midi_buffer->write (midi_data, data_size); + return true; +} + +bool +WinMMEMidiInputDevice::start () +{ + if (!m_started) { + MMRESULT result = midiInStart (m_handle); + m_started = (result == MMSYSERR_NOERROR); + if (!m_started) { + DEBUG_MIDI (get_error_string (result)); + } else { + DEBUG_MIDI ( + string_compose ("WinMMEMidiInput: device %1 started\n", name ())); + } + } + return m_started; +} + +bool +WinMMEMidiInputDevice::stop () +{ + if (m_started) { + MMRESULT result = midiInStop (m_handle); + m_started = (result != MMSYSERR_NOERROR); + if (m_started) { + DEBUG_MIDI (get_error_string (result)); + } else { + DEBUG_MIDI ( + string_compose ("WinMMEMidiInput: device %1 stopped\n", name ())); + } + } + return !m_started; +} + +} // namespace ARDOUR diff --git a/libs/backends/portaudio/winmmemidi_input_device.h b/libs/backends/portaudio/winmmemidi_input_device.h new file mode 100644 index 0000000000..dab8dbfd53 --- /dev/null +++ b/libs/backends/portaudio/winmmemidi_input_device.h @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef WINMME_MIDI_INPUT_DEVICE_H +#define WINMME_MIDI_INPUT_DEVICE_H + +#include <windows.h> +#include <mmsystem.h> + +#include <stdint.h> + +#include <string> + +#include <boost/scoped_array.hpp> +#include <boost/scoped_ptr.hpp> + +#include <pbd/ringbuffer.h> + +namespace ARDOUR { + +class WinMMEMidiInputDevice { +public: // ctors + WinMMEMidiInputDevice (int index); + + ~WinMMEMidiInputDevice (); + +public: // methods + + /** + * Dequeue events that have accumulated in winmm_input_callback. + * + * This is called by the audio processing thread/callback to transfer events + * into midi ports before processing. + */ + bool dequeue_midi_event (uint64_t timestamp_start, + uint64_t timestamp_end, + uint64_t& timestamp, + uint8_t* data, + size_t& size); + + bool start (); + bool stop (); + + void set_enabled (bool enable); + + bool get_enabled (); + + /** + * @return Name of midi device + */ + std::string name () const { return m_name; } + +private: // methods + bool open (UINT index, std::string& error_msg); + bool close (std::string& error_msg); + + bool add_sysex_buffer (std::string& error_msg); + bool set_device_name (UINT index); + + std::string get_error_string (MMRESULT error_code); + + static void CALLBACK winmm_input_callback (HMIDIIN handle, + UINT msg, + DWORD_PTR instance, + DWORD_PTR midi_msg, + DWORD timestamp); + + void handle_short_msg (const uint8_t* midi_data, uint32_t timestamp); + + void handle_sysex_msg (MIDIHDR* const midi_header, uint32_t timestamp); + + bool enqueue_midi_msg (const uint8_t* midi_data, size_t size, uint32_t timestamp); + +private: // data + HMIDIIN m_handle; + MIDIHDR m_sysex_header; + + bool m_started; + bool m_enabled; + + std::string m_name; + + // can't use unique_ptr yet + boost::scoped_ptr<RingBuffer<uint8_t> > m_midi_buffer; + boost::scoped_array<uint8_t> m_sysex_buffer; +}; + +} + +#endif // WINMME_MIDI_INPUT_DEVICE_H diff --git a/libs/backends/portaudio/winmmemidi_io.cc b/libs/backends/portaudio/winmmemidi_io.cc new file mode 100644 index 0000000000..710456da0b --- /dev/null +++ b/libs/backends/portaudio/winmmemidi_io.cc @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <windows.h> +#include <mmsystem.h> + +#include <sstream> + +#include "pbd/error.h" +#include "pbd/compose.h" + +#include "winmmemidi_io.h" +#include "win_utils.h" +#include "debug.h" + +#include "i18n.h" + +using namespace ARDOUR; +using namespace utils; + +WinMMEMidiIO::WinMMEMidiIO() + : m_active (false) + , m_enabled (true) + , m_run (false) + , m_changed_callback (0) + , m_changed_arg (0) +{ + pthread_mutex_init (&m_device_lock, 0); +} + +WinMMEMidiIO::~WinMMEMidiIO() +{ + pthread_mutex_lock (&m_device_lock); + cleanup(); + pthread_mutex_unlock (&m_device_lock); + pthread_mutex_destroy (&m_device_lock); +} + +void +WinMMEMidiIO::cleanup() +{ + DEBUG_MIDI ("MIDI cleanup\n"); + m_active = false; + + destroy_input_devices (); + destroy_output_devices (); +} + +bool +WinMMEMidiIO::dequeue_input_event (uint32_t port, + uint64_t timestamp_start, + uint64_t timestamp_end, + uint64_t ×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); +} diff --git a/libs/backends/portaudio/winmmemidi_io.h b/libs/backends/portaudio/winmmemidi_io.h new file mode 100644 index 0000000000..a2868241d8 --- /dev/null +++ b/libs/backends/portaudio/winmmemidi_io.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef WINMME_MIDI_IO_H +#define WINMME_MIDI_IO_H + +#include <map> +#include <vector> +#include <string> +#include <stdint.h> + +#include <boost/shared_ptr.hpp> +#include "pbd/ringbuffer.h" + +#include "winmmemidi_input_device.h" +#include "winmmemidi_output_device.h" + +namespace ARDOUR { + +struct WinMMEMIDIPacket { + +#if 0 + WinMMEMIDIPacket (const WinMMEMIDIPacket& other) + : timeStamp (other.timeStamp) + , length (other.length) + { + if (length > 0) { + memcpy (data, other.data, length); + } + } +#endif + + // MIDITimeStamp timeStamp; + uint16_t length; + uint8_t data[256]; +}; + +typedef std::vector<boost::shared_ptr<WinMMEMIDIPacket> > WinMMEMIDIQueue; + +class WinMMEMidiIO { +public: + WinMMEMidiIO (); + ~WinMMEMidiIO (); + + void start (); + void stop (); + + bool dequeue_input_event (uint32_t port, + uint64_t timestamp_start, + uint64_t timestamp_end, + uint64_t& timestamp, + uint8_t* data, + size_t& size); + + bool enqueue_output_event (uint32_t port, + uint64_t timestamp, + const uint8_t* data, + const size_t size); + + uint32_t n_midi_inputs (void) const { return m_inputs.size(); } + uint32_t n_midi_outputs (void) const { return m_outputs.size(); } + + std::vector<WinMMEMidiInputDevice*> get_inputs () { return m_inputs; } + std::vector<WinMMEMidiOutputDevice*> get_outputs () { return m_outputs; } + + std::string port_id (uint32_t, bool input); + std::string port_name (uint32_t, bool input); + + void set_enabled (bool yn = true) { m_enabled = yn; } + bool enabled (void) const { return m_active && m_enabled; } + + void set_port_changed_callback (void (changed_callback (void*)), void *arg) { + m_changed_callback = changed_callback; + m_changed_arg = arg; + } + +private: // Methods + void discover (); + void cleanup (); + + void create_input_devices (); + void create_output_devices (); + + void destroy_input_devices (); + void destroy_output_devices (); + + void start_devices (); + void stop_devices (); + +private: // Data + + std::vector<WinMMEMidiInputDevice*> m_inputs; + std::vector<WinMMEMidiOutputDevice*> m_outputs; + + bool m_active; + bool m_enabled; + bool m_run; + + void (* m_changed_callback) (void*); + void * m_changed_arg; + + // protects access to m_inputs and m_outputs + pthread_mutex_t m_device_lock; +}; + +} // namespace + +#endif // WINMME_MIDI_IO_H + diff --git a/libs/backends/portaudio/winmmemidi_output_device.cc b/libs/backends/portaudio/winmmemidi_output_device.cc new file mode 100644 index 0000000000..6b31488b00 --- /dev/null +++ b/libs/backends/portaudio/winmmemidi_output_device.cc @@ -0,0 +1,494 @@ +/* + * Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "winmmemidi_output_device.h" + +#include <glibmm.h> + +#include "pbd/debug.h" +#include "pbd/compose.h" + +#include "rt_thread.h" +#include "win_utils.h" +#include "midi_util.h" + +#include "debug.h" + +// remove dup with input_device +static const uint32_t MIDI_BUFFER_SIZE = 32768; +static const uint32_t MAX_MIDI_MSG_SIZE = 256; // fix this for sysex +static const uint32_t MAX_QUEUE_SIZE = 4096; + +namespace ARDOUR { + +WinMMEMidiOutputDevice::WinMMEMidiOutputDevice (int index) + : m_handle(0) + , m_queue_semaphore(0) + , m_sysex_semaphore(0) + , m_timer(0) + , m_started(false) + , m_enabled(false) + , m_thread_running(false) + , m_thread_quit(false) + , m_midi_buffer(new RingBuffer<uint8_t>(MIDI_BUFFER_SIZE)) +{ + DEBUG_MIDI (string_compose ("Creating midi output device index: %1\n", index)); + + std::string error_msg; + + if (!open (index, error_msg)) { + DEBUG_MIDI (error_msg); + throw std::runtime_error (error_msg); + } + + set_device_name (index); +} + +WinMMEMidiOutputDevice::~WinMMEMidiOutputDevice () +{ + std::string error_msg; + if (!close (error_msg)) { + DEBUG_MIDI (error_msg); + } +} + +bool +WinMMEMidiOutputDevice::enqueue_midi_event (uint64_t timestamp, + const uint8_t* data, + size_t size) +{ + const uint32_t total_bytes = sizeof(MidiEventHeader) + size; + if (m_midi_buffer->write_space () < total_bytes) { + DEBUG_MIDI ("WinMMEMidiOutput: ring buffer overflow\n"); + return false; + } + + MidiEventHeader h (timestamp, size); + m_midi_buffer->write ((uint8_t*)&h, sizeof(MidiEventHeader)); + m_midi_buffer->write (data, size); + + signal (m_queue_semaphore); + return true; +} + +bool +WinMMEMidiOutputDevice::open (UINT index, std::string& error_msg) +{ + MMRESULT result = midiOutOpen (&m_handle, + index, + (DWORD_PTR)winmm_output_callback, + (DWORD_PTR) this, + CALLBACK_FUNCTION); + if (result != MMSYSERR_NOERROR) { + error_msg = get_error_string (result); + return false; + } + + m_queue_semaphore = CreateSemaphore (NULL, 0, MAX_QUEUE_SIZE, NULL); + if (m_queue_semaphore == NULL) { + DEBUG_MIDI ("WinMMEMidiOutput: Unable to create queue semaphore\n"); + return false; + } + m_sysex_semaphore = CreateSemaphore (NULL, 0, 1, NULL); + if (m_sysex_semaphore == NULL) { + DEBUG_MIDI ("WinMMEMidiOutput: Unable to create sysex semaphore\n"); + return false; + } + return true; +} + +bool +WinMMEMidiOutputDevice::close (std::string& error_msg) +{ + // return error message for first error encountered? + bool success = true; + MMRESULT result = midiOutReset (m_handle); + if (result != MMSYSERR_NOERROR) { + error_msg = get_error_string (result); + DEBUG_MIDI (error_msg); + success = false; + } + result = midiOutClose (m_handle); + if (result != MMSYSERR_NOERROR) { + error_msg = get_error_string (result); + DEBUG_MIDI (error_msg); + success = false; + } + + if (m_sysex_semaphore) { + if (!CloseHandle (m_sysex_semaphore)) { + DEBUG_MIDI ("WinMMEMidiOut Unable to close sysex semaphore\n"); + success = false; + } else { + m_sysex_semaphore = 0; + } + } + if (m_queue_semaphore) { + if (!CloseHandle (m_queue_semaphore)) { + DEBUG_MIDI ("WinMMEMidiOut Unable to close queue semaphore\n"); + success = false; + } else { + m_queue_semaphore = 0; + } + } + + m_handle = 0; + return success; +} + +bool +WinMMEMidiOutputDevice::set_device_name (UINT index) +{ + MIDIOUTCAPS capabilities; + MMRESULT result = + midiOutGetDevCaps (index, &capabilities, sizeof(capabilities)); + + if (result != MMSYSERR_NOERROR) { + DEBUG_MIDI (get_error_string (result)); + m_name = "Unknown Midi Output Device"; + return false; + } else { + m_name = capabilities.szPname; + } + return true; +} + +std::string +WinMMEMidiOutputDevice::get_error_string (MMRESULT error_code) +{ + char error_msg[MAXERRORLENGTH]; + MMRESULT result = midiOutGetErrorText (error_code, error_msg, MAXERRORLENGTH); + if (result != MMSYSERR_NOERROR) { + return error_msg; + } + return "WinMMEMidiOutput: Unknown Error code"; +} + +bool +WinMMEMidiOutputDevice::start () +{ + if (m_thread_running) { + DEBUG_MIDI ( + string_compose ("WinMMEMidiOutput: device %1 already started\n", m_name)); + return true; + } + + m_timer = CreateWaitableTimer (NULL, FALSE, NULL); + + if (!m_timer) { + DEBUG_MIDI ("WinMMEMidiOutput: unable to create waitable timer\n"); + return false; + } + + if (!start_midi_output_thread ()) { + DEBUG_MIDI ("WinMMEMidiOutput: Failed to start MIDI output thread\n"); + + if (!CloseHandle (m_timer)) { + DEBUG_MIDI ("WinMMEMidiOutput: unable to close waitable timer\n"); + } + return false; + } + return true; +} + +bool +WinMMEMidiOutputDevice::stop () +{ + if (!m_thread_running) { + DEBUG_MIDI ("WinMMEMidiOutputDevice: device already stopped\n"); + return true; + } + + if (!stop_midi_output_thread ()) { + DEBUG_MIDI ("WinMMEMidiOutput: Failed to start MIDI output thread\n"); + return false; + } + + if (!CloseHandle (m_timer)) { + DEBUG_MIDI ("WinMMEMidiOutput: unable to close waitable timer\n"); + return false; + } + m_timer = 0; + return true; +} + +bool +WinMMEMidiOutputDevice::start_midi_output_thread () +{ + m_thread_quit = false; + + //pthread_attr_t attr; + size_t stacksize = 100000; + + // TODO Use native threads + if (_realtime_pthread_create (SCHED_FIFO, -21, stacksize, + &m_output_thread_handle, midi_output_thread, this)) { + return false; + } + + int timeout = 5000; + while (!m_thread_running && --timeout > 0) { Glib::usleep (1000); } + if (timeout == 0 || !m_thread_running) { + DEBUG_MIDI (string_compose ("Unable to start midi output device thread: %1\n", + m_name)); + return false; + } + return true; +} + +bool +WinMMEMidiOutputDevice::stop_midi_output_thread () +{ + int timeout = 5000; + m_thread_quit = true; + + while (m_thread_running && --timeout > 0) { Glib::usleep (1000); } + if (timeout == 0 || m_thread_running) { + DEBUG_MIDI (string_compose ("Unable to stop midi output device thread: %1\n", + m_name)); + return false; + } + + void *status; + if (pthread_join (m_output_thread_handle, &status)) { + DEBUG_MIDI (string_compose ("Unable to join midi output device thread: %1\n", + m_name)); + return false; + } + return true; +} + +bool +WinMMEMidiOutputDevice::signal (HANDLE semaphore) +{ + bool result = (bool)ReleaseSemaphore (semaphore, 1, NULL); + if (!result) { + DEBUG_MIDI ("WinMMEMidiOutDevice: Cannot release semaphore\n"); + } + return result; +} + +bool +WinMMEMidiOutputDevice::wait (HANDLE semaphore) +{ + DWORD result = WaitForSingleObject (semaphore, INFINITE); + switch (result) { + case WAIT_FAILED: + DEBUG_MIDI ("WinMMEMidiOutDevice: WaitForSingleObject Failed\n"); + break; + case WAIT_OBJECT_0: + return true; + default: + DEBUG_MIDI ("WinMMEMidiOutDevice: Unexpected result from WaitForSingleObject\n"); + } + return false; +} + +void CALLBACK +WinMMEMidiOutputDevice::winmm_output_callback (HMIDIOUT handle, + UINT msg, + DWORD_PTR instance, + DWORD_PTR midi_data, + DWORD_PTR timestamp) +{ + ((WinMMEMidiOutputDevice*)instance) + ->midi_output_callback (msg, midi_data, timestamp); +} + +void +WinMMEMidiOutputDevice::midi_output_callback (UINT message, + DWORD_PTR midi_data, + DWORD_PTR timestamp) +{ + switch (message) { + case MOM_CLOSE: + DEBUG_MIDI ("WinMMEMidiOutput - MIDI device closed\n"); + break; + case MOM_DONE: + signal (m_sysex_semaphore); + break; + case MOM_OPEN: + DEBUG_MIDI ("WinMMEMidiOutput - MIDI device opened\n"); + break; + case MOM_POSITIONCB: + LPMIDIHDR header = (LPMIDIHDR)midi_data; + DEBUG_MIDI (string_compose ("WinMMEMidiOut - %1 bytes out of %2 bytes of " + "the current sysex message have been sent.\n", + header->dwOffset, + header->dwBytesRecorded)); + } +} + +void* +WinMMEMidiOutputDevice::midi_output_thread (void *arg) +{ + WinMMEMidiOutputDevice* output_device = reinterpret_cast<WinMMEMidiOutputDevice*> (arg); + output_device->midi_output_thread (); + return 0; +} + +void +WinMMEMidiOutputDevice::midi_output_thread () +{ + m_thread_running = true; + + while (!m_thread_quit) { + if (!wait (m_queue_semaphore)) { + break; + } + + MidiEventHeader h (0, 0); + uint8_t data[MAX_MIDI_MSG_SIZE]; + + const uint32_t read_space = m_midi_buffer->read_space (); + + if (read_space > sizeof(MidiEventHeader)) { + if (m_midi_buffer->read ((uint8_t*)&h, sizeof(MidiEventHeader)) != + sizeof(MidiEventHeader)) { + DEBUG_MIDI ("WinMMEMidiOut: Garbled MIDI EVENT HEADER!!\n"); + break; + } + assert (read_space >= h.size); + + if (h.size > MAX_MIDI_MSG_SIZE) { + m_midi_buffer->increment_read_idx (h.size); + DEBUG_MIDI ("WinMMEMidiOut: MIDI event too large!\n"); + continue; + } + if (m_midi_buffer->read (&data[0], h.size) != h.size) { + DEBUG_MIDI ("WinMMEMidiOut: Garbled MIDI EVENT DATA!!\n"); + break; + } + } else { + // error/assert? + DEBUG_MIDI ("WinMMEMidiOut: MIDI buffer underrun, shouldn't occur\n"); + continue; + } + uint64_t current_time = utils::get_microseconds (); + + DEBUG_TIMING (string_compose ( + "WinMMEMidiOut: h.time = %1, current_time = %2\n", h.time, current_time)); + + if (h.time > current_time) { + + DEBUG_TIMING (string_compose ("WinMMEMidiOut: waiting at %1 for %2 " + "milliseconds before sending message\n", + ((double)current_time) / 1000.0, + ((double)(h.time - current_time)) / 1000.0)); + + if (!wait_for_microseconds (h.time - current_time)) + { + DEBUG_MIDI ("WinMMEMidiOut: Error waiting for timer\n"); + break; + } + + uint64_t wakeup_time = utils::get_microseconds (); + DEBUG_TIMING (string_compose ("WinMMEMidiOut: woke up at %1(ms)\n", + ((double)wakeup_time) / 1000.0)); + if (wakeup_time > h.time) { + DEBUG_TIMING (string_compose ("WinMMEMidiOut: overslept by %1(ms)\n", + ((double)(wakeup_time - h.time)) / 1000.0)); + } else if (wakeup_time < h.time) { + DEBUG_TIMING (string_compose ("WinMMEMidiOut: woke up %1(ms) too early\n", + ((double)(h.time - wakeup_time)) / 1000.0)); + } + + } else if (h.time < current_time) { + DEBUG_TIMING (string_compose ( + "WinMMEMidiOut: MIDI event at sent to driver %1(ms) late\n", + ((double)(current_time - h.time)) / 1000.0)); + } + + DWORD message = 0; + MMRESULT result; + switch (h.size) { + case 3: + message |= (((DWORD)data[2]) << 16); + // Fallthrough on purpose. + case 2: + message |= (((DWORD)data[1]) << 8); + // Fallthrough on purpose. + case 1: + message |= (DWORD)data[0]; + result = midiOutShortMsg (m_handle, message); + if (result != MMSYSERR_NOERROR) { + DEBUG_MIDI ( + string_compose ("WinMMEMidiOutput: %1\n", get_error_string (result))); + } + continue; + } + +#if ENABLE_SYSEX + MIDIHDR header; + header.dwBufferLength = h.size; + header.dwFlags = 0; + header.lpData = (LPSTR)data; + + result = midiOutPrepareHeader (m_handle, &header, sizeof(MIDIHDR)); + if (result != MMSYSERR_NOERROR) { + DEBUG_MIDI (string_compose ("WinMMEMidiOutput: midiOutPrepareHeader %1\n", + get_error_string (result))); + continue; + } + + result = midiOutLongMsg (m_handle, &header, sizeof(MIDIHDR)); + if (result != MMSYSERR_NOERROR) { + DEBUG_MIDI (string_compose ("WinMMEMidiOutput: midiOutLongMsg %1\n", + get_error_string (result))); + continue; + } + + // Sysex messages may be sent synchronously or asynchronously. The + // choice is up to the WinMME driver. So, we wait until the message is + // sent, regardless of the driver's choice. + if (!wait (m_sysex_semaphore)) { + break; + } + + result = midiOutUnprepareHeader (m_handle, &header, sizeof(MIDIHDR)); + if (result != MMSYSERR_NOERROR) { + DEBUG_MIDI (string_compose ("WinMMEMidiOutput: midiOutUnprepareHeader %1\n", + get_error_string (result))); + break; + } +#endif + } + + m_thread_running = false; +} + +bool +WinMMEMidiOutputDevice::wait_for_microseconds (int64_t wait_us) +{ + LARGE_INTEGER due_time; + + // 100 ns resolution + due_time.QuadPart = -((LONGLONG)(wait_us * 10)); + if (!SetWaitableTimer (m_timer, &due_time, 0, NULL, NULL, 0)) { + DEBUG_MIDI ("WinMMEMidiOut: Error waiting for timer\n"); + return false; + } + + if (!wait (m_timer)) { + return false; + } + + return true; +} + +} // namespace ARDOUR diff --git a/libs/backends/portaudio/winmmemidi_output_device.h b/libs/backends/portaudio/winmmemidi_output_device.h new file mode 100644 index 0000000000..ead85d7169 --- /dev/null +++ b/libs/backends/portaudio/winmmemidi_output_device.h @@ -0,0 +1,103 @@ +/* + * Copyright (C) 2015 Tim Mayberry <mojofunk@gmail.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef WINMME_MIDI_OUTPUT_DEVICE_H +#define WINMME_MIDI_OUTPUT_DEVICE_H + +#include <windows.h> +#include <mmsystem.h> + +#include <stdint.h> + +#include <string> + +#include <boost/scoped_ptr.hpp> + +#include <pbd/ringbuffer.h> + +namespace ARDOUR { + +class WinMMEMidiOutputDevice { +public: + WinMMEMidiOutputDevice (int index); + + ~WinMMEMidiOutputDevice (); + + bool enqueue_midi_event (uint64_t rel_event_time_us, + const uint8_t* data, + const size_t size); + + bool start (); + bool stop (); + + void set_enabled (bool enable); + bool get_enabled (); + + std::string name () const { return m_name; } + +private: // Methods + bool open (UINT index, std::string& error_msg); + bool close (std::string& error_msg); + + bool set_device_name (UINT index); + + std::string get_error_string (MMRESULT error_code); + + bool start_midi_output_thread (); + bool stop_midi_output_thread (); + + bool signal (HANDLE semaphore); + bool wait (HANDLE semaphore); + + static void* midi_output_thread (void*); + void midi_output_thread (); + + bool wait_for_microseconds (int64_t us); + + static void CALLBACK winmm_output_callback (HMIDIOUT handle, + UINT msg, + DWORD_PTR instance, + DWORD_PTR midi_data, + DWORD_PTR timestamp); + + void midi_output_callback (UINT msg, DWORD_PTR data, DWORD_PTR timestamp); + +private: // Data + HMIDIOUT m_handle; + + HANDLE m_queue_semaphore; + HANDLE m_sysex_semaphore; + + HANDLE m_timer; + + bool m_started; + bool m_enabled; + + std::string m_name; + + pthread_t m_output_thread_handle; + + bool m_thread_running; + bool m_thread_quit; + + boost::scoped_ptr<RingBuffer<uint8_t> > m_midi_buffer; +}; + +} // namespace ARDOUR + +#endif // WINMME_MIDI_OUTPUT_DEVICE_H diff --git a/libs/backends/portaudio/wscript b/libs/backends/portaudio/wscript index 4b77f9670b..8bcfa5fa46 100644 --- a/libs/backends/portaudio/wscript +++ b/libs/backends/portaudio/wscript @@ -22,7 +22,11 @@ def build(bld): obj = bld(features = 'cxx cxxshlib') obj.source = [ 'portaudio_backend.cc', 'portaudio_io.cc', -# 'portmidi_io.cc' + 'winmmemidi_io.cc', + 'winmmemidi_input_device.cc', + 'winmmemidi_output_device.cc', + 'win_utils.cc', + 'midi_util.cc' ] obj.includes = ['.'] obj.name = 'portaudio_backend' |