Skeleton for NI Maschine2 Surface

This commit is contained in:
Robin Gareus 2016-11-17 13:08:12 +01:00 committed by Paul Davis
parent 0c2e25a506
commit 0a6d1ab06e
25 changed files with 3338 additions and 1 deletions

View file

@ -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:.

View file

@ -0,0 +1,460 @@
/*
* Copyright (C) 2016 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "ardour/session.h"
#include "gtkmm2ext/colors.h"
#include "gtkmm2ext/actions.h"
#include "gtkmm2ext/gui_thread.h"
#include "pbd/i18n.h"
#include "maschine2.h"
#include "m2controls.h"
#include "midi++/port.h"
#define COLOR_WHITE 0xffffffff
#define COLOR_GRAY 0x606060ff
#define COLOR_BLACK 0x000000ff
using namespace ARDOUR;
using namespace ArdourSurface;
void
Maschine2::connect_signals ()
{
// TODO: use some convenience macros here
/* Signals */
session->TransportStateChange.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Maschine2::notify_transport_state_changed, this), this);
session->TransportLooped.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Maschine2::notify_loop_state_changed, this), this);
session->RecordStateChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Maschine2::notify_record_state_changed, this), this);
Config->ParameterChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Maschine2::notify_parameter_changed, this, _1), this);
session->config.ParameterChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Maschine2::notify_parameter_changed, this, _1), this);
session->DirtyChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Maschine2::notify_session_dirty_changed, this), this);
session->history().Changed.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Maschine2::notify_history_changed, this), this);
/* Actions */
Glib::RefPtr<Gtk::Action> act;
#if 0
act = ActionManager::get_action (X_("Editor"), X_("ToggleMeasureVisibility"));
if (act) {
Glib::RefPtr<Gtk::ToggleAction> tact = Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic (act);
tact->signal_toggled ().connect (sigc::mem_fun (*this, &Maschine2::notify_grid_change));
}
#endif
act = ActionManager::get_action (X_("Editor"), X_("snap-off"));
if (act) {
Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
ract->signal_toggled ().connect (sigc::mem_fun (*this, &Maschine2::notify_snap_change));
}
act = ActionManager::get_action (X_("Editor"), X_("snap-magnetic"));
if (act) {
Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
ract->signal_toggled ().connect (sigc::mem_fun (*this, &Maschine2::notify_snap_change));
}
act = ActionManager::get_action (X_("Editor"), X_("snap-normal"));
if (act) {
Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
ract->signal_toggled ().connect (sigc::mem_fun (*this, &Maschine2::notify_snap_change));
}
/* Surface events */
_ctrl->button (M2Contols::Play)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_play, this));
_ctrl->button (M2Contols::Rec)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_record, this));
_ctrl->button (M2Contols::Loop)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_loop, this));
_ctrl->button (M2Contols::Metronom)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_metronom, this));
_ctrl->button (M2Contols::GotoStart)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_rewind, this));
_ctrl->button (M2Contols::FastRewind)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Transport", "RewindSlow"));
_ctrl->button (M2Contols::FastForward)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Transport", "ForwardSlow"));
_ctrl->button (M2Contols::Panic)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "MIDI", "panic"));
_ctrl->button (M2Contols::JumpForward)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Editor", "jump-forward-to-mark"));
_ctrl->button (M2Contols::JumpBackward)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Editor", "jump-backward-to-mark"));
_ctrl->button (M2Contols::Grid)->pressed.connect (button_connections, invalidator (*this), boost::bind (&Maschine2::button_snap_pressed, this), gui_context());
_ctrl->button (M2Contols::Grid)->released.connect (button_connections, invalidator (*this), boost::bind (&Maschine2::button_snap_released, this), gui_context());
_ctrl->button (M2Contols::Grid)->changed.connect (button_connections, invalidator (*this), boost::bind (&Maschine2::button_snap_changed, this, _1), gui_context());
_ctrl->button (M2Contols::Save)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Common", "Save"));
_ctrl->button (M2Contols::Undo)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Editor", "undo"));
_ctrl->button (M2Contols::Redo)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Editor", "redo"));
_ctrl->button (M2Contols::MasterVolume)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::handle_master_change, this, MST_VOLUME));
_ctrl->button (M2Contols::MasterTempo)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::handle_master_change, this, MST_TEMPO));
_ctrl->button (M2Contols::EncoderWheel)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_encoder, this));
_ctrl->encoder (0)->changed.connect_same_thread (button_connections, boost::bind (&Maschine2::encoder_master, this, _1));
for (unsigned int pad = 0; pad < 16; ++pad) {
_ctrl->pad (pad)->event.connect_same_thread (button_connections, boost::bind (&Maschine2::pad_event, this, pad, _1, _2));
_ctrl->pad (pad)->changed.connect_same_thread (button_connections, boost::bind (&Maschine2::pad_change, this, pad, _1));
}
/* set initial values */
notify_record_state_changed ();
notify_transport_state_changed ();
notify_loop_state_changed ();
notify_parameter_changed ("clicking");
notify_snap_change ();
notify_session_dirty_changed ();
notify_history_changed ();
}
void
Maschine2::notify_record_state_changed ()
{
switch (session->record_status ()) {
case Session::Disabled:
_ctrl->button (M2Contols::Rec)->set_color (0);
_ctrl->button (M2Contols::Rec)->set_blinking (false);
break;
case Session::Enabled:
_ctrl->button (M2Contols::Rec)->set_color (COLOR_WHITE);
_ctrl->button (M2Contols::Rec)->set_blinking (true);
break;
case Session::Recording:
_ctrl->button (M2Contols::Rec)->set_color (COLOR_WHITE);
_ctrl->button (M2Contols::Rec)->set_blinking (false);
break;
}
}
void
Maschine2::notify_transport_state_changed ()
{
if (session->transport_rolling ()) {
_ctrl->button (M2Contols::Play)->set_color (COLOR_WHITE);
} else {
_ctrl->button (M2Contols::Play)->set_color (0);
}
notify_loop_state_changed ();
}
void
Maschine2::notify_loop_state_changed ()
{
bool looping = false;
Location* looploc = session->locations ()->auto_loop_location ();
if (looploc && session->get_play_loop ()) {
looping = true;
}
_ctrl->button (M2Contols::Loop)->set_color (looping ? COLOR_GRAY : 0);
}
void
Maschine2::notify_parameter_changed (std::string param)
{
if (param == "clicking") {
_ctrl->button (M2Contols::Metronom)->set_color (Config->get_clicking () ? COLOR_GRAY : 0);
}
}
#if 0
void
Maschine2::notify_grid_change ()
{
Glib::RefPtr<Gtk::Action> act = ActionManager::get_action (X_("Editor"), X_("ToggleMeasureVisibility"));
if (act) {
Glib::RefPtr<Gtk::ToggleAction> tact = Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic (act);
_ctrl->button (M2Contols::Grid)->set_color (tact->get_active () ? COLOR_WHITE : 0);
}
}
#endif
void
Maschine2::notify_snap_change ()
{
uint32_t rgba = 0;
if (_ctrl->button (M2Contols::Grid)->is_pressed ()) {
return;
}
Glib::RefPtr<Gtk::Action> act = ActionManager::get_action (X_("Editor"), X_("snap-magnetic"));
if (act) {
Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
if (ract->get_active ()) { rgba = COLOR_GRAY; }
}
act = ActionManager::get_action (X_("Editor"), X_("snap-normal"));
if (act) {
Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
if (ract->get_active ()) { rgba = COLOR_WHITE; }
}
_ctrl->button (M2Contols::Grid)->set_color (rgba);
}
void
Maschine2::notify_session_dirty_changed ()
{
bool is_dirty = session->dirty ();
_ctrl->button (M2Contols::Save)->set_color (is_dirty ? COLOR_WHITE : COLOR_BLACK);
_ctrl->button (M2Contols::Save)->set_blinking (is_dirty);
}
void
Maschine2::notify_history_changed ()
{
_ctrl->button (M2Contols::Redo)->set_color (session->redo_depth() > 0 ? COLOR_WHITE : COLOR_BLACK);
_ctrl->button (M2Contols::Undo)->set_color (session->undo_depth() > 0 ? COLOR_WHITE : COLOR_BLACK);
}
void
Maschine2::button_play ()
{
if (session->transport_rolling ()) {
transport_stop ();
} else {
transport_play ();
}
}
void
Maschine2::button_record ()
{
set_record_enable (!get_record_enabled ());
}
void
Maschine2::button_loop ()
{
loop_toggle ();
}
void
Maschine2::button_metronom ()
{
Config->set_clicking (!Config->get_clicking ());
}
void
Maschine2::button_rewind ()
{
goto_start (session->transport_rolling ());
}
void
Maschine2::button_action (const std::string& group, const std::string& item)
{
AccessAction (group, item);
}
#if 0
void
Maschine2::button_grid ()
{
Glib::RefPtr<Gtk::Action> act = ActionManager::get_action (X_("Editor"), X_("ToggleMeasureVisibility"));
if (act) {
Glib::RefPtr<Gtk::ToggleAction> tact = Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic (act);
tact->set_active (!tact->get_active ());
}
}
#endif
void
Maschine2::button_snap_pressed ()
{
_ctrl->button (M2Contols::Grid)->set_color (COLOR_WHITE);
_ctrl->button (M2Contols::Grid)->set_blinking (true);
}
void
Maschine2::button_snap_changed (bool pressed)
{
if (!pressed) {
_ctrl->button (M2Contols::Grid)->set_blinking (false);
notify_snap_change ();
}
notify_master_change ();
}
void
Maschine2::button_snap_released ()
{
_ctrl->button (M2Contols::Grid)->set_blinking (false);
const char* action = 0;
Glib::RefPtr<Gtk::Action> act = ActionManager::get_action (X_("Editor"), X_("snap-off"));
if (act) {
Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
if (ract->get_active ()) { action = "snap-normal"; }
}
act = ActionManager::get_action (X_("Editor"), X_("snap-normal"));
if (act) {
Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
if (ract->get_active ()) { action = "snap-magnetic"; }
}
act = ActionManager::get_action (X_("Editor"), X_("snap-magnetic"));
if (act) {
Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
if (ract->get_active ()) { action = "snap-off"; }
}
if (!action) {
assert (0);
return;
}
act = ActionManager::get_action (X_("Editor"), action);
if (act) {
Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
ract->set_active (true);
}
}
/* Master mode + state -- main encoder fn */
void
Maschine2::handle_master_change (enum MasterMode id)
{
switch (id) {
case MST_VOLUME:
if (_master_state == MST_VOLUME) { _master_state = MST_NONE; } else { _master_state = MST_VOLUME; }
break;
case MST_TEMPO:
if (_master_state == MST_TEMPO) { _master_state = MST_NONE; } else { _master_state = MST_TEMPO; }
break;
default:
return;
break;
}
notify_master_change ();
}
void
Maschine2::notify_master_change ()
{
if (_ctrl->button (M2Contols::Grid)->is_pressed ()) {
_ctrl->button (M2Contols::MasterVolume)->set_color (COLOR_BLACK);
_ctrl->button (M2Contols::MasterTempo)->set_color (COLOR_BLACK);
return;
}
switch (_master_state) {
case MST_NONE:
_ctrl->button (M2Contols::MasterVolume)->set_color (COLOR_BLACK);
_ctrl->button (M2Contols::MasterTempo)->set_color (COLOR_BLACK);
break;
case MST_VOLUME:
_ctrl->button (M2Contols::MasterVolume)->set_color (COLOR_WHITE);
_ctrl->button (M2Contols::MasterTempo)->set_color (COLOR_BLACK);
break;
case MST_TEMPO:
_ctrl->button (M2Contols::MasterVolume)->set_color (COLOR_BLACK);
_ctrl->button (M2Contols::MasterTempo)->set_color (COLOR_WHITE);
break;
}
}
static void apply_ac_delta (boost::shared_ptr<AutomationControl> ac, double d) {
if (!ac) {
return;
}
ac->set_value (ac->interface_to_internal (min (ac->upper(), max (ac->lower(), ac->internal_to_interface (ac->get_value()) + d))),
PBD::Controllable::UseGroup);
}
void
Maschine2::encoder_master (int delta)
{
if (_ctrl->button (M2Contols::Grid)->is_pressed ()) {
_ctrl->button (M2Contols::Grid)->ignore_release ();
if (delta > 0) {
AccessAction ("Editor", "next-snap-choice");
} else {
AccessAction ("Editor", "prev-snap-choice");
}
return;
}
switch (_master_state) {
case MST_NONE:
if (_ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->active ()) {
if (delta > 0) {
AccessAction ("Editor", "temporal-zoom-in");
} else {
AccessAction ("Editor", "temporal-zoom-out");
}
} else {
if (delta > 0) {
AccessAction ("Editor", "playhead-forward-to-grid");
} else {
AccessAction ("Editor", "playhead-backward-to-grid");
}
}
break;
case MST_VOLUME:
{
boost::shared_ptr<Route> master = session->master_out ();
if (master) {
// TODO consider _ctrl->button (M2Contols::EncoderWheel)->is_pressed() for fine grained
const double factor = _ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->active () ? 256. : 32.;
apply_ac_delta (master->gain_control(), delta / factor);
}
}
break;
case MST_TEMPO:
// set new tempo.. apply with "enter"
break;
}
}
void
Maschine2::button_encoder ()
{
switch (_master_state) {
case MST_NONE:
// OR: add marker ??
if (_ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->active ()) {
AccessAction ("Editor", "zoom-to-session");
}
break;
case MST_VOLUME:
// ignore -> fine gained?
break;
case MST_TEMPO:
// add new tempo.. ?
break;
}
}
void
Maschine2::pad_change (unsigned int pad, float v)
{
float lvl = v; // _ctrl->pad (pad)->value () / 4095.f;
Gtkmm2ext::Color c = Gtkmm2ext::hsva_to_color (270 - 270.f * lvl, 1.0, lvl * lvl, 1.0);
_ctrl->pad (pad)->set_color (c);
}
void
Maschine2::pad_event (unsigned int pad, float v, bool ev)
{
if (ev) {
uint8_t msg[3];
msg[0] = v > 0 ? 0x90 : 0x80;
msg[1] = 36 + pad; // TODO map note to scale
msg[2] = ((uint8_t)floor (v * 127)) & 0x7f;
_output_port->write (msg, 3, 0);
} else {
uint8_t msg[3];
msg[0] = 0xa0;
msg[1] = 36 + pad; // TODO map note to scale
msg[2] = ((uint8_t)floor (v * 127)) & 0x7f;
_output_port->write (msg, 3, 0);
}
//printf ("[%2d] %s %.1f\n", pad, ev ? "On/Off" : "Aftertouch" , v * 127);
}

