push2: tweak layout APIs etc.
[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 <cairomm/region.h>
20 #include <pangomm/layout.h>
21
22 #include "pbd/compose.h"
23 #include "pbd/convert.h"
24 #include "pbd/debug.h"
25 #include "pbd/failed_constructor.h"
26 #include "pbd/file_utils.h"
27 #include "pbd/search_path.h"
28 #include "pbd/enumwriter.h"
29
30 #include "midi++/parser.h"
31 #include "timecode/time.h"
32 #include "timecode/bbt_time.h"
33
34 #include "ardour/async_midi_port.h"
35 #include "ardour/audioengine.h"
36 #include "ardour/debug.h"
37 #include "ardour/filesystem_paths.h"
38 #include "ardour/midiport_manager.h"
39 #include "ardour/midi_track.h"
40 #include "ardour/midi_port.h"
41 #include "ardour/session.h"
42 #include "ardour/tempo.h"
43 #include "ardour/utils.h"
44 #include "ardour/vca_manager.h"
45
46 #include "canvas/colors.h"
47 #include "canvas/line.h"
48 #include "canvas/rectangle.h"
49 #include "canvas/text.h"
50
51 #include "gtkmm2ext/gui_thread.h"
52
53 #include "canvas.h"
54 #include "knob.h"
55 #include "level_meter.h"
56 #include "mix.h"
57 #include "push2.h"
58 #include "utils.h"
59
60 #include "pbd/i18n.h"
61
62 using namespace ARDOUR;
63 using namespace std;
64 using namespace PBD;
65 using namespace Glib;
66 using namespace ArdourSurface;
67 using namespace ArdourCanvas;
68
69 MixLayout::MixLayout (Push2& p, Session & s, std::string const & name)
70         : Push2Layout (p, s, name)
71         , bank_start (0)
72         , vpot_mode (Volume)
73 {
74         /* background */
75
76         bg = new Rectangle (this);
77         bg->set (Rect (0, 0, display_width(), display_height()));
78         bg->set_fill_color (p2.get_color (Push2::DarkBackground));
79
80         /* upper line */
81
82         upper_line = new Line (this);
83         upper_line->set (Duple (0, 22.5), Duple (display_width(), 22.5));
84         upper_line->set_outline_color (p2.get_color (Push2::LightBackground));
85
86         Pango::FontDescription fd2 ("Sans 10");
87
88         for (int n = 0; n < 8; ++n) {
89
90                 /* background for text labels for knob function */
91
92                 Rectangle* r = new Rectangle (this);
93                 Coord x0 = 10 + (n*Push2Canvas::inter_button_spacing()) - 5;
94                 r->set (Rect (x0, 2, x0 + Push2Canvas::inter_button_spacing(), 2 + 21));
95                 upper_backgrounds.push_back (r);
96
97                 r = new Rectangle (this);
98                 r->set (Rect (x0, 137, x0 + Push2Canvas::inter_button_spacing(), 137 + 21));
99                 lower_backgrounds.push_back (r);
100
101                 /* text labels for knob function*/
102
103                 Text* t = new Text (this);
104                 t->set_font_description (fd2);
105                 t->set_color (p2.get_color (Push2::ParameterName));
106                 t->set_position (Duple (10 + (n*Push2Canvas::inter_button_spacing()), 5));
107
108                 string txt;
109                 switch (n) {
110                 case 0:
111                         txt = _("Volumes");
112                         break;
113                 case 1:
114                         txt = _("Pans");
115                         break;
116                 case 2:
117                         txt = _("Pan Widths");
118                         break;
119                 case 3:
120                         txt = _("A Sends");
121                         break;
122                 case 4:
123                         txt = _("B Sends");
124                         break;
125                 case 5:
126                         txt = _("C Sends");
127                         break;
128                 case 6:
129                         txt = _("D Sends");
130                         break;
131                 case 7:
132                         txt = _("E Sends");
133                         break;
134                 }
135                 t->set (txt);
136                 upper_text.push_back (t);
137
138                 /* GainMeters */
139
140                 gain_meter[n] = new GainMeter (this, p2);
141                 gain_meter[n]->set_position (Duple (40 + (n * Push2Canvas::inter_button_spacing()), 95));
142
143                 /* stripable names */
144
145                 t = new Text (this);
146                 t->set_font_description (fd2);
147                 t->set_color (p2.get_color (Push2::ParameterName));
148                 t->set_position (Duple (10 + (n*Push2Canvas::inter_button_spacing()), 140));
149                 lower_text.push_back (t);
150
151         }
152
153         mode_button = p2.button_by_id (Push2::Upper1);
154
155         session.RouteAdded.connect (session_connections, invalidator(*this), boost::bind (&MixLayout::stripables_added, this), &p2);
156         session.vca_manager().VCAAdded.connect (session_connections, invalidator (*this), boost::bind (&MixLayout::stripables_added, this), &p2);
157 }
158
159 MixLayout::~MixLayout ()
160 {
161         // Item destructor deletes all children
162 }
163
164 void
165 MixLayout::show ()
166 {
167         Push2::ButtonID upper_buttons[] = { Push2::Upper1, Push2::Upper2, Push2::Upper3, Push2::Upper4,
168                                             Push2::Upper5, Push2::Upper6, Push2::Upper7, Push2::Upper8 };
169
170
171         for (size_t n = 0; n < sizeof (upper_buttons) / sizeof (upper_buttons[0]); ++n) {
172                 Push2::Button* b = p2.button_by_id (upper_buttons[n]);
173
174                 if (b != mode_button) {
175                         b->set_color (Push2::LED::DarkGray);
176                 } else {
177                         b->set_color (Push2::LED::White);
178                 }
179                 b->set_state (Push2::LED::OneShot24th);
180                 p2.write (b->state_msg());
181         }
182
183         switch_bank (bank_start);
184
185         Container::show ();
186 }
187
188 void
189 MixLayout::render (Rect const& area, Cairo::RefPtr<Cairo::Context> context) const
190 {
191         Container::render (area, context);
192 }
193
194 void
195 MixLayout::button_upper (uint32_t n)
196 {
197         Push2::Button* b;
198         switch (n) {
199         case 0:
200                 vpot_mode = Volume;
201                 b = p2.button_by_id (Push2::Upper1);
202                 break;
203         case 1:
204                 vpot_mode = PanAzimuth;
205                 b = p2.button_by_id (Push2::Upper2);
206                 break;
207         case 2:
208                 vpot_mode = PanWidth;
209                 b = p2.button_by_id (Push2::Upper3);
210                 break;
211         case 3:
212                 vpot_mode = Send1;
213                 b = p2.button_by_id (Push2::Upper4);
214                 break;
215         case 4:
216                 vpot_mode = Send2;
217                 b = p2.button_by_id (Push2::Upper5);
218                 break;
219         case 5:
220                 vpot_mode = Send3;
221                 b = p2.button_by_id (Push2::Upper6);
222                 break;
223         case 6:
224                 vpot_mode = Send4;
225                 b = p2.button_by_id (Push2::Upper7);
226                 break;
227         case 7:
228                 vpot_mode = Send5;
229                 b = p2.button_by_id (Push2::Upper8);
230                 break;
231         }
232
233         if (b != mode_button) {
234                 mode_button->set_color (Push2::LED::Black);
235                 mode_button->set_state (Push2::LED::OneShot24th);
236                 p2.write (mode_button->state_msg());
237         }
238
239         mode_button = b;
240
241         show_vpot_mode ();
242 }
243
244 void
245 MixLayout::show_vpot_mode ()
246 {
247         mode_button->set_color (Push2::LED::White);
248         mode_button->set_state (Push2::LED::OneShot24th);
249         p2.write (mode_button->state_msg());
250
251         for (int s = 0; s < 8; ++s) {
252                 upper_backgrounds[s]->hide ();
253                 upper_text[s]->set_color (p2.get_color (Push2::ParameterName));
254         }
255
256         uint32_t n = 0;
257
258         boost::shared_ptr<AutomationControl> ac;
259         switch (vpot_mode) {
260         case Volume:
261                 for (int s = 0; s < 8; ++s) {
262                         if (stripable[s]) {
263                                 gain_meter[s]->knob->set_controllable (stripable[s]->gain_control());
264                                 boost::shared_ptr<PeakMeter> pm = stripable[s]->peak_meter(); 
265                                 if (pm) {
266                                         gain_meter[s]->meter->set_meter (pm.get());
267                                 } else {
268                                         gain_meter[s]->meter->set_meter (0);
269                                 }
270                         } else {
271                                 gain_meter[s]->knob->set_controllable (boost::shared_ptr<AutomationControl>());
272                                 gain_meter[s]->meter->set_meter (0);
273                         }
274                         gain_meter[s]->knob->remove_flag (Push2Knob::ArcToZero);
275                         gain_meter[s]->meter->show ();
276                 }
277                 n = 0;
278                 break;
279         case PanAzimuth:
280                 for (int s = 0; s < 8; ++s) {
281                         if (stripable[s]) {
282                                 gain_meter[s]->knob->set_controllable (stripable[s]->pan_azimuth_control());
283                                 gain_meter[s]->knob->add_flag (Push2Knob::ArcToZero);
284                         } else {
285                                 gain_meter[s]->knob->set_controllable (boost::shared_ptr<AutomationControl>());
286
287                         }
288                         gain_meter[s]->meter->hide ();
289                 }
290                 n = 1;
291                 break;
292         case PanWidth:
293                 for (int s = 0; s < 8; ++s) {
294                         if (stripable[s]) {
295                                 gain_meter[s]->knob->set_controllable (stripable[s]->pan_width_control());
296                         } else {
297                                 gain_meter[s]->knob->set_controllable (boost::shared_ptr<AutomationControl>());
298
299                         }
300                         gain_meter[s]->knob->remove_flag (Push2Knob::ArcToZero);
301                         gain_meter[s]->meter->hide ();
302                 }
303                 n = 2;
304                 break;
305         case Send1:
306                 for (int s = 0; s < 8; ++s) {
307                         if (stripable[s]) {
308                                 gain_meter[s]->knob->set_controllable (stripable[s]->send_level_controllable (0));
309                         } else {
310                                 gain_meter[s]->knob->set_controllable (boost::shared_ptr<AutomationControl>());
311
312                         }
313                         gain_meter[s]->knob->remove_flag (Push2Knob::ArcToZero);
314                         gain_meter[s]->meter->hide ();
315                 }
316                 n = 3;
317                 break;
318         case Send2:
319                 for (int s = 0; s < 8; ++s) {
320                         if (stripable[s]) {
321                                 gain_meter[s]->knob->set_controllable (stripable[s]->send_level_controllable (1));
322                         } else {
323                                 gain_meter[s]->knob->set_controllable (boost::shared_ptr<AutomationControl>());
324
325                         }
326                         gain_meter[s]->knob->remove_flag (Push2Knob::ArcToZero);
327                         gain_meter[s]->meter->hide ();
328                 }
329                 n = 4;
330                 break;
331         case Send3:
332                 for (int s = 0; s < 8; ++s) {
333                         if (stripable[s]) {
334                                 gain_meter[s]->knob->set_controllable (stripable[s]->send_level_controllable (2));
335                         } else {
336                                 gain_meter[s]->knob->set_controllable (boost::shared_ptr<AutomationControl>());
337
338                         }
339                         gain_meter[s]->knob->remove_flag (Push2Knob::ArcToZero);
340                         gain_meter[s]->meter->hide ();
341                 }
342                 n = 5;
343                 break;
344         case Send4:
345                 for (int s = 0; s < 8; ++s) {
346                         if (stripable[s]) {
347                                 gain_meter[s]->knob->set_controllable (stripable[s]->send_level_controllable (3));
348                         } else {
349                                 gain_meter[s]->knob->set_controllable (boost::shared_ptr<AutomationControl>());
350
351                         }
352                         gain_meter[s]->knob->remove_flag (Push2Knob::ArcToZero);
353                         gain_meter[s]->meter->hide ();
354                 }
355                 n = 6;
356                 break;
357         case Send5:
358                 for (int s = 0; s < 8; ++s) {
359                         if (stripable[s]) {
360                                 gain_meter[s]->knob->set_controllable (stripable[s]->send_level_controllable (4));
361                         } else {
362                                 gain_meter[s]->knob->set_controllable (boost::shared_ptr<AutomationControl>());
363
364                         }
365                         gain_meter[s]->knob->remove_flag (Push2Knob::ArcToZero);
366                         gain_meter[s]->meter->hide ();
367                 }
368                 n = 7;
369                 break;
370         default:
371                 break;
372         }
373
374         upper_backgrounds[n]->set_fill_color (p2.get_color (Push2::ParameterName));
375         upper_backgrounds[n]->set_outline_color (p2.get_color (Push2::ParameterName));
376         upper_backgrounds[n]->show ();
377         upper_text[n]->set_color (contrasting_text_color (p2.get_color (Push2::ParameterName)));
378 }
379
380 void
381 MixLayout::button_mute ()
382 {
383         boost::shared_ptr<Stripable> s = ControlProtocol::first_selected_stripable();
384         if (s) {
385                 boost::shared_ptr<AutomationControl> ac = s->mute_control();
386                 if (ac) {
387                         ac->set_value (!ac->get_value(), PBD::Controllable::UseGroup);
388                 }
389         }
390 }
391
392 void
393 MixLayout::button_solo ()
394 {
395         boost::shared_ptr<Stripable> s = ControlProtocol::first_selected_stripable();
396         if (s) {
397                 boost::shared_ptr<AutomationControl> ac = s->solo_control();
398                 if (ac) {
399                         ac->set_value (!ac->get_value(), PBD::Controllable::UseGroup);
400                 }
401         }
402 }
403
404 void
405 MixLayout::button_lower (uint32_t n)
406 {
407         if (!stripable[n]) {
408                 return;
409         }
410
411         ControlProtocol::SetStripableSelection (stripable[n]);
412 }
413
414 void
415 MixLayout::strip_vpot (int n, int delta)
416 {
417         boost::shared_ptr<Controllable> ac = gain_meter[n]->knob->controllable();
418
419         if (ac) {
420                 ac->set_value (ac->interface_to_internal (
421                                        min (ac->upper(), max (ac->lower(), ac->internal_to_interface (ac->get_value()) + (delta/256.0)))),
422                                PBD::Controllable::UseGroup);
423         }
424 }
425
426 void
427 MixLayout::strip_vpot_touch (int n, bool touching)
428 {
429         if (stripable[n]) {
430                 boost::shared_ptr<AutomationControl> ac = stripable[n]->gain_control();
431                 if (ac) {
432                         if (touching) {
433                                 ac->start_touch (session.audible_frame());
434                         } else {
435                                 ac->stop_touch (true, session.audible_frame());
436                         }
437                 }
438         }
439 }
440
441 void
442 MixLayout::stripable_property_change (PropertyChange const& what_changed, uint32_t which)
443 {
444         if (what_changed.contains (Properties::color)) {
445                 lower_backgrounds[which]->set_fill_color (stripable[which]->presentation_info().color());
446
447                 if (stripable[which]->presentation_info().selected()) {
448                         lower_text[which]->set_fill_color (contrasting_text_color (stripable[which]->presentation_info().color()));
449                         /* might not be a MIDI track, in which case this will
450                            do nothing
451                         */
452                         p2.update_selection_color ();
453                 }
454         }
455
456         if (what_changed.contains (Properties::hidden)) {
457                 switch_bank (bank_start);
458         }
459
460         if (what_changed.contains (Properties::selected)) {
461
462                 if (!stripable[which]) {
463                         return;
464                 }
465
466                 if (stripable[which]->presentation_info().selected()) {
467                         show_selection (which);
468                 } else {
469                         hide_selection (which);
470                 }
471         }
472
473 }
474
475 void
476 MixLayout::show_selection (uint32_t n)
477 {
478         lower_backgrounds[n]->show ();
479         lower_backgrounds[n]->set_fill_color (stripable[n]->presentation_info().color());
480         lower_text[n]->set_color (ArdourCanvas::contrasting_text_color (lower_backgrounds[n]->fill_color()));
481 }
482
483 void
484 MixLayout::hide_selection (uint32_t n)
485 {
486         lower_backgrounds[n]->hide ();
487         if (stripable[n]) {
488                 lower_text[n]->set_color (stripable[n]->presentation_info().color());
489         }
490 }
491
492 void
493 MixLayout::solo_changed (uint32_t n)
494 {
495         solo_mute_changed (n);
496 }
497
498 void
499 MixLayout::mute_changed (uint32_t n)
500 {
501         solo_mute_changed (n);
502 }
503
504 void
505 MixLayout::solo_mute_changed (uint32_t n)
506 {
507         string shortname = short_version (stripable[n]->name(), 10);
508         string text;
509         boost::shared_ptr<AutomationControl> ac;
510         ac = stripable[n]->solo_control();
511         if (ac && ac->get_value()) {
512                 text += "* ";
513         }
514         boost::shared_ptr<MuteControl> mc;
515         mc = stripable[n]->mute_control ();
516         if (mc) {
517                 if (mc->muted_by_self_or_masters()) {
518                         text += "! ";
519                 } else if (mc->muted_by_others_soloing()) {
520                         text += "- "; // it would be nice to use Unicode mute"\uD83D\uDD07 ";
521                 }
522         }
523         text += shortname;
524         lower_text[n]->set (text);
525 }
526
527 void
528 MixLayout::switch_bank (uint32_t base)
529 {
530         stripable_connections.drop_connections ();
531
532         /* work backwards so we can tell if we should actually switch banks */
533
534         boost::shared_ptr<Stripable> s[8];
535         uint32_t different = 0;
536
537         for (int n = 0; n < 8; ++n) {
538                 s[n] = session.get_remote_nth_stripable (base+n, PresentationInfo::Flag (PresentationInfo::Route|PresentationInfo::VCA));
539                 if (s[n] != stripable[n]) {
540                         different++;
541                 }
542         }
543
544         if (!s[0]) {
545                 /* not even the first stripable exists, do nothing */
546                 return;
547         }
548
549         for (int n = 0; n < 8; ++n) {
550                 stripable[n] = s[n];
551         }
552
553         /* at least one stripable in this bank */
554
555         bank_start = base;
556
557         for (int n = 0; n < 8; ++n) {
558
559                 if (!stripable[n]) {
560                         lower_text[n]->hide ();
561                         hide_selection (n);
562                 } else {
563
564                         lower_text[n]->show ();
565
566                         /* stripable goes away? refill the bank, starting at the same point */
567
568                         stripable[n]->DropReferences.connect (stripable_connections, invalidator (*this), boost::bind (&MixLayout::switch_bank, this, bank_start), &p2);
569                         stripable[n]->presentation_info().PropertyChanged.connect (stripable_connections, invalidator (*this), boost::bind (&MixLayout::stripable_property_change, this, _1, n), &p2);
570                         stripable[n]->solo_control()->Changed.connect (stripable_connections, invalidator (*this), boost::bind (&MixLayout::solo_changed, this, n), &p2);
571                         stripable[n]->mute_control()->Changed.connect (stripable_connections, invalidator (*this), boost::bind (&MixLayout::mute_changed, this, n), &p2);
572
573                         if (stripable[n]->presentation_info().selected()) {
574                                 show_selection (n);
575                         } else {
576                                 hide_selection (n);
577                         }
578
579                         /* this will set lower text to the correct value (basically
580                            the stripable name)
581                         */
582
583                         solo_mute_changed (n);
584
585                         gain_meter[n]->knob->set_text_color (stripable[n]->presentation_info().color());
586                         gain_meter[n]->knob->set_arc_start_color (stripable[n]->presentation_info().color());
587                         gain_meter[n]->knob->set_arc_end_color (stripable[n]->presentation_info().color());
588                 }
589
590
591                 Push2::Button* b;
592
593                 switch (n) {
594                 case 0:
595                         b = p2.button_by_id (Push2::Lower1);
596                         break;
597                 case 1:
598                         b = p2.button_by_id (Push2::Lower2);
599                         break;
600                 case 2:
601                         b = p2.button_by_id (Push2::Lower3);
602                         break;
603                 case 3:
604                         b = p2.button_by_id (Push2::Lower4);
605                         break;
606                 case 4:
607                         b = p2.button_by_id (Push2::Lower5);
608                         break;
609                 case 5:
610                         b = p2.button_by_id (Push2::Lower6);
611                         break;
612                 case 6:
613                         b = p2.button_by_id (Push2::Lower7);
614                         break;
615                 case 7:
616                         b = p2.button_by_id (Push2::Lower8);
617                         break;
618                 }
619
620                 if (stripable[n]) {
621                         b->set_color (p2.get_color_index (stripable[n]->presentation_info().color()));
622                 } else {
623                         b->set_color (Push2::LED::Black);
624                 }
625
626                 b->set_state (Push2::LED::OneShot24th);
627                 p2.write (b->state_msg());
628         }
629
630         show_vpot_mode ();
631 }
632
633 void
634 MixLayout::button_right ()
635 {
636         switch_bank (max (0, bank_start + 8));
637 }
638
639 void
640 MixLayout::button_left ()
641 {
642         switch_bank (max (0, bank_start - 8));
643 }
644
645
646 void
647 MixLayout::button_select_press ()
648 {
649 }
650
651 void
652 MixLayout::button_select_release ()
653 {
654         if (!(p2.modifier_state() & Push2::ModSelect)) {
655                 /* somebody else used us as a modifier */
656                 return;
657         }
658
659         int selected = -1;
660
661         for (int n = 0; n < 8; ++n) {
662                 if (stripable[n]) {
663                         if (stripable[n]->presentation_info().selected()) {
664                                         selected = n;
665                                         break;
666                         }
667                 }
668         }
669
670         if (selected < 0) {
671
672                 /* no visible track selected, select first (if any) */
673
674                 if (stripable[0]) {
675                         ControlProtocol::SetStripableSelection (stripable[0]);
676                 }
677
678         } else {
679
680                 if (p2.modifier_state() & Push2::ModShift) {
681                         /* select prev */
682
683                         if (selected == 0) {
684                                 /* current selected is leftmost ... cancel selection,
685                                    switch banks by one, and select leftmost
686                                 */
687                                 if (bank_start != 0) {
688                                         ControlProtocol::ClearStripableSelection ();
689                                         switch_bank (bank_start-1);
690                                         if (stripable[0]) {
691                                                 ControlProtocol::SetStripableSelection (stripable[0]);
692                                         }
693                                 }
694                         } else {
695                                 /* select prev, if any */
696                                 int n = selected - 1;
697                                 while (n >= 0 && !stripable[n]) {
698                                         --n;
699                                 }
700                                 if (n >= 0) {
701                                         ControlProtocol::SetStripableSelection (stripable[n]);
702                                 }
703                         }
704
705                 } else {
706
707                         /* select next */
708
709                         if (selected == 7) {
710                                 /* current selected is rightmost ... cancel selection,
711                                    switch banks by one, and select righmost
712                                 */
713                                 ControlProtocol::ToggleStripableSelection (stripable[selected]);
714                                 switch_bank (bank_start+1);
715                                 if (stripable[7]) {
716                                         ControlProtocol::SetStripableSelection (stripable[7]);
717                                 }
718                         } else {
719                                 /* select next, if any */
720                                 int n = selected + 1;
721                                 while (n < 8 && !stripable[n]) {
722                                         ++n;
723                                 }
724
725                                 if (n != 8) {
726                                         ControlProtocol::SetStripableSelection (stripable[n]);
727                                 }
728                         }
729                 }
730         }
731 }
732
733 void
734 MixLayout::stripables_added ()
735 {
736         /* reload current bank */
737         switch_bank (bank_start);
738 }
739
740 void
741 MixLayout::button_down ()
742 {
743         p2.scroll_dn_1_track ();
744 }
745
746 void
747 MixLayout::button_up ()
748 {
749         p2.scroll_up_1_track ();
750 }
751
752 void
753 MixLayout::update_meters ()
754 {
755         if (vpot_mode != Volume) {
756                 return;
757         }
758
759         for (uint32_t n = 0; n < 8; ++n) {
760                 gain_meter[n]->meter->update_meters ();
761         }
762 }
763
764 MixLayout::GainMeter::GainMeter (Item* parent, Push2& p2)
765         : Container (parent)
766 {
767         knob = new Push2Knob (p2, this);
768         knob->set_radius (25);
769         /* leave position at (0,0) */
770
771         meter = new LevelMeter (p2, this, 90, ArdourCanvas::Meter::Vertical);
772         meter->set_position (Duple (40, -60));
773 }