overhaul meterbridge:
[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
55 MeterStrip::MeterStrip (Meterbridge& mtr, Session* sess, boost::shared_ptr<ARDOUR::Route> rt)
56         : _route(rt)
57         , style_changed (false)
58 {
59         set_spacing(2);
60
61         int meter_width = 6;
62         if (_route->shared_peak_meter()->input_streams().n_total() == 1) {
63                 meter_width = 12;
64         }
65
66         // add level meter
67         level_meter = new LevelMeter(sess);
68         level_meter->set_meter (_route->shared_peak_meter().get());
69         level_meter->clear_meters();
70         level_meter->setup_meters (350, meter_width, 6);
71         level_meter->pack_start (meter_metric_area, false, false);
72
73         Gtk::Alignment *meter_align = Gtk::manage (new Gtk::Alignment());
74         meter_align->set(0.5, 0.5, 0.0, 1.0);
75         meter_align->add(*level_meter);
76
77         // add track-name label
78         // TODO
79         // * fixed-height labels (or table layout)
80         // * print lables at angle (allow longer text)
81         label.set_text(_route->name().c_str());
82         label.set_name("MeterbridgeLabel");
83 #if 0
84         label.set_ellipsize(Pango::ELLIPSIZE_MIDDLE);
85         label.set_max_width_chars(7);
86         label.set_width_chars(7);
87         label.set_alignment(0.5, 0.5);
88 #else //ellipsize & angle are incompatible :(
89         label.set_angle(90.0);
90         label.set_alignment(0.5, 0.0);
91 #endif
92         label.set_size_request(12, 36);
93
94         pack_start(*meter_align, true, true);
95         pack_start (label, false, false);
96
97         meter_metric_area.show();
98         level_meter->show();
99         meter_align->show();
100         label.show();
101
102         _route->shared_peak_meter()->ConfigurationChanged.connect (
103                         route_connections, invalidator (*this), boost::bind (&MeterStrip::meter_configuration_changed, this, _1), gui_context()
104                         );
105         meter_configuration_changed (_route->shared_peak_meter()->input_streams ());
106
107         set_size_request_to_display_given_text (meter_metric_area, "-8888", 1, 0);
108         meter_metric_area.signal_expose_event().connect (
109                         sigc::mem_fun(*this, &MeterStrip::meter_metrics_expose));
110
111         _route->DropReferences.connect (route_connections, invalidator (*this), boost::bind (&MeterStrip::self_delete, this), gui_context());
112         _route->PropertyChanged.connect (route_connections, invalidator (*this), boost::bind (&MeterStrip::strip_property_changed, this, _1), gui_context());
113
114         UI::instance()->theme_changed.connect (sigc::mem_fun(*this, &MeterStrip::on_theme_changed));
115 }
116
117 MeterStrip::~MeterStrip ()
118 {
119         delete level_meter;
120         CatchDeletion (this);
121 }
122
123 void
124 MeterStrip::self_delete ()
125 {
126         delete this;
127 }
128
129 void
130 MeterStrip::strip_property_changed (const PropertyChange& what_changed)
131 {
132         if (!what_changed.contains (ARDOUR::Properties::name)) {
133                 return;
134         }
135         ENSURE_GUI_THREAD (*this, &MeterStrip::strip_name_changed, what_changed)
136         label.set_text(_route->name());
137 }
138
139 void
140 MeterStrip::fast_update ()
141 {
142         const float mpeak = level_meter->update_meters();
143         // TODO peak indicator if mpeak > 0
144 }
145
146 void
147 MeterStrip::display_metrics (bool show)
148 {
149         if (show) {
150                 meter_metric_area.show();
151         } else {
152                 meter_metric_area.hide();
153         }
154 }
155
156 void
157 MeterStrip::on_theme_changed()
158 {
159         style_changed = true;
160 }
161
162 void
163 MeterStrip::meter_configuration_changed (ChanCount c)
164 {
165         int type = 0;
166         _types.clear ();
167
168         for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) {
169                 if (c.get (*i) > 0) {
170                         _types.push_back (*i);
171                         type |= 1 << (*i);
172                 }
173         }
174
175         // TODO draw Inactive routes or busses with different styles
176         if (boost::dynamic_pointer_cast<AudioTrack>(_route) == 0
177                         && boost::dynamic_pointer_cast<MidiTrack>(_route) == 0
178                         ) {
179                 meter_metric_area.set_name ("AudioBusMetrics");
180         }
181         else if (type == (1 << DataType::AUDIO)) {
182                 meter_metric_area.set_name ("AudioTrackMetrics");
183         }
184         else if (type == (1 << DataType::MIDI)) {
185                 meter_metric_area.set_name ("MidiTrackMetrics");
186         } else {
187                 meter_metric_area.set_name ("AudioMidiTrackMetrics");
188         }
189         style_changed = true;
190         meter_metric_area.queue_draw ();
191 }
192
193 void
194 MeterStrip::on_size_request (Gtk::Requisition* r)
195 {
196         style_changed = true;
197         VBox::on_size_request(r);
198 }
199
200 void
201 MeterStrip::on_size_allocate (Gtk::Allocation& a)
202 {
203         style_changed = true;
204         VBox::on_size_allocate(a);
205 }
206
207 /* XXX code-copy from gain_meter.cc -- TODO consolidate
208  *
209  * slightly different:
210  *  - ticks & label positions are swapped
211  *  - more ticks for audio (longer meter by default)
212  *  - right-aligned lables, unit-legend
213  *  - height limitation of FastMeter::max_pattern_metric_size is included here
214  */
215 cairo_pattern_t*
216 MeterStrip::render_metrics (Gtk::Widget& w, vector<DataType> types)
217 {
218         Glib::RefPtr<Gdk::Window> win (w.get_window());
219
220         gint width, height;
221         win->get_size (width, height);
222
223         cairo_surface_t* surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height);
224         cairo_t* cr = cairo_create (surface);
225         Glib::RefPtr<Pango::Layout> layout = Pango::Layout::create(w.get_pango_context());
226
227
228         Pango::AttrList audio_font_attributes;
229         Pango::AttrList midi_font_attributes;
230         Pango::AttrList unit_font_attributes;
231
232         Pango::AttrFontDesc* font_attr;
233         Pango::FontDescription font;
234
235         font = Pango::FontDescription (""); // use defaults
236         //font = get_font_for_style("gain-fader");
237         //font = w.get_style()->get_font();
238
239         font.set_weight (Pango::WEIGHT_NORMAL);
240         font.set_size (10.0 * PANGO_SCALE);
241         font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
242         audio_font_attributes.change (*font_attr);
243         delete font_attr;
244
245         font.set_weight (Pango::WEIGHT_ULTRALIGHT);
246         font.set_stretch (Pango::STRETCH_ULTRA_CONDENSED);
247         font.set_size (7.5 * PANGO_SCALE);
248         font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
249         midi_font_attributes.change (*font_attr);
250         delete font_attr;
251
252         font.set_size (7.0 * PANGO_SCALE);
253         font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font));
254         unit_font_attributes.change (*font_attr);
255         delete font_attr;
256
257         cairo_move_to (cr, 0, 0);
258         cairo_rectangle (cr, 0, 0, width, height);
259         {
260                 Gdk::Color c = w.get_style()->get_bg (Gtk::STATE_NORMAL);
261                 cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
262         }
263         cairo_fill (cr);
264
265         height = min(1024, height); // XXX see FastMeter::max_pattern_metric_size
266
267         for (vector<DataType>::const_iterator i = types.begin(); i != types.end(); ++i) {
268
269                 Gdk::Color c;
270
271                 if (types.size() > 1) {
272                         /* we're overlaying more than 1 set of marks, so use different colours */
273                         Gdk::Color c;
274                         switch (*i) {
275                         case DataType::AUDIO:
276                                 c = w.get_style()->get_fg (Gtk::STATE_NORMAL);
277                                 cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
278                                 break;
279                         case DataType::MIDI:
280                                 c = w.get_style()->get_fg (Gtk::STATE_ACTIVE);
281                                 cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
282                                 break;
283                         }
284                 } else {
285                         c = w.get_style()->get_fg (Gtk::STATE_NORMAL);
286                         cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
287                 }
288
289                 vector<int> points;
290
291                 switch (*i) {
292                 case DataType::AUDIO:
293                         layout->set_attributes (audio_font_attributes);
294                         points.push_back (-50);
295                         points.push_back (-40);
296                         points.push_back (-30);
297                         points.push_back (-20);
298                         points.push_back (-18);
299                         points.push_back (-10);
300                         points.push_back (-6);
301                         points.push_back (-3);
302                         points.push_back (0);
303                         points.push_back (4);
304                         break;
305
306                 case DataType::MIDI:
307                         layout->set_attributes (midi_font_attributes);
308                         points.push_back (0);
309                         if (types.size() == 1) {
310                                 points.push_back (32);
311                         } else {
312                                 /* tweak so as not to overlay the -30dB mark */
313                                 points.push_back (48);
314                         }
315                         if (types.size() == 1) {
316                                 points.push_back (64); // very close to -18dB
317                                 points.push_back (96); // overlays with -6dB mark
318                         } else {
319                                 points.push_back (72);
320                                 points.push_back (88);
321                         }
322                         points.push_back (127);
323                         break;
324                 }
325
326                 char buf[32];
327
328                 for (vector<int>::const_iterator j = points.begin(); j != points.end(); ++j) {
329
330                         float fraction = 0;
331                         switch (*i) {
332                         case DataType::AUDIO:
333                                 fraction = log_meter (*j);
334                                 snprintf (buf, sizeof (buf), "%+2d", *j);
335                                 break;
336                         case DataType::MIDI:
337                                 fraction = *j / 127.0;
338                                 snprintf (buf, sizeof (buf), "%3d", *j);
339                                 break;
340                         }
341
342                         gint const pos = height - (gint) floor (height * fraction);
343
344                         cairo_set_line_width (cr, 1.0);
345                         cairo_move_to (cr, width-3.5, pos);
346                         cairo_line_to (cr, width, pos);
347                         cairo_stroke (cr);
348
349                         layout->set_text(buf);
350
351                         /* we want logical extents, not ink extents here */
352
353                         int tw, th;
354                         layout->get_pixel_size(tw, th);
355
356                         int p = pos - (th / 2);
357                         p = min (p, height - th);
358                         p = max (p, 0);
359
360                         cairo_move_to (cr, width-5-tw, p);
361                         pango_cairo_show_layout (cr, layout->gobj());
362                 }
363         }
364
365         if (types.size() == 1) {
366                 int tw, th;
367                 layout->set_attributes (unit_font_attributes);
368                 switch (types.at(0)) {
369                         case DataType::AUDIO:
370                                 layout->set_text("dBFS");
371                                 layout->get_pixel_size(tw, th);
372                                 break;
373                         case DataType::MIDI:
374                                 layout->set_text("vel");
375                                 layout->get_pixel_size(tw, th);
376                                 break;
377                 }
378                 Gdk::Color c = w.get_style()->get_fg (Gtk::STATE_ACTIVE);
379                 cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p());
380                 cairo_move_to (cr, 1, height - th);
381                 pango_cairo_show_layout (cr, layout->gobj());
382         }
383
384         cairo_pattern_t* pattern = cairo_pattern_create_for_surface (surface);
385         MetricPatterns::iterator p;
386
387         if ((p = metric_patterns.find (w.get_name())) != metric_patterns.end()) {
388                 cairo_pattern_destroy (p->second);
389         }
390
391         metric_patterns[w.get_name()] = pattern;
392
393         cairo_destroy (cr);
394         cairo_surface_destroy (surface);
395
396         return pattern;
397 }
398
399 /* XXX code-copy from gain_meter.cc -- TODO consolidate */
400 gint
401 MeterStrip::meter_metrics_expose (GdkEventExpose *ev)
402 {
403         Glib::RefPtr<Gdk::Window> win (meter_metric_area.get_window());
404         cairo_t* cr;
405
406         cr = gdk_cairo_create (win->gobj());
407
408         /* clip to expose area */
409
410         gdk_cairo_rectangle (cr, &ev->area);
411         cairo_clip (cr);
412
413         cairo_pattern_t* pattern;
414         MetricPatterns::iterator i = metric_patterns.find (meter_metric_area.get_name());
415
416         if (i == metric_patterns.end() || style_changed) {
417                 pattern = render_metrics (meter_metric_area, _types);
418         } else {
419                 pattern = i->second;
420         }
421
422         cairo_move_to (cr, 0, 0);
423         cairo_set_source (cr, pattern);
424
425         gint width, height;
426         win->get_size (width, height);
427
428         cairo_rectangle (cr, 0, 0, width, height);
429         cairo_fill (cr);
430
431         style_changed = false;
432
433         cairo_destroy (cr);
434
435         return true;
436 }