X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fsurfaces%2Ffaderport%2Ffaderport.cc;h=72190c1e8a0370970723688de1d5f832d41ae39c;hb=ba5c14036d8a9ecb0b23400ce556612de2209149;hp=e99978f359ccd848fe4b36b40d78652cc3df4853;hpb=dd6cbac20e0ad04500ae72767a16f1ddcfef9f31;p=ardour.git diff --git a/libs/surfaces/faderport/faderport.cc b/libs/surfaces/faderport/faderport.cc index e99978f359..72190c1e8a 100644 --- a/libs/surfaces/faderport/faderport.cc +++ b/libs/surfaces/faderport/faderport.cc @@ -26,7 +26,6 @@ #include #include -#include "pbd/controllable_descriptor.h" #include "pbd/error.h" #include "pbd/failed_constructor.h" #include "pbd/file_utils.h" @@ -38,13 +37,20 @@ #include "ardour/async_midi_port.h" #include "ardour/audioengine.h" +#include "ardour/amp.h" +#include "ardour/bundle.h" +#include "ardour/controllable_descriptor.h" #include "ardour/debug.h" #include "ardour/filesystem_paths.h" #include "ardour/midi_port.h" #include "ardour/midiport_manager.h" +#include "ardour/monitor_processor.h" +#include "ardour/profile.h" #include "ardour/rc_configuration.h" -#include "ardour/route.h" +#include "ardour/record_enable_control.h" +#include "ardour/stripable.h" #include "ardour/session.h" +#include "ardour/session_configuration.h" #include "ardour/track.h" #include "faderport.h" @@ -55,15 +61,13 @@ using namespace PBD; using namespace Glib; using namespace std; -#include "i18n.h" +#include "pbd/i18n.h" #include "pbd/abstract_ui.cc" // instantiate template FaderPort::FaderPort (Session& s) - : ControlProtocol (s, _("Faderport")) - , AbstractUI ("faderport") - , _motorised (true) - , _threshold (10) + : ControlProtocol (s, _("PreSonus FaderPort")) + , AbstractUI (name()) , gui (0) , connection_state (ConnectionState (0)) , _device_active (false) @@ -72,7 +76,10 @@ FaderPort::FaderPort (Session& s) , fader_is_touched (false) , button_state (ButtonState (0)) , blink_state (false) + , rec_enable_state (false) { + last_encoder_time = 0; + boost::shared_ptr inp; boost::shared_ptr outp; @@ -86,96 +93,142 @@ FaderPort::FaderPort (Session& s) throw failed_constructor(); } - do_feedback = false; - _feedback_interval = 10 * 1000; // microseconds - last_feedback_time = 0; - native_counter = 0; - - _current_bank = 0; - _bank_size = 0; + _input_bundle.reset (new ARDOUR::Bundle (_("Faderport Support (Receive)"), true)); + _output_bundle.reset (new ARDOUR::Bundle (_("Faderport Support (Send) "), false)); - TrackSelectionChanged.connect (selection_connection, MISSING_INVALIDATOR, boost::bind (&FaderPort::gui_track_selection_changed, this, _1), this); + _input_bundle->add_channel ( + inp->name(), + ARDOUR::DataType::MIDI, + session->engine().make_port_name_non_relative (inp->name()) + ); - Session::SendFeedback.connect_same_thread (*this, boost::bind (&FaderPort::send_feedback, this)); - //Session::SendFeedback.connect (*this, MISSING_INVALIDATOR, boost::bind (&FaderPort::send_feedback, this), this);; + _output_bundle->add_channel ( + outp->name(), + ARDOUR::DataType::MIDI, + session->engine().make_port_name_non_relative (outp->name()) + ); /* Catch port connections and disconnections */ ARDOUR::AudioEngine::instance()->PortConnectedOrDisconnected.connect (port_connection, MISSING_INVALIDATOR, boost::bind (&FaderPort::connection_handler, this, _1, _2, _3, _4, _5), this); - buttons.insert (std::make_pair (Mute, ButtonInfo (*this, _("Mute"), Mute, 21))); - buttons.insert (std::make_pair (Solo, ButtonInfo (*this, _("Solo"), Solo, 22))); - buttons.insert (std::make_pair (Rec, ButtonInfo (*this, _("Rec"), Rec, 23))); - buttons.insert (std::make_pair (Left, ButtonInfo (*this, _("Left"), Left, 20))); - buttons.insert (std::make_pair (Bank, ButtonInfo (*this, _("Bank"), Bank, 19))); - buttons.insert (std::make_pair (Right, ButtonInfo (*this, _("Right"), Right, 18))); - buttons.insert (std::make_pair (Output, ButtonInfo (*this, _("Output"), Output, 17))); - buttons.insert (std::make_pair (Read, ButtonInfo (*this, _("Read"), Read, 13))); - buttons.insert (std::make_pair (Write, ButtonInfo (*this, _("Write"), Write, 14))); - buttons.insert (std::make_pair (Touch, ButtonInfo (*this, _("Touch"), Touch, 15))); - buttons.insert (std::make_pair (Off, ButtonInfo (*this, _("Off"), Off, 16))); - buttons.insert (std::make_pair (Mix, ButtonInfo (*this, _("Mix"), Mix, 12))); - buttons.insert (std::make_pair (Proj, ButtonInfo (*this, _("Proj"), Proj, 11))); - buttons.insert (std::make_pair (Trns, ButtonInfo (*this, _("Trns"), Trns, 10))); - buttons.insert (std::make_pair (Undo, ButtonInfo (*this, _("Undo"), Undo, 9))); - buttons.insert (std::make_pair (Shift, ButtonInfo (*this, _("Shift"), Shift, 5))); - buttons.insert (std::make_pair (Punch, ButtonInfo (*this, _("Punch"), Punch, 6))); - buttons.insert (std::make_pair (User, ButtonInfo (*this, _("User"), User, 7))); - buttons.insert (std::make_pair (Loop, ButtonInfo (*this, _("Loop"), Loop, 8))); - buttons.insert (std::make_pair (Rewind, ButtonInfo (*this, _("Rewind"), Rewind, 4))); - buttons.insert (std::make_pair (Ffwd, ButtonInfo (*this, _("Ffwd"), Ffwd, 3))); - buttons.insert (std::make_pair (Stop, ButtonInfo (*this, _("Stop"), Stop, 2))); - buttons.insert (std::make_pair (Play, ButtonInfo (*this, _("Play"), Play, 1))); - buttons.insert (std::make_pair (RecEnable, ButtonInfo (*this, _("RecEnable"), RecEnable, 0))); - buttons.insert (std::make_pair (FaderTouch, ButtonInfo (*this, _("Fader (touch)"), FaderTouch, -1))); - - button_info (Undo).set_action (boost::bind (&FaderPort::undo, this), true); - button_info (Undo).set_action (boost::bind (&FaderPort::redo, this), true, ShiftDown); - button_info (Undo).set_flash (true); - - button_info (Play).set_action (boost::bind (&BasicUI::transport_play, this, true), true); - button_info (RecEnable).set_action (boost::bind (&BasicUI::rec_enable_toggle, this), true); + buttons.insert (std::make_pair (Mute, Button (*this, _("Mute"), Mute, 21))); + buttons.insert (std::make_pair (Solo, Button (*this, _("Solo"), Solo, 22))); + buttons.insert (std::make_pair (Rec, Button (*this, _("Rec"), Rec, 23))); + buttons.insert (std::make_pair (Left, Button (*this, _("Left"), Left, 20))); + buttons.insert (std::make_pair (Bank, Button (*this, _("Bank"), Bank, 19))); + buttons.insert (std::make_pair (Right, Button (*this, _("Right"), Right, 18))); + buttons.insert (std::make_pair (Output, Button (*this, _("Output"), Output, 17))); + buttons.insert (std::make_pair (FP_Read, Button (*this, _("Read"), FP_Read, 13))); + buttons.insert (std::make_pair (FP_Write, Button (*this, _("Write"), FP_Write, 14))); + buttons.insert (std::make_pair (FP_Touch, Button (*this, _("Touch"), FP_Touch, 15))); + buttons.insert (std::make_pair (FP_Off, Button (*this, _("Off"), FP_Off, 16))); + buttons.insert (std::make_pair (Mix, Button (*this, _("Mix"), Mix, 12))); + buttons.insert (std::make_pair (Proj, Button (*this, _("Proj"), Proj, 11))); + buttons.insert (std::make_pair (Trns, Button (*this, _("Trns"), Trns, 10))); + buttons.insert (std::make_pair (Undo, Button (*this, _("Undo"), Undo, 9))); + buttons.insert (std::make_pair (Shift, Button (*this, _("Shift"), Shift, 5))); + buttons.insert (std::make_pair (Punch, Button (*this, _("Punch"), Punch, 6))); + buttons.insert (std::make_pair (User, Button (*this, _("User"), User, 7))); + buttons.insert (std::make_pair (Loop, Button (*this, _("Loop"), Loop, 8))); + buttons.insert (std::make_pair (Rewind, Button (*this, _("Rewind"), Rewind, 4))); + buttons.insert (std::make_pair (Ffwd, Button (*this, _("Ffwd"), Ffwd, 3))); + buttons.insert (std::make_pair (Stop, Button (*this, _("Stop"), Stop, 2))); + buttons.insert (std::make_pair (Play, Button (*this, _("Play"), Play, 1))); + buttons.insert (std::make_pair (RecEnable, Button (*this, _("RecEnable"), RecEnable, 0))); + buttons.insert (std::make_pair (Footswitch, Button (*this, _("Footswitch"), Footswitch, -1))); + buttons.insert (std::make_pair (FaderTouch, Button (*this, _("Fader (touch)"), FaderTouch, -1))); + + get_button (Shift).set_flash (true); + get_button (Mix).set_flash (true); + get_button (Proj).set_flash (true); + get_button (Trns).set_flash (true); + get_button (User).set_flash (true); + + get_button (Left).set_action ( boost::bind (&FaderPort::left, this), true); + get_button (Right).set_action ( boost::bind (&FaderPort::right, this), true); + + get_button (Undo).set_action (boost::bind (&FaderPort::undo, this), true); + get_button (Undo).set_action (boost::bind (&FaderPort::redo, this), true, ShiftDown); + get_button (Undo).set_flash (true); + + get_button (FP_Read).set_action (boost::bind (&FaderPort::read, this), true); + get_button (FP_Read).set_action (boost::bind (&FaderPort::off, this), false, LongPress); + get_button (FP_Write).set_action (boost::bind (&FaderPort::write, this), true); + get_button (FP_Write).set_action (boost::bind (&FaderPort::off, this), false, LongPress); + get_button (FP_Touch).set_action (boost::bind (&FaderPort::touch, this), true); + get_button (FP_Touch).set_action (boost::bind (&FaderPort::off, this), false, LongPress); + get_button (FP_Off).set_action (boost::bind (&FaderPort::off, this), true); + + get_button (Play).set_action (boost::bind (&BasicUI::transport_play, this, true), true); + get_button (RecEnable).set_action (boost::bind (&BasicUI::rec_enable_toggle, this), true); /* Stop is a modifier, so we have to use its own button state to get the default action (since StopDown will be set when looking for the action to invoke. */ - button_info (Stop).set_action (boost::bind (&BasicUI::transport_stop, this), true, StopDown); - button_info (Ffwd).set_action (boost::bind (&BasicUI::ffwd, this), true); + get_button (Stop).set_action (boost::bind (&BasicUI::transport_stop, this), true, StopDown); + get_button (Ffwd).set_action (boost::bind (&BasicUI::ffwd, this), true); /* See comments about Stop above .. */ - button_info (Rewind).set_action (boost::bind (&BasicUI::rewind, this), true, RewindDown); - button_info (Rewind).set_action (boost::bind (&BasicUI::goto_zero, this), true, ButtonState (RewindDown|StopDown)); - button_info (Rewind).set_action (boost::bind (&BasicUI::goto_start, this), true, ButtonState (RewindDown|ShiftDown)); + get_button (Rewind).set_action (boost::bind (&BasicUI::rewind, this), true, RewindDown); + get_button (Rewind).set_action (boost::bind (&BasicUI::goto_zero, this), true, ButtonState (RewindDown|StopDown)); + get_button (Rewind).set_action (boost::bind (&BasicUI::goto_start, this, false), true, ButtonState (RewindDown|ShiftDown)); + + get_button (Ffwd).set_action (boost::bind (&BasicUI::ffwd, this), true); + get_button (Ffwd).set_action (boost::bind (&BasicUI::goto_end, this), true, ShiftDown); - button_info (Ffwd).set_action (boost::bind (&BasicUI::ffwd, this), true); - button_info (Ffwd).set_action (boost::bind (&BasicUI::goto_end, this), true, ShiftDown); + get_button (Punch).set_action (boost::bind (&FaderPort::punch, this), true); - button_info (Loop).set_action (boost::bind (&BasicUI::loop_toggle, this), true); - button_info (Loop).set_action (boost::bind (&BasicUI::add_marker, this, string()), true, ShiftDown); + get_button (Loop).set_action (boost::bind (&BasicUI::loop_toggle, this), true); + get_button (Loop).set_action (boost::bind (&BasicUI::add_marker, this, string()), true, ShiftDown); - button_info (Punch).set_action (boost::bind (&BasicUI::prev_marker, this), true, ShiftDown); - button_info (User).set_action (boost::bind (&BasicUI::next_marker, this), true, ShiftDown); + get_button (Punch).set_action (boost::bind (&BasicUI::prev_marker, this), true, ShiftDown); + get_button (User).set_action (boost::bind (&BasicUI::next_marker, this), true, ButtonState(ShiftDown|UserDown)); - button_info (Mute).set_action (boost::bind (&FaderPort::mute, this), true); - button_info (Solo).set_action (boost::bind (&FaderPort::solo, this), true); - button_info (Rec).set_action (boost::bind (&FaderPort::rec_enable, this), true); + get_button (Mute).set_action (boost::bind (&FaderPort::mute, this), true); + get_button (Solo).set_action (boost::bind (&FaderPort::solo, this), true); + get_button (Rec).set_action (boost::bind (&FaderPort::rec_enable, this), true); + + get_button (Output).set_action (boost::bind (&FaderPort::use_master, this), true); + get_button (Output).set_action (boost::bind (&FaderPort::use_monitor, this), true, ShiftDown); } FaderPort::~FaderPort () { + cerr << "~FP\n"; + + all_lights_out (); + if (_input_port) { DEBUG_TRACE (DEBUG::FaderPort, string_compose ("unregistering input port %1\n", boost::shared_ptr(_input_port)->name())); + Glib::Threads::Mutex::Lock em (AudioEngine::instance()->process_lock()); AudioEngine::instance()->unregister_port (_input_port); _input_port.reset (); } if (_output_port) { -// _output_port->drain (10000); //ToDo: is this necessary? It hangs the shutdown, for me + _output_port->drain (10000, 250000); /* check every 10 msecs, wait up to 1/4 second for the port to drain */ DEBUG_TRACE (DEBUG::FaderPort, string_compose ("unregistering output port %1\n", boost::shared_ptr(_output_port)->name())); + Glib::Threads::Mutex::Lock em (AudioEngine::instance()->process_lock()); AudioEngine::instance()->unregister_port (_output_port); _output_port.reset (); } tear_down_gui (); + + /* stop event loop */ + DEBUG_TRACE (DEBUG::FaderPort, "BaseUI::quit ()\n"); + BaseUI::quit (); +} + +void* +FaderPort::request_factory (uint32_t num_requests) +{ + /* AbstractUI::request_buffer_factory() is a template method only + instantiated in this source module. To provide something visible for + use in the interface/descriptor, we have this static method that is + template-free. + */ + return request_buffer_factory (num_requests); } void @@ -183,8 +236,8 @@ FaderPort::start_midi_handling () { /* handle device inquiry response */ _input_port->parser()->sysex.connect_same_thread (midi_connections, boost::bind (&FaderPort::sysex_handler, this, _1, _2, _3)); - /* handle switches */ - _input_port->parser()->poly_pressure.connect_same_thread (midi_connections, boost::bind (&FaderPort::switch_handler, this, _1, _2)); + /* handle buttons */ + _input_port->parser()->poly_pressure.connect_same_thread (midi_connections, boost::bind (&FaderPort::button_handler, this, _1, _2)); /* handle encoder */ _input_port->parser()->pitchbend.connect_same_thread (midi_connections, boost::bind (&FaderPort::encoder_handler, this, _1, _2)); /* handle fader */ @@ -195,7 +248,7 @@ FaderPort::start_midi_handling () * method, which will read the data, and invoke the parser. */ - _input_port->xthread().set_receive_handler (sigc::bind (sigc::mem_fun (this, &FaderPort::midi_input_handler), _input_port)); + _input_port->xthread().set_receive_handler (sigc::bind (sigc::mem_fun (this, &FaderPort::midi_input_handler), boost::weak_ptr (_input_port))); _input_port->xthread().attach (main_loop()->get_context()); } @@ -233,75 +286,196 @@ FaderPort::stop () void FaderPort::thread_init () { - struct sched_param rtparam; - - pthread_set_name (X_("FaderPort")); - - PBD::notify_gui_about_thread_creation (X_("gui"), pthread_self(), X_("FaderPort"), 2048); - ARDOUR::SessionEvent::create_per_thread_pool (X_("FaderPort"), 128); + pthread_set_name (event_loop_name().c_str()); - memset (&rtparam, 0, sizeof (rtparam)); - rtparam.sched_priority = 9; /* XXX should be relative to audio (JACK) thread */ + PBD::notify_event_loops_about_thread_creation (pthread_self(), event_loop_name(), 2048); + ARDOUR::SessionEvent::create_per_thread_pool (event_loop_name(), 128); - if (pthread_setschedparam (pthread_self(), SCHED_FIFO, &rtparam) != 0) { - // do we care? not particularly. - } + set_thread_priority (); } void FaderPort::all_lights_out () { for (ButtonMap::iterator b = buttons.begin(); b != buttons.end(); ++b) { - b->second.set_led_state (_output_port, false, true); + b->second.set_led_state (_output_port, false); } } -FaderPort::ButtonInfo& -FaderPort::button_info (ButtonID id) const +FaderPort::Button& +FaderPort::get_button (ButtonID id) const { ButtonMap::const_iterator b = buttons.find (id); assert (b != buttons.end()); - return const_cast(b->second); + return const_cast(b->second); +} + +bool +FaderPort::button_long_press_timeout (ButtonID id) +{ + if (buttons_down.find (id) != buttons_down.end()) { + get_button (id).invoke (ButtonState (LongPress|button_state), false); + } else { + /* release happened and somehow we were not cancelled */ + } + + /* whichever button this was, we've used it ... don't invoke the + release action. + */ + consumed.insert (id); + + return false; /* don't get called again */ +} + +void +FaderPort::start_press_timeout (Button& button, ButtonID id) +{ + Glib::RefPtr timeout = Glib::TimeoutSource::create (500); // milliseconds + button.timeout_connection = timeout->connect (sigc::bind (sigc::mem_fun (*this, &FaderPort::button_long_press_timeout), id)); + timeout->attach (main_loop()->get_context()); } void -FaderPort::switch_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb) +FaderPort::button_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb) { ButtonID id (ButtonID (tb->controller_number)); + Button& button (get_button (id)); + + DEBUG_TRACE (DEBUG::FaderPort, string_compose ("button event for ID %1 press ? %2\n", (int) tb->controller_number, (tb->value ? "yes" : "no"))); + + if (tb->value) { + buttons_down.insert (id); + } else { + buttons_down.erase (id); + button.timeout_connection.disconnect (); + } + + ButtonState bs (ButtonState (0)); switch (id) { case Shift: - button_state = (tb->value ? ButtonState (button_state|ShiftDown) : ButtonState (button_state&~ShiftDown)); + bs = ShiftDown; break; case Stop: - button_state = (tb->value ? ButtonState (button_state|StopDown) : ButtonState (button_state&~StopDown)); + bs = StopDown; break; case Rewind: - button_state = (tb->value ? ButtonState (button_state|RewindDown) : ButtonState (button_state&~RewindDown)); + bs = RewindDown; + break; + case User: + bs = UserDown; break; case FaderTouch: fader_is_touched = tb->value; + if (_current_stripable) { + boost::shared_ptr gain = _current_stripable->gain_control (); + if (gain) { + framepos_t now = session->engine().sample_time(); + if (tb->value) { + gain->start_touch (now); + } else { + gain->stop_touch (now); + } + } + } break; default: + if (tb->value) { + start_press_timeout (button, id); + } break; } - ButtonInfo& bi (button_info (id)); + if (bs) { + button_state = (tb->value ? ButtonState (button_state|bs) : ButtonState (button_state&~bs)); + DEBUG_TRACE (DEBUG::FaderPort, string_compose ("reset button state to %1 using %2\n", button_state, (int) bs)); + } - if (bi.uses_flash()) { - bi.set_led_state (_output_port, (int)tb->value); + if (button.uses_flash()) { + button.set_led_state (_output_port, (int)tb->value); } - bi.invoke (button_state, tb->value ? true : false); + set::iterator c = consumed.find (id); + + if (c == consumed.end()) { + button.invoke (button_state, tb->value ? true : false); + } else { + DEBUG_TRACE (DEBUG::FaderPort, "button was consumed, ignored\n"); + consumed.erase (c); + } } void FaderPort::encoder_handler (MIDI::Parser &, MIDI::pitchbend_t pb) { - if (pb < 8192) { - cerr << "Encoder right\n"; - } else { - cerr << "Encoder left\n"; + int delta = 1; + + if (pb >= 8192) { + delta = -1; + } + + //knob debouncing and hysteresis. The presonus encoder often sends bursts of events, or goes the wrong direction + { + last_last_encoder_delta = last_encoder_delta; + last_encoder_delta = delta; + microseconds_t now = get_microseconds (); + if ((now - last_encoder_time) < 10*1000) { //require at least 10ms interval between changes, because the device sometimes sends multiple deltas + return; + } + if ((now - last_encoder_time) < 100*1000) { //avoid directional changes while "spinning", 100ms window + if ( (delta == last_encoder_delta) && (delta == last_last_encoder_delta) ) { + last_good_encoder_delta = delta; //3 in a row, grudgingly accept this as the new direction + } + if (delta != last_good_encoder_delta) { //otherwise ensure we keep going the same way + delta = last_good_encoder_delta; + } + } else { //we aren't yet in a spin window, just assume this move is really what we want + //NOTE: if you are worried about where these get initialized, here it is. + last_last_encoder_delta = delta; + last_encoder_delta = delta; + } + last_encoder_time = now; + last_good_encoder_delta = delta; + } + + if (_current_stripable) { + + ButtonState trim_modifier; + ButtonState width_modifier; + + if (Profile->get_mixbus()) { + trim_modifier = ShiftDown; + width_modifier = ButtonState (0); + } else { + trim_modifier = UserDown; + width_modifier = ShiftDown; + } + + if ((button_state & trim_modifier) == trim_modifier ) { // mod+encoder = input trim + boost::shared_ptr trim = _current_stripable->trim_control (); + if (trim) { + float val = accurate_coefficient_to_dB (trim->get_value()); + val += delta * .5f; // use 1/2 dB Steps -20..+20 + trim->set_value (dB_to_coefficient (val), Controllable::UseGroup); + } + } else if (width_modifier && ((button_state & width_modifier) == width_modifier)) { + ardour_pan_width (delta); + + } else { // pan/balance + if (!Profile->get_mixbus()) { + ardour_pan_azimuth (delta); + } else { + mixbus_pan (delta); + } + } + } + + /* if the user button was pressed, mark it as consumed so that its + * release action has no effect. + */ + + if (!Profile->get_mixbus() && (button_state & UserDown)) { + consumed.insert (User); } } @@ -319,12 +493,16 @@ FaderPort::fader_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb) } if (was_fader) { - if (_current_route) { - boost::shared_ptr gain = _current_route->gain_control (); + if (_current_stripable) { + boost::shared_ptr gain = _current_stripable->gain_control (); if (gain) { int ival = (fader_msb << 7) | fader_lsb; float val = gain->interface_to_internal (ival/16384.0); - _current_route->set_gain (val, this); + /* even though the faderport only controls a + single stripable at a time, allow the fader to + modify the group, if appropriate. + */ + _current_stripable->gain_control()->set_value (val, Controllable::UseGroup); } } } @@ -333,6 +511,8 @@ FaderPort::fader_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb) void FaderPort::sysex_handler (MIDI::Parser &p, MIDI::byte *buf, size_t sz) { + DEBUG_TRACE (DEBUG::FaderPort, string_compose ("sysex message received, size = %1\n", sz)); + if (sz < 17) { return; } @@ -352,7 +532,7 @@ FaderPort::sysex_handler (MIDI::Parser &p, MIDI::byte *buf, size_t sz) _device_active = true; - cerr << "FaderPort identified\n"; + DEBUG_TRACE (DEBUG::FaderPort, "FaderPort identified via MIDI Device Inquiry response\n"); /* put it into native mode */ @@ -367,14 +547,17 @@ FaderPort::sysex_handler (MIDI::Parser &p, MIDI::byte *buf, size_t sz) /* catch up on state */ - notify_transport_state_changed (); - notify_record_state_changed (); + /* make sure that rec_enable_state is consistent with current device state */ + get_button (RecEnable).set_led_state (_output_port, rec_enable_state); + + map_transport_state (); + map_recenable_state (); } int FaderPort::set_active (bool yn) { - DEBUG_TRACE (DEBUG::FaderPort, string_compose("MackieControlProtocol::set_active init with yn: '%1'\n", yn)); + DEBUG_TRACE (DEBUG::FaderPort, string_compose("Faderport::set_active init with yn: '%1'\n", yn)); if (yn == active()) { return 0; @@ -392,6 +575,10 @@ FaderPort::set_active (bool yn) blink_connection = blink_timeout->connect (sigc::mem_fun (*this, &FaderPort::blink)); blink_timeout->attach (main_loop()->get_context()); + Glib::RefPtr periodic_timeout = Glib::TimeoutSource::create (100); // milliseconds + periodic_connection = periodic_timeout->connect (sigc::mem_fun (*this, &FaderPort::periodic)); + periodic_timeout->attach (main_loop()->get_context()); + } else { BaseUI::quit (); @@ -401,20 +588,52 @@ FaderPort::set_active (bool yn) ControlProtocol::set_active (yn); - DEBUG_TRACE (DEBUG::FaderPort, string_compose("MackieControlProtocol::set_active done with yn: '%1'\n", yn)); + DEBUG_TRACE (DEBUG::FaderPort, string_compose("Faderport::set_active done with yn: '%1'\n", yn)); return 0; } +bool +FaderPort::periodic () +{ + if (!_current_stripable) { + return true; + } + + ARDOUR::AutoState gain_state = _current_stripable->gain_control()->automation_state(); + + if (gain_state == ARDOUR::Touch || gain_state == ARDOUR::Play) { + map_gain (); + } + + return true; +} + +void +FaderPort::stop_blinking (ButtonID id) +{ + blinkers.remove (id); + get_button (id).set_led_state (_output_port, false); +} + +void +FaderPort::start_blinking (ButtonID id) +{ + blinkers.push_back (id); + get_button (id).set_led_state (_output_port, true); +} + bool FaderPort::blink () { blink_state = !blink_state; for (Blinkers::iterator b = blinkers.begin(); b != blinkers.end(); b++) { - button_info(*b).set_led_state (_output_port, blink_state); + get_button(*b).set_led_state (_output_port, blink_state); } + map_recenable_state (); + return true; } @@ -428,79 +647,110 @@ FaderPort::close () port_connection.disconnect (); blink_connection.disconnect (); selection_connection.disconnect (); - route_connections.drop_connections (); + stripable_connections.drop_connections (); #if 0 - route_connections.drop_connections (); + stripable_connections.drop_connections (); #endif } void -FaderPort::notify_record_state_changed () +FaderPort::map_recenable_state () { + /* special case for RecEnable because its status can change as a + * confluence of unrelated parameters: (a) session rec-enable state (b) + * rec-enabled tracks. So we don't add the button to the blinkers list, + * we just call this: + * + * * from the blink callback + * * when the session tells us about a status change + * + * We do the last one so that the button changes state promptly rather + * than waiting for the next blink callback. The change in "blinking" + * based on having record-enabled tracks isn't urgent, and that happens + * during the blink callback. + */ + + bool onoff; + switch (session->record_status()) { case Session::Disabled: - button_info (RecEnable).set_led_state (_output_port, false); - blinkers.remove (RecEnable); + onoff = false; break; case Session::Enabled: - button_info (RecEnable).set_led_state (_output_port, true); - blinkers.push_back (RecEnable); + onoff = blink_state; break; case Session::Recording: - button_info (RecEnable).set_led_state (_output_port, true); - blinkers.remove (RecEnable); + if (session->have_rec_enabled_track ()) { + onoff = true; + } else { + onoff = blink_state; + } break; } + + if (onoff != rec_enable_state) { + get_button(RecEnable).set_led_state (_output_port, onoff); + rec_enable_state = onoff; + } } void -FaderPort::notify_transport_state_changed () +FaderPort::map_transport_state () { - button_info (Loop).set_led_state (_output_port, session->get_play_loop()); - button_info (Play).set_led_state (_output_port, session->transport_speed() == 1.0); - button_info (Stop).set_led_state (_output_port, session->transport_stopped ()); - button_info (Rewind).set_led_state (_output_port, session->transport_speed() < 0.0); - button_info (Ffwd).set_led_state (_output_port, session->transport_speed() > 1.0); + get_button (Loop).set_led_state (_output_port, session->get_play_loop()); + + float ts = session->transport_speed(); + + if (ts == 0) { + stop_blinking (Play); + } else if (fabs (ts) == 1.0) { + stop_blinking (Play); + get_button (Play).set_led_state (_output_port, true); + } else { + start_blinking (Play); + } + + get_button (Stop).set_led_state (_output_port, session->transport_stopped ()); + get_button (Rewind).set_led_state (_output_port, session->transport_speed() < 0.0); + get_button (Ffwd).set_led_state (_output_port, session->transport_speed() > 1.0); } void -FaderPort::connect_session_signals() +FaderPort::parameter_changed (string what) { - session->RecordStateChanged.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::notify_record_state_changed, this), this); - session->TransportStateChange.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::notify_transport_state_changed, this), this); + if (what == "punch-in" || what == "punch-out") { + bool in = session->config.get_punch_in (); + bool out = session->config.get_punch_out (); + if (in && out) { + get_button (Punch).set_led_state (_output_port, true); + blinkers.remove (Punch); + } else if (in || out) { + start_blinking (Punch); + } else { + stop_blinking (Punch); + } + } } void -FaderPort::set_feedback_interval (microseconds_t ms) +FaderPort::connect_session_signals() { - _feedback_interval = ms; + session->RecordStateChanged.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::map_recenable_state, this), this); + session->TransportStateChange.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::map_transport_state, this), this); + /* not session, but treat it similarly */ + session->config.ParameterChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::parameter_changed, this, _1), this); } -void -FaderPort::send_feedback () +bool +FaderPort::midi_input_handler (Glib::IOCondition ioc, boost::weak_ptr wport) { - /* This is executed in RT "process" context", so no blocking calls - */ - - if (!do_feedback) { - return; - } - - microseconds_t now = get_microseconds (); + boost::shared_ptr port (wport.lock()); - if (last_feedback_time != 0) { - if ((now - last_feedback_time) < _feedback_interval) { - return; - } + if (!port) { + return false; } - last_feedback_time = now; -} - -bool -FaderPort::midi_input_handler (Glib::IOCondition ioc, boost::shared_ptr port) -{ DEBUG_TRACE (DEBUG::FaderPort, string_compose ("something happend on %1\n", boost::shared_ptr(port)->name())); if (ioc & ~IO_IN) { @@ -509,10 +759,7 @@ FaderPort::midi_input_handler (Glib::IOCondition ioc, boost::shared_ptrclear (); - } - + port->clear (); DEBUG_TRACE (DEBUG::FaderPort, string_compose ("data available on %1\n", boost::shared_ptr(port)->name())); framepos_t now = session->engine().sample_time(); port->parse (now); @@ -538,6 +785,18 @@ FaderPort::get_state () child->add_child_nocopy (boost::shared_ptr(_output_port)->get_state()); node.add_child_nocopy (*child); + /* Save action state for Mix, Proj, Trns and User buttons, since these + * are user controlled. We can only save named-action operations, since + * internal functions are just pointers to functions and hard to + * serialize without enumerating them all somewhere. + */ + + node.add_child_nocopy (get_button (Mix).get_state()); + node.add_child_nocopy (get_button (Proj).get_state()); + node.add_child_nocopy (get_button (Trns).get_state()); + node.add_child_nocopy (get_button (User).get_state()); + node.add_child_nocopy (get_button (Footswitch).get_state()); + return node; } @@ -566,66 +825,27 @@ FaderPort::set_state (const XMLNode& node, int version) } } - return 0; -} - -int -FaderPort::set_feedback (bool yn) -{ - do_feedback = yn; - last_feedback_time = 0; - return 0; -} - -bool -FaderPort::get_feedback () const -{ - return do_feedback; -} - -void -FaderPort::set_current_bank (uint32_t b) -{ - _current_bank = b; -// reset_controllables (); -} - -void -FaderPort::next_bank () -{ - _current_bank++; -// reset_controllables (); -} - -void -FaderPort::prev_bank() -{ - if (_current_bank) { - _current_bank--; -// reset_controllables (); + for (XMLNodeList::const_iterator n = node.children().begin(); n != node.children().end(); ++n) { + if ((*n)->name() == X_("Button")) { + int32_t xid; + if (!(*n)->get_property (X_("id"), xid)) { + continue; + } + ButtonMap::iterator b = buttons.find (ButtonID (xid)); + if (b == buttons.end()) { + continue; + } + b->second.set_state (**n); + } } -} - -void -FaderPort::set_motorised (bool m) -{ - _motorised = m; -} - -void -FaderPort::set_threshold (int t) -{ - _threshold = t; -} -void -FaderPort::reset_controllables () -{ + return 0; } bool FaderPort::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; } @@ -646,6 +866,7 @@ FaderPort::connection_handler (boost::weak_ptr, std::string name1, 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; } @@ -658,6 +879,7 @@ FaderPort::connection_handler (boost::weak_ptr, std::string name1, */ g_usleep (100000); + DEBUG_TRACE (DEBUG::FaderPort, "device now connected for both input and output\n"); connected (); } else { @@ -665,13 +887,17 @@ FaderPort::connection_handler (boost::weak_ptr, std::string name1, _device_active = false; } + ConnectionChange (); /* emit signal for our GUI */ + + DEBUG_TRACE (DEBUG::FaderPort, "FaderPort::connection_handler end\n"); + return true; /* connection status changed */ } void FaderPort::connected () { - std::cerr << "faderport connected\n"; + DEBUG_TRACE (DEBUG::FaderPort, "sending device inquiry message...\n"); start_midi_handling (); @@ -690,86 +916,107 @@ FaderPort::connected () } void -FaderPort::ButtonInfo::invoke (FaderPort::ButtonState bs, bool press) +FaderPort::Button::invoke (FaderPort::ButtonState bs, bool press) { - switch (type) { + DEBUG_TRACE (DEBUG::FaderPort, string_compose ("invoke button %1 for %2 state %3%4%5\n", id, (press ? "press":"release"), hex, bs, dec)); + + ToDoMap::iterator x; + + if (press) { + if ((x = on_press.find (bs)) == on_press.end()) { + DEBUG_TRACE (DEBUG::FaderPort, string_compose ("no press action for button %1 state %2 @ %3 in %4\n", id, bs, this, &on_press)); + return; + } + } else { + if ((x = on_release.find (bs)) == on_release.end()) { + DEBUG_TRACE (DEBUG::FaderPort, string_compose ("no release action for button %1 state %2 @%3 in %4\n", id, bs, this, &on_release)); + return; + } + } + + switch (x->second.type) { case NamedAction: - if (press) { - ToDoMap::iterator x = on_press.find (bs); - if (x != on_press.end()) { - if (!x->second.action_name.empty()) { - fp.access_action (x->second.action_name); - } - } - } else { - ToDoMap::iterator x = on_release.find (bs); - if (x != on_release.end()) { - if (!x->second.action_name.empty()) { - fp.access_action (x->second.action_name); - } - } + if (!x->second.action_name.empty()) { + fp.access_action (x->second.action_name); } break; case InternalFunction: - if (press) { - ToDoMap::iterator x = on_press.find (bs); - if (x != on_press.end()) { - if (x->second.function) { - x->second.function (); - } - } - } else { - ToDoMap::iterator x = on_release.find (bs); - if (x != on_release.end()) { - if (x->second.function) { - x->second.function (); - } - } + if (x->second.function) { + x->second.function (); } - break; } } void -FaderPort::ButtonInfo::set_action (string const& name, bool when_pressed, FaderPort::ButtonState bs) +FaderPort::Button::set_action (string const& name, bool when_pressed, FaderPort::ButtonState bs) { ToDo todo; - type = NamedAction; + todo.type = NamedAction; if (when_pressed) { - todo.action_name = name; - on_press[bs] = todo; + if (name.empty()) { + on_press.erase (bs); + } else { + DEBUG_TRACE (DEBUG::FaderPort, string_compose ("set button %1 to action %2 on press + %3%4%5\n", id, name, bs)); + todo.action_name = name; + on_press[bs] = todo; + } } else { - todo.action_name = name; - on_release[bs] = todo; + if (name.empty()) { + on_release.erase (bs); + } else { + DEBUG_TRACE (DEBUG::FaderPort, string_compose ("set button %1 to action %2 on release + %3%4%5\n", id, name, bs)); + todo.action_name = name; + on_release[bs] = todo; + } } +} + +string +FaderPort::Button::get_action (bool press, FaderPort::ButtonState bs) +{ + ToDoMap::iterator x; + if (press) { + if ((x = on_press.find (bs)) == on_press.end()) { + return string(); + } + if (x->second.type != NamedAction) { + return string (); + } + return x->second.action_name; + } else { + if ((x = on_release.find (bs)) == on_release.end()) { + return string(); + } + if (x->second.type != NamedAction) { + return string (); + } + return x->second.action_name; + } } void -FaderPort::ButtonInfo::set_action (boost::function f, bool when_pressed, FaderPort::ButtonState bs) +FaderPort::Button::set_action (boost::function f, bool when_pressed, FaderPort::ButtonState bs) { ToDo todo; - type = InternalFunction; + todo.type = InternalFunction; if (when_pressed) { + DEBUG_TRACE (DEBUG::FaderPort, string_compose ("set button %1 (%2) @ %5 to some functor on press + %3 in %4\n", id, name, bs, &on_press, this)); todo.function = f; on_press[bs] = todo; } else { + DEBUG_TRACE (DEBUG::FaderPort, string_compose ("set button %1 (%2) @ %5 to some functor on release + %3\n", id, name, bs, this)); todo.function = f; on_release[bs] = todo; } } void -FaderPort::ButtonInfo::set_led_state (boost::shared_ptr port, int onoff, bool force) +FaderPort::Button::set_led_state (boost::shared_ptr port, bool onoff) { - if (!force && (led_on == (bool) onoff)) { - /* nothing to do */ - return; - } - if (out < 0) { /* fader button ID - no LED */ return; @@ -780,65 +1027,221 @@ FaderPort::ButtonInfo::set_led_state (boost::shared_ptr port, int on buf[1] = out; buf[2] = onoff ? 1 : 0; port->write (buf, 3, 0); - led_on = (onoff ? true : false); +} + +int +FaderPort::Button::set_state (XMLNode const& node) +{ + int32_t xid; + if (!node.get_property ("id", xid) || xid != id) { + return -1; + } + + typedef pair state_pair_t; + vector state_pairs; + + state_pairs.push_back (make_pair (string ("plain"), ButtonState (0))); + state_pairs.push_back (make_pair (string ("shift"), ShiftDown)); + state_pairs.push_back (make_pair (string ("long"), LongPress)); + + for (vector::const_iterator sp = state_pairs.begin(); sp != state_pairs.end(); ++sp) { + + string propname = sp->first + X_("-press"); + string value; + if (node.get_property (propname.c_str(), value)) { + set_action (value, true, sp->second); + } + + propname = sp->first + X_("-release"); + if (node.get_property (propname.c_str(), value)) { + set_action (value, false, sp->second); + } + } + + return 0; +} + +XMLNode& +FaderPort::Button::get_state () const +{ + XMLNode* node = new XMLNode (X_("Button")); + + node->set_property (X_("id"), to_string(id)); + + ToDoMap::const_iterator x; + ToDo null; + null.type = NamedAction; + + typedef pair state_pair_t; + vector state_pairs; + + state_pairs.push_back (make_pair (string ("plain"), ButtonState (0))); + state_pairs.push_back (make_pair (string ("shift"), ShiftDown)); + state_pairs.push_back (make_pair (string ("long"), LongPress)); + + for (vector::const_iterator sp = state_pairs.begin(); sp != state_pairs.end(); ++sp) { + if ((x = on_press.find (sp->second)) != on_press.end()) { + if (x->second.type == NamedAction) { + node->set_property (string (sp->first + X_("-press")).c_str(), x->second.action_name); + } + } + + if ((x = on_release.find (sp->second)) != on_release.end()) { + if (x->second.type == NamedAction) { + node->set_property (string (sp->first + X_("-release")).c_str(), x->second.action_name); + } + } + } + + return *node; } void -FaderPort::gui_track_selection_changed (RouteNotificationListPtr routes) +FaderPort::stripable_selection_changed () { - if (routes->empty()) { - _current_route.reset (); - } else { - _current_route = routes->front().lock(); + set_current_stripable (ControlProtocol::first_selected_stripable()); +} + +void +FaderPort::drop_current_stripable () +{ + if (_current_stripable) { + if (_current_stripable == session->monitor_out()) { + set_current_stripable (session->master_out()); + } else { + set_current_stripable (boost::shared_ptr()); + } } +} + +void +FaderPort::set_current_stripable (boost::shared_ptr r) +{ + stripable_connections.drop_connections (); + + _current_stripable = r; - route_connections.drop_connections (); + /* turn this off. It will be turned on back on in use_master() or + use_monitor() as appropriate. + */ + get_button(Output).set_led_state (_output_port, false); - if (_current_route) { - _current_route->mute_changed.connect (route_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::map_mute, this, _1), this); - _current_route->solo_changed.connect (route_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::map_solo, this, _1, _2, _3), this); - _current_route->listen_changed.connect (route_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::map_listen, this, _1, _2), this); + if (_current_stripable) { + _current_stripable->DropReferences.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::drop_current_stripable, this), this); - boost::shared_ptr t = boost::dynamic_pointer_cast (_current_route); + _current_stripable->mute_control()->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::map_mute, this), this); + _current_stripable->solo_control()->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::map_solo, this), this); + + boost::shared_ptr t = boost::dynamic_pointer_cast (_current_stripable); if (t) { - t->RecordEnableChanged.connect (route_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::map_recenable, this), this); + t->rec_enable_control()->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::map_recenable, this), this); } - boost::shared_ptr control = _current_route->gain_control (); + boost::shared_ptr control = _current_stripable->gain_control (); if (control) { - control->Changed.connect (route_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::map_gain, this), this); + control->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::map_gain, this), this); + control->alist()->automation_state_changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::map_auto, this), this); + } + + boost::shared_ptr mp = _current_stripable->monitor_control(); + if (mp) { + mp->cut_control()->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::map_cut, this), this); } } - map_route_state (); + //ToDo: subscribe to the fader automation modes so we can light the LEDs + + map_stripable_state (); } void -FaderPort::map_mute (void*) +FaderPort::map_auto () { - button_info (Mute).set_led_state (_output_port, _current_route->muted()); + /* Under no circumstances send a message to "enable" the LED state of + * the Off button, because this will disable the fader. + */ + + boost::shared_ptr control = _current_stripable->gain_control (); + const AutoState as = control->automation_state (); + + switch (as) { + case ARDOUR::Play: + get_button (FP_Read).set_led_state (_output_port, true); + get_button (FP_Write).set_led_state (_output_port, false); + get_button (FP_Touch).set_led_state (_output_port, false); + break; + case ARDOUR::Write: + get_button (FP_Read).set_led_state (_output_port, false); + get_button (FP_Write).set_led_state (_output_port, true); + get_button (FP_Touch).set_led_state (_output_port, false); + break; + case ARDOUR::Touch: + get_button (FP_Read).set_led_state (_output_port, false); + get_button (FP_Write).set_led_state (_output_port, false); + get_button (FP_Touch).set_led_state (_output_port, true); + break; + case ARDOUR::Off: + get_button (FP_Read).set_led_state (_output_port, false); + get_button (FP_Write).set_led_state (_output_port, false); + get_button (FP_Touch).set_led_state (_output_port, false); + break; + } + } + void -FaderPort::map_solo (bool, void*, bool) +FaderPort::map_cut () { - button_info (Solo).set_led_state (_output_port, _current_route->soloed() || _current_route->listening_via_monitor()); + boost::shared_ptr mp = _current_stripable->monitor_control(); + + if (mp) { + bool yn = mp->cut_all (); + if (yn) { + start_blinking (Mute); + } else { + stop_blinking (Mute); + } + } else { + stop_blinking (Mute); + } } void -FaderPort::map_listen (void*, bool) +FaderPort::map_mute () { - button_info (Solo).set_led_state (_output_port, _current_route->listening_via_monitor()); + if (_current_stripable) { + if (_current_stripable->mute_control()->muted()) { + stop_blinking (Mute); + get_button (Mute).set_led_state (_output_port, true); + } else if (_current_stripable->mute_control()->muted_by_others_soloing () || _current_stripable->mute_control()->muted_by_masters()) { + start_blinking (Mute); + } else { + stop_blinking (Mute); + } + } else { + stop_blinking (Mute); + } +} + +void +FaderPort::map_solo () +{ + if (_current_stripable) { + get_button (Solo).set_led_state (_output_port, _current_stripable->solo_control()->soloed()); + } else { + get_button (Solo).set_led_state (_output_port, false); + } } void FaderPort::map_recenable () { - boost::shared_ptr t = boost::dynamic_pointer_cast (_current_route); + boost::shared_ptr t = boost::dynamic_pointer_cast (_current_stripable); if (t) { - button_info (Rec).set_led_state (_output_port, t->record_enabled()); + get_button (Rec).set_led_state (_output_port, t->rec_enable_control()->get_value()); } else { - button_info (Rec).set_led_state (_output_port, false); + get_button (Rec).set_led_state (_output_port, false); } } @@ -850,11 +1253,11 @@ FaderPort::map_gain () return; } - if (!_current_route) { + if (!_current_stripable) { return; } - boost::shared_ptr control = _current_route->gain_control (); + boost::shared_ptr control = _current_stripable->gain_control (); double val; if (!control) { @@ -893,18 +1296,59 @@ FaderPort::map_gain () } void -FaderPort::map_route_state () +FaderPort::map_stripable_state () { - if (!_current_route) { - button_info (Mute).set_led_state (_output_port, false); - button_info (Solo).set_led_state (_output_port, false); - button_info (Rec).set_led_state (_output_port, false); + if (!_current_stripable) { + stop_blinking (Mute); + stop_blinking (Solo); + get_button (Rec).set_led_state (_output_port, false); } else { - /* arguments to these map_*() methods are all ignored */ - map_mute (0); - map_solo (false, 0, false); + map_solo (); map_recenable (); - map_gain (); + map_auto (); + + if (_current_stripable == session->monitor_out()) { + map_cut (); + } else { + map_mute (); + } } } + +list > +FaderPort::bundles () +{ + list > b; + + if (_input_bundle) { + b.push_back (_input_bundle); + b.push_back (_output_bundle); + } + + return b; +} + +boost::shared_ptr +FaderPort::output_port() +{ + return _output_port; +} + +boost::shared_ptr +FaderPort::input_port() +{ + return _input_port; +} + +void +FaderPort::set_action (ButtonID id, std::string const& action_name, bool on_press, ButtonState bs) +{ + get_button(id).set_action (action_name, on_press, bs); +} + +string +FaderPort::get_action (ButtonID id, bool press, ButtonState bs) +{ + return get_button(id).get_action (press, bs); +}