/* * Copyright (C) 2015 Tim Mayberry * * 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 #include #include #include #include #include "pbd/error.h" #include "pbd/compose.h" #include "pbd/windows_timer_utils.h" #include "winmmemidi_io.h" #include "debug.h" #include "pbd/i18n.h" using namespace ARDOUR; 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"); PBD::MMTIMERS::set_min_resolution(); discover(); start_devices (); } void WinMMEMidiIO::stop () { if (!m_run) { DEBUG_MIDI ("MIDI driver already stopped\n"); return; } DEBUG_MIDI ("Stopping MIDI driver\n"); m_run = false; stop_devices (); pthread_mutex_lock (&m_device_lock); cleanup (); pthread_mutex_unlock (&m_device_lock); PBD::MMTIMERS::reset_resolution(); } void WinMMEMidiIO::start_devices () { for (std::vector::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::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::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::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::clear_device_info () { for (std::vector::iterator i = m_device_info.begin(); i != m_device_info.end(); ++i) { delete *i; } m_device_info.clear(); } bool WinMMEMidiIO::get_input_name_from_index (int index, std::string& name) { MIDIINCAPS capabilities; MMRESULT result = midiInGetDevCaps(index, &capabilities, sizeof(capabilities)); if (result == MMSYSERR_NOERROR) { DEBUG_MIDI(string_compose("Input Device: name : %1, mid : %2, pid : %3\n", capabilities.szPname, capabilities.wMid, capabilities.wPid)); name = Glib::locale_to_utf8 (capabilities.szPname); return true; } else { DEBUG_MIDI ("Unable to get WinMME input device capabilities\n"); } return false; } bool WinMMEMidiIO::get_output_name_from_index (int index, std::string& name) { MIDIOUTCAPS capabilities; MMRESULT result = midiOutGetDevCaps(index, &capabilities, sizeof(capabilities)); if (result == MMSYSERR_NOERROR) { DEBUG_MIDI(string_compose("Output Device: name : %1, mid : %2, pid : %3\n", capabilities.szPname, capabilities.wMid, capabilities.wPid)); name = Glib::locale_to_utf8 (capabilities.szPname); return true; } else { DEBUG_MIDI ("Unable to get WinMME output device capabilities\n"); } return false; } void WinMMEMidiIO::update_device_info () { std::set device_names; int in_count = midiInGetNumDevs (); for (int i = 0; i < in_count; ++i) { std::string input_name; if (get_input_name_from_index(i, input_name)) { device_names.insert(input_name); } } int out_count = midiOutGetNumDevs (); for (int i = 0; i < out_count; ++i) { std::string output_name; if (get_output_name_from_index(i, output_name)) { device_names.insert(output_name); } } clear_device_info (); for (std::set::const_iterator i = device_names.begin(); i != device_names.end(); ++i) { m_device_info.push_back(new MidiDeviceInfo(*i)); } } MidiDeviceInfo* WinMMEMidiIO::get_device_info (const std::string& name) { for (std::vector::const_iterator i = m_device_info.begin(); i != m_device_info.end(); ++i) { if ((*i)->device_name == name) { return *i; } } return 0; } void WinMMEMidiIO::create_input_devices () { int srcCount = midiInGetNumDevs (); DEBUG_MIDI (string_compose ("MidiIn count: %1\n", srcCount)); for (int i = 0; i < srcCount; ++i) { std::string input_name; if (!get_input_name_from_index (i, input_name)) { DEBUG_MIDI ("Unable to get MIDI input name from index\n"); continue; } MidiDeviceInfo* info = get_device_info (input_name); if (!info) { DEBUG_MIDI ("Unable to MIDI device info from name\n"); continue; } if (!info->enable) { DEBUG_MIDI(string_compose( "MIDI input device %1 not enabled, not opening device\n", input_name)); continue; } 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) { std::string output_name; if (!get_output_name_from_index (i, output_name)) { DEBUG_MIDI ("Unable to get MIDI output name from index\n"); continue; } MidiDeviceInfo* info = get_device_info (output_name); if (!info) { DEBUG_MIDI ("Unable to MIDI device info from name\n"); continue; } if (!info->enable) { DEBUG_MIDI(string_compose( "MIDI output device %1 not enabled, not opening device\n", output_name)); continue; } 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); }