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