fix (mostly) measure lines and click box text and meter markings
[ardour.git] / gtk2_ardour / gain_meter.cc
1 /*
2   Copyright (C) 2002 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   $Id$
19 */
20
21 #include <limits.h>
22
23 #include <ardour/io.h>
24 #include <ardour/route.h>
25 #include <ardour/route_group.h>
26 #include <ardour/session.h>
27 #include <ardour/session_route.h>
28 #include <ardour/dB.h>
29
30 #include <gtkmm2ext/utils.h>
31 #include <gtkmm2ext/pix.h>
32 #include <gtkmm2ext/fastmeter.h>
33 #include <gtkmm2ext/stop_signal.h>
34 #include <gtkmm2ext/barcontroller.h>
35 #include <midi++/manager.h>
36 #include <pbd/fastlog.h>
37
38 #include "ardour_ui.h"
39 #include "gain_meter.h"
40 #include "utils.h"
41 #include "logmeter.h"
42 #include "gui_thread.h"
43 #include "keyboard.h"
44
45 #include <ardour/session.h>
46 #include <ardour/route.h>
47
48 #include "i18n.h"
49 #include "misc_xpms"
50
51 using namespace ARDOUR;
52 using namespace Gtkmm2ext;
53 using namespace Gtk;
54 using namespace sigc;
55
56 sigc::signal<void> GainMeter::ResetAllPeakDisplays;
57 sigc::signal<void,RouteGroup*> GainMeter::ResetGroupPeakDisplays;
58 Pix* GainMeter::slider_pix = 0;
59
60 int
61 GainMeter::setup_slider_pix ()
62 {
63         vector<const char **> xpms;
64
65         xpms.push_back (vslider_rail_xpm);
66         xpms.push_back (vslider_slider_xpm);
67
68         if ((slider_pix = get_pix ("sliders", xpms, false)) == 0) {
69                 error << _("Cannot create slider pixmaps") << endmsg;
70                 return -1;
71         }
72
73         slider_pix->ref ();
74         return 0;
75 }
76
77 GainMeter::GainMeter (IO& io, Session& s)
78         : _io (io),
79           _session (s),
80           gain_slider (0),
81           // 0.781787 is the value needed for gain to be set to 0.
82           gain_adjustment (0.781787, 0.0, 1.0, 0.01, 0.1),
83           gain_display (&gain_adjustment, "MixerStripGainDisplay"),
84           gain_unit_label (_("dbFS")),
85           meter_point_label (_("pre")),
86           top_table (1, 2)
87         
88 {
89         if (slider_pix == 0) {
90                 setup_slider_pix ();
91         }
92
93         ignore_toggle = false;
94         meter_menu = 0;
95         
96         gain_slider = manage (new VSliderController (slider_pix,
97                                                      &gain_adjustment,
98                                                      & _io.midi_gain_control(),
99                                                      false));
100
101         gain_slider->signal_button_press_event().connect (mem_fun(*this, &GainMeter::start_gain_touch));
102         gain_slider->signal_button_release_event().connect (mem_fun(*this, &GainMeter::end_gain_touch));
103         gain_slider->set_name ("MixerGainMeter");
104
105         if (_session.midi_port()) {
106                 _io.set_midi_to_gain_function (slider_position_to_gain);
107                 _io.set_gain_to_midi_function (gain_to_slider_position);
108         }
109
110         gain_display.set_print_func (_gain_printer, this);
111
112         gain_unit_button.add (gain_unit_label);
113         gain_unit_button.set_name ("MixerStripGainUnitButton");
114         gain_unit_label.set_name ("MixerStripGainUnitButton");
115
116         top_table.set_col_spacings (2);
117         top_table.set_homogeneous (true);
118         top_table.attach (gain_unit_button, 0, 1, 0, 1);
119
120         Route* r;
121
122         if ((r = dynamic_cast<Route*> (&_io)) != 0) {
123                 r->meter_change.connect (mem_fun(*this, &GainMeter::meter_changed));
124                 meter_point_button.add (meter_point_label);
125                 meter_point_button.set_name ("MixerStripMeterPreButton");
126                 meter_point_label.set_name ("MixerStripMeterPreButton");
127                 
128                 switch (r->meter_point()) {
129                 case MeterInput:
130                         meter_point_label.set_text (_("input"));
131                         break;
132                         
133                 case MeterPreFader:
134                         meter_point_label.set_text (_("pre"));
135                         break;
136                         
137                 case MeterPostFader:
138                         meter_point_label.set_text (_("post"));
139                         break;
140                 }
141                 
142                 /* TRANSLATORS: this string should be longest of the strings
143                    used to describe meter points. In english, its "input".
144                 */
145                 
146                 set_size_request_to_display_given_text (meter_point_button, _("tupni"), 2, 2);
147
148                 meter_point_button.signal_button_press_event().connect (mem_fun(*this, &GainMeter::meter_press));
149                 meter_point_button.signal_button_release_event().connect (mem_fun(*this, &GainMeter::meter_release));
150
151                 top_table.attach (meter_point_button, 1, 2, 0, 1);
152         }
153         gain_display_box.set_spacing (2);
154         set_size_request_to_display_given_text (gain_display_frame, "-86.0", 2, 2);
155         gain_display_frame.set_shadow_type (Gtk::SHADOW_IN);
156         gain_display_frame.set_name ("BaseFrame");
157         gain_display_frame.add (gain_display);
158         gain_display_box.pack_start (gain_display_frame,  Gtk::PACK_SHRINK);
159
160         peak_display.set_name ("MixerStripPeakDisplay");
161         peak_display.add (peak_display_label);
162         set_size_request_to_display_given_text (peak_display_frame, "-86.0", 2, 2);
163         peak_display_frame.set_shadow_type (Gtk::SHADOW_IN);
164         peak_display_frame.set_name ("BaseFrame");
165         peak_display_frame.add (peak_display);
166         max_peak = minus_infinity();
167         peak_display_label.set_text (_("-inf"));
168
169         gain_display_box.pack_end (peak_display_frame,  Gtk::PACK_SHRINK);
170
171         meter_metric_area.set_size_request (18, -1);
172         meter_metric_area.set_name ("MeterMetricsStrip");
173
174         meter_packer.set_spacing (2);
175         fader_box.set_spacing (2);
176
177         fader_box.pack_start (*gain_slider,  Gtk::PACK_SHRINK);
178
179         hbox.set_spacing (4);
180         hbox.pack_start (fader_box,  Gtk::PACK_SHRINK);
181         hbox.pack_start (meter_packer,  Gtk::PACK_SHRINK);
182
183         set_spacing (4);
184         pack_start (top_table,  Gtk::PACK_SHRINK);
185         /* here's the culprit  gain display box*/
186         pack_start (gain_display_box,  Gtk::PACK_SHRINK);
187         pack_start (hbox,  Gtk::PACK_SHRINK);
188
189         _io.gain_changed.connect (mem_fun(*this, &GainMeter::gain_changed));
190
191         meter_metric_area.signal_expose_event().connect (mem_fun(*this, &GainMeter::meter_metrics_expose));
192         gain_adjustment.signal_value_changed().connect (mem_fun(*this, &GainMeter::gain_adjusted));
193         peak_display.signal_button_release_event().connect (mem_fun(*this, &GainMeter::peak_button_release));
194
195         _session.MeterHoldChanged.connect (mem_fun(*this, &GainMeter::meter_hold_changed));
196         
197         gain_changed (0);
198         update_gain_sensitive ();
199
200         ResetAllPeakDisplays.connect (mem_fun(*this, &GainMeter::reset_peak_display));
201         ResetGroupPeakDisplays.connect (mem_fun(*this, &GainMeter::reset_group_peak_display));
202 }
203
204 void
205 GainMeter::set_width (Width w)
206 {
207         switch (w) {
208         case Wide:
209                 peak_display_frame.show_all();
210                 break;
211         case Narrow:
212                 peak_display_frame.hide_all();
213                 break;
214         }
215
216         _width = w;
217         setup_meters ();
218 }
219
220 gint
221 GainMeter::meter_metrics_expose (GdkEventExpose *ev)
222 {
223         /* XXX optimize this so that it doesn't do it all everytime */
224
225         double fraction;
226         Glib::RefPtr<Gdk::Window> win (meter_metric_area.get_window());
227         Glib::RefPtr<Gdk::GC> fg_gc (meter_metric_area.get_style()->get_fg_gc (Gtk::STATE_NORMAL));
228         Glib::RefPtr<Gdk::GC> bg_gc (meter_metric_area.get_style()->get_bg_gc (Gtk::STATE_NORMAL));
229         gint x, y, width, height, depth;
230         gint pos;
231         int  db_points[] = { -50, -10, -3, 0, 6 };
232         uint32_t i;
233         char buf[32];
234         GdkRectangle base_rect;
235         GdkRectangle draw_rect;
236         int theight;
237         int twidth;
238
239         win->get_geometry (x, y, width, height, depth);
240         
241         base_rect.width = width;
242         base_rect.height = height;
243         base_rect.x = 0;
244         base_rect.y = 0;
245
246         gdk_rectangle_intersect (&ev->area, &base_rect, &draw_rect);
247         win->draw_rectangle (bg_gc, true, draw_rect.x, draw_rect.y, draw_rect.width, draw_rect.height);
248
249         Glib::RefPtr<Pango::Layout> layout = meter_metric_area.create_pango_layout("");
250
251         for (i = 0; i < sizeof (db_points)/sizeof (db_points[0]); ++i) {
252
253                 fraction = log_meter (db_points[i]);
254                 pos = height - (gint) floor (height * fraction);
255
256                 snprintf (buf, sizeof (buf), "%d", db_points[i]);
257
258                 layout->set_text (buf);
259                 layout->get_pixel_size (twidth, theight);
260
261                 win->draw_layout (fg_gc, width - twidth, pos + theight, layout);
262         }
263
264         return true;
265 }
266
267 GainMeter::~GainMeter ()
268 {
269
270         if (meter_menu) {
271                 delete meter_menu;
272         }
273
274         for (vector<MeterInfo>::iterator i = meters.begin(); i != meters.end(); i++) {
275                 if ((*i).meter) {
276                         delete (*i).meter;
277                 }
278         }
279 }
280
281 void
282 GainMeter::update_meters ()
283 {
284         vector<MeterInfo>::iterator i;
285         uint32_t n;
286         float peak;
287         char buf[32];
288         
289         for (n = 0, i = meters.begin(); i != meters.end(); ++i, ++n) {
290                 if ((*i).packed) {
291                         peak = _io.peak_input_power (n);
292
293                         if (_session.meter_falloff() == 0.0f || peak > (*i).meter->get_user_level()) {
294                                 (*i).meter->set (log_meter (peak), peak);
295                         }
296
297                         if (peak > max_peak) {
298                                 max_peak = peak;
299                                 /* set peak display */
300                                 snprintf (buf, sizeof(buf), "%.1f", max_peak);
301                                 peak_display_label.set_text (buf);
302
303                                 if (max_peak >= 0.0f) {
304                                         peak_display.set_name ("MixerStripPeakDisplayPeak");
305                                 }
306                         }
307                 }
308         }
309
310 }
311
312 void
313 GainMeter::update_meters_falloff ()
314 {
315         vector<MeterInfo>::iterator i;
316         uint32_t n;
317         float dbpeak;
318         
319         for (n = 0, i = meters.begin(); i != meters.end(); ++i, ++n) {
320                 if ((*i).packed) {
321                         // just do falloff
322                         //peak = (*i).meter->get_level() * _falloff_rate;
323                         dbpeak = (*i).meter->get_user_level() - _session.meter_falloff();
324
325                         dbpeak = std::max(dbpeak, -200.0f);
326                         
327                         // cerr << "tmplevel: " << tmplevel << endl;
328                         (*i).meter->set (log_meter (dbpeak), dbpeak);
329                 }
330         }
331         
332 }
333
334
335 void
336 GainMeter::meter_hold_changed()
337 {
338         ENSURE_GUI_THREAD(mem_fun(*this, &GainMeter::meter_hold_changed));
339         
340         vector<MeterInfo>::iterator i;
341         uint32_t n;
342         
343         for (n = 0, i = meters.begin(); i != meters.end(); ++i, ++n) {
344                 
345                 (*i).meter->set_hold_count ((uint32_t) floor(_session.meter_hold()));
346         }
347 }
348
349 void
350 GainMeter::hide_all_meters ()
351 {
352         bool remove_metric_area = false;
353
354         for (vector<MeterInfo>::iterator i = meters.begin(); i != meters.end(); ++i) {
355                 if ((*i).packed) {
356                         remove_metric_area = true;
357                         meter_packer.remove (*((*i).meter));
358                         (*i).packed = false;
359                 }
360         }
361
362         if (remove_metric_area) {
363                 if (meter_metric_area.get_parent()) {
364                         meter_packer.remove (meter_metric_area);
365                 }
366         }
367 }
368
369 void
370 GainMeter::setup_meters ()
371 {
372         uint32_t nmeters = _io.n_outputs();
373         guint16 width;
374
375         hide_all_meters ();
376
377         Route* r;
378
379         if ((r = dynamic_cast<Route*> (&_io)) != 0) {
380
381                 switch (r->meter_point()) {
382                 case MeterPreFader:
383                 case MeterInput:
384                         nmeters = r->n_inputs();
385                         break;
386                 case MeterPostFader:
387                         nmeters = r->n_outputs();
388                         break;
389                 }
390
391         } else {
392
393                 nmeters = _io.n_outputs();
394
395         }
396
397         if (nmeters == 0) {
398                 return;
399         }
400
401         if (_width == Wide) {
402                 meter_packer.pack_start (meter_metric_area, Gtk::PACK_SHRINK);
403                 meter_metric_area.show_all ();
404         }
405
406         if (nmeters <= 2) {
407                 width = regular_meter_width;
408         } else {
409                 width = thin_meter_width;
410         }
411
412         while (meters.size() < nmeters) {
413                 meters.push_back (MeterInfo());
414         }
415
416         for (uint32_t n = 0; n < nmeters; ++n) {
417                 if (meters[n].width != width) {
418                         delete meters[n].meter;
419                         meters[n].meter = new FastMeter ((uint32_t) floor (_session.meter_hold()), width, FastMeter::Vertical);
420                         meters[n].width = width;
421
422                         meters[n].meter->add_events (Gdk::BUTTON_RELEASE_MASK);
423                         meters[n].meter->signal_button_release_event().connect (bind (mem_fun(*this, &GainMeter::meter_button_release), n));
424                 }
425
426                 meter_packer.pack_start (*meters[n].meter, Gtk::PACK_SHRINK);
427                 meters[n].meter->show_all ();
428                 meters[n].packed = true;
429         }
430 }       
431
432 gint
433 GainMeter::peak_button_release (GdkEventButton* ev)
434 {
435         /* reset peak label */
436
437         if (ev->button == 1 && Keyboard::modifier_state_equals (ev->state, Keyboard::Control|Keyboard::Shift)) {
438                 ResetAllPeakDisplays ();
439         } else if (ev->button == 1 && Keyboard::modifier_state_equals (ev->state, Keyboard::Control)) {
440                 Route* r;
441                 if ((r = dynamic_cast<Route*> (&_io)) != 0) {
442                         ResetGroupPeakDisplays (r->mix_group());
443                 }
444         } else {
445                 reset_peak_display ();
446         }
447         return TRUE;
448 }
449
450 void
451 GainMeter::reset_peak_display ()
452 {
453         max_peak = minus_infinity();
454         peak_display_label.set_text (_("-inf"));
455         peak_display.set_name ("MixerStripPeakDisplay");
456 }
457
458 void
459 GainMeter::reset_group_peak_display (RouteGroup* group)
460 {
461         Route* r;
462         if ((r = dynamic_cast<Route*> (&_io)) != 0) {
463                 if (group == r->mix_group()) {
464                         reset_peak_display ();
465                 }
466         }
467 }
468
469 gint
470 GainMeter::meter_button_release (GdkEventButton* ev, uint32_t which)
471 {
472         switch (ev->button) {
473         case 1:
474                 meters[which].meter->clear();
475                 max_peak = minus_infinity();
476                 peak_display_label.set_text (_("-inf"));
477                 peak_display.set_name ("MixerStripPeakDisplay");
478                 break;
479
480         case 3:
481                 // popup_meter_menu (ev);
482                 break;
483         };
484
485         return TRUE;
486 }
487
488 void
489 GainMeter::popup_meter_menu (GdkEventButton *ev)
490 {
491         using namespace Menu_Helpers;
492
493         if (meter_menu == 0) {
494                 meter_menu = new Gtk::Menu;
495                 MenuList& items = meter_menu->items();
496
497                 items.push_back (MenuElem ("-inf .. +0dBFS"));
498                 items.push_back (MenuElem ("-10dB .. +0dBFS"));
499                 items.push_back (MenuElem ("-4 .. +0dBFS"));
500                 items.push_back (SeparatorElem());
501                 items.push_back (MenuElem ("-inf .. -2dBFS"));
502                 items.push_back (MenuElem ("-10dB .. -2dBFS"));
503                 items.push_back (MenuElem ("-4 .. -2dBFS"));
504         }
505
506         meter_menu->popup (1, ev->time);
507 }
508
509 void
510 GainMeter::_gain_printer (char buf[32], Gtk::Adjustment& adj, void *arg)
511 {
512         static_cast<GainMeter *>(arg)->gain_printer (buf, adj);
513 }
514
515 void
516 GainMeter::gain_printer (char buf[32], Gtk::Adjustment& adj)
517 {
518         float v = adj.get_value();
519
520         if (v == 0.0) {
521                 strcpy (buf, _("-inf"));
522         } else {
523                 snprintf (buf, 32, "%.1f", coefficient_to_dB (slider_position_to_gain (v)));
524         }
525 }
526
527 void
528 GainMeter::gain_adjusted ()
529 {
530         if (!ignore_toggle) {
531                 _io.set_gain (slider_position_to_gain (gain_adjustment.get_value()), this);
532         }
533 }
534
535 void
536 GainMeter::effective_gain_display ()
537 {
538         gfloat value = gain_to_slider_position (_io.effective_gain());
539         
540         if (gain_adjustment.get_value() != value) {
541                 ignore_toggle = true;
542                 gain_adjustment.set_value (value);
543                 ignore_toggle = false;
544         }
545 }
546
547 void
548 GainMeter::gain_changed (void *src)
549 {
550         Gtkmm2ext::UI::instance()->call_slot (mem_fun(*this, &GainMeter::effective_gain_display));
551 }
552
553 void
554 GainMeter::set_meter_strip_name (const char * name)
555 {
556         meter_metric_area.set_name (name);
557 }
558
559 void
560 GainMeter::set_fader_name (const char * name)
561 {
562         gain_slider->set_name (name);
563 }
564
565 void
566 GainMeter::update_gain_sensitive ()
567 {
568         static_cast<Gtkmm2ext::SliderController*>(gain_slider)->set_sensitive (!(_io.gain_automation_state() & Play));
569 }
570
571
572 static MeterPoint
573 next_meter_point (MeterPoint mp)
574 {
575         switch (mp) {
576         case MeterInput:
577                 return MeterPreFader;
578                 break;
579                 
580         case MeterPreFader:
581                 return MeterPostFader;
582                 break;
583                 
584         case MeterPostFader:
585                 return MeterInput;
586                 break;
587         }
588         /*NOTREACHED*/
589         return MeterInput;
590 }
591
592 gint
593 GainMeter::meter_press(GdkEventButton* ev)
594 {
595         Route* _route;
596
597         wait_for_release = false;
598
599         if ((_route = dynamic_cast<Route*>(&_io)) == 0) {
600                 return FALSE;
601         }
602
603         if (!ignore_toggle) {
604
605                 if (Keyboard::is_context_menu_event (ev)) {
606                         
607                         // no menu at this time.
608
609                 } else {
610
611                         if (ev->button == 2) {
612
613                                 // ctrl-button2 click is the midi binding click
614                                 // button2-click is "momentary"
615                                 
616                                 if (!Keyboard::modifier_state_equals (ev->state, Keyboard::ModifierMask (Keyboard::Control))) {
617                                         wait_for_release = true;
618                                         old_meter_point = _route->meter_point ();
619                                 }
620                         }
621
622                         if (ev->button == 1 || ev->button == 2) {
623
624                                 if (Keyboard::modifier_state_equals (ev->state, Keyboard::ModifierMask (Keyboard::Control|Keyboard::Shift))) {
625
626                                         /* ctrl-shift-click applies change to all routes */
627
628                                         _session.foreach_route (this, &GainMeter::set_meter_point, next_meter_point (_route->meter_point()));
629                                         
630                                 } else if (Keyboard::modifier_state_equals (ev->state, Keyboard::Control)) {
631
632                                         /* ctrl-click: solo mix group.
633                                            ctrl-button2 is MIDI learn.
634                                         */
635                                         
636                                         if (ev->button == 1) {
637                                                 set_mix_group_meter_point (*_route, next_meter_point (_route->meter_point()));
638                                         }
639                                         
640                                 } else {
641                                         
642                                         /* click: solo this route */
643                                         
644                                         _route->set_meter_point (next_meter_point (_route->meter_point()), this);
645                                 }
646                         }
647                 }
648         }
649
650         return stop_signal (meter_point_button, "button-press-event");
651
652 }
653
654 gint
655 GainMeter::meter_release(GdkEventButton* ev)
656 {
657         if(!ignore_toggle){
658                 if (wait_for_release){
659                         wait_for_release = false;
660                         set_meter_point (*(dynamic_cast<Route*>(&_io)), old_meter_point);
661                         stop_signal (meter_point_button, "button-release-event");
662                 }
663         }
664         return TRUE;
665 }
666
667 void
668 GainMeter::set_meter_point (Route& route, MeterPoint mp)
669 {
670         route.set_meter_point (mp, this);
671 }
672
673 void
674 GainMeter::set_mix_group_meter_point (Route& route, MeterPoint mp)
675 {
676         RouteGroup* mix_group;
677
678         if((mix_group = route.mix_group()) != 0){
679                 mix_group->apply (&Route::set_meter_point, mp, this);
680         } else {
681                 route.set_meter_point (mp, this);
682         }
683 }
684
685 void
686 GainMeter::meter_changed (void *src)
687 {
688         Route* r;
689
690         ENSURE_GUI_THREAD (bind (mem_fun(*this, &GainMeter::meter_changed), src));
691
692         if ((r = dynamic_cast<Route*> (&_io)) != 0) {
693
694                 switch (r->meter_point()) {
695                 case MeterInput:
696                         meter_point_label.set_text (_("input"));
697                         break;
698                         
699                 case MeterPreFader:
700                         meter_point_label.set_text (_("pre"));
701                         break;
702                         
703                 case MeterPostFader:
704                         meter_point_label.set_text (_("post"));
705                         break;
706                 }
707
708                 setup_meters ();
709         }
710 }
711
712 void
713 GainMeter::meter_point_clicked ()
714 {
715         Route* r;
716
717         if ((r = dynamic_cast<Route*> (&_io)) != 0) {
718
719         }
720 }
721
722 gint
723 GainMeter::start_gain_touch (GdkEventButton* ev)
724 {
725         _io.start_gain_touch ();
726         return FALSE;
727 }
728
729 gint
730 GainMeter::end_gain_touch (GdkEventButton* ev)
731 {
732         _io.end_gain_touch ();
733         return FALSE;
734 }