continuing work on refining push2 mix layout
[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         , _dirty (true)
61         , bank_start (0)
62         , vpot_mode (Volume)
63 {
64         tc_clock_layout = Pango::Layout::create (context);
65         bbt_clock_layout = Pango::Layout::create (context);
66
67         Pango::FontDescription fd ("Sans Bold 24");
68         tc_clock_layout->set_font_description (fd);
69         bbt_clock_layout->set_font_description (fd);
70
71         Pango::FontDescription fd2 ("Sans 10");
72         for (int n = 0; n < 8; ++n) {
73                 upper_layout[n] = Pango::Layout::create (context);
74                 upper_layout[n]->set_font_description (fd2);
75
76                 string txt;
77                 switch (n) {
78                 case 0:
79                         txt = _("Volumes");
80                         break;
81                 case 1:
82                         txt = _("Pans");
83                         break;
84                 case 2:
85                         txt = _("Pan Widths");
86                         break;
87                 case 3:
88                         txt = _("A Sends");
89                         break;
90                 case 4:
91                         txt = _("B Sends");
92                         break;
93                 case 5:
94                         txt = _("C Sends");
95                         break;
96                 case 6:
97                         txt = _("D Sends");
98                         break;
99                 case 7:
100                         txt = _("E Sends");
101                         break;
102                 }
103                 upper_layout[n]->set_text (txt);
104         }
105
106         Pango::FontDescription fd3 ("Sans 10");
107         for (int n = 0; n < 8; ++n) {
108                 lower_layout[n] = Pango::Layout::create (context);
109                 lower_layout[n]->set_font_description (fd3);
110         }
111
112         for (int n = 0; n < 8; ++n) {
113                 knobs[n] = new Push2Knob (p2, context);
114                 knobs[n]->set_position (60 + (n*120), 95);
115                 knobs[n]->set_radius (25);
116         }
117
118         mode_button = p2.button_by_id (Push2::Upper1);
119 }
120
121 MixLayout::~MixLayout ()
122 {
123         for (int n = 0; n < 8; ++n) {
124                 delete knobs[n];
125         }
126 }
127
128 void
129 MixLayout::on_show ()
130 {
131         mode_button->set_color (Push2::LED::White);
132         mode_button->set_state (Push2::LED::OneShot24th);
133         p2.write (mode_button->state_msg());
134
135         switch_bank (bank_start);
136         show_vpot_mode ();
137 }
138
139 bool
140 MixLayout::redraw (Cairo::RefPtr<Cairo::Context> context) const
141 {
142         framepos_t audible = session.audible_frame();
143         Timecode::Time TC;
144         bool negative = false;
145         string tc_clock_text;
146         string bbt_clock_text;
147
148         if (audible < 0) {
149                 audible = -audible;
150                 negative = true;
151         }
152
153         session.timecode_time (audible, TC);
154
155         TC.negative = TC.negative || negative;
156
157         tc_clock_text = Timecode::timecode_format_time(TC);
158
159         Timecode::BBT_Time bbt = session.tempo_map().bbt_at_frame (audible);
160         char buf[16];
161
162 #define BBT_BAR_CHAR "|"
163
164         if (negative) {
165                 snprintf (buf, sizeof (buf), "-%03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32,
166                           bbt.bars, bbt.beats, bbt.ticks);
167         } else {
168                 snprintf (buf, sizeof (buf), " %03" PRIu32 BBT_BAR_CHAR "%02" PRIu32 BBT_BAR_CHAR "%04" PRIu32,
169                           bbt.bars, bbt.beats, bbt.ticks);
170         }
171
172         bbt_clock_text = buf;
173
174         bool children_dirty = false;
175
176         if (tc_clock_text != tc_clock_layout->get_text()) {
177                 children_dirty = true;
178                 tc_clock_layout->set_text (tc_clock_text);
179         }
180
181         if (bbt_clock_text != bbt_clock_layout->get_text()) {
182                 children_dirty = true;
183                 bbt_clock_layout->set_text (bbt_clock_text);
184         }
185
186         for (int n = 0; n < 8; ++n) {
187                 if (knobs[n]->dirty()) {
188                         children_dirty = true;
189                         break;
190                 }
191         }
192
193         for (int n = 0; n < 8; ++n) {
194                 if (stripable[n]) {
195                         string shortname = short_version (stripable[n]->name(), 10);
196                         string text;
197                         boost::shared_ptr<AutomationControl> ac;
198                         ac = stripable[n]->solo_control();
199                         if (ac && ac->get_value()) {
200                                 text += "* ";
201                         }
202                         boost::shared_ptr<MuteControl> mc;
203                         mc = stripable[n]->mute_control ();
204                         if (mc) {
205                                 if (mc->muted_by_self_or_masters()) {
206                                         text += "! ";
207                                 } else if (mc->muted_by_others_soloing()) {
208                                         text += "- "; // it would be nice to use Unicode mute"\uD83D\uDD07 ";
209                                 }
210                         }
211                         text += shortname;
212
213                         if (text != lower_layout[n]->get_text()) {
214                                 lower_layout[n]->set_text (text);
215                                 children_dirty = true;
216                         }
217                 }
218         }
219
220         if (!children_dirty && !_dirty) {
221                 return false;
222         }
223
224         set_source_rgb (context, p2.get_color (Push2::DarkBackground));
225         context->rectangle (0, 0, p2.cols, p2.rows);
226         context->fill ();
227
228         /* clocks */
229
230         set_source_rgb (context, ArdourCanvas::contrasting_text_color (p2.get_color (Push2::DarkBackground)));
231         context->move_to (650, 30);
232         tc_clock_layout->update_from_cairo_context (context);
233         tc_clock_layout->show_in_cairo_context (context);
234         context->move_to (650, 65);
235         bbt_clock_layout->update_from_cairo_context (context);
236         bbt_clock_layout->show_in_cairo_context (context);
237
238         set_source_rgb (context, p2.get_color (Push2::ParameterName));
239
240         for (int n = 0; n < 8; ++n) {
241
242                 if (upper_layout[n]->get_text().empty()) {
243                         continue;
244                 }
245
246                 /* Draw highlight box */
247
248                 uint32_t color = p2.get_color (Push2::ParameterName);
249
250                 if (n == (int) vpot_mode) {
251                         set_source_rgb (context, color);
252                         context->rectangle (10 + (n*120) - 5, 2, 120, 21);
253                         context->fill();
254                         set_source_rgb (context, ArdourCanvas::contrasting_text_color (color));
255                 }  else {
256                         set_source_rgb (context, color);
257                 }
258
259                 context->move_to (10 + (n*120), 2);
260                 upper_layout[n]->update_from_cairo_context (context);
261                 upper_layout[n]->show_in_cairo_context (context);
262         }
263
264         context->move_to (0, 22.5);
265         context->line_to (p2.cols, 22.5);
266         context->set_line_width (1.0);
267         context->stroke ();
268
269         for (int n = 0; n < 8; ++n) {
270                 knobs[n]->redraw (context);
271         }
272
273         for (int n = 0; n < 8; ++n) {
274
275                 if (lower_layout[n]->get_text().empty()) {
276                         continue;
277                 }
278
279                 if (stripable[n]) {
280                         uint32_t color = stripable[n]->presentation_info().color();
281
282                         if (stripable[n]->presentation_info().selected()) {
283                                 set_source_rgb (context, color);
284                                 context->rectangle (10 + (n*120) - 5, 137, 120, 21);
285                                 context->fill();
286                                 set_source_rgb (context, ArdourCanvas::contrasting_text_color (color));
287                         }  else {
288                                 set_source_rgb (context, color);
289                         }
290
291                         context->move_to (10 + (n*120), 140);
292                         lower_layout[n]->update_from_cairo_context (context);
293                         lower_layout[n]->show_in_cairo_context (context);
294                 }
295         }
296
297         _dirty = false;
298
299         return true;
300 }
301
302 void
303 MixLayout::button_upper (uint32_t n)
304 {
305         Push2::Button* b;
306         switch (n) {
307         case 0:
308                 vpot_mode = Volume;
309                 b = p2.button_by_id (Push2::Upper1);
310                 break;
311         case 1:
312                 vpot_mode = PanAzimuth;
313                 b = p2.button_by_id (Push2::Upper2);
314                 break;
315         case 2:
316                 vpot_mode = PanWidth;
317                 b = p2.button_by_id (Push2::Upper3);
318                 break;
319         case 3:
320                 vpot_mode = Send1;
321                 b = p2.button_by_id (Push2::Upper4);
322                 break;
323         case 4:
324                 vpot_mode = Send2;
325                 b = p2.button_by_id (Push2::Upper5);
326                 break;
327         case 5:
328                 vpot_mode = Send3;
329                 b = p2.button_by_id (Push2::Upper6);
330                 break;
331         case 6:
332                 vpot_mode = Send4;
333                 b = p2.button_by_id (Push2::Upper7);
334                 break;
335         case 7:
336                 vpot_mode = Send5;
337                 b = p2.button_by_id (Push2::Upper8);
338                 break;
339         }
340
341         if (b != mode_button) {
342                 mode_button->set_color (Push2::LED::Black);
343                 mode_button->set_state (Push2::LED::OneShot24th);
344                 p2.write (mode_button->state_msg());
345         }
346
347         mode_button = b;
348
349         show_vpot_mode ();
350 }
351
352 void
353 MixLayout::show_vpot_mode ()
354 {
355         mode_button->set_color (Push2::LED::White);
356         mode_button->set_state (Push2::LED::OneShot24th);
357         p2.write (mode_button->state_msg());
358
359         boost::shared_ptr<AutomationControl> ac;
360         switch (vpot_mode) {
361         case Volume:
362                 for (int s = 0; s < 8; ++s) {
363                         if (stripable[s]) {
364                                 knobs[s]->set_controllable (stripable[s]->gain_control());
365                         } else {
366                                 knobs[s]->set_controllable (boost::shared_ptr<AutomationControl>());
367                         }
368                         knobs[s]->remove_flag (Push2Knob::ArcToZero);
369                 }
370                 break;
371         case PanAzimuth:
372                 for (int s = 0; s < 8; ++s) {
373                         if (stripable[s]) {
374                                 knobs[s]->set_controllable (stripable[s]->pan_azimuth_control());
375                                 knobs[s]->add_flag (Push2Knob::ArcToZero);
376                         } else {
377                                 knobs[s]->set_controllable (boost::shared_ptr<AutomationControl>());
378
379                         }
380                 }
381                 break;
382         case PanWidth:
383                 for (int s = 0; s < 8; ++s) {
384                         if (stripable[s]) {
385                                 knobs[s]->set_controllable (stripable[s]->pan_width_control());
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::solo_change (int n)
477 {
478 }
479
480 void
481 MixLayout::mute_change (int n)
482 {
483 }
484
485 void
486 MixLayout::switch_bank (uint32_t base)
487 {
488         stripable_connections.drop_connections ();
489
490         /* try to get the first stripable for the requested bank */
491
492         stripable[0] = session.get_remote_nth_stripable (base, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
493
494         if (!stripable[0]) {
495                 return;
496         }
497
498         /* at least one stripable in this bank */
499         bank_start = base;
500
501         stripable[1] = session.get_remote_nth_stripable (base+1, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
502         stripable[2] = session.get_remote_nth_stripable (base+2, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
503         stripable[3] = session.get_remote_nth_stripable (base+3, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
504         stripable[4] = session.get_remote_nth_stripable (base+4, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
505         stripable[5] = session.get_remote_nth_stripable (base+5, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
506         stripable[6] = session.get_remote_nth_stripable (base+6, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
507         stripable[7] = session.get_remote_nth_stripable (base+7, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
508
509
510         for (int n = 0; n < 8; ++n) {
511                 if (!stripable[n]) {
512                         continue;
513                 }
514
515                 /* stripable goes away? refill the bank, starting at the same point */
516
517                 stripable[n]->DropReferences.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&MixLayout::switch_bank, this, bank_start), &p2);
518                 stripable[n]->presentation_info().PropertyChanged.connect (stripable_connections, MISSING_INVALIDATOR, boost::bind (&MixLayout::stripable_property_change, this, _1, n), &p2);
519
520                 solo_change (n);
521                 mute_change (n);
522
523                 Push2::Button* b;
524
525                 switch (n) {
526                 case 0:
527                         b = p2.button_by_id (Push2::Lower1);
528                         break;
529                 case 1:
530                         b = p2.button_by_id (Push2::Lower2);
531                         break;
532                 case 2:
533                         b = p2.button_by_id (Push2::Lower3);
534                         break;
535                 case 3:
536                         b = p2.button_by_id (Push2::Lower4);
537                         break;
538                 case 4:
539                         b = p2.button_by_id (Push2::Lower5);
540                         break;
541                 case 5:
542                         b = p2.button_by_id (Push2::Lower6);
543                         break;
544                 case 6:
545                         b = p2.button_by_id (Push2::Lower7);
546                         break;
547                 case 7:
548                         b = p2.button_by_id (Push2::Lower8);
549                         break;
550                 }
551
552                 b->set_color (p2.get_color_index (stripable[n]->presentation_info().color()));
553                 b->set_state (Push2::LED::OneShot24th);
554                 p2.write (b->state_msg());
555
556                 knobs[n]->set_text_color (stripable[n]->presentation_info().color());
557         }
558 }
559
560 void
561 MixLayout::button_right ()
562 {
563         switch_bank (max (0, bank_start + 8));
564 }
565
566 void
567 MixLayout::button_left ()
568 {
569         switch_bank (max (0, bank_start - 8));
570 }
571
572
573 void
574 MixLayout::button_select_press ()
575 {
576 }
577
578 void
579 MixLayout::button_select_release ()
580 {
581         if (!(p2.modifier_state() & Push2::ModSelect)) {
582                 /* somebody else used us as a modifier */
583                 return;
584         }
585
586         int selected = -1;
587
588         for (int n = 0; n < 8; ++n) {
589                 if (stripable[n]) {
590                         if (stripable[n]->presentation_info().selected()) {
591                                         selected = n;
592                                         break;
593                         }
594                 }
595         }
596
597         if (selected < 0) {
598
599                 /* no visible track selected, select first (if any) */
600
601                 if (stripable[0]) {
602                         ControlProtocol::SetStripableSelection (stripable[0]);
603                 }
604
605         } else {
606
607                 if (p2.modifier_state() & Push2::ModShift) {
608                         std::cerr << "select prev\n";
609                         /* select prev */
610
611                         if (selected == 0) {
612                                 /* current selected is leftmost ... cancel selection,
613                                    switch banks by one, and select leftmost
614                                 */
615                                 if (bank_start != 0) {
616                                         ControlProtocol::ClearStripableSelection ();
617                                         switch_bank (bank_start-1);
618                                         if (stripable[0]) {
619                                                 ControlProtocol::SetStripableSelection (stripable[0]);
620                                         }
621                                 }
622                         } else {
623                                 /* select prev, if any */
624                                 int n = selected - 1;
625                                 while (n >= 0 && !stripable[n]) {
626                                         --n;
627                                 }
628                                 if (n >= 0) {
629                                         ControlProtocol::SetStripableSelection (stripable[n]);
630                                 }
631                         }
632
633                 } else {
634
635                         std::cerr << "select next\n";
636                         /* select next */
637
638                         if (selected == 7) {
639                                 /* current selected is rightmost ... cancel selection,
640                                    switch banks by one, and select righmost
641                                 */
642                                 ControlProtocol::ToggleStripableSelection (stripable[selected]);
643                                 switch_bank (bank_start+1);
644                                 if (stripable[7]) {
645                                         ControlProtocol::SetStripableSelection (stripable[7]);
646                                 }
647                         } else {
648                                 /* select next, if any */
649                                 int n = selected + 1;
650                                 while (n < 8 && !stripable[n]) {
651                                         ++n;
652                                 }
653
654                                 if (n != 8) {
655                                         ControlProtocol::SetStripableSelection (stripable[n]);
656                                 }
657                         }
658                 }
659         }
660 }