X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fsurfaces%2Fmackie%2Fsurface.cc;h=0cc0f02a77fb8ab18dba52181d5ba46056e7b9fc;hb=40ef762db4c830b482aa7830c30dca4f32b753c4;hp=971934c2d9b5441c7f700791e3e6bcf88e78b08f;hpb=12357ec4bb3a3219cb75664fdded988febee9447;p=ardour.git diff --git a/libs/surfaces/mackie/surface.cc b/libs/surfaces/mackie/surface.cc index 971934c2d9..0cc0f02a77 100644 --- a/libs/surfaces/mackie/surface.cc +++ b/libs/surfaces/mackie/surface.cc @@ -7,24 +7,25 @@ #include "midi++/port.h" #include "midi++/manager.h" +#include "ardour/automation_control.h" #include "ardour/debug.h" #include "ardour/route.h" #include "ardour/panner.h" #include "ardour/panner_shell.h" #include "ardour/rc_configuration.h" +#include "ardour/session.h" +#include "ardour/utils.h" #include "control_group.h" #include "surface_port.h" #include "surface.h" #include "strip.h" -#include "mackie_midi_builder.h" #include "mackie_control_protocol.h" -#include "mackie_jog_wheel.h" +#include "jog_wheel.h" #include "strip.h" #include "button.h" #include "led.h" -#include "ledring.h" #include "pot.h" #include "fader.h" #include "jog.h" @@ -38,55 +39,54 @@ using namespace Mackie; using ARDOUR::Route; using ARDOUR::Panner; using ARDOUR::Pannable; -using ARDOUR::PannerShell; +using ARDOUR::AutomationControl; -// The MCU sysex header -static MidiByteArray mackie_sysex_hdr (5, MIDI::sysex, 0x0, 0x0, 0x66, 0x10); +#define ui_context() MackieControlProtocol::instance() /* a UICallback-derived object that specifies the event loop for signal handling */ -// The MCU extender sysex header -static MidiByteArray mackie_sysex_hdr_xt (5, MIDI::sysex, 0x0, 0x0, 0x66, 0x11); +// The MCU sysex header.4th byte Will be overwritten +// when we get an incoming sysex that identifies +// the device type +static MidiByteArray mackie_sysex_hdr (5, MIDI::sysex, 0x0, 0x0, 0x66, 0x14); + +// The MCU extender sysex header.4th byte Will be overwritten +// when we get an incoming sysex that identifies +// the device type +static MidiByteArray mackie_sysex_hdr_xt (5, MIDI::sysex, 0x0, 0x0, 0x66, 0x15); static MidiByteArray empty_midi_byte_array; -Surface::Surface (MackieControlProtocol& mcp, jack_client_t* jack, const std::string& device_name, uint32_t number, surface_type_t stype) +Surface::Surface (MackieControlProtocol& mcp, const std::string& device_name, uint32_t number, surface_type_t stype) : _mcp (mcp) , _stype (stype) , _number (number) + , _name (device_name) , _active (false) , _connected (false) , _jog_wheel (0) { DEBUG_TRACE (DEBUG::MackieControl, "Surface::init\n"); - MIDI::Manager * mm = MIDI::Manager::instance(); - MIDI::Port * input = mm->add_port (new MIDI::Port (string_compose (_("%1 in"), device_name), MIDI::Port::IsInput, jack)); - MIDI::Port * output = mm->add_port (new MIDI::Port (string_compose (_("%1 out"), device_name), MIDI::Port::IsOutput, jack)); + _port = new SurfacePort (*this); - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("surface has ports named %1 and %2\n", - input->name(), output->name())); + /* only the first Surface object has global controls */ - _port = new SurfacePort (*this, *input, *output); - _port->open(); - _port->inactive_event.connect_same_thread (*this, boost::bind (&Surface::handle_port_inactive, this, _port)); + if (_number == 0) { + if (_mcp.device_info().has_global_controls()) { + init_controls (); + } - switch (stype) { - case mcu: - init_controls (); - _jog_wheel = new Mackie::JogWheel (_mcp); - break; - default: - break; + if (_mcp.device_info().has_master_fader()) { + setup_master (); + } } - switch (stype) { - case mcu: - case ext: - strips.resize (8); - init_strips (); - break; - default: - break; + uint32_t n = _mcp.device_info().strip_cnt(); + + if (n) { + init_strips (n); } + + connect_to_signals (); DEBUG_TRACE (DEBUG::MackieControl, "Surface::init finish\n"); } @@ -123,80 +123,15 @@ Surface::sysex_hdr() const } static GlobalControlDefinition mackie_global_controls[] = { - { "jog", 0x3c, Jog::factory, "none" }, - { "external", 0x2e, Pot::factory, "none" }, - { "io", 0x28, Button::factory, "assignment" }, - { "sends", 0x29, Button::factory, "assignment" }, - { "pan", 0x2a, Button::factory, "assignment" }, - { "plugin", 0x2b, Button::factory, "assignment" }, - { "eq", 0x2c, Button::factory, "assignment" }, - { "dyn", 0x2d, Button::factory, "assignment" }, - { "left", 0x2e, Button::factory, "bank" }, - { "right", 0x2f, Button::factory, "bank" }, - { "channel_left", 0x30, Button::factory, "bank" }, - { "channel_right", 0x31, Button::factory, "bank" }, - { "flip", 0x32, Button::factory, "none" }, - { "edit", 0x33, Button::factory, "none" }, - { "name_value", 0x34, Button::factory, "display" }, - { "timecode_beats", 0x35, Button::factory, "display" }, - { "F1", 0x36, Button::factory, "none" }, - { "F2", 0x37, Button::factory, "none" }, - { "F3", 0x38, Button::factory, "none" }, - { "F4", 0x39, Button::factory, "none" }, - { "F5", 0x3a, Button::factory, "none" }, - { "F6", 0x3b, Button::factory, "none" }, - { "F7", 0x3c, Button::factory, "none" }, - { "F8", 0x3d, Button::factory, "none" }, - { "F9", 0x3e, Button::factory, "none" }, - { "F10", 0x3f, Button::factory, "none" }, - { "F11", 0x40, Button::factory, "none" }, - { "F12", 0x41, Button::factory, "none" }, - { "F13", 0x42, Button::factory, "none" }, - { "F14", 0x43, Button::factory, "none" }, - { "F15", 0x44, Button::factory, "none" }, - { "F16", 0x45, Button::factory, "none" }, - { "shift", 0x46, Button::factory, "modifiers" }, - { "option", 0x47, Button::factory, "modifiers" }, - { "control", 0x48, Button::factory, "modifiers" }, - { "cmd_alt", 0x49, Button::factory, "modifiers" }, - { "on", 0x4a, Button::factory, "automation" }, - { "rec_ready", 0x4b, Button::factory, "automation" }, - { "undo", 0x4c, Button::factory, "functions" }, - { "snapshot", 0x4d, Button::factory, "automation" }, - { "touch", 0x4e, Button::factory, "automation" }, - { "redo", 0x4f, Button::factory, "functions" }, - { "marker", 0x50, Button::factory, "functions" }, - { "enter", 0x51, Button::factory, "functions" }, - { "cancel", 0x52, Button::factory, "functions" }, - { "mixer", 0x53, Button::factory, "functions" }, - { "frm_left", 0x54, Button::factory, "transport" }, - { "frm_right", 0x55, Button::factory, "transport" }, - { "loop", 0x56, Button::factory, "transport" }, - { "punch_in", 0x57, Button::factory, "transport" }, - { "punch_out", 0x58, Button::factory, "transport" }, - { "home", 0x59, Button::factory, "transport" }, - { "end", 0x5a, Button::factory, "transport" }, - { "rewind", 0x5b, Button::factory, "transport" }, - { "ffwd", 0x5c, Button::factory, "transport" }, - { "stop", 0x5d, Button::factory, "transport" }, - { "play", 0x5e, Button::factory, "transport" }, - { "record", 0x5f, Button::factory, "transport" }, - { "cursor_up", 0x60, Button::factory, "cursor" }, - { "cursor_down", 0x61, Button::factory, "cursor" }, - { "cursor_left", 0x62, Button::factory, "cursor" }, - { "cursor_right", 0x63, Button::factory, "cursor" }, - { "zoom", 0x64, Button::factory, "none" }, - { "scrub", 0x65, Button::factory, "none" }, - { "user_a", 0x66, Button::factory, "user" }, - { "user_b", 0x67, Button::factory, "user" }, - { "fader_touch", 0x70, Led::factory, "master" }, - { "timecode", 0x71, Led::factory, "none" }, - { "beats", 0x72, Led::factory, "none" }, - { "solo", 0x73, Led::factory, "none" }, - { "relay_click", 0x73, Led::factory, "none" }, - { "", 0, Button::factory, "" } + { "external", Pot::External, Pot::factory, "none" }, + { "fader_touch", Led::FaderTouch, Led::factory, "master" }, + { "timecode", Led::Timecode, Led::factory, "none" }, + { "beats", Led::Beats, Led::factory, "none" }, + { "solo", Led::RudeSolo, Led::factory, "none" }, + { "relay_click", Led::RelayClick, Led::factory, "none" }, + { "", 0, Led::factory, "" } }; - + void Surface::init_controls() { @@ -213,58 +148,80 @@ Surface::init_controls() groups["transport"] = new Group ("transport"); groups["user"] = new Group ("user"); groups["master"] = new Group ("master"); + groups["view"] = new Group ("view"); + + if (_mcp.device_info().has_jog_wheel()) { + _jog_wheel = new Mackie::JogWheel (_mcp); + } for (uint32_t n = 0; mackie_global_controls[n].name[0]; ++n) { group = groups[mackie_global_controls[n].group_name]; Control* control = mackie_global_controls[n].factory (*this, mackie_global_controls[n].id, mackie_global_controls[n].name, *group); - controls_by_name[mackie_global_controls[n].name] = control; - group->add (*control); - } -} - -static StripControlDefinition mackie_strip_controls[] = { - { "gain", Control::fader_base_id, Fader::factory, }, - { "vpot", Control::pot_base_id, Pot::factory, }, - { "recenable", Control::recenable_button_base_id, Button::factory, }, - { "solo", Control::solo_button_base_id, Button::factory, }, - { "mute", Control::mute_button_base_id, Button::factory, }, - { "select", Control::select_button_base_id, Button::factory, }, - { "vselect", Control::vselect_button_base_id, Button::factory, }, - { "fader_touch", Control::fader_touch_button_base_id, Button::factory, }, - { "meter", 0, Meter::factory, }, - { "", 0, Button::factory, } -}; + controls_by_device_independent_id[mackie_global_controls[n].id] = control; + } + + /* add global buttons */ + + const map& global_buttons (_mcp.device_info().global_buttons()); + + for (map::const_iterator b = global_buttons.begin(); b != global_buttons.end(); ++b){ + group = groups[b->second.group]; + controls_by_device_independent_id[b->first] = Button::factory (*this, b->first, b->second.id, b->second.label, *group); + } +} void -Surface::init_strips () +Surface::init_strips (uint32_t n) { - for (uint32_t i = 0; i < 8; ++i) { + const map& strip_buttons (_mcp.device_info().strip_buttons()); + + for (uint32_t i = 0; i < n; ++i) { char name[32]; snprintf (name, sizeof (name), "strip_%d", (8* _number) + i); - cerr << "Register strip " << i << endl; - - Strip* strip = new Strip (*this, name, i, mackie_strip_controls); + Strip* strip = new Strip (*this, name, i, strip_buttons); groups[name] = strip; - strips[i] = strip; + strips.push_back (strip); } } -void -Surface::display_timecode (const std::string & timecode, const std::string & timecode_last) +void +Surface::setup_master () { - if (has_timecode_display()) { - _port->write (builder.timecode_display (*this, timecode, timecode_last)); + _master_fader = dynamic_cast (Fader::factory (*this, 8, "master", *groups["master"])); + + boost::shared_ptr m; + + if ((m = _mcp.get_session().monitor_out()) == 0) { + m = _mcp.get_session().master_out(); + } + + if (!m) { + return; } + + _master_fader->set_control (m->gain_control()); + m->gain_control()->Changed.connect (*this, MISSING_INVALIDATOR, boost::bind (&Surface::master_gain_changed, this), ui_context()); +} + +void +Surface::master_gain_changed () +{ + boost::shared_ptr ac = _master_fader->control(); + float pos = ac->internal_to_interface (ac->get_value()); + _port->write (_master_fader->set_position (pos)); } float -Surface::scaled_delta (const ControlState & state, float current_speed) +Surface::scaled_delta (float delta, float current_speed) { - return state.sign * (std::pow (float(state.ticks + 1), 2) + current_speed) / 100.0; + /* XXX needs work before use */ + const float sign = delta < 0.0 ? -1.0 : 1.0; + + return ((sign * std::pow (delta + 1.0, 2.0)) + current_speed) / 100.0; } void @@ -272,29 +229,26 @@ Surface::display_bank_start (uint32_t current_bank) { if (current_bank == 0) { // send Ar. to 2-char display on the master - _port->write (builder.two_char_display ("Ar", "..")); + show_two_char_display ("Ar", ".."); } else { // write the current first remote_id to the 2-char display - _port->write (builder.two_char_display (current_bank)); + show_two_char_display (current_bank); } } void Surface::blank_jog_ring () { - Control* control = controls_by_name["jog"]; + Control* control = controls_by_device_independent_id[Jog::ID]; if (control) { - _port->write (builder.build_led_ring (*(dynamic_cast (control)), off)); + Pot* pot = dynamic_cast (control); + if (pot) { + _port->write (pot->set (0.0, false, Pot::spread)); + } } } -bool -Surface::has_timecode_display () const -{ - return false; -} - float Surface::scrub_scaling_factor () const { @@ -306,15 +260,20 @@ Surface::connect_to_signals () { if (!_connected) { + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("Surface %1 connecting to signals on port %2\n", number(), _port->input_port().name())); MIDI::Parser* p = _port->input_port().parser(); + /* Incoming sysex */ + p->sysex.connect_same_thread (*this, boost::bind (&Surface::handle_midi_sysex, this, _1, _2, _3)); /* V-Pot messages are Controller */ p->controller.connect_same_thread (*this, boost::bind (&Surface::handle_midi_controller_message, this, _1, _2)); /* Button messages are NoteOn */ p->note_on.connect_same_thread (*this, boost::bind (&Surface::handle_midi_note_on_message, this, _1, _2)); + /* Button messages are NoteOn. libmidi++ sends note-on w/velocity = 0 as note-off so catch them too */ + p->note_off.connect_same_thread (*this, boost::bind (&Surface::handle_midi_note_on_message, this, _1, _2)); /* Fader messages are Pitchbend */ p->channel_pitchbend[0].connect_same_thread (*this, boost::bind (&Surface::handle_midi_pitchbend_message, this, _1, _2, 0U)); p->channel_pitchbend[1].connect_same_thread (*this, boost::bind (&Surface::handle_midi_pitchbend_message, this, _1, _2, 1U)); @@ -329,18 +288,37 @@ Surface::connect_to_signals () } } - void Surface::handle_midi_pitchbend_message (MIDI::Parser&, MIDI::pitchbend_t pb, uint32_t fader_id) { + /* Pitchbend messages are fader messages. Nothing in the data we get + * from the MIDI::Parser conveys the fader ID, which was given by the + * channel ID in the status byte. + * + * Instead, we have used bind() to supply the fader-within-strip ID + * when we connected to the per-channel pitchbend events. + */ + + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("handle_midi pitchbend on port %3, fader = %1 value = %2\n", fader_id, pb, _number)); - Control* control = faders[fader_id]; + if (_mcp.device_info().no_handshake()) { + turn_it_on (); + } - if (control) { - float midi_pos = pb >> 4; // only the top 10 bytes are used - handle_control_event (*control, midi_pos / 1023.0); + Fader* fader = faders[fader_id]; + + if (fader) { + Strip* strip = dynamic_cast (&fader->group()); + float pos = (pb >> 4)/1023.0; // only the top 10 bytes are used + if (strip) { + strip->handle_fader (*fader, pos); + } else { + /* master fader */ + fader->set_value (pos); // alter master gain + _port->write (fader->set_position (pos)); // write back value (required for servo) + } } else { DEBUG_TRACE (DEBUG::MackieControl, "fader not found\n"); } @@ -349,146 +327,199 @@ Surface::handle_midi_pitchbend_message (MIDI::Parser&, MIDI::pitchbend_t pb, uin void Surface::handle_midi_note_on_message (MIDI::Parser &, MIDI::EventTwoBytes* ev) { - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("SurfacePort::handle_note_on %1 = %2\n", ev->note_number, ev->velocity)); + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("SurfacePort::handle_note_on %1 = %2\n", (int) ev->note_number, (int) ev->velocity)); + + if (_mcp.device_info().no_handshake()) { + turn_it_on (); + } - Control* control = buttons[ev->note_number]; + Button* button = buttons[ev->note_number]; - if (control) { - ControlState control_state (ev->velocity == 0x7f ? press : release); - control->set_in_use (control_state.button_state == press); - handle_control_event (*control, control_state); + if (button) { + Strip* strip = dynamic_cast (&button->group()); + + if (strip) { + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("strip %1 button %2 pressed ? %3\n", + strip->index(), button->name(), (ev->velocity > 64))); + strip->handle_button (*button, ev->velocity > 64 ? press : release); + } else { + /* global button */ + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("global button %1\n", button->id())); + _mcp.handle_button_event (*this, *button, ev->velocity > 64 ? press : release); + } } else { - DEBUG_TRACE (DEBUG::MackieControl, "button not found\n"); + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("no button found for %1\n", ev->note_number)); } } void Surface::handle_midi_controller_message (MIDI::Parser &, MIDI::EventTwoBytes* ev) { - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("SurfacePort::handle_midi_controller %1 = %2\n", ev->controller_number, ev->value)); + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("SurfacePort::handle_midi_controller %1 = %2\n", (int) ev->controller_number, (int) ev->value)); + + if (_mcp.device_info().no_handshake()) { + turn_it_on (); + } - Control* control = pots[ev->controller_number]; + Pot* pot = pots[ev->controller_number]; - if (!control && ev->controller_number == Control::jog_base_id) { - control = controls_by_name["jog"]; + // bit 6 gives the sign + float sign = (ev->value & 0x40) == 0 ? 1.0 : -1.0; + // bits 0..5 give the velocity. we interpret this as "ticks + // moved before this message was sent" + float ticks = (ev->value & 0x3f); + if (ticks == 0) { + /* euphonix and perhaps other devices send zero + when they mean 1, we think. + */ + ticks = 1; } + float delta = sign * (ticks / (float) 0x3f); + + if (!pot) { + if (ev->controller_number == Jog::ID && _jog_wheel) { - if (control) { - ControlState state; - - // bytes[2] & 0b01000000 (0x40) give sign - state.sign = (ev->value & 0x40) == 0 ? 1 : -1; - // bytes[2] & 0b00111111 (0x3f) gives delta - state.ticks = (ev->value & 0x3f); - if (state.ticks == 0) { - /* euphonix and perhaps other devices send zero - when they mean 1, we think. - */ - state.ticks = 1; + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("Jog wheel moved %1\n", ticks)); + _jog_wheel->jog_event (delta); + return; } - state.delta = float (state.ticks) / float (0x3f); - - /* Pots only emit events when they move, not when they - stop moving. So to get a stop event, we need to use a timeout. - */ - - control->set_in_use (true); - _mcp.add_in_use_timeout (*this, *control, control); - handle_control_event (*control, state); - } else { - DEBUG_TRACE (DEBUG::MackieControl, "pot not found\n"); + return; } + + Strip* strip = dynamic_cast (&pot->group()); + if (strip) { + strip->handle_pot (*pot, delta); + } } void -Surface::handle_control_event (Control & control, const ControlState & state) -{ - // find the route for the control, if there is one - boost::shared_ptr route; - Strip* strip; - - if ((strip = dynamic_cast (&control.group())) != 0) { - route = strip->route (); - } - - // This handles control element events from the surface - // the state of the controls on the surface is usually updated - // from UI events. - - switch (control.type()) { - case Control::type_fader: - // find the route in the route table for the id - // if the route isn't available, skip it - // at which point the fader should just reset itself - if (route != 0) { - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("fader to %1\n", state.pos)); - - route->gain_control()->set_value (slider_position_to_gain (state.pos)); - - if (ARDOUR::Config->get_mackie_emulation() == "bcf") { - /* reset the timeout while we're still moving the fader */ - _mcp.add_in_use_timeout (*this, control, control.in_use_touch_control); +Surface::handle_midi_sysex (MIDI::Parser &, MIDI::byte * raw_bytes, size_t count) +{ + MidiByteArray bytes (count, raw_bytes); + + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("handle_midi_sysex: %1\n", bytes)); + + if (_mcp.device_info().no_handshake()) { + turn_it_on (); + } + + /* always save the device type ID so that our outgoing sysex messages + * are correct + */ + + if (_stype == mcu) { + mackie_sysex_hdr[3] = bytes[4]; + } else { + mackie_sysex_hdr_xt[3] = bytes[4]; + } + + switch (bytes[5]) { + case 0x01: + /* MCP: Device Ready + LCP: Connection Challenge + */ + if (bytes[4] == 0x10 || bytes[4] == 0x11) { + write_sysex (host_connection_query (bytes)); + } else { + if (!_active) { + turn_it_on (); } - - // must echo bytes back to slider now, because - // the notifier only works if the fader is not being - // touched. Which it is if we're getting input. - _port->write (builder.build_fader ((Fader&)control, state.pos)); } break; - - case Control::type_button: - if (strip) { - strip->handle_button (*_port, control, state.button_state); - } else { - // handle all non-strip buttons - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("global button %1\n", control.id())); - _mcp.handle_button_event (*this, dynamic_cast(control), state.button_state); - + + case 0x03: /* LCP Connection Confirmation */ + if (bytes[4] == 0x10 || bytes[4] == 0x11) { + write_sysex (host_connection_confirmation (bytes)); + _active = true; } break; - - // pot (jog wheel, external control) - case Control::type_pot: - if (strip) { - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("strip pot %1\n", control.id())); - if (route) { - boost::shared_ptr panner = route->panner_shell()->panner(); - // pan for mono input routes, or stereo linked panners - if (panner) { - double p = panner->position (); - - // calculate new value, and adjust - p += state.delta * state.sign; - p = min (1.0, p); - p = max (0.0, p); - panner->set_position (p); - } - } else { - // it's a pot for an umnapped route, so turn all the lights off - _port->write (builder.build_led_ring (dynamic_cast (control), off)); - } - } else { - if (control.is_jog()) { - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("Jog wheel moved %1\n", state.ticks)); - if (_jog_wheel) { - _jog_wheel->jog_event (*_port, control, state); - } - } else { - DEBUG_TRACE (DEBUG::MackieControl, string_compose ("External controller moved %1\n", state.ticks)); - cout << "external controller" << state.ticks * state.sign << endl; - } - } + + case 0x04: /* LCP: Confirmation Denied */ + _active = false; break; - default: - break; + error << "MCP: unknown sysex: " << bytes << endmsg; + } +} + +static MidiByteArray +calculate_challenge_response (MidiByteArray::iterator begin, MidiByteArray::iterator end) +{ + MidiByteArray l; + back_insert_iterator back (l); + copy (begin, end, back); + + MidiByteArray retval; + + // this is how to calculate the response to the challenge. + // from the Logic docs. + retval << (0x7f & (l[0] + (l[1] ^ 0xa) - l[3])); + retval << (0x7f & ( (l[2] >> l[3]) ^ (l[0] + l[3]))); + retval << (0x7f & ((l[3] - (l[2] << 2)) ^ (l[0] | l[1]))); + retval << (0x7f & (l[1] - l[2] + (0xf0 ^ (l[3] << 4)))); + + return retval; +} + +// not used right now +MidiByteArray +Surface::host_connection_query (MidiByteArray & bytes) +{ + MidiByteArray response; + + if (bytes[4] != 0x10 && bytes[4] != 0x11) { + /* not a Logic Control device - no response required */ + return response; + } + + // handle host connection query + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("host connection query: %1\n", bytes)); + + if (bytes.size() != 18) { + cerr << "expecting 18 bytes, read " << bytes << " from " << _port->input_port().name() << endl; + return response; + } + + // build and send host connection reply + response << 0x02; + copy (bytes.begin() + 6, bytes.begin() + 6 + 7, back_inserter (response)); + response << calculate_challenge_response (bytes.begin() + 6 + 7, bytes.begin() + 6 + 7 + 4); + return response; +} + +// not used right now +MidiByteArray +Surface::host_connection_confirmation (const MidiByteArray & bytes) +{ + DEBUG_TRACE (DEBUG::MackieControl, string_compose ("host_connection_confirmation: %1\n", bytes)); + + // decode host connection confirmation + if (bytes.size() != 14) { + ostringstream os; + os << "expecting 14 bytes, read " << bytes << " from " << _port->input_port().name(); + throw MackieControlException (os.str()); + } + + // send version request + return MidiByteArray (2, 0x13, 0x00); +} + +void +Surface::turn_it_on () +{ + if (!_active) { + _active = true; + zero_controls (); + for (Strips::iterator s = strips.begin(); s != strips.end(); ++s) { + (*s)->notify_all (); + } + update_view_mode_display (); } } void -Surface::handle_port_inactive (SurfacePort * port) +Surface::handle_port_inactive (SurfacePort*) { _active = false; } @@ -513,18 +544,21 @@ Surface::write_sysex (MIDI::byte msg) _port->write (buf); } -void -Surface::drop_routes () -{ - for (Strips::iterator s = strips.begin(); s != strips.end(); ++s) { - (*s)->set_route (boost::shared_ptr()); - } -} - uint32_t -Surface::n_strips () const +Surface::n_strips (bool with_locked_strips) const { - return strips.size(); + if (with_locked_strips) { + return strips.size(); + } + + uint32_t n = 0; + + for (Strips::const_iterator it = strips.begin(); it != strips.end(); ++it) { + if (!(*it)->locked()) { + ++n; + } + } + return n; } Strip* @@ -543,7 +577,17 @@ Surface::zero_all () // zero all strips for (Strips::iterator it = strips.begin(); it != strips.end(); ++it) { - _port->write (builder.zero_strip (*this, **it)); + (*it)->zero(); + } + + zero_controls (); +} + +void +Surface::zero_controls () +{ + if (_stype != mcu || !_mcp.device_info().has_global_controls()) { + return; } // turn off global buttons and leds @@ -552,55 +596,252 @@ Surface::zero_all () for (Controls::iterator it = controls.begin(); it != controls.end(); ++it) { Control & control = **it; - if (!control.group().is_strip() && control.accepts_feedback()) { - _port->write (builder.zero_control (control)); + if (!control.group().is_strip()) { + _port->write (control.zero()); } } - // any hardware-specific stuff - // clear 2-char display - _port->write (builder.two_char_display ("LC")); + if (_number == 0 && _mcp.device_info().has_two_character_display()) { + // any hardware-specific stuff + // clear 2-char display + show_two_char_display (" "); + } // and the led ring for the master strip blank_jog_ring (); } void -Surface::periodic () +Surface::periodic (uint64_t now_usecs) { for (Strips::iterator s = strips.begin(); s != strips.end(); ++s) { - (*s)->periodic (); + (*s)->periodic (now_usecs); } } void Surface::write (const MidiByteArray& data) { - _port->write (data); + if (_active) { + _port->write (data); + } } -void -Surface::jog_wheel_state_display (JogWheel::State state) -{ - switch (state) { - case JogWheel::zoom: - _port->write (builder.two_char_display ("Zm")); - break; - case JogWheel::scroll: - _port->write (builder.two_char_display ("Sc")); - break; - case JogWheel::scrub: - _port->write (builder.two_char_display ("Sb")); - break; - case JogWheel::shuttle: - _port->write (builder.two_char_display ("Sh")); - break; - case JogWheel::speed: - _port->write (builder.two_char_display ("Sp")); - break; - case JogWheel::select: - _port->write (builder.two_char_display ("Se")); - break; +void +Surface::map_routes (const vector >& routes) +{ + vector >::const_iterator r; + Strips::iterator s = strips.begin(); + + for (r = routes.begin(); r != routes.end() && s != strips.end(); ++s) { + + /* don't try to assign routes to a locked strip. it won't + use it anyway, but if we do, then we get out of sync + with the proposed mapping. + */ + + if (!(*s)->locked()) { + (*s)->set_route (*r); + ++r; + } + } + + for (; s != strips.end(); ++s) { + (*s)->set_route (boost::shared_ptr()); + } + + +} + +static char +translate_seven_segment (char achar) +{ + achar = toupper (achar); + + if (achar >= 0x40 && achar <= 0x60) { + return achar - 0x40; + } else if (achar >= 0x21 && achar <= 0x3f) { + return achar; + } else { + return 0x00; } } +void +Surface::show_two_char_display (const std::string & msg, const std::string & dots) +{ + if (_stype != mcu || !_mcp.device_info().has_two_character_display() || msg.length() != 2 || dots.length() != 2) { + return; + } + + MidiByteArray right (3, 0xb0, 0x4b, 0x00); + MidiByteArray left (3, 0xb0, 0x4a, 0x00); + + right[2] = translate_seven_segment (msg[0]) + (dots[0] == '.' ? 0x40 : 0x00); + left[2] = translate_seven_segment (msg[1]) + (dots[1] == '.' ? 0x40 : 0x00); + + _port->write (right); + _port->write (left); +} + +void +Surface::show_two_char_display (unsigned int value, const std::string & /*dots*/) +{ + ostringstream os; + os << setfill('0') << setw(2) << value % 100; + show_two_char_display (os.str()); +} + +void +Surface::display_timecode (const std::string & timecode, const std::string & last_timecode) +{ + if (!_active || !_mcp.device_info().has_timecode_display()) { + return; + } + // if there's no change, send nothing, not even sysex header + if (timecode == last_timecode) return; + + // length sanity checking + string local_timecode = timecode; + + // truncate to 10 characters + if (local_timecode.length() > 10) { + local_timecode = local_timecode.substr (0, 10); + } + + // pad to 10 characters + while (local_timecode.length() < 10) { + local_timecode += " "; + } + + // find the suffix of local_timecode that differs from last_timecode + std::pair pp = mismatch (last_timecode.begin(), last_timecode.end(), local_timecode.begin()); + + int position = 0x40; + + // translate characters. These are sent in reverse order of display + // hence the reverse iterators + string::reverse_iterator rend = reverse_iterator (pp.second); + for (string::reverse_iterator it = local_timecode.rbegin(); it != rend; ++it) { + MidiByteArray retval (2, 0xb0, position++); + retval << translate_seven_segment (*it); + _port->write (retval); + } +} + +void +Surface::update_flip_mode_display () +{ + for (Strips::iterator s = strips.begin(); s != strips.end(); ++s) { + (*s)->flip_mode_changed (true); + } +} + +void +Surface::update_view_mode_display () +{ + string text; + int id = -1; + + if (!_active) { + return; + } + + switch (_mcp.view_mode()) { + case MackieControlProtocol::Mixer: + show_two_char_display ("Mx"); + id = Button::Pan; + break; + case MackieControlProtocol::Dynamics: + show_two_char_display ("Dy"); + id = Button::Dyn; + break; + case MackieControlProtocol::EQ: + show_two_char_display ("EQ"); + id = Button::Eq; + break; + case MackieControlProtocol::Loop: + show_two_char_display ("LP"); + id = Button::Loop; + break; + case MackieControlProtocol::AudioTracks: + show_two_char_display ("AT"); + break; + case MackieControlProtocol::MidiTracks: + show_two_char_display ("MT"); + break; + case MackieControlProtocol::Sends: + show_two_char_display ("Sn"); + id = Button::Sends; + break; + case MackieControlProtocol::Plugins: + show_two_char_display ("Pl"); + id = Button::Plugin; + break; + default: + break; + } + + if (id >= 0) { + + /* we are attempting to turn a global button/LED on */ + + map::iterator x = controls_by_device_independent_id.find (id); + + if (x != controls_by_device_independent_id.end()) { + Button* button = dynamic_cast (x->second); + if (button) { + _port->write (button->set_state (on)); + } + } + } + + if (!text.empty()) { + for (Strips::iterator s = strips.begin(); s != strips.end(); ++s) { + _port->write ((*s)->display (1, text)); + } + } +} + +void +Surface::gui_selection_changed (const ARDOUR::StrongRouteNotificationList& routes) +{ + for (Strips::iterator s = strips.begin(); s != strips.end(); ++s) { + (*s)->gui_selection_changed (routes); + } +} + +void +Surface::say_hello () +{ + /* wakeup for Mackie Control */ + MidiByteArray wakeup (7, MIDI::sysex, 0x00, 0x00, 0x66, 0x14, 0x00, MIDI::eox); + _port->write (wakeup); + wakeup[4] = 0x15; /* wakup Mackie XT */ + _port->write (wakeup); + wakeup[4] = 0x10; /* wakupe Logic Control */ + _port->write (wakeup); + wakeup[4] = 0x11; /* wakeup Logic Control XT */ + _port->write (wakeup); +} + +void +Surface::next_jog_mode () +{ +} + +void +Surface::set_jog_mode (JogWheel::Mode) +{ +} + +bool +Surface::route_is_locked_to_strip (boost::shared_ptr r) const +{ + for (Strips::const_iterator s = strips.begin(); s != strips.end(); ++s) { + if ((*s)->route() == r && (*s)->locked()) { + return true; + } + } + return false; +}