move SlavableAutomationControl into its own header
[ardour.git] / libs / ardour / slavable_automation_control.cc
1 /*
2     Copyright (C) 2016 Paul Davis
3
4     This program is free software; you can redistribute it and/or modify it
5     under the terms of the GNU General Public License as published by the Free
6     Software Foundation; either version 2 of the License, or (at your option)
7     any later version.
8
9     This program is distributed in the hope that it will be useful, but WITHOUT
10     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11     FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12     for more details.
13
14     You should have received a copy of the GNU General Public License along
15     with this program; if not, write to the Free Software Foundation, Inc.,
16     675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19 #ifndef __libardour_slavable_automation_control_h__
20 #define __libardour_slavable_automation_control_h__
21
22 #include "pbd/enumwriter.h"
23
24 #include "ardour/slavable_automation_control.h"
25 #include "ardour/session.h"
26
27 using namespace std;
28 using namespace ARDOUR;
29 using namespace PBD;
30
31 SlavableAutomationControl::SlavableAutomationControl(ARDOUR::Session& s,
32                                                      const Evoral::Parameter&                  parameter,
33                                                      const ParameterDescriptor&                desc,
34                                                      boost::shared_ptr<ARDOUR::AutomationList> l,
35                                                      const std::string&                        name)
36         : AutomationControl (s, parameter, desc, l, name)
37 {
38 }
39
40 double
41 SlavableAutomationControl::get_masters_value_locked () const
42 {
43         double v = _desc.normal;
44
45         if (_desc.toggled) {
46                 for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
47                         if (mr->second.master()->get_value()) {
48                                 return _desc.upper;
49                         }
50                 }
51                 return _desc.lower;
52         }
53
54         for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
55                 /* get current master value, scale by our current ratio with that master */
56                 v *= mr->second.master()->get_value () * mr->second.ratio();
57         }
58
59         return min ((double) _desc.upper, v);
60 }
61
62 double
63 SlavableAutomationControl::get_value_locked() const
64 {
65         /* read or write masters lock must be held */
66
67         if (_masters.empty()) {
68                 return Control::get_double (false, _session.transport_frame());
69         }
70
71         if (_desc.toggled) {
72                 /* for boolean/toggle controls, if this slave OR any master is
73                  * enabled, this slave is enabled. So check our own value
74                  * first, because if we are enabled, we can return immediately.
75                  */
76                 if (Control::get_double (false, _session.transport_frame())) {
77                         return _desc.upper;
78                 }
79         }
80
81         return get_masters_value_locked ();
82 }
83
84 /** Get the current effective `user' value based on automation state */
85 double
86 SlavableAutomationControl::get_value() const
87 {
88         bool from_list = _list && boost::dynamic_pointer_cast<AutomationList>(_list)->automation_playback();
89
90         if (!from_list) {
91                 Glib::Threads::RWLock::ReaderLock lm (master_lock);
92                 return get_value_locked ();
93         } else {
94                 return Control::get_double (from_list, _session.transport_frame());
95         }
96 }
97
98 void
99 SlavableAutomationControl::actually_set_value (double val, Controllable::GroupControlDisposition group_override)
100 {
101         val = std::max (std::min (val, (double)_desc.upper), (double)_desc.lower);
102
103         {
104                 Glib::Threads::RWLock::WriterLock lm (master_lock);
105
106                 if (!_masters.empty()) {
107                         recompute_masters_ratios (val);
108                 }
109         }
110
111         /* this sets the Evoral::Control::_user_value for us, which will
112            be retrieved by AutomationControl::get_value ()
113         */
114         AutomationControl::actually_set_value (val, group_override);
115
116         _session.set_dirty ();
117 }
118
119 void
120 SlavableAutomationControl::add_master (boost::shared_ptr<AutomationControl> m)
121 {
122         double current_value;
123         double new_value;
124         std::pair<Masters::iterator,bool> res;
125
126         {
127                 Glib::Threads::RWLock::WriterLock lm (master_lock);
128                 current_value = get_value_locked ();
129
130                 /* ratio will be recomputed below */
131
132                 res = _masters.insert (make_pair<PBD::ID,MasterRecord> (m->id(), MasterRecord (m, 1.0)));
133
134                 if (res.second) {
135
136                         if (_desc.toggled) {
137                                 recompute_masters_ratios (current_value);
138                         }
139
140                         /* note that we bind @param m as a weak_ptr<AutomationControl>, thus
141                            avoiding holding a reference to the control in the binding
142                            itself.
143                         */
144
145                         m->DropReferences.connect_same_thread (masters_connections, boost::bind (&SlavableAutomationControl::master_going_away, this, m));
146
147                         /* Store the connection inside the MasterRecord, so that when we destroy it, the connection is destroyed
148                            and we no longer hear about changes to the AutomationControl.
149
150                            Note that we fix the "from_self" argument that will
151                            be given to our own Changed signal to "false",
152                            because the change came from the master.
153                         */
154
155                         m->Changed.connect_same_thread (res.first->second.connection, boost::bind (&SlavableAutomationControl::master_changed, this, _1, _2));
156                         cerr << this << enum_2_string ((AutomationType) _parameter.type()) << " now listening to Changed from " << m << endl;
157                 }
158
159                 new_value = get_value_locked ();
160         }
161
162         if (res.second) {
163                 /* this will notify everyone that we're now slaved to the master */
164                 MasterStatusChange (); /* EMIT SIGNAL */
165         }
166
167         if (new_value != current_value) {
168                 /* need to do this without a writable() check in case
169                  * the master is removed while this control is doing
170                  * automation playback.
171                  */
172                  actually_set_value (new_value, Controllable::NoGroup);
173         }
174
175 }
176
177 void
178 SlavableAutomationControl::master_changed (bool /*from_self*/, GroupControlDisposition gcd)
179 {
180         /* our value has (likely) changed, but not because we were
181          * modified. Just the master.
182          */
183
184         /* propagate master state into our own control so that if we stop
185          * being slaved, our value doesn't change, and propagate to any
186          * group this control is part of.
187          */
188
189         cerr << this << ' ' << enum_2_string ((AutomationType) _parameter.type()) << " pass along " << get_masters_value() << " from master to group\n";
190         actually_set_value (get_masters_value(), Controllable::UseGroup);
191 }
192
193 void
194 SlavableAutomationControl::master_going_away (boost::weak_ptr<AutomationControl> wm)
195 {
196         boost::shared_ptr<AutomationControl> m = wm.lock();
197         if (m) {
198                 remove_master (m);
199         }
200 }
201
202 void
203 SlavableAutomationControl::remove_master (boost::shared_ptr<AutomationControl> m)
204 {
205         double current_value;
206         double new_value;
207         bool masters_left;
208         Masters::size_type erased = 0;
209
210         {
211                 Glib::Threads::RWLock::WriterLock lm (master_lock);
212                 current_value = get_value_locked ();
213                 erased = _masters.erase (m->id());
214                 if (erased) {
215                         recompute_masters_ratios (current_value);
216                 }
217                 masters_left = _masters.size ();
218                 new_value = get_value_locked ();
219         }
220
221         if (erased) {
222                 MasterStatusChange (); /* EMIT SIGNAL */
223         }
224
225         if (new_value != current_value) {
226                 if (masters_left == 0) {
227                         /* no masters left, make sure we keep the same value
228                            that we had before.
229                         */
230                         actually_set_value (current_value, Controllable::UseGroup);
231                 }
232         }
233 }
234
235 void
236 SlavableAutomationControl::clear_masters ()
237 {
238         double current_value;
239         double new_value;
240         bool had_masters = false;
241
242         {
243                 Glib::Threads::RWLock::WriterLock lm (master_lock);
244                 current_value = get_value_locked ();
245                 if (!_masters.empty()) {
246                         had_masters = true;
247                 }
248                 _masters.clear ();
249                 new_value = get_value_locked ();
250         }
251
252         if (had_masters) {
253                 MasterStatusChange (); /* EMIT SIGNAL */
254         }
255
256         if (new_value != current_value) {
257                 Changed (false, Controllable::NoGroup);
258         }
259
260 }
261
262 bool
263 SlavableAutomationControl::slaved_to (boost::shared_ptr<AutomationControl> m) const
264 {
265         Glib::Threads::RWLock::ReaderLock lm (master_lock);
266         return _masters.find (m->id()) != _masters.end();
267 }
268
269 bool
270 SlavableAutomationControl::slaved () const
271 {
272         Glib::Threads::RWLock::ReaderLock lm (master_lock);
273         return !_masters.empty();
274 }
275
276 #endif /* __libardour_slavable_automation_control_h__ */