FP8: limit fader range
[ardour.git] / libs / surfaces / faderport8 / fp8_strip.cc
1 /*
2  * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License
6  * as published by the Free Software Foundation; either version 2
7  * of the License, or (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
15  * along with this program; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17  */
18
19 #include "ardour/automation_control.h"
20 #include "ardour/gain_control.h"
21 #include "ardour/meter.h"
22 #include "ardour/plugin_insert.h"
23 #include "ardour/session.h"
24 #include "ardour/stripable.h"
25 #include "ardour/track.h"
26 #include "ardour/value_as_string.h"
27
28 #include "control_protocol/control_protocol.h"
29
30 #include "fp8_strip.h"
31
32 using namespace ARDOUR;
33 using namespace ArdourSurface;
34 using namespace ArdourSurface::FP8Types;
35
36 FP8Strip::FP8Strip (FP8Base& b, uint8_t id)
37         : _base (b)
38         , _id (id)
39         , _solo   (b, 0x08 + id)
40         , _mute   (b, 0x10 + id)
41         , _selrec (b, 0x18 + id, true)
42         , _touching (false)
43         , _strip_mode (0)
44         , _bar_mode (0)
45         , _displaymode (Stripables)
46 {
47         assert (id < 8);
48
49         _last_fader = 65535;
50         _last_meter = _last_redux = _last_barpos = 0xff;
51
52         _mute.StateChange.connect_same_thread (_button_connections, boost::bind (&FP8Strip::set_mute, this, _1));
53         _solo.StateChange.connect_same_thread (_button_connections, boost::bind (&FP8Strip::set_solo, this, _1));
54         select_button ().released.connect_same_thread (_button_connections, boost::bind (&FP8Strip::set_select, this));
55         recarm_button ().released.connect_same_thread (_button_connections, boost::bind (&FP8Strip::set_recarm, this));
56         b.Periodic.connect_same_thread (_base_connection, boost::bind (&FP8Strip::periodic, this));
57 }
58
59 FP8Strip::~FP8Strip ()
60 {
61         _fader_connection.disconnect ();
62         _mute_connection.disconnect ();
63         _solo_connection.disconnect ();
64         _rec_connection.disconnect ();
65         _pan_connection.disconnect ();
66
67         _fader_ctrl.reset ();
68         _mute_ctrl.reset ();
69         _solo_ctrl.reset ();
70         _rec_ctrl.reset ();
71         _pan_ctrl.reset ();
72
73         _base_connection.disconnect ();
74         _button_connections.drop_connections ();
75 }
76
77 void
78 FP8Strip::initialize ()
79 {
80         /* this is called once midi transmission is possible,
81          * ie from FaderPort8::connected()
82          */
83         _solo.set_active (false);
84         _mute.set_active (false);
85
86         /* reset momentary button state */
87         _mute.reset ();
88         _solo.reset ();
89
90         /* clear cached values */
91         _last_fader = 65535;
92         _last_meter = _last_redux = _last_barpos = 0xff;
93
94         select_button ().set_color (0xffffffff);
95         select_button ().set_active (false);
96         select_button ().set_blinking (false);
97
98         recarm_button ().set_active (false);
99         recarm_button ().set_color (0xffffffff);
100
101         set_strip_mode (0, true);
102
103         // force unset txt
104         _last_line[0].clear ();
105         _last_line[1].clear ();
106         _last_line[2].clear ();
107         _last_line[3].clear ();
108         _base.tx_sysex (4, 0x12, _id, 0x00, 0x00);
109         _base.tx_sysex (4, 0x12, _id, 0x01, 0x00);
110         _base.tx_sysex (4, 0x12, _id, 0x02, 0x00);
111         _base.tx_sysex (4, 0x12, _id, 0x03, 0x00);
112
113         set_bar_mode (4); // off
114
115         _base.tx_midi2 (0xd0 + _id, 0); // reset meter
116         _base.tx_midi2 (0xd8 + _id, 0); // reset redux
117
118         _base.tx_midi3 (0xe0 + _id, 0, 0); // fader
119 }
120
121
122 #define GENERATE_SET_CTRL_FUNCTION(NAME)                                            \
123 void                                                                                \
124 FP8Strip::set_ ##NAME##_controllable (boost::shared_ptr<AutomationControl> ac)      \
125 {                                                                                   \
126   if (_##NAME##_ctrl == ac) {                                                       \
127     return;                                                                         \
128   }                                                                                 \
129   _##NAME##_connection.disconnect();                                                \
130   _##NAME##_ctrl = ac;                                                              \
131                                                                                     \
132   if (ac) {                                                                         \
133     ac->Changed.connect (_##NAME##_connection, MISSING_INVALIDATOR,                 \
134       boost::bind (&FP8Strip::notify_##NAME##_changed, this), fp8_context());       \
135   }                                                                                 \
136   notify_##NAME##_changed ();                                                       \
137 }
138
139
140 GENERATE_SET_CTRL_FUNCTION (fader)
141 GENERATE_SET_CTRL_FUNCTION (mute)
142 GENERATE_SET_CTRL_FUNCTION (solo)
143 GENERATE_SET_CTRL_FUNCTION (rec)
144 GENERATE_SET_CTRL_FUNCTION (pan)
145 GENERATE_SET_CTRL_FUNCTION (x_select)
146
147 #undef GENERATE_SET_CTRL_FUNCTION
148
149 // special case -- w/_select_plugin_functor
150 void
151 FP8Strip::set_select_controllable (boost::shared_ptr<AutomationControl> ac)
152 {
153         _select_plugin_functor.clear ();
154         set_x_select_controllable (ac);
155 }
156
157 void
158 FP8Strip::set_select_cb (boost::function<void ()>& functor)
159 {
160         set_select_controllable (boost::shared_ptr<AutomationControl>());
161         _select_plugin_functor = functor;
162 }
163
164 void
165 FP8Strip::unset_controllables (int which)
166 {
167         _peak_meter = boost::shared_ptr<ARDOUR::PeakMeter>();
168         _redux_ctrl = boost::shared_ptr<ARDOUR::ReadOnlyControl>();
169
170         if (which & CTRL_FADER) {
171                 set_fader_controllable (boost::shared_ptr<AutomationControl>());
172         }
173         if (which & CTRL_MUTE) {
174                 set_mute_controllable (boost::shared_ptr<AutomationControl>());
175         }
176         if (which & CTRL_SOLO) {
177                 set_solo_controllable (boost::shared_ptr<AutomationControl>());
178         }
179         if (which & CTRL_REC) {
180                 set_rec_controllable (boost::shared_ptr<AutomationControl>());
181         }
182         if (which & CTRL_PAN) {
183                 set_pan_controllable (boost::shared_ptr<AutomationControl>());
184         }
185         if (which & CTRL_SELECT) {
186                 set_select_controllable (boost::shared_ptr<AutomationControl>());
187                 select_button ().set_color (0xffffffff);
188                 select_button ().set_active (false);
189                 select_button ().set_blinking (false);
190         }
191         if (which & CTRL_TEXT0) {
192                 set_text_line (0x00, "");
193         }
194         if (which & CTRL_TEXT1) {
195                 set_text_line (0x01, "");
196         }
197         if (which & CTRL_TEXT2) {
198                 set_text_line (0x02, "");
199         }
200         if (which & CTRL_TEXT3) {
201                 set_text_line (0x03, "");
202         }
203         set_bar_mode (4); // Off
204 }
205
206 void
207 FP8Strip::set_stripable (boost::shared_ptr<Stripable> s, bool panmode)
208 {
209         assert (s);
210
211         if (panmode) {
212                 set_fader_controllable (s->pan_azimuth_control ());
213         } else {
214                 set_fader_controllable (s->gain_control ());
215         }
216         set_pan_controllable (s->pan_azimuth_control ());
217
218         if (s->is_monitor ()) {
219                 set_mute_controllable (boost::shared_ptr<AutomationControl>());
220         } else {
221                 set_mute_controllable (s->mute_control ());
222         }
223         set_solo_controllable (s->solo_control ());
224
225         if (boost::dynamic_pointer_cast<Track> (s)) {
226                 boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track>(s);
227                 set_rec_controllable (t->rec_enable_control ());
228                 recarm_button ().set_color (0xff0000ff);
229         } else {
230                 set_rec_controllable (boost::shared_ptr<AutomationControl>());
231                 recarm_button ().set_color (0xffffffff);
232                 recarm_button ().set_active (false);
233         }
234         _peak_meter = s->peak_meter ();
235         _redux_ctrl = s->comp_redux_controllable ();
236
237         set_select_controllable (boost::shared_ptr<AutomationControl>());
238         select_button ().set_active (s->is_selected ());
239         select_button ().set_color (s->presentation_info ().color());
240         //select_button ().set_blinking (false);
241
242         set_strip_mode (0x05);
243         set_text_line (0x00, s->name ());
244         set_text_line (0x01, _pan_ctrl ? _pan_ctrl->get_user_string () : "");
245         set_text_line (0x02, "");
246         set_text_line (0x03, "");
247 }
248
249 /* *****************************************************************************
250  * Parse Strip Specifig MIDI Events
251  */
252
253 bool
254 FP8Strip::midi_touch (bool t)
255 {
256         _touching = t;
257         boost::shared_ptr<AutomationControl> ac = _fader_ctrl;
258         if (!ac) {
259                 return false;
260         }
261         if (t) {
262                 if (!ac->touching ()) {
263                         ac->start_touch (ac->session().transport_frame());
264                 }
265         } else {
266                 ac->stop_touch (true, ac->session().transport_frame());
267         }
268         return true;
269 }
270
271 bool
272 FP8Strip::midi_fader (float val)
273 {
274         assert (val >= 0.f && val <= 1.f);
275         if (!_touching) {
276                 return false;
277         }
278         boost::shared_ptr<AutomationControl> ac = _fader_ctrl;
279         if (!ac) {
280                 return false;
281         }
282         if (ac->automation_state() == Touch && !ac->touching ()) {
283                 ac->start_touch (ac->session().transport_frame());
284         }
285         ac->set_value (ac->interface_to_internal (val), group_mode ());
286         return true;
287 }
288
289 /* *****************************************************************************
290  * Actions from Controller, Update Model
291  */
292
293 PBD::Controllable::GroupControlDisposition
294 FP8Strip::group_mode () const
295 {
296         if (_base.shift_mod ()) {
297                 return PBD::Controllable::InverseGroup;
298         } else {
299                 return PBD::Controllable::UseGroup;
300         }
301 }
302
303 void
304 FP8Strip::set_mute (bool on)
305 {
306         if (_mute_ctrl) {
307                 if (_mute_ctrl->automation_state() == Touch && !_mute_ctrl->touching ()) {
308                         _mute_ctrl->start_touch (_mute_ctrl->session().transport_frame());
309                 }
310                 _mute_ctrl->set_value (on ? 1.0 : 0.0, group_mode ());
311         }
312 }
313
314 void
315 FP8Strip::set_solo (bool on)
316 {
317         if (_solo_ctrl) {
318                 if (_solo_ctrl->automation_state() == Touch && !_solo_ctrl->touching ()) {
319                         _solo_ctrl->start_touch (_solo_ctrl->session().transport_frame());
320                 }
321                 _solo_ctrl->set_value (on ? 1.0 : 0.0, group_mode ());
322         }
323 }
324
325 void
326 FP8Strip::set_recarm ()
327 {
328         if (_rec_ctrl) {
329                 const bool on = !recarm_button ().is_active();
330                 _rec_ctrl->set_value (on ? 1.0 : 0.0, group_mode ());
331         }
332 }
333
334 void
335 FP8Strip::set_select ()
336 {
337         if (!_select_plugin_functor.empty ()) {
338                 assert (!_x_select_ctrl);
339                 _select_plugin_functor ();
340         } else if (_x_select_ctrl) {
341                 if (_x_select_ctrl->automation_state() == Touch && !_x_select_ctrl->touching ()) {
342                         _x_select_ctrl->start_touch (_x_select_ctrl->session().transport_frame());
343                 }
344                 const bool on = !select_button ().is_active();
345                 _x_select_ctrl->set_value (on ? 1.0 : 0.0, group_mode ());
346         }
347 }
348
349 /* *****************************************************************************
350  * Callbacks from Stripable, Update View
351  */
352
353 void
354 FP8Strip::notify_fader_changed ()
355 {
356         boost::shared_ptr<AutomationControl> ac = _fader_ctrl;
357         if (_touching) {
358                 return;
359         }
360         float val = 0;
361         if (ac) {
362                 val = ac->internal_to_interface (ac->get_value()) * 16368.f; /* 16 * 1023 */
363                 val = std::max (0.f, std::min (1.f, val));
364         }
365         unsigned short mv = lrintf (val);
366         if (mv == _last_fader) {
367                 return;
368         }
369         _last_fader = mv;
370         _base.tx_midi3 (0xe0 + _id, (mv & 0x7f), (mv >> 7) & 0x7f);
371 }
372
373 void
374 FP8Strip::notify_solo_changed ()
375 {
376         if (_solo_ctrl) {
377                 _solo.set_active (_solo_ctrl->get_value () > 0);
378         } else {
379                 _solo.set_active (false);
380         }
381 }
382
383 void
384 FP8Strip::notify_mute_changed ()
385 {
386         if (_mute_ctrl) {
387                 _mute.set_active (_mute_ctrl->get_value () > 0);
388         } else {
389                 _mute.set_active (false);
390         }
391 }
392
393 void
394 FP8Strip::notify_rec_changed ()
395 {
396         if (_rec_ctrl) {
397                 recarm_button ().set_active (_rec_ctrl->get_value() > 0.);
398         } else {
399                 recarm_button ().set_active (false);
400         }
401 }
402
403 void
404 FP8Strip::notify_pan_changed ()
405 {
406         // display only
407 }
408
409 void
410 FP8Strip::notify_x_select_changed ()
411 {
412         if (!_select_plugin_functor.empty ()) {
413                 assert (!_x_select_ctrl);
414                 return;
415         }
416
417         if (_x_select_ctrl) {
418                 assert (_select_plugin_functor.empty ());
419                 select_button ().set_active (_x_select_ctrl->get_value() > 0.);
420                 select_button ().set_color (0xffff00ff);
421                 select_button ().set_blinking (false);
422         } else {
423                 ; // leave alone.
424         }
425 }
426
427 /* *****************************************************************************
428  * Periodic View Updates 
429  */
430
431 void
432 FP8Strip::periodic_update_fader ()
433 {
434         boost::shared_ptr<AutomationControl> ac = _fader_ctrl;
435         if (!ac || _touching) {
436                 return;
437         }
438
439         ARDOUR::AutoState state = ac->automation_state();
440         if (state == Touch || state == Play) {
441                 notify_fader_changed ();
442         }
443 }
444
445 void
446 FP8Strip::set_periodic_display_mode (DisplayMode m) {
447         _displaymode = m;
448         if (_displaymode == SendDisplay || _displaymode == PluginParam) {
449                 // need to change to 4 lines before calling set_text()
450                 set_strip_mode (2); // 4 lines of small text
451         }
452 }
453
454 void
455 FP8Strip::periodic_update_meter ()
456 {
457         bool have_meter = false;
458         bool have_panner = false;
459
460         if (_peak_meter) {
461                 have_meter = true;
462                 float dB = _peak_meter->meter_level (0, MeterMCP);
463                 // TODO: deflect meter
464                 int val = std::min (127.f, std::max (0.f, 2.f * dB + 127.f));
465                 if (val != _last_meter || val > 0) {
466                         _base.tx_midi2 (0xd0 + _id, val & 0x7f); // falls off automatically
467                         _last_meter = val;
468                 }
469
470         } else {
471                 if (0 != _last_meter) {
472                         _base.tx_midi2 (0xd0 + _id, 0);
473                         _last_meter = 0;
474                 }
475         }
476
477         // show redux only if there's a meter, too  (strip display mode 5)
478         if (_peak_meter && _redux_ctrl) {
479                 float rx = (1.f - _redux_ctrl->get_parameter ()) * 127.f;
480                 // TODO: deflect redux
481                 int val = std::min (127.f, std::max (0.f, rx));
482                 if (val != _last_redux) {
483                         _base.tx_midi2 (0xd8 + _id, val & 0x7f);
484                         _last_redux = val;
485                 }
486         } else {
487                 if (0 != _last_redux) {
488                         _base.tx_midi2 (0xd8 + _id, 0);
489                         _last_redux = 0;
490                 }
491         }
492
493         if (_displaymode == PluginParam) {
494                 if (_fader_ctrl) {
495                         set_bar_mode (2); // Fill
496                         set_text_line (0x01, value_as_string(_fader_ctrl->desc(), _fader_ctrl->get_value()));
497                         float barpos = _fader_ctrl->internal_to_interface (_fader_ctrl->get_value());
498                         int val = std::min (127.f, std::max (0.f, barpos * 128.f));
499                         if (val != _last_barpos) {
500                                 _base.tx_midi3 (0xb0, 0x30 + _id, val & 0x7f);
501                                 _last_barpos = val;
502                         }
503                 } else {
504                         set_bar_mode (4); // Off
505                         set_text_line (0x01, "");
506                 }
507         }
508         else if (_displaymode == SendDisplay) {
509                 set_bar_mode (4); // Off
510                 if (_fader_ctrl) {
511                         set_text_line (0x01, value_as_string(_fader_ctrl->desc(), _fader_ctrl->get_value()));
512                 } else {
513                         set_text_line (0x01, "");
514                 }
515         } else if (_pan_ctrl) {
516                 have_panner = true;
517                 float panpos = _pan_ctrl->internal_to_interface (_pan_ctrl->get_value());
518                 int val = std::min (127.f, std::max (0.f, panpos * 128.f));
519                 set_bar_mode (1); // Bipolar
520                 if (val != _last_barpos) {
521                         _base.tx_midi3 (0xb0, 0x30 + _id, val & 0x7f);
522                         _last_barpos = val;
523                 }
524                 set_text_line (0x01, _pan_ctrl->get_user_string ());
525         } else {
526                 set_bar_mode (4); // Off
527         }
528
529         if (_displaymode == SendDisplay || _displaymode == PluginParam) {
530                 set_strip_mode (2); // 4 lines of small text + value-bar
531         }
532         else if (have_meter && have_panner) {
533                 set_strip_mode (5); // small meters + 3 lines of text (3rd is large)  + value-bar
534         }
535         else if (have_meter) {
536                 set_strip_mode (4); // big meters + 3 lines of text (3rd line is large)
537         }
538         else if (have_panner) {
539                 set_strip_mode (0); // 3 lines of text (3rd line is large) + value-bar
540         } else {
541                 set_strip_mode (0); // 3 lines of text (3rd line is large) + value-bar
542         }
543 }
544
545 void
546 FP8Strip::set_strip_mode (uint8_t strip_mode, bool clear)
547 {
548         if (strip_mode == _strip_mode && !clear) {
549                 return;
550         }
551         _strip_mode = strip_mode;
552         _base.tx_sysex (3, 0x13, _id, (_strip_mode & 0x07) | (clear ? 0x10 : 0));
553         //_base.tx_midi3 (0xb0, 0x38 + _id, _bar_mode);
554 }
555
556 void
557 FP8Strip::set_bar_mode (uint8_t bar_mode)
558 {
559         if (bar_mode == _bar_mode) {
560                 return;
561         }
562         _bar_mode = bar_mode;
563         _base.tx_midi3 (0xb0, 0x38 + _id, bar_mode);
564 }
565
566 void
567 FP8Strip::set_text_line (uint8_t line, std::string const& txt, bool inv)
568 {
569         assert (line < 4);
570         if (_last_line[line] == txt) {
571                 return;
572         }
573         _base.tx_text (_id, line, inv ? 0x04 : 0x00, txt);
574         _last_line[line] = txt;
575 }
576
577 void
578 FP8Strip::periodic_update_timecode ()
579 {
580         if (_id >= 2 && _id < 6) {
581                 std::string const& tc = _base.timecode();
582                 //" HH:MM:SS:FF"
583                 std::string t;
584                 if (tc.size () == 12) {
585                         t = tc.substr (1 + (_id - 2) * 3, 2);
586                 }
587                 set_text_line (0x02, t);
588         }
589 }
590
591 void
592 FP8Strip::periodic ()
593 {
594         periodic_update_fader ();
595         periodic_update_meter ();
596         if (_displaymode != PluginSelect) {
597                 periodic_update_timecode ();
598         }
599 }