From a4373a99c6b5869fa66244d1b72a08a34b62a87f Mon Sep 17 00:00:00 2001 From: Robin Gareus Date: Sun, 23 Jun 2013 16:14:39 +0200 Subject: meter-bridge details: * sort routes * draw metric area * layout & style --- gtk2_ardour/ardour3_styles.rc.in | 5 + gtk2_ardour/ardour3_widget_list.rc | 2 + gtk2_ardour/meter_strip.cc | 344 +++++++++++++++++++++++++++++++++++-- gtk2_ardour/meter_strip.h | 19 +- gtk2_ardour/meterbridge.cc | 74 +++++--- gtk2_ardour/meterbridge.h | 4 +- 6 files changed, 406 insertions(+), 42 deletions(-) diff --git a/gtk2_ardour/ardour3_styles.rc.in b/gtk2_ardour/ardour3_styles.rc.in index da636eefd1..44b7f8a18d 100644 --- a/gtk2_ardour/ardour3_styles.rc.in +++ b/gtk2_ardour/ardour3_styles.rc.in @@ -766,6 +766,11 @@ style "audio_bus_metrics_inactive" = "track_controls_inactive" font_name = "@FONT_TINY@" } +style "meterbridge_label" = "very_small_text" +{ + +} + style "track_name_display" = "medium_text" { fg[NORMAL] = @@COLPREFIX@_fg diff --git a/gtk2_ardour/ardour3_widget_list.rc b/gtk2_ardour/ardour3_widget_list.rc index 343075c1ea..5bd30671d9 100644 --- a/gtk2_ardour/ardour3_widget_list.rc +++ b/gtk2_ardour/ardour3_widget_list.rc @@ -373,3 +373,5 @@ widget "*transport option button" style:highest "small_text" widget "*transport active option button" style:highest "small_text" widget "*plugin bypass button" style:highest "small_text" widget "*punch button" style:highest "small_text" + +widget "*MeterbridgeLabel*" style:highest "meterbridge_label" diff --git a/gtk2_ardour/meter_strip.cc b/gtk2_ardour/meter_strip.cc index ab80aa697d..fc092ae7cc 100644 --- a/gtk2_ardour/meter_strip.cc +++ b/gtk2_ardour/meter_strip.cc @@ -24,10 +24,13 @@ #include "ardour/session.h" #include "ardour/route.h" #include "ardour/route_group.h" +#include "ardour/meter.h" #include +#include #include "ardour_ui.h" +#include "logmeter.h" #include "gui_thread.h" #include "ardour_window.h" @@ -44,32 +47,59 @@ using namespace std; PBD::Signal1 MeterStrip::CatchDeletion; +MeterStrip::MetricPatterns MeterStrip::metric_patterns; + MeterStrip::MeterStrip (Meterbridge& mtr, Session* sess, boost::shared_ptr rt) - : _meterbridge(mtr) + : _route(rt) + , style_changed (false) { - _route = rt; + set_spacing(2); + // add level meter level_meter = new LevelMeter(sess); - level_meter->set_meter (rt->shared_peak_meter().get()); + level_meter->set_meter (_route->shared_peak_meter().get()); level_meter->clear_meters(); level_meter->setup_meters (350, 6); + level_meter->pack_start (meter_metric_area, false, false); + + Gtk::Alignment *meter_align = Gtk::manage (new Gtk::Alignment()); + meter_align->set(0.5, 0.5, 0.0, 1.0); + meter_align->add(*level_meter); - rt->DropReferences.connect (route_connections, invalidator (*this), boost::bind (&MeterStrip::self_delete, this), gui_context()); - rt->PropertyChanged.connect (route_connections, invalidator (*this), boost::bind (&MeterStrip::strip_property_changed, this, _1), gui_context()); + // add track-name label + // TODO + // * fixed-height labels (or table layout) + // * print lables at angle (allow longer text) + label.set_text(_route->name().c_str()); + label.set_ellipsize(Pango::ELLIPSIZE_MIDDLE); + label.set_max_width_chars(7); + label.set_width_chars(7); + label.set_alignment(0.5, 0.5); + label.set_name("MeterbridgeLabel"); + //ellipsize & angle are incompatible :( + //label.property_angle().set_value(90.0); + pack_start(*meter_align, true, true); + pack_start (label, false, false); - pack_start (*level_meter, true, true); + meter_metric_area.show(); level_meter->show(); + meter_align->show(); + label.show(); - label = manage(new Gtk::Label(rt->name().c_str())); - pack_start (*label, true, true); - label->show(); -} + _route->shared_peak_meter()->ConfigurationChanged.connect ( + route_connections, invalidator (*this), boost::bind (&MeterStrip::meter_configuration_changed, this, _1), gui_context() + ); + meter_configuration_changed (_route->shared_peak_meter()->input_streams ()); -void -MeterStrip::fast_update () -{ - float mpeak = level_meter->update_meters(); + set_size_request_to_display_given_text (meter_metric_area, "-8888", 1, 0); + meter_metric_area.signal_expose_event().connect ( + sigc::mem_fun(*this, &MeterStrip::meter_metrics_expose)); + + _route->DropReferences.connect (route_connections, invalidator (*this), boost::bind (&MeterStrip::self_delete, this), gui_context()); + _route->PropertyChanged.connect (route_connections, invalidator (*this), boost::bind (&MeterStrip::strip_property_changed, this, _1), gui_context()); + + UI::instance()->theme_changed.connect (sigc::mem_fun(*this, &MeterStrip::on_theme_changed)); } MeterStrip::~MeterStrip () @@ -91,5 +121,289 @@ MeterStrip::strip_property_changed (const PropertyChange& what_changed) return; } ENSURE_GUI_THREAD (*this, &MeterStrip::strip_name_changed, what_changed) - label->set_text(_route->name()); + label.set_text(_route->name()); +} + +void +MeterStrip::fast_update () +{ + const float mpeak = level_meter->update_meters(); + // TODO peak indicator if mpeak > 0 +} + +void +MeterStrip::on_theme_changed() +{ + style_changed = true; +} + +void +MeterStrip::meter_configuration_changed (ChanCount c) +{ + int type = 0; + _types.clear (); + + for (DataType::iterator i = DataType::begin(); i != DataType::end(); ++i) { + if (c.get (*i) > 0) { + _types.push_back (*i); + type |= 1 << (*i); + } + } + + // TODO draw Inactive routes and busses with different styles + if (type == (1 << DataType::AUDIO)) { + meter_metric_area.set_name ("AudioTrackMetrics"); + } + else if (type == (1 << DataType::MIDI)) { + meter_metric_area.set_name ("MidiTrackMetrics"); + } else { + meter_metric_area.set_name ("AudioMidiTrackMetrics"); + } + style_changed = true; + meter_metric_area.queue_draw (); +} + +void +MeterStrip::on_size_request (Gtk::Requisition* r) +{ + style_changed = true; + VBox::on_size_request(r); +} + +void +MeterStrip::on_size_allocate (Gtk::Allocation& a) +{ + style_changed = true; + VBox::on_size_allocate(a); +} + +/* XXX code-copy from gain_meter.cc -- TODO consolidate + * + * slightly different: + * - ticks & label positions are swapped + * - more ticks for audio (longer meter by default) + * - right-aligned lables, unit-legend + * - height limitation of FastMeter::max_pattern_metric_size is included here + */ +cairo_pattern_t* +MeterStrip::render_metrics (Gtk::Widget& w, vector types) +{ + Glib::RefPtr win (w.get_window()); + + gint width, height; + win->get_size (width, height); + + cairo_surface_t* surface = cairo_image_surface_create (CAIRO_FORMAT_RGB24, width, height); + cairo_t* cr = cairo_create (surface); + Glib::RefPtr layout = Pango::Layout::create(w.get_pango_context()); + + + Pango::AttrList audio_font_attributes; + Pango::AttrList midi_font_attributes; + Pango::AttrList unit_font_attributes; + + Pango::AttrFontDesc* font_attr; + Pango::FontDescription font; + + font = Pango::FontDescription (""); // use defaults + //font = get_font_for_style("gain-fader"); + //font = w.get_style()->get_font(); + + font.set_weight (Pango::WEIGHT_NORMAL); + font.set_size (10.0 * PANGO_SCALE); + font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font)); + audio_font_attributes.change (*font_attr); + delete font_attr; + + font.set_weight (Pango::WEIGHT_ULTRALIGHT); + font.set_stretch (Pango::STRETCH_ULTRA_CONDENSED); + font.set_size (7.5 * PANGO_SCALE); + font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font)); + midi_font_attributes.change (*font_attr); + delete font_attr; + + font.set_size (7.0 * PANGO_SCALE); + font_attr = new Pango::AttrFontDesc (Pango::Attribute::create_attr_font_desc (font)); + unit_font_attributes.change (*font_attr); + delete font_attr; + + cairo_move_to (cr, 0, 0); + cairo_rectangle (cr, 0, 0, width, height); + { + Gdk::Color c = w.get_style()->get_bg (Gtk::STATE_NORMAL); + cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p()); + } + cairo_fill (cr); + + height = min(1024, height); // XXX see FastMeter::max_pattern_metric_size + + for (vector::const_iterator i = types.begin(); i != types.end(); ++i) { + + Gdk::Color c; + + if (types.size() > 1) { + /* we're overlaying more than 1 set of marks, so use different colours */ + Gdk::Color c; + switch (*i) { + case DataType::AUDIO: + c = w.get_style()->get_fg (Gtk::STATE_NORMAL); + cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p()); + break; + case DataType::MIDI: + c = w.get_style()->get_fg (Gtk::STATE_ACTIVE); + cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p()); + break; + } + } else { + c = w.get_style()->get_fg (Gtk::STATE_NORMAL); + cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p()); + } + + vector points; + + switch (*i) { + case DataType::AUDIO: + layout->set_attributes (audio_font_attributes); + points.push_back (-50); + points.push_back (-40); + points.push_back (-30); + points.push_back (-20); + points.push_back (-18); + points.push_back (-10); + points.push_back (-6); + points.push_back (-3); + points.push_back (0); + points.push_back (4); + break; + + case DataType::MIDI: + layout->set_attributes (midi_font_attributes); + points.push_back (0); + if (types.size() == 1) { + points.push_back (32); + } else { + /* tweak so as not to overlay the -30dB mark */ + points.push_back (48); + } + if (types.size() == 1) { + points.push_back (64); // very close to -18dB + points.push_back (96); // overlays with -6dB mark + } else { + points.push_back (72); + points.push_back (88); + } + points.push_back (127); + break; + } + + char buf[32]; + + for (vector::const_iterator j = points.begin(); j != points.end(); ++j) { + + float fraction = 0; + switch (*i) { + case DataType::AUDIO: + fraction = log_meter (*j); + snprintf (buf, sizeof (buf), "%+2d", *j); + break; + case DataType::MIDI: + fraction = *j / 127.0; + snprintf (buf, sizeof (buf), "%3d", *j); + break; + } + + gint const pos = height - (gint) floor (height * fraction); + + cairo_set_line_width (cr, 1.0); + cairo_move_to (cr, width-3.5, pos); + cairo_line_to (cr, width, pos); + cairo_stroke (cr); + + layout->set_text(buf); + + /* we want logical extents, not ink extents here */ + + int tw, th; + layout->get_pixel_size(tw, th); + + int p = pos - (th / 2); + p = min (p, height - th); + p = max (p, 0); + + cairo_move_to (cr, width-5-tw, p); + pango_cairo_show_layout (cr, layout->gobj()); + } + } + + if (types.size() == 1) { + int tw, th; + layout->set_attributes (unit_font_attributes); + switch (types.at(0)) { + case DataType::AUDIO: + layout->set_text("dBFS"); + layout->get_pixel_size(tw, th); + break; + case DataType::MIDI: + layout->set_text("vel"); + layout->get_pixel_size(tw, th); + break; + } + Gdk::Color c = w.get_style()->get_fg (Gtk::STATE_ACTIVE); + cairo_set_source_rgb (cr, c.get_red_p(), c.get_green_p(), c.get_blue_p()); + cairo_move_to (cr, 1, height - th); + pango_cairo_show_layout (cr, layout->gobj()); + } + + cairo_pattern_t* pattern = cairo_pattern_create_for_surface (surface); + MetricPatterns::iterator p; + + if ((p = metric_patterns.find (w.get_name())) != metric_patterns.end()) { + cairo_pattern_destroy (p->second); + } + + metric_patterns[w.get_name()] = pattern; + + cairo_destroy (cr); + cairo_surface_destroy (surface); + + return pattern; +} + +/* XXX code-copy from gain_meter.cc -- TODO consolidate */ +gint +MeterStrip::meter_metrics_expose (GdkEventExpose *ev) +{ + Glib::RefPtr win (meter_metric_area.get_window()); + cairo_t* cr; + + cr = gdk_cairo_create (win->gobj()); + + /* clip to expose area */ + + gdk_cairo_rectangle (cr, &ev->area); + cairo_clip (cr); + + cairo_pattern_t* pattern; + MetricPatterns::iterator i = metric_patterns.find (meter_metric_area.get_name()); + + if (i == metric_patterns.end() || style_changed) { + pattern = render_metrics (meter_metric_area, _types); + } else { + pattern = i->second; + } + + cairo_move_to (cr, 0, 0); + cairo_set_source (cr, pattern); + + gint width, height; + win->get_size (width, height); + + cairo_rectangle (cr, 0, 0, width, height); + cairo_fill (cr); + + style_changed = false; + + cairo_destroy (cr); + + return true; } diff --git a/gtk2_ardour/meter_strip.h b/gtk2_ardour/meter_strip.h index f74093ba08..653e045a73 100644 --- a/gtk2_ardour/meter_strip.h +++ b/gtk2_ardour/meter_strip.h @@ -49,6 +49,7 @@ class MeterStrip : public Gtk::VBox ~MeterStrip (); void fast_update (); + boost::shared_ptr route() { return _route; } static PBD::Signal1 CatchDeletion; @@ -57,15 +58,29 @@ class MeterStrip : public Gtk::VBox PBD::ScopedConnectionList route_connections; void self_delete (); + gint meter_metrics_expose (GdkEventExpose *); + + typedef std::map MetricPatterns; + static MetricPatterns metric_patterns; + static cairo_pattern_t* render_metrics (Gtk::Widget &, std::vector); + + void on_theme_changed (); + bool style_changed; + + void on_size_allocate (Gtk::Allocation&); + void on_size_request (Gtk::Requisition*); + private: - Meterbridge& _meterbridge; - Gtk::Label *label; + Gtk::Label label; + Gtk::DrawingArea meter_metric_area; + std::vector _types; LevelMeter *level_meter; void meter_changed (); PBD::ScopedConnection _config_connection; void strip_property_changed (const PBD::PropertyChange&); + void meter_configuration_changed (ARDOUR::ChanCount); }; diff --git a/gtk2_ardour/meterbridge.cc b/gtk2_ardour/meterbridge.cc index 3eeb6404fa..0209d5075c 100644 --- a/gtk2_ardour/meterbridge.cc +++ b/gtk2_ardour/meterbridge.cc @@ -71,6 +71,45 @@ Meterbridge::instance () return _instance; } +/* copy from gtk2_ardour/mixer_ui.cc -- TODO consolidate + * used by Meterbridge::set_session() below + */ +struct SignalOrderRouteSorter { + bool operator() (boost::shared_ptr a, boost::shared_ptr b) { + if (a->is_master() || a->is_monitor()) { + /* "a" is a special route (master, monitor, etc), and comes + * last in the mixer ordering + */ + return false; + } else if (b->is_master() || b->is_monitor()) { + /* everything comes before b */ + return true; + } + return a->order_key (MixerSort) < b->order_key (MixerSort); + } +}; + +/* modified version of above + * used in Meterbridge::sync_order_keys() + */ +struct MeterOrderRouteSorter { + bool operator() (MeterStrip *ma, MeterStrip *mb) { + boost::shared_ptr a = ma->route(); + boost::shared_ptr b = mb->route(); + if (a->is_master() || a->is_monitor()) { + /* "a" is a special route (master, monitor, etc), and comes + * last in the mixer ordering + */ + return false; + } else if (b->is_master() || b->is_monitor()) { + /* everything comes before b */ + return true; + } + return a->order_key (MixerSort) < b->order_key (MixerSort); + } +}; + + Meterbridge::Meterbridge () : Window (Gtk::WINDOW_TOPLEVEL) , VisibilityTracker (*((Gtk::Window*) this)) @@ -82,6 +121,7 @@ Meterbridge::Meterbridge () signal_delete_event().connect (sigc::mem_fun (*this, &Meterbridge::hide_window)); signal_configure_event().connect (sigc::mem_fun (*ARDOUR_UI::instance(), &ARDOUR_UI::configure_handler)); + Route::SyncOrderKeys.connect (*this, invalidator (*this), boost::bind (&Meterbridge::sync_order_keys, this, _1), gui_context()); MeterStrip::CatchDeletion.connect (*this, invalidator (*this), boost::bind (&Meterbridge::remove_strip, this, _1), gui_context()); @@ -151,23 +191,6 @@ Meterbridge::on_key_release_event (GdkEventKey* ev) return true; } - -// copy from gtk2_ardour/mixer_ui.cc -struct SignalOrderRouteSorter { - bool operator() (boost::shared_ptr a, boost::shared_ptr b) { - if (a->is_master() || a->is_monitor()) { - /* "a" is a special route (master, monitor, etc), and comes - * last in the mixer ordering - */ - return false; - } else if (b->is_master() || b->is_monitor()) { - /* everything comes before b */ - return true; - } - return a->order_key (MixerSort) < b->order_key (MixerSort); - } -}; - void Meterbridge::set_session (Session* s) { @@ -191,8 +214,6 @@ Meterbridge::set_session (Session* s) _session->RouteAdded.connect (_session_connections, invalidator (*this), boost::bind (&Meterbridge::add_strips, this, _1), gui_context()); - _session->config.ParameterChanged.connect (_session_connections, invalidator (*this), boost::bind (&Meterbridge::parameter_changed, this, _1), gui_context()); - if (_visible) { show_window(); ActionManager::check_toggleaction ("/Common/toggle-meterbridge"); @@ -340,12 +361,11 @@ Meterbridge::add_strips (RouteList& routes) strip = new MeterStrip (*this, _session, route); strips.push_back (strip); - // TODO sort-routes, insert at proper position - // order_key - global_hpacker.pack_start (*strip, false, false); strip->show(); } + + sync_order_keys(MixerSort); } void @@ -362,6 +382,14 @@ Meterbridge::remove_strip (MeterStrip* strip) } void -Meterbridge::parameter_changed (string const & p) +Meterbridge::sync_order_keys (RouteSortOrderKey src) { + MeterOrderRouteSorter sorter; + std::list copy (strips); + copy.sort(sorter); + + int pos = 0; + for (list::iterator i = copy.begin(); i != copy.end(); ++i) { + global_hpacker.reorder_child(*(*i), pos++); + } } diff --git a/gtk2_ardour/meterbridge.h b/gtk2_ardour/meterbridge.h index 3bdf0483bd..99c6b16a7a 100644 --- a/gtk2_ardour/meterbridge.h +++ b/gtk2_ardour/meterbridge.h @@ -73,14 +73,14 @@ class Meterbridge : void add_strips (ARDOUR::RouteList&); void remove_strip (MeterStrip *); - void parameter_changed (std::string const &); - void session_going_away (); + void sync_order_keys (ARDOUR::RouteSortOrderKey src); std::list strips; static const int32_t default_width = 300; static const int32_t default_height = 400; + // for restoring window geometry. int m_root_x, m_root_y, m_width, m_height; -- cgit v1.2.3