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