push2: steps toward a more ableton-like layout in mix mode
[ardour.git] / libs / surfaces / push2 / mix.cc
1 /*
2   Copyright (C) 2016 Paul Davis
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 of the License, or
7   (at your option) 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
16   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 */
18
19 #include <pangomm/layout.h>
20
21 #include "pbd/compose.h"
22 #include "pbd/convert.h"
23 #include "pbd/debug.h"
24 #include "pbd/failed_constructor.h"
25 #include "pbd/file_utils.h"
26 #include "pbd/search_path.h"
27 #include "pbd/enumwriter.h"
28
29 #include "midi++/parser.h"
30 #include "timecode/time.h"
31 #include "timecode/bbt_time.h"
32
33 #include "ardour/async_midi_port.h"
34 #include "ardour/audioengine.h"
35 #include "ardour/debug.h"
36 #include "ardour/filesystem_paths.h"
37 #include "ardour/midiport_manager.h"
38 #include "ardour/midi_track.h"
39 #include "ardour/midi_port.h"
40 #include "ardour/session.h"
41 #include "ardour/tempo.h"
42
43 #include "canvas/colors.h"
44
45 #include "mix.h"
46 #include "knob.h"
47 #include "push2.h"
48 #include "utils.h"
49
50 #include "i18n.h"
51
52 using namespace ARDOUR;
53 using namespace std;
54 using namespace PBD;
55 using namespace Glib;
56 using namespace ArdourSurface;
57
58 MixLayout::MixLayout (Push2& p, Session& s, Cairo::RefPtr<Cairo::Context> context)
59         : Push2Layout (p, s)
60         , bank_start (0)
61 {
62         tc_clock_layout = Pango::Layout::create (context);
63         bbt_clock_layout = Pango::Layout::create (context);
64
65         Pango::FontDescription fd ("Sans Bold 24");
66         tc_clock_layout->set_font_description (fd);
67         bbt_clock_layout->set_font_description (fd);
68
69         Pango::FontDescription fd2 ("Sans 10");
70         for (int n = 0; n < 8; ++n) {
71                 upper_layout[n] = Pango::Layout::create (context);
72                 upper_layout[n]->set_font_description (fd2);
73
74                 string txt;
75                 switch (n) {
76                 case 0:
77                         txt = _("Volumes");
78                         break;
79                 case 1:
80                         txt = _("Pans");
81                         break;
82                 case 2:
83                         txt = _("Pan Widths");
84                         break;
85                 case 3:
86                         txt = _("A Sends");
87                         break;
88                 case 4:
89                         txt = _("B Sends");
90                         break;
91                 case 5:
92                         txt = _("C Sends");
93                         break;
94                 case 6:
95                         txt = _("D Sends");
96                         break;
97                 case 7:
98                         txt = _("E Sends");
99                         break;
100                 }
101                 upper_layout[n]->set_text (txt);
102         }
103
104         Pango::FontDescription fd3 ("Sans 10");
105         for (int n = 0; n < 8; ++n) {
106                 lower_layout[n] = Pango::Layout::create (context);
107                 lower_layout[n]->set_font_description (fd3);
108         }
109
110         for (int n = 0; n < 8; ++n) {
111                 knobs[n] = new Push2Knob (p2);
112                 knobs[n]->set_position (60 + (n*120), 95);
113                 knobs[n]->set_radius (25);
114         }
115
116         switch_bank (0);
117 }
118
119 MixLayout::~MixLayout ()
120 {
121 }
122
123 bool
124 MixLayout::redraw (Cairo::RefPtr<Cairo::Context> context) const
125 {
126         framepos_t audible = session.audible_frame();
127         Timecode::Time TC;
128         bool negative = false;
129         string tc_clock_text;
130         string bbt_clock_text;
131
132         if (audible < 0) {
133                 audible = -audible;
134                 negative = true;
135         }
136
137         session.timecode_time (audible, TC);
138
139         TC.negative = TC.negative || negative;
140
141         tc_clock_text = Timecode::timecode_format_time(TC);
142
143         Timecode::BBT_Time bbt = session.tempo_map().bbt_at_frame (audible);
144         char buf[16];
145
146 #define BBT_BAR_CHAR "|"
147
148         if (negative) {
149                 snprintf (buf, sizeof (buf), "-%03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32,
150                           bbt.bars, bbt.beats, bbt.ticks);
151         } else {
152                 snprintf (buf, sizeof (buf), " %03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32,
153                           bbt.bars, bbt.beats, bbt.ticks);
154         }
155
156         bbt_clock_text = buf;
157
158         bool children_dirty = false;
159
160         if (tc_clock_text != tc_clock_layout->get_text()) {
161                 children_dirty = true;
162                 tc_clock_layout->set_text (tc_clock_text);
163         }
164
165         if (bbt_clock_text != tc_clock_layout->get_text()) {
166                 children_dirty = true;
167                 bbt_clock_layout->set_text (bbt_clock_text);
168         }
169
170         for (int n = 0; n < 8; ++n) {
171                 if (knobs[n]->dirty()) {
172                         children_dirty = true;
173                         break;
174                 }
175         }
176
177         for (int n = 0; n < 8; ++n) {
178                 if (stripable[n]) {
179                         string name = short_version (stripable[n]->name(), 10);
180                         if (name != lower_layout[n]->get_text()) {
181                                 lower_layout[n]->set_text (name);
182                                 children_dirty = true;
183                         }
184                 }
185         }
186
187         if (!children_dirty) {
188                 return false;
189         }
190
191         set_source_rgb (context, p2.get_color (Push2::DarkBackground));
192         context->rectangle (0, 0, p2.cols, p2.rows);
193         context->fill ();
194
195         /* clocks */
196
197         set_source_rgb (context, ArdourCanvas::contrasting_text_color (p2.get_color (Push2::DarkBackground)));
198         context->move_to (650, 30);
199         tc_clock_layout->update_from_cairo_context (context);
200         tc_clock_layout->show_in_cairo_context (context);
201         context->move_to (650, 65);
202         bbt_clock_layout->update_from_cairo_context (context);
203         bbt_clock_layout->show_in_cairo_context (context);
204
205         set_source_rgb (context, p2.get_color (Push2::ParameterName));
206
207         for (int n = 0; n < 8; ++n) {
208                 if (upper_layout[n]->get_text().empty()) {
209                         continue;
210                 }
211                 context->move_to (10 + (n*120), 2);
212                 upper_layout[n]->update_from_cairo_context (context);
213                 upper_layout[n]->show_in_cairo_context (context);
214         }
215
216         context->move_to (0, 22.5);
217         context->line_to (p2.cols, 22.5);
218         context->set_line_width (1.0);
219         context->stroke ();
220
221         for (int n = 0; n < 8; ++n) {
222                 knobs[n]->redraw (context);
223         }
224
225         for (int n = 0; n < 8; ++n) {
226
227                 if (lower_layout[n]->get_text().empty()) {
228                         continue;
229                 }
230
231                 if (stripable[n]) {
232                         if (stripable[n]->presentation_info().selected()) {
233                                 set_source_rgb (context, stripable[n]->presentation_info().color());
234                                 context->rectangle (10 + (n*120) - 5, 137, 120, 22);
235                                 context->fill();
236                                 set_source_rgb (context, ArdourCanvas::contrasting_text_color (stripable[n]->presentation_info().color()));
237                         }  else {
238                                 set_source_rgb (context, stripable[n]->presentation_info().color());
239                         }
240                 }
241                 /* XXX what color is used here? text should be empty anyway... no stripable */
242                 context->move_to (10 + (n*120), 140);
243                 lower_layout[n]->update_from_cairo_context (context);
244                 lower_layout[n]->show_in_cairo_context (context);
245         }
246
247         return true;
248 }
249
250 void
251 MixLayout::button_upper (uint32_t n)
252 {
253         if (!stripable[n]) {
254                 return;
255         }
256
257         if (p2.modifier_state() & Push2::ModShift) {
258                 boost::shared_ptr<AutomationControl> sc = stripable[n]->rec_enable_control ();
259                 if (sc) {
260                         sc->set_value (!sc->get_value(), PBD::Controllable::UseGroup);
261                 }
262         } else {
263                 boost::shared_ptr<SoloControl> sc = stripable[n]->solo_control ();
264                 if (sc) {
265                         sc->set_value (!sc->self_soloed(), PBD::Controllable::UseGroup);
266                 }
267         }
268 }
269
270 void
271 MixLayout::button_lower (uint32_t n)
272 {
273         if (!stripable[n]) {
274                 return;
275         }
276
277         ControlProtocol::SetStripableSelection (stripable[n]);
278 }
279
280 void
281 MixLayout::strip_vpot (int n, int delta)
282 {
283         if (stripable[n]) {
284                 boost::shared_ptr<AutomationControl> ac = stripable[n]->gain_control();
285                 if (ac) {
286                         ac->set_value (ac->get_value() + ((2.0/64.0) * delta), PBD::Controllable::UseGroup);
287                 }
288         }
289 }
290
291 void
292 MixLayout::strip_vpot_touch (int n, bool touching)
293 {
294         if (stripable[n]) {
295                 boost::shared_ptr<AutomationControl> ac = stripable[n]->gain_control();
296                 if (ac) {
297                         if (touching) {
298                                 ac->start_touch (session.audible_frame());
299                         } else {
300                                 ac->stop_touch (true, session.audible_frame());
301                         }
302                 }
303         }
304 }
305
306 void
307 MixLayout::stripable_property_change (PropertyChange const& what_changed, int which)
308 {
309         if (what_changed.contains (Properties::selected)) {
310                 if (!stripable[which]) {
311                         return;
312                 }
313
314                 /* cancel string, which will cause a redraw on the next update
315                  * cycle. The redraw will reflect selected status
316                  */
317
318                 lower_layout[which]->set_text (string());
319         }
320 }
321
322 void
323 MixLayout::solo_change (int n)
324 {
325         Push2::ButtonID bid;
326
327         switch (n) {
328         case 0:
329                 bid = Push2::Upper1;
330                 break;
331         case 1:
332                 bid = Push2::Upper2;
333                 break;
334         case 2:
335                 bid = Push2::Upper3;
336                 break;
337         case 3:
338                 bid = Push2::Upper4;
339                 break;
340         case 4:
341                 bid = Push2::Upper5;
342                 break;
343         case 5:
344                 bid = Push2::Upper6;
345                 break;
346         case 6:
347                 bid = Push2::Upper7;
348                 break;
349         case 7:
350                 bid = Push2::Upper8;
351                 break;
352         default:
353                 return;
354         }
355
356         boost::shared_ptr<SoloControl> ac = stripable[n]->solo_control ();
357         if (!ac) {
358                 return;
359         }
360
361         Push2::Button* b = p2.button_by_id (bid);
362
363         if (ac->soloed()) {
364                 b->set_color (Push2::LED::Green);
365         } else {
366                 b->set_color (Push2::LED::Black);
367         }
368
369         if (ac->soloed_by_others_upstream() || ac->soloed_by_others_downstream()) {
370                 b->set_state (Push2::LED::Blinking4th);
371         } else {
372                 b->set_state (Push2::LED::OneShot24th);
373         }
374
375         p2.write (b->state_msg());
376 }
377
378 void
379 MixLayout::mute_change (int n)
380 {
381         Push2::ButtonID bid;
382
383         if (!stripable[n]) {
384                 return;
385         }
386
387         cerr << "Mute changed on " << n << ' ' << stripable[n]->name() << endl;
388
389         switch (n) {
390         case 0:
391                 bid = Push2::Lower1;
392                 break;
393         case 1:
394                 bid = Push2::Lower2;
395                 break;
396         case 2:
397                 bid = Push2::Lower3;
398                 break;
399         case 3:
400                 bid = Push2::Lower4;
401                 break;
402         case 4:
403                 bid = Push2::Lower5;
404                 break;
405         case 5:
406                 bid = Push2::Lower6;
407                 break;
408         case 6:
409                 bid = Push2::Lower7;
410                 break;
411         case 7:
412                 bid = Push2::Lower8;
413                 break;
414         default:
415                 return;
416         }
417
418         boost::shared_ptr<MuteControl> mc = stripable[n]->mute_control ();
419
420         if (!mc) {
421                 return;
422         }
423
424         Push2::Button* b = p2.button_by_id (bid);
425
426         if (Config->get_show_solo_mutes() && !Config->get_solo_control_is_listen_control ()) {
427
428                 if (mc->muted_by_self ()) {
429                         /* full mute */
430                         b->set_color (Push2::LED::Blue);
431                         b->set_state (Push2::LED::OneShot24th);
432                         cerr << "FULL MUTE1\n";
433                 } else if (mc->muted_by_others_soloing () || mc->muted_by_masters ()) {
434                         /* this will reflect both solo mutes AND master mutes */
435                         b->set_color (Push2::LED::Blue);
436                         b->set_state (Push2::LED::Blinking4th);
437                         cerr << "OTHER MUTE1\n";
438                 } else {
439                         /* no mute at all */
440                         b->set_color (Push2::LED::Black);
441                         b->set_state (Push2::LED::OneShot24th);
442                         cerr << "NO MUTE1\n";
443                 }
444
445         } else {
446
447                 if (mc->muted_by_self()) {
448                         /* full mute */
449                         b->set_color (Push2::LED::Blue);
450                         b->set_state (Push2::LED::OneShot24th);
451                         cerr << "FULL MUTE2\n";
452                 } else if (mc->muted_by_masters ()) {
453                         /* this shows only master mutes, not mute-by-others-soloing */
454                         b->set_color (Push2::LED::Blue);
455                         b->set_state (Push2::LED::Blinking4th);
456                         cerr << "OTHER MUTE1\n";
457                 } else {
458                         /* no mute at all */
459                         b->set_color (Push2::LED::Black);
460                         b->set_state (Push2::LED::OneShot24th);
461                         cerr << "NO MUTE2\n";
462                 }
463         }
464
465         p2.write (b->state_msg());
466 }
467
468 void
469 MixLayout::switch_bank (uint32_t base)
470 {
471         stripable_connections.drop_connections ();
472
473         /* try to get the first stripable for the requested bank */
474
475         stripable[0] = session.get_remote_nth_stripable (base, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
476
477         if (!stripable[0]) {
478                 return;
479         }
480
481         /* at least one stripable in this bank */
482         bank_start = base;
483
484         stripable[1] = session.get_remote_nth_stripable (base+1, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
485         stripable[2] = session.get_remote_nth_stripable (base+2, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
486         stripable[3] = session.get_remote_nth_stripable (base+3, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
487         stripable[4] = session.get_remote_nth_stripable (base+4, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
488         stripable[5] = session.get_remote_nth_stripable (base+5, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
489         stripable[6] = session.get_remote_nth_stripable (base+6, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
490         stripable[7] = session.get_remote_nth_stripable (base+7, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
491
492
493         for (int n = 0; n < 8; ++n) {
494                 if (!stripable[n]) {
495                         continue;
496                 }
497
498                 /* stripable goes away? refill the bank, starting at the same point */
499
500                 stripable[n]->DropReferences.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&MixLayout::switch_bank, this, bank_start), &p2);
501                 boost::shared_ptr<AutomationControl> sc = stripable[n]->solo_control();
502                 if (sc) {
503                         sc->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&MixLayout::solo_change, this, n), &p2);
504                 }
505
506                 boost::shared_ptr<AutomationControl> mc = stripable[n]->mute_control();
507                 if (mc) {
508                         mc->Changed.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&MixLayout::mute_change, this, n), &p2);
509                 }
510
511                 stripable[n]->presentation_info().PropertyChanged.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&MixLayout::stripable_property_change, this, _1, n), &p2);
512
513                 solo_change (n);
514                 mute_change (n);
515
516                 Push2::Button* b;
517
518                 switch (n) {
519                 case 0:
520                         b = p2.button_by_id (Push2::Lower1);
521                         break;
522                 case 1:
523                         b = p2.button_by_id (Push2::Lower2);
524                         break;
525                 case 2:
526                         b = p2.button_by_id (Push2::Lower3);
527                         break;
528                 case 3:
529                         b = p2.button_by_id (Push2::Lower4);
530                         break;
531                 case 4:
532                         b = p2.button_by_id (Push2::Lower5);
533                         break;
534                 case 5:
535                         b = p2.button_by_id (Push2::Lower6);
536                         break;
537                 case 6:
538                         b = p2.button_by_id (Push2::Lower7);
539                         break;
540                 case 7:
541                         b = p2.button_by_id (Push2::Lower8);
542                         break;
543                 }
544
545                 b->set_color (p2.get_color_index (stripable[n]->presentation_info().color()));
546                 b->set_state (Push2::LED::OneShot24th);
547                 p2.write (b->state_msg());
548         }
549 }
550
551 void
552 MixLayout::button_right ()
553 {
554         switch_bank (max (0, bank_start + 8));
555 }
556
557 void
558 MixLayout::button_left ()
559 {
560         switch_bank (max (0, bank_start - 8));
561 }
562
563
564 void
565 MixLayout::button_select_press ()
566 {
567 }
568
569 void
570 MixLayout::button_select_release ()
571 {
572         if (!(p2.modifier_state() & Push2::ModSelect)) {
573                 /* somebody else used us as a modifier */
574                 return;
575         }
576
577         int selected = -1;
578
579         for (int n = 0; n < 8; ++n) {
580                 if (stripable[n]) {
581                         if (stripable[n]->presentation_info().selected()) {
582                                         selected = n;
583                                         break;
584                         }
585                 }
586         }
587
588         if (selected < 0) {
589
590                 /* no visible track selected, select first (if any) */
591
592                 if (stripable[0]) {
593                         ControlProtocol::SetStripableSelection (stripable[0]);
594                 }
595
596         } else {
597
598                 if (p2.modifier_state() & Push2::ModShift) {
599                         std::cerr << "select prev\n";
600                         /* select prev */
601
602                         if (selected == 0) {
603                                 /* current selected is leftmost ... cancel selection,
604                                    switch banks by one, and select leftmost
605                                 */
606                                 if (bank_start != 0) {
607                                         ControlProtocol::ClearStripableSelection ();
608                                         switch_bank (bank_start-1);
609                                         if (stripable[0]) {
610                                                 ControlProtocol::SetStripableSelection (stripable[0]);
611                                         }
612                                 }
613                         } else {
614                                 /* select prev, if any */
615                                 int n = selected - 1;
616                                 while (n >= 0 && !stripable[n]) {
617                                         --n;
618                                 }
619                                 if (n >= 0) {
620                                         ControlProtocol::SetStripableSelection (stripable[n]);
621                                 }
622                         }
623
624                 } else {
625
626                         std::cerr << "select next\n";
627                         /* select next */
628
629                         if (selected == 7) {
630                                 /* current selected is rightmost ... cancel selection,
631                                    switch banks by one, and select righmost
632                                 */
633                                 ControlProtocol::ToggleStripableSelection (stripable[selected]);
634                                 switch_bank (bank_start+1);
635                                 if (stripable[7]) {
636                                         ControlProtocol::SetStripableSelection (stripable[7]);
637                                 }
638                         } else {
639                                 /* select next, if any */
640                                 int n = selected + 1;
641                                 while (n < 8 && !stripable[n]) {
642                                         ++n;
643                                 }
644
645                                 if (n != 8) {
646                                         ControlProtocol::SetStripableSelection (stripable[n]);
647                                 }
648                         }
649                 }
650         }
651 }