View file

@ -0,0 +1,151 @@
/*
* Copyright (C) 2016 Paul Davis
* Copyright (C) 2016 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <vector>
#include <cairomm/region.h>
#include <cairomm/surface.h>
#include <cairomm/context.h>
#include "pbd/compose.h"
#include "pbd/error.h"
#include "pbd/i18n.h"
#include "canvas.h"
#include "layout.h"
#include "maschine2.h"
#include "m2device.h"
#ifdef __APPLE__
#define Rect ArdourCanvas::Rect
#endif
using namespace ArdourCanvas;
using namespace ArdourSurface;
using namespace PBD;
Maschine2Canvas::Maschine2Canvas (Maschine2&m, M2Device* hw)
: m2 (m)
{
context = Cairo::Context::create (hw->surface ());
expose_region = Cairo::Region::create ();
_width = hw->surface ()->get_width ();
_height = hw->surface ()->get_height ();
hw->vblank.connect_same_thread (vblank_connections, boost::bind (&Maschine2Canvas::expose, this));
}
Maschine2Canvas::~Maschine2Canvas ()
{
}
void
Maschine2Canvas::request_redraw ()
{
request_redraw (Rect (0, 0, _width, _height));
}
void
Maschine2Canvas::request_redraw (Rect const & r)
{
Cairo::RectangleInt cr;
cr.x = r.x0;
cr.y = r.y0;
cr.width = r.width();
cr.height = r.height();
expose_region->do_union (cr);
/* next vblank will redraw */
}
bool
Maschine2Canvas::expose ()
{
if (expose_region->empty()) {
return false; /* nothing drawn */
}
/* set up clipping */
const int nrects = expose_region->get_num_rectangles ();
for (int n = 0; n < nrects; ++n) {
Cairo::RectangleInt r = expose_region->get_rectangle (n);
context->rectangle (r.x, r.y, r.width, r.height);
}
context->clip ();
Maschine2Layout* layout = m2.current_layout();
if (layout) {
/* all layouts cover (at least) the full size of the video
display, so we do not need to check if the layout intersects
the bounding box of the full expose region.
*/
Cairo::RectangleInt r = expose_region->get_extents();
Rect rr (r.x, r.y, r.x + r.width, r.y + r.height);
layout->render (Rect (r.x, r.y, r.x + r.width, r.y + r.height), context);
}
context->reset_clip ();
/* why is there no "reset()" method for Cairo::Region? */
expose_region = Cairo::Region::create ();
return true;
}
void
Maschine2Canvas::request_size (Duple)
{
/* fixed size canvas */
}
Rect
Maschine2Canvas::visible_area () const
{
/* may need to get more sophisticated once we do scrolling */
return Rect (0, 0, _width, _height);
}
Glib::RefPtr<Pango::Context>
Maschine2Canvas::get_pango_context ()
{
if (!pango_context) {
PangoFontMap* map = pango_cairo_font_map_get_default ();
if (!map) {
error << _("Default Cairo font map is null!") << endmsg;
return Glib::RefPtr<Pango::Context> ();
}
PangoContext* context = pango_font_map_create_context (map);
if (!context) {
error << _("cannot create new PangoContext from cairo font map") << endmsg;
return Glib::RefPtr<Pango::Context> ();
}
pango_context = Glib::wrap (context);
}
return pango_context;
}

View file

@ -0,0 +1,87 @@
/*
* Copyright (C) 2016 Paul Davis
* Copyright (C) 2016 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _ardour_maschine2_canvas_h_
#define _ardour_maschine2_canvas_h_
#include <cairomm/refptr.h>
#include <glibmm/threads.h>
#include "canvas/canvas.h"
namespace Cairo {
class ImageSurface;
class Context;
class Region;
}
namespace ArdourSurface {
class M2Device;
class Maschine2;
/* A canvas which renders to the Push2 display */
class Maschine2Canvas : public ArdourCanvas::Canvas
{
public:
Maschine2Canvas (Maschine2&, M2Device*);
~Maschine2Canvas();
void request_redraw ();
void request_redraw (ArdourCanvas::Rect const &);
bool vblank ();
Cairo::RefPtr<Cairo::Context> image_context() { return context; }
ArdourCanvas::Coord width() const { return _width; }
ArdourCanvas::Coord height() const { return _height; }
void request_size (ArdourCanvas::Duple);
ArdourCanvas::Rect visible_area () const;
/* API that does nothing since we have no input events */
void ungrab () {}
void grab (ArdourCanvas::Item*) {}
void focus (ArdourCanvas::Item*) {}
void unfocus (ArdourCanvas::Item*) {}
void re_enter() {}
void pick_current_item (int) {}
void pick_current_item (ArdourCanvas::Duple const &, int) {}
bool get_mouse_position (ArdourCanvas::Duple&) const { return false; }
Glib::RefPtr<Pango::Context> get_pango_context ();
private:
int _width;
int _height;
Cairo::RefPtr<Cairo::Context> context;
Cairo::RefPtr<Cairo::Region> expose_region;
Glib::RefPtr<Pango::Context> pango_context;
Maschine2& m2;
PBD::ScopedConnection vblank_connections;
bool expose ();
};
} /* namespace ArdourSurface */
#endif

View file

@ -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
};

View file

