8a1a16d0a48bd67527f4a9c50bc29e8627a9e100
[ardour.git] / libs / surfaces / push2 / scale.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 "pbd/i18n.h"
20
21 #include "gtkmm2ext/gui_thread.h"
22
23 #include "canvas/colors.h"
24 #include "canvas/rectangle.h"
25 #include "canvas/text.h"
26
27 #include "canvas.h"
28 #include "menu.h"
29 #include "push2.h"
30 #include "scale.h"
31
32 #ifdef __APPLE__
33 #define Rect ArdourCanvas::Rect
34 #endif
35
36 using namespace ARDOUR;
37 using namespace std;
38 using namespace PBD;
39 using namespace Glib;
40 using namespace ArdourSurface;
41 using namespace ArdourCanvas;
42
43 static double unselected_root_alpha = 0.5;
44
45 ScaleLayout::ScaleLayout (Push2& p, Session & s, std::string const & name)
46         : Push2Layout (p, s, name)
47         , last_vpot (-1)
48         , vpot_delta_cnt (0)
49         , root_button (0)
50 {
51         Pango::FontDescription fd ("Sans 10");
52
53         /* background */
54
55         bg = new ArdourCanvas::Rectangle (this);
56         bg->set (Rect (0, 0, display_width(), display_height()));
57         bg->set_fill_color (p2.get_color (Push2::DarkBackground));
58
59         left_scroll_text = new Text (this);
60         left_scroll_text->set_font_description (fd);
61         left_scroll_text->set_position (Duple (10, 5));
62         left_scroll_text->set_color (p2.get_color (Push2::LightBackground));
63
64         close_text = new Text (this);
65         close_text->set_font_description (fd);
66         close_text->set_position (Duple (25, 5));
67         close_text->set_color (p2.get_color (Push2::LightBackground));
68         close_text->set (_("Close"));
69
70         right_scroll_text = new Text (this);
71         right_scroll_text->set_font_description (fd);
72         right_scroll_text->set_position (Duple (10 + (7 * Push2Canvas::inter_button_spacing()), 5));
73         right_scroll_text->set_color (p2.get_color (Push2::LightBackground));
74
75         Pango::FontDescription fd2 ("Sans 8");
76         inkey_text = new Text (this);
77         inkey_text->set_font_description (fd2);
78         inkey_text->set_position (Duple (10, 140));
79         inkey_text->set_color (p2.get_color (Push2::LightBackground));
80         inkey_text->set (_("InKey"));
81
82         chromatic_text = new Text (this);
83         chromatic_text->set_font_description (fd2);
84         chromatic_text->set_position (Duple (45, 140));
85         chromatic_text->set_color (p2.get_color (Push2::LightBackground));
86         chromatic_text->set (_("Chromatic"));
87
88         for (int n = 0; n < 8; ++n) {
89
90                 /* text labels for root notes etc.*/
91
92                 Text* t = new Text (this);
93                 t->set_font_description (fd);
94                 t->set_color (change_alpha (p2.get_color (Push2::LightBackground), unselected_root_alpha));
95                 t->set_position (Duple (10 + (n * Push2Canvas::inter_button_spacing()), 5));
96
97                 switch (n) {
98                 case 0:
99                         /* zeroth element is a dummy */
100                         break;
101                 case 1:
102                         t->set (S_("Note|C"));
103                         break;
104                 case 2:
105                         t->set (S_("Note|G"));
106                         break;
107                 case 3:
108                         t->set (S_("Note|D"));
109                         break;
110                 case 4:
111                         t->set (S_("Note|A"));
112                         break;
113                 case 5:
114                         t->set (S_("Note|E"));
115                         break;
116                 case 6:
117                         t->set (S_("Note|B"));
118                         break;
119                 }
120
121                 upper_text.push_back (t);
122
123                 t = new Text (this);
124                 t->set_font_description (fd);
125                 t->set_color (change_alpha (p2.get_color (Push2::LightBackground), unselected_root_alpha));
126                 t->set_position (Duple (10 + (n*Push2Canvas::inter_button_spacing()), 140));
127
128                 switch (n) {
129                 case 0:
130                         /* zeroth element is a dummy */
131                         break;
132                 case 1:
133                         t->set (S_("Note|F"));
134                         break;
135                 case 2:
136                         t->set (S_("Note|B\u266D/A\u266F"));
137                         break;
138                 case 3:
139                         t->set (S_("Note|E\u266D/D\u266F"));
140                         break;
141                 case 4:
142                         t->set (S_("Note|A\u266D/G\u266F"));
143                         break;
144                 case 5:
145                         t->set (S_("Note|D\u266D/C\u266F"));
146                         break;
147                 case 6:
148                         t->set (S_("Note|G\u266D/F\u266F"));
149                         break;
150                 }
151
152                 lower_text.push_back (t);
153         }
154
155         build_scale_menu ();
156
157         p2.ScaleChange.connect (p2_connections, invalidator (*this), boost::bind (&ScaleLayout::show_root_state, this), &p2);
158 }
159
160 ScaleLayout::~ScaleLayout ()
161 {
162 }
163
164 void
165 ScaleLayout::render (Rect const& area, Cairo::RefPtr<Cairo::Context> context) const
166 {
167         render_children (area, context);
168 }
169
170 void
171 ScaleLayout::button_upper (uint32_t n)
172 {
173         if (n == 0) {
174                 if (scale_menu->can_scroll_left()) {
175                         scale_menu->scroll (Push2Menu::DirectionLeft, true);
176                 } else {
177                         p2.use_previous_layout ();
178                 }
179                 return;
180         }
181
182         if (n == 7) {
183                 scale_menu->scroll (Push2Menu::DirectionRight, true);
184                 return;
185         }
186
187         int root;
188
189         switch (n) {
190         case 1:
191                 /* C */
192                 root = 0;
193                 break;
194         case 2:
195                 /* G */
196                 root = 7;
197                 break;
198         case 3:
199                 /* D */
200                 root = 2;
201                 break;
202         case 4:
203                 /* A */
204                 root = 9;
205                 break;
206         case 5:
207                 /* E */
208                 root = 4;
209                 break;
210         case 6:
211                 /* B */
212                 root = 11;
213                 break;
214         case 7:
215                 /* unused */
216                 return;
217         }
218
219         p2.set_pad_scale (root, p2.root_octave(), p2.mode(), p2.in_key());
220 }
221
222 void
223 ScaleLayout::button_lower (uint32_t n)
224 {
225         if (n == 0) {
226                 p2.set_pad_scale (p2.scale_root(), p2.root_octave(), p2.mode(), !p2.in_key());
227                 return;
228         }
229
230         int root;
231
232         switch (n) {
233         case 1:
234                 /* F */
235                 root = 5;
236                 break;
237         case 2:
238                 /* B-flat */
239                 root = 10;
240                 break;
241         case 3:
242                 /* E flat */
243                 root = 3;
244                 break;
245         case 4:
246                 /* A flat */
247                 root = 8;
248                 break;
249         case 5:
250                 /* D flat */
251                 root = 1;
252                 break;
253         case 6:
254                 /* G flat */
255                 root = 6;
256                 break;
257         case 7:
258                 /* fixed mode */
259                 return;
260         }
261
262         p2.set_pad_scale (root, p2.root_octave(), p2.mode(), p2.in_key());
263 }
264
265 void
266 ScaleLayout::button_up ()
267 {
268         scale_menu->scroll (Push2Menu::DirectionUp);
269 }
270
271 void
272 ScaleLayout::button_down ()
273 {
274         scale_menu->scroll (Push2Menu::DirectionDown);
275 }
276
277 void
278 ScaleLayout::button_left ()
279 {
280         scale_menu->scroll (Push2Menu::DirectionLeft);
281 }
282
283 void
284 ScaleLayout::button_right ()
285 {
286         scale_menu->scroll (Push2Menu::DirectionRight);
287 }
288
289 void
290 ScaleLayout::show ()
291 {
292         Push2::Button* b;
293
294         last_vpot = -1;
295
296         b = p2.button_by_id (Push2::Upper1);
297         b->set_color (Push2::LED::White);
298         b->set_state (Push2::LED::OneShot24th);
299         p2.write (b->state_msg());
300
301         b = p2.button_by_id (Push2::Upper8);
302         b->set_color (Push2::LED::White);
303         b->set_state (Push2::LED::OneShot24th);
304         p2.write (b->state_msg());
305
306         b = p2.button_by_id (Push2::Lower1);
307         b->set_color (Push2::LED::White);
308         b->set_state (Push2::LED::OneShot24th);
309         p2.write (b->state_msg());
310
311         /* all root buttons should be dimly lit */
312
313         Push2::ButtonID root_buttons[] = { Push2::Upper2, Push2::Upper3, Push2::Upper4, Push2::Upper5, Push2::Upper6, Push2::Upper7,
314                                            Push2::Lower2, Push2::Lower3, Push2::Lower4, Push2::Lower5, Push2::Lower6, Push2::Lower7, };
315
316         for (size_t n = 0; n < sizeof (root_buttons) / sizeof (root_buttons[0]); ++n) {
317                 b = p2.button_by_id (root_buttons[n]);
318
319                 b->set_color (Push2::LED::DarkGray);
320                 b->set_state (Push2::LED::OneShot24th);
321                 p2.write (b->state_msg());
322         }
323
324         show_root_state ();
325
326         Container::show ();
327 }
328
329 void
330 ScaleLayout::strip_vpot (int n, int delta)
331 {
332         /* menu starts under the 2nd-from-left vpot */
333
334         if (n == 0) {
335                 return;
336         }
337
338         if (last_vpot != n) {
339                 uint32_t effective_column = n - 1;
340                 uint32_t active = scale_menu->active ();
341
342                 if (active / scale_menu->rows() != effective_column) {
343                         /* knob turned is different than the current active column.
344                            Just change that.
345                         */
346                         scale_menu->set_active (effective_column * scale_menu->rows()); /* top entry of that column */
347                         return;
348                 }
349
350                 /* new vpot, reset delta cnt */
351
352                 vpot_delta_cnt = 0;
353         }
354
355         if ((delta < 0 && vpot_delta_cnt > 0) || (delta > 0 && vpot_delta_cnt < 0)) {
356                 /* direction changed, reset */
357                 vpot_delta_cnt = 0;
358         }
359
360         vpot_delta_cnt += delta;
361         last_vpot = n;
362
363         /* this thins out vpot delta events so that we don't scroll so fast
364            through the menu.
365         */
366
367         const int vpot_slowdown_factor = 4;
368
369         if ((vpot_delta_cnt < 0) && (vpot_delta_cnt % vpot_slowdown_factor == 0)) {
370                 scale_menu->scroll (Push2Menu::DirectionUp);
371         } else if (vpot_delta_cnt % vpot_slowdown_factor == 0) {
372                 scale_menu->scroll (Push2Menu::DirectionDown);
373         }
374 }
375
376 void
377 ScaleLayout::build_scale_menu ()
378 {
379         vector<string> v;
380
381         /* must match in which enums are declared in push2.h
382          */
383
384         v.push_back ("Dorian");
385         v.push_back ("Ionian (Major)");
386         v.push_back ("Aeolian (Minor)");
387         v.push_back ("Harmonic Minor");
388         v.push_back ("MelodicMinor Asc.");
389         v.push_back ("MelodicMinor Desc.");
390         v.push_back ("Phrygian");
391         v.push_back ("Lydian");
392         v.push_back ("Mixolydian");
393         v.push_back ("Locrian");
394         v.push_back ("Pentatonic Major");
395         v.push_back ("Pentatonic Minor");
396         v.push_back ("Chromatic");
397         v.push_back ("Blues Scale");
398         v.push_back ("Neapolitan Minor");
399         v.push_back ("Neapolitan Major");
400         v.push_back ("Oriental");
401         v.push_back ("Double Harmonic");
402         v.push_back ("Enigmatic");
403         v.push_back ("Hirajoshi");
404         v.push_back ("Hungarian Minor");
405         v.push_back ("Hungarian Major");
406         v.push_back ("Kumoi");
407         v.push_back ("Iwato");
408         v.push_back ("Hindu");
409         v.push_back ("Spanish 8 Tone");
410         v.push_back ("Pelog");
411         v.push_back ("Hungarian Gypsy");
412         v.push_back ("Overtone");
413         v.push_back ("Leading Whole Tone");
414         v.push_back ("Arabian");
415         v.push_back ("Balinese");
416         v.push_back ("Gypsy");
417         v.push_back ("Mohammedan");
418         v.push_back ("Javanese");
419         v.push_back ("Persian");
420         v.push_back ("Algeria");
421
422         scale_menu = new Push2Menu (this, v);
423         scale_menu->Rearranged.connect (menu_connections, invalidator (*this), boost::bind (&ScaleLayout::menu_rearranged, this), &p2);
424
425         scale_menu->set_layout (6, 6);
426         scale_menu->set_text_color (p2.get_color (Push2::ParameterName));
427         scale_menu->set_active_color (p2.get_color (Push2::LightBackground));
428
429         Pango::FontDescription fd ("Sans Bold 8");
430         scale_menu->set_font_description (fd);
431
432         /* move menu into position so that its leftmost column is in the
433          * 2nd-from-left column of the display/button layout.
434          */
435
436         scale_menu->set_position (Duple (10 + Push2Canvas::inter_button_spacing(), 40));
437
438         /* listen for changes */
439
440         scale_menu->ActiveChanged.connect (menu_connections, invalidator (*this), boost::bind (&ScaleLayout::mode_changed, this), &p2);
441 }
442
443 void
444 ScaleLayout::show_root_state ()
445 {
446         if (!parent()) {
447                 /* don't do this stuff if we're not visible */
448                 return;
449         }
450
451         if (p2.in_key()) {
452                 chromatic_text->set_color (change_alpha (chromatic_text->color(), unselected_root_alpha));
453                 inkey_text->set_color (change_alpha (inkey_text->color(), 1.0));
454         } else {
455                 inkey_text->set_color (change_alpha (chromatic_text->color(), unselected_root_alpha));
456                 chromatic_text->set_color (change_alpha (inkey_text->color(), 1.0));
457         }
458
459         Pango::FontDescription fd_bold ("Sans Bold 10");
460         Pango::FontDescription fd ("Sans 10");
461
462         uint32_t highlight_text = 0;
463         vector<Text*>* none_text_array;
464         vector<Text*>* one_text_array;
465         Push2::ButtonID bid;
466
467         switch (p2.scale_root()) {
468         case 0:
469                 highlight_text = 1;
470                 none_text_array = &lower_text;
471                 one_text_array = &upper_text;
472                 bid = Push2::Upper2;
473                 break;
474         case 1:
475                 highlight_text = 5;
476                 none_text_array = &lower_text;
477                 one_text_array = &upper_text;
478                 bid = Push2::Lower6;
479                 break;
480         case 2:
481                 highlight_text = 3;
482                 none_text_array = &lower_text;
483                 one_text_array = &upper_text;
484                 bid = Push2::Upper4;
485                 break;
486         case 3:
487                 highlight_text = 3;
488                 none_text_array = &upper_text;
489                 one_text_array = &lower_text;
490                 bid = Push2::Lower4;
491                 break;
492         case 4:
493                 highlight_text = 5;
494                 none_text_array = &lower_text;
495                 one_text_array = &upper_text;
496                 bid = Push2::Upper6;
497                 break;
498         case 5:
499                 highlight_text = 1;
500                 none_text_array = &upper_text;
501                 one_text_array = &lower_text;
502                 bid = Push2::Lower2;
503                 break;
504         case 6:
505                 highlight_text = 6;
506                 none_text_array = &upper_text;
507                 one_text_array = &lower_text;
508                 bid = Push2::Lower7;
509                 break;
510         case 7:
511                 highlight_text = 2;
512                 none_text_array = &lower_text;
513                 one_text_array = &upper_text;
514                 bid = Push2::Upper3;
515                 break;
516         case 8:
517                 highlight_text = 4;
518                 none_text_array = &upper_text;
519                 one_text_array = &lower_text;
520                 bid = Push2::Lower5;
521                 break;
522         case 9:
523                 highlight_text = 4;
524                 none_text_array = &lower_text;
525                 one_text_array = &upper_text;
526                 bid = Push2::Upper5;
527                 break;
528         case 10:
529                 highlight_text = 2;
530                 none_text_array = &upper_text;
531                 one_text_array = &lower_text;
532                 bid = Push2::Lower3;
533                 break;
534         case 11:
535                 highlight_text = 6;
536                 none_text_array = &lower_text;
537                 one_text_array = &upper_text;
538                 bid = Push2::Upper7;
539                 break;
540         }
541
542         if (none_text_array) {
543
544                 for (uint32_t nn = 1; nn < 7; ++nn) {
545                         (*none_text_array)[nn]->set_font_description (fd);
546                         (*none_text_array)[nn]->set_color (change_alpha ((*none_text_array)[nn]->color(), unselected_root_alpha));
547
548                         if (nn == highlight_text) {
549                                 (*one_text_array)[nn]->set_font_description (fd_bold);
550                                 (*one_text_array)[nn]->set_color (change_alpha ((*one_text_array)[nn]->color(), 1.0));
551                         } else {
552                                 (*one_text_array)[nn]->set_font_description (fd);
553                                 (*one_text_array)[nn]->set_color (change_alpha ((*one_text_array)[nn]->color(), unselected_root_alpha));
554                         }
555                 }
556
557         }
558
559         Push2::Button* b = p2.button_by_id (bid);
560
561         if (b != root_button) {
562                 if (root_button) {
563                         /* turn the old one off (but not totally) */
564                         root_button->set_color (Push2::LED::DarkGray);
565                         root_button->set_state (Push2::LED::OneShot24th);
566                         p2.write (root_button->state_msg());
567                 }
568
569                 root_button = b;
570
571                 if (root_button) {
572                         /* turn the new one on */
573                         root_button->set_color (Push2::LED::White);
574                         root_button->set_state (Push2::LED::OneShot24th);
575                         p2.write (root_button->state_msg());
576                 }
577         }
578
579         scale_menu->set_active ((uint32_t) p2.mode ());
580 }
581
582 void
583 ScaleLayout::mode_changed ()
584 {
585         MusicalMode::Type m = (MusicalMode::Type) scale_menu->active();
586         p2.set_pad_scale (p2.scale_root(), p2.root_octave(), m, p2.in_key());
587 }
588
589 void
590 ScaleLayout::menu_rearranged ()
591 {
592         if (scale_menu->can_scroll_left()) {
593                 left_scroll_text->set ("<");
594                 close_text->hide ();
595         } else {
596                 left_scroll_text->set (string());
597                 close_text->show ();
598         }
599
600         if (scale_menu->can_scroll_right()) {
601                 right_scroll_text->set (">");
602         } else {
603                 right_scroll_text->set (string());
604         }
605 }
606
607 void
608 ScaleLayout::update_cursor_buttons ()
609 {
610         Push2::Button* b;
611         bool change;
612
613         b = p2.button_by_id (Push2::Up);
614         change = false;
615
616         if (scale_menu->active() == 0) {
617                 if (b->color_index() != Push2::LED::Black) {
618                         b->set_color (Push2::LED::Black);
619                         change = true;
620                 }
621         } else {
622                 if (b->color_index() != Push2::LED::White) {
623                         b->set_color (Push2::LED::White);
624                         change = true;
625                 }
626         }
627
628         if (change) {
629                 b->set_state (Push2::LED::OneShot24th);
630                 p2.write (b->state_msg());
631         }
632
633         /* down */
634
635         b = p2.button_by_id (Push2::Down);
636         change = false;
637
638         if (scale_menu->active() == scale_menu->items() - 1) {
639                 if (b->color_index() != Push2::LED::Black) {
640                         b->set_color (Push2::LED::Black);
641                         change = true;
642                 }
643         } else {
644                 if (b->color_index() != Push2::LED::White) {
645                         b->set_color (Push2::LED::White);
646                         change = true;
647                 }
648
649         }
650         if (change) {
651                 b->set_color (Push2::LED::OneShot24th);
652                 p2.write (b->state_msg());
653         }
654
655         /* left */
656
657         b = p2.button_by_id (Push2::Left);
658         change = false;
659
660         if (scale_menu->active() < scale_menu->rows()) {
661                 if (b->color_index() != Push2::LED::Black) {
662                         b->set_color (Push2::LED::Black);
663                         change = true;
664                 }
665         } else {
666                 if (b->color_index() != Push2::LED::White) {
667                         b->set_color (Push2::LED::White);
668                         change = true;
669                 }
670
671         }
672         if (change) {
673                 b->set_color (Push2::LED::OneShot24th);
674                 p2.write (b->state_msg());
675         }
676
677         /* right */
678
679         b = p2.button_by_id (Push2::Right);
680         change = false;
681
682         if (scale_menu->active() > (scale_menu->items() - scale_menu->rows())) {
683                 if (b->color_index() != Push2::LED::Black) {
684                         b->set_color (Push2::LED::Black);
685                         change = true;
686                 }
687         } else {
688                 if (b->color_index() != Push2::LED::White) {
689                         b->set_color (Push2::LED::White);
690                         change = true;
691                 }
692
693         }
694
695         if (change) {
696                 b->set_color (Push2::LED::OneShot24th);
697                 p2.write (b->state_msg());
698         }
699 }