From 6673dcd28e88b7cd42f216229f23b590e79499e9 Mon Sep 17 00:00:00 2001 From: Jonathan Moore Liles Date: Mon, 30 Sep 2013 19:10:17 -0700 Subject: [PATCH] Mixer: Implement plugin spectrum view. --- mixer/src/Module.C | 41 ++++ mixer/src/Module.H | 15 +- mixer/src/Module_Parameter_Editor.C | 47 +++- mixer/src/Module_Parameter_Editor.H | 5 +- mixer/src/Plugin_Module.C | 74 ++++++ mixer/src/Plugin_Module.H | 4 + mixer/src/SpectrumView.C | 342 ++++++++++++++++++++++++++++ mixer/src/SpectrumView.H | 62 +++++ mixer/wscript | 1 + nonlib/dsp.C | 2 +- 10 files changed, 586 insertions(+), 7 deletions(-) create mode 100644 mixer/src/SpectrumView.C create mode 100644 mixer/src/SpectrumView.H diff --git a/mixer/src/Module.C b/mixer/src/Module.C index 20d5fec..36df7d8 100644 --- a/mixer/src/Module.C +++ b/mixer/src/Module.C @@ -672,6 +672,41 @@ Module::draw_box ( int tx, int ty, int tw, int th ) fl_pop_clip(); } +#include "SpectrumView.H" +#include + + +bool +Module::show_analysis_window ( void ) +{ + nframes_t nframes = 4096; + float *buf = new float[nframes]; + + if ( ! get_impulse_response( buf, nframes ) ) + return false; + + Fl_Double_Window *w = new Fl_Double_Window( 1000, 500 ); + + { + SpectrumView * o = new SpectrumView( 25,25, 1000 - 50, 500 - 50, label() ); + o->labelsize(10); + o->align(FL_ALIGN_RIGHT|FL_ALIGN_TOP); + o->sample_rate( sample_rate() ); + /* o->minimum_frequency( 10 ); */ + /* o->maximum_frequency( 50000 ); */ + o->data( buf, nframes ); + } + + w->end(); + + w->show(); + + while ( w->shown() ) + Fl::wait(); + + return true; +} + void Module::draw_label ( int tx, int ty, int tw, int th ) { @@ -888,6 +923,10 @@ Module::menu_cb ( const Fl_Menu_ *m ) { paste_before(); } + else if ( ! strcmp( picked, "Show Analysis" ) ) + { + show_analysis_window(); + } else if ( ! strcmp( picked, "Remove" ) ) command_remove(); } @@ -924,10 +963,12 @@ Module::menu ( void ) const m.add( "Insert", 0, &Module::menu_cb, (void*)this, 0); m.add( "Insert", 0, &Module::menu_cb, const_cast< Fl_Menu_Item *>( insert_menu->menu() ), FL_SUBMENU_POINTER ); m.add( "Edit Parameters", ' ', &Module::menu_cb, (void*)this, 0 ); + m.add( "Show Analysis", 's', &Module::menu_cb, (void*)this, 0); m.add( "Bypass", 'b', &Module::menu_cb, (void*)this, FL_MENU_TOGGLE | ( bypass() ? FL_MENU_VALUE : 0 ) ); m.add( "Cut", FL_CTRL + 'x', &Module::menu_cb, (void*)this, is_default() ? FL_MENU_INACTIVE : 0 ); m.add( "Copy", FL_CTRL + 'c', &Module::menu_cb, (void*)this, is_default() ? FL_MENU_INACTIVE : 0 ); m.add( "Paste", FL_CTRL + 'v', &Module::menu_cb, (void*)this, _copied_module_empty ? 0 : FL_MENU_INACTIVE ); + m.add( "Remove", FL_Delete, &Module::menu_cb, (void*)this ); // menu_set_callback( menu, &Module::menu_cb, (void*)this ); diff --git a/mixer/src/Module.H b/mixer/src/Module.H index ca711ba..db21d05 100644 --- a/mixer/src/Module.H +++ b/mixer/src/Module.H @@ -440,7 +440,9 @@ public: char *get_parameters ( void ) const; void set_parameters ( const char * ); - + + bool show_analysis_window ( void ); + void send_feedback ( void ); virtual bool initialize ( void ) { return true; } @@ -468,6 +470,13 @@ public: virtual void handle_port_connection_change () {} + /* module should create a new context, run against this impulse, + * and return true if there's anything worth reporting */ + virtual bool get_impulse_response ( sample_t *buf, nframes_t nframes ) + { + return false; + } + #define MODULE_CLONE_FUNC(class) \ virtual Module *clone_empty ( void ) const \ { \ @@ -480,8 +489,6 @@ public: protected: - nframes_t sample_rate ( void ) const { return Module::_sample_rate; } - void draw_connections ( void ); void draw_label ( int X, int Y, int W, int H ); void draw_box ( int X, int Y, int W, int H ); @@ -495,6 +502,8 @@ protected: bool add_aux_port ( bool input, const char *prefix, int n ); public: + nframes_t sample_rate ( void ) const { return Module::_sample_rate; } + void auto_connect_outputs(); void auto_disconnect_outputs(); diff --git a/mixer/src/Module_Parameter_Editor.C b/mixer/src/Module_Parameter_Editor.C index 26fc1dd..04da74b 100644 --- a/mixer/src/Module_Parameter_Editor.C +++ b/mixer/src/Module_Parameter_Editor.C @@ -47,6 +47,8 @@ #include "FL/menu_popup.H" +#include "SpectrumView.H" + Module_Parameter_Editor::Module_Parameter_Editor ( Module *module ) : Fl_Double_Window( 900,240) { _module = module; @@ -97,6 +99,7 @@ Module_Parameter_Editor::Module_Parameter_Editor ( Module *module ) : Fl_Double_ o->flow( true ); o->vspacing( 5 ); o->hspacing( 5 ); + o->end(); } o->resizable( 0 ); @@ -117,6 +120,29 @@ Module_Parameter_Editor::~Module_Parameter_Editor ( ) +void +Module_Parameter_Editor::update_spectrum ( void ) +{ + nframes_t nframes = 4096; + float *buf = new float[nframes]; + SpectrumView *o = spectrum_view; + + o->sample_rate( _module->sample_rate() ); + + if ( ! _module->get_impulse_response( buf, nframes ) ) + { + o->data( buf, 1 ); + /* o->hide(); */ + } + else + { + o->data( buf, nframes ); + o->parent()->show(); + } + + o->redraw(); +} + void Module_Parameter_Editor::make_controls ( void ) { @@ -124,6 +150,17 @@ Module_Parameter_Editor::make_controls ( void ) control_pack->clear(); + { SpectrumView *o = spectrum_view = new SpectrumView( 25, 40, 300, 240, "Spectrum" ); + o->labelsize(9); + o->align(FL_ALIGN_TOP); + + + Fl_Labelpad_Group *flg = new Fl_Labelpad_Group( (Fl_Widget*)o ); + flg->hide(); + control_pack->add( flg ); + } + + controls_by_port.clear(); /* these are for detecting related parameter groups which can be @@ -156,14 +193,14 @@ Module_Parameter_Editor::make_controls ( void ) control_pack->flow(true); control_pack->flowdown(false); control_pack->type( FL_HORIZONTAL ); - control_pack->size( 900, 350 ); + control_pack->size( 900, 250 ); } else if ( mode_choice->value() == 0 ) { control_pack->vspacing( 10 ); control_pack->hspacing( 10 ); control_pack->flow(true); - control_pack->flowdown(false); + control_pack->flowdown(true); control_pack->type( FL_HORIZONTAL ); control_pack->size( 700, 50 ); @@ -381,6 +418,8 @@ Module_Parameter_Editor::make_controls ( void ) } update_control_visibility(); + + update_spectrum(); control_pack->dolayout(); @@ -504,6 +543,8 @@ Module_Parameter_Editor::handle_control_changed ( Module::Port *p ) v->value( p->control_value() ); } + + update_spectrum(); } @@ -524,6 +565,8 @@ Module_Parameter_Editor::set_value (int i, float value ) if ( _module->control_input[i].connected() ) _module->control_input[i].connected_port()->module()->handle_control_changed( _module->control_input[i].connected_port() ); } + + update_spectrum(); // _module->handle_control_changed( &_module->control_input[i] ); } diff --git a/mixer/src/Module_Parameter_Editor.H b/mixer/src/Module_Parameter_Editor.H index 33b6895..5b01154 100644 --- a/mixer/src/Module_Parameter_Editor.H +++ b/mixer/src/Module_Parameter_Editor.H @@ -27,6 +27,7 @@ class Module; class Fl_Menu_Button; class Panner; class Fl_Scroll; +class SpectrumView; #include #include @@ -76,10 +77,12 @@ class Module_Parameter_Editor : public Fl_Double_Window void set_value (int i, float value ); void bind_control ( int i ); void make_controls ( void ); + void update_spectrum ( void ); static void menu_cb ( Fl_Widget *w, void *v ); void menu_cb ( Fl_Menu_ *m ); - + + SpectrumView *spectrum_view; Fl_Scroll *control_scroll; Fl_Flowpack *control_pack; Fl_Menu_Button *mode_choice; diff --git a/mixer/src/Plugin_Module.C b/mixer/src/Plugin_Module.C index 0dee078..686fd74 100644 --- a/mixer/src/Plugin_Module.C +++ b/mixer/src/Plugin_Module.C @@ -778,6 +778,80 @@ Plugin_Module::handle_port_connection_change ( void ) +bool +Plugin_Module::get_impulse_response ( sample_t *buf, nframes_t nframes ) +{ + memset( buf, 0, sizeof( float ) * nframes ); + + buf[0] = 1; + + apply( buf, nframes ); + + if ( buffer_is_digital_black( buf + 1, nframes - 1 )) + /* no impulse response... */ + return false; + + return true; +} + +/** Instantiate a temporary version of the plugin, and run it (in place) against the provided buffer */ +bool +Plugin_Module::apply ( sample_t *buf, nframes_t nframes ) +{ +// actually osc or UI THREAD_ASSERT( UI ); + + LADSPA_Handle h; + + if ( ! (h = _idata->descriptor->instantiate( _idata->descriptor, sample_rate() ) ) ) + { + WARNING( "Failed to instantiate plugin" ); + return false; + } + + int ij = 0; + int oj = 0; + for ( unsigned int k = 0; k < _idata->descriptor->PortCount; ++k ) + { + if ( LADSPA_IS_PORT_CONTROL( _idata->descriptor->PortDescriptors[k] ) ) + { + if ( LADSPA_IS_PORT_INPUT( _idata->descriptor->PortDescriptors[k] ) ) + _idata->descriptor->connect_port( h, k, (LADSPA_Data*)control_input[ij++].buffer() ); + else if ( LADSPA_IS_PORT_OUTPUT( _idata->descriptor->PortDescriptors[k] ) ) + _idata->descriptor->connect_port( h, k, (LADSPA_Data*)control_output[oj++].buffer() ); + } + } + + + if ( _idata->descriptor->activate ) + _idata->descriptor->activate( h ); + + int tframes = 512; + float tmp[tframes]; + + memset( tmp, 0, sizeof( float ) * tframes ); + + for ( unsigned int k = 0; k < _idata->descriptor->PortCount; ++k ) + if ( LADSPA_IS_PORT_AUDIO( _idata->descriptor->PortDescriptors[k] ) ) + _idata->descriptor->connect_port( h, k, tmp ); + + + /* flush any parameter interpolation */ + _idata->descriptor->run( h, tframes ); + + for ( unsigned int k = 0; k < _idata->descriptor->PortCount; ++k ) + if ( LADSPA_IS_PORT_AUDIO( _idata->descriptor->PortDescriptors[k] ) ) + _idata->descriptor->connect_port( h, k, buf ); + + /* run for real */ + _idata->descriptor->run( h, nframes ); + + if ( _idata->descriptor->deactivate ) + _idata->descriptor->deactivate( h ); + if ( _idata->descriptor->cleanup ) + _idata->descriptor->cleanup( h ); + + return true; +} /**********/ /* Client */ /**********/ diff --git a/mixer/src/Plugin_Module.H b/mixer/src/Plugin_Module.H index 5fdfa9a..7409e8d 100644 --- a/mixer/src/Plugin_Module.H +++ b/mixer/src/Plugin_Module.H @@ -99,6 +99,8 @@ private: void set_control_buffer ( int n, void *buf ); void activate ( void ); void deactivate ( void ); + + bool apply ( sample_t *buf, nframes_t nframes ); void process ( unsigned long nframes ); bool plugin_instances ( unsigned int ); @@ -109,6 +111,8 @@ private: public: + virtual bool get_impulse_response ( sample_t *buf, nframes_t nframes ); + virtual nframes_t get_module_latency ( void ) const; virtual void update ( void ); diff --git a/mixer/src/SpectrumView.C b/mixer/src/SpectrumView.C new file mode 100644 index 0000000..08f223d --- /dev/null +++ b/mixer/src/SpectrumView.C @@ -0,0 +1,342 @@ +/*******************************************************************************/ +/* Copyright (C) 2013 Mark McCurry */ +/* Copyright (C) 2013 Jonathan Moore Liles */ +/* */ +/* 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; see the file COPYING. If not,write to the Free Software */ +/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +/*******************************************************************************/ + +#include "SpectrumView.H" +#include +#include + +#include + +#include +#include +#include + +#include + +static std::map _cached_plan; + +float SpectrumView::_fmin = 10; +float SpectrumView::_fmax = 24000; +unsigned int SpectrumView::_sample_rate = 48000; + + +void +SpectrumView::clear_bands ( void ) +{ + if ( _bands ) + delete[] _bands; + + _bands = NULL; +} + +void +SpectrumView::data ( float *data, unsigned int nframes ) +{ + if ( _data ) + delete[] _data; + + _data = data; + _nframes = nframes; + + clear_bands(); + + redraw(); +} + +void +SpectrumView::sample_rate ( unsigned int sample_rate ) +{ + if ( _sample_rate != sample_rate ) + { + _sample_rate = sample_rate; + _fmin = 10; + _fmax = _sample_rate * 0.5f; + + /* invalidate all plans */ + + for ( std::map::iterator i = _cached_plan.begin(); + i != _cached_plan.end(); + i++ ) + { + delete[] i->second; + } + + _cached_plan.clear(); + } +} + + +#define min(a,b) (a b=log(Fmin)/log(10) + // log10(Fmax)=a+b -> a=log(Fmax)/log(10)-b + + const float b = logf(Fmin)/logf(10); + const float a = logf(Fmax)/logf(10)-b; + + //Evaluate at set frequencies + const float one_over_samples = 1.0f / samples; + const float one_over_samplerate = 1.0f / Fs; + + for(unsigned i=0; i -0.001) + fl_line(xloc*W+x(), y(), xloc*W+x(), y()+H); + } + } + + fl_end_line(); + + fl_font( FL_HELVETICA_ITALIC, 7 ); + //Place the text labels + char label[256]; + for(int i=0; i<8; ++i) { + int level = (y()+H*i/8.0) + 3; + float value = (1-i/8.0)*(_dbmax-_dbmin) + _dbmin; + sprintf(label, "%.1f dB", value); +// fl_draw(label, x()+w() + 3, level); + fl_draw(label, x(), level, w(), 7, FL_ALIGN_RIGHT ); + } + + for(int i=min_base; i<=max_base; ++i) { + { + const float freq = pow(10.0, i)*1; + const float xloc = (logf(freq)/logf(10)-b)/a; + sprintf(label, "%0.f %s", freq < 1000.0 ? freq : freq / 1000.0, freq < 1000.0 ? "Hz" : "KHz" ); + if(xloc<1.0) + fl_draw(label, xloc*W+x()+1, y()+h()); + } + } +} + +void +SpectrumView::draw_curve ( void ) +{ + int W = w() - padding_right; + + //Build lines + float inc = 1.0 / (float)W; + + float fx = 0; + for( int i = 0; i < W; i++, fx += inc ) + fl_vertex(fx, 1.0 - _bands[i]); +} + +void +SpectrumView::draw ( void ) +{ + //Clear Widget + Fl_Box::draw(); + + int W = w() - padding_right; + int H = h() - padding_bottom; + + if ( !_bands ) { + analyze_data( W ); + } + + //Draw grid + fl_color(fl_color_add_alpha(fl_rgb_color( 100,100,100), 50 )); + + draw_semilog(); + + fl_push_clip( x(),y(),W,H); + + + fl_color(fl_color_add_alpha( selection_color(), 20 )); + + fl_push_matrix(); + fl_translate( x(), y() + 2 ); + fl_scale( W,H- 2 ); + + fl_begin_polygon(); + + fl_vertex(0.0,1.0); + + draw_curve(); + + fl_vertex(1.0,1.0); + + fl_end_polygon(); + + fl_color(fl_color_add_alpha( selection_color(), 100 )); + fl_begin_line(); + fl_line_style(FL_SOLID,2); + + /* fl_vertex(0.0,1.0); */ + + draw_curve(); + + /* fl_vertex(1.0,1.0); */ + + fl_end_line(); + + fl_pop_matrix(); + + fl_line_style(FL_SOLID,0); + + fl_pop_clip(); +} + +void +SpectrumView::resize ( int X, int Y, int W, int H ) +{ + if ( W != w() ) + clear_bands(); + + Fl_Box::resize(X,Y,W,H); +} + diff --git a/mixer/src/SpectrumView.H b/mixer/src/SpectrumView.H new file mode 100644 index 0000000..1c0329e --- /dev/null +++ b/mixer/src/SpectrumView.H @@ -0,0 +1,62 @@ + +/*******************************************************************************/ +/* Copyright (C) 2013 Mark McCurry */ +/* Copyright (C) 2013 Jonathan Moore Liles */ +/* */ +/* 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; see the file COPYING. If not,write to the Free Software */ +/* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ +/*******************************************************************************/ + +#include + +class SpectrumView : public Fl_Box +{ + static unsigned int _sample_rate; + static float _fmin; + static float _fmax; + + float * _data; + unsigned int _nframes; + float * _bands; + float _dbmin; + float _dbmax; + bool _auto_level; + + void draw_curve ( void ); + void draw_semilog ( void ); + void analyze_data ( unsigned int plan_size ); + void clear_bands ( void ); + +public: + + static void sample_rate ( unsigned int sample_rate ); + + /* set dB range. If min == max, then auto leveling will be enabled */ + void db_range ( float min, float max ) + { + _dbmin = min; + _dbmax = max; + _auto_level = min == max; + } + + /** /data/ must point to allocated memory. It will be freed when new data is set or when the control is destroyed */ + void data ( float *data, unsigned int nframes ); + + SpectrumView ( int X, int Y, int W, int H, const char *L=0 ); + virtual ~SpectrumView ( ); + + virtual void resize ( int X, int Y, int W, int H ); + virtual void draw ( void ); +}; + diff --git a/mixer/wscript b/mixer/wscript index abae91e..2094e37 100644 --- a/mixer/wscript +++ b/mixer/wscript @@ -66,6 +66,7 @@ src/Plugin_Module.C src/Project.C src/Group.C src/main.C +src/SpectrumView.C src/Spatialization_Console.C ''', target = 'non-mixer', diff --git a/nonlib/dsp.C b/nonlib/dsp.C index 2f5a434..e77ae45 100644 --- a/nonlib/dsp.C +++ b/nonlib/dsp.C @@ -181,7 +181,7 @@ buffer_fill_with_silence ( sample_t *buf, nframes_t nframes ) } bool -buffer_is_digital_black ( sample_t *buf, nframes_t nframes ) +buffer_is_digital_black ( const sample_t *buf, nframes_t nframes ) { while ( nframes-- ) {