Faderport8 control surface support

This commit is contained in:
Robin Gareus 2017-04-05 11:04:16 +02:00
parent d64ca9be08
commit d43a23fe28
19 changed files with 5069 additions and 1 deletions

View File

@ -13,7 +13,7 @@ export GTK2_RC_FILES=/nonexistent
# can find all the components.
#
export ARDOUR_SURFACES_PATH=$libs/surfaces/osc:$libs/surfaces/faderport:$libs/surfaces/generic_midi:$libs/surfaces/tranzport:$libs/surfaces/powermate:$libs/surfaces/mackie:$libs/surfaces/wiimote:$libs/surfaces/push2
export ARDOUR_SURFACES_PATH=$libs/surfaces/osc:$libs/surfaces/faderport8:$libs/surfaces/faderport:$libs/surfaces/generic_midi:$libs/surfaces/tranzport:$libs/surfaces/powermate:$libs/surfaces/mackie:$libs/surfaces/wiimote:$libs/surfaces/push2
export ARDOUR_PANNER_PATH=$libs/panners
export ARDOUR_DATA_PATH=$TOP:$TOP/build:$TOP/gtk2_ardour:$TOP/build/gtk2_ardour:.
export ARDOUR_MIDIMAPS_PATH=$TOP/midi_maps:.

View File

