MCP: share add-marker code with BasicUI; don't allow route locking if the strip has...
[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/panner_shell.h"
36 #include "ardour/rc_configuration.h"
37 #include "ardour/meter.h"
38
39 #include "mackie_control_protocol.h"
40 #include "surface_port.h"
41 #include "surface.h"
42 #include "button.h"
43 #include "led.h"
44 #include "pot.h"
45 #include "fader.h"
46 #include "jog.h"
47 #include "meter.h"
48
49 using namespace Mackie;
50 using namespace std;
51 using namespace ARDOUR;
52 using namespace PBD;
53
54 #define ui_context() MackieControlProtocol::instance() /* a UICallback-derived object that specifies the event loop for signal handling */
55 #define ui_bind(f, ...) boost::protect (boost::bind (f, __VA_ARGS__))
56
57 extern PBD::EventLoop::InvalidationRecord* __invalidator (sigc::trackable& trackable, const char*, int);
58 #define invalidator() __invalidator (*(MackieControlProtocol::instance()), __FILE__, __LINE__)
59
60 Strip::Strip (Surface& s, const std::string& name, int index, StripControlDefinition* ctls)
61         : Group (name)
62         , _solo (0)
63         , _recenable (0)
64         , _mute (0)
65         , _select (0)
66         , _vselect (0)
67         , _fader_touch (0)
68         , _vpot (0)
69         , _gain (0)
70         , _index (index)
71         , _surface (&s)
72         , _route_locked (false)
73 {
74         /* build the controls for this track, which will automatically add them
75            to the Group 
76         */
77
78         for (uint32_t i = 0; ctls[i].name[0]; ++i) {
79                 ctls[i].factory (*_surface, ctls[i].base_id + index, ctls[i].name, *this);
80         }
81 }       
82
83 Strip::~Strip ()
84 {
85         
86 }
87
88 /**
89         TODO could optimise this to use enum, but it's only
90         called during the protocol class instantiation.
91 */
92 void Strip::add (Control & control)
93 {
94         Group::add (control);
95
96         Fader* fader;
97         Pot* pot;
98         Button* button;
99         Meter* meter;
100
101         if ((fader = dynamic_cast<Fader*>(&control)) != 0) {
102
103                 _gain = fader;
104
105         } else if ((pot = dynamic_cast<Pot*>(&control)) != 0) {
106
107                 _vpot = pot;
108
109         } else if ((button = dynamic_cast<Button*>(&control)) != 0) {
110
111                 if (control.id() >= Button::recenable_base_id &&
112                     control.id() < Button::recenable_base_id + 8) {
113                         
114                         _recenable = button;
115
116                 } else if (control.id() >= Button::mute_base_id &&
117                            control.id() < Button::mute_base_id + 8) {
118
119                         _mute = button;
120
121                 } else if (control.id() >= Button::solo_base_id &&
122                            control.id() < Button::solo_base_id + 8) {
123
124                         _solo = button;
125
126                 } else if (control.id() >= Button::select_base_id &&
127                            control.id() < Button::select_base_id + 8) {
128
129                         _select = button;
130
131                 } else if (control.id() >= Button::vselect_base_id &&
132                            control.id() < Button::vselect_base_id + 8) {
133
134                         _vselect = button;
135
136                 } else if (control.id() >= Button::fader_touch_base_id &&
137                            control.id() < Button::fader_touch_base_id + 8) {
138
139                         _fader_touch = button;
140                 }
141
142         } else if ((meter = dynamic_cast<Meter*>(&control)) != 0) {
143                 _meter = meter;
144         }
145 }
146
147 Fader& 
148 Strip::gain()
149 {
150         if  (_gain == 0) {
151                 throw MackieControlException ("gain is null");
152         }
153         return *_gain;
154 }
155
156 Pot& 
157 Strip::vpot()
158 {
159         if  (_vpot == 0) {
160                 throw MackieControlException ("vpot is null");
161         }
162         return *_vpot;
163 }
164
165 Button& 
166 Strip::recenable()
167 {
168         if  (_recenable == 0) {
169                 throw MackieControlException ("recenable is null");
170         }
171         return *_recenable;
172 }
173
174 Button& 
175 Strip::solo()
176 {
177         if  (_solo == 0) {
178                 throw MackieControlException ("solo is null");
179         }
180         return *_solo;
181 }
182 Button& 
183 Strip::mute()
184 {
185         if  (_mute == 0) {
186                 throw MackieControlException ("mute is null");
187         }
188         return *_mute;
189 }
190
191 Button& 
192 Strip::select()
193 {
194         if  (_select == 0) {
195                 throw MackieControlException ("select is null");
196         }
197         return *_select;
198 }
199
200 Button& 
201 Strip::vselect()
202 {
203         if  (_vselect == 0) {
204                 throw MackieControlException ("vselect is null");
205         }
206         return *_vselect;
207 }
208
209 Button& 
210 Strip::fader_touch()
211 {
212         if  (_fader_touch == 0) {
213                 throw MackieControlException ("fader_touch is null");
214         }
215         return *_fader_touch;
216 }
217
218 Meter& 
219 Strip::meter()
220 {
221         if  (_meter == 0) {
222                 throw MackieControlException ("meter is null");
223         }
224         return *_meter;
225 }
226
227 std::ostream & Mackie::operator <<  (std::ostream & os, const Strip & strip)
228 {
229         os << typeid (strip).name();
230         os << " { ";
231         os << "has_solo: " << boolalpha << strip.has_solo();
232         os << ", ";
233         os << "has_recenable: " << boolalpha << strip.has_recenable();
234         os << ", ";
235         os << "has_mute: " << boolalpha << strip.has_mute();
236         os << ", ";
237         os << "has_select: " << boolalpha << strip.has_select();
238         os << ", ";
239         os << "has_vselect: " << boolalpha << strip.has_vselect();
240         os << ", ";
241         os << "has_fader_touch: " << boolalpha << strip.has_fader_touch();
242         os << ", ";
243         os << "has_vpot: " << boolalpha << strip.has_vpot();
244         os << ", ";
245         os << "has_gain: " << boolalpha << strip.has_gain();
246         os << " }";
247         
248         return os;
249 }
250
251 void
252 Strip::set_route (boost::shared_ptr<Route> r)
253 {
254         if (_route_locked) {
255                 return;
256         }
257
258         route_connections.drop_connections ();
259
260         _route = r;
261
262         if (r) {
263                 
264                 DEBUG_TRACE (DEBUG::MackieControl, string_compose ("Surface %1 strip %2 now mapping route %3\n",
265                                                                    _surface->number(), _index, _route->name()));
266                 
267
268                 if (has_solo()) {
269                         _route->solo_control()->Changed.connect(route_connections, invalidator(), ui_bind (&Strip::notify_solo_changed, this), ui_context());
270                 }
271                 if (has_mute()) {
272                         _route->mute_control()->Changed.connect(route_connections, invalidator(), ui_bind (&Strip::notify_mute_changed, this), ui_context());
273                 }
274                 
275                 if (has_gain()) {
276                         _route->gain_control()->Changed.connect(route_connections, invalidator(), ui_bind (&Strip::notify_gain_changed, this, false), ui_context());
277                 }
278                 
279                 _route->PropertyChanged.connect (route_connections, invalidator(), ui_bind (&Strip::notify_property_changed, this, _1), ui_context());
280                 
281                 if (_route->pannable()) {
282                         _route->pannable()->pan_azimuth_control->Changed.connect(route_connections, invalidator(), ui_bind (&Strip::notify_panner_changed, this, false), ui_context());
283                         _route->pannable()->pan_width_control->Changed.connect(route_connections, invalidator(), ui_bind (&Strip::notify_panner_changed, this, false), ui_context());
284                 }
285                 
286                 boost::shared_ptr<Track> trk = boost::dynamic_pointer_cast<ARDOUR::Track>(_route);
287         
288                 if (trk) {
289                         trk->rec_enable_control()->Changed .connect(route_connections, invalidator(), ui_bind (&Strip::notify_record_enable_changed, this), ui_context());
290                 }
291                 
292                 // TODO this works when a currently-banked route is made inactive, but not
293                 // when a route is activated which should be currently banked.
294                 
295                 _route->active_changed.connect (route_connections, invalidator(), ui_bind (&Strip::notify_active_changed, this), ui_context());
296                 _route->DropReferences.connect (route_connections, invalidator(), ui_bind (&Strip::notify_route_deleted, this), ui_context());
297         
298                 // TODO
299                 // SelectedChanged
300                 // RemoteControlIDChanged. Better handled at Session level.
301
302                 /* Update */
303
304                 notify_all ();
305         } else {
306                 DEBUG_TRACE (DEBUG::MackieControl, string_compose ("Surface %1 strip %2 now unmapped\n",
307                                                                    _surface->number(), _index));
308         }
309 }
310
311 void 
312 Strip::notify_all()
313 {
314         if  (has_solo()) {
315                 notify_solo_changed ();
316         }
317         
318         if  (has_mute()) {
319                 notify_mute_changed ();
320         }
321         
322         if  (has_gain()) {
323                 notify_gain_changed ();
324         }
325         
326         notify_property_changed (PBD::PropertyChange (ARDOUR::Properties::name));
327         
328         if  (has_vpot()) {
329                 notify_panner_changed ();
330         }
331         
332         if  (has_recenable()) {
333                 notify_record_enable_changed ();
334         }
335 }
336
337 void 
338 Strip::notify_solo_changed ()
339 {
340         if (_route && _solo) {
341                 _surface->write (_solo->set_state (_route->soloed() ? on : off));
342         }
343 }
344
345 void 
346 Strip::notify_mute_changed ()
347 {
348         DEBUG_TRACE (DEBUG::MackieControl, string_compose ("Strip %1 mute changed\n", _index));
349         if (_route && _mute) {
350                 DEBUG_TRACE (DEBUG::MackieControl, string_compose ("\troute muted ? %1\n", _route->muted()));
351                 DEBUG_TRACE (DEBUG::MackieControl, string_compose ("mute message: %1\n", _mute->set_state (_route->muted() ? on : off)));
352
353                 _surface->write (_mute->set_state (_route->muted() ? on : off));
354         }
355 }
356
357 void 
358 Strip::notify_record_enable_changed ()
359 {
360         if (_route && _recenable)  {
361                 _surface->write (_recenable->set_state (_route->record_enabled() ? on : off));
362         }
363 }
364
365 void 
366 Strip::notify_active_changed ()
367 {
368         _surface->mcp().refresh_current_bank();
369 }
370
371 void 
372 Strip::notify_route_deleted ()
373 {
374         _surface->mcp().refresh_current_bank();
375 }
376
377 void 
378 Strip::notify_gain_changed (bool force_update)
379 {
380         if (_route) {
381                 Fader & fader = gain();
382
383                 if (!fader.in_use()) {
384                         float position = gain_to_slider_position (_route->gain_control()->get_value());
385                         // check that something has actually changed
386                         if (force_update || position != _last_gain_written) {
387                                 _surface->write (fader.set_position (position));
388                                 _last_gain_written = position;
389                         }
390                 }
391         }
392 }
393
394 void 
395 Strip::notify_property_changed (const PropertyChange& what_changed)
396 {
397         if (!what_changed.contains (ARDOUR::Properties::name)) {
398                 return;
399         }
400
401         if (_route) {
402                 string line1;
403                 string fullname = _route->name();
404                 
405                 if (fullname.length() <= 6) {
406                         line1 = fullname;
407                 } else {
408                         line1 = PBD::short_version (fullname, 6);
409                 }
410                 
411                 _surface->write (display (0, line1));
412         }
413 }
414
415 void 
416 Strip::notify_panner_changed (bool force_update)
417 {
418         if (_route) {
419                 Pot & pot = vpot();
420                 boost::shared_ptr<Panner> panner = _route->panner();
421
422                 if (panner) {
423                         double pos = panner->position ();
424
425                         // cache the MidiByteArray here, because the mackie led control is much lower
426                         // resolution than the panner control. So we save lots of byte
427                         // sends in spite of more work on the comparison
428
429                         MidiByteArray bytes = pot.set_all (pos, true, Pot::dot);
430
431                         // check that something has actually changed
432                         if (force_update || bytes != _last_pan_written)
433                         {
434                                 _surface->write (bytes);
435                                 _last_pan_written = bytes;
436                         }
437                 } else {
438                         _surface->write (pot.zero());
439                 }
440         }
441 }
442
443 void
444 Strip::handle_button (Button& button, ButtonState bs)
445 {
446         button.set_in_use (bs == press);
447
448         if (!_route) {
449                 // no route so always switch the light off
450                 // because no signals will be emitted by a non-route
451                 _surface->write (button.set_state  (off));
452                 return;
453         }
454
455         if (bs == press) {
456                 if (button.id() >= Button::recenable_base_id &&
457                     button.id() < Button::recenable_base_id + 8) {
458
459                         _route->set_record_enabled (!_route->record_enabled(), this);
460
461                 } else if (button.id() >= Button::mute_base_id &&
462                            button.id() < Button::mute_base_id + 8) {
463
464                         _route->set_mute (!_route->muted(), this);
465
466                 } else if (button.id() >= Button::solo_base_id &&
467                            button.id() < Button::solo_base_id + 8) {
468
469                         _route->set_solo (!_route->soloed(), this);
470
471                 } else if (button.id() >= Button::select_base_id &&
472                            button.id() < Button::select_base_id + 8) {
473
474                         int lock_mod = (MackieControlProtocol::MODIFIER_CONTROL|MackieControlProtocol::MODIFIER_SHIFT);
475
476                         if ((_surface->mcp().modifier_state() & lock_mod) == lock_mod) {
477                                 if (_route) {
478                                         _route_locked = !_route_locked;
479                                 }
480                         } else {
481                                 _surface->mcp().select_track (_route);
482                         }
483
484                 } else if (button.id() >= Button::vselect_base_id &&
485                            button.id() < Button::vselect_base_id + 8) {
486
487                 }
488         }
489
490         if (button.id() >= Button::fader_touch_base_id &&
491             button.id() < Button::fader_touch_base_id + 8) {
492
493                 DEBUG_TRACE (DEBUG::MackieControl, string_compose ("fader touch, press ? %1\n", (bs == press)));
494
495                 bool state = (bs == press);
496
497                 _gain->set_in_use (state);
498                 
499                 if (ARDOUR::Config->get_mackie_emulation() == "bcf" && state) {
500
501                         /* BCF faders don't support touch, so add a timeout to reset
502                            their `in_use' state.
503                         */
504
505                         _surface->mcp().add_in_use_timeout (*_surface, gain(), &fader_touch());
506                 }
507         }
508 }
509
510 void
511 Strip::handle_fader (Fader& fader, float position)
512 {
513         DEBUG_TRACE (DEBUG::MackieControl, string_compose ("fader to %1\n", position));
514
515         switch (_surface->mcp().flip_mode()) {
516         case MackieControlProtocol::Normal:
517                 break;
518         case MackieControlProtocol::Zero:
519                 break;
520         case MackieControlProtocol::Mirror:
521                 break;
522         case MackieControlProtocol::Swap:
523                 if (_vpot) {
524                         handle_pot (*_vpot, 1.0);
525                 }
526                 return;
527         }
528
529         if (_route) {
530                 _route->gain_control()->set_value (slider_position_to_gain (position));
531         }
532         
533         if (ARDOUR::Config->get_mackie_emulation() == "bcf") {
534                 /* reset the timeout while we're still moving the fader */
535                 _surface->mcp().add_in_use_timeout (*_surface, fader, fader.in_use_touch_control);
536         }
537         
538         // must echo bytes back to slider now, because
539         // the notifier only works if the fader is not being
540         // touched. Which it is if we're getting input.
541
542         _surface->write (fader.set_position (position));
543 }
544
545 void
546 Strip::handle_pot (Pot& pot, float delta)
547 {
548         if (!_route) {
549                 _surface->write (pot.set_onoff (false));
550                 return;
551         }
552
553         boost::shared_ptr<Pannable> pannable = _route->pannable();
554
555         if (pannable) {
556                 boost::shared_ptr<AutomationControl> ac;
557                 
558                 if (_surface->mcp().modifier_state() & MackieControlProtocol::MODIFIER_CONTROL) {
559                         ac = pannable->pan_width_control;
560                 } else {
561                         ac = pannable->pan_azimuth_control;
562                 }
563                 
564                 double p = ac->get_value();
565                 
566                 // calculate new value, and adjust
567                 p += delta;
568                 p = min (1.0, p);
569                 p = max (0.0, p);
570
571                 ac->set_value (p);
572         }
573 }
574
575 void
576 Strip::periodic ()
577 {
578         if (!_route) {
579                 return;
580         }
581
582         update_automation ();
583         update_meter ();
584 }
585
586 void 
587 Strip::update_automation ()
588 {
589         ARDOUR::AutoState gain_state = _route->gain_control()->automation_state();
590
591         if (gain_state == Touch || gain_state == Play) {
592                 notify_gain_changed (false);
593         }
594
595         if (_route->panner()) {
596                 ARDOUR::AutoState panner_state = _route->panner()->automation_state();
597                 if (panner_state == Touch || panner_state == Play) {
598                         notify_panner_changed (false);
599                 }
600         }
601 }
602
603 void
604 Strip::update_meter ()
605 {
606         float dB = const_cast<PeakMeter&> (_route->peak_meter()).peak_power (0);
607         _surface->write (meter().update_message (dB));
608 }
609
610 MidiByteArray
611 Strip::zero ()
612 {
613         MidiByteArray retval;
614
615         for (Group::Controls::const_iterator it = _controls.begin(); it != _controls.end(); ++it) {
616                 retval << (*it)->zero ();
617         }
618
619         retval << blank_display (0);
620         retval << blank_display (1);
621         
622         return retval;
623 }
624
625 MidiByteArray
626 Strip::blank_display (uint32_t line_number)
627 {
628         return display (line_number, string());
629 }
630
631 MidiByteArray
632 Strip::display (uint32_t line_number, const std::string& line)
633 {
634         assert (line_number <= 1);
635
636         MidiByteArray retval;
637
638         DEBUG_TRACE (DEBUG::MackieControl, string_compose ("strip_display index: %1, line %2 = %3\n", _index, line_number, line));
639
640         // sysex header
641         retval << _surface->sysex_hdr();
642         
643         // code for display
644         retval << 0x12;
645         // offset (0 to 0x37 first line, 0x38 to 0x6f for second line)
646         retval << (_index * 7 + (line_number * 0x38));
647         
648         // ascii data to display
649         retval << line;
650         // pad with " " out to 6 chars
651         for (int i = line.length(); i < 6; ++i) {
652                 retval << ' ';
653         }
654         
655         // column spacer, unless it's the right-hand column
656         if (_index < 7) {
657                 retval << ' ';
658         }
659
660         // sysex trailer
661         retval << MIDI::eox;
662         
663         DEBUG_TRACE (DEBUG::MackieControl, string_compose ("MackieMidiBuilder::strip_display midi: %1\n", retval));
664
665         return retval;
666 }
667
668 void
669 Strip::lock_route ()
670 {
671         /* don't lock unless we have a route */
672         if (_route) {
673                 _route_locked = true;
674         }
675 }
676
677 void
678 Strip::unlock_route ()
679 {
680         _route_locked = false;
681 }