Skeleton for NI Maschine2 Surface
authorRobin Gareus <robin@gareus.org>
Thu, 17 Nov 2016 12:08:12 +0000 (13:08 +0100)
committerPaul Davis <paul@linuxaudiosystems.com>
Mon, 18 Sep 2017 15:40:53 +0000 (11:40 -0400)
25 files changed:
gtk2_ardour/ardev_common.sh.in
libs/surfaces/maschine2/callbacks.cc [new file with mode: 0644]
libs/surfaces/maschine2/canvas.cc [new file with mode: 0644]
libs/surfaces/maschine2/canvas.h [new file with mode: 0644]
libs/surfaces/maschine2/images.h [new file with mode: 0644]
libs/surfaces/maschine2/interface.cc [new file with mode: 0644]
libs/surfaces/maschine2/layout.cc [new file with mode: 0644]
libs/surfaces/maschine2/layout.h [new file with mode: 0644]
libs/surfaces/maschine2/m2_button.h [new file with mode: 0644]
libs/surfaces/maschine2/m2_dev_mikro.cc [new file with mode: 0644]
libs/surfaces/maschine2/m2_dev_mikro.h [new file with mode: 0644]
libs/surfaces/maschine2/m2_dev_mk2.cc [new file with mode: 0644]
libs/surfaces/maschine2/m2_dev_mk2.h [new file with mode: 0644]
libs/surfaces/maschine2/m2_encoder.h [new file with mode: 0644]
libs/surfaces/maschine2/m2_map_mikro.cc [new file with mode: 0644]
libs/surfaces/maschine2/m2_map_mikro.h [new file with mode: 0644]
libs/surfaces/maschine2/m2_map_mk2.cc [new file with mode: 0644]
libs/surfaces/maschine2/m2_map_mk2.h [new file with mode: 0644]
libs/surfaces/maschine2/m2_pad.h [new file with mode: 0644]
libs/surfaces/maschine2/m2controls.h [new file with mode: 0644]
libs/surfaces/maschine2/m2device.h [new file with mode: 0644]
libs/surfaces/maschine2/maschine2.cc [new file with mode: 0644]
libs/surfaces/maschine2/maschine2.h [new file with mode: 0644]
libs/surfaces/maschine2/wscript [new file with mode: 0644]
libs/surfaces/wscript

index 4a96798e702332486af7318079cc6f6aa542f32f..792eababab3336291032fd66f53d0722f3ee1de1 100644 (file)
@@ -13,7 +13,7 @@ export GTK2_RC_FILES=/nonexistent
 # can find all the components.
 #
 
 # can find all the components.
 #
 
-export ARDOUR_SURFACES_PATH=$libs/surfaces/osc:$libs/surfaces/faderport8:$libs/surfaces/faderport:$libs/surfaces/generic_midi:$libs/surfaces/tranzport:$libs/surfaces/powermate:$libs/surfaces/mackie:$libs/surfaces/wiimote:$libs/surfaces/push2
+export ARDOUR_SURFACES_PATH=$libs/surfaces/osc:$libs/surfaces/faderport8:$libs/surfaces/faderport:$libs/surfaces/generic_midi:$libs/surfaces/tranzport:$libs/surfaces/powermate:$libs/surfaces/mackie:$libs/surfaces/wiimote:$libs/surfaces/push2:$libs/surfaces/maschine2
 export ARDOUR_PANNER_PATH=$libs/panners
 export ARDOUR_DATA_PATH=$TOP:$TOP/build:$TOP/gtk2_ardour:$TOP/build/gtk2_ardour:.
 export ARDOUR_MIDIMAPS_PATH=$TOP/midi_maps:.
 export ARDOUR_PANNER_PATH=$libs/panners
 export ARDOUR_DATA_PATH=$TOP:$TOP/build:$TOP/gtk2_ardour:$TOP/build/gtk2_ardour:.
 export ARDOUR_MIDIMAPS_PATH=$TOP/midi_maps:.