@ -80,6 +80,7 @@ namespace PBD {
LIBARDOUR_API extern DebugBits BackendPorts;
LIBARDOUR_API extern DebugBits VSTCallbacks;
LIBARDOUR_API extern DebugBits FaderPort;
LIBARDOUR_API extern DebugBits FaderPort8;
LIBARDOUR_API extern DebugBits CC121;
LIBARDOUR_API extern DebugBits VCA;
LIBARDOUR_API extern DebugBits Push2;

View File

@ -77,6 +77,7 @@ PBD::DebugBits PBD::DEBUG::BackendThreads = PBD::new_debug_bit ("backendthreads"
PBD::DebugBits PBD::DEBUG::BackendPorts = PBD::new_debug_bit ("backendports");
PBD::DebugBits PBD::DEBUG::VSTCallbacks = PBD::new_debug_bit ("vstcallbacks");
PBD::DebugBits PBD::DEBUG::FaderPort = PBD::new_debug_bit ("faderport");
PBD::DebugBits PBD::DEBUG::FaderPort8 = PBD::new_debug_bit ("faderport8");
PBD::DebugBits PBD::DEBUG::CC121 = PBD::new_debug_bit ("cc121");
PBD::DebugBits PBD::DEBUG::VCA = PBD::new_debug_bit ("vca");
PBD::DebugBits PBD::DEBUG::Push2 = PBD::new_debug_bit ("push2");

View File

@ -896,6 +896,7 @@ PortManager::port_is_control_only (std::string const& name)
const char * const control_only_ports[] = {
X_(".*Ableton Push.*"),
X_(".*FaderPort .*"),
X_(".*FaderPort8 .*"),
};
pattern = "(";

View File

@ -0,0 +1,470 @@
/* Faderport 8 Control Surface
* This is the button "Controller" of the MVC surface inteface,
* see callbacks.cc for the "View".
*
* Copyright (C) 2017 Robin Gareus <robin@gareus.org>
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "ardour/dB.h"
#include "ardour/session.h"
#include "ardour/session_configuration.h"
#include "ardour/types.h"
#include "gtkmm2ext/actions.h"
#include "faderport8.h"
#include "pbd/i18n.h"
using namespace ARDOUR;
using namespace ArdourSurface;
using namespace std;
using namespace ArdourSurface::FP8Types;
#define BindMethod(ID, CB) \
_ctrls.button (FP8Controls::ID).released.connect_same_thread (button_connections, boost::bind (&FaderPort8:: CB, this));
#define BindFunction(ID, ACT, CB, ...) \
_ctrls.button (FP8Controls::ID). ACT .connect_same_thread (button_connections, boost::bind (&FaderPort8:: CB, this, __VA_ARGS__));
#define BindAction(ID, GRP, ITEM) \
_ctrls.button (FP8Controls::ID).released.connect_same_thread (button_connections, boost::bind (&FaderPort8::button_action, this, GRP, ITEM));
#define BindUserAction(ID) \
_ctrls.button (ID).pressed.connect_same_thread (button_connections, boost::bind (&FaderPort8::button_user, this, true, ID)); \
_ctrls.button (ID).released.connect_same_thread (button_connections, boost::bind (&FaderPort8::button_user, this, false, ID));
void
FaderPort8::setup_actions ()
{
BindMethod (BtnPlay, button_play);
BindMethod (BtnStop, button_stop);
BindMethod (BtnLoop, button_loop);
BindMethod (BtnRecord, button_record);
BindMethod (BtnClick, button_metronom);
BindAction (BtnRedo, "Editor", "redo");
BindAction (BtnSave, "Common", "Save");
BindAction (BtnUndo, "Editor", "undo");
BindAction (BtnRedo, "Editor", "redo");
BindAction (BtnSoloClear, "Main", "cancel-solo");
BindMethod (BtnMuteClear, button_mute_clear);
BindMethod (FP8Controls::BtnArmAll, button_arm_all);
BindFunction (BtnRewind, pressed, button_varispeed, false);
BindFunction (BtnFastForward, pressed, button_varispeed, true);
BindFunction (BtnPrev, released, button_prev_next, false);
BindFunction (BtnNext, released, button_prev_next, true);
BindFunction (BtnArm, pressed, button_arm, true);
BindFunction (BtnArm, released, button_arm, false);
BindFunction (BtnAOff, released, button_automation, ARDOUR::Off);
BindFunction (BtnATouch, released, button_automation, ARDOUR::Touch);
BindFunction (BtnARead, released, button_automation, ARDOUR::Play);
BindFunction (BtnAWrite, released, button_automation, ARDOUR::Write);
_ctrls.button (FP8Controls::BtnEncoder).pressed.connect_same_thread (button_connections, boost::bind (&FaderPort8::button_encoder, this));
_ctrls.button (FP8Controls::BtnParam).pressed.connect_same_thread (button_connections, boost::bind (&FaderPort8::button_parameter, this));
BindAction (BtnBypass, "Mixer", "ab-plugins");
BindAction (BtnBypassAll, "Mixer", "ab-plugins"); // XXX
BindAction (BtnMacro, "Mixer", "show-editor");
BindAction (BtnLink, "Window", "show-mixer");
BindAction (BtnOpen, "Common", "addExistingAudioFiles");
BindAction (BtnLock, "Editor", "lock");
// user-specific
for (FP8Controls::UserButtonMap::const_iterator i = _ctrls.user_buttons ().begin ();
i != _ctrls.user_buttons ().end (); ++i) {
BindUserAction ((*i).first);
}
}
void
FaderPort8::button_play ()
{
if (session->transport_rolling ()) {
if (session->transport_speed () != 1.0) {
session->request_transport_speed (1.0);
} else {
transport_stop ();
}
} else {
transport_play ();
}
}
void
FaderPort8::button_stop ()
{
transport_stop ();
}
void
FaderPort8::button_record ()
{
set_record_enable (!get_record_enabled ());
}
void
FaderPort8::button_loop ()
{
loop_toggle ();
}
void
FaderPort8::button_metronom ()
{
Config->set_clicking (!Config->get_clicking ());
}
void
FaderPort8::button_automation (ARDOUR::AutoState as)
{
FaderMode fadermode = _ctrls.fader_mode ();
switch (fadermode) {
case ModePlugins:
#if 0 // Plugin Control Automation Mode
for ( std::list <ProcessorCtrl>::iterator i = _proc_params.begin(); i != _proc_params.end(); ++i) {
((*i).ac)->set_automation_state (as);
}
#endif
return;
case ModeSend:
if (first_selected_stripable()) {
#if 0 // Send Level Automation
boost::shared_ptr<Stripable> s = first_selected_stripable();
boost::shared_ptr<AutomationControl> send;
uint32_t i = 0;
while (0 != (send = s->send_level_controllable (i))) {
send->set_automation_state (as);
++i;
}
#endif
}
return;
default:
break;
}
// apply to all selected tracks
StripableList all;
session->get_stripables (all);
for (StripableList::const_iterator i = all.begin(); i != all.end(); ++i) {
if ((*i)->is_master() || (*i)->is_monitor()) {
continue;
}
if (!(*i)->is_selected()) {
continue;
}
boost::shared_ptr<AutomationControl> ac;
switch (fadermode) {
case ModeTrack:
ac = (*i)->gain_control ();
break;
case ModePan:
ac = (*i)->pan_azimuth_control ();
break;
default:
break;
}
if (ac) {
ac->set_automation_state (as);
}
}
}
void
FaderPort8::button_varispeed (bool ffw)
{
/* pressing both rew + ffwd -> return to zero */
FP8ButtonInterface& b_rew = _ctrls.button (FP8Controls::BtnRewind);
FP8ButtonInterface& b_ffw = _ctrls.button (FP8Controls::BtnFastForward);
if (b_rew.is_pressed () && b_ffw.is_pressed ()){
// stop key-repeat
dynamic_cast<FP8RepeatButton*>(&b_ffw)->stop_repeat();
dynamic_cast<FP8RepeatButton*>(&b_rew)->stop_repeat();
AccessAction ("Transport", "GotoStart");
return;
}
// switch play direction, if needed
if (ffw) {
if (session->transport_speed () <= 0) {
session->request_transport_speed (1.0);
return ;
}
} else {
if (session->transport_speed () >= 0) {
session->request_transport_speed (-1.0);
return ;
}
}
// incremetally increase speed. double speed every 10 clicks
// (keypress auto-repeat is 100ms)
float maxspeed = Config->get_shuttle_max_speed();
float speed = exp2f(0.1f) * session->transport_speed ();
speed = std::max (-maxspeed, std::min (maxspeed, speed));
session->request_transport_speed (speed, false);
}
void
FaderPort8::button_mute_clear ()
{
StripableList all;
session->get_stripables (all);
boost::shared_ptr<ControlList> cl (new ControlList);
for (StripableList::const_iterator i = all.begin(); i != all.end(); ++i) {
if ((*i)->is_master() || (*i)->is_monitor()) {
continue;
}
boost::shared_ptr<AutomationControl> ac = (*i)->mute_control();
if (ac) {
cl->push_back (ac);
}
}
session->set_controls (cl, 0.0, PBD::Controllable::UseGroup);
}
void
FaderPort8::button_arm (bool press)
{
FaderMode fadermode = _ctrls.fader_mode ();
if (fadermode == ModeTrack || fadermode == ModePan) {
_ctrls.button (FP8Controls::BtnArm).set_active (press);
ARMButtonChange (press);
}
}
void
FaderPort8::button_arm_all ()
{
BasicUI::all_tracks_rec_in ();
}
void
FaderPort8::button_action (const std::string& group, const std::string& item)
{
AccessAction (group, item);
}
void
FaderPort8::button_prev_next (bool next)
{
switch (_ctrls.nav_mode()) {
case NavMaster:
case NavChannel:
case NavScroll:
bank (!next, false);
break;
case NavBank:
bank (!next, true);
break;
case NavZoom:
if (next) {
StepTracksDown ();
} else {
StepTracksUp ();
}
break;
case NavSection:
// TODO nudge
break;
case NavMarker:
if (next) {
next_marker ();
} else {
prev_marker ();
}
break;
}
}
/* handle navigation encoder press */
void
FaderPort8::button_encoder ()
{
switch (_ctrls.nav_mode()) {
case NavZoom:
ZoomToSession (); // XXX undo zoom
break;
case NavScroll:
ZoomToSession ();
break;
case NavChannel:
case NavBank:
move_selected_into_view ();
break;
case NavMaster:
{
/* master || monitor level -- reset to 0dB */
boost::shared_ptr<AutomationControl> ac;
if (session->monitor_active() && !_ctrls.button (FP8Controls::BtnMaster).is_pressed ()) {
ac = session->monitor_out()->gain_control ();
} else if (session->master_out()) {
ac = session->master_out()->gain_control ();
}
if (ac) {
ac->set_value (ac->normal(), PBD::Controllable::NoGroup);
}
}
break;
case NavSection:
break;
case NavMarker:
{
string markername;
/* Don't add another mark if one exists within 1/100th of a second of
* the current position and we're not rolling.
*/
framepos_t where = session->audible_frame();
if (session->transport_stopped() && session->locations()->mark_at (where, session->frame_rate() / 100.0)) {
return;
}
session->locations()->next_available_name (markername,"mark");
add_marker (markername);
}
break;
}
}
/* handle navigation encoder turn */
void
FaderPort8::encoder_navigate (bool neg, int steps)
{
/* special-case metronome level */
if (_ctrls.button (FP8Controls::BtnClick).is_pressed ()) {
// compare to ARDOUR_UI::click_button_scroll()
gain_t gain = Config->get_click_gain();
float gain_db = accurate_coefficient_to_dB (gain);
gain_db += (neg ? -1.f : 1.f) * steps;
gain_db = std::max (-60.f, gain_db);
gain = dB_to_coefficient (gain_db);
gain = std::min (gain, Config->get_max_gain());
Config->set_click_gain (gain);
_ctrls.button (FP8Controls::BtnClick).ignore_release();
return;
}
switch (_ctrls.nav_mode()) {
case NavChannel:
if (neg) {
StepTracksUp ();
} else {
StepTracksDown ();
}
break;
case NavZoom:
if (neg) {
ZoomOut ();
} else {
ZoomIn ();
}
break;
case NavMarker:
case NavScroll:
ScrollTimeline ((neg ? -1.f : 1.f) * steps / (shift_mod() ? 1024.f : 256.f));
break;
case NavBank:
bank (neg, false);
break;
case NavMaster:
{
/* master || monitor level */
boost::shared_ptr<AutomationControl> ac;
if (session->monitor_active() && !_ctrls.button (FP8Controls::BtnMaster).is_pressed ()) {
ac = session->monitor_out()->gain_control ();
} else if (session->master_out()) {
ac = session->master_out()->gain_control ();
}
if (ac) {
double v = ac->internal_to_interface (ac->get_value());
v = std::max (0.0, std::min (1.0, v + steps * (neg ? -.01 : .01)));
ac->set_value (ac->interface_to_internal(v), PBD::Controllable::NoGroup);
}
}
break;
case NavSection:
// nudge event
break;
}
}
/* handle pan/param encoder press */
void
FaderPort8::button_parameter ()
{
switch (_ctrls.fader_mode()) {
case ModeTrack:
case ModePan:
// pan-width see FaderPort8::encoder_parameter()
break;
case ModePlugins:
break;
case ModeSend:
break;
}
}
/* handle pan/param encoder turn */
void
FaderPort8::encoder_parameter (bool neg, int steps)
{
switch (_ctrls.fader_mode()) {
case ModeTrack:
case ModePan:
{
boost::shared_ptr<Stripable> s = first_selected_stripable();
if (s) {
boost::shared_ptr<AutomationControl> ac;
if (_ctrls.button (FP8Controls::BtnParam).is_pressed ()) {
ac = s->pan_width_control ();
} else {
ac = s->pan_azimuth_control ();
}
if (ac) {
double v = ac->internal_to_interface (ac->get_value());
v = std::max (0.0, std::min (1.0, v + steps * (neg ? -.01 : .01)));
ac->set_value (ac->interface_to_internal(v), PBD::Controllable::UseGroup);
}
}
}
break;
case ModePlugins:
case ModeSend:
while (steps > 0) {
bank_param (neg, false);
--steps;
}
break;
}
}
/* handle user-specific actions */
void
FaderPort8::button_user (bool press, FP8Controls::ButtonId btn)
{
_user_action_map[btn].call (*this, press);
}

View File

@ -0,0 +1,206 @@
/* Faderport 8 Control Surface
* This is the button "View" of the MVC surface inteface,
* see actions.cc for the "Controller"
*
* Copyright (C) 2017 Robin Gareus <robin@gareus.org>
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "ardour/session.h"
#include "ardour/session_configuration.h"
#include "gtkmm2ext/actions.h"
#include "faderport8.h"
#include "pbd/i18n.h"
using namespace ARDOUR;
using namespace ArdourSurface;
using namespace ArdourSurface::FP8Types;
void
FaderPort8::connect_session_signals ()
{
session->RouteAdded.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::notify_stripable_added_or_removed, this), this);
PresentationInfo::Change.connect(session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::notify_pi_property_changed, this, _1), this);
Config->ParameterChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::notify_parameter_changed, this, _1), this);
session->config.ParameterChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::notify_parameter_changed, this, _1), this);
session->TransportStateChange.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::notify_transport_state_changed, this), this);
session->TransportLooped.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::notify_loop_state_changed, this), this);
session->RecordStateChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::notify_record_state_changed, this), this);
session->DirtyChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::notify_session_dirty_changed, this), this);
session->SoloChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::notify_solo_changed, this), this);
session->MuteChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::notify_mute_changed, this), this);
session->history().Changed.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&FaderPort8::notify_history_changed, this), this);
}
void
FaderPort8::send_session_state ()
{
notify_transport_state_changed ();
notify_record_state_changed ();
notify_session_dirty_changed ();
notify_history_changed ();
notify_solo_changed ();
notify_mute_changed ();
notify_parameter_changed ("clicking");
notify_automation_mode_changed (); // XXX (stip specific, see below)
}
// TODO: AutomationState display of plugin & send automation ?!
void
FaderPort8::notify_automation_mode_changed ()
{
boost::shared_ptr<Stripable> s = first_selected_stripable();
boost::shared_ptr<AutomationControl> ac;
if (s) {
switch (_ctrls.fader_mode ()) {
case ModeTrack:
ac = s->gain_control();
break;
case ModePan:
ac = s->pan_azimuth_control();
break;
default:
break;
}
}
if (!s || !ac) {
_ctrls.button (FP8Controls::BtnALatch).set_active (false);
_ctrls.button (FP8Controls::BtnATrim).set_active (false);
_ctrls.button (FP8Controls::BtnAOff).set_active (false);
_ctrls.button (FP8Controls::BtnATouch).set_active (false);
_ctrls.button (FP8Controls::BtnARead).set_active (false);
_ctrls.button (FP8Controls::BtnAWrite).set_active (false);
return;
}
ARDOUR::AutoState as = ac->automation_state();
_ctrls.button (FP8Controls::BtnAOff).set_active (as == Off);
_ctrls.button (FP8Controls::BtnATouch).set_active (as == Touch);
_ctrls.button (FP8Controls::BtnARead).set_active (as == Play);
_ctrls.button (FP8Controls::BtnAWrite).set_active (as == Write);
}
void
FaderPort8::notify_parameter_changed (std::string param)
{
if (param == "clicking") {
_ctrls.button (FP8Controls::BtnClick).set_active (Config->get_clicking ());
}
}
void
FaderPort8::notify_transport_state_changed ()
{
if (session->transport_rolling ()) {
_ctrls.button (FP8Controls::BtnPlay).set_active (true);
_ctrls.button (FP8Controls::BtnStop).set_active (false);
} else {
_ctrls.button (FP8Controls::BtnPlay).set_active (false);
_ctrls.button (FP8Controls::BtnStop).set_active (true);
}
/* set rewind/fastforward lights */
const float ts = session->transport_speed ();
FP8ButtonInterface& b_rew = _ctrls.button (FP8Controls::BtnRewind);
FP8ButtonInterface& b_ffw = _ctrls.button (FP8Controls::BtnFastForward);
const bool rew = (ts < 0.f);
const bool ffw = (ts > 0.f && ts != 1.f);
if (b_rew.is_active() != rew) {
b_rew.set_active (rew);
}
if (b_ffw.is_active() != ffw) {
b_ffw.set_active (ffw);
}
notify_loop_state_changed ();
}
void
FaderPort8::notify_record_state_changed ()
{
switch (session->record_status ()) {
case Session::Disabled:
_ctrls.button (FP8Controls::BtnRecord).set_active (0);
_ctrls.button (FP8Controls::BtnRecord).set_blinking (false);
break;
case Session::Enabled:
_ctrls.button (FP8Controls::BtnRecord).set_active (true);
_ctrls.button (FP8Controls::BtnRecord).set_blinking (true);
break;
case Session::Recording:
_ctrls.button (FP8Controls::BtnRecord).set_active (true);
_ctrls.button (FP8Controls::BtnRecord).set_blinking (false);
break;
}
}
void
FaderPort8::notify_loop_state_changed ()
{
bool looping = false;
Location* looploc = session->locations ()->auto_loop_location ();
if (looploc && session->get_play_loop ()) {
looping = true;
}
_ctrls.button (FP8Controls::BtnLoop).set_active (looping);
}
void
FaderPort8::notify_session_dirty_changed ()
{
const bool is_dirty = session->dirty ();
_ctrls.button (FP8Controls::BtnSave).set_active (is_dirty);
_ctrls.button (FP8Controls::BtnSave).set_color (is_dirty ? 0xff0000ff : 0x00ff00ff);
}
void
FaderPort8::notify_history_changed ()
{
_ctrls.button (FP8Controls::BtnRedo).set_active (session->redo_depth() > 0);
_ctrls.button (FP8Controls::BtnUndo).set_active (session->undo_depth() > 0);
}
void
FaderPort8::notify_solo_changed ()
{
_ctrls.button (FP8Controls::BtnSoloClear).set_active (session->soloing() || session->listening());
}
void
FaderPort8::notify_mute_changed ()
{
bool muted = false;
boost::shared_ptr<RouteList> rl = session->get_routes();
for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
if ((*i)->is_master() || (*i)->is_monitor()) {
continue;
}
boost::shared_ptr<MuteControl> mc = (*i)->mute_control();
if (mc && mc->muted ()) {
muted = true;
break;
}
}
_ctrls.button (FP8Controls::BtnMuteClear).set_active (muted);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,334 @@
/*
* Copyright (C) 2017 Robin Gareus <robin@gareus.org>
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef ardour_surface_faderport8_h
#define ardour_surface_faderport8_h
#include <list>
#include <map>
#include <glibmm/threads.h>
#define ABSTRACT_UI_EXPORTS
#include "pbd/abstract_ui.h"
#include "pbd/properties.h"
#include "ardour/types.h"
#include "ardour/async_midi_port.h"
#include "ardour/midi_port.h"
#include "control_protocol/control_protocol.h"
#include "fp8_base.h"
#include "fp8_controls.h"
namespace MIDI {
class Parser;
}
namespace ARDOUR {
class Bundle;
class Session;
class Processor;
}
namespace ArdourSurface {
struct FaderPort8Request : public BaseUI::BaseRequestObject
{
public:
FaderPort8Request () {}
~FaderPort8Request () {}
};
class FaderPort8 : public FP8Base, public ARDOUR::ControlProtocol, public AbstractUI<FaderPort8Request>
{
public:
FaderPort8 (ARDOUR::Session&);
virtual ~FaderPort8();
int set_active (bool yn);
/* we probe for a device when our ports are connected. Before that,
* there's no way to know if the device exists or not.
*/
static bool probe() { return true; }
static void* request_factory (uint32_t);
XMLNode& get_state ();
int set_state (const XMLNode&, int version);
/* configuration GUI */
bool has_editor () const { return true; }
void* get_gui () const;
void tear_down_gui ();
PBD::Signal0<void> ConnectionChange;
void set_button_action (FP8Controls::ButtonId, bool, std::string const&);
std::string get_button_action (FP8Controls::ButtonId, bool);
FP8Controls const& control () const { return _ctrls; }
int stop ();
void do_request (FaderPort8Request*);
void thread_init ();
boost::shared_ptr<ARDOUR::Port> input_port() const { return _input_port; }
boost::shared_ptr<ARDOUR::Port> output_port() const { return _output_port; }
std::list<boost::shared_ptr<ARDOUR::Bundle> > bundles ();
size_t tx_midi (std::vector<uint8_t> const&) const;
private:
void close ();
void start_midi_handling ();
void stop_midi_handling ();
/* I/O Ports */
PBD::ScopedConnection port_connection;
boost::shared_ptr<ARDOUR::AsyncMIDIPort> _input_port;
boost::shared_ptr<ARDOUR::AsyncMIDIPort> _output_port;
boost::shared_ptr<ARDOUR::Bundle> _input_bundle;
boost::shared_ptr<ARDOUR::Bundle> _output_bundle;
bool midi_input_handler (Glib::IOCondition ioc, boost::weak_ptr<ARDOUR::AsyncMIDIPort> port);
bool connection_handler (boost::weak_ptr<ARDOUR::Port>, std::string name1, boost::weak_ptr<ARDOUR::Port>, std::string name2, bool yn);
enum ConnectionState {
InputConnected = 0x1,
OutputConnected = 0x2
};
void connected ();
void disconnected ();
int _connection_state;
bool _device_active;
/* MIDI input message handling */
void sysex_handler (MIDI::Parser &p, MIDI::byte *, size_t);
void polypressure_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb);
void pitchbend_handler (MIDI::Parser &, uint8_t chan, MIDI::pitchbend_t pb);
void controller_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb);
void note_on_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb);
void note_off_handler (MIDI::Parser &, MIDI::EventTwoBytes* tb);
PBD::ScopedConnectionList midi_connections;
/* ***************************************************************************
* Control Elements
*/
FP8Controls _ctrls;
void notify_stripable_added_or_removed ();
void notify_fader_mode_changed ();
void filter_stripables (ARDOUR::StripableList& strips) const;
void assign_stripables ();
void set_periodic_display_mode (FP8Strip::DisplayMode);
void assign_strips (bool reset_bank);
void bank (bool down, bool page);
void move_selected_into_view ();
void assign_sends ();
void spill_plugins ();
void assign_processor_ctrls ();
void build_well_known_processor_ctrls (boost::shared_ptr<ARDOUR::Stripable>, bool);
void select_plugin (int num);
void bank_param (bool down, bool page);
/* bank offsets */
int _channel_off;
int _plugin_off;
int _parameter_off;
/* plugin + send mode stripable
*
* This is used when parameters of one strip are assigned to
* individual FP8Strip controls (Edit Send, Edit Plugins).
*
* When there's one stripable per FP8Strip, FP8Strip itself keeps
* track of the object lifetime and these are NULL.
*/
PBD::ScopedConnectionList processor_connections;
PBD::ScopedConnectionList assigned_stripable_connections;
typedef std::map<boost::shared_ptr<ARDOUR::Stripable>, uint8_t> StripAssignmentMap;
StripAssignmentMap _assigned_strips;
void drop_ctrl_connections ();
void select_strip (boost::weak_ptr<ARDOUR::Stripable>);
void notify_pi_property_changed (const PBD::PropertyChange&);
void notify_stripable_property_changed (boost::weak_ptr<ARDOUR::Stripable>, const PBD::PropertyChange&);
void gui_track_selection_changed ();
PBD::ScopedConnection selection_connection;
PBD::ScopedConnectionList automation_state_connections;
PBD::ScopedConnectionList modechange_connections;
/* **************************************************************************/
struct ProcessorCtrl {
ProcessorCtrl (std::string const &n, boost::shared_ptr<ARDOUR::AutomationControl> c)
: name (n)
, ac (c)
{}
std::string name;
boost::shared_ptr<ARDOUR::AutomationControl> ac;
};
std::list <ProcessorCtrl> _proc_params;
/* **************************************************************************/
/* periodic updates, parameter poll */
sigc::connection _periodic_connection;
bool periodic ();
std::string _timecode;
std::string const& timecode () const { return _timecode; }
/* sync button blink -- the FP's blink mode does not work */
sigc::connection _blink_connection;
bool _blink_onoff;
bool blink_it ();
/* shift key */
sigc::connection _shift_connection;
bool _shift_lock;
bool _shift_pressed;
bool shift_timeout () { _shift_lock = true; return false; }
bool shift_mod () const { return _shift_lock | _shift_pressed; }
/* GUI */
void build_gui ();
mutable void *gui;
/* setup callbacks & actions */
void connect_session_signals ();
void setup_actions ();
void send_session_state ();
/* callbacks */
PBD::ScopedConnectionList session_connections;
void notify_parameter_changed (std::string);
void notify_record_state_changed ();
void notify_transport_state_changed ();
void notify_loop_state_changed ();
void notify_snap_change ();
void notify_session_dirty_changed ();
void notify_history_changed ();
void notify_solo_changed ();
void notify_mute_changed ();
void notify_automation_mode_changed ();
/* actions */
PBD::ScopedConnectionList button_connections;
void button_play ();
void button_stop ();
void button_record ();
void button_loop ();
void button_metronom ();
void button_varispeed (bool);
void button_mute_clear ();
void button_arm (bool);
void button_arm_all ();
void button_automation (ARDOUR::AutoState);
void button_prev_next (bool);
void button_action (const std::string& group, const std::string& item);
void button_encoder ();
void button_parameter ();
void encoder_navigate (bool, int);
void encoder_parameter (bool, int);
/* user bound actions */
void button_user (bool, FP8Controls::ButtonId);
enum ActionType {
Unset,
NamedAction,
// InternalFunction, // unused
};
struct UserAction {
UserAction () : _type (Unset) {}
ActionType _type;
std::string _action_name;
//boost::function<void()> function; // unused
void clear ()
{
_type = Unset;
_action_name.clear();
}
void assign_action (std::string const& action_name)
{
if (action_name.empty ()) {
_type = Unset;
_action_name.clear();
} else {
_type = NamedAction;
_action_name = action_name;
}
}
bool empty () const
{
return _type == Unset;
}
void call (FaderPort8& _base) const
{
switch (_type) {
case NamedAction:
_base.access_action (_action_name);
break;
default:
break;
}
}
};
struct ButtonAction {
UserAction on_press;
UserAction on_release;
UserAction& action (bool press)
{
return press ? on_press : on_release;
}
UserAction const& action (bool press) const
{
return press ? on_press : on_release;
}
void call (FaderPort8& _base, bool press) const
{
action (press).call (_base);
}
bool empty () const
{
return on_press.empty () && on_release.empty();
}
};
typedef std::map<FP8Controls::ButtonId, ButtonAction> UserActionMap;
UserActionMap _user_action_map;
};
} /* namespace */
#endif /* ardour_surface_faderport8_h */

