push2: continued mix layout display refinement, plus scrolling fixes
[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 #include "ardour/vca_manager.h"
43
44 #include "canvas/colors.h"
45
46 #include "mix.h"
47 #include "knob.h"
48 #include "push2.h"
49 #include "utils.h"
50
51 #include "i18n.h"
52
53 using namespace ARDOUR;
54 using namespace std;
55 using namespace PBD;
56 using namespace Glib;
57 using namespace ArdourSurface;
58
59 MixLayout::MixLayout (Push2& p, Session& s, Cairo::RefPtr<Cairo::Context> context)
60         : Push2Layout (p, s)
61         , _dirty (true)
62         , bank_start (0)
63         , vpot_mode (Volume)
64 {
65         Pango::FontDescription fd2 ("Sans 10");
66         for (int n = 0; n < 8; ++n) {
67                 upper_layout[n] = Pango::Layout::create (context);
68                 upper_layout[n]->set_font_description (fd2);
69
70                 string txt;
71                 switch (n) {
72                 case 0:
73                         txt = _("Volumes");
74                         break;
75                 case 1:
76                         txt = _("Pans");
77                         break;
78                 case 2:
79                         txt = _("Pan Widths");
80                         break;
81                 case 3:
82                         txt = _("A Sends");
83                         break;
84                 case 4:
85                         txt = _("B Sends");
86                         break;
87                 case 5:
88                         txt = _("C Sends");
89                         break;
90                 case 6:
91                         txt = _("D Sends");
92                         break;
93                 case 7:
94                         txt = _("E Sends");
95                         break;
96                 }
97                 upper_layout[n]->set_text (txt);
98         }
99
100         Pango::FontDescription fd3 ("Sans 10");
101         for (int n = 0; n < 8; ++n) {
102                 lower_layout[n] = Pango::Layout::create (context);
103                 lower_layout[n]->set_font_description (fd3);
104         }
105
106         for (int n = 0; n < 8; ++n) {
107                 knobs[n] = new Push2Knob (p2, context);
108                 knobs[n]->set_position (60 + (n*120), 95);
109                 knobs[n]->set_radius (25);
110         }
111
112         mode_button = p2.button_by_id (Push2::Upper1);
113
114         session.RouteAdded.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&MixLayout::stripables_added, this), &p2);
115         session.vca_manager().VCAAdded.connect (session_connections, MISSING_INVALIDATOR, boost::bind (&MixLayout::stripables_added, this), &p2);
116 }
117
118 MixLayout::~MixLayout ()
119 {
120         for (int n = 0; n < 8; ++n) {
121                 delete knobs[n];
122         }
123 }
124
125 void
126 MixLayout::on_show ()
127 {
128         mode_button->set_color (Push2::LED::White);
129         mode_button->set_state (Push2::LED::OneShot24th);
130         p2.write (mode_button->state_msg());
131
132         switch_bank (bank_start);
133 }
134
135 bool
136 MixLayout::redraw (Cairo::RefPtr<Cairo::Context> context) const
137 {
138         bool children_dirty = false;
139
140         for (int n = 0; n < 8; ++n) {
141                 if (knobs[n]->dirty()) {
142                         children_dirty = true;
143                         break;
144                 }
145         }
146
147         for (int n = 0; n < 8; ++n) {
148                 if (stripable[n]) {
149
150                         string shortname = short_version (stripable[n]->name(), 10);
151                         string text;
152                         boost::shared_ptr<AutomationControl> ac;
153                         ac = stripable[n]->solo_control();
154                         if (ac && ac->get_value()) {
155                                 text += "* ";
156                         }
157                         boost::shared_ptr<MuteControl> mc;
158                         mc = stripable[n]->mute_control ();
159                         if (mc) {
160                                 if (mc->muted_by_self_or_masters()) {
161                                         text += "! ";
162                                 } else if (mc->muted_by_others_soloing()) {
163                                         text += "- "; // it would be nice to use Unicode mute"\uD83D\uDD07 ";
164                                 }
165                         }
166                         text += shortname;
167
168                         if (text != lower_layout[n]->get_text()) {
169                                 lower_layout[n]->set_text (text);
170                                 children_dirty = true;
171                         }
172                 }
173         }
174
175         if (!children_dirty && !_dirty) {
176                 return false;
177         }
178
179         set_source_rgb (context, p2.get_color (Push2::DarkBackground));
180         context->rectangle (0, 0, p2.cols, p2.rows);
181         context->fill ();
182
183         set_source_rgb (context, p2.get_color (Push2::ParameterName));
184
185         for (int n = 0; n < 8; ++n) {
186
187                 if (upper_layout[n]->get_text().empty()) {
188                         continue;
189                 }
190
191                 /* Draw highlight box */
192
193                 uint32_t color = p2.get_color (Push2::ParameterName);
194
195                 if (n == (int) vpot_mode) {
196                         set_source_rgb (context, color);
197                         context->rectangle (10 + (n*120) - 5, 2, 120, 21);
198                         context->fill();
199                         set_source_rgb (context, ArdourCanvas::contrasting_text_color (color));
200                 }  else {
201                         set_source_rgb (context, color);
202                 }
203
204                 context->move_to (10 + (n*120), 2);
205                 upper_layout[n]->update_from_cairo_context (context);
206                 upper_layout[n]->show_in_cairo_context (context);
207         }
208
209         context->move_to (0, 22.5);
210         context->line_to (p2.cols, 22.5);
211         context->set_line_width (1.0);
212         context->stroke ();
213
214         for (int n = 0; n < 8; ++n) {
215                 knobs[n]->redraw (context);
216         }
217
218         for (int n = 0; n < 8; ++n) {
219
220                 if (lower_layout[n]->get_text().empty()) {
221                         continue;
222                 }
223
224                 if (stripable[n]) {
225                         uint32_t color = stripable[n]->presentation_info().color();
226
227                         if (stripable[n]->presentation_info().selected()) {
228                                 set_source_rgb (context, color);
229                                 context->rectangle (10 + (n*120) - 5, 137, 120, 21);
230                                 context->fill();
231                                 set_source_rgb (context, ArdourCanvas::contrasting_text_color (color));
232                         }  else {
233                                 set_source_rgb (context, color);
234                         }
235
236                         context->move_to (10 + (n*120), 140);
237                         lower_layout[n]->update_from_cairo_context (context);
238                         lower_layout[n]->show_in_cairo_context (context);
239                 }
240         }
241
242         _dirty = false;
243
244         return true;
245 }
246
247 void
248 MixLayout::button_upper (uint32_t n)
249 {
250         Push2::Button* b;
251         switch (n) {
252         case 0:
253                 vpot_mode = Volume;
254                 b = p2.button_by_id (Push2::Upper1);
255                 break;
256         case 1:
257                 vpot_mode = PanAzimuth;
258                 b = p2.button_by_id (Push2::Upper2);
259                 break;
260         case 2:
261                 vpot_mode = PanWidth;
262                 b = p2.button_by_id (Push2::Upper3);
263                 break;
264         case 3:
265                 vpot_mode = Send1;
266                 b = p2.button_by_id (Push2::Upper4);
267                 break;
268         case 4:
269                 vpot_mode = Send2;
270                 b = p2.button_by_id (Push2::Upper5);
271                 break;
272         case 5:
273                 vpot_mode = Send3;
274                 b = p2.button_by_id (Push2::Upper6);
275                 break;
276         case 6:
277                 vpot_mode = Send4;
278                 b = p2.button_by_id (Push2::Upper7);
279                 break;
280         case 7:
281                 vpot_mode = Send5;
282                 b = p2.button_by_id (Push2::Upper8);
283                 break;
284         }
285
286         if (b != mode_button) {
287                 mode_button->set_color (Push2::LED::Black);
288                 mode_button->set_state (Push2::LED::OneShot24th);
289                 p2.write (mode_button->state_msg());
290         }
291
292         mode_button = b;
293
294         show_vpot_mode ();
295 }
296
297 void
298 MixLayout::show_vpot_mode ()
299 {
300         mode_button->set_color (Push2::LED::White);
301         mode_button->set_state (Push2::LED::OneShot24th);
302         p2.write (mode_button->state_msg());
303
304         boost::shared_ptr<AutomationControl> ac;
305         switch (vpot_mode) {
306         case Volume:
307                 for (int s = 0; s < 8; ++s) {
308                         if (stripable[s]) {
309                                 knobs[s]->set_controllable (stripable[s]->gain_control());
310                         } else {
311                                 knobs[s]->set_controllable (boost::shared_ptr<AutomationControl>());
312                         }
313                         knobs[s]->remove_flag (Push2Knob::ArcToZero);
314                 }
315                 break;
316         case PanAzimuth:
317                 for (int s = 0; s < 8; ++s) {
318                         if (stripable[s]) {
319                                 knobs[s]->set_controllable (stripable[s]->pan_azimuth_control());
320                                 knobs[s]->add_flag (Push2Knob::ArcToZero);
321                         } else {
322                                 knobs[s]->set_controllable (boost::shared_ptr<AutomationControl>());
323
324                         }
325                 }
326                 break;
327         case PanWidth:
328                 for (int s = 0; s < 8; ++s) {
329                         if (stripable[s]) {
330                                 knobs[s]->set_controllable (stripable[s]->pan_width_control());
331                         } else {
332                                 knobs[s]->set_controllable (boost::shared_ptr<AutomationControl>());
333
334                         }
335                         knobs[s]->remove_flag (Push2Knob::ArcToZero);
336                 }
337                 break;
338         case Send1:
339                 for (int s = 0; s < 8; ++s) {
340                         if (stripable[s]) {
341                                 knobs[s]->set_controllable (stripable[s]->send_level_controllable (0));
342                         } else {
343                                 knobs[s]->set_controllable (boost::shared_ptr<AutomationControl>());
344
345                         }
346                         knobs[s]->remove_flag (Push2Knob::ArcToZero);
347                 }
348                 break;
349         case Send2:
350                 for (int s = 0; s < 8; ++s) {
351                         if (stripable[s]) {
352                                 knobs[s]->set_controllable (stripable[s]->send_level_controllable (1));
353                         } else {
354                                 knobs[s]->set_controllable (boost::shared_ptr<AutomationControl>());
355
356                         }
357                         knobs[s]->remove_flag (Push2Knob::ArcToZero);
358                 }
359                 break;
360         case Send3:
361                 for (int s = 0; s < 8; ++s) {
362                         if (stripable[s]) {
363                                 knobs[s]->set_controllable (stripable[s]->send_level_controllable (2));
364                         } else {
365                                 knobs[s]->set_controllable (boost::shared_ptr<AutomationControl>());
366
367                         }
368                         knobs[s]->remove_flag (Push2Knob::ArcToZero);
369                 }
370                 break;
371         case Send4:
372                 for (int s = 0; s < 8; ++s) {
373                         if (stripable[s]) {
374                                 knobs[s]->set_controllable (stripable[s]->send_level_controllable (3));
375                         } else {
376                                 knobs[s]->set_controllable (boost::shared_ptr<AutomationControl>());
377
378                         }
379                         knobs[s]->remove_flag (Push2Knob::ArcToZero);
380                 }
381                 break;
382         case Send5:
383                 for (int s = 0; s < 8; ++s) {
384                         if (stripable[s]) {
385                                 knobs[s]->set_controllable (stripable[s]->send_level_controllable (4));
386                         } else {
387                                 knobs[s]->set_controllable (boost::shared_ptr<AutomationControl>());
388
389                         }
390                         knobs[s]->remove_flag (Push2Knob::ArcToZero);
391                 }
392                 break;
393         default:
394                 break;
395         }
396
397         _dirty = true;
398 }
399
400 void
401 MixLayout::button_mute ()
402 {
403         boost::shared_ptr<Stripable> s = ControlProtocol::first_selected_stripable();
404         if (s) {
405                 boost::shared_ptr<AutomationControl> ac = s->mute_control();
406                 if (ac) {
407                         ac->set_value (!ac->get_value(), PBD::Controllable::UseGroup);
408                 }
409         }
410 }
411
412 void
413 MixLayout::button_solo ()
414 {
415         boost::shared_ptr<Stripable> s = ControlProtocol::first_selected_stripable();
416         if (s) {
417                 boost::shared_ptr<AutomationControl> ac = s->solo_control();
418                 if (ac) {
419                         ac->set_value (!ac->get_value(), PBD::Controllable::UseGroup);
420                 }
421         }
422 }
423
424 void
425 MixLayout::button_lower (uint32_t n)
426 {
427         if (!stripable[n]) {
428                 return;
429         }
430
431         ControlProtocol::SetStripableSelection (stripable[n]);
432 }
433
434 void
435 MixLayout::strip_vpot (int n, int delta)
436 {
437         boost::shared_ptr<Controllable> ac = knobs[n]->controllable();
438
439         if (ac) {
440                 ac->set_value (ac->get_value() + ((2.0/64.0) * delta), PBD::Controllable::UseGroup);
441         }
442 }
443
444 void
445 MixLayout::strip_vpot_touch (int n, bool touching)
446 {
447         if (stripable[n]) {
448                 boost::shared_ptr<AutomationControl> ac = stripable[n]->gain_control();
449                 if (ac) {
450                         if (touching) {
451                                 ac->start_touch (session.audible_frame());
452                         } else {
453                                 ac->stop_touch (true, session.audible_frame());
454                         }
455                 }
456         }
457 }
458
459 void
460 MixLayout::stripable_property_change (PropertyChange const& what_changed, int which)
461 {
462         if (what_changed.contains (Properties::selected)) {
463                 if (!stripable[which]) {
464                         return;
465                 }
466
467                 /* cancel string, which will cause a redraw on the next update
468                  * cycle. The redraw will reflect selected status
469                  */
470
471                 lower_layout[which]->set_text (string());
472         }
473 }
474
475 void
476 MixLayout::switch_bank (uint32_t base)
477 {
478         stripable_connections.drop_connections ();
479
480         /* work backwards so we can tell if we should actually switch banks */
481
482         boost::shared_ptr<Stripable> s[8];
483         uint32_t old_empty = 0;
484         uint32_t new_empty = 0;
485
486         for (int n = 0; n < 8; ++n) {
487                 if (!stripable[n]) {
488                         old_empty++;
489                 }
490                 s[n] = session.get_remote_nth_stripable (base+n, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
491                 if (!s[n]) {
492                         new_empty++;
493                 }
494         }
495
496         if ((new_empty != 0) && (new_empty >= old_empty)) {
497                 /* some missing strips; new bank the same or more empty stripables than the old one, do
498                    nothing since we had already reached the end.
499                 */
500                 return;
501         }
502
503         for (int n = 0; n < 8; ++n) {
504                 stripable[n] = s[n];
505         }
506
507         /* at least one stripable in this bank */
508
509         bank_start = base;
510
511         for (int n = 0; n < 8; ++n) {
512                 if (!stripable[n]) {
513                         continue;
514                 }
515
516                 /* stripable goes away? refill the bank, starting at the same point */
517
518                 stripable[n]->DropReferences.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&MixLayout::switch_bank, this, bank_start), &p2);
519                 stripable[n]->presentation_info().PropertyChanged.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&MixLayout::stripable_property_change, this, _1, n), &p2);
520
521                 Push2::Button* b;
522
523                 switch (n) {
524                 case 0:
525                         b = p2.button_by_id (Push2::Lower1);
526                         break;
527                 case 1:
528                         b = p2.button_by_id (Push2::Lower2);
529                         break;
530                 case 2:
531                         b = p2.button_by_id (Push2::Lower3);
532                         break;
533                 case 3:
534                         b = p2.button_by_id (Push2::Lower4);
535                         break;
536                 case 4:
537                         b = p2.button_by_id (Push2::Lower5);
538                         break;
539                 case 5:
540                         b = p2.button_by_id (Push2::Lower6);
541                         break;
542                 case 6:
543                         b = p2.button_by_id (Push2::Lower7);
544                         break;
545                 case 7:
546                         b = p2.button_by_id (Push2::Lower8);
547                         break;
548                 }
549
550                 b->set_color (p2.get_color_index (stripable[n]->presentation_info().color()));
551                 b->set_state (Push2::LED::OneShot24th);
552                 p2.write (b->state_msg());
553
554                 knobs[n]->set_text_color (stripable[n]->presentation_info().color());
555                 knobs[n]->set_arc_start_color (stripable[n]->presentation_info().color());
556                 knobs[n]->set_arc_end_color (stripable[n]->presentation_info().color());
557         }
558
559         show_vpot_mode ();
560 }
561
562 void
563 MixLayout::button_right ()
564 {
565         switch_bank (max (0, bank_start + 8));
566 }
567
568 void
569 MixLayout::button_left ()
570 {
571         switch_bank (max (0, bank_start - 8));
572 }
573
574
575 void
576 MixLayout::button_select_press ()
577 {
578 }
579
580 void
581 MixLayout::button_select_release ()
582 {
583         if (!(p2.modifier_state() & Push2::ModSelect)) {
584                 /* somebody else used us as a modifier */
585                 return;
586         }
587
588         int selected = -1;
589
590         for (int n = 0; n < 8; ++n) {
591                 if (stripable[n]) {
592                         if (stripable[n]->presentation_info().selected()) {
593                                         selected = n;
594                                         break;
595                         }
596                 }
597         }
598
599         if (selected < 0) {
600
601                 /* no visible track selected, select first (if any) */
602
603                 if (stripable[0]) {
604                         ControlProtocol::SetStripableSelection (stripable[0]);
605                 }
606
607         } else {
608
609                 if (p2.modifier_state() & Push2::ModShift) {
610                         std::cerr << "select prev\n";
611                         /* select prev */
612
613                         if (selected == 0) {
614                                 /* current selected is leftmost ... cancel selection,
615                                    switch banks by one, and select leftmost
616                                 */
617                                 if (bank_start != 0) {
618                                         ControlProtocol::ClearStripableSelection ();
619                                         switch_bank (bank_start-1);
620                                         if (stripable[0]) {
621                                                 ControlProtocol::SetStripableSelection (stripable[0]);
622                                         }
623                                 }
624                         } else {
625                                 /* select prev, if any */
626                                 int n = selected - 1;
627                                 while (n >= 0 && !stripable[n]) {
628                                         --n;
629                                 }
630                                 if (n >= 0) {
631                                         ControlProtocol::SetStripableSelection (stripable[n]);
632                                 }
633                         }
634
635                 } else {
636
637                         std::cerr << "select next\n";
638                         /* select next */
639
640                         if (selected == 7) {
641                                 /* current selected is rightmost ... cancel selection,
642                                    switch banks by one, and select righmost
643                                 */
644                                 ControlProtocol::ToggleStripableSelection (stripable[selected]);
645                                 switch_bank (bank_start+1);
646                                 if (stripable[7]) {
647                                         ControlProtocol::SetStripableSelection (stripable[7]);
648                                 }
649                         } else {
650                                 /* select next, if any */
651                                 int n = selected + 1;
652                                 while (n < 8 && !stripable[n]) {
653                                         ++n;
654                                 }
655
656                                 if (n != 8) {
657                                         ControlProtocol::SetStripableSelection (stripable[n]);
658                                 }
659                         }
660                 }
661         }
662 }
663
664 void
665 MixLayout::stripables_added ()
666 {
667         /* reload current bank */
668         switch_bank (bank_start);
669 }