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