From d9106e99a456a7aa45fc4a9a3e4d4282f63ed59d Mon Sep 17 00:00:00 2001 From: Sampo Savolainen Date: Mon, 13 Oct 2008 19:45:20 +0000 Subject: First draft of the EQ visualization system. Now force fed to all plugin UIs. git-svn-id: svn://localhost/ardour2/branches/3.0@3958 d708f5d6-7413-0410-9779-e7cbd77b26cf --- gtk2_ardour/SConscript | 21 +- gtk2_ardour/editor.cc | 13 - gtk2_ardour/editor.h | 6 - gtk2_ardour/eq_gui.cc | 562 +++++++++++++++++++++++++++++++++++++ gtk2_ardour/eq_gui.h | 130 +++++++++ gtk2_ardour/fft.cc | 107 +++++++ gtk2_ardour/fft.h | 65 +++++ gtk2_ardour/plugin_ui.cc | 11 +- libs/ardour/ardour/plugin_insert.h | 6 + libs/ardour/plugin_insert.cc | 19 ++ 10 files changed, 905 insertions(+), 35 deletions(-) create mode 100644 gtk2_ardour/eq_gui.cc create mode 100644 gtk2_ardour/eq_gui.h create mode 100644 gtk2_ardour/fft.cc create mode 100644 gtk2_ardour/fft.h diff --git a/gtk2_ardour/SConscript b/gtk2_ardour/SConscript index 818b0fa15c..2f105427c1 100644 --- a/gtk2_ardour/SConscript +++ b/gtk2_ardour/SConscript @@ -78,10 +78,6 @@ if gtkardour['DMALLOC']: gtkardour.Merge([libraries['dmalloc']]) gtkardour.Append(CCFLAGS='-DUSE_DMALLOC') -if gtkardour['FFT_ANALYSIS']: - gtkardour.Merge ([libraries['fftw3f']]) - gtkardour.Append(CCFLAGS='-DFFT_ANALYSIS') - if gtkardour['FREESOUND']: gtkardour.Merge ([libraries['curl']]) gtkardour.Append(CCFLAGS='-DFREESOUND') @@ -112,6 +108,7 @@ about.cc actions.cc add_midi_cc_track_dialog.cc add_route_dialog.cc +analysis_window.cc ardour_dialog.cc ardour_ui.cc ardour_ui2.cc @@ -179,6 +176,10 @@ export_format_dialog.cc export_format_selector.cc export_preset_selector.cc export_timespan_selector.cc +fft.cc +fft_graph.cc +fft_result.cc +eq_gui.cc gain_meter.cc generic_pluginui.cc ghostregion.cc @@ -245,12 +246,6 @@ version.cc waveview.cc """) -fft_analysis_files=Split(""" -analysis_window.cc -fft_graph.cc -fft_result.cc -""") - freesound_files=Split(""" sfdb_freesound_mootcher.cc """) @@ -315,9 +310,6 @@ else: extra_sources += x11_files -if env['FFT_ANALYSIS']: - extra_sources += fft_analysis_files - if env['FREESOUND']: extra_sources += freesound_files @@ -481,7 +473,7 @@ else: env.Alias('install', env.InstallAs(os.path.join(install_prefix, 'bin')+'/ardour3', ardoursh)) if env['NLS']: - i18n (gtkardour, gtkardour_files+skipped_files+fft_analysis_files, env) + i18n (gtkardour, gtkardour_files+skipped_files, env) # configuration files env.Alias('install', env.Install(os.path.join(config_prefix, 'ardour3'), ardour_dark_theme)) @@ -564,7 +556,6 @@ env.Alias ('tarball', env.Distribute (env['DISTTREE'], audiounit_files + gtkosx_files + x11_files + - fft_analysis_files + freesound_files + glob.glob('po/*.po') + glob.glob('*.h'))) diff --git a/gtk2_ardour/editor.cc b/gtk2_ardour/editor.cc index fb62ad8d70..2edc155159 100644 --- a/gtk2_ardour/editor.cc +++ b/gtk2_ardour/editor.cc @@ -86,10 +86,7 @@ #include "rhythm_ferret.h" #include "actions.h" #include "tempo_lines.h" - -#ifdef FFT_ANALYSIS #include "analysis_window.h" -#endif #include "i18n.h" @@ -299,9 +296,7 @@ Editor::Editor () interthread_progress_window = 0; logo_item = 0; -#ifdef FFT_ANALYSIS analysis_window = 0; -#endif current_interthread_info = 0; _show_measures = true; @@ -1285,10 +1280,8 @@ Editor::connect_to_session (Session *t) rhythm_ferret->set_session (session); } -#ifdef FFT_ANALYSIS if (analysis_window != 0) analysis_window->set_session (session); -#endif Location* loc = session->locations()->auto_loop_location(); if (loc == 0) { @@ -1760,7 +1753,6 @@ Editor::build_track_crossfade_context_menu (nframes64_t frame) return &track_crossfade_context_menu; } -#ifdef FFT_ANALYSIS void Editor::analyze_region_selection() { @@ -1796,7 +1788,6 @@ Editor::analyze_range_selection() analysis_window->present(); } -#endif /* FFT_ANALYSIS */ Menu* Editor::build_track_selection_context_menu (nframes64_t ignored) @@ -1923,11 +1914,9 @@ Editor::add_region_context_items (StreamView* sv, boost::shared_ptr regi items.push_back (MenuElem (_("Export"), mem_fun(*this, &Editor::export_region))); items.push_back (MenuElem (_("Bounce"), mem_fun(*this, &Editor::bounce_region_selection))); -#ifdef FFT_ANALYSIS if (ar) { items.push_back (MenuElem (_("Spectral Analysis"), mem_fun(*this, &Editor::analyze_region_selection))); } -#endif items.push_back (SeparatorElem()); @@ -2110,10 +2099,8 @@ Editor::add_selection_context_items (Menu_Helpers::MenuList& edit_items) items.push_back (MenuElem (_("Play range"), mem_fun(*this, &Editor::play_selection))); items.push_back (MenuElem (_("Loop range"), bind (mem_fun(*this, &Editor::set_loop_from_selection), true))); -#ifdef FFT_ANALYSIS items.push_back (SeparatorElem()); items.push_back (MenuElem (_("Spectral Analysis"), mem_fun(*this, &Editor::analyze_range_selection))); -#endif items.push_back (SeparatorElem()); items.push_back (MenuElem (_("Extend Range to End of Region"), bind (mem_fun(*this, &Editor::extend_selection_to_end_of_region), false))); diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h index 3a4ebbc993..bd161f792b 100644 --- a/gtk2_ardour/editor.h +++ b/gtk2_ardour/editor.h @@ -107,9 +107,7 @@ class AudioStreamView; class ControlPoint; class SoundFileOmega; class RhythmFerret; -#ifdef FFT_ANALYSIS class AnalysisWindow; -#endif /* */ class ImageFrameView; @@ -261,11 +259,9 @@ class Editor : public PublicEditor void set_show_measures (bool yn); bool show_measures () const { return _show_measures; } -#ifdef FFT_ANALYSIS /* analysis window */ void analyze_region_selection(); void analyze_range_selection(); -#endif /* export */ @@ -1284,9 +1280,7 @@ class Editor : public PublicEditor void build_interthread_progress_window (); ARDOUR::InterThreadInfo* current_interthread_info; -#ifdef FFT_ANALYSIS AnalysisWindow* analysis_window; -#endif /* import specific info */ diff --git a/gtk2_ardour/eq_gui.cc b/gtk2_ardour/eq_gui.cc new file mode 100644 index 0000000000..c58bfa3025 --- /dev/null +++ b/gtk2_ardour/eq_gui.cc @@ -0,0 +1,562 @@ +/* + 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 "eq_gui.h" +#include "fft.h" + +#include "ardour_ui.h" +#include +#include + +#include +#include +#include + +#include + + +PluginEqGui::PluginEqGui(boost::shared_ptr pluginInsert) + : _mindB(-12.0), + _maxdB(+12.0), + _dBStep(3.0), + _impulseFft(0) +{ + _samplerate = ARDOUR_UI::instance()->the_session()->frame_rate(); + + _plugin = pluginInsert->get_impulse_analysis_plugin(); + _plugin->activate(); + + setBufferSize(4096); + + _logCoeff = (1.0 - 2.0 * (1000.0/(_samplerate/2.0))) / powf(1000.0/(_samplerate/2.0), 2.0); + _logMax = log10f(1 + _logCoeff); + + + // Setup analysis drawing area + _analysisScaleSurface = 0; + + _analysisArea = new Gtk::DrawingArea(); + _analysisWidth = 500.0; + _analysisHeight = 500.0; + _analysisArea->set_size_request(_analysisWidth, _analysisHeight); + + _analysisArea->signal_expose_event().connect( sigc::mem_fun (*this, &PluginEqGui::exposeAnalysisArea)); + _analysisArea->signal_size_allocate().connect( sigc::mem_fun (*this, &PluginEqGui::resizeAnalysisArea)); + + + // 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::dBScaleChanged) ); + + 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 + phaseSelect = new Gtk::CheckButton("Show phase"); + phaseSelect->set_active(true); + phaseSelect->signal_toggled().connect( sigc::mem_fun(*this, &PluginEqGui::redrawScales)); + + // Update button + Gtk::Button *btn = new Gtk::Button("Update"); + btn->signal_clicked().connect( sigc::mem_fun(*this, &PluginEqGui::runAnalysis)); + + // populate table + attach( *manage(_analysisArea), 1, 4, 1, 2); + attach( *manage(dBSelectBin), 1, 2, 2, 3, Gtk::SHRINK, Gtk::SHRINK); + attach( *manage(phaseSelect), 2, 3, 2, 3, Gtk::SHRINK, Gtk::SHRINK); + attach( *manage(btn), 3, 4, 2, 3, Gtk::SHRINK, Gtk::SHRINK); + + + // Timeout + //_updateConn = Glib::signal_timeout().connect( sigc::mem_fun(this, &PluginEqGui::timeoutCallback), 250); +} + +PluginEqGui::~PluginEqGui() +{ + std::cerr << "Destroying PluginEqGui for " << _plugin->name() << std::endl; + if (_analysisScaleSurface) { + cairo_surface_destroy (_analysisScaleSurface); + } + + delete _impulseFft; + _plugin->deactivate(); + + // all gui objects are *manage'd by the inherited Table object +} + +void +PluginEqGui::on_hide() +{ + Gtk::Table::on_hide(); + _updateConn.disconnect(); +} + +void +PluginEqGui::on_show() +{ + Gtk::Table::on_show(); + _updateConn = Glib::signal_timeout().connect( sigc::mem_fun(this, &PluginEqGui::timeoutCallback), 250); +} + +void +PluginEqGui::dBScaleChanged() +{ + Gtk::TreeModel::iterator iter = dBScaleCombo -> get_active(); + + Gtk::TreeModel::Row row; + + if(iter && (row = *iter)) { + _mindB = row[dBColumns.dBMin]; + _maxdB = row[dBColumns.dBMax]; + _dBStep = row[dBColumns.dBStep]; + + + redrawScales(); + } +} + +void +PluginEqGui::redrawScales() +{ + + if (_analysisScaleSurface) { + cairo_surface_destroy (_analysisScaleSurface); + _analysisScaleSurface = 0; + } + + _analysisArea->queue_draw(); +} + +void +PluginEqGui::setBufferSize(uint32_t size) +{ + if (_bufferSize == size) + return; + + _bufferSize = size; + + if (_impulseFft) { + delete _impulseFft; + _impulseFft = 0; + } + + _impulseFft = new FFT(_bufferSize); + + 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, _bufferSize); + + ARDOUR::ChanCount chanCount(ARDOUR::DataType::AUDIO, n_chans); + _bufferset.set_count(chanCount); + + /* + + const uint32_t nbufs = _bufferset.count().n_audio(); + std::cerr << "ensure_buffers(ARDOUR::DataType::Audio, " << n_chans << ", " << _bufferSize << "), _bufferset.count().n_audio() = " << nbufs << std::endl; + */ +} + +void +PluginEqGui::resizeAnalysisArea(Gtk::Allocation& size) +{ + _analysisWidth = (float)size.get_width(); + _analysisHeight = (float)size.get_height(); + + if (_analysisScaleSurface) { + cairo_surface_destroy (_analysisScaleSurface); + _analysisScaleSurface = 0; + } +} + +bool +PluginEqGui::timeoutCallback() +{ + /* + struct timeval tv; + struct timezone tz; + + gettimeofday(&tv, &tz); + std::cerr << " time = " << tv.tv_sec << ":" << tv.tv_usec << std::endl; + */ + runAnalysis(); + + return true; +} + +void +PluginEqGui::runAnalysis() +{ + uint32_t inputs = _plugin->get_info()->n_inputs.n_audio(); + uint32_t outputs = _plugin->get_info()->n_outputs.n_audio(); + + const uint32_t nbufs = _bufferset.count().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(_bufferSize, 0); + memset(d, 0, sizeof(ARDOUR::Sample)*_bufferSize); + *d = 1.0; + } + uint32_t x,y; + x=y=0; + + _plugin->connect_and_run(_bufferset, x, y, _bufferSize, (nframes_t)0); + + // Analyze all output buffers + _impulseFft->reset(); + for (uint32_t i = 0; i < outputs; ++i) { + _impulseFft->analyze(_bufferset.get_audio(i).data(_bufferSize, 0)); + } + + // normalize the output + _impulseFft->calculate(); + + // This signals calls exposeAnalysisArea() + _analysisArea->queue_draw(); + +} + +bool +PluginEqGui::exposeAnalysisArea(GdkEventExpose *evt) +{ + redrawAnalysisArea(); + + return false; +} + +void +PluginEqGui::generateAnalysisScale(cairo_t *ref_cr) +{ + // TODO: check whether we need rounding + _analysisScaleSurface = cairo_surface_create_similar(cairo_get_target(ref_cr), + CAIRO_CONTENT_COLOR, + _analysisWidth, + _analysisHeight); + + cairo_t *cr = cairo_create (_analysisScaleSurface); + + cairo_set_source_rgb(cr, 0.0, 0.0, 0.0); + cairo_rectangle(cr, 0.0, 0.0, _analysisWidth, _analysisHeight); + cairo_fill(cr); + + + drawPowerScale(_analysisArea, cr); + if (phaseSelect->get_active()) { + drawPhaseScale(_analysisArea, cr); + } + + cairo_destroy(cr); + +} + +void +PluginEqGui::redrawAnalysisArea() +{ + cairo_t *cr; + + cr = gdk_cairo_create(GDK_DRAWABLE(_analysisArea->get_window()->gobj())); + + if (_analysisScaleSurface == 0) { + generateAnalysisScale(cr); + } + + + cairo_copy_page(cr); + + cairo_set_source_surface(cr, _analysisScaleSurface, 0.0, 0.0); + cairo_paint(cr); + + if (phaseSelect->get_active()) { + drawPhase(_analysisArea, cr); + } + drawPower(_analysisArea, cr); + + + cairo_destroy(cr); + + +} + +#define PHASE_PROPORTION 0.6 + +void +PluginEqGui::drawPhaseScale(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 = _analysisHeight/2.0 - (float)i*(_analysisHeight/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, _analysisWidth - 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, _analysisWidth, y); + + + y = _analysisHeight/2.0 + (float)i*(_analysisHeight/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, _analysisWidth - 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, _analysisWidth, y); + + cairo_set_line_width (cr, 0.25 + 1.0/(float)(i+1)); + cairo_stroke(cr); + } +} + +void +PluginEqGui::drawPhase(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 < _impulseFft->bins()-1; i++) { + // x coordinate of bin i + x = log10f(1.0 + (float)i / (float)_impulseFft->bins() * _logCoeff) / _logMax; + x *= _analysisWidth; + + y = _analysisHeight/2.0 - (_impulseFft->phaseAtBin(i)/M_PI)*(_analysisHeight/2.0)*PHASE_PROPORTION; + + if ( i == 0 ) { + cairo_move_to(cr, x, y); + + avgY = 0; + avgNum = 0; + } else if (rint(x) > prevX || i == _impulseFft->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::drawPowerScale(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 / _impulseFft->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)_impulseFft->bins() * _logCoeff) / _logMax; + x *= _analysisWidth; + + 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, _analysisHeight); + 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 < _maxdB; dB += _dBStep ) { + snprintf(buf, 256, "+%0.0f", dB ); + + y = ( _maxdB - dB) / ( _maxdB - _mindB ); + //std::cerr << " y = " << y << std::endl; + y *= _analysisHeight; + + 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, _analysisWidth, y); + cairo_stroke(cr); + + if (dB == 0.0) { + cairo_set_dash(cr, dashes, 2, 0.0); + } + } + + + + for (float dB = - _dBStep; dB > _mindB; dB -= _dBStep ) { + snprintf(buf, 256, "%0.0f", dB ); + + y = ( _maxdB - dB) / ( _maxdB - _mindB ); + y *= _analysisHeight; + + 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, _analysisWidth, 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::drawPower(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 < _impulseFft->bins()-1; i++) { + // x coordinate of bin i + x = log10f(1.0 + (float)i / (float)_impulseFft->bins() * _logCoeff) / _logMax; + x *= _analysisWidth; + + float yCoeff = ( power_to_dB(_impulseFft->powerAtBin(i)) - _mindB) / (_maxdB - _mindB); + + y = _analysisHeight - _analysisHeight*yCoeff; + + if ( i == 0 ) { + cairo_move_to(cr, x, y); + + avgY = 0; + avgNum = 0; + } else if (rint(x) > prevX || i == _impulseFft->bins()-1 ) { + cairo_line_to(cr, prevX, avgY/(float)avgNum); + + avgY = 0; + avgNum = 0; + + } + + prevX = rint(x); + avgY += y; + avgNum++; + } + + cairo_stroke(cr); +} + diff --git a/gtk2_ardour/eq_gui.h b/gtk2_ardour/eq_gui.h new file mode 100644 index 0000000000..ef503ffb4e --- /dev/null +++ b/gtk2_ardour/eq_gui.h @@ -0,0 +1,130 @@ +/* + 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. + +*/ + +#ifndef __ardour_eq_gui_h +#define __ardour_eq_gui_h + +#include +#include +#include + +#include +#include +#include +#include + +class Plugin; +class FFT; + +class PluginEqGui : public Gtk::Table +{ + public: + PluginEqGui(boost::shared_ptr); + ~PluginEqGui(); + + + + private: + // Setup + void setBufferSize(uint32_t); + void dBScaleChanged(); + + // Analysis + void runAnalysis(); + + // Drawing + virtual void on_hide(); + virtual void on_show(); + + void resizeAnalysisArea(Gtk::Allocation&); + + void redrawAnalysisArea(); + void generateAnalysisScale(cairo_t *); + bool exposeAnalysisArea(GdkEventExpose *); + + void drawPowerScale(Gtk::Widget *, cairo_t *); + void drawPower(Gtk::Widget *,cairo_t *); + + void drawPhaseScale(Gtk::Widget *,cairo_t *); + void drawPhase(Gtk::Widget *,cairo_t *); + + // Helpers + bool timeoutCallback(); + void redrawScales(); + + + // Fields: + + // analysis parameters + float _samplerate; + + float _mindB; + float _maxdB; + float _dBStep; + + + float _logCoeff; + float _logMax; + + nframes_t _bufferSize; + + // buffers + ARDOUR::BufferSet _bufferset; + + + // dimensions + float _analysisWidth; + float _analysisHeight; + + // My objects + FFT *_impulseFft; + boost::shared_ptr _plugin; + + // gui objects + Gtk::DrawingArea *_analysisArea; + cairo_surface_t *_analysisScaleSurface; + + + // dB scale selection: + class dBSelectionColumns : public Gtk::TreeModel::ColumnRecord + { + public: + dBSelectionColumns() + { add(dBMin); add(dBMax); add(dBStep); add(name); } + + Gtk::TreeModelColumn dBMin; + Gtk::TreeModelColumn dBMax; + Gtk::TreeModelColumn dBStep; + Gtk::TreeModelColumn name; + }; + + dBSelectionColumns dBColumns; + + Gtk::ComboBox *dBScaleCombo; + Glib::RefPtr dBScaleModel; + + Gtk::CheckButton *phaseSelect; + + // signals and connections + sigc::connection _updateConn; +}; + +#endif + diff --git a/gtk2_ardour/fft.cc b/gtk2_ardour/fft.cc new file mode 100644 index 0000000000..92f52bb64e --- /dev/null +++ b/gtk2_ardour/fft.cc @@ -0,0 +1,107 @@ +/* + 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 "fft.h" + +#include +#include +#include + +FFT::FFT(uint32_t windowSize) + : _windowSize(windowSize), + _dataSize(_windowSize/2), + _iterations(0) +{ + _fftInput = (float *) fftwf_malloc(sizeof(float) * _windowSize); + + _fftOutput = (float *) fftwf_malloc(sizeof(float) * _windowSize); + + _powerAtBin = (float *) malloc(sizeof(float) * _dataSize); + _phaseAtBin = (float *) malloc(sizeof(float) * _dataSize); + + _plan = fftwf_plan_r2r_1d(_windowSize, _fftInput, _fftOutput, FFTW_R2HC, FFTW_ESTIMATE); + + reset(); +} + +void +FFT::reset() +{ + memset(_powerAtBin, 0, sizeof(float) * _dataSize); + memset(_phaseAtBin, 0, sizeof(float) * _dataSize); + + _iterations = 0; +} + +void +FFT::analyze(ARDOUR::Sample *input) +{ + _iterations++; + + memcpy(_fftInput, input, sizeof(float) * _windowSize); + + fftwf_execute(_plan); + + _powerAtBin[0] += _fftOutput[0] * _fftOutput[0]; + _phaseAtBin[0] += 0.0; + + float power; + float phase; + +#define Re (_fftOutput[i]) +#define Im (_fftOutput[_windowSize-i]) + for (uint32_t i=1; i < _dataSize - 1; i++) { + + power = (Re * Re) + (Im * Im); + phase = atanf(Im / Re); + + if (Re < 0.0 && Im > 0.0) { + phase += M_PI; + } else if (Re < 0.0 && Im < 0.0) { + phase -= M_PI; + } + + _powerAtBin[i] += power; + _phaseAtBin[i] += phase; + } +#undef Re +#undef Im +} + +void +FFT::calculate() +{ + if (_iterations > 1) { + for (uint32_t i=0; i < _dataSize - 1; i++) { + _powerAtBin[i] /= (float)_iterations; + _phaseAtBin[i] /= (float)_iterations; + } + _iterations = 1; + } +} + + +FFT::~FFT() +{ + fftwf_destroy_plan(_plan); + free(_powerAtBin); + free(_phaseAtBin); + free(_fftOutput); + free(_fftInput); +} diff --git a/gtk2_ardour/fft.h b/gtk2_ardour/fft.h new file mode 100644 index 0000000000..a68ed59edc --- /dev/null +++ b/gtk2_ardour/fft.h @@ -0,0 +1,65 @@ +/* + 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. + +*/ + +#ifndef __ardour_fft_h__ +#define __ardour_fft_h__ + + +#include + +#include +#include +#include // this needs to be included before fftw3.h +#include + + +#include + +class FFT +{ + public: + FFT(uint32_t); + ~FFT(); + + void reset(); + void analyze(ARDOUR::Sample *); + void calculate(); + + uint32_t bins() const { return _dataSize; } + + float powerAtBin(uint32_t i) const { return _powerAtBin[i]; } + float phaseAtBin(uint32_t i) const { return _phaseAtBin[i]; } + + private: + + uint32_t const _windowSize; + uint32_t const _dataSize; + uint32_t _iterations; + + float *_fftInput; + float *_fftOutput; + + float *_powerAtBin; + float *_phaseAtBin; + + fftwf_plan _plan; +}; + +#endif diff --git a/gtk2_ardour/plugin_ui.cc b/gtk2_ardour/plugin_ui.cc index a91a5bf9d1..ceb31561cd 100644 --- a/gtk2_ardour/plugin_ui.cc +++ b/gtk2_ardour/plugin_ui.cc @@ -27,6 +27,7 @@ #include #include +#include #include #include #include @@ -56,6 +57,7 @@ #include "gui_thread.h" #include "public_editor.h" #include "keyboard.h" +#include "eq_gui.h" #include "i18n.h" @@ -112,8 +114,15 @@ PluginUIWindow::PluginUIWindow (Gtk::Window* win, boost::shared_ptrpack_start( *pu); + // TODO: this should be nicer + hbox->pack_start( *manage(new PluginEqGui(insert))); + add (*manage(hbox)); + + set_wmclass (X_("ardour_plugin_editor"), "Ardour"); signal_map_event().connect (mem_fun (*pu, &GenericPluginUI::start_updating)); diff --git a/libs/ardour/ardour/plugin_insert.h b/libs/ardour/ardour/plugin_insert.h index d83b41d1e0..3d4a4b727a 100644 --- a/libs/ardour/ardour/plugin_insert.h +++ b/libs/ardour/ardour/plugin_insert.h @@ -23,6 +23,8 @@ #include #include +#include + #include #include #include @@ -102,6 +104,8 @@ class PluginInsert : public Processor nframes_t signal_latency() const; + boost::shared_ptr get_impulse_analysis_plugin(); + private: void parameter_changed (Evoral::Parameter, float); @@ -112,6 +116,8 @@ class PluginInsert : public Processor float default_parameter_value (const Evoral::Parameter& param); std::vector > _plugins; + + boost::weak_ptr _impulseAnalysisPlugin; void automation_run (BufferSet& bufs, nframes_t nframes, nframes_t offset); void connect_and_run (BufferSet& bufs, nframes_t nframes, nframes_t offset, bool with_auto, nframes_t now = 0); diff --git a/libs/ardour/plugin_insert.cc b/libs/ardour/plugin_insert.cc index eede6b3388..2c08473b54 100644 --- a/libs/ardour/plugin_insert.cc +++ b/libs/ardour/plugin_insert.cc @@ -899,6 +899,11 @@ PluginInsert::PluginControl::set_value (float val) (*i)->set_parameter (_list->parameter().id(), val); } + boost::shared_ptr iasp = _plugin->_impulseAnalysisPlugin.lock(); + if (iasp) { + iasp->set_parameter (_list->parameter().id(), val); + } + AutomationControl::set_value(val); } @@ -925,3 +930,17 @@ PluginInsert::PluginControl::get_value (void) const }*/ } +boost::shared_ptr +PluginInsert::get_impulse_analysis_plugin() +{ + boost::shared_ptr ret; + if (_impulseAnalysisPlugin.expired()) { + ret = plugin_factory(_plugins[0]); + _impulseAnalysisPlugin = ret; + } else { + ret = _impulseAnalysisPlugin.lock(); + } + + return ret; +} + -- cgit v1.2.3