diff --git a/lib/ntk b/lib/ntk index f084e8d..977a180 160000 --- a/lib/ntk +++ b/lib/ntk @@ -1 +1 @@ -Subproject commit f084e8d2ccdcf8f5c997618b984c493462532a1c +Subproject commit 977a180175b776f700e799112ac138281b29a7a4 diff --git a/mixer/pixmaps/panner-512x512.png b/mixer/pixmaps/panner-512x512.png deleted file mode 100644 index a3a6510..0000000 Binary files a/mixer/pixmaps/panner-512x512.png and /dev/null differ diff --git a/mixer/pixmaps/panner-92x92.png b/mixer/pixmaps/panner-92x92.png deleted file mode 100644 index df2c412..0000000 Binary files a/mixer/pixmaps/panner-92x92.png and /dev/null differ diff --git a/mixer/pixmaps/panner-plane-502x502.png b/mixer/pixmaps/panner-plane-502x502.png new file mode 100644 index 0000000..041c60f Binary files /dev/null and b/mixer/pixmaps/panner-plane-502x502.png differ diff --git a/mixer/pixmaps/panner-plane-802x802.png b/mixer/pixmaps/panner-plane-802x802.png new file mode 100644 index 0000000..edd346a Binary files /dev/null and b/mixer/pixmaps/panner-plane-802x802.png differ diff --git a/mixer/pixmaps/panner-plane-92x92.png b/mixer/pixmaps/panner-plane-92x92.png new file mode 100644 index 0000000..4f00b2d Binary files /dev/null and b/mixer/pixmaps/panner-plane-92x92.png differ diff --git a/mixer/pixmaps/panner-sphere-502x502.png b/mixer/pixmaps/panner-sphere-502x502.png new file mode 100644 index 0000000..12356d3 Binary files /dev/null and b/mixer/pixmaps/panner-sphere-502x502.png differ diff --git a/mixer/pixmaps/panner-sphere-802x802.png b/mixer/pixmaps/panner-sphere-802x802.png new file mode 100644 index 0000000..b63fca3 Binary files /dev/null and b/mixer/pixmaps/panner-sphere-802x802.png differ diff --git a/mixer/pixmaps/panner-sphere-92x92.png b/mixer/pixmaps/panner-sphere-92x92.png new file mode 100644 index 0000000..dad8dc7 Binary files /dev/null and b/mixer/pixmaps/panner-sphere-92x92.png differ diff --git a/mixer/src/AUX_Module.C b/mixer/src/AUX_Module.C index 81b3593..9c7e4e6 100644 --- a/mixer/src/AUX_Module.C +++ b/mixer/src/AUX_Module.C @@ -112,12 +112,13 @@ AUX_Module::process ( nframes_t nframes ) { float gt = DB_CO( control_input[0].control_value() ); - if ( !smoothing.target_reached( gt ) ) - { - sample_t gainbuf[nframes]; - - smoothing.apply( gainbuf, nframes, gt ); + sample_t gainbuf[nframes]; + + bool use_gainbuf = smoothing.apply( gainbuf, nframes, gt ); + if ( use_gainbuf ) + { + for ( unsigned int i = 0; i < audio_input.size(); ++i ) { if ( audio_input[i].connected() ) diff --git a/mixer/src/Chain.C b/mixer/src/Chain.C index b7d332a..eac7427 100644 --- a/mixer/src/Chain.C +++ b/mixer/src/Chain.C @@ -908,8 +908,7 @@ Chain::update_connection_status ( void ) { Module *m = module(i); - if ( !strcmp( m->name(), "JACK" ) || - !strcmp( m->name(), "AUX" )) + if ( !strcmp( m->basename(), "JACK" ) ) { ((JACK_Module*)m)->update_connection_status(); } diff --git a/mixer/src/Controller_Module.C b/mixer/src/Controller_Module.C index 76a0489..d3929c7 100644 --- a/mixer/src/Controller_Module.C +++ b/mixer/src/Controller_Module.C @@ -234,6 +234,101 @@ Controller_Module::mode ( Mode m ) _mode = m ; } + +bool +Controller_Module::connect_spatializer_radius_to ( Module *m ) +{ + Port *radius_port = NULL; + float radius_value = 0.0f; + + for ( unsigned int i = 0; i < m->control_input.size(); ++i ) + { + Port *p = &m->control_input[i]; + + if ( !strcasecmp( "Radius", p->name() ) ) + /* 90.0f == p->hints.maximum && */ + /* -90.0f == p->hints.minimum ) */ + { + radius_port = p; + radius_value = p->control_value(); + continue; + } + } + + if ( ! radius_port ) + return false; + + if ( control_output.size() != 3 ) + { + control_output.clear(); + add_port( Port( this, Port::OUTPUT, Port::CONTROL ) ); + add_port( Port( this, Port::OUTPUT, Port::CONTROL ) ); + add_port( Port( this, Port::OUTPUT, Port::CONTROL ) ); + } + + + control_output[2].connect_to( radius_port ); + + maybe_create_panner(); + + Panner *o = (Panner*)control; + + o->point( 0 )->radius( radius_value ); + + if ( Mixer::spatialization_console ) + Mixer::spatialization_console->update(); + + return true; +} + +void +Controller_Module::maybe_create_panner ( void ) +{ + if ( _type != SPATIALIZATION ) + { + clear(); + + Panner *o = new Panner( 0,0, 92,92 ); + + o->box(FL_FLAT_BOX); + o->color(FL_GRAY0); + o->selection_color(FL_BACKGROUND_COLOR); + o->labeltype(FL_NORMAL_LABEL); + o->labelfont(0); + o->labelcolor(FL_FOREGROUND_COLOR); + o->align(FL_ALIGN_TOP); + o->when(FL_WHEN_CHANGED); + label( "Spatialization" ); + + o->align(FL_ALIGN_TOP); + o->labelsize( 10 ); +// o->callback( cb_panner_value_handle, new callback_data( this, azimuth_port_number, elevation_port_number ) ); + + + o->callback( cb_spatializer_handle, this ); + + control = (Fl_Valuator*)o; + + if ( _pad ) + { + Fl_Labelpad_Group *flg = new Fl_Labelpad_Group( o ); + flg->position( x(), y() ); + flg->set_visible_focus(); + size( flg->w(), flg->h() ); + add( flg ); + } + else + { + o->resize( x(), y(), w(), h() ); + add( o ); + resizable( o ); + init_sizes(); + } + + _type = SPATIALIZATION; + } +} + /** attempt to transform this controller into a spatialization controller and connect to the given module's spatialization control inputs. Returns true on success, false if given module @@ -241,6 +336,8 @@ Controller_Module::mode ( Mode m ) bool Controller_Module::connect_spatializer_to ( Module *m ) { + connect_spatializer_radius_to( m ); + /* these are for detecting related parameter groups which can be better represented by a single control */ Port *azimuth_port = NULL; @@ -269,60 +366,28 @@ Controller_Module::connect_spatializer_to ( Module *m ) continue; } } - + if ( ! ( azimuth_port && elevation_port ) ) return false; - control_output.clear(); - add_port( Port( this, Port::OUTPUT, Port::CONTROL ) ); - add_port( Port( this, Port::OUTPUT, Port::CONTROL ) ); + if ( control_output.size() != 3 ) + { + control_output.clear(); + add_port( Port( this, Port::OUTPUT, Port::CONTROL ) ); + add_port( Port( this, Port::OUTPUT, Port::CONTROL ) ); + add_port( Port( this, Port::OUTPUT, Port::CONTROL ) ); + } control_output[0].connect_to( azimuth_port ); control_output[1].connect_to( elevation_port ); - clear(); - - Panner *o = new Panner( 0,0, 92,92 ); - - o->box(FL_FLAT_BOX); - o->color(FL_GRAY0); - o->selection_color(FL_BACKGROUND_COLOR); - o->labeltype(FL_NORMAL_LABEL); - o->labelfont(0); - o->labelcolor(FL_FOREGROUND_COLOR); - o->align(FL_ALIGN_TOP); - o->when(FL_WHEN_CHANGED); - label( "Spatialization" ); - - o->align(FL_ALIGN_TOP); - o->labelsize( 10 ); -// o->callback( cb_panner_value_handle, new callback_data( this, azimuth_port_number, elevation_port_number ) ); + maybe_create_panner(); + + Panner *o = (Panner*)control; o->point( 0 )->azimuth( azimuth_value ); o->point( 0 )->elevation( elevation_value ); - - o->callback( cb_spatializer_handle, this ); - - control = (Fl_Valuator*)o; - - if ( _pad ) - { - Fl_Labelpad_Group *flg = new Fl_Labelpad_Group( o ); - flg->position( x(), y() ); - flg->set_visible_focus(); - size( flg->w(), flg->h() ); - add( flg ); - } - else - { - o->resize( x(), y(), w(), h() ); - add( o ); - resizable( o ); - init_sizes(); - } - - _type = SPATIALIZATION; - + if ( Mixer::spatialization_console ) Mixer::spatialization_console->update(); @@ -491,6 +556,11 @@ Controller_Module::cb_spatializer_handle ( Fl_Widget *w ) control_output[0].connected_port()->control_value( pan->point( 0 )->azimuth() ); control_output[1].connected_port()->control_value( pan->point( 0 )->elevation() ); } + + if ( control_output[2].connected() ) + { + control_output[2].connected_port()->control_value( pan->point( 0 )->radius() ); + } } void @@ -806,6 +876,14 @@ Controller_Module::handle_control_changed ( Port *p ) pan->point( 0 )->azimuth( control_output[0].control_value() ); pan->point( 0 )->elevation( control_output[1].control_value() ); + if ( control_output[2].connected() ) + { + Port *pp = control_output[2].connected_port(); + float v = control_output[2].control_value(); + float s = pp->hints.maximum - pp->hints.minimum; + + pan->point( 0 )->radius( v ); + } if ( visible_r() ) pan->redraw(); } diff --git a/mixer/src/Controller_Module.H b/mixer/src/Controller_Module.H index ee89913..72347ee 100644 --- a/mixer/src/Controller_Module.H +++ b/mixer/src/Controller_Module.H @@ -84,6 +84,7 @@ public: void connect_to ( Port *p ); bool connect_spatializer_to ( Module *m ); + bool connect_spatializer_radius_to ( Module *m ); void disconnect ( void ); void handle_control_changed ( Port *p ); @@ -111,6 +112,7 @@ protected: private: + void maybe_create_panner ( void ); char *generate_osc_path ( void ); void change_osc_path ( char *path ); diff --git a/mixer/src/Gain_Module.C b/mixer/src/Gain_Module.C index b900392..18a7d91 100644 --- a/mixer/src/Gain_Module.C +++ b/mixer/src/Gain_Module.C @@ -90,12 +90,13 @@ Gain_Module::process ( nframes_t nframes ) { const float gt = DB_CO( control_input[0].control_value() ); - if ( !smoothing.target_reached( gt ) ) - { - sample_t gainbuf[nframes]; - - smoothing.apply( gainbuf, nframes, gt ); + sample_t gainbuf[nframes]; + + bool use_gainbuf = smoothing.apply( gainbuf, nframes, gt ); + if ( use_gainbuf ) + { + for ( int i = audio_input.size(); i--; ) { if ( audio_input[i].connected() && audio_output[i].connected() ) diff --git a/mixer/src/JACK_Module.C b/mixer/src/JACK_Module.C index d01d977..50e87b1 100644 --- a/mixer/src/JACK_Module.C +++ b/mixer/src/JACK_Module.C @@ -54,6 +54,12 @@ JACK_Module::JACK_Module ( bool log ) { _prefix = 0; + _connection_handle_outputs[0][0] = 0; + _connection_handle_outputs[0][1] = 0; + _connection_handle_outputs[1][0] = 0; + _connection_handle_outputs[1][1] = 0; + + align( FL_ALIGN_TOP | FL_ALIGN_INSIDE ); if ( log ) @@ -135,7 +141,13 @@ JACK_Module::JACK_Module ( bool log ) o->hide(); } - { Fl_Box *o = output_connection_handle = new Fl_Box( x(), y(), 18, 18 ); + { Fl_Box *o = output_connection_handle = new Fl_Box( x(), y(), 12, 12 ); + o->tooltip( "Drag and drop to make and break JACK connections."); + o->image( output_connector_image ? output_connector_image : output_connector_image = new Fl_PNG_Image( "output_connector", img_io_output_connector_10x10_png, img_io_output_connector_10x10_png_len ) ); + o->hide(); + } + + { Fl_Box *o = output_connection2_handle = new Fl_Box( x(), y(), 12, 12 ); o->tooltip( "Drag and drop to make and break JACK connections."); o->image( output_connector_image ? output_connector_image : output_connector_image = new Fl_PNG_Image( "output_connector", img_io_output_connector_10x10_png, img_io_output_connector_10x10_png_len ) ); o->hide(); @@ -335,6 +347,41 @@ JACK_Module::can_support_inputs ( int ) return audio_output.size(); } + +void +JACK_Module::remove_jack_outputs ( void ) +{ + for ( unsigned int i = jack_output.size(); i--; ) + { + jack_output.back().shutdown(); + jack_output.pop_back(); + } +} + +bool +JACK_Module::add_jack_output ( const char *prefix, int n ) +{ + JACK::Port *po = NULL; + + if ( !prefix ) + po = new JACK::Port( chain()->engine(), JACK::Port::Output, JACK::Port::Audio, n ); + else + po = new JACK::Port( chain()->engine(), JACK::Port::Output, JACK::Port::Audio, prefix, n ); + + if ( ! po->activate() ) + { + jack_port_activation_error( po ); + return false; + } + + if ( po->valid() ) + { + jack_output.push_back( *po ); + } + + delete po; +} + bool JACK_Module::configure_inputs ( int n ) { @@ -386,6 +433,9 @@ JACK_Module::configure_inputs ( int n ) } } + _connection_handle_outputs[0][0] = 0; + _connection_handle_outputs[0][1] = jack_output.size(); + if ( is_default() ) control_input[0].control_value_no_callback( n ); @@ -465,7 +515,7 @@ JACK_Module::initialize ( void ) void JACK_Module::handle_control_changed ( Port *p ) { - THREAD_ASSERT( UI ); +// THREAD_ASSERT( UI ); if ( 0 == strcmp( p->name(), "Inputs" ) ) { @@ -492,6 +542,8 @@ JACK_Module::handle_control_changed ( Port *p ) p->connected_port()->control_value( noutputs() ); } } + + Module::handle_control_changed( p ); } void @@ -519,14 +571,28 @@ JACK_Module::handle ( int m ) return Module::handle(m) || 1; case FL_DRAG: { - if ( ! Fl::event_inside( this ) && ! Fl::selection_owner() ) + if ( Fl::event_is_click() ) + return 1; + + int connection_handle = -1; + if ( Fl::event_inside( output_connection_handle ) ) + connection_handle = 0; + if ( Fl::event_inside( output_connection2_handle ) ) + connection_handle = 1; + + + + if ( Fl::event_button1() && + connection_handle >= 0 + && ! Fl::selection_owner() ) { DMESSAGE( "initiation of drag" ); char *s = (char*)malloc(256); s[0] = 0; - for ( unsigned int i = 0; i < jack_output.size(); ++i ) + for ( unsigned int i = _connection_handle_outputs[connection_handle][0]; + i < jack_output.size() && i < _connection_handle_outputs[connection_handle][1]; ++i ) { char *s2; asprintf(&s2, "jack.port://%s/%s:%s\r\n", instance_name, chain()->name(), jack_output[i].name() ); @@ -552,12 +618,15 @@ JACK_Module::handle ( int m ) } /* we have to prevent Fl_Group::handle() from getting these, otherwise it will mess up Fl::belowmouse() */ case FL_MOVE: - return 0; + Module::handle(m); + return 1; case FL_ENTER: case FL_DND_ENTER: + Module::handle(m); return 1; case FL_LEAVE: case FL_DND_LEAVE: + Module::handle(m); if ( this == receptive_to_drop ) { receptive_to_drop = NULL; diff --git a/mixer/src/JACK_Module.H b/mixer/src/JACK_Module.H index 15aa074..8697698 100644 --- a/mixer/src/JACK_Module.H +++ b/mixer/src/JACK_Module.H @@ -52,9 +52,14 @@ protected: Fl_Browser * connection_display; Fl_Box * input_connection_handle; Fl_Box * output_connection_handle; + Fl_Box * output_connection2_handle; static void cb_button ( Fl_Widget *w, void *v ); void cb_button ( Fl_Widget *w ); + +protected: + + int _connection_handle_outputs[2][2]; public: @@ -63,6 +68,7 @@ public: JACK_Module ( bool log = true ); virtual ~JACK_Module ( ); + virtual const char *basename ( void ) const { return "JACK"; } virtual const char *name ( void ) const { return "JACK"; } virtual bool initialize ( void ); @@ -70,6 +76,8 @@ public: virtual int handle ( int m ); virtual int can_support_inputs ( int ); + bool add_jack_output ( const char *prefix, int n ); + void remove_jack_outputs ( void ); virtual bool configure_inputs ( int n ); virtual bool configure_outputs ( int n ); diff --git a/mixer/src/Module.C b/mixer/src/Module.C index 393bafb..71a546c 100644 --- a/mixer/src/Module.C +++ b/mixer/src/Module.C @@ -34,6 +34,7 @@ #include "Meter_Module.H" #include "Plugin_Module.H" #include "AUX_Module.H" +#include "Spatializer_Module.H" #include #include "FL/test_press.H" @@ -137,6 +138,7 @@ Module::init ( void ) align( FL_ALIGN_CENTER | FL_ALIGN_INSIDE ); set_visible_focus(); selection_color( FL_RED ); + labelsize(12); color( fl_color_average( FL_WHITE, FL_CYAN, 0.40 ) ); } @@ -664,7 +666,7 @@ Module::draw_label ( int tx, int ty, int tw, int th ) fl_color( active_r() ? c : fl_inactive(c) ); - fl_font( FL_HELVETICA, 12 ); + fl_font( FL_HELVETICA, labelsize() ); int LW = fl_width( lp ); @@ -720,8 +722,32 @@ Module::insert_menu_cb ( const Fl_Menu_ *m ) mod = jm; } + if ( !strcmp( picked, "Spatializer" ) ) + { + int n = 0; + for ( int i = 0; i < chain()->modules(); i++ ) + { + if ( !strcmp( chain()->module(i)->name(), "Spatializer" ) ) + n++; + } + + if ( n == 0 ) + { + Spatializer_Module *jm = new Spatializer_Module(); + + jm->chain( chain() ); +// jm->number( n ); +// jm->configure_inputs( ninputs() ); +// jm->configure_outputs( ninputs() ); + jm->initialize(); + + mod = jm; + } + } else if ( !strcmp( picked, "Gain" ) ) mod = new Gain_Module(); + /* else if ( !strcmp( picked, "Spatializer" ) ) */ + /* mod = new Spatializer_Module(); */ else if ( !strcmp( picked, "Meter" ) ) mod = new Meter_Module(); else if ( !strcmp( picked, "Mono Pan" )) @@ -826,6 +852,8 @@ Module::menu ( void ) const insert_menu->add( "Meter", 0, 0 ); insert_menu->add( "Mono Pan", 0, 0 ); insert_menu->add( "Aux", 0, 0 ); + insert_menu->add( "Distance", 0, 0 ); + insert_menu->add( "Spatializer", 0, 0 ); insert_menu->add( "Plugin", 0, 0 ); insert_menu->callback( &Module::insert_menu_cb, (void*)this ); diff --git a/mixer/src/Module.H b/mixer/src/Module.H index ce8e430..aae87a2 100644 --- a/mixer/src/Module.H +++ b/mixer/src/Module.H @@ -344,6 +344,7 @@ public: } virtual const char *name ( void ) const = 0; + virtual const char *basename ( void ) const { return "Module"; } std::vector audio_input; std::vector audio_output; diff --git a/mixer/src/Module_Parameter_Editor.C b/mixer/src/Module_Parameter_Editor.C index 4a6528d..945f1c6 100644 --- a/mixer/src/Module_Parameter_Editor.C +++ b/mixer/src/Module_Parameter_Editor.C @@ -42,6 +42,8 @@ #include "debug.h" + + Module_Parameter_Editor::Module_Parameter_Editor ( Module *module ) : Fl_Double_Window( 800, 600 ) @@ -144,7 +146,9 @@ Module_Parameter_Editor::make_controls ( void ) float azimuth_value = 0.0f; elevation_port_number = -1; float elevation_value = 0.0f; - + radius_port_number = -1; + float radius_value = 0.0f; + Fl_Color fc = fl_color_add_alpha( FL_CYAN, 200 ); Fl_Color bc = FL_BACKGROUND2_COLOR; @@ -174,6 +178,12 @@ Module_Parameter_Editor::make_controls ( void ) elevation_port_number = i; elevation_value = p->control_value(); continue; + } + else if ( !strcasecmp( "Radius", p->name() ) ) + { + radius_port_number = i; + radius_value = p->control_value(); + continue; } if ( p->hints.type == Module::Port::Hints::BOOLEAN ) @@ -268,10 +278,12 @@ Module_Parameter_Editor::make_controls ( void ) w->align(FL_ALIGN_TOP); w->labelsize( 10 ); + _callback_data.push_back( callback_data( this, i ) ); + if ( p->hints.type == Module::Port::Hints::BOOLEAN ) - w->callback( cb_button_handle, new callback_data( this, i ) ); + w->callback( cb_button_handle, &_callback_data.back() ); else - w->callback( cb_value_handle, new callback_data( this, i ) ); + w->callback( cb_value_handle, &_callback_data.back() ); { Fl_Group *o = new Fl_Group( 0, 0, 50, 75 ); { @@ -284,7 +296,7 @@ Module_Parameter_Editor::make_controls ( void ) o->value( p->connected() ); - o->callback( cb_bound_handle, new callback_data( this, i ) ); + o->callback( cb_bound_handle, &_callback_data.back() ); } o->resizable( 0 ); @@ -305,7 +317,7 @@ Module_Parameter_Editor::make_controls ( void ) if ( azimuth_port_number >= 0 && elevation_port_number >= 0 ) { - Panner *o = new Panner( 0,0, 512,512 ); + Panner *o = new Panner( 0,0, 502,502 ); o->box(FL_FLAT_BOX); o->color(FL_GRAY0); o->selection_color(FL_BACKGROUND_COLOR); @@ -318,10 +330,13 @@ Module_Parameter_Editor::make_controls ( void ) o->align(FL_ALIGN_TOP); o->labelsize( 10 ); - o->callback( cb_panner_value_handle, new callback_data( this, azimuth_port_number, elevation_port_number ) ); + + _callback_data.push_back( callback_data( this, azimuth_port_number, elevation_port_number, radius_port_number ) ); + o->callback( cb_panner_value_handle, &_callback_data.back() ); o->point( 0 )->azimuth( azimuth_value ); o->point( 0 )->elevation( elevation_value ); + o->point( 0 )->radius( radius_value ); Fl_Labelpad_Group *flg = new Fl_Labelpad_Group( o ); @@ -329,6 +344,7 @@ Module_Parameter_Editor::make_controls ( void ) controls_by_port[azimuth_port_number] = o; controls_by_port[elevation_port_number] = o; + controls_by_port[radius_port_number] = o; } @@ -367,6 +383,8 @@ Module_Parameter_Editor::cb_panner_value_handle ( Fl_Widget *w, void *v ) cd->base_widget->set_value( cd->port_number[0], ((Panner*)w)->point( 0 )->azimuth() ); cd->base_widget->set_value( cd->port_number[1], ((Panner*)w)->point( 0 )->elevation() ); + cd->base_widget->set_value( cd->port_number[2], ((Panner*)w)->point( 0 )->radius() ); + } void @@ -417,7 +435,8 @@ Module_Parameter_Editor::handle_control_changed ( Module::Port *p ) Fl_Widget *w = controls_by_port[i]; if ( i == azimuth_port_number || - i == elevation_port_number ) + i == elevation_port_number || + i == radius_port_number ) { Panner *_panner = (Panner*)w; @@ -425,6 +444,8 @@ Module_Parameter_Editor::handle_control_changed ( Module::Port *p ) _panner->point(0)->azimuth( p->control_value() ); else if ( i == elevation_port_number ) _panner->point(0)->elevation( p->control_value() ); + else if ( i == radius_port_number ) + _panner->point(0)->radius( p->control_value() ); _panner->redraw(); diff --git a/mixer/src/Module_Parameter_Editor.H b/mixer/src/Module_Parameter_Editor.H index 8b49fe0..cc447ed 100644 --- a/mixer/src/Module_Parameter_Editor.H +++ b/mixer/src/Module_Parameter_Editor.H @@ -28,11 +28,12 @@ class Fl_Menu_Button; class Panner; #include +#include class Module_Parameter_Editor : public Fl_Double_Window { Module *_module; - + struct callback_data { Module_Parameter_Editor *base_widget; @@ -44,6 +45,7 @@ class Module_Parameter_Editor : public Fl_Double_Window this->base_widget = base_widget; this->port_number[0] = port_number; this->port_number[1] = -1; + this->port_number[2] = -1; } callback_data ( Module_Parameter_Editor *base_widget, int port_number1, int port_number2 ) @@ -51,6 +53,15 @@ class Module_Parameter_Editor : public Fl_Double_Window this->base_widget = base_widget; this->port_number[0] = port_number1; this->port_number[1] = port_number2; + this->port_number[2] = -1; + } + + callback_data ( Module_Parameter_Editor *base_widget, int port_number1, int port_number2, int port_number3 ) + { + this->base_widget = base_widget; + this->port_number[0] = port_number1; + this->port_number[1] = port_number2; + this->port_number[2] = port_number3; } }; @@ -72,7 +83,9 @@ class Module_Parameter_Editor : public Fl_Double_Window int azimuth_port_number; int elevation_port_number; - + int radius_port_number; + + std::list _callback_data; std::vector controls_by_port; public: diff --git a/mixer/src/Panner.C b/mixer/src/Panner.C index 45a1970..9985de1 100644 --- a/mixer/src/Panner.C +++ b/mixer/src/Panner.C @@ -25,11 +25,49 @@ // #include #include +#include + +#include "debug.h" /* 2D Panner widget. Supports various multichannel configurations. */ Panner::Point *Panner::drag; +Panner::Panner ( int X, int Y, int W, int H, const char *L ) : + Fl_Group( X, Y, W, H, L ) +{ + _range = 15.0f; +// _projection = POLAR; + _points.push_back( Point( 1, 0 ) ); + + static int ranges[] = { 1,5,10,15 }; + { Fl_Choice *o = _range_choice = new Fl_Choice(X + 40,Y + H - 18,75,18,"Range:"); + o->box(FL_UP_FRAME); + o->down_box(FL_DOWN_FRAME); + o->textsize(9); + o->labelsize(9); + o->align(FL_ALIGN_LEFT); + o->add("1 Meter",0,0,&ranges[0]); + o->add("5 Meters",0,0,&ranges[1]); + o->add("10 Meters",0,0,&ranges[2]); + o->add("15 Meters",0,0,&ranges[3]); + o->value(3); + } + + { Fl_Choice *o = _projection_choice = new Fl_Choice(X + W - 75,Y + H - 18,75,18,"Projection:"); + o->box(FL_UP_FRAME); + o->down_box(FL_DOWN_FRAME); + o->textsize(9); + o->labelsize(9); + o->align(FL_ALIGN_LEFT); + o->add("Spherical"); + o->add("Planar"); + o->value(0); + } + + end(); +} + /** set X, Y, W, and H to the bounding box of point /p/ in screen coords */ void Panner::point_bbox ( const Point *p, int *X, int *Y, int *W, int *H ) const @@ -38,21 +76,32 @@ Panner::point_bbox ( const Point *p, int *X, int *Y, int *W, int *H ) const bbox( tx, ty, tw, th ); - const float PW = pw(); - const float PH = ph(); - - tw -= PW; - th -= PH; - float px, py; + float s = 1.0f; + + if ( projection() == POLAR ) + { + project_polar( p, &px, &py, &s ); + } + else + { + project_ortho( p, &px, &py, &s ); + } - p->axes( &px, &py ); + const float htw = float(tw)*0.5f; + const float hth = float(th)*0.5f; - *X = tx + ((tw / 2) * px + (tw / 2)); - *Y = ty + ((th / 2) * py + (th / 2)); - *W = PW; - *H = PH; + *W = *H = tw * s; + + if ( *W < 8 ) + *W = 8; + if ( *H < 8 ) + *H = 8; + + *X = tx + (htw * px + htw) - *W/2; + *Y = ty + (hth * py + hth) - *H/2; + } Panner::Point * @@ -85,18 +134,183 @@ Panner::draw_the_box ( int tx, int ty, int tw, int th ) { draw_box(); - if ( tw == 92 ) - { - Fl_Image *i = Fl_Shared_Image::get( PIXMAP_PATH "/non-mixer/panner-92x92.png" ); - - i->draw( tx, ty ); - } - else if ( tw > 400 ) - { - Fl_Image *i = Fl_Shared_Image::get( PIXMAP_PATH "/non-mixer/panner-512x512.png" ); - - i->draw( tx, ty ); - } + Fl_Image *i = 0; + + if ( projection() == POLAR ) + { + switch ( tw ) + { + case 802: + i = Fl_Shared_Image::get( PIXMAP_PATH "/non-mixer/panner-sphere-802x802.png" ); + break; + case 92: + i = Fl_Shared_Image::get( PIXMAP_PATH "/non-mixer/panner-sphere-92x92.png" ); + break; + case 502: + i = Fl_Shared_Image::get( PIXMAP_PATH "/non-mixer/panner-sphere-502x502.png" ); + break; + } + } + else + { + switch ( tw ) + { + case 802: + i = Fl_Shared_Image::get( PIXMAP_PATH "/non-mixer/panner-plane-802x802.png" ); + break; + case 92: + i = Fl_Shared_Image::get( PIXMAP_PATH "/non-mixer/panner-plane-92x92.png" ); + break; + case 502: + i = Fl_Shared_Image::get( PIXMAP_PATH "/non-mixer/panner-plane-502x502.png" ); + break; + } + } + + if ( i ) + i->draw( tx, ty ); +} + +void +Panner::draw_grid ( int tx, int ty, int tw, int th ) +{ + + fl_push_matrix(); + fl_translate(tx,ty); + fl_scale(tw,th); + + fl_color( fl_color_add_alpha( FL_WHITE, 25 ) ); + + for ( float x = 0.0f; x <= 1.0f; x += 0.05f ) + { + fl_begin_line(); + for ( float y = 0.0f; y <= 1.0f; y += 0.5f ) + { + fl_vertex( x, y ); + } + fl_end_line(); + } + + for ( float y = 0.0f; y <= 1.0f; y += 0.05f ) + { + fl_begin_line(); + for ( float x = 0.0f; x <= 1.0f; x += 0.5f ) + { + fl_vertex( x, y ); + } + fl_end_line(); + } + + for ( float x = 0.0f; x < 1.0f; x += 0.05f ) + { + fl_begin_points(); + for ( float y = 0.0f; y < 1.0f; y += 0.05f ) + { + fl_vertex( x, y ); + } + fl_end_points(); + } + + fl_pop_matrix(); + +} + +/** translate angle /a/ into x/y coords and place the result in /X/ and /Y/ */ +void +Panner::project_polar ( const Point *p, float *X, float *Y, float *S ) const +{ + float xp = 0.0f; + float yp = 0.0f; + float zp = 8.0f; + + float x = 0 - p->y; + float y = 0 - p->x; + float z = 0 - p->z; + + x /= range(); + y /= range(); + z /= range(); + + *X = ((x-xp) / (z + zp)) * (zp); + *Y = ((y-yp) / (z + zp)) * (zp); + *S = (0.025f / (z + zp)) * (zp); +} + +/** translate angle /a/ into x/y coords and place the result in /X/ and /Y/ */ +void +Panner::project_ortho ( const Point *p, float *X, float *Y, float *S ) const +{ + const float x = ( 0 - p->y ) / range(); + const float y = ( 0 - p->x ) / range(); + const float z = p->z; + + float zp = 4.0f; + + *X = x; + *Y = y; + + *S = 0.025f; +} + + +void +Panner::set_ortho ( Point *p, float x, float y ) +{ + y = 0 - y; + + y *= 2; + x *= 2; + + p->x = (y) * range(); + p->y = (0 - x) * range(); +} + +void +Panner::set_polar ( Point *p, float x, float y ) +{ + /* FIXME: not quite the inverse of the projection... */ + x = 0 - x; + y = 0 - y; + x *= 2; + y *= 2; + + float r = powf( x,2 ) + powf(y,2 ); + float X = ( 2 * x ) / ( 1 + r ); + float Y = ( 2 * y ) / ( 1 + r ); + float Z = ( -1 + r ) / ( 1 + r ); + + float S = p->radius() / range(); + + X *= S; + Y *= S; + Z *= S; + + p->azimuth( -atan2f( X,Y ) * ( 180 / M_PI ) ); + + if ( p->elevation() > 0.0f ) + p->elevation( -atan2f( Z, sqrtf( powf(X,2) + powf( Y, 2 ))) * ( 180 / M_PI ) ); + else + p->elevation( atan2f( Z, sqrtf( powf(X,2) + powf( Y, 2 ))) * ( 180 / M_PI ) ); +} + + + +void +Panner::set_polar_radius ( Point *p, float x, float y ) +{ + y = 0 - y; + + float r = sqrtf( powf( y, 2 ) + powf( x, 2 ) ); + + if ( r > 1.0f ) + r = 1.0f; + + r *= range() * 2; + + if ( r > range() ) + r = range(); + + p->radius( r ); } void @@ -115,21 +329,20 @@ Panner::draw ( void ) // draw_box(); draw_label(); - if ( _bypassed ) - { - draw_box(); - fl_color( 0 ); - fl_font( FL_HELVETICA, 12 ); - fl_draw( "(bypass)", x(), y(), w(), h(), FL_ALIGN_CENTER ); - goto done; - } + /* if ( _bypassed ) */ + /* { */ + /* draw_box(); */ + /* fl_color( 0 ); */ + /* fl_font( FL_HELVETICA, 12 ); */ + /* fl_draw( "(bypass)", x(), y(), w(), h(), FL_ALIGN_CENTER ); */ + /* goto done; */ + /* } */ /* tx += b; */ /* ty += b; */ /* tw -= b * 2; */ /* th -= b * 2; */ - fl_line_style( FL_SOLID, 1 ); fl_color( FL_WHITE ); @@ -141,50 +354,72 @@ Panner::draw ( void ) if ( ! p->visible ) continue; - Fl_Color c = fl_color_add_alpha( p->color, 127 ); - + Fl_Color c = fl_color_add_alpha( p->color, 100 ); + + fl_color(c); + int px, py, pw, ph; point_bbox( p, &px, &py, &pw, &ph ); - + { - - const float S = ( 0.5 + ( 1.0f - p->d ) ); - - float po = 5 * S; + float po = 5; fl_push_clip( px - ( po * 12 ), py - ( po * 12 ), pw + ( po * 24 ), ph + (po * 24 )); -// fl_color( fl_color_add_alpha( fl_rgb_color( 254,254,254 ), 254 ) ); - fl_pie( px + 5, py + 5, pw - 10, ph - 10, 0, 360 ); - fl_color(c); fl_pie( px, py, pw, ph, 0, 360 ); - if ( Fl::belowmouse() == this ) - { - /* draw echo */ - fl_color( c = fl_darker( c ) ); + fl_pop_clip(); - fl_arc( px - po, py - po, pw + ( po * 2 ), ph + ( po * 2 ), 0, 360 ); - - fl_color( c = fl_darker( c ) ); - - fl_arc( px - ( po * 2 ), py - ( po * 2 ), pw + ( po * 4 ), ph + ( po * 4 ), 0, 360 ); + if ( projection() == POLAR ) + { + + fl_color( fl_color_average( fl_rgb_color( 127,127,127 ), p->color, 0.50 ) ); + fl_begin_loop(); + fl_circle( tx + tw/2, ty + th/2, tw/2.0f * ( ( p->radius() / range() ))); + fl_end_loop(); } - fl_pop_clip(); } const char *s = p->label; fl_color( fl_color_add_alpha( fl_rgb_color( 220,255,255 ), 127 ) ); - fl_font( FL_HELVETICA_BOLD_ITALIC, 12 ); + fl_font( FL_HELVETICA_BOLD_ITALIC, 10 ); fl_draw( s, px + 20, py + 1, 50, ph - 1, FL_ALIGN_LEFT ); + + if ( tw > 100 ) + { + char pat[50]; + snprintf( pat, sizeof(pat), "%.1f°:%.1f° %.1fm", p->azimuth(), p->elevation(), p->radius() ); + +// fl_color( fl_color_add_alpha( fl_rgb_color( 220,255,255 ), 127 ) ); + fl_font( FL_COURIER, 9 ); + + fl_draw( pat, px + 20, py + 15, 50, ph - 1, FL_ALIGN_LEFT | FL_ALIGN_WRAP ); + + /* fl_font( FL_HELVETICA_ITALIC, 9 ); */ + /* snprintf(pat, sizeof(pat), "range: %.1f meters", range() ); */ + /* fl_draw( pat, tx, ty, tw, th, FL_ALIGN_LEFT | FL_ALIGN_BOTTOM | FL_ALIGN_INSIDE ); */ + + /* if ( _projection == POLAR ) */ + /* { */ + /* fl_draw( "Polar perspective; azimuth, elevation and radius input. Right click controls radius.", tx, ty, tw, th, FL_ALIGN_BOTTOM | FL_ALIGN_RIGHT | FL_ALIGN_INSIDE ); */ + /* } */ + /* else */ + /* { */ + /* fl_draw( "Polar orthographic; angle and distance input.", tx, ty, tw, th, FL_ALIGN_BOTTOM | FL_ALIGN_RIGHT | FL_ALIGN_INSIDE ); */ + /* } */ + } + } + + if ( tw > 200 ) + draw_children(); done: @@ -203,7 +438,7 @@ Panner::point( int i ) int Panner::handle ( int m ) { - int r = Fl_Widget::handle( m ); + int r = Fl_Group::handle( m ); switch ( m ) { @@ -213,16 +448,16 @@ Panner::handle ( int m ) return 1; case FL_PUSH: { - if ( Fl::event_button2() ) - { - _bypassed = ! _bypassed; - redraw(); - return 1; - } - - if ( Fl::event_button1() ) + if ( Fl::event_button1() || Fl::event_button3() ) drag = event_point(); + if ( Fl::event_button2() ) + { + /* if ( _projection == POLAR ) */ + /* _projection = ORTHO; */ + /* else */ + /* _projection = POLAR; */ + } return 1; } case FL_RELEASE: @@ -237,29 +472,42 @@ Panner::handle ( int m ) return 0; case FL_MOUSEWHEEL: { - /* TODO: place point on opposite face of sphere */ - return 0; +/* Point *p = event_point(); */ + +/* if ( p ) */ +/* drag = p; */ + +/* if ( drag ) */ +/* { */ +/* // drag->elevation( drag->elevation() + Fl::event_dy()); */ +/* drag->elevation( 0 - drag->elevation() ); */ +/* do_callback(); */ +/* redraw(); */ +/* return 1; */ +/* } */ + + return 1; } case FL_DRAG: { if ( ! drag ) return 0; - /* else if ( Fl::event_button1() && ( drag = event_point() ) ) */ - /* return 1; */ - /* else */ - - int tx, ty, tw, th; bbox( tx, ty, tw, th ); - float X = Fl::event_x() - tx; - float Y = Fl::event_y() - ty; - -/* if ( _outs < 3 ) */ -/* drag->angle( (float)(X / (tw / 2)) - 1.0f, 0.0f ); */ -/* else */ - drag->angle( (float)(X / (tw / 2)) - 1.0f, (float)(Y / (th / 2)) - 1.0f ); + float X = (float(Fl::event_x() - tx) / tw ) - 0.5f; + float Y = (float(Fl::event_y() - ty) / th) - 0.5f; + + if ( Fl::event_button1() ) + { + if ( POLAR == projection() ) + set_polar( drag,X,Y ); + else + set_ortho( drag, X,Y ); + } + else + set_polar_radius( drag,X,Y ); if ( when() & FL_WHEN_CHANGED ) do_callback(); diff --git a/mixer/src/Panner.H b/mixer/src/Panner.H index f15c17c..d34dd4d 100644 --- a/mixer/src/Panner.H +++ b/mixer/src/Panner.H @@ -19,130 +19,113 @@ #pragma once -#include +#include #include #include - +#include #include #include using namespace std; -class Panner : public Fl_Widget +class Panner : public Fl_Group { + Fl_Choice *_range_choice; + Fl_Choice *_projection_choice; + void draw_grid( int,int,int,int); void draw_the_box( int, int, int, int ); public: + struct Point { - /* axes */ - - /* distance from center (from 0 to 1) */ - float d; - /* angle */ - float a; + float x,y,z; const char *label; void *userdata; Fl_Color color; - bool visible; - Point ( ) : d( 0.0f ), a( 0.0f ), label(0), visible(1){ } - Point ( float D, float A ) : d( D ), a( A ), label(0), visible(1) { } + Point ( ) { + x = 1; + y = 0; + z = 0; + label = 0; + visible = 1; + color = FL_WHITE; + } - /** translate angle /a/ into x/y coords and place the result in /X/ and /Y/ */ - void - axes ( float *X, float *Y ) const + Point ( float D, float A ) { - /* rotate */ - float A = ( 270 - a ) * ( M_PI / 180 ); - - *X = -d * cosf( A ); - *Y = d * sinf( A ); + radius( D ); + azimuth( A ); + label = 0; + visible = 1; + color = FL_WHITE; } - float azimuth ( void ) const + static inline void spherical_to_cartesian (float a, float e, float &x, float &y, float &z ) { - return a > 180.0f ? a - 360.0f : a; + a *= M_PI / 180.0f; + e *= M_PI / 180.0f; + + z = sinf(e); + const float ce = cosf(e); + x = ce * cosf(-a); + y = ce * sinf(-a); } - - - float elevation ( void ) const + + float azimuth ( void ) const { + return -atan2f( y,x ) * ( 180 / M_PI ); + } + float elevation ( void ) const { + return atan2f(z,sqrtf(powf(x,2)+powf(y,2)) ) * ( 180 / M_PI ); + } + float radius ( void ) const { + return sqrtf(powf(x,2)+powf(y,2)+powf(z,2)); + } + + void azimuth ( float v ) { - return ( 1.0f - d ) * 90.0f; - } + float r = radius(); - void azimuth ( float v ) - { - a = v < 0.0f ? v + 360.0f : v; - a = a < 0.0f ? 0.0f : a > 360.0f ? 360.0f : a; + spherical_to_cartesian( v, elevation(), x,y,z ); + x *= r; + y *= r; + z *= r; } - + void elevation ( float v ) { - d = 1.0f - ( v / 90.0f ); - d = d < 0.0f ? 0.0f : d > 1.0f ? 1.0f : d; - } + float r = radius(); - /** set point position in X, Y coordinates (0.0 to 1.0) */ - void - angle ( float X1, float Y1 ) + spherical_to_cartesian( azimuth(), v, x,y,z ); + x *= r; + y *= r; + z *= r; + } + + + void radius ( float v ) { + float r = v; - float X2, Y2; + spherical_to_cartesian( azimuth(), elevation(), x,y,z ); - Y2 = X2 = 0; - - float t; - - t = atan2( X2 - X1, Y2 - Y1 ); - - a = t * (180 / M_PI); - - if ( a < 0 ) - a = 360 + a; - - a = 360 - a; - - /* standard distance calculation */ - d = sqrt( pow( X2 - X1, 2 ) + pow( Y2 - Y1, 2 ) ); - - if ( d > 1.0f ) - d = 1.0f; + x *= r; + y *= r; + z *= r; } - - /** return the distance between the point and that referenced by /p/ */ - float - distance ( const Point &p ) - { - /* first, transform point coords */ - - float x1, y1, x2, y2; - - axes( &x1, &y1 ); - p.axes( &x2, &y2 ); - - /* standard distance calculation */ - return sqrt( pow( x1 - x2, 2 ) + pow( y1 - y2, 2 ) ); - } - }; private: + + float _range; - /* channel configuration */ - int _ins, - _outs; - - bool _bypassed; - + vector _points; - static int pw ( void ) { return 16; } - static int ph ( void ) { return 16; } - static int _configs[][12]; void bbox ( int &X, int &Y, int &W, int &H ) const @@ -169,6 +152,14 @@ private: static Point * drag; + void set_polar ( Point *p, float x, float y ); + void set_ortho ( Point *p, float x, float y ); + void set_polar_radius ( Point *p, float x, float y ); + void project_polar ( const Point *p, float *X, float *Y, float *S ) const; + void project_ortho ( const Point *p, float *X, float *Y, float *S ) const; + + int _projection; + protected: virtual void draw ( void ); @@ -176,18 +167,15 @@ protected: public: + enum { POLAR, ORTHO }; - Panner ( int X, int Y, int W, int H, const char *L = 0 ) : - Fl_Widget( X, Y, W, H, L ) - { - _bypassed = false; + float projection ( void ) const { return _projection_choice->value(); } + void projection ( int v ) { _projection_choice->value(v); } - _ins = 1; + Panner ( int X, int Y, int W, int H, const char *L = 0 ); - _outs = 1; - - _points.push_back( Point( 1, 0 ) ); - } + float range ( void ) const { return *((int*)_range_choice->mvalue()->user_data()); } + /* void range ( float v ) { _range = v; } */ void clear_points ( void ) { _points.clear(); } diff --git a/mixer/src/Spatialization_Console.C b/mixer/src/Spatialization_Console.C index 42876cf..d525880 100644 --- a/mixer/src/Spatialization_Console.C +++ b/mixer/src/Spatialization_Console.C @@ -35,10 +35,11 @@ #include "Mixer.H" #include "debug.h" +#include -Spatialization_Console::Spatialization_Console ( void ) : Fl_Double_Window( 565, 565 ) +Spatialization_Console::Spatialization_Console ( void ) : Fl_Double_Window( 850, 850 ) { _resized = false; _min_width = 100; @@ -46,8 +47,8 @@ Spatialization_Console::Spatialization_Console ( void ) : Fl_Double_Window( 565, label( "Spatialization Console" ); fl_font( FL_HELVETICA, 14 ); - - panner = new Panner( 25,25, 512, 512 ); + + panner = new Panner( 25,25, 802,802 ); panner->callback( cb_panner_value_handle, this ); panner->when( FL_WHEN_CHANGED ); @@ -62,6 +63,11 @@ Spatialization_Console::~Spatialization_Console ( ) // controls_by_port.clear(); } + + + + + void @@ -87,6 +93,7 @@ Spatialization_Console::make_controls ( void ) p.azimuth( o->spatializer()->control_output[0].control_value() ); p.elevation( o->spatializer()->control_output[1].control_value() ); + p.radius( o->spatializer()->control_output[2].control_value() ); } else p.visible = false; @@ -111,6 +118,7 @@ Spatialization_Console::cb_panner_value_handle ( Fl_Widget *w, void *v ) cm->control_output[0].control_value( p->azimuth() ); cm->control_output[1].control_value( p->elevation() ); + cm->control_output[2].control_value( p->radius() ); } /* Display changes initiated via automation or from other parts of the GUI */ @@ -128,6 +136,7 @@ Spatialization_Console::handle_control_changed ( Controller_Module *m ) { p->azimuth( m->control_output[0].control_value() ); p->elevation( m->control_output[1].control_value() ); + p->radius( m->control_output[2].control_value() ); if ( panner->visible_r() ) panner->redraw(); diff --git a/mixer/src/Spatialization_Console.H b/mixer/src/Spatialization_Console.H index b2c8b62..c1c0792 100644 --- a/mixer/src/Spatialization_Console.H +++ b/mixer/src/Spatialization_Console.H @@ -22,6 +22,7 @@ #include #include + class Fl_Pack; class Fl_Flowpack; class Module; diff --git a/mixer/src/Spatializer_Module.C b/mixer/src/Spatializer_Module.C new file mode 100644 index 0000000..d8dfc30 --- /dev/null +++ b/mixer/src/Spatializer_Module.C @@ -0,0 +1,674 @@ + +/*******************************************************************************/ +/* 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 +#include +#include "Spatializer_Module.H" +#include "dsp.h" + +static const float max_distance = 15.0f; +static const float HIGHPASS_FREQ = 200.0f; +//static const float LOWPASS_FREQ = 70000.0f; +static const float LOWPASS_FREQ = 22000.0f; + +#include + +class filter +{ +protected: + + float _sample_rate; + float _w; + float _last_output; + float _last_cutoff; + float _amount_of_current; + float _amount_of_last; + bool _bypass; + + void recalculate ( float cutoff ) + { + _last_cutoff = cutoff; + + if (_last_cutoff <= 10 ) + { + _bypass = true; + } + else if (_last_cutoff > _sample_rate * 0.5f ) + { + _bypass = true; + } + else + { + const float c = 2.0f - cosf(_w * _last_cutoff); + _amount_of_last = c - sqrtf(c * c - 1.0f); + _amount_of_current = 1 - _amount_of_last; + + _bypass = false; + } + } + +public: + + void sample_rate ( nframes_t srate ) + { + _sample_rate = srate; + _w = (2 * M_PI) / (float)srate; + } + + filter () + { + _last_output = 0; + _last_cutoff = 0; + _w = 0; + _sample_rate = 0; + _amount_of_current = 0; + _amount_of_last = 0; + _bypass = false; + } + + + void + run_lowpass ( float *buf, float cutoff, nframes_t nframes ) + { + if (cutoff != _last_cutoff) + { + recalculate( cutoff ); + } + + if ( !_bypass ) + { + while ( nframes-- ) + { + *buf = _last_output = (_amount_of_current * *buf + _amount_of_last * _last_output); + buf++; + } + } + } + + void + run_highpass ( float *buf, float cutoff, nframes_t nframes ) + { + if (cutoff != _last_cutoff) + { + recalculate( cutoff ); + } + + if ( !_bypass ) + { + while ( nframes-- ) + { + _last_output = ((_amount_of_current * *buf) + (_amount_of_last * _last_output)); + *buf = *buf - _last_output; + buf++; + } + } + } + +}; + +class delay +{ + unsigned int _sample_rate; + float *_buffer; + long _write_index; + unsigned int _buffer_mask; + float _max_delay; + +public: + void sample_rate ( float srate ) + { + if ( _buffer ) + free( _buffer ); + + unsigned int size, minsize; + minsize = (unsigned long)(srate * _max_delay); + + size = 1; + while (size < minsize) + size <<= 1; + + _buffer = (float *)calloc(size, sizeof(float)); + + _buffer_mask = size - 1; + + _sample_rate = srate; + + _write_index = 0; + } + + + delay ( float max_delay ) + { + _max_delay = max_delay; + _write_index = 0; + _sample_rate = 0; + _buffer = 0; + _buffer_mask =0; + } + + ~delay ( ) + { + if ( _buffer ) + free( _buffer ); + } + + void run ( float *buf, float *delaybuf, float delay, nframes_t nframes ) + { + const nframes_t min_delay_samples = 4; + + + if ( delaybuf ) + { + for (nframes_t i = 0; i < nframes; i++ ) + { + float delay_samples = delaybuf[i] * _sample_rate; + + if ( delay_samples > _buffer_mask + 1 ) + delay_samples = _buffer_mask; + else if ( delay_samples < min_delay_samples ) + delay_samples = min_delay_samples; + + long idelay_samples = (long)delay_samples; + const float frac = delay_samples - idelay_samples; + const long read_index = _write_index - idelay_samples; + + _buffer[_write_index++ & _buffer_mask] = buf[i]; + + const float read = interpolate_cubic (frac, + _buffer[(read_index-1) & _buffer_mask], + _buffer[read_index & _buffer_mask], + _buffer[(read_index+1) & _buffer_mask], + _buffer[(read_index+2) & _buffer_mask]); + + buf[i] = read; + } + } + else + { + float delay_samples = delay * _sample_rate; + + if ( delay_samples > _buffer_mask + 1 ) + delay_samples = _buffer_mask; + else if ( delay_samples < min_delay_samples ) + delay_samples = min_delay_samples; + + long idelay_samples = (long)delay_samples; + const float frac = delay_samples - idelay_samples; + + for (nframes_t i = 0; i < nframes; i++ ) + { + const long read_index = _write_index - idelay_samples; + + _buffer[_write_index++ & _buffer_mask] = buf[i]; + + const float read = interpolate_cubic (frac, + _buffer[(read_index-1) & _buffer_mask], + _buffer[read_index & _buffer_mask], + _buffer[(read_index+1) & _buffer_mask], + _buffer[(read_index+2) & _buffer_mask]); + + buf[i] = read; + } + } + + } +}; + +class ambisonic_panner +{ + /* last values */ + float _x, _y, _z; + + /* for stereo */ + float _xr, _yr; + + static inline void spherical_to_cartesian (float a, float e, float &x, float &y, float &z ) + { + a *= DEG2RAD; + e *= DEG2RAD; + + z = sinf(e); + const float ce = cosf(e); + x = ce * cosf(-a); + y = ce * sinf(-a); + } + +public: + + ambisonic_panner ( ) + { + _x = _y = _z = _xr = _yr = 1.0f; + } + + void + run_mono ( float *in, + float *out_w, float *out_x, float *out_y, float *out_z, + float a, float e, + nframes_t nframes ) + { + float x = _x; + float y = _y; + float z = _z; + + spherical_to_cartesian( a, e, _x, _y, _z ); + + const float c = 1.0f / (float)nframes; + + /* calculate increment for linear interpolation */ + const float dx = (_x - x) * c; + const float dy = (_y - y) * c; + const float dz = (_z - z) * c; + + while ( nframes-- ) + { + x += dx; + y += dy; + z += dz; + + const float t = *in++; + + *out_w++ = ONEOVERSQRT2 * t; + *out_x++ = x * t; + *out_y++ = y * t; + *out_z++ = z * t; + } + } + + void + run_stereo ( float *in_l, float *in_r, + float *out_w, float *out_x, float *out_y, float *out_z, + float a, float e, float w, + nframes_t nframes ) + { + float x = _x; + float y = _y; + float z = _z; + float xr = _xr; + float yr = _yr; + + w *= 0.5f; + + spherical_to_cartesian( a - w, e, _x, _y, _z ); + spherical_to_cartesian( a + w, e, _xr, _yr, _z ); + + const float c = 1.0f / (float)nframes; + + /* calculate increment for linear interpolation */ + const float dx = (_x - x) * c; + const float dy = (_y - y) * c; + const float dz = (_z - z) * c; + const float dxr = (_xr - xr) * c; + const float dyr = (_yr - yr) * c; + + while ( nframes-- ) + { + x += dx; + y += dy; + z += dz; + xr += dxr; + yr += dyr; + + const float L = *in_l++; + const float R = *in_r++; + + const float LR = L + R; + + *out_w++ = ONEOVERSQRT2 * LR; + *out_x++ = x * L + xr * R; + *out_y++ = y * L + yr * R; + *out_z++ = z * LR; + } + } +}; + + + +Spatializer_Module::Spatializer_Module ( ) : JACK_Module ( false ) +{ + is_default( false ); + + _panner = 0; + + { + Port p( this, Port::INPUT, Port::CONTROL, "Azimuth" ); + p.hints.type = Port::Hints::LINEAR; + p.hints.ranged = true; + p.hints.minimum = -180.0f; + p.hints.maximum = 180.0f; + p.hints.default_value = 0.0f; + + p.connect_to( new float ); + p.control_value( p.hints.default_value ); + + add_port( p ); + } + + { + Port p( this, Port::INPUT, Port::CONTROL, "Elevation" ); + p.hints.type = Port::Hints::LINEAR; + p.hints.ranged = true; + p.hints.minimum = -90.0f; + p.hints.maximum = 90.0f; + p.hints.default_value = 0.0f; + + p.connect_to( new float ); + p.control_value( p.hints.default_value ); + + add_port( p ); + } + + { + Port p( this, Port::INPUT, Port::CONTROL, "Radius" ); + p.hints.type = Port::Hints::LINEAR; + p.hints.ranged = true; + p.hints.minimum = 0.0f; + p.hints.maximum = max_distance; + p.hints.default_value = 1.0f; + + p.connect_to( new float ); + p.control_value( p.hints.default_value ); + + add_port( p ); + } + + + { + Port p( this, Port::INPUT, Port::CONTROL, "Highpass (Hz)" ); + p.hints.type = Port::Hints::LINEAR; + p.hints.ranged = true; + p.hints.minimum = 0.0f; + p.hints.maximum = 600.0f; + p.hints.default_value = 200.0f; + + p.connect_to( new float ); + p.control_value( p.hints.default_value ); + + add_port( p ); + } + + { + Port p( this, Port::INPUT, Port::CONTROL, "Width" ); + p.hints.type = Port::Hints::LINEAR; + p.hints.ranged = true; + p.hints.minimum = -90.0f; + p.hints.maximum = 90.0f; + p.hints.default_value = 90.0f; + p.hints.visible = false; + p.connect_to( new float ); + p.control_value( p.hints.default_value ); + + add_port( p ); + } + + log_create(); + + _panner = new ambisonic_panner(); + + labelsize(9); + + color( FL_DARK1 ); + + copy_label( "Spatializer" ); + align(FL_ALIGN_LEFT|FL_ALIGN_TOP|FL_ALIGN_INSIDE); + + gain_smoothing.sample_rate( sample_rate() ); + delay_smoothing.cutoff( 0.5f ); + delay_smoothing.sample_rate( sample_rate() ); +} + +Spatializer_Module::~Spatializer_Module ( ) +{ + configure_inputs(0); + delete _panner; + delete (float*)control_input[0].buffer(); + delete (float*)control_input[1].buffer(); + delete (float*)control_input[2].buffer(); + delete (float*)control_input[3].buffer(); + delete (float*)control_input[4].buffer(); +} + + + + +void +Spatializer_Module::handle_sample_rate_change ( nframes_t n ) +{ + gain_smoothing.sample_rate( n ); + delay_smoothing.sample_rate( n ); + + for ( unsigned int i = 0; i < audio_input.size(); i++ ) + { + _lowpass[i]->sample_rate( n ); + _highpass[i]->sample_rate( n ); + _delay[i]->sample_rate( n ); + } +} + +void +Spatializer_Module::draw ( void ) +{ + int W = 5; + + child(0)->size( w() - W, h() ); + Module::draw_box(x(),y(),w() - W,h()); + Module::draw_label(x() + 4,y(),w() - W,h()); + + Module *m = this; + + fl_color( fl_darker( FL_FOREGROUND_COLOR ) ); + + int spacing, offset; + + int ni = jack_output.size(); + + spacing = h() / ni; + offset = spacing / 2; + for ( int i = ni; i--; ) + { + int xi = offset + ( spacing * i ); + fl_rectf( m->x() + m->w() - W, m->y() + xi, W, 2 ); + } +} + +void +Spatializer_Module::process ( nframes_t nframes ) +{ + if ( !bypass() ) + { + float azimuth = control_input[0].control_value(); + float elevation = control_input[1].control_value(); + float radius = control_input[2].control_value(); + float highpass_freq = control_input[3].control_value(); + float width = control_input[4].control_value(); + + float delay_seconds = 0.0f; + + if ( radius > 1.0f ) + delay_seconds = ( radius - 1.0f ) / 340.29f; + + /* direct sound follows inverse square law */ + /* but it's just the inverse as far as SPL goes */ + + /* let's not go nuts... */ + if ( radius < 0.01f ) + radius = 0.01f; + + float gain = 1.0f / radius; + + float cutoff_frequency = gain * LOWPASS_FREQ; + + sample_t gainbuf[nframes]; + sample_t delaybuf[nframes]; + + bool use_gainbuf = gain_smoothing.apply( gainbuf, nframes, gain ); + bool use_delaybuf = delay_smoothing.apply( delaybuf, nframes, delay_seconds ); + + for ( unsigned int i = 0; i < audio_input.size(); i++ ) + { + sample_t *buf = (sample_t*) audio_input[i].buffer(); + + /* frequency effects */ + _highpass[i]->run_highpass( buf, highpass_freq, nframes ); + _lowpass[i]->run_lowpass( buf, cutoff_frequency, nframes ); + + /* send to late reverb */ + if ( i == 0 ) + buffer_copy( (sample_t*)jack_output[0].buffer(nframes), buf, nframes ); + else + buffer_mix( (sample_t*)jack_output[0].buffer(nframes), buf, nframes ); + + /* /\* FIXME: use smoothed value... *\/ */ + /* buffer_apply_gain( (sample_t*)jack_output[0].buffer(nframes), nframes, 1.0f / sqrt(D) ); */ + + if ( use_delaybuf ) + _delay[i]->run( buf, delaybuf, 0, nframes ); + else + _delay[i]->run( buf, 0, delay_seconds, nframes ); + } + + if ( audio_input.size() == 1 ) + { + _panner->run_mono( (sample_t*)audio_input[0].buffer(), + (sample_t*)audio_output[0].buffer(), + (sample_t*)audio_output[1].buffer(), + (sample_t*)audio_output[2].buffer(), + (sample_t*)audio_output[3].buffer(), + azimuth, + elevation, + nframes ); + } + else + { + _panner->run_stereo( (sample_t*)audio_input[0].buffer(), + (sample_t*)audio_input[1].buffer(), + (sample_t*)audio_output[0].buffer(), + (sample_t*)audio_output[1].buffer(), + (sample_t*)audio_output[2].buffer(), + (sample_t*)audio_output[3].buffer(), + azimuth, + elevation, + width, + nframes ); + } + + /* send to early reverb */ + for ( int i = 4; i--; ) + buffer_copy( (sample_t*)jack_output[1 + i].buffer(nframes), + (sample_t*)audio_output[0 + i].buffer(), + nframes ); + + /* gain effects */ + if ( use_gainbuf ) + { + for ( int i = 4; i--; ) + buffer_apply_gain_buffer( (sample_t*)audio_output[i].buffer(), gainbuf, nframes ); + } + else + { + for ( int i = 4; i--; ) + buffer_apply_gain( (sample_t*)audio_output[i].buffer(), nframes, gain ); + } + } +} + +bool +Spatializer_Module::configure_inputs ( int n ) +{ + output_connection_handle->show(); + output_connection_handle->tooltip( "Late Reverb" ); + output_connection2_handle->show(); + output_connection2_handle->tooltip( "Early Reverb" ); + + int on = audio_input.size(); + + if ( n > on ) + { + for ( int i = n - on; i--; ) + { + { filter *o = new filter(); + o->sample_rate( sample_rate() ); + _lowpass.push_back( o ); + } + + { + filter *o = new filter(); + o->sample_rate( sample_rate() ); + _highpass.push_back( o ); + } + { + delay *o = new delay( max_distance / 340.29f ); + o->sample_rate( sample_rate() ); + _delay.push_back( o ); + } + + add_port( Port( this, Port::INPUT, Port::AUDIO ) ); + } + } + else if ( n < on ) + { + + for ( int i = on - n; i--; ) + { + delete _lowpass.back(); + _lowpass.pop_back(); + delete _highpass.back(); + _highpass.pop_back(); + delete _delay.back(); + _delay.pop_back(); + + audio_input.pop_back(); + } + } + + control_input[4].hints.visible = audio_input.size() == 2; + + + if ( n == 0 ) + { + remove_jack_outputs(); + audio_output.clear(); + audio_input.clear(); + } + else + { + if ( audio_output.size() != 4 ) + { + for ( int i = 0; i < 4; i++ ) + { + add_port( Port( this, Port::OUTPUT, Port::AUDIO ) ); + } + } + + if ( jack_output.size() != 5 ) + { + add_jack_output( "late reverb", 0 ); + add_jack_output( "early reverb", 0 ); + add_jack_output( "early reverb", 1 ); + add_jack_output( "early reverb", 2 ); + add_jack_output( "early reverb", 3 ); + } + } + + _connection_handle_outputs[0][0] = 0; + _connection_handle_outputs[0][1] = 1; + _connection_handle_outputs[1][0] = 1; + _connection_handle_outputs[1][1] = jack_output.size(); + + return true; +} diff --git a/mixer/src/Spatializer_Module.H b/mixer/src/Spatializer_Module.H new file mode 100644 index 0000000..3476b2c --- /dev/null +++ b/mixer/src/Spatializer_Module.H @@ -0,0 +1,62 @@ + +/*******************************************************************************/ +/* 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. */ +/*******************************************************************************/ + +#pragma once + +#include "JACK_Module.H" +#include "dsp.h" +#include + +class filter; +class delay; +class ambisonic_panner; +class Spatializer_Module : public JACK_Module +{ + Value_Smoothing_Filter gain_smoothing; + Value_Smoothing_Filter delay_smoothing; + + std::vector _lowpass; + std::vector _highpass; + std::vector _delay; + + ambisonic_panner *_panner; + +public: + + virtual const char *name ( void ) const { return "Spatializer"; } + + int can_support_inputs ( int n ) { return n > 0 && n < 3 ? 4 : -1; } + + virtual bool configure_inputs ( int n ); + + Spatializer_Module ( ); + virtual ~Spatializer_Module ( ); + + LOG_CREATE_FUNC( Spatializer_Module ); + + virtual void handle_sample_rate_change ( nframes_t n ); + + virtual void draw ( void ); + +protected: + + virtual void process ( nframes_t nframes ); + +}; + diff --git a/mixer/src/main.C b/mixer/src/main.C index b9acbc3..e3ae71f 100644 --- a/mixer/src/main.C +++ b/mixer/src/main.C @@ -43,6 +43,7 @@ /* for registration */ #include "Module.H" #include "Gain_Module.H" +#include "Spatializer_Module.H" #include "Plugin_Module.H" #include "JACK_Module.H" #include "Meter_Module.H" @@ -151,6 +152,7 @@ main ( int argc, char **argv ) LOG_REGISTER_CREATE( Chain ); LOG_REGISTER_CREATE( Plugin_Module ); LOG_REGISTER_CREATE( Gain_Module ); + LOG_REGISTER_CREATE( Spatializer_Module ); LOG_REGISTER_CREATE( Meter_Module ); LOG_REGISTER_CREATE( JACK_Module ); LOG_REGISTER_CREATE( Mono_Pan_Module ); diff --git a/mixer/wscript b/mixer/wscript index e969762..eb56ece 100644 --- a/mixer/wscript +++ b/mixer/wscript @@ -48,6 +48,7 @@ src/Controller_Module.C src/DPM.C src/Engine/Engine.C src/Gain_Module.C +src/Spatializer_Module.C src/JACK_Module.C src/AUX_Module.C src/LADSPAInfo.C @@ -93,8 +94,11 @@ src/Spatialization_Console.C cwd=start_dir, relative_trick=True) bld.install_as('${DATADIR}/pixmaps/' + APPNAME + '/icon-256x256.png', 'icons/hicolor/256x256/apps/' + APPNAME + '.png') - bld.install_as('${DATADIR}/pixmaps/' + APPNAME + '/panner-512x125.png', 'pixmaps/panner-512x512.png') - bld.install_as('${DATADIR}/pixmaps/' + APPNAME + '/panner-92x125.png', 'pixmaps/panner-92x92.png') + + start_dir = bld.path.find_dir( 'pixmaps' ) + + bld.install_files('${DATADIR}/pixmaps/' + APPNAME + '/', start_dir.ant_glob('*.png'), + cwd=start_dir, relative_trick=True) bld.install_files( '/'.join( [ '${DATADIR}/doc', APPNAME ] ), bld.path.ant_glob( 'doc/*.html doc/*.png' ) ) diff --git a/nonlib/JACK/Port.C b/nonlib/JACK/Port.C index 94ee1f5..f38a4d8 100644 --- a/nonlib/JACK/Port.C +++ b/nonlib/JACK/Port.C @@ -58,7 +58,7 @@ namespace JACK _client = client; _port = port; _name = strdup( jack_port_name( port ) ); - _direction = jack_port_flags( _port ) == JackPortIsOutput ? Output : Input; + _direction = ( jack_port_flags( _port ) & JackPortIsOutput ) ? Output : Input; const char *type = jack_port_type( _port ); _type = Audio; @@ -169,10 +169,19 @@ namespace JACK bool Port::activate ( void ) { + int flags = 0; + + if ( _direction == Output ) + flags |= JackPortIsOutput; + else + flags |= JackPortIsInput; + + if ( _terminal ) + flags |= JackPortIsTerminal; + _port = jack_port_register( _client->jack_client(), _name, _type == Audio ? JACK_DEFAULT_AUDIO_TYPE : JACK_DEFAULT_MIDI_TYPE, - ( _direction == Output ? JackPortIsOutput : JackPortIsInput ) | - ( _terminal ? JackPortIsTerminal : 0 ), + flags, 0 ); if ( ! _port ) diff --git a/nonlib/dsp.C b/nonlib/dsp.C index fd9bbd3..037df51 100644 --- a/nonlib/dsp.C +++ b/nonlib/dsp.C @@ -134,10 +134,10 @@ Value_Smoothing_Filter::sample_rate ( nframes_t n ) const float FS = n; const float T = 0.05f; - w = 10.0f / (FS * T); + w = _cutoff / (FS * T); } -void +bool Value_Smoothing_Filter::apply( sample_t *dst, nframes_t nframes, float gt ) { const float a = 0.07f; @@ -148,6 +148,9 @@ Value_Smoothing_Filter::apply( sample_t *dst, nframes_t nframes, float gt ) float g1 = this->g1; float g2 = this->g2; + if ( target_reached(gt) ) + return false; + for (nframes_t i = 0; i < nframes; i++) { g1 += w * (gm - g1 - a * g2); @@ -160,4 +163,6 @@ Value_Smoothing_Filter::apply( sample_t *dst, nframes_t nframes, float gt ) this->g1 = g1; this->g2 = g2; + + return true; } diff --git a/nonlib/dsp.h b/nonlib/dsp.h index 746c0e0..19ebc7f 100644 --- a/nonlib/dsp.h +++ b/nonlib/dsp.h @@ -38,25 +38,38 @@ void buffer_copy_and_apply_gain ( sample_t *dst, const sample_t *src, nframes_t class Value_Smoothing_Filter { float w, g1, g2; - + + float _cutoff; public: Value_Smoothing_Filter ( ) { g1 = g2 = 0; + _cutoff = 10.0f; } + void cutoff ( float v ) { _cutoff = v; } + void sample_rate ( nframes_t v ); inline bool target_reached ( float gt ) const { return gt == g2; } - void apply ( sample_t *dst, nframes_t nframes, float target ); + bool apply ( sample_t *dst, nframes_t nframes, float target ); }; +static inline float interpolate_cubic ( const float fr, const float inm1, const float in, const float inp1, const float inp2) +{ + return in + 0.5f * fr * (inp1 - inm1 + + fr * (4.0f * inp1 + 2.0f * inm1 - 5.0f * in - inp2 + + fr * (3.0f * (in - inp1) - inm1 + inp2))); +} // from SWH plugins. // Convert a value in dB's to a coefficent #define DB_CO(g) ((g) > -90.0f ? powf(10.0f, (g) * 0.05f) : 0.0f) #define CO_DB(v) (20.0f * log10f(v)) + +#define DEG2RAD 0.01745329251f +#define ONEOVERSQRT2 0.70710678118f