ardour/gtk2_ardour/plugin_eq_gui.cc
Robin Gareus cc5e758a08 Fix plugin analysis for some VST and AU Plugins.
set_block_size() implies plugin deactivate(), activate() calls
to re-initialize AU and VST plugins. So plugins will reset the
internal state and not immediately respond correctly.
Some plugins zero the output or ramp up internally, leading the analyzer
to show invalid or random/uncorrelated information.

This avoid periodic calls to de/activate()
2018-04-13 23:19:13 +02:00

852 lines
22 KiB
C++

/*
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 <algorithm>
#include <math.h>
#include <iomanip>
#include <iostream>
#include <sstream>
#ifdef COMPILER_MSVC
#include <float.h>
/* isinf() & isnan() are C99 standards, which older MSVC doesn't provide */
#define ISINF(val) !((bool)_finite((double)val))
#define ISNAN(val) (bool)_isnan((double)val)
#else
#define ISINF(val) std::isinf((val))
#define ISNAN(val) std::isnan((val))
#endif
#include <gtkmm/box.h>
#include <gtkmm/button.h>
#include <gtkmm/checkbutton.h>
#include "ardour/audio_buffer.h"
#include "ardour/data_type.h"
#include "ardour/chan_mapping.h"
#include "ardour/plugin_insert.h"
#include "ardour/session.h"
#include "plugin_eq_gui.h"
#include "fft.h"
#include "ardour_ui.h"
#include "gui_thread.h"
#include "pbd/i18n.h"
using namespace ARDOUR;
PluginEqGui::PluginEqGui(boost::shared_ptr<ARDOUR::PluginInsert> pluginInsert)
: _min_dB(-12.0)
, _max_dB(+12.0)
, _step_dB(3.0)
, _buffer_size(0)
, _signal_buffer_size(0)
, _impulse_fft(0)
, _signal_input_fft(0)
, _signal_output_fft(0)
, _plugin_insert(pluginInsert)
{
_signal_analysis_running = false;
_samplerate = ARDOUR_UI::instance()->the_session()->sample_rate();
_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 = 256.0;
_analysis_height = 256.0;
_analysis_area->set_size_request(_analysis_width, _analysis_height);
_analysis_area->add_events(Gdk::POINTER_MOTION_MASK | Gdk::LEAVE_NOTIFY_MASK | Gdk::BUTTON_PRESS_MASK);
_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));
_analysis_area->signal_motion_notify_event().connect( sigc::mem_fun (*this, &PluginEqGui::analysis_area_mouseover));
_analysis_area->signal_leave_notify_event().connect( sigc::mem_fun (*this, &PluginEqGui::analysis_area_mouseexit));
_analysis_area->signal_button_press_event().connect( sigc::mem_fun (*this, &PluginEqGui::analysis_area_mousedown));
// dB selection
dBScaleModel = Gtk::ListStore::create(dBColumns);
dBScaleCombo = new Gtk::ComboBox (dBScaleModel, false);
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");
ADD_DB_ROW(-64, +64,12, "-64dB .. +64dB");
#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));
// Freq/dB info for mouse over
_pointer_info = new Gtk::Label ("", 1, 0.5);
_pointer_info->set_size_request(_analysis_width / 4, -1);
_pointer_info->set_name("PluginAnalysisInfoLabel");
// populate table
attach( *manage(_analysis_area), 1, 4, 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);
attach( *manage(_pointer_info), 3, 4, 2, 3, Gtk::FILL, Gtk::SHRINK);
}
PluginEqGui::~PluginEqGui()
{
stop_listening ();
if (_analysis_scale_surface) {
cairo_surface_destroy (_analysis_scale_surface);
}
delete _impulse_fft;
_impulse_fft = 0;
delete _signal_input_fft;
_signal_input_fft = 0;
delete _signal_output_fft;
_signal_output_fft = 0;
// all gui objects are *manage'd by the inherited Table object
}
void
PluginEqGui::start_listening ()
{
if (!_plugin) {
_plugin = _plugin_insert->get_impulse_analysis_plugin();
}
_plugin->activate();
set_buffer_size(4096, 16384);
_plugin->set_block_size (_buffer_size);
// Connect the realtime signal collection callback
_plugin_insert->AnalysisDataGathered.connect (analysis_connection, invalidator (*this), boost::bind (&PluginEqGui::signal_collect_callback, this, _1, _2), gui_context());
}
void
PluginEqGui::stop_listening ()
{
analysis_connection.disconnect ();
_plugin->deactivate ();
}
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) {
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();
// TODO: Add graph legend!
}
void
PluginEqGui::set_buffer_size(uint32_t size, uint32_t signal_size)
{
if (_buffer_size == size && _signal_buffer_size == signal_size) {
return;
}
GTKArdour::FFT *tmp1 = _impulse_fft;
GTKArdour::FFT *tmp2 = _signal_input_fft;
GTKArdour::FFT *tmp3 = _signal_output_fft;
try {
_impulse_fft = new GTKArdour::FFT(size);
_signal_input_fft = new GTKArdour::FFT(signal_size);
_signal_output_fft = new GTKArdour::FFT(signal_size);
} catch( ... ) {
// Don't care about lost memory, we're screwed anyhow
_impulse_fft = tmp1;
_signal_input_fft = tmp2;
_signal_output_fft = tmp3;
throw;
}
delete tmp1;
delete tmp2;
delete tmp3;
_buffer_size = size;
_signal_buffer_size = signal_size;
// allocate separate in+out buffers, VST cannot process in-place
ARDOUR::ChanCount acount (_plugin->get_info()->n_inputs + _plugin->get_info()->n_outputs);
ARDOUR::ChanCount ccount = ARDOUR::ChanCount::max (_plugin->get_info()->n_inputs, _plugin->get_info()->n_outputs);
for (ARDOUR::DataType::iterator i = ARDOUR::DataType::begin(); i != ARDOUR::DataType::end(); ++i) {
_bufferset.ensure_buffers (*i, acount.get (*i), _buffer_size);
_collect_bufferset.ensure_buffers (*i, ccount.get (*i), _buffer_size);
}
_bufferset.set_count (acount);
_collect_bufferset.set_count (ccount);
}
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;
}
_pointer_info->set_size_request(_analysis_width / 4, -1);
}
bool
PluginEqGui::timeout_callback()
{
if (!_signal_analysis_running) {
_signal_analysis_running = true;
_plugin_insert -> collect_signal_for_analysis(_signal_buffer_size);
}
run_impulse_analysis();
return true;
}
void
PluginEqGui::signal_collect_callback(ARDOUR::BufferSet *in, ARDOUR::BufferSet *out)
{
ENSURE_GUI_THREAD (*this, &PluginEqGui::signal_collect_callback, in, out);
_signal_input_fft ->reset();
_signal_output_fft->reset();
for (uint32_t i = 0; i < _plugin_insert->input_streams().n_audio(); ++i) {
_signal_input_fft ->analyze(in ->get_audio(i).data(), GTKArdour::FFT::HANN);
}
for (uint32_t i = 0; i < _plugin_insert->output_streams().n_audio(); ++i) {
_signal_output_fft->analyze(out->get_audio(i).data(), GTKArdour::FFT::HANN);
}
_signal_input_fft ->calculate();
_signal_output_fft->calculate();
_signal_analysis_running = false;
// This signals calls expose_analysis_area()
_analysis_area->queue_draw();
}
void
PluginEqGui::run_impulse_analysis()
{
/* Allocate some thread-local buffers so that Plugin::connect_and_run can use them */
ARDOUR_UI::instance()->get_process_buffers ();
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();
memset(d, 0, sizeof(ARDOUR::Sample)*_buffer_size);
*d = 1.0;
}
ARDOUR::ChanMapping in_map(_plugin->get_info()->n_inputs);
ARDOUR::ChanMapping out_map(_plugin->get_info()->n_outputs);
// map output buffers after input buffers (no inplace for VST)
out_map.offset_to (DataType::AUDIO, inputs);
_plugin->connect_and_run(_bufferset, 0, _buffer_size, 1.0, in_map, out_map, _buffer_size, 0);
samplecnt_t f = _plugin->signal_latency ();
// Adding user_latency() could be interesting
// Gather all output, taking latency into account.
_impulse_fft->reset();
// Silence collect buffers to copy data to, can't use silence() because consecutive calls won't work
for (uint32_t i = 0; i < outputs; ++i) {
ARDOUR::AudioBuffer &buf = _collect_bufferset.get_audio(i);
ARDOUR::Sample *d = buf.data();
memset(d, 0, sizeof(ARDOUR::Sample)*_buffer_size);
}
if (f == 0) {
//std::cerr << "0: no latency, copying full buffer, trivial.." << std::endl;
for (uint32_t i = 0; i < outputs; ++i) {
memcpy(_collect_bufferset.get_audio(i).data(),
_bufferset.get_audio(inputs + i).data(), _buffer_size * sizeof(float));
}
} else {
//int C = 0;
//std::cerr << (++C) << ": latency is " << f << " samples, doing split processing.." << std::endl;
samplecnt_t target_offset = 0;
samplecnt_t samples_left = _buffer_size; // refaktoroi
do {
if (f >= _buffer_size) {
//std::cerr << (++C) << ": f (=" << f << ") is larger than buffer_size, still trying to reach the actual output" << std::endl;
// there is no data in this buffer regarding to the input!
f -= _buffer_size;
} else {
// this buffer contains either the first, last or a whole bu the output of the impulse
// first part: offset is 0, so we copy to the start of _collect_bufferset
// we start at output offset "f"
// .. and copy "buffer size" - "f" - "offset" samples
samplecnt_t length = _buffer_size - f - target_offset;
//std::cerr << (++C) << ": copying " << length << " samples to _collect_bufferset.get_audio(i)+" << target_offset << " from bufferset at offset " << f << std::endl;
for (uint32_t i = 0; i < outputs; ++i) {
memcpy(_collect_bufferset.get_audio(i).data(target_offset),
_bufferset.get_audio(inputs + i).data() + f,
length * sizeof(float));
}
target_offset += length;
samples_left -= length;
f = 0;
}
if (samples_left > 0) {
// Silence the buffers
for (uint32_t i = 0; i < inputs; ++i) {
ARDOUR::AudioBuffer &buf = _bufferset.get_audio(i);
ARDOUR::Sample *d = buf.data();
memset(d, 0, sizeof(ARDOUR::Sample)*_buffer_size);
}
_plugin->connect_and_run (_bufferset, target_offset, target_offset + _buffer_size, 1.0, in_map, out_map, _buffer_size, 0);
}
} while ( samples_left > 0);
}
for (uint32_t i = 0; i < outputs; ++i) {
_impulse_fft->analyze(_collect_bufferset.get_audio(i).data());
}
// normalize the output
_impulse_fft->calculate();
// This signals calls expose_analysis_area()
_analysis_area->queue_draw();
ARDOUR_UI::instance()->drop_process_buffers ();
}
void
PluginEqGui::update_pointer_info(float x, float y)
{
const int freq = std::max(1, (int) roundf((powf(10, x / _analysis_width * _log_max) - 1) * _samplerate / 2.0 / _log_coeff));
const float dB = _max_dB - y / _analysis_height * ( _max_dB - _min_dB );
std::stringstream ss;
ss << std::fixed;
if (freq >= 10000) {
ss << std::setprecision (1) << freq / 1000.0 << "kHz";
} else if (freq >= 1000) {
ss << std::setprecision (2) << freq / 1000.0 << "kHz";
} else {
ss << std::setprecision (0) << freq << "Hz";
}
ss << " " << std::setw(5) << std::setprecision (1) << std::showpos << dB;
ss << std::setw(0) << "dB";
_pointer_info->set_text(ss.str());
}
bool
PluginEqGui::analysis_area_mouseover(GdkEventMotion *event)
{
update_pointer_info(event->x, event->y);
return true;
}
bool
PluginEqGui::analysis_area_mouseexit(GdkEventCrossing *)
{
_pointer_info->set_text("");
return true;
}
bool
PluginEqGui::analysis_area_mousedown(GdkEventButton *event)
{
update_pointer_info(event->x, event->y);
return true;
}
bool
PluginEqGui::expose_analysis_area(GdkEventExpose *)
{
redraw_analysis_area();
return true;
}
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_impulse_phase(_analysis_area, cr);
}
plot_impulse_amplitude(_analysis_area, cr);
// TODO: make this optional
plot_signal_amplitude_difference(_analysis_area, cr);
cairo_destroy(cr);
}
#define PHASE_PROPORTION 0.5
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_impulse_phase(Gtk::Widget *w, cairo_t *cr)
{
float x,y;
int prevX = 0;
float avgY = 0.0;
int avgNum = 0;
// float width = w->get_width();
float height = w->get_height();
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 ) {
avgY = avgY/(float)avgNum;
if (avgY > (height * 10.0) ) avgY = height * 10.0;
if (avgY < (-height * 10.0) ) avgY = -height * 10.0;
cairo_line_to(cr, prevX, avgY);
//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)
{
if (_impulse_fft == 0) {
return;
}
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_move_to(cr, x - extents.height, 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_impulse_amplitude(Gtk::Widget *w, cairo_t *cr)
{
float x,y;
int prevX = 0;
float avgY = 0.0;
int avgNum = 0;
// float width = w->get_width();
float height = w->get_height();
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 ) {
avgY = avgY/(float)avgNum;
if (avgY > (height * 10.0) ) avgY = height * 10.0;
if (avgY < (-height * 10.0) ) avgY = -height * 10.0;
cairo_line_to(cr, prevX, avgY);
//cairo_line_to(cr, prevX, avgY/(float)avgNum);
avgY = 0;
avgNum = 0;
}
prevX = rint(x);
avgY += y;
avgNum++;
}
cairo_stroke(cr);
}
void
PluginEqGui::plot_signal_amplitude_difference(Gtk::Widget *w, cairo_t *cr)
{
float x,y;
int prevX = 0;
float avgY = 0.0;
int avgNum = 0;
// float width = w->get_width();
float height = w->get_height();
cairo_set_source_rgb(cr, 0.0, 1.0, 0.0);
cairo_set_line_width (cr, 1.5);
for (uint32_t i = 0; i < _signal_input_fft->bins()-1; i++) {
// x coordinate of bin i
x = log10f(1.0 + (float)i / (float)_signal_input_fft->bins() * _log_coeff) / _log_max;
x *= _analysis_width;
float power_out = _signal_output_fft->power_at_bin (i) + 1e-30;
float power_in = _signal_input_fft ->power_at_bin (i) + 1e-30;
float power = power_to_dB (power_out / power_in);
assert (!ISINF(power));
assert (!ISNAN(power));
float yCoeff = ( power - _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 ) {
avgY = avgY/(float)avgNum;
if (avgY > (height * 10.0) ) avgY = height * 10.0;
if (avgY < (-height * 10.0) ) avgY = -height * 10.0;
cairo_line_to(cr, prevX, avgY);
avgY = 0;
avgNum = 0;
}
prevX = rint(x);
avgY += y;
avgNum++;
}
cairo_stroke(cr);
}