ardour/libs/surfaces/faderport/faderport.cc
Paul Davis 0d9efc1148 redesign cross-thread registration/signalling system
This new design will work even when threads that need to receive
messages from RT threads are created *after* the RT threads. The
existing design would fail because the RT thread(s) would never
be known the later created threads, and so signals emitted by the
RT thread and causing call_slot() in the receiver would end up
being enqueued using a lock-protected list. The new design ensures
that communication always uses a lock-free FIFO instead
2015-12-28 10:14:17 -05:00

1345 lines
37 KiB
C++

/*
Copyright (C) 2015 Paul Davis
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <cstdlib>
#include <sstream>
#include <algorithm>
#include <stdint.h>
#include <glibmm/fileutils.h>
#include <glibmm/miscutils.h>
#include "pbd/controllable_descriptor.h"
#include "pbd/error.h"
#include "pbd/failed_constructor.h"
#include "pbd/file_utils.h"
#include "pbd/pthread_utils.h"
#include "pbd/compose.h"
#include "pbd/xml++.h"
#include "midi++/port.h"
#include "ardour/async_midi_port.h"
#include "ardour/audioengine.h"
#include "ardour/amp.h"
#include "ardour/bundle.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/session.h"
#include "ardour/session_configuration.h"
#include "ardour/track.h"
#include "faderport.h"
using namespace ARDOUR;
using namespace ArdourSurface;
using namespace PBD;
using namespace Glib;
using namespace std;
#include "i18n.h"
#include "pbd/abstract_ui.cc" // instantiate template
FaderPort::FaderPort (Session& s)
: ControlProtocol (s, _("Faderport"))
, AbstractUI<FaderPortRequest> (name())
, gui (0)
, connection_state (ConnectionState (0))
, _device_active (false)
, fader_msb (0)
, fader_lsb (0)
, fader_is_touched (false)
, button_state (ButtonState (0))
, blink_state (false)
{
last_encoder_time = 0;
boost::shared_ptr<ARDOUR::Port> inp;
boost::shared_ptr<ARDOUR::Port> outp;
inp = AudioEngine::instance()->register_input_port (DataType::MIDI, "Faderport Recv", true);
outp = AudioEngine::instance()->register_output_port (DataType::MIDI, "Faderport Send", true);
_input_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(inp);
_output_port = boost::dynamic_pointer_cast<AsyncMIDIPort>(outp);
if (_input_port == 0 || _output_port == 0) {
throw failed_constructor();
}
_input_bundle.reset (new ARDOUR::Bundle (_("Faderport Support (Receive)"), true));
_output_bundle.reset (new ARDOUR::Bundle (_("Faderport Support (Send) "), false));
_input_bundle->add_channel (
inp->name(),
ARDOUR::DataType::MIDI,
session->engine().make_port_name_non_relative (inp->name())
);
_output_bundle->add_channel (
outp->name(),
ARDOUR::DataType::MIDI,
session->engine().make_port_name_non_relative (outp->name())
);
TrackSelectionChanged.connect (selection_connection, MISSING_INVALIDATOR, boost::bind (&FaderPort::gui_track_selection_changed, this, _1), this);
/* 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, 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 (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.
*/
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 .. */
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), 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);
get_button (Punch).set_action (boost::bind (&FaderPort::punch, this), true);
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);
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, ShiftDown);
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 ()
{
if (_input_port) {
DEBUG_TRACE (DEBUG::FaderPort, string_compose ("unregistering input port %1\n", boost::shared_ptr<ARDOUR::Port>(_input_port)->name()));
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
DEBUG_TRACE (DEBUG::FaderPort, string_compose ("unregistering output port %1\n", boost::shared_ptr<ARDOUR::Port>(_output_port)->name()));
AudioEngine::instance()->unregister_port (_output_port);
_output_port.reset ();
}
tear_down_gui ();
}
void*
FaderPort::request_factory (uint32_t num_requests)
{
/* AbstractUI<T>::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
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 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 */
_input_port->parser()->controller.connect_same_thread (midi_connections, boost::bind (&FaderPort::fader_handler, this, _1, _2));
/* This connection means that whenever data is ready from the input
* port, the relevant thread will invoke our ::midi_input_handler()
* 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().attach (main_loop()->get_context());
}
void
FaderPort::stop_midi_handling ()
{
midi_connections.drop_connections ();
/* Note: the input handler is still active at this point, but we're no
* longer connected to any of the parser signals
*/
}
void
FaderPort::do_request (FaderPortRequest* req)
{
if (req->type == CallSlot) {
call_slot (MISSING_INVALIDATOR, req->the_slot);
} else if (req->type == Quit) {
stop ();
}
}
int
FaderPort::stop ()
{
BaseUI::quit ();
return 0;
}
void
FaderPort::thread_init ()
{
struct sched_param rtparam;
pthread_set_name (event_loop_name().c_str());
PBD::notify_event_loops_about_thread_creation (pthread_self(), event_loop_name(), 2048);
ARDOUR::SessionEvent::create_per_thread_pool (event_loop_name(), 128);
memset (&rtparam, 0, sizeof (rtparam));
rtparam.sched_priority = 9; /* XXX should be relative to audio (JACK) thread */
if (pthread_setschedparam (pthread_self(), SCHED_FIFO, &rtparam) != 0) {
// do we care? not particularly.
}
}
void
FaderPort::all_lights_out ()
{
for (ButtonMap::iterator b = buttons.begin(); b != buttons.end(); ++b) {
b->second.set_led_state (_output_port, false);
}
}
FaderPort::Button&
FaderPort::get_button (ButtonID id) const
{
ButtonMap::const_iterator b = buttons.find (id);
assert (b != buttons.end());
return const_cast<Button&>(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<Glib::TimeoutSource> 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::button_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb)
{
ButtonID id (ButtonID (tb->controller_number));
Button& button (get_button (id));
if (tb->value) {
buttons_down.insert (id);
} else {
buttons_down.erase (id);
button.timeout_connection.disconnect ();
}
ButtonState bs (ButtonState (0));
switch (id) {
case Shift:
/* set this bit on press, do NOT clear it on release */
if (tb->value) {
bs = ShiftDown;
}
break;
case Stop:
bs = StopDown;
break;
case Rewind:
bs = RewindDown;
break;
case User:
bs = UserDown;
break;
case FaderTouch:
fader_is_touched = tb->value;
if (_current_route) {
boost::shared_ptr<AutomationControl> gain = _current_route->gain_control ();
if (gain) {
framepos_t now = session->engine().sample_time();
if (tb->value) {
gain->start_touch (now);
} else {
gain->stop_touch (true, now);
}
}
}
break;
default:
if (tb->value) {
start_press_timeout (button, id);
}
break;
}
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%2 using %3%4\n", hex, button_state, bs, dec));
}
if (button.uses_flash()) {
button.set_led_state (_output_port, (int)tb->value);
}
set<ButtonID>::iterator c = consumed.find (id);
if (c == consumed.end()) {
button.invoke (button_state, tb->value ? true : false);
} else {
consumed.erase (c);
}
if (!tb->value && (id != Shift)) {
/* non-shift key was released, clear shift modifier */
button_state = ButtonState (button_state&~ShiftDown);
DEBUG_TRACE (DEBUG::FaderPort, "clear shift modifier\n");
}
if (!tb->value && (id != User)) {
consumed.insert (User);
DEBUG_TRACE (DEBUG::FaderPort, "clear user modifier\n");
}
}
void
FaderPort::encoder_handler (MIDI::Parser &, MIDI::pitchbend_t pb)
{
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_route) {
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<AutomationControl> trim = _current_route->trim()->gain_control ();
if (trim) {
float val = trim->get_user(); //for gain elements, the "user" value is in dB
val += delta;
trim->set_user(val);
}
} 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);
}
}
void
FaderPort::fader_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb)
{
bool was_fader = false;
if (tb->controller_number == 0x0) {
fader_msb = tb->value;
was_fader = true;
} else if (tb->controller_number == 0x20) {
fader_lsb = tb->value;
was_fader = true;
}
if (was_fader) {
if (_current_route) {
boost::shared_ptr<AutomationControl> gain = _current_route->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);
}
}
}
}
void
FaderPort::sysex_handler (MIDI::Parser &p, MIDI::byte *buf, size_t sz)
{
if (sz < 17) {
return;
}
if (buf[2] != 0x7f ||
buf[3] != 0x06 ||
buf[4] != 0x02 ||
buf[5] != 0x0 ||
buf[6] != 0x1 ||
buf[7] != 0x06 ||
buf[8] != 0x02 ||
buf[9] != 0x0 ||
buf[10] != 0x01 ||
buf[11] != 0x0) {
return;
}
_device_active = true;
DEBUG_TRACE (DEBUG::FaderPort, "FaderPort identified via MIDI Device Inquiry response\n");
/* put it into native mode */
MIDI::byte native[3];
native[0] = 0x91;
native[1] = 0x00;
native[2] = 0x64;
_output_port->write (native, 3, 0);
all_lights_out ();
/* catch up on state */
map_transport_state ();
map_recenable_state ();
}
int
FaderPort::set_active (bool yn)
{
DEBUG_TRACE (DEBUG::FaderPort, string_compose("Faderport::set_active init with yn: '%1'\n", yn));
if (yn == active()) {
return 0;
}
if (yn) {
/* start event loop */
BaseUI::run ();
connect_session_signals ();
Glib::RefPtr<Glib::TimeoutSource> blink_timeout = Glib::TimeoutSource::create (200); // milliseconds
blink_connection = blink_timeout->connect (sigc::mem_fun (*this, &FaderPort::blink));
blink_timeout->attach (main_loop()->get_context());
Glib::RefPtr<Glib::TimeoutSource> 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 ();
close ();
}
ControlProtocol::set_active (yn);
DEBUG_TRACE (DEBUG::FaderPort, string_compose("Faderport::set_active done with yn: '%1'\n", yn));
return 0;
}
bool
FaderPort::periodic ()
{
if (!_current_route) {
return true;
}
ARDOUR::AutoState gain_state = _current_route->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++) {
get_button(*b).set_led_state (_output_port, blink_state);
}
return true;
}
void
FaderPort::close ()
{
all_lights_out ();
stop_midi_handling ();
session_connections.drop_connections ();
port_connection.disconnect ();
blink_connection.disconnect ();
selection_connection.disconnect ();
route_connections.drop_connections ();
#if 0
route_connections.drop_connections ();
#endif
}
void
FaderPort::map_recenable_state ()
{
switch (session->record_status()) {
case Session::Disabled:
stop_blinking (RecEnable);
break;
case Session::Enabled:
start_blinking (RecEnable);
break;
case Session::Recording:
stop_blinking (RecEnable);
break;
}
}
void
FaderPort::map_transport_state ()
{
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::parameter_changed (string what)
{
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::connect_session_signals()
{
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);
}
bool
FaderPort::midi_input_handler (Glib::IOCondition ioc, boost::shared_ptr<ARDOUR::AsyncMIDIPort> port)
{
DEBUG_TRACE (DEBUG::FaderPort, string_compose ("something happend on %1\n", boost::shared_ptr<MIDI::Port>(port)->name()));
if (ioc & ~IO_IN) {
return false;
}
if (ioc & IO_IN) {
port->clear ();
DEBUG_TRACE (DEBUG::FaderPort, string_compose ("data available on %1\n", boost::shared_ptr<MIDI::Port>(port)->name()));
framepos_t now = session->engine().sample_time();
port->parse (now);
}
return true;
}
XMLNode&
FaderPort::get_state ()
{
XMLNode& node (ControlProtocol::get_state());
XMLNode* child;
child = new XMLNode (X_("Input"));
child->add_child_nocopy (boost::shared_ptr<ARDOUR::Port>(_input_port)->get_state());
node.add_child_nocopy (*child);
child = new XMLNode (X_("Output"));
child->add_child_nocopy (boost::shared_ptr<ARDOUR::Port>(_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());
return node;
}
int
FaderPort::set_state (const XMLNode& node, int version)
{
XMLNodeList nlist;
XMLNodeConstIterator niter;
XMLNode const* child;
if (ControlProtocol::set_state (node, version)) {
return -1;
}
if ((child = node.child (X_("Input"))) != 0) {
XMLNode* portnode = child->child (Port::state_node_name.c_str());
if (portnode) {
boost::shared_ptr<ARDOUR::Port>(_input_port)->set_state (*portnode, version);
}
}
if ((child = node.child (X_("Output"))) != 0) {
XMLNode* portnode = child->child (Port::state_node_name.c_str());
if (portnode) {
boost::shared_ptr<ARDOUR::Port>(_output_port)->set_state (*portnode, version);
}
}
for (XMLNodeList::const_iterator n = node.children().begin(); n != node.children().end(); ++n) {
if ((*n)->name() == X_("Button")) {
XMLProperty const * prop = (*n)->property (X_("id"));
if (!prop) {
continue;
}
int xid = atoi (prop->value());
ButtonMap::iterator b = buttons.find (ButtonID (xid));
if (b == buttons.end()) {
continue;
}
b->second.set_state (**n);
}
}
return 0;
}
bool
FaderPort::connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn)
{
DEBUG_TRACE (DEBUG::FaderPort, "FaderPort::connection_handler start\n");
if (!_input_port || !_output_port) {
return false;
}
string ni = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr<ARDOUR::Port>(_input_port)->name());
string no = ARDOUR::AudioEngine::instance()->make_port_name_non_relative (boost::shared_ptr<ARDOUR::Port>(_output_port)->name());
if (ni == name1 || ni == name2) {
if (yn) {
connection_state |= InputConnected;
} else {
connection_state &= ~InputConnected;
}
} else if (no == name1 || no == name2) {
if (yn) {
connection_state |= OutputConnected;
} else {
connection_state &= ~OutputConnected;
}
} else {
DEBUG_TRACE (DEBUG::FaderPort, string_compose ("Connections between %1 and %2 changed, but I ignored it\n", name1, name2));
/* not our ports */
return false;
}
if ((connection_state & (InputConnected|OutputConnected)) == (InputConnected|OutputConnected)) {
/* XXX this is a horrible hack. Without a short sleep here,
something prevents the device wakeup messages from being
sent and/or the responses from being received.
*/
g_usleep (100000);
connected ();
} else {
DEBUG_TRACE (DEBUG::FaderPort, "Device disconnected (input or output or both) or not yet fully connected\n");
_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 ()
{
DEBUG_TRACE (DEBUG::FaderPort, "connection status changed\n");
start_midi_handling ();
/* send device inquiry */
MIDI::byte buf[6];
buf[0] = 0xf0;
buf[1] = 0x7e;
buf[2] = 0x7f;
buf[3] = 0x06;
buf[4] = 0x01;
buf[5] = 0xf7;
_output_port->write (buf, 6, 0);
}
void
FaderPort::Button::invoke (FaderPort::ButtonState bs, bool press)
{
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\%4\n", id, hex, bs, dec));
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\%4\n", id, hex, bs, dec));
return;
}
}
switch (x->second.type) {
case NamedAction:
if (!x->second.action_name.empty()) {
fp.access_action (x->second.action_name);
}
break;
case InternalFunction:
if (x->second.function) {
x->second.function ();
}
}
}
void
FaderPort::Button::set_action (string const& name, bool when_pressed, FaderPort::ButtonState bs)
{
ToDo todo;
todo.type = NamedAction;
if (when_pressed) {
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 {
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::Button::set_action (boost::function<void()> f, bool when_pressed, FaderPort::ButtonState bs)
{
ToDo todo;
todo.type = InternalFunction;
if (when_pressed) {
todo.function = f;
on_press[bs] = todo;
} else {
todo.function = f;
on_release[bs] = todo;
}
}
void
FaderPort::Button::set_led_state (boost::shared_ptr<MIDI::Port> port, bool onoff)
{
if (out < 0) {
/* fader button ID - no LED */
return;
}
MIDI::byte buf[3];
buf[0] = 0xa0;
buf[1] = out;
buf[2] = onoff ? 1 : 0;
port->write (buf, 3, 0);
}
int
FaderPort::Button::set_state (XMLNode const& node)
{
const XMLProperty* prop = node.property ("id");
if (!prop) {
return -1;
}
int xid = atoi (prop->value());
if (xid != id) {
return -1;
}
typedef pair<string,FaderPort::ButtonState> state_pair_t;
vector<state_pair_t> 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));
on_press.clear ();
on_release.clear ();
for (vector<state_pair_t>::const_iterator sp = state_pairs.begin(); sp != state_pairs.end(); ++sp) {
string propname;
propname = sp->first + X_("-press");
if ((prop = node.property (propname)) != 0) {
set_action (prop->value(), true, sp->second);
}
propname = sp->first + X_("-release");
if ((prop = node.property (propname)) != 0) {
set_action (prop->value(), false, sp->second);
}
}
return 0;
}
XMLNode&
FaderPort::Button::get_state () const
{
XMLNode* node = new XMLNode (X_("Button"));
char buf[16];
snprintf (buf, sizeof (buf), "%d", id);
node->add_property (X_("id"), buf);
ToDoMap::const_iterator x;
ToDo null;
null.type = NamedAction;
typedef pair<string,FaderPort::ButtonState> state_pair_t;
vector<state_pair_t> 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<state_pair_t>::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->add_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->add_property (string (sp->first + X_("-release")).c_str(), x->second.action_name);
}
}
}
return *node;
}
void
FaderPort::gui_track_selection_changed (RouteNotificationListPtr routes)
{
boost::shared_ptr<Route> r;
if (!routes->empty()) {
r = routes->front().lock();
}
set_current_route (r);
}
void
FaderPort::drop_current_route ()
{
if (_current_route) {
if (_current_route == session->monitor_out()) {
set_current_route (session->master_out());
} else {
set_current_route (boost::shared_ptr<Route>());
}
}
}
void
FaderPort::set_current_route (boost::shared_ptr<Route> r)
{
route_connections.drop_connections ();
_current_route = r;
/* 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->DropReferences.connect (route_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::drop_current_route, this), this);
_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);
boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track> (_current_route);
if (t) {
t->RecordEnableChanged.connect (route_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::map_recenable, this), this);
}
boost::shared_ptr<AutomationControl> control = _current_route->gain_control ();
if (control) {
control->Changed.connect (route_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::map_gain, this), this);
control->alist()->automation_state_changed.connect (route_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::map_auto, this), this);
}
boost::shared_ptr<MonitorProcessor> mp = _current_route->monitor_control();
if (mp) {
mp->cut_control()->Changed.connect (route_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort::map_cut, this), this);
}
}
//ToDo: subscribe to the fader automation modes so we can light the LEDs
map_route_state ();
}
void
FaderPort::map_auto ()
{
/* Under no circumstances send a message to "enable" the LED state of
* the Off button, because this will disable the fader.
*/
boost::shared_ptr<AutomationControl> control = _current_route->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_cut ()
{
boost::shared_ptr<MonitorProcessor> mp = _current_route->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_mute (void*)
{
if (_current_route) {
if (_current_route->muted()) {
stop_blinking (Mute);
get_button (Mute).set_led_state (_output_port, true);
} else if (_current_route->muted_by_others()) {
start_blinking (Mute);
} else {
stop_blinking (Mute);
}
} else {
stop_blinking (Mute);
}
}
void
FaderPort::map_solo (bool, void*, bool)
{
if (_current_route) {
get_button (Solo).set_led_state (_output_port, _current_route->soloed() || _current_route->listening_via_monitor());
} else {
get_button (Solo).set_led_state (_output_port, false);
}
}
void
FaderPort::map_listen (void*, bool)
{
if (_current_route) {
get_button (Solo).set_led_state (_output_port, _current_route->listening_via_monitor());
} else {
get_button (Solo).set_led_state (_output_port, false);
}
}
void
FaderPort::map_recenable ()
{
boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track> (_current_route);
if (t) {
get_button (Rec).set_led_state (_output_port, t->record_enabled());
} else {
get_button (Rec).set_led_state (_output_port, false);
}
}
void
FaderPort::map_gain ()
{
if (fader_is_touched) {
/* Do not send fader moves while the user is touching the fader */
return;
}
if (!_current_route) {
return;
}
boost::shared_ptr<AutomationControl> control = _current_route->gain_control ();
double val;
if (!control) {
val = 0.0;
} else {
val = control->internal_to_interface (control->get_value ());
}
/* Faderport sends fader position with range 0..16384 (though some of
* the least-significant bits at the top end are missing - it may only
* get to 1636X or so).
*
* But ... position must be sent in the range 0..1023.
*
* Thanks, Obama.
*/
int ival = (int) lrintf (val * 1023.0);
/* MIDI normalization requires that we send two separate messages here,
* not one single 6 byte one.
*/
MIDI::byte buf[3];
buf[0] = 0xb0;
buf[1] = 0x0;
buf[2] = ival >> 7;
_output_port->write (buf, 3, 0);
buf[1] = 0x20;
buf[2] = ival & 0x7f;
_output_port->write (buf, 3, 0);
}
void
FaderPort::map_route_state ()
{
if (!_current_route) {
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_solo (false, 0, false);
map_recenable ();
map_gain ();
map_auto ();
if (_current_route == session->monitor_out()) {
map_cut ();
} else {
map_mute (0);
}
}
}
list<boost::shared_ptr<ARDOUR::Bundle> >
FaderPort::bundles ()
{
list<boost::shared_ptr<ARDOUR::Bundle> > b;
if (_input_bundle) {
b.push_back (_input_bundle);
b.push_back (_output_bundle);
}
return b;
}
boost::shared_ptr<Port>
FaderPort::output_port()
{
return _output_port;
}
boost::shared_ptr<Port>
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);
}