@ -0,0 +1,79 @@
/*
* Copyright (C) 2016 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <stdexcept>
#include "pbd/error.h"
#include "ardour/rc_configuration.h"
#include "control_protocol/control_protocol.h"
#include "maschine2.h"
using namespace ARDOUR;
using namespace PBD;
using namespace ArdourSurface;
static ControlProtocol*
new_maschine2 (ControlProtocolDescriptor*, Session* s)
{
Maschine2* m2 = 0;
try {
m2 = new Maschine2 (*s);
}
catch (std::exception & e) {
PBD::error << "Failed to instantiate Maschine2: " << e.what() << endmsg;
delete m2;
m2 = 0;
}
m2->set_active (true);
return m2;
}
static void
delete_maschine2 (ControlProtocolDescriptor*, ControlProtocol* cp)
{
delete cp;
}
static bool
probe_maschine2 (ControlProtocolDescriptor*)
{
return true;
}
static void*
maschine2_request_buffer_factory (uint32_t num_requests)
{
return Maschine2::request_factory (num_requests);
}
static ControlProtocolDescriptor maschine2_descriptor = {
/*name : */ "NI Maschine2",
/*id : */ "uri://ardour.org/surfaces/maschine2:0",
/*ptr : */ 0,
/*module : */ 0,
/*mandatory : */ 0,
/*supports_feedback : */ false,
/*probe : */ probe_maschine2,
/*initialize : */ new_maschine2,
/*destroy : */ delete_maschine2,
/*request_buffer_factory */ maschine2_request_buffer_factory
};
extern "C" ARDOURSURFACE_API ControlProtocolDescriptor* protocol_descriptor () { return &maschine2_descriptor; }

View file

@ -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();
}

View file

@ -0,0 +1,55 @@
/*
* Copyright (C) 2016 Paul Davis
* Copyright (C) 2016 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _ardour_maschine2_layout_h_
#define _ardour_maschine2_layout_h_
#include <sigc++/trackable.h>
#include <cairomm/refptr.h>
#include "canvas/container.h"
namespace ARDOUR {
class Session;
}
namespace ArdourSurface {
class Maschine2;
class Maschine2Layout : public sigc::trackable, public ArdourCanvas::Container
{
public:
Maschine2Layout (Maschine2& m2, ARDOUR::Session& s, std::string const & name);
virtual ~Maschine2Layout ();
std::string name() const { return _name; }
int display_width () const;
int display_height () const;
void compute_bounding_box () const;
protected:
Maschine2& _m2;
ARDOUR::Session& _session;
std::string _name;
};
} /* namespace */
#endif /* _ardour_maschine2_layout_h_ */

View file

@ -0,0 +1,220 @@
/*
* Copyright (C) 2016 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _ardour_surfaces_m2button_h_
#define _ardour_surfaces_m2button_h_
#include <stdint.h>
#include "gtkmm2ext/colors.h"
#include "pbd/signals.h"
namespace ArdourSurface {
class M2ButtonInterface
{
public:
M2ButtonInterface () {}
virtual ~M2ButtonInterface () {}
/* user API */
PBD::Signal1<void, bool> changed;
PBD::Signal0<void> pressed;
PBD::Signal0<void> released;
virtual void set_blinking (bool) {}
virtual void set_color (uint32_t rgba) {}
virtual bool is_pressed () const { return false; }
virtual bool active () const { return is_pressed (); }
virtual void ignore_release () {}
// TODO allow to suspend *next* release signal
// e.g. press + hold "grid", move encoder -> release "grid" -> noop
/* internal API - called from device thread */
virtual bool set_active (bool a) { return false; }
virtual uint8_t lightness (float) const { return 0; }
virtual uint32_t color (float) const { return 0; }
};
class M2Button : public M2ButtonInterface
{
public:
M2Button ()
: M2ButtonInterface ()
, _pressed (false)
, _blink (false)
, _ignore_release (false)
, _lightness (0)
, _rgba (0)
{}
/* user API */
void set_blinking (bool en) {
_blink = en;
}
virtual void set_color (uint32_t rgba) {
_rgba = rgba;
/* 7 bit color */
const uint8_t r = ((rgba >> 24) & 0xff) >> 1;
const uint8_t g = ((rgba >> 16) & 0xff) >> 1;
const uint8_t b = ((rgba >> 8) & 0xff) >> 1;
_lightness = std::max (r, std::max (g, b));
}
bool is_pressed () const { return _pressed; }
void ignore_release () {
if (_pressed) {
_ignore_release = true;
}
}
/* internal API - called from device thread */
virtual bool set_active (bool a) {
if (a == _pressed) {
return false;
}
_pressed = a;
if (a) {
pressed (); /* EMIT SIGNAL */
} else {
if (_ignore_release) {
_ignore_release = false;
} else {
released (); /* EMIT SIGNAL */
}
}
changed (a); /* EMIT SIGNAL */
return true;
}
uint8_t lightness (float blink) const {
if (_blink && blink >= 0.f && blink <= 1.f) {
return (uint8_t) floorf(blink * _lightness);
}
return _lightness;
}
uint32_t color (float blink) const {
if (_blink && blink >= 0.f && blink <= 1.f) {
Gtkmm2ext::HSV hsv (_rgba);
Gtkmm2ext::HSV s (hsv.shade (blink));
return s.color();
}
return _rgba;
}
protected:
bool _pressed;
bool _blink;
bool _ignore_release;
uint8_t _lightness;
uint32_t _rgba;
};
class M2StatelessButton : public M2Button
{
public:
M2StatelessButton () : M2Button () {}
bool set_active (bool a) {
if (a == _pressed) {
return false;
}
if (a) {
set_color (0xffffffff);
} else {
set_color (0x000000ff);
}
return M2Button::set_active (a);
}
};
class M2ToggleButton : public M2Button
{
public:
M2ToggleButton ()
: M2Button ()
, _active (false)
{
changed.connect_same_thread (changed_connection, boost::bind (&M2ToggleButton::change_event, this, _1));
}
PBD::Signal1<void, bool> toggled;
bool active () const { return _active; }
protected:
void change_event (bool down) {
if (down) { return; }
_active = !_active;
set_color (_active ? 0xffffffff : 0x000000ff);
toggled (_active);
}
PBD::ScopedConnection changed_connection;
bool _active;
};
class M2ToggleHoldButton : public M2Button
{
public:
M2ToggleHoldButton ()
: M2Button ()
, _active (false)
, _active_on_release (false)
{
changed.connect_same_thread (changed_connection, boost::bind (&M2ToggleHoldButton::change_event, this, _1));
}
PBD::Signal1<void, bool> toggled;
bool active () const { return _active; }
void unset_active_on_release () { if (is_pressed ()) { _active_on_release = false; } }
protected:
void change_event (bool down) {
if (down) {
if (_active) {
_active_on_release = false;
return;
}
_active = true;
_active_on_release = true;
} else {
if (_active == _active_on_release) {
return;
}
_active = _active_on_release;
}
set_color (_active ? 0xffffffff : 0x000000ff);
toggled (_active);
}
PBD::ScopedConnection changed_connection;
bool _active;
bool _active_on_release;
};
} /* namespace */
#endif /* _ardour_surfaces_m2button_h_ */

View file

