From: Robin Gareus Date: Thu, 17 Nov 2016 12:08:12 +0000 (+0100) Subject: Skeleton for NI Maschine2 Surface X-Git-Tag: 6.0-pre0~23 X-Git-Url: https://main.carlh.net/gitweb/?p=ardour.git;a=commitdiff_plain;h=0a6d1ab06e20de7d1c3da7f7f9df14c633b3d566 Skeleton for NI Maschine2 Surface --- diff --git a/gtk2_ardour/ardev_common.sh.in b/gtk2_ardour/ardev_common.sh.in index 4a96798e70..792eababab 100644 --- a/gtk2_ardour/ardev_common.sh.in +++ b/gtk2_ardour/ardev_common.sh.in @@ -13,7 +13,7 @@ export GTK2_RC_FILES=/nonexistent # can find all the components. # -export ARDOUR_SURFACES_PATH=$libs/surfaces/osc:$libs/surfaces/faderport8:$libs/surfaces/faderport:$libs/surfaces/generic_midi:$libs/surfaces/tranzport:$libs/surfaces/powermate:$libs/surfaces/mackie:$libs/surfaces/wiimote:$libs/surfaces/push2 +export ARDOUR_SURFACES_PATH=$libs/surfaces/osc:$libs/surfaces/faderport8:$libs/surfaces/faderport:$libs/surfaces/generic_midi:$libs/surfaces/tranzport:$libs/surfaces/powermate:$libs/surfaces/mackie:$libs/surfaces/wiimote:$libs/surfaces/push2:$libs/surfaces/maschine2 export ARDOUR_PANNER_PATH=$libs/panners export ARDOUR_DATA_PATH=$TOP:$TOP/build:$TOP/gtk2_ardour:$TOP/build/gtk2_ardour:. export ARDOUR_MIDIMAPS_PATH=$TOP/midi_maps:. 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 + * + * 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 act; +#if 0 + act = ActionManager::get_action (X_("Editor"), X_("ToggleMeasureVisibility")); + if (act) { + Glib::RefPtr tact = Glib::RefPtr::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 ract = Glib::RefPtr::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 ract = Glib::RefPtr::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 ract = Glib::RefPtr::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 act = ActionManager::get_action (X_("Editor"), X_("ToggleMeasureVisibility")); + if (act) { + Glib::RefPtr tact = Glib::RefPtr::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 act = ActionManager::get_action (X_("Editor"), X_("snap-magnetic")); + if (act) { + Glib::RefPtr ract = Glib::RefPtr::cast_dynamic (act); + if (ract->get_active ()) { rgba = COLOR_GRAY; } + } + act = ActionManager::get_action (X_("Editor"), X_("snap-normal")); + if (act) { + Glib::RefPtr ract = Glib::RefPtr::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 act = ActionManager::get_action (X_("Editor"), X_("ToggleMeasureVisibility")); + if (act) { + Glib::RefPtr tact = Glib::RefPtr::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 act = ActionManager::get_action (X_("Editor"), X_("snap-off")); + if (act) { + Glib::RefPtr ract = Glib::RefPtr::cast_dynamic (act); + if (ract->get_active ()) { action = "snap-normal"; } + } + + act = ActionManager::get_action (X_("Editor"), X_("snap-normal")); + if (act) { + Glib::RefPtr ract = Glib::RefPtr::cast_dynamic (act); + if (ract->get_active ()) { action = "snap-magnetic"; } + } + + act = ActionManager::get_action (X_("Editor"), X_("snap-magnetic")); + if (act) { + Glib::RefPtr ract = Glib::RefPtr::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 ract = Glib::RefPtr::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 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 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 + * + * 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 + +#include +#include +#include + +#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 +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 (); + } + + 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 = 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 + * + * 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 +#include + +#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 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 get_pango_context (); + + private: + int _width; + int _height; + + Cairo::RefPtr context; + Cairo::RefPtr expose_region; + Glib::RefPtr 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 + * + * 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 + +#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 + * + * 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 +#include +#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 + * + * 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 +#include "gtkmm2ext/colors.h" +#include "pbd/signals.h" + +namespace ArdourSurface { + +class M2ButtonInterface +{ + public: + M2ButtonInterface () {} + virtual ~M2ButtonInterface () {} + + /* user API */ + PBD::Signal1 changed; + PBD::Signal0 pressed; + PBD::Signal0 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 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 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 + +#include "pbd/compose.h" + +#include "maschine2.h" +#include "m2controls.h" +#include "m2_dev_mikro.h" + +#include + +#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 cr = Cairo::Context::create (_surface); + if (!splash) { + mikro_png_readoff = 0; + Cairo::RefPtr 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 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 (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 + * + * 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 +#include + +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 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 _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 + +#include "pbd/compose.h" + +#include "maschine2.h" +#include "m2controls.h" +#include "m2_dev_mk2.h" + +#include + +#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 c = Cairo::Context::create (_surface); + c->set_operator (Cairo::OPERATOR_CLEAR); + c->paint (); + return; +#endif + + maschine_png_readoff = 0; + Cairo::RefPtr sf = Cairo::ImageSurface::create_from_png_stream (sigc::ptr_fun (maschine_png_read)); + Cairo::RefPtr cr = Cairo::Context::create (_surface); + cr->set_source(sf, 0, 0); + cr->paint (); + + Glib::RefPtr 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 (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 + * + * 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 +#include + +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 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 _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 + * + * 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 +#include "pbd/signals.h" + +namespace ArdourSurface { + +class M2EncoderInterface +{ + public: + M2EncoderInterface () {} + virtual ~M2EncoderInterface () {} + + /* user API */ + PBD::Signal1 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 + * + * 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 + * + * 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 + * + * 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 + * + * 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 + * + * 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 +#include "pbd/signals.h" + +namespace ArdourSurface { + +class M2PadInterface +{ + public: + M2PadInterface () {} + virtual ~M2PadInterface () {} + + /* user API */ + PBD::Signal1 pressed; + PBD::Signal0 released; + PBD::Signal1 aftertouch; + PBD::Signal2 event; + PBD::Signal1 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 + * + * 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 + +#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 PhysicalMap; + typedef std::map 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 + * + * 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 +#include +#include +#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 surface () = 0; + + PBD::Signal0 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 + * + * 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 +#include +#include +#include +#include +#include + +#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 (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(_midi_out)->set_flush_at_cycle_start (true); + _output_port = boost::dynamic_pointer_cast(_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 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 read_timeout = Glib::TimeoutSource::create (20); +#else + Glib::RefPtr 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 (_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 + * + * 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 +#endif + +#include + +#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 +{ + 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 _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()