View File

@ -0,0 +1,81 @@
/*
* Copyright (C) 2017 Robin Gareus <robin@gareus.org>
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <pbd/failed_constructor.h>
#include "control_protocol/control_protocol.h"
#include "faderport8.h"
using namespace ARDOUR;
using namespace ArdourSurface;
static ControlProtocol*
new_faderport8_midi_protocol (ControlProtocolDescriptor* /*descriptor*/, Session* s)
{
FaderPort8* fp;
try {
fp = new FaderPort8 (*s);
} catch (failed_constructor& err) {
return 0;
}
if (fp->set_active (true)) {
delete fp;
return 0;
}
return fp;
}
static void
delete_faderport8_midi_protocol (ControlProtocolDescriptor* /*descriptor*/, ControlProtocol* cp)
{
delete cp;
}
static bool
probe_faderport8_midi_protocol (ControlProtocolDescriptor* /*descriptor*/)
{
return FaderPort8::probe ();
}
static void*
faderport8_request_buffer_factory (uint32_t num_requests)
{
return FaderPort8::request_factory (num_requests);
}
static ControlProtocolDescriptor faderport8_midi_descriptor = {
/*name : */ "PreSonus FaderPort8",
/*id : */ "uri://ardour.org/surfaces/faderport8:0",
/*ptr : */ 0,
/*module : */ 0,
/*mandatory : */ 0,
/*supports_feedback : */ true,
/*probe : */ probe_faderport8_midi_protocol,
/*initialize : */ new_faderport8_midi_protocol,
/*destroy : */ delete_faderport8_midi_protocol,
/*request_buffer_factory */ faderport8_request_buffer_factory
};
extern "C" ARDOURSURFACE_API
ControlProtocolDescriptor* protocol_descriptor () {
return &faderport8_midi_descriptor;
}

View File

@ -0,0 +1,151 @@
/*
* Copyright (C) 2017 Robin Gareus <robin@gareus.org>
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _ardour_surfaces_fp8base_h_
#define _ardour_surfaces_fp8base_h_
#include <stdint.h>
#include <vector>
#include "pbd/signals.h"
namespace ArdourSurface {
/* conveniece wrappers depending on "FP8Base& _base" */
#define fp8_loop dynamic_cast<BaseUI*>(&_base)->main_loop
#define fp8_context() dynamic_cast<BaseUI*>(&_base)
#define fp8_protocol() dynamic_cast<ControlProtocol*>(&_base)
class FP8Base
{
public:
virtual ~FP8Base() {}
virtual size_t tx_midi (std::vector<uint8_t> const&) const = 0;
virtual std::string const& timecode () const = 0;
size_t tx_midi2 (uint8_t sb, uint8_t d1) const
{
std::vector<uint8_t> d;
d.push_back (sb);
d.push_back (d1);
return tx_midi (d);
}
size_t tx_midi3 (uint8_t sb, uint8_t d1, uint8_t d2) const
{
std::vector<uint8_t> d;
d.push_back (sb);
d.push_back (d1);
d.push_back (d2);
return tx_midi (d);
}
size_t tx_sysex (size_t count, ...)
{
std::vector<uint8_t> d;
sysexhdr (d);
va_list var_args;
va_start (var_args, count);
for (size_t i = 0; i < count; ++i)
{
// uint8_t {aka unsigned char} is promoted to int when passed through ...
uint8_t b = va_arg (var_args, int);
d.push_back (b);
}
va_end (var_args);
d.push_back (0xf7);
return tx_midi (d);
}
size_t tx_text (uint8_t id, uint8_t line, uint8_t align, std::string const& txt)
{
std::vector<uint8_t> d;
sysexhdr (d);
d.push_back (0x12);
d.push_back (id & 0x07);
d.push_back (line & 0x03);
d.push_back (align & 0x07);
for (size_t i = 0; i < txt.size(); ++i)
{
d.push_back (txt[i]);
if (i >= 8) {
break;
}
}
d.push_back (0xf7);
return tx_midi (d);
}
PBD::Signal1<void, bool> ShiftButtonChange;
PBD::Signal1<void, bool> ARMButtonChange;
PBD::Signal1<void, bool> BlinkIt;
PBD::Signal0<void> Periodic;
private:
void sysexhdr (std::vector<uint8_t>& d)
{
/* faderport8 <SysExHdr> */
d.push_back (0xf0);
d.push_back (0x00);
d.push_back (0x01);
d.push_back (0x06);
d.push_back (0x02);
}
};
namespace FP8Types {
enum FaderMode {
ModeTrack,
ModePlugins,
ModeSend,
ModePan
};
enum NavigationMode {
NavChannel,
NavZoom,
NavScroll,
NavBank,
NavMaster,
NavSection,
NavMarker
};
enum MixMode {
MixAudio,
MixInstrument,
MixBus,
MixVCA,
MixAll,
MixInputs,
MixMIDI,
MixOutputs,
MixFX,
MixUser,
};
};
} /* namespace */
#endif /* _ardour_surfaces_fp8base_h_ */

View File