@ -0,0 +1,284 @@
#include <math.h>
#include "pbd/compose.h"
#include "maschine2.h"
#include "m2controls.h"
#include "m2_dev_mikro.h"
#include <pangomm/fontdescription.h>
#include "images.h"
static size_t mikro_png_readoff = 0;
static Cairo::ErrorStatus maschine_png_read (unsigned char* d, unsigned int s) {
if (s + mikro_png_readoff > sizeof (mikro_png)) {
return CAIRO_STATUS_READ_ERROR;
}
memcpy (d, &mikro_png[mikro_png_readoff], s);
mikro_png_readoff += s;
return CAIRO_STATUS_SUCCESS;
}
using namespace ArdourSurface;
Maschine2Mikro::Maschine2Mikro () : M2Device ()
{
_surface = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, 128, 64);
clear (true);
}
void
Maschine2Mikro::clear (bool splash)
{
M2Device::clear (splash);
memset (&ctrl_in, 0, sizeof (ctrl_in));
memset (pad, 0, sizeof (pad));
_lights[0] = 0xff;
for (int l = 0; l < 4; ++l) {
_img[l][0] = 0xff;
}
Cairo::RefPtr<Cairo::Context> cr = Cairo::Context::create (_surface);
if (!splash) {
mikro_png_readoff = 0;
Cairo::RefPtr<Cairo::ImageSurface> sf = Cairo::ImageSurface::create_from_png_stream (sigc::ptr_fun (maschine_png_read));
cr->set_source(sf, 0, 0);
cr->paint ();
} else {
cr->set_operator (Cairo::OPERATOR_CLEAR);
cr->paint ();
cr->set_operator (Cairo::OPERATOR_OVER);
Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create (cr);
Pango::FontDescription fd ("Sans Bold 18px");
layout->set_font_description (fd);
layout->set_alignment (Pango::ALIGN_CENTER);
layout->set_text (string_compose ("%1\n%2", PROGRAM_NAME, VERSIONSTRING));
int tw, th;
layout->get_pixel_size (tw, th);
cr->move_to (128 - tw * 0.5, 32 - th * 0.5);
cr->set_source_rgb (1, 1, 1);
layout->show_in_cairo_context(cr);
}
//_surface->write_to_png ("/tmp/amaschine.png");
}
void
Maschine2Mikro::read (hid_device* handle, M2Contols* ctrl)
{
assert (ctrl);
while (true) {
uint8_t buf[256];
int res = hid_read (handle, buf, 256);
if (res < 1) {
return;
}
// TODO parse incrementally if chunked at 64
if (res > 4 && buf[0] == 0x01) {
memcpy (&ctrl_in, &buf[1], sizeof (ctrl_in));
assign_controls (ctrl);
}
else if (res > 32 && buf[0] == 0x20) {
for (unsigned int i = 0; i < 16; ++i) {
uint8_t v0 = buf[1 + 2 * i];
uint8_t v1 = buf[2 + 2 * i];
uint8_t p = (v1 & 0xf0) >> 4;
pad[p] = ((v1 & 0xf) << 8) | v0;
unsigned int pid = 15 - ((i & 0xc) + (3 - (i & 0x3)));
ctrl->pad (pid)->set_value (pad[p]);
}
// TODO read complete 65 byte msg, expect buf[33] == 0x00
}
}
}
void
Maschine2Mikro::write (hid_device* handle, M2Contols* ctrl)
{
bump_blink ();
uint8_t buf[265];
//TODO double-buffer, send changes only if needed
/* 30 control buttons, 8-bit brightness,
* + 16 RGB pads
*/
buf[0] = 0x80;
set_lights (ctrl, &buf[1]);
set_pads (ctrl, &buf[31]);
if (memcmp (_lights, buf, 79)) {
hid_write (handle, buf, 79);
memcpy (_lights, buf, 79);
}
if (_splashcnt < _splashtime ) {
++_splashcnt;
}
else if (! vblank () /* EMIT SIGNAL*/) {
/* check clear/initial draw */
if (_img[0][0] != 0xff) {
return;
}
}
/* display */
_surface->flush ();
const unsigned char* img = _surface->get_data ();
const int stride = _surface->get_stride ();
memset (buf, 0, 9);
buf[0] = 0xe0;
for (int l = 0; l < 4; ++l) {
buf[1] = 32 * l;
buf[5] = 0x20;
buf[7] = 0x08;
int y0 = l * 16;
for (int p = 0; p < 256; ++p) {
uint8_t v = 0;
const int y = y0 + p / 16;
for (int b = 0; b < 8; ++b) {
const int x = (p % 16) * 8 + b;
int off = y * stride + x * 4 /* ARGB32 */;
/* off + 0 == blue
* off + 1 == green
* off + 2 == red
* off + 3 == alpha
*/
/* calculate lightness */
uint8_t l = std::max (img[off + 0], std::max (img[off + 1], img[off + 2]));
if (l > 0x7e) { // TODO: take alpha channel into account?!
v |= 1 << (7 - b);
}
}
buf[9 + p] = v;
}
if (memcmp (_img[l], buf, 265)) {
hid_write (handle, buf, 265);
memcpy (_img[l], buf, 265);
}
}
}
void
Maschine2Mikro::assign_controls (M2Contols* ctrl) const
{
ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->set_active (ctrl_in.trs_shift ? true : false);
M2Contols::Modifier mod = ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->active () ? M2Contols::ModShift : M2Contols::ModNone;
bool change = false;
#define ASSIGN(BTN, VAR) \
change |= ctrl->button (M2Contols:: BTN, mod)->set_active (ctrl_in. VAR ? true : false)
ASSIGN (BtnRestart, trs_restart);
ASSIGN (BtnStepLeft, trs_left);
ASSIGN (BtnStepRight, trs_right);
ASSIGN (BtnGrid, trs_grid);
ASSIGN (BtnPlay, trs_play);
ASSIGN (BtnRec, trs_rec);
ASSIGN (BtnErase, trs_erase);
ASSIGN (BtnGroupA, group);
ASSIGN (BtnBrowse, browse);
ASSIGN (BtnSampling, sampling);
ASSIGN (BtnNoteRepeat, note_repeat);
ASSIGN (BtnWheel, mst_wheel);
ASSIGN (BtnTop0, f1);
ASSIGN (BtnTop1, f1);
ASSIGN (BtnTop2, f3);
ASSIGN (BtnControl, control);
ASSIGN (BtnNavigate, navigate); // XXX
ASSIGN (BtnNavLeft, nav_left);
ASSIGN (BtnNavRight, nav_right);
ASSIGN (BtnEnter, main);
ASSIGN (BtnScene, pads_scene);
ASSIGN (BtnPattern, pads_pattern);
ASSIGN (BtnPadMode, pads_mode);
ASSIGN (BtnNavigate, pads_navigate);
ASSIGN (BtnDuplicate, pads_duplicate);
ASSIGN (BtnSelect, pads_select);
ASSIGN (BtnSolo, pads_solo);
ASSIGN (BtnMute, pads_mute);
#undef ASSIGN
change |= ctrl->encoder (0)->set_value (ctrl_in.mst_wheel_pos);
if (change && mod == M2Contols::ModShift) {
M2ToggleHoldButton* btn = dynamic_cast<M2ToggleHoldButton*> (ctrl->button (M2Contols::BtnShift, M2Contols::ModNone));
if (btn) {
btn->unset_active_on_release ();
}
}
}
#define LIGHT(BIT, BTN) \
b[BIT] = ctrl->button (M2Contols:: BTN, mod)->lightness (_blink_shade)
void
Maschine2Mikro::set_pads (M2Contols* ctrl, uint8_t* b) const
{
if (!ctrl) {
memset (b, 0, 48);
return;
}
for (unsigned int i = 0; i < 16; ++i) {
unsigned int pid = 15 - ((i & 0xc) + (3 - (i & 0x3)));
ctrl->pad (pid)->color (b[i * 3], b[1 + i * 3], b[2 + i * 3]);
}
}
void
Maschine2Mikro::set_lights (M2Contols* ctrl, uint8_t* b) const
{
if (!ctrl) {
memset (b, 0, 29);
return;
}
M2Contols::Modifier mod = ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->active () ? M2Contols::ModShift : M2Contols::ModNone;
LIGHT ( 0, BtnTop0); // F1
LIGHT ( 1, BtnTop1); // F2
LIGHT ( 2, BtnTop2); // F3
LIGHT ( 3, BtnControl);
LIGHT ( 4, BtnNavigate); // XXX
LIGHT ( 5, BtnNavLeft);
LIGHT ( 6, BtnNavRight);
LIGHT ( 7, BtnEnter); // Main
const uint32_t rgb = ctrl->button (M2Contols::BtnGroupA, mod)->color (_blink_shade);
b[8] = (rgb >> 0) & 0xff;
b[9] = (rgb >> 8) & 0xff;
b[10] = (rgb >> 16) & 0xff;
LIGHT (11, BtnBrowse);
LIGHT (12, BtnSampling);
LIGHT (13, BtnNoteRepeat);
LIGHT (14, BtnRestart);
LIGHT (15, BtnStepLeft);
LIGHT (16, BtnStepRight);
LIGHT (17, BtnGrid);
LIGHT (18, BtnPlay);
LIGHT (19, BtnRec);
LIGHT (20, BtnErase);
LIGHT (21, BtnShift);
LIGHT (22, BtnScene);
LIGHT (23, BtnPattern);
LIGHT (24, BtnPadMode);
LIGHT (25, BtnNavigate);
LIGHT (26, BtnDuplicate);
LIGHT (27, BtnSelect);
LIGHT (28, BtnSolo);
LIGHT (29, BtnMute);
}

View file

@ -0,0 +1,99 @@
/*
* Copyright (C) 2016 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _ardour_surfaces_m2mikro_h_
#define _ardour_surfaces_m2mikro_h_
#include "m2device.h"
#include <cairomm/context.h>
#include <pangomm/layout.h>
namespace ArdourSurface {
class Maschine2Mikro : public M2Device
{
public:
Maschine2Mikro ();
void clear (bool splash = false);
void read (hid_device*, M2Contols*);
void write (hid_device*, M2Contols*);
Cairo::RefPtr<Cairo::ImageSurface> surface () { return _surface; }
private:
#if defined(__GNUC__)
#define ATTRIBUTE_PACKED __attribute__((__packed__))
#else
#define ATTRIBUTE_PACKED
#pragma pack(1)
#endif
struct machine_mk2_input {
unsigned int trs_restart : 1; // 0
unsigned int trs_left : 1;
unsigned int trs_right : 1;
unsigned int trs_grid : 1;
unsigned int trs_play : 1;
unsigned int trs_rec : 1;
unsigned int trs_erase : 1;
unsigned int trs_shift : 1;
unsigned int group : 1; // 8
unsigned int browse : 1;
unsigned int sampling : 1;
unsigned int note_repeat : 1;
unsigned int mst_wheel : 1;
unsigned int reserved : 3;
unsigned int f1 : 1; // 16
unsigned int f2 : 1;
unsigned int f3 : 1;
unsigned int control : 1;
unsigned int navigate : 1;
unsigned int nav_left : 1;
unsigned int nav_right : 1;
unsigned int main : 1;
unsigned int pads_scene : 1; // 24
unsigned int pads_pattern : 1;
unsigned int pads_mode : 1;
unsigned int pads_navigate : 1;
unsigned int pads_duplicate : 1;
unsigned int pads_select : 1;
unsigned int pads_solo : 1;
unsigned int pads_mute : 1; // 31
unsigned int mst_wheel_pos : 8; // 32..40 // range: 0..15
} ATTRIBUTE_PACKED ctrl_in;
#if (!defined __GNUC__)
#pragma pack()
#endif
uint16_t pad[16];
Cairo::RefPtr<Cairo::ImageSurface> _surface;
private:
void assign_controls (M2Contols*) const;
void set_lights (M2Contols*, uint8_t*) const;
void set_pads (M2Contols*, uint8_t*) const;
uint8_t _lights[79];
uint8_t _img[4][265];
};
} /* namespace */
#endif

View file

