2 * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
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.
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.
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.
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"
28 #include "control_protocol/control_protocol.h"
30 #include "fp8_strip.h"
32 using namespace ARDOUR;
33 using namespace ArdourSurface::FP_NAMESPACE;
34 using namespace ArdourSurface::FP_NAMESPACE::FP8Types;
37 FP8Strip::midi_ctrl_id (CtrlElement type, uint8_t id)
39 assert (id < N_STRIPS);
67 if (id == 0) { // strip 8
88 FP8Strip::FP8Strip (FP8Base& b, uint8_t id)
91 , _solo (b, midi_ctrl_id (BtnSolo, id))
92 , _mute (b, midi_ctrl_id (BtnMute, id))
93 , _selrec (b, midi_ctrl_id (BtnSelect, id), true)
97 , _displaymode (Stripables)
99 assert (id < N_STRIPS);
102 _last_meter = _last_redux = _last_barpos = 0xff;
104 _mute.StateChange.connect_same_thread (_button_connections, boost::bind (&FP8Strip::set_mute, this, _1));
105 _solo.StateChange.connect_same_thread (_button_connections, boost::bind (&FP8Strip::set_solo, this, _1));
106 select_button ().released.connect_same_thread (_button_connections, boost::bind (&FP8Strip::set_select, this));
107 recarm_button ().released.connect_same_thread (_button_connections, boost::bind (&FP8Strip::set_recarm, this));
108 b.Periodic.connect_same_thread (_base_connection, boost::bind (&FP8Strip::periodic, this));
111 FP8Strip::~FP8Strip ()
113 drop_automation_controls ();
114 _base_connection.disconnect ();
115 _button_connections.drop_connections ();
119 FP8Strip::drop_automation_controls ()
121 _fader_connection.disconnect ();
122 _mute_connection.disconnect ();
123 _solo_connection.disconnect ();
124 _rec_connection.disconnect ();
125 _pan_connection.disconnect ();
126 _x_select_connection.disconnect ();
128 _fader_ctrl.reset ();
133 _x_select_ctrl.reset ();
134 _peak_meter.reset ();
135 _redux_ctrl.reset ();
136 _select_plugin_functor.clear ();
140 FP8Strip::initialize ()
142 /* this is called once midi transmission is possible,
143 * ie from FaderPort8::connected()
145 _solo.set_active (false);
146 _solo.set_blinking (false);
147 _mute.set_active (false);
149 /* reset momentary button state */
153 drop_automation_controls ();
155 select_button ().set_color (0xffffffff);
156 select_button ().set_active (false);
157 select_button ().set_blinking (false);
159 recarm_button ().set_active (false);
160 recarm_button ().set_color (0xffffffff);
162 set_strip_mode (0, true);
165 _last_line[0].clear ();
166 _last_line[1].clear ();
167 _last_line[2].clear ();
168 _last_line[3].clear ();
169 _base.tx_sysex (4, 0x12, _id, 0x00, 0x00);
170 _base.tx_sysex (4, 0x12, _id, 0x01, 0x00);
171 _base.tx_sysex (4, 0x12, _id, 0x02, 0x00);
172 _base.tx_sysex (4, 0x12, _id, 0x03, 0x00);
174 set_bar_mode (4); // off
176 _base.tx_midi2 (midi_ctrl_id (Meter, _id), 0); // reset meter
177 _base.tx_midi2 (midi_ctrl_id (Redux, _id), 0); // reset redux
179 _base.tx_midi3 (midi_ctrl_id (Fader, _id), 0, 0); // fader
181 /* clear cached values */
183 _last_meter = _last_redux = _last_barpos = 0xff;
187 #define GENERATE_SET_CTRL_FUNCTION(NAME) \
189 FP8Strip::set_ ##NAME##_controllable (boost::shared_ptr<AutomationControl> ac) \
191 if (_##NAME##_ctrl == ac) { \
194 _##NAME##_connection.disconnect(); \
195 _##NAME##_ctrl = ac; \
198 ac->Changed.connect (_##NAME##_connection, MISSING_INVALIDATOR, \
199 boost::bind (&FP8Strip::notify_##NAME##_changed, this), fp8_context()); \
201 notify_##NAME##_changed (); \
205 GENERATE_SET_CTRL_FUNCTION (fader)
206 GENERATE_SET_CTRL_FUNCTION (mute)
207 GENERATE_SET_CTRL_FUNCTION (solo)
208 GENERATE_SET_CTRL_FUNCTION (rec)
209 GENERATE_SET_CTRL_FUNCTION (pan)
210 GENERATE_SET_CTRL_FUNCTION (x_select)
212 #undef GENERATE_SET_CTRL_FUNCTION
214 // special case -- w/_select_plugin_functor
216 FP8Strip::set_select_controllable (boost::shared_ptr<AutomationControl> ac)
218 _select_plugin_functor.clear ();
219 set_x_select_controllable (ac);
223 FP8Strip::set_select_cb (boost::function<void ()>& functor)
225 set_select_controllable (boost::shared_ptr<AutomationControl>());
226 _select_plugin_functor = functor;
230 FP8Strip::unset_controllables (int which)
232 _peak_meter = boost::shared_ptr<ARDOUR::PeakMeter>();
233 _redux_ctrl = boost::shared_ptr<ARDOUR::ReadOnlyControl>();
234 _stripable_name.clear ();
236 if (which & CTRL_FADER) {
237 set_fader_controllable (boost::shared_ptr<AutomationControl>());
239 if (which & CTRL_MUTE) {
240 set_mute_controllable (boost::shared_ptr<AutomationControl>());
242 if (which & CTRL_SOLO) {
243 set_solo_controllable (boost::shared_ptr<AutomationControl>());
245 if (which & CTRL_REC) {
246 set_rec_controllable (boost::shared_ptr<AutomationControl>());
248 if (which & CTRL_PAN) {
249 set_pan_controllable (boost::shared_ptr<AutomationControl>());
251 if (which & CTRL_SELECT) {
252 set_select_controllable (boost::shared_ptr<AutomationControl>());
253 select_button ().set_color (0xffffffff);
254 select_button ().set_active (false);
255 select_button ().set_blinking (false);
257 if (which & CTRL_TEXT0) {
258 set_text_line (0, "");
260 if (which & CTRL_TEXT1) {
261 set_text_line (1, "");
263 if (which & CTRL_TEXT2) {
264 set_text_line (2, "");
266 if (which & CTRL_TEXT3) {
267 set_text_line (3, "");
269 set_bar_mode (4); // Off
273 FP8Strip::set_strip_name ()
275 size_t lb = _base.show_meters () ? 6 : 9;
276 set_text_line (0, _stripable_name.substr (0, lb));
277 set_text_line (1, _stripable_name.length() > lb ? _stripable_name.substr (lb) : "");
281 FP8Strip::set_stripable (boost::shared_ptr<Stripable> s, bool panmode)
285 if (_base.show_meters () && _base.show_panner ()) {
286 set_strip_mode (5, true);
287 } else if (_base.show_meters ()) {
288 set_strip_mode (4, true);
290 set_strip_mode (0, true);
292 if (!_base.show_panner ()) {
293 set_bar_mode (4, true); // Off
297 set_fader_controllable (s->pan_azimuth_control ());
299 set_fader_controllable (s->gain_control ());
301 set_pan_controllable (s->pan_azimuth_control ());
303 if (s->is_monitor ()) {
304 set_mute_controllable (boost::shared_ptr<AutomationControl>());
306 set_mute_controllable (s->mute_control ());
308 set_solo_controllable (s->solo_control ());
310 if (boost::dynamic_pointer_cast<Track> (s)) {
311 boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track>(s);
312 set_rec_controllable (t->rec_enable_control ());
313 recarm_button ().set_color (0xff0000ff);
315 set_rec_controllable (boost::shared_ptr<AutomationControl>());
316 recarm_button ().set_color (0xffffffff);
317 recarm_button ().set_active (false);
319 _peak_meter = s->peak_meter ();
320 _redux_ctrl = s->comp_redux_controllable ();
322 set_select_controllable (boost::shared_ptr<AutomationControl>());
323 select_button ().set_active (s->is_selected ());
325 set_select_button_color (s->presentation_info ().color());
326 //select_button ().set_blinking (false);
328 _stripable_name = s->name ();
330 if (_base.twolinetext ()) {
333 set_text_line (0, s->name ());
334 set_text_line (1, _pan_ctrl ? _pan_ctrl->get_user_string () : "");
336 set_text_line (2, "");
337 set_text_line (3, "");
340 /* *****************************************************************************
341 * Parse Strip Specifig MIDI Events
345 FP8Strip::midi_touch (bool t)
348 boost::shared_ptr<AutomationControl> ac = _fader_ctrl;
353 ac->start_touch (ac->session().transport_sample());
355 ac->stop_touch (ac->session().transport_sample());
361 FP8Strip::midi_fader (float val)
363 assert (val >= 0.f && val <= 1.f);
367 boost::shared_ptr<AutomationControl> ac = _fader_ctrl;
371 ac->start_touch (ac->session().transport_sample());
372 ac->set_value (ac->interface_to_internal (val), group_mode ());
376 /* *****************************************************************************
377 * Actions from Controller, Update Model
380 PBD::Controllable::GroupControlDisposition
381 FP8Strip::group_mode () const
383 if (_base.shift_mod ()) {
384 return PBD::Controllable::InverseGroup;
386 return PBD::Controllable::UseGroup;
391 FP8Strip::set_mute (bool on)
396 _mute_ctrl->start_touch (_mute_ctrl->session().transport_sample());
397 _mute_ctrl->set_value (on ? 1.0 : 0.0, group_mode ());
401 FP8Strip::set_solo (bool on)
406 _solo_ctrl->start_touch (_solo_ctrl->session().transport_sample());
407 _solo_ctrl->set_value (on ? 1.0 : 0.0, group_mode ());
411 FP8Strip::set_recarm ()
416 const bool on = !recarm_button ().is_active();
417 _rec_ctrl->set_value (on ? 1.0 : 0.0, group_mode ());
421 FP8Strip::set_select ()
423 if (!_select_plugin_functor.empty ()) {
424 assert (!_x_select_ctrl);
425 _select_plugin_functor ();
426 } else if (_x_select_ctrl) {
427 _x_select_ctrl->start_touch (_x_select_ctrl->session().transport_sample());
428 const bool on = !select_button ().is_active();
429 _x_select_ctrl->set_value (on ? 1.0 : 0.0, group_mode ());
433 /* *****************************************************************************
434 * Callbacks from Stripable, Update View
438 FP8Strip::notify_fader_changed ()
440 boost::shared_ptr<AutomationControl> ac = _fader_ctrl;
446 val = ac->internal_to_interface (ac->get_value());
447 val = std::max (0.f, std::min (1.f, val)) * 16368.f; /* 16 * 1023 */
449 unsigned short mv = lrintf (val);
450 if (mv == _last_fader) {
454 _base.tx_midi3 (midi_ctrl_id (Fader, _id), (mv & 0x7f), (mv >> 7) & 0x7f);
458 FP8Strip::notify_solo_changed ()
461 boost::shared_ptr<SoloControl> sc = boost::dynamic_pointer_cast<SoloControl> (_solo_ctrl);
463 _solo.set_blinking (sc->soloed_by_others () && !sc->self_soloed ());
464 _solo.set_active (sc->self_soloed ());
466 _solo.set_blinking (false);
467 _solo.set_active (_solo_ctrl->get_value () > 0);
470 _solo.set_blinking (false);
471 _solo.set_active (false);
476 FP8Strip::notify_mute_changed ()
479 _mute.set_active (_mute_ctrl->get_value () > 0);
481 _mute.set_active (false);
486 FP8Strip::notify_rec_changed ()
489 recarm_button ().set_active (_rec_ctrl->get_value() > 0.);
491 recarm_button ().set_active (false);
496 FP8Strip::notify_pan_changed ()
502 FP8Strip::notify_x_select_changed ()
504 if (!_select_plugin_functor.empty ()) {
505 assert (!_x_select_ctrl);
509 if (_x_select_ctrl) {
510 assert (_select_plugin_functor.empty ());
511 select_button ().set_active (_x_select_ctrl->get_value() > 0.);
512 select_button ().set_color (0xffff00ff);
513 select_button ().set_blinking (false);
517 /* *****************************************************************************
518 * Periodic View Updates
522 FP8Strip::periodic_update_fader ()
524 boost::shared_ptr<AutomationControl> ac = _fader_ctrl;
525 if (!ac || _touching) {
529 if (!ac->automation_playback ()) {
532 notify_fader_changed ();
536 FP8Strip::set_periodic_display_mode (DisplayMode m) {
538 if (_displaymode == SendDisplay || _displaymode == PluginParam) {
539 // need to change to 4 lines before calling set_text()
540 set_strip_mode (2); // 4 lines of small text
545 FP8Strip::periodic_update_meter ()
547 bool show_meters = _base.show_meters ();
548 bool have_meter = false;
549 bool have_panner = false;
551 if (_peak_meter && show_meters) {
553 float dB = _peak_meter->meter_level (0, MeterMCP);
554 // TODO: deflect meter
555 int val = std::min (127.f, std::max (0.f, 2.f * dB + 127.f));
556 if (val != _last_meter || val > 0) {
557 _base.tx_midi2 (midi_ctrl_id (Meter, _id), val & 0x7f); // falls off automatically
561 } else if (show_meters) {
562 if (0 != _last_meter) {
563 _base.tx_midi2 (midi_ctrl_id (Meter, _id), 0);
568 // show redux only if there's a meter, too (strip display mode 5)
569 if (_peak_meter && _redux_ctrl && show_meters) {
570 float rx = (1.f - _redux_ctrl->get_parameter ()) * 127.f;
571 // TODO: deflect redux
572 int val = std::min (127.f, std::max (0.f, rx));
573 if (val != _last_redux) {
574 _base.tx_midi2 (midi_ctrl_id (Redux, _id), val & 0x7f);
577 } else if (show_meters) {
578 if (0 != _last_redux) {
579 _base.tx_midi2 (midi_ctrl_id (Redux, _id), 0);
584 if (_displaymode == PluginParam) {
586 set_bar_mode (2); // Fill
587 set_text_line (2, value_as_string(_fader_ctrl->desc(), _fader_ctrl->get_value()));
588 float barpos = _fader_ctrl->internal_to_interface (_fader_ctrl->get_value());
589 int val = std::min (127.f, std::max (0.f, barpos * 128.f));
590 if (val != _last_barpos) {
591 _base.tx_midi3 (0xb0, midi_ctrl_id (BarVal, _id), val & 0x7f);
595 set_bar_mode (4); // Off
596 set_text_line (2, "");
599 else if (_displaymode == PluginSelect) {
600 set_bar_mode (4); // Off
602 else if (_displaymode == SendDisplay) {
603 set_bar_mode (4); // Off
605 set_text_line (1, value_as_string(_fader_ctrl->desc(), _fader_ctrl->get_value()));
607 set_text_line (1, "");
609 } else if (_pan_ctrl) {
610 have_panner = _base.show_panner ();
611 float panpos = _pan_ctrl->internal_to_interface (_pan_ctrl->get_value());
612 int val = std::min (127.f, std::max (0.f, panpos * 128.f));
613 set_bar_mode (have_panner ? 1 : 4); // Bipolar or Off
614 if (val != _last_barpos && have_panner) {
615 _base.tx_midi3 (0xb0, midi_ctrl_id (BarVal, _id), val & 0x7f);
618 if (_base.twolinetext ()) {
621 set_text_line (1, _pan_ctrl->get_user_string ());
624 set_bar_mode (4); // Off
625 if (_base.twolinetext ()) {
628 set_text_line (1, "");
632 if (_displaymode == SendDisplay || _displaymode == PluginParam) {
633 set_strip_mode (2); // 4 lines of small text + value-bar
635 else if (have_meter && have_panner) {
636 set_strip_mode (5); // small meters + 3 lines of text (3rd is large) + value-bar
638 else if (have_meter) {
639 /* we cannot use "big meters" mode 4, since that implies
640 * 2 "Large" (4char) text lines, followed by a HUGE 2 char line
641 * and hides timecode-clock */
644 else if (have_panner) {
645 set_strip_mode (0); // 3 lines of text (3rd line is large + long) + value-bar
647 set_strip_mode (0); // 3 lines of text (3rd line is large + long) + value-bar
652 FP8Strip::set_strip_mode (uint8_t strip_mode, bool clear)
654 if (strip_mode == _strip_mode && !clear) {
658 _strip_mode = strip_mode;
659 _base.tx_sysex (3, 0x13, _id, (_strip_mode & 0x07) | (clear ? 0x10 : 0));
662 /* work-around, when swiching modes, the FP8 may not
663 * properly redraw long lines. Only update lines 0, 1
664 * (line 2 is timecode, line 3 may be inverted)
666 _base.tx_text (_id, 0, 0x00, _last_line[0]);
667 _base.tx_text (_id, 1, 0x00, _last_line[1]);
672 FP8Strip::set_bar_mode (uint8_t bar_mode, bool force)
674 if (bar_mode == _bar_mode && !force) {
679 _base.tx_midi3 (0xb0, midi_ctrl_id (BarVal, _id), 0);
683 _bar_mode = bar_mode;
684 _base.tx_midi3 (0xb0, midi_ctrl_id (BarMode, _id), bar_mode);
688 FP8Strip::set_text_line (uint8_t line, std::string const& txt, bool inv)
691 if (_last_line[line] == txt) {
694 _base.tx_text (_id, line, inv ? 0x04 : 0x00, txt);
695 _last_line[line] = txt;
699 FP8Strip::periodic_update_timecode (uint32_t m)
706 std::string const& tc = mc ? _base.musical_time () : _base.timecode();
708 if (tc.size () == 12) {
709 t = tc.substr (1 + (_id - (mc ? 4 : 0)) * 3, 2);
711 set_text_line (2, t);
712 } else if (_id >= 2 && _id < 6) {
713 std::string const& tc = (m == 2) ? _base.musical_time () : _base.timecode();
714 //" HH:MM:SS:FF" or " BR|BT|TI|CK"
716 if (tc.size () == 12) {
717 t = tc.substr (1 + (_id - 2) * 3, 2);
719 set_text_line (2, t);
721 set_text_line (2, "");
726 FP8Strip::periodic ()
728 periodic_update_fader ();
729 periodic_update_meter ();
730 if (_displaymode != PluginSelect && _displaymode != PluginParam) {
731 periodic_update_timecode (_base.clock_mode ());