1 /*
2 * Copyright (C) 2020-2021 Luciano Iam <oss@lucianoiam.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License 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 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17 */
18
19 #include "ardour/plugin_insert.h"
20 #include "ardour/session.h"
21 #include "ardour/tempo.h"
22
23 #include "pbd/abstract_ui.cc" // instantiate template
24
25 #include "feedback.h"
26 #include "transport.h"
27 #include "server.h"
28 #include "state.h"
29
30 // TO DO: make this configurable
31 #define POLL_INTERVAL_MS 100
32
33 using namespace ARDOUR;
34 using namespace ArdourSurface;
35
36 struct TransportObserver {
operator ()TransportObserver37 void operator() (ArdourFeedback* p)
38 {
39 p->update_all (Node::transport_roll, p->transport ().roll ());
40 }
41 };
42
43 struct RecordStateObserver {
operator ()RecordStateObserver44 void operator() (ArdourFeedback* p)
45 {
46 p->update_all (Node::transport_record, p->transport ().record ());
47 }
48 };
49
50 struct TempoObserver {
operator ()TempoObserver51 void operator() (ArdourFeedback* p)
52 {
53 p->update_all (Node::transport_tempo, p->transport ().tempo ());
54 }
55 };
56
57 struct StripGainObserver {
operator ()StripGainObserver58 void operator() (ArdourFeedback* p, uint32_t strip_id)
59 {
60 // fires multiple times (4x as of ardour 6.0)
61 p->update_all (Node::strip_gain, strip_id, p->mixer ().strip (strip_id).gain ());
62 }
63 };
64
65 struct StripPanObserver {
operator ()StripPanObserver66 void operator() (ArdourFeedback* p, uint32_t strip_id)
67 {
68 p->update_all (Node::strip_pan, strip_id, p->mixer ().strip (strip_id).pan ());
69 }
70 };
71
72 struct StripMuteObserver {
operator ()StripMuteObserver73 void operator() (ArdourFeedback* p, uint32_t strip_id)
74 {
75 p->update_all (Node::strip_mute, strip_id, p->mixer ().strip (strip_id).mute ());
76 }
77 };
78
79 struct PluginBypassObserver {
operator ()PluginBypassObserver80 void operator() (ArdourFeedback* p, uint32_t strip_id, uint32_t plugin_id)
81 {
82 p->update_all (Node::strip_plugin_enable, strip_id, plugin_id,
83 p->mixer ().strip (strip_id).plugin (plugin_id).enabled ());
84 }
85 };
86
87 struct PluginParamValueObserver {
operator ()PluginParamValueObserver88 void operator() (ArdourFeedback* p, uint32_t strip_id, uint32_t plugin_id,
89 uint32_t param_id, boost::weak_ptr<AutomationControl> ctrl)
90 {
91 boost::shared_ptr<AutomationControl> control = ctrl.lock ();
92
93 if (!control) {
94 return;
95 }
96
97 p->update_all (Node::strip_plugin_param_value, strip_id, plugin_id, param_id,
98 ArdourMixerPlugin::param_value (control));
99 }
100 };
101
FeedbackHelperUI()102 FeedbackHelperUI::FeedbackHelperUI()
103 : AbstractUI<BaseUI::BaseRequestObject> ("WS_FeedbackHelperUI")
104 {
105 char name[64];
106 snprintf (name, 64, "WS-%p", (void*)DEBUG_THREAD_SELF);
107 pthread_set_name (name);
108 set_event_loop_for_thread (this);
109 }
110
111 void
do_request(BaseUI::BaseRequestObject * req)112 FeedbackHelperUI::do_request (BaseUI::BaseRequestObject* req) {
113 if (req->type == CallSlot) {
114 call_slot (MISSING_INVALIDATOR, req->the_slot);
115 } else if (req->type == Quit) {
116 quit ();
117 }
118 };
119
120 int
start()121 ArdourFeedback::start ()
122 {
123 observe_transport ();
124 observe_mixer ();
125
126 // some values need polling like the strip meters
127 Glib::RefPtr<Glib::TimeoutSource> periodic_timeout = Glib::TimeoutSource::create (POLL_INTERVAL_MS);
128 _periodic_connection = periodic_timeout->connect (sigc::mem_fun (*this,
129 &ArdourFeedback::poll));
130
131 // server must be started before feedback otherwise
132 // read_blocks_event_loop() will always return false
133 if (server ().read_blocks_event_loop ()) {
134 _helper.run();
135 periodic_timeout->attach (_helper.main_loop()->get_context ());
136 } else {
137 periodic_timeout->attach (main_loop ()->get_context ());
138 }
139
140 return 0;
141 }
142
143 int
stop()144 ArdourFeedback::stop ()
145 {
146 if (server ().read_blocks_event_loop ()) {
147 _helper.quit();
148 }
149
150 _periodic_connection.disconnect ();
151 _transport_connections.drop_connections ();
152
153 return 0;
154 }
155
156 void
update_all(std::string node,TypedValue value) const157 ArdourFeedback::update_all (std::string node, TypedValue value) const
158 {
159 update_all (node, ADDR_NONE, ADDR_NONE, ADDR_NONE, value);
160 }
161
162 void
update_all(std::string node,uint32_t strip_id,TypedValue value) const163 ArdourFeedback::update_all (std::string node, uint32_t strip_id, TypedValue value) const
164 {
165 update_all (node, strip_id, ADDR_NONE, ADDR_NONE, value);
166 }
167
168 void
update_all(std::string node,uint32_t strip_id,uint32_t plugin_id,TypedValue value) const169 ArdourFeedback::update_all (std::string node, uint32_t strip_id, uint32_t plugin_id,
170 TypedValue value) const
171 {
172 update_all (node, strip_id, plugin_id, ADDR_NONE, value);
173 }
174
175 void
update_all(std::string node,uint32_t strip_id,uint32_t plugin_id,uint32_t param_id,TypedValue value) const176 ArdourFeedback::update_all (std::string node, uint32_t strip_id, uint32_t plugin_id, uint32_t param_id,
177 TypedValue value) const
178 {
179 AddressVector addr = AddressVector ();
180
181 if (strip_id != ADDR_NONE) {
182 addr.push_back (strip_id);
183 }
184
185 if (plugin_id != ADDR_NONE) {
186 addr.push_back (plugin_id);
187 }
188
189 if (param_id != ADDR_NONE) {
190 addr.push_back (param_id);
191 }
192
193 ValueVector val = ValueVector ();
194 val.push_back (value);
195
196 server ().update_all_clients (NodeState (node, addr, val), false);
197 }
198
199 PBD::EventLoop*
event_loop() const200 ArdourFeedback::event_loop () const
201 {
202 if (server ().read_blocks_event_loop ()) {
203 return static_cast<PBD::EventLoop*> (&_helper);
204 } else {
205 return SurfaceComponent::event_loop ();
206 }
207 }
208
209 bool
poll() const210 ArdourFeedback::poll () const
211 {
212 update_all (Node::transport_time, transport ().time ());
213
214 Glib::Threads::Mutex::Lock lock (mixer ().mutex ());
215
216 for (ArdourMixer::StripMap::iterator it = mixer ().strips ().begin (); it != mixer ().strips ().end (); ++it) {
217 double db = it->second->meter_level_db ();
218 update_all (Node::strip_meter, it->first, db);
219 }
220
221 return true;
222 }
223
224 void
observe_transport()225 ArdourFeedback::observe_transport ()
226 {
227 ARDOUR::Session& sess = session ();
228 sess.TransportStateChange.connect (_transport_connections, MISSING_INVALIDATOR,
229 boost::bind<void> (TransportObserver (), this), event_loop ());
230 sess.RecordStateChanged.connect (_transport_connections, MISSING_INVALIDATOR,
231 boost::bind<void> (RecordStateObserver (), this), event_loop ());
232 sess.tempo_map ().PropertyChanged.connect (_transport_connections, MISSING_INVALIDATOR,
233 boost::bind<void> (TempoObserver (), this), event_loop ());
234 }
235
236 void
observe_mixer()237 ArdourFeedback::observe_mixer ()
238 {
239 for (ArdourMixer::StripMap::iterator it = mixer().strips().begin(); it != mixer().strips().end(); ++it) {
240 uint32_t strip_id = it->first;
241 boost::shared_ptr<ArdourMixerStrip> strip = it->second;
242
243 boost::shared_ptr<Stripable> stripable = strip->stripable ();
244
245 stripable->gain_control ()->Changed.connect (*it->second, MISSING_INVALIDATOR,
246 boost::bind<void> (StripGainObserver (), this, strip_id), event_loop ());
247
248 if (stripable->pan_azimuth_control ()) {
249 stripable->pan_azimuth_control ()->Changed.connect (*it->second, MISSING_INVALIDATOR,
250 boost::bind<void> (StripPanObserver (), this, strip_id), event_loop ());
251 }
252
253 stripable->mute_control ()->Changed.connect (*it->second, MISSING_INVALIDATOR,
254 boost::bind<void> (StripMuteObserver (), this, strip_id), event_loop ());
255
256 observe_strip_plugins (strip_id, strip->plugins ());
257 }
258 }
259
260 void
observe_strip_plugins(uint32_t strip_id,ArdourMixerStrip::PluginMap & plugins)261 ArdourFeedback::observe_strip_plugins (uint32_t strip_id, ArdourMixerStrip::PluginMap& plugins)
262 {
263 for (ArdourMixerStrip::PluginMap::iterator it = plugins.begin(); it != plugins.end(); ++it) {
264 uint32_t plugin_id = it->first;
265 boost::shared_ptr<ArdourMixerPlugin> plugin = it->second;
266 boost::shared_ptr<PluginInsert> insert = plugin->insert ();
267 uint32_t bypass = insert->plugin ()->designated_bypass_port ();
268 Evoral::Parameter param = Evoral::Parameter (PluginAutomation, 0, bypass);
269 boost::shared_ptr<AutomationControl> control = insert->automation_control (param);
270
271 if (control) {
272 control->Changed.connect (*plugin, MISSING_INVALIDATOR,
273 boost::bind<void> (PluginBypassObserver (), this, strip_id, plugin_id), event_loop ());
274 }
275
276 for (uint32_t param_id = 0; param_id < plugin->param_count (); ++param_id) {
277 try {
278 boost::shared_ptr<AutomationControl> control = plugin->param_control (param_id);
279
280 control->Changed.connect (*plugin, MISSING_INVALIDATOR,
281 boost::bind<void> (PluginParamValueObserver (), this, strip_id, plugin_id, param_id,
282 boost::weak_ptr<AutomationControl>(control)),
283 event_loop ());
284 } catch (ArdourMixerNotFoundException& e) {
285 /* ignore */
286 }
287 }
288 }
289 }
290