@ -0,0 +1,380 @@
#include <math.h>
#include "pbd/compose.h"
#include "maschine2.h"
#include "m2controls.h"
#include "m2_dev_mk2.h"
#include <pangomm/fontdescription.h>
#include "images.h"
static size_t maschine_png_readoff = 0;
static Cairo::ErrorStatus maschine_png_read (unsigned char* d, unsigned int s) {
if (s + maschine_png_readoff > sizeof (maschine_png)) {
return CAIRO_STATUS_READ_ERROR;
}
memcpy (d, &maschine_png[maschine_png_readoff], s);
maschine_png_readoff += s;
return CAIRO_STATUS_SUCCESS;
}
using namespace ArdourSurface;
Maschine2Mk2::Maschine2Mk2 () : M2Device ()
{
_surface = Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, 512, 64);
clear (true);
}
void
Maschine2Mk2::clear (bool splash)
{
M2Device::clear (splash);
memset (&ctrl_in, 0, sizeof (ctrl_in));
memset (pad, 0, sizeof (pad));
ctrl80[0] = 0xff;
ctrl81[0] = 0xff;
ctrl82[0] = 0xff;
for (int d = 0; d < 2; ++d) {
for (int l = 0; l < 8; ++l) {
_img[d][l][0] = 0xff;
}
}
#if 0
Cairo::RefPtr<Cairo::Context> c = Cairo::Context::create (_surface);
c->set_operator (Cairo::OPERATOR_CLEAR);
c->paint ();
return;
#endif
maschine_png_readoff = 0;
Cairo::RefPtr<Cairo::ImageSurface> sf = Cairo::ImageSurface::create_from_png_stream (sigc::ptr_fun (maschine_png_read));
Cairo::RefPtr<Cairo::Context> cr = Cairo::Context::create (_surface);
cr->set_source(sf, 0, 0);
cr->paint ();
Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create (cr);
Pango::FontDescription fd ("Sans Bold 18px");
layout->set_font_description (fd);
layout->set_alignment (Pango::ALIGN_CENTER);
int cx;
if (splash) {
layout->set_text (string_compose ("%1\n%2", PROGRAM_NAME, VERSIONSTRING));
cx = 384;
} else {
cr->rectangle (326, 0, 186, 64);
cr->set_source_rgb (0, 0, 0);
cr->fill ();
layout->set_text ("Keep Groovin'");
cx = 421;
}
int tw, th;
layout->get_pixel_size (tw, th);
cr->move_to (cx - tw * 0.5, 32 - th * 0.5);
cr->set_source_rgb (1, 1, 1);
layout->show_in_cairo_context(cr);
//_surface->write_to_png ("/tmp/amaschine.png");
}
void
Maschine2Mk2::read (hid_device* handle, M2Contols* ctrl)
{
assert (ctrl);
while (true) {
uint8_t buf[256];
int res = hid_read (handle, buf, 256);
if (res < 1) {
return;
}
// TODO parse incrementally if chunked at 64
if (res > 24 && buf[0] == 0x01) {
memcpy (&ctrl_in, &buf[1], sizeof (ctrl_in));
assign_controls (ctrl);
}
else if (res > 32 && buf[0] == 0x20) {
for (unsigned int i = 0; i < 16; ++i) {
uint8_t v0 = buf[1 + 2 * i];
uint8_t v1 = buf[2 + 2 * i];
uint8_t p = (v1 & 0xf0) >> 4;
pad[p] = ((v1 & 0xf) << 8) | v0;
unsigned int pid = 15 - ((i & 0xc) + (3 - (i & 0x3)));
ctrl->pad (pid)->set_value (pad[p]);
}
// TODO read complete 65 byte msg, expect buf[33] == 0x00
}
}
}
void
Maschine2Mk2::write (hid_device* handle, M2Contols* ctrl)
{
bump_blink ();
uint8_t buf[265];
//TODO double-buffer, send changes only if needed
/* 31 control buttons: 8 mst + 8 top + 8 pads + 7 mst
* 8-bit brightness
*/
buf[0] = 0x82;
set_colors82 (ctrl, &buf[1]);
if (memcmp (ctrl82, buf, 32)) {
hid_write (handle, buf, 32);
memcpy (ctrl82, buf, 32);
}
/* 8 group rgb|rgb + 8 on/off transport buttons */
buf[0] = 0x81;
set_colors81 (ctrl, &buf[1]);
if (memcmp (ctrl81, buf, 57)) {
hid_write (handle, buf, 57);
memcpy (ctrl81, buf, 57);
}
/* 16 RGB grid pads */
buf[0] = 0x80;
set_colors80 (ctrl, &buf[1]);
if (memcmp (ctrl80, buf, 49)) {
hid_write (handle, buf, 49);
memcpy (ctrl80, buf, 49);
}
if (_splashcnt < _splashtime) {
++_splashcnt;
}
else if (! vblank () /* EMIT SIGNAL*/) {
/* check clear/initial draw */
if (_img[0][0][0] != 0xff) {
return;
}
}
/* display */
_surface->flush ();
const unsigned char* img = _surface->get_data ();
const int stride = _surface->get_stride ();
for (int d = 0; d < 2; ++d) {
memset (buf, 0, 9);
buf[0] = 0xe0 | d;
for (int l = 0; l < 8; ++l) {
buf[3] = 8 * l;
buf[5] = 0x20;
buf[7] = 0x08;
int y0 = l * 8;
int x0 = d * 256;
for (int p = 0; p < 256; ++p) {
uint8_t v = 0;
const int y = y0 + p / 32;
for (int b = 0; b < 8; ++b) {
const int x = x0 + (p % 32) * 8 + b;
int off = y * stride + x * 4 /* ARGB32 */;
/* off + 0 == blue
* off + 1 == green
* off + 2 == red
* off + 3 == alpha
*/
/* calculate lightness */
uint8_t l = std::max (img[off + 0], std::max (img[off + 1], img[off + 2]));
if (l > 0x7e) { // TODO: take alpha channel into account?!
v |= 1 << (7 - b);
}
}
buf[9 + p] = v;
}
if (memcmp (_img[d][l], buf, 265)) {
hid_write (handle, buf, 265);
memcpy (_img[d][l], buf, 265);
}
}
}
}
void
Maschine2Mk2::assign_controls (M2Contols* ctrl) const
{
ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->set_active (ctrl_in.trs_shift ? true : false);
M2Contols::Modifier mod = ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->active () ? M2Contols::ModShift : M2Contols::ModNone;
bool change = false;
#define ASSIGN(BTN, VAR) \
change |= ctrl->button (M2Contols:: BTN, mod)->set_active (ctrl_in. VAR ? true : false)
ASSIGN (BtnRestart, trs_restart);
ASSIGN (BtnStepLeft, trs_left);
ASSIGN (BtnStepRight, trs_right);
ASSIGN (BtnGrid, trs_grid);
ASSIGN (BtnPlay, trs_play);
ASSIGN (BtnRec, trs_rec);
ASSIGN (BtnErase, trs_erase);
ASSIGN (BtnScene, pads_scene);
ASSIGN (BtnPattern, pads_pattern);
ASSIGN (BtnPadMode, pads_mode);
ASSIGN (BtnNavigate, pads_navigate);
ASSIGN (BtnDuplicate, pads_duplicate);
ASSIGN (BtnSelect, pads_select);
ASSIGN (BtnSolo, pads_solo);
ASSIGN (BtnMute, pads_mute);
ASSIGN (BtnControl, top_control);
ASSIGN (BtnStep, top_step);
ASSIGN (BtnBrowse, top_browse);
ASSIGN (BtnSampling, top_sampling);
ASSIGN (BtnSelLeft, top_left);
ASSIGN (BtnSelRight, top_right);
ASSIGN (BtnAll, top_all);
ASSIGN (BtnAuto, top_auto);
ASSIGN (BtnVolume, mst_volume);
ASSIGN (BtnSwing, mst_swing);
ASSIGN (BtnTempo, mst_tempo);
ASSIGN (BtnNavLeft, mst_left);
ASSIGN (BtnNavRight, mst_right);
ASSIGN (BtnEnter, mst_enter);
ASSIGN (BtnNoteRepeat, mst_note_repeat);
ASSIGN (BtnWheel, mst_wheel);
ASSIGN (BtnGroupA, groups_a);
ASSIGN (BtnGroupB, groups_b);
ASSIGN (BtnGroupC, groups_c);
ASSIGN (BtnGroupD, groups_d);
ASSIGN (BtnGroupE, groups_e);
ASSIGN (BtnGroupF, groups_f);
ASSIGN (BtnGroupG, groups_g);
ASSIGN (BtnGroupH, groups_h);
ASSIGN (BtnTop0, top_0);
ASSIGN (BtnTop1, top_1);
ASSIGN (BtnTop2, top_2);
ASSIGN (BtnTop3, top_3);
ASSIGN (BtnTop4, top_4);
ASSIGN (BtnTop5, top_5);
ASSIGN (BtnTop6, top_6);
ASSIGN (BtnTop7, top_7);
#undef ASSIGN
change |= ctrl->encoder (0)->set_value (ctrl_in.mst_wheel_pos);
for (int i = 0; i < 8; ++i) {
change |= ctrl->encoder (1 + i)->set_value (ctrl_in.top_knobs[i]);
}
if (change && mod == M2Contols::ModShift) {
M2ToggleHoldButton* btn = dynamic_cast<M2ToggleHoldButton*> (ctrl->button (M2Contols::BtnShift, M2Contols::ModNone));
if (btn) {
btn->unset_active_on_release ();
}
}
}
#define LIGHT(BIT, BTN) \
b[BIT] = ctrl->button (M2Contols:: BTN, mod)->lightness (_blink_shade)
#define COLOR(BIT, BTN) \
{ \
const uint32_t rgb = ctrl->button (M2Contols:: BTN, mod)->color (_blink_shade); \
b[0 + BIT ] = (rgb >> 0) & 0xff; \
b[1 + BIT ] = (rgb >> 8) & 0xff; \
b[2 + BIT ] = (rgb >> 16) & 0xff; \
b[3 + BIT ] = (rgb >> 0) & 0xff; \
b[4 + BIT ] = (rgb >> 8) & 0xff; \
b[5 + BIT ] = (rgb >> 16) & 0xff; \
}
void
Maschine2Mk2::set_colors80 (M2Contols* ctrl, uint8_t* b) const
{
if (!ctrl) {
memset (b, 0, 48);
return;
}
for (unsigned int i = 0; i < 16; ++i) {
unsigned int pid = 15 - ((i & 0xc) + (3 - (i & 0x3)));
ctrl->pad (pid)->color (b[i * 3], b[1 + i * 3], b[2 + i * 3]);
}
}
void
Maschine2Mk2::set_colors81 (M2Contols* ctrl, uint8_t* b) const
{
if (!ctrl) {
memset (b, 0, 56);
return;
}
M2Contols::Modifier mod = ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->active () ? M2Contols::ModShift : M2Contols::ModNone;
COLOR ( 0, BtnGroupA);
COLOR ( 6, BtnGroupB);
COLOR (12, BtnGroupC);
COLOR (18, BtnGroupD);
COLOR (24, BtnGroupE);
COLOR (30, BtnGroupF);
COLOR (36, BtnGroupG);
COLOR (42, BtnGroupH);
LIGHT (48, BtnRestart);
LIGHT (49, BtnStepLeft);
LIGHT (50, BtnStepRight);
LIGHT (51, BtnGrid);
LIGHT (52, BtnPlay);
LIGHT (53, BtnRec);
LIGHT (54, BtnErase);
LIGHT (55, BtnShift);
}
void
Maschine2Mk2::set_colors82 (M2Contols* ctrl, uint8_t* b) const
{
if (!ctrl) {
memset (b, 0, 31);
return;
}
M2Contols::Modifier mod = ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->active () ? M2Contols::ModShift : M2Contols::ModNone;
LIGHT ( 0, BtnControl);
LIGHT ( 1, BtnStep);
LIGHT ( 2, BtnBrowse);
LIGHT ( 3, BtnSampling);
LIGHT ( 4, BtnSelLeft);
LIGHT ( 5, BtnSelRight);
LIGHT ( 6, BtnAll);
LIGHT ( 7, BtnAuto);
LIGHT ( 8, BtnTop0);
LIGHT ( 9, BtnTop1);
LIGHT (10, BtnTop2);
LIGHT (11, BtnTop3);
LIGHT (12, BtnTop4);
LIGHT (13, BtnTop5);
LIGHT (14, BtnTop6);
LIGHT (15, BtnTop7);
LIGHT (16, BtnScene);
LIGHT (17, BtnPattern);
LIGHT (18, BtnPadMode);
LIGHT (19, BtnNavigate);
LIGHT (20, BtnDuplicate);
LIGHT (21, BtnSelect);
LIGHT (22, BtnSolo);
LIGHT (23, BtnMute);
LIGHT (24, BtnVolume);
LIGHT (25, BtnSwing);
LIGHT (26, BtnTempo);
LIGHT (27, BtnNavLeft);
LIGHT (28, BtnNavRight);
LIGHT (29, BtnEnter);
LIGHT (30, BtnNoteRepeat);
}

