fd9f4299d0eaa89995f634232ea7d5ff3e3dc110
[ardour.git] / libs / surfaces / mackie / strip.cc
1 /*
2         Copyright (C) 2006,2007 John Anderson
3         Copyright (C) 2012 Paul Davis
4
5         This program is free software; you can redistribute it and/or modify
6         it under the terms of the GNU General Public License as published by
7         the Free Software Foundation; either version 2 of the License, or
8         (at your option) any later version.
9
10         This program is distributed in the hope that it will be useful,
11         but WITHOUT ANY WARRANTY; without even the implied warranty of
12         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13         GNU General Public License for more details.
14
15         You should have received a copy of the GNU General Public License
16         along with this program; if not, write to the Free Software
17         Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20 #include <sstream>
21 #include <stdint.h>
22 #include "strip.h"
23
24 #include "midi++/port.h"
25
26 #include "pbd/compose.h"
27 #include "pbd/convert.h"
28
29 #include "ardour/debug.h"
30 #include "ardour/midi_ui.h"
31 #include "ardour/route.h"
32 #include "ardour/track.h"
33 #include "ardour/pannable.h"
34 #include "ardour/panner.h"
35 #include "ardour/rc_configuration.h"
36 #include "ardour/meter.h"
37
38 #include "mackie_control_protocol.h"
39 #include "surface.h"
40 #include "button.h"
41 #include "led.h"
42 #include "ledring.h"
43 #include "pot.h"
44 #include "fader.h"
45 #include "jog.h"
46 #include "meter.h"
47
48 using namespace Mackie;
49 using namespace std;
50 using namespace ARDOUR;
51 using namespace PBD;
52
53 #define midi_ui_context() ARDOUR::MidiControlUI::instance() /* a UICallback-derived object that specifies the event loop for signal handling */
54 #define ui_bind(f, ...) boost::protect (boost::bind (f, __VA_ARGS__))
55
56 extern PBD::EventLoop::InvalidationRecord* __invalidator (sigc::trackable& trackable, const char*, int);
57 #define invalidator(x) __invalidator (*(MidiControlUI::instance()), __FILE__, __LINE__)
58
59 Strip::Strip (Surface& s, const std::string& name, int index, StripControlDefinition* ctls)
60         : Group (name)
61         , _solo (0)
62         , _recenable (0)
63         , _mute (0)
64         , _select (0)
65         , _vselect (0)
66         , _fader_touch (0)
67         , _vpot (0)
68         , _gain (0)
69         , _index (index)
70         , _surface (&s)
71 {
72         /* build the controls for this track, which will automatically add them
73            to the Group 
74         */
75
76         for (uint32_t i = 0; ctls[i].name[0]; ++i) {
77                 ctls[i].factory (*_surface, ctls[i].base_id + index, ctls[i].name, *this);
78         }
79 }       
80
81 Strip::~Strip ()
82 {
83         
84 }
85
86 /**
87         TODO could optimise this to use enum, but it's only
88         called during the protocol class instantiation.
89 */
90 void Strip::add (Control & control)
91 {
92         Group::add (control);
93
94         if  (control.name() == "gain") {
95                 _gain = reinterpret_cast<Fader*>(&control);
96         } else if  (control.name() == "vpot") {
97                 _vpot = reinterpret_cast<Pot*>(&control);
98         } else if  (control.name() == "recenable") {
99                 _recenable = reinterpret_cast<Button*>(&control);
100         } else if  (control.name() == "solo") {
101                 _solo = reinterpret_cast<Button*>(&control);
102         } else if  (control.name() == "mute") {
103                 _mute = reinterpret_cast<Button*>(&control);
104         } else if  (control.name() == "select") {
105                 _select = reinterpret_cast<Button*>(&control);
106         } else if  (control.name() == "vselect") {
107                 _vselect = reinterpret_cast<Button*>(&control);
108         } else if  (control.name() == "fader_touch") {
109                 _fader_touch = reinterpret_cast<Button*>(&control);
110         } else if  (control.name() == "meter") {
111                 _meter = reinterpret_cast<Meter*>(&control);
112         } else if  (control.type() == Control::type_led || control.type() == Control::type_led_ring) {
113                 // relax
114         } else {
115                 ostringstream os;
116                 os << "Strip::add: unknown control type " << control;
117                 throw MackieControlException (os.str());
118         }
119 }
120
121 Fader& 
122 Strip::gain()
123 {
124         if  (_gain == 0) {
125                 throw MackieControlException ("gain is null");
126         }
127         return *_gain;
128 }
129
130 Pot& 
131 Strip::vpot()
132 {
133         if  (_vpot == 0) {
134                 throw MackieControlException ("vpot is null");
135         }
136         return *_vpot;
137 }
138
139 Button& 
140 Strip::recenable()
141 {
142         if  (_recenable == 0) {
143                 throw MackieControlException ("recenable is null");
144         }
145         return *_recenable;
146 }
147
148 Button& 
149 Strip::solo()
150 {
151         if  (_solo == 0) {
152                 throw MackieControlException ("solo is null");
153         }
154         return *_solo;
155 }
156 Button& 
157 Strip::mute()
158 {
159         if  (_mute == 0) {
160                 throw MackieControlException ("mute is null");
161         }
162         return *_mute;
163 }
164
165 Button& 
166 Strip::select()
167 {
168         if  (_select == 0) {
169                 throw MackieControlException ("select is null");
170         }
171         return *_select;
172 }
173
174 Button& 
175 Strip::vselect()
176 {
177         if  (_vselect == 0) {
178                 throw MackieControlException ("vselect is null");
179         }
180         return *_vselect;
181 }
182
183 Button& 
184 Strip::fader_touch()
185 {
186         if  (_fader_touch == 0) {
187                 throw MackieControlException ("fader_touch is null");
188         }
189         return *_fader_touch;
190 }
191
192 Meter& 
193 Strip::meter()
194 {
195         if  (_meter == 0) {
196                 throw MackieControlException ("meter is null");
197         }
198         return *_meter;
199 }
200
201 std::ostream & Mackie::operator <<  (std::ostream & os, const Strip & strip)
202 {
203         os << typeid (strip).name();
204         os << " { ";
205         os << "has_solo: " << boolalpha << strip.has_solo();
206         os << ", ";
207         os << "has_recenable: " << boolalpha << strip.has_recenable();
208         os << ", ";
209         os << "has_mute: " << boolalpha << strip.has_mute();
210         os << ", ";
211         os << "has_select: " << boolalpha << strip.has_select();
212         os << ", ";
213         os << "has_vselect: " << boolalpha << strip.has_vselect();
214         os << ", ";
215         os << "has_fader_touch: " << boolalpha << strip.has_fader_touch();
216         os << ", ";
217         os << "has_vpot: " << boolalpha << strip.has_vpot();
218         os << ", ";
219         os << "has_gain: " << boolalpha << strip.has_gain();
220         os << " }";
221         
222         return os;
223 }
224
225 void
226 Strip::set_route (boost::shared_ptr<Route> r)
227 {
228         route_connections.drop_connections ();
229
230         _route = r;
231
232         if (r) {
233
234                 if (has_solo()) {
235                         _route->solo_control()->Changed.connect(route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_solo_changed, this), midi_ui_context());
236                 }
237                 if (has_mute()) {
238                         _route->mute_control()->Changed.connect(route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_mute_changed, this), midi_ui_context());
239                 }
240                 
241                 if (has_gain()) {
242                         _route->gain_control()->Changed.connect(route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_gain_changed, this, false), midi_ui_context());
243                 }
244                 
245                 _route->PropertyChanged.connect (route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_property_changed, this, _1), midi_ui_context());
246                 
247                 if (_route->pannable()) {
248                         _route->pannable()->pan_azimuth_control->Changed.connect(route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_panner_changed, this, false), midi_ui_context());
249                         _route->pannable()->pan_width_control->Changed.connect(route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_panner_changed, this, false), midi_ui_context());
250                 }
251                 
252                 boost::shared_ptr<Track> trk = boost::dynamic_pointer_cast<ARDOUR::Track>(_route);
253         
254                 if (trk) {
255                         trk->rec_enable_control()->Changed .connect(route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_record_enable_changed, this), midi_ui_context());
256                 }
257                 
258                 // TODO this works when a currently-banked route is made inactive, but not
259                 // when a route is activated which should be currently banked.
260                 
261                 _route->active_changed.connect (route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_active_changed, this), midi_ui_context());
262                 _route->DropReferences.connect (route_connections, MISSING_INVALIDATOR, ui_bind (&Strip::notify_route_deleted, this), midi_ui_context());
263         
264                 // TODO
265                 // SelectedChanged
266                 // RemoteControlIDChanged. Better handled at Session level.
267
268                 /* Update */
269
270                 notify_all ();
271         }
272 }
273
274 void 
275 Strip::notify_all()
276 {
277         if  (has_solo()) {
278                 notify_solo_changed ();
279         }
280         
281         if  (has_mute()) {
282                 notify_mute_changed ();
283         }
284         
285         if  (has_gain()) {
286                 notify_gain_changed ();
287         }
288         
289         notify_property_changed (PBD::PropertyChange (ARDOUR::Properties::name));
290         
291         if  (has_vpot()) {
292                 notify_panner_changed ();
293         }
294         
295         if  (has_recenable()) {
296                 notify_record_enable_changed ();
297         }
298 }
299
300 void 
301 Strip::notify_solo_changed ()
302 {
303         if (_route) {
304                 Button& button = solo();
305                 _surface->write (builder.build_led (button, _route->soloed()));
306         }
307 }
308
309 void 
310 Strip::notify_mute_changed ()
311 {
312         if (_route) {
313                 Button & button = mute();
314                 _surface->write (builder.build_led (button, _route->muted()));
315         }
316 }
317
318 void 
319 Strip::notify_record_enable_changed ()
320 {
321         if (_route) {
322                 Button & button = recenable();
323                 _surface->write (builder.build_led (button, _route->record_enabled()));
324         }
325 }
326
327 void 
328 Strip::notify_active_changed ()
329 {
330         _surface->mcp().refresh_current_bank();
331 }
332
333 void 
334 Strip::notify_route_deleted ()
335 {
336         _surface->mcp().refresh_current_bank();
337 }
338
339 void 
340 Strip::notify_gain_changed (bool force_update)
341 {
342         if (_route) {
343                 Fader & fader = gain();
344                 DEBUG_TRACE (DEBUG::MackieControl, string_compose ("route %1 gain change, update fader %2 on port %3\n", 
345                                                                    _route->name(), 
346                                                                    fader.raw_id(),
347                                                                    _surface->port().output_port().name()));
348                 if (!fader.in_use()) {
349                         float gain_value = gain_to_slider_position (_route->gain_control()->get_value());
350                         // check that something has actually changed
351                         if (force_update || gain_value != _last_gain_written) {
352                                 _surface->write (builder.build_fader (fader, gain_value));
353                                 _last_gain_written = gain_value;
354                         }
355                 }
356         }
357 }
358
359 void 
360 Strip::notify_property_changed (const PropertyChange& what_changed)
361 {
362         if (!what_changed.contains (ARDOUR::Properties::name)) {
363                 return;
364         }
365
366         if (_route) {
367                 string line1;
368                 string fullname = _route->name();
369                 
370                 if (fullname.length() <= 6) {
371                         line1 = fullname;
372                 } else {
373                         line1 = PBD::short_version (fullname, 6);
374                 }
375                 
376                 _surface->write (builder.strip_display (*_surface, *this, 0, line1));
377                 _surface->write (builder.strip_display_blank (*_surface, *this, 1));
378         }
379 }
380
381 void 
382 Strip::notify_panner_changed (bool force_update)
383 {
384         if (_route) {
385                 Pot & pot = vpot();
386                 boost::shared_ptr<Panner> panner = _route->panner();
387                 if (panner) {
388                         double pos = panner->position ();
389
390                         // cache the MidiByteArray here, because the mackie led control is much lower
391                         // resolution than the panner control. So we save lots of byte
392                         // sends in spite of more work on the comparison
393                         MidiByteArray bytes = builder.build_led_ring (pot, ControlState (on, pos), MackieMidiBuilder::midi_pot_mode_dot);
394                         // check that something has actually changed
395                         if (force_update || bytes != _last_pan_written)
396                         {
397                                 _surface->write (bytes);
398                                 _last_pan_written = bytes;
399                         }
400                 } else {
401                         _surface->write (builder.zero_control (pot));
402                 }
403         }
404 }
405
406 bool 
407 Strip::handle_button (SurfacePort & port, Control & control, ButtonState bs)
408 {
409         if (!_route) {
410                 // no route so always switch the light off
411                 // because no signals will be emitted by a non-route
412                 _surface->write (builder.build_led (control.led(), off));
413                 return false;
414         }
415
416         bool state = false;
417
418         if (bs == press) {
419                 if (control.name() == "recenable") {
420                         state = !_route->record_enabled();
421                         _route->set_record_enabled (state, this);
422                 } else if (control.name() == "mute") {
423                         state = !_route->muted();
424                         _route->set_mute (state, this);
425                 } else if (control.name() == "solo") {
426                         state = !_route->soloed();
427                         _route->set_solo (state, this);
428                 } else if (control.name() == "select") {
429                         _surface->mcp().select_track (_route);
430                 } else if (control.name() == "vselect") {
431                         // TODO could be used to select different things to apply the pot to?
432                         //state = default_button_press (dynamic_cast<Button&> (control));
433                 }
434         }
435
436         if (control.name() == "fader_touch") {
437
438                 state = (bs == press);
439                 
440                 gain().set_in_use (state);
441                 
442                 if (ARDOUR::Config->get_mackie_emulation() == "bcf" && state) {
443
444                         /* BCF faders don't support touch, so add a timeout to reset
445                            their `in_use' state.
446                         */
447
448                         _surface->mcp().add_in_use_timeout (*_surface, gain(), &fader_touch());
449                 }
450         }
451
452         return state;
453 }
454
455 void
456 Strip::periodic ()
457 {
458         if (!_route) {
459                 return;
460         }
461
462         update_automation ();
463         update_meter ();
464 }
465
466 void 
467 Strip::update_automation ()
468 {
469         ARDOUR::AutoState gain_state = _route->gain_control()->automation_state();
470
471         if (gain_state == Touch || gain_state == Play) {
472                 notify_gain_changed (false);
473         }
474
475         if (_route->panner()) {
476                 ARDOUR::AutoState panner_state = _route->panner()->automation_state();
477                 if (panner_state == Touch || panner_state == Play) {
478                         notify_panner_changed (false);
479                 }
480         }
481 }
482
483 void
484 Strip::update_meter ()
485 {
486         float dB = const_cast<PeakMeter&> (_route->peak_meter()).peak_power (0);
487         _surface->write (meter().update_message (dB));
488 }