meterbridge ticks/metric experiments..
[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         // add level meter
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         // add track-name label
86         // TODO
87         // * fixed-height labels (or table layout)
88         // * print lables at angle (allow longer text)
89         label.set_text(_route->name().c_str());
90         label.set_name("MeterbridgeLabel");
91 #if 0
92         label.set_ellipsize(Pango::ELLIPSIZE_MIDDLE);
93         label.set_max_width_chars(7);
94         label.set_width_chars(7);
95         label.set_alignment(0.5, 0.5);
96 #else //ellipsize & angle are incompatible :(
97         label.set_angle(90.0);
98         label.set_alignment(0.5, 0.0);
99 #endif
100         label.set_size_request(12, 36);
101
102         Gtk::HBox* btnbox = Gtk::manage (new Gtk::HBox());
103         btnbox->pack_start(*rec_enable_button, true, false);
104         btnbox->set_size_request(-1, 16);
105
106         pack_start (meterbox, true, true);
107         pack_start (*btnbox, false, false);
108         pack_start (label, false, false);
109
110         meter_ticks1_area.hide();
111         meter_ticks2_area.show();
112         meter_metric_area.show();
113         meterbox.show();
114         level_meter->show();
115         meter_align->show();
116         btnbox->show();
117         label.show();
118
119         _route->shared_peak_meter()->ConfigurationChanged.connect (
120                         route_connections, invalidator (*this), boost::bind (&MeterStrip::meter_configuration_changed, this, _1), gui_context()
121                         );
122         meter_configuration_changed (_route->shared_peak_meter()->input_streams ());
123
124         set_size_request_to_display_given_text (meter_metric_area, "-8888", 1, 0);
125         meter_metric_area.signal_expose_event().connect (
126                         sigc::mem_fun(*this, &MeterStrip::meter_metrics_expose));
127
128         meter_ticks1_area.set_size_request(4,-1);
129         meter_ticks2_area.set_size_request(4,-1);
130         meter_ticks1_area.signal_expose_event().connect (sigc::mem_fun(*this, &MeterStrip::meter_ticks1_expose));
131         meter_ticks2_area.signal_expose_event().connect (sigc::mem_fun(*this, &MeterStrip::meter_ticks2_expose));
132
133         _route->DropReferences.connect (route_connections, invalidator (*this), boost::bind (&MeterStrip::self_delete, this), gui_context());
134         _route->PropertyChanged.connect (route_connections, invalidator (*this), boost::bind (&MeterStrip::strip_property_changed, this, _1), gui_context());
135
136         UI::instance()->theme_changed.connect (sigc::mem_fun(*this, &MeterStrip::on_theme_changed));
137 }
138
139 MeterStrip::~MeterStrip ()
140 {
141         delete level_meter;
142         CatchDeletion (this);
143 }
144
145 void
146 MeterStrip::self_delete ()
147 {
148         delete this;
149 }
150
151 void
152 MeterStrip::update_rec_display ()
153 {
154         RouteUI::update_rec_display ();
155 }
156
157 std::string
158 MeterStrip::state_id() const
159 {
160         return string_compose ("mtrs %1", _route->id().to_s());
161 }
162
163 void
164 MeterStrip::set_button_names()
165 {
166         rec_enable_button->set_text (_("R"));
167 }
168
169 void
170 MeterStrip::strip_property_changed (const PropertyChange& what_changed)
171 {
172         if (!what_changed.contains (ARDOUR::Properties::name)) {
173                 return;
174         }
175         ENSURE_GUI_THREAD (*this, &MeterStrip::strip_name_changed, what_changed)
176         label.set_text(_route->name());
177 }
178
179 void
180 MeterStrip::fast_update ()
181 {
182         const float mpeak = level_meter->update_meters();
183         // TODO peak indicator if mpeak > 0
184 }
185
186 void
187 MeterStrip::display_metrics (bool show)
188 {
189         if (show) {
190                 meter_metric_area.show();
191                 meter_ticks1_area.hide();
192         } else {
193                 meter_metric_area.hide();
194                 meter_ticks1_area.show();
195         }
196 }
197
198 void
199 MeterStrip::on_theme_changed()
200 {
201         style_changed = true;
202 }
203
204 void
205 MeterStrip::meter_configuration_changed (ChanCount c)
206 {
207         int type = 0;
208         _types.clear ();
209
210         for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
211                 if (c.get (*i) > 0) {
212                         _types.push_back (*i);
213                         type |= 1 << (*i);
214                 }
215         }
216
217         // TODO draw Inactive routes or busses with different styles
218         if (boost::dynamic_pointer_cast<AudioTrack>(_route) == 0
219                         && boost::dynamic_pointer_cast<MidiTrack>(_route) == 0
220                         ) {
221                 meter_metric_area.set_name ("AudioBusMetrics");
222                 meter_ticks1_area.set_name ("AudioBusMetrics");
223                 meter_ticks2_area.set_name ("AudioBusMetrics");
224         }
225         else if (type == (1 << DataType::AUDIO)) {
226                 meter_metric_area.set_name ("AudioTrackMetrics");
227                 meter_ticks1_area.set_name ("AudioTrackMetrics");
228                 meter_ticks2_area.set_name ("AudioTrackMetrics");
229         }
230         else if (type == (1 << DataType::MIDI)) {
231                 meter_metric_area.set_name ("MidiTrackMetrics");
232                 meter_ticks1_area.set_name ("MidiTrackMetrics");
233                 meter_ticks2_area.set_name ("MidiTrackMetrics");
234         } else {
235                 meter_metric_area.set_name ("AudioMidiTrackMetrics");
236                 meter_ticks1_area.set_name ("AudioMidiTrackMetrics");
237                 meter_ticks2_area.set_name ("AudioMidiTrackMetrics");
238         }
239         style_changed = true;
240         meter_metric_area.queue_draw ();
241 }
242
243 void
244 MeterStrip::on_size_request (Gtk::Requisition* r)
245 {
246         style_changed = true;
247         VBox::on_size_request(r);
248 }
249
250 void
251 MeterStrip::on_size_allocate (Gtk::Allocation& a)
252 {
253         style_changed = true;
254         VBox::on_size_allocate(a);
255 }
256
257 /* XXX code-copy from gain_meter.cc -- TODO consolidate
258  *
259  * slightly different:
260  *  - ticks & label positions are swapped
261  *  - more ticks for audio (longer meter by default)
262  *  - right-aligned lables, unit-legend
263  *  - height limitation of FastMeter::max_pattern_metric_size is included here
264  */
265 cairo_pattern_t*
266 MeterStrip::render_metrics (Gtk::Widget& w, vector<DataType> types)
267 {
268         Glib::RefPtr<Gdk::Window> win (w.get_window());
269
270         gint width, height;
271         win->get_size (width, height);
272
273         cairo_surface_t* surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
274         cairo_t* cr = cairo_create (surface);
275         Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create(w.get_pango_context());
276
277
278         Pango::AttrList audio_font_attributes;
279         Pango::AttrList midi_font_attributes;
280         Pango::AttrList unit_font_attributes;
281
282         Pango::AttrFontDesc* font_attr;
283         Pango::FontDescription font;
284
285         font = Pango::FontDescription (""); // use defaults
286         //font = get_font_for_style("gain-fader");
287         //font = w.get_style()->get_font();
288
289         font.set_weight (Pango::WEIGHT_NORMAL);
290         font.set_size (10.0 * PANGO_SCALE);
291         font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
292         audio_font_attributes.change (*font_attr);
293         delete font_attr;
294
295         font.set_weight (Pango::WEIGHT_ULTRALIGHT);
296         font.set_stretch (Pango::STRETCH_ULTRA_CONDENSED);
297         font.set_size (7.5 * PANGO_SCALE);
298         font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
299         midi_font_attributes.change (*font_attr);
300         delete font_attr;
301
302         font.set_size (7.0 * PANGO_SCALE);
303         font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
304         unit_font_attributes.change (*font_attr);
305         delete font_attr;
306
307         cairo_move_to (cr, 0, 0);
308         cairo_rectangle (cr, 0, 0, width, height);
309         {
310                 Gdk::Color c = w.get_style()->get_bg (Gtk::STATE_NORMAL);
311                 cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
312         }
313         cairo_fill (cr);
314
315         height = min(1024, height); // XXX see FastMeter::max_pattern_metric_size
316
317         for (vector<DataType>::const_iterator i = types.begin(); i != types.end(); ++i) {
318
319                 Gdk::Color c;
320
321                 if (types.size() > 1) {
322                         /* we're overlaying more than 1 set of marks, so use different colours */
323                         Gdk::Color c;
324                         switch (*i) {
325                         case DataType::AUDIO:
326                                 c = w.get_style()->get_fg (Gtk::STATE_NORMAL);
327                                 cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
328                                 break;
329                         case DataType::MIDI:
330                                 c = w.get_style()->get_fg (Gtk::STATE_ACTIVE);
331                                 cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
332                                 break;
333                         }
334                 } else {
335                         c = w.get_style()->get_fg (Gtk::STATE_NORMAL);
336                         cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
337                 }
338
339                 vector<int> points;
340
341                 switch (*i) {
342                 case DataType::AUDIO:
343                         layout->set_attributes (audio_font_attributes);
344                         points.push_back (-50);
345                         points.push_back (-40);
346                         points.push_back (-30);
347                         points.push_back (-20);
348                         points.push_back (-18);
349                         points.push_back (-10);
350                         points.push_back (-6);
351                         points.push_back (-3);
352                         points.push_back (0);
353                         points.push_back (4);
354                         break;
355
356                 case DataType::MIDI:
357                         layout->set_attributes (midi_font_attributes);
358                         points.push_back (0);
359                         if (types.size() == 1) {
360                                 points.push_back (32);
361                         } else {
362                                 /* tweak so as not to overlay the -30dB mark */
363                                 points.push_back (48);
364                         }
365                         if (types.size() == 1) {
366                                 points.push_back (64); // very close to -18dB
367                                 points.push_back (96); // overlays with -6dB mark
368                         } else {
369                                 points.push_back (72);
370                                 points.push_back (88);
371                         }
372                         points.push_back (127);
373                         break;
374                 }
375
376                 char buf[32];
377
378                 for (vector<int>::const_iterator j = points.begin(); j != points.end(); ++j) {
379
380                         float fraction = 0;
381                         switch (*i) {
382                         case DataType::AUDIO:
383                                 fraction = log_meter (*j);
384                                 snprintf (buf, sizeof (buf), "%+2d", *j);
385                                 break;
386                         case DataType::MIDI:
387                                 fraction = *j / 127.0;
388                                 snprintf (buf, sizeof (buf), "%3d", *j);
389                                 break;
390                         }
391
392                         gint const pos = height - (gint) floor (height * fraction);
393
394                         cairo_set_line_width (cr, 1.0);
395                         cairo_move_to (cr, width-3.5, pos);
396                         cairo_line_to (cr, width, pos);
397                         cairo_stroke (cr);
398
399                         layout->set_text(buf);
400
401                         /* we want logical extents, not ink extents here */
402
403                         int tw, th;
404                         layout->get_pixel_size(tw, th);
405
406                         int p = pos - (th / 2);
407                         p = min (p, height - th);
408                         p = max (p, 0);
409
410                         cairo_move_to (cr, width-5-tw, p);
411                         pango_cairo_show_layout (cr, layout->gobj());
412                 }
413         }
414
415         if (types.size() == 1) {
416                 int tw, th;
417                 layout->set_attributes (unit_font_attributes);
418                 switch (types.at(0)) {
419                         case DataType::AUDIO:
420                                 layout->set_text("dBFS");
421                                 layout->get_pixel_size(tw, th);
422                                 break;
423                         case DataType::MIDI:
424                                 layout->set_text("vel");
425                                 layout->get_pixel_size(tw, th);
426                                 break;
427                 }
428                 Gdk::Color c = w.get_style()->get_fg (Gtk::STATE_ACTIVE);
429                 cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
430                 cairo_move_to (cr, 1, height - th);
431                 pango_cairo_show_layout (cr, layout->gobj());
432         }
433
434         cairo_pattern_t* pattern = cairo_pattern_create_for_surface (surface);
435         MetricPatterns::iterator p;
436
437         if ((p = metric_patterns.find (w.get_name())) != metric_patterns.end()) {
438                 cairo_pattern_destroy (p->second);
439         }
440
441         metric_patterns[w.get_name()] = pattern;
442
443         cairo_destroy (cr);
444         cairo_surface_destroy (surface);
445
446         return pattern;
447 }
448
449 /* XXX code-copy from gain_meter.cc -- TODO consolidate */
450 gint
451 MeterStrip::meter_metrics_expose (GdkEventExpose *ev)
452 {
453         Glib::RefPtr<Gdk::Window> win (meter_metric_area.get_window());
454         cairo_t* cr;
455
456         cr = gdk_cairo_create (win->gobj());
457
458         /* clip to expose area */
459
460         gdk_cairo_rectangle (cr, &ev->area);
461         cairo_clip (cr);
462
463         cairo_pattern_t* pattern;
464         MetricPatterns::iterator i = metric_patterns.find (meter_metric_area.get_name());
465
466         if (i == metric_patterns.end() || style_changed) {
467                 pattern = render_metrics (meter_metric_area, _types);
468         } else {
469                 pattern = i->second;
470         }
471
472         cairo_move_to (cr, 0, 0);
473         cairo_set_source (cr, pattern);
474
475         gint width, height;
476         win->get_size (width, height);
477
478         cairo_rectangle (cr, 0, 0, width, height);
479         cairo_fill (cr);
480
481         style_changed = false;
482
483         cairo_destroy (cr);
484
485         return true;
486 }
487
488 cairo_pattern_t*
489 MeterStrip::render_ticks (Gtk::Widget& w, vector<DataType> types)
490 {
491         Glib::RefPtr<Gdk::Window> win (w.get_window());
492
493         gint width, height;
494         win->get_size (width, height);
495
496         cairo_surface_t* surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
497         cairo_t* cr = cairo_create (surface);
498
499         cairo_move_to (cr, 0, 0);
500         cairo_rectangle (cr, 0, 0, width, height);
501         {
502                 Gdk::Color c = w.get_style()->get_bg (Gtk::STATE_NORMAL);
503                 cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
504         }
505         cairo_fill (cr);
506
507         height = min(1024, height); // XXX see FastMeter::max_pattern_metric_size
508
509         for (vector<DataType>::const_iterator i = types.begin(); i != types.end(); ++i) {
510
511                 Gdk::Color c;
512
513                 if (types.size() > 1) {
514                         /* we're overlaying more than 1 set of marks, so use different colours */
515                         Gdk::Color c;
516                         switch (*i) {
517                         case DataType::AUDIO:
518                                 c = w.get_style()->get_fg (Gtk::STATE_NORMAL);
519                                 cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
520                                 break;
521                         case DataType::MIDI:
522                                 c = w.get_style()->get_fg (Gtk::STATE_ACTIVE);
523                                 cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
524                                 break;
525                         }
526                 } else {
527                         c = w.get_style()->get_fg (Gtk::STATE_NORMAL);
528                         cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
529                 }
530
531                 vector<int> points;
532
533                 switch (*i) {
534                 case DataType::AUDIO:
535                         points.push_back (-50);
536                         points.push_back (-40);
537                         points.push_back (-30);
538                         points.push_back (-20);
539                         points.push_back (-18);
540                         points.push_back (-10);
541                         points.push_back (-6);
542                         points.push_back (-3);
543                         points.push_back (0);
544                         points.push_back (4);
545                         break;
546
547                 case DataType::MIDI:
548                         points.push_back (0);
549                         points.push_back (32);
550                         points.push_back (64);
551                         points.push_back (96);
552                         points.push_back (127);
553                         break;
554                 }
555
556                 for (vector<int>::const_iterator j = points.begin(); j != points.end(); ++j) {
557
558                         float fraction = 0;
559                         switch (*i) {
560                         case DataType::AUDIO:
561                                 fraction = log_meter (*j);
562                                 break;
563                         case DataType::MIDI:
564                                 fraction = *j / 127.0;
565                                 break;
566                         }
567
568                         gint const pos = height - (gint) floor (height * fraction);
569                         cairo_set_line_width (cr, 1.0);
570                         cairo_arc(cr, 1.5, pos, 1.0, 0, 2 * M_PI);
571                         cairo_fill_preserve(cr);
572                         cairo_stroke (cr);
573                 }
574         }
575
576         cairo_pattern_t* pattern = cairo_pattern_create_for_surface (surface);
577         TickPatterns::iterator p;
578
579         if ((p = ticks_patterns.find (w.get_name())) != metric_patterns.end()) {
580                 cairo_pattern_destroy (p->second);
581         }
582
583         ticks_patterns[w.get_name()] = pattern;
584
585         cairo_destroy (cr);
586         cairo_surface_destroy (surface);
587
588         return pattern;
589 }
590
591 gint
592 MeterStrip::meter_ticks1_expose (GdkEventExpose *ev)
593 {
594         return meter_ticks_expose(ev, &meter_ticks1_area);
595 }
596
597 gint
598 MeterStrip::meter_ticks2_expose (GdkEventExpose *ev)
599 {
600         return meter_ticks_expose(ev, &meter_ticks2_area);
601 }
602
603 gint
604 MeterStrip::meter_ticks_expose (GdkEventExpose *ev, Gtk::DrawingArea *mta)
605 {
606         Glib::RefPtr<Gdk::Window> win (mta->get_window());
607         cairo_t* cr;
608
609         cr = gdk_cairo_create (win->gobj());
610
611         /* clip to expose area */
612
613         gdk_cairo_rectangle (cr, &ev->area);
614         cairo_clip (cr);
615
616         cairo_pattern_t* pattern;
617         TickPatterns::iterator i = ticks_patterns.find (mta->get_name());
618
619         if (i == ticks_patterns.end() || style_changed) {
620                 pattern = render_ticks (*mta, _types);
621         } else {
622                 pattern = i->second;
623         }
624
625         cairo_move_to (cr, 0, 0);
626         cairo_set_source (cr, pattern);
627
628         gint width, height;
629         win->get_size (width, height);
630
631         cairo_rectangle (cr, 0, 0, width, height);
632         cairo_fill (cr);
633
634         style_changed = false;
635
636         cairo_destroy (cr);
637
638         return true;
639 }