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