replace ::cast_dynamic() with relevant ActionManager::get_*_action() calls
[ardour.git] / libs / surfaces / maschine2 / callbacks.cc
1 /*
2  * Copyright (C) 2016 Robin Gareus <robin@gareus.org>
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2, or (at your option)
7  * 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 Foundation,
16  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
17  */
18
19 #include "ardour/session.h"
20 #include "gtkmm2ext/colors.h"
21 #include "gtkmm2ext/actions.h"
22 #include "gtkmm2ext/gui_thread.h"
23 #include "pbd/i18n.h"
24
25 #include "maschine2.h"
26 #include "m2controls.h"
27
28 #include "midi++/port.h"
29
30 #define COLOR_WHITE 0xffffffff
31 #define COLOR_GRAY 0x606060ff
32 #define COLOR_BLACK 0x000000ff
33
34 using namespace ARDOUR;
35 using namespace ArdourSurface;
36
37 void
38 Maschine2::connect_signals ()
39 {
40         // TODO: use some convenience macros here
41
42         /* Signals */
43         session->TransportStateChange.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Maschine2::notify_transport_state_changed, this), this);
44         session->TransportLooped.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Maschine2::notify_loop_state_changed, this), this);
45         session->RecordStateChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Maschine2::notify_record_state_changed, this), this);
46         Config->ParameterChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Maschine2::notify_parameter_changed, this, _1), this);
47         session->config.ParameterChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Maschine2::notify_parameter_changed, this, _1), this);
48         session->DirtyChanged.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Maschine2::notify_session_dirty_changed, this), this);
49         session->history().Changed.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&Maschine2::notify_history_changed, this), this);
50
51         /* Actions */
52         Glib::RefPtr<Gtk::ToggleAction> tact;
53         Glib::RefPtr<Gtk::RadioAction> ract;
54 #if 0
55         tact = ActionManager::find_toggle_action (X_("Editor"), X_("ToggleMeasureVisibility"));
56         tact->signal_toggled ().connect (sigc::mem_fun (*this, &Maschine2::notify_grid_change));
57 #endif
58         ract = ActionManager::find_radio_action (X_("Editor"), X_("snap-off"));
59         ract->signal_toggled ().connect (sigc::mem_fun (*this, &Maschine2::notify_snap_change));
60
61         ract = ActionManager::find_radio_action (X_("Editor"), X_("snap-magnetic"));
62         ract->signal_toggled ().connect (sigc::mem_fun (*this, &Maschine2::notify_snap_change));
63
64         ract = ActionManager::find_radio_action (X_("Editor"), X_("snap-normal"));
65         ract->signal_toggled ().connect (sigc::mem_fun (*this, &Maschine2::notify_snap_change));
66
67         /* Surface events */
68         _ctrl->button (M2Contols::Play)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_play, this));
69         _ctrl->button (M2Contols::Rec)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_record, this));
70         _ctrl->button (M2Contols::Loop)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_loop, this));
71         _ctrl->button (M2Contols::Metronom)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_metronom, this));
72         _ctrl->button (M2Contols::GotoStart)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_rewind, this));
73         _ctrl->button (M2Contols::FastRewind)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Transport", "RewindSlow"));
74         _ctrl->button (M2Contols::FastForward)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Transport", "ForwardSlow"));
75         _ctrl->button (M2Contols::Panic)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "MIDI", "panic"));
76         _ctrl->button (M2Contols::JumpForward)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Editor", "jump-forward-to-mark"));
77         _ctrl->button (M2Contols::JumpBackward)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Editor", "jump-backward-to-mark"));
78
79         _ctrl->button (M2Contols::Grid)->pressed.connect (button_connections, invalidator (*this), boost::bind (&Maschine2::button_snap_pressed, this), gui_context());
80         _ctrl->button (M2Contols::Grid)->released.connect (button_connections, invalidator (*this), boost::bind (&Maschine2::button_snap_released, this), gui_context());
81         _ctrl->button (M2Contols::Grid)->changed.connect (button_connections, invalidator (*this), boost::bind (&Maschine2::button_snap_changed, this, _1), gui_context());
82
83         _ctrl->button (M2Contols::Save)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Common", "Save"));
84         _ctrl->button (M2Contols::Undo)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Editor", "undo"));
85         _ctrl->button (M2Contols::Redo)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Editor", "redo"));
86
87         _ctrl->button (M2Contols::MasterVolume)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::handle_master_change, this, MST_VOLUME));
88         _ctrl->button (M2Contols::MasterTempo)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::handle_master_change, this, MST_TEMPO));
89
90         _ctrl->button (M2Contols::EncoderWheel)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_encoder, this));
91         _ctrl->encoder (0)->changed.connect_same_thread (button_connections, boost::bind (&Maschine2::encoder_master, this, _1));
92
93         for (unsigned int pad = 0; pad < 16; ++pad) {
94                 _ctrl->pad (pad)->event.connect_same_thread (button_connections, boost::bind (&Maschine2::pad_event, this, pad, _1, _2));
95                 _ctrl->pad (pad)->changed.connect_same_thread (button_connections, boost::bind (&Maschine2::pad_change, this, pad, _1));
96         }
97
98         /* set initial values */
99         notify_record_state_changed ();
100         notify_transport_state_changed ();
101         notify_loop_state_changed ();
102         notify_parameter_changed ("clicking");
103         notify_snap_change ();
104         notify_session_dirty_changed ();
105         notify_history_changed ();
106 }
107
108 void
109 Maschine2::notify_record_state_changed ()
110 {
111         switch (session->record_status ()) {
112                 case Session::Disabled:
113                         _ctrl->button (M2Contols::Rec)->set_color (0);
114                         _ctrl->button (M2Contols::Rec)->set_blinking (false);
115                         break;
116                 case Session::Enabled:
117                         _ctrl->button (M2Contols::Rec)->set_color (COLOR_WHITE);
118                         _ctrl->button (M2Contols::Rec)->set_blinking (true);
119                         break;
120                 case Session::Recording:
121                         _ctrl->button (M2Contols::Rec)->set_color (COLOR_WHITE);
122                         _ctrl->button (M2Contols::Rec)->set_blinking (false);
123                         break;
124         }
125 }
126
127 void
128 Maschine2::notify_transport_state_changed ()
129 {
130         if (session->transport_rolling ()) {
131                 _ctrl->button (M2Contols::Play)->set_color (COLOR_WHITE);
132         } else {
133                 _ctrl->button (M2Contols::Play)->set_color (0);
134         }
135         notify_loop_state_changed ();
136 }
137
138 void
139 Maschine2::notify_loop_state_changed ()
140 {
141         bool looping = false;
142         Location* looploc = session->locations ()->auto_loop_location ();
143         if (looploc && session->get_play_loop ()) {
144                 looping = true;
145         }
146         _ctrl->button (M2Contols::Loop)->set_color (looping ? COLOR_GRAY : 0);
147 }
148
149 void
150 Maschine2::notify_parameter_changed (std::string param)
151 {
152         if (param == "clicking") {
153                 _ctrl->button (M2Contols::Metronom)->set_color (Config->get_clicking () ? COLOR_GRAY : 0);
154         }
155 }
156
157 #if 0
158 void
159 Maschine2::notify_grid_change ()
160 {
161         Glib::RefPtr<Gtk::ToggleAction> tact = ActionManager::find_toggle_action (X_("Editor"), X_("ToggleMeasureVisibility"));
162         _ctrl->button (M2Contols::Grid)->set_color (tact->get_active () ? COLOR_WHITE : 0);
163 }
164 #endif
165
166 void
167 Maschine2::notify_snap_change ()
168 {
169         uint32_t rgba = 0;
170         if (_ctrl->button (M2Contols::Grid)->is_pressed ()) {
171                 return;
172         }
173
174         Glib::RefPtr<Gtk::RadioAction> ract = ActionManager::find_radio_action (X_("Editor"), X_("snap-magnetic"));
175         if (ract->get_active ()) { rgba = COLOR_GRAY; }
176         ract = ActionManager::find_radio_action (X_("Editor"), X_("snap-normal"));
177         if (ract->get_active ()) { rgba = COLOR_WHITE; }
178
179         _ctrl->button (M2Contols::Grid)->set_color (rgba);
180 }
181
182 void
183 Maschine2::notify_session_dirty_changed ()
184 {
185         bool is_dirty = session->dirty ();
186         _ctrl->button (M2Contols::Save)->set_color (is_dirty ? COLOR_WHITE : COLOR_BLACK);
187         _ctrl->button (M2Contols::Save)->set_blinking (is_dirty);
188 }
189
190 void
191 Maschine2::notify_history_changed ()
192 {
193         _ctrl->button (M2Contols::Redo)->set_color (session->redo_depth() > 0 ? COLOR_WHITE : COLOR_BLACK);
194         _ctrl->button (M2Contols::Undo)->set_color (session->undo_depth() > 0 ? COLOR_WHITE : COLOR_BLACK);
195 }
196
197
198 void
199 Maschine2::button_play ()
200 {
201         if (session->transport_rolling ()) {
202                 transport_stop ();
203         } else {
204                 transport_play ();
205         }
206 }
207
208 void
209 Maschine2::button_record ()
210 {
211         set_record_enable (!get_record_enabled ());
212 }
213
214 void
215 Maschine2::button_loop ()
216 {
217         loop_toggle ();
218 }
219
220 void
221 Maschine2::button_metronom ()
222 {
223         Config->set_clicking (!Config->get_clicking ());
224 }
225
226 void
227 Maschine2::button_rewind ()
228 {
229         goto_start (session->transport_rolling ());
230 }
231
232 void
233 Maschine2::button_action (const std::string& group, const std::string& item)
234 {
235         AccessAction (group, item);
236 }
237
238 #if 0
239 void
240 Maschine2::button_grid ()
241 {
242         Glib::RefPtr<Gtk::ToggleAction> tact = ActionManager::find_toggle_action (X_("Editor"), X_("ToggleMeasureVisibility"));
243         tact->set_active (!tact->get_active ());
244 }
245 #endif
246
247 void
248 Maschine2::button_snap_pressed ()
249 {
250         _ctrl->button (M2Contols::Grid)->set_color (COLOR_WHITE);
251         _ctrl->button (M2Contols::Grid)->set_blinking (true);
252 }
253
254 void
255 Maschine2::button_snap_changed (bool pressed)
256 {
257         if (!pressed) {
258                 _ctrl->button (M2Contols::Grid)->set_blinking (false);
259                 notify_snap_change ();
260         }
261         notify_master_change ();
262 }
263
264 void
265 Maschine2::button_snap_released ()
266 {
267         _ctrl->button (M2Contols::Grid)->set_blinking (false);
268
269         const char* action = 0;
270
271         Glib::RefPtr<Gtk::RadioAction> ract = ActionManager::find_radio_action (X_("Editor"), X_("snap-off"));
272         if (ract->get_active ()) { action = "snap-normal"; }
273
274         ract = ActionManager::find_radio_action (X_("Editor"), X_("snap-normal"));
275         if (ract->get_active ()) { action = "snap-magnetic"; }
276
277         ract = ActionManager::find_radio_action (X_("Editor"), X_("snap-magnetic"));
278         if (ract->get_active ()) { action = "snap-off"; }
279
280         ract = ActionManager::find_radio_action (X_("Editor"), action);
281         ract->set_active (true);
282 }
283
284 /* Master mode + state -- main encoder fn */
285
286 void
287 Maschine2::handle_master_change (enum MasterMode id)
288 {
289         switch (id) {
290                 case MST_VOLUME:
291                         if (_master_state == MST_VOLUME) { _master_state = MST_NONE; } else { _master_state = MST_VOLUME; }
292                         break;
293                 case MST_TEMPO:
294                         if (_master_state == MST_TEMPO) { _master_state = MST_NONE; } else { _master_state = MST_TEMPO; }
295                         break;
296                 default:
297                         return;
298                         break;
299         }
300         notify_master_change ();
301 }
302
303 void
304 Maschine2::notify_master_change ()
305 {
306         if (_ctrl->button (M2Contols::Grid)->is_pressed ()) {
307                 _ctrl->button (M2Contols::MasterVolume)->set_color (COLOR_BLACK);
308                 _ctrl->button (M2Contols::MasterTempo)->set_color (COLOR_BLACK);
309                 return;
310         }
311         switch (_master_state) {
312                 case MST_NONE:
313                         _ctrl->button (M2Contols::MasterVolume)->set_color (COLOR_BLACK);
314                         _ctrl->button (M2Contols::MasterTempo)->set_color (COLOR_BLACK);
315                         break;
316                 case MST_VOLUME:
317                         _ctrl->button (M2Contols::MasterVolume)->set_color (COLOR_WHITE);
318                         _ctrl->button (M2Contols::MasterTempo)->set_color (COLOR_BLACK);
319                         break;
320                 case MST_TEMPO:
321                         _ctrl->button (M2Contols::MasterVolume)->set_color (COLOR_BLACK);
322                         _ctrl->button (M2Contols::MasterTempo)->set_color (COLOR_WHITE);
323                         break;
324         }
325 }
326
327 static void apply_ac_delta (boost::shared_ptr<AutomationControl> ac, double d) {
328         if (!ac) {
329                 return;
330         }
331         ac->set_value (ac->interface_to_internal (min (ac->upper(), max (ac->lower(), ac->internal_to_interface (ac->get_value()) + d))),
332                         PBD::Controllable::UseGroup);
333 }
334
335 void
336 Maschine2::encoder_master (int delta)
337 {
338         if (_ctrl->button (M2Contols::Grid)->is_pressed ()) {
339                 _ctrl->button (M2Contols::Grid)->ignore_release ();
340                 if (delta > 0) {
341                         AccessAction ("Editor", "next-snap-choice");
342                 } else {
343                         AccessAction ("Editor", "prev-snap-choice");
344                 }
345                 return;
346         }
347         switch (_master_state) {
348                 case MST_NONE:
349                         if (_ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->active ()) {
350                                 if (delta > 0) {
351                                         AccessAction ("Editor", "temporal-zoom-in");
352                                 } else {
353                                         AccessAction ("Editor", "temporal-zoom-out");
354                                 }
355                         } else {
356                                 if (delta > 0) {
357                                         AccessAction ("Editor", "playhead-forward-to-grid");
358                                 } else {
359                                         AccessAction ("Editor", "playhead-backward-to-grid");
360                                 }
361                         }
362                         break;
363                 case MST_VOLUME:
364                         {
365                                 boost::shared_ptr<Route> master = session->master_out ();
366                                 if (master) {
367                                         // TODO consider _ctrl->button (M2Contols::EncoderWheel)->is_pressed() for fine grained
368                                         const double factor = _ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->active () ? 256. : 32.;
369                                         apply_ac_delta (master->gain_control(), delta / factor);
370                                 }
371                         }
372                         break;
373                 case MST_TEMPO:
374                         // set new tempo..  apply with "enter"
375                         break;
376         }
377 }
378
379 void
380 Maschine2::button_encoder ()
381 {
382         switch (_master_state) {
383                 case MST_NONE:
384                         // OR: add marker ??
385                         if (_ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->active ()) {
386                                 AccessAction ("Editor", "zoom-to-session");
387                         }
388                         break;
389                 case MST_VOLUME:
390                         // ignore -> fine gained?
391                         break;
392                 case MST_TEMPO:
393                         // add new tempo.. ?
394                         break;
395         }
396 }
397
398 void
399 Maschine2::pad_change (unsigned int pad, float v)
400 {
401         float lvl = v; // _ctrl->pad (pad)->value () / 4095.f;
402         Gtkmm2ext::Color c = Gtkmm2ext::hsva_to_color (270 - 270.f * lvl, 1.0, lvl * lvl, 1.0);
403         _ctrl->pad (pad)->set_color (c);
404 }
405
406 void
407 Maschine2::pad_event (unsigned int pad, float v, bool ev)
408 {
409         if (ev) {
410                 uint8_t msg[3];
411                 msg[0] = v > 0 ? 0x90 : 0x80;
412                 msg[1] = 36 + pad; // TODO map note to scale
413                 msg[2] = ((uint8_t)floor (v * 127)) & 0x7f;
414                 _output_port->write (msg, 3, 0);
415         } else {
416                 uint8_t msg[3];
417                 msg[0] = 0xa0;
418                 msg[1] = 36 + pad; // TODO map note to scale
419                 msg[2] = ((uint8_t)floor (v * 127)) & 0x7f;
420                 _output_port->write (msg, 3, 0);
421         }
422         //printf ("[%2d] %s %.1f\n", pad, ev ? "On/Off" : "Aftertouch" , v * 127);
423 }