View file

@ -0,0 +1,122 @@
/*
* Copyright (C) 2016 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _ardour_surfaces_m2mk2_h_
#define _ardour_surfaces_m2mk2_h_
#include "m2device.h"
#include <cairomm/context.h>
#include <pangomm/layout.h>
namespace ArdourSurface {
class Maschine2Mk2 : public M2Device
{
public:
Maschine2Mk2 ();
void clear (bool splash = false);
void read (hid_device*, M2Contols*);
void write (hid_device*, M2Contols*);
Cairo::RefPtr<Cairo::ImageSurface> surface () { return _surface; }
private:
#if defined(__GNUC__)
#define ATTRIBUTE_PACKED __attribute__((__packed__))
#else
#define ATTRIBUTE_PACKED
#pragma pack(1)
#endif
struct machine_mk2_input {
unsigned int top_0 : 1; // 0
unsigned int top_1 : 1;
unsigned int top_2 : 1;
unsigned int top_3 : 1;
unsigned int top_4 : 1;
unsigned int top_5 : 1;
unsigned int top_6 : 1;
unsigned int top_7 : 1;
unsigned int top_control : 1; // 8
unsigned int top_step : 1;
unsigned int top_browse : 1;
unsigned int top_sampling : 1;
unsigned int top_left : 1;
unsigned int top_right : 1;
unsigned int top_all : 1;
unsigned int top_auto : 1;
unsigned int mst_volume : 1; // 16
unsigned int mst_swing : 1;
unsigned int mst_tempo : 1;
unsigned int mst_left : 1;
unsigned int mst_right : 1;
unsigned int mst_enter : 1;
unsigned int mst_note_repeat : 1;
unsigned int mst_wheel : 1;
unsigned int groups_a : 1; // 24
unsigned int groups_b : 1;
unsigned int groups_c : 1;
unsigned int groups_d : 1;
unsigned int groups_e : 1;
unsigned int groups_f : 1;
unsigned int groups_g : 1;
unsigned int groups_h : 1;
unsigned int trs_restart : 1; // 32
unsigned int trs_left : 1;
unsigned int trs_right : 1;
unsigned int trs_grid : 1;
unsigned int trs_play : 1;
unsigned int trs_rec : 1;
unsigned int trs_erase : 1;
unsigned int trs_shift : 1;
unsigned int pads_scene : 1; // 40
unsigned int pads_pattern : 1;
unsigned int pads_mode : 1;
unsigned int pads_navigate : 1;
unsigned int pads_duplicate : 1;
unsigned int pads_select : 1;
unsigned int pads_solo : 1;
unsigned int pads_mute : 1;
unsigned int reserved : 8; // 48
unsigned int mst_wheel_pos : 8; // 56 // range: 0..15
uint16_t top_knobs[8]; // 64 ... 191 // range 0..999
} ATTRIBUTE_PACKED ctrl_in;
#if (!defined __GNUC__)
#pragma pack()
#endif
uint16_t pad[16];
Cairo::RefPtr<Cairo::ImageSurface> _surface;
private:
void assign_controls (M2Contols*) const;
void set_colors80 (M2Contols*, uint8_t*) const;
void set_colors81 (M2Contols*, uint8_t*) const;
void set_colors82 (M2Contols*, uint8_t*) const;
uint8_t ctrl82[32];
uint8_t ctrl81[57];
uint8_t ctrl80[49];
uint8_t _img[2][8][265];
};
} /* namespace */
#endif

View file

@ -0,0 +1,97 @@
/*
* Copyright (C) 2016 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _ardour_surfaces_m2encoder_h_
#define _ardour_surfaces_m2encoder_h_
#include <stdint.h>
#include "pbd/signals.h"
namespace ArdourSurface {
class M2EncoderInterface
{
public:
M2EncoderInterface () {}
virtual ~M2EncoderInterface () {}
/* user API */
PBD::Signal1<void, int> changed;
virtual float value () const { return 0.f; }
virtual float range () const { return 0.f; }
/* internal API - called from device thread */
virtual bool set_value (unsigned int v) { return false; }
};
class M2Encoder : public M2EncoderInterface
{
public:
M2Encoder (unsigned int upper = 1000)
: M2EncoderInterface ()
, _upper (upper /* limit, exclusive. eg [0..15]: 16 */)
, _value (0)
, _initialized (false)
{
assert (_upper > 7);
_wrapcnt = std::max (3U, upper / 6);
}
float value () const { return _value / (_upper - 1.f); }
float range () const { return (_upper - 1.f); }
bool set_value (unsigned int v) {
if (!_initialized) {
_initialized = true;
_value = v;
return false;
}
if (v == _value) {
return false;
}
int delta;
if (v < _wrapcnt && _value > _upper - _wrapcnt) {
// wrap around max -> min
delta = v + _upper - _value;
}
else if (_value < _wrapcnt && v > _upper - _wrapcnt) {
// wrap around min -> max
delta = v - _upper - _value;
}
else {
delta = v - _value;
}
_value = v;
changed (delta);
return true;
}
protected:
unsigned int _upper;
unsigned int _value;
unsigned int _wrapcnt;
bool _initialized;
};
} /* namespace */
#endif /* _ardour_surfaces_m2encoder_h_ */

View file

@ -0,0 +1,57 @@
/*
* Copyright (C) 2016 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "m2_map_mikro.h"
using namespace ArdourSurface;
M2MapMikro::M2MapMikro ()
: M2Contols ()
, enc_master (16)
{}
M2ButtonInterface*
M2MapMikro::button (PhysicalButtonId id, Modifier m)
{
return M2Contols::button (id, m);
}
M2ButtonInterface*
M2MapMikro::button (SemanticButtonId id)
{
return M2Contols::button (id);
}
M2EncoderInterface*
M2MapMikro::encoder (unsigned int id)
{
if (id == 0) {
return &enc_master;
}
// TODO map "nav" (select) and Left/Right to encoder(s) delta.
return M2Contols::encoder (id);
}
M2PadInterface*
M2MapMikro::pad (unsigned int id)
{
if (id < 16) {
return &pads[id];
}
return M2Contols::pad (id);
}

View file

@ -0,0 +1,42 @@
/*
* Copyright (C) 2016 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _ardour_surfaces_m2map_mikro_h_
#define _ardour_surfaces_m2map_mikro_h_
#include "m2controls.h"
namespace ArdourSurface {
class M2MapMikro : public M2Contols
{
public:
M2MapMikro ();
M2ButtonInterface* button (PhysicalButtonId id, Modifier m);
M2ButtonInterface* button (SemanticButtonId id);
M2EncoderInterface* encoder (unsigned int id);
M2PadInterface* pad (unsigned int id);
private:
M2Encoder enc_master;
M2Pad pads[16];
};
} /* namespace */
#endif

View file

