diff options
author | W.P. van Paassen <wp.van.paassen@gmail.com> | 2016-10-11 19:21:21 +0200 |
---|---|---|
committer | Paul Davis <paul@linuxaudiosystems.com> | 2016-10-13 12:10:39 -0400 |
commit | 5a3fe4aa206232bd242e71a1b8c3edee9ff343a1 (patch) | |
tree | 3e4619ccaa24b7e43c5bebc00e6c07c6a3963c9f /libs/surfaces/cc121/cc121.cc | |
parent | bbb20272d5b954e288c1dd47ab5f9848ab991c8a (diff) |
Inital support for Steinberg's CC121 control surface
Diffstat (limited to 'libs/surfaces/cc121/cc121.cc')
-rw-r--r-- | libs/surfaces/cc121/cc121.cc | 1290 |
1 files changed, 1290 insertions, 0 deletions
diff --git a/libs/surfaces/cc121/cc121.cc b/libs/surfaces/cc121/cc121.cc new file mode 100644 index 0000000000..5e68b7b446 --- /dev/null +++ b/libs/surfaces/cc121/cc121.cc @@ -0,0 +1,1290 @@ +/* + Copyright (C) 2015 Paul Davis + Copyright (C) 2016 W.P. van Paassen + + Thanks to Rolf Meyerhoff for reverse engineering the CC121 protocol. + + 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 <cstdlib> +#include <sstream> +#include <algorithm> + +#include <stdint.h> + +#include <glibmm/fileutils.h> +#include <glibmm/miscutils.h> + +#include "pbd/error.h" +#include "pbd/failed_constructor.h" +#include "pbd/file_utils.h" +#include "pbd/pthread_utils.h" +#include "pbd/compose.h" +#include "pbd/xml++.h" + +#include "midi++/port.h" + +#include "control_protocol/basic_ui.h" + +#include "ardour/async_midi_port.h" +#include "ardour/audioengine.h" +#include "ardour/amp.h" +#include "ardour/bundle.h" +#include "ardour/controllable_descriptor.h" +#include "ardour/debug.h" +#include "ardour/filesystem_paths.h" +#include "ardour/midi_port.h" +#include "ardour/midiport_manager.h" +#include "ardour/monitor_processor.h" +#include "ardour/profile.h" +#include "ardour/rc_configuration.h" +#include "ardour/record_enable_control.h" +#include "ardour/stripable.h" +#include "ardour/session.h" +#include "ardour/session_configuration.h" +#include "ardour/track.h" + +#include "cc121.h" + +using namespace ARDOUR; +using namespace ArdourSurface; +using namespace PBD; +using namespace Glib; +using namespace std; + +#include "pbd/i18n.h" + +#include "pbd/abstract_ui.cc" // instantiate template + +CC121::CC121 (Session& s) + : ControlProtocol (s, _("Steinberg CC121")) + , AbstractUI<CC121Request> (name()) + , gui (0) + , connection_state (ConnectionState (0)) + , _device_active (false) + , fader_msb (0) + , fader_lsb (0) + , fader_is_touched (false) + , _jogmode(scroll) + , button_state (ButtonState (0)) + , blink_state (false) + , rec_enable_state (false) +{ + last_encoder_time = 0; + + boost::shared_ptr<ARDOUR::Port> inp; + boost::shared_ptr<ARDOUR::Port> outp; + + inp = AudioEngine::instance()->register_input_port (DataType::MIDI, "CC121 Recv", true); + outp = AudioEngine::instance()->register_output_port (DataType::MIDI, "CC121 Send", true); + + _input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(inp); + _output_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(outp); + + if (_input_port == 0 || _output_port == 0) { + throw failed_constructor(); + } + + _input_bundle.reset (new ARDOUR::Bundle (_("CC121 Support (Receive)"), true)); + _output_bundle.reset (new ARDOUR::Bundle (_("CC121 Support (Send) "), false)); + + _input_bundle->add_channel ( + inp->name(), + ARDOUR::DataType::MIDI, + session->engine().make_port_name_non_relative (inp->name()) + ); + + _output_bundle->add_channel ( + outp->name(), + ARDOUR::DataType::MIDI, + session->engine().make_port_name_non_relative (outp->name()) + ); + + + StripableSelectionChanged.connect (selection_connection, MISSING_INVALIDATOR, boost::bind (&CC121::gui_track_selection_changed, this, _1), this); + + /* Catch port connections and disconnections */ + ARDOUR::AudioEngine::instance()->PortConnectedOrDisconnected.connect (port_connection, MISSING_INVALIDATOR, boost::bind (&CC121::connection_handler, this, _1, _2, _3, _4, _5), this); + buttons.insert (std::make_pair (EButton, Button (*this, _("EButton"), EButton))); + buttons.insert (std::make_pair (OpenVST, Button (*this, _("OpenVST"), OpenVST))); + buttons.insert (std::make_pair (InputMonitor, Button (*this, _("InputMonitor"), InputMonitor))); + buttons.insert (std::make_pair (EQ1Enable, Button (*this, _("EQ1Enable"), EQ1Enable))); + buttons.insert (std::make_pair (EQ2Enable, Button (*this, _("EQ2Enable"), EQ2Enable))); + buttons.insert (std::make_pair (EQ3Enable, Button (*this, _("EQ3Enable"), EQ3Enable))); + buttons.insert (std::make_pair (EQ4Enable, Button (*this, _("EQ4Enable"), EQ4Enable))); + buttons.insert (std::make_pair (EQType, Button (*this, _("EQType"), EQType))); + buttons.insert (std::make_pair (AllBypass, Button (*this, _("AllBypass"), AllBypass))); + buttons.insert (std::make_pair (Function1, Button (*this, _("Function1"), Function1))); + buttons.insert (std::make_pair (Function2, Button (*this, _("Function2"), Function2))); + buttons.insert (std::make_pair (Function3, Button (*this, _("Function3"), Function3))); + buttons.insert (std::make_pair (Function4, Button (*this, _("Function4"), Function4))); + buttons.insert (std::make_pair (Value, Button (*this, _("Value"), Value))); + buttons.insert (std::make_pair (Jog, Button (*this, _("Jog"), Jog))); + buttons.insert (std::make_pair (Lock, Button (*this, _("Lock"), Lock))); + buttons.insert (std::make_pair (ToStart, Button (*this, _("ToStart"), ToStart))); + buttons.insert (std::make_pair (ToEnd, Button (*this, _("ToEnd"), ToEnd))); + buttons.insert (std::make_pair (Mute, Button (*this, _("Mute"), Mute))); + buttons.insert (std::make_pair (Solo, Button (*this, _("Solo"), Solo))); + buttons.insert (std::make_pair (Rec, Button (*this, _("Rec"), Rec))); + buttons.insert (std::make_pair (Left, Button (*this, _("Left"), Left))); + buttons.insert (std::make_pair (Right, Button (*this, _("Right"), Right))); + buttons.insert (std::make_pair (Output, Button (*this, _("Output"), Output))); + buttons.insert (std::make_pair (FP_Read, Button (*this, _("Read"), FP_Read))); + buttons.insert (std::make_pair (FP_Write, Button (*this, _("Write"), FP_Write))); + buttons.insert (std::make_pair (Loop, Button (*this, _("Loop"), Loop))); + buttons.insert (std::make_pair (Rewind, Button (*this, _("Rewind"), Rewind))); + buttons.insert (std::make_pair (Ffwd, Button (*this, _("Ffwd"), Ffwd))); + buttons.insert (std::make_pair (Stop, Button (*this, _("Stop"), Stop))); + buttons.insert (std::make_pair (Play, Button (*this, _("Play"), Play))); + buttons.insert (std::make_pair (RecEnable, Button (*this, _("RecEnable"), RecEnable))); + buttons.insert (std::make_pair (Footswitch, Button (*this, _("Footswitch"), Footswitch))); + buttons.insert (std::make_pair (FaderTouch, Button (*this, _("Fader (touch)"), FaderTouch))); + + get_button (Left).set_action ( boost::bind (&CC121::left, this), true); + get_button (Right).set_action ( boost::bind (&CC121::right, this), true); + + get_button (FP_Read).set_action (boost::bind (&CC121::read, this), true); + get_button (FP_Write).set_action (boost::bind (&CC121::write, this), true); + get_button (EButton).set_action (boost::bind (&CC121::touch, this), true); + get_button (OpenVST).set_action (boost::bind (&CC121::off, this), true); + + get_button (Play).set_action (boost::bind (&BasicUI::transport_play, this, true), true); + get_button (ToStart).set_action (boost::bind (&BasicUI::prev_marker, this), true); + get_button (ToEnd).set_action (boost::bind (&BasicUI::next_marker, this), true); + get_button (RecEnable).set_action (boost::bind (&BasicUI::rec_enable_toggle, this), true); + get_button (Stop).set_action (boost::bind (&BasicUI::transport_stop, this), true); + get_button (Ffwd).set_action (boost::bind (&BasicUI::ffwd, this), true); + + get_button (Rewind).set_action (boost::bind (&BasicUI::rewind, this), true); + get_button (Loop).set_action (boost::bind (&BasicUI::loop_toggle, this), true); + + get_button (Jog).set_action (boost::bind (&CC121::jog, this), true); + get_button (Mute).set_action (boost::bind (&CC121::mute, this), true); + get_button (Solo).set_action (boost::bind (&CC121::solo, this), true); + get_button (Rec).set_action (boost::bind (&CC121::rec_enable, this), true); + + get_button (InputMonitor).set_action (boost::bind (&CC121::input_monitor, this), true); +} + +CC121::~CC121 () +{ + all_lights_out (); + + if (_input_port) { + DEBUG_TRACE (DEBUG::CC121, string_compose ("unregistering input port %1\n", boost::shared_ptr<ARDOUR::Port>(_input_port)->name())); + AudioEngine::instance()->unregister_port (_input_port); + _input_port.reset (); + } + + if (_output_port) { + _output_port->drain (10000, 250000); /* check every 10 msecs, wait up to 1/4 second for the port to drain */ + DEBUG_TRACE (DEBUG::CC121, string_compose ("unregistering output port %1\n", boost::shared_ptr<ARDOUR::Port>(_output_port)->name())); + AudioEngine::instance()->unregister_port (_output_port); + _output_port.reset (); + } + + tear_down_gui (); + + /* stop event loop */ + DEBUG_TRACE (DEBUG::CC121, "BaseUI::quit ()\n"); + BaseUI::quit (); +} + +void* +CC121::request_factory (uint32_t num_requests) +{ + /* AbstractUI<T>::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 +CC121::start_midi_handling () +{ + /* handle buttons press */ + _input_port->parser()->channel_note_on[0].connect_same_thread (midi_connections, boost::bind (&CC121::button_press_handler, this, _1, _2)); + /* handle buttons release*/ + _input_port->parser()->channel_note_off[0].connect_same_thread (midi_connections, boost::bind (&CC121::button_release_handler, this, _1, _2)); + /* handle fader */ + _input_port->parser()->pitchbend.connect_same_thread (midi_connections, boost::bind (&CC121::fader_handler, this, _1, _2)); + /* handle encoder */ + _input_port->parser()->controller.connect_same_thread (midi_connections, boost::bind (&CC121::encoder_handler, this, _1, _2)); + + /* This connection means that whenever data is ready from the input + * port, the relevant thread will invoke our ::midi_input_handler() + * method, which will read the data, and invoke the parser. + */ + + _input_port->xthread().set_receive_handler (sigc::bind (sigc::mem_fun (this, &CC121::midi_input_handler), _input_port)); + _input_port->xthread().attach (main_loop()->get_context()); +} + +void +CC121::stop_midi_handling () +{ + midi_connections.drop_connections (); + + /* Note: the input handler is still active at this point, but we're no + * longer connected to any of the parser signals + */ +} + +void +CC121::do_request (CC121Request* req) +{ + if (req->type == CallSlot) { + + call_slot (MISSING_INVALIDATOR, req->the_slot); + + } else if (req->type == Quit) { + + stop (); + } +} + +int +CC121::stop () +{ + BaseUI::quit (); + + return 0; +} + +void +CC121::thread_init () +{ + struct sched_param rtparam; + + pthread_set_name (event_loop_name().c_str()); + + PBD::notify_event_loops_about_thread_creation (pthread_self(), event_loop_name(), 2048); + ARDOUR::SessionEvent::create_per_thread_pool (event_loop_name(), 128); + + memset (&rtparam, 0, sizeof (rtparam)); + rtparam.sched_priority = 9; /* XXX should be relative to audio (JACK) thread */ + + if (pthread_setschedparam (pthread_self(), SCHED_FIFO, &rtparam) != 0) { + // do we care? not particularly. + } +} + +void +CC121::all_lights_out () +{ + for (ButtonMap::iterator b = buttons.begin(); b != buttons.end(); ++b) { + b->second.set_led_state (_output_port, false); + } +} + +CC121::Button& +CC121::get_button (ButtonID id) const +{ + ButtonMap::const_iterator b = buttons.find (id); + assert (b != buttons.end()); + return const_cast<Button&>(b->second); +} + +void +CC121::button_press_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb) +{ + DEBUG_TRACE (DEBUG::CC121, string_compose ("button press event for ID %1 press ? %2\n", (int) tb->controller_number, (tb->value ? "yes" : "no"))); + + ButtonID id (ButtonID (tb->controller_number)); + Button& button (get_button (id)); + + buttons_down.insert (id); + ButtonState bs (ButtonState (0)); + + switch (id) { + case FaderTouch: + fader_is_touched = true; + if (_current_stripable) { + boost::shared_ptr<AutomationControl> gain = _current_stripable->gain_control (); + if (gain) { + framepos_t now = session->engine().sample_time(); + gain->start_touch (now); + } + } + break; + default: + break; + } + + if (bs) { + button_state = ButtonState (button_state|bs); + DEBUG_TRACE (DEBUG::CC121, string_compose ("reset button state to %1 using %2\n", button_state, (int) bs)); + } + + if (button.uses_flash()) { + button.set_led_state (_output_port, (int)tb->value); + } + + set<ButtonID>::iterator c = consumed.find (id); + + if (c == consumed.end()) { + button.invoke (button_state, true); + } else { + DEBUG_TRACE (DEBUG::CC121, "button was consumed, ignored\n"); + consumed.erase (c); + } +} + +void +CC121::button_release_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb) +{ + DEBUG_TRACE (DEBUG::CC121, string_compose ("button release event for ID %1 release ? %2\n", (int) tb->controller_number, (tb->value ? "yes" : "no"))); + + ButtonID id (ButtonID (tb->controller_number)); + Button& button (get_button (id)); + + buttons_down.erase (id); + button.timeout_connection.disconnect (); + + ButtonState bs (ButtonState (0)); + + switch (id) { + case FaderTouch: + fader_is_touched = false; + if (_current_stripable) { + boost::shared_ptr<AutomationControl> gain = _current_stripable->gain_control (); + if (gain) { + framepos_t now = session->engine().sample_time(); + gain->stop_touch (true, now); + } + } + break; + default: + break; + } + + if (bs) { + button_state = ButtonState (button_state&~bs); + DEBUG_TRACE (DEBUG::CC121, string_compose ("reset button state to %1 using %2\n", button_state, (int) bs)); + } + + if (button.uses_flash()) { + button.set_led_state (_output_port, (int)tb->value); + } + + set<ButtonID>::iterator c = consumed.find (id); + + if (c == consumed.end()) { + button.invoke (button_state, false); + } else { + DEBUG_TRACE (DEBUG::CC121, "button was consumed, ignored\n"); + consumed.erase (c); + } +} + +void +CC121::encoder_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb) +{ + DEBUG_TRACE (DEBUG::CC121, "encoder handler"); + + /* Extract absolute value*/ + float adj = static_cast<float>(tb->value & ~0x40); + + /* Get direction (negative values start at 0x40)*/ + float sign = (tb->value & 0x40) ? -1.0 : 1.0; + switch(tb->controller_number) { + case 0x10: + /* pan */ + DEBUG_TRACE (DEBUG::CC121, "PAN encoder"); + if (_current_stripable) { + /* Get amount of change (encoder clicks) * (change per click)*/ + /*Create an exponential curve*/ + float curve = sign * pow(adj, (1.0 + 10.0) / 10.0); + adj = curve * (31 / 1000.0); + ardour_pan_azimuth (adj); + } + break; + case 0x20: + /* EQ 1 Q */ + break; + case 0x21: + /* EQ 2 Q */ + break; + case 0x22: + /* EQ 3 Q */ + break; + case 0x23: + /* EQ 4 Q */ + break; + case 0x30: + /* EQ 1 Frequency */ + break; + case 0x31: + /* EQ 2 Frequency */ + break; + case 0x32: + /* EQ 3 Frequency */ + break; + case 0x33: + /* EQ 4 Frequency */ + break; + case 0x3C: + /* AI */ + if (sign < 0.0f) { + if (_jogmode == scroll) { + ScrollTimeline(-0.05); + } + else { + ZoomIn(); + } + } + else { + if (_jogmode == scroll) { + ScrollTimeline(0.05); + } + else { + ZoomOut(); + } + } + break; + case 0x40: + /* EQ 1 Gain */ + break; + case 0x41: + /* EQ 2 Gain */ + break; + case 0x42: + /* EQ 3 Gain */ + break; + case 0x43: + /* EQ 4 Gain */ + break; + case 0x50: + /* Value */ + break; + default: + break; + } +} + +void +CC121::fader_handler (MIDI::Parser &, MIDI::pitchbend_t pb) +{ + DEBUG_TRACE (DEBUG::CC121, "fader handler"); + + if (_current_stripable) { + boost::shared_ptr<AutomationControl> gain = _current_stripable->gain_control (); + if (gain) { + float val = gain->interface_to_internal (pb/16384.0); + /* even though the cc121 only controls a + single stripable at a time, allow the fader to + modify the group, if appropriate. + */ + _current_stripable->gain_control()->set_value (val, Controllable::UseGroup); + } + } +} + +int +CC121::set_active (bool yn) +{ + DEBUG_TRACE (DEBUG::CC121, string_compose("CC121::set_active init with yn: '%1'\n", yn)); + + if (yn == active()) { + return 0; + } + + if (yn) { + + /* start event loop */ + + BaseUI::run (); + + connect_session_signals (); + + Glib::RefPtr<Glib::TimeoutSource> blink_timeout = Glib::TimeoutSource::create (200); // milliseconds + blink_connection = blink_timeout->connect (sigc::mem_fun (*this, &CC121::blink)); + blink_timeout->attach (main_loop()->get_context()); + + Glib::RefPtr<Glib::TimeoutSource> heartbeat_timeout = Glib::TimeoutSource::create (800); // milliseconds + heartbeat_connection = heartbeat_timeout->connect (sigc::mem_fun (*this, &CC121::beat)); + heartbeat_timeout->attach (main_loop()->get_context()); + + Glib::RefPtr<Glib::TimeoutSource> periodic_timeout = Glib::TimeoutSource::create (100); // milliseconds + periodic_connection = periodic_timeout->connect (sigc::mem_fun (*this, &CC121::periodic)); + periodic_timeout->attach (main_loop()->get_context()); + + } else { + + BaseUI::quit (); + close (); + + } + + ControlProtocol::set_active (yn); + + DEBUG_TRACE (DEBUG::CC121, string_compose("CC121::set_active done with yn: '%1'\n", yn)); + + return 0; +} + +bool +CC121::periodic () +{ + if (!_current_stripable) { + return true; + } + + ARDOUR::AutoState gain_state = _current_stripable->gain_control()->automation_state(); + + if (gain_state == ARDOUR::Touch || gain_state == ARDOUR::Play) { + map_gain (); + } + + return true; +} + +void +CC121::stop_blinking (ButtonID id) +{ + blinkers.remove (id); + get_button (id).set_led_state (_output_port, false); +} + +void +CC121::start_blinking (ButtonID id) +{ + blinkers.push_back (id); + get_button (id).set_led_state (_output_port, true); +} + +bool +CC121::beat () +{ + MIDI::byte buf[8]; + + buf[0] = 0xf0; + buf[1] = 0x43; + buf[2] = 0x10; + buf[3] = 0x3e; + buf[4] = 0x15; + buf[5] = 0x00; + buf[6] = 0x01; + buf[7] = 0xF7; + + _output_port->write (buf, 8, 0); + + return true; +} + +bool +CC121::blink () +{ + blink_state = !blink_state; + + for (Blinkers::iterator b = blinkers.begin(); b != blinkers.end(); b++) { + get_button(*b).set_led_state (_output_port, blink_state); + } + + map_recenable_state (); + + return true; +} + +void +CC121::close () +{ + all_lights_out (); + + stop_midi_handling (); + session_connections.drop_connections (); + port_connection.disconnect (); + blink_connection.disconnect (); + heartbeat_connection.disconnect (); + selection_connection.disconnect (); + stripable_connections.drop_connections (); + +#if 0 + stripable_connections.drop_connections (); +#endif +} + +void +CC121::map_recenable_state () +{ + /* special case for RecEnable because its status can change as a + * confluence of unrelated parameters: (a) session rec-enable state (b) + * rec-enabled tracks. So we don't add the button to the blinkers list, + * we just call this: + * + * * from the blink callback + * * when the session tells us about a status change + * + * We do the last one so that the button changes state promptly rather + * than waiting for the next blink callback. The change in "blinking" + * based on having record-enabled tracks isn't urgent, and that happens + * during the blink callback. + */ + + bool onoff; + + switch (session->record_status()) { + case Session::Disabled: + onoff = false; + break; + case Session::Enabled: + onoff = blink_state; + break; + case Session::Recording: + if (session->have_rec_enabled_track ()) { + onoff = true; + } else { + onoff = blink_state; + } + break; + } + + if (onoff != rec_enable_state) { + get_button(RecEnable).set_led_state (_output_port, onoff); + rec_enable_state = onoff; + } +} + +void +CC121::map_transport_state () +{ + get_button (Loop).set_led_state (_output_port, session->get_play_loop()); + + float ts = session->transport_speed(); + + if (ts == 0) { + stop_blinking (Play); + } else if (fabs (ts) == 1.0) { + stop_blinking (Play); + get_button (Play).set_led_state (_output_port, true); + } else { + start_blinking (Play); + } + + get_button (Stop).set_led_state (_output_port, session->transport_stopped ()); + get_button (Rewind).set_led_state (_output_port, session->transport_speed() < 0.0); + get_button (Ffwd).set_led_state (_output_port, session->transport_speed() > 1.0); + get_button (Jog).set_led_state (_output_port, _jogmode == scroll); +} + +void +CC121::connect_session_signals() +{ + session->RecordStateChanged.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&CC121::map_recenable_state, this), this); + session->TransportStateChange.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&CC121::map_transport_state, this), this); +} + +bool +CC121::midi_input_handler (Glib::IOCondition ioc, boost::shared_ptr<ARDOUR::AsyncMIDIPort> port) +{ + DEBUG_TRACE (DEBUG::CC121, string_compose ("something happend on %1\n", boost::shared_ptr<MIDI::Port>(port)->name())); + + if (ioc & ~IO_IN) { + return false; + } + + if (ioc & IO_IN) { + + port->clear (); + DEBUG_TRACE (DEBUG::CC121, string_compose ("data available on %1\n", boost::shared_ptr<MIDI::Port>(port)->name())); + framepos_t now = session->engine().sample_time(); + port->parse (now); + } + + return true; +} + + +XMLNode& +CC121::get_state () +{ + XMLNode& node (ControlProtocol::get_state()); + + XMLNode* child; + + child = new XMLNode (X_("Input")); + child->add_child_nocopy (boost::shared_ptr<ARDOUR::Port>(_input_port)->get_state()); + node.add_child_nocopy (*child); + + + child = new XMLNode (X_("Output")); + child->add_child_nocopy (boost::shared_ptr<ARDOUR::Port>(_output_port)->get_state()); + node.add_child_nocopy (*child); + + /* Save action state for Function1..4, Lock, Value, EQnEnable, EQType, + * AllBypass and Footswitch buttons, since these + * are user controlled. We can only save named-action operations, since + * internal functions are just pointers to functions and hard to + * serialize without enumerating them all somewhere. + */ + + node.add_child_nocopy (get_button (Function1).get_state()); + node.add_child_nocopy (get_button (Function2).get_state()); + node.add_child_nocopy (get_button (Function3).get_state()); + node.add_child_nocopy (get_button (Function4).get_state()); + node.add_child_nocopy (get_button (Value).get_state()); + node.add_child_nocopy (get_button (Lock).get_state()); + node.add_child_nocopy (get_button (EQ1Enable).get_state()); + node.add_child_nocopy (get_button (EQ2Enable).get_state()); + node.add_child_nocopy (get_button (EQ3Enable).get_state()); + node.add_child_nocopy (get_button (EQ4Enable).get_state()); + node.add_child_nocopy (get_button (EQType).get_state()); + node.add_child_nocopy (get_button (AllBypass).get_state()); + node.add_child_nocopy (get_button (Footswitch).get_state()); + + return node; +} + +int +CC121::set_state (const XMLNode& node, int version) +{ + XMLNodeList nlist; + XMLNodeConstIterator niter; + XMLNode const* child; + + if (ControlProtocol::set_state (node, version)) { + return -1; + } + + if ((child = node.child (X_("Input"))) != 0) { + XMLNode* portnode = child->child (Port::state_node_name.c_str()); + if (portnode) { + boost::shared_ptr<ARDOUR::Port>(_input_port)->set_state (*portnode, version); + } + } + + if ((child = node.child (X_("Output"))) != 0) { + XMLNode* portnode = child->child (Port::state_node_name.c_str()); + if (portnode) { + boost::shared_ptr<ARDOUR::Port>(_output_port)->set_state (*portnode, version); + } + } + + for (XMLNodeList::const_iterator n = node.children().begin(); n != node.children().end(); ++n) { + if ((*n)->name() == X_("Button")) { + XMLProperty const * prop = (*n)->property (X_("id")); + if (!prop) { + continue; + } + int xid = atoi (prop->value()); + ButtonMap::iterator b = buttons.find (ButtonID (xid)); + if (b == buttons.end()) { + continue; + } + b->second.set_state (**n); + } + } + + return 0; +} + +bool +CC121::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn) +{ + DEBUG_TRACE (DEBUG::CC121, "CC121::connection_handler start\n"); + if (!_input_port || !_output_port) { + return false; + } + + string ni = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr<ARDOUR::Port>(_input_port)->name()); + string no = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr<ARDOUR::Port>(_output_port)->name()); + + if (ni == name1 || ni == name2) { + if (yn) { + connection_state |= InputConnected; + } else { + connection_state &= ~InputConnected; + } + } else if (no == name1 || no == name2) { + if (yn) { + connection_state |= OutputConnected; + } else { + connection_state &= ~OutputConnected; + } + } else { + DEBUG_TRACE (DEBUG::CC121, string_compose ("Connections between %1 and %2 changed, but I ignored it\n", name1, name2)); + /* not our ports */ + return false; + } + + if ((connection_state & (InputConnected|OutputConnected)) == (InputConnected|OutputConnected)) { + + /* XXX this is a horrible hack. Without a short sleep here, + something prevents the device wakeup messages from being + sent and/or the responses from being received. + */ + + g_usleep (100000); + DEBUG_TRACE (DEBUG::CC121, "device now connected for both input and output\n"); + connected (); + + } else { + DEBUG_TRACE (DEBUG::CC121, "Device disconnected (input or output or both) or not yet fully connected\n"); + _device_active = false; + } + + ConnectionChange (); /* emit signal for our GUI */ + + DEBUG_TRACE (DEBUG::CC121, "CC121::connection_handler end\n"); + + return true; /* connection status changed */ +} + +void +CC121::connected () +{ + DEBUG_TRACE (DEBUG::CC121, "connected"); + + _device_active = true; + + start_midi_handling (); + + all_lights_out (); + + /* catch up on state */ + + /* make sure that rec_enable_state is consistent with current device state */ + get_button (RecEnable).set_led_state (_output_port, rec_enable_state); + + map_transport_state (); + map_recenable_state (); +} + +void +CC121::Button::invoke (CC121::ButtonState bs, bool press) +{ + DEBUG_TRACE (DEBUG::CC121, string_compose ("invoke button %1 for %2 state %3%4%5\n", id, (press ? "press":"release"), hex, bs, dec)); + + ToDoMap::iterator x; + + if (press) { + if ((x = on_press.find (bs)) == on_press.end()) { + DEBUG_TRACE (DEBUG::CC121, string_compose ("no press action for button %1 state %2 @ %3 in %4\n", id, bs, this, &on_press)); + return; + } + } else { + if ((x = on_release.find (bs)) == on_release.end()) { + DEBUG_TRACE (DEBUG::CC121, string_compose ("no release action for button %1 state %2 @%3 in %4\n", id, bs, this, &on_release)); + return; + } + } + + switch (x->second.type) { + case NamedAction: + if (!x->second.action_name.empty()) { + fp.access_action (x->second.action_name); + } + break; + case InternalFunction: + if (x->second.function) { + x->second.function (); + } + } +} + +void +CC121::Button::set_action (string const& name, bool when_pressed, CC121::ButtonState bs) +{ + ToDo todo; + + todo.type = NamedAction; + + if (when_pressed) { + if (name.empty()) { + on_press.erase (bs); + } else { + DEBUG_TRACE (DEBUG::CC121, string_compose ("set button %1 to action %2 on press + %3%4%5\n", id, name, bs)); + todo.action_name = name; + on_press[bs] = todo; + } + } else { + if (name.empty()) { + on_release.erase (bs); + } else { + DEBUG_TRACE (DEBUG::CC121, string_compose ("set button %1 to action %2 on release + %3%4%5\n", id, name, bs)); + todo.action_name = name; + on_release[bs] = todo; + } + } +} + +string +CC121::Button::get_action (bool press, CC121::ButtonState bs) +{ + ToDoMap::iterator x; + + if (press) { + if ((x = on_press.find (bs)) == on_press.end()) { + return string(); + } + if (x->second.type != NamedAction) { + return string (); + } + return x->second.action_name; + } else { + if ((x = on_release.find (bs)) == on_release.end()) { + return string(); + } + if (x->second.type != NamedAction) { + return string (); + } + return x->second.action_name; + } +} + +void +CC121::Button::set_action (boost::function<void()> f, bool when_pressed, CC121::ButtonState bs) +{ + ToDo todo; + todo.type = InternalFunction; + + if (when_pressed) { + DEBUG_TRACE (DEBUG::CC121, string_compose ("set button %1 (%2) @ %5 to some functor on press + %3 in %4\n", id, name, bs, &on_press, this)); + todo.function = f; + on_press[bs] = todo; + } else { + DEBUG_TRACE (DEBUG::CC121, string_compose ("set button %1 (%2) @ %5 to some functor on release + %3\n", id, name, bs, this)); + todo.function = f; + on_release[bs] = todo; + } +} + +void +CC121::Button::set_led_state (boost::shared_ptr<MIDI::Port> port, bool onoff) +{ + MIDI::byte buf[3]; + DEBUG_TRACE(DEBUG::CC121, "Set Led State\n"); + buf[0] = 0x90; + buf[1] = id; + buf[2] = (onoff ? 0x7F:0x00); + port->write (buf, 3, 0); +} + +int +CC121::Button::set_state (XMLNode const& node) +{ + const XMLProperty* prop = node.property ("id"); + if (!prop) { + return -1; + } + + int xid = atoi (prop->value()); + if (xid != id) { + return -1; + } + + typedef pair<string,CC121::ButtonState> state_pair_t; + vector<state_pair_t> state_pairs; + + state_pairs.push_back (make_pair (string ("plain"), ButtonState (0))); + + for (vector<state_pair_t>::const_iterator sp = state_pairs.begin(); sp != state_pairs.end(); ++sp) { + string propname; + + propname = sp->first + X_("-press"); + if ((prop = node.property (propname)) != 0) { + set_action (prop->value(), true, sp->second); + } + + propname = sp->first + X_("-release"); + if ((prop = node.property (propname)) != 0) { + set_action (prop->value(), false, sp->second); + } + } + + return 0; +} + +XMLNode& +CC121::Button::get_state () const +{ + XMLNode* node = new XMLNode (X_("Button")); + char buf[16]; + snprintf (buf, sizeof (buf), "%d", id); + + node->add_property (X_("id"), buf); + + ToDoMap::const_iterator x; + ToDo null; + null.type = NamedAction; + + typedef pair<string,CC121::ButtonState> state_pair_t; + vector<state_pair_t> state_pairs; + + state_pairs.push_back (make_pair (string ("plain"), ButtonState (0))); + + for (vector<state_pair_t>::const_iterator sp = state_pairs.begin(); sp != state_pairs.end(); ++sp) { + if ((x = on_press.find (sp->second)) != on_press.end()) { + if (x->second.type == NamedAction) { + node->add_property (string (sp->first + X_("-press")).c_str(), x->second.action_name); + } + } + + if ((x = on_release.find (sp->second)) != on_release.end()) { + if (x->second.type == NamedAction) { + node->add_property (string (sp->first + X_("-release")).c_str(), x->second.action_name); + } + } + } + + return *node; +} + +void +CC121::gui_track_selection_changed (StripableNotificationListPtr stripables) +{ + boost::shared_ptr<Stripable> r; + + if (!stripables->empty()) { + r = stripables->front().lock(); + } + + set_current_stripable (r); +} + +void +CC121::drop_current_stripable () +{ + if (_current_stripable) { + if (_current_stripable == session->monitor_out()) { + set_current_stripable (session->master_out()); + } else { + set_current_stripable (boost::shared_ptr<Stripable>()); + } + } +} + +void +CC121::set_current_stripable (boost::shared_ptr<Stripable> r) +{ + stripable_connections.drop_connections (); + + _current_stripable = r; + + if (_current_stripable) { + _current_stripable->DropReferences.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&CC121::drop_current_stripable, this), this); + + _current_stripable->mute_control()->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&CC121::map_mute, this), this); + _current_stripable->solo_control()->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&CC121::map_solo, this), this); + + boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track> (_current_stripable); + if (t) { + t->rec_enable_control()->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&CC121::map_recenable, this), this); + } + + boost::shared_ptr<AutomationControl> control = _current_stripable->gain_control (); + if (control) { + control->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&CC121::map_gain, this), this); + control->alist()->automation_state_changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&CC121::map_auto, this), this); + } + + boost::shared_ptr<MonitorProcessor> mp = _current_stripable->monitor_control(); + if (mp) { + mp->cut_control()->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&CC121::map_cut, this), this); + } + } + + //ToDo: subscribe to the fader automation modes so we can light the LEDs + + map_stripable_state (); +} + +void +CC121::map_auto () +{ + boost::shared_ptr<AutomationControl> control = _current_stripable->gain_control (); + const AutoState as = control->automation_state (); + + switch (as) { + case ARDOUR::Play: + get_button (FP_Read).set_led_state (_output_port, true); + get_button (FP_Write).set_led_state (_output_port, false); + get_button (EButton).set_led_state (_output_port, false); + get_button (OpenVST).set_led_state (_output_port, false); + break; + case ARDOUR::Write: + get_button (FP_Read).set_led_state (_output_port, false); + get_button (FP_Write).set_led_state (_output_port, true); + get_button (EButton).set_led_state (_output_port, false); + get_button (OpenVST).set_led_state (_output_port, false); + break; + case ARDOUR::Touch: + get_button (EButton).set_led_state (_output_port, true); + get_button (FP_Read).set_led_state (_output_port, false); + get_button (FP_Write).set_led_state(_output_port, false); + get_button (OpenVST).set_led_state (_output_port, false); + break; + case ARDOUR::Off: + get_button (OpenVST).set_led_state (_output_port, true); + get_button (FP_Read).set_led_state (_output_port, false); + get_button (FP_Write).set_led_state (_output_port, false); + get_button (EButton).set_led_state (_output_port, false); + break; + } +} + +void +CC121::map_cut () +{ + boost::shared_ptr<MonitorProcessor> mp = _current_stripable->monitor_control(); + + if (mp) { + bool yn = mp->cut_all (); + if (yn) { + start_blinking (Mute); + } else { + stop_blinking (Mute); + } + } else { + stop_blinking (Mute); + } +} + +void +CC121::map_mute () +{ + if (_current_stripable) { + if (_current_stripable->mute_control()->muted()) { + stop_blinking (Mute); + get_button (Mute).set_led_state (_output_port, true); + } else if (_current_stripable->mute_control()->muted_by_others_soloing () || _current_stripable->mute_control()->muted_by_masters()) { + start_blinking (Mute); + } else { + stop_blinking (Mute); + } + } else { + stop_blinking (Mute); + } +} + +void +CC121::map_solo () +{ + if (_current_stripable) { + get_button (Solo).set_led_state (_output_port, _current_stripable->solo_control()->soloed()); + } else { + get_button (Solo).set_led_state (_output_port, false); + } +} + +void +CC121::map_recenable () +{ + boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track> (_current_stripable); + if (t) { + get_button (Rec).set_led_state (_output_port, t->rec_enable_control()->get_value()); + } else { + get_button (Rec).set_led_state (_output_port, false); + } +} + +void +CC121::map_gain () +{ + if (fader_is_touched) { + /* Do not send fader moves while the user is touching the fader */ + return; + } + + if (!_current_stripable) { + return; + } + + boost::shared_ptr<AutomationControl> control = _current_stripable->gain_control (); + double val; + + if (!control) { + val = 0.0; + } else { + val = control->internal_to_interface (control->get_value ()); + } + + float fval = (val* 16384.0); + if (fval <0.0) + fval = 0.0; + else if (fval > 16383.0) + fval = 16383.0; + int ival = (int)(fval + 0.5); + + MIDI::byte buf[3]; + + buf[0] = 0xE0; + buf[1] = ival & 0x7F; + buf[2] = (ival >> 7) & 0x7F; + + _output_port->write (buf, 3, 0); +} + +void +CC121::map_stripable_state () +{ + if (!_current_stripable) { + stop_blinking (Mute); + stop_blinking (Solo); + get_button (Rec).set_led_state (_output_port, false); + } else { + map_solo (); + map_recenable (); + map_gain (); + map_auto (); + + if (_current_stripable == session->monitor_out()) { + map_cut (); + } else { + map_mute (); + } + } +} + +list<boost::shared_ptr<ARDOUR::Bundle> > +CC121::bundles () +{ + list<boost::shared_ptr<ARDOUR::Bundle> > b; + + if (_input_bundle) { + b.push_back (_input_bundle); + b.push_back (_output_bundle); + } + + return b; +} + +boost::shared_ptr<Port> +CC121::output_port() +{ + return _output_port; +} + +boost::shared_ptr<Port> +CC121::input_port() +{ + return _input_port; +} + +void +CC121::set_action (ButtonID id, std::string const& action_name, bool on_press, ButtonState bs) +{ + get_button(id).set_action (action_name, on_press, bs); +} + +string +CC121::get_action (ButtonID id, bool press, ButtonState bs) +{ + return get_button(id).get_action (press, bs); +} |