@ -0,0 +1,490 @@
/*
* Copyright (C) 2017 Robin Gareus <robin@gareus.org>
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _ardour_surfaces_fp8button_h_
#define _ardour_surfaces_fp8button_h_
#include <stdint.h>
#include "pbd/base_ui.h"
#include "pbd/signals.h"
#include "fp8_base.h"
namespace ArdourSurface {
class FP8ButtonInterface
{
public:
FP8ButtonInterface () {}
virtual ~FP8ButtonInterface () {}
/* user API */
PBD::Signal0<void> pressed;
PBD::Signal0<void> released;
virtual bool is_pressed () const { return false; }
virtual bool is_active () const { return false; }
virtual void ignore_release () {}
/* internal API - called from midi thread,
* user pressed/released button the device
*/
virtual bool midi_event (bool) = 0;
/* internal API - called from surface thread
* set Light on the button
*/
virtual void set_active (bool a) = 0;
virtual void set_color (uint32_t rgba) {}
virtual void set_blinking (bool) {}
static bool force_change; // used during init
};
class FP8DummyButton : public FP8ButtonInterface
{
public:
virtual void set_active (bool a) {}
virtual bool midi_event (bool) { return false; }
};
class FP8ButtonBase : public FP8ButtonInterface
{
public:
FP8ButtonBase (FP8Base& b)
: _base (b)
, _pressed (false)
, _active (false)
, _ignore_release (false)
, _rgba (0)
, _blinking (false)
{ }
bool is_pressed () const { return _pressed; }
bool is_active () const { return _active; }
virtual bool midi_event (bool a)
{
if (a == _pressed) {
return false;
}
_pressed = a;
if (a) {
pressed (); /* EMIT SIGNAL */
} else {
if (_ignore_release) {
_ignore_release = false;
} else {
released (); /* EMIT SIGNAL */
}
}
return true;
}
void ignore_release () {
if (_pressed) {
_ignore_release = true;
}
}
void set_blinking (bool yes) {
if (yes && !_blinking) {
_blinking = true;
_base.BlinkIt.connect_same_thread (_blink_connection, boost::bind (&FP8ButtonBase::blink, this, _1));
} else if (!yes && _blinking) {
_blink_connection.disconnect ();
blink (true);
_blinking = false;
}
}
protected:
FP8Base& _base;
bool _pressed;
bool _active;
bool _ignore_release;
uint32_t _rgba;
virtual void blink (bool onoff) = 0;
private:
PBD::ScopedConnection _blink_connection;
bool _blinking;
};
class FP8Button : public FP8ButtonBase
{
public:
FP8Button (FP8Base& b, uint8_t id, bool color = false)
: FP8ButtonBase (b)
, _midi_id (id)
, _has_color (color)
{ }
virtual void set_active (bool a)
{
if (_active == a && !force_change) {
return;
}
_active = a;
_base.tx_midi3 (0x90, _midi_id, a ? 0x7f : 0x00);
}
void set_color (uint32_t rgba)
{
if (!_has_color || _rgba == rgba) {
return;
}
_rgba = rgba;
_base.tx_midi3 (0x91, _midi_id, (_rgba >> 25) & 0x7f);
_base.tx_midi3 (0x92, _midi_id, (_rgba >> 17) & 0x7f);
_base.tx_midi3 (0x93, _midi_id, (_rgba >> 9) & 0x7f);
}
protected:
void blink (bool onoff)
{
if (!_active) { return; }
_base.tx_midi3 (0x90, _midi_id, onoff ? 0x7f : 0x00);
}
uint8_t _midi_id; // MIDI-note
bool _has_color;
};
class FP8ReadOnlyButton : public FP8Button
{
public:
FP8ReadOnlyButton (FP8Base& b, uint8_t id, bool color = false)
: FP8Button (b, id, color)
{}
void set_active (bool) { }
};
/* virtual button. used for shift toggle. */
class ShadowButton : public FP8ButtonBase
{
public:
ShadowButton (FP8Base& b)
: FP8ButtonBase (b)
{}
PBD::Signal1<void, bool> ActiveChanged;
PBD::Signal0<void> ColourChanged;
uint32_t color () const { return _rgba; }
bool midi_event (bool a)
{
assert (0);
return false;
}
bool set_pressed (bool a)
{
return FP8ButtonBase::midi_event (a);
}
void set_active (bool a)
{
if (_active == a && !force_change) {
return;
}
_active = a;
ActiveChanged (a); /* EMIT SIGNAL */
}
void set_color (uint32_t rgba)
{
if (_rgba == rgba) {
return;
}
_rgba = rgba;
ColourChanged ();
}
protected:
void blink (bool onoff) {
if (!_active) { return; }
ActiveChanged (onoff);
}
};
/* Wraps 2 buttons with the same physical MIDI ID */
class FP8DualButton : public FP8ButtonInterface
{
public:
FP8DualButton (FP8Base& b, uint8_t id, bool color = false)
: _base (b)
, _b0 (b)
, _b1 (b)
, _midi_id (id)
, _has_color (color)
, _rgba (0)
, _shift (false)
{
_b0.ActiveChanged.connect_same_thread (_button_connections, boost::bind (&FP8DualButton::active_changed, this, false, _1));
_b1.ActiveChanged.connect_same_thread (_button_connections, boost::bind (&FP8DualButton::active_changed, this, true, _1));
if (_has_color) {
_b0.ColourChanged.connect_same_thread (_button_connections, boost::bind (&FP8DualButton::colour_changed, this, false));
_b1.ColourChanged.connect_same_thread (_button_connections, boost::bind (&FP8DualButton::colour_changed, this, true));
}
}
bool midi_event (bool a) {
return (_shift ? _b1 : _b0).set_pressed (a);
}
void set_active (bool a) {
/* This button is never directly used
* by the libardour side API.
*/
assert (0);
}
void active_changed (bool s, bool a) {
if (s != _shift) {
return;
}
_base.tx_midi3 (0x90, _midi_id, a ? 0x7f : 0x00);
}
void colour_changed (bool s) {
if (s != _shift || !_has_color) {
return;
}
uint32_t rgba = (_shift ? _b1 : _b0).color ();
if (rgba == _rgba) {
return;
}
_rgba = rgba;
_base.tx_midi3 (0x91, _midi_id, (rgba >> 25) & 0x7f);
_base.tx_midi3 (0x92, _midi_id, (rgba >> 17) & 0x7f);
_base.tx_midi3 (0x93, _midi_id, (rgba >> 9) & 0x7f);
}
FP8ButtonInterface* button () { return &_b0; }
FP8ButtonInterface* button_shift () { return &_b1; }
protected:
FP8Base& _base;
virtual void connect_toggle () = 0;
void shift_changed (bool shift) {
if (_shift == shift) {
return;
}
(_shift ? _b1 : _b0).set_pressed (false);
_shift = shift;
active_changed (_shift, (_shift ? _b1 : _b0).is_active());
colour_changed (_shift);
}
private:
ShadowButton _b0;
ShadowButton _b1;
uint8_t _midi_id; // MIDI-note
bool _has_color;
uint32_t _rgba;
bool _shift;
PBD::ScopedConnectionList _button_connections;
};
class FP8ShiftSensitiveButton : public FP8DualButton
{
public:
FP8ShiftSensitiveButton (FP8Base& b, uint8_t id, bool color = false)
:FP8DualButton (b, id, color)
{
connect_toggle ();
}
protected:
void connect_toggle ()
{
_base.ShiftButtonChange.connect_same_thread (_shift_connection, boost::bind (&FP8ShiftSensitiveButton::shift_changed, this, _1));
}
private:
PBD::ScopedConnection _shift_connection;
};
class FP8ARMSensitiveButton : public FP8DualButton
{
public:
FP8ARMSensitiveButton (FP8Base& b, uint8_t id, bool color = false)
:FP8DualButton (b, id, color)
{
connect_toggle ();
}
protected:
void connect_toggle ()
{
_base.ARMButtonChange.connect_same_thread (_arm_connection, boost::bind (&FP8ARMSensitiveButton::shift_changed, this, _1));
}
private:
PBD::ScopedConnection _arm_connection;
};
// short press: activate in press, deactivate on release,
// long press + hold, activate on press, de-activate directly on release
// e.g. mute/solo press + hold => changed()
class FP8MomentaryButton : public FP8ButtonInterface
{
public:
FP8MomentaryButton (FP8Base& b, uint8_t id)
: _base (b)
, _midi_id (id)
, _pressed (false)
, _active (false)
{}
~FP8MomentaryButton () {
_hold_connection.disconnect ();
}
PBD::Signal1<void, bool> StateChange;
void set_active (bool a)
{
if (_active == a && !force_change) {
return;
}
_active = a;
_base.tx_midi3 (0x90, _midi_id, a ? 0x7f : 0x00);
}
void reset ()
{
_was_active_on_press = false;
_hold_connection.disconnect ();
}
bool midi_event (bool a)
{
if (a == _pressed) {
return false;
}
_pressed = a;
if (a) {
_was_active_on_press = _active;
}
if (a && !_active) {
_momentaty = false;
StateChange (true); /* EMIT SIGNAL */
Glib::RefPtr<Glib::TimeoutSource> hold_timer =
Glib::TimeoutSource::create (500);
hold_timer->attach (fp8_loop()->get_context());
_hold_connection = hold_timer->connect (sigc::mem_fun (*this, &FP8MomentaryButton::hold_timeout));
} else if (!a && _was_active_on_press) {
_hold_connection.disconnect ();
_momentaty = false;
StateChange (false); /* EMIT SIGNAL */
} else if (!a && _momentaty) {
_hold_connection.disconnect ();
_momentaty = false;
StateChange (false); /* EMIT SIGNAL */
}
return true;
}
protected:
FP8Base& _base;
uint8_t _midi_id; // MIDI-note
bool _pressed;
bool _momentaty;
bool _was_active_on_press;
bool _active;
private:
bool hold_timeout ()
{
_momentaty = true;
return false;
}
sigc::connection _hold_connection;
};
class FP8RepeatButton : public FP8Button
{
public:
FP8RepeatButton (FP8Base& b, uint8_t id, bool color = false)
: FP8Button (b, id, color)
, _skip (0)
{}
~FP8RepeatButton ()
{
stop_repeat ();
}
bool midi_event (bool a)
{
bool rv = FP8Button::midi_event (a);
if (rv && a) {
start_repeat ();
}
return rv;
}
void stop_repeat ()
{
_press_timeout_connection.disconnect ();
}
private:
void start_repeat ()
{
stop_repeat ();
_skip = 5;
Glib::RefPtr<Glib::TimeoutSource> press_timer =
Glib::TimeoutSource::create (100);
press_timer->attach (fp8_loop()->get_context());
_press_timeout_connection = press_timer->connect (sigc::mem_fun (*this, &FP8RepeatButton::repeat_press));
}
bool repeat_press ()
{
if (!_pressed) {
return false;
}
if (_skip > 0) {
--_skip;
return true;
}
pressed ();
return true;
}
int _skip;
sigc::connection _press_timeout_connection;
};
} /* namespace */
#endif /* _ardour_surfaces_fp8button_h_ */

View File

