summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Davis <paul@linuxaudiosystems.com>2016-04-08 16:51:34 -0400
committerPaul Davis <paul@linuxaudiosystems.com>2016-05-31 15:30:40 -0400
commit4aa1c242ab1e1bf4fc3575c5d88a22824976a04f (patch)
treef71d4c56255a9c72bfd582668d03caed1d2c731c
parent653ae4acd639fef149314fe6f8c7a0d862afae40 (diff)
add new files to source tree
-rw-r--r--libs/ardour/ardour/control_group.h108
-rw-r--r--libs/ardour/ardour/monitor_control.h58
-rw-r--r--libs/ardour/ardour/monitorable.h35
-rw-r--r--libs/ardour/ardour/mute_control.h73
-rw-r--r--libs/ardour/ardour/muteable.h51
-rw-r--r--libs/ardour/ardour/phase_control.h71
-rw-r--r--libs/ardour/ardour/record_enable_control.h60
-rw-r--r--libs/ardour/ardour/recordable.h35
-rw-r--r--libs/ardour/ardour/solo_control.h99
-rw-r--r--libs/ardour/ardour/solo_isolate_control.h90
-rw-r--r--libs/ardour/ardour/solo_safe_control.h53
-rw-r--r--libs/ardour/ardour/soloable.h38
-rw-r--r--libs/ardour/control_group.cc287
-rw-r--r--libs/ardour/monitor_control.cc80
-rw-r--r--libs/ardour/mute_control.cc115
-rw-r--r--libs/ardour/muteable.cc27
-rw-r--r--libs/ardour/phase_control.cc97
-rw-r--r--libs/ardour/record_enable_control.cc73
-rw-r--r--libs/ardour/slavable_automation_control.cc226
-rw-r--r--libs/ardour/solo_control.cc264
-rw-r--r--libs/ardour/solo_isolate_control.cc180
-rw-r--r--libs/ardour/solo_safe_control.cc86
22 files changed, 2206 insertions, 0 deletions
diff --git a/libs/ardour/ardour/control_group.h b/libs/ardour/ardour/control_group.h
new file mode 100644
index 0000000000..e1b83bb7b4
--- /dev/null
+++ b/libs/ardour/ardour/control_group.h
@@ -0,0 +1,108 @@
+/*
+ Copyright (C) 2016 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is 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.
+*/
+
+#ifndef __libardour_control_group_h__
+#define __libardour_control_group_h__
+
+#include <map>
+#include <vector>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/enable_shared_from_this.hpp>
+
+#include <glibmm/threads.h>
+
+#include "pbd/controllable.h"
+
+#include "evoral/Parameter.hpp"
+
+#include "ardour/automation_control.h"
+#include "ardour/types.h"
+
+namespace ARDOUR {
+
+class LIBARDOUR_API ControlGroup : public boost::enable_shared_from_this<ControlGroup>
+{
+ public:
+ ControlGroup (Evoral::Parameter p);
+ virtual ~ControlGroup ();
+
+ enum Mode {
+ Relative = 0x1,
+ Inverted = 0x2,
+ };
+
+ int add_control (boost::shared_ptr<AutomationControl>);
+ int remove_control (boost::shared_ptr<AutomationControl>);
+
+ ControlList controls () const;
+
+ void clear ();
+
+ void set_active (bool);
+ bool active() const { return _active; }
+
+ void set_mode (Mode m);
+ Mode mode () const { return _mode; }
+
+ Evoral::Parameter parameter() const { return _parameter; }
+
+ virtual void set_group_value (boost::shared_ptr<AutomationControl>, double val);
+
+ bool use_me (PBD::Controllable::GroupControlDisposition gcd) const {
+ switch (gcd) {
+ case PBD::Controllable::ForGroup:
+ return false;
+ case PBD::Controllable::NoGroup:
+ return false;
+ case PBD::Controllable::InverseGroup:
+ return !_active;
+ default:
+ return _active;
+ }
+ }
+
+ protected:
+ typedef std::map<PBD::ID,boost::shared_ptr<AutomationControl> > ControlMap;
+ Evoral::Parameter _parameter;
+ mutable Glib::Threads::RWLock controls_lock;
+ ControlMap _controls;
+ bool _active;
+ Mode _mode;
+ PBD::ScopedConnectionList member_connections;
+ bool propagating;
+
+ void control_going_away (boost::weak_ptr<AutomationControl>);
+};
+
+
+class LIBARDOUR_API GainControlGroup : public ControlGroup
+{
+ public:
+ GainControlGroup();
+
+ void set_group_value (boost::shared_ptr<AutomationControl>, double val);
+
+ private:
+ gain_t get_max_factor (gain_t);
+ gain_t get_min_factor (gain_t);
+};
+
+} /* namespace */
+
+#endif /* __libardour_control_group_h__ */
diff --git a/libs/ardour/ardour/monitor_control.h b/libs/ardour/ardour/monitor_control.h
new file mode 100644
index 0000000000..871263a0c6
--- /dev/null
+++ b/libs/ardour/ardour/monitor_control.h
@@ -0,0 +1,58 @@
+/*
+ Copyright (C) 2016 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is 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.
+*/
+
+#ifndef __ardour_monitor_control_h__
+#define __ardour_monitor_control_h__
+
+#include <string>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/dynamic_bitset.hpp>
+
+#include "ardour/automation_control.h"
+#include "ardour/monitorable.h"
+
+#include "ardour/libardour_visibility.h"
+
+namespace ARDOUR {
+
+class Session;
+
+class LIBARDOUR_API MonitorControl : public SlavableAutomationControl
+{
+ public:
+ MonitorControl (Session& session, std::string const & name, Monitorable& m);
+ ~MonitorControl() {}
+
+ MonitorChoice monitoring_choice() const { return static_cast<MonitorChoice> (get_value()); }
+ MonitorState monitoring_state () const { return _monitorable.monitoring_state(); }
+
+ int set_state (XMLNode const&, int);
+ XMLNode& get_state ();
+
+ protected:
+ void actually_set_value (double, PBD::Controllable::GroupControlDisposition group_override);
+
+ private:
+ Monitorable& _monitorable;
+ MonitorChoice _monitoring;
+};
+
+} /* namespace */
+
+#endif /* __libardour_monitor_control_h__ */
diff --git a/libs/ardour/ardour/monitorable.h b/libs/ardour/ardour/monitorable.h
new file mode 100644
index 0000000000..03968e61a5
--- /dev/null
+++ b/libs/ardour/ardour/monitorable.h
@@ -0,0 +1,35 @@
+/*
+ Copyright (C) 2016 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is 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.
+*/
+
+#ifndef __ardour_monitorable_h__
+#define __ardour_monitorable_h__
+
+#include "ardour/types.h"
+
+namespace ARDOUR {
+
+class Monitorable {
+ public:
+ virtual ~Monitorable() {}
+
+ virtual MonitorState monitoring_state() const = 0;
+};
+
+} /* namespace */
+
+#endif /* __ardour_monitorable_h__ */
diff --git a/libs/ardour/ardour/mute_control.h b/libs/ardour/ardour/mute_control.h
new file mode 100644
index 0000000000..f9a55cacfc
--- /dev/null
+++ b/libs/ardour/ardour/mute_control.h
@@ -0,0 +1,73 @@
+/*
+ Copyright (C) 2016 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is 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.
+*/
+
+#ifndef __ardour_mute_control_h__
+#define __ardour_mute_control_h__
+
+#include <string>
+
+#include <boost/shared_ptr.hpp>
+
+#include "ardour/libardour_visibility.h"
+
+namespace ARDOUR {
+
+class Session;
+class Muteable;
+
+class LIBARDOUR_API MuteControl : public SlavableAutomationControl
+{
+ public:
+ MuteControl (Session& session, std::string const& name, Muteable&);
+
+ double get_value () const;
+
+ /* Export additional API so that objects that only get access
+ * to a Controllable/AutomationControl can do more fine-grained
+ * operations with respect to mute. Obviously, they would need
+ * to dynamic_cast<MuteControl> first.
+ *
+ * Mute state is not representable by a single scalar value,
+ * so set_value() and get_value() is not enough.
+ *
+ * This means that the Controllable is technically
+ * asymmetric. It is possible to call ::set_value (0.0) to
+ * turn off mute, and then call ::get_value() and get a
+ * return of 1.0 because the control is affected by
+ * upstream/downstream or a master.
+ */
+
+ bool muted () const;
+
+ bool muted_by_others_soloing () const;
+ bool muted_by_others () const;
+
+ void set_mute_points (MuteMaster::MutePoint);
+ MuteMaster::MutePoint mute_points () const;
+
+ protected:
+ void master_changed (bool, PBD::Controllable::GroupControlDisposition);
+ void actually_set_value (double, PBD::Controllable::GroupControlDisposition group_override);
+
+ private:
+ Muteable& _muteable;
+};
+
+} /* namespace */
+
+#endif /* __libardour_mute_control_h__ */
diff --git a/libs/ardour/ardour/muteable.h b/libs/ardour/ardour/muteable.h
new file mode 100644
index 0000000000..57db66b1ee
--- /dev/null
+++ b/libs/ardour/ardour/muteable.h
@@ -0,0 +1,51 @@
+/*
+ Copyright (C) 2016 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is 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.
+*/
+
+#ifndef __ardour_muteable_h__
+#define __ardour_muteable_h__
+
+#include <boost/shared_ptr.hpp>
+
+#include "pbd/signals.h"
+
+namespace ARDOUR {
+
+class MuteMaster;
+class Session;
+
+class Muteable {
+ public:
+ Muteable (Session&, std::string const &name);
+ virtual ~Muteable() {}
+
+ virtual bool can_be_muted_by_others () const = 0;
+ virtual void act_on_mute () {}
+
+ boost::shared_ptr<MuteMaster> mute_master() const {
+ return _mute_master;
+ }
+
+ PBD::Signal0<void> mute_points_changed;
+
+ protected:
+ boost::shared_ptr<MuteMaster> _mute_master;
+};
+
+} /* namespace */
+
+#endif /* __ardour_muteable_h__ */
diff --git a/libs/ardour/ardour/phase_control.h b/libs/ardour/ardour/phase_control.h
new file mode 100644
index 0000000000..054ad4a5a4
--- /dev/null
+++ b/libs/ardour/ardour/phase_control.h
@@ -0,0 +1,71 @@
+/*
+ Copyright (C) 2016 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is 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.
+*/
+
+#ifndef __ardour_phase_control_h__
+#define __ardour_phase_control_h__
+
+#include <string>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/dynamic_bitset.hpp>
+
+#include "ardour/automation_control.h"
+#include "ardour/libardour_visibility.h"
+
+namespace ARDOUR {
+
+class Session;
+
+/* Note that PhaseControl is not Slavable. There's no particular reason for
+ * this, it could be changed at any time. But it seems useless.
+ */
+
+class LIBARDOUR_API PhaseControl : public AutomationControl
+{
+ public:
+ PhaseControl (Session& session, std::string const & name);
+
+ /* There are two approaches to designing/using a PhaseControl. One is
+ * to have one such control for every channel of the control's
+ * owner. The other is to have a single control which manages all
+ * channels. For now (Spring 2016) we're using the second design.
+ */
+
+ void set_phase_invert (uint32_t, bool yn);
+ void set_phase_invert (boost::dynamic_bitset<>);
+ bool inverted (uint32_t chn) const { return _phase_invert[chn]; }
+
+ bool none () const { return !_phase_invert.any(); }
+ bool any() const { return _phase_invert.any(); }
+ uint64_t count() const { return _phase_invert.count(); }
+ uint64_t size() const { return _phase_invert.size(); }
+ void resize (uint32_t);
+
+ int set_state (XMLNode const&, int);
+ XMLNode& get_state ();
+
+ protected:
+ void actually_set_value (double, PBD::Controllable::GroupControlDisposition group_override);
+
+ private:
+ boost::dynamic_bitset<> _phase_invert;
+};
+
+} /* namespace */
+
+#endif /* __libardour_phase_control_h__ */
diff --git a/libs/ardour/ardour/record_enable_control.h b/libs/ardour/ardour/record_enable_control.h
new file mode 100644
index 0000000000..0bb5edac8d
--- /dev/null
+++ b/libs/ardour/ardour/record_enable_control.h
@@ -0,0 +1,60 @@
+/*
+ Copyright (C) 2016 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is 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.
+*/
+
+#ifndef __ardour_record_enable_control_h__
+#define __ardour_record_enable_control_h__
+
+#include <string>
+
+#include <boost/shared_ptr.hpp>
+#include <boost/dynamic_bitset.hpp>
+
+#include "ardour/automation_control.h"
+#include "ardour/recordable.h"
+
+#include "ardour/libardour_visibility.h"
+
+namespace ARDOUR {
+
+class Session;
+
+class LIBARDOUR_API RecordEnableControl : public SlavableAutomationControl
+{
+ public:
+ RecordEnableControl (Session& session, std::string const & name, Recordable& m);
+ ~RecordEnableControl() {}
+
+ /* Most (Slavable)AutomationControls do not override this, but we need
+ * to in order to prepare the Recordable for a change that will happen
+ * subsequently, in a realtime context. So the change is divided into
+ * two parts: the non-RT preparation, executed inside ::set_value(),
+ * then the second RT part.
+ */
+
+ void set_value (double, PBD::Controllable::GroupControlDisposition);
+
+ protected:
+ void actually_set_value (double val, Controllable::GroupControlDisposition gcd);
+
+ private:
+ Recordable& _recordable;
+};
+
+} /* namespace */
+
+#endif /* __libardour_record_enable_control_h__ */
diff --git a/libs/ardour/ardour/recordable.h b/libs/ardour/ardour/recordable.h
new file mode 100644
index 0000000000..e8568cfd1f
--- /dev/null
+++ b/libs/ardour/ardour/recordable.h
@@ -0,0 +1,35 @@
+/*
+ Copyright (C) 2016 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is 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.
+*/
+
+#ifndef __ardour_recordable_h__
+#define __ardour_recordable_h__
+
+namespace ARDOUR {
+
+class Recordable {
+ public:
+ virtual ~Recordable() {}
+
+ virtual int prep_record_enabled (bool yn) = 0;
+ virtual bool can_be_record_enabled() = 0;
+ virtual bool can_be_record_safe() = 0;
+};
+
+} /* namespace */
+
+#endif /* __ardour_recordable_h__ */
diff --git a/libs/ardour/ardour/solo_control.h b/libs/ardour/ardour/solo_control.h
new file mode 100644
index 0000000000..7f7e532669
--- /dev/null
+++ b/libs/ardour/ardour/solo_control.h
@@ -0,0 +1,99 @@
+/*
+ Copyright (C) 2016 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is 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.
+*/
+
+#ifndef __ardour_solo_control_h__
+#define __ardour_solo_control_h__
+
+#include <string>
+
+#include <boost/shared_ptr.hpp>
+
+#include "ardour/automation_control.h"
+#include "ardour/libardour_visibility.h"
+
+namespace ARDOUR {
+
+class Session;
+class Soloable;
+class Muteable;
+
+class LIBARDOUR_API SoloControl : public SlavableAutomationControl
+{
+ public:
+ SoloControl (Session& session, std::string const & name, Soloable& soloable, Muteable& m);
+
+ double get_value () const;
+
+ /* Export additional API so that objects that only get access
+ * to a Controllable/AutomationControl can do more fine-grained
+ * operations with respect to solo. Obviously, they would need
+ * to dynamic_cast<SoloControl> first.
+ *
+ * Solo state is not representable by a single scalar value,
+ * so set_value() and get_value() is not enough.
+ *
+ * This means that the Controllable is technically
+ * asymmetric. It is possible to call ::set_value (0.0) to
+ * disable (self)solo, and then call ::get_value() and get a
+ * return of 1.0 because the control is soloed by
+ * upstream/downstream or a master.
+ */
+
+ void mod_solo_by_others_upstream (int32_t delta);
+ void mod_solo_by_others_downstream (int32_t delta);
+
+ /* API to check different aspects of solo substate
+ */
+
+ bool soloed_by_others () const {
+ return _soloed_by_others_downstream || _soloed_by_others_downstream;
+ }
+ uint32_t soloed_by_others_upstream () const {
+ return _soloed_by_others_upstream;
+ }
+ uint32_t soloed_by_others_downstream () const {
+ return _soloed_by_others_downstream;
+ }
+ bool self_soloed () const {
+ return _self_solo;
+ }
+ bool soloed() const { return self_soloed() || soloed_by_others(); }
+
+ void clear_all_solo_state ();
+
+ int set_state (XMLNode const&, int);
+ XMLNode& get_state ();
+
+ protected:
+ void master_changed (bool, PBD::Controllable::GroupControlDisposition);
+ void actually_set_value (double, PBD::Controllable::GroupControlDisposition group_override);
+
+ private:
+ Soloable& _soloable;
+ Muteable& _muteable;
+ bool _self_solo;
+ uint32_t _soloed_by_others_upstream;
+ uint32_t _soloed_by_others_downstream;
+
+ void set_self_solo (bool yn);
+ void set_mute_master_solo ();
+};
+
+} /* namespace */
+
+#endif /* __libardour_solo_control_h__ */
diff --git a/libs/ardour/ardour/solo_isolate_control.h b/libs/ardour/ardour/solo_isolate_control.h
new file mode 100644
index 0000000000..cb8ea8b507
--- /dev/null
+++ b/libs/ardour/ardour/solo_isolate_control.h
@@ -0,0 +1,90 @@
+/*
+ Copyright (C) 2016 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is 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.
+*/
+
+#ifndef __ardour_solo_isolate_control_h__
+#define __ardour_solo_isolate_control_h__
+
+#include <string>
+
+#include <boost/shared_ptr.hpp>
+
+#include "ardour/libardour_visibility.h"
+
+class XMLNode;
+
+namespace ARDOUR {
+
+class Session;
+class Soloable;
+class Muteable;
+
+class LIBARDOUR_API SoloIsolateControl : public SlavableAutomationControl
+{
+ public:
+ SoloIsolateControl (Session& session, std::string const & name, Soloable& soloable, Muteable& m);
+
+ double get_value () const;
+
+ /* Export additional API so that objects that only get access
+ * to a Controllable/AutomationControl can do more fine-grained
+ * operations with respect to solo isolate. Obviously, they would need
+ * to dynamic_cast<SoloControl> first.
+ *
+ * Solo Isolate state is not representable by a single scalar value,
+ * so set_value() and get_value() is not enough.
+ *
+ * This means that the Controllable is technically
+ * asymmetric. It is possible to call ::set_value (0.0) to
+ * disable (self)solo, and then call ::get_value() and get a
+ * return of 1.0 because the control is isolated by
+ * upstream/downstream or a master.
+ */
+
+ void mod_solo_isolated_by_upstream (int32_t delta);
+
+ /* API to check different aspects of solo isolate substate
+ */
+
+ uint32_t solo_isolated_by_upstream () const {
+ return _solo_isolated_by_upstream;
+ }
+ bool self_solo_isolated () const {
+ return _solo_isolated;
+ }
+ bool solo_isolated() const { return self_solo_isolated() || solo_isolated_by_upstream(); }
+
+ int set_state (XMLNode const&, int);
+ XMLNode& get_state ();
+
+ protected:
+ void master_changed (bool from_self, PBD::Controllable::GroupControlDisposition gcd);
+ void actually_set_value (double, PBD::Controllable::GroupControlDisposition group_override);
+
+ private:
+ Soloable& _soloable;
+ Muteable& _muteable;
+ bool _solo_isolated;
+ uint32_t _solo_isolated_by_upstream;
+
+ void set_solo_isolated (bool yn, Controllable::GroupControlDisposition group_override);
+
+};
+
+} /* namespace */
+
+#endif /* __libardour_solo_isolate_control_h__ */
diff --git a/libs/ardour/ardour/solo_safe_control.h b/libs/ardour/ardour/solo_safe_control.h
new file mode 100644
index 0000000000..d230a9c2d9
--- /dev/null
+++ b/libs/ardour/ardour/solo_safe_control.h
@@ -0,0 +1,53 @@
+/*
+ Copyright (C) 2016 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is 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.
+*/
+
+#ifndef __ardour_solo_safe_control_h__
+#define __ardour_solo_safe_control_h__
+
+#include <string>
+
+#include "ardour/libardour_visibility.h"
+
+class XMLNode;
+
+namespace ARDOUR {
+
+class Session;
+
+class LIBARDOUR_API SoloSafeControl : public SlavableAutomationControl
+{
+ public:
+ SoloSafeControl (Session& session, std::string const & name);
+
+ double get_value () const;
+
+ bool solo_safe() const { return _solo_safe; }
+
+ int set_state (XMLNode const&, int);
+ XMLNode& get_state ();
+
+ protected:
+ void actually_set_value (double, PBD::Controllable::GroupControlDisposition group_override);
+
+ private:
+ bool _solo_safe;
+};
+
+} /* namespace */
+
+#endif /* __libardour_solo_safe_control_h__ */
diff --git a/libs/ardour/ardour/soloable.h b/libs/ardour/ardour/soloable.h
new file mode 100644
index 0000000000..6dafac7041
--- /dev/null
+++ b/libs/ardour/ardour/soloable.h
@@ -0,0 +1,38 @@
+/*
+ Copyright (C) 2016 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is 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.
+*/
+
+#ifndef __ardour_soloable_h__
+#define __ardour_soloable_h__
+
+#include <stdint.h>
+
+namespace ARDOUR {
+
+class Soloable {
+ public:
+ virtual ~Soloable() {}
+
+ virtual void push_solo_upstream (int32_t delta) = 0;
+ virtual void push_solo_isolate_upstream (int32_t delta) = 0;
+ virtual bool is_safe () const = 0;
+ virtual bool can_solo () const = 0;
+};
+
+} /* namespace */
+
+#endif /* __ardour_soloable_h__ */
diff --git a/libs/ardour/control_group.cc b/libs/ardour/control_group.cc
new file mode 100644
index 0000000000..6752940d27
--- /dev/null
+++ b/libs/ardour/control_group.cc
@@ -0,0 +1,287 @@
+/*
+ Copyright (C) 2016 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is 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 <vector>
+
+#include "pbd/unwind.h"
+
+#include "ardour/control_group.h"
+#include "ardour/gain_control.h"
+
+using namespace ARDOUR;
+using namespace PBD;
+
+ControlGroup::ControlGroup (Evoral::Parameter p)
+ : _parameter (p)
+ , _active (true)
+ , _mode (Mode (0))
+ , propagating (false)
+{
+}
+
+
+ControlGroup::~ControlGroup ()
+{
+ clear ();
+}
+
+void
+ControlGroup::set_active (bool yn)
+{
+ _active = yn;
+ std::cerr << " CG for " << enum_2_string ((AutomationType) _parameter.type()) << " now active ? " << _active << std::endl;
+}
+
+void
+ControlGroup::clear ()
+{
+ /* we're giving up on all members, so we don't care about their
+ * DropReferences signals anymore
+ */
+
+ member_connections.drop_connections ();
+
+ /* make a copy so that when the control calls ::remove_control(), we
+ * don't deadlock.
+ */
+
+ std::vector<boost::shared_ptr<AutomationControl> > controls;
+ {
+ Glib::Threads::RWLock::WriterLock lm (controls_lock);
+ for (ControlMap::const_iterator i = _controls.begin(); i != _controls.end(); ++i) {
+ controls.push_back (i->second);
+ }
+ }
+
+ _controls.clear ();
+
+ for (std::vector<boost::shared_ptr<AutomationControl> >::iterator c = controls.begin(); c != controls.end(); ++c) {
+ (*c)->set_group (boost::shared_ptr<ControlGroup>());
+ }
+}
+
+ControlList
+ControlGroup::controls () const
+{
+ ControlList c;
+
+ if (_active) {
+ Glib::Threads::RWLock::WriterLock lm (controls_lock);
+ for (ControlMap::const_iterator i = _controls.begin(); i != _controls.end(); ++i) {
+ c.push_back (i->second);
+ }
+ }
+
+ return c;
+}
+
+void
+ControlGroup::control_going_away (boost::weak_ptr<AutomationControl> wac)
+{
+ boost::shared_ptr<AutomationControl> ac (wac.lock());
+ if (!ac) {
+ return;
+ }
+
+ remove_control (ac);
+}
+
+int
+ControlGroup::remove_control (boost::shared_ptr<AutomationControl> ac)
+{
+ Glib::Threads::RWLock::WriterLock lm (controls_lock);
+ /* return zero if erased, non-zero otherwise */
+ return !(_controls.erase (ac->id()) > 0);
+}
+
+int
+ControlGroup::add_control (boost::shared_ptr<AutomationControl> ac)
+{
+ if (ac->parameter() != _parameter) {
+ return -1;
+ }
+
+ std::pair<ControlMap::iterator,bool> res;
+
+ {
+ Glib::Threads::RWLock::WriterLock lm (controls_lock);
+ res = _controls.insert (std::make_pair (ac->id(), ac));
+ }
+
+ if (!res.second) {
+ /* already in ControlMap */
+ return -1;
+ }
+
+ /* Inserted */
+
+ ac->set_group (shared_from_this());
+
+ ac->DropReferences.connect_same_thread (member_connections, boost::bind (&ControlGroup::control_going_away, this, boost::weak_ptr<AutomationControl>(ac)));
+
+ return 0;
+}
+
+void
+ControlGroup::set_group_value (boost::shared_ptr<AutomationControl> control, double val)
+{
+ double old = control->get_value ();
+
+ /* set the primary control */
+
+ control->set_value (val, Controllable::ForGroup);
+
+ /* now propagate across the group */
+
+ Glib::Threads::RWLock::ReaderLock lm (controls_lock);
+
+ if (_mode & Relative) {
+
+ const double factor = old / control->get_value ();
+
+ for (ControlMap::iterator c = _controls.begin(); c != _controls.end(); ++c) {
+ if (c->second != control) {
+ c->second->set_value (factor * c->second->get_value(), Controllable::ForGroup);
+ }
+ }
+
+ } else {
+
+ for (ControlMap::iterator c = _controls.begin(); c != _controls.end(); ++c) {
+ if (c->second != control) {
+ c->second->set_value (val, Controllable::ForGroup);
+ }
+ }
+ }
+}
+
+/*---- GAIN CONTROL GROUP -----------*/
+
+gain_t
+GainControlGroup::get_min_factor (gain_t factor)
+{
+ /* CALLER MUST HOLD READER LOCK */
+
+ for (ControlMap::iterator c = _controls.begin(); c != _controls.end(); ++c) {
+ gain_t const g = c->second->get_value();
+
+ if ((g + g * factor) >= 0.0f) {
+ continue;
+ }
+
+ if (g <= 0.0000003f) {
+ return 0.0f;
+ }
+
+ factor = 0.0000003f / g - 1.0f;
+ }
+
+ return factor;
+}
+
+gain_t
+GainControlGroup::get_max_factor (gain_t factor)
+{
+ /* CALLER MUST HOLD READER LOCK */
+
+ for (ControlMap::iterator c = _controls.begin(); c != _controls.end(); ++c) {
+ gain_t const g = c->second->get_value();
+
+ // if the current factor woulnd't raise this route above maximum
+ if ((g + g * factor) <= 1.99526231f) {
+ continue;
+ }
+
+ // if route gain is already at peak, return 0.0f factor
+ if (g >= 1.99526231f) {
+ return 0.0f;
+ }
+
+ // factor is calculated so that it would raise current route to max
+ factor = 1.99526231f / g - 1.0f;
+ }
+
+ return factor;
+}
+
+void
+GainControlGroup::set_group_value (boost::shared_ptr<AutomationControl> control, double val)
+{
+ /* set the primary control */
+
+ control->set_value (val, Controllable::ForGroup);
+
+ /* now propagate across the group */
+
+ Glib::Threads::RWLock::ReaderLock lm (controls_lock);
+
+ if (_mode & Relative) {
+
+ gain_t usable_gain = control->get_value();
+
+ if (usable_gain < 0.000001f) {
+ usable_gain = 0.000001f;
+ }
+
+ gain_t delta = val;
+ if (delta < 0.000001f) {
+ delta = 0.000001f;
+ }
+
+ delta -= usable_gain;
+
+ if (delta == 0.0f)
+ return;
+
+ gain_t factor = delta / usable_gain;
+
+ if (factor > 0.0f) {
+ factor = get_max_factor (factor);
+ if (factor == 0.0f) {
+ control->Changed (true, Controllable::ForGroup); /* EMIT SIGNAL */
+ return;
+ }
+ } else {
+ factor = get_min_factor (factor);
+ if (factor == 0.0f) {
+ control->Changed (true, Controllable::ForGroup); /* EMIT SIGNAL */
+ return;
+ }
+ }
+
+ for (ControlMap::iterator c = _controls.begin(); c != _controls.end(); ++c) {
+ if (c->second == control) {
+ continue;
+ }
+
+ boost::shared_ptr<GainControl> gc = boost::dynamic_pointer_cast<GainControl> (c->second);
+
+ if (gc) {
+ gc->inc_gain (factor);
+ }
+ }
+
+ } else {
+
+ for (ControlMap::iterator c = _controls.begin(); c != _controls.end(); ++c) {
+ if (c->second != control) {
+ c->second->set_value (val, Controllable::ForGroup);
+ }
+ }
+ }
+}
diff --git a/libs/ardour/monitor_control.cc b/libs/ardour/monitor_control.cc
new file mode 100644
index 0000000000..c16de3b7c1
--- /dev/null
+++ b/libs/ardour/monitor_control.cc
@@ -0,0 +1,80 @@
+/*
+ Copyright (C) 2016 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is 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 "ardour/monitor_control.h"
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+using namespace PBD;
+
+MonitorControl::MonitorControl (Session& session, std::string const & name, Monitorable& m)
+ : SlavableAutomationControl (session, MonitoringAutomation, ParameterDescriptor (MonitoringAutomation),
+ boost::shared_ptr<AutomationList>(new AutomationList(Evoral::Parameter(MonitoringAutomation))),
+ name)
+
+ , _monitorable (m)
+ , _monitoring (MonitorAuto)
+{
+ _list->set_interpolation(Evoral::ControlList::Discrete);
+ /* monitoring changes must be synchronized by the process cycle */
+ set_flags (Controllable::Flag (flags() | Controllable::RealTime));
+}
+
+void
+MonitorControl::actually_set_value (double val, Controllable::GroupControlDisposition gcd)
+{
+ int v = (int) val;
+ switch (v) {
+ case MonitorAuto:
+ case MonitorInput:
+ case MonitorDisk:
+ case MonitorCue:
+ break;
+ default:
+ /* illegal value */
+ return;
+ }
+
+ _monitoring = MonitorChoice (v);
+ AutomationControl::actually_set_value (val, gcd);
+}
+
+XMLNode&
+MonitorControl::get_state ()
+{
+ XMLNode& node (SlavableAutomationControl::get_state());
+ node.add_property (X_("monitoring"), enum_2_string (_monitoring));
+ return node;
+}
+
+int
+MonitorControl::set_state (XMLNode const & node, int version)
+{
+ SlavableAutomationControl::set_state (node, version);
+
+ const XMLProperty* prop;
+
+ if ((prop = node.property (X_("monitoring"))) != 0) {
+ _monitoring = MonitorChoice (string_2_enum (prop->value(), _monitoring));
+ } else {
+ _monitoring = MonitorAuto;
+ }
+
+ return 0;
+}
diff --git a/libs/ardour/mute_control.cc b/libs/ardour/mute_control.cc
new file mode 100644
index 0000000000..b0513d1138
--- /dev/null
+++ b/libs/ardour/mute_control.cc
@@ -0,0 +1,115 @@
+/*
+ Copyright (C) 2016 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is 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 "evoral/ControlList.hpp"
+
+#include "ardour/mute_master.h"
+#include "ardour/session.h"
+#include "ardour/mute_control.h"
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+using namespace std;
+
+
+MuteControl::MuteControl (Session& session, std::string const & name, Muteable& m)
+ : SlavableAutomationControl (session, MuteAutomation, ParameterDescriptor (MuteAutomation),
+ boost::shared_ptr<AutomationList> (new AutomationList (Evoral::Parameter (MuteAutomation))),
+ name)
+ , _muteable (m)
+{
+ _list->set_interpolation (Evoral::ControlList::Discrete);
+ /* mute changes must be synchronized by the process cycle */
+ set_flags (Controllable::Flag (flags() | Controllable::RealTime));
+}
+
+void
+MuteControl::master_changed (bool from_self, PBD::Controllable::GroupControlDisposition gcd)
+{
+ bool master_muted;
+
+ {
+ Glib::Threads::RWLock::ReaderLock lm (master_lock);
+ master_muted = (bool) get_masters_value_locked ();
+ }
+
+ _muteable.mute_master()->mod_muted_by_others (master_muted ? 1 : -1);
+
+ SlavableAutomationControl::master_changed (false, gcd);
+}
+
+void
+MuteControl::actually_set_value (double val, Controllable::GroupControlDisposition gcd)
+{
+ if (muted() != bool (val)) {
+ _muteable.mute_master()->set_muted_by_self (val);
+
+ /* allow the Muteable to respond to the mute change
+ before anybody else knows about it.
+ */
+ _muteable.act_on_mute ();
+ }
+
+ AutomationControl::actually_set_value (val, gcd);
+}
+
+double
+MuteControl::get_value () const
+{
+ if (slaved()) {
+ Glib::Threads::RWLock::ReaderLock lm (master_lock);
+ return get_masters_value_locked () ? 1.0 : 0.0;
+ }
+
+ if (_list && boost::dynamic_pointer_cast<AutomationList>(_list)->automation_playback()) {
+ // Playing back automation, get the value from the list
+ return AutomationControl::get_value();
+ }
+
+ return muted() ? 1.0 : 0.0;
+}
+
+void
+MuteControl::set_mute_points (MuteMaster::MutePoint mp)
+{
+ _muteable.mute_master()->set_mute_points (mp);
+ _muteable.mute_points_changed (); /* EMIT SIGNAL */
+
+ if (_muteable.mute_master()->muted_by_self()) {
+ Changed (true, Controllable::UseGroup); /* EMIT SIGNAL */
+ }
+}
+
+MuteMaster::MutePoint
+MuteControl::mute_points () const
+{
+ return _muteable.mute_master()->mute_points ();
+}
+
+bool
+MuteControl::muted () const
+{
+ return _muteable.mute_master()->muted_by_self();
+}
+
+bool
+MuteControl::muted_by_others () const
+{
+ return _muteable.mute_master()->muted_by_others ();
+}
diff --git a/libs/ardour/muteable.cc b/libs/ardour/muteable.cc
new file mode 100644
index 0000000000..9d434888bf
--- /dev/null
+++ b/libs/ardour/muteable.cc
@@ -0,0 +1,27 @@
+/*
+ Copyright (C) 2016 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is 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 "ardour/muteable.h"
+#include "ardour/mute_master.h"
+
+using namespace ARDOUR;
+
+Muteable::Muteable (Session& s, std::string const & name)
+ : _mute_master (new MuteMaster (s, name))
+{
+}
diff --git a/libs/ardour/phase_control.cc b/libs/ardour/phase_control.cc
new file mode 100644
index 0000000000..bc237893a0
--- /dev/null
+++ b/libs/ardour/phase_control.cc
@@ -0,0 +1,97 @@
+/*
+ Copyright (C) 2016 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is 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 "ardour/phase_control.h"
+#include "ardour/session.h"
+
+#include "i18n.h"
+
+using namespace std;
+using namespace PBD;
+using namespace ARDOUR;
+
+PhaseControl::PhaseControl (Session& session, std::string const & name)
+ : AutomationControl (session, PhaseAutomation, ParameterDescriptor (PhaseAutomation),
+ boost::shared_ptr<AutomationList>(new AutomationList(Evoral::Parameter(PhaseAutomation))),
+ name)
+{
+}
+
+void
+PhaseControl::actually_set_value (double val, Controllable::GroupControlDisposition gcd)
+{
+ _phase_invert = boost::dynamic_bitset<> (std::numeric_limits<double>::digits, (unsigned long) val);
+
+ AutomationControl::actually_set_value (val, gcd);
+}
+
+/** @param c Audio channel index.
+ * @param yn true to invert phase, otherwise false.
+ */
+void
+PhaseControl::set_phase_invert (uint32_t c, bool yn)
+{
+ if (_phase_invert[c] != yn) {
+ _phase_invert[c] = yn;
+ AutomationControl::actually_set_value (_phase_invert.to_ulong(), Controllable::NoGroup);
+ _session.set_dirty ();
+ }
+}
+
+void
+PhaseControl::set_phase_invert (boost::dynamic_bitset<> p)
+{
+ if (_phase_invert != p) {
+ _phase_invert = p;
+ AutomationControl::actually_set_value (_phase_invert.to_ulong(), Controllable::NoGroup);
+ Changed (true, Controllable::NoGroup); /* EMIT SIGNAL */
+ _session.set_dirty ();
+ }
+}
+
+void
+PhaseControl::resize (uint32_t n)
+{
+ _phase_invert.resize (n);
+}
+
+XMLNode&
+PhaseControl::get_state ()
+{
+ XMLNode& node (AutomationControl::get_state ());
+
+ string p;
+ boost::to_string (_phase_invert, p);
+ node.add_property("phase-invert", p);
+
+ return node;
+}
+
+int
+PhaseControl::set_state (XMLNode const & node, int version)
+{
+ AutomationControl::set_state (node, version);
+
+ const XMLProperty* prop;
+
+ if ((prop = node.property (X_("phase-invert"))) != 0) {
+ set_phase_invert (boost::dynamic_bitset<> (prop->value ()));
+ }
+
+ return 0;
+}
diff --git a/libs/ardour/record_enable_control.cc b/libs/ardour/record_enable_control.cc
new file mode 100644
index 0000000000..f71fd5034b
--- /dev/null
+++ b/libs/ardour/record_enable_control.cc
@@ -0,0 +1,73 @@
+/*
+ Copyright (C) 2016 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is 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 "ardour/audioengine.h"
+#include "ardour/record_enable_control.h"
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+using namespace PBD;
+
+RecordEnableControl::RecordEnableControl (Session& session, std::string const & name, Recordable& r)
+ : SlavableAutomationControl (session, RecEnableAutomation, ParameterDescriptor (RecEnableAutomation),
+ boost::shared_ptr<AutomationList>(new AutomationList(Evoral::Parameter(RecEnableAutomation))),
+ name)
+ , _recordable (r)
+{
+ _list->set_interpolation(Evoral::ControlList::Discrete);
+
+ /* record-enable changes must be synchronized by the process cycle */
+ set_flags (Controllable::Flag (flags() | Controllable::RealTime));
+}
+
+void
+RecordEnableControl::set_value (double val, Controllable::GroupControlDisposition gcd)
+{
+ /* do the non-RT part of rec-enabling first - the RT part will be done
+ * on the next process cycle. This does mean that theoretically we are
+ * doing things provisionally on the assumption that the rec-enable
+ * change will work, but this had better be a solid assumption for
+ * other reasons.
+ */
+
+ if (!AudioEngine::instance()->in_process_thread()) {
+ if (_recordable.prep_record_enabled (val)) {
+ /* failed */
+ std::cerr << "Prep rec-enable failed\n";
+ return;
+ }
+ }
+
+ /* Because we are marked as a RealTime control, this will queue
+ up the control change to be executed in a realtime context.
+ */
+ SlavableAutomationControl::set_value (val, gcd);
+}
+
+void
+RecordEnableControl::actually_set_value (double val, Controllable::GroupControlDisposition gcd)
+{
+ if (val && !_recordable.can_be_record_enabled()) {
+ std::cerr << "rec-enable not allowed\n";
+ return;
+ }
+
+ SlavableAutomationControl::actually_set_value (val, gcd);
+}
+
diff --git a/libs/ardour/slavable_automation_control.cc b/libs/ardour/slavable_automation_control.cc
new file mode 100644
index 0000000000..900e640e55
--- /dev/null
+++ b/libs/ardour/slavable_automation_control.cc
@@ -0,0 +1,226 @@
+/*
+ Copyright (C) 2016 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is 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.
+*/
+
+#ifndef __libardour_slavable_automation_control_h__
+#define __libardour_slavable_automation_control_h__
+
+#include "ardour/automation_control.h"
+#include "ardour/session.h"
+
+using namespace std;
+using namespace ARDOUR;
+using namespace PBD;
+
+SlavableAutomationControl::SlavableAutomationControl(ARDOUR::Session& s,
+ const Evoral::Parameter& parameter,
+ const ParameterDescriptor& desc,
+ boost::shared_ptr<ARDOUR::AutomationList> l,
+ const std::string& name)
+ : AutomationControl (s, parameter, desc, l, name)
+{
+}
+
+SlavableAutomationControl::~SlavableAutomationControl ()
+{
+
+}
+
+double
+SlavableAutomationControl::get_masters_value_locked () const
+{
+ gain_t v = 1.0;
+
+ for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
+ /* get current master value, scale by our current ratio with that master */
+ v *= mr->second.master()->get_value () * mr->second.ratio();
+ }
+
+ return min (_desc.upper, v);
+}
+
+double
+SlavableAutomationControl::get_value_locked() const
+{
+ /* read or write masters lock must be held */
+
+ if (_masters.empty()) {
+ return Control::get_double (false, _session.transport_frame());
+ }
+
+ return get_masters_value_locked ();
+}
+
+/** Get the current effective `user' value based on automation state */
+double
+SlavableAutomationControl::get_value() const
+{
+ bool from_list = _list && ((AutomationList*)_list.get())->automation_playback();
+
+ if (!from_list) {
+ Glib::Threads::RWLock::ReaderLock lm (master_lock);
+ return get_value_locked ();
+ } else {
+ return Control::get_double (from_list, _session.transport_frame());
+ }
+}
+
+void
+SlavableAutomationControl::add_master (boost::shared_ptr<AutomationControl> m)
+{
+ double current_value;
+ double new_value;
+ std::pair<Masters::iterator,bool> res;
+
+ {
+ Glib::Threads::RWLock::WriterLock lm (master_lock);
+ current_value = get_value_locked ();
+
+ /* ratio will be recomputed below */
+
+ res = _masters.insert (make_pair<PBD::ID,MasterRecord> (m->id(), MasterRecord (m, 1.0)));
+
+ if (res.second) {
+
+ recompute_masters_ratios (current_value);
+
+ /* note that we bind @param m as a weak_ptr<AutomationControl>, thus
+ avoiding holding a reference to the control in the binding
+ itself.
+ */
+
+ m->DropReferences.connect_same_thread (masters_connections, boost::bind (&SlavableAutomationControl::master_going_away, this, m));
+
+ /* Store the connection inside the MasterRecord, so that when we destroy it, the connection is destroyed
+ and we no longer hear about changes to the AutomationControl.
+
+ Note that we fix the "from_self" argument that will
+ be given to our own Changed signal to "false",
+ because the change came from the master.
+ */
+
+
+ m->Changed.connect_same_thread (res.first->second.connection, boost::bind (&SlavableAutomationControl::master_changed, this, _1, _2));
+ }
+
+ new_value = get_value_locked ();
+ }
+
+ if (res.second) {
+ /* this will notify everyone that we're now slaved to the master */
+ MasterStatusChange (); /* EMIT SIGNAL */
+ }
+
+ if (new_value != current_value) {
+ /* force a call to to ::master_changed() to carry the
+ * consequences that would occur if the master assumed
+ * its current value WHILE we were slaved.
+ */
+ master_changed (false, Controllable::NoGroup);
+ /* effective value changed by master */
+ Changed (false, Controllable::NoGroup);
+ }
+
+}
+
+void
+SlavableAutomationControl::master_changed (bool /*from_self*/, GroupControlDisposition gcd)
+{
+ /* our value has (likely) changed, but not because we were
+ * modified. Just the master.
+ */
+
+ Changed (false, gcd); /* EMIT SIGNAL */
+}
+
+void
+SlavableAutomationControl::master_going_away (boost::weak_ptr<AutomationControl> wm)
+{
+ boost::shared_ptr<AutomationControl> m = wm.lock();
+ if (m) {
+ remove_master (m);
+ }
+}
+
+void
+SlavableAutomationControl::remove_master (boost::shared_ptr<AutomationControl> m)
+{
+ double current_value;
+ double new_value;
+ Masters::size_type erased = 0;
+
+ {
+ Glib::Threads::RWLock::WriterLock lm (master_lock);
+ current_value = get_value_locked ();
+ erased = _masters.erase (m->id());
+ if (erased) {
+ recompute_masters_ratios (current_value);
+ }
+ new_value = get_value_locked ();
+ }
+
+ if (erased) {
+ MasterStatusChange (); /* EMIT SIGNAL */
+ }
+
+ if (new_value != current_value) {
+ Changed (false, Controllable::NoGroup);
+ }
+}
+
+void
+SlavableAutomationControl::clear_masters ()
+{
+ double current_value;
+ double new_value;
+ bool had_masters = false;
+
+ {
+ Glib::Threads::RWLock::WriterLock lm (master_lock);
+ current_value = get_value_locked ();
+ if (!_masters.empty()) {
+ had_masters = true;
+ }
+ _masters.clear ();
+ new_value = get_value_locked ();
+ }
+
+ if (had_masters) {
+ MasterStatusChange (); /* EMIT SIGNAL */
+ }
+
+ if (new_value != current_value) {
+ Changed (false, Controllable::NoGroup);
+ }
+
+}
+
+bool
+SlavableAutomationControl::slaved_to (boost::shared_ptr<AutomationControl> m) const
+{
+ Glib::Threads::RWLock::ReaderLock lm (master_lock);
+ return _masters.find (m->id()) != _masters.end();
+}
+
+bool
+SlavableAutomationControl::slaved () const
+{
+ Glib::Threads::RWLock::ReaderLock lm (master_lock);
+ return !_masters.empty();
+}
+
+#endif /* __libardour_slavable_automation_control_h__ */
diff --git a/libs/ardour/solo_control.cc b/libs/ardour/solo_control.cc
new file mode 100644
index 0000000000..45ec5b90ec
--- /dev/null
+++ b/libs/ardour/solo_control.cc
@@ -0,0 +1,264 @@
+/*
+ Copyright (C) 2016 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is 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 "ardour/debug.h"
+#include "ardour/mute_master.h"
+#include "ardour/session.h"
+#include "ardour/solo_control.h"
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+using namespace std;
+using namespace PBD;
+
+SoloControl::SoloControl (Session& session, std::string const & name, Soloable& s, Muteable& m)
+ : SlavableAutomationControl (session, SoloAutomation, ParameterDescriptor (SoloAutomation),
+ boost::shared_ptr<AutomationList>(new AutomationList(Evoral::Parameter(SoloAutomation))),
+ name)
+ , _soloable (s)
+ , _muteable (m)
+ , _self_solo (false)
+ , _soloed_by_others_upstream (0)
+ , _soloed_by_others_downstream (0)
+{
+ _list->set_interpolation (Evoral::ControlList::Discrete);
+ /* solo changes must be synchronized by the process cycle */
+ set_flags (Controllable::Flag (flags() | Controllable::RealTime));
+}
+
+void
+SoloControl::set_self_solo (bool yn)
+{
+ DEBUG_TRACE (DEBUG::Solo, string_compose ("%1: set SELF solo => %2\n", name(), yn));
+ _self_solo = yn;
+ set_mute_master_solo ();
+}
+
+void
+SoloControl::set_mute_master_solo ()
+{
+ _muteable.mute_master()->set_soloed_by_self (self_soloed());
+
+ if (Config->get_solo_control_is_listen_control()) {
+ _muteable.mute_master()->set_soloed_by_others (false);
+ } else {
+ _muteable.mute_master()->set_soloed_by_others (soloed_by_others_downstream() || soloed_by_others_upstream());
+ }
+}
+
+void
+SoloControl::master_changed (bool from_self, PBD::Controllable::GroupControlDisposition gcd)
+{
+ if (_soloable.is_safe() || !_soloable.can_solo()) {
+ return;
+ }
+
+ bool master_soloed;
+
+ {
+ Glib::Threads::RWLock::ReaderLock lm (master_lock);
+ master_soloed = (bool) get_masters_value_locked ();
+ }
+
+ /* Master is considered equivalent to an upstream solo control, not
+ * direct control over self-soloed.
+ */
+
+ mod_solo_by_others_upstream (master_soloed ? 1 : -1);
+
+ /* no need to call AutomationControl::master_changed() since it just
+ emits Changed() which we already did in mod_solo_by_others_upstream()
+ */
+}
+
+void
+SoloControl::mod_solo_by_others_downstream (int32_t delta)
+{
+ if (_soloable.is_safe() || !_soloable.can_solo()) {
+ return;
+ }
+
+ DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 mod solo-by-downstream by %2, current up = %3 down = %4\n",
+ name(), delta, _soloed_by_others_upstream, _soloed_by_others_downstream));
+
+ if (delta < 0) {
+ if (_soloed_by_others_downstream >= (uint32_t) abs (delta)) {
+ _soloed_by_others_downstream += delta;
+ } else {
+ _soloed_by_others_downstream = 0;
+ }
+ } else {
+ _soloed_by_others_downstream += delta;
+ }
+
+ DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 SbD delta %2 = %3\n", name(), delta, _soloed_by_others_downstream));
+
+ set_mute_master_solo ();
+ Changed (false, Controllable::UseGroup); /* EMIT SIGNAL */
+}
+
+void
+SoloControl::mod_solo_by_others_upstream (int32_t delta)
+{
+ if (_soloable.is_safe() || !_soloable.can_solo()) {
+ return;
+ }
+
+ DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 mod solo-by-upstream by %2, current up = %3 down = %4\n",
+ name(), delta, _soloed_by_others_upstream, _soloed_by_others_downstream));
+
+ uint32_t old_sbu = _soloed_by_others_upstream;
+
+ if (delta < 0) {
+ if (_soloed_by_others_upstream >= (uint32_t) abs (delta)) {
+ _soloed_by_others_upstream += delta;
+ } else {
+ _soloed_by_others_upstream = 0;
+ }
+ } else {
+ _soloed_by_others_upstream += delta;
+ }
+
+ DEBUG_TRACE (DEBUG::Solo, string_compose (
+ "%1 SbU delta %2 = %3 old = %4 sbd %5 ss %6 exclusive %7\n",
+ name(), delta, _soloed_by_others_upstream, old_sbu,
+ _soloed_by_others_downstream, _self_solo, Config->get_exclusive_solo()));
+
+
+ /* push the inverse solo change to everything that feeds us.
+
+ This is important for solo-within-group. When we solo 1 track out of N that
+ feed a bus, that track will cause mod_solo_by_upstream (+1) to be called
+ on the bus. The bus then needs to call mod_solo_by_downstream (-1) on all
+ tracks that feed it. This will silence them if they were audible because
+ of a bus solo, but the newly soloed track will still be audible (because
+ it is self-soloed).
+
+ but .. do this only when we are being told to solo-by-upstream (i.e delta = +1),
+ not in reverse.
+ */
+
+ if ((_self_solo || _soloed_by_others_downstream) &&
+ ((old_sbu == 0 && _soloed_by_others_upstream > 0) ||
+ (old_sbu > 0 && _soloed_by_others_upstream == 0))) {
+
+ if (delta > 0 || !Config->get_exclusive_solo()) {
+ _soloable.push_solo_upstream (delta);
+ }
+ }
+
+ set_mute_master_solo ();
+ Changed (false, Controllable::NoGroup); /* EMIT SIGNAL */
+}
+
+void
+SoloControl::actually_set_value (double val, PBD::Controllable::GroupControlDisposition group_override)
+{
+ if (_soloable.is_safe() || !_soloable.can_solo()) {
+ return;
+ }
+
+ set_self_solo (val == 1.0);
+
+ /* this sets the Evoral::Control::_user_value for us, which will
+ be retrieved by AutomationControl::get_value (), and emits Changed
+ */
+
+ AutomationControl::actually_set_value (val, group_override);
+ _session.set_dirty ();
+}
+
+double
+SoloControl::get_value () const
+{
+ if (slaved()) {
+ Glib::Threads::RWLock::ReaderLock lm (master_lock);
+ return get_masters_value_locked () ? 1.0 : 0.0;
+ }
+
+ std::cerr << "solo control @ " << this << " list = " << _list << " as AL " << boost::dynamic_pointer_cast<AutomationList>(_list) << std::endl;
+
+ if (_list && boost::dynamic_pointer_cast<AutomationList>(_list)->automation_playback()) {
+ // Playing back automation, get the value from the list
+ return AutomationControl::get_value();
+ }
+
+ return self_soloed() ? 1.0 : 0.0;
+}
+
+void
+SoloControl::clear_all_solo_state ()
+{
+ // ideally this function will never do anything, it only exists to forestall Murphy
+
+#ifndef NDEBUG
+ // these are really debug messages, but of possible interest.
+ if (self_soloed()) {
+ PBD::info << string_compose (_("Cleared Explicit solo: %1\n"), name());
+ }
+ if (_soloed_by_others_upstream || _soloed_by_others_downstream) {
+ PBD::info << string_compose (_("Cleared Implicit solo: %1 up:%2 down:%3\n"),
+ name(), _soloed_by_others_upstream, _soloed_by_others_downstream);
+ }
+#endif
+
+ _soloed_by_others_upstream = 0;
+ _soloed_by_others_downstream = 0;
+
+ set_self_solo (false);
+
+ Changed (false, Controllable::UseGroup); /* EMIT SIGNAL */
+}
+
+int
+SoloControl::set_state (XMLNode const & node, int)
+{
+ XMLProperty const * prop;
+
+ if ((prop = node.property ("self-solo")) != 0) {
+ set_self_solo (string_is_affirmative (prop->value()));
+ }
+
+ if ((prop = node.property ("soloed-by-upstream")) != 0) {
+ _soloed_by_others_upstream = 0; // needed for mod_.... () to work
+ mod_solo_by_others_upstream (atoi (prop->value()));
+ }
+
+ if ((prop = node.property ("soloed-by-downstream")) != 0) {
+ _soloed_by_others_downstream = 0; // needed for mod_.... () to work
+ mod_solo_by_others_downstream (atoi (prop->value()));
+ }
+
+ return 0;
+}
+
+XMLNode&
+SoloControl::get_state ()
+{
+ XMLNode& node (SlavableAutomationControl::get_state());
+
+ node.add_property (X_("self-solo"), _self_solo ? X_("yes") : X_("no"));
+ char buf[32];
+ snprintf (buf, sizeof(buf), "%d", _soloed_by_others_upstream);
+ node.add_property (X_("soloed-by-upstream"), buf);
+ snprintf (buf, sizeof(buf), "%d", _soloed_by_others_downstream);
+ node.add_property (X_("soloed-by-downstream"), buf);
+
+ return node;
+}
diff --git a/libs/ardour/solo_isolate_control.cc b/libs/ardour/solo_isolate_control.cc
new file mode 100644
index 0000000000..d85a973ba3
--- /dev/null
+++ b/libs/ardour/solo_isolate_control.cc
@@ -0,0 +1,180 @@
+/*
+ Copyright (C) 2016 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is 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 "ardour/debug.h"
+#include "ardour/mute_master.h"
+#include "ardour/session.h"
+#include "ardour/solo_isolate_control.h"
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+using namespace std;
+using namespace PBD;
+
+SoloIsolateControl::SoloIsolateControl (Session& session, std::string const & name, Soloable& s, Muteable& m)
+ : SlavableAutomationControl (session, SoloAutomation, ParameterDescriptor (SoloIsolateAutomation),
+ boost::shared_ptr<AutomationList>(new AutomationList(Evoral::Parameter(SoloIsolateAutomation))),
+ name)
+ , _soloable (s)
+ , _muteable (m)
+ , _solo_isolated (false)
+ , _solo_isolated_by_upstream (0)
+{
+ _list->set_interpolation (Evoral::ControlList::Discrete);
+ /* isolate changes must be synchronized by the process cycle */
+ set_flags (Controllable::Flag (flags() | Controllable::RealTime));
+}
+
+void
+SoloIsolateControl::master_changed (bool from_self, PBD::Controllable::GroupControlDisposition gcd)
+{
+ if (!_soloable.can_solo()) {
+ return;
+ }
+
+ bool master_soloed;
+
+ {
+ Glib::Threads::RWLock::ReaderLock lm (master_lock);
+ master_soloed = (bool) get_masters_value_locked ();
+ }
+
+ /* Master is considered equivalent to an upstream solo control, not
+ * direct control over self-soloed.
+ */
+
+ mod_solo_isolated_by_upstream (master_soloed ? 1 : -1);
+
+ /* no need to call AutomationControl::master_changed() since it just
+ emits Changed() which we already did in mod_solo_by_others_upstream()
+ */
+}
+
+void
+SoloIsolateControl::mod_solo_isolated_by_upstream (int32_t delta)
+{
+ bool old = solo_isolated ();
+ DEBUG_TRACE (DEBUG::Solo, string_compose ("%1 mod_solo_isolated_by_upstream cur: %2 d: %3\n",
+ name(), _solo_isolated_by_upstream, delta));
+
+ if (delta < 0) {
+ if (_solo_isolated_by_upstream >= (uint32_t) abs(delta)) {
+ _solo_isolated_by_upstream += delta;
+ } else {
+ _solo_isolated_by_upstream = 0;
+ }
+ } else {
+ _solo_isolated_by_upstream += delta;
+ }
+
+ if (solo_isolated() != old) {
+ /* solo isolated status changed */
+ _muteable.mute_master()->set_solo_ignore (solo_isolated());
+ Changed (false, Controllable::NoGroup); /* EMIT SIGNAL */
+ }
+}
+
+void
+SoloIsolateControl::actually_set_value (double val, PBD::Controllable::GroupControlDisposition gcd)
+{
+ if (!_soloable.can_solo()) {
+ return;
+ }
+
+ set_solo_isolated (val == 0.0 ? false : true, gcd);
+
+ /* this sets the Evoral::Control::_user_value for us, which will
+ be retrieved by AutomationControl::get_value (), and emits Changed
+ */
+
+ AutomationControl::actually_set_value (val, gcd);
+ _session.set_dirty ();
+}
+
+void
+SoloIsolateControl::set_solo_isolated (bool yn, Controllable::GroupControlDisposition group_override)
+{
+ if (_soloable.can_solo()) {
+ return;
+ }
+
+ bool changed = false;
+
+ if (yn) {
+ if (_solo_isolated == false) {
+ _muteable.mute_master()->set_solo_ignore (true);
+ changed = true;
+ }
+ _solo_isolated = true;
+ } else {
+ if (_solo_isolated == true) {
+ _solo_isolated = false;
+ _muteable.mute_master()->set_solo_ignore (false);
+ changed = true;
+ }
+ }
+
+
+ if (!changed) {
+ return;
+ }
+
+ _soloable.push_solo_isolate_upstream (yn ? 1 : -1);
+
+ /* XXX should we back-propagate as well? (April 2010: myself and chris goddard think not) */
+
+ Changed (true, group_override); /* EMIT SIGNAL */
+}
+
+
+double
+SoloIsolateControl::get_value () const
+{
+ if (slaved()) {
+ Glib::Threads::RWLock::ReaderLock lm (master_lock);
+ return get_masters_value_locked () ? 1.0 : 0.0;
+ }
+
+ if (_list && boost::dynamic_pointer_cast<AutomationList>(_list)->automation_playback()) {
+ // Playing back automation, get the value from the list
+ return AutomationControl::get_value();
+ }
+
+ return solo_isolated () ? 1.0 : 0.0;
+}
+
+int
+SoloIsolateControl::set_state (XMLNode const & node, int)
+{
+ XMLProperty const * prop;
+
+ if ((prop = node.property ("solo-isolated")) != 0) {
+ _solo_isolated = string_is_affirmative (prop->value());
+ }
+
+ return 0;
+}
+
+XMLNode&
+SoloIsolateControl::get_state ()
+{
+ XMLNode& node (SlavableAutomationControl::get_state());
+ node.add_property (X_("solo-isolated"), _solo_isolated ? X_("yes") : X_("no"));
+ return node;
+}
diff --git a/libs/ardour/solo_safe_control.cc b/libs/ardour/solo_safe_control.cc
new file mode 100644
index 0000000000..040f454ffc
--- /dev/null
+++ b/libs/ardour/solo_safe_control.cc
@@ -0,0 +1,86 @@
+/*
+ Copyright (C) 2016 Paul Davis
+
+ This program is free software; you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 2 of the License, or (at your option)
+ any later version.
+
+ This program is 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 "ardour/debug.h"
+#include "ardour/mute_master.h"
+#include "ardour/session.h"
+#include "ardour/solo_isolate_control.h"
+
+#include "i18n.h"
+
+using namespace ARDOUR;
+using namespace std;
+using namespace PBD;
+
+SoloSafeControl::SoloSafeControl (Session& session, std::string const & name)
+ : SlavableAutomationControl (session, SoloAutomation, ParameterDescriptor (SoloSafeAutomation),
+ boost::shared_ptr<AutomationList>(new AutomationList(Evoral::Parameter(SoloSafeAutomation))),
+ name)
+ , _solo_safe (false)
+{
+ _list->set_interpolation(Evoral::ControlList::Discrete);
+}
+
+void
+SoloSafeControl::actually_set_value (double val, PBD::Controllable::GroupControlDisposition gcd)
+{
+ _solo_safe = (val ? true : false);
+
+ /* this sets the Evoral::Control::_user_value for us, which will
+ be retrieved by AutomationControl::get_value (), and emits Changed
+ */
+
+ AutomationControl::actually_set_value (val, gcd);
+ _session.set_dirty ();
+}
+
+double
+SoloSafeControl::get_value () const
+{
+ if (slaved()) {
+ Glib::Threads::RWLock::ReaderLock lm (master_lock);
+ return get_masters_value_locked () ? 1.0 : 0.0;
+ }
+
+ if (_list && boost::dynamic_pointer_cast<AutomationList>(_list)->automation_playback()) {
+ // Playing back automation, get the value from the list
+ return AutomationControl::get_value();
+ }
+
+ return _solo_safe ? 1.0 : 0.0;
+}
+
+int
+SoloSafeControl::set_state (XMLNode const & node, int)
+{
+ XMLProperty const * prop;
+
+ if ((prop = node.property ("solo-safe")) != 0) {
+ _solo_safe = string_is_affirmative (prop->value());
+ }
+
+ return 0;
+}
+
+XMLNode&
+SoloSafeControl::get_state ()
+{
+ XMLNode& node (SlavableAutomationControl::get_state());
+ node.add_property (X_("solo-safe"), _solo_safe ? X_("yes") : X_("no"));
+ return node;
+}