X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fsurfaces%2Fpush2%2Fpush2.cc;h=68c99dd6f7d5605fcfb46cdebf4b1ab648486195;hb=1aab360b7d944350dea1f5ef81394229adb07c1e;hp=ddefee3f5645503acf240cea603dba896d787e59;hpb=2afb7f1a309b3fc46f6f29dbfe8dc65da4b7d0e4;p=ardour.git diff --git a/libs/surfaces/push2/push2.cc b/libs/surfaces/push2/push2.cc index ddefee3f56..68c99dd6f7 100644 --- a/libs/surfaces/push2/push2.cc +++ b/libs/surfaces/push2/push2.cc @@ -1,42 +1,65 @@ /* - 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 -#include -#include +#include #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" + +#ifdef PLATFORM_WINDOWS +#define random() rand() +#endif using namespace ARDOUR; using namespace std; @@ -44,14 +67,8 @@ using namespace PBD; 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 @@ -59,46 +76,93 @@ Push2::Push2 (ARDOUR::Session& s) : ControlProtocol (s, string (X_("Ableton Push 2"))) , AbstractUI (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) + , in_range_select (false) { - 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 in; + vector 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 @@ -119,36 +183,86 @@ Push2::open () 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(_async_in).get(); _output_port = boost::dynamic_pointer_cast(_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(_async_in)->add_shadow_port (string_compose (_("%1 Pads"), X_("Push 2")), boost::bind (&Push2::pad_filter, this, _1, _2)); + boost::shared_ptr shadow_port = boost::dynamic_pointer_cast(_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 > +Push2::bundles () +{ + list > 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 (_output_port); + asp->drain (10000, 500000); + AudioEngine::instance()->unregister_port (_async_in); AudioEngine::instance()->unregister_port (_async_out); @@ -157,10 +271,20 @@ Push2::close () _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); @@ -168,31 +292,75 @@ Push2::close () 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 @@ -225,7 +393,6 @@ Push2::request_factory (uint32_t num_requests) 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); @@ -244,177 +411,34 @@ Push2::stop () 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; } @@ -464,9 +488,10 @@ Push2::set_active (bool yn) periodic_connection = periodic_timeout->connect (sigc::mem_fun (*this, &Push2::periodic)); periodic_timeout->attach (main_loop()->get_context()); - init_buttons (); + init_buttons (true); init_touch_strip (); - switch_bank (0); + set_pad_scale (_scale_root, _root_octave, _mode, _in_key); + splash (); } else { @@ -498,7 +523,6 @@ Push2::init_touch_strip () void Push2::write (const MidiByteArray& data) { - cerr << data << endl; /* immediate delivery */ _output_port->write (&data[0], data.size(), 0); } @@ -556,20 +580,75 @@ Push2::connect_to_parser () 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 (ev->value) { + /* any press cancels any pending long press timeouts */ + for (set::iterator x = buttons_down.begin(); x != buttons_down.end(); ++x) { + Button* bb = id_button_map[*x]; + bb->timeout_connection.disconnect (); + } + } + if (b != cc_button_map.end()) { - if (ev->value == 0) { - (this->*b->second->release_method)(); + + Button* button = b->second; + + if (ev->value) { + buttons_down.insert (button->id); + start_press_timeout (*button, button->id); + } else { + buttons_down.erase (button->id); + button->timeout_connection.disconnect (); + } + + + set::iterator c = consumed.find (button->id); + + if (c == consumed.end()) { + if (ev->value == 0) { + (this->*button->release_method)(); + } else { + (this->*button->press_method)(); + } } else { - (this->*b->second->press_method)(); + DEBUG_TRACE (DEBUG::Push2, "button was consumed, ignored\n"); + consumed.erase (c); } + } else { /* encoder/vpot */ @@ -582,33 +661,33 @@ Push2::handle_midi_controller_message (MIDI::Parser&, MIDI::EventTwoBytes* ev) switch (ev->controller_number) { case 71: - strip_vpot (0, delta); + _current_layout->strip_vpot (0, delta); break; case 72: - strip_vpot (1, delta); + _current_layout->strip_vpot (1, delta); break; case 73: - strip_vpot (2, delta); + _current_layout->strip_vpot (2, delta); break; case 74: - strip_vpot (3, delta); + _current_layout->strip_vpot (3, delta); break; case 75: - strip_vpot (4, delta); + _current_layout->strip_vpot (4, delta); break; case 76: - strip_vpot (5, delta); + _current_layout->strip_vpot (5, delta); break; case 77: - strip_vpot (6, delta); + _current_layout->strip_vpot (6, delta); break; case 78: - strip_vpot (7, delta); + _current_layout->strip_vpot (7, delta); break; /* left side pair */ case 14: - strip_vpot (8, delta); + other_vpot (8, delta); break; case 15: other_vpot (1, delta); @@ -623,32 +702,39 @@ Push2::handle_midi_controller_message (MIDI::Parser&, MIDI::EventTwoBytes* ev) } void -Push2::handle_midi_note_on_message (MIDI::Parser&, MIDI::EventTwoBytes* ev) +Push2::handle_midi_note_on_message (MIDI::Parser& parser, MIDI::EventTwoBytes* ev) { + // DEBUG_TRACE (DEBUG::Push2, string_compose ("Note On %1 (velocity %2)\n", (int) ev->note_number, (int) ev->velocity)); + + if (ev->velocity == 0) { + handle_midi_note_off_message (parser, ev); + return; + } + switch (ev->note_number) { case 0: - strip_vpot_touch (0, ev->velocity > 64); + _current_layout->strip_vpot_touch (0, ev->velocity > 64); break; case 1: - strip_vpot_touch (1, ev->velocity > 64); + _current_layout->strip_vpot_touch (1, ev->velocity > 64); break; case 2: - strip_vpot_touch (2, ev->velocity > 64); + _current_layout->strip_vpot_touch (2, ev->velocity > 64); break; case 3: - strip_vpot_touch (3, ev->velocity > 64); + _current_layout->strip_vpot_touch (3, ev->velocity > 64); break; case 4: - strip_vpot_touch (4, ev->velocity > 64); + _current_layout->strip_vpot_touch (4, ev->velocity > 64); break; case 5: - strip_vpot_touch (5, ev->velocity > 64); + _current_layout->strip_vpot_touch (5, ev->velocity > 64); break; case 6: - strip_vpot_touch (6, ev->velocity > 64); + _current_layout->strip_vpot_touch (6, ev->velocity > 64); break; case 7: - strip_vpot_touch (7, ev->velocity > 64); + _current_layout->strip_vpot_touch (7, ev->velocity > 64); break; /* left side */ @@ -672,196 +758,82 @@ Push2::handle_midi_note_on_message (MIDI::Parser&, MIDI::EventTwoBytes* ev) 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 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)); -void -Push2::handle_midi_pitchbend_message (MIDI::Parser&, MIDI::pitchbend_t pb) -{ - if (!session) { + 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; } - float speed; - /* range of +1 .. -1 */ - speed = ((int32_t) pb - 8192) / 8192.0; - /* convert to range of +3 .. -3 */ - session->request_transport_speed (speed * 3.0); + const Pad * const pad_pressed = pm->second; + + pair 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::build_maps () +Push2::handle_midi_pitchbend_message (MIDI::Parser&, MIDI::pitchbend_t pb) { - /* 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 @@ -1011,6 +983,11 @@ Push2::get_state() 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; } @@ -1041,227 +1018,705 @@ Push2::set_state (const XMLNode & node, int version) } } - return retval; -} + XMLProperty const* prop; -void -Push2::switch_bank (uint32_t base) -{ - if (!session) { - return; + if ((prop = node.property (X_("root"))) != 0) { + _scale_root = atoi (prop->value()); } - stripable_connections.drop_connections (); - - /* try to get the first stripable for the requested bank */ - - stripable[0] = session->get_nth_stripable (base+0); - - if (!stripable[0]) { - return; + if ((prop = node.property (X_("root_octave"))) != 0) { + _root_octave = atoi (prop->value()); } - /* at least one stripable in this bank */ - bank_start = base; - - 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); - - for (int n = 0; n < 8; ++n) { - if (!stripable[n]) { - continue; - } - - /* stripable goes away? refill the bank, starting at the same point */ - - stripable[n]->DropReferences.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&Push2::switch_bank, this, bank_start), this); - boost::shared_ptr sc = stripable[n]->solo_control(); - if (sc) { - sc->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&Push2::solo_change, this, n), this); - } - - boost::shared_ptr mc = stripable[n]->mute_control(); - if (mc) { - mc->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&Push2::mute_change, this, n), this); - } - - solo_change (n); - mute_change (n); + 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); } - /* master cannot be removed, so no need to connect to going-away signal */ - master = session->master_out (); + return retval; } void -Push2::solo_change (int n) +Push2::other_vpot (int n, int delta) { - ButtonID bid; - + boost::shared_ptr click_gain; switch (n) { case 0: - bid = Upper1; + /* tempo control */ break; case 1: - bid = Upper2; + /* metronome gain control */ + click_gain = session->click_gain(); + if (click_gain) { + boost::shared_ptr 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: - bid = Upper3; - break; - case 3: - bid = Upper4; - break; - case 4: - bid = Upper5; - break; - case 5: - bid = Upper6; + /* master gain control */ + if (master) { + boost::shared_ptr 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; - case 6: - bid = Upper7; + } +} + +void +Push2::other_vpot_touch (int n, bool touching) +{ + switch (n) { + case 0: break; - case 7: - bid = Upper8; + case 1: break; - default: - return; + case 2: + if (master) { + boost::shared_ptr ac = master->gain_control(); + if (ac) { + if (touching) { + ac->start_touch (session->audible_frame()); + } else { + ac->stop_touch (true, session->audible_frame()); + } + } + } } +} - boost::shared_ptr ac = stripable[n]->solo_control (); - if (!ac) { - return; +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()); } +} - Button* b = id_button_map[bid]; - if (ac->get_value()) { - b->set_color (LED::Red); +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, std::string name1, boost::weak_ptr, 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(_async_in)->name()); + string no = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr(_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 { - b->set_color (LED::Black); + DEBUG_TRACE (DEBUG::FaderPort, string_compose ("Connections between %1 and %2 changed, but I ignored it\n", name1, name2)); + /* not our ports */ + return false; } - b->set_state (LED::OneShot24th); - write (b->state_msg()); + + 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"); + } + + ConnectionChange (); /* emit signal for our GUI */ + + DEBUG_TRACE (DEBUG::FaderPort, "FaderPort::connection_handler end\n"); + + return true; /* connection status changed */ } void -Push2::mute_change (int n) +Push2::connected () { - ButtonID bid; + request_pressure_mode (); +} - 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; +boost::shared_ptr +Push2::output_port() +{ + return _async_out; +} + +boost::shared_ptr +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 (nni != nn_pad_map.end()) { + return nni->second->filtered; } - boost::shared_ptr ac = stripable[n]->mute_control (); - if (!ac) { + return 0; +} + +void +Push2::update_selection_color () +{ + boost::shared_ptr current_midi_track = current_pad_target.lock(); + + if (!current_midi_track) { return; } - Button* b = id_button_map[bid]; + 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()); + + reset_pad_colors (); +} + +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::iterator interval; + int note; + const int original_root = root; + + interval = m.steps.begin(); + root += (octave*12); + note = root; + + const int root_start = root; + + set mode_map; /* contains only notes in mode, O(logN) lookup */ + vector 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 (); + + if (inkey) { + + vector::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()); + } + } - if (ac->get_value ()) { - b->set_color (LED::Blue); } else { - b->set_color (LED::Black); + + /* 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()); + } + } + + /* 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 */ } - b->set_state (LED::OneShot24th); - write (b->state_msg()); } void -Push2::strip_vpot (int n, int delta) +Push2::set_percussive_mode (bool yn) { - if (stripable[n]) { - boost::shared_ptr ac = stripable[n]->gain_control(); - if (ac) { - ac->set_value (ac->get_value() + ((2.0/64.0) * delta), PBD::Controllable::UseGroup); + if (!yn) { + cerr << "back to scale\n"; + set_pad_scale (_scale_root, _root_octave, _mode, _in_key); + percussion = false; + return; + } + + 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::strip_vpot_touch (int n, bool touching) +Push2::stripable_selection_change (StripableNotificationListPtr selected) { - if (stripable[n]) { - boost::shared_ptr ac = stripable[n]->gain_control(); - if (ac) { - if (touching) { - ac->start_touch (session->audible_frame()); - } else { - ac->stop_touch (true, session->audible_frame()); - } + boost::shared_ptr pad_port = boost::dynamic_pointer_cast(_async_in)->shadow_port(); + boost::shared_ptr current_midi_track = current_pad_target.lock(); + boost::shared_ptr 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 ((*si).lock()); + + if (new_pad_target) { + break; } } + + if (current_midi_track == new_pad_target) { + /* nothing to do */ + return; + } + + if (!new_pad_target) { + /* leave existing connection alone */ + return; + } + + /* 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 { + current_pad_target.reset (); + selection_color = LED::Green; + contrast_color = LED::Green; + } + + 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::other_vpot (int n, int delta) +Push2::build_color_map () { - switch (n) { - case 0: - break; - case 1: - break; - case 2: - /* master gain control */ - if (master) { - boost::shared_ptr ac = master->gain_control(); - if (ac) { - ac->set_value (ac->get_value() + ((2.0/64.0) * delta), PBD::Controllable::UseGroup); - } + /* 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. + */ + + 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); + } +} + +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; + } + + return random(); +} + +void +Push2::set_current_layout (Push2Layout* layout) +{ + if (layout && layout == _current_layout) { + _current_layout->show (); + } else { + + if (_current_layout) { + _current_layout->hide (); + _canvas->root()->remove (_current_layout); + _previous_layout = _current_layout; } - break; + + _current_layout = layout; + + if (_current_layout) { + _canvas->root()->add (_current_layout); + _current_layout->show (); + } + + + _canvas->request_redraw (); } } void -Push2::other_vpot_touch (int n, bool touching) +Push2::use_previous_layout () { - switch (n) { - case 0: + 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 1: + case PolyPressure: + msg[7] = 0x1; break; - case 2: - if (master) { - boost::shared_ptr ac = master->gain_control(); - if (ac) { - if (touching) { - ac->start_touch (session->audible_frame()); - } else { - ac->stop_touch (true, session->audible_frame()); - } - } - } + default: + return; } + + write (msg); + cerr << "Sent PM message " << msg << endl; }