@ -0,0 +1,413 @@
/* Faderport 8 Control Surface
* Abstraction of Surface Control Elements.
*
* Copyright (C) 2017 Robin Gareus <robin@gareus.org>
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "fp8_controls.h"
using namespace ArdourSurface;
using namespace ArdourSurface::FP8Types;
bool FP8ButtonInterface::force_change = false;
#define NEWBUTTON(midi_id, button_id, color) \
do { \
assert (_midimap.end() == _midimap.find (midi_id)); \
assert (_ctrlmap.end() == _ctrlmap.find (button_id)); \
FP8Button *t = new FP8Button (b, midi_id); \
_midimap[midi_id] = t; \
_ctrlmap[button_id] = t; \
} while (0)
#define NEWTYPEBUTTON(TYPE, midi_id, button_id, color) \
do { \
assert (_midimap.end() == _midimap.find (midi_id)); \
assert (_ctrlmap.end() == _ctrlmap.find (button_id)); \
TYPE *t = new TYPE (b, midi_id); \
_midimap[midi_id] = t; \
_ctrlmap[button_id] = t; \
} while (0)
#define NEWSHIFTBUTTON(midi_id, id1, id2, color) \
do { \
assert (_midimap.end() == _midimap.find (midi_id)); \
assert (_ctrlmap.end() == _ctrlmap.find (id1)); \
assert (_ctrlmap.end() == _ctrlmap.find (id2)); \
FP8ShiftSensitiveButton *t = \
new FP8ShiftSensitiveButton (b, midi_id, color); \
_midimap[midi_id] = t; \
_ctrlmap[id1] = t->button (); \
_ctrlmap[id2] = t->button_shift (); \
} while (0)
FP8Controls::FP8Controls (FP8Base& b)
: _fadermode (ModeTrack)
, _navmode (NavMaster)
, _mixmode (MixAll)
, _display_timecode (false)
{
NEWBUTTON (0x56, BtnLoop, false);
NEWTYPEBUTTON (FP8RepeatButton, 0x5b, BtnRewind, false);
NEWTYPEBUTTON (FP8RepeatButton, 0x5c, BtnFastForward, false);
NEWBUTTON (0x5d, BtnStop, false);
NEWBUTTON (0x5e, BtnPlay, false);
NEWBUTTON (0x5f, BtnRecord, false);
NEWSHIFTBUTTON (0x4a, BtnARead, BtnUser3, true);
NEWSHIFTBUTTON (0x4b, BtnAWrite, BtnUser2, true);
NEWSHIFTBUTTON (0x4c, BtnATrim, BtnRedo, true);
NEWSHIFTBUTTON (0x4d, BtnATouch, BtnUser1, true);
NEWSHIFTBUTTON (0x4e, BtnALatch, BtnSave, true);
NEWSHIFTBUTTON (0x4f, BtnAOff, BtnUndo, true);
NEWBUTTON (0x2e, BtnPrev, false);
NEWBUTTON (0x2f, BtnNext, false);
NEWSHIFTBUTTON (0x36, BtnChannel, BtnF1, false);
NEWSHIFTBUTTON (0x37, BtnZoom, BtnF2, false);
NEWSHIFTBUTTON (0x38, BtnScroll, BtnF3, false);
NEWSHIFTBUTTON (0x39, BtnBank, BtnF4, false);
NEWSHIFTBUTTON (0x3a, BtnMaster, BtnF5, false);
NEWSHIFTBUTTON (0x3b, BtnClick, BtnF6, false);
NEWSHIFTBUTTON (0x3c, BtnSection, BtnF7, false);
NEWSHIFTBUTTON (0x3d, BtnMarker, BtnF8, false);
NEWSHIFTBUTTON (0x28, BtnTrack, BtnTimecode, false);
NEWBUTTON (0x2b, BtnPlugins, false);
NEWBUTTON (0x29, BtnSend, false);
NEWBUTTON (0x2a, BtnPan, false);
NEWSHIFTBUTTON (0x00, BtnArm, BtnArmAll, false);
NEWBUTTON (0x01, BtnSoloClear, false);
NEWBUTTON (0x02, BtnMuteClear, false);
NEWSHIFTBUTTON (0x03, BtnBypass, BtnBypassAll, true);
NEWSHIFTBUTTON (0x04, BtnMacro, BtnOpen, true);
NEWSHIFTBUTTON (0x05, BtnLink, BtnLock, true);
NEWSHIFTBUTTON (0x3e, BtnMAudio, BtnMInputs, true);
NEWSHIFTBUTTON (0x3f, BtnMVI, BtnMMIDI, true);
NEWSHIFTBUTTON (0x40, BtnMBus, BtnMOutputs, true);
NEWSHIFTBUTTON (0x41, BtnMVCA, BtnMFX, true);
NEWSHIFTBUTTON (0x42, BtnMAll, BtnMUser, true);
NEWTYPEBUTTON (FP8ReadOnlyButton, 0x53, BtnEncoder, false);
NEWTYPEBUTTON (FP8ReadOnlyButton, 0x20, BtnParam, false);
NEWTYPEBUTTON (FP8ReadOnlyButton, 0x66, BtnFootswitch, false);
/* internal bindings */
#define BindMethod(ID, CB) \
button (ID).released.connect_same_thread (button_connections, boost::bind (&FP8Controls:: CB, this));
BindMethod (FP8Controls::BtnTimecode, toggle_timecode);
#define BindNav(BTN, MODE)\
button (BTN).released.connect_same_thread (button_connections, boost::bind (&FP8Controls::set_nav_mode, this, MODE))
BindNav (BtnChannel, NavChannel);
BindNav (BtnZoom, NavZoom);
BindNav (BtnScroll, NavScroll);
BindNav (BtnBank, NavBank);
BindNav (BtnMaster, NavMaster);
BindNav (BtnSection, NavSection);
BindNav (BtnMarker, NavMarker);
#define BindFader(BTN, MODE)\
button (BTN).released.connect_same_thread (button_connections, boost::bind (&FP8Controls::set_fader_mode, this, MODE))
BindFader (BtnTrack, ModeTrack);
BindFader (BtnPlugins, ModePlugins);
BindFader (BtnSend, ModeSend);
BindFader (BtnPan, ModePan);
#define BindMix(BTN, MODE)\
button (BTN).released.connect_same_thread (button_connections, boost::bind (&FP8Controls::set_mix_mode, this, MODE))
BindMix (BtnMAudio, MixAudio);
BindMix (BtnMVI, MixInstrument);
BindMix (BtnMBus, MixBus);
BindMix (BtnMVCA, MixVCA);
BindMix (BtnMAll, MixAll);
BindMix (BtnMInputs, MixInputs);
BindMix (BtnMMIDI, MixMIDI);
BindMix (BtnMOutputs, MixOutputs);
BindMix (BtnMFX, MixFX);
BindMix (BtnMUser, MixUser);
/* create channelstrips */
for (uint8_t id = 0; id < 8; ++id) {
chanstrip[id] = new FP8Strip (b, id);
_midimap_strip[0x08 + id] = &(chanstrip[id]->solo_button());
_midimap_strip[0x10 + id] = &(chanstrip[id]->mute_button());
_midimap_strip[0x18 + id] = &(chanstrip[id]->selrec_button());
}
/* set User button names */
#define REGISTER_ENUM(ID, NAME) \
_user_str_to_enum[#ID] = ID; \
_user_enum_to_str[ID] = #ID; \
_user_buttons[ID] = NAME;
REGISTER_ENUM (BtnFootswitch, "Footswitch");
REGISTER_ENUM (BtnUser1 , "User 1");
REGISTER_ENUM (BtnUser2 , "User 2");
REGISTER_ENUM (BtnUser3 , "User 3");
REGISTER_ENUM (BtnF1 , "F1");
REGISTER_ENUM (BtnF2 , "F2");
REGISTER_ENUM (BtnF3 , "F3");
REGISTER_ENUM (BtnF4 , "F4");
REGISTER_ENUM (BtnF5 , "F5");
REGISTER_ENUM (BtnF6 , "F6");
REGISTER_ENUM (BtnF7 , "F7");
REGISTER_ENUM (BtnF8 , "F8");
#undef REGISTER_ENUM
}
FP8Controls::~FP8Controls ()
{
for (MidiButtonMap::const_iterator i = _midimap.begin (); i != _midimap.end (); ++i) {
delete i->second;
}
for (uint8_t id = 0; id < 8; ++id) {
delete chanstrip[id];
}
_midimap_strip.clear ();
_ctrlmap.clear ();
_midimap.clear ();
}
bool
FP8Controls::button_name_to_enum (std::string const& n, ButtonId& id) const
{
std::map<std::string, ButtonId>::const_iterator i = _user_str_to_enum.find (n);
if (i == _user_str_to_enum.end()) {
return false;
}
id = i->second;
return true;
}
bool
FP8Controls::button_enum_to_name (ButtonId id, std::string& n) const
{
std::map<ButtonId, std::string>::const_iterator i = _user_enum_to_str.find (id);
if (i == _user_enum_to_str.end()) {
return false;
}
n = i->second;
return true;
}
void
FP8Controls::initialize ()
{
FP8ButtonInterface::force_change = true;
/* set RGB colors */
button (BtnUndo).set_color (0x00ff00ff);
button (BtnRedo).set_color (0x00ff00ff);
button (BtnAOff).set_color (0xffffffff);
button (BtnATrim).set_color (0x000030ff);
button (BtnARead).set_color (0x00ff00ff);
button (BtnAWrite).set_color (0xff0000ff);
button (BtnATouch).set_color (0xff8800ff);
button (BtnUser1).set_color (0x0000ffff);
button (BtnUser2).set_color (0x0000ffff);
button (BtnUser3).set_color (0x0000ffff);
button (BtnALatch).set_color (0x0000ffff);
button (BtnBypass).set_color (0x888888ff);
button (BtnBypassAll).set_color (0xffffffff);
button (BtnMacro).set_color (0x888888ff);
button (BtnOpen).set_color (0xffffffff);
button (BtnLink).set_color (0x888888ff);
button (BtnLock).set_color (0xffffffff);
button (BtnMAudio).set_color (0x0000ffff);
button (BtnMVI).set_color (0x0000ffff);
button (BtnMBus).set_color (0x0000ffff);
button (BtnMVCA).set_color (0x0000ffff);
button (BtnMAll).set_color (0x0000ffff);
button (BtnMInputs).set_color (0x0000ffff);
button (BtnMMIDI).set_color (0x0000ffff);
button (BtnMOutputs).set_color (0x0000ffff);
button (BtnMFX).set_color (0x0000ffff);
button (BtnMUser).set_color (0x0000ffff);
for (uint8_t id = 0; id < 8; ++id) {
chanstrip[id]->initialize ();
}
/* initally turn all lights off */
for (CtrlButtonMap::const_iterator i = _ctrlmap.begin (); i != _ctrlmap.end (); ++i) {
i->second->set_active (false);
}
/* default modes */
button (BtnMaster).set_active (true);
button (BtnTrack).set_active (true);
button (BtnMAll).set_active (true);
button (BtnTimecode).set_active (_display_timecode);
FP8ButtonInterface::force_change = false;
}
FP8ButtonInterface&
FP8Controls::button (ButtonId id)
{
CtrlButtonMap::const_iterator i = _ctrlmap.find (id);
if (i == _ctrlmap.end()) {
assert (0);
return _dummy_button;
}
return *(i->second);
}
FP8Strip&
FP8Controls::strip (uint8_t id)
{
assert (id < 8);
return *chanstrip[id];
}
/* *****************************************************************************
* Delegate MIDI events
*/
bool
FP8Controls::midi_event (uint8_t id, uint8_t val)
{
MidiButtonMap::const_iterator i;
i = _midimap_strip.find (id);
if (i != _midimap_strip.end()) {
return i->second->midi_event (val > 0x40);
}
i = _midimap.find (id);
if (i != _midimap.end()) {
return i->second->midi_event (val > 0x40);
}
return false;
}
bool
FP8Controls::midi_touch (uint8_t id, uint8_t val)
{
assert (id < 8);
return chanstrip[id]->midi_touch (val > 0x40);
}
bool
FP8Controls::midi_fader (uint8_t id, unsigned short val)
{
assert (id < 8);
return chanstrip[id]->midi_fader ((val >> 4) / 1023.f);
}
/* *****************************************************************************
* Internal Model + View for Modes
*/
void
FP8Controls::set_nav_mode (NavigationMode m)
{
if (_navmode == m) {
return;
}
// TODO add special-cases:
// - master/monitor (blink when button is held + monitor section present)
// - "click" hold -> encoder sets click volume, encoder-press toggle rec-only-metro
button (BtnChannel).set_active (m == NavChannel);
button (BtnZoom).set_active (m == NavZoom);
button (BtnScroll).set_active (m == NavScroll);
button (BtnBank).set_active (m == NavBank);
button (BtnMaster).set_active (m == NavMaster);
button (BtnSection).set_active (m == NavSection);
button (BtnMarker).set_active (m == NavMarker);
_navmode = m;
}
void
FP8Controls::set_fader_mode (FaderMode m)
{
if (_fadermode == m) {
if (m == ModePlugins || m == ModeSend) {
/* "Edit Plugins" while editing Plugin-params, returns back
* to plugin selection.
* "Sends" button banks through sends.
*/
FaderModeChanged ();
}
return;
}
// set lights
button (BtnTrack).set_active (m == ModeTrack);
button (BtnPlugins).set_active (m == ModePlugins);
button (BtnSend).set_active (m == ModeSend);
button (BtnPan).set_active (m == ModePan);
_fadermode = m;
FaderModeChanged ();
}
void
FP8Controls::set_mix_mode (MixMode m)
{
if (_mixmode == m) {
if (m == MixUser || m == MixInputs) {
/* always re-assign:
* - MixUser: depends on selection
* - MixInputs: depends on rec-arm
*/
MixModeChanged ();
}
return;
}
button (BtnMAudio).set_active (m == MixAudio);
button (BtnMVI).set_active (m == MixInstrument);
button (BtnMBus).set_active (m == MixBus);
button (BtnMVCA).set_active (m == MixVCA);
button (BtnMAll).set_active (m == MixAll);
button (BtnMInputs).set_active (m == MixInputs);
button (BtnMMIDI).set_active (m == MixMIDI);
button (BtnMOutputs).set_active (m == MixOutputs);
button (BtnMFX).set_active (m == MixFX);
button (BtnMUser).set_active (m == MixUser);
_mixmode = m;
MixModeChanged ();
}
void
FP8Controls::toggle_timecode ()
{
_display_timecode = !_display_timecode;
button (BtnTimecode).set_active (_display_timecode);
}

View File