@ -0,0 +1,108 @@
/*
* Copyright (C) 2016 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "m2_map_mk2.h"
using namespace ArdourSurface;
using namespace std;
M2MapMk2::M2MapMk2 ()
: M2Contols ()
, enc_master (16)
{
#define PSMAP(MOD, PHYS, SEM, BTN) \
pmap[MOD].insert (make_pair (PHYS, BTN)); \
smap.insert (make_pair (SEM, BTN));
#define PSMAPALL(PHYS, SEM, BTN) \
pmap[ModNone].insert (make_pair (PHYS, BTN)); \
pmap[ModShift].insert (make_pair (PHYS, BTN)); \
smap.insert (make_pair (SEM, BTN)); \
PSMAP(ModNone, BtnPlay, Play, &tr[0]);
PSMAP(ModShift, BtnPlay, Metronom, &tr[1]);
PSMAP(ModNone, BtnRec, Rec, &tr[2]);
PSMAP(ModNone, BtnGrid, Grid, &tr[3]);
PSMAP(ModNone, BtnRestart, GotoStart, &ts[0]);
PSMAP(ModShift, BtnRestart, Loop, &tr[4]);
PSMAP(ModNone, BtnStepLeft, FastRewind, &ts[1]);
PSMAP(ModNone, BtnStepRight, FastForward, &ts[2]);
PSMAP(ModShift, BtnStepLeft, JumpBackward, &ts[3]);
PSMAP(ModShift, BtnStepRight, JumpForward, &ts[4]);
PSMAPALL(BtnWheel, EncoderWheel, &mst[0]);
PSMAPALL(BtnVolume, MasterVolume, &mst[1]);
//PSMAPALL(BtnSwing, Master?????, &mst[2]);
PSMAPALL(BtnTempo, MasterTempo, &mst[3]);
PSMAP(ModShift, BtnAll, Save, &save);
PSMAP(ModShift, BtnNavLeft, Undo, &undoredo[0]);
PSMAP(ModShift, BtnNavRight, Redo, &undoredo[1]);
PSMAP(ModNone, BtnMute, Mute, &sm[0]);
PSMAP(ModShift, BtnMute, Panic, &panic);
PSMAPALL(BtnSolo, Solo, &sm[1]);
// TODO:
pmap[ModNone].insert (make_pair (BtnErase, &ts[5]));
pmap[ModShift].insert (make_pair (BtnErase, &ts[5]));
}
M2ButtonInterface*
M2MapMk2::button (PhysicalButtonId id, Modifier m)
{
PhysicalMap::const_iterator i = pmap[m].find (id);
if (i != pmap[m].end()) {
return i->second;
}
return M2Contols::button (id, m);
}
M2ButtonInterface*
M2MapMk2::button (SemanticButtonId id)
{
SematicMap::const_iterator i = smap.find (id);
if (i != smap.end()) {
return i->second;
}
return M2Contols::button (id);
}
M2EncoderInterface*
M2MapMk2::encoder (unsigned int id)
{
if (id == 0) {
return &enc_master;
}
else if (id < 9) {
return &enc_top[id - 1];
}
return M2Contols::encoder (id);
}
M2PadInterface*
M2MapMk2::pad (unsigned int id)
{
if (id < 16) {
return &pads[id];
}
return M2Contols::pad (id);
}

View file

@ -0,0 +1,58 @@
/*
* Copyright (C) 2016 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _ardour_surfaces_m2map_mk2_h_
#define _ardour_surfaces_m2map_mk2_h_
#include "m2controls.h"
namespace ArdourSurface {
class M2MapMk2 : public M2Contols
{
public:
M2MapMk2 ();
M2ButtonInterface* button (PhysicalButtonId id, Modifier m);
M2ButtonInterface* button (SemanticButtonId id);
M2EncoderInterface* encoder (unsigned int id);
M2PadInterface* pad (unsigned int id);
private:
PhysicalMap pmap[2]; // 2: Modifiers
SematicMap smap;
M2Button tr[5]; // transport controlables
M2StatelessButton ts[6]; // transport pushbuttons
M2Button mst[4]; // master "volume", "swing", "tempo", "encoder-push"
M2Button save;
M2Button undoredo[2];
M2Button sm[2]; // solo, mute
M2StatelessButton panic;
M2Encoder enc_master;
M2Encoder enc_top[8];
M2Pad pads[16];
};
} /* namespace */
#endif

View file

@ -0,0 +1,148 @@
/*
* Copyright (C) 2016 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _ardour_surfaces_m2pad_h_
#define _ardour_surfaces_m2pad_h_
#include <stdint.h>
#include "pbd/signals.h"
namespace ArdourSurface {
class M2PadInterface
{
public:
M2PadInterface () {}
virtual ~M2PadInterface () {}
/* user API */
PBD::Signal1<void, float> pressed;
PBD::Signal0<void> released;
PBD::Signal1<void, float> aftertouch;
PBD::Signal2<void, float, bool> event;
PBD::Signal1<void, float> changed;
virtual uint16_t value () const { return 0; }
virtual float pressure () const { return 0.f; }
virtual void set_color (uint32_t rgba) {}
/* internal API - called from device thread */
virtual void set_value (uint16_t v) {}
virtual void color (uint8_t& r, uint8_t& g, uint8_t& b) const {
r = g = b = 0;
}
};
class M2Pad : public M2PadInterface
{
public:
M2Pad ()
: M2PadInterface ()
, _pressed (false)
, _pressure (0)
, _last (0)
, _cnt (0)
, _rgba (0)
{
for (int i = 0; i < 4; ++i) {
hist[i] = 0;
}
}
uint16_t value () const { return _raw; }
float pressure () const { return _pressure; }
void set_color (uint32_t rgba) { _rgba = rgba; }
void color (uint8_t& r, uint8_t& g, uint8_t& b) const
{
r = ((_rgba >> 24) & 0xff) >> 1;
g = ((_rgba >> 16) & 0xff) >> 1;
b = ((_rgba >> 8) & 0xff) >> 1;
}
void set_value (uint16_t v)
{
// bleed to neighboring pads...
static const uint16_t high = 159;
static const float low = 159 / 4095.f;
static const float mindelta = 32.f / 4096.f;
if (_raw != v) {
changed (v / 4095.f);
_raw = v;
}
// some pads never return to "0", and there's
// TODO map pressure from a min..max range,
// even hard hits rarely exceed 3400 or thereabouts.
// -> "pad sensitivity" config or "calibrate pads"
hist[_cnt] = v;
_cnt = (_cnt + 1) & 3;
if (_pressed) {
const float p = v / 4095.f;
_pressure += .1 * (p - _pressure);
if (_pressure < low) {
_pressure = 0;
_pressed = false;
released (); /* EMIT SIGNAL */
event (_pressure, true); /* EMIT SIGNAL */
} else {
if (fabsf (_last - _pressure) > mindelta) {
_last = _pressure;
aftertouch (_pressure); /* EMIT SIGNAL */
event (_pressure, false); /* EMIT SIGNAL */
}
}
} else {
bool above_thresh = true;
uint16_t max = 0;
for (int i = 0; i < 4; ++i) {
if (hist[i] < high) {
above_thresh = false;
break;
}
max = std::max (max, hist[i]);
}
if (above_thresh) {
_pressed = true;
_last = _pressure = max / 4095.f;
pressed (_pressure);
event (_pressure, true); /* EMIT SIGNAL */
}
}
}
protected:
bool _pressed;
float _pressure;
uint16_t _raw;
float _last;
uint16_t hist[4];
unsigned int _cnt;
uint32_t _rgba;
};
} /* namespace */
#endif /* _ardour_surfaces_m2pad_h_ */

View file

@ -0,0 +1,169 @@
/*
* Copyright (C) 2016 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _ardour_surfaces_m2controls_h_
#define _ardour_surfaces_m2controls_h_
#include <map>
#include "m2_button.h"
#include "m2_encoder.h"
#include "m2_pad.h"
namespace ArdourSurface {
/** Abstraction for various variants:
* - NI Maschine Mikro
* - NI Maschine
* - NI Maschine Studio
*/
class M2Contols
{
public:
M2Contols () {}
virtual ~M2Contols () {}
typedef enum {
ModNone = 0,
ModShift,
} Modifier;
typedef enum {
/* Transport */
BtnRestart,
BtnStepLeft,
BtnStepRight,
BtnGrid,
BtnPlay,
BtnRec,
BtnErase,
BtnShift,
/* modes */
BtnScene,
BtnPattern,
BtnPadMode,
BtnNavigate, // aka. "view" on Mikro
BtnDuplicate,
BtnSelect,
BtnSolo,
BtnMute,
/* global */
#if 0
BtnArrange, // Studio only
BtnMix, // Studio only
#endif
BtnControl, // Studio: "Channel"
BtnStep, // Studio: "Plug-In"
BtnBrowse,
BtnSampling,
BtnSelLeft,
BtnSelRight,
BtnAll,
BtnAuto,
/* master */
BtnVolume,
BtnSwing,
BtnTempo,
BtnNavLeft,
BtnNavRight,
BtnEnter,
BtnNoteRepeat, // Tap
BtnWheel, // Encoder Push
/* Selectors above display */
BtnTop0, BtnTop1, BtnTop2, BtnTop3, // Mikro F1, F2, F3
BtnTop4, BtnTop5, BtnTop6, BtnTop7,
/* Maschine & Studio "Groups" */
BtnGroupA, BtnGroupB, BtnGroupC, BtnGroupD,
BtnGroupE, BtnGroupF, BtnGroupG, BtnGroupH,
#if 1 // Studio only -- Edit
BtnCopy,
BtnPaste,
BtnNote,
BtnNudge,
BtnUndo,
BtnRedo,
BtnQuantize,
BtnClear,
BtnIn1, BtnIn2, BtnIn3, BtnIn4,
BtnMst, BtnGrp, BtnSnd, BtnCue,
#endif
} PhysicalButtonId;
typedef enum {
Play,
Rec,
Loop,
Metronom,
GotoStart,
GotoEnd,
JumpBackward,
JumpForward,
FastRewind,
FastForward,
Grid,
Delete,
Undo, Redo,
Save,
EncoderWheel, // multi-purpose
MasterVolume,
MasterTempo,
Solo, Mute,
Panic
} SemanticButtonId;
typedef std::map <PhysicalButtonId, M2ButtonInterface*> PhysicalMap;
typedef std::map <SemanticButtonId, M2ButtonInterface*> SematicMap;
virtual M2ButtonInterface* button (PhysicalButtonId id, Modifier m) {
if (id == BtnShift) {
return &_shift;
}
return &_dummy_button;
}
virtual M2ButtonInterface* button (SemanticButtonId id) {
return &_dummy_button;
}
virtual M2EncoderInterface* encoder (unsigned int id) {
return &_dummy_encoder;
}
virtual M2PadInterface* pad (unsigned int id) {
return &_dummy_pad;
}
protected:
M2ButtonInterface _dummy_button;
M2EncoderInterface _dummy_encoder;
M2PadInterface _dummy_pad;
M2ToggleHoldButton _shift;
};
} /* namespace */
#endif /* _ardour_surfaces_m2controls_h_*/

View file

@ -0,0 +1,76 @@
/*
* Copyright (C) 2016 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _ardour_surfaces_maschine2hardware_h_
#define _ardour_surfaces_maschine2hardware_h_
#include <hidapi.h>
#include <cairomm/refptr.h>
#include <cairomm/surface.h>
#include "pbd/signals.h"
namespace ArdourSurface {
class M2Contols;
/** Abstraction for various variants:
* - NI Maschine Mikro
* - NI Maschine
* - NI Maschine Studio
*/
class M2Device
{
public:
M2Device ()
: _splashcnt (0)
, _blink_counter (0)
, _blink_shade (0.f)
{}
virtual ~M2Device () {}
virtual void clear (bool splash = false) {
if (splash) {
_splashcnt = 0;
} else {
_splashcnt = _splashtime;
}
_blink_counter = 0;
_blink_shade = 0.f;
}
virtual void read (hid_device*, M2Contols*) = 0;
virtual void write (hid_device*, M2Contols*) = 0;
virtual Cairo::RefPtr<Cairo::ImageSurface> surface () = 0;
PBD::Signal0<bool> vblank;
protected:
void bump_blink () {
_blink_counter = (_blink_counter + 1) % 12;
_blink_shade = fabsf (1.f - _blink_counter / 6.f);
}
uint32_t _splashcnt;
static const uint32_t _splashtime = 25 * 3;
unsigned int _blink_counter;
float _blink_shade;
};
} /* namespace */
#endif /* _ardour_surfaces_maschine2_h_*/

