Session::request_locate() takes a tri-valued second argument for "roll-after-locate"
[ardour.git] / libs / surfaces / faderport8 / actions.cc
1 /*
2  * Copyright (C) 2017-2018 Robin Gareus <robin@gareus.org>
3  * Copyright (C) 2018 Ben Loftis <ben@harrisonconsoles.com>
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 along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18  */
19
20 /* Faderport 8 Control Surface
21  * This is the button "Controller" of the MVC surface inteface,
22  * see callbacks.cc for the "View".
23  */
24
25 #include "ardour/dB.h"
26 #include "ardour/plugin_insert.h"
27 #include "ardour/session.h"
28 #include "ardour/session_configuration.h"
29 #include "ardour/track.h"
30 #include "ardour/types.h"
31
32 #include "gtkmm2ext/actions.h"
33
34 #include "faderport8.h"
35
36 #include "pbd/i18n.h"
37
38 using namespace std;
39 using namespace ARDOUR;
40 using namespace ArdourSurface::FP_NAMESPACE;
41 using namespace ArdourSurface::FP_NAMESPACE::FP8Types;
42
43 #define BindMethod(ID, CB) \
44         _ctrls.button (FP8Controls::ID).released.connect_same_thread (button_connections, boost::bind (&FaderPort8:: CB, this));
45
46 #define BindMethod2(ID, ACT, CB) \
47         _ctrls.button (FP8Controls::ID). ACT .connect_same_thread (button_connections, boost::bind (&FaderPort8:: CB, this));
48
49 #define BindFunction(ID, ACT, CB, ...) \
50         _ctrls.button (FP8Controls::ID). ACT .connect_same_thread (button_connections, boost::bind (&FaderPort8:: CB, this, __VA_ARGS__));
51
52 #define BindAction(ID, GRP, ITEM) \
53         _ctrls.button (FP8Controls::ID).released.connect_same_thread (button_connections, boost::bind (&FaderPort8::button_action, this, GRP, ITEM));
54
55 #define BindUserAction(ID) \
56         _ctrls.button (ID).pressed.connect_same_thread (button_connections, boost::bind (&FaderPort8::button_user, this, true, ID)); \
57 _ctrls.button (ID).released.connect_same_thread (button_connections, boost::bind (&FaderPort8::button_user, this, false, ID));
58
59
60 /* Bind button signals (press, release) to callback methods
61  * (called once after constructing buttons).
62  * Bound actions are handled the the ctrl-surface thread.
63  */
64 void
65 FaderPort8::setup_actions ()
66 {
67         BindMethod2 (BtnPlay, pressed, button_play);
68         BindMethod2 (BtnStop, pressed, button_stop);
69         BindMethod2 (BtnLoop, pressed, button_loop);
70         BindMethod2 (BtnRecord, pressed, button_record);
71         BindMethod2 (BtnClick, pressed, button_metronom);
72         BindAction (BtnRedo, "Editor", "redo");
73
74         BindAction (BtnSave, "Common", "Save");
75         BindAction (BtnUndo, "Editor", "undo");
76         BindAction (BtnRedo, "Editor", "redo");
77
78 #ifdef FP8_MUTESOLO_UNDO
79         BindMethod (BtnSoloClear, button_solo_clear);
80 #else
81         BindAction (BtnSoloClear, "Main", "cancel-solo");
82 #endif
83         BindMethod (BtnMuteClear, button_mute_clear);
84
85         BindMethod (FP8Controls::BtnArmAll, button_arm_all);
86
87         BindFunction (BtnRewind, pressed, button_varispeed, false);
88         BindFunction (BtnFastForward, pressed, button_varispeed, true);
89
90         BindFunction (BtnPrev, released, button_prev_next, false);
91         BindFunction (BtnNext, released, button_prev_next, true);
92
93         BindFunction (BtnArm, pressed, button_arm, true);
94         BindFunction (BtnArm, released, button_arm, false);
95
96         BindFunction (BtnAOff, released, button_automation, ARDOUR::Off);
97         BindFunction (BtnATouch, released, button_automation, ARDOUR::Touch);
98         BindFunction (BtnARead, released, button_automation, ARDOUR::Play);
99         BindFunction (BtnAWrite, released, button_automation, ARDOUR::Write);
100         BindFunction (BtnALatch, released, button_automation, ARDOUR::Latch);
101
102         _ctrls.button (FP8Controls::BtnEncoder).pressed.connect_same_thread (button_connections, boost::bind (&FaderPort8::button_encoder, this));
103 #ifdef FADERPORT2
104         _ctrls.button (FP8Controls::BtnParam).pressed.connect_same_thread (button_connections, boost::bind (&FaderPort8::button_encoder, this));
105 #else
106         _ctrls.button (FP8Controls::BtnParam).pressed.connect_same_thread (button_connections, boost::bind (&FaderPort8::button_parameter, this));
107 #endif
108
109
110         BindMethod (BtnBypass, button_bypass);
111         BindAction (BtnBypassAll, "Mixer", "ab-plugins");
112
113         BindAction (BtnMacro, "Common", "toggle-editor-and-mixer");
114         BindMethod (BtnOpen, button_open);
115
116         BindMethod (BtnLink, button_link);
117         BindMethod (BtnLock, button_lock);
118
119 #ifdef FADERPORT2
120         BindMethod (BtnChanLock, button_chanlock);
121         BindMethod (BtnFlip, button_flip);
122 #endif
123
124         // user-specific
125         for (FP8Controls::UserButtonMap::const_iterator i = _ctrls.user_buttons ().begin ();
126                         i != _ctrls.user_buttons ().end (); ++i) {
127                 BindUserAction ((*i).first);
128         }
129 }
130
131 /* ****************************************************************************
132  * Direct control callback Actions
133  */
134
135 void
136 FaderPort8::button_play ()
137 {
138         if (session->transport_rolling ()) {
139                 if (session->transport_speed () != 1.0) {
140                         session->request_transport_speed (1.0);
141                 } else {
142                         transport_stop ();
143                 }
144         } else {
145                 transport_play ();
146         }
147 }
148
149 void
150 FaderPort8::button_stop ()
151 {
152         if (session->transport_rolling ()) {
153                 transport_stop ();
154         } else {
155                 AccessAction ("Transport", "GotoStart");
156         }
157 }
158
159 void
160 FaderPort8::button_record ()
161 {
162         set_record_enable (!get_record_enabled ());
163 }
164
165 void
166 FaderPort8::button_loop ()
167 {
168         loop_toggle ();
169 }
170
171 void
172 FaderPort8::button_metronom ()
173 {
174         Config->set_clicking (!Config->get_clicking ());
175 }
176
177 void
178 FaderPort8::button_bypass ()
179 {
180         boost::shared_ptr<PluginInsert> pi = _plugin_insert.lock();
181         if (pi) {
182                 pi->enable (! pi->enabled ());
183         } else {
184                 AccessAction ("Mixer", "ab-plugins");
185         }
186 }
187
188 void
189 FaderPort8::button_open ()
190 {
191         boost::shared_ptr<PluginInsert> pi = _plugin_insert.lock();
192         if (pi) {
193                 pi->ToggleUI (); /* EMIT SIGNAL */
194         } else {
195                 AccessAction ("Common", "addExistingAudioFiles");
196         }
197 }
198
199 void
200 FaderPort8::button_chanlock ()
201 {
202         _chan_locked = !_chan_locked;
203
204         _ctrls.button (FP8Controls::BtnChannel).set_blinking (_chan_locked);
205 }
206
207 void
208 FaderPort8::button_flip ()
209 {
210 }
211
212 void
213 FaderPort8::button_lock ()
214 {
215         if (!_link_enabled) {
216                 AccessAction ("Editor", "lock");
217                 return;
218         }
219         if (_link_locked) {
220                 unlock_link ();
221         } else if (!_link_control.expired ()) {
222                 lock_link ();
223         }
224 }
225
226 void
227 FaderPort8::button_link ()
228 {
229         switch (_ctrls.fader_mode()) {
230                 case ModeTrack:
231                 case ModePan:
232                         if (_link_enabled) {
233                                 stop_link ();
234                         } else {
235                                 start_link ();
236                         }
237                         break;
238                 default:
239                         //AccessAction ("Window", "show-mixer");
240                         break;
241         }
242 }
243
244 void
245 FaderPort8::button_automation (ARDOUR::AutoState as)
246 {
247         FaderMode fadermode = _ctrls.fader_mode ();
248         switch (fadermode) {
249                 case ModePlugins:
250 #if 0 // Plugin Control Automation Mode
251                         for (std::list <ProcessorCtrl>::iterator i = _proc_params.begin(); i != _proc_params.end(); ++i) {
252                                 ((*i).ac)->set_automation_state (as);
253                         }
254 #endif
255                         return;
256                 case ModeSend:
257                         if (first_selected_stripable()) {
258 #if 0 // Send Level Automation
259                                 boost::shared_ptr<Stripable> s = first_selected_stripable();
260                                 boost::shared_ptr<AutomationControl> send;
261                                 uint32_t i = 0;
262                                 while (0 != (send = s->send_level_controllable (i))) {
263                                         send->set_automation_state (as);
264                                         ++i;
265                                 }
266 #endif
267                         }
268                         return;
269                 default:
270                         break;
271         }
272
273         // TODO link/lock control automation?
274
275         // apply to all selected tracks
276         StripableList all;
277         session->get_stripables (all);
278         for (StripableList::const_iterator i = all.begin(); i != all.end(); ++i) {
279                 if ((*i)->is_master() || (*i)->is_monitor()) {
280                         continue;
281                 }
282                 if (!(*i)->is_selected()) {
283                         continue;
284                 }
285                 boost::shared_ptr<AutomationControl> ac;
286                 switch (fadermode) {
287                         case ModeTrack:
288                                 ac = (*i)->gain_control ();
289                                 break;
290                         case ModePan:
291                                 ac = (*i)->pan_azimuth_control ();
292                                 break;
293                         default:
294                                 break;
295                 }
296                 if (ac) {
297                         ac->set_automation_state (as);
298                 }
299         }
300 }
301
302 void
303 FaderPort8::button_varispeed (bool ffw)
304 {
305         /* pressing both rew + ffwd -> return to zero */
306         FP8ButtonInterface& b_rew = _ctrls.button (FP8Controls::BtnRewind);
307         FP8ButtonInterface& b_ffw = _ctrls.button (FP8Controls::BtnFastForward);
308         if (b_rew.is_pressed () && b_ffw.is_pressed ()){
309                 // stop key-repeat
310                 dynamic_cast<FP8RepeatButton*>(&b_ffw)->stop_repeat();
311                 dynamic_cast<FP8RepeatButton*>(&b_rew)->stop_repeat();
312                 session->request_locate (0, MustStop);
313                 return;
314         }
315
316         // switch play direction, if needed
317         if (ffw) {
318                 if (session->transport_speed () <= 0) {
319                         session->request_transport_speed (1.0);
320                         return ;
321                 }
322         } else {
323                 if (session->transport_speed () >= 0) {
324                         session->request_transport_speed (-1.0);
325                         return ;
326                 }
327         }
328         // incremetally increase speed. double speed every 10 clicks
329         // (keypress auto-repeat is 100ms)
330         float maxspeed = Config->get_shuttle_max_speed();
331         float speed = exp2f(0.1f) * session->transport_speed ();
332         speed = std::max (-maxspeed, std::min (maxspeed, speed));
333         session->request_transport_speed (speed, false);
334 }
335
336 #ifdef FP8_MUTESOLO_UNDO
337 void
338 FaderPort8::button_solo_clear ()
339 {
340         bool soloing = session->soloing() || session->listening();
341 #ifdef MIXBUS
342         soloing |= session->mixbus_soloed();
343 #endif
344         if (soloing) {
345                 StripableList all;
346                 session->get_stripables (all);
347                 for (StripableList::const_iterator i = all.begin(); i != all.end(); ++i) {
348                         if ((*i)->is_master() || (*i)->is_auditioner() || (*i)->is_monitor()) {
349                                 continue;
350                         }
351                         boost::shared_ptr<SoloControl> sc = (*i)->solo_control();
352                         if (sc && sc->self_soloed ()) {
353                                 _solo_state.push_back (boost::weak_ptr<AutomationControl>(sc));
354                         }
355                 }
356                 cancel_all_solo (); // AccessAction ("Main", "cancel-solo");
357         } else {
358                 /* restore solo */
359                 boost::shared_ptr<ControlList> cl (new ControlList);
360                 for (std::vector <boost::weak_ptr<AutomationControl> >::const_iterator i = _solo_state.begin(); i != _solo_state.end(); ++i) {
361                         boost::shared_ptr<AutomationControl> ac = (*i).lock();
362                         if (!ac) {
363                                 continue;
364                         }
365                         ac->start_touch (ac->session().transport_sample());
366                         cl->push_back (ac);
367                 }
368                 if (!cl->empty()) {
369                         session->set_controls (cl, 1.0, PBD::Controllable::NoGroup);
370                 }
371         }
372 }
373 #endif
374
375 void
376 FaderPort8::button_mute_clear ()
377 {
378 #ifdef FP8_MUTESOLO_UNDO
379         if (session->muted ()) {
380                 _mute_state = session->cancel_all_mute ();
381         } else {
382                 /* restore mute */
383                 boost::shared_ptr<ControlList> cl (new ControlList);
384                 for (std::vector <boost::weak_ptr<AutomationControl> >::const_iterator i = _mute_state.begin(); i != _mute_state.end(); ++i) {
385                         boost::shared_ptr<AutomationControl> ac = (*i).lock();
386                         if (!ac) {
387                                 continue;
388                         }
389                         cl->push_back (ac);
390                         ac->start_touch (ac->session().transport_sample());
391                 }
392                 if (!cl->empty()) {
393                         session->set_controls (cl, 1.0, PBD::Controllable::NoGroup);
394                 }
395         }
396 #else
397         session->cancel_all_mute ();
398 #endif
399 }
400
401 void
402 FaderPort8::button_arm_all ()
403 {
404         BasicUI::all_tracks_rec_in ();
405 }
406
407 /* access generic action */
408 void
409 FaderPort8::button_action (const std::string& group, const std::string& item)
410 {
411         AccessAction (group, item);
412 }
413
414 /* ****************************************************************************
415  * Control Interaction (encoder)
416  */
417
418 void
419 FaderPort8::handle_encoder_pan (int steps)
420 {
421         boost::shared_ptr<Stripable> s = first_selected_stripable();
422         if (s) {
423                 boost::shared_ptr<AutomationControl> ac;
424                 if (shift_mod () || _ctrls.fader_mode() == ModePan) {
425                         ac = s->pan_width_control ();
426                 } else {
427                         ac = s->pan_azimuth_control ();
428                 }
429                 if (ac) {
430                         ac->start_touch (ac->session().transport_sample());
431                         if (steps == 0) {
432                                 ac->set_value (ac->normal(), PBD::Controllable::UseGroup);
433                         } else {
434                                 double v = ac->internal_to_interface (ac->get_value());
435                                 v = std::max (0.0, std::min (1.0, v + steps * .01));
436                                 ac->set_value (ac->interface_to_internal(v), PBD::Controllable::UseGroup);
437                         }
438                 }
439         }
440 }
441
442 void
443 FaderPort8::handle_encoder_link (int steps)
444 {
445         if (_link_control.expired ()) {
446                 return;
447         }
448         boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl> (_link_control.lock ());
449         if (!ac) {
450                 return;
451         }
452
453         double v = ac->internal_to_interface (ac->get_value());
454         ac->start_touch (ac->session().transport_sample());
455
456         if (steps == 0) {
457                 ac->set_value (ac->normal(), PBD::Controllable::UseGroup);
458                 return;
459         }
460
461         if (ac->desc().toggled) {
462                 v = v > 0 ? 0. : 1.;
463         } else if (ac->desc().integer_step) {
464                 v += steps / (1.f + ac->desc().upper - ac->desc().lower);
465         } else if (ac->desc().enumeration) {
466                 ac->set_value (ac->desc().step_enum (ac->get_value(), steps < 0), PBD::Controllable::UseGroup);
467                 return;
468         } else {
469                 v = std::max (0.0, std::min (1.0, v + steps * .01));
470         }
471         ac->set_value (ac->interface_to_internal(v), PBD::Controllable::UseGroup);
472 }
473
474
475 /* ****************************************************************************
476  * Mode specific and internal callbacks
477  */
478
479 /* handle "ARM" press -- act like shift, change "Select" button mode */
480 void
481 FaderPort8::button_arm (bool press)
482 {
483 #ifdef FADERPORT2
484         boost::shared_ptr<Stripable> s = first_selected_stripable();
485         if (press && s) {
486                 boost::shared_ptr<Track> t = boost::dynamic_pointer_cast<Track>(s);
487                 if (t) {
488                         t->rec_enable_control()->set_value (!t->rec_enable_control()->get_value(), PBD::Controllable::UseGroup);
489                 }
490         }
491 #else
492         FaderMode fadermode = _ctrls.fader_mode ();
493         if (fadermode == ModeTrack || fadermode == ModePan) {
494                 _ctrls.button (FP8Controls::BtnArm).set_active (press);
495                 ARMButtonChange (press); /* EMIT SIGNAL */
496         }
497 #endif
498 }
499
500 void
501 FaderPort8::button_prev_next (bool next)
502 {
503         switch (_ctrls.nav_mode()) {
504                 case NavChannel:
505 #ifndef FADERPORT2
506                         select_prev_next (next);
507                         break;
508 #endif
509                 case NavMaster:
510                 case NavScroll:
511                 case NavPan:
512                         bank (!next, false);
513                         break;
514                 case NavBank:
515                         bank (!next, true);
516                         break;
517                 case NavZoom:
518                         if (next) {
519                                 VerticalZoomInSelected ();
520                         } else {
521                                 VerticalZoomOutSelected ();
522                         }
523                         break;
524                 case NavSection:
525                         if (next) {
526                                 AccessAction ("Region", "nudge-forward");
527                         } else {
528                                 AccessAction ("Region", "nudge-backward");
529                         }
530                         break;
531                 case NavMarker:
532                         if (next) {
533                                 next_marker ();
534                         } else {
535                                 prev_marker ();
536                         }
537                         break;
538         }
539 }
540
541 /* handle navigation encoder press */
542 void
543 FaderPort8::button_encoder ()
544 {
545         /* special-case metronome level */
546         if (_ctrls.button (FP8Controls::BtnClick).is_pressed ()) {
547                 Config->set_click_gain (1.0);
548                 _ctrls.button (FP8Controls::BtnClick).ignore_release();
549                 return;
550         }
551         switch (_ctrls.nav_mode()) {
552                 case NavZoom:
553                         ZoomToSession (); // XXX undo zoom
554                         break;
555                 case NavScroll:
556                         ZoomToSession ();
557                         break;
558                 case NavChannel:
559                         AccessAction ("Editor", "select-topmost");
560                         break;
561                 case NavBank:
562                         move_selected_into_view ();
563                         break;
564                 case NavMaster:
565                         {
566                                 /* master || monitor level -- reset to 0dB */
567                                 boost::shared_ptr<AutomationControl> ac;
568                                 if (session->monitor_active() && !_ctrls.button (FP8Controls::BtnMaster).is_pressed ()) {
569                                         ac = session->monitor_out()->gain_control ();
570                                 } else if (session->master_out()) {
571                                         ac = session->master_out()->gain_control ();
572                                 }
573                                 if (ac) {
574                                         ac->start_touch (ac->session().transport_sample());
575                                         ac->set_value (ac->normal(), PBD::Controllable::NoGroup);
576                                 }
577                         }
578                         break;
579                 case NavPan:
580                         break;
581                 case NavSection:
582                         // TODO nudge
583                         break;
584                 case NavMarker:
585                         {
586                                 string markername;
587                                 /* Don't add another mark if one exists within 1/100th of a second of
588                                  * the current position and we're not rolling.
589                                  */
590                                 samplepos_t where = session->audible_sample();
591                                 if (session->transport_stopped_or_stopping() && session->locations()->mark_at (where, session->sample_rate() / 100.0)) {
592                                         return;
593                                 }
594
595                                 session->locations()->next_available_name (markername,"mark");
596                                 add_marker (markername);
597                         }
598                         break;
599         }
600 }
601
602 /* handle navigation encoder turn */
603 void
604 FaderPort8::encoder_navigate (bool neg, int steps)
605 {
606         /* special-case metronome level */
607         if (_ctrls.button (FP8Controls::BtnClick).is_pressed ()) {
608                 // compare to ARDOUR_UI::click_button_scroll()
609                 gain_t gain = Config->get_click_gain();
610                 float gain_db = accurate_coefficient_to_dB (gain);
611                 gain_db += (neg ? -1.f : 1.f) * steps;
612                 gain_db = std::max (-60.f, gain_db);
613                 gain = dB_to_coefficient (gain_db);
614                 gain = std::min (gain, Config->get_max_gain());
615                 Config->set_click_gain (gain);
616                 _ctrls.button (FP8Controls::BtnClick).ignore_release();
617                 return;
618         }
619
620         switch (_ctrls.nav_mode()) {
621                 case NavChannel:
622                         if (neg) {
623                                 AccessAction ("Mixer", "scroll-left");
624                                 AccessAction ("Editor", "step-tracks-up");
625                         } else {
626                                 AccessAction ("Mixer", "scroll-right");
627                                 AccessAction ("Editor", "step-tracks-down");
628                         }
629                         break;
630                 case NavZoom:
631                         if (neg) {
632                                 ZoomOut ();
633                         } else {
634                                 ZoomIn ();
635                         }
636                         break;
637                 case NavMarker:
638                 case NavScroll:
639                         ScrollTimeline ((neg ? -1.f : 1.f) * steps / (shift_mod() ? 1024.f : 256.f));
640                         break;
641                 case NavBank:
642                         bank (neg, false);
643                         break;
644                 case NavMaster:
645                         {
646                                 /* master || monitor level */
647                                 boost::shared_ptr<AutomationControl> ac;
648                                 if (session->monitor_active() && !_ctrls.button (FP8Controls::BtnMaster).is_pressed ()) {
649                                         ac = session->monitor_out()->gain_control ();
650                                 } else if (session->master_out()) {
651                                         ac = session->master_out()->gain_control ();
652                                 }
653                                 if (ac) {
654                                         double v = ac->internal_to_interface (ac->get_value());
655                                         v = std::max (0.0, std::min (1.0, v + steps * (neg ? -.01 : .01)));
656                                         ac->start_touch (ac->session().transport_sample());
657                                         ac->set_value (ac->interface_to_internal(v), PBD::Controllable::NoGroup);
658                                 }
659                         }
660                         break;
661                 case NavSection:
662                         if (neg) {
663                                 AccessAction ("Common", "nudge-playhead-backward");
664                         } else {
665                                 AccessAction ("Common", "nudge-playhead-forward");
666                         }
667                         break;
668                 case NavPan:
669                         abort(); /*NOTREACHED*/
670                         break;
671         }
672 }
673
674 /* handle pan/param encoder press */
675 void
676 FaderPort8::button_parameter ()
677 {
678         switch (_ctrls.fader_mode()) {
679                 case ModeTrack:
680                 case ModePan:
681                         if (_link_enabled || _link_locked) {
682                                 handle_encoder_link (0);
683                         } else {
684                                 handle_encoder_pan (0);
685                         }
686                         break;
687                 case ModePlugins:
688                         toggle_preset_param_mode ();
689                         break;
690                 case ModeSend:
691                         break;
692         }
693 }
694
695 /* handle pan/param encoder turn */
696 void
697 FaderPort8::encoder_parameter (bool neg, int steps)
698 {
699         switch (_ctrls.fader_mode()) {
700                 case ModeTrack:
701                 case ModePan:
702                         if (steps != 0) {
703                                 if (_link_enabled || _link_locked) {
704                                         handle_encoder_link (neg ? -steps : steps);
705                                 } else {
706                                         handle_encoder_pan (neg ? -steps : steps);
707                                 }
708                         }
709                         break;
710                 case ModePlugins:
711                 case ModeSend:
712                         while (steps > 0) {
713                                 bank_param (neg, shift_mod());
714                                 --steps;
715                         }
716                         break;
717         }
718 }
719
720 /* handle user-specific actions */
721 void
722 FaderPort8::button_user (bool press, FP8Controls::ButtonId btn)
723 {
724         _user_action_map[btn].call (*this, press);
725 }