/*
- Copyright (C) 2016 Paul Davis
+ 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 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.
+ 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.
+ 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 <cairomm/context.h>
-#include <cairomm/surface.h>
-#include <pangomm/layout.h>
+#include <stdlib.h>
#include "pbd/compose.h"
#include "pbd/convert.h"
#include "pbd/debug.h"
#include "pbd/failed_constructor.h"
+#include "pbd/file_utils.h"
+#include "pbd/search_path.h"
+#include "pbd/enumwriter.h"
#include "midi++/parser.h"
#include "timecode/time.h"
#include "timecode/bbt_time.h"
+#include "ardour/amp.h"
#include "ardour/async_midi_port.h"
#include "ardour/audioengine.h"
#include "ardour/debug.h"
#include "ardour/midiport_manager.h"
+#include "ardour/midi_track.h"
+#include "ardour/midi_port.h"
#include "ardour/session.h"
#include "ardour/tempo.h"
+#include "gtkmm2ext/gui_thread.h"
+#include "gtkmm2ext/rgb_macros.h"
+
+#include "canvas/colors.h"
+
+#include "canvas.h"
+#include "gui.h"
+#include "layout.h"
+#include "menu.h"
+#include "mix.h"
#include "push2.h"
+#include "scale.h"
+#include "splash.h"
+#include "track_mix.h"
+
+#include "pbd/i18n.h"
using namespace ARDOUR;
using namespace std;
using namespace Glib;
using namespace ArdourSurface;
-#include "i18n.h"
-
#include "pbd/abstract_ui.cc" // instantiate template
-const int Push2::cols = 960;
-const int Push2::rows = 160;
-const int Push2::pixels_per_row = 1024;
-
#define ABLETON 0x2982
#define PUSH2 0x1967
+__attribute__((constructor)) static void
+register_enums ()
+{
+ EnumWriter& enum_writer (EnumWriter::instance());
+ vector<int> i;
+ vector<string> s;
+
+
+#define REGISTER(e) enum_writer.register_distinct (typeid(e).name(), i, s); i.clear(); s.clear()
+#define REGISTER_CLASS_ENUM(t,e) i.push_back (t::e); s.push_back (#e)
+
+}
+
Push2::Push2 (ARDOUR::Session& s)
: ControlProtocol (s, string (X_("Ableton Push 2")))
, AbstractUI<Push2Request> (name())
, handle (0)
- , device_buffer (0)
- , frame_buffer (Cairo::ImageSurface::create (Cairo::FORMAT_ARGB32, cols, rows))
- , modifier_state (None)
- , bank_start (0)
+ , _modifier_state (None)
+ , splash_start (0)
+ , _current_layout (0)
+ , _previous_layout (0)
+ , connection_state (ConnectionState (0))
+ , gui (0)
+ , _mode (MusicalMode::IonianMajor)
+ , _scale_root (0)
+ , _root_octave (3)
+ , _in_key (true)
+ , octave_shift (0)
+ , percussion (false)
+ , _pressure_mode (AfterTouch)
+ , selection_color (LED::Green)
+ , contrast_color (LED::Green)
{
- context = Cairo::Context::create (frame_buffer);
- tc_clock_layout = Pango::Layout::create (context);
- bbt_clock_layout = Pango::Layout::create (context);
- Pango::FontDescription fd ("Sans Bold 24");
- tc_clock_layout->set_font_description (fd);
- bbt_clock_layout->set_font_description (fd);
+ build_maps ();
+ build_color_map ();
+ fill_color_table ();
- Pango::FontDescription fd2 ("Sans 10");
- for (int n = 0; n < 8; ++n) {
- upper_layout[n] = Pango::Layout::create (context);
- upper_layout[n]->set_font_description (fd2);
- upper_layout[n]->set_text ("solo");
- lower_layout[n] = Pango::Layout::create (context);
- lower_layout[n]->set_font_description (fd2);
- lower_layout[n]->set_text ("mute");
- }
+ /* master cannot be removed, so no need to connect to going-away signal */
+ master = session->master_out ();
- Pango::FontDescription fd3 ("Sans Bold 10");
- for (int n = 0; n < 8; ++n) {
- mid_layout[n] = Pango::Layout::create (context);
- mid_layout[n]->set_font_description (fd3);
+ if (open ()) {
+ throw failed_constructor ();
}
- build_maps ();
+ ControlProtocol::StripableSelectionChanged.connect (selection_connection, MISSING_INVALIDATOR, boost::bind (&Push2::stripable_selection_change, this, _1), this);
- if (open ()) {
- throw failed_constructor ();
+ /* catch current selection, if any */
+ {
+ StripableNotificationListPtr sp (new StripableNotificationList (ControlProtocol::last_selected()));
+ stripable_selection_change (sp);
}
+ /* catch arrival and departure of Push2 itself */
+ ARDOUR::AudioEngine::instance()->PortRegisteredOrUnregistered.connect (port_reg_connection, MISSING_INVALIDATOR, boost::bind (&Push2::port_registration_handler, this), this);
+
+ /* Catch port connections and disconnections */
+ ARDOUR::AudioEngine::instance()->PortConnectedOrDisconnected.connect (port_connection, MISSING_INVALIDATOR, boost::bind (&Push2::connection_handler, this, _1, _2, _3, _4, _5), this);
+
+ /* ports might already be there */
+ port_registration_handler ();
}
Push2::~Push2 ()
{
- close ();
+ stop ();
+
+ delete track_mix_layout;
+ delete mix_layout;
+ delete scale_layout;
+}
+
+void
+Push2::port_registration_handler ()
+{
+ if (!_async_in && !_async_out) {
+ /* ports not registered yet */
+ return;
+ }
+
+ if (_async_in->connected() && _async_out->connected()) {
+ /* don't waste cycles here */
+ return;
+ }
+
+ string input_port_name = X_("Ableton Push 2 MIDI 1 in");
+ string output_port_name = X_("Ableton Push 2 MIDI 1 out");
+ vector<string> in;
+ vector<string> out;
+
+ AudioEngine::instance()->get_ports (string_compose (".*%1", input_port_name), DataType::MIDI, PortFlags (IsPhysical|IsOutput), in);
+ AudioEngine::instance()->get_ports (string_compose (".*%1", output_port_name), DataType::MIDI, PortFlags (IsPhysical|IsInput), out);
+
+ if (!in.empty() && !out.empty()) {
+ cerr << "Push2: both ports found\n";
+ cerr << "\tconnecting to " << in.front() << " + " << out.front() << endl;
+ if (!_async_in->connected()) {
+ AudioEngine::instance()->connect (_async_in->name(), in.front());
+ }
+ if (!_async_out->connected()) {
+ AudioEngine::instance()->connect (_async_out->name(), out.front());
+ }
+ }
}
int
return -1;
}
- device_frame_buffer = new uint16_t[rows*pixels_per_row];
-
- memset (device_frame_buffer, 0, sizeof (uint16_t) * rows * pixels_per_row);
-
- frame_header[0] = 0xef;
- frame_header[1] = 0xcd;
- frame_header[2] = 0xab;
- frame_header[3] = 0x89;
- memset (&frame_header[4], 0, 12);
+ try {
+ _canvas = new Push2Canvas (*this, 960, 160);
+ mix_layout = new MixLayout (*this, *session, "globalmix");
+ scale_layout = new ScaleLayout (*this, *session, "scale");
+ track_mix_layout = new TrackMixLayout (*this, *session, "trackmix");
+ splash_layout = new SplashLayout (*this, *session, "splash");
+ } catch (...) {
+ error << _("Cannot construct Canvas for display") << endmsg;
+ libusb_release_interface (handle, 0x00);
+ libusb_close (handle);
+ handle = 0;
+ return -1;
+ }
/* setup ports */
- _async_in = AudioEngine::instance()->register_input_port (DataType::MIDI, X_("push2 in"), true);
- _async_out = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("push2 out"), true);
+ _async_in = AudioEngine::instance()->register_input_port (DataType::MIDI, X_("Push 2 in"), true);
+ _async_out = AudioEngine::instance()->register_output_port (DataType::MIDI, X_("Push 2 out"), true);
if (_async_in == 0 || _async_out == 0) {
return -1;
}
+ /* We do not add our ports to the input/output bundles because we don't
+ * want users wiring them by hand. They could use JACK tools if they
+ * really insist on that.
+ */
+
_input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_in).get();
_output_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_out).get();
+ /* Create a shadow port where, depending on the state of the surface,
+ * we will make pad note on/off events appear. The surface code will
+ * automatically this port to the first selected MIDI track.
+ */
+
+ boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_in)->add_shadow_port (string_compose (_("%1 Pads"), X_("Push 2")), boost::bind (&Push2::pad_filter, this, _1, _2));
+ boost::shared_ptr<MidiPort> shadow_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_in)->shadow_port();
+
+ if (shadow_port) {
+
+ _output_bundle.reset (new ARDOUR::Bundle (_("Push 2 Pads"), false));
+
+ _output_bundle->add_channel (
+ shadow_port->name(),
+ ARDOUR::DataType::MIDI,
+ session->engine().make_port_name_non_relative (shadow_port->name())
+ );
+ }
+
+ session->BundleAddedOrRemoved ();
+
connect_to_parser ();
return 0;
}
+list<boost::shared_ptr<ARDOUR::Bundle> >
+Push2::bundles ()
+{
+ list<boost::shared_ptr<ARDOUR::Bundle> > b;
+
+ if (_output_bundle) {
+ b.push_back (_output_bundle);
+ }
+
+ return b;
+}
+
int
Push2::close ()
{
+ init_buttons (false);
+ strip_buttons_off ();
+
+ /* wait for button data to be flushed */
+ AsyncMIDIPort* asp;
+ asp = dynamic_cast<AsyncMIDIPort*> (_output_port);
+ asp->drain (10000, 500000);
+
AudioEngine::instance()->unregister_port (_async_in);
AudioEngine::instance()->unregister_port (_async_out);
_input_port = 0;
_output_port = 0;
- vblank_connection.disconnect ();
periodic_connection.disconnect ();
session_connections.drop_connections ();
- stripable_connections.drop_connections ();
+
+ if (_current_layout) {
+ _canvas->root()->remove (_current_layout);
+ _current_layout = 0;
+ }
+
+ delete mix_layout;
+ mix_layout = 0;
+ delete scale_layout;
+ scale_layout = 0;
+ delete splash_layout;
+ splash_layout = 0;
if (handle) {
libusb_release_interface (handle, 0x00);
handle = 0;
}
- for (int n = 0; n < 8; ++n) {
- stripable[n].reset ();
- }
+ return 0;
+}
+
+void
+Push2::strip_buttons_off ()
+{
+ ButtonID strip_buttons[] = { Upper1, Upper2, Upper3, Upper4, Upper5, Upper6, Upper7, Upper8,
+ Lower1, Lower2, Lower3, Lower4, Lower5, Lower6, Lower7, Lower8, };
- delete [] device_frame_buffer;
- device_frame_buffer = 0;
+ for (size_t n = 0; n < sizeof (strip_buttons) / sizeof (strip_buttons[0]); ++n) {
+ Button* b = id_button_map[strip_buttons[n]];
- return 0;
+ b->set_color (LED::Black);
+ b->set_state (LED::OneShot24th);
+ write (b->state_msg());
+ }
}
+
void
-Push2::init_buttons ()
+Push2::init_buttons (bool startup)
{
+ /* This is a list of buttons that we want lit because they do something
+ in ardour related (loosely, sometimes) to their illuminated label.
+ */
+
ButtonID buttons[] = { Mute, Solo, Master, Up, Right, Left, Down, Note, Session, Mix, AddTrack, Delete, Undo,
- Metronome, Shift, Select, Play, RecordEnable, Automate, Repeat, Note, Session, DoubleLoop,
- Quantize, Duplicate,
+ Metronome, Shift, Select, Play, RecordEnable, Automate, Repeat, Note, Session,
+ Quantize, Duplicate, Browse, PageRight, PageLeft, OctaveUp, OctaveDown, Layout, Scale
};
for (size_t n = 0; n < sizeof (buttons) / sizeof (buttons[0]); ++n) {
Button* b = id_button_map[buttons[n]];
- b->set_color (LED::White);
+ if (startup) {
+ b->set_color (LED::White);
+ } else {
+ b->set_color (LED::Black);
+ }
b->set_state (LED::OneShot24th);
write (b->state_msg());
}
+
+ if (startup) {
+
+ /* all other buttons are off (black) */
+
+ ButtonID off_buttons[] = { TapTempo, Setup, User, Stop, Convert, New, FixedLength,
+ Fwd32ndT, Fwd32nd, Fwd16thT, Fwd16th, Fwd8thT, Fwd8th, Fwd4trT, Fwd4tr,
+ Accent, Note, Session, };
+
+ for (size_t n = 0; n < sizeof (off_buttons) / sizeof (off_buttons[0]); ++n) {
+ Button* b = id_button_map[off_buttons[n]];
+
+ b->set_color (LED::Black);
+ b->set_state (LED::OneShot24th);
+ write (b->state_msg());
+ }
+ }
+
+ if (!startup) {
+ for (NNPadMap::iterator pi = nn_pad_map.begin(); pi != nn_pad_map.end(); ++pi) {
+ Pad* pad = pi->second;
+
+ pad->set_color (LED::Black);
+ pad->set_state (LED::OneShot24th);
+ write (pad->state_msg());
+ }
+ }
}
bool
void
Push2::do_request (Push2Request * req)
{
- DEBUG_TRACE (DEBUG::Push2, string_compose ("doing request type %1\n", req->type));
if (req->type == CallSlot) {
call_slot (MISSING_INVALIDATOR, req->the_slot);
return 0;
}
-/** render host-side frame buffer (a Cairo ImageSurface) to the current
- * device-side frame buffer. The device frame buffer will be pushed to the
- * device on the next call to vblank()
- */
-int
-Push2::bitblt_to_device_frame_buffer ()
+void
+Push2::splash ()
{
- /* ensure that all drawing has been done before we fetch pixel data */
-
- frame_buffer->flush ();
-
- const int stride = 3840; /* bytes per row for Cairo::FORMAT_ARGB32 */
- const uint8_t* data = frame_buffer->get_data ();
-
- /* fill frame buffer (320kB) */
-
- uint16_t* fb = (uint16_t*) device_frame_buffer;
-
- for (int row = 0; row < rows; ++row) {
-
- const uint8_t* dp = data + row * stride;
-
- for (int col = 0; col < cols; ++col) {
-
- /* fetch r, g, b (range 0..255). Ignore alpha */
-
- const int r = (*((const uint32_t*)dp) >> 16) & 0xff;
- const int g = (*((const uint32_t*)dp) >> 8) & 0xff;
- const int b = *((const uint32_t*)dp) & 0xff;
-
- /* convert to 5 bits, 6 bits, 5 bits, respectively */
- /* generate 16 bit BGB565 value */
-
- *fb++ = (r >> 3) | ((g & 0xfc) << 3) | ((b & 0xf8) << 8);
-
- dp += 4;
- }
-
- /* skip 128 bytes to next line. This is filler, used to avoid line borders occuring in the middle of 512
- byte USB buffers
- */
-
- fb += 64; /* 128 bytes = 64 int16_t */
- }
-
- return 0;
+ set_current_layout (splash_layout);
+ splash_start = get_microseconds ();
}
bool
-Push2::redraw ()
+Push2::vblank ()
{
- string tc_clock_text;
- string bbt_clock_text;
-
- if (session) {
- framepos_t audible = session->audible_frame();
- Timecode::Time TC;
- bool negative = false;
-
- if (audible < 0) {
- audible = -audible;
- negative = true;
- }
-
- session->timecode_time (audible, TC);
-
- TC.negative = TC.negative || negative;
-
- tc_clock_text = Timecode::timecode_format_time(TC);
-
- Timecode::BBT_Time bbt = session->tempo_map().bbt_at_frame (audible);
- char buf[16];
-
-#define BBT_BAR_CHAR "|"
-
- if (negative) {
- snprintf (buf, sizeof (buf), "-%03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32,
- bbt.bars, bbt.beats, bbt.ticks);
- } else {
- snprintf (buf, sizeof (buf), " %03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32,
- bbt.bars, bbt.beats, bbt.ticks);
- }
-
- bbt_clock_text = buf;
- }
-
- bool dirty = false;
-
- if (tc_clock_text != tc_clock_layout->get_text()) {
- dirty = true;
- tc_clock_layout->set_text (tc_clock_text);
- }
-
- if (bbt_clock_text != tc_clock_layout->get_text()) {
- dirty = true;
- bbt_clock_layout->set_text (bbt_clock_text);
- }
+ if (splash_start) {
- string mid_text;
+ /* display splash for 2 seconds */
- for (int n = 0; n < 8; ++n) {
- if (stripable[n]) {
- mid_text = short_version (stripable[n]->name(), 10);
- if (mid_text != mid_layout[n]->get_text()) {
- mid_layout[n]->set_text (mid_text);
- dirty = true;
- }
+ if (get_microseconds() - splash_start > 2000000) {
+ splash_start = 0;
+ DEBUG_TRACE (DEBUG::Push2, "splash interval ended, switch to mix layout\n");
+ set_current_layout (mix_layout);
}
}
- if (!dirty) {
- return false;
- }
-
- context->set_source_rgb (0.764, 0.882, 0.882);
- context->rectangle (0, 0, 960, 160);
- context->fill ();
- context->set_source_rgb (0.23, 0.0, 0.349);
- context->move_to (650, 25);
- tc_clock_layout->update_from_cairo_context (context);
- tc_clock_layout->show_in_cairo_context (context);
- context->move_to (650, 60);
- bbt_clock_layout->update_from_cairo_context (context);
- bbt_clock_layout->show_in_cairo_context (context);
-
- for (int n = 0; n < 8; ++n) {
- context->move_to (10 + (n*120), 2);
- upper_layout[n]->update_from_cairo_context (context);
- upper_layout[n]->show_in_cairo_context (context);
- }
-
- for (int n = 0; n < 8; ++n) {
- context->move_to (10 + (n*120), 140);
- lower_layout[n]->update_from_cairo_context (context);
- lower_layout[n]->show_in_cairo_context (context);
- }
-
- for (int n = 0; n < 8; ++n) {
- context->move_to (10 + (n*120), 120);
- context->set_source_rgb (0.0, 0.0, 0.0);
- mid_layout[n]->update_from_cairo_context (context);
- mid_layout[n]->show_in_cairo_context (context);
- }
-
- /* render clock */
- /* render foo */
- /* render bar */
-
- return true;
-}
-
-bool
-Push2::vblank ()
-{
- int transferred = 0;
- const int timeout_msecs = 1000;
- int err;
-
- if ((err = libusb_bulk_transfer (handle, 0x01, frame_header, sizeof (frame_header), &transferred, timeout_msecs))) {
- return false;
- }
-
- if (redraw()) {
- /* things changed */
- bitblt_to_device_frame_buffer ();
+ if (_current_layout) {
+ _current_layout->update_meters ();
+ _current_layout->update_clocks ();
}
- if ((err = libusb_bulk_transfer (handle, 0x01, (uint8_t*) device_frame_buffer , 2 * rows * pixels_per_row, &transferred, timeout_msecs))) {
- return false;
- }
+ _canvas->vblank();
return true;
}
periodic_connection = periodic_timeout->connect (sigc::mem_fun (*this, &Push2::periodic));
periodic_timeout->attach (main_loop()->get_context());
- init_buttons ();
- switch_bank (0);
+ init_buttons (true);
+ init_touch_strip ();
+ set_pad_scale (_scale_root, _root_octave, _mode, _in_key);
+ splash ();
} else {
return 0;
}
+void
+Push2::init_touch_strip ()
+{
+ MidiByteArray msg (9, 0xf0, 0x00, 0x21, 0x1d, 0x01, 0x01, 0x17, 0x00, 0xf7);
+ /* flags are the final byte (ignore end-of-sysex */
+
+ /* show bar, not point
+ autoreturn to center
+ bar starts at center
+ */
+ msg[7] = (1<<4) | (1<<5) | (1<<6);
+ write (msg);
+}
+
void
Push2::write (const MidiByteArray& data)
{
- cerr << data << endl;
/* immediate delivery */
_output_port->write (&data[0], data.size(), 0);
}
void
Push2::handle_midi_sysex (MIDI::Parser&, MIDI::byte* raw_bytes, size_t sz)
{
- cerr << "sysex, " << sz << " bytes\n";
+ DEBUG_TRACE (DEBUG::Push2, string_compose ("Sysex, %1 bytes\n", sz));
+
+ if (sz < 8) {
+ return;
+ }
+
+ MidiByteArray msg (sz, raw_bytes);
+ MidiByteArray push2_sysex_header (6, 0xF0, 0x00, 0x21, 0x1D, 0x01, 0x01);
+
+ if (!push2_sysex_header.compare_n (msg, 6)) {
+ return;
+ }
+
+ switch (msg[6]) {
+ case 0x1f: /* pressure mode */
+ if (msg[7] == 0x0) {
+ _pressure_mode = AfterTouch;
+ PressureModeChange (AfterTouch);
+ cerr << "Pressure mode is after\n";
+ } else {
+ _pressure_mode = PolyPressure;
+ PressureModeChange (PolyPressure);
+ cerr << "Pressure mode is poly\n";
+ }
+ break;
+ }
}
void
Push2::handle_midi_controller_message (MIDI::Parser&, MIDI::EventTwoBytes* ev)
{
+ DEBUG_TRACE (DEBUG::Push2, string_compose ("CC %1 (value %2)\n", (int) ev->controller_number, (int) ev->value));
+
CCButtonMap::iterator b = cc_button_map.find (ev->controller_number);
- if (b != cc_button_map.end()) {
- if (ev->value == 0) {
- (this->*b->second->release_method)();
- } else {
- (this->*b->second->press_method)();
+
+ if (ev->value) {
+ /* any press cancels any pending long press timeouts */
+ for (set<ButtonID>::iterator x = buttons_down.begin(); x != buttons_down.end(); ++x) {
+ Button* bb = id_button_map[*x];
+ bb->timeout_connection.disconnect ();
}
}
-}
-void
-Push2::handle_midi_note_on_message (MIDI::Parser&, MIDI::EventTwoBytes* ev)
-{
- cerr << "note on" << (int) ev->note_number << ", velocity " << (int) ev->velocity << endl;
-}
+ if (b != cc_button_map.end()) {
-void
-Push2::handle_midi_note_off_message (MIDI::Parser&, MIDI::EventTwoBytes* ev)
-{
- cerr << "note on" << (int) ev->note_number << ", velocity " << (int) ev->velocity << endl;
-}
+ Button* button = b->second;
-void
-Push2::handle_midi_pitchbend_message (MIDI::Parser&, MIDI::pitchbend_t pb)
-{
- cerr << "pitchbend @ " << pb << endl;
-}
+ if (ev->value) {
+ buttons_down.insert (button->id);
+ start_press_timeout (*button, button->id);
+ } else {
+ buttons_down.erase (button->id);
+ button->timeout_connection.disconnect ();
+ }
-void
-Push2::build_maps ()
-{
- /* Pads */
-
- Pad* pad;
-
-#define MAKE_PAD(x,y,nn) \
- pad = new Pad ((x), (y), (nn)); \
- nn_pad_map.insert (make_pair (pad->extra(), pad)); \
- coord_pad_map.insert (make_pair (pad->coord(), pad));
-
- MAKE_PAD (0, 1, 93);
- MAKE_PAD (0, 2, 94);
- MAKE_PAD (0, 3, 95);
- MAKE_PAD (0, 4, 96);
- MAKE_PAD (0, 5, 97);
- MAKE_PAD (0, 6, 98);
- MAKE_PAD (0, 7, 90);
- MAKE_PAD (1, 0, 84);
- MAKE_PAD (1, 1, 85);
- MAKE_PAD (1, 2, 86);
- MAKE_PAD (1, 3, 87);
- MAKE_PAD (1, 4, 88);
- MAKE_PAD (1, 5, 89);
- MAKE_PAD (1, 6, 90);
- MAKE_PAD (1, 7, 91);
- MAKE_PAD (2, 0, 76);
- MAKE_PAD (2, 1, 77);
- MAKE_PAD (2, 2, 78);
- MAKE_PAD (2, 3, 79);
- MAKE_PAD (2, 4, 80);
- MAKE_PAD (2, 5, 81);
- MAKE_PAD (2, 6, 82);
- MAKE_PAD (2, 7, 83);
- MAKE_PAD (3, 0, 68);
- MAKE_PAD (3, 1, 69);
- MAKE_PAD (3, 2, 70);
- MAKE_PAD (3, 3, 71);
- MAKE_PAD (3, 4, 72);
- MAKE_PAD (3, 5, 73);
- MAKE_PAD (3, 6, 74);
- MAKE_PAD (3, 7, 75);
- MAKE_PAD (4, 0, 60);
- MAKE_PAD (4, 1, 61);
- MAKE_PAD (4, 2, 62);
- MAKE_PAD (4, 3, 63);
- MAKE_PAD (4, 4, 64);
- MAKE_PAD (4, 5, 65);
- MAKE_PAD (4, 6, 66);
- MAKE_PAD (4, 7, 67);
- MAKE_PAD (5, 0, 52);
- MAKE_PAD (5, 1, 53);
- MAKE_PAD (5, 2, 54);
- MAKE_PAD (5, 3, 56);
- MAKE_PAD (5, 4, 56);
- MAKE_PAD (5, 5, 57);
- MAKE_PAD (5, 6, 58);
- MAKE_PAD (5, 7, 59);
- MAKE_PAD (6, 0, 44);
- MAKE_PAD (6, 1, 45);
- MAKE_PAD (6, 2, 46);
- MAKE_PAD (6, 3, 47);
- MAKE_PAD (6, 4, 48);
- MAKE_PAD (6, 5, 49);
- MAKE_PAD (6, 6, 50);
- MAKE_PAD (6, 7, 51);
- MAKE_PAD (7, 0, 36);
- MAKE_PAD (7, 1, 37);
- MAKE_PAD (7, 2, 38);
- MAKE_PAD (7, 3, 39);
- MAKE_PAD (7, 4, 40);
- MAKE_PAD (7, 5, 41);
- MAKE_PAD (7, 6, 42);
- MAKE_PAD (7, 7, 43);
-
- /* Now color buttons */
-
- Button *button;
-
-#define MAKE_COLOR_BUTTON(i,cc) \
- button = new ColorButton ((i), (cc)); \
- cc_button_map.insert (make_pair (button->controller_number(), button)); \
- id_button_map.insert (make_pair (button->id, button));
-#define MAKE_COLOR_BUTTON_PRESS(i,cc,p)\
- button = new ColorButton ((i), (cc), (p)); \
- cc_button_map.insert (make_pair (button->controller_number(), button)); \
- id_button_map.insert (make_pair (button->id, button))
-
- MAKE_COLOR_BUTTON_PRESS (Upper1, 102, &Push2::button_upper_1);
- MAKE_COLOR_BUTTON_PRESS (Upper2, 103, &Push2::button_upper_2);
- MAKE_COLOR_BUTTON_PRESS (Upper3, 104, &Push2::button_upper_3);
- MAKE_COLOR_BUTTON_PRESS (Upper4, 105, &Push2::button_upper_4);
- MAKE_COLOR_BUTTON_PRESS (Upper5, 106, &Push2::button_upper_5);
- MAKE_COLOR_BUTTON_PRESS (Upper6, 107, &Push2::button_upper_6);
- MAKE_COLOR_BUTTON_PRESS (Upper7, 108, &Push2::button_upper_7);
- MAKE_COLOR_BUTTON_PRESS (Upper8, 109, &Push2::button_upper_8);
- MAKE_COLOR_BUTTON_PRESS (Lower1, 20, &Push2::button_lower_1);
- MAKE_COLOR_BUTTON_PRESS (Lower2, 21, &Push2::button_lower_2);
- MAKE_COLOR_BUTTON_PRESS (Lower3, 22, &Push2::button_lower_3);
- MAKE_COLOR_BUTTON_PRESS (Lower4, 23, &Push2::button_lower_4);
- MAKE_COLOR_BUTTON_PRESS (Lower5, 24, &Push2::button_lower_5);
- MAKE_COLOR_BUTTON_PRESS (Lower6, 25, &Push2::button_lower_6);
- MAKE_COLOR_BUTTON_PRESS (Lower7, 26, &Push2::button_lower_7);
- MAKE_COLOR_BUTTON_PRESS (Lower8, 27, &Push2::button_lower_8);
- MAKE_COLOR_BUTTON (Master, 28);
- MAKE_COLOR_BUTTON (Mute, 60);
- MAKE_COLOR_BUTTON_PRESS (Solo, 61, &Push2::button_solo);
- MAKE_COLOR_BUTTON (Stop, 29);
- MAKE_COLOR_BUTTON_PRESS (Fwd32ndT, 43, &Push2::button_fwd32t);
- MAKE_COLOR_BUTTON_PRESS (Fwd32nd,42 , &Push2::button_fwd32);
- MAKE_COLOR_BUTTON_PRESS (Fwd16thT, 41, &Push2::button_fwd16t);
- MAKE_COLOR_BUTTON_PRESS (Fwd16th, 40, &Push2::button_fwd16);
- MAKE_COLOR_BUTTON_PRESS (Fwd8thT, 39 , &Push2::button_fwd8t);
- MAKE_COLOR_BUTTON_PRESS (Fwd8th, 38, &Push2::button_fwd8);
- MAKE_COLOR_BUTTON_PRESS (Fwd4trT, 37, &Push2::button_fwd4t);
- MAKE_COLOR_BUTTON_PRESS (Fwd4tr, 36, &Push2::button_fwd4);
- MAKE_COLOR_BUTTON (Automate, 89);
- MAKE_COLOR_BUTTON_PRESS (RecordEnable, 86, &Push2::button_recenable);
- MAKE_COLOR_BUTTON_PRESS (Play, 85, &Push2::button_play);
-
-#define MAKE_WHITE_BUTTON(i,cc)\
- button = new WhiteButton ((i), (cc)); \
- cc_button_map.insert (make_pair (button->controller_number(), button)); \
- id_button_map.insert (make_pair (button->id, button))
-#define MAKE_WHITE_BUTTON_PRESS(i,cc,p)\
- button = new WhiteButton ((i), (cc), (p)); \
- cc_button_map.insert (make_pair (button->controller_number(), button)); \
- id_button_map.insert (make_pair (button->id, button))
-#define MAKE_WHITE_BUTTON_PRESS_RELEASE(i,cc,p,r) \
- button = new WhiteButton ((i), (cc), (p), (r)); \
- cc_button_map.insert (make_pair (button->controller_number(), button)); \
- id_button_map.insert (make_pair (button->id, button))
-
- MAKE_WHITE_BUTTON (TapTempo, 3);
- MAKE_WHITE_BUTTON_PRESS (Metronome, 9, &Push2::button_metronome);
- MAKE_WHITE_BUTTON (Setup, 30);
- MAKE_WHITE_BUTTON (User, 59);
- MAKE_WHITE_BUTTON (Delete, 118);
- MAKE_WHITE_BUTTON (AddDevice, 52);
- MAKE_WHITE_BUTTON (Device, 110);
- MAKE_WHITE_BUTTON (Mix, 112);
- MAKE_WHITE_BUTTON_PRESS (Undo, 119, &Push2::button_undo);
- MAKE_WHITE_BUTTON (AddTrack, 53);
- MAKE_WHITE_BUTTON_PRESS (Browse, 111, &Push2::button_browse);
- MAKE_WHITE_BUTTON_PRESS (Clip, 113, &Push2::button_clip);
- MAKE_WHITE_BUTTON (Convert, 35);
- MAKE_WHITE_BUTTON (DoubleLoop, 117);
- MAKE_WHITE_BUTTON (Quantize, 116);
- MAKE_WHITE_BUTTON (Duplicate, 88);
- MAKE_WHITE_BUTTON_PRESS (New, 87, &Push2::button_new);
- MAKE_WHITE_BUTTON_PRESS (FixedLength, 90, &Push2::button_fixed_length);
- MAKE_WHITE_BUTTON_PRESS (Up, 46, &Push2::button_up);
- MAKE_WHITE_BUTTON_PRESS (Right, 45, &Push2::button_right);
- MAKE_WHITE_BUTTON_PRESS (Down, 47, &Push2::button_down);
- MAKE_WHITE_BUTTON_PRESS (Left, 44, &Push2::button_left);
- MAKE_WHITE_BUTTON_PRESS (Repeat, 56, &Push2::button_repeat);
- MAKE_WHITE_BUTTON (Accent, 57);
- MAKE_WHITE_BUTTON (Scale, 58);
- MAKE_WHITE_BUTTON (Layout, 31);
- MAKE_WHITE_BUTTON (Note, 50);
- MAKE_WHITE_BUTTON (Session, 51);
- MAKE_WHITE_BUTTON (Layout, 31);
- MAKE_WHITE_BUTTON (OctaveUp, 55);
- MAKE_WHITE_BUTTON (PageRight, 63);
- MAKE_WHITE_BUTTON (OctaveDown, 54);
- MAKE_WHITE_BUTTON (PageLeft, 62);
- MAKE_WHITE_BUTTON_PRESS_RELEASE (Shift, 49, &Push2::button_shift_press, &Push2::button_shift_release);
- MAKE_WHITE_BUTTON (Select, 48);
-}
-void
-Push2::thread_init ()
-{
- struct sched_param rtparam;
+ set<ButtonID>::iterator c = consumed.find (button->id);
- pthread_set_name (event_loop_name().c_str());
+ if (c == consumed.end()) {
+ if (ev->value == 0) {
+ (this->*button->release_method)();
+ } else {
+ (this->*button->press_method)();
+ }
+ } else {
+ DEBUG_TRACE (DEBUG::Push2, "button was consumed, ignored\n");
+ consumed.erase (c);
+ }
- PBD::notify_event_loops_about_thread_creation (pthread_self(), event_loop_name(), 2048);
- ARDOUR::SessionEvent::create_per_thread_pool (event_loop_name(), 128);
+ } else {
- memset (&rtparam, 0, sizeof (rtparam));
- rtparam.sched_priority = 9; /* XXX should be relative to audio (JACK) thread */
+ /* encoder/vpot */
- if (pthread_setschedparam (pthread_self(), SCHED_FIFO, &rtparam) != 0) {
- // do we care? not particularly.
- }
-}
+ int delta = ev->value;
-void
-Push2::connect_session_signals()
-{
- // receive routes added
- //session->RouteAdded.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&MackieControlProtocol::notify_routes_added, this, _1), this);
- // receive VCAs added
- //session->vca_manager().VCAAdded.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&Push2::notify_vca_added, this, _1), this);
+ if (delta > 63) {
+ delta = -(128 - delta);
+ }
- // receive record state toggled
- session->RecordStateChanged.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&Push2::notify_record_state_changed, this), this);
- // receive transport state changed
- session->TransportStateChange.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&Push2::notify_transport_state_changed, this), this);
- session->TransportLooped.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Push2::notify_loop_state_changed, this), this);
- // receive punch-in and punch-out
- Config->ParameterChanged.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&Push2::notify_parameter_changed, this, _1), this);
- session->config.ParameterChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Push2::notify_parameter_changed, this, _1), this);
- // receive rude solo changed
- session->SoloActive.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&Push2::notify_solo_active_changed, this, _1), this);
+ switch (ev->controller_number) {
+ case 71:
+ _current_layout->strip_vpot (0, delta);
+ break;
+ case 72:
+ _current_layout->strip_vpot (1, delta);
+ break;
+ case 73:
+ _current_layout->strip_vpot (2, delta);
+ break;
+ case 74:
+ _current_layout->strip_vpot (3, delta);
+ break;
+ case 75:
+ _current_layout->strip_vpot (4, delta);
+ break;
+ case 76:
+ _current_layout->strip_vpot (5, delta);
+ break;
+ case 77:
+ _current_layout->strip_vpot (6, delta);
+ break;
+ case 78:
+ _current_layout->strip_vpot (7, delta);
+ break;
+
+ /* left side pair */
+ case 14:
+ other_vpot (8, delta);
+ break;
+ case 15:
+ other_vpot (1, delta);
+ break;
+
+ /* right side */
+ case 79:
+ other_vpot (2, delta);
+ break;
+ }
+ }
}
void
-Push2::notify_record_state_changed ()
+Push2::handle_midi_note_on_message (MIDI::Parser& parser, MIDI::EventTwoBytes* ev)
{
- IDButtonMap::iterator b = id_button_map.find (RecordEnable);
+ // DEBUG_TRACE (DEBUG::Push2, string_compose ("Note On %1 (velocity %2)\n", (int) ev->note_number, (int) ev->velocity));
- if (b == id_button_map.end()) {
+ if (ev->velocity == 0) {
+ handle_midi_note_off_message (parser, ev);
return;
}
- switch (session->record_status ()) {
- case Session::Disabled:
- b->second->set_color (LED::White);
- b->second->set_state (LED::NoTransition);
+ switch (ev->note_number) {
+ case 0:
+ _current_layout->strip_vpot_touch (0, ev->velocity > 64);
break;
- case Session::Enabled:
- b->second->set_color (LED::Red);
- b->second->set_state (LED::Blinking4th);
+ case 1:
+ _current_layout->strip_vpot_touch (1, ev->velocity > 64);
break;
- case Session::Recording:
- b->second->set_color (LED::Red);
- b->second->set_state (LED::OneShot24th);
+ case 2:
+ _current_layout->strip_vpot_touch (2, ev->velocity > 64);
break;
- }
-
- write (b->second->state_msg());
-}
-
-void
-Push2::notify_transport_state_changed ()
+ case 3:
+ _current_layout->strip_vpot_touch (3, ev->velocity > 64);
+ break;
+ case 4:
+ _current_layout->strip_vpot_touch (4, ev->velocity > 64);
+ break;
+ case 5:
+ _current_layout->strip_vpot_touch (5, ev->velocity > 64);
+ break;
+ case 6:
+ _current_layout->strip_vpot_touch (6, ev->velocity > 64);
+ break;
+ case 7:
+ _current_layout->strip_vpot_touch (7, ev->velocity > 64);
+ break;
+
+ /* left side */
+ case 10:
+ other_vpot_touch (0, ev->velocity > 64);
+ break;
+ case 9:
+ other_vpot_touch (1, ev->velocity > 64);
+ break;
+
+ /* right side */
+ case 8:
+ other_vpot_touch (3, ev->velocity > 64);
+ break;
+
+ /* touch strip */
+ case 12:
+ if (ev->velocity < 64) {
+ transport_stop ();
+ }
+ break;
+ }
+
+ if (ev->note_number < 11) {
+ return;
+ }
+
+ /* Pad illuminations */
+
+ NNPadMap::const_iterator pm = nn_pad_map.find (ev->note_number);
+
+ if (pm == nn_pad_map.end()) {
+ return;
+ }
+
+ const Pad * const pad_pressed = pm->second;
+
+ pair<FNPadMap::iterator,FNPadMap::iterator> pads_with_note = fn_pad_map.equal_range (pad_pressed->filtered);
+
+ if (pads_with_note.first == fn_pad_map.end()) {
+ return;
+ }
+
+ for (FNPadMap::iterator pi = pads_with_note.first; pi != pads_with_note.second; ++pi) {
+ Pad* pad = pi->second;
+
+ pad->set_color (contrast_color);
+ pad->set_state (LED::OneShot24th);
+ write (pad->state_msg());
+ }
+}
+
+void
+Push2::handle_midi_note_off_message (MIDI::Parser&, MIDI::EventTwoBytes* ev)
+{
+ // DEBUG_TRACE (DEBUG::Push2, string_compose ("Note Off %1 (velocity %2)\n", (int) ev->note_number, (int) ev->velocity));
+
+ if (ev->note_number < 11) {
+ /* theoretically related to encoder touch start/end, but
+ * actually they send note on with two different velocity
+ * values (127 & 64).
+ */
+ return;
+ }
+
+ /* Pad illuminations */
+
+ NNPadMap::const_iterator pm = nn_pad_map.find (ev->note_number);
+
+ if (pm == nn_pad_map.end()) {
+ return;
+ }
+
+ const Pad * const pad_pressed = pm->second;
+
+ pair<FNPadMap::iterator,FNPadMap::iterator> pads_with_note = fn_pad_map.equal_range (pad_pressed->filtered);
+
+ if (pads_with_note.first == fn_pad_map.end()) {
+ return;
+ }
+
+ for (FNPadMap::iterator pi = pads_with_note.first; pi != pads_with_note.second; ++pi) {
+ Pad* pad = pi->second;
+
+ if (pad->do_when_pressed == Pad::FlashOn) {
+ pad->set_color (LED::Black);
+ pad->set_state (LED::OneShot24th);
+ write (pad->state_msg());
+ } else if (pad->do_when_pressed == Pad::FlashOff) {
+ pad->set_color (pad->perma_color);
+ pad->set_state (LED::OneShot24th);
+ write (pad->state_msg());
+ }
+ }
+}
+
+void
+Push2::handle_midi_pitchbend_message (MIDI::Parser&, MIDI::pitchbend_t pb)
+{
+}
+
+void
+Push2::thread_init ()
+{
+ struct sched_param rtparam;
+
+ pthread_set_name (event_loop_name().c_str());
+
+ PBD::notify_event_loops_about_thread_creation (pthread_self(), event_loop_name(), 2048);
+ ARDOUR::SessionEvent::create_per_thread_pool (event_loop_name(), 128);
+
+ memset (&rtparam, 0, sizeof (rtparam));
+ rtparam.sched_priority = 9; /* XXX should be relative to audio (JACK) thread */
+
+ if (pthread_setschedparam (pthread_self(), SCHED_FIFO, &rtparam) != 0) {
+ // do we care? not particularly.
+ }
+}
+
+void
+Push2::connect_session_signals()
+{
+ // receive routes added
+ //session->RouteAdded.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&MackieControlProtocol::notify_routes_added, this, _1), this);
+ // receive VCAs added
+ //session->vca_manager().VCAAdded.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&Push2::notify_vca_added, this, _1), this);
+
+ // receive record state toggled
+ session->RecordStateChanged.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&Push2::notify_record_state_changed, this), this);
+ // receive transport state changed
+ session->TransportStateChange.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&Push2::notify_transport_state_changed, this), this);
+ session->TransportLooped.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Push2::notify_loop_state_changed, this), this);
+ // receive punch-in and punch-out
+ Config->ParameterChanged.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&Push2::notify_parameter_changed, this, _1), this);
+ session->config.ParameterChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Push2::notify_parameter_changed, this, _1), this);
+ // receive rude solo changed
+ session->SoloActive.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&Push2::notify_solo_active_changed, this, _1), this);
+}
+
+void
+Push2::notify_record_state_changed ()
+{
+ IDButtonMap::iterator b = id_button_map.find (RecordEnable);
+
+ if (b == id_button_map.end()) {
+ return;
+ }
+
+ switch (session->record_status ()) {
+ case Session::Disabled:
+ b->second->set_color (LED::White);
+ b->second->set_state (LED::NoTransition);
+ break;
+ case Session::Enabled:
+ b->second->set_color (LED::Red);
+ b->second->set_state (LED::Blinking4th);
+ break;
+ case Session::Recording:
+ b->second->set_color (LED::Red);
+ b->second->set_state (LED::OneShot24th);
+ break;
+ }
+
+ write (b->second->state_msg());
+}
+
+void
+Push2::notify_transport_state_changed ()
{
Button* b = id_button_map[Play];
child->add_child_nocopy (_async_out->get_state());
node.add_child_nocopy (*child);
+ node.add_property (X_("root"), to_string (_scale_root, std::dec));
+ node.add_property (X_("root_octave"), to_string (_root_octave, std::dec));
+ node.add_property (X_("in_key"), _in_key ? X_("yes") : X_("no"));
+ node.add_property (X_("mode"), enum_2_string (_mode));
+
return node;
}
}
}
+ XMLProperty const* prop;
+
+ if ((prop = node.property (X_("root"))) != 0) {
+ _scale_root = atoi (prop->value());
+ }
+
+ if ((prop = node.property (X_("root_octave"))) != 0) {
+ _root_octave = atoi (prop->value());
+ }
+
+ if ((prop = node.property (X_("in_key"))) != 0) {
+ _in_key = string_is_affirmative (prop->value());
+ }
+
+ if ((prop = node.property (X_("mode"))) != 0) {
+ _mode = (MusicalMode::Type) string_2_enum (prop->value(), _mode);
+ }
+
return retval;
}
void
-Push2::switch_bank (uint32_t base)
+Push2::other_vpot (int n, int delta)
{
- if (!session) {
- return;
+ boost::shared_ptr<Amp> click_gain;
+ switch (n) {
+ case 0:
+ /* tempo control */
+ break;
+ case 1:
+ /* metronome gain control */
+ click_gain = session->click_gain();
+ if (click_gain) {
+ boost::shared_ptr<AutomationControl> ac = click_gain->gain_control();
+ if (ac) {
+ ac->set_value (ac->interface_to_internal (
+ min (ac->upper(), max (ac->lower(), ac->internal_to_interface (ac->get_value()) + (delta/256.0)))),
+ PBD::Controllable::UseGroup);
+ }
+ }
+ break;
+ case 2:
+ /* master gain control */
+ if (master) {
+ boost::shared_ptr<AutomationControl> ac = master->gain_control();
+ if (ac) {
+ ac->set_value (ac->interface_to_internal (
+ min (ac->upper(), max (ac->lower(), ac->internal_to_interface (ac->get_value()) + (delta/256.0)))),
+ PBD::Controllable::UseGroup);
+ }
+ }
+ break;
+ }
+}
+
+void
+Push2::other_vpot_touch (int n, bool touching)
+{
+ switch (n) {
+ case 0:
+ break;
+ case 1:
+ break;
+ case 2:
+ if (master) {
+ boost::shared_ptr<AutomationControl> ac = master->gain_control();
+ if (ac) {
+ if (touching) {
+ ac->start_touch (session->audible_frame());
+ } else {
+ ac->stop_touch (true, session->audible_frame());
+ }
+ }
+ }
+ }
+}
+
+void
+Push2::start_shift ()
+{
+ cerr << "start shift\n";
+ _modifier_state = ModifierState (_modifier_state | ModShift);
+ Button* b = id_button_map[Shift];
+ b->set_color (LED::White);
+ b->set_state (LED::Blinking16th);
+ write (b->state_msg());
+}
+
+void
+Push2::end_shift ()
+{
+ if (_modifier_state & ModShift) {
+ cerr << "end shift\n";
+ _modifier_state = ModifierState (_modifier_state & ~(ModShift));
+ Button* b = id_button_map[Shift];
+ b->timeout_connection.disconnect ();
+ b->set_color (LED::White);
+ b->set_state (LED::OneShot24th);
+ write (b->state_msg());
+ }
+}
+
+bool
+Push2::pad_filter (MidiBuffer& in, MidiBuffer& out) const
+{
+ /* This filter is called asynchronously from a realtime process
+ context. It must use atomics to check state, and must not block.
+ */
+
+ bool matched = false;
+
+ for (MidiBuffer::iterator ev = in.begin(); ev != in.end(); ++ev) {
+ if ((*ev).is_note_on() || (*ev).is_note_off()) {
+
+ /* encoder touch start/touch end use note
+ * 0-10. touchstrip uses note 12
+ */
+
+ if ((*ev).note() > 10 && (*ev).note() != 12) {
+
+ const int n = (*ev).note ();
+ NNPadMap::const_iterator nni = nn_pad_map.find (n);
+
+ if (nni != nn_pad_map.end()) {
+ Pad const * pad = nni->second;
+ /* shift for output to the shadow port */
+ if (pad->filtered >= 0) {
+ (*ev).set_note (pad->filtered + (octave_shift*12));
+ out.push_back (*ev);
+ /* shift back so that the pads light correctly */
+ (*ev).set_note (n);
+ } else {
+ /* no mapping, don't send event */
+ }
+ } else {
+ out.push_back (*ev);
+ }
+
+ matched = true;
+ }
+ } else if ((*ev).is_pitch_bender() || (*ev).is_poly_pressure() || (*ev).is_channel_pressure()) {
+ out.push_back (*ev);
+ }
+ }
+
+ return matched;
+}
+
+bool
+Push2::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn)
+{
+ DEBUG_TRACE (DEBUG::FaderPort, "FaderPort::connection_handler start\n");
+ if (!_input_port || !_output_port) {
+ return false;
+ }
+
+ string ni = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr<ARDOUR::Port>(_async_in)->name());
+ string no = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr<ARDOUR::Port>(_async_out)->name());
+
+ if (ni == name1 || ni == name2) {
+ if (yn) {
+ connection_state |= InputConnected;
+ } else {
+ connection_state &= ~InputConnected;
+ }
+ } else if (no == name1 || no == name2) {
+ if (yn) {
+ connection_state |= OutputConnected;
+ } else {
+ connection_state &= ~OutputConnected;
+ }
+ } else {
+ DEBUG_TRACE (DEBUG::FaderPort, string_compose ("Connections between %1 and %2 changed, but I ignored it\n", name1, name2));
+ /* not our ports */
+ return false;
+ }
+
+ if ((connection_state & (InputConnected|OutputConnected)) == (InputConnected|OutputConnected)) {
+
+ /* XXX this is a horrible hack. Without a short sleep here,
+ something prevents the device wakeup messages from being
+ sent and/or the responses from being received.
+ */
+
+ g_usleep (100000);
+ DEBUG_TRACE (DEBUG::FaderPort, "device now connected for both input and output\n");
+ connected ();
+
+ } else {
+ DEBUG_TRACE (DEBUG::FaderPort, "Device disconnected (input or output or both) or not yet fully connected\n");
}
- stripable_connections.drop_connections ();
+ ConnectionChange (); /* emit signal for our GUI */
- /* try to get the first stripable for the requested bank */
+ DEBUG_TRACE (DEBUG::FaderPort, "FaderPort::connection_handler end\n");
- stripable[0] = session->get_nth_stripable (base+0);
+ return true; /* connection status changed */
+}
+
+void
+Push2::connected ()
+{
+ request_pressure_mode ();
+}
+
+boost::shared_ptr<Port>
+Push2::output_port()
+{
+ return _async_out;
+}
+
+boost::shared_ptr<Port>
+Push2::input_port()
+{
+ return _async_in;
+}
+
+int
+Push2::pad_note (int row, int col) const
+{
+ NNPadMap::const_iterator nni = nn_pad_map.find (36+(row*8)+col);
- if (!stripable[0]) {
+ if (nni != nn_pad_map.end()) {
+ return nni->second->filtered;
+ }
+
+ return 0;
+}
+
+void
+Push2::update_selection_color ()
+{
+ boost::shared_ptr<MidiTrack> current_midi_track = current_pad_target.lock();
+
+ if (!current_midi_track) {
return;
}
- /* at least one stripable in this bank */
- bank_start = base;
+ selection_color = get_color_index (current_midi_track->presentation_info().color());
+ contrast_color = get_color_index (ArdourCanvas::HSV (current_midi_track->presentation_info().color()).opposite().color());
- stripable[1] = session->get_nth_stripable (base+1);
- stripable[2] = session->get_nth_stripable (base+2);
- stripable[3] = session->get_nth_stripable (base+3);
- stripable[4] = session->get_nth_stripable (base+4);
- stripable[5] = session->get_nth_stripable (base+5);
- stripable[6] = session->get_nth_stripable (base+6);
- stripable[7] = session->get_nth_stripable (base+7);
+ reset_pad_colors ();
+}
- for (int n = 0; n < 8; ++n) {
- if (!stripable[n]) {
- continue;
+void
+Push2::reset_pad_colors ()
+{
+ set_pad_scale (_scale_root, _root_octave, _mode, _in_key);
+}
+
+void
+Push2::set_pad_scale (int root, int octave, MusicalMode::Type mode, bool inkey)
+{
+ MusicalMode m (mode);
+ vector<float>::iterator interval;
+ int note;
+ const int original_root = root;
+
+ interval = m.steps.begin();
+ root += (octave*12);
+ note = root;
+
+ const int root_start = root;
+
+ set<int> mode_map; /* contains only notes in mode, O(logN) lookup */
+ vector<int> mode_vector; /* sorted in note order */
+
+ mode_map.insert (note);
+ mode_vector.push_back (note);
+
+ /* build a map of all notes in the mode, from the root to 127 */
+
+ while (note < 128) {
+
+ if (interval == m.steps.end()) {
+
+ /* last distance was the end of the scale,
+ so wrap, adding the next note at one
+ octave above the last root.
+ */
+
+ interval = m.steps.begin();
+ root += 12;
+ mode_map.insert (root);
+ mode_vector.push_back (root);
+
+ } else {
+ note = (int) floor (root + (2.0 * (*interval)));
+ interval++;
+ mode_map.insert (note);
+ mode_vector.push_back (note);
}
+ }
+
+ fn_pad_map.clear ();
- /* stripable goes away? refill the bank, starting at the same point */
+ if (inkey) {
- stripable[n]->DropReferences.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&Push2::switch_bank, this, bank_start), this);
- boost::shared_ptr<AutomationControl> sc = stripable[n]->solo_control();
- if (sc) {
- sc->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&Push2::solo_change, this, n), this);
+ vector<int>::iterator notei;
+ int row_offset = 0;
+
+ for (int row = 0; row < 8; ++row) {
+
+ /* Ableton's grid layout wraps the available notes in the scale
+ * by offsetting 3 notes per row (from the bottom)
+ */
+
+ notei = mode_vector.begin();
+ notei += row_offset;
+ row_offset += 3;
+
+ for (int col = 0; col < 8; ++col) {
+ int index = 36 + (row*8) + col;
+ Pad* pad = nn_pad_map[index];
+ int notenum;
+ if (notei != mode_vector.end()) {
+
+ notenum = *notei;
+ pad->filtered = notenum;
+
+ fn_pad_map.insert (make_pair (notenum, pad));
+
+ if ((notenum % 12) == original_root) {
+ pad->set_color (selection_color);
+ pad->perma_color = selection_color;
+ } else {
+ pad->set_color (LED::White);
+ pad->perma_color = LED::White;
+ }
+
+ pad->do_when_pressed = Pad::FlashOff;
+ notei++;
+
+ } else {
+
+ pad->set_color (LED::Black);
+ pad->do_when_pressed = Pad::Nothing;
+ pad->filtered = -1;
+ }
+
+ pad->set_state (LED::OneShot24th);
+ write (pad->state_msg());
+ }
}
- boost::shared_ptr<AutomationControl> mc = stripable[n]->mute_control();
- if (mc) {
- mc->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&Push2::mute_change, this, n), this);
+ } else {
+
+ /* chromatic: all notes available, but highlight those in the scale */
+
+ for (note = 36; note < 100; ++note) {
+
+ Pad* pad = nn_pad_map[note];
+
+ /* Chromatic: all pads play, half-tone steps. Light
+ * those in the scale, and highlight root notes
+ */
+
+ pad->filtered = root_start + (note - 36);
+
+ fn_pad_map.insert (make_pair (pad->filtered, pad));
+
+ if (mode_map.find (note) != mode_map.end()) {
+
+ if ((note % 12) == original_root) {
+ pad->set_color (selection_color);
+ pad->perma_color = selection_color;
+ } else {
+ pad->set_color (LED::White);
+ pad->perma_color = LED::White;
+ }
+
+ pad->do_when_pressed = Pad::FlashOff;
+
+ } else {
+
+ /* note is not in mode, turn it off */
+
+ pad->do_when_pressed = Pad::FlashOn;
+ pad->set_color (LED::Black);
+
+ }
+
+ pad->set_state (LED::OneShot24th);
+ write (pad->state_msg());
}
+ }
- solo_change (n);
- mute_change (n);
+ /* store state */
+ bool changed = false;
+
+ if (_scale_root != original_root) {
+ _scale_root = original_root;
+ changed = true;
+ }
+ if (_root_octave != octave) {
+ _root_octave = octave;
+ changed = true;
+ }
+ if (_in_key != inkey) {
+ _in_key = inkey;
+ changed = true;
+ }
+ if (_mode != mode) {
+ _mode = mode;
+ changed = true;
+ }
+
+ if (changed) {
+ ScaleChange (); /* EMIT SIGNAL */
}
}
void
-Push2::solo_change (int n)
+Push2::set_percussive_mode (bool yn)
{
- ButtonID bid;
+ if (!yn) {
+ cerr << "back to scale\n";
+ set_pad_scale (_scale_root, _root_octave, _mode, _in_key);
+ percussion = false;
+ return;
+ }
- switch (n) {
- case 0:
- bid = Upper1;
- break;
- case 1:
- bid = Upper2;
- break;
- case 2:
- bid = Upper3;
- break;
- case 3:
- bid = Upper4;
- break;
- case 4:
- bid = Upper5;
- break;
- case 5:
- bid = Upper6;
- break;
- case 6:
- bid = Upper7;
- break;
- case 7:
- bid = Upper8;
- break;
- default:
+ int drum_note = 36;
+
+ fn_pad_map.clear ();
+
+ for (int row = 0; row < 8; ++row) {
+
+ for (int col = 0; col < 4; ++col) {
+
+ int index = 36 + (row*8) + col;
+ Pad* pad = nn_pad_map[index];
+
+ pad->filtered = drum_note;
+ drum_note++;
+ }
+ }
+
+ for (int row = 0; row < 8; ++row) {
+
+ for (int col = 4; col < 8; ++col) {
+
+ int index = 36 + (row*8) + col;
+ Pad* pad = nn_pad_map[index];
+
+ pad->filtered = drum_note;
+ drum_note++;
+ }
+ }
+
+ percussion = true;
+}
+
+Push2Layout*
+Push2::current_layout () const
+{
+ Glib::Threads::Mutex::Lock lm (layout_lock);
+ return _current_layout;
+}
+
+void
+Push2::stripable_selection_change (StripableNotificationListPtr selected)
+{
+ boost::shared_ptr<MidiPort> pad_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(_async_in)->shadow_port();
+ boost::shared_ptr<MidiTrack> current_midi_track = current_pad_target.lock();
+ boost::shared_ptr<MidiTrack> new_pad_target;
+
+ /* See if there's a MIDI track selected */
+
+ for (StripableNotificationList::iterator si = selected->begin(); si != selected->end(); ++si) {
+
+ new_pad_target = boost::dynamic_pointer_cast<MidiTrack> ((*si).lock());
+
+ if (new_pad_target) {
+ break;
+ }
+ }
+
+ if (current_midi_track == new_pad_target) {
+ /* nothing to do */
return;
}
- boost::shared_ptr<AutomationControl> ac = stripable[n]->solo_control ();
- if (!ac) {
+ if (!new_pad_target) {
+ /* leave existing connection alone */
return;
}
- Button* b = id_button_map[bid];
- if (ac->get_value()) {
- b->set_color (LED::Red);
+ /* disconnect from pad port, if appropriate */
+
+ if (current_midi_track && pad_port) {
+
+ /* XXX this could possibly leave dangling MIDI notes.
+ *
+ * A general libardour fix is required. It isn't obvious
+ * how note resolution can be done unless disconnecting
+ * becomes "slow" (i.e. deferred for as long as it takes
+ * to resolve notes).
+ */
+ current_midi_track->input()->disconnect (current_midi_track->input()->nth(0), pad_port->name(), this);
+ }
+
+ /* now connect the pad port to this (newly) selected midi
+ * track, if indeed there is one.
+ */
+
+ if (new_pad_target && pad_port) {
+ new_pad_target->input()->connect (new_pad_target->input()->nth (0), pad_port->name(), this);
+ current_pad_target = new_pad_target;
+ selection_color = get_color_index (new_pad_target->presentation_info().color());
+ contrast_color = get_color_index (ArdourCanvas::HSV (new_pad_target->presentation_info().color()).opposite().color());
} else {
- b->set_color (LED::Black);
+ current_pad_target.reset ();
+ selection_color = LED::Green;
+ contrast_color = LED::Green;
}
- b->set_state (LED::OneShot24th);
- write (b->state_msg());
+
+ reset_pad_colors ();
+}
+
+Push2::Button*
+Push2::button_by_id (ButtonID bid)
+{
+ return id_button_map[bid];
+}
+
+uint8_t
+Push2::get_color_index (ArdourCanvas::Color rgba)
+{
+ ColorMap::iterator i = color_map.find (rgba);
+
+ if (i != color_map.end()) {
+ return i->second;
+ }
+
+ double dr, dg, db, da;
+ int r, g, b;
+ ArdourCanvas::color_to_rgba (rgba, dr, dg, db, da);
+ int w = 126; /* not sure where/when we should get this value */
+
+
+ r = (int) floor (255.0 * dr);
+ g = (int) floor (255.0 * dg);
+ b = (int) floor (255.0 * db);
+
+ /* get a free index */
+
+ uint8_t index;
+
+ if (color_map_free_list.empty()) {
+ /* random replacement of any entry above zero and below 122 (where the
+ * Ableton standard colors live)
+ */
+ index = 1 + (random() % 121);
+ } else {
+ index = color_map_free_list.top();
+ color_map_free_list.pop();
+ }
+
+ MidiByteArray palette_msg (17,
+ 0xf0,
+ 0x00 , 0x21, 0x1d, 0x01, 0x01, 0x03, /* reset palette header */
+ 0x00, /* index = 7 */
+ 0x00, 0x00, /* r = 8 & 9 */
+ 0x00, 0x00, /* g = 10 & 11 */
+ 0x00, 0x00, /* b = 12 & 13 */
+ 0x00, 0x00, /* w (a?) = 14 & 15*/
+ 0xf7);
+ palette_msg[7] = index;
+ palette_msg[8] = r & 0x7f;
+ palette_msg[9] = (r & 0x80) >> 7;
+ palette_msg[10] = g & 0x7f;
+ palette_msg[11] = (g & 0x80) >> 7;
+ palette_msg[12] = b & 0x7f;
+ palette_msg[13] = (b & 0x80) >> 7;
+ palette_msg[14] = w & 0x7f;
+ palette_msg[15] = w & 0x80;
+
+ write (palette_msg);
+
+ MidiByteArray update_pallette_msg (8, 0xf0, 0x00, 0x21, 0x1d, 0x01, 0x01, 0x05, 0xF7);
+ write (update_pallette_msg);
+
+ color_map[rgba] = index;
+
+ return index;
}
void
-Push2::mute_change (int n)
+Push2::build_color_map ()
{
- ButtonID bid;
+ /* These are "standard" colors that Ableton docs suggest will always be
+ there. Put them in our color map so that when we look up these
+ colors, we will use the Ableton indices for them.
+ */
- switch (n) {
- case 0:
- bid = Lower1;
- break;
- case 1:
- bid = Lower2;
- break;
- case 2:
- bid = Lower3;
- break;
- case 3:
- bid = Lower4;
- break;
- case 4:
- bid = Lower5;
- break;
- case 5:
- bid = Lower6;
- break;
- case 6:
- bid = Lower7;
- break;
- case 7:
- bid = Lower8;
- break;
- default:
- return;
+ color_map.insert (make_pair (RGB_TO_UINT (0,0,0), 0));
+ color_map.insert (make_pair (RGB_TO_UINT (204,204,204), 122));
+ color_map.insert (make_pair (RGB_TO_UINT (64,64,64), 123));
+ color_map.insert (make_pair (RGB_TO_UINT (20,20,20), 124));
+ color_map.insert (make_pair (RGB_TO_UINT (0,0,255), 125));
+ color_map.insert (make_pair (RGB_TO_UINT (0,255,0), 126));
+ color_map.insert (make_pair (RGB_TO_UINT (255,0,0), 127));
+
+ for (uint8_t n = 1; n < 122; ++n) {
+ color_map_free_list.push (n);
}
+}
- boost::shared_ptr<AutomationControl> ac = stripable[n]->mute_control ();
- if (!ac) {
- return;
+void
+Push2::fill_color_table ()
+{
+ colors.insert (make_pair (DarkBackground, ArdourCanvas::rgba_to_color (0, 0, 0, 1)));
+ colors.insert (make_pair (LightBackground, ArdourCanvas::rgba_to_color (0.98, 0.98, 0.98, 1)));
+
+ colors.insert (make_pair (ParameterName, ArdourCanvas::rgba_to_color (0.98, 0.98, 0.98, 1)));
+
+ colors.insert (make_pair (KnobArcBackground, ArdourCanvas::rgba_to_color (0.3, 0.3, 0.3, 1.0)));
+ colors.insert (make_pair (KnobArcStart, ArdourCanvas::rgba_to_color (1.0, 0.0, 0.0, 1.0)));
+ colors.insert (make_pair (KnobArcEnd, ArdourCanvas::rgba_to_color (0.0, 1.0, 0.0, 1.0)));
+
+ colors.insert (make_pair (KnobLineShadow, ArdourCanvas::rgba_to_color (0, 0, 0, 0.3)));
+ colors.insert (make_pair (KnobLine, ArdourCanvas::rgba_to_color (1, 1, 1, 1)));
+
+ colors.insert (make_pair (KnobForeground, ArdourCanvas::rgba_to_color (0.2, 0.2, 0.2, 1)));
+ colors.insert (make_pair (KnobBackground, ArdourCanvas::rgba_to_color (0.2, 0.2, 0.2, 1)));
+ colors.insert (make_pair (KnobShadow, ArdourCanvas::rgba_to_color (0, 0, 0, 0.1)));
+ colors.insert (make_pair (KnobBorder, ArdourCanvas::rgba_to_color (0, 0, 0, 1)));
+
+}
+
+ArdourCanvas::Color
+Push2::get_color (ColorName name)
+{
+ Colors::iterator c = colors.find (name);
+ if (c != colors.end()) {
+ return c->second;
}
- Button* b = id_button_map[bid];
+ return random();
+}
- if (ac->get_value ()) {
- b->set_color (LED::Blue);
+void
+Push2::set_current_layout (Push2Layout* layout)
+{
+ if (layout && layout == _current_layout) {
+ _current_layout->show ();
} else {
- b->set_color (LED::Black);
+
+ if (_current_layout) {
+ _current_layout->hide ();
+ _canvas->root()->remove (_current_layout);
+ _previous_layout = _current_layout;
+ }
+
+ _current_layout = layout;
+
+ if (_current_layout) {
+ _canvas->root()->add (_current_layout);
+ _current_layout->show ();
+ }
+
+
+ _canvas->request_redraw ();
}
- b->set_state (LED::OneShot24th);
- write (b->state_msg());
+}
+
+void
+Push2::use_previous_layout ()
+{
+ if (_previous_layout) {
+ set_current_layout (_previous_layout);
+ }
+}
+
+void
+Push2::request_pressure_mode ()
+{
+ MidiByteArray msg (8, 0xF0, 0x00, 0x21, 0x1D, 0x01, 0x01, 0x1F, 0xF7);
+ write (msg);
+}
+
+void
+Push2::set_pressure_mode (PressureMode pm)
+{
+ MidiByteArray msg (9, 0xF0, 0x00, 0x21, 0x1D, 0x01, 0x01, 0x1E, 0x0, 0xF7);
+
+ switch (pm) {
+ case AfterTouch:
+ /* nothing to do, message is correct */
+ break;
+ case PolyPressure:
+ msg[7] = 0x1;
+ break;
+ default:
+ return;
+ }
+
+ write (msg);
+ cerr << "Sent PM message " << msg << endl;
}