diff --git a/libs/surfaces/maschine2/callbacks.cc b/libs/surfaces/maschine2/callbacks.cc
new file mode 100644 (file)
index 0000000..32466fc
--- /dev/null
@@ -0,0 +1,460 @@
+/*
+ * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
+ *
+ * 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "ardour/session.h"
+#include "gtkmm2ext/colors.h"
+#include "gtkmm2ext/actions.h"
+#include "gtkmm2ext/gui_thread.h"
+#include "pbd/i18n.h"
+
+#include "maschine2.h"
+#include "m2controls.h"
+
+#include "midi++/port.h"
+
+#define COLOR_WHITE 0xffffffff
+#define COLOR_GRAY 0x606060ff
+#define COLOR_BLACK 0x000000ff
+
+using namespace ARDOUR;
+using namespace ArdourSurface;
+
+void
+Maschine2::connect_signals ()
+{
+       // TODO: use some convenience macros here
+
+       /* Signals */
+       session->TransportStateChange.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Maschine2::notify_transport_state_changed, this), this);
+       session->TransportLooped.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Maschine2::notify_loop_state_changed, this), this);
+       session->RecordStateChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Maschine2::notify_record_state_changed, this), this);
+       Config->ParameterChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Maschine2::notify_parameter_changed, this, _1), this);
+       session->config.ParameterChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Maschine2::notify_parameter_changed, this, _1), this);
+       session->DirtyChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Maschine2::notify_session_dirty_changed, this), this);
+       session->history().Changed.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Maschine2::notify_history_changed, this), this);
+
+       /* Actions */
+       Glib::RefPtr<Gtk::Action> act;
+#if 0
+       act = ActionManager::get_action (X_("Editor"), X_("ToggleMeasureVisibility"));
+       if (act) {
+               Glib::RefPtr<Gtk::ToggleAction> tact = Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic (act);
+               tact->signal_toggled ().connect (sigc::mem_fun (*this, &Maschine2::notify_grid_change));
+       }
+#endif
+       act = ActionManager::get_action (X_("Editor"), X_("snap-off"));
+       if (act) {
+               Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
+               ract->signal_toggled ().connect (sigc::mem_fun (*this, &Maschine2::notify_snap_change));
+       }
+       act = ActionManager::get_action (X_("Editor"), X_("snap-magnetic"));
+       if (act) {
+               Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
+               ract->signal_toggled ().connect (sigc::mem_fun (*this, &Maschine2::notify_snap_change));
+       }
+       act = ActionManager::get_action (X_("Editor"), X_("snap-normal"));
+       if (act) {
+               Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
+               ract->signal_toggled ().connect (sigc::mem_fun (*this, &Maschine2::notify_snap_change));
+       }
+
+       /* Surface events */
+       _ctrl->button (M2Contols::Play)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_play, this));
+       _ctrl->button (M2Contols::Rec)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_record, this));
+       _ctrl->button (M2Contols::Loop)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_loop, this));
+       _ctrl->button (M2Contols::Metronom)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_metronom, this));
+       _ctrl->button (M2Contols::GotoStart)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_rewind, this));
+       _ctrl->button (M2Contols::FastRewind)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Transport", "RewindSlow"));
+       _ctrl->button (M2Contols::FastForward)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Transport", "ForwardSlow"));
+       _ctrl->button (M2Contols::Panic)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "MIDI", "panic"));
+       _ctrl->button (M2Contols::JumpForward)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Editor", "jump-forward-to-mark"));
+       _ctrl->button (M2Contols::JumpBackward)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Editor", "jump-backward-to-mark"));
+
+       _ctrl->button (M2Contols::Grid)->pressed.connect (button_connections, invalidator (*this), boost::bind (&Maschine2::button_snap_pressed, this), gui_context());
+       _ctrl->button (M2Contols::Grid)->released.connect (button_connections, invalidator (*this), boost::bind (&Maschine2::button_snap_released, this), gui_context());
+       _ctrl->button (M2Contols::Grid)->changed.connect (button_connections, invalidator (*this), boost::bind (&Maschine2::button_snap_changed, this, _1), gui_context());
+
+       _ctrl->button (M2Contols::Save)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Common", "Save"));
+       _ctrl->button (M2Contols::Undo)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Editor", "undo"));
+       _ctrl->button (M2Contols::Redo)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Editor", "redo"));
+
+       _ctrl->button (M2Contols::MasterVolume)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::handle_master_change, this, MST_VOLUME));
+       _ctrl->button (M2Contols::MasterTempo)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::handle_master_change, this, MST_TEMPO));
+
+       _ctrl->button (M2Contols::EncoderWheel)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_encoder, this));
+       _ctrl->encoder (0)->changed.connect_same_thread (button_connections, boost::bind (&Maschine2::encoder_master, this, _1));
+
+       for (unsigned int pad = 0; pad < 16; ++pad) {
+               _ctrl->pad (pad)->event.connect_same_thread (button_connections, boost::bind (&Maschine2::pad_event, this, pad, _1, _2));
+               _ctrl->pad (pad)->changed.connect_same_thread (button_connections, boost::bind (&Maschine2::pad_change, this, pad, _1));
+       }
+
+       /* set initial values */
+       notify_record_state_changed ();
+       notify_transport_state_changed ();
+       notify_loop_state_changed ();
+       notify_parameter_changed ("clicking");
+       notify_snap_change ();
+       notify_session_dirty_changed ();
+       notify_history_changed ();
+}
+
+void
+Maschine2::notify_record_state_changed ()
+{
+       switch (session->record_status ()) {
+               case Session::Disabled:
+                       _ctrl->button (M2Contols::Rec)->set_color (0);
+                       _ctrl->button (M2Contols::Rec)->set_blinking (false);
+                       break;
+               case Session::Enabled:
+                       _ctrl->button (M2Contols::Rec)->set_color (COLOR_WHITE);
+                       _ctrl->button (M2Contols::Rec)->set_blinking (true);
+                       break;
+               case Session::Recording:
+                       _ctrl->button (M2Contols::Rec)->set_color (COLOR_WHITE);
+                       _ctrl->button (M2Contols::Rec)->set_blinking (false);
+                       break;
+       }
+}
+
+void
+Maschine2::notify_transport_state_changed ()
+{
+       if (session->transport_rolling ()) {
+               _ctrl->button (M2Contols::Play)->set_color (COLOR_WHITE);
+       } else {
+               _ctrl->button (M2Contols::Play)->set_color (0);
+       }
+       notify_loop_state_changed ();
+}
+
+void
+Maschine2::notify_loop_state_changed ()
+{
+       bool looping = false;
+       Location* looploc = session->locations ()->auto_loop_location ();
+       if (looploc && session->get_play_loop ()) {
+               looping = true;
+       }
+       _ctrl->button (M2Contols::Loop)->set_color (looping ? COLOR_GRAY : 0);
+}
+
+void
+Maschine2::notify_parameter_changed (std::string param)
+{
+       if (param == "clicking") {
+               _ctrl->button (M2Contols::Metronom)->set_color (Config->get_clicking () ? COLOR_GRAY : 0);
+       }
+}
+
+#if 0
+void
+Maschine2::notify_grid_change ()
+{
+       Glib::RefPtr<Gtk::Action> act = ActionManager::get_action (X_("Editor"), X_("ToggleMeasureVisibility"));
+       if (act) {
+               Glib::RefPtr<Gtk::ToggleAction> tact = Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic (act);
+               _ctrl->button (M2Contols::Grid)->set_color (tact->get_active () ? COLOR_WHITE : 0);
+       }
+}
+#endif
+
+void
+Maschine2::notify_snap_change ()
+{
+       uint32_t rgba = 0;
+       if (_ctrl->button (M2Contols::Grid)->is_pressed ()) {
+               return;
+       }
+
+       Glib::RefPtr<Gtk::Action> act = ActionManager::get_action (X_("Editor"), X_("snap-magnetic"));
+       if (act) {
+               Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
+               if (ract->get_active ()) { rgba = COLOR_GRAY; }
+       }
+       act = ActionManager::get_action (X_("Editor"), X_("snap-normal"));
+       if (act) {
+               Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
+               if (ract->get_active ()) { rgba = COLOR_WHITE; }
+       }
+
+       _ctrl->button (M2Contols::Grid)->set_color (rgba);
+}
+
+void
+Maschine2::notify_session_dirty_changed ()
+{
+       bool is_dirty = session->dirty ();
+       _ctrl->button (M2Contols::Save)->set_color (is_dirty ? COLOR_WHITE : COLOR_BLACK);
+       _ctrl->button (M2Contols::Save)->set_blinking (is_dirty);
+}
+
+void
+Maschine2::notify_history_changed ()
+{
+       _ctrl->button (M2Contols::Redo)->set_color (session->redo_depth() > 0 ? COLOR_WHITE : COLOR_BLACK);
+       _ctrl->button (M2Contols::Undo)->set_color (session->undo_depth() > 0 ? COLOR_WHITE : COLOR_BLACK);
+}
+
+
+void
+Maschine2::button_play ()
+{
+       if (session->transport_rolling ()) {
+               transport_stop ();
+       } else {
+               transport_play ();
+       }
+}
+
+void
+Maschine2::button_record ()
+{
+       set_record_enable (!get_record_enabled ());
+}
+
+void
+Maschine2::button_loop ()
+{
+       loop_toggle ();
+}
+
+void
+Maschine2::button_metronom ()
+{
+       Config->set_clicking (!Config->get_clicking ());
+}
+
+void
+Maschine2::button_rewind ()
+{
+       goto_start (session->transport_rolling ());
+}
+
+void
+Maschine2::button_action (const std::string& group, const std::string& item)
+{
+       AccessAction (group, item);
+}
+
+#if 0
+void
+Maschine2::button_grid ()
+{
+       Glib::RefPtr<Gtk::Action> act = ActionManager::get_action (X_("Editor"), X_("ToggleMeasureVisibility"));
+       if (act) {
+               Glib::RefPtr<Gtk::ToggleAction> tact = Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic (act);
+               tact->set_active (!tact->get_active ());
+       }
+}
+#endif
+
+void
+Maschine2::button_snap_pressed ()
+{
+       _ctrl->button (M2Contols::Grid)->set_color (COLOR_WHITE);
+       _ctrl->button (M2Contols::Grid)->set_blinking (true);
+}
+
+void
+Maschine2::button_snap_changed (bool pressed)
+{
+       if (!pressed) {
+               _ctrl->button (M2Contols::Grid)->set_blinking (false);
+               notify_snap_change ();
+       }
+       notify_master_change ();
+}
+
+void
+Maschine2::button_snap_released ()
+{
+       _ctrl->button (M2Contols::Grid)->set_blinking (false);
+
+       const char* action = 0;
+       Glib::RefPtr<Gtk::Action> act = ActionManager::get_action (X_("Editor"), X_("snap-off"));
+       if (act) {
+               Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
+               if (ract->get_active ()) { action = "snap-normal"; }
+       }
+
+       act = ActionManager::get_action (X_("Editor"), X_("snap-normal"));
+       if (act) {
+               Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
+               if (ract->get_active ()) { action = "snap-magnetic"; }
+       }
+
+       act = ActionManager::get_action (X_("Editor"), X_("snap-magnetic"));
+       if (act) {
+               Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
+               if (ract->get_active ()) { action = "snap-off"; }
+       }
+
+       if (!action) {
+               assert (0);
+               return;
+       }
+
+       act = ActionManager::get_action (X_("Editor"), action);
+       if (act) {
+               Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
+               ract->set_active (true);
+       }
+}
+
+/* Master mode + state -- main encoder fn */
+
+void
+Maschine2::handle_master_change (enum MasterMode id)
+{
+       switch (id) {
+               case MST_VOLUME:
+                       if (_master_state == MST_VOLUME) { _master_state = MST_NONE; } else { _master_state = MST_VOLUME; }
+                       break;
+               case MST_TEMPO:
+                       if (_master_state == MST_TEMPO) { _master_state = MST_NONE; } else { _master_state = MST_TEMPO; }
+                       break;
+               default:
+                       return;
+                       break;
+       }
+       notify_master_change ();
+}
+
+void
+Maschine2::notify_master_change ()
+{
+       if (_ctrl->button (M2Contols::Grid)->is_pressed ()) {
+               _ctrl->button (M2Contols::MasterVolume)->set_color (COLOR_BLACK);
+               _ctrl->button (M2Contols::MasterTempo)->set_color (COLOR_BLACK);
+               return;
+       }
+       switch (_master_state) {
+               case MST_NONE:
+                       _ctrl->button (M2Contols::MasterVolume)->set_color (COLOR_BLACK);
+                       _ctrl->button (M2Contols::MasterTempo)->set_color (COLOR_BLACK);
+                       break;
+               case MST_VOLUME:
+                       _ctrl->button (M2Contols::MasterVolume)->set_color (COLOR_WHITE);
+                       _ctrl->button (M2Contols::MasterTempo)->set_color (COLOR_BLACK);
+                       break;
+               case MST_TEMPO:
+                       _ctrl->button (M2Contols::MasterVolume)->set_color (COLOR_BLACK);
+                       _ctrl->button (M2Contols::MasterTempo)->set_color (COLOR_WHITE);
+                       break;
+       }
+}
+
+static void apply_ac_delta (boost::shared_ptr<AutomationControl> ac, double d) {
+       if (!ac) {
+               return;
+       }
+       ac->set_value (ac->interface_to_internal (min (ac->upper(), max (ac->lower(), ac->internal_to_interface (ac->get_value()) + d))),
+                       PBD::Controllable::UseGroup);
+}
+
+void
+Maschine2::encoder_master (int delta)
+{
+       if (_ctrl->button (M2Contols::Grid)->is_pressed ()) {
+               _ctrl->button (M2Contols::Grid)->ignore_release ();
+               if (delta > 0) {
+                       AccessAction ("Editor", "next-snap-choice");
+               } else {
+                       AccessAction ("Editor", "prev-snap-choice");
+               }
+               return;
+       }
+       switch (_master_state) {
+               case MST_NONE:
+                       if (_ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->active ()) {
+                               if (delta > 0) {
+                                       AccessAction ("Editor", "temporal-zoom-in");
+                               } else {
+                                       AccessAction ("Editor", "temporal-zoom-out");
+                               }
+                       } else {
+                               if (delta > 0) {
+                                       AccessAction ("Editor", "playhead-forward-to-grid");
+                               } else {
+                                       AccessAction ("Editor", "playhead-backward-to-grid");
+                               }
+                       }
+                       break;
+               case MST_VOLUME:
+                       {
+                               boost::shared_ptr<Route> master = session->master_out ();
+                               if (master) {
+                                       // TODO consider _ctrl->button (M2Contols::EncoderWheel)->is_pressed() for fine grained
+                                       const double factor = _ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->active () ? 256. : 32.;
+                                       apply_ac_delta (master->gain_control(), delta / factor);
+                               }
+                       }
+                       break;
+               case MST_TEMPO:
+                       // set new tempo..  apply with "enter"
+                       break;
+       }
+}
+
+void
+Maschine2::button_encoder ()
+{
+       switch (_master_state) {
+               case MST_NONE:
+                       // OR: add marker ??
+                       if (_ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->active ()) {
+                               AccessAction ("Editor", "zoom-to-session");
+                       }
+                       break;
+               case MST_VOLUME:
+                       // ignore -> fine gained?
+                       break;
+               case MST_TEMPO:
+                       // add new tempo.. ?
+                       break;
+       }
+}
+
+void
+Maschine2::pad_change (unsigned int pad, float v)
+{
+       float lvl = v; // _ctrl->pad (pad)->value () / 4095.f;
+       Gtkmm2ext::Color c = Gtkmm2ext::hsva_to_color (270 - 270.f * lvl, 1.0, lvl * lvl, 1.0);
+       _ctrl->pad (pad)->set_color (c);
+}
+
+void
+Maschine2::pad_event (unsigned int pad, float v, bool ev)
+{
+       if (ev) {
+               uint8_t msg[3];
+               msg[0] = v > 0 ? 0x90 : 0x80;
+               msg[1] = 36 + pad; // TODO map note to scale
+               msg[2] = ((uint8_t)floor (v * 127)) & 0x7f;
+               _output_port->write (msg, 3, 0);
+       } else {
+               uint8_t msg[3];
+               msg[0] = 0xa0;
+               msg[1] = 36 + pad; // TODO map note to scale
+               msg[2] = ((uint8_t)floor (v * 127)) & 0x7f;
+               _output_port->write (msg, 3, 0);
+       }
+       //printf ("[%2d] %s %.1f\n", pad, ev ? "On/Off" : "Aftertouch" , v * 127);
+}
diff --git a/libs/surfaces/maschine2/canvas.cc b/libs/surfaces/maschine2/canvas.cc
new file mode 100644 (file)
index 0000000..bde0d67
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2016 Paul Davis
+ * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
+ *
+ * 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <vector>
+
+#include <cairomm/region.h>
+#include <cairomm/surface.h>
+#include <cairomm/context.h>
+
+#include "pbd/compose.h"
+#include "pbd/error.h"
+#include "pbd/i18n.h"
+
+#include "canvas.h"
+#include "layout.h"
+
+#include "maschine2.h"
+#include "m2device.h"
+
+#ifdef __APPLE__
+#define Rect ArdourCanvas::Rect
+#endif
+
+using namespace ArdourCanvas;
+using namespace ArdourSurface;
+using namespace PBD;
+
+Maschine2Canvas::Maschine2Canvas (Maschine2&m, M2Device* hw)
+       : m2 (m)
+{
+       context = Cairo::Context::create (hw->surface ());
+       expose_region = Cairo::Region::create ();
+       _width = hw->surface ()->get_width ();
+       _height = hw->surface ()->get_height ();
+
+       hw->vblank.connect_same_thread (vblank_connections, boost::bind (&Maschine2Canvas::expose, this));
+}
+
+Maschine2Canvas::~Maschine2Canvas ()
+{
+}
+
+void
+Maschine2Canvas::request_redraw ()
+{
+       request_redraw (Rect (0, 0, _width, _height));
+}
+
+void
+Maschine2Canvas::request_redraw (Rect const & r)
+{
+       Cairo::RectangleInt cr;
+
+       cr.x = r.x0;
+       cr.y = r.y0;
+       cr.width = r.width();
+       cr.height = r.height();
+
+       expose_region->do_union (cr);
+
+       /* next vblank will redraw */
+}
+
+bool
+Maschine2Canvas::expose ()
+{
+       if (expose_region->empty()) {
+               return false; /* nothing drawn */
+       }
+
+       /* set up clipping */
+
+       const int nrects = expose_region->get_num_rectangles ();
+
+       for (int n = 0; n < nrects; ++n) {
+               Cairo::RectangleInt r = expose_region->get_rectangle (n);
+               context->rectangle (r.x, r.y, r.width, r.height);
+       }
+
+       context->clip ();
+
+       Maschine2Layout* layout = m2.current_layout();
+
+       if (layout) {
+               /* all layouts cover (at least) the full size of the video
+                  display, so we do not need to check if the layout intersects
+                  the bounding box of the full expose region.
+               */
+               Cairo::RectangleInt r = expose_region->get_extents();
+               Rect rr (r.x, r.y, r.x + r.width, r.y + r.height);
+               layout->render (Rect (r.x, r.y, r.x + r.width, r.y + r.height), context);
+       }
+
+       context->reset_clip ();
+
+       /* why is there no "reset()" method for Cairo::Region? */
+       expose_region = Cairo::Region::create ();
+       return true;
+}
+
+void
+Maschine2Canvas::request_size (Duple)
+{
+       /* fixed size canvas */
+}
+
+Rect
+Maschine2Canvas::visible_area () const
+{
+       /* may need to get more sophisticated once we do scrolling */
+       return Rect (0, 0, _width, _height);
+}
+
+Glib::RefPtr<Pango::Context>
+Maschine2Canvas::get_pango_context ()
+{
+       if (!pango_context) {
+               PangoFontMap* map = pango_cairo_font_map_get_default ();
+               if (!map) {
+                       error << _("Default Cairo font map is null!") << endmsg;
+                       return Glib::RefPtr<Pango::Context> ();
+               }
+
+               PangoContext* context = pango_font_map_create_context (map);
+
+               if (!context) {
+                       error << _("cannot create new PangoContext from cairo font map") << endmsg;
+                       return Glib::RefPtr<Pango::Context> ();
+               }
+
+               pango_context = Glib::wrap (context);
+       }
+
+       return pango_context;
+}
diff --git a/libs/surfaces/maschine2/canvas.h b/libs/surfaces/maschine2/canvas.h
new file mode 100644 (file)
index 0000000..0e14245
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2016 Paul Davis
+ * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
+ *
+ * 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _ardour_maschine2_canvas_h_
+#define _ardour_maschine2_canvas_h_
+
+#include <cairomm/refptr.h>
+#include <glibmm/threads.h>
+
+#include "canvas/canvas.h"
+
+namespace Cairo {
+       class ImageSurface;
+       class Context;
+       class Region;
+}
+
+namespace ArdourSurface {
+
+class M2Device;
+class Maschine2;
+
+/* A canvas which renders to the Push2 display */
+
+class Maschine2Canvas : public ArdourCanvas::Canvas
+{
+  public:
+       Maschine2Canvas (Maschine2&, M2Device*);
+       ~Maschine2Canvas();
+
+       void request_redraw ();
+       void request_redraw (ArdourCanvas::Rect const &);
+       bool vblank ();
+
+       Cairo::RefPtr<Cairo::Context> image_context() { return context; }
+
+       ArdourCanvas::Coord width() const { return _width; }
+       ArdourCanvas::Coord height() const { return _height; }
+
+       void request_size (ArdourCanvas::Duple);
+       ArdourCanvas::Rect visible_area () const;
+
+       /* API that does nothing since we have no input events */
+       void ungrab () {}
+       void grab (ArdourCanvas::Item*) {}
+       void focus (ArdourCanvas::Item*) {}
+       void unfocus (ArdourCanvas::Item*) {}
+       void re_enter() {}
+       void pick_current_item (int) {}
+       void pick_current_item (ArdourCanvas::Duple const &, int) {}
+       bool get_mouse_position (ArdourCanvas::Duple&) const { return false; }
+
+       Glib::RefPtr<Pango::Context> get_pango_context ();
+
+  private:
+       int _width;
+       int _height;
+
+       Cairo::RefPtr<Cairo::Context> context;
+       Cairo::RefPtr<Cairo::Region> expose_region;
+       Glib::RefPtr<Pango::Context> pango_context;
+
+       Maschine2& m2;
+       PBD::ScopedConnection vblank_connections;
+
+       bool expose ();
+};
+
+} /* namespace ArdourSurface */
+
+#endif
diff --git a/libs/surfaces/maschine2/images.h b/libs/surfaces/maschine2/images.h
new file mode 100644 (file)
index 0000000..89c0cab
--- /dev/null
@@ -0,0 +1,85 @@
+static const uint8_t maschine_png[] = {
+  0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
+  0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x40,
+  0x01, 0x03, 0x00, 0x00, 0x00, 0x56, 0x71, 0x5d, 0xfc, 0x00, 0x00, 0x00,
+  0x06, 0x50, 0x4c, 0x54, 0x45, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xa5,
+  0xd9, 0x9f, 0xdd, 0x00, 0x00, 0x02, 0xa0, 0x49, 0x44, 0x41, 0x54, 0x58,
+  0xc3, 0xed, 0xd7, 0x41, 0x8b, 0x13, 0x31, 0x14, 0x07, 0xf0, 0x0c, 0x11,
+  0x72, 0x33, 0x4a, 0x2f, 0x1e, 0xca, 0xe6, 0xe6, 0x5d, 0xf6, 0xa0, 0xa0,
+  0x34, 0xfa, 0x4d, 0x04, 0xbf, 0x80, 0xde, 0x2a, 0x5b, 0x3a, 0xb3, 0xec,
+  0x61, 0x2b, 0x48, 0x8b, 0xc7, 0x45, 0xd0, 0x4f, 0x22, 0x56, 0xe6, 0x50,
+  0x85, 0x05, 0xcf, 0x7b, 0x50, 0xa7, 0x54, 0xe8, 0x65, 0xc1, 0x94, 0x3d,
+  0x98, 0x65, 0xd3, 0x79, 0x66, 0x32, 0xc9, 0xb4, 0x6c, 0xd3, 0xce, 0x38,
+  0xe2, 0x2e, 0xe8, 0xce, 0xa1, 0x9d, 0xc3, 0xcc, 0xaf, 0x79, 0x2f, 0xf9,
+  0x67, 0xa6, 0x08, 0x5d, 0x1d, 0xff, 0xc5, 0x11, 0x82, 0x39, 0xa2, 0xbf,
+  0x00, 0x3c, 0xbe, 0x74, 0x40, 0x54, 0x03, 0xda, 0xfd, 0x3b, 0x2f, 0x9f,
+  0xdc, 0x1a, 0xad, 0x02, 0x81, 0xbc, 0x20, 0x60, 0x6d, 0x09, 0x58, 0x5d,
+  0x10, 0xd0, 0xee, 0x67, 0xfd, 0x26, 0xab, 0x00, 0x49, 0x2f, 0x1b, 0xa0,
+  0x50, 0x9c, 0x76, 0x37, 0xf6, 0x20, 0x5d, 0x03, 0xb0, 0xa2, 0x2f, 0x18,
+  0x86, 0x75, 0x46, 0xb0, 0x00, 0xe8, 0x54, 0xd4, 0x01, 0x78, 0xf1, 0xbb,
+  0x5b, 0xa8, 0x8d, 0x78, 0x44, 0x13, 0x2c, 0x10, 0x97, 0x88, 0x09, 0x92,
+  0xd0, 0x38, 0x91, 0x81, 0x00, 0xa8, 0x08, 0xdc, 0x43, 0x4d, 0x03, 0x10,
+  0x89, 0x20, 0x45, 0x5c, 0x19, 0x40, 0x39, 0x60, 0xed, 0x3a, 0x08, 0x21,
+  0xb1, 0x67, 0x4f, 0xbb, 0xd7, 0x11, 0x1f, 0x32, 0x0d, 0x04, 0xfa, 0x1e,
+  0x48, 0x0d, 0x90, 0x96, 0x02, 0x00, 0xae, 0xf2, 0x31, 0xec, 0x1b, 0x80,
+  0x2a, 0x0c, 0xb0, 0x0b, 0xf0, 0xdb, 0x40, 0x0c, 0x31, 0xe2, 0x09, 0xd7,
+  0x00, 0x91, 0x7c, 0x0f, 0x60, 0x3f, 0x03, 0xa0, 0xe8, 0xc1, 0x60, 0xe7,
+  0xeb, 0xcf, 0x77, 0x3f, 0x56, 0x00, 0x3d, 0x5a, 0x9b, 0xa6, 0x60, 0x04,
+  0x93, 0xc8, 0x02, 0x09, 0x8b, 0x25, 0xcb, 0x81, 0x5d, 0x21, 0x37, 0xa6,
+  0x71, 0x09, 0x18, 0xa0, 0x99, 0x01, 0x98, 0xae, 0x1e, 0xe9, 0xf1, 0x8f,
+  0x0c, 0xf0, 0xc1, 0x02, 0x21, 0xf8, 0x47, 0xa0, 0xcb, 0xb5, 0x69, 0xc2,
+  0x7d, 0xf4, 0x6c, 0xc8, 0x45, 0x98, 0x01, 0x12, 0x91, 0x21, 0x39, 0x0f,
+  0xf8, 0x7b, 0xb0, 0x04, 0xbc, 0x41, 0xcf, 0x0d, 0xc0, 0x39, 0x81, 0x84,
+  0x0c, 0x11, 0x01, 0xd0, 0xc0, 0xb8, 0x04, 0xd0, 0x57, 0xa5, 0xcb, 0x80,
+  0x04, 0x0d, 0x60, 0x50, 0xd4, 0x01, 0x33, 0x91, 0x5f, 0xd0, 0x9e, 0xd2,
+  0xe6, 0x8d, 0x47, 0xab, 0x0b, 0x89, 0x02, 0xd8, 0x34, 0x61, 0xbd, 0x12,
+  0x0d, 0xd0, 0x62, 0xba, 0x31, 0x34, 0xca, 0x81, 0xb3, 0x53, 0x07, 0xc4,
+  0x95, 0x80, 0xd6, 0xfc, 0x2c, 0xe9, 0xb2, 0x28, 0x84, 0xc1, 0x79, 0x20,
+  0x8c, 0xa9, 0xf2, 0x01, 0xac, 0xa8, 0xcb, 0x94, 0x60, 0x01, 0x0a, 0x9f,
+  0x6d, 0x09, 0xa7, 0xf3, 0xa2, 0x04, 0x68, 0x56, 0x01, 0xc2, 0x93, 0x04,
+  0x74, 0x3a, 0xf8, 0xc8, 0xce, 0x82, 0x04, 0xb7, 0x0e, 0xc0, 0x0f, 0x70,
+  0x00, 0x9b, 0x26, 0x33, 0x8d, 0x0e, 0xa0, 0x05, 0x10, 0x96, 0xcc, 0xc2,
+  0x02, 0x30, 0x0b, 0xa9, 0xc5, 0x27, 0x06, 0xc8, 0xee, 0xce, 0x01, 0xee,
+  0x46, 0xd0, 0x1b, 0x78, 0x47, 0x10, 0x7e, 0x03, 0x1b, 0x47, 0xb3, 0x94,
+  0x35, 0x30, 0x06, 0x98, 0x0a, 0xea, 0x96, 0x72, 0x29, 0x30, 0xff, 0x52,
+  0xa4, 0x29, 0x0b, 0x53, 0x0e, 0x4c, 0x14, 0x8b, 0x6d, 0x98, 0x24, 0x5b,
+  0xa4, 0x51, 0x95, 0x00, 0x59, 0x9c, 0x5b, 0x2c, 0x9e, 0x08, 0x7c, 0xb2,
+  0x88, 0xb3, 0xa4, 0x0b, 0xc0, 0x37, 0x82, 0x40, 0x1d, 0xbd, 0x75, 0x69,
+  0xca, 0x36, 0x94, 0x1c, 0x98, 0xe9, 0x66, 0xd9, 0x0d, 0x45, 0x12, 0x17,
+  0x67, 0x7f, 0x09, 0xcb, 0x40, 0xb6, 0xa5, 0xb5, 0xe8, 0xde, 0x24, 0x09,
+  0x44, 0x08, 0x28, 0x54, 0x2b, 0x80, 0x6f, 0x16, 0xf0, 0xf1, 0xd1, 0x6b,
+  0x97, 0xa6, 0x6c, 0x53, 0xd5, 0xc0, 0x34, 0x09, 0x24, 0x53, 0xc5, 0xa6,
+  0x8a, 0x37, 0x97, 0x40, 0x0e, 0x35, 0x90, 0x56, 0xda, 0xd6, 0xfd, 0x25,
+  0x90, 0xc3, 0x4f, 0x07, 0x45, 0x1c, 0x37, 0x3f, 0x58, 0x7a, 0x03, 0xdf,
+  0x2c, 0xd0, 0x0c, 0x70, 0x0f, 0x37, 0x28, 0x79, 0xbc, 0x37, 0xbd, 0xc0,
+  0x8b, 0x83, 0xf7, 0x50, 0xf1, 0xfd, 0xc0, 0x07, 0x30, 0x03, 0x44, 0xf5,
+  0x5f, 0x30, 0xb6, 0x2a, 0x03, 0x6b, 0x9a, 0xf8, 0x40, 0x03, 0xf7, 0x37,
+  0x36, 0xaf, 0x04, 0xe8, 0x68, 0x60, 0xbb, 0x78, 0xb8, 0x95, 0x94, 0xe0,
+  0x9b, 0x85, 0xce, 0xc7, 0xde, 0xab, 0x46, 0x25, 0x60, 0xcd, 0x08, 0xd4,
+  0x10, 0xdd, 0x6c, 0x70, 0x51, 0x1f, 0x38, 0x7e, 0x88, 0x82, 0x6a, 0x80,
+  0x7f, 0x16, 0x82, 0xef, 0xfa, 0xa3, 0x71, 0x57, 0xfe, 0x19, 0x10, 0x6c,
+  0xd7, 0x07, 0xb0, 0x99, 0x40, 0xdc, 0xa9, 0x0d, 0x5c, 0xcb, 0xbf, 0x6e,
+  0x5f, 0xfd, 0x21, 0xfb, 0x37, 0x8e, 0x5f, 0xd7, 0x55, 0xc2, 0x86, 0x4a,
+  0xcd, 0xa5, 0x35, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae,
+  0x42, 0x60, 0x82
+};
+
+static const uint8_t mikro_png[] = {
+  0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
+  0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x40,
+  0x01, 0x03, 0x00, 0x00, 0x00, 0xe8, 0x18, 0xed, 0x3c, 0x00, 0x00, 0x00,
+  0x06, 0x50, 0x4c, 0x54, 0x45, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xa5,
+  0xd9, 0x9f, 0xdd, 0x00, 0x00, 0x00, 0x77, 0x49, 0x44, 0x41, 0x54, 0x38,
+  0xcb, 0x63, 0x60, 0x18, 0x05, 0xe4, 0x01, 0xf6, 0x07, 0x68, 0x02, 0xf6,
+  0x7f, 0xd0, 0x04, 0xfe, 0xd5, 0xa3, 0xf2, 0x19, 0x1f, 0xb0, 0x37, 0xa0,
+  0x08, 0x30, 0x37, 0x30, 0x1e, 0x40, 0x35, 0x93, 0xfd, 0x01, 0xaa, 0xa9,
+  0x7c, 0xf2, 0x3f, 0x0a, 0x50, 0x04, 0x78, 0xec, 0xff, 0x18, 0xa0, 0x08,
+  0xc8, 0xd4, 0xff, 0xb3, 0x40, 0x11, 0x90, 0xf8, 0xc0, 0x2f, 0x81, 0x22,
+  0x60, 0xf1, 0x80, 0x5d, 0x06, 0x45, 0xc0, 0xf2, 0x01, 0xfb, 0x1c, 0xfc,
+  0x02, 0x86, 0x0f, 0xd8, 0x7b, 0xf0, 0x0b, 0x10, 0x36, 0x03, 0xc3, 0x5a,
+  0x0c, 0x87, 0x61, 0x38, 0x1d, 0xc3, 0x73, 0x18, 0xde, 0xc7, 0x08, 0x20,
+  0x8c, 0x20, 0xc4, 0x08, 0x64, 0x8c, 0x68, 0xc0, 0x8c, 0x28, 0xfe, 0x0f,
+  0xa3, 0xc9, 0x99, 0x4c, 0x00, 0x00, 0x2c, 0x35, 0x29, 0x11, 0x00, 0x07,
+  0x1a, 0x05, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42,
+  0x60, 0x82
+};
diff --git a/libs/surfaces/maschine2/interface.cc b/libs/surfaces/maschine2/interface.cc
new file mode 100644 (file)
index 0000000..101d27b
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
+ *
+ * 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <stdexcept>
+
+#include "pbd/error.h"
+#include "ardour/rc_configuration.h"
+#include "control_protocol/control_protocol.h"
+#include "maschine2.h"
+
+using namespace ARDOUR;
+using namespace PBD;
+using namespace ArdourSurface;
+
+static ControlProtocol*
+new_maschine2 (ControlProtocolDescriptor*, Session* s)
+{
+       Maschine2* m2 = 0;
+
+       try {
+               m2 = new Maschine2 (*s);
+       }
+       catch (std::exception & e) {
+               PBD::error << "Failed to instantiate Maschine2: " << e.what() << endmsg;
+               delete m2;
+               m2 = 0;
+       }
+
+       m2->set_active (true);
+       return m2;
+}
+
+static void
+delete_maschine2 (ControlProtocolDescriptor*, ControlProtocol* cp)
+{
+       delete cp;
+}
+
+static bool
+probe_maschine2 (ControlProtocolDescriptor*)
+{
+       return true;
+}
+
+static void*
+maschine2_request_buffer_factory (uint32_t num_requests)
+{
+       return Maschine2::request_factory (num_requests);
+}
+
+static ControlProtocolDescriptor maschine2_descriptor = {
+       /*name :              */   "NI Maschine2",
+       /*id :                */   "uri://ardour.org/surfaces/maschine2:0",
+       /*ptr :               */   0,
+       /*module :            */   0,
+       /*mandatory :         */   0,
+       /*supports_feedback : */   false,
+       /*probe :             */   probe_maschine2,
+       /*initialize :        */   new_maschine2,
+       /*destroy :           */   delete_maschine2,
+       /*request_buffer_factory */ maschine2_request_buffer_factory
+};
+
+extern "C" ARDOURSURFACE_API ControlProtocolDescriptor* protocol_descriptor () { return &maschine2_descriptor; }
diff --git a/libs/surfaces/maschine2/layout.cc b/libs/surfaces/maschine2/layout.cc
new file mode 100644 (file)
index 0000000..30d248e
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+  Copyright (C) 2016 Paul Davis
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License as published by
+  the Free Software Foundation; either version 2 of the License, or
+  (at your option) any later version.
+
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+  GNU General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+#include "maschine2.h"
+#include "canvas.h"
+#include "layout.h"
+
+#ifdef __APPLE__
+#define Rect ArdourCanvas::Rect
+#endif
+
+using namespace ARDOUR;
+using namespace ArdourSurface;
+using namespace ArdourCanvas;
+
+Maschine2Layout::Maschine2Layout (Maschine2& m2, Session& s, const std::string& name)
+       : Container (m2.canvas())
+       , _m2 (m2)
+       , _session (s)
+       , _name (name)
+{
+}
+
+Maschine2Layout::~Maschine2Layout ()
+{
+}
+
+void
+Maschine2Layout::compute_bounding_box () const
+{
+       /* all layouts occupy at least the full screen, even if their combined
+        * child boxes do not.
+        */
+       _bounding_box = Rect (0, 0, display_width(), display_height());
+       _bounding_box_dirty = false;
+}
+
+int
+Maschine2Layout::display_height() const
+{
+       return _m2.canvas()->height();
+}
+
+int
+Maschine2Layout::display_width() const
+{
+       return _m2.canvas()->width();
+}
diff --git a/libs/surfaces/maschine2/layout.h b/libs/surfaces/maschine2/layout.h
new file mode 100644 (file)
index 0000000..3cacf09
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 Paul Davis
+ * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
+ *
+ * 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _ardour_maschine2_layout_h_
+#define _ardour_maschine2_layout_h_
+
+#include <sigc++/trackable.h>
+#include <cairomm/refptr.h>
+#include "canvas/container.h"
+
+namespace ARDOUR {
+       class Session;
+}
+
+namespace ArdourSurface {
+
+class Maschine2;
+
+class Maschine2Layout : public sigc::trackable, public ArdourCanvas::Container
+{
+  public:
+       Maschine2Layout (Maschine2& m2, ARDOUR::Session& s, std::string const & name);
+       virtual ~Maschine2Layout ();
+
+       std::string name() const { return _name; }
+       int display_width () const;
+       int display_height () const;
+
+       void compute_bounding_box () const;
+
+  protected:
+       Maschine2& _m2;
+       ARDOUR::Session& _session;
+       std::string _name;
+};
+
+} /* namespace */
+
+#endif /* _ardour_maschine2_layout_h_ */
diff --git a/libs/surfaces/maschine2/m2_button.h b/libs/surfaces/maschine2/m2_button.h
new file mode 100644 (file)
index 0000000..cfe26b1
--- /dev/null
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
+ *
+ * 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _ardour_surfaces_m2button_h_
+#define _ardour_surfaces_m2button_h_
+
+#include <stdint.h>
+#include "gtkmm2ext/colors.h"
+#include "pbd/signals.h"
+
+namespace ArdourSurface {
+
+class M2ButtonInterface
+{
+       public:
+               M2ButtonInterface () {}
+               virtual ~M2ButtonInterface () {}
+
+               /* user API */
+               PBD::Signal1<void, bool> changed;
+               PBD::Signal0<void> pressed;
+               PBD::Signal0<void> released;
+
+               virtual void set_blinking (bool) {}
+               virtual void set_color (uint32_t rgba) {}
+
+               virtual bool is_pressed () const { return false; }
+               virtual bool active () const { return is_pressed (); }
+
+               virtual void ignore_release () {}
+
+               // TODO allow to suspend *next* release signal
+               // e.g. press + hold "grid", move encoder -> release "grid" -> noop
+
+               /* internal API - called from device thread */
+               virtual bool set_active (bool a) { return false; }
+               virtual uint8_t lightness (float) const { return 0; }
+               virtual uint32_t color (float) const { return 0; }
+};
+
+class M2Button : public M2ButtonInterface
+{
+       public:
+               M2Button ()
+                       : M2ButtonInterface ()
+                       , _pressed (false)
+                       , _blink (false)
+                       , _ignore_release (false)
+                       , _lightness (0)
+                       , _rgba (0)
+               {}
+
+               /* user API */
+               void set_blinking (bool en) {
+                       _blink = en;
+               }
+
+               virtual void set_color (uint32_t rgba) {
+                       _rgba = rgba;
+                       /* 7 bit color */
+                       const uint8_t r = ((rgba >> 24) & 0xff) >> 1;
+                       const uint8_t g = ((rgba >> 16) & 0xff) >> 1;
+                       const uint8_t b = ((rgba >>  8) & 0xff) >> 1;
+                       _lightness = std::max (r, std::max (g, b));
+               }
+
+               bool is_pressed () const { return _pressed; }
+
+               void ignore_release () {
+                       if (_pressed) {
+                               _ignore_release = true;
+                       }
+               }
+               
+               /* internal API - called from device thread */
+               virtual bool set_active (bool a) {
+                       if (a == _pressed) {
+                               return false;
+                       }
+                       _pressed = a;
+
+                       if (a) {
+                               pressed (); /* EMIT SIGNAL */
+                       } else {
+                               if (_ignore_release) {
+                                       _ignore_release = false;
+                               } else {
+                                       released (); /* EMIT SIGNAL */
+                               }
+                       }
+                       changed (a); /* EMIT SIGNAL */
+                       return true;
+               }
+
+               uint8_t lightness (float blink) const {
+                       if (_blink && blink >= 0.f && blink <= 1.f) {
+                               return (uint8_t) floorf(blink * _lightness);
+                       }
+                       return _lightness;
+               }
+
+               uint32_t color (float blink) const {
+                       if (_blink && blink >= 0.f && blink <= 1.f) {
+                               Gtkmm2ext::HSV hsv (_rgba);
+                               Gtkmm2ext::HSV s (hsv.shade (blink));
+                               return s.color();
+                       }
+                       return _rgba;
+               }
+
+       protected:
+               bool     _pressed;
+               bool     _blink;
+               bool     _ignore_release;
+               uint8_t  _lightness;
+               uint32_t _rgba;
+};
+
+class M2StatelessButton : public M2Button
+{
+       public:
+               M2StatelessButton () : M2Button () {}
+
+               bool set_active (bool a) {
+                       if (a == _pressed) {
+                               return false;
+                       }
+                       if (a) {
+                               set_color (0xffffffff);
+                       } else {
+                               set_color (0x000000ff);
+                       }
+                       return M2Button::set_active (a);
+               }
+};
+
+class M2ToggleButton : public M2Button
+{
+       public:
+               M2ToggleButton ()
+               : M2Button ()
+               , _active (false)
+               {
+                       changed.connect_same_thread (changed_connection, boost::bind (&M2ToggleButton::change_event, this, _1));
+               }
+
+               PBD::Signal1<void, bool> toggled;
+               bool active () const { return _active; }
+
+       protected:
+               void change_event (bool down) {
+                       if (down) { return; }
+                       _active = !_active;
+                       set_color (_active ? 0xffffffff : 0x000000ff);
+                       toggled (_active);
+               }
+
+               PBD::ScopedConnection changed_connection;
+               bool _active;
+};
+
+class M2ToggleHoldButton : public M2Button
+{
+       public:
+               M2ToggleHoldButton ()
+               : M2Button ()
+               , _active (false)
+               , _active_on_release (false)
+               {
+                       changed.connect_same_thread (changed_connection, boost::bind (&M2ToggleHoldButton::change_event, this, _1));
+               }
+
+               PBD::Signal1<void, bool> toggled;
+               bool active () const { return _active; }
+               void unset_active_on_release () { if (is_pressed ()) { _active_on_release = false; } }
+
+       protected:
+               void change_event (bool down) {
+                       if (down) {
+                               if (_active) {
+                                       _active_on_release = false;
+                                       return;
+                               }
+                               _active = true;
+                               _active_on_release = true;
+                       } else {
+                               if (_active == _active_on_release) {
+                                       return;
+                               }
+                               _active = _active_on_release;
+                       }
+
+                       set_color (_active ? 0xffffffff : 0x000000ff);
+                       toggled (_active);
+               }
+
+               PBD::ScopedConnection changed_connection;
+               bool _active;
+               bool _active_on_release;
+};
+
+
+} /* namespace */
+#endif /* _ardour_surfaces_m2button_h_ */
+
diff --git a/libs/surfaces/maschine2/m2_dev_mikro.cc b/libs/surfaces/maschine2/m2_dev_mikro.cc
new file mode 100644 (file)
index 0000000..f31f6ec
--- /dev/null
@@ -0,0 +1,284 @@
+#include <math.h>
+
+#include "pbd/compose.h"
+
+#include "maschine2.h"
+#include "m2controls.h"
+#include "m2_dev_mikro.h"
+
+#include <pangomm/fontdescription.h>
+
+#include "images.h"
+
+static size_t mikro_png_readoff = 0;
+
+static Cairo::ErrorStatus maschine_png_read (unsigned char* d, unsigned int s) {
+       if (s + mikro_png_readoff > sizeof (mikro_png)) {
+               return CAIRO_STATUS_READ_ERROR;
+       }
+       memcpy (d, &mikro_png[mikro_png_readoff], s);
+       mikro_png_readoff += s;
+       return CAIRO_STATUS_SUCCESS;
+}
+
+using namespace ArdourSurface;
+
+Maschine2Mikro::Maschine2Mikro () : M2Device ()
+{
+       _surface = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, 128, 64);
+       clear (true);
+}
+
+void
+Maschine2Mikro::clear (bool splash)
+{
+       M2Device::clear (splash);
+
+       memset (&ctrl_in, 0, sizeof (ctrl_in));
+       memset (pad, 0, sizeof (pad));
+
+       _lights[0] = 0xff;
+
+       for (int l = 0; l < 4; ++l) {
+               _img[l][0] = 0xff;
+       }
+
+       Cairo::RefPtr<Cairo::Context> cr = Cairo::Context::create (_surface);
+       if (!splash) {
+               mikro_png_readoff = 0;
+               Cairo::RefPtr<Cairo::ImageSurface> sf = Cairo::ImageSurface::create_from_png_stream (sigc::ptr_fun (maschine_png_read));
+               cr->set_source(sf, 0, 0);
+               cr->paint ();
+       } else {
+               cr->set_operator (Cairo::OPERATOR_CLEAR);
+               cr->paint ();
+               cr->set_operator (Cairo::OPERATOR_OVER);
+
+               Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create (cr);
+               Pango::FontDescription fd ("Sans Bold 18px");
+               layout->set_font_description (fd);
+               layout->set_alignment (Pango::ALIGN_CENTER);
+
+               layout->set_text (string_compose ("%1\n%2", PROGRAM_NAME, VERSIONSTRING));
+               int tw, th;
+               layout->get_pixel_size (tw, th);
+               cr->move_to (128 - tw * 0.5, 32 - th * 0.5);
+               cr->set_source_rgb (1, 1, 1);
+               layout->show_in_cairo_context(cr);
+       }
+       //_surface->write_to_png ("/tmp/amaschine.png");
+}
+
+void
+Maschine2Mikro::read (hid_device* handle, M2Contols* ctrl)
+{
+       assert (ctrl);
+       while (true) {
+               uint8_t buf[256];
+               int res = hid_read (handle, buf, 256);
+               if (res < 1) {
+                       return;
+               }
+
+               // TODO parse incrementally if chunked at 64
+
+               if (res > 4 && buf[0] == 0x01) {
+                       memcpy (&ctrl_in, &buf[1], sizeof (ctrl_in));
+                       assign_controls (ctrl);
+               }
+               else if (res > 32 && buf[0] == 0x20) {
+                       for (unsigned int i = 0; i < 16; ++i) {
+                               uint8_t v0 = buf[1 + 2 * i];
+                               uint8_t v1 = buf[2 + 2 * i];
+                               uint8_t p = (v1 & 0xf0) >> 4;
+                               pad[p] = ((v1 & 0xf) << 8) | v0;
+                               unsigned int pid = 15 - ((i & 0xc) + (3 - (i & 0x3)));
+                               ctrl->pad (pid)->set_value (pad[p]);
+                       }
+                       // TODO read complete 65 byte msg, expect buf[33] == 0x00
+               }
+       }
+}
+
+void
+Maschine2Mikro::write (hid_device* handle, M2Contols* ctrl)
+{
+       bump_blink ();
+       uint8_t buf[265];
+
+       //TODO double-buffer, send changes only if needed
+
+       /* 30 control buttons, 8-bit brightness,
+        * + 16 RGB pads
+        */
+       buf[0] = 0x80;
+       set_lights (ctrl, &buf[1]);
+       set_pads (ctrl, &buf[31]);
+       if (memcmp (_lights, buf, 79)) {
+                       hid_write (handle, buf, 79);
+                       memcpy (_lights, buf, 79);
+       }
+
+       if (_splashcnt < _splashtime ) {
+               ++_splashcnt;
+       }
+       else if (! vblank () /* EMIT SIGNAL*/) {
+               /* check clear/initial draw */
+               if (_img[0][0] != 0xff) {
+                       return;
+               }
+       }
+
+       /* display */
+       _surface->flush ();
+       const unsigned char* img = _surface->get_data ();
+       const int stride = _surface->get_stride ();
+       memset (buf, 0, 9);
+       buf[0] = 0xe0;
+       for (int l = 0; l < 4; ++l) {
+               buf[1] = 32 * l;
+               buf[5] = 0x20;
+               buf[7] = 0x08;
+
+               int y0 = l * 16;
+               for (int p = 0; p < 256; ++p) {
+                       uint8_t v = 0;
+                       const int y = y0 + p / 16;
+                       for (int b = 0; b < 8; ++b) {
+                               const int x = (p % 16) * 8 + b;
+                               int off = y * stride + x * 4 /* ARGB32 */;
+                               /* off + 0 == blue
+                                * off + 1 == green
+                                * off + 2 == red
+                                * off + 3 == alpha
+                                */
+                               /* calculate lightness */
+                               uint8_t l = std::max (img[off + 0], std::max (img[off + 1], img[off + 2]));
+                               if (l > 0x7e) { // TODO: take alpha channel into account?!
+                                       v |= 1 << (7 - b);
+                               }
+                       }
+                       buf[9 + p] = v;
+               }
+               if (memcmp (_img[l], buf, 265)) {
+                       hid_write (handle, buf, 265);
+                       memcpy (_img[l], buf, 265);
+               }
+       }
+}
+
+void
+Maschine2Mikro::assign_controls (M2Contols* ctrl) const
+{
+       ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->set_active (ctrl_in.trs_shift ? true : false);
+       M2Contols::Modifier mod = ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->active () ? M2Contols::ModShift : M2Contols::ModNone;
+
+       bool change = false;
+#define ASSIGN(BTN, VAR) \
+       change |= ctrl->button (M2Contols:: BTN, mod)->set_active (ctrl_in. VAR ? true : false)
+
+       ASSIGN (BtnRestart,   trs_restart);
+       ASSIGN (BtnStepLeft,  trs_left);
+       ASSIGN (BtnStepRight, trs_right);
+       ASSIGN (BtnGrid,      trs_grid);
+       ASSIGN (BtnPlay,      trs_play);
+       ASSIGN (BtnRec,       trs_rec);
+       ASSIGN (BtnErase,     trs_erase);
+
+       ASSIGN (BtnGroupA,     group);
+       ASSIGN (BtnBrowse,     browse);
+       ASSIGN (BtnSampling,   sampling);
+       ASSIGN (BtnNoteRepeat, note_repeat);
+       ASSIGN (BtnWheel,      mst_wheel);
+
+       ASSIGN (BtnTop0, f1);
+       ASSIGN (BtnTop1, f1);
+       ASSIGN (BtnTop2, f3);
+
+       ASSIGN (BtnControl,    control);
+       ASSIGN (BtnNavigate,   navigate); // XXX
+       ASSIGN (BtnNavLeft,    nav_left);
+       ASSIGN (BtnNavRight,   nav_right);
+       ASSIGN (BtnEnter,      main);
+
+       ASSIGN (BtnScene,     pads_scene);
+       ASSIGN (BtnPattern,   pads_pattern);
+       ASSIGN (BtnPadMode,   pads_mode);
+       ASSIGN (BtnNavigate,  pads_navigate);
+       ASSIGN (BtnDuplicate, pads_duplicate);
+       ASSIGN (BtnSelect,    pads_select);
+       ASSIGN (BtnSolo,      pads_solo);
+       ASSIGN (BtnMute,      pads_mute);
+#undef ASSIGN
+
+       change |= ctrl->encoder (0)->set_value (ctrl_in.mst_wheel_pos);
+
+       if (change && mod == M2Contols::ModShift) {
+               M2ToggleHoldButton* btn = dynamic_cast<M2ToggleHoldButton*> (ctrl->button (M2Contols::BtnShift, M2Contols::ModNone));
+               if (btn) {
+                       btn->unset_active_on_release ();
+               }
+       }
+}
+
+#define LIGHT(BIT, BTN) \
+       b[BIT] = ctrl->button (M2Contols:: BTN, mod)->lightness (_blink_shade)
+
+void
+Maschine2Mikro::set_pads (M2Contols* ctrl, uint8_t* b) const
+{
+       if (!ctrl) {
+               memset (b, 0, 48);
+               return;
+       }
+       for (unsigned int i = 0; i < 16; ++i) {
+               unsigned int pid = 15 - ((i & 0xc) + (3 - (i & 0x3)));
+               ctrl->pad (pid)->color (b[i * 3], b[1 + i * 3], b[2 + i * 3]);
+       }
+}
+
+void
+Maschine2Mikro::set_lights (M2Contols* ctrl, uint8_t* b) const
+{
+       if (!ctrl) {
+               memset (b, 0, 29);
+               return;
+       }
+       M2Contols::Modifier mod = ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->active () ? M2Contols::ModShift : M2Contols::ModNone;
+
+       LIGHT ( 0, BtnTop0); // F1
+       LIGHT ( 1, BtnTop1); // F2
+       LIGHT ( 2, BtnTop2); // F3
+       LIGHT ( 3, BtnControl);
+       LIGHT ( 4, BtnNavigate); // XXX
+       LIGHT ( 5, BtnNavLeft);
+       LIGHT ( 6, BtnNavRight);
+       LIGHT ( 7, BtnEnter); // Main
+
+       const uint32_t rgb = ctrl->button (M2Contols::BtnGroupA, mod)->color (_blink_shade);
+       b[8]  = (rgb >>  0) & 0xff;
+       b[9]  = (rgb >>  8) & 0xff;
+       b[10] = (rgb >> 16) & 0xff;
+
+       LIGHT (11, BtnBrowse);
+       LIGHT (12, BtnSampling);
+       LIGHT (13, BtnNoteRepeat);
+
+       LIGHT (14, BtnRestart);
+       LIGHT (15, BtnStepLeft);
+       LIGHT (16, BtnStepRight);
+       LIGHT (17, BtnGrid);
+       LIGHT (18, BtnPlay);
+       LIGHT (19, BtnRec);
+       LIGHT (20, BtnErase);
+       LIGHT (21, BtnShift);
+
+       LIGHT (22, BtnScene);
+       LIGHT (23, BtnPattern);
+       LIGHT (24, BtnPadMode);
+       LIGHT (25, BtnNavigate);
+       LIGHT (26, BtnDuplicate);
+       LIGHT (27, BtnSelect);
+       LIGHT (28, BtnSolo);
+       LIGHT (29, BtnMute);
+}
diff --git a/libs/surfaces/maschine2/m2_dev_mikro.h b/libs/surfaces/maschine2/m2_dev_mikro.h
new file mode 100644 (file)
index 0000000..c836c14
--- /dev/null
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
+ *
+ * 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _ardour_surfaces_m2mikro_h_
+#define _ardour_surfaces_m2mikro_h_
+
+#include "m2device.h"
+
+#include <cairomm/context.h>
+#include <pangomm/layout.h>
+
+namespace ArdourSurface {
+
+class Maschine2Mikro : public M2Device
+{
+       public:
+               Maschine2Mikro ();
+               void clear (bool splash = false);
+               void read (hid_device*, M2Contols*);
+               void write (hid_device*, M2Contols*);
+               Cairo::RefPtr<Cairo::ImageSurface> surface () { return _surface; }
+
+       private:
+
+#if defined(__GNUC__)
+#define ATTRIBUTE_PACKED  __attribute__((__packed__))
+#else
+#define ATTRIBUTE_PACKED
+#pragma pack(1)
+#endif
+
+               struct machine_mk2_input {
+                       unsigned int trs_restart     : 1; // 0
+                       unsigned int trs_left        : 1;
+                       unsigned int trs_right       : 1;
+                       unsigned int trs_grid        : 1;
+                       unsigned int trs_play        : 1;
+                       unsigned int trs_rec         : 1;
+                       unsigned int trs_erase       : 1;
+                       unsigned int trs_shift       : 1;
+                       unsigned int group           : 1; // 8
+                       unsigned int browse          : 1;
+                       unsigned int sampling        : 1;
+                       unsigned int note_repeat     : 1;
+                       unsigned int mst_wheel       : 1;
+                       unsigned int reserved        : 3;
+                       unsigned int f1              : 1; // 16
+                       unsigned int f2              : 1;
+                       unsigned int f3              : 1;
+                       unsigned int control         : 1;
+                       unsigned int navigate        : 1;
+                       unsigned int nav_left        : 1;
+                       unsigned int nav_right       : 1;
+                       unsigned int main            : 1;
+                       unsigned int pads_scene      : 1; // 24
+                       unsigned int pads_pattern    : 1;
+                       unsigned int pads_mode       : 1;
+                       unsigned int pads_navigate   : 1;
+                       unsigned int pads_duplicate  : 1;
+                       unsigned int pads_select     : 1;
+                       unsigned int pads_solo       : 1;
+                       unsigned int pads_mute       : 1; // 31
+                       unsigned int mst_wheel_pos   : 8; // 32..40 // range: 0..15
+               } ATTRIBUTE_PACKED ctrl_in;
+
+#if (!defined __GNUC__)
+#pragma pack()
+#endif
+               uint16_t pad[16];
+
+               Cairo::RefPtr<Cairo::ImageSurface> _surface;
+
+       private:
+               void assign_controls (M2Contols*) const;
+
+               void set_lights (M2Contols*, uint8_t*) const;
+               void set_pads (M2Contols*, uint8_t*) const;
+
+               uint8_t _lights[79];
+               uint8_t _img[4][265];
+};
+} /* namespace */
+
+#endif
diff --git a/libs/surfaces/maschine2/m2_dev_mk2.cc b/libs/surfaces/maschine2/m2_dev_mk2.cc
new file mode 100644 (file)
index 0000000..ba8d876
--- /dev/null
@@ -0,0 +1,380 @@
+#include <math.h>
+
+#include "pbd/compose.h"
+
+#include "maschine2.h"
+#include "m2controls.h"
+#include "m2_dev_mk2.h"
+
+#include <pangomm/fontdescription.h>
+
+#include "images.h"
+
+static size_t maschine_png_readoff = 0;
+
+static Cairo::ErrorStatus maschine_png_read (unsigned char* d, unsigned int s) {
+       if (s + maschine_png_readoff > sizeof (maschine_png)) {
+               return CAIRO_STATUS_READ_ERROR;
+       }
+       memcpy (d, &maschine_png[maschine_png_readoff], s);
+       maschine_png_readoff += s;
+       return CAIRO_STATUS_SUCCESS;
+}
+
+using namespace ArdourSurface;
+
+Maschine2Mk2::Maschine2Mk2 () : M2Device ()
+{
+       _surface = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, 512, 64);
+       clear (true);
+}
+
+void
+Maschine2Mk2::clear (bool splash)
+{
+       M2Device::clear (splash);
+
+       memset (&ctrl_in, 0, sizeof (ctrl_in));
+       memset (pad, 0, sizeof (pad));
+
+       ctrl80[0] = 0xff;
+       ctrl81[0] = 0xff;
+       ctrl82[0] = 0xff;
+
+       for (int d = 0; d < 2; ++d) {
+               for (int l = 0; l < 8; ++l) {
+                       _img[d][l][0] = 0xff;
+               }
+       }
+
+#if 0
+       Cairo::RefPtr<Cairo::Context> c = Cairo::Context::create (_surface);
+       c->set_operator (Cairo::OPERATOR_CLEAR);
+       c->paint ();
+       return;
+#endif
+
+       maschine_png_readoff = 0;
+       Cairo::RefPtr<Cairo::ImageSurface> sf = Cairo::ImageSurface::create_from_png_stream (sigc::ptr_fun (maschine_png_read));
+       Cairo::RefPtr<Cairo::Context> cr = Cairo::Context::create (_surface);
+       cr->set_source(sf, 0, 0);
+       cr->paint ();
+
+       Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create (cr);
+       Pango::FontDescription fd ("Sans Bold 18px");
+       layout->set_font_description (fd);
+       layout->set_alignment (Pango::ALIGN_CENTER);
+
+       int cx;
+       if (splash) {
+               layout->set_text (string_compose ("%1\n%2", PROGRAM_NAME, VERSIONSTRING));
+               cx = 384;
+       } else {
+               cr->rectangle (326, 0, 186, 64);
+               cr->set_source_rgb (0, 0, 0);
+               cr->fill ();
+               layout->set_text ("Keep Groovin'");
+               cx = 421;
+       }
+
+       int tw, th;
+       layout->get_pixel_size (tw, th);
+       cr->move_to (cx - tw * 0.5, 32 - th * 0.5);
+       cr->set_source_rgb (1, 1, 1);
+       layout->show_in_cairo_context(cr);
+       //_surface->write_to_png ("/tmp/amaschine.png");
+}
+
+void
+Maschine2Mk2::read (hid_device* handle, M2Contols* ctrl)
+{
+       assert (ctrl);
+       while (true) {
+               uint8_t buf[256];
+               int res = hid_read (handle, buf, 256);
+               if (res < 1) {
+                       return;
+               }
+
+               // TODO parse incrementally if chunked at 64
+
+               if (res > 24 && buf[0] == 0x01) {
+                       memcpy (&ctrl_in, &buf[1], sizeof (ctrl_in));
+                       assign_controls (ctrl);
+               }
+               else if (res > 32 && buf[0] == 0x20) {
+                       for (unsigned int i = 0; i < 16; ++i) {
+                               uint8_t v0 = buf[1 + 2 * i];
+                               uint8_t v1 = buf[2 + 2 * i];
+                               uint8_t p = (v1 & 0xf0) >> 4;
+                               pad[p] = ((v1 & 0xf) << 8) | v0;
+                               unsigned int pid = 15 - ((i & 0xc) + (3 - (i & 0x3)));
+                               ctrl->pad (pid)->set_value (pad[p]);
+                       }
+                       // TODO read complete 65 byte msg, expect buf[33] == 0x00
+               }
+       }
+}
+
+void
+Maschine2Mk2::write (hid_device* handle, M2Contols* ctrl)
+{
+       bump_blink ();
+       uint8_t buf[265];
+
+       //TODO double-buffer, send changes only if needed
+
+       /* 31 control buttons: 8 mst + 8 top + 8 pads + 7 mst
+        * 8-bit brightness
+        */
+       buf[0] = 0x82;
+       set_colors82 (ctrl, &buf[1]);
+       if (memcmp (ctrl82, buf, 32)) {
+                       hid_write (handle, buf, 32);
+                       memcpy (ctrl82, buf, 32);
+       }
+
+       /* 8 group rgb|rgb + 8 on/off transport buttons */
+       buf[0] = 0x81;
+       set_colors81 (ctrl, &buf[1]);
+       if (memcmp (ctrl81, buf, 57)) {
+                       hid_write (handle, buf, 57);
+                       memcpy (ctrl81, buf, 57);
+       }
+
+       /* 16 RGB grid pads */
+       buf[0] = 0x80;
+       set_colors80 (ctrl, &buf[1]);
+       if (memcmp (ctrl80, buf, 49)) {
+                       hid_write (handle, buf, 49);
+                       memcpy (ctrl80, buf, 49);
+       }
+
+       if (_splashcnt < _splashtime) {
+               ++_splashcnt;
+       }
+       else if (! vblank () /* EMIT SIGNAL*/) {
+               /* check clear/initial draw */
+               if (_img[0][0][0] != 0xff) {
+                       return;
+               }
+       }
+
+       /* display */
+       _surface->flush ();
+       const unsigned char* img = _surface->get_data ();
+       const int stride = _surface->get_stride ();
+       for (int d = 0; d < 2; ++d) {
+               memset (buf, 0, 9);
+               buf[0] = 0xe0 | d;
+               for (int l = 0; l < 8; ++l) {
+                       buf[3] = 8 * l;
+                       buf[5] = 0x20;
+                       buf[7] = 0x08;
+
+                       int y0 = l * 8;
+                       int x0 = d * 256;
+
+                       for (int p = 0; p < 256; ++p) {
+                               uint8_t v = 0;
+                               const int y = y0 + p / 32;
+                               for (int b = 0; b < 8; ++b) {
+                                       const int x = x0 + (p % 32) * 8 + b;
+                                       int off = y * stride + x * 4 /* ARGB32 */;
+                                       /* off + 0 == blue
+                                        * off + 1 == green
+                                        * off + 2 == red
+                                        * off + 3 == alpha
+                                        */
+                                       /* calculate lightness */
+                                       uint8_t l = std::max (img[off + 0], std::max (img[off + 1], img[off + 2]));
+                                       if (l > 0x7e) { // TODO: take alpha channel into account?!
+                                               v |= 1 << (7 - b);
+                                       }
+                               }
+                               buf[9 + p] = v;
+                       }
+                       if (memcmp (_img[d][l], buf, 265)) {
+                               hid_write (handle, buf, 265);
+                               memcpy (_img[d][l], buf, 265);
+                       }
+               }
+       }
+}
+
+void
+Maschine2Mk2::assign_controls (M2Contols* ctrl) const
+{
+       ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->set_active (ctrl_in.trs_shift ? true : false);
+       M2Contols::Modifier mod = ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->active () ? M2Contols::ModShift : M2Contols::ModNone;
+
+       bool change = false;
+#define ASSIGN(BTN, VAR) \
+       change |= ctrl->button (M2Contols:: BTN, mod)->set_active (ctrl_in. VAR ? true : false)
+
+       ASSIGN (BtnRestart,   trs_restart);
+       ASSIGN (BtnStepLeft,  trs_left);
+       ASSIGN (BtnStepRight, trs_right);
+       ASSIGN (BtnGrid,      trs_grid);
+       ASSIGN (BtnPlay,      trs_play);
+       ASSIGN (BtnRec,       trs_rec);
+       ASSIGN (BtnErase,     trs_erase);
+
+       ASSIGN (BtnScene,     pads_scene);
+       ASSIGN (BtnPattern,   pads_pattern);
+       ASSIGN (BtnPadMode,   pads_mode);
+       ASSIGN (BtnNavigate,  pads_navigate);
+       ASSIGN (BtnDuplicate, pads_duplicate);
+       ASSIGN (BtnSelect,    pads_select);
+       ASSIGN (BtnSolo,      pads_solo);
+       ASSIGN (BtnMute,      pads_mute);
+
+       ASSIGN (BtnControl,  top_control);
+       ASSIGN (BtnStep,     top_step);
+       ASSIGN (BtnBrowse,   top_browse);
+       ASSIGN (BtnSampling, top_sampling);
+       ASSIGN (BtnSelLeft,  top_left);
+       ASSIGN (BtnSelRight, top_right);
+       ASSIGN (BtnAll,      top_all);
+       ASSIGN (BtnAuto,     top_auto);
+
+       ASSIGN (BtnVolume,     mst_volume);
+       ASSIGN (BtnSwing,      mst_swing);
+       ASSIGN (BtnTempo,      mst_tempo);
+       ASSIGN (BtnNavLeft,    mst_left);
+       ASSIGN (BtnNavRight,   mst_right);
+       ASSIGN (BtnEnter,      mst_enter);
+       ASSIGN (BtnNoteRepeat, mst_note_repeat);
+       ASSIGN (BtnWheel,      mst_wheel);
+
+       ASSIGN (BtnGroupA, groups_a);
+       ASSIGN (BtnGroupB, groups_b);
+       ASSIGN (BtnGroupC, groups_c);
+       ASSIGN (BtnGroupD, groups_d);
+       ASSIGN (BtnGroupE, groups_e);
+       ASSIGN (BtnGroupF, groups_f);
+       ASSIGN (BtnGroupG, groups_g);
+       ASSIGN (BtnGroupH, groups_h);
+
+       ASSIGN (BtnTop0, top_0);
+       ASSIGN (BtnTop1, top_1);
+       ASSIGN (BtnTop2, top_2);
+       ASSIGN (BtnTop3, top_3);
+       ASSIGN (BtnTop4, top_4);
+       ASSIGN (BtnTop5, top_5);
+       ASSIGN (BtnTop6, top_6);
+       ASSIGN (BtnTop7, top_7);
+#undef ASSIGN
+
+       change |= ctrl->encoder (0)->set_value (ctrl_in.mst_wheel_pos);
+       for (int i = 0; i < 8; ++i) {
+               change |= ctrl->encoder (1 + i)->set_value (ctrl_in.top_knobs[i]);
+       }
+
+       if (change && mod == M2Contols::ModShift) {
+               M2ToggleHoldButton* btn = dynamic_cast<M2ToggleHoldButton*> (ctrl->button (M2Contols::BtnShift, M2Contols::ModNone));
+               if (btn) {
+                       btn->unset_active_on_release ();
+               }
+       }
+}
+
+#define LIGHT(BIT, BTN) \
+       b[BIT] = ctrl->button (M2Contols:: BTN, mod)->lightness (_blink_shade)
+
+#define COLOR(BIT, BTN) \
+{ \
+       const uint32_t rgb = ctrl->button (M2Contols:: BTN, mod)->color (_blink_shade); \
+               b[0 + BIT ] = (rgb >>  0) & 0xff; \
+               b[1 + BIT ] = (rgb >>  8) & 0xff; \
+               b[2 + BIT ] = (rgb >> 16) & 0xff; \
+               b[3 + BIT ] = (rgb >>  0) & 0xff; \
+               b[4 + BIT ] = (rgb >>  8) & 0xff; \
+               b[5 + BIT ] = (rgb >> 16) & 0xff; \
+}
+
+void
+Maschine2Mk2::set_colors80 (M2Contols* ctrl, uint8_t* b) const
+{
+       if (!ctrl) {
+               memset (b, 0, 48);
+               return;
+       }
+       for (unsigned int i = 0; i < 16; ++i) {
+               unsigned int pid = 15 - ((i & 0xc) + (3 - (i & 0x3)));
+               ctrl->pad (pid)->color (b[i * 3], b[1 + i * 3], b[2 + i * 3]);
+       }
+}
+
+void
+Maschine2Mk2::set_colors81 (M2Contols* ctrl, uint8_t* b) const
+{
+       if (!ctrl) {
+               memset (b, 0, 56);
+               return;
+       }
+       M2Contols::Modifier mod = ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->active () ? M2Contols::ModShift : M2Contols::ModNone;
+
+       COLOR ( 0, BtnGroupA);
+       COLOR ( 6, BtnGroupB);
+       COLOR (12, BtnGroupC);
+       COLOR (18, BtnGroupD);
+       COLOR (24, BtnGroupE);
+       COLOR (30, BtnGroupF);
+       COLOR (36, BtnGroupG);
+       COLOR (42, BtnGroupH);
+
+       LIGHT (48, BtnRestart);
+       LIGHT (49, BtnStepLeft);
+       LIGHT (50, BtnStepRight);
+       LIGHT (51, BtnGrid);
+       LIGHT (52, BtnPlay);
+       LIGHT (53, BtnRec);
+       LIGHT (54, BtnErase);
+       LIGHT (55, BtnShift);
+}
+
+void
+Maschine2Mk2::set_colors82 (M2Contols* ctrl, uint8_t* b) const
+{
+       if (!ctrl) {
+               memset (b, 0, 31);
+               return;
+       }
+       M2Contols::Modifier mod = ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->active () ? M2Contols::ModShift : M2Contols::ModNone;
+
+       LIGHT ( 0, BtnControl);
+       LIGHT ( 1, BtnStep);
+       LIGHT ( 2, BtnBrowse);
+       LIGHT ( 3, BtnSampling);
+       LIGHT ( 4, BtnSelLeft);
+       LIGHT ( 5, BtnSelRight);
+       LIGHT ( 6, BtnAll);
+       LIGHT ( 7, BtnAuto);
+
+       LIGHT ( 8, BtnTop0);
+       LIGHT ( 9, BtnTop1);
+       LIGHT (10, BtnTop2);
+       LIGHT (11, BtnTop3);
+       LIGHT (12, BtnTop4);
+       LIGHT (13, BtnTop5);
+       LIGHT (14, BtnTop6);
+       LIGHT (15, BtnTop7);
+
+       LIGHT (16, BtnScene);
+       LIGHT (17, BtnPattern);
+       LIGHT (18, BtnPadMode);
+       LIGHT (19, BtnNavigate);
+       LIGHT (20, BtnDuplicate);
+       LIGHT (21, BtnSelect);
+       LIGHT (22, BtnSolo);
+       LIGHT (23, BtnMute);
+
+       LIGHT (24, BtnVolume);
+       LIGHT (25, BtnSwing);
+       LIGHT (26, BtnTempo);
+       LIGHT (27, BtnNavLeft);
+       LIGHT (28, BtnNavRight);
+       LIGHT (29, BtnEnter);
+       LIGHT (30, BtnNoteRepeat);
+}
diff --git a/libs/surfaces/maschine2/m2_dev_mk2.h b/libs/surfaces/maschine2/m2_dev_mk2.h
new file mode 100644 (file)
index 0000000..a9adfa6
--- /dev/null
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
+ *
+ * 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _ardour_surfaces_m2mk2_h_
+#define _ardour_surfaces_m2mk2_h_
+
+#include "m2device.h"
+
+#include <cairomm/context.h>
+#include <pangomm/layout.h>
+
+namespace ArdourSurface {
+
+class Maschine2Mk2 : public M2Device
+{
+       public:
+               Maschine2Mk2 ();
+               void clear (bool splash = false);
+               void read (hid_device*, M2Contols*);
+               void write (hid_device*, M2Contols*);
+               Cairo::RefPtr<Cairo::ImageSurface> surface () { return _surface; }
+
+       private:
+
+#if defined(__GNUC__)
+#define ATTRIBUTE_PACKED  __attribute__((__packed__))
+#else
+#define ATTRIBUTE_PACKED
+#pragma pack(1)
+#endif
+
+               struct machine_mk2_input {
+                       unsigned int top_0           : 1; // 0
+                       unsigned int top_1           : 1;
+                       unsigned int top_2           : 1;
+                       unsigned int top_3           : 1;
+                       unsigned int top_4           : 1;
+                       unsigned int top_5           : 1;
+                       unsigned int top_6           : 1;
+                       unsigned int top_7           : 1;
+                       unsigned int top_control     : 1; // 8
+                       unsigned int top_step        : 1;
+                       unsigned int top_browse      : 1;
+                       unsigned int top_sampling    : 1;
+                       unsigned int top_left        : 1;
+                       unsigned int top_right       : 1;
+                       unsigned int top_all         : 1;
+                       unsigned int top_auto        : 1;
+                       unsigned int mst_volume      : 1; // 16
+                       unsigned int mst_swing       : 1;
+                       unsigned int mst_tempo       : 1;
+                       unsigned int mst_left        : 1;
+                       unsigned int mst_right       : 1;
+                       unsigned int mst_enter       : 1;
+                       unsigned int mst_note_repeat : 1;
+                       unsigned int mst_wheel       : 1;
+                       unsigned int groups_a        : 1; // 24
+                       unsigned int groups_b        : 1;
+                       unsigned int groups_c        : 1;
+                       unsigned int groups_d        : 1;
+                       unsigned int groups_e        : 1;
+                       unsigned int groups_f        : 1;
+                       unsigned int groups_g        : 1;
+                       unsigned int groups_h        : 1;
+                       unsigned int trs_restart     : 1; // 32
+                       unsigned int trs_left        : 1;
+                       unsigned int trs_right       : 1;
+                       unsigned int trs_grid        : 1;
+                       unsigned int trs_play        : 1;
+                       unsigned int trs_rec         : 1;
+                       unsigned int trs_erase       : 1;
+                       unsigned int trs_shift       : 1;
+                       unsigned int pads_scene      : 1; // 40
+                       unsigned int pads_pattern    : 1;
+                       unsigned int pads_mode       : 1;
+                       unsigned int pads_navigate   : 1;
+                       unsigned int pads_duplicate  : 1;
+                       unsigned int pads_select     : 1;
+                       unsigned int pads_solo       : 1;
+                       unsigned int pads_mute       : 1;
+                       unsigned int reserved        : 8; // 48
+                       unsigned int mst_wheel_pos   : 8; // 56  // range: 0..15
+                       uint16_t top_knobs[8];            // 64 ... 191 // range 0..999
+               } ATTRIBUTE_PACKED ctrl_in;
+
+#if (!defined __GNUC__)
+#pragma pack()
+#endif
+               uint16_t pad[16];
+
+               Cairo::RefPtr<Cairo::ImageSurface> _surface;
+
+       private:
+               void assign_controls (M2Contols*) const;
+
+               void set_colors80 (M2Contols*, uint8_t*) const;
+               void set_colors81 (M2Contols*, uint8_t*) const;
+               void set_colors82 (M2Contols*, uint8_t*) const;
+
+               uint8_t ctrl82[32];
+               uint8_t ctrl81[57];
+               uint8_t ctrl80[49];
+               uint8_t _img[2][8][265];
+};
+} /* namespace */
+
+#endif
diff --git a/libs/surfaces/maschine2/m2_encoder.h b/libs/surfaces/maschine2/m2_encoder.h
new file mode 100644 (file)
index 0000000..39032e6
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
+ *
+ * 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _ardour_surfaces_m2encoder_h_
+#define _ardour_surfaces_m2encoder_h_
+
+#include <stdint.h>
+#include "pbd/signals.h"
+
+namespace ArdourSurface {
+
+class M2EncoderInterface
+{
+       public:
+               M2EncoderInterface () {}
+               virtual ~M2EncoderInterface () {}
+
+               /* user API */
+               PBD::Signal1<void, int> changed;
+               virtual float value () const { return 0.f; }
+               virtual float range () const { return 0.f; }
+
+               /* internal API - called from device thread */
+               virtual bool set_value (unsigned int v) { return false; }
+};
+
+class M2Encoder : public M2EncoderInterface
+{
+       public:
+               M2Encoder (unsigned int upper = 1000)
+                       : M2EncoderInterface ()
+                       , _upper (upper /* limit, exclusive. eg [0..15]: 16 */)
+                       , _value (0)
+                       , _initialized (false)
+               {
+                       assert (_upper > 7);
+                       _wrapcnt = std::max (3U, upper / 6);
+               }
+
+               float value () const { return _value / (_upper - 1.f); }
+               float range () const { return (_upper - 1.f); }
+
+               bool set_value (unsigned int v) {
+                       if (!_initialized) {
+                               _initialized = true;
+                               _value = v;
+                               return false;
+                       }
+
+                       if (v == _value) {
+                               return false;
+                       }
+
+                       int delta;
+                       if (v < _wrapcnt && _value > _upper - _wrapcnt) {
+                               // wrap around max -> min
+                               delta = v + _upper - _value;
+                       }
+                       else if (_value < _wrapcnt && v > _upper - _wrapcnt) {
+                               // wrap around min -> max
+                               delta = v - _upper - _value;
+                       }
+                       else {
+                               delta = v - _value;
+                       }
+
+                       _value = v;
+                       changed (delta);
+                       return true;
+               }
+
+       protected:
+               unsigned int _upper;
+               unsigned int _value;
+               unsigned int _wrapcnt;
+               bool _initialized;
+};
+
+} /* namespace */
+#endif /* _ardour_surfaces_m2encoder_h_ */
+
+
diff --git a/libs/surfaces/maschine2/m2_map_mikro.cc b/libs/surfaces/maschine2/m2_map_mikro.cc
new file mode 100644 (file)
index 0000000..64915b5
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
+ *
+ * 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "m2_map_mikro.h"
+
+using namespace ArdourSurface;
+
+M2MapMikro::M2MapMikro ()
+       : M2Contols ()
+       , enc_master (16)
+{}
+
+M2ButtonInterface*
+M2MapMikro::button (PhysicalButtonId id, Modifier m)
+{
+       return M2Contols::button (id, m);
+}
+
+M2ButtonInterface*
+M2MapMikro::button (SemanticButtonId id)
+{
+       return M2Contols::button (id);
+}
+
+M2EncoderInterface*
+M2MapMikro::encoder (unsigned int id)
+{
+       if (id == 0) {
+               return &enc_master;
+       }
+       // TODO map "nav" (select) and Left/Right to encoder(s) delta.
+       return M2Contols::encoder (id);
+}
+
+M2PadInterface*
+M2MapMikro::pad (unsigned int id)
+{
+       if (id < 16) {
+               return &pads[id];
+       }
+       return M2Contols::pad (id);
+}
diff --git a/libs/surfaces/maschine2/m2_map_mikro.h b/libs/surfaces/maschine2/m2_map_mikro.h
new file mode 100644 (file)
index 0000000..838ba65
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
+ *
+ * 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _ardour_surfaces_m2map_mikro_h_
+#define _ardour_surfaces_m2map_mikro_h_
+
+#include "m2controls.h"
+
+namespace ArdourSurface {
+
+class M2MapMikro : public M2Contols
+{
+       public:
+               M2MapMikro ();
+
+               M2ButtonInterface*  button  (PhysicalButtonId id, Modifier m);
+               M2ButtonInterface*  button  (SemanticButtonId id);
+               M2EncoderInterface* encoder (unsigned int id);
+               M2PadInterface*     pad     (unsigned int id);
+
+       private:
+               M2Encoder enc_master;
+               M2Pad pads[16];
+};
+
+} /* namespace */
+#endif
diff --git a/libs/surfaces/maschine2/m2_map_mk2.cc b/libs/surfaces/maschine2/m2_map_mk2.cc
new file mode 100644 (file)
index 0000000..849f9be
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
+ *
+ * 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "m2_map_mk2.h"
+
+using namespace ArdourSurface;
+using namespace std;
+
+M2MapMk2::M2MapMk2 ()
+       : M2Contols ()
+       , enc_master (16)
+{
+#define PSMAP(MOD, PHYS, SEM, BTN) \
+       pmap[MOD].insert (make_pair (PHYS, BTN)); \
+       smap.insert (make_pair (SEM, BTN));
+
+#define PSMAPALL(PHYS, SEM, BTN) \
+       pmap[ModNone].insert (make_pair (PHYS, BTN)); \
+       pmap[ModShift].insert (make_pair (PHYS, BTN)); \
+       smap.insert (make_pair (SEM, BTN)); \
+
+       PSMAP(ModNone,  BtnPlay, Play,         &tr[0]);
+       PSMAP(ModShift, BtnPlay, Metronom,     &tr[1]);
+       PSMAP(ModNone,  BtnRec, Rec,           &tr[2]);
+       PSMAP(ModNone,  BtnGrid, Grid,         &tr[3]);
+       PSMAP(ModNone,  BtnRestart, GotoStart, &ts[0]);
+       PSMAP(ModShift, BtnRestart, Loop,      &tr[4]);
+
+       PSMAP(ModNone,  BtnStepLeft,  FastRewind,   &ts[1]);
+       PSMAP(ModNone,  BtnStepRight, FastForward,  &ts[2]);
+       PSMAP(ModShift, BtnStepLeft,  JumpBackward, &ts[3]);
+       PSMAP(ModShift, BtnStepRight, JumpForward,  &ts[4]);
+
+       PSMAPALL(BtnWheel,  EncoderWheel, &mst[0]);
+       PSMAPALL(BtnVolume, MasterVolume, &mst[1]);
+       //PSMAPALL(BtnSwing, Master?????, &mst[2]);
+       PSMAPALL(BtnTempo,  MasterTempo,  &mst[3]);
+
+       PSMAP(ModShift,  BtnAll, Save, &save);
+
+       PSMAP(ModShift,  BtnNavLeft,  Undo, &undoredo[0]);
+       PSMAP(ModShift,  BtnNavRight, Redo, &undoredo[1]);
+
+       PSMAP(ModNone,  BtnMute, Mute,  &sm[0]);
+       PSMAP(ModShift, BtnMute, Panic, &panic);
+       PSMAPALL(BtnSolo, Solo,         &sm[1]);
+
+       // TODO:
+       pmap[ModNone].insert  (make_pair (BtnErase, &ts[5]));
+       pmap[ModShift].insert (make_pair (BtnErase, &ts[5]));
+
+}
+
+M2ButtonInterface*
+M2MapMk2::button (PhysicalButtonId id, Modifier m)
+{
+       PhysicalMap::const_iterator i = pmap[m].find (id);
+       if (i != pmap[m].end()) {
+               return i->second;
+       }
+       return M2Contols::button (id, m);
+}
+
+M2ButtonInterface*
+M2MapMk2::button (SemanticButtonId id)
+{
+       SematicMap::const_iterator i = smap.find (id);
+       if (i != smap.end()) {
+               return i->second;
+       }
+       return M2Contols::button (id);
+}
+
+M2EncoderInterface*
+M2MapMk2::encoder (unsigned int id)
+{
+       if (id == 0) {
+               return &enc_master;
+       }
+       else if (id < 9) {
+               return &enc_top[id - 1];
+       }
+       return M2Contols::encoder (id);
+}
+
+M2PadInterface*
+M2MapMk2::pad (unsigned int id)
+{
+       if (id < 16) {
+               return &pads[id];
+       }
+       return M2Contols::pad (id);
+}
diff --git a/libs/surfaces/maschine2/m2_map_mk2.h b/libs/surfaces/maschine2/m2_map_mk2.h
new file mode 100644 (file)
index 0000000..75607b4
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
+ *
+ * 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _ardour_surfaces_m2map_mk2_h_
+#define _ardour_surfaces_m2map_mk2_h_
+
+#include "m2controls.h"
+
+namespace ArdourSurface {
+
+class M2MapMk2 : public M2Contols
+{
+       public:
+               M2MapMk2 ();
+
+               M2ButtonInterface*  button  (PhysicalButtonId id, Modifier m);
+               M2ButtonInterface*  button  (SemanticButtonId id);
+               M2EncoderInterface* encoder (unsigned int id);
+               M2PadInterface*     pad     (unsigned int id);
+
+       private:
+               PhysicalMap pmap[2]; // 2: Modifiers
+               SematicMap  smap;
+
+               M2Button tr[5]; // transport controlables
+               M2StatelessButton ts[6]; // transport pushbuttons
+
+               M2Button mst[4]; // master "volume", "swing", "tempo", "encoder-push"
+
+               M2Button save;
+
+               M2Button undoredo[2];
+               M2Button sm[2]; // solo, mute
+               M2StatelessButton panic;
+
+               M2Encoder enc_master;
+               M2Encoder enc_top[8];
+
+               M2Pad pads[16];
+};
+
+} /* namespace */
+#endif
diff --git a/libs/surfaces/maschine2/m2_pad.h b/libs/surfaces/maschine2/m2_pad.h
new file mode 100644 (file)
index 0000000..97266f2
--- /dev/null
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
+ *
+ * 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _ardour_surfaces_m2pad_h_
+#define _ardour_surfaces_m2pad_h_
+
+#include <stdint.h>
+#include "pbd/signals.h"
+
+namespace ArdourSurface {
+
+class M2PadInterface
+{
+       public:
+               M2PadInterface () {}
+               virtual ~M2PadInterface () {}
+
+               /* user API */
+               PBD::Signal1<void, float> pressed;
+               PBD::Signal0<void> released;
+               PBD::Signal1<void, float> aftertouch;
+               PBD::Signal2<void, float, bool> event;
+               PBD::Signal1<void, float> changed;
+
+               virtual uint16_t value () const { return 0; }
+               virtual float pressure () const { return 0.f; }
+               virtual void set_color (uint32_t rgba) {}
+
+               /* internal API - called from device thread */
+               virtual void set_value (uint16_t v) {}
+
+               virtual void color (uint8_t& r, uint8_t& g, uint8_t& b) const {
+                       r = g = b = 0;
+               }
+};
+
+class M2Pad : public M2PadInterface
+{
+       public:
+               M2Pad ()
+                       : M2PadInterface ()
+                       , _pressed (false)
+                       , _pressure (0)
+                       , _last (0)
+                       , _cnt (0)
+                       , _rgba (0)
+               {
+                       for (int i = 0; i < 4; ++i) {
+                               hist[i] = 0;
+                       }
+               }
+
+               uint16_t value () const { return _raw; }
+               float pressure () const { return _pressure; }
+
+               void set_color (uint32_t rgba) { _rgba = rgba; }
+
+               void color (uint8_t& r, uint8_t& g, uint8_t& b) const
+               {
+                       r = ((_rgba >> 24) & 0xff) >> 1;
+                       g = ((_rgba >> 16) & 0xff) >> 1;
+                       b = ((_rgba >>  8) & 0xff) >> 1;
+               }
+
+               void set_value (uint16_t v)
+               {
+                       // bleed to neighboring pads...
+                       static const uint16_t high  = 159;
+                       static const float    low   = 159 / 4095.f;
+                       static const float mindelta = 32.f / 4096.f;
+
+                       if (_raw != v) {
+                               changed (v / 4095.f);
+                               _raw = v;
+                       }
+
+                       // some pads never return to "0", and there's
+                       // TODO map pressure from a min..max range,
+                       // even hard hits rarely exceed 3400 or thereabouts.
+                       // -> "pad sensitivity" config or "calibrate pads"
+
+                       hist[_cnt] = v;
+                       _cnt = (_cnt + 1) & 3;
+
+                       if (_pressed) {
+                               const float p = v / 4095.f;
+                               _pressure += .1 * (p - _pressure);
+                               if (_pressure < low) {
+                                       _pressure = 0;
+                                       _pressed = false;
+                                       released (); /* EMIT SIGNAL */
+                                       event (_pressure, true); /* EMIT SIGNAL */
+                               } else {
+                                       if (fabsf (_last - _pressure) > mindelta) {
+                                               _last = _pressure;
+                                               aftertouch (_pressure); /* EMIT SIGNAL */
+                                               event (_pressure, false); /* EMIT SIGNAL */
+                                       }
+                               }
+                       } else {
+                               bool above_thresh = true;
+                               uint16_t max = 0;
+                               for (int i = 0; i < 4; ++i) {
+                                       if (hist[i] < high) {
+                                               above_thresh = false;
+                                               break;
+                                       }
+                                       max = std::max (max, hist[i]);
+                               }
+                               if (above_thresh) {
+                                       _pressed = true;
+                                       _last = _pressure = max / 4095.f;
+                                       pressed (_pressure);
+                                       event (_pressure, true); /* EMIT SIGNAL */
+                               }
+                       }
+               }
+
+       protected:
+               bool  _pressed;
+               float _pressure;
+               uint16_t _raw;
+               float _last;
+               uint16_t hist[4];
+               unsigned int _cnt;
+               uint32_t _rgba;
+};
+
+} /* namespace */
+#endif /* _ardour_surfaces_m2pad_h_ */
+
+
+
diff --git a/libs/surfaces/maschine2/m2controls.h b/libs/surfaces/maschine2/m2controls.h
new file mode 100644 (file)
index 0000000..a19e074
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
+ *
+ * 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _ardour_surfaces_m2controls_h_
+#define _ardour_surfaces_m2controls_h_
+
+#include <map>
+
+#include "m2_button.h"
+#include "m2_encoder.h"
+#include "m2_pad.h"
+
+namespace ArdourSurface {
+
+/** Abstraction for various variants:
+ *  - NI Maschine Mikro
+ *  - NI Maschine
+ *  - NI Maschine Studio
+ */
+
+class M2Contols
+{
+       public:
+               M2Contols () {}
+               virtual ~M2Contols () {}
+
+               typedef enum {
+                       ModNone = 0,
+                       ModShift,
+               } Modifier;
+
+               typedef enum {
+                       /* Transport */
+                       BtnRestart,
+                       BtnStepLeft,
+                       BtnStepRight,
+                       BtnGrid,
+                       BtnPlay,
+                       BtnRec,
+                       BtnErase,
+                       BtnShift,
+
+                       /* modes */
+                       BtnScene,
+                       BtnPattern,
+                       BtnPadMode,
+                       BtnNavigate, // aka. "view" on Mikro
+                       BtnDuplicate,
+                       BtnSelect,
+                       BtnSolo,
+                       BtnMute,
+
+                       /* global */
+#if 0
+                       BtnArrange, // Studio only
+                       BtnMix,     // Studio only
+#endif
+
+                       BtnControl, // Studio: "Channel"
+                       BtnStep,    // Studio: "Plug-In"
+                       BtnBrowse,
+                       BtnSampling,
+                       BtnSelLeft,
+                       BtnSelRight,
+                       BtnAll,
+                       BtnAuto,
+
+                       /* master */
+                       BtnVolume,
+                       BtnSwing,
+                       BtnTempo,
+                       BtnNavLeft,
+                       BtnNavRight,
+                       BtnEnter,
+                       BtnNoteRepeat, // Tap
+                       BtnWheel, // Encoder Push
+
+                       /* Selectors above display */
+                       BtnTop0, BtnTop1, BtnTop2, BtnTop3, // Mikro F1, F2, F3
+                       BtnTop4, BtnTop5, BtnTop6, BtnTop7,
+
+                       /* Maschine & Studio "Groups" */
+                       BtnGroupA, BtnGroupB, BtnGroupC, BtnGroupD,
+                       BtnGroupE, BtnGroupF, BtnGroupG, BtnGroupH,
+
+#if 1 // Studio only -- Edit
+                       BtnCopy,
+                       BtnPaste,
+                       BtnNote,
+                       BtnNudge,
+                       BtnUndo,
+                       BtnRedo,
+                       BtnQuantize,
+                       BtnClear,
+
+                       BtnIn1, BtnIn2, BtnIn3, BtnIn4,
+                       BtnMst, BtnGrp, BtnSnd, BtnCue,
+#endif
+               } PhysicalButtonId;
+
+               typedef enum {
+                       Play,
+                       Rec,
+                       Loop,
+                       Metronom,
+                       GotoStart,
+                       GotoEnd,
+                       JumpBackward,
+                       JumpForward,
+                       FastRewind,
+                       FastForward,
+                       Grid,
+                       Delete,
+                       Undo, Redo,
+                       Save,
+                       EncoderWheel, // multi-purpose
+                       MasterVolume,
+                       MasterTempo,
+                       Solo, Mute,
+                       Panic
+               } SemanticButtonId;
+
+               typedef std::map <PhysicalButtonId, M2ButtonInterface*> PhysicalMap;
+               typedef std::map <SemanticButtonId, M2ButtonInterface*> SematicMap;
+
+               virtual M2ButtonInterface* button (PhysicalButtonId id, Modifier m) {
+                       if (id == BtnShift) {
+                               return &_shift;
+                       }
+                       return &_dummy_button;
+               }
+
+               virtual M2ButtonInterface* button (SemanticButtonId id) {
+                       return &_dummy_button;
+               }
+
+               virtual M2EncoderInterface* encoder (unsigned int id) {
+                       return &_dummy_encoder;
+               }
+
+               virtual M2PadInterface* pad (unsigned int id) {
+                       return &_dummy_pad;
+               }
+
+       protected:
+               M2ButtonInterface  _dummy_button;
+               M2EncoderInterface _dummy_encoder;
+               M2PadInterface     _dummy_pad;
+
+               M2ToggleHoldButton  _shift;
+};
+
+} /* namespace */
+#endif /* _ardour_surfaces_m2controls_h_*/
diff --git a/libs/surfaces/maschine2/m2device.h b/libs/surfaces/maschine2/m2device.h
new file mode 100644 (file)
index 0000000..40349ab
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
+ *
+ * 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _ardour_surfaces_maschine2hardware_h_
+#define _ardour_surfaces_maschine2hardware_h_
+
+#include <hidapi.h>
+#include <cairomm/refptr.h>
+#include <cairomm/surface.h>
+#include "pbd/signals.h"
+
+namespace ArdourSurface {
+
+class M2Contols;
+
+/** Abstraction for various variants:
+ *  - NI Maschine Mikro
+ *  - NI Maschine
+ *  - NI Maschine Studio
+ */
+
+class M2Device
+{
+       public:
+               M2Device ()
+                       : _splashcnt (0)
+                       , _blink_counter (0)
+                       , _blink_shade (0.f)
+                       {}
+               virtual ~M2Device () {}
+
+               virtual void clear (bool splash = false) {
+                       if (splash) {
+                               _splashcnt = 0;
+                       } else {
+                               _splashcnt = _splashtime;
+                       }
+                       _blink_counter = 0;
+                       _blink_shade = 0.f;
+               }
+
+               virtual void read (hid_device*, M2Contols*) = 0;
+               virtual void write (hid_device*, M2Contols*) = 0;
+               virtual Cairo::RefPtr<Cairo::ImageSurface> surface () = 0;
+
+               PBD::Signal0<bool> vblank;
+
+       protected:
+               void bump_blink () {
+                       _blink_counter = (_blink_counter + 1) % 12;
+                       _blink_shade = fabsf (1.f - _blink_counter / 6.f);
+               }
+
+               uint32_t _splashcnt;
+               static const uint32_t _splashtime = 25 * 3;
+               unsigned int _blink_counter;
+               float        _blink_shade;
+};
+
+} /* namespace */
+#endif /* _ardour_surfaces_maschine2_h_*/
diff --git a/libs/surfaces/maschine2/maschine2.cc b/libs/surfaces/maschine2/maschine2.cc
new file mode 100644 (file)
index 0000000..b378639
--- /dev/null
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
+ *
+ * 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <pthread.h>
+
+#include "pbd/compose.h"
+#include "pbd/error.h"
+#include "pbd/i18n.h"
+#include "pbd/abstract_ui.cc" // instantiate template
+
+#include "ardour/async_midi_port.h"
+#include "ardour/audioengine.h"
+#include "ardour/session.h"
+
+#include "midi++/port.h"
+
+#include "maschine2.h"
+
+#include "m2_dev_mk2.h"
+#include "m2_map_mk2.h"
+
+#include "m2_dev_mikro.h"
+#include "m2_map_mikro.h"
+
+#include "canvas.h"
+
+using namespace ARDOUR;
+using namespace PBD;
+using namespace ArdourSurface;
+
+Maschine2::Maschine2 (ARDOUR::Session& s)
+       : ControlProtocol (s, string (X_("NI Maschine2")))
+       , AbstractUI<Maschine2Request> (name())
+       , _handle (0)
+       , _hw (0)
+       , _ctrl (0)
+       , _canvas (0)
+       , _maschine_type (Maschine)
+       , _master_state (MST_NONE)
+{
+       if (hid_init()) {
+               throw Maschine2Exception ("HIDAPI initialization failed");
+       }
+       run_event_loop ();
+}
+
+Maschine2::~Maschine2 ()
+{
+       stop ();
+       hid_exit ();
+}
+
+void*
+Maschine2::request_factory (uint32_t num_requests)
+{
+       return request_buffer_factory (num_requests);
+}
+
+void
+Maschine2::do_request (Maschine2Request* req)
+{
+       if (req->type == CallSlot) {
+               call_slot (MISSING_INVALIDATOR, req->the_slot);
+       } else if (req->type == Quit) {
+               stop ();
+       }
+}
+
+int
+Maschine2::set_active (bool yn)
+{
+       if (yn == active()) {
+               return 0;
+       }
+
+       if (yn) {
+               if (start ()) {
+                       return -1;
+               }
+       } else {
+               if (stop ()) {
+                       return -1;
+               }
+       }
+
+       ControlProtocol::set_active (yn);
+       return 0;
+}
+
+XMLNode&
+Maschine2::get_state()
+{
+       XMLNode& node (ControlProtocol::get_state());
+       return node;
+}
+
+int
+Maschine2::set_state (const XMLNode & node, int version)
+{
+       if (ControlProtocol::set_state (node, version)) {
+               return -1;
+       }
+       return 0;
+}
+
+int
+Maschine2::start ()
+{
+       _maschine_type = Maschine;
+       _handle = hid_open (0x17cc, 0x1140, NULL); // Maschine
+
+#if 0
+       if (!_handle) {
+               if ((_handle = hid_open (0x17cc, 0x1300, NULL))) {
+                       _maschine_type = Studio;
+               }
+       }
+#endif
+
+       if (!_handle) {
+               if ((_handle = hid_open (0x17cc, 0x1110, NULL))) {
+                       _maschine_type = Mikro;
+               }
+       }
+       if (!_handle) {
+               if ((_handle = hid_open (0x17cc, 0x1200, NULL))) {
+                       _maschine_type = Mikro;
+               }
+       }
+
+       if (!_handle) {
+               error << _("Cannot find or connect to Maschine2\n");
+               return -1;
+       }
+
+       hid_set_nonblocking (_handle, 1);
+
+       _midi_out = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("Maschine2 out"), true);
+       if (!_midi_out) {
+               error << _("Cannot create Maschine2 PAD MIDI Port");
+               stop ();
+               return -1;
+       }
+
+       boost::dynamic_pointer_cast<AsyncMIDIPort>(_midi_out)->set_flush_at_cycle_start (true);
+       _output_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_midi_out).get();
+
+       switch (_maschine_type) {
+               case Mikro:
+                       _hw = new Maschine2Mikro ();
+                       _ctrl = new M2MapMikro ();
+                       info << _("Maschine2 Mikro control surface intialized");
+                       break;
+               case Maschine:
+                       _hw = new Maschine2Mk2 ();
+                       _ctrl = new M2MapMk2 ();
+                       info << _("Maschine2 control surface intialized");
+                       break;
+               case Studio:
+                       error << _("Maschine2 Studio is not yet supported");
+                       stop ();
+                       return -1;
+                       break;
+       }
+
+       _canvas = new Maschine2Canvas (*this, _hw);
+       connect_signals ();
+
+       Glib::RefPtr<Glib::TimeoutSource> write_timeout = Glib::TimeoutSource::create (40);
+       write_connection = write_timeout->connect (sigc::mem_fun (*this, &Maschine2::dev_write));
+       write_timeout->attach (main_loop()->get_context());
+
+#ifdef PLATFORM_WINDOWS
+       Glib::RefPtr<Glib::TimeoutSource> read_timeout = Glib::TimeoutSource::create (20);
+#else
+       Glib::RefPtr<Glib::TimeoutSource> read_timeout = Glib::TimeoutSource::create (1);
+#endif
+       read_connection = read_timeout->connect (sigc::mem_fun (*this, &Maschine2::dev_read));
+       read_timeout->attach (main_loop ()->get_context());
+
+       return 0;
+}
+
+int
+Maschine2::stop ()
+{
+       read_connection.disconnect ();
+       write_connection.disconnect ();
+
+       session_connections.drop_connections ();
+       button_connections.drop_connections ();
+
+       if (_handle && _hw) {
+               _hw->clear ();
+               _hw->write (_handle, NULL);
+       }
+
+       hid_close (_handle);
+       _handle = 0;
+
+       stop_event_loop ();
+
+       if (_midi_out) {
+               AsyncMIDIPort* asp = dynamic_cast<AsyncMIDIPort*> (_output_port);
+               asp->drain (10000, 500000);
+
+               AudioEngine::instance()->unregister_port (_midi_out);
+               _midi_out.reset ((ARDOUR::Port*) 0);
+               _output_port = 0;
+       }
+
+       delete _canvas;
+       delete _hw;
+       delete _ctrl;
+
+       _canvas = 0;
+       _hw = 0;
+       _ctrl = 0;
+       return 0;
+}
+
+void
+Maschine2::thread_init ()
+{
+       pthread_set_name (event_loop_name().c_str());
+       ARDOUR::SessionEvent::create_per_thread_pool (event_loop_name(), 1024);
+       PBD::notify_event_loops_about_thread_creation (pthread_self(), event_loop_name(), 1024);
+
+       struct sched_param rtparam;
+       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
+Maschine2::run_event_loop ()
+{
+       BaseUI::run ();
+}
+
+void
+Maschine2::stop_event_loop ()
+{
+       BaseUI::quit ();
+}
+
+bool
+Maschine2::dev_read ()
+{
+       _hw->read (_handle, _ctrl);
+       return true;
+}
+
+bool
+Maschine2::dev_write ()
+{
+       _hw->write (_handle, _ctrl);
+       return true;
+}
+
+// move to callbacks.c || M2Contols implementation
+Maschine2Layout*
+Maschine2::current_layout() const
+{
+       return NULL;
+}
diff --git a/libs/surfaces/maschine2/maschine2.h b/libs/surfaces/maschine2/maschine2.h
new file mode 100644 (file)
index 0000000..5d485d5
--- /dev/null
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
+ *
+ * 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, 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef _ardour_surfaces_maschine2_h_
+#define _ardour_surfaces_maschine2_h_
+
+#ifdef PLATFORM_WINDOWS
+#include <windows.h>
+#endif
+
+#include <hidapi.h>
+
+#define ABSTRACT_UI_EXPORTS
+#include "pbd/abstract_ui.h"
+#include "ardour/types.h"
+#include "ardour/port.h"
+#include "control_protocol/control_protocol.h"
+
+namespace MIDI {
+       class Port;
+}
+
+namespace ArdourSurface {
+
+class M2Contols;
+class M2Device;
+class Maschine2Canvas;
+class Maschine2Layout;
+
+class Maschine2Exception : public std::exception
+{
+       public:
+               Maschine2Exception (const std::string& msg) : _msg (msg) { }
+               virtual ~Maschine2Exception () throw () {}
+               const char* what () const throw () { return _msg.c_str (); }
+       private:
+               std::string _msg;
+};
+
+struct Maschine2Request : public BaseUI::BaseRequestObject {
+  public:
+       Maschine2Request () {}
+       ~Maschine2Request () {}
+};
+
+class Maschine2: public ARDOUR::ControlProtocol, public AbstractUI<Maschine2Request>
+{
+       public:
+               Maschine2 (ARDOUR::Session&);
+               ~Maschine2 ();
+
+               static void* request_factory (uint32_t);
+
+#if 0
+               bool has_editor () const { return false; }
+               void* get_gui () const;
+               void  tear_down_gui ();
+#endif
+
+               int set_active (bool yn);
+               XMLNode& get_state ();
+               int set_state (const XMLNode & node, int version);
+
+               Maschine2Canvas* canvas () const { return _canvas; }
+               Maschine2Layout* current_layout() const;
+
+               typedef enum {
+                       Mikro,
+                       Maschine,
+                       Studio
+               } Maschine2Type;
+
+       private:
+               void do_request (Maschine2Request*);
+
+               int start ();
+               int stop ();
+
+               void thread_init ();
+               void run_event_loop ();
+               void stop_event_loop ();
+
+               sigc::connection read_connection;
+               sigc::connection write_connection;
+
+               bool dev_write ();
+               bool dev_read ();
+
+               hid_device* _handle;
+               M2Device* _hw;
+               M2Contols* _ctrl;
+               Maschine2Canvas* _canvas;
+
+               Maschine2Type _maschine_type;
+
+               PBD::ScopedConnectionList session_connections;
+               PBD::ScopedConnectionList button_connections;
+
+               void connect_signals ();
+               void stripable_selection_changed () {}
+
+
+               /* Master Mode */
+               enum MasterMode {
+                       MST_NONE,
+                       MST_VOLUME,
+                       MST_TEMPO
+               } _master_state;
+
+               void handle_master_change (enum MasterMode);
+               void notify_master_change ();
+
+               /* PAD Port */
+               boost::shared_ptr<ARDOUR::Port> _midi_out;
+               MIDI::Port* _output_port;
+
+               /* callbacks */
+               void notify_record_state_changed ();
+               void notify_transport_state_changed ();
+               void notify_loop_state_changed ();
+               void notify_parameter_changed (std::string);
+               void notify_snap_change ();
+               void notify_session_dirty_changed ();
+               void notify_history_changed ();
+
+               void button_play ();
+               void button_record ();
+               void button_loop ();
+               void button_metronom ();
+               void button_rewind ();
+
+               void button_action (const std::string&, const std::string&);
+
+               void button_snap_released ();
+               void button_snap_pressed ();
+               void button_snap_changed (bool);
+
+               void encoder_master (int);
+               void button_encoder ();
+
+               void pad_event (unsigned int, float, bool);
+               void pad_change (unsigned int, float);
+};
+
+} /* namespace */
+#endif /* _ardour_surfaces_maschine2_h_*/
diff --git a/libs/surfaces/maschine2/wscript b/libs/surfaces/maschine2/wscript
new file mode 100644 (file)
index 0000000..6194c8c
--- /dev/null
@@ -0,0 +1,43 @@
+#!/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):
+    conf.load ('compiler_cxx')
+    autowaf.configure(conf)
+    autowaf.check_pkg(conf, 'pangomm-1.4', uselib_store='PANGOMM', atleast_version='1.4', mandatory=True)
+    autowaf.check_pkg(conf, 'cairomm-1.0', uselib_store='CAIROMM', atleast_version='1.8.4', mandatory=True)
+
+def build(bld):
+    obj = bld(features = 'cxx cxxshlib')
+    obj.source = '''
+            maschine2.cc
+            callbacks.cc
+            canvas.cc
+           interface.cc
+           layout.cc
+           m2_dev_mk2.cc
+           m2_map_mk2.cc
+           m2_dev_mikro.cc
+           m2_map_mikro.cc
+    '''
+    obj.export_includes = ['.']
+    obj.defines      = [ 'PACKAGE="ardour_maschine2"' ]
+    obj.defines     += [ 'ARDOURSURFACE_DLL_EXPORTS' ]
+    obj.defines     += [ 'VERSIONSTRING="' + bld.env['VERSION'] + '"' ]
+    obj.includes     = [ '.', './maschine2']
+    obj.name         = 'libardour_maschine2'
+    obj.target       = 'ardour_maschine2'
+    obj.uselib       = 'CAIROMM PANGOMM'
+    obj.use          = 'libardour libardour_cp libpbd libcanvas hidapi libgtkmm2ext'
+    obj.install_path = os.path.join(bld.env['LIBDIR'], 'surfaces')
+
+def shutdown():
+    autowaf.shutdown()
index 0e34356de15e9723e7242366b0f11bfcf8fdd86b..a1a3de56a0a1438d22dbdcaf2b64a9983700c77d 100644 (file)
@@ -50,6 +50,9 @@ def configure(conf):
     else:
         print ('You are missing the libusb-1.0 development package needed to compile Push2 support')
     
     else:
         print ('You are missing the libusb-1.0 development package needed to compile Push2 support')
     
+    if conf.is_defined('HAVE_HIDAPI'):
+        children += [ 'maschine2' ]
+
     if autowaf.check_pkg (conf, 'liblo', mandatory=False, uselib_store="LO", atleast_version="0.24"):
         children += [ 'osc' ]
 
     if autowaf.check_pkg (conf, 'liblo', mandatory=False, uselib_store="LO", atleast_version="0.24"):
         children += [ 'osc' ]
 
@@ -88,6 +91,8 @@ def build(bld):
         bld.recurse('tranzport')
     if bld.is_defined('HAVE_USB'):
         bld.recurse('push2')
         bld.recurse('tranzport')
     if bld.is_defined('HAVE_USB'):
         bld.recurse('push2')
+    if bld.is_defined('HAVE_HIDAPI'):
+        bld.recurse('maschine2')
 
 def shutdown():
     autowaf.shutdown()
 
 def shutdown():
     autowaf.shutdown()