07a2d5633a70779b0b15986b3acaf6455b150409
[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 "ardour/automation_control.h"
23 #include "ardour/session.h"
24
25 using namespace std;
26 using namespace ARDOUR;
27 using namespace PBD;
28
29 SlavableAutomationControl::SlavableAutomationControl(ARDOUR::Session& s,
30                                                      const Evoral::Parameter&                  parameter,
31                                                      const ParameterDescriptor&                desc,
32                                                      boost::shared_ptr<ARDOUR::AutomationList> l,
33                                                      const std::string&                        name)
34         : AutomationControl (s, parameter, desc, l, name)
35 {
36 }
37
38 SlavableAutomationControl::~SlavableAutomationControl ()
39 {
40
41 }
42
43 double
44 SlavableAutomationControl::get_masters_value_locked () const
45 {
46         gain_t v = _desc.normal;
47
48         for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
49                 if (_desc.toggled) {
50                         /* if any master is enabled, the slaves are too */
51                         if (mr->second.master()->get_value()) {
52                                 return _desc.upper;
53                         }
54                 } else {
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
60         return min (_desc.upper, v);
61 }
62
63 double
64 SlavableAutomationControl::get_value_locked() const
65 {
66         /* read or write masters lock must be held */
67
68         if (_masters.empty()) {
69                 return Control::get_double (false, _session.transport_frame());
70         }
71
72         return get_masters_value_locked ();
73 }
74
75 /** Get the current effective `user' value based on automation state */
76 double
77 SlavableAutomationControl::get_value() const
78 {
79         bool from_list = _list && boost::dynamic_pointer_cast<AutomationList>(_list)->automation_playback();
80
81         if (!from_list) {
82                 Glib::Threads::RWLock::ReaderLock lm (master_lock);
83                 return get_value_locked ();
84         } else {
85                 return Control::get_double (from_list, _session.transport_frame());
86         }
87 }
88
89 void
90 SlavableAutomationControl::add_master (boost::shared_ptr<AutomationControl> m)
91 {
92         double current_value;
93         double new_value;
94         std::pair<Masters::iterator,bool> res;
95
96         {
97                 Glib::Threads::RWLock::WriterLock lm (master_lock);
98                 current_value = get_value_locked ();
99
100                 /* ratio will be recomputed below */
101
102                 res = _masters.insert (make_pair<PBD::ID,MasterRecord> (m->id(), MasterRecord (m, 1.0)));
103
104                 if (res.second) {
105
106                         recompute_masters_ratios (current_value);
107
108                         /* note that we bind @param m as a weak_ptr<AutomationControl>, thus
109                            avoiding holding a reference to the control in the binding
110                            itself.
111                         */
112
113                         m->DropReferences.connect_same_thread (masters_connections, boost::bind (&SlavableAutomationControl::master_going_away, this, m));
114
115                         /* Store the connection inside the MasterRecord, so that when we destroy it, the connection is destroyed
116                            and we no longer hear about changes to the AutomationControl.
117
118                            Note that we fix the "from_self" argument that will
119                            be given to our own Changed signal to "false",
120                            because the change came from the master.
121                         */
122
123                         m->Changed.connect_same_thread (res.first->second.connection, boost::bind (&SlavableAutomationControl::master_changed, this, _1, _2));
124                         cerr << this << enum_2_string ((AutomationType) _parameter.type()) << " now listening to Changed from " << m << endl;
125                 }
126
127                 new_value = get_value_locked ();
128         }
129
130         if (res.second) {
131                 /* this will notify everyone that we're now slaved to the master */
132                 MasterStatusChange (); /* EMIT SIGNAL */
133         }
134
135         if (new_value != current_value) {
136                 /* need to do this without a writable() check in case
137                  * the master is removed while this control is doing
138                  * automation playback.
139                  */
140                  actually_set_value (new_value, Controllable::NoGroup);
141         }
142
143 }
144
145 void
146 SlavableAutomationControl::master_changed (bool /*from_self*/, GroupControlDisposition gcd)
147 {
148         cerr << this << enum_2_string ((AutomationType)_parameter.type()) << " master changed, relay changed along\n";
149
150         /* our value has (likely) changed, but not because we were
151          * modified. Just the master.
152          */
153
154         Changed (false, gcd); /* EMIT SIGNAL */
155 }
156
157 void
158 SlavableAutomationControl::master_going_away (boost::weak_ptr<AutomationControl> wm)
159 {
160         boost::shared_ptr<AutomationControl> m = wm.lock();
161         if (m) {
162                 remove_master (m);
163         }
164 }
165
166 void
167 SlavableAutomationControl::remove_master (boost::shared_ptr<AutomationControl> m)
168 {
169         double current_value;
170         double new_value;
171         Masters::size_type erased = 0;
172
173         {
174                 Glib::Threads::RWLock::WriterLock lm (master_lock);
175                 current_value = get_value_locked ();
176                 erased = _masters.erase (m->id());
177                 if (erased) {
178                         recompute_masters_ratios (current_value);
179                 }
180                 new_value = get_value_locked ();
181         }
182
183         if (erased) {
184                 MasterStatusChange (); /* EMIT SIGNAL */
185         }
186
187         if (new_value != current_value) {
188                 Changed (false, Controllable::NoGroup);
189         }
190 }
191
192 void
193 SlavableAutomationControl::clear_masters ()
194 {
195         double current_value;
196         double new_value;
197         bool had_masters = false;
198
199         {
200                 Glib::Threads::RWLock::WriterLock lm (master_lock);
201                 current_value = get_value_locked ();
202                 if (!_masters.empty()) {
203                         had_masters = true;
204                 }
205                 _masters.clear ();
206                 new_value = get_value_locked ();
207         }
208
209         if (had_masters) {
210                 MasterStatusChange (); /* EMIT SIGNAL */
211         }
212
213         if (new_value != current_value) {
214                 Changed (false, Controllable::NoGroup);
215         }
216
217 }
218
219 bool
220 SlavableAutomationControl::slaved_to (boost::shared_ptr<AutomationControl> m) const
221 {
222         Glib::Threads::RWLock::ReaderLock lm (master_lock);
223         return _masters.find (m->id()) != _masters.end();
224 }
225
226 bool
227 SlavableAutomationControl::slaved () const
228 {
229         Glib::Threads::RWLock::ReaderLock lm (master_lock);
230         return !_masters.empty();
231 }
232
233 #endif /* __libardour_slavable_automation_control_h__ */