Skeleton for NI Maschine2 Surface
[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::Action> act;
53 #if 0
54         act = ActionManager::get_action (X_("Editor"), X_("ToggleMeasureVisibility"));
55         if (act) {
56                 Glib::RefPtr<Gtk::ToggleAction> tact = Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic (act);
57                 tact->signal_toggled ().connect (sigc::mem_fun (*this, &Maschine2::notify_grid_change));
58         }
59 #endif
60         act = ActionManager::get_action (X_("Editor"), X_("snap-off"));
61         if (act) {
62                 Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
63                 ract->signal_toggled ().connect (sigc::mem_fun (*this, &Maschine2::notify_snap_change));
64         }
65         act = ActionManager::get_action (X_("Editor"), X_("snap-magnetic"));
66         if (act) {
67                 Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
68                 ract->signal_toggled ().connect (sigc::mem_fun (*this, &Maschine2::notify_snap_change));
69         }
70         act = ActionManager::get_action (X_("Editor"), X_("snap-normal"));
71         if (act) {
72                 Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
73                 ract->signal_toggled ().connect (sigc::mem_fun (*this, &Maschine2::notify_snap_change));
74         }
75
76         /* Surface events */
77         _ctrl->button (M2Contols::Play)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_play, this));
78         _ctrl->button (M2Contols::Rec)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_record, this));
79         _ctrl->button (M2Contols::Loop)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_loop, this));
80         _ctrl->button (M2Contols::Metronom)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_metronom, this));
81         _ctrl->button (M2Contols::GotoStart)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_rewind, this));
82         _ctrl->button (M2Contols::FastRewind)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Transport", "RewindSlow"));
83         _ctrl->button (M2Contols::FastForward)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Transport", "ForwardSlow"));
84         _ctrl->button (M2Contols::Panic)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "MIDI", "panic"));
85         _ctrl->button (M2Contols::JumpForward)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Editor", "jump-forward-to-mark"));
86         _ctrl->button (M2Contols::JumpBackward)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Editor", "jump-backward-to-mark"));
87
88         _ctrl->button (M2Contols::Grid)->pressed.connect (button_connections, invalidator (*this), boost::bind (&Maschine2::button_snap_pressed, this), gui_context());
89         _ctrl->button (M2Contols::Grid)->released.connect (button_connections, invalidator (*this), boost::bind (&Maschine2::button_snap_released, this), gui_context());
90         _ctrl->button (M2Contols::Grid)->changed.connect (button_connections, invalidator (*this), boost::bind (&Maschine2::button_snap_changed, this, _1), gui_context());
91
92         _ctrl->button (M2Contols::Save)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Common", "Save"));
93         _ctrl->button (M2Contols::Undo)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Editor", "undo"));
94         _ctrl->button (M2Contols::Redo)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_action, this, "Editor", "redo"));
95
96         _ctrl->button (M2Contols::MasterVolume)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::handle_master_change, this, MST_VOLUME));
97         _ctrl->button (M2Contols::MasterTempo)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::handle_master_change, this, MST_TEMPO));
98
99         _ctrl->button (M2Contols::EncoderWheel)->released.connect_same_thread (button_connections, boost::bind (&Maschine2::button_encoder, this));
100         _ctrl->encoder (0)->changed.connect_same_thread (button_connections, boost::bind (&Maschine2::encoder_master, this, _1));
101
102         for (unsigned int pad = 0; pad < 16; ++pad) {
103                 _ctrl->pad (pad)->event.connect_same_thread (button_connections, boost::bind (&Maschine2::pad_event, this, pad, _1, _2));
104                 _ctrl->pad (pad)->changed.connect_same_thread (button_connections, boost::bind (&Maschine2::pad_change, this, pad, _1));
105         }
106
107         /* set initial values */
108         notify_record_state_changed ();
109         notify_transport_state_changed ();
110         notify_loop_state_changed ();
111         notify_parameter_changed ("clicking");
112         notify_snap_change ();
113         notify_session_dirty_changed ();
114         notify_history_changed ();
115 }
116
117 void
118 Maschine2::notify_record_state_changed ()
119 {
120         switch (session->record_status ()) {
121                 case Session::Disabled:
122                         _ctrl->button (M2Contols::Rec)->set_color (0);
123                         _ctrl->button (M2Contols::Rec)->set_blinking (false);
124                         break;
125                 case Session::Enabled:
126                         _ctrl->button (M2Contols::Rec)->set_color (COLOR_WHITE);
127                         _ctrl->button (M2Contols::Rec)->set_blinking (true);
128                         break;
129                 case Session::Recording:
130                         _ctrl->button (M2Contols::Rec)->set_color (COLOR_WHITE);
131                         _ctrl->button (M2Contols::Rec)->set_blinking (false);
132                         break;
133         }
134 }
135
136 void
137 Maschine2::notify_transport_state_changed ()
138 {
139         if (session->transport_rolling ()) {
140                 _ctrl->button (M2Contols::Play)->set_color (COLOR_WHITE);
141         } else {
142                 _ctrl->button (M2Contols::Play)->set_color (0);
143         }
144         notify_loop_state_changed ();
145 }
146
147 void
148 Maschine2::notify_loop_state_changed ()
149 {
150         bool looping = false;
151         Location* looploc = session->locations ()->auto_loop_location ();
152         if (looploc && session->get_play_loop ()) {
153                 looping = true;
154         }
155         _ctrl->button (M2Contols::Loop)->set_color (looping ? COLOR_GRAY : 0);
156 }
157
158 void
159 Maschine2::notify_parameter_changed (std::string param)
160 {
161         if (param == "clicking") {
162                 _ctrl->button (M2Contols::Metronom)->set_color (Config->get_clicking () ? COLOR_GRAY : 0);
163         }
164 }
165
166 #if 0
167 void
168 Maschine2::notify_grid_change ()
169 {
170         Glib::RefPtr<Gtk::Action> act = ActionManager::get_action (X_("Editor"), X_("ToggleMeasureVisibility"));
171         if (act) {
172                 Glib::RefPtr<Gtk::ToggleAction> tact = Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic (act);
173                 _ctrl->button (M2Contols::Grid)->set_color (tact->get_active () ? COLOR_WHITE : 0);
174         }
175 }
176 #endif
177
178 void
179 Maschine2::notify_snap_change ()
180 {
181         uint32_t rgba = 0;
182         if (_ctrl->button (M2Contols::Grid)->is_pressed ()) {
183                 return;
184         }
185
186         Glib::RefPtr<Gtk::Action> act = ActionManager::get_action (X_("Editor"), X_("snap-magnetic"));
187         if (act) {
188                 Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
189                 if (ract->get_active ()) { rgba = COLOR_GRAY; }
190         }
191         act = ActionManager::get_action (X_("Editor"), X_("snap-normal"));
192         if (act) {
193                 Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
194                 if (ract->get_active ()) { rgba = COLOR_WHITE; }
195         }
196
197         _ctrl->button (M2Contols::Grid)->set_color (rgba);
198 }
199
200 void
201 Maschine2::notify_session_dirty_changed ()
202 {
203         bool is_dirty = session->dirty ();
204         _ctrl->button (M2Contols::Save)->set_color (is_dirty ? COLOR_WHITE : COLOR_BLACK);
205         _ctrl->button (M2Contols::Save)->set_blinking (is_dirty);
206 }
207
208 void
209 Maschine2::notify_history_changed ()
210 {
211         _ctrl->button (M2Contols::Redo)->set_color (session->redo_depth() > 0 ? COLOR_WHITE : COLOR_BLACK);
212         _ctrl->button (M2Contols::Undo)->set_color (session->undo_depth() > 0 ? COLOR_WHITE : COLOR_BLACK);
213 }
214
215
216 void
217 Maschine2::button_play ()
218 {
219         if (session->transport_rolling ()) {
220                 transport_stop ();
221         } else {
222                 transport_play ();
223         }
224 }
225
226 void
227 Maschine2::button_record ()
228 {
229         set_record_enable (!get_record_enabled ());
230 }
231
232 void
233 Maschine2::button_loop ()
234 {
235         loop_toggle ();
236 }
237
238 void
239 Maschine2::button_metronom ()
240 {
241         Config->set_clicking (!Config->get_clicking ());
242 }
243
244 void
245 Maschine2::button_rewind ()
246 {
247         goto_start (session->transport_rolling ());
248 }
249
250 void
251 Maschine2::button_action (const std::string& group, const std::string& item)
252 {
253         AccessAction (group, item);
254 }
255
256 #if 0
257 void
258 Maschine2::button_grid ()
259 {
260         Glib::RefPtr<Gtk::Action> act = ActionManager::get_action (X_("Editor"), X_("ToggleMeasureVisibility"));
261         if (act) {
262                 Glib::RefPtr<Gtk::ToggleAction> tact = Glib::RefPtr<Gtk::ToggleAction>::cast_dynamic (act);
263                 tact->set_active (!tact->get_active ());
264         }
265 }
266 #endif
267
268 void
269 Maschine2::button_snap_pressed ()
270 {
271         _ctrl->button (M2Contols::Grid)->set_color (COLOR_WHITE);
272         _ctrl->button (M2Contols::Grid)->set_blinking (true);
273 }
274
275 void
276 Maschine2::button_snap_changed (bool pressed)
277 {
278         if (!pressed) {
279                 _ctrl->button (M2Contols::Grid)->set_blinking (false);
280                 notify_snap_change ();
281         }
282         notify_master_change ();
283 }
284
285 void
286 Maschine2::button_snap_released ()
287 {
288         _ctrl->button (M2Contols::Grid)->set_blinking (false);
289
290         const char* action = 0;
291         Glib::RefPtr<Gtk::Action> act = ActionManager::get_action (X_("Editor"), X_("snap-off"));
292         if (act) {
293                 Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
294                 if (ract->get_active ()) { action = "snap-normal"; }
295         }
296
297         act = ActionManager::get_action (X_("Editor"), X_("snap-normal"));
298         if (act) {
299                 Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
300                 if (ract->get_active ()) { action = "snap-magnetic"; }
301         }
302
303         act = ActionManager::get_action (X_("Editor"), X_("snap-magnetic"));
304         if (act) {
305                 Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
306                 if (ract->get_active ()) { action = "snap-off"; }
307         }
308
309         if (!action) {
310                 assert (0);
311                 return;
312         }
313
314         act = ActionManager::get_action (X_("Editor"), action);
315         if (act) {
316                 Glib::RefPtr<Gtk::RadioAction> ract = Glib::RefPtr<Gtk::RadioAction>::cast_dynamic (act);
317                 ract->set_active (true);
318         }
319 }
320
321 /* Master mode + state -- main encoder fn */
322
323 void
324 Maschine2::handle_master_change (enum MasterMode id)
325 {
326         switch (id) {
327                 case MST_VOLUME:
328                         if (_master_state == MST_VOLUME) { _master_state = MST_NONE; } else { _master_state = MST_VOLUME; }
329                         break;
330                 case MST_TEMPO:
331                         if (_master_state == MST_TEMPO) { _master_state = MST_NONE; } else { _master_state = MST_TEMPO; }
332                         break;
333                 default:
334                         return;
335                         break;
336         }
337         notify_master_change ();
338 }
339
340 void
341 Maschine2::notify_master_change ()
342 {
343         if (_ctrl->button (M2Contols::Grid)->is_pressed ()) {
344                 _ctrl->button (M2Contols::MasterVolume)->set_color (COLOR_BLACK);
345                 _ctrl->button (M2Contols::MasterTempo)->set_color (COLOR_BLACK);
346                 return;
347         }
348         switch (_master_state) {
349                 case MST_NONE:
350                         _ctrl->button (M2Contols::MasterVolume)->set_color (COLOR_BLACK);
351                         _ctrl->button (M2Contols::MasterTempo)->set_color (COLOR_BLACK);
352                         break;
353                 case MST_VOLUME:
354                         _ctrl->button (M2Contols::MasterVolume)->set_color (COLOR_WHITE);
355                         _ctrl->button (M2Contols::MasterTempo)->set_color (COLOR_BLACK);
356                         break;
357                 case MST_TEMPO:
358                         _ctrl->button (M2Contols::MasterVolume)->set_color (COLOR_BLACK);
359                         _ctrl->button (M2Contols::MasterTempo)->set_color (COLOR_WHITE);
360                         break;
361         }
362 }
363
364 static void apply_ac_delta (boost::shared_ptr<AutomationControl> ac, double d) {
365         if (!ac) {
366                 return;
367         }
368         ac->set_value (ac->interface_to_internal (min (ac->upper(), max (ac->lower(), ac->internal_to_interface (ac->get_value()) + d))),
369                         PBD::Controllable::UseGroup);
370 }
371
372 void
373 Maschine2::encoder_master (int delta)
374 {
375         if (_ctrl->button (M2Contols::Grid)->is_pressed ()) {
376                 _ctrl->button (M2Contols::Grid)->ignore_release ();
377                 if (delta > 0) {
378                         AccessAction ("Editor", "next-snap-choice");
379                 } else {
380                         AccessAction ("Editor", "prev-snap-choice");
381                 }
382                 return;
383         }
384         switch (_master_state) {
385                 case MST_NONE:
386                         if (_ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->active ()) {
387                                 if (delta > 0) {
388                                         AccessAction ("Editor", "temporal-zoom-in");
389                                 } else {
390                                         AccessAction ("Editor", "temporal-zoom-out");
391                                 }
392                         } else {
393                                 if (delta > 0) {
394                                         AccessAction ("Editor", "playhead-forward-to-grid");
395                                 } else {
396                                         AccessAction ("Editor", "playhead-backward-to-grid");
397                                 }
398                         }
399                         break;
400                 case MST_VOLUME:
401                         {
402                                 boost::shared_ptr<Route> master = session->master_out ();
403                                 if (master) {
404                                         // TODO consider _ctrl->button (M2Contols::EncoderWheel)->is_pressed() for fine grained
405                                         const double factor = _ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->active () ? 256. : 32.;
406                                         apply_ac_delta (master->gain_control(), delta / factor);
407                                 }
408                         }
409                         break;
410                 case MST_TEMPO:
411                         // set new tempo..  apply with "enter"
412                         break;
413         }
414 }
415
416 void
417 Maschine2::button_encoder ()
418 {
419         switch (_master_state) {
420                 case MST_NONE:
421                         // OR: add marker ??
422                         if (_ctrl->button (M2Contols::BtnShift, M2Contols::ModNone)->active ()) {
423                                 AccessAction ("Editor", "zoom-to-session");
424                         }
425                         break;
426                 case MST_VOLUME:
427                         // ignore -> fine gained?
428                         break;
429                 case MST_TEMPO:
430                         // add new tempo.. ?
431                         break;
432         }
433 }
434
435 void
436 Maschine2::pad_change (unsigned int pad, float v)
437 {
438         float lvl = v; // _ctrl->pad (pad)->value () / 4095.f;
439         Gtkmm2ext::Color c = Gtkmm2ext::hsva_to_color (270 - 270.f * lvl, 1.0, lvl * lvl, 1.0);
440         _ctrl->pad (pad)->set_color (c);
441 }
442
443 void
444 Maschine2::pad_event (unsigned int pad, float v, bool ev)
445 {
446         if (ev) {
447                 uint8_t msg[3];
448                 msg[0] = v > 0 ? 0x90 : 0x80;
449                 msg[1] = 36 + pad; // TODO map note to scale
450                 msg[2] = ((uint8_t)floor (v * 127)) & 0x7f;
451                 _output_port->write (msg, 3, 0);
452         } else {
453                 uint8_t msg[3];
454                 msg[0] = 0xa0;
455                 msg[1] = 36 + pad; // TODO map note to scale
456                 msg[2] = ((uint8_t)floor (v * 127)) & 0x7f;
457                 _output_port->write (msg, 3, 0);
458         }
459         //printf ("[%2d] %s %.1f\n", pad, ev ? "On/Off" : "Aftertouch" , v * 127);
460 }