redesign control slave/master system, move code from GainControl to AutomationControl
[ardour.git] / libs / ardour / gain_control.cc
1 /*
2     Copyright (C) 2006-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 #include <cmath>
20
21 #include "pbd/convert.h"
22 #include "pbd/strsplit.h"
23
24 #include "ardour/dB.h"
25 #include "ardour/gain_control.h"
26 #include "ardour/session.h"
27 #include "ardour/vca.h"
28 #include "ardour/vca_manager.h"
29
30 #include "i18n.h"
31
32 using namespace ARDOUR;
33 using namespace std;
34
35 GainControl::GainControl (Session& session, const Evoral::Parameter &param, boost::shared_ptr<AutomationList> al)
36         : AutomationControl (session, param, ParameterDescriptor(param),
37                              al ? al : boost::shared_ptr<AutomationList> (new AutomationList (param)),
38                              param.type() == GainAutomation ? X_("gaincontrol") : X_("trimcontrol")) {
39
40         alist()->reset_default (1.0);
41
42         lower_db = accurate_coefficient_to_dB (_desc.lower);
43         range_db = accurate_coefficient_to_dB (_desc.upper) - lower_db;
44 }
45
46 void
47 GainControl::set_value (double val, PBD::Controllable::GroupControlDisposition group_override)
48 {
49         if (writable()) {
50                 _set_value (val, group_override);
51         }
52 }
53
54 void
55 GainControl::set_value_unchecked (double val)
56 {
57         /* used only automation playback */
58         _set_value (val, Controllable::NoGroup);
59 }
60
61 void
62 GainControl::_set_value (double val, Controllable::GroupControlDisposition group_override)
63 {
64         val = std::max (std::min (val, (double)_desc.upper), (double)_desc.lower);
65
66         {
67                 Glib::Threads::RWLock::WriterLock lm (master_lock);
68
69                 if (!_masters.empty()) {
70                         recompute_masters_ratios (val);
71                 }
72         }
73
74         /* this sets the Evoral::Control::_user_value for us, which will
75            be retrieved by AutomationControl::get_value ()
76         */
77
78         AutomationControl::set_value (val, group_override);
79
80         _session.set_dirty ();
81 }
82
83 double
84 GainControl::internal_to_interface (double v) const
85 {
86         if (_desc.type == GainAutomation) {
87                 return gain_to_slider_position (v);
88         } else {
89                 return (accurate_coefficient_to_dB (v) - lower_db) / range_db;
90         }
91 }
92
93 double
94 GainControl::interface_to_internal (double v) const
95 {
96         if (_desc.type == GainAutomation) {
97                 return slider_position_to_gain (v);
98         } else {
99                 return dB_to_coefficient (lower_db + v * range_db);
100         }
101 }
102
103 double
104 GainControl::internal_to_user (double v) const
105 {
106         return accurate_coefficient_to_dB (v);
107 }
108
109 double
110 GainControl::user_to_internal (double u) const
111 {
112         return dB_to_coefficient (u);
113 }
114
115 std::string
116 GainControl::get_user_string () const
117 {
118         char theBuf[32]; sprintf( theBuf, _("%3.1f dB"), accurate_coefficient_to_dB (get_value()));
119         return std::string(theBuf);
120 }
121
122 void
123 GainControl::recompute_masters_ratios (double val)
124 {
125         /* Master WRITE lock must be held */
126
127         /* V' is the new gain value for this
128
129            Mv(n) is the return value of ::get_value() for the n-th master
130            Mr(n) is the return value of ::ratio() for the n-th master record
131
132            the slave should return V' on the next call to ::get_value().
133
134            but the value is determined by the masters, so we know:
135
136            V' = (Mv(1) * Mr(1)) * (Mv(2) * Mr(2)) * ... * (Mv(n) * Mr(n))
137
138            hence:
139
140            Mr(1) * Mr(2) * ... * (Mr(n) = V' / (Mv(1) * Mv(2) * ... * Mv(n))
141
142            if we make all ratios equal (i.e. each master contributes the same
143            fraction of its own gain level to make the final slave gain), then we
144            have:
145
146            pow (Mr(n), n) = V' / (Mv(1) * Mv(2) * ... * Mv(n))
147
148            which gives
149
150            Mr(n) = pow ((V' / (Mv(1) * Mv(2) * ... * Mv(n))), 1/n)
151
152            Mr(n) is the new ratio number for the slaves
153         */
154
155
156         const double nmasters = _masters.size();
157         double masters_total_gain_coefficient = 1.0;
158
159         for (Masters::iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
160                 masters_total_gain_coefficient *= mr->second.master()->get_value();
161         }
162
163         const double new_universal_ratio = pow ((val / masters_total_gain_coefficient), (1.0/nmasters));
164
165         for (Masters::iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
166                 mr->second.reset_ratio (new_universal_ratio);
167         }
168 }
169
170 XMLNode&
171 GainControl::get_state ()
172 {
173         XMLNode& node (AutomationControl::get_state());
174
175 #if 0
176         /* store VCA master IDs */
177
178         string str;
179
180         {
181                 Glib::Threads::RWLock::ReaderLock lm (master_lock);
182                 for (Masters::const_iterator mr = _masters.begin(); mr != _masters.end(); ++mr) {
183                         if (!str.empty()) {
184                                 str += ',';
185                         }
186                         str += PBD::to_string (mr->first, std::dec);
187                 }
188         }
189
190         if (!str.empty()) {
191                 node.add_property (X_("masters"), str);
192         }
193 #endif
194
195         return node;
196 }
197
198 int
199 GainControl::set_state (XMLNode const& node, int version)
200 {
201         AutomationControl::set_state (node, version);
202
203 #if 0
204         XMLProperty const* prop = node.property (X_("masters"));
205
206         /* Problem here if we allow VCA's to be slaved to other VCA's .. we
207          * have to load all VCAs first, then set up slave/master relationships
208          * once we have them all.
209          */
210
211         if (prop) {
212                 masters_string = prop->value ();
213
214                 if (_session.vca_manager().vcas_loaded()) {
215                         vcas_loaded ();
216                 } else {
217                         _session.vca_manager().VCAsLoaded.connect_same_thread (vca_loaded_connection, boost::bind (&GainControl::vcas_loaded, this));
218                 }
219         }
220 #endif
221
222         return 0;
223 }
224
225 void
226 GainControl::vcas_loaded ()
227 {
228         if (masters_string.empty()) {
229                 return;
230         }
231
232         vector<string> masters;
233         split (masters_string, masters, ',');
234
235         for (vector<string>::const_iterator m = masters.begin(); m != masters.end(); ++m) {
236                 boost::shared_ptr<VCA> vca = _session.vca_manager().vca_by_number (PBD::atoi (*m));
237                 if (vca) {
238                         add_master (vca->gain_control());
239                 }
240         }
241
242         vca_loaded_connection.disconnect ();
243         masters_string.clear ();
244 }