add peak-hold button (independent from mixer)
[ardour.git] / gtk2_ardour / meter_strip.cc
1 /*
2     Copyright (C) 2013 Paul Davis
3     Author: Robin Gareus
4
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14
15     You should have received a copy of the GNU General Public License
16     along with this program; if not, write to the Free Software
17     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 */
19
20 #include <list>
21
22 #include <sigc++/bind.h>
23
24 #include "ardour/session.h"
25 #include "ardour/route.h"
26 #include "ardour/route_group.h"
27 #include "ardour/meter.h"
28
29 #include "ardour/audio_track.h"
30 #include "ardour/midi_track.h"
31
32 #include <gtkmm2ext/gtk_ui.h>
33 #include <gtkmm2ext/utils.h>
34
35 #include "ardour_ui.h"
36 #include "logmeter.h"
37 #include "gui_thread.h"
38 #include "ardour_window.h"
39
40 #include "meterbridge.h"
41 #include "meter_strip.h"
42
43 #include "i18n.h"
44
45 using namespace ARDOUR;
46 using namespace PBD;
47 using namespace Gtk;
48 using namespace Gtkmm2ext;
49 using namespace std;
50
51 PBD::Signal1<void,MeterStrip*> MeterStrip::CatchDeletion;
52
53 MeterStrip::MetricPatterns MeterStrip::metric_patterns;
54 MeterStrip::TickPatterns MeterStrip::ticks_patterns;
55
56 MeterStrip::MeterStrip (Meterbridge& mtr, Session* sess, boost::shared_ptr<ARDOUR::Route> rt)
57         : AxisView(sess)
58         , RouteUI(sess)
59         , _route(rt)
60         , style_changed (false)
61 {
62         set_spacing(2);
63         RouteUI::set_route (rt);
64
65         int meter_width = 6;
66         if (_route->shared_peak_meter()->input_streams().n_total() == 1) {
67                 meter_width = 12;
68         }
69
70         // level meter + ticks
71         level_meter = new LevelMeter(sess);
72         level_meter->set_meter (_route->shared_peak_meter().get());
73         level_meter->clear_meters();
74         level_meter->setup_meters (350, meter_width, 6);
75         level_meter->pack_start (meter_metric_area, false, false);
76
77         Gtk::Alignment *meter_align = Gtk::manage (new Gtk::Alignment());
78         meter_align->set(0.5, 0.5, 0.0, 1.0);
79         meter_align->add(*level_meter);
80
81         meterbox.pack_start(meter_ticks1_area, true, false);
82         meterbox.pack_start(*meter_align, true, true);
83         meterbox.pack_start(meter_ticks2_area, true, false);
84
85         // peak display
86         peak_display.set_name ("MixerStripPeakDisplay");
87         set_size_request_to_display_given_text (peak_display, "-80.g", 2, 6);
88         max_peak = minus_infinity();
89         peak_display.set_label (_("-inf"));
90         peak_display.unset_flags (Gtk::CAN_FOCUS);
91
92         peakbx.pack_start(peak_display, true, true);
93         peakbx.set_size_request(-1, 16);
94
95         // add track-name label -- TODO ellipsize
96         label.set_text(_route->name().c_str());
97         label.set_name("MeterbridgeLabel");
98         label.set_angle(90.0);
99         label.set_alignment(0.5, 0.0);
100         label.set_size_request(12, 36);
101
102         // rec-enable button
103         btnbox.pack_start(*rec_enable_button, true, false);
104         btnbox.set_size_request(-1, 16);
105
106         pack_start (peakbx, false, false);
107         pack_start (meterbox, true, true);
108         pack_start (btnbox, false, false);
109         pack_start (label, false, false);
110
111         peak_display.show();
112         peakbx.show();
113         meter_ticks1_area.hide();
114         meter_ticks2_area.show();
115         meter_metric_area.show();
116         meterbox.show();
117         level_meter->show();
118         meter_align->show();
119         btnbox.show();
120         label.show();
121
122         _route->shared_peak_meter()->ConfigurationChanged.connect (
123                         route_connections, invalidator (*this), boost::bind (&MeterStrip::meter_configuration_changed, this, _1), gui_context()
124                         );
125         meter_configuration_changed (_route->shared_peak_meter()->input_streams ());
126
127         set_size_request_to_display_given_text (meter_metric_area, "-8888", 1, 0);
128         meter_metric_area.signal_expose_event().connect (
129                         sigc::mem_fun(*this, &MeterStrip::meter_metrics_expose));
130
131         meter_ticks1_area.set_size_request(4,-1);
132         meter_ticks2_area.set_size_request(4,-1);
133         meter_ticks1_area.signal_expose_event().connect (sigc::mem_fun(*this, &MeterStrip::meter_ticks1_expose));
134         meter_ticks2_area.signal_expose_event().connect (sigc::mem_fun(*this, &MeterStrip::meter_ticks2_expose));
135
136         _route->DropReferences.connect (route_connections, invalidator (*this), boost::bind (&MeterStrip::self_delete, this), gui_context());
137         _route->PropertyChanged.connect (route_connections, invalidator (*this), boost::bind (&MeterStrip::strip_property_changed, this, _1), gui_context());
138
139         peak_display.signal_button_release_event().connect (sigc::mem_fun(*this, &MeterStrip::peak_button_release), false);
140
141         UI::instance()->theme_changed.connect (sigc::mem_fun(*this, &MeterStrip::on_theme_changed));
142 }
143
144 MeterStrip::~MeterStrip ()
145 {
146         delete level_meter;
147         CatchDeletion (this);
148 }
149
150 void
151 MeterStrip::self_delete ()
152 {
153         delete this;
154 }
155
156 void
157 MeterStrip::update_rec_display ()
158 {
159         RouteUI::update_rec_display ();
160 }
161
162 std::string
163 MeterStrip::state_id() const
164 {
165         return string_compose ("mtrs %1", _route->id().to_s());
166 }
167
168 void
169 MeterStrip::set_button_names()
170 {
171         rec_enable_button->set_text (_("R"));
172 }
173
174 void
175 MeterStrip::strip_property_changed (const PropertyChange& what_changed)
176 {
177         if (!what_changed.contains (ARDOUR::Properties::name)) {
178                 return;
179         }
180         ENSURE_GUI_THREAD (*this, &MeterStrip::strip_name_changed, what_changed)
181         label.set_text(_route->name());
182 }
183
184 void
185 MeterStrip::fast_update ()
186 {
187         char buf[32];
188         float mpeak = level_meter->update_meters();
189         if (mpeak > max_peak) {
190                 max_peak = mpeak;
191                 if (mpeak <= -200.0f) {
192                         peak_display.set_label (_("-inf"));
193                 } else {
194                         snprintf (buf, sizeof(buf), "%.1f", mpeak);
195                         peak_display.set_label (buf);
196                 }
197
198                 if (mpeak >= 0.0f) {
199                         peak_display.set_name ("MixerStripPeakDisplayPeak");
200                 }
201         }
202 }
203
204 void
205 MeterStrip::display_metrics (bool show)
206 {
207         if (show) {
208                 meter_metric_area.show();
209                 meter_ticks1_area.hide();
210         } else {
211                 meter_metric_area.hide();
212                 meter_ticks1_area.show();
213         }
214 }
215
216 void
217 MeterStrip::on_theme_changed()
218 {
219         style_changed = true;
220 }
221
222 void
223 MeterStrip::meter_configuration_changed (ChanCount c)
224 {
225         int type = 0;
226         _types.clear ();
227
228         for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
229                 if (c.get (*i) > 0) {
230                         _types.push_back (*i);
231                         type |= 1 << (*i);
232                 }
233         }
234
235         // TODO draw Inactive routes or busses with different styles
236         if (boost::dynamic_pointer_cast<AudioTrack>(_route) == 0
237                         && boost::dynamic_pointer_cast<MidiTrack>(_route) == 0
238                         ) {
239                 meter_metric_area.set_name ("AudioBusMetrics");
240                 meter_ticks1_area.set_name ("AudioBusMetrics");
241                 meter_ticks2_area.set_name ("AudioBusMetrics");
242         }
243         else if (type == (1 << DataType::AUDIO)) {
244                 meter_metric_area.set_name ("AudioTrackMetrics");
245                 meter_ticks1_area.set_name ("AudioTrackMetrics");
246                 meter_ticks2_area.set_name ("AudioTrackMetrics");
247         }
248         else if (type == (1 << DataType::MIDI)) {
249                 meter_metric_area.set_name ("MidiTrackMetrics");
250                 meter_ticks1_area.set_name ("MidiTrackMetrics");
251                 meter_ticks2_area.set_name ("MidiTrackMetrics");
252         } else {
253                 meter_metric_area.set_name ("AudioMidiTrackMetrics");
254                 meter_ticks1_area.set_name ("AudioMidiTrackMetrics");
255                 meter_ticks2_area.set_name ("AudioMidiTrackMetrics");
256         }
257         style_changed = true;
258         meter_metric_area.queue_draw ();
259 }
260
261 void
262 MeterStrip::on_size_request (Gtk::Requisition* r)
263 {
264         style_changed = true;
265         VBox::on_size_request(r);
266 }
267
268 void
269 MeterStrip::on_size_allocate (Gtk::Allocation& a)
270 {
271         style_changed = true;
272         VBox::on_size_allocate(a);
273 }
274
275 /* XXX code-copy from gain_meter.cc -- TODO consolidate
276  *
277  * slightly different:
278  *  - ticks & label positions are swapped
279  *  - more ticks for audio (longer meter by default)
280  *  - right-aligned lables, unit-legend
281  *  - height limitation of FastMeter::max_pattern_metric_size is included here
282  */
283 cairo_pattern_t*
284 MeterStrip::render_metrics (Gtk::Widget& w, vector<DataType> types)
285 {
286         Glib::RefPtr<Gdk::Window> win (w.get_window());
287
288         gint width, height;
289         win->get_size (width, height);
290
291         cairo_surface_t* surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
292         cairo_t* cr = cairo_create (surface);
293         Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create(w.get_pango_context());
294
295
296         Pango::AttrList audio_font_attributes;
297         Pango::AttrList midi_font_attributes;
298         Pango::AttrList unit_font_attributes;
299
300         Pango::AttrFontDesc* font_attr;
301         Pango::FontDescription font;
302
303         font = Pango::FontDescription (""); // use defaults
304         //font = get_font_for_style("gain-fader");
305         //font = w.get_style()->get_font();
306
307         font.set_weight (Pango::WEIGHT_NORMAL);
308         font.set_size (10.0 * PANGO_SCALE);
309         font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
310         audio_font_attributes.change (*font_attr);
311         delete font_attr;
312
313         font.set_weight (Pango::WEIGHT_ULTRALIGHT);
314         font.set_stretch (Pango::STRETCH_ULTRA_CONDENSED);
315         font.set_size (7.5 * PANGO_SCALE);
316         font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
317         midi_font_attributes.change (*font_attr);
318         delete font_attr;
319
320         font.set_size (7.0 * PANGO_SCALE);
321         font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
322         unit_font_attributes.change (*font_attr);
323         delete font_attr;
324
325         cairo_move_to (cr, 0, 0);
326         cairo_rectangle (cr, 0, 0, width, height);
327         {
328                 Gdk::Color c = w.get_style()->get_bg (Gtk::STATE_NORMAL);
329                 cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
330         }
331         cairo_fill (cr);
332
333         height = min(1024, height); // XXX see FastMeter::max_pattern_metric_size
334
335         for (vector<DataType>::const_iterator i = types.begin(); i != types.end(); ++i) {
336
337                 Gdk::Color c;
338
339                 if (types.size() > 1) {
340                         /* we're overlaying more than 1 set of marks, so use different colours */
341                         Gdk::Color c;
342                         switch (*i) {
343                         case DataType::AUDIO:
344                                 c = w.get_style()->get_fg (Gtk::STATE_NORMAL);
345                                 cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
346                                 break;
347                         case DataType::MIDI:
348                                 c = w.get_style()->get_fg (Gtk::STATE_ACTIVE);
349                                 cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
350                                 break;
351                         }
352                 } else {
353                         c = w.get_style()->get_fg (Gtk::STATE_NORMAL);
354                         cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
355                 }
356
357                 vector<int> points;
358
359                 switch (*i) {
360                 case DataType::AUDIO:
361                         layout->set_attributes (audio_font_attributes);
362                         points.push_back (-50);
363                         points.push_back (-40);
364                         points.push_back (-30);
365                         points.push_back (-20);
366                         points.push_back (-18);
367                         points.push_back (-10);
368                         points.push_back (-6);
369                         points.push_back (-3);
370                         points.push_back (0);
371                         points.push_back (4);
372                         break;
373
374                 case DataType::MIDI:
375                         layout->set_attributes (midi_font_attributes);
376                         points.push_back (0);
377                         if (types.size() == 1) {
378                                 points.push_back (32);
379                         } else {
380                                 /* tweak so as not to overlay the -30dB mark */
381                                 points.push_back (48);
382                         }
383                         if (types.size() == 1) {
384                                 points.push_back (64); // very close to -18dB
385                                 points.push_back (96); // overlays with -6dB mark
386                         } else {
387                                 points.push_back (72);
388                                 points.push_back (88);
389                         }
390                         points.push_back (127);
391                         break;
392                 }
393
394                 char buf[32];
395
396                 for (vector<int>::const_iterator j = points.begin(); j != points.end(); ++j) {
397
398                         float fraction = 0;
399                         switch (*i) {
400                         case DataType::AUDIO:
401                                 fraction = log_meter (*j);
402                                 snprintf (buf, sizeof (buf), "%+2d", *j);
403                                 break;
404                         case DataType::MIDI:
405                                 fraction = *j / 127.0;
406                                 snprintf (buf, sizeof (buf), "%3d", *j);
407                                 break;
408                         }
409
410                         gint const pos = height - (gint) floor (height * fraction);
411
412                         cairo_set_line_width (cr, 1.0);
413                         cairo_move_to (cr, width-3.5, pos);
414                         cairo_line_to (cr, width, pos);
415                         cairo_stroke (cr);
416
417                         layout->set_text(buf);
418
419                         /* we want logical extents, not ink extents here */
420
421                         int tw, th;
422                         layout->get_pixel_size(tw, th);
423
424                         int p = pos - (th / 2);
425                         p = min (p, height - th);
426                         p = max (p, 0);
427
428                         cairo_move_to (cr, width-5-tw, p);
429                         pango_cairo_show_layout (cr, layout->gobj());
430                 }
431         }
432
433         if (types.size() == 1) {
434                 int tw, th;
435                 layout->set_attributes (unit_font_attributes);
436                 switch (types.at(0)) {
437                         case DataType::AUDIO:
438                                 layout->set_text("dBFS");
439                                 layout->get_pixel_size(tw, th);
440                                 break;
441                         case DataType::MIDI:
442                                 layout->set_text("vel");
443                                 layout->get_pixel_size(tw, th);
444                                 break;
445                 }
446                 Gdk::Color c = w.get_style()->get_fg (Gtk::STATE_ACTIVE);
447                 cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
448                 cairo_move_to (cr, 1, height - th);
449                 pango_cairo_show_layout (cr, layout->gobj());
450         }
451
452         cairo_pattern_t* pattern = cairo_pattern_create_for_surface (surface);
453         MetricPatterns::iterator p;
454
455         if ((p = metric_patterns.find (w.get_name())) != metric_patterns.end()) {
456                 cairo_pattern_destroy (p->second);
457         }
458
459         metric_patterns[w.get_name()] = pattern;
460
461         cairo_destroy (cr);
462         cairo_surface_destroy (surface);
463
464         return pattern;
465 }
466
467 /* XXX code-copy from gain_meter.cc -- TODO consolidate */
468 gint
469 MeterStrip::meter_metrics_expose (GdkEventExpose *ev)
470 {
471         Glib::RefPtr<Gdk::Window> win (meter_metric_area.get_window());
472         cairo_t* cr;
473
474         cr = gdk_cairo_create (win->gobj());
475
476         /* clip to expose area */
477
478         gdk_cairo_rectangle (cr, &ev->area);
479         cairo_clip (cr);
480
481         cairo_pattern_t* pattern;
482         MetricPatterns::iterator i = metric_patterns.find (meter_metric_area.get_name());
483
484         if (i == metric_patterns.end() || style_changed) {
485                 pattern = render_metrics (meter_metric_area, _types);
486         } else {
487                 pattern = i->second;
488         }
489
490         cairo_move_to (cr, 0, 0);
491         cairo_set_source (cr, pattern);
492
493         gint width, height;
494         win->get_size (width, height);
495
496         cairo_rectangle (cr, 0, 0, width, height);
497         cairo_fill (cr);
498
499         style_changed = false;
500
501         cairo_destroy (cr);
502
503         return true;
504 }
505
506 cairo_pattern_t*
507 MeterStrip::render_ticks (Gtk::Widget& w, vector<DataType> types)
508 {
509         Glib::RefPtr<Gdk::Window> win (w.get_window());
510
511         gint width, height;
512         win->get_size (width, height);
513
514         cairo_surface_t* surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
515         cairo_t* cr = cairo_create (surface);
516
517         cairo_move_to (cr, 0, 0);
518         cairo_rectangle (cr, 0, 0, width, height);
519         {
520                 Gdk::Color c = w.get_style()->get_bg (Gtk::STATE_NORMAL);
521                 cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
522         }
523         cairo_fill (cr);
524
525         height = min(1024, height); // XXX see FastMeter::max_pattern_metric_size
526
527         for (vector<DataType>::const_iterator i = types.begin(); i != types.end(); ++i) {
528
529                 Gdk::Color c;
530
531                 if (types.size() > 1) {
532                         /* we're overlaying more than 1 set of marks, so use different colours */
533                         Gdk::Color c;
534                         switch (*i) {
535                         case DataType::AUDIO:
536                                 c = w.get_style()->get_fg (Gtk::STATE_NORMAL);
537                                 cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
538                                 break;
539                         case DataType::MIDI:
540                                 c = w.get_style()->get_fg (Gtk::STATE_ACTIVE);
541                                 cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
542                                 break;
543                         }
544                 } else {
545                         c = w.get_style()->get_fg (Gtk::STATE_NORMAL);
546                         cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
547                 }
548
549                 vector<int> points;
550
551                 switch (*i) {
552                 case DataType::AUDIO:
553                         points.push_back (-50);
554                         points.push_back (-40);
555                         points.push_back (-30);
556                         points.push_back (-20);
557                         points.push_back (-18);
558                         points.push_back (-10);
559                         points.push_back (-6);
560                         points.push_back (-3);
561                         points.push_back (0);
562                         points.push_back (4);
563                         break;
564
565                 case DataType::MIDI:
566                         points.push_back (0);
567                         points.push_back (32);
568                         points.push_back (64);
569                         points.push_back (96);
570                         points.push_back (127);
571                         break;
572                 }
573
574                 for (vector<int>::const_iterator j = points.begin(); j != points.end(); ++j) {
575
576                         float fraction = 0;
577                         switch (*i) {
578                         case DataType::AUDIO:
579                                 fraction = log_meter (*j);
580                                 break;
581                         case DataType::MIDI:
582                                 fraction = *j / 127.0;
583                                 break;
584                         }
585
586                         gint const pos = height - (gint) floor (height * fraction);
587                         cairo_set_line_width (cr, 1.0);
588                         cairo_arc(cr, 1.5, pos, 1.0, 0, 2 * M_PI);
589                         cairo_fill_preserve(cr);
590                         cairo_stroke (cr);
591                 }
592         }
593
594         cairo_pattern_t* pattern = cairo_pattern_create_for_surface (surface);
595         TickPatterns::iterator p;
596
597         if ((p = ticks_patterns.find (w.get_name())) != metric_patterns.end()) {
598                 cairo_pattern_destroy (p->second);
599         }
600
601         ticks_patterns[w.get_name()] = pattern;
602
603         cairo_destroy (cr);
604         cairo_surface_destroy (surface);
605
606         return pattern;
607 }
608
609 gint
610 MeterStrip::meter_ticks1_expose (GdkEventExpose *ev)
611 {
612         return meter_ticks_expose(ev, &meter_ticks1_area);
613 }
614
615 gint
616 MeterStrip::meter_ticks2_expose (GdkEventExpose *ev)
617 {
618         return meter_ticks_expose(ev, &meter_ticks2_area);
619 }
620
621 gint
622 MeterStrip::meter_ticks_expose (GdkEventExpose *ev, Gtk::DrawingArea *mta)
623 {
624         Glib::RefPtr<Gdk::Window> win (mta->get_window());
625         cairo_t* cr;
626
627         cr = gdk_cairo_create (win->gobj());
628
629         /* clip to expose area */
630
631         gdk_cairo_rectangle (cr, &ev->area);
632         cairo_clip (cr);
633
634         cairo_pattern_t* pattern;
635         TickPatterns::iterator i = ticks_patterns.find (mta->get_name());
636
637         if (i == ticks_patterns.end() || style_changed) {
638                 pattern = render_ticks (*mta, _types);
639         } else {
640                 pattern = i->second;
641         }
642
643         cairo_move_to (cr, 0, 0);
644         cairo_set_source (cr, pattern);
645
646         gint width, height;
647         win->get_size (width, height);
648
649         cairo_rectangle (cr, 0, 0, width, height);
650         cairo_fill (cr);
651
652         style_changed = false;
653
654         cairo_destroy (cr);
655
656         return true;
657 }
658
659 void
660 MeterStrip::reset_group_peak_display (RouteGroup* group)
661 {
662         if (_route && group == _route->route_group()) {
663                 reset_peak_display ();
664         }
665 }
666
667 void
668 MeterStrip::reset_peak_display ()
669 {
670         _route->shared_peak_meter()->reset_max();
671         level_meter->clear_meters();
672         max_peak = -INFINITY;
673         peak_display.set_label (_("-inf"));
674         peak_display.set_name ("MixerStripPeakDisplay");
675 }
676
677 bool
678 MeterStrip::peak_button_release (GdkEventButton* ev)
679 {
680         reset_peak_display ();
681         return true;
682 }