summaryrefslogtreecommitdiff
path: root/libs
diff options
context:
space:
mode:
authorRobin Gareus <robin@gareus.org>2016-11-17 13:08:12 +0100
committerPaul Davis <paul@linuxaudiosystems.com>2017-09-18 11:40:53 -0400
commit0a6d1ab06e20de7d1c3da7f7f9df14c633b3d566 (patch)
treeda9616b6bad857e2c1a179165b0836967ff56293 /libs
parent0c2e25a506cf83455744a5aacaa1a3787b00aa04 (diff)
Skeleton for NI Maschine2 Surface
Diffstat (limited to 'libs')
-rw-r--r--libs/surfaces/maschine2/callbacks.cc460
-rw-r--r--libs/surfaces/maschine2/canvas.cc151
-rw-r--r--libs/surfaces/maschine2/canvas.h87
-rw-r--r--libs/surfaces/maschine2/images.h85
-rw-r--r--libs/surfaces/maschine2/interface.cc79
-rw-r--r--libs/surfaces/maschine2/layout.cc63
-rw-r--r--libs/surfaces/maschine2/layout.h55
-rw-r--r--libs/surfaces/maschine2/m2_button.h220
-rw-r--r--libs/surfaces/maschine2/m2_dev_mikro.cc284
-rw-r--r--libs/surfaces/maschine2/m2_dev_mikro.h99
-rw-r--r--libs/surfaces/maschine2/m2_dev_mk2.cc380
-rw-r--r--libs/surfaces/maschine2/m2_dev_mk2.h122
-rw-r--r--libs/surfaces/maschine2/m2_encoder.h97
-rw-r--r--libs/surfaces/maschine2/m2_map_mikro.cc57
-rw-r--r--libs/surfaces/maschine2/m2_map_mikro.h42
-rw-r--r--libs/surfaces/maschine2/m2_map_mk2.cc108
-rw-r--r--libs/surfaces/maschine2/m2_map_mk2.h58
-rw-r--r--libs/surfaces/maschine2/m2_pad.h148
-rw-r--r--libs/surfaces/maschine2/m2controls.h169
-rw-r--r--libs/surfaces/maschine2/m2device.h76
-rw-r--r--libs/surfaces/maschine2/maschine2.cc288
-rw-r--r--libs/surfaces/maschine2/maschine2.h161
-rw-r--r--libs/surfaces/maschine2/wscript43
-rw-r--r--libs/surfaces/wscript5
24 files changed, 3337 insertions, 0 deletions
diff --git a/libs/surfaces/maschine2/callbacks.cc b/libs/surfaces/maschine2/callbacks.cc
new file mode 100644
index 0000000000..32466fc7be
--- /dev/null
+++ b/libs/surfaces/maschine2/callbacks.cc
@@ -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
index 0000000000..bde0d6777c
--- /dev/null
+++ b/libs/surfaces/maschine2/canvas.cc
@@ -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
index 0000000000..0e1424515a
--- /dev/null
+++ b/libs/surfaces/maschine2/canvas.h
@@ -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
index 0000000000..89c0cab349
--- /dev/null
+++ b/libs/surfaces/maschine2/images.h
@@ -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
index 0000000000..101d27b6d0
--- /dev/null
+++ b/libs/surfaces/maschine2/interface.cc
@@ -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
index 0000000000..30d248e6dd
--- /dev/null
+++ b/libs/surfaces/maschine2/layout.cc
@@ -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
index 0000000000..3cacf09ff7
--- /dev/null
+++ b/libs/surfaces/maschine2/layout.h
@@ -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
index 0000000000..cfe26b12d0
--- /dev/null
+++ b/libs/surfaces/maschine2/m2_button.h
@@ -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
index 0000000000..f31f6ec8f1
--- /dev/null
+++ b/libs/surfaces/maschine2/m2_dev_mikro.cc
@@ -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
index 0000000000..c836c14a20
--- /dev/null
+++ b/libs/surfaces/maschine2/m2_dev_mikro.h
@@ -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
index 0000000000..ba8d876783
--- /dev/null
+++ b/libs/surfaces/maschine2/m2_dev_mk2.cc
@@ -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
index 0000000000..a9adfa6b1a
--- /dev/null
+++ b/libs/surfaces/maschine2/m2_dev_mk2.h
@@ -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
index 0000000000..39032e62dd
--- /dev/null
+++ b/libs/surfaces/maschine2/m2_encoder.h
@@ -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
index 0000000000..64915b5d7c
--- /dev/null
+++ b/libs/surfaces/maschine2/m2_map_mikro.cc
@@ -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
index 0000000000..838ba65eb2
--- /dev/null
+++ b/libs/surfaces/maschine2/m2_map_mikro.h
@@ -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
index 0000000000..849f9be94c
--- /dev/null
+++ b/libs/surfaces/maschine2/m2_map_mk2.cc
@@ -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
index 0000000000..75607b4338
--- /dev/null
+++ b/libs/surfaces/maschine2/m2_map_mk2.h
@@ -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
index 0000000000..97266f28c1
--- /dev/null
+++ b/libs/surfaces/maschine2/m2_pad.h
@@ -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
index 0000000000..a19e074d8d
--- /dev/null
+++ b/libs/surfaces/maschine2/m2controls.h
@@ -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
index 0000000000..40349ab152
--- /dev/null
+++ b/libs/surfaces/maschine2/m2device.h
@@ -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
index 0000000000..b378639f11
--- /dev/null
+++ b/libs/surfaces/maschine2/maschine2.cc
@@ -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
index 0000000000..5d485d56b6
--- /dev/null
+++ b/libs/surfaces/maschine2/maschine2.h
@@ -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
index 0000000000..6194c8c8e3
--- /dev/null
+++ b/libs/surfaces/maschine2/wscript
@@ -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()
diff --git a/libs/surfaces/wscript b/libs/surfaces/wscript
index 0e34356de1..a1a3de56a0 100644
--- a/libs/surfaces/wscript
+++ b/libs/surfaces/wscript
@@ -50,6 +50,9 @@ def configure(conf):
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' ]
@@ -88,6 +91,8 @@ def build(bld):
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()