View Issue Details
| ID | Project | Category | View Status | Date Submitted | Last Update |
|---|---|---|---|---|---|
| 0007065 | ardour | other | public | 2016-10-09 16:13 | 2016-10-13 18:12 |
| Reporter | paas44 | Assigned To | |||
| Priority | normal | Severity | minor | Reproducibility | N/A |
| Status | closed | Resolution | fixed | ||
| Product Version | 5.4 | ||||
| Summary | 0007065: Patch for Steinberg's CC121 midi controller | ||||
| Description | Attached a working version for Steinberg's CC121 midi controller. Enjoy! | ||||
| Tags | No tags attached. | ||||
|
|
cc121.diff (120,020 bytes)
diff --git a/gtk2_ardour/icons/cc121.png b/gtk2_ardour/icons/cc121.png
new file mode 100644
index 0000000000000000000000000000000000000000..098ed8eb11f86891b084c1575c47043b8bf8bb6a
GIT binary patch
literal 26938
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
RGgklr002ovPDHLkV1kK9xrP7$
literal 0
HcmV?d00001
diff --git a/libs/ardour/ardour/debug.h b/libs/ardour/ardour/debug.h
index 0c259b480f25c1d3cdb30f9d7226edf1f3768fed..0b7e5fefb5be80699545ab5b6360f82b93b634f8 100644
--- a/libs/ardour/ardour/debug.h
+++ b/libs/ardour/ardour/debug.h
@@ -80,6 +80,7 @@ namespace PBD {
LIBARDOUR_API extern DebugBits BackendPorts;
LIBARDOUR_API extern DebugBits VSTCallbacks;
LIBARDOUR_API extern DebugBits FaderPort;
+ LIBARDOUR_API extern DebugBits CC121;
LIBARDOUR_API extern DebugBits VCA;
LIBARDOUR_API extern DebugBits Push2;
diff --git a/libs/ardour/debug.cc b/libs/ardour/debug.cc
index f52181611dd047cadaa08d2a8defa6933e1a5643..8c88dd4d1d34a56194df6d123e3598ae8e29517e 100644
--- a/libs/ardour/debug.cc
+++ b/libs/ardour/debug.cc
@@ -77,5 +77,6 @@ PBD::DebugBits PBD::DEBUG::BackendThreads = PBD::new_debug_bit ("backendthreads"
PBD::DebugBits PBD::DEBUG::BackendPorts = PBD::new_debug_bit ("backendports");
PBD::DebugBits PBD::DEBUG::VSTCallbacks = PBD::new_debug_bit ("vstcallbacks");
PBD::DebugBits PBD::DEBUG::FaderPort = PBD::new_debug_bit ("faderport");
+PBD::DebugBits PBD::DEBUG::CC121 = PBD::new_debug_bit ("cc121");
PBD::DebugBits PBD::DEBUG::VCA = PBD::new_debug_bit ("vca");
PBD::DebugBits PBD::DEBUG::Push2 = PBD::new_debug_bit ("push2");
diff --git a/libs/surfaces/cc121/cc121.cc b/libs/surfaces/cc121/cc121.cc
new file mode 100644
index 0000000000000000000000000000000000000000..5e68b7b4464992c31d2e46387fa5a627d06a77fa
--- /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);
+}
diff --git a/libs/surfaces/cc121/cc121.h b/libs/surfaces/cc121/cc121.h
new file mode 100644
index 0000000000000000000000000000000000000000..a5f0363a224b98e4bbfe23e6f201b9c76cd99312
--- /dev/null
+++ b/libs/surfaces/cc121/cc121.h
@@ -0,0 +1,342 @@
+/*
+ Copyright (C) 2006 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.
+
+*/
+
+#ifndef ardour_surface_cc121_h
+#define ardour_surface_cc121_h
+
+#include <list>
+#include <map>
+#include <set>
+#include <glibmm/threads.h>
+
+#define ABSTRACT_UI_EXPORTS
+#include "pbd/abstract_ui.h"
+
+#include "ardour/types.h"
+
+#include "control_protocol/control_protocol.h"
+
+namespace PBD {
+ class Controllable;
+ class ControllableDescriptor;
+}
+
+#include <midi++/types.h>
+
+//#include "pbd/signals.h"
+
+
+//#include "midi_byte_array.h"
+#include "types.h"
+
+#include "glibmm/main.h"
+
+namespace MIDI {
+ class Parser;
+ class Port;
+}
+
+
+namespace ARDOUR {
+ class AsyncMIDIPort;
+ class Bundle;
+ class Port;
+ class Session;
+ class MidiPort;
+}
+
+
+class MIDIControllable;
+class MIDIFunction;
+class MIDIAction;
+
+namespace ArdourSurface {
+
+struct CC121Request : public BaseUI::BaseRequestObject {
+public:
+ CC121Request () {}
+ ~CC121Request () {}
+};
+
+class CC121 : public ARDOUR::ControlProtocol, public AbstractUI<CC121Request> {
+ public:
+ CC121 (ARDOUR::Session&);
+ virtual ~CC121();
+
+ int set_active (bool yn);
+
+ /* we probe for a device when our ports are connected. Before that,
+ there's no way to know if the device exists or not.
+ */
+ static bool probe() { return true; }
+ static void* request_factory (uint32_t);
+
+ XMLNode& get_state ();
+ int set_state (const XMLNode&, int version);
+
+ bool has_editor () const { return true; }
+ void* get_gui () const;
+ void tear_down_gui ();
+
+
+ /* Note: because the CC121 speaks an inherently duplex protocol,
+ we do not implement get/set_feedback() since this aspect of
+ support for the protocol is not optional.
+ */
+
+ void do_request (CC121Request*);
+ int stop ();
+
+ void thread_init ();
+
+ PBD::Signal0<void> ConnectionChange;
+
+ boost::shared_ptr<ARDOUR::Port> input_port();
+ boost::shared_ptr<ARDOUR::Port> output_port();
+
+ enum ButtonID {
+ Rec = 0x00,
+ Solo = 0x08,
+ Mute = 0x10,
+ Left = 0x30,
+ Right = 0x31,
+ EButton = 0x33,
+ Function1 = 0x36,
+ Function2 = 0x37,
+ Function3 = 0x38,
+ Function4 = 0x39,
+ Value = 0x3A,
+ Footswitch = 0x3B,
+ FP_Read = 0x4A,
+ FP_Write = 0x4B,
+ Loop = 0x56,
+ ToStart = 0x58,
+ ToEnd = 0x5A,
+ Rewind = 0x5B,
+ Ffwd = 0x5C,
+ Stop = 0x5D,
+ Play = 0x5E,
+ RecEnable = 0x5F,
+ FaderTouch = 0x68,
+ EQ1Enable = 0x70,
+ EQ2Enable = 0x71,
+ EQ3Enable = 0x72,
+ EQ4Enable = 0x73,
+ EQType = 0x74,
+ AllBypass = 0x75,
+ Jog = 0x76,
+ Lock = 0x77,
+ InputMonitor = 0x78,
+ OpenVST = 0x79,
+ Output = 22
+ };
+
+ enum ButtonState {
+ ShiftDown = 0x1,
+ RewindDown = 0x2,
+ StopDown = 0x4,
+ UserDown = 0x8,
+ LongPress = 0x10
+ };
+
+ void set_action (ButtonID, std::string const& action_name, bool on_press, CC121::ButtonState = ButtonState (0));
+ std::string get_action (ButtonID, bool on_press, CC121::ButtonState = ButtonState (0));
+
+ std::list<boost::shared_ptr<ARDOUR::Bundle> > bundles ();
+
+ private:
+ boost::shared_ptr<ARDOUR::Stripable> _current_stripable;
+ boost::weak_ptr<ARDOUR::Stripable> pre_master_stripable;
+ boost::weak_ptr<ARDOUR::Stripable> pre_monitor_stripable;
+
+ boost::shared_ptr<ARDOUR::AsyncMIDIPort> _input_port;
+ boost::shared_ptr<ARDOUR::AsyncMIDIPort> _output_port;
+
+ // Bundle to represent our input ports
+ boost::shared_ptr<ARDOUR::Bundle> _input_bundle;
+ // Bundle to represent our output ports
+ boost::shared_ptr<ARDOUR::Bundle> _output_bundle;
+
+ PBD::ScopedConnectionList midi_connections;
+
+ bool midi_input_handler (Glib::IOCondition ioc, boost::shared_ptr<ARDOUR::AsyncMIDIPort> port);
+
+ mutable void *gui;
+ void build_gui ();
+
+ bool connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn);
+ PBD::ScopedConnection port_connection;
+
+ enum ConnectionState {
+ InputConnected = 0x1,
+ OutputConnected = 0x2
+ };
+
+ int connection_state;
+ void connected ();
+ bool _device_active;
+ int fader_msb;
+ int fader_lsb;
+ bool fader_is_touched;
+ enum JogMode { scroll=1, zoom=2 };
+ JogMode _jogmode;
+
+ ARDOUR::microseconds_t last_encoder_time;
+ int last_good_encoder_delta;
+ int last_encoder_delta, last_last_encoder_delta;
+
+ void button_press_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb);
+ void button_release_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb);
+ void fader_handler (MIDI::Parser &, MIDI::pitchbend_t pb);
+ void encoder_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb);
+ /* void fader_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb);*/
+
+ ButtonState button_state;
+
+ friend class Button;
+
+ class Button {
+ public:
+
+ enum ActionType {
+ NamedAction,
+ InternalFunction,
+ };
+
+ Button (CC121& f, std::string const& str, ButtonID i)
+ : fp (f)
+ , name (str)
+ , id (i)
+ , flash (false)
+ {}
+
+ void set_action (std::string const& action_name, bool on_press, CC121::ButtonState = ButtonState (0));
+ void set_action (boost::function<void()> function, bool on_press, CC121::ButtonState = ButtonState (0));
+ std::string get_action (bool press, CC121::ButtonState bs = ButtonState (0));
+
+ void set_led_state (boost::shared_ptr<MIDI::Port>, bool onoff);
+ void invoke (ButtonState bs, bool press);
+ bool uses_flash () const { return flash; }
+ void set_flash (bool yn) { flash = yn; }
+
+ XMLNode& get_state () const;
+ int set_state (XMLNode const&);
+
+ sigc::connection timeout_connection;
+
+ private:
+ CC121& fp;
+ std::string name;
+ ButtonID id;
+ bool flash;
+
+ struct ToDo {
+ ActionType type;
+ /* could be a union if boost::function didn't require a
+ * constructor
+ */
+ std::string action_name;
+ boost::function<void()> function;
+ };
+
+ typedef std::map<CC121::ButtonState,ToDo> ToDoMap;
+ ToDoMap on_press;
+ ToDoMap on_release;
+ };
+
+ typedef std::map<ButtonID,Button> ButtonMap;
+
+ ButtonMap buttons;
+ Button& get_button (ButtonID) const;
+
+ std::set<ButtonID> buttons_down;
+ std::set<ButtonID> consumed;
+
+ void all_lights_out ();
+ void close ();
+ void start_midi_handling ();
+ void stop_midi_handling ();
+
+ PBD::ScopedConnectionList session_connections;
+ void connect_session_signals ();
+ void map_recenable_state ();
+ void map_transport_state ();
+
+ sigc::connection periodic_connection;
+ bool periodic ();
+
+ sigc::connection heartbeat_connection;
+ sigc::connection blink_connection;
+ typedef std::list<ButtonID> Blinkers;
+ Blinkers blinkers;
+ bool blink_state;
+ bool blink ();
+ bool beat ();
+ void start_blinking (ButtonID);
+ void stop_blinking (ButtonID);
+
+ void set_current_stripable (boost::shared_ptr<ARDOUR::Stripable>);
+ void drop_current_stripable ();
+ void use_master ();
+ void use_monitor ();
+ void gui_track_selection_changed (ARDOUR::StripableNotificationListPtr);
+ PBD::ScopedConnection selection_connection;
+ PBD::ScopedConnectionList stripable_connections;
+
+ void map_stripable_state ();
+ void map_solo ();
+ void map_mute ();
+ bool rec_enable_state;
+ void map_recenable ();
+ void map_gain ();
+ void map_cut ();
+ void map_auto ();
+
+ /* operations (defined in operations.cc) */
+
+ void read ();
+ void write ();
+
+ void input_monitor ();
+ void left ();
+ void right ();
+
+ void touch ();
+ void off ();
+
+ void undo ();
+ void redo ();
+ void solo ();
+ void mute ();
+ void jog ();
+ void rec_enable ();
+
+ void ardour_pan_azimuth (float);
+ void ardour_pan_width (float);
+ void mixbus_pan (float);
+
+ void punch ();
+};
+
+}
+
+#endif /* ardour_surface_cc121_h */
diff --git a/libs/surfaces/cc121/cc121_interface.cc b/libs/surfaces/cc121/cc121_interface.cc
new file mode 100644
index 0000000000000000000000000000000000000000..7483b39d3593202660a3d15b75b4cde10f542db2
--- /dev/null
+++ b/libs/surfaces/cc121/cc121_interface.cc
@@ -0,0 +1,80 @@
+/*
+ Copyright (C) 2012 Paul Davis
+ Copyright (C) 2016 W.P. van Paassen
+
+ 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 <pbd/failed_constructor.h>
+
+#include "control_protocol/control_protocol.h"
+#include "cc121.h"
+
+using namespace ARDOUR;
+using namespace ArdourSurface;
+
+static ControlProtocol*
+new_cc121_midi_protocol (ControlProtocolDescriptor* /*descriptor*/, Session* s)
+{
+ CC121* fp;
+
+ try {
+ fp = new CC121 (*s);
+ } catch (failed_constructor& err) {
+ return 0;
+ }
+
+ if (fp->set_active (true)) {
+ delete fp;
+ return 0;
+ }
+
+ return fp;
+}
+
+static void
+delete_cc121_midi_protocol (ControlProtocolDescriptor* /*descriptor*/, ControlProtocol* cp)
+{
+ delete cp;
+}
+
+static bool
+probe_cc121_midi_protocol (ControlProtocolDescriptor* /*descriptor*/)
+{
+ return CC121::probe ();
+}
+
+static void*
+cc121_request_buffer_factory (uint32_t num_requests)
+{
+ return CC121::request_factory (num_requests);
+}
+
+static ControlProtocolDescriptor cc121_midi_descriptor = {
+ /*name : */ "Steinberg CC121",
+ /*id : */ "uri://ardour.org/surfaces/cc121:0",
+ /*ptr : */ 0,
+ /*module : */ 0,
+ /*mandatory : */ 0,
+ /*supports_feedback : */ true,
+ /*probe : */ probe_cc121_midi_protocol,
+ /*initialize : */ new_cc121_midi_protocol,
+ /*destroy : */ delete_cc121_midi_protocol,
+ /*request_buffer_factory */ cc121_request_buffer_factory
+};
+
+extern "C" ARDOURSURFACE_API ControlProtocolDescriptor* protocol_descriptor () { return &cc121_midi_descriptor; }
+
diff --git a/libs/surfaces/cc121/gui.cc b/libs/surfaces/cc121/gui.cc
new file mode 100644
index 0000000000000000000000000000000000000000..ca06887102c1050d03c92da42bfc139dd56ce027
--- /dev/null
+++ b/libs/surfaces/cc121/gui.cc
@@ -0,0 +1,629 @@
+/*
+ Copyright (C) 2015 Paul Davis
+ Copyright (C) 2016 W.P. van Paassen
+
+ 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 <gtkmm/alignment.h>
+#include <gtkmm/label.h>
+#include <gtkmm/liststore.h>
+
+#include "pbd/unwind.h"
+#include "pbd/strsplit.h"
+#include "pbd/file_utils.h"
+
+#include "gtkmm2ext/bindings.h"
+#include "gtkmm2ext/gtk_ui.h"
+#include "gtkmm2ext/gui_thread.h"
+#include "gtkmm2ext/utils.h"
+
+#include "ardour/audioengine.h"
+#include "ardour/filesystem_paths.h"
+
+#include "cc121.h"
+#include "gui.h"
+
+#include "pbd/i18n.h"
+
+using namespace PBD;
+using namespace ARDOUR;
+using namespace ArdourSurface;
+using namespace std;
+using namespace Gtk;
+using namespace Gtkmm2ext;
+
+void*
+CC121::get_gui () const
+{
+ if (!gui) {
+ const_cast<CC121*>(this)->build_gui ();
+ }
+ static_cast<Gtk::VBox*>(gui)->show_all();
+ return gui;
+}
+
+void
+CC121::tear_down_gui ()
+{
+ if (gui) {
+ Gtk::Widget *w = static_cast<Gtk::VBox*>(gui)->get_parent();
+ if (w) {
+ w->hide();
+ delete w;
+ }
+ }
+ delete static_cast<CC121GUI*> (gui);
+ gui = 0;
+}
+
+void
+CC121::build_gui ()
+{
+ gui = (void*) new CC121GUI (*this);
+}
+
+/*--------------------*/
+
+CC121GUI::CC121GUI (CC121& p)
+ : fp (p)
+ , table (2, 5)
+ , action_table (5, 4)
+ , ignore_active_change (false)
+{
+ set_border_width (12);
+
+ table.set_row_spacings (4);
+ table.set_col_spacings (6);
+ table.set_border_width (12);
+ table.set_homogeneous (false);
+
+ std::string data_file_path;
+ string name = "cc121.png";
+ Searchpath spath(ARDOUR::ardour_data_search_path());
+ spath.add_subdirectory_to_paths ("icons");
+ find_file (spath, name, data_file_path);
+ if (!data_file_path.empty()) {
+ image.set (data_file_path);
+ hpacker.pack_start (image, false, false);
+ }
+
+ Gtk::Label* l;
+ Gtk::Alignment* align;
+ int row = 0;
+ int action_row = 1;
+
+ input_combo.pack_start (midi_port_columns.short_name);
+ output_combo.pack_start (midi_port_columns.short_name);
+
+ input_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &CC121GUI::active_port_changed), &input_combo, true));
+ output_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &CC121GUI::active_port_changed), &output_combo, false));
+
+ l = manage (new Gtk::Label);
+ l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Incoming MIDI on:")));
+ l->set_alignment (1.0, 0.5);
+ table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
+ table.attach (input_combo, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
+ row++;
+
+ l = manage (new Gtk::Label);
+ l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Outgoing MIDI on:")));
+ l->set_alignment (1.0, 0.5);
+ table.attach (*l, 0, 1, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
+ table.attach (output_combo, 1, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
+ row++;
+
+ build_available_action_menu ();
+
+ build_user_action_combo (function1_combo, CC121::ButtonState(0), CC121::Function1);
+ build_user_action_combo (function2_combo, CC121::ButtonState(0), CC121::Function2);
+ build_user_action_combo (function3_combo, CC121::ButtonState(0), CC121::Function3);
+ build_user_action_combo (function4_combo, CC121::ButtonState(0), CC121::Function4);
+ build_user_action_combo (value_combo, CC121::ButtonState(0), CC121::Value);
+ build_user_action_combo (lock_combo, CC121::ButtonState(0), CC121::Lock);
+ build_user_action_combo (eq1_combo, CC121::ButtonState(0), CC121::EQ1Enable);
+ build_user_action_combo (eq2_combo, CC121::ButtonState(0), CC121::EQ2Enable);
+ build_user_action_combo (eq3_combo, CC121::ButtonState(0), CC121::EQ3Enable);
+ build_user_action_combo (eq4_combo, CC121::ButtonState(0), CC121::EQ4Enable);
+ build_user_action_combo (eqtype_combo, CC121::ButtonState(0), CC121::EQType);
+ build_user_action_combo (allbypass_combo, CC121::ButtonState(0), CC121::AllBypass);
+ build_foot_action_combo (foot_combo, CC121::ButtonState(0));
+ action_table.set_row_spacings (4);
+ action_table.set_col_spacings (6);
+ action_table.set_border_width (12);
+ action_table.set_homogeneous (false);
+
+ l = manage (new Gtk::Label);
+ l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Function 1")));
+ l->set_alignment (1.0, 0.5);
+ action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ align = manage (new Alignment);
+ align->set (0.0, 0.5);
+ align->add (function1_combo);
+ action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ action_row++;
+
+ l = manage (new Gtk::Label);
+ l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Function 2")));
+ l->set_alignment (1.0, 0.5);
+ action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ align = manage (new Alignment);
+ align->set (0.0, 0.5);
+ align->add (function2_combo);
+ action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ action_row++;
+
+ l = manage (new Gtk::Label);
+ l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Function 3")));
+ l->set_alignment (1.0, 0.5);
+ action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ align = manage (new Alignment);
+ align->set (0.0, 0.5);
+ align->add (function3_combo);
+ action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ action_row++;
+
+ l = manage (new Gtk::Label);
+ l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Function 4")));
+ l->set_alignment (1.0, 0.5);
+ action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ align = manage (new Alignment);
+ align->set (0.0, 0.5);
+ align->add (function4_combo);
+ action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ action_row++;
+
+ l = manage (new Gtk::Label);
+ l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Value")));
+ l->set_alignment (1.0, 0.5);
+ action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ align = manage (new Alignment);
+ align->set (0.0, 0.5);
+ align->add (value_combo);
+ action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ action_row++;
+
+ l = manage (new Gtk::Label);
+ l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Lock")));
+ l->set_alignment (1.0, 0.5);
+ action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ align = manage (new Alignment);
+ align->set (0.0, 0.5);
+ align->add (lock_combo);
+ action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ action_row++;
+
+ l = manage (new Gtk::Label);
+ l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("EQ1")));
+ l->set_alignment (1.0, 0.5);
+ action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ align = manage (new Alignment);
+ align->set (0.0, 0.5);
+ align->add (eq1_combo);
+ action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ action_row++;
+
+ l = manage (new Gtk::Label);
+ l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("EQ2")));
+ l->set_alignment (1.0, 0.5);
+ action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ align = manage (new Alignment);
+ align->set (0.0, 0.5);
+ align->add (eq2_combo);
+ action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ action_row++;
+
+ l = manage (new Gtk::Label);
+ l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("EQ3")));
+ l->set_alignment (1.0, 0.5);
+ action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ align = manage (new Alignment);
+ align->set (0.0, 0.5);
+ align->add (eq3_combo);
+ action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ action_row++;
+
+ l = manage (new Gtk::Label);
+ l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("EQ4")));
+ l->set_alignment (1.0, 0.5);
+ action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ align = manage (new Alignment);
+ align->set (0.0, 0.5);
+ align->add (eq4_combo);
+ action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ action_row++;
+
+ l = manage (new Gtk::Label);
+ l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("EQType")));
+ l->set_alignment (1.0, 0.5);
+ action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ align = manage (new Alignment);
+ align->set (0.0, 0.5);
+ align->add (eqtype_combo);
+ action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ action_row++;
+
+ l = manage (new Gtk::Label);
+ l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("AllBypass")));
+ l->set_alignment (1.0, 0.5);
+ action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ align = manage (new Alignment);
+ align->set (0.0, 0.5);
+ align->add (allbypass_combo);
+ action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ action_row++;
+
+ l = manage (new Gtk::Label);
+ l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Footswitch")));
+ l->set_alignment (1.0, 0.5);
+ action_table.attach (*l, 0, 1, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ align = manage (new Alignment);
+ align->set (0.0, 0.5);
+ align->add (foot_combo);
+ action_table.attach (*align, 1, 2, action_row, action_row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ action_row++;
+
+ table.attach (action_table, 0, 5, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions (0));
+ row++;
+
+ hpacker.pack_start (table, true, true);
+ pack_start (hpacker, false, false);
+
+ /* update the port connection combos */
+
+ update_port_combos ();
+
+ /* catch future changes to connection state */
+
+ fp.ConnectionChange.connect (connection_change_connection, invalidator (*this), boost::bind (&CC121GUI::connection_handler, this), gui_context());
+}
+
+CC121GUI::~CC121GUI ()
+{
+}
+
+void
+CC121GUI::connection_handler ()
+{
+ /* ignore all changes to combobox active strings here, because we're
+ updating them to match a new ("external") reality - we were called
+ because port connections have changed.
+ */
+
+ PBD::Unwinder<bool> ici (ignore_active_change, true);
+
+ update_port_combos ();
+}
+
+void
+CC121GUI::update_port_combos ()
+{
+ vector<string> midi_inputs;
+ vector<string> midi_outputs;
+
+ ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsOutput|ARDOUR::IsTerminal), midi_inputs);
+ ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsInput|ARDOUR::IsTerminal), midi_outputs);
+
+ Glib::RefPtr<Gtk::ListStore> input = build_midi_port_list (midi_inputs, true);
+ Glib::RefPtr<Gtk::ListStore> output = build_midi_port_list (midi_outputs, false);
+ bool input_found = false;
+ bool output_found = false;
+ int n;
+
+ input_combo.set_model (input);
+ output_combo.set_model (output);
+
+ Gtk::TreeModel::Children children = input->children();
+ Gtk::TreeModel::Children::iterator i;
+ i = children.begin();
+ ++i; /* skip "Disconnected" */
+
+
+ for (n = 1; i != children.end(); ++i, ++n) {
+ string port_name = (*i)[midi_port_columns.full_name];
+ if (fp.input_port()->connected_to (port_name)) {
+ input_combo.set_active (n);
+ input_found = true;
+ break;
+ }
+ }
+
+ if (!input_found) {
+ input_combo.set_active (0); /* disconnected */
+ }
+
+ children = output->children();
+ i = children.begin();
+ ++i; /* skip "Disconnected" */
+
+ for (n = 1; i != children.end(); ++i, ++n) {
+ string port_name = (*i)[midi_port_columns.full_name];
+ if (fp.output_port()->connected_to (port_name)) {
+ output_combo.set_active (n);
+ output_found = true;
+ break;
+ }
+ }
+
+ if (!output_found) {
+ output_combo.set_active (0); /* disconnected */
+ }
+}
+
+void
+CC121GUI::build_available_action_menu ()
+{
+ /* build a model of all available actions (needs to be tree structured
+ * more)
+ */
+
+ available_action_model = TreeStore::create (action_columns);
+
+ vector<string> paths;
+ vector<string> labels;
+ vector<string> tooltips;
+ vector<string> keys;
+ vector<Glib::RefPtr<Gtk::Action> > actions;
+
+ Gtkmm2ext::ActionMap::get_all_actions (paths, labels, tooltips, keys, actions);
+
+ typedef std::map<string,TreeIter> NodeMap;
+ NodeMap nodes;
+ NodeMap::iterator r;
+
+
+ vector<string>::iterator k;
+ vector<string>::iterator p;
+ vector<string>::iterator t;
+ vector<string>::iterator l;
+
+ available_action_model->clear ();
+
+ TreeIter rowp;
+ TreeModel::Row parent;
+
+ /* Disabled item (row 0) */
+
+ rowp = available_action_model->append();
+ parent = *(rowp);
+ parent[action_columns.name] = _("Disabled");
+
+ for (l = labels.begin(), k = keys.begin(), p = paths.begin(), t = tooltips.begin(); l != labels.end(); ++k, ++p, ++t, ++l) {
+
+ TreeModel::Row row;
+ vector<string> parts;
+
+ parts.clear ();
+
+ split (*p, parts, '/');
+
+ if (parts.empty()) {
+ continue;
+ }
+
+ //kinda kludgy way to avoid displaying menu items as mappable
+ if ( parts[1] == _("Main_menu") )
+ continue;
+ if ( parts[1] == _("JACK") )
+ continue;
+ if ( parts[1] == _("redirectmenu") )
+ continue;
+ if ( parts[1] == _("Editor_menus") )
+ continue;
+ if ( parts[1] == _("RegionList") )
+ continue;
+ if ( parts[1] == _("ProcessorMenu") )
+ continue;
+
+ if ((r = nodes.find (parts[1])) == nodes.end()) {
+
+ /* top level is missing */
+
+ TreeIter rowp;
+ TreeModel::Row parent;
+ rowp = available_action_model->append();
+ nodes[parts[1]] = rowp;
+ parent = *(rowp);
+ parent[action_columns.name] = parts[1];
+
+ row = *(available_action_model->append (parent.children()));
+
+ } else {
+
+ row = *(available_action_model->append ((*r->second)->children()));
+
+ }
+
+ /* add this action */
+
+ if (l->empty ()) {
+ row[action_columns.name] = *t;
+ action_map[*t] = *p;
+ } else {
+ row[action_columns.name] = *l;
+ action_map[*l] = *p;
+ }
+
+ string path = (*p);
+ /* ControlProtocol::access_action() is not interested in the
+ legacy "<Actions>/" prefix part of a path.
+ */
+ path = path.substr (strlen ("<Actions>/"));
+
+ row[action_columns.path] = path;
+ }
+}
+
+void
+CC121GUI::action_changed (Gtk::ComboBox* cb, CC121::ButtonID id, CC121::ButtonState bs)
+{
+ TreeModel::const_iterator row = cb->get_active ();
+ string action_path = (*row)[action_columns.path];
+
+ /* release binding */
+ fp.set_action (id, action_path, false, bs);
+}
+
+void
+CC121GUI::build_action_combo (Gtk::ComboBox& cb, vector<pair<string,string> > const & actions, CC121::ButtonID id, CC121::ButtonState bs)
+{
+ Glib::RefPtr<Gtk::ListStore> model (Gtk::ListStore::create (action_columns));
+ TreeIter rowp;
+ TreeModel::Row row;
+ string current_action = fp.get_action (id, false, bs); /* lookup release action */
+ int active_row = -1;
+ int n;
+ vector<pair<string,string> >::const_iterator i;
+
+ rowp = model->append();
+ row = *(rowp);
+ row[action_columns.name] = _("Disabled");
+ row[action_columns.path] = string();
+
+ if (current_action.empty()) {
+ active_row = 0;
+ }
+
+ for (i = actions.begin(), n = 0; i != actions.end(); ++i, ++n) {
+ rowp = model->append();
+ row = *(rowp);
+ row[action_columns.name] = i->first;
+ row[action_columns.path] = i->second;
+ if (current_action == i->second) {
+ active_row = n+1;
+ }
+ }
+
+ cb.set_model (model);
+ cb.pack_start (action_columns.name);
+
+ if (active_row >= 0) {
+ cb.set_active (active_row);
+ }
+
+ cb.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &CC121GUI::action_changed), &cb, id, bs));
+}
+
+void
+CC121GUI::build_foot_action_combo (Gtk::ComboBox& cb, CC121::ButtonState bs)
+{
+ vector<pair<string,string> > actions;
+
+ actions.push_back (make_pair (string("Toggle Roll"), string(X_("Transport/ToggleRoll"))));
+ actions.push_back (make_pair (string("Toggle Rec-Enable"), string(X_("Transport/Record"))));
+ actions.push_back (make_pair (string("Toggle Roll+Rec"), string(X_("Transport/record-roll"))));
+ actions.push_back (make_pair (string("Toggle Loop"), string(X_("Transport/Loop"))));
+ actions.push_back (make_pair (string("Toggle Click"), string(X_("Transport/ToggleClick"))));
+
+ build_action_combo (cb, actions, CC121::Footswitch, bs);
+}
+
+bool
+CC121GUI::find_action_in_model (const TreeModel::iterator& iter, std::string const & action_path, TreeModel::iterator* found)
+{
+ TreeModel::Row row = *iter;
+ string path = row[action_columns.path];
+
+ if (path == action_path) {
+ *found = iter;
+ return true;
+ }
+
+ return false;
+}
+
+void
+CC121GUI::build_user_action_combo (Gtk::ComboBox& cb, CC121::ButtonState bs, CC121::ButtonID id)
+{
+ cb.set_model (available_action_model);
+ cb.pack_start (action_columns.name);
+ cb.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &CC121GUI::action_changed), &cb, id, bs));
+
+ /* set the active "row" to the right value for the current button binding */
+
+ string current_action = fp.get_action (id, false, bs); /* lookup release action */
+
+ if (current_action.empty()) {
+ cb.set_active (0); /* "disabled" */
+ return;
+ }
+
+ TreeModel::iterator iter = available_action_model->children().end();
+
+ available_action_model->foreach_iter (sigc::bind (sigc::mem_fun (*this, &CC121GUI::find_action_in_model), current_action, &iter));
+
+ if (iter != available_action_model->children().end()) {
+ cb.set_active (iter);
+ } else {
+ cb.set_active (0);
+ }
+}
+
+Glib::RefPtr<Gtk::ListStore>
+CC121GUI::build_midi_port_list (vector<string> const & ports, bool for_input)
+{
+ Glib::RefPtr<Gtk::ListStore> store = ListStore::create (midi_port_columns);
+ TreeModel::Row row;
+
+ row = *store->append ();
+ row[midi_port_columns.full_name] = string();
+ row[midi_port_columns.short_name] = _("Disconnected");
+
+ for (vector<string>::const_iterator p = ports.begin(); p != ports.end(); ++p) {
+ row = *store->append ();
+ row[midi_port_columns.full_name] = *p;
+ std::string pn = ARDOUR::AudioEngine::instance()->get_pretty_name_by_name (*p);
+ if (pn.empty ()) {
+ pn = (*p).substr ((*p).find (':') + 1);
+ }
+ row[midi_port_columns.short_name] = pn;
+ }
+
+ return store;
+}
+
+void
+CC121GUI::active_port_changed (Gtk::ComboBox* combo, bool for_input)
+{
+ if (ignore_active_change) {
+ return;
+ }
+
+ TreeModel::iterator active = combo->get_active ();
+ string new_port = (*active)[midi_port_columns.full_name];
+
+ if (new_port.empty()) {
+ if (for_input) {
+ fp.input_port()->disconnect_all ();
+ } else {
+ fp.output_port()->disconnect_all ();
+ }
+
+ return;
+ }
+
+ if (for_input) {
+ if (!fp.input_port()->connected_to (new_port)) {
+ fp.input_port()->disconnect_all ();
+ fp.input_port()->connect (new_port);
+ }
+ } else {
+ if (!fp.output_port()->connected_to (new_port)) {
+ fp.output_port()->disconnect_all ();
+ fp.output_port()->connect (new_port);
+ }
+ }
+}
diff --git a/libs/surfaces/cc121/gui.h b/libs/surfaces/cc121/gui.h
new file mode 100644
index 0000000000000000000000000000000000000000..107a81ed3279259ca073ea46efa3c02af9820501
--- /dev/null
+++ b/libs/surfaces/cc121/gui.h
@@ -0,0 +1,116 @@
+/*
+ Copyright (C) 2015 Paul Davis
+ Copyright (C) 2016 W.P. van Paassen
+
+ 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 __ardour_cc121_gui_h__
+#define __ardour_cc121_gui_h__
+
+#include <vector>
+#include <string>
+
+#include <gtkmm/box.h>
+#include <gtkmm/combobox.h>
+#include <gtkmm/image.h>
+#include <gtkmm/table.h>
+#include <gtkmm/treestore.h>
+
+namespace Gtk {
+ class CellRendererCombo;
+ class ListStore;
+}
+
+#include "cc121.h"
+
+namespace ArdourSurface {
+
+class CC121GUI : public Gtk::VBox
+{
+public:
+ CC121GUI (CC121&);
+ ~CC121GUI ();
+
+private:
+ CC121& fp;
+ Gtk::HBox hpacker;
+ Gtk::Table table;
+ Gtk::Table action_table;
+ Gtk::ComboBox input_combo;
+ Gtk::ComboBox output_combo;
+ Gtk::Image image;
+
+ Gtk::ComboBox foot_combo;
+ Gtk::ComboBox function1_combo;
+ Gtk::ComboBox function2_combo;
+ Gtk::ComboBox function3_combo;
+ Gtk::ComboBox function4_combo;
+ Gtk::ComboBox value_combo;
+ Gtk::ComboBox lock_combo;
+ Gtk::ComboBox eq1_combo;
+ Gtk::ComboBox eq2_combo;
+ Gtk::ComboBox eq3_combo;
+ Gtk::ComboBox eq4_combo;
+ Gtk::ComboBox eqtype_combo;
+ Gtk::ComboBox allbypass_combo;
+
+ void update_port_combos ();
+ PBD::ScopedConnection connection_change_connection;
+ void connection_handler ();
+
+ struct MidiPortColumns : public Gtk::TreeModel::ColumnRecord {
+ MidiPortColumns() {
+ add (short_name);
+ add (full_name);
+ }
+ Gtk::TreeModelColumn<std::string> short_name;
+ Gtk::TreeModelColumn<std::string> full_name;
+ };
+
+ MidiPortColumns midi_port_columns;
+ bool ignore_active_change;
+
+ Glib::RefPtr<Gtk::ListStore> build_midi_port_list (std::vector<std::string> const & ports, bool for_input);
+ void active_port_changed (Gtk::ComboBox*,bool for_input);
+
+ struct ActionColumns : public Gtk::TreeModel::ColumnRecord {
+ ActionColumns() {
+ add (name);
+ add (path);
+ }
+ Gtk::TreeModelColumn<std::string> name;
+ Gtk::TreeModelColumn<std::string> path;
+ };
+
+ ActionColumns action_columns;
+ Glib::RefPtr<Gtk::TreeStore> available_action_model;
+ std::map<std::string,std::string> action_map; // map from action names to paths
+
+ void build_action_combo (Gtk::ComboBox& cb, std::vector<std::pair<std::string,std::string> > const & actions, CC121::ButtonID, CC121::ButtonState);
+ void build_user_action_combo (Gtk::ComboBox&, CC121::ButtonState, CC121::ButtonID);
+ void build_foot_action_combo (Gtk::ComboBox&, CC121::ButtonState);
+
+ void build_available_action_menu ();
+ void action_changed (Gtk::ComboBox*, CC121::ButtonID, CC121::ButtonState);
+
+ bool find_action_in_model (const Gtk::TreeModel::iterator& iter, std::string const & action_path, Gtk::TreeModel::iterator* found);
+
+};
+
+}
+
+#endif /* __ardour_cc121_gui_h__ */
diff --git a/libs/surfaces/cc121/operations.cc b/libs/surfaces/cc121/operations.cc
new file mode 100644
index 0000000000000000000000000000000000000000..67aafda777d38d87ee85126ecc51cffcd4ede0ac
--- /dev/null
+++ b/libs/surfaces/cc121/operations.cc
@@ -0,0 +1,340 @@
+/*
+ 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 "ardour/async_midi_port.h"
+#include "ardour/monitor_processor.h"
+#include "ardour/monitor_control.h"
+#include "ardour/pannable.h"
+#include "ardour/plugin_insert.h"
+#include "ardour/rc_configuration.h"
+#include "ardour/record_enable_control.h"
+#include "ardour/session.h"
+#include "ardour/track.h"
+#include "ardour/types.h"
+
+#include "cc121.h"
+
+using namespace ARDOUR;
+using namespace ArdourSurface;
+using namespace PBD;
+
+/* this value is chosen to given smooth motion from 0..1.0 in about 270 degrees
+ * of encoder rotation.
+ */
+static const double encoder_divider = 24.0;
+
+void
+CC121::input_monitor ()
+{
+ if (_current_stripable) {
+ MonitorChoice choice = _current_stripable->monitoring_control()->monitoring_choice ();
+ switch(choice) {
+ case MonitorAuto:
+ _current_stripable->monitoring_control()->set_value (MonitorInput, PBD::Controllable::NoGroup);
+ get_button(InputMonitor).set_led_state (_output_port, true);
+ break;
+ case MonitorInput:
+ _current_stripable->monitoring_control()->set_value (MonitorDisk, PBD::Controllable::NoGroup);
+ get_button(InputMonitor).set_led_state (_output_port, false);
+ break;
+ case MonitorDisk:
+ _current_stripable->monitoring_control()->set_value (MonitorCue, PBD::Controllable::NoGroup);
+ get_button(InputMonitor).set_led_state (_output_port, false);
+ break;
+ case MonitorCue:
+ _current_stripable->monitoring_control()->set_value (MonitorInput, PBD::Controllable::NoGroup);
+ get_button(InputMonitor).set_led_state (_output_port, true);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void
+CC121::left ()
+{
+ access_action ("Editor/select-prev-route");
+
+ //ToDo: bank by 8?
+ //if ( (button_state & ShiftDown) == ShiftDown )
+
+}
+
+void
+CC121::right ()
+{
+ access_action ("Editor/select-next-route");
+
+ //ToDo: bank by 8?
+ //if ( (button_state & ShiftDown) == ShiftDown )
+}
+
+
+void
+CC121::read ()
+{
+ if (_current_stripable) {
+ boost::shared_ptr<AutomationControl> gain = _current_stripable->gain_control ();
+ if (gain) {
+ gain->set_automation_state( (ARDOUR::AutoState) ARDOUR::Play );
+ }
+ }
+}
+
+void
+CC121::write ()
+{
+ if (_current_stripable) {
+ boost::shared_ptr<AutomationControl> gain = _current_stripable->gain_control ();
+ if (gain) {
+ gain->set_automation_state( (ARDOUR::AutoState) ARDOUR::Write );
+ }
+ }
+}
+
+void
+CC121::touch ()
+{
+ if (_current_stripable) {
+ boost::shared_ptr<AutomationControl> gain = _current_stripable->gain_control ();
+ if (gain) {
+ gain->set_automation_state( (ARDOUR::AutoState) ARDOUR::Touch );
+ }
+ }
+}
+
+void
+CC121::off ()
+{
+ if (_current_stripable) {
+ boost::shared_ptr<AutomationControl> gain = _current_stripable->gain_control ();
+ if (gain) {
+ gain->set_automation_state( (ARDOUR::AutoState) ARDOUR::Off );
+ }
+ }
+}
+
+
+
+
+void
+CC121::undo ()
+{
+ ControlProtocol::Undo (); /* EMIT SIGNAL */
+}
+
+void
+CC121::redo ()
+{
+ ControlProtocol::Redo (); /* EMIT SIGNAL */
+}
+
+void
+CC121::jog()
+{
+ if (_jogmode == scroll) {
+ _jogmode = zoom;
+ }
+ else {
+ _jogmode = scroll;
+ }
+ get_button (Jog).set_led_state (_output_port, _jogmode == scroll);
+}
+
+void
+CC121::mute ()
+{
+ if (!_current_stripable) {
+ return;
+ }
+
+ if (_current_stripable == session->monitor_out()) {
+ boost::shared_ptr<MonitorProcessor> mp = _current_stripable->monitor_control();
+ mp->set_cut_all (!mp->cut_all());
+ return;
+ }
+
+ _current_stripable->mute_control()->set_value (!_current_stripable->mute_control()->muted(), PBD::Controllable::UseGroup);
+}
+
+void
+CC121::solo ()
+{
+ if (!_current_stripable) {
+ return;
+ }
+ _current_stripable->solo_control()->set_value (!_current_stripable->solo_control()->soloed(), PBD::Controllable::UseGroup);
+}
+
+void
+CC121::rec_enable ()
+{
+ if (!_current_stripable) {
+ return;
+ }
+
+ boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track>(_current_stripable);
+
+ if (!t) {
+ return;
+ }
+
+ t->rec_enable_control()->set_value (!t->rec_enable_control()->get_value(), Controllable::UseGroup);
+}
+
+void
+CC121::use_master ()
+{
+ boost::shared_ptr<Stripable> r = session->master_out();
+ if (r) {
+ if (_current_stripable == r) {
+ r = pre_master_stripable.lock();
+ set_current_stripable (r);
+ get_button(Output).set_led_state (_output_port, false);
+ blinkers.remove (Output);
+ } else {
+ if (_current_stripable != session->master_out() && _current_stripable != session->monitor_out()) {
+ pre_master_stripable = boost::weak_ptr<Stripable> (_current_stripable);
+ }
+ set_current_stripable (r);
+ get_button(Output).set_led_state (_output_port, true);
+ blinkers.remove (Output);
+ }
+ }
+}
+
+void
+CC121::use_monitor ()
+{
+ boost::shared_ptr<Stripable> r = session->monitor_out();
+
+ if (r) {
+ if (_current_stripable == r) {
+ r = pre_monitor_stripable.lock();
+ set_current_stripable (r);
+ get_button(Output).set_led_state (_output_port, false);
+ blinkers.remove (Output);
+ } else {
+ if (_current_stripable != session->master_out() && _current_stripable != session->monitor_out()) {
+ pre_monitor_stripable = boost::weak_ptr<Stripable> (_current_stripable);
+ }
+ set_current_stripable (r);
+ get_button(Output).set_led_state (_output_port, true);
+ blinkers.push_back (Output);
+ }
+ }
+}
+
+void
+CC121::ardour_pan_azimuth (float delta)
+{
+ if (!_current_stripable) {
+ return;
+ }
+
+ boost::shared_ptr<Route> r = boost::dynamic_pointer_cast<Route> (_current_stripable);
+
+ if (!r) {
+ return;
+ }
+
+ boost::shared_ptr<Pannable> pannable = r->pannable ();
+
+ if (!pannable) {
+ return;
+ }
+
+ boost::shared_ptr<AutomationControl> azimuth = pannable->pan_azimuth_control;
+
+ if (!azimuth) {
+ return;
+ }
+
+ azimuth->set_value (azimuth->interface_to_internal (azimuth->internal_to_interface (azimuth->get_value()) + (delta)), Controllable::NoGroup);
+}
+
+
+void
+CC121::ardour_pan_width(float delta)
+{
+ if (!_current_stripable) {
+ return;
+ }
+
+ boost::shared_ptr<Route> r = boost::dynamic_pointer_cast<Route> (_current_stripable);
+
+ if (!r) {
+ return;
+ }
+
+ boost::shared_ptr<Pannable> pannable = r->pannable ();
+
+ if (!pannable) {
+ return;
+ }
+
+ boost::shared_ptr<AutomationControl> width = pannable->pan_width_control;
+
+ if (!width) {
+ return;
+ }
+
+ width->set_value (width->interface_to_internal (width->internal_to_interface (width->get_value()) + (delta)), Controllable::NoGroup);
+}
+
+void
+CC121::mixbus_pan (float delta)
+{
+#ifdef MIXBUS
+ if (!_current_stripable) {
+ return;
+ }
+ boost::shared_ptr<Route> r = boost::dynamic_pointer_cast<Route> (_current_stripable);
+
+ if (!r) {
+ return;
+ }
+
+
+ const uint32_t port_channel_post_pan = 2; // gtk2_ardour/mixbus_ports.h
+ boost::shared_ptr<ARDOUR::PluginInsert> plug = r->ch_post();
+
+ if (!plug) {
+ return;
+ }
+
+ boost::shared_ptr<AutomationControl> azimuth = boost::dynamic_pointer_cast<ARDOUR::AutomationControl> (plug->control (Evoral::Parameter (ARDOUR::PluginAutomation, 0, port_channel_post_pan)));
+
+ if (!azimuth) {
+ return;
+ }
+
+ azimuth->set_value (azimuth->interface_to_internal (azimuth->internal_to_interface (azimuth->get_value()) + (delta)), Controllable::NoGroup);
+#endif
+}
+
+void
+CC121::punch ()
+{
+ access_action ("Transport/TogglePunch");
+}
diff --git a/libs/surfaces/cc121/wscript b/libs/surfaces/cc121/wscript
new file mode 100644
index 0000000000000000000000000000000000000000..478834aa3a2ea766f47e08b86fed8ab7921234d3
--- /dev/null
+++ b/libs/surfaces/cc121/wscript
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+from waflib.extras import autowaf as autowaf
+import os
+
+# Mandatory variables
+top = '.'
+out = 'build'
+
+def options(opt):
+ autowaf.set_options(opt)
+
+def configure(conf):
+ autowaf.configure(conf)
+
+def build(bld):
+ obj = bld(features = 'cxx cxxshlib')
+ obj.source = '''
+ cc121.cc
+ gui.cc
+ cc121_interface.cc
+ operations.cc
+ '''
+ obj.export_includes = ['.']
+ obj.defines = [ 'PACKAGE="ardour_cc121"' ]
+ obj.defines += [ 'ARDOURSURFACE_DLL_EXPORTS' ]
+ obj.includes = [ '.', './cc121']
+ obj.name = 'libardour_cc121'
+ obj.target = 'ardour_cc121'
+ obj.uselib = 'GTKMM GTK GDK XML'
+ obj.use = 'libardour libardour_cp libgtkmm2ext libpbd'
+ obj.install_path = os.path.join(bld.env['LIBDIR'], 'surfaces')
+
+def shutdown():
+ autowaf.shutdown()
diff --git a/libs/surfaces/wscript b/libs/surfaces/wscript
index 0c736733a12da7c8602752215b349128af6a6eb2..7ab04ef9f8fc82c91390117dd722d83842a902a6 100644
--- a/libs/surfaces/wscript
+++ b/libs/surfaces/wscript
@@ -22,6 +22,7 @@ out = 'build'
children = [
'control_protocol',
'faderport',
+ 'cc121',
'generic_midi',
'mackie',
]
@@ -74,6 +75,7 @@ def build(bld):
bld.recurse('control_protocol')
bld.recurse('generic_midi')
bld.recurse('faderport')
+ bld.recurse('cc121')
bld.recurse('mackie')
if bld.is_defined ('HAVE_LO'):
|
|
|
Looks like some nice work, Thanks a lot. If you send a pull request via github.com, it may be merged more promptly and it will include commit/author information. Is there any documentation that should be included with this new control surface module? |
|
|
I just added a pull request via github.com. |
|
|
Merged! |
| Date Modified | Username | Field | Change |
|---|---|---|---|
| 2016-10-09 16:13 | paas44 | New Issue | |
| 2016-10-09 16:13 | paas44 | File Added: cc121.diff | |
| 2016-10-10 12:06 | timbyr | Note Added: 0018782 | |
| 2016-10-10 12:07 | timbyr | Status | new => feedback |
| 2016-10-11 17:28 | paas44 | Note Added: 0018784 | |
| 2016-10-11 17:28 | paas44 | Status | feedback => new |
| 2016-10-13 18:12 | paas44 | Note Added: 0018806 | |
| 2016-10-13 18:12 | paas44 | Status | new => closed |
| 2016-10-13 18:12 | paas44 | Resolution | open => fixed |