View file

@ -0,0 +1,288 @@
/*
* Copyright (C) 2016 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <unistd.h>
#include <pthread.h>
#include "pbd/compose.h"
#include "pbd/error.h"
#include "pbd/i18n.h"
#include "pbd/abstract_ui.cc" // instantiate template
#include "ardour/async_midi_port.h"
#include "ardour/audioengine.h"
#include "ardour/session.h"
#include "midi++/port.h"
#include "maschine2.h"
#include "m2_dev_mk2.h"
#include "m2_map_mk2.h"
#include "m2_dev_mikro.h"
#include "m2_map_mikro.h"
#include "canvas.h"
using namespace ARDOUR;
using namespace PBD;
using namespace ArdourSurface;
Maschine2::Maschine2 (ARDOUR::Session& s)
: ControlProtocol (s, string (X_("NI Maschine2")))
, AbstractUI<Maschine2Request> (name())
, _handle (0)
, _hw (0)
, _ctrl (0)
, _canvas (0)
, _maschine_type (Maschine)
, _master_state (MST_NONE)
{
if (hid_init()) {
throw Maschine2Exception ("HIDAPI initialization failed");
}
run_event_loop ();
}
Maschine2::~Maschine2 ()
{
stop ();
hid_exit ();
}
void*
Maschine2::request_factory (uint32_t num_requests)
{
return request_buffer_factory (num_requests);
}
void
Maschine2::do_request (Maschine2Request* req)
{
if (req->type == CallSlot) {
call_slot (MISSING_INVALIDATOR, req->the_slot);
} else if (req->type == Quit) {
stop ();
}
}
int
Maschine2::set_active (bool yn)
{
if (yn == active()) {
return 0;
}
if (yn) {
if (start ()) {
return -1;
}
} else {
if (stop ()) {
return -1;
}
}
ControlProtocol::set_active (yn);
return 0;
}
XMLNode&
Maschine2::get_state()
{
XMLNode& node (ControlProtocol::get_state());
return node;
}
int
Maschine2::set_state (const XMLNode & node, int version)
{
if (ControlProtocol::set_state (node, version)) {
return -1;
}
return 0;
}
int
Maschine2::start ()
{
_maschine_type = Maschine;
_handle = hid_open (0x17cc, 0x1140, NULL); // Maschine
#if 0
if (!_handle) {
if ((_handle = hid_open (0x17cc, 0x1300, NULL))) {
_maschine_type = Studio;
}
}
#endif
if (!_handle) {
if ((_handle = hid_open (0x17cc, 0x1110, NULL))) {
_maschine_type = Mikro;
}
}
if (!_handle) {
if ((_handle = hid_open (0x17cc, 0x1200, NULL))) {
_maschine_type = Mikro;
}
}
if (!_handle) {
error << _("Cannot find or connect to Maschine2\n");
return -1;
}
hid_set_nonblocking (_handle, 1);
_midi_out = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("Maschine2 out"), true);
if (!_midi_out) {
error << _("Cannot create Maschine2 PAD MIDI Port");
stop ();
return -1;
}
boost::dynamic_pointer_cast<AsyncMIDIPort>(_midi_out)->set_flush_at_cycle_start (true);
_output_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_midi_out).get();
switch (_maschine_type) {
case Mikro:
_hw = new Maschine2Mikro ();
_ctrl = new M2MapMikro ();
info << _("Maschine2 Mikro control surface intialized");
break;
case Maschine:
_hw = new Maschine2Mk2 ();
_ctrl = new M2MapMk2 ();
info << _("Maschine2 control surface intialized");
break;
case Studio:
error << _("Maschine2 Studio is not yet supported");
stop ();
return -1;
break;
}
_canvas = new Maschine2Canvas (*this, _hw);
connect_signals ();
Glib::RefPtr<Glib::TimeoutSource> write_timeout = Glib::TimeoutSource::create (40);
write_connection = write_timeout->connect (sigc::mem_fun (*this, &Maschine2::dev_write));
write_timeout->attach (main_loop()->get_context());
#ifdef PLATFORM_WINDOWS
Glib::RefPtr<Glib::TimeoutSource> read_timeout = Glib::TimeoutSource::create (20);
#else
Glib::RefPtr<Glib::TimeoutSource> read_timeout = Glib::TimeoutSource::create (1);
#endif
read_connection = read_timeout->connect (sigc::mem_fun (*this, &Maschine2::dev_read));
read_timeout->attach (main_loop ()->get_context());
return 0;
}
int
Maschine2::stop ()
{
read_connection.disconnect ();
write_connection.disconnect ();
session_connections.drop_connections ();
button_connections.drop_connections ();
if (_handle && _hw) {
_hw->clear ();
_hw->write (_handle, NULL);
}
hid_close (_handle);
_handle = 0;
stop_event_loop ();
if (_midi_out) {
AsyncMIDIPort* asp = dynamic_cast<AsyncMIDIPort*> (_output_port);
asp->drain (10000, 500000);
AudioEngine::instance()->unregister_port (_midi_out);
_midi_out.reset ((ARDOUR::Port*) 0);
_output_port = 0;
}
delete _canvas;
delete _hw;
delete _ctrl;
_canvas = 0;
_hw = 0;
_ctrl = 0;
return 0;
}
void
Maschine2::thread_init ()
{
pthread_set_name (event_loop_name().c_str());
ARDOUR::SessionEvent::create_per_thread_pool (event_loop_name(), 1024);
PBD::notify_event_loops_about_thread_creation (pthread_self(), event_loop_name(), 1024);
struct sched_param rtparam;
memset (&rtparam, 0, sizeof (rtparam));
rtparam.sched_priority = 9; /* XXX should be relative to audio (JACK) thread */
if (pthread_setschedparam (pthread_self(), SCHED_FIFO, &rtparam) != 0) {
// do we care? not particularly.
}
}
void
Maschine2::run_event_loop ()
{
BaseUI::run ();
}
void
Maschine2::stop_event_loop ()
{
BaseUI::quit ();
}
bool
Maschine2::dev_read ()
{
_hw->read (_handle, _ctrl);
return true;
}
bool
Maschine2::dev_write ()
{
_hw->write (_handle, _ctrl);
return true;
}
// move to callbacks.c || M2Contols implementation
Maschine2Layout*
Maschine2::current_layout() const
{
return NULL;
}

View file

@ -0,0 +1,161 @@
/*
* Copyright (C) 2016 Robin Gareus <robin@gareus.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _ardour_surfaces_maschine2_h_
#define _ardour_surfaces_maschine2_h_
#ifdef PLATFORM_WINDOWS
#include <windows.h>
#endif
#include <hidapi.h>
#define ABSTRACT_UI_EXPORTS
#include "pbd/abstract_ui.h"
#include "ardour/types.h"
#include "ardour/port.h"
#include "control_protocol/control_protocol.h"
namespace MIDI {
class Port;
}
namespace ArdourSurface {
class M2Contols;
class M2Device;
class Maschine2Canvas;
class Maschine2Layout;
class Maschine2Exception : public std::exception
{
public:
Maschine2Exception (const std::string& msg) : _msg (msg) { }
virtual ~Maschine2Exception () throw () {}
const char* what () const throw () { return _msg.c_str (); }
private:
std::string _msg;
};
struct Maschine2Request : public BaseUI::BaseRequestObject {
public:
Maschine2Request () {}
~Maschine2Request () {}
};
class Maschine2: public ARDOUR::ControlProtocol, public AbstractUI<Maschine2Request>
{
public:
Maschine2 (ARDOUR::Session&);
~Maschine2 ();
static void* request_factory (uint32_t);
#if 0
bool has_editor () const { return false; }
void* get_gui () const;
void tear_down_gui ();
#endif
int set_active (bool yn);
XMLNode& get_state ();
int set_state (const XMLNode & node, int version);
Maschine2Canvas* canvas () const { return _canvas; }
Maschine2Layout* current_layout() const;
typedef enum {
Mikro,
Maschine,
Studio
} Maschine2Type;
private:
void do_request (Maschine2Request*);
int start ();
int stop ();
void thread_init ();
void run_event_loop ();
void stop_event_loop ();
sigc::connection read_connection;
sigc::connection write_connection;
bool dev_write ();
bool dev_read ();
hid_device* _handle;
M2Device* _hw;
M2Contols* _ctrl;
Maschine2Canvas* _canvas;
Maschine2Type _maschine_type;
PBD::ScopedConnectionList session_connections;
PBD::ScopedConnectionList button_connections;
void connect_signals ();
void stripable_selection_changed () {}
/* Master Mode */
enum MasterMode {
MST_NONE,
MST_VOLUME,
MST_TEMPO
} _master_state;
void handle_master_change (enum MasterMode);
void notify_master_change ();
/* PAD Port */
boost::shared_ptr<ARDOUR::Port> _midi_out;
MIDI::Port* _output_port;
/* callbacks */
void notify_record_state_changed ();
void notify_transport_state_changed ();
void notify_loop_state_changed ();
void notify_parameter_changed (std::string);
void notify_snap_change ();
void notify_session_dirty_changed ();
void notify_history_changed ();
void button_play ();
void button_record ();
void button_loop ();
void button_metronom ();
void button_rewind ();
void button_action (const std::string&, const std::string&);
void button_snap_released ();
void button_snap_pressed ();
void button_snap_changed (bool);
void encoder_master (int);
void button_encoder ();
void pad_event (unsigned int, float, bool);
void pad_change (unsigned int, float);
};
} /* namespace */
#endif /* _ardour_surfaces_maschine2_h_*/

View file

@ -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()

View file

@ -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()