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