Optimize automation-event process splitting
[ardour.git] / gtk2_ardour / transport_control_ui.cc
1 /*
2  * Copyright (C) 2017 Robin Gareus <robin@gareus.org>
3  * Copyright (C) 2013 Paul Davis
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License
7  * as published by the Free Software Foundation; either version 2
8  * of the License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18  */
19
20 #include <gtkmm/sizegroup.h>
21
22 #include "ardour/dB.h"
23 #include "ardour/profile.h"
24 #include "widgets/tooltips.h"
25 #include "gtkmm2ext/gui_thread.h"
26
27 #include "actions.h"
28 #include "ardour_ui.h"
29 #include "timers.h"
30 #include "transport_control_ui.h"
31
32 #include "pbd/i18n.h"
33
34 using namespace Glib;
35 using namespace Gtk;
36 using namespace ARDOUR;
37 using namespace ArdourWidgets;
38
39 TransportControlUI::TransportControlUI ()
40 {
41         Config->ParameterChanged.connect (config_connection, MISSING_INVALIDATOR, boost::bind (&TransportControlUI::parameter_changed, this, _1), gui_context());
42 }
43
44 void
45 TransportControlUI::map_actions ()
46 {
47         /* setup actions */
48         RefPtr<Action> act;
49
50         act = ActionManager::get_action (X_("Transport"), X_("ToggleClick"));
51         click_button.set_related_action (act);
52         act = ActionManager::get_action (X_("Transport"), X_("Stop"));
53         stop_button.set_related_action (act);
54         act = ActionManager::get_action (X_("Transport"), X_("Roll"));
55         roll_button.set_related_action (act);
56         act = ActionManager::get_action (X_("Transport"), X_("Record"));
57         rec_button.set_related_action (act);
58         act = ActionManager::get_action (X_("Transport"), X_("GotoStart"));
59         goto_start_button.set_related_action (act);
60         act = ActionManager::get_action (X_("Transport"), X_("GotoEnd"));
61         goto_end_button.set_related_action (act);
62         act = ActionManager::get_action (X_("Transport"), X_("Loop"));
63         auto_loop_button.set_related_action (act);
64         act = ActionManager::get_action (X_("Transport"), X_("PlaySelection"));
65         play_selection_button.set_related_action (act);
66
67         act = ActionManager::get_action (X_("MIDI"), X_("panic"));
68         midi_panic_button.set_related_action (act);
69
70         /* tooltips depend on actions */
71         set_tooltip (roll_button, _("Play from playhead"));
72         set_tooltip (stop_button, _("Stop playback"));
73         set_tooltip (rec_button, _("Toggle record"));
74         set_tooltip (play_selection_button, _("Play range/selection"));
75         set_tooltip (goto_start_button, _("Go to start of session"));
76         set_tooltip (goto_end_button, _("Go to end of session"));
77         set_tooltip (auto_loop_button, _("Play loop range"));
78         set_tooltip (midi_panic_button, _("MIDI Panic\nSend note off and reset controller messages on all MIDI channels"));
79
80         /* set click_button tooltip */
81         parameter_changed ("click-gain");
82 }
83
84 void
85 TransportControlUI::setup (TransportControlProvider* ui)
86 {
87         click_button.signal_button_press_event().connect (sigc::mem_fun (*ui, &TransportControlProvider::click_button_clicked), false);
88         click_button.signal_scroll_event().connect (sigc::mem_fun (*this, &TransportControlUI::click_button_scroll), false);
89
90         /* setup icons */
91
92         click_button.set_icon (ArdourIcon::TransportMetronom);
93         goto_start_button.set_icon (ArdourIcon::TransportStart);
94         goto_end_button.set_icon (ArdourIcon::TransportEnd);
95         roll_button.set_icon (ArdourIcon::TransportPlay);
96         stop_button.set_icon (ArdourIcon::TransportStop);
97         play_selection_button.set_icon (ArdourIcon::TransportRange);
98         auto_loop_button.set_icon (ArdourIcon::TransportLoop);
99         rec_button.set_icon (ArdourIcon::RecButton);
100         midi_panic_button.set_icon (ArdourIcon::TransportPanic);
101
102         /* transport control size-group */
103
104         Glib::RefPtr<SizeGroup> transport_button_size_group = SizeGroup::create (SIZE_GROUP_BOTH);
105         transport_button_size_group->add_widget (goto_start_button);
106         transport_button_size_group->add_widget (goto_end_button);
107         transport_button_size_group->add_widget (auto_loop_button);
108         transport_button_size_group->add_widget (rec_button);
109         if (!ARDOUR::Profile->get_mixbus()) {
110                 /*note: since we aren't showing this button, it doesn't get allocated
111                  * and therefore blows-up the size-group.  so remove it.
112                  */
113                 transport_button_size_group->add_widget (play_selection_button);
114         }
115         transport_button_size_group->add_widget (roll_button);
116         transport_button_size_group->add_widget (stop_button);
117
118         transport_button_size_group->add_widget (midi_panic_button);
119         transport_button_size_group->add_widget (click_button);
120
121 #define PX_SCALE(px) std::max((float)px, rintf((float)px * UIConfiguration::instance().get_ui_scale()))
122
123         click_button.set_size_request (PX_SCALE(20), PX_SCALE(20));
124         set_spacing (PX_SCALE(2));
125
126 #undef PX_SCALE
127
128         if (!ARDOUR::Profile->get_mixbus()) {
129                 pack_start (midi_panic_button, true, true, 0);
130         } else {
131                 pack_start (midi_panic_button, true, true, 3);
132         }
133         pack_start (click_button, true, true, 0);
134         pack_start (goto_start_button, true, true);
135         pack_start (goto_end_button, true, true);
136         pack_start (auto_loop_button, true, true);
137         if (!ARDOUR::Profile->get_mixbus()) {
138                 pack_start (play_selection_button, true, true);
139         }
140         pack_start (roll_button, true, true);
141         pack_start (stop_button, true, true);
142         pack_start (rec_button, true, true, 3);
143
144         roll_button.set_name ("transport button");
145         stop_button.set_name ("transport button");
146         goto_start_button.set_name ("transport button");
147         goto_end_button.set_name ("transport button");
148         auto_loop_button.set_name ("transport button");
149         play_selection_button.set_name ("transport button");
150         rec_button.set_name ("transport recenable button");
151         midi_panic_button.set_name ("transport button"); // XXX ???
152         click_button.set_name ("transport button");
153
154         roll_button.set_controllable (ui->roll_controllable);
155         stop_button.set_controllable (ui->stop_controllable);
156         goto_start_button.set_controllable (ui->goto_start_controllable);
157         goto_end_button.set_controllable (ui->goto_end_controllable);
158         auto_loop_button.set_controllable (ui->auto_loop_controllable);
159         play_selection_button.set_controllable (ui->play_selection_controllable);
160         rec_button.set_controllable (ui->rec_controllable);
161
162         stop_button.set_active (true);
163
164         Timers::blink_connect (sigc::mem_fun (*this, &TransportControlUI::blink_rec_enable));
165 }
166
167 void
168 TransportControlUI::set_session (ARDOUR::Session *s)
169 {
170         SessionHandlePtr::set_session (s);
171         set_loop_sensitivity ();
172         map_transport_state ();
173
174         if (!_session) {
175                 rec_button.set_sensitive (false);
176                 return;
177         }
178
179         _session->config.ParameterChanged.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&TransportControlUI::parameter_changed, this, _1), gui_context());
180         _session->StepEditStatusChange.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&TransportControlUI::step_edit_status_change, this, _1), gui_context());
181         _session->TransportStateChange.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&TransportControlUI::map_transport_state, this), gui_context());
182         _session->auto_loop_location_changed.connect (_session_connections, MISSING_INVALIDATOR, boost::bind (&TransportControlUI::set_loop_sensitivity, this), gui_context ());
183
184         rec_button.set_sensitive (true);
185 }
186
187 void
188 TransportControlUI::parameter_changed (std::string p)
189 {
190         if (p == "external-sync") {
191                 set_loop_sensitivity ();
192         } else if (p == "click-record-only") {
193                 // TODO set a flag, blink or gray-out metronome button while rolling, only
194                 if (Config->get_click_record_only()) {
195                         click_button.set_name ("generic button"); // XXX
196                 } else {
197                         click_button.set_name ("transport button");
198                 }
199         } else if (p == "click-gain") {
200                 float gain_db = accurate_coefficient_to_dB (Config->get_click_gain());
201                 char tmp[32];
202                 snprintf(tmp, 31, "%+.1f", gain_db);
203                 set_tooltip (click_button, string_compose (_("Enable/Disable metronome\n\nRight-click to access preferences\nMouse-wheel to modify level\nSignal Level: %1 dBFS"), tmp));
204         }
205 }
206
207 void
208 TransportControlUI::map_transport_state ()
209 {
210         if (!_session) {
211                 auto_loop_button.unset_active_state ();
212                 play_selection_button.unset_active_state ();
213                 roll_button.unset_active_state ();
214                 stop_button.set_active_state (Gtkmm2ext::ExplicitActive);
215                 return;
216         }
217
218         float sp = _session->transport_speed();
219
220         if (sp != 0.0f) {
221
222                 /* we're rolling */
223
224                 if (_session->get_play_range()) {
225
226                         play_selection_button.set_active_state (Gtkmm2ext::ExplicitActive);
227                         roll_button.unset_active_state ();
228                         auto_loop_button.unset_active_state ();
229
230                 } else if (_session->get_play_loop ()) {
231
232                         auto_loop_button.set_active (true);
233                         play_selection_button.set_active (false);
234
235                         if (Config->get_loop_is_mode()) {
236                                 roll_button.set_active (true);
237                         } else {
238                                 roll_button.set_active (false);
239                         }
240
241                 } else {
242
243                         roll_button.set_active (true);
244                         play_selection_button.set_active (false);
245                         auto_loop_button.set_active (false);
246
247                 }
248
249                 if (UIConfiguration::instance().get_follow_edits() && !_session->config.get_external_sync()) {
250                         /* light up both roll and play-selection if they are joined */
251                         roll_button.set_active (true);
252                         play_selection_button.set_active (true);
253                 }
254
255                 stop_button.set_active (false);
256
257         } else {
258
259                 stop_button.set_active (true);
260                 roll_button.set_active (false);
261                 play_selection_button.set_active (false);
262                 if (Config->get_loop_is_mode ()) {
263                         auto_loop_button.set_active (_session->get_play_loop());
264                 } else {
265                         auto_loop_button.set_active (false);
266                 }
267         }
268 }
269
270 void
271 TransportControlUI::step_edit_status_change (bool yn)
272 {
273         // XXX should really store pre-step edit status of things
274         // we make insensitive
275
276         if (yn) {
277                 rec_button.set_active_state (Gtkmm2ext::ImplicitActive);
278                 rec_button.set_sensitive (false);
279         } else {
280                 rec_button.unset_active_state ();;
281                 rec_button.set_sensitive (true);
282         }
283 }
284
285 void
286 TransportControlUI::set_loop_sensitivity ()
287 {
288         if (!_session || _session->config.get_external_sync()) {
289                 auto_loop_button.set_sensitive (false);
290         } else {
291                 auto_loop_button.set_sensitive (_session && _session->locations()->auto_loop_location());
292         }
293 }
294
295 void
296 TransportControlUI::blink_rec_enable (bool onoff)
297 {
298         if (_session == 0) {
299                 return;
300         }
301
302         if (_session->step_editing()) {
303                 return;
304         }
305
306         Session::RecordState const r = _session->record_status ();
307         bool const h = _session->have_rec_enabled_track ();
308
309         if (r == Session::Enabled || (r == Session::Recording && !h)) {
310                 if (onoff) {
311                         rec_button.set_active_state (Gtkmm2ext::ExplicitActive);
312                 } else {
313                         rec_button.set_active_state (Gtkmm2ext::Off);
314                 }
315         } else if (r == Session::Recording && h) {
316                 rec_button.set_active_state (Gtkmm2ext::ExplicitActive);
317         } else {
318                 rec_button.unset_active_state ();
319         }
320 }
321
322 bool
323 TransportControlUI::click_button_scroll (GdkEventScroll* ev)
324 {
325         gain_t gain = Config->get_click_gain();
326         float gain_db = accurate_coefficient_to_dB (gain);
327
328         switch (ev->direction) {
329                 case GDK_SCROLL_UP:
330                 case GDK_SCROLL_LEFT:
331                         gain_db += 1;
332                         break;
333                 case GDK_SCROLL_DOWN:
334                 case GDK_SCROLL_RIGHT:
335                         gain_db -= 1;
336                         break;
337         }
338         gain_db = std::max (-60.f, gain_db);
339         gain = dB_to_coefficient (gain_db);
340         gain = std::min (gain, Config->get_max_gain());
341         Config->set_click_gain (gain);
342         return true;
343 }