/* Copyright (C) 2016 Paul Davis 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 "pbd/compose.h" #include "pbd/convert.h" #include "pbd/debug.h" #include "pbd/failed_constructor.h" #include "pbd/file_utils.h" #include "pbd/search_path.h" #include "pbd/enumwriter.h" #include "midi++/parser.h" #include "ardour/amp.h" #include "ardour/async_midi_port.h" #include "ardour/audioengine.h" #include "ardour/debug.h" #include "ardour/midiport_manager.h" #include "ardour/midi_track.h" #include "ardour/midi_port.h" #include "ardour/session.h" #include "ardour/solo_isolate_control.h" #include "ardour/tempo.h" #include "ardour/types_convert.h" #include "ardour/vca_manager.h" #include "gtkmm2ext/gui_thread.h" #include "gui.h" #include "launch_control_xl.h" #include "pbd/i18n.h" #ifdef PLATFORM_WINDOWS #define random() rand() #endif using namespace ARDOUR; using namespace std; using namespace PBD; using namespace Glib; using namespace ArdourSurface; #include "pbd/abstract_ui.cc" // instantiate template /* init global object */ LaunchControlXL* lcxl = 0; LaunchControlXL::LaunchControlXL (ARDOUR::Session& s) : ControlProtocol (s, string (X_("Novation Launch Control XL"))) , AbstractUI (name()) , in_use (false) , _track_mode(TrackMute) , _template_number(8) // default template (factory 1) , _fader8master (false) , _refresh_leds_flag (false) , bank_start (0) , connection_state (ConnectionState (0)) , gui (0) , in_range_select (false) { lcxl = this; /* we're going to need this */ build_maps (); /* master cannot be removed, so no need to connect to going-away signal */ master = session->master_out (); run_event_loop (); /* Ports exist for the life of this instance */ ports_acquire (); /* catch arrival and departure of LaunchControlXL itself */ ARDOUR::AudioEngine::instance()->PortRegisteredOrUnregistered.connect (port_reg_connection, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::port_registration_handler, this), this); /* Catch port connections and disconnections */ ARDOUR::AudioEngine::instance()->PortConnectedOrDisconnected.connect (port_connection, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::connection_handler, this, _1, _2, _3, _4, _5), this); /* Launch Control XL ports might already be there */ port_registration_handler (); session->RouteAdded.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::stripables_added, this), lcxl); session->vca_manager().VCAAdded.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&LaunchControlXL::stripables_added, this), lcxl); switch_bank (bank_start); } LaunchControlXL::~LaunchControlXL () { DEBUG_TRACE (DEBUG::LaunchControlXL, "Launch Control XL control surface object being destroyed\n"); /* do this before stopping the event loop, so that we don't get any notifications */ port_reg_connection.disconnect (); port_connection.disconnect (); session_connections.drop_connections (); stripable_connections.drop_connections (); stop_using_device (); ports_release (); stop_event_loop (); tear_down_gui (); } void LaunchControlXL::run_event_loop () { DEBUG_TRACE (DEBUG::LaunchControlXL, "start event loop\n"); BaseUI::run (); } void LaunchControlXL::stop_event_loop () { DEBUG_TRACE (DEBUG::LaunchControlXL, "stop event loop\n"); BaseUI::quit (); } int LaunchControlXL::begin_using_device () { DEBUG_TRACE (DEBUG::LaunchControlXL, "begin using device\n"); switch_template(template_number()); // first factory template connect_session_signals (); init_buttons (true); in_use = true; DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose("fader8master inital value '%1'\n", fader8master())); if (fader8master()) { set_fader8master (fader8master()); } return 0; } int LaunchControlXL::stop_using_device () { DEBUG_TRACE (DEBUG::LaunchControlXL, "stop using device\n"); if (!in_use) { DEBUG_TRACE (DEBUG::LaunchControlXL, "nothing to do, device not in use\n"); return 0; } init_buttons (false); session_connections.drop_connections (); in_use = false; return 0; } int LaunchControlXL::ports_acquire () { DEBUG_TRACE (DEBUG::LaunchControlXL, "acquiring ports\n"); /* setup ports */ _async_in = AudioEngine::instance()->register_input_port (DataType::MIDI, X_("Launch Control XL in"), true); _async_out = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("Launch Control XL out"), true); if (_async_in == 0 || _async_out == 0) { DEBUG_TRACE (DEBUG::LaunchControlXL, "cannot register ports\n"); return -1; } /* We do not add our ports to the input/output bundles because we don't * want users wiring them by hand. They could use JACK tools if they * really insist on that (and use JACK) */ _input_port = boost::dynamic_pointer_cast(_async_in).get(); _output_port = boost::dynamic_pointer_cast(_async_out).get(); session->BundleAddedOrRemoved (); connect_to_parser (); /* Connect input port to event loop */ AsyncMIDIPort* asp; asp = static_cast (_input_port); asp->xthread().set_receive_handler (sigc::bind (sigc::mem_fun (this, &LaunchControlXL::midi_input_handler), _input_port)); asp->xthread().attach (main_loop()->get_context()); return 0; } void LaunchControlXL::ports_release () { DEBUG_TRACE (DEBUG::LaunchControlXL, "releasing ports\n"); /* wait for button data to be flushed */ AsyncMIDIPort* asp; asp = static_cast (_output_port); asp->drain (10000, 500000); { Glib::Threads::Mutex::Lock em (AudioEngine::instance()->process_lock()); AudioEngine::instance()->unregister_port (_async_in); AudioEngine::instance()->unregister_port (_async_out); } _async_in.reset ((ARDOUR::Port*) 0); _async_out.reset ((ARDOUR::Port*) 0); _input_port = 0; _output_port = 0; } list > LaunchControlXL::bundles () { list > b; if (_output_bundle) { b.push_back (_output_bundle); } return b; } void LaunchControlXL::init_buttons (bool startup) { reset(template_number()); if (startup) { switch_bank(bank_start); } } bool LaunchControlXL::probe () { return true; } void* LaunchControlXL::request_factory (uint32_t num_requests) { /* AbstractUI::request_buffer_factory() is a template method only instantiated in this source module. To provide something visible for use in the interface/descriptor, we have this static method that is template-free. */ return request_buffer_factory (num_requests); } void LaunchControlXL::do_request (LaunchControlRequest * req) { if (req->type == CallSlot) { call_slot (MISSING_INVALIDATOR, req->the_slot); } else if (req->type == Quit) { stop_using_device (); } } void LaunchControlXL::reset(uint8_t chan) { MidiByteArray msg (3, 176 + chan, 0, 0); // turn off all leds, reset buffer settings and duty cycle write(msg); } int LaunchControlXL::set_active (bool yn) { DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose("LaunchControlProtocol::set_active init with yn: '%1'\n", yn)); if (yn == active()) { return 0; } if (yn) { if ((connection_state & (InputConnected|OutputConnected)) == (InputConnected|OutputConnected)) { begin_using_device (); } else { /* begin_using_device () will get called once we're connected */ } } else { /* Control Protocol Manager never calls us with false, but * insteads destroys us. */ } ControlProtocol::set_active (yn); DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose("LaunchControlProtocol::set_active done with yn: '%1'\n", yn)); return 0; } void LaunchControlXL::write (const MidiByteArray& data) { /* immediate delivery */ _output_port->write (&data[0], data.size(), 0); } /* Device to Ardour message handling */ bool LaunchControlXL::midi_input_handler (IOCondition ioc, MIDI::Port* port) { if (ioc & ~IO_IN) { DEBUG_TRACE (DEBUG::LaunchControlXL, "MIDI port closed\n"); return false; } if (ioc & IO_IN) { DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("something happened on %1\n", port->name())); AsyncMIDIPort* asp = static_cast(port); if (asp) { asp->clear (); } DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("data available on %1\n", port->name())); if (in_use) { samplepos_t now = AudioEngine::instance()->sample_time(); port->parse (now); } } return true; } void LaunchControlXL::connect_to_parser () { DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("Connecting to signals on port %1\n", _input_port->name())); MIDI::Parser* p = _input_port->parser(); /* Incoming sysex */ p->sysex.connect_same_thread (*this, boost::bind (&LaunchControlXL::handle_midi_sysex, this, _1, _2, _3)); for (MIDI::channel_t n = 0; n < 16; ++n) { /* Controller */ p->channel_controller[(int)n].connect_same_thread (*this, boost::bind (&LaunchControlXL::handle_midi_controller_message, this, _1, _2, n)); /* Button messages are NoteOn */ p->channel_note_on[(int)n].connect_same_thread (*this, boost::bind (&LaunchControlXL::handle_midi_note_on_message, this, _1, _2, n)); /* Button messages are NoteOn but libmidi++ sends note-on w/velocity = 0 as note-off so catch them too */ p->channel_note_off[(int)n].connect_same_thread (*this, boost::bind (&LaunchControlXL::handle_midi_note_off_message, this, _1, _2, n)); } } void LaunchControlXL::handle_midi_sysex (MIDI::Parser&, MIDI::byte* raw_bytes, size_t sz) { DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("Sysex, %1 bytes\n", sz)); if (sz < 8) { return; } MidiByteArray msg (sz, raw_bytes); MidiByteArray lcxl_sysex_header (6, 0xF0, 0x00, 0x20, 0x29, 0x02, 0x11); if (!lcxl_sysex_header.compare_n (msg, 6)) { return; } switch (msg[6]) { case 0x77: /* template change */ DEBUG_TRACE (DEBUG::LaunchControlXL, string_compose ("Template change: %1 n", msg[7])); _template_number = msg[7]; break; } } void LaunchControlXL::handle_button_message(boost::shared_ptr