From 1bca6b5c74064b9e8a6f70b45fb1c04a95534d30 Mon Sep 17 00:00:00 2001 From: Sampo Savolainen Date: Wed, 15 Oct 2008 19:21:26 +0000 Subject: Make EQ Gui optional and seize updating the graph when the analysis is not visible (either via the toggle or window visibility). git-svn-id: svn://localhost/ardour2/branches/3.0@3973 d708f5d6-7413-0410-9779-e7cbd77b26cf --- gtk2_ardour/plugin_eq_gui.cc | 569 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 569 insertions(+) create mode 100644 gtk2_ardour/plugin_eq_gui.cc (limited to 'gtk2_ardour/plugin_eq_gui.cc') diff --git a/gtk2_ardour/plugin_eq_gui.cc b/gtk2_ardour/plugin_eq_gui.cc new file mode 100644 index 0000000000..6b176fbde8 --- /dev/null +++ b/gtk2_ardour/plugin_eq_gui.cc @@ -0,0 +1,569 @@ +/* + Copyright (C) 2008 Paul Davis + Author: Sampo Savolainen + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +*/ + +#include "plugin_eq_gui.h" +#include "fft.h" + +#include "ardour_ui.h" +#include +#include + +#include +#include +#include + +#include + + +PluginEqGui::PluginEqGui(boost::shared_ptr pluginInsert) + : _min_dB(-12.0), + _max_dB(+12.0), + _step_dB(3.0), + _impulse_fft(0) +{ + _samplerate = ARDOUR_UI::instance()->the_session()->frame_rate(); + + _plugin = pluginInsert->get_impulse_analysis_plugin(); + _plugin->activate(); + + set_buffer_size(4096); + + _log_coeff = (1.0 - 2.0 * (1000.0/(_samplerate/2.0))) / powf(1000.0/(_samplerate/2.0), 2.0); + _log_max = log10f(1 + _log_coeff); + + + // Setup analysis drawing area + _analysis_scale_surface = 0; + + _analysis_area = new Gtk::DrawingArea(); + _analysis_width = 500.0; + _analysis_height = 500.0; + _analysis_area->set_size_request(_analysis_width, _analysis_height); + + _analysis_area->signal_expose_event().connect( sigc::mem_fun (*this, &PluginEqGui::expose_analysis_area)); + _analysis_area->signal_size_allocate().connect( sigc::mem_fun (*this, &PluginEqGui::resize_analysis_area)); + + + // dB selection + dBScaleModel = Gtk::ListStore::create(dBColumns); + + dBScaleCombo = new Gtk::ComboBox(dBScaleModel); + dBScaleCombo -> set_title("dB scale"); + +#define ADD_DB_ROW(MIN,MAX,STEP,NAME) \ + { \ + Gtk::TreeModel::Row row = *(dBScaleModel->append()); \ + row[dBColumns.dBMin] = (MIN); \ + row[dBColumns.dBMax] = (MAX); \ + row[dBColumns.dBStep] = (STEP); \ + row[dBColumns.name] = NAME; \ + } + + ADD_DB_ROW( -6, +6, 1, "-6dB .. +6dB"); + ADD_DB_ROW(-12, +12, 3, "-12dB .. +12dB"); + ADD_DB_ROW(-24, +24, 5, "-24dB .. +24dB"); + ADD_DB_ROW(-36, +36, 6, "-36dB .. +36dB"); + +#undef ADD_DB_ROW + + dBScaleCombo -> pack_start(dBColumns.name); + dBScaleCombo -> set_active(1); + + dBScaleCombo -> signal_changed().connect( sigc::mem_fun(*this, &PluginEqGui::change_dB_scale) ); + + Gtk::Label *dBComboLabel = new Gtk::Label("dB scale"); + + Gtk::HBox *dBSelectBin = new Gtk::HBox(false, 5); + dBSelectBin->add( *manage(dBComboLabel)); + dBSelectBin->add( *manage(dBScaleCombo)); + + // Phase checkbutton + _phase_button = new Gtk::CheckButton("Show phase"); + _phase_button->set_active(true); + _phase_button->signal_toggled().connect( sigc::mem_fun(*this, &PluginEqGui::redraw_scales)); + + // populate table + attach( *manage(_analysis_area), 1, 3, 1, 2); + attach( *manage(dBSelectBin), 1, 2, 2, 3, Gtk::SHRINK, Gtk::SHRINK); + attach( *manage(_phase_button), 2, 3, 2, 3, Gtk::SHRINK, Gtk::SHRINK); +} + +PluginEqGui::~PluginEqGui() +{ + if (_analysis_scale_surface) { + cairo_surface_destroy (_analysis_scale_surface); + } + + delete _impulse_fft; + _plugin->deactivate(); + + // all gui objects are *manage'd by the inherited Table object +} + + +void +PluginEqGui::on_hide() +{ + stop_updating(); + Gtk::Table::on_hide(); +} + +void +PluginEqGui::stop_updating() +{ + if (_update_connection.connected()) { + _update_connection.disconnect(); + } +} + +void +PluginEqGui::start_updating() +{ + if (!_update_connection.connected() && is_visible()) { + _update_connection = Glib::signal_timeout().connect( sigc::mem_fun(this, &PluginEqGui::timeout_callback), 250); + } +} + +void +PluginEqGui::on_show() +{ + Gtk::Table::on_show(); + + start_updating(); + + Gtk::Widget *toplevel = get_toplevel(); + if (!toplevel) { + std::cerr << "No toplevel widget for PluginEqGui?!?!" << std::endl; + } + + if (!_window_unmap_connection.connected()) { + _window_unmap_connection = toplevel->signal_unmap().connect( sigc::mem_fun(this, &PluginEqGui::stop_updating)); + } + + if (!_window_map_connection.connected()) { + _window_map_connection = toplevel->signal_map().connect( sigc::mem_fun(this, &PluginEqGui::start_updating)); + } + +} + +void +PluginEqGui::change_dB_scale() +{ + Gtk::TreeModel::iterator iter = dBScaleCombo -> get_active(); + + Gtk::TreeModel::Row row; + + if(iter && (row = *iter)) { + _min_dB = row[dBColumns.dBMin]; + _max_dB = row[dBColumns.dBMax]; + _step_dB = row[dBColumns.dBStep]; + + + redraw_scales(); + } +} + +void +PluginEqGui::redraw_scales() +{ + + if (_analysis_scale_surface) { + cairo_surface_destroy (_analysis_scale_surface); + _analysis_scale_surface = 0; + } + + _analysis_area->queue_draw(); +} + +void +PluginEqGui::set_buffer_size(uint32_t size) +{ + if (_buffer_size == size) + return; + + _buffer_size = size; + + if (_impulse_fft) { + delete _impulse_fft; + _impulse_fft = 0; + } + + _impulse_fft = new FFT(_buffer_size); + + uint32_t inputs = _plugin->get_info()->n_inputs.n_audio(); + uint32_t outputs = _plugin->get_info()->n_outputs.n_audio(); + + uint32_t n_chans = std::max(inputs, outputs); + _bufferset.ensure_buffers(ARDOUR::DataType::AUDIO, n_chans, _buffer_size); + + ARDOUR::ChanCount chanCount(ARDOUR::DataType::AUDIO, n_chans); + _bufferset.set_count(chanCount); +} + +void +PluginEqGui::resize_analysis_area(Gtk::Allocation& size) +{ + _analysis_width = (float)size.get_width(); + _analysis_height = (float)size.get_height(); + + if (_analysis_scale_surface) { + cairo_surface_destroy (_analysis_scale_surface); + _analysis_scale_surface = 0; + } +} + +bool +PluginEqGui::timeout_callback() +{ + run_analysis(); + + return true; +} + +void +PluginEqGui::run_analysis() +{ + uint32_t inputs = _plugin->get_info()->n_inputs.n_audio(); + uint32_t outputs = _plugin->get_info()->n_outputs.n_audio(); + + // Create the impulse, can't use silence() because consecutive calls won't work + for (uint32_t i = 0; i < inputs; ++i) { + ARDOUR::AudioBuffer &buf = _bufferset.get_audio(i); + ARDOUR::Sample *d = buf.data(_buffer_size, 0); + memset(d, 0, sizeof(ARDOUR::Sample)*_buffer_size); + *d = 1.0; + } + uint32_t x,y; + x=y=0; + + _plugin->connect_and_run(_bufferset, x, y, _buffer_size, (nframes_t)0); + + // Analyze all output buffers + _impulse_fft->reset(); + for (uint32_t i = 0; i < outputs; ++i) { + _impulse_fft->analyze(_bufferset.get_audio(i).data(_buffer_size, 0)); + } + + // normalize the output + _impulse_fft->calculate(); + + // This signals calls expose_analysis_area() + _analysis_area->queue_draw(); + +} + +bool +PluginEqGui::expose_analysis_area(GdkEventExpose *evt) +{ + redraw_analysis_area(); + + return false; +} + +void +PluginEqGui::draw_analysis_scales(cairo_t *ref_cr) +{ + // TODO: check whether we need rounding + _analysis_scale_surface = cairo_surface_create_similar(cairo_get_target(ref_cr), + CAIRO_CONTENT_COLOR, + _analysis_width, + _analysis_height); + + cairo_t *cr = cairo_create (_analysis_scale_surface); + + cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); + cairo_rectangle(cr, 0.0, 0.0, _analysis_width, _analysis_height); + cairo_fill(cr); + + + draw_scales_power(_analysis_area, cr); + if (_phase_button->get_active()) { + draw_scales_phase(_analysis_area, cr); + } + + cairo_destroy(cr); + +} + +void +PluginEqGui::redraw_analysis_area() +{ + cairo_t *cr; + + cr = gdk_cairo_create(GDK_DRAWABLE(_analysis_area->get_window()->gobj())); + + if (_analysis_scale_surface == 0) { + draw_analysis_scales(cr); + } + + + cairo_copy_page(cr); + + cairo_set_source_surface(cr, _analysis_scale_surface, 0.0, 0.0); + cairo_paint(cr); + + if (_phase_button->get_active()) { + plot_phase(_analysis_area, cr); + } + plot_amplitude(_analysis_area, cr); + + + cairo_destroy(cr); + + +} + +#define PHASE_PROPORTION 0.6 + +void +PluginEqGui::draw_scales_phase(Gtk::Widget *w, cairo_t *cr) +{ + float y; + cairo_font_extents_t extents; + cairo_font_extents(cr, &extents); + + char buf[256]; + cairo_text_extents_t t_ext; + + for (uint32_t i = 0; i < 3; i++) { + + y = _analysis_height/2.0 - (float)i*(_analysis_height/8.0)*PHASE_PROPORTION; + + cairo_set_source_rgb(cr, .8, .9, 0.2); + if (i == 0) { + snprintf(buf,256, "0\u00b0"); + } else { + snprintf(buf,256, "%d\u00b0", (i * 45)); + } + cairo_text_extents(cr, buf, &t_ext); + cairo_move_to(cr, _analysis_width - t_ext.width - t_ext.x_bearing - 2.0, y - extents.descent); + cairo_show_text(cr, buf); + + if (i == 0) + continue; + + + cairo_set_source_rgba(cr, .8, .9, 0.2, 0.6/(float)i); + cairo_move_to(cr, 0.0, y); + cairo_line_to(cr, _analysis_width, y); + + + y = _analysis_height/2.0 + (float)i*(_analysis_height/8.0)*PHASE_PROPORTION; + + // label + snprintf(buf,256, "-%d\u00b0", (i * 45)); + cairo_set_source_rgb(cr, .8, .9, 0.2); + cairo_text_extents(cr, buf, &t_ext); + cairo_move_to(cr, _analysis_width - t_ext.width - t_ext.x_bearing - 2.0, y - extents.descent); + cairo_show_text(cr, buf); + + // line + cairo_set_source_rgba(cr, .8, .9, 0.2, 0.6/(float)i); + cairo_move_to(cr, 0.0, y); + cairo_line_to(cr, _analysis_width, y); + + cairo_set_line_width (cr, 0.25 + 1.0/(float)(i+1)); + cairo_stroke(cr); + } +} + +void +PluginEqGui::plot_phase(Gtk::Widget *w, cairo_t *cr) +{ + float x,y; + + int prevX = 0; + float avgY = 0.0; + int avgNum = 0; + + cairo_set_source_rgba(cr, 0.95, 0.3, 0.2, 1.0); + for (uint32_t i = 0; i < _impulse_fft->bins()-1; i++) { + // x coordinate of bin i + x = log10f(1.0 + (float)i / (float)_impulse_fft->bins() * _log_coeff) / _log_max; + x *= _analysis_width; + + y = _analysis_height/2.0 - (_impulse_fft->phase_at_bin(i)/M_PI)*(_analysis_height/2.0)*PHASE_PROPORTION; + + if ( i == 0 ) { + cairo_move_to(cr, x, y); + + avgY = 0; + avgNum = 0; + } else if (rint(x) > prevX || i == _impulse_fft->bins()-1 ) { + cairo_line_to(cr, prevX, avgY/(float)avgNum); + + avgY = 0; + avgNum = 0; + + } + + prevX = rint(x); + avgY += y; + avgNum++; + } + + cairo_set_line_width (cr, 2.0); + cairo_stroke(cr); +} + +void +PluginEqGui::draw_scales_power(Gtk::Widget *w, cairo_t *cr) +{ + static float scales[] = { 30.0, 70.0, 125.0, 250.0, 500.0, 1000.0, 2000.0, 5000.0, 10000.0, 15000.0, 20000.0, -1.0 }; + + float divisor = _samplerate / 2.0 / _impulse_fft->bins(); + float x; + + cairo_set_line_width (cr, 1.5); + cairo_set_font_size(cr, 9); + + cairo_font_extents_t extents; + cairo_font_extents(cr, &extents); + float fontXOffset = extents.descent + 1.0; + + char buf[256]; + + for (uint32_t i = 0; scales[i] != -1.0; ++i) { + float bin = scales[i] / divisor; + + x = log10f(1.0 + bin / (float)_impulse_fft->bins() * _log_coeff) / _log_max; + x *= _analysis_width; + + if (scales[i] < 1000.0) { + snprintf(buf, 256, "%0.0f", scales[i]); + } else { + snprintf(buf, 256, "%0.0fk", scales[i]/1000.0); + } + + cairo_set_source_rgb(cr, 0.4, 0.4, 0.4); + + cairo_move_to(cr, x + fontXOffset, 3.0); + + cairo_rotate(cr, M_PI / 2.0); + cairo_show_text(cr, buf); + cairo_rotate(cr, -M_PI / 2.0); + cairo_stroke(cr); + + cairo_set_source_rgb(cr, 0.3, 0.3, 0.3); + cairo_move_to(cr, x, _analysis_height); + cairo_line_to(cr, x, 0.0); + cairo_stroke(cr); + } + + float y; + + //double dashes[] = { 1.0, 3.0, 4.5, 3.0 }; + double dashes[] = { 3.0, 5.0 }; + + for (float dB = 0.0; dB < _max_dB; dB += _step_dB ) { + snprintf(buf, 256, "+%0.0f", dB ); + + y = ( _max_dB - dB) / ( _max_dB - _min_dB ); + //std::cerr << " y = " << y << std::endl; + y *= _analysis_height; + + if (dB != 0.0) { + cairo_set_source_rgb(cr, 0.4, 0.4, 0.4); + cairo_move_to(cr, 1.0, y + extents.height + 1.0); + cairo_show_text(cr, buf); + cairo_stroke(cr); + } + + cairo_set_source_rgb(cr, 0.2, 0.2, 0.2); + cairo_move_to(cr, 0, y); + cairo_line_to(cr, _analysis_width, y); + cairo_stroke(cr); + + if (dB == 0.0) { + cairo_set_dash(cr, dashes, 2, 0.0); + } + } + + + + for (float dB = - _step_dB; dB > _min_dB; dB -= _step_dB ) { + snprintf(buf, 256, "%0.0f", dB ); + + y = ( _max_dB - dB) / ( _max_dB - _min_dB ); + y *= _analysis_height; + + cairo_set_source_rgb(cr, 0.4, 0.4, 0.4); + cairo_move_to(cr, 1.0, y - extents.descent - 1.0); + cairo_show_text(cr, buf); + cairo_stroke(cr); + + cairo_set_source_rgb(cr, 0.2, 0.2, 0.2); + cairo_move_to(cr, 0, y); + cairo_line_to(cr, _analysis_width, y); + cairo_stroke(cr); + } + + cairo_set_dash(cr, 0, 0, 0.0); + +} + +inline float +power_to_dB(float a) +{ + return 10.0 * log10f(a); +} + +void +PluginEqGui::plot_amplitude(Gtk::Widget *w, cairo_t *cr) +{ + float x,y; + + int prevX = 0; + float avgY = 0.0; + int avgNum = 0; + + cairo_set_source_rgb(cr, 1.0, 1.0, 1.0); + cairo_set_line_width (cr, 2.5); + + for (uint32_t i = 0; i < _impulse_fft->bins()-1; i++) { + // x coordinate of bin i + x = log10f(1.0 + (float)i / (float)_impulse_fft->bins() * _log_coeff) / _log_max; + x *= _analysis_width; + + float yCoeff = ( power_to_dB(_impulse_fft->power_at_bin(i)) - _min_dB) / (_max_dB - _min_dB); + + y = _analysis_height - _analysis_height*yCoeff; + + if ( i == 0 ) { + cairo_move_to(cr, x, y); + + avgY = 0; + avgNum = 0; + } else if (rint(x) > prevX || i == _impulse_fft->bins()-1 ) { + cairo_line_to(cr, prevX, avgY/(float)avgNum); + + avgY = 0; + avgNum = 0; + + } + + prevX = rint(x); + avgY += y; + avgNum++; + } + + cairo_stroke(cr); +} + -- cgit v1.2.3