@ -0,0 +1,172 @@
/*
* Copyright (C) 2017 Robin Gareus <robin@gareus.org>
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _ardour_surfaces_fp8controls_h_
#define _ardour_surfaces_fp8controls_h_
#include <map>
#include "fp8_base.h"
#include "fp8_button.h"
#include "fp8_strip.h"
namespace ArdourSurface {
class FP8Controls
{
public:
FP8Controls (FP8Base&);
virtual ~FP8Controls ();
enum ButtonId {
BtnPlay,
BtnStop,
BtnRecord,
BtnLoop,
BtnRewind,
BtnFastForward,
BtnALatch,
BtnATrim,
BtnAOff,
BtnATouch,
BtnAWrite,
BtnARead,
// Automation
BtnSave,
BtnRedo,
BtnUndo,
BtnUser1,
BtnUser2,
BtnUser3,
BtnFootswitch,
// Pan/Param encoder press
BtnParam,
// Navigation
BtnPrev,
BtnNext,
BtnEncoder,
BtnChannel,
BtnZoom,
BtnScroll,
BtnBank,
BtnMaster,
BtnClick,
BtnSection,
BtnMarker,
BtnF1, BtnF2, BtnF3, BtnF4,
BtnF5, BtnF6, BtnF7, BtnF8,
// FaderMode
BtnTrack,
BtnPlugins,
BtnSend,
BtnPan,
BtnTimecode,
// Mix Management
BtnMAudio,
BtnMVI,
BtnMBus,
BtnMVCA,
BtnMAll,
BtnMInputs,
BtnMMIDI,
BtnMOutputs,
BtnMFX,
BtnMUser,
// General Controls
BtnArm,
BtnArmAll,
BtnSoloClear,
BtnMuteClear,
BtnBypass,
BtnBypassAll,
BtnMacro,
BtnOpen,
BtnLink,
BtnLock,
};
typedef std::map <ButtonId, std::string> UserButtonMap;
UserButtonMap const& user_buttons () const {
return _user_buttons;
}
bool button_name_to_enum (std::string const&, ButtonId&) const;
bool button_enum_to_name (ButtonId, std::string&) const;
PBD::Signal0<void> FaderModeChanged;
PBD::Signal0<void> MixModeChanged;
FP8Types::FaderMode fader_mode () const { return _fadermode; }
FP8Types::NavigationMode nav_mode () const { return _navmode; }
FP8Types::MixMode mix_mode () const { return _mixmode; }
bool display_timecode () const { return _display_timecode; }
FP8ButtonInterface& button (ButtonId id);
FP8Strip& strip (uint8_t id);
bool midi_event (uint8_t id, uint8_t val);
bool midi_touch (uint8_t id, uint8_t val);
bool midi_fader (uint8_t id, unsigned short val);
void initialize ();
void set_fader_mode (FP8Types::FaderMode);
protected:
typedef std::map <uint8_t, FP8ButtonInterface*> MidiButtonMap;
typedef std::map <ButtonId, FP8ButtonInterface*> CtrlButtonMap;
void set_nav_mode (FP8Types::NavigationMode);
void set_mix_mode (FP8Types::MixMode);
void toggle_timecode ();
MidiButtonMap _midimap;
CtrlButtonMap _ctrlmap;
MidiButtonMap _midimap_strip;
FP8Strip* chanstrip[8];
FP8Types::FaderMode _fadermode;
FP8Types::NavigationMode _navmode;
FP8Types::MixMode _mixmode;
bool _display_timecode;
UserButtonMap _user_buttons;
FP8DummyButton _dummy_button;
std::map<std::string, ButtonId> _user_str_to_enum;
std::map<ButtonId, std::string> _user_enum_to_str;
PBD::ScopedConnectionList button_connections;
};
} /* namespace */
#endif /* _ardour_surfaces_fp8controls_h_ */

View File

@ -0,0 +1,524 @@
/*
* Copyright (C) 2017 Robin Gareus <robin@gareus.org>
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include "ardour/automation_control.h"
#include "ardour/gain_control.h"
#include "ardour/meter.h"
#include "ardour/mute_control.h"
#include "ardour/plugin_insert.h"
#include "ardour/session.h"
#include "ardour/solo_control.h"
#include "ardour/stripable.h"
#include "ardour/track.h"
#include "ardour/value_as_string.h"
#include "control_protocol/control_protocol.h"
#include "fp8_strip.h"
using namespace ARDOUR;
using namespace ArdourSurface;
using namespace ArdourSurface::FP8Types;
FP8Strip::FP8Strip (FP8Base& b, uint8_t id)
: _base (b)
, _id (id)
, _solo (b, 0x08 + id)
, _mute (b, 0x10 + id)
, _selrec (b, 0x18 + id, true)
, _touching (false)
, _strip_mode (0)
, _bar_mode (0)
, _displaymode (Stripables)
{
assert (id < 8);
_last_fader = 65535;
_last_meter = _last_redux = _last_panpos = 0xff;
_mute.StateChange.connect_same_thread (_button_connections, boost::bind (&FP8Strip::set_mute, this, _1));
_solo.StateChange.connect_same_thread (_button_connections, boost::bind (&FP8Strip::set_solo, this, _1));
select_button ().released.connect_same_thread (_button_connections, boost::bind (&FP8Strip::set_select, this));
recarm_button ().released.connect_same_thread (_button_connections, boost::bind (&FP8Strip::set_recarm, this));
b.Periodic.connect_same_thread (_base_connection, boost::bind (&FP8Strip::periodic, this));
}
FP8Strip::~FP8Strip ()
{
_fader_connection.disconnect ();
_mute_connection.disconnect ();
_solo_connection.disconnect ();
_rec_connection.disconnect ();
_pan_connection.disconnect ();
_fader_ctrl.reset ();
_mute_ctrl.reset ();
_solo_ctrl.reset ();
_rec_ctrl.reset ();
_pan_ctrl.reset ();
_base_connection.disconnect ();
_button_connections.drop_connections ();
}
void
FP8Strip::initialize ()
{
/* this is called once midi transmission is possible,
* ie from FaderPort8::connected()
*/
_solo.set_active (false);
_mute.set_active (false);
/* reset momentary button state */
_mute.reset ();
_solo.reset ();
/* clear cached values */
_last_fader = 65535;
_last_meter = _last_redux = _last_panpos = 0xff;
select_button ().set_color (0xffffffff);
select_button ().set_active (false);
select_button ().set_blinking (false);
recarm_button ().set_active (false);
recarm_button ().set_color (0xffffffff);
set_strip_mode (0, true);
// force unset txt
_last_line[0].clear ();
_last_line[1].clear ();
_last_line[2].clear ();
_last_line[3].clear ();
_base.tx_sysex (4, 0x12, _id, 0x00, 0x00);
_base.tx_sysex (4, 0x12, _id, 0x01, 0x00);
_base.tx_sysex (4, 0x12, _id, 0x02, 0x00);
_base.tx_sysex (4, 0x12, _id, 0x03, 0x00);
set_bar_mode (4); // off
_base.tx_midi2 (0xd0 + _id, 0); // reset meter
_base.tx_midi2 (0xd8 + _id, 0); // reset redux
_base.tx_midi3 (0xe0 + _id, 0, 0); // fader
}
#define GENERATE_SET_CTRL_FUNCTION(NAME) \
void \
FP8Strip::set_ ##NAME##_controllable (boost::shared_ptr<AutomationControl> ac) \
{ \
if (_##NAME##_ctrl == ac) { \
return; \
} \
_##NAME##_connection.disconnect(); \
_##NAME##_ctrl = ac; \
\
if (ac) { \
ac->Changed.connect (_##NAME##_connection, MISSING_INVALIDATOR, \
boost::bind (&FP8Strip::notify_##NAME##_changed, this), fp8_context()); \
} \
notify_##NAME##_changed (); \
}
GENERATE_SET_CTRL_FUNCTION (fader)
GENERATE_SET_CTRL_FUNCTION (mute)
GENERATE_SET_CTRL_FUNCTION (solo)
GENERATE_SET_CTRL_FUNCTION (rec)
GENERATE_SET_CTRL_FUNCTION (pan)
#undef GENERATE_SET_CTRL_FUNCTION
void
FP8Strip::unset_controllables (int which)
{
_peak_meter = boost::shared_ptr<ARDOUR::PeakMeter>();
_redux_ctrl = boost::shared_ptr<ARDOUR::ReadOnlyControl>();
if (which & CTRL_FADER) {
set_fader_controllable (boost::shared_ptr<AutomationControl>());
}
if (which & CTRL_MUTE) {
set_mute_controllable (boost::shared_ptr<AutomationControl>());
}
if (which & CTRL_SOLO) {
set_solo_controllable (boost::shared_ptr<AutomationControl>());
}
if (which & CTRL_REC) {
set_rec_controllable (boost::shared_ptr<AutomationControl>());
}
if (which & CTRL_PAN) {
set_bar_mode (4); // off
set_pan_controllable (boost::shared_ptr<AutomationControl>());
}
if (which & CTRL_SELECT) {
_select_plugin_functor.clear ();
select_button ().set_color (0xffffffff);
select_button ().set_active (false);
select_button ().set_blinking (false);
}
if (which & CTRL_TEXT1) {
set_text_line (0x00, "");
}
if (which & CTRL_TEXT2) {
set_text_line (0x01, "");
}
if (which & CTRL_TEXT3) {
set_text_line (0x02, "");
}
if (which & CTRL_TEXT4) {
set_text_line (0x03, "");
}
}
void
FP8Strip::set_stripable (boost::shared_ptr<Stripable> s, bool panmode)
{
assert (s);
if (panmode) {
set_fader_controllable (s->pan_azimuth_control ());
} else {
set_fader_controllable (s->gain_control ());
}
set_pan_controllable (s->pan_azimuth_control ());
if (s->is_monitor ()) {
set_mute_controllable (boost::shared_ptr<AutomationControl>());
} else {
set_mute_controllable (s->mute_control ());
}
set_solo_controllable (s->solo_control ());
if (boost::dynamic_pointer_cast<Track> (s)) {
boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track>(s);
set_rec_controllable (t->rec_enable_control ());
recarm_button ().set_color (0xff0000ff);
} else {
set_rec_controllable (boost::shared_ptr<AutomationControl>());
recarm_button ().set_color (0xffffffff);
recarm_button ().set_active (false);
}
_peak_meter = s->peak_meter ();
_redux_ctrl = s->comp_redux_controllable ();
_select_plugin_functor.clear ();
select_button ().set_active (s->is_selected ());
select_button ().set_color (s->presentation_info ().color());
//select_button ().set_blinking (false);
set_strip_mode (0x05);
set_text_line (0x00, s->name ());
set_text_line (0x01, _pan_ctrl ? _pan_ctrl->get_user_string () : "");
set_text_line (0x02, "");
set_text_line (0x03, "");
}
void
FP8Strip::set_select_cb (boost::function<void ()>& functor)
{
_select_plugin_functor.clear ();
_select_plugin_functor = functor;
}
/* *****************************************************************************
* Parse Strip Specifig MIDI Events
*/
bool
FP8Strip::midi_touch (bool t)
{
_touching = t;
boost::shared_ptr<AutomationControl> ac = _fader_ctrl;
if (!ac) {
return false;
}
if (t) {
ac->start_touch (ac->session().transport_frame());
} else {
ac->stop_touch (true, ac->session().transport_frame());
}
return true;
}
bool
FP8Strip::midi_fader (float val)
{
assert (val >= 0.f && val <= 1.f);
if (!_touching) {
return false;
}
boost::shared_ptr<AutomationControl> ac = _fader_ctrl;
if (!ac) {
return false;
}
ac->set_value (ac->interface_to_internal (val), PBD::Controllable::UseGroup);
return true;
}
/* *****************************************************************************
* Actions from Controller, Update Model
*/
void
FP8Strip::set_mute (bool on)
{
if (_mute_ctrl) {
if (!_mute_ctrl->touching ()) {
_mute_ctrl->start_touch (_mute_ctrl->session().transport_frame());
}
_mute_ctrl->set_value (on ? 1.0 : 0.0, PBD::Controllable::UseGroup);
}
}
void
FP8Strip::set_solo (bool on)
{
if (_solo_ctrl) {
if (!_solo_ctrl->touching ()) {
_solo_ctrl->start_touch (_solo_ctrl->session().transport_frame());
}
_solo_ctrl->set_value (on ? 1.0 : 0.0, PBD::Controllable::UseGroup);
}
}
void
FP8Strip::set_recarm ()
{
if (_rec_ctrl) {
const bool on = !recarm_button().is_active();
_rec_ctrl->set_value (on ? 1.0 : 0.0, PBD::Controllable::UseGroup);
}
}
void
FP8Strip::set_select ()
{
if (!_select_plugin_functor.empty ()) {
_select_plugin_functor ();
}
}
/* *****************************************************************************
* Callbacks from Stripable, Update View
*/
void
FP8Strip::notify_fader_changed ()
{
boost::shared_ptr<AutomationControl> ac = _fader_ctrl;
if (_touching) {
return;
}
float val = 0;
if (ac) {
val = ac->internal_to_interface (ac->get_value()) * 16368.f; /* 16 * 1023 */
}
unsigned short mv = lrintf (val);
if (mv == _last_fader) {
return;
}
_last_fader = mv;
_base.tx_midi3 (0xe0 + _id, (mv & 0x7f), (mv >> 7) & 0x7f);
}
void
FP8Strip::notify_solo_changed ()
{
if (_solo_ctrl) {
_solo.set_active (_solo_ctrl->get_value () > 0);
} else {
_solo.set_active (false);
}
}
void
FP8Strip::notify_mute_changed ()
{
if (_mute_ctrl) {
_mute.set_active (_mute_ctrl->get_value () > 0);
} else {
_mute.set_active (false);
}
}
void
FP8Strip::notify_rec_changed ()
{
if (_rec_ctrl) {
recarm_button ().set_active (_rec_ctrl->get_value() > 0.);
} else {
recarm_button ().set_active (false);
}
}
void
FP8Strip::notify_pan_changed ()
{
}
/* *****************************************************************************
* Periodic View Updates
*/
void
FP8Strip::periodic_update_fader ()
{
boost::shared_ptr<AutomationControl> ac = _fader_ctrl;
if (!ac || _touching) {
return;
}
ARDOUR::AutoState state = ac->automation_state();
if (state == Touch || state == Play) {
notify_fader_changed ();
}
}
void
FP8Strip::periodic_update_meter ()
{
bool have_meter = false;
bool have_panner = false;
if (_peak_meter) {
have_meter = true;
float dB = _peak_meter->meter_level (0, MeterMCP);
// TODO: deflect meter
int val = std::min (127.f, std::max (0.f, 2.f * dB + 127.f));
if (val != _last_meter || val > 0) {
_base.tx_midi2 (0xd0 + _id, val & 0x7f); // falls off automatically
_last_meter = val;
}
} else {
if (0 != _last_meter) {
_base.tx_midi2 (0xd0 + _id, 0);
_last_meter = 0;
}
}
// show redux only if there's a meter, too (strip display mode 5)
if (_peak_meter && _redux_ctrl) {
float rx = (1.f - _redux_ctrl->get_parameter ()) * 127.f;
// TODO: deflect redux
int val = std::min (127.f, std::max (0.f, rx));
if (val != _last_redux) {
_base.tx_midi2 (0xd8 + _id, val & 0x7f);
_last_redux = val;
}
} else {
if (0 != _last_redux) {
_base.tx_midi2 (0xd8 + _id, 0);
_last_redux = 0;
}
}
if (_displaymode == PluginParam) {
set_bar_mode (4); // Off
if (_fader_ctrl) {
set_text_line (0x01, value_as_string(_fader_ctrl->desc(), _fader_ctrl->get_value()));
} else {
set_text_line (0x01, "");
}
} else if (_pan_ctrl) {
have_panner = true;
float panpos = _pan_ctrl->internal_to_interface (_pan_ctrl->get_value());
int val = std::min (127.f, std::max (0.f, panpos * 128.f));
set_bar_mode (1); // Bipolar
if (val != _last_panpos) {
_base.tx_midi3 (0xb0, 0x30 + _id, val & 0x7f);
_last_panpos = val;
}
set_text_line (0x01, _pan_ctrl->get_user_string ());
} else {
set_bar_mode (4); // Off
}
if (have_meter && have_panner) {
set_strip_mode (5); // small meter mode
}
else if (have_meter) {
set_strip_mode (4); // big meter mode
}
else if (have_panner) {
set_strip_mode (0); // 3 lines of text + value
} else {
set_strip_mode (0); // 3 lines of text + value
}
}
void
FP8Strip::set_strip_mode (uint8_t strip_mode, bool clear)
{
if (strip_mode == _strip_mode && !clear) {
return;
}
_strip_mode = strip_mode;
_base.tx_sysex (3, 0x13, _id, (_strip_mode & 0x07) | (clear ? 0x10 : 0));
//_base.tx_midi3 (0xb0, 0x38 + _id, _bar_mode);
}
void
FP8Strip::set_bar_mode (uint8_t bar_mode)
{
if (bar_mode == _bar_mode) {
return;
}
_bar_mode = bar_mode;
_base.tx_midi3 (0xb0, 0x38 + _id, bar_mode);
}
void
FP8Strip::set_text_line (uint8_t line, std::string const& txt)
{
assert (line < 4);
if (_last_line[line] == txt) {
return;
}
_base.tx_text (_id, line, 0x00, txt);
_last_line[line] = txt;
}
void
FP8Strip::periodic_update_timecode ()
{
if (_id >= 2 && _id < 6) {
std::string const& tc = _base.timecode();
//" HH:MM:SS:FF"
std::string t;
if (tc.size () == 12) {
t = tc.substr (1 + (_id - 2) * 3, 2);
}
set_text_line (0x02, t);
}
}
void
FP8Strip::periodic ()
{
periodic_update_fader ();
periodic_update_meter ();
if (_displaymode != PluginSelect) {
periodic_update_timecode ();
}
}

View File

@ -0,0 +1,162 @@
/*
* Copyright (C) 2017 Robin Gareus <robin@gareus.org>
*
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef _ardour_surfaces_fp8strip_h_
#define _ardour_surfaces_fp8strip_h_
#include <stdint.h>
#include <boost/shared_ptr.hpp>
#include "pbd/signals.h"
#include "fp8_base.h"
#include "fp8_button.h"
namespace ARDOUR {
class Stripable;
class AutomationControl;
class PeakMeter;
class ReadOnlyControl;
}
namespace ArdourSurface {
class FP8Strip
{
public:
FP8Strip (FP8Base& b, uint8_t id);
~FP8Strip ();
FP8ButtonInterface& solo_button () { return _solo; }
FP8ButtonInterface& mute_button () { return _mute; }
FP8ButtonInterface& selrec_button () { return _selrec; }
FP8ButtonInterface& recarm_button () { return *_selrec.button_shift(); }
FP8ButtonInterface& select_button () { return *_selrec.button(); }
bool midi_touch (bool t);
bool midi_fader (float val);
void initialize (); // call only when connected, sends midi
void set_select_cb (boost::function<void ()>&);
enum DisplayMode {
Stripables,
PluginSelect, // no clock display
PluginParam, // param value
};
void set_periodic_display_mode (DisplayMode m) {
_displaymode = m;
}
// convenience function to call all set_XXX_controllable
void set_stripable (boost::shared_ptr<ARDOUR::Stripable>, bool panmode);
void set_text_line (uint8_t, std::string const&);
enum CtrlMask {
CTRL_FADER = 0x001,
CTRL_MUTE = 0x002,
CTRL_SOLO = 0x004,
CTRL_REC = 0x004,
CTRL_PAN = 0x008,
CTRL_SELECT = 0x010,
CTRL_TEXT1 = 0x100,
CTRL_TEXT2 = 0x200,
CTRL_TEXT3 = 0x400,
CTRL_TEXT4 = 0x800,
CTRL_TEXT = 0xf00,
CTRL_ALL = 0xfff,
};
void unset_controllables (int which = CTRL_ALL);
void set_fader_controllable (boost::shared_ptr<ARDOUR::AutomationControl>);
void set_mute_controllable (boost::shared_ptr<ARDOUR::AutomationControl>);
void set_solo_controllable (boost::shared_ptr<ARDOUR::AutomationControl>);
void set_rec_controllable (boost::shared_ptr<ARDOUR::AutomationControl>);
void set_pan_controllable (boost::shared_ptr<ARDOUR::AutomationControl>);
private:
FP8Base& _base;
uint8_t _id;
FP8MomentaryButton _solo;
FP8MomentaryButton _mute;
FP8ARMSensitiveButton _selrec;
bool _touching;
PBD::ScopedConnection _base_connection; // periodic
PBD::ScopedConnectionList _button_connections;
boost::shared_ptr<ARDOUR::Stripable> _stripable;
boost::shared_ptr<ARDOUR::AutomationControl> _fader_ctrl;
boost::shared_ptr<ARDOUR::AutomationControl> _mute_ctrl;
boost::shared_ptr<ARDOUR::AutomationControl> _solo_ctrl;
boost::shared_ptr<ARDOUR::AutomationControl> _rec_ctrl;
boost::shared_ptr<ARDOUR::AutomationControl> _pan_ctrl;
PBD::ScopedConnection _fader_connection;
PBD::ScopedConnection _mute_connection;
PBD::ScopedConnection _solo_connection;
PBD::ScopedConnection _rec_connection;
PBD::ScopedConnection _pan_connection;
boost::shared_ptr<ARDOUR::PeakMeter> _peak_meter;
boost::shared_ptr<ARDOUR::ReadOnlyControl> _redux_ctrl;
boost::function<void ()> _select_plugin_functor;
/* notifications, update view */
void notify_fader_changed ();
void notify_solo_changed ();
void notify_mute_changed ();
void notify_rec_changed ();
void notify_pan_changed ();
/* actions, update model */
void set_mute (bool);
void set_solo (bool);
void set_select ();
void set_recarm ();
/* periodic poll, update view */
void periodic_update_fader ();
void periodic_update_meter ();
void periodic_update_timecode ();
void periodic ();
/* cache */
unsigned short _last_fader;
uint8_t _last_meter;
uint8_t _last_redux;
uint8_t _last_panpos;
/* display */
void set_strip_mode (uint8_t, bool clear = false);
void set_bar_mode (uint8_t);
uint8_t _strip_mode;
uint8_t _bar_mode;
DisplayMode _displaymode;
std::string _last_line[4];
};
} /* namespace */
#endif /* _ardour_surfaces_fp8strip_h_ */

View File

@ -0,0 +1,424 @@
/*
* Copyright (C) 2017 Robin Gareus <robin@gareus.org>
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <gtkmm/alignment.h>
#include <gtkmm/label.h>
#include <gtkmm/liststore.h>
#include "pbd/unwind.h"
#include "pbd/strsplit.h"
#include "pbd/file_utils.h"
#include "gtkmm2ext/bindings.h"
#include "gtkmm2ext/gtk_ui.h"
#include "gtkmm2ext/gui_thread.h"
#include "gtkmm2ext/utils.h"
#include "ardour/audioengine.h"
#include "ardour/filesystem_paths.h"
#include "faderport8.h"
#include "gui.h"
#include "pbd/i18n.h"
using namespace PBD;
using namespace ARDOUR;
using namespace ArdourSurface;
using namespace std;
using namespace Gtk;
using namespace Gtkmm2ext;
void*
FaderPort8::get_gui () const
{
if (!gui) {
const_cast<FaderPort8*>(this)->build_gui ();
}
static_cast<Gtk::VBox*>(gui)->show_all();
return gui;
}
void
FaderPort8::tear_down_gui ()
{
if (gui) {
Gtk::Widget *w = static_cast<Gtk::VBox*>(gui)->get_parent();
if (w) {
w->hide();
delete w;
}
}
delete static_cast<FP8GUI*> (gui);
gui = 0;
}
void
FaderPort8::build_gui ()
{
gui = (void*) new FP8GUI (*this);
}
/* ****************************************************************************/
FP8GUI::FP8GUI (FaderPort8& p)
: fp (p)
, table (2, 3)
, ignore_active_change (false)
{
set_border_width (12);
table.set_row_spacings (4);
table.set_col_spacings (6);
table.set_border_width (12);
table.set_homogeneous (false);
Gtk::Label* l;
int row = 0;
input_combo.pack_start (midi_port_columns.short_name);
output_combo.pack_start (midi_port_columns.short_name);
input_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &FP8GUI::active_port_changed), &input_combo, true));
output_combo.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &FP8GUI::active_port_changed), &output_combo, false));
l = manage (new Gtk::Label);
l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Incoming MIDI on:")));
l->set_alignment (1.0, 0.5);
table.attach (*l, 0, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
table.attach (input_combo, 2, 6, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
row++;
l = manage (new Gtk::Label);
l->set_markup (string_compose ("<span weight=\"bold\">%1</span>", _("Outgoing MIDI on:")));
l->set_alignment (1.0, 0.5);
table.attach (*l, 0, 2, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0));
table.attach (output_combo, 2, 6, row, row+1, AttachOptions(FILL|EXPAND), AttachOptions(0), 0, 0);
row++;
pack_start (table);
/* actions */
build_available_action_menu ();
int action_row = 0;
int action_col = 0;
Gtk::Alignment* align;
for (FP8Controls::UserButtonMap::const_iterator i = fp.control().user_buttons ().begin ();
i != fp.control().user_buttons ().end (); ++i) {
Gtk::ComboBox* user_combo = manage (new Gtk::ComboBox);
build_action_combo (*user_combo, i->first);
l = manage (new Gtk::Label);
l->set_markup (string_compose ("<span weight=\"bold\">%1:</span>", i->second));
l->set_alignment (1.0, 0.5);
table.attach (*l, 2 * action_col, 2 * action_col + 1, row + action_row, row + action_row + 1, AttachOptions(FILL|EXPAND), AttachOptions (0));
align = manage (new Alignment);
align->set (0.0, 0.5);
align->add (*user_combo);
table.attach (*align, 2 * action_col + 1, 2 * action_col + 2, row + action_row, row + action_row + 1, AttachOptions(FILL|EXPAND), AttachOptions (0));
if (++action_row == 4) {
action_row = 0;
++action_col;
}
}
/* update the port connection combos */
update_port_combos ();
/* catch future changes to connection state */
fp.ConnectionChange.connect (connection_change_connection, invalidator (*this), boost::bind (&FP8GUI::connection_handler, this), gui_context());
}
FP8GUI::~FP8GUI ()
{
}
void
FP8GUI::connection_handler ()
{
PBD::Unwinder<bool> ici (ignore_active_change, true);
update_port_combos ();
}
void
FP8GUI::update_port_combos ()
{
vector<string> midi_inputs;
vector<string> midi_outputs;
ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsOutput|ARDOUR::IsTerminal), midi_inputs);
ARDOUR::AudioEngine::instance()->get_ports ("", ARDOUR::DataType::MIDI, ARDOUR::PortFlags (ARDOUR::IsInput|ARDOUR::IsTerminal), midi_outputs);
Glib::RefPtr<Gtk::ListStore> input = build_midi_port_list (midi_inputs, true);
Glib::RefPtr<Gtk::ListStore> output = build_midi_port_list (midi_outputs, false);
bool input_found = false;
bool output_found = false;
int n;
input_combo.set_model (input);
output_combo.set_model (output);
Gtk::TreeModel::Children children = input->children();
Gtk::TreeModel::Children::iterator i;
i = children.begin();
++i; /* skip "Disconnected" */
for (n = 1; i != children.end(); ++i, ++n) {
string port_name = (*i)[midi_port_columns.full_name];
if (fp.input_port()->connected_to (port_name)) {
input_combo.set_active (n);
input_found = true;
break;
}
}
if (!input_found) {
input_combo.set_active (0); /* disconnected */
}
children = output->children();
i = children.begin();
++i; /* skip "Disconnected" */
for (n = 1; i != children.end(); ++i, ++n) {
string port_name = (*i)[midi_port_columns.full_name];
if (fp.output_port()->connected_to (port_name)) {
output_combo.set_active (n);
output_found = true;
break;
}
}
if (!output_found) {
output_combo.set_active (0); /* disconnected */
}
}
Glib::RefPtr<Gtk::ListStore>
FP8GUI::build_midi_port_list (vector<string> const & ports, bool for_input)
{
Glib::RefPtr<Gtk::ListStore> store = ListStore::create (midi_port_columns);
TreeModel::Row row;
row = *store->append ();
row[midi_port_columns.full_name] = string();
row[midi_port_columns.short_name] = _("Disconnected");
for (vector<string>::const_iterator p = ports.begin(); p != ports.end(); ++p) {
row = *store->append ();
row[midi_port_columns.full_name] = *p;
std::string pn = ARDOUR::AudioEngine::instance()->get_pretty_name_by_name (*p);
if (pn.empty ()) {
pn = (*p).substr ((*p).find (':') + 1);
}
row[midi_port_columns.short_name] = pn;
}
return store;
}
void
FP8GUI::active_port_changed (Gtk::ComboBox* combo, bool for_input)
{
if (ignore_active_change) {
return;
}
TreeModel::iterator active = combo->get_active ();
string new_port = (*active)[midi_port_columns.full_name];
if (new_port.empty()) {
if (for_input) {
fp.input_port()->disconnect_all ();
} else {
fp.output_port()->disconnect_all ();
}
return;
}
if (for_input) {
if (!fp.input_port()->connected_to (new_port)) {
fp.input_port()->disconnect_all ();
fp.input_port()->connect (new_port);
}
} else {
if (!fp.output_port()->connected_to (new_port)) {
fp.output_port()->disconnect_all ();
fp.output_port()->connect (new_port);
}
}
}
void
FP8GUI::build_available_action_menu ()
{
/* build a model of all available actions (needs to be tree structured
* more)
*/
available_action_model = TreeStore::create (action_columns);
vector<string> paths;
vector<string> labels;
vector<string> tooltips;
vector<string> keys;
vector<Glib::RefPtr<Gtk::Action> > actions;
Gtkmm2ext::ActionMap::get_all_actions (paths, labels, tooltips, keys, actions);
typedef std::map<string,TreeIter> NodeMap;
NodeMap nodes;
NodeMap::iterator r;
vector<string>::iterator k;
vector<string>::iterator p;
vector<string>::iterator t;
vector<string>::iterator l;
available_action_model->clear ();
TreeIter rowp;
TreeModel::Row parent;
/* Disabled item (row 0) */
rowp = available_action_model->append();
parent = *(rowp);
parent[action_columns.name] = _("Disabled");
for (l = labels.begin(), k = keys.begin(), p = paths.begin(), t = tooltips.begin(); l != labels.end(); ++k, ++p, ++t, ++l) {
TreeModel::Row row;
vector<string> parts;
parts.clear ();
split (*p, parts, '/');
if (parts.empty()) {
continue;
}
//kinda kludgy way to avoid displaying menu items as mappable
if ( parts[1] == _("Main_menu") )
continue;
if ( parts[1] == _("JACK") )
continue;
if ( parts[1] == _("redirectmenu") )
continue;
if ( parts[1] == _("Editor_menus") )
continue;
if ( parts[1] == _("RegionList") )
continue;
if ( parts[1] == _("ProcessorMenu") )
continue;
if ((r = nodes.find (parts[1])) == nodes.end()) {
/* top level is missing */
TreeIter rowp;
TreeModel::Row parent;
rowp = available_action_model->append();
nodes[parts[1]] = rowp;
parent = *(rowp);
parent[action_columns.name] = parts[1];
row = *(available_action_model->append (parent.children()));
} else {
row = *(available_action_model->append ((*r->second)->children()));
}
/* add this action */
if (l->empty ()) {
row[action_columns.name] = *t;
action_map[*t] = *p;
} else {
row[action_columns.name] = *l;
action_map[*l] = *p;
}
string path = (*p);
/* ControlProtocol::access_action() is not interested in the
legacy "<Actions>/" prefix part of a path.
*/
path = path.substr (strlen ("<Actions>/"));
row[action_columns.path] = path;
}
}
bool
FP8GUI::find_action_in_model (const TreeModel::iterator& iter, std::string const& action_path, TreeModel::iterator* found)
{
TreeModel::Row row = *iter;
string path = row[action_columns.path];
if (path == action_path) {
*found = iter;
return true;
}
return false;
}
void
FP8GUI::build_action_combo (Gtk::ComboBox& cb, FP8Controls::ButtonId id)
{
cb.set_model (available_action_model);
cb.pack_start (action_columns.name);
/* set the active "row" to the right value for the current button binding */
string current_action = fp.get_button_action (id, false); /* lookup release action */
if (current_action.empty()) {
cb.set_active (0); /* "disabled" */
} else {
TreeModel::iterator iter = available_action_model->children().end();
available_action_model->foreach_iter (sigc::bind (sigc::mem_fun (*this, &FP8GUI::find_action_in_model), current_action, &iter));
if (iter != available_action_model->children().end()) {
cb.set_active (iter);
} else {
cb.set_active (0);
}
}
/* bind signal _after_ setting the current value */
cb.signal_changed().connect (sigc::bind (sigc::mem_fun (*this, &FP8GUI::action_changed), &cb, id));
}
void
FP8GUI::action_changed (Gtk::ComboBox* cb, FP8Controls::ButtonId id)
{
TreeModel::const_iterator row = cb->get_active ();
string action_path = (*row)[action_columns.path];
fp.set_button_action (id, false, action_path);
}

View File

@ -0,0 +1,98 @@
/*
* Copyright (C) 2017 Robin Gareus <robin@gareus.org>
* 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#ifndef __ardour_faderport8_gui_h__
#define __ardour_faderport8_gui_h__
#include <vector>
#include <string>
#include <gtkmm/box.h>
#include <gtkmm/combobox.h>
#include <gtkmm/image.h>
#include <gtkmm/table.h>
#include <gtkmm/treestore.h>
namespace Gtk {
class CellRendererCombo;
class ListStore;
}
#include "faderport8.h"
namespace ArdourSurface {
class FP8GUI : public Gtk::VBox
{
public:
FP8GUI (FaderPort8&);
~FP8GUI ();
private:
FaderPort8& fp;
Gtk::Table table;
Gtk::Image image;
/* port connections */
Gtk::ComboBox input_combo;
Gtk::ComboBox output_combo;
void update_port_combos ();
void connection_handler ();
PBD::ScopedConnection connection_change_connection;
struct MidiPortColumns : public Gtk::TreeModel::ColumnRecord {
MidiPortColumns() {
add (short_name);
add (full_name);
}
Gtk::TreeModelColumn<std::string> short_name;
Gtk::TreeModelColumn<std::string> full_name;
};
MidiPortColumns midi_port_columns;
bool ignore_active_change;
Glib::RefPtr<Gtk::ListStore> build_midi_port_list (std::vector<std::string> const & ports, bool for_input);
void active_port_changed (Gtk::ComboBox*,bool for_input);
/* user actions */
void build_available_action_menu ();
void build_action_combo (Gtk::ComboBox& cb, FP8Controls::ButtonId id);
void action_changed (Gtk::ComboBox* cb, FP8Controls::ButtonId id);
struct ActionColumns : public Gtk::TreeModel::ColumnRecord {
ActionColumns() {
add (name);
add (path);
}
Gtk::TreeModelColumn<std::string> name;
Gtk::TreeModelColumn<std::string> path;
};
ActionColumns action_columns;
Glib::RefPtr<Gtk::TreeStore> available_action_model;
std::map<std::string,std::string> action_map; // map from action names to paths
bool find_action_in_model (const Gtk::TreeModel::iterator& iter, std::string const & action_path, Gtk::TreeModel::iterator* found);
};
}
#endif /* __ardour_faderport8_gui_h__ */

View File

@ -0,0 +1,37 @@
#!/usr/bin/env python
from waflib.extras import autowaf as autowaf
import os
# Mandatory variables
top = '.'
out = 'build'
def options(opt):
autowaf.set_options(opt)
def configure(conf):
autowaf.configure(conf)
def build(bld):
obj = bld(features = 'cxx cxxshlib')
obj.source = [
'faderport8.cc',
'faderport8_interface.cc',
'fp8_controls.cc',
'fp8_strip.cc',
'callbacks.cc',
'actions.cc',
'gui.cc'
]
obj.export_includes = ['.']
obj.defines = [ 'PACKAGE="ardour_faderport8"' ]
obj.defines += [ 'ARDOURSURFACE_DLL_EXPORTS' ]
obj.includes = [ '.', './faderport8']
obj.name = 'libardour_faderport8'
obj.target = 'ardour_faderport8'
obj.uselib = 'GTKMM GTK GDK XML'
obj.use = 'libardour libardour_cp libgtkmm2ext libpbd'
obj.install_path = os.path.join(bld.env['LIBDIR'], 'surfaces')
def shutdown():
autowaf.shutdown()

View File

@ -22,6 +22,7 @@ out = 'build'
children = [
'control_protocol',
'faderport',
'faderport8',
'cc121',
'generic_midi',
'mackie',
@ -75,6 +76,7 @@ def build(bld):
bld.recurse('control_protocol')
bld.recurse('generic_midi')
bld.recurse('faderport')
bld.recurse('faderport8')
bld.recurse('cc121')
bld.recurse('mackie')