From eacbcc173dceac0e2a33731a1fd1fb51c5e0e7cf Mon Sep 17 00:00:00 2001 From: Jonathan Moore Liles Date: Thu, 24 Dec 2009 18:59:39 -0600 Subject: [PATCH] Implement Mixer. --- Makefile | 12 +- Mixer/Chain.C | 583 +++++++++++++++++++++++ Mixer/Chain.H | 104 +++++ Mixer/Controller_Module.C | 259 +++++++++++ Mixer/Controller_Module.H | 81 ++++ Mixer/DPM.C | 179 ++++++++ Mixer/DPM.H | 81 ++++ Mixer/Engine/Engine.C | 147 ++++++ Mixer/Engine/Engine.H | 66 +++ Mixer/Gain_Module.C | 98 ++++ Mixer/Gain_Module.H | 40 ++ Mixer/JACK_Module.C | 187 ++++++++ Mixer/JACK_Module.H | 58 +++ Mixer/LADSPAInfo.C | 791 ++++++++++++++++++++++++++++++++ Mixer/LADSPAInfo.h | 199 ++++++++ Mixer/Meter.H | 138 ++++++ Mixer/Meter_Indicator_Module.C | 180 ++++++++ Mixer/Meter_Indicator_Module.H | 75 +++ Mixer/Meter_Module.C | 201 ++++++++ Mixer/Meter_Module.H | 51 ++ Mixer/Mixer.C | 230 ++++++++++ Mixer/Mixer.H | 63 +++ Mixer/Mixer_Strip.C | 471 +++++++++++++++++++ Mixer/Mixer_Strip.H | 100 ++++ Mixer/Module.C | 169 +++++++ Mixer/Module.H | 321 +++++++++++++ Mixer/Module_Parameter_Editor.C | 293 ++++++++++++ Mixer/Module_Parameter_Editor.H | 62 +++ Mixer/Panner.C | 283 ++++++++++++ Mixer/Panner.H | 172 +++++++ Mixer/Plugin_Module.C | 577 +++++++++++++++++++++++ Mixer/Plugin_Module.H | 114 +++++ Mixer/main.C | 114 +++++ Mixer/makefile.inc | 21 + configure | 1 + 35 files changed, 6516 insertions(+), 5 deletions(-) create mode 100644 Mixer/Chain.C create mode 100644 Mixer/Chain.H create mode 100644 Mixer/Controller_Module.C create mode 100644 Mixer/Controller_Module.H create mode 100644 Mixer/DPM.C create mode 100644 Mixer/DPM.H create mode 100644 Mixer/Engine/Engine.C create mode 100644 Mixer/Engine/Engine.H create mode 100644 Mixer/Gain_Module.C create mode 100644 Mixer/Gain_Module.H create mode 100644 Mixer/JACK_Module.C create mode 100644 Mixer/JACK_Module.H create mode 100644 Mixer/LADSPAInfo.C create mode 100644 Mixer/LADSPAInfo.h create mode 100644 Mixer/Meter.H create mode 100644 Mixer/Meter_Indicator_Module.C create mode 100644 Mixer/Meter_Indicator_Module.H create mode 100644 Mixer/Meter_Module.C create mode 100644 Mixer/Meter_Module.H create mode 100644 Mixer/Mixer.C create mode 100644 Mixer/Mixer.H create mode 100644 Mixer/Mixer_Strip.C create mode 100644 Mixer/Mixer_Strip.H create mode 100644 Mixer/Module.C create mode 100644 Mixer/Module.H create mode 100644 Mixer/Module_Parameter_Editor.C create mode 100644 Mixer/Module_Parameter_Editor.H create mode 100644 Mixer/Panner.C create mode 100644 Mixer/Panner.H create mode 100644 Mixer/Plugin_Module.C create mode 100644 Mixer/Plugin_Module.H create mode 100644 Mixer/main.C create mode 100644 Mixer/makefile.inc diff --git a/Makefile b/Makefile index 1445b29..119872b 100644 --- a/Makefile +++ b/Makefile @@ -96,9 +96,10 @@ DONE := $(BOLD)$(GREEN)done$(SGR0) include FL/makefile.inc include nonlib/makefile.inc include Timeline/makefile.inc +include Mixer/makefile.inc -SRCS:=$(FL_SRCS) $(nonlib_SRCS) $(Timeline_SRCS) -OBJS:=$(FL_OBJS) $(nonlib_OBJS) $(Timeline_OBJS) +SRCS:=$(FL_SRCS) $(nonlib_SRCS) $(Timeline_SRCS) $(Mixer_SRCS) +OBJS:=$(FL_OBJS) $(nonlib_OBJS) $(Timeline_OBJS) $(Mixer_OBJS) # FIXME: isn't there a better way? $(OBJS): .config Makefile @@ -110,13 +111,14 @@ TAGS: $(SRCS) ifneq ($(CALCULATING),yes) @ echo -n Calculating dependencies... @ makedepend -f- -- $(CXXFLAGS) $(INCLUDES) -- $(SRCS) 2>/dev/null > .deps && echo $(DONE) + # @ gcc -M $(CXXFLAGS) $(INCLUDES) $(SRCS) > .deps && echo $(DONE) endif install: all @ echo -n "Installing..." @ install Timeline/timeline $(prefix)/bin/non-daw -# @ install Mixer/mixer $(prefix)/bin/non-mixer + @ install Mixer/mixer $(prefix)/bin/non-mixer @ mkdir -p $(SYSTEM_PATH) @ mkdir -p $(PIXMAP_PATH) @ cp pixmaps/*.png $(PIXMAP_PATH) @@ -125,7 +127,7 @@ install: all ifneq ($(USE_DEBUG),yes) @ echo -n "Stripping..." @ strip $(prefix)/bin/non-daw -# @ strip $(prefix)/bin/non-mixer + @ strip $(prefix)/bin/non-mixer @ echo "$(DONE)" endif @@ -134,7 +136,7 @@ clean_deps: .PHONEY: clean config depend clean_deps -clean: FL_clean nonlib_clean Timeline_clean +clean: FL_clean nonlib_clean Timeline_clean Mixer_clean dist: git archive --prefix=non-daw-$(VERSION)/ v$(VERSION) | bzip2 > non-daw-$(VERSION).tar.bz2 diff --git a/Mixer/Chain.C b/Mixer/Chain.C new file mode 100644 index 0000000..21a6afb --- /dev/null +++ b/Mixer/Chain.C @@ -0,0 +1,583 @@ + +/*******************************************************************************/ +/* Copyright (C) 2009 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. */ +/*******************************************************************************/ + +/* Filter chain. This would all be much simpler if we chose not to + * allow non 1:1 plugins to be mixed in a single chain... + * + * Supporting the mixture requires duplicating some inputs (to satisfy + * stereo input plugins reading mono outputs) and duplicating some + * plugins (to satisfy mono input plugins reading stereo outputs). + * + * Basically, what this means is that the intermediate number of + * buffers need not have any relation to the starting and ending + * buffer count. (Picture an ambisonic panner going into an ambisonic + * decoder (1:6:2). + * + * The chain will allocate enough buffers to hold data from the + * maximum number of channels used by a contained module. + * + * The process thread goes as follows: + * + * 1. Copy inputs to chain buffers. + * + * 2. process() each module in turn (reusing buffers in-place) (inputs + * will be copied or plugins duplicated as necessary) + * + * 3. Copy chain buffers to outputs. + * + * For chains where the number of channels never exceeds the maximum + * of the number of inputs and outputs, the first copy can be + * optimized out. + */ + +#include "Chain.H" + +#include "Module.H" +#include "Meter_Module.H" +#include "JACK_Module.H" +#include "Gain_Module.H" +#include "Plugin_Module.H" + +#include +#include +#include + +#include +#include "util/debug.h" + +#include + +#include + +#include "Engine/Engine.H" +#include +#include "FL/Fl_Flowpack.H" +#include "FL/Fl_Scroll.H" +#include + +Chain::Chain ( int X, int Y, int W, int H, const char *L ) : + Fl_Group( X, Y, W, H, L) +{ + _outs = 1; + _ins = 1; + + _configure_outputs_callback = NULL; + + _name = NULL; + + { Fl_Tabs *o = tabs = new Fl_Tabs( X, Y, W, H ); + { Fl_Group *o = new Fl_Group( X, Y + 24, W, H - 24, "Chain" ); + o->box( FL_FLAT_BOX ); + o->labelsize( 9 ); + { Fl_Pack *o = modules_pack = new Fl_Pack( X, Y + 24, W, H - 24 ); + o->type( Fl_Pack::VERTICAL ); + o->spacing( 10 ); + o->end(); + } + o->end(); + } + { Fl_Group *o = new Fl_Group( X, Y + 24, W, H - 24, "Controls" ); + o->labelsize( 9 ); + o->hide(); + { Fl_Scroll *o = new Fl_Scroll( X, Y + 24, W, H - 24 ); + o->type( Fl_Scroll::VERTICAL ); + { Fl_Flowpack *o = controls_pack = new Fl_Flowpack( X, Y + 24, W, H - 24 ); + o->hspacing( 10 ); + o->vspacing( 10 ); +// o->box( FL_FLAT_BOX ); +// o->color( FL_RED ); + o->end(); + Fl_Group::current()->resizable( o ); + } + o->end(); + Fl_Group::current()->resizable( o ); + } + o->end(); + Fl_Group::current()->resizable( o ); + } + o->end(); + Fl_Group::current()->resizable( o ); + } + + end(); +} + +/* Fill this chain with JACK I/O, Gain, and Meter modules. */ +void +Chain::initialize_with_default ( void ) +{ + + { + JACK_Module *jm = new JACK_Module( 50, 50, "JACK" ); + jm->chain( this ); + jm->configure_outputs( 1 ); + + jm->initialize(); + jm->color( FL_BLACK ); + insert( NULL, jm ); + } + + { + JACK_Module *m = new JACK_Module( 50, 50, "JACK" ); + m->chain( this ); + m->initialize(); + m->color( FL_BLACK ); + insert( NULL, m ); + } +} + + +void Chain::cb_handle(Fl_Widget* o) { + /* if ( o == head_button ) */ + /* { */ + /* Module *m = Module::pick_plugin(); */ + + /* insert_before( (Module*)modules_pack->child( 0 ), m ); */ + /* } */ + /* else if ( o == tail_button ) */ + /* { */ + /* Module *m = Module::pick_plugin(); */ + /* insert_before( 0, m ); */ + /* } */ +} + +void Chain::cb_handle(Fl_Widget* o, void* v) { + ((Chain*)(v))->cb_handle(o); +} + + +/* remove a module from the chain. this isn't guaranteed to succeed, + * because removing the module might result in an invalid routing */ +void +Chain::remove ( Module *m ) +{ + int i = modules_pack->find( m ); + + int ins = 0; + + if ( i != 0 ) + ins = module( i - 1 )->noutputs(); + + if ( ! can_configure_outputs( m, ins ) ) + { + fl_alert( "Can't remove module at this point because the resultant chain is invalid" ); + } + + modules_pack->remove( m ); + + configure_ports(); +} + +/* determine number of output ports, signal if changed. */ +void +Chain::configure_ports ( void ) +{ + int old_outs = outs(); + int nouts = 0; + + engine->lock(); + + for ( int i = 0; i < modules(); ++i ) + { + module( i )->configure_inputs( nouts ); + nouts = module( i )->noutputs(); + } + + outs( nouts ); + + int req_buffers = required_buffers(); + + if ( outs() != old_outs ) + { + if ( configure_outputs_callback() ) + configure_outputs_callback()( this, _configure_outputs_userdata ); + } + + DMESSAGE( "required_buffers = %i", req_buffers ); + + if ( port.size() != req_buffers ) + { + for ( unsigned int i = port.size(); i--; ) + delete[] port[i].buffer(); + port.clear(); + + for ( unsigned int i = 0; i < req_buffers; ++i ) + { + Module::Port p( NULL, Module::Port::OUTPUT, Module::Port::AUDIO ); + p.connect_to( new sample_t[engine->nframes()] ); + port.push_back( p ); + } + } + + build_process_queue(); + + engine->unlock(); + + parent()->redraw(); +} + +/* calculate the minimum number of buffers required to satisfy this chain */ +int +Chain::required_buffers ( void ) +{ + int buffers = 0; + int outs = 0; + + for ( int i = 0; i < modules(); ++i ) + { + outs = module( i )->can_support_inputs( outs ); + + if ( outs > buffers ) + buffers = outs; + } + + return buffers; +} + +/* called by a module when it wants to alter the number of its + * outputs. Also used to test for chain validity when inserting / + * removing modules */ +bool +Chain::can_configure_outputs ( Module *m, int n ) const +{ + /* start at the requesting module */ + + int outs = n; + + int i = modules_pack->find( m ); + + if ( modules() - 1 == i ) + /* last module */ + return true; + + for ( i++ ; i < modules(); ++i ) + { + outs = module( i )->can_support_inputs( outs ); + + if ( outs < 0 ) + return false; + } + + return true; +} + +/* return true if this chain can be converted to support /n/ input channels */ +bool +Chain::can_support_input_channels ( int n ) +{ + /* FIXME: implement */ + return true; +} + +/* rename chain... we have to let our modules know our name has + * changed so they can take the appropriate action (in particular the + * JACK module). */ +void +Chain::name ( const char *name ) +{ + _name = name; + + for ( int i = 0; i < modules(); ++i ) + module( i )->handle_chain_name_changed(); +} + + + + + +#include "FL/menu_popup.H" + +bool +Chain::insert ( Module *m, Module *n ) +{ + + engine->lock(); + + if ( !m ) + { + if ( modules() == 0 && n->can_support_inputs( 0 ) >= 0 ) + { + n->configure_inputs( 0 ); + modules_pack->add( n ); + n->chain( this ); + } + else if ( n->can_support_inputs( module( modules() - 1 )->noutputs() ) >= 0 ) + { + n->configure_inputs( module( modules() - 1 )->noutputs() ); + modules_pack->add( n ); + n->chain( this ); + } + else + goto err; + } + else + { + int i = modules_pack->find( m ); + + if ( 0 == i ) + { + /* inserting to head of chain*/ + if ( n->can_support_inputs( 0 ) >= 0 ) + n->configure_inputs( 0 ); + else + goto err; + } + else + { + if ( n->can_support_inputs( module( i - 1 )->noutputs() ) >= 0 ) + { + n->configure_inputs( module( i - 1 )->noutputs() ); + + m->configure_inputs( n->noutputs() ); + + for ( int j = i + 1; j < modules(); ++j ) + module( j )->configure_inputs( module( j - 1 )->noutputs() ); + } + else + goto err; + } + + modules_pack->insert( *n, i ); + n->chain( this ); + } + + DMESSAGE( "Module has %i:%i audio and %i:%i control ports", + n->ninputs(), + n->noutputs(), + n->ncontrol_inputs(), + n->ncontrol_outputs() ); + + configure_ports(); + + engine->unlock(); + + return true; + +err: + + engine->unlock(); + + return false; +} + +/* add a control to the control strip. Assumed to already be connected! */ +void +Chain::add_control ( Module *m ) +{ + controls_pack->add( m ); +} + +void +Chain::draw_connections ( Module *m ) +{ + int spacing; + int offset; + + Fl_Color c =fl_color_average( FL_WHITE, FL_YELLOW, 0.50 ); + fl_color( c ); + + if ( m->ninputs() ) + { + spacing = w() / m->ninputs(); + offset = spacing / 2; + + for ( int i = m->ninputs(); i--; ) + fl_rectf( m->x() + offset + ( spacing * i ), m->y() - 5, 2, 5 ); + } + + fl_color( fl_darker( c ) ); + + if ( m->noutputs() ) + { + spacing = w() / m->noutputs(); + offset = spacing / 2; + for ( int i = m->noutputs(); i--; ) + fl_rectf( m->x() + offset + ( spacing * i ), m->y() + m->h(), 2, 5 ); + } +} + +void +Chain::add_to_process_queue ( Module *m ) +{ + for ( std::list::const_iterator i = process_queue.begin(); i != process_queue.end(); ++i ) + if ( m == *i ) + return; + + process_queue.push_back( m ); +} + +/* run any time the internal connection graph might have + * changed... Tells the process thread what order modules need to be + * run in. */ +void +Chain::build_process_queue ( void ) +{ + process_queue.clear(); + + for ( int i = 0; i < modules(); ++i ) + { + Module *m = (Module*)module( i ); + + /* controllers */ + for ( unsigned int j = 0; j < m->control_input.size(); ++j ) + { + if ( m->control_input[j].connected() ) + { + add_to_process_queue( m->control_input[j].connected_port()->module() ); + } + } + + /* audio modules */ + add_to_process_queue( m ); + + /* indicators */ + for ( unsigned int j = 0; j < m->control_output.size(); ++j ) + { + if ( m->control_output[j].connected() ) + { + add_to_process_queue( m->control_output[j].connected_port()->module() ); + } + } + } + + /* connect all the ports to the buffers */ + for ( int i = 0; i < modules(); ++i ) + { + Module *m = module( i ); + for ( unsigned int j = 0; j < m->audio_input.size(); ++j ) + { + m->audio_input[j].connect_to( &port[j] ); + } + for ( unsigned int j = 0; j < m->audio_output.size(); ++j ) + { + m->audio_output[j].connect_to( &port[j] ); + } + } + + DMESSAGE( "Process queue looks like:" ); + + for ( std::list::const_iterator i = process_queue.begin(); i != process_queue.end(); ++i ) + { + const Module* m = *i; + + if ( m->audio_input.size() || m->audio_output.size() ) + DMESSAGE( "\t%s", (*i)->name() ); + else if ( m->control_output.size() ) + DMESSAGE( "\t%s -->", (*i)->name() ); + else if ( m->control_input.size() ) + DMESSAGE( "\t%s <--", (*i)->name() ); + } +} + +void +Chain::draw ( void ) +{ + Fl_Group::draw(); + + if ( 0 == strcmp( "Chain", tabs->value()->label() ) ) + for ( int i = 0; i < modules(); ++i ) + draw_connections( module( i ) ); +} + +void +Chain::resize ( int X, int Y, int W, int H ) +{ + Fl_Group::resize( X, Y, W, H ); + +/* this won't naturally resize because it's inside of an Fl_Scroll... */ + controls_pack->size( W, controls_pack->h() ); +} + +#include "FL/test_press.H" + +int +Chain::handle ( int m ) +{ + switch ( m ) + { + case FL_PUSH: + { + if ( Fl::belowmouse() != this ) + { + Module *m = NULL; + + for ( int i = 0; i < modules(); ++i ) + if ( Fl::event_inside( module( i ) ) ) + { + m = module( i ); + break; + } + + if ( m ) + { + if ( test_press( FL_BUTTON3 | FL_CTRL ) ) + { + if ( FL_BLACK == m->color() ) + { + /* FIXME: hack */ + fl_alert( "Cannot delete this module." ); + } + else + { + remove( m ); + delete m; + redraw(); + } + return 1; + } + else if ( test_press( FL_BUTTON1 | FL_SHIFT ) ) + { + Module *mod = (Module*)Plugin_Module::pick_plugin(); + if ( mod ) + { + if ( ! insert( m, mod ) ) + fl_alert( "Cannot insert this module at this point in the chain" ); + redraw(); + } + return 1; + } + else if ( test_press( FL_BUTTON1 | FL_CTRL ) ) + { + if ( m->active() ) + m->deactivate(); + else + m->activate(); + return 1; + } + } + } + break; + } + } + + return Fl_Group::handle( m ); +} + +void +Chain::process ( nframes_t nframes ) +{ + for ( std::list::const_iterator i = process_queue.begin(); i != process_queue.end(); ++i ) + { + Module *m = *i; + + m->nframes( nframes ); + if ( m->active() ) + m->process(); + } +} diff --git a/Mixer/Chain.H b/Mixer/Chain.H new file mode 100644 index 0000000..3713bd2 --- /dev/null +++ b/Mixer/Chain.H @@ -0,0 +1,104 @@ + +/*******************************************************************************/ +/* Copyright (C) 2009 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 + +#include "Module.H" +#include "JACK/Port.H" +#include +#include + +class Fl_Flowpack; +class Fl_Tabs; + +class Chain : public Fl_Group { + + Fl_Pack *modules_pack; + Fl_Flowpack *controls_pack; + Fl_Tabs *tabs; + + void cb_handle(Fl_Widget*); + static void cb_handle(Fl_Widget*, void*); + + int _ins; + int _outs; + +// sample_t **_buffer; +// int _nbuffers; + + Fl_Callback *_configure_outputs_callback; + void *_configure_outputs_userdata; + + const char *_name; + + void draw_connections ( Module *m ); + + std::list process_queue; + + void build_process_queue ( void ); + void add_to_process_queue ( Module *m ); + +public: + + std::vector port; + + const char *name ( void ) const { return _name; } + void name ( const char *name ); + + void configure_ports ( void ); + int required_buffers ( void ); + + Chain ( int X, int Y, int W, int H, const char *L = 0 ); + + bool can_support_input_channels ( int n ); + + void ins ( int i ) { _ins = i; } + void outs ( int i ) { _outs = i; } + int ins ( void ) const { return _ins; } + int outs ( void ) const { return _outs; } + + int modules ( void ) const { return modules_pack->children(); } + Module *module ( int n ) const { return (Module*)modules_pack->child( n ); } + void remove ( Module *m ); + bool insert ( Module *m, Module *n ); + void add_control ( Module *m ); + + void initialize_with_default ( void ); + + bool can_configure_outputs ( Module *m, int n ) const; + + void configure_outputs_callback ( Fl_Callback *cb, void *v ) + { + _configure_outputs_callback = cb; + _configure_outputs_userdata = v; + } + + Fl_Callback * configure_outputs_callback ( void ) const { return _configure_outputs_callback; } + + void process ( nframes_t ); + +protected: + + int handle ( int m ); + void draw ( void ); + void resize ( int X, int Y, int W, int H ); + +}; diff --git a/Mixer/Controller_Module.C b/Mixer/Controller_Module.C new file mode 100644 index 0000000..ae196b3 --- /dev/null +++ b/Mixer/Controller_Module.C @@ -0,0 +1,259 @@ + +/*******************************************************************************/ +/* Copyright (C) 2009 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 "Controller_Module.H" + +#include +#include +#include +#include +#include "FL/Fl_Arc_Dial.H" +#include "FL/Fl_Light_Button.H" +#include "FL/Boxtypes.H" +#include +#include "FL/Fl_Labelpad_Group.H" +#include +#include "Engine/Engine.H" +#include "Chain.H" + + + +const float CONTROL_UPDATE_FREQ = 0.1f; + + + +Controller_Module::Controller_Module ( int W, int H, const char *L ) + : Module ( W, 100, L ) +{ +// label( "" ); + box( FL_NO_BOX ); + + _pad = true; + control = 0; + control_value =0.0f; + add_port( Port( this, Port::OUTPUT, Port::CONTROL ) ); + + mode( GUI ); +// mode( CV ); +// configure_inputs( 1 ); + + end(); + + Fl::add_timeout( CONTROL_UPDATE_FREQ, update_cb, this ); +} + +Controller_Module::~Controller_Module ( ) +{ + +} + + + +void +Controller_Module::update_cb ( void *v ) +{ + ((Controller_Module*)v)->update_cb(); +} + +void +Controller_Module::update_cb ( void ) +{ + Fl::repeat_timeout( CONTROL_UPDATE_FREQ, update_cb, this ); + + if ( control && control_output[0].connected() ) + control->value(control_value); +} + +void +Controller_Module::cb_handle ( Fl_Widget *w, void *v ) +{ + ((Controller_Module*)v)->cb_handle( w ); +} + +void +Controller_Module::cb_handle ( Fl_Widget *w ) +{ + control_value = ((Fl_Valuator*)w)->value(); + if ( control_output[0].connected() ) + { + control_output[0].control_value( control_value ); + Port *p = control_output[0].connected_port(); + Module *m = p->module(); + + m->handle_control_changed( p ); + } +} + +void +Controller_Module::connect_to ( Port *p ) +{ + control_output[0].connect_to( p ); + + if( mode() == CV ) + { + engine->lock(); + { + char name[256]; + snprintf( name, sizeof( name ), "%s-CV", p->name() ); + + JACK::Port po( engine->client(), JACK::Port::Input, chain()->name(), 0, name ); + + if ( po.valid() ) + { + jack_input.push_back( po ); + } + } + engine->unlock(); + } + + Fl_Widget *w; + + if ( p->hints.type == Module::Port::Hints::BOOLEAN ) + { + Fl_Light_Button *o = new Fl_Light_Button( 0, 0, 40, 40, p->name() ); + w = o; + o->value( p->control_value() ); + + } + else if ( p->hints.type == Module::Port::Hints::INTEGER ) + { + + Fl_Counter *o = new Fl_Counter(0, 0, 58, 24, p->name() ); + control = o; + w = o; + + o->type(1); + o->step(1); + + if ( p->hints.ranged ) + { + o->minimum( p->hints.minimum ); + o->maximum( p->hints.maximum ); + } + + o->value( p->control_value() ); + } + else if ( p->hints.type == Module::Port::Hints::LOGARITHMIC ) + { + Fl_Value_Slider *o = new Fl_Value_Slider(0, 0, 30, 250, p->name() ); + control = o; + w = o; + + o->type(4); + o->color(FL_GRAY0); + o->selection_color((Fl_Color)1); + o->minimum(1.5); + o->maximum(0); + o->step(0.01); + o->value(1); + o->textsize(14); + +// o->type( FL_VERTICAL ); +// o->type(1); + + if ( p->hints.ranged ) + { + o->minimum( p->hints.maximum ); + o->maximum( p->hints.minimum ); + } + + o->value( p->control_value() ); + } + else + { + { Fl_Arc_Dial *o = new Fl_Arc_Dial( 0, 0, 40, 40, p->name() ); + w = o; + control = o; + if ( p->hints.ranged ) + { + o->minimum( p->hints.minimum ); + o->maximum( p->hints.maximum ); + } + + o->box( FL_BURNISHED_OVAL_BOX ); +// o->box( FL_OVAL_BOX ); +// o->type( FL_FILL_DIAL ); + o->color( fl_darker( fl_darker( FL_GRAY ) ) ); + o->selection_color( FL_WHITE ); + o->value( p->control_value() ); + + } + + } + + control_value = p->control_value(); + + w->align(FL_ALIGN_TOP); + w->labelsize( 10 ); + w->callback( cb_handle, this ); + + if ( _pad ) + { + Fl_Labelpad_Group *flg = new Fl_Labelpad_Group( w ); + size( flg->w(), flg->h() ); + add( flg ); + } + else + { + w->resize( x(), y(), this->w(), h() ); + add( w ); + resizable( w ); + } +} + +int +Controller_Module::handle ( int m ) +{ + return Fl_Group::handle( m ); +} + + + +void +Controller_Module::process ( void ) +{ + if ( control_output[0].connected() ) + { + float f = control_value; + + if ( mode() == CV ) + { + f = *((float*)jack_input[0].buffer( engine->nframes() )); + + const Port *p = control_output[0].connected_port(); + + if (p->hints.ranged ) + { + // scale value to range. + // we assume that CV values are between 0 and 1 + + float scale = p->hints.maximum - p->hints.minimum; + float offset = p->hints.minimum; + + f = ( f * scale ) + offset; + } + } +// else +// f = *((float*)control_output[0].buffer()); + + *((float*)control_output[0].buffer()) = f; + + control_value = f; + } +} diff --git a/Mixer/Controller_Module.H b/Mixer/Controller_Module.H new file mode 100644 index 0000000..2b18d23 --- /dev/null +++ b/Mixer/Controller_Module.H @@ -0,0 +1,81 @@ + +/*******************************************************************************/ +/* Copyright (C) 2009 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 "Module.H" +#include +#include "JACK/Port.H" + +class Fl_Valuator; + +class Controller_Module : public Module +{ + + static void update_cb ( void *v ); + void update_cb ( void ); + + bool _pad; + + volatile float control_value; + +public: + + enum Mode { GUI, CV, OSC, MIDI }; + + Mode mode ( void ) const { return _mode; } + void mode ( Mode v ) { _mode = v; } + + Controller_Module ( int W, int H, const char *L=0 ); + virtual ~Controller_Module ( ); + + const char *name ( void ) const { return "Controller"; } + + int can_support_inputs ( int n ) { return 0; } + bool configure_inputs ( int n ) { return false; } + + void pad ( bool v ) { _pad = v; } + + static void cb_handle ( Fl_Widget *w, void *v ); + void cb_handle ( Fl_Widget *w ); + + void connect_to ( Port *p ); + +protected: + + + // virtual void draw ( void ); + virtual void process ( void ); + + virtual void draw ( void ) + { + draw_box(); + Fl_Group::draw(); + } + + virtual int handle ( int m ); + +private: + + std::vector jack_input; + Mode _mode; + + Fl_Valuator *control; + +}; diff --git a/Mixer/DPM.C b/Mixer/DPM.C new file mode 100644 index 0000000..979c979 --- /dev/null +++ b/Mixer/DPM.C @@ -0,0 +1,179 @@ + +/*******************************************************************************/ +/* Copyright (C) 2008 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. */ +/*******************************************************************************/ + +/* a Digital Peak Meter, either horizontal or vertical. Color is a + gradient from min_color() to max_color(). box() is used to draw the + individual 'lights'. division() controls how many 'lights' there + are. value() is volume in dBFS */ + +#include "DPM.H" + +/* we cache the gradient for (probably excessive) speed */ +float DPM::_dim; +Fl_Color DPM::_gradient[128] = { (Fl_Color)-1 }; +Fl_Color DPM::_dim_gradient[128]; + +#include +#include +#include + +#include +#include + +DPM::DPM ( int X, int Y, int W, int H, const char *L ) : + Meter( X, Y, W, H, L ) +{ + _last_drawn_hi_segment = 0; + + pixels_per_segment( 4 ); + + type( FL_VERTICAL ); + + resize( X, Y, W, H ); + + dim( 0.70f ); + + /* initialize gradients */ + if ( DPM::_gradient[ 0 ] == -1 ) + DPM::blend( FL_GREEN, FL_RED ); + + box( FL_ROUNDED_BOX ); +} + +/* which marks to draw beside meter */ +const int marks [] = { -70, -50, -40, -30, -20, -10, -3, 0, 4 }; + +void +DPM::draw_label ( void ) +{ + /* dirty hack */ + if ( parent()->child( 0 ) == this ) + { + fl_font( FL_TIMES, 8 ); + fl_color( FL_WHITE ); + /* draw marks */ + char pat[5]; + if ( type() == FL_HORIZONTAL ) + { + for ( int i = sizeof( marks ) / sizeof( marks[0] ); i-- ; ) + { + sprintf( pat, "%d", marks[ i ] ); + + int v = w() * deflection( (float)marks[ i ] ); + + fl_draw( pat, x() + v, (y() + h() + 8), 19, 8, (Fl_Align) (FL_ALIGN_RIGHT | FL_ALIGN_TOP) ); + } + + } + else + { + for ( int i = sizeof( marks ) / sizeof( marks[0] ); i-- ; ) + { + sprintf( pat, "%d", marks[ i ] ); + + int v = h() * deflection( (float)marks[ i ] ); + + fl_draw( pat, x() - 20, (y() + h() - 8) - v, 19, 8, (Fl_Align) (FL_ALIGN_RIGHT | FL_ALIGN_TOP) ); + } + } + } +} + +void +DPM::resize ( int X, int Y, int W, int H ) +{ + if ( type() == FL_HORIZONTAL ) + _segments = W / _pixels_per_segment; + else + _segments = H / _pixels_per_segment; + + Fl_Widget::resize( X, Y, W, H ); +} + +void +DPM::draw ( void ) +{ + int v = pos( value() ); + int pv = pos( peak() ); + + int bh = h() / _segments; + int bw = w() / _segments; + + if ( damage() == FL_DAMAGE_ALL ) + draw_label(); + + const int active = active_r(); + + int hi, lo; + + /* only draw as many segments as necessary */ + if ( damage() == FL_DAMAGE_USER1 ) + { + if ( _last_drawn_hi_segment > pos( value() ) ) + { + hi = _last_drawn_hi_segment; + lo = v; + } + else + { + hi = v; + lo = _last_drawn_hi_segment; + } + } + else + { + lo = 0; + hi = _segments; + } + + _last_drawn_hi_segment = hi; + + for ( int p = hi; p > lo; p-- ) + { + Fl_Color c = DPM::div_color( p ); + + if ( p > v && p != pv ) + c = dim_div_color( p ); + + if ( ! active ) + c = fl_inactive( c ); + + if ( _pixels_per_segment < 4 ) + { + if ( type() == FL_HORIZONTAL ) + fl_rectf( x() + (p * bw), y(), bw, h(), c ); + else + fl_rectf( x(), y() + h() - (p * bh), w(), bh, c ); + } + else + { + if ( type() == FL_HORIZONTAL ) + fl_draw_box( box(), x() + (p * bw), y(), bw, h(), c ); + else + fl_draw_box( box(), x(), y() + h() - (p * bh), w(), bh, c ); + } + +/* fl_color( c ); */ +/* fl_rectf( x(), y() + h() - (p * bh), w(), bh ); */ +/* fl_color( FL_BLACK ); */ +/* fl_rect( x(), y() + h() - (p * bh), w(), bh ); */ + + } + +} diff --git a/Mixer/DPM.H b/Mixer/DPM.H new file mode 100644 index 0000000..4d9bdf7 --- /dev/null +++ b/Mixer/DPM.H @@ -0,0 +1,81 @@ + +/*******************************************************************************/ +/* Copyright (C) 2008 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 // for FL_HORIZONTAL and FL_VERTICAL + +#include "Meter.H" + +class DPM : public Meter +{ + int _segments; + int _pixels_per_segment; + int _last_drawn_hi_segment; + + int pos ( float v ) + { + return deflection( v ) * _segments; + } + + static float _dim; + static Fl_Color _gradient[]; + static Fl_Color _dim_gradient[]; + + Fl_Color + div_color ( int i ) + { + return _gradient[ i * 127 / _segments ]; + } + + Fl_Color + dim_div_color ( int i ) + { + return _dim_gradient[ i * 127 / _segments ]; + } + + +protected: + + virtual void draw_label ( void ); + virtual void draw ( void ); + virtual void resize ( int, int, int, int ); + +public: + + DPM ( int X, int Y, int W, int H, const char *L = 0 ); + +// void value ( float v ) { if ( pos( v ) != pos( value() ) ) redraw(); Meter::value( v ) } + + void pixels_per_segment ( int v ) { _pixels_per_segment = v; } + + float dim ( void ) const { return _dim; } + void dim ( float v ) { _dim = v; redraw(); } + + static + void + blend ( Fl_Color min, Fl_Color max ) + { + for ( int i = 128; i-- ; ) + _gradient[ i ] = fl_color_average( max, min, i / (float)128 ); + + for ( int i = 128; i-- ; ) + _dim_gradient[ i ] = fl_color_average( FL_BLACK, _gradient[ i ], _dim ); + } +}; diff --git a/Mixer/Engine/Engine.C b/Mixer/Engine/Engine.C new file mode 100644 index 0000000..54959a2 --- /dev/null +++ b/Mixer/Engine/Engine.C @@ -0,0 +1,147 @@ + +/*******************************************************************************/ +/* Copyright (C) 2008 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 "Engine.H" + +#include "../Mixer.H" // for process() + +/* This is the home of the JACK process callback */ + +// #include "const.h" +#include "util/debug.h" +#include "util/Thread.H" + + + +Engine::Engine ( ) : _thread( "RT" ) +{ + _buffers_dropped = 0; +} + +Engine::~Engine ( ) +{ +} + + + +/*************/ +/* Callbacks */ +/*************/ + +/* THREAD: RT */ +/** This is the jack xrun callback */ +int +Engine::xrun ( void ) +{ + return 0; +} + +/* THREAD: RT */ +void +Engine::freewheel ( bool starting ) +{ + if ( starting ) + DMESSAGE( "entering freewheeling mode" ); + else + DMESSAGE( "leaving freewheeling mode" ); +} + +/* THREAD: RT (non-RT) */ +int +Engine::buffer_size ( nframes_t nframes ) +{ + // timeline->resize_buffers( nframes ); + + return 0; +} + +int Engine::sync ( jack_transport_state_t state, jack_position_t *pos ) +{ +} + +void +Engine::timebase ( jack_transport_state_t state, jack_nframes_t nframes, jack_position_t *pos, int new_pos ) +{ + + +} + +void +Engine::timebase ( jack_transport_state_t state, jack_nframes_t nframes, jack_position_t *pos ) +{ + +} + +/* THREAD: RT */ +int +Engine::process ( nframes_t nframes ) +{ + /* FIXME: wrong place for this */ + _thread.set( "RT" ); + + if ( freewheeling() ) + { +/* /\* freewheeling mode/export. We're actually running */ +/* non-RT. Assume that everything is quiescent, locking is */ +/* unecessary and do I/O synchronously *\/ */ +/* if ( timeline ) */ +/* timeline->process( nframes ); */ + +/* /\* because we're going faster than realtime. *\/ */ +/* timeline->wait_for_buffers(); */ + } + else + { + if ( ! trylock() ) + { + /* the data structures we need to access here (tracks and + * their ports, but not track contents) may be in an + * inconsistent state at the moment. Just punt and drop this + * buffer. */ + ++_buffers_dropped; + return 0; + } + + /* handle chicken/egg problem */ + if ( mixer ) + /* this will initiate the process() call graph for the various + * number and types of tracks, which will in turn send data out + * the appropriate ports. */ + mixer->process( nframes ); + + unlock(); + } + + return 0; +} + + +/* TRHEAD: RT */ +void +Engine::thread_init ( void ) +{ + _thread.set( "RT" ); +} + +/* THREAD: RT */ +void +Engine::shutdown ( void ) +{ +} diff --git a/Mixer/Engine/Engine.H b/Mixer/Engine/Engine.H new file mode 100644 index 0000000..0db6c5f --- /dev/null +++ b/Mixer/Engine/Engine.H @@ -0,0 +1,66 @@ + +/*******************************************************************************/ +/* Copyright (C) 2008 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 "util/Mutex.H" + +class Port; + +#include "JACK/Client.H" + +#include "Thread.H" + +class Engine : public JACK::Client, public Mutex +{ + Thread _thread; /* only used for thread checking */ + + int _buffers_dropped; /* buffers dropped because of locking */ +/* int _buffers_dropped; /\* buffers dropped because of locking *\/ */ + + void shutdown ( void ); + int process ( nframes_t nframes ); + int sync ( jack_transport_state_t state, jack_position_t *pos ); + int xrun ( void ); + void timebase ( jack_transport_state_t state, jack_nframes_t nframes, jack_position_t *pos ); + void timebase ( jack_transport_state_t state, jack_nframes_t nframes, jack_position_t *pos, int new_pos ); + void freewheel ( bool yes ); + int buffer_size ( nframes_t nframes ); + void thread_init ( void ); + + Engine ( const Engine &rhs ); + Engine & operator = ( const Engine &rhs ); + + void request_locate ( nframes_t frame ); + +private: + + friend class Port; + friend class Transport; + +public: + + Engine ( ); + ~Engine ( ); + + int dropped ( void ) const { return _buffers_dropped; } + +}; + +extern Engine * engine; diff --git a/Mixer/Gain_Module.C b/Mixer/Gain_Module.C new file mode 100644 index 0000000..2d148fd --- /dev/null +++ b/Mixer/Gain_Module.C @@ -0,0 +1,98 @@ + +/*******************************************************************************/ +/* Copyright (C) 2009 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 "Gain_Module.H" +#include +#include +#include + +Gain_Module::Gain_Module ( int W, int H, const char *L ) + : Module ( W, 24, L ) +{ + add_port( Port( this, Port::INPUT, Port::AUDIO ) ); + add_port( Port( this, Port::OUTPUT, Port::AUDIO ) ); + + Port p( this, Port::INPUT, Port::CONTROL, "gain" ); + p.hints.type = Port::Hints::LOGARITHMIC; + p.hints.ranged = true; + p.hints.minimum = 0.0f; +// p.hints.maximum = HUGE; + p.hints.maximum = 10.0f; + p.hints.default_value = 1.0f; + + p.connect_to( new float ); + p.control_value( 1.0f ); + + add_port( p ); + + color( FL_BLACK ); + + end(); +} + +Gain_Module::~Gain_Module ( ) +{ +} + + + +bool +Gain_Module::configure_inputs ( int n ) +{ + audio_input.clear(); + audio_output.clear(); +// control_input.clear(); + + for ( int i = 0; i < n; ++i ) + { + add_port( Port( this, Port::INPUT, Port::AUDIO ) ); + add_port( Port( this, Port::OUTPUT, Port::AUDIO ) ); +// add_port( Port( this, Port::INPUT, Port::CONTROL ) ); + +/* Port p( Port::OUTPUT, Port::CONTROL, "dB level" ); */ +/* p.hints.type = Port::Hints::LOGARITHMIC; */ +/* add_port( p ); */ + } + + return true; +} + + + +void +Gain_Module::process ( void ) +{ + if ( control_input[0].connected() ) + { + float g = control_input[0].control_value(); + + for ( int i = audio_input.size(); i--; ) + { + if ( audio_input[i].connected() && audio_output[i].connected() ) + { + buffer_apply_gain( (sample_t*)audio_input[i].buffer(), nframes(), g ); + +/* buffer_copy_and_apply_gain( (sample_t*)audio_output[0].buffer(), */ +/* (sample_t*)audio_input[0].buffer(), */ +/* nframes(), */ +/* g ); */ + } + } + } +} diff --git a/Mixer/Gain_Module.H b/Mixer/Gain_Module.H new file mode 100644 index 0000000..0baa02a --- /dev/null +++ b/Mixer/Gain_Module.H @@ -0,0 +1,40 @@ + +/*******************************************************************************/ +/* Copyright (C) 2009 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 "Module.H" + +class Gain_Module : public Module +{ +public: + + Gain_Module ( int W, int H, const char *L=0 ); + virtual ~Gain_Module ( ); + + const char *name ( void ) const { return "Gain"; } + + int can_support_inputs ( int n ) { return n; } + bool configure_inputs ( int n ); + +protected: + + virtual void process ( void ); + +}; diff --git a/Mixer/JACK_Module.C b/Mixer/JACK_Module.C new file mode 100644 index 0000000..2c8b640 --- /dev/null +++ b/Mixer/JACK_Module.C @@ -0,0 +1,187 @@ + +/*******************************************************************************/ +/* Copyright (C) 2009 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 "JACK_Module.H" +#include + +#include "Engine/Engine.H" +#include "dsp.h" +#include +#include "Chain.H" + +JACK_Module::JACK_Module ( int W, int H, const char *L ) + : Module ( W, 24, L ) +{ + /* FIXME: how do Controls find out that a connected value has changed? How does this work in ladspa? */ + { + Port p( this, Port::INPUT, Port::CONTROL, "Inputs" ); + p.hints.type = Port::Hints::INTEGER; + p.hints.minimum = 0; + p.hints.maximum = 6; + + p.connect_to( new float ); + p.control_value_no_callback( 0 ); + + add_port( p ); + } + + { + Port p( this, Port::INPUT, Port::CONTROL, "Outputs" ); + p.hints.type = Port::Hints::INTEGER; + p.hints.minimum = 0; + p.hints.maximum = 6; + + p.connect_to( new float ); + p.control_value_no_callback( 0 ); + + add_port( p ); + } + + end(); +} + +int +JACK_Module::can_support_inputs ( int n ) +{ + return audio_output.size(); +} + +bool +JACK_Module::configure_inputs ( int n ) +{ + int on = audio_input.size(); + + if ( n > on ) + { + for ( int i = on; i < n; ++i ) + { + JACK::Port po( engine->client(), JACK::Port::Output, chain()->name(), i ); + + if ( po.valid() ) + { + add_port( Port( this, Port::INPUT, Port::AUDIO ) ); + jack_output.push_back( po ); + } + } + } + else + { + for ( int i = on; i > n; --i ) + { + audio_input.back().disconnect(); + audio_input.pop_back(); + jack_output.back().shutdown(); + jack_output.pop_back(); + } + } + + control_input[0].control_value_no_callback( n ); + + return true; +} + +bool +JACK_Module::configure_outputs ( int n ) +{ + int on = audio_output.size(); + + if ( n > on ) + { + for ( int i = on; i < n; ++i ) + { + JACK::Port po( engine->client(), JACK::Port::Input, chain()->name(), i ); + + if ( po.valid() ) + { + add_port( Port( this, Port::OUTPUT, Port::AUDIO ) ); + jack_input.push_back( po ); + } + } + } + else + { + for ( int i = on; i > n; --i ) + { + audio_output.back().disconnect(); + audio_output.pop_back(); + jack_input.back().shutdown(); + jack_input.pop_back(); + } + } + + control_input[1].control_value_no_callback( n ); + + return true; +} + +bool +JACK_Module::initialize ( void ) +{ +// configure_inputs( 1 ); + return true; +} + +JACK_Module::~JACK_Module ( ) +{ + configure_inputs( 0 ); +} + +void +JACK_Module::handle_control_changed ( Port *p ) +{ + if ( 0 == strcmp( p->name(), "Inputs" ) ) + { + DMESSAGE( "Adjusting number of inputs (JACK outputs)" ); + configure_inputs( p->control_value() ); + chain()->configure_ports(); + } + else if ( 0 == strcmp( p->name(), "Outputs" ) ) + { + DMESSAGE( "Adjusting number of outputs (JACK inputs)" ); + if ( chain()->can_configure_outputs( this, p->control_value() ) ) + { + configure_outputs( p->control_value() ); + chain()->configure_ports(); + } + } +} + +void +JACK_Module::handle_chain_name_changed ( void ) +{ + for ( unsigned int i = 0; i < jack_output.size(); ++i ) + jack_output[ i ].name( chain()->name(), i ); + + for ( unsigned int i = 0; i < jack_input.size(); ++i ) + jack_input[ i ].name( chain()->name(), i ); +} + + + +void +JACK_Module::process ( void ) +{ + for ( int i = 0; i < audio_input.size(); ++i ) + if ( audio_input[i].connected() ) + buffer_copy( (sample_t*)jack_output[i].buffer( nframes() ), (sample_t*)audio_input[i].buffer(), nframes() ); + + for ( int i = 0; i < audio_output.size(); ++i ) + if ( audio_output[i].connected() ) + buffer_copy( (sample_t*)audio_output[i].buffer(), (sample_t*)jack_input[i].buffer( nframes() ), nframes() ); +} diff --git a/Mixer/JACK_Module.H b/Mixer/JACK_Module.H new file mode 100644 index 0000000..c3a16a9 --- /dev/null +++ b/Mixer/JACK_Module.H @@ -0,0 +1,58 @@ + +/*******************************************************************************/ +/* Copyright (C) 2009 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 "Module.H" +#include "JACK/Port.H" +#include + +class JACK_Module : public Module +{ + const char *_strip_name; + + std::vector jack_input; + std::vector jack_output; + +public: + + JACK_Module ( int W, int H, const char *L=0 ); + virtual ~JACK_Module ( ); + + const char *name ( void ) const { return "JACK"; } + + void strip_name ( const char *name ) { _strip_name = name; } + + bool initialize ( void ); + + int can_support_inputs ( int ); + bool configure_inputs ( int n ); + bool configure_outputs ( int n ); + + void add_output ( void ); + + void handle_control_changed ( Port *p ); + void handle_chain_name_changed (); + +protected: + + virtual void process ( void ); + +}; diff --git a/Mixer/LADSPAInfo.C b/Mixer/LADSPAInfo.C new file mode 100644 index 0000000..cbc5a46 --- /dev/null +++ b/Mixer/LADSPAInfo.C @@ -0,0 +1,791 @@ +// +// LADSPAInfo.C - Class for indexing information on LADSPA Plugins +// +// Copyleft (C) 2002 Mike Rawes +// +// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// + +// #include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define HAVE_LIBLRDF 1 +#ifdef HAVE_LIBLRDF +#include +#endif + +#include "LADSPAInfo.h" + +using namespace std; + +LADSPAInfo::LADSPAInfo(bool override, + const char *path_list) +{ + if (strlen(path_list) > 0) { + m_ExtraPaths = strdup(path_list); + } else { + m_ExtraPaths = NULL; + } + m_LADSPAPathOverride = override; + + RescanPlugins(); +} + +LADSPAInfo::~LADSPAInfo() +{ + CleanUp(); +} + +void +LADSPAInfo::RescanPlugins(void) +{ +// Clear out what we've got + CleanUp(); + + if (!m_LADSPAPathOverride) { + // Get $LADPSA_PATH, if available + char *ladspa_path = getenv("LADSPA_PATH"); + if (ladspa_path) { + ScanPathList(ladspa_path, &LADSPAInfo::ExaminePluginLibrary); + + } else { + + cerr << "WARNING: LADSPA_PATH environment variable not set" << endl; + cerr << " Assuming /usr/lib/ladspa:/usr/local/lib/ladspa" << endl; + + ScanPathList("/usr/lib/ladspa:/usr/local/lib/ladspa", &LADSPAInfo::ExaminePluginLibrary); + } + } + +// Check any supplied extra paths + if (m_ExtraPaths) { + ScanPathList(m_ExtraPaths, &LADSPAInfo::ExaminePluginLibrary); + } + +// Do we have any plugins now? + if (m_Plugins.size() == 0) { + cerr << "WARNING: No plugins found" << endl; + } else { + cerr << m_Plugins.size() << " plugins found in " << m_Libraries.size() << " libraries" << endl; + +#ifdef HAVE_LIBLRDF + // Got some plugins. Now search for RDF data + lrdf_init(); + + char *rdf_path = getenv("LADSPA_RDF_PATH"); + + if (rdf_path) { + // Examine rdf info + ScanPathList(rdf_path, &LADSPAInfo::ExamineRDFFile); + + } else { + cerr << "WARNING: LADSPA_RDF_PATH environment variable not set" << endl; + cerr << " Assuming /usr/share/ladspa/rdf:/usr/local/share/ladspa/rdf" << endl; + + // Examine rdf info + ScanPathList("/usr/share/ladspa/rdf:/usr/local/share/ladspa/rdf", &LADSPAInfo::ExamineRDFFile); + } + MetadataRDFDescend(LADSPA_BASE "Plugin", 0); + + // See which plugins were not added to an rdf group, and add them + // all into the top level 'LADSPA' one + list rdf_p; + + // Get indices of plugins added to groups + for (vector::iterator ri = m_RDFURIs.begin(); ri != m_RDFURIs.end(); ri++) { + rdf_p.insert(rdf_p.begin(), ri->Plugins.begin(), ri->Plugins.end()); + } + + // Add all uncategorized plugins to top level group, subclassed by their + // library's basename. + rdf_p.unique(); + rdf_p.sort(); + unsigned long last_p = 0; + for (list::iterator p = rdf_p.begin(); p != rdf_p.end(); p++) { + if ((*p - last_p) > 1) { + for (unsigned long i = last_p + 1; i < *p; i++) { + // URI 0 is top-level "LADSPA" group + m_RDFURIs[0].Plugins.push_back(i); + } + } + last_p = *p; + } + while (++last_p < m_Plugins.size()) { + // URI 0 is top-level "LADSPA" group + m_RDFURIs[0].Plugins.push_back(last_p); + } + + lrdf_cleanup(); +#else + // No RDF. Add all plugins to top-level group + RDFURIInfo ri; + + ri.URI = ""; + ri.Label = "LADSPA"; + + m_RDFURIs.push_back(ri); + m_RDFLabelLookup["LADSPA"] = 0; + + for (unsigned long i = 0; i < m_Plugins.size(); i++) { + // Add plugin index + m_RDFURIs[0].Plugins.push_back(i); + } +#endif + } +} + +void +LADSPAInfo::UnloadAllLibraries(void) +{ +// Blank descriptors + for (vector::iterator i = m_Plugins.begin(); + i != m_Plugins.end(); i++) { + if (i->Descriptor) i->Descriptor = NULL; + } +// Unload DLLs, + for (vector::iterator i = m_Libraries.begin(); + i != m_Libraries.end(); i++) { + if (i->Handle) { + dlclose(i->Handle); + i->Handle = NULL; + } + i->RefCount = 0; + } +} + +const LADSPA_Descriptor * +LADSPAInfo::GetDescriptorByID(unsigned long unique_id) +{ + if (m_IDLookup.find(unique_id) == m_IDLookup.end()) { + cerr << "LADSPA Plugin ID " << unique_id << " not found!" << endl; + return NULL; + } + +// Got plugin index + unsigned long plugin_index = m_IDLookup[unique_id]; + + PluginInfo *pi = &(m_Plugins[plugin_index]); + LibraryInfo *li = &(m_Libraries[pi->LibraryIndex]); + + if (!(pi->Descriptor)) { + LADSPA_Descriptor_Function desc_func = GetDescriptorFunctionForLibrary(pi->LibraryIndex); + if (desc_func) pi->Descriptor = desc_func(pi->Index); + } + + if (pi->Descriptor) { + + // Success, so increment ref counter for library + li->RefCount++; + } + + return pi->Descriptor; +} + +void +LADSPAInfo::DiscardDescriptorByID(unsigned long unique_id) +{ + if (m_IDLookup.find(unique_id) == m_IDLookup.end()) { + cerr << "LADSPA Plugin ID " << unique_id << " not found!" << endl; + } else { + + // Get plugin index + unsigned long plugin_index = m_IDLookup[unique_id]; + + PluginInfo *pi = &(m_Plugins[plugin_index]); + LibraryInfo *li = &(m_Libraries[pi->LibraryIndex]); + + pi->Descriptor = NULL; + + // Decrement reference counter for library, and unload if last + if (li->RefCount > 0) { + li->RefCount--; + if (li->RefCount == 0) { + + // Unload library + dlclose(li->Handle); + li->Handle = NULL; + } + } + } +} + +// **************************************************************************** +// ** SSM Specific Functions ** +// **************************************************************************** + +unsigned long +LADSPAInfo::GetIDFromFilenameAndLabel(std::string filename, + std::string label) +{ + bool library_loaded = false; + + if (m_FilenameLookup.find(filename) == m_FilenameLookup.end()) { + cerr << "LADSPA Library " << filename << " not found!" << endl; + return 0; + } + + unsigned long library_index = m_FilenameLookup[filename]; + + if (!(m_Libraries[library_index].Handle)) library_loaded = true; + + LADSPA_Descriptor_Function desc_func = GetDescriptorFunctionForLibrary(library_index); + + if (!desc_func) { + return 0; + } + +// Search for label in library + const LADSPA_Descriptor *desc; + for (unsigned long i = 0; (desc = desc_func(i)) != NULL; i++) { + string l = desc->Label; + if (l == label) { + + // If we had to load the library, unload it + unsigned long id = desc->UniqueID; + if (library_loaded) { + dlclose(m_Libraries[library_index].Handle); + m_Libraries[library_index].Handle = NULL; + } + return id; + } + } + + cerr << "Plugin " << label << " not found in library " << filename << endl; + return 0; +} + +const vector +LADSPAInfo::GetMenuList(void) +{ + m_SSMMenuList.clear(); + + DescendGroup("", "LADSPA", 1); + + return m_SSMMenuList; +} + +unsigned long +LADSPAInfo::GetPluginListEntryByID(unsigned long unique_id) +{ + unsigned long j = 0; + for (vector::iterator i = m_SSMMenuList.begin(); + i != m_SSMMenuList.end(); i++, j++) { + if (i->UniqueID == unique_id) return j; + } + return m_SSMMenuList.size(); +} + +// **************************************************************************** +// ** Private Member Functions ** +// **************************************************************************** + +// Build a list of plugins by group, suitable for SSM LADSPA Plugin drop-down +// The top-level "LADSPA" group is not included + +void +LADSPAInfo::DescendGroup(string prefix, + const string group, + unsigned int depth) +{ + list groups = GetSubGroups(group); + + if (prefix.length() > 0) { + // Add an explicit '/' as we're creating sub-menus from groups + prefix += "/"; + } + + for (list::iterator g = groups.begin(); g != groups.end(); g++) { + string name; + + // Escape '/' and '|' characters + unsigned int x = g->find_first_of("/|"); + if (x == string::npos) { + name = *g; + } else { + unsigned int last_x = 0; + while (x < string::npos) { + name += g->substr(last_x, x - last_x) + '\\' + (*g)[x]; + last_x = x + 1; + x = g->find_first_of("/|", x + 1); + } + name += g->substr(last_x, x - last_x); + } + + DescendGroup(prefix + name, *g, depth + 1); + } + if (m_RDFLabelLookup.find(group) != m_RDFLabelLookup.end()) { + unsigned long uri_index = m_RDFLabelLookup[group]; + + // Create group for unclassified plugins + if (prefix.length() == 0) { + prefix = "Unclassified/"; + depth = depth + 1; + } + + // Temporary list (for sorting the plugins by name) + list plugins; + + for (vector::iterator p = m_RDFURIs[uri_index].Plugins.begin(); + p != m_RDFURIs[uri_index].Plugins.end(); p++) { + + PluginInfo *pi = &(m_Plugins[*p]); + string name; + + // Escape '/' and '|' characters + unsigned int x = pi->Name.find_first_of("/|"); + if (x == string::npos) { + name = pi->Name; + } else { + unsigned int last_x = 0; + while (x < string::npos) { + name += pi->Name.substr(last_x, x - last_x) + '\\' + pi->Name[x]; + last_x = x + 1; + x = pi->Name.find_first_of("/|", x + 1); + } + name += pi->Name.substr(last_x, x - last_x); + } + + PluginEntry pe; + + pe.Depth = depth; + pe.UniqueID = pi->UniqueID; + pe.Name = prefix + name; + + plugins.push_back(pe); + } + plugins.sort(); + + // Deal with duplicates by numbering them + for (list::iterator i = plugins.begin(); + i != plugins.end(); ) { + string name = i->Name; + + i++; + unsigned long n = 2; + while ((i != plugins.end()) && (i->Name == name)) { + stringstream s; + s << n; + i->Name = name + " (" + s.str() + ")"; + n++; + i++; + } + } + + // Add all ordered entries to the Menu List + // This ensures that plugins appear after groups + for (list::iterator p = plugins.begin(); p != plugins.end(); p++) { + m_SSMMenuList.push_back(*p); + } + } +} + +// Get list of groups that are within given group. The root group is +// always "LADSPA" +list +LADSPAInfo::GetSubGroups(const string group) +{ + list groups; + unsigned long uri_index; + + if (m_RDFLabelLookup.find(group) == m_RDFLabelLookup.end()) { + return groups; + } else { + uri_index = m_RDFLabelLookup[group]; + } + + for (vector::iterator sg = m_RDFURIs[uri_index].Children.begin(); + sg != m_RDFURIs[uri_index].Children.end(); sg++) { + groups.push_back(m_RDFURIs[*sg].Label); + } + + groups.sort(); + + return groups; +} + +// Unload any loaded DLLs and clear vectors etc +void +LADSPAInfo::CleanUp(void) +{ + m_MaxInputPortCount = 0; + + m_IDLookup.clear(); + m_Plugins.clear(); + +// Unload loaded dlls + for (vector::iterator i = m_Libraries.begin(); + i != m_Libraries.end(); i++) { + if (i->Handle) dlclose(i->Handle); + } + + m_Libraries.clear(); + m_Paths.clear(); + + m_RDFURILookup.clear(); + m_RDFURIs.clear(); + + if (m_ExtraPaths) { + free(m_ExtraPaths); + m_ExtraPaths = NULL; + } +} + +// Given a colon-separated list of paths, examine the contents of each +// path, examining any regular files using the given member function, +// which currently can be: +// +// ExaminePluginLibrary - add plugin library info from plugins +// ExamineRDFFile - add plugin information from .rdf/.rdfs files +void +LADSPAInfo::ScanPathList(const char *path_list, + void (LADSPAInfo::*ExamineFunc)(const string, + const string)) +{ + const char *start; + const char *end; + int extra; + char *path; + string basename; + DIR *dp; + struct dirent *ep; + struct stat sb; + +// This does the same kind of thing as strtok, but strtok won't +// like the const + start = path_list; + while (*start != '\0') { + while (*start == ':') start++; + end = start; + while (*end != ':' && *end != '\0') end++; + + if (end - start > 0) { + extra = (*(end - 1) == '/') ? 0 : 1; + path = (char *)malloc(end - start + 1 + extra); + if (path) { + strncpy(path, start, end - start); + if (extra == 1) path[end - start] = '/'; + path[end - start + extra] = '\0'; + + dp = opendir(path); + if (!dp) { + cerr << "WARNING: Could not open path " << path << endl; + } else { + while ((ep = readdir(dp))) { + + // Stat file to get type + basename = ep->d_name; + if (!stat((path + basename).c_str(), &sb)) { + + // We only want regular files + if (S_ISREG(sb.st_mode)) (*this.*ExamineFunc)(path, basename); + } + } + closedir(dp); + } + free(path); + } + } + start = end; + } +} + +// Check given file is a valid LADSPA Plugin library +// +// If so, add path, library and plugin info +// to the m_Paths, m_Libraries and m_Plugins vectors. +// +void +LADSPAInfo::ExaminePluginLibrary(const string path, + const string basename) +{ + void *handle; + LADSPA_Descriptor_Function desc_func; + const LADSPA_Descriptor *desc; + string fullpath = path + basename; + +// We're not executing any code, so be lazy about resolving symbols + handle = dlopen(fullpath.c_str(), RTLD_LAZY); + + if (!handle) { + cerr << "WARNING: File " << fullpath + << " could not be examined" << endl; + cerr << "dlerror() output:" << endl; + cerr << dlerror() << endl; + } else { + + // It's a DLL, so now see if it's a LADSPA plugin library + desc_func = (LADSPA_Descriptor_Function)dlsym(handle, + "ladspa_descriptor"); + if (!desc_func) { + + // Is DLL, but not a LADSPA one + cerr << "WARNING: DLL " << fullpath + << " has no ladspa_descriptor function" << endl; + cerr << "dlerror() output:" << endl; + cerr << dlerror() << endl; + } else { + + // Got ladspa_descriptor, so we can now get plugin info + bool library_added = false; + unsigned long i = 0; + desc = desc_func(i); + while (desc) { + + // First, check that it's not a dupe + if (m_IDLookup.find(desc->UniqueID) != m_IDLookup.end()) { + unsigned long plugin_index = m_IDLookup[desc->UniqueID]; + unsigned long library_index = m_Plugins[plugin_index].LibraryIndex; + unsigned long path_index = m_Libraries[library_index].PathIndex; + + cerr << "WARNING: Duplicated Plugin ID (" + << desc->UniqueID << ") found:" << endl; + + cerr << " Plugin " << m_Plugins[plugin_index].Index + << " in library: " << m_Paths[path_index] + << m_Libraries[library_index].Basename + << " [First instance found]" << endl; + cerr << " Plugin " << i << " in library: " << fullpath + << " [Duplicate not added]" << endl; + } else { + if (CheckPlugin(desc)) { + + // Add path if not already added + unsigned long path_index; + vector::iterator p = find(m_Paths.begin(), m_Paths.end(), path); + if (p == m_Paths.end()) { + path_index = m_Paths.size(); + m_Paths.push_back(path); + } else { + path_index = p - m_Paths.begin(); + } + + // Add library info if not already added + if (!library_added) { + LibraryInfo li; + li.PathIndex = path_index; + li.Basename = basename; + li.RefCount = 0; + li.Handle = NULL; + m_Libraries.push_back(li); + + library_added = true; + } + + // Add plugin info + PluginInfo pi; + pi.LibraryIndex = m_Libraries.size() - 1; + pi.Index = i; + pi.UniqueID = desc->UniqueID; + pi.Label = desc->Label; + pi.Name = desc->Name; + pi.Descriptor = NULL; + m_Plugins.push_back(pi); + + // Find number of input ports + unsigned long in_port_count = 0; + for (unsigned long p = 0; p < desc->PortCount; p++) { + if (LADSPA_IS_PORT_INPUT(desc->PortDescriptors[p])) { + in_port_count++; + } + } + if (in_port_count > m_MaxInputPortCount) { + m_MaxInputPortCount = in_port_count; + } + + // Add to index + m_IDLookup[desc->UniqueID] = m_Plugins.size() - 1; + + } else { + cerr << "WARNING: Plugin " << desc->UniqueID << " not added" << endl; + } + } + + desc = desc_func(++i); + } + } + dlclose(handle); + } +} + +#ifdef HAVE_LIBLRDF +// Examine given RDF plugin meta-data file +void +LADSPAInfo::ExamineRDFFile(const std::string path, + const std::string basename) +{ + string fileuri = "file://" + path + basename; + + if (lrdf_read_file(fileuri.c_str())) { + cerr << "WARNING: File " << path + basename << " could not be parsed [Ignored]" << endl; + } +} + +// Recursively add rdf information for plugins that have been +// found from scanning LADSPA_PATH +void +LADSPAInfo::MetadataRDFDescend(const char * uri, + unsigned long parent) +{ + unsigned long this_uri_index; + +// Check URI not already added + if (m_RDFURILookup.find(uri) == m_RDFURILookup.end()) { + + // Not found + RDFURIInfo ri; + + ri.URI = uri; + + if (ri.URI == LADSPA_BASE "Plugin") { + + // Add top level group as "LADSPA" + // This will always happen, even if there are no .rdf files read by liblrdf + // or if there is no liblrdf support + ri.Label = "LADSPA"; + } else { + char * label = lrdf_get_label(uri); + if (label) { + ri.Label = label; + } else { + ri.Label = "(No label)"; + } + } + + // Add any instances found + lrdf_uris * instances = lrdf_get_instances(uri); + if (instances) { + for (long j = 0; j < instances->count; j++) { + unsigned long uid = lrdf_get_uid(instances->items[j]); + if (m_IDLookup.find(uid) != m_IDLookup.end()) { + ri.Plugins.push_back(m_IDLookup[uid]); + } + } + } + + lrdf_free_uris(instances); + + m_RDFURIs.push_back(ri); + this_uri_index = m_RDFURIs.size() - 1; + + m_RDFURILookup[ri.URI] = this_uri_index; + m_RDFLabelLookup[ri.Label] = this_uri_index; + + } else { + + // Already added + this_uri_index = m_RDFURILookup[uri]; + } + +// Only add parent - child info if this uri is NOT the first (root) uri + if (this_uri_index > 0) { + m_RDFURIs[this_uri_index].Parents.push_back(parent); + m_RDFURIs[parent].Children.push_back(this_uri_index); + } + + lrdf_uris * uris = lrdf_get_subclasses(uri); + + if (uris) { + for (long i = 0; i < uris->count; i++) { + MetadataRDFDescend(uris->items[i], this_uri_index); + } + } + + lrdf_free_uris(uris); +} +#endif + +bool +LADSPAInfo::CheckPlugin(const LADSPA_Descriptor *desc) +{ +#define test(t, m) { \ + if (!(t)) { \ + cerr << m << endl; \ + return false; \ + } \ +} + test(desc->instantiate, "WARNING: Plugin has no instatiate function"); + test(desc->connect_port, "WARNING: Warning: Plugin has no connect_port funciton"); + test(desc->run, "WARNING: Plugin has no run function"); + test(!(desc->run_adding != 0 && desc->set_run_adding_gain == 0), + "WARNING: Plugin has run_adding but no set_run_adding_gain"); + test(!(desc->run_adding == 0 && desc->set_run_adding_gain != 0), + "WARNING: Plugin has set_run_adding_gain but no run_adding"); + test(desc->cleanup, "WARNING: Plugin has no cleanup function"); + test(!LADSPA_IS_INPLACE_BROKEN(desc->Properties), + "WARNING: Plugin cannot use in place processing"); + test(desc->PortCount, "WARNING: Plugin has no ports"); + + return true; +} + +LADSPA_Descriptor_Function +LADSPAInfo::GetDescriptorFunctionForLibrary(unsigned long library_index) +{ + LibraryInfo *li = &(m_Libraries[library_index]); + + if (!(li->Handle)) { + + // Need full path + string fullpath = m_Paths[li->PathIndex]; + fullpath.append(li->Basename); + + // Immediate symbol resolution, as plugin code is likely to be executed + li->Handle = dlopen(fullpath.c_str(), RTLD_NOW); + if (!(li->Handle)) { + + // Plugin library changed since last path scan + cerr << "WARNING: Plugin library " << fullpath << " cannot be loaded" << endl; + cerr << "Rescan of plugins recommended" << endl; + cerr << "dlerror() output:" << endl; + cerr << dlerror() << endl; + return NULL; + } + } + +// Got handle so now verify that it's a LADSPA plugin library + const LADSPA_Descriptor_Function desc_func = (LADSPA_Descriptor_Function)dlsym(li->Handle, + "ladspa_descriptor"); + if (!desc_func) { + + // Is DLL, but not a LADSPA one (changed since last path scan?) + cerr << "WARNING: DLL " << m_Paths[li->PathIndex] << li->Basename + << " has no ladspa_descriptor function" << endl; + cerr << "Rescan of plugins recommended" << endl; + cerr << "dlerror() output:" << endl; + cerr << dlerror() << endl; + + // Unload library + dlclose(li->Handle); + return NULL; + } + + return desc_func; +} diff --git a/Mixer/LADSPAInfo.h b/Mixer/LADSPAInfo.h new file mode 100644 index 0000000..e41911f --- /dev/null +++ b/Mixer/LADSPAInfo.h @@ -0,0 +1,199 @@ +// +// LADSPAInfo.h - Header file for LADSPA Plugin info class +// +// Copyleft (C) 2002 Mike Rawes +// +// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +// + +#ifndef __ladspa_info_h__ +#define __ladspa_info_h__ + +// #include + +#include +#include +#include +#include +#include + +class LADSPAInfo +{ +public: +// If override is false, examine $LADSPA_PATH +// Also examine supplied path list +// For all paths, add basic plugin information for later lookup, +// instantiation and so on. + LADSPAInfo(bool override = false, const char *path_list = ""); + +// Unload all loaded plugins and clean up + ~LADSPAInfo(); + +// ************************************************************************ +// Loading/Unloading plugin libraries +// +// At first, no library dlls are loaded. +// +// A plugin library may have more than one plugin descriptor. The +// descriptor is used to instantiate, activate, execute plugin instances. +// Administration of plugin instances are outwith the scope of this class, +// instead, descriptors are requested using GetDecriptorByID, and disposed +// of using DiscardDescriptorByID. +// +// Each library keeps a reference count of descriptors requested. A library +// is loaded when a descriptor is requested for the first time, and remains +// loaded until the number of discards matches the number of requests. + +// Rescan all paths in $LADSPA_PATH, as per constructor. +// This will also unload all libraries, and make any descriptors that +// have not been discarded with DiscardDescriptorByID invalid. + void RescanPlugins(void); + +// Unload all dlopened libraries. This will make any descriptors that +// have not been discarded with DiscardDescriptorByID invalid. + void UnloadAllLibraries(void); + +// Get descriptor of plugin with given ID. This increments the descriptor +// count for the corresponding library. + const LADSPA_Descriptor *GetDescriptorByID(unsigned long unique_id); + +// Notify that a descriptor corresponding to the given ID has been +// discarded. This decrements the descriptor count for the corresponding +// library. + void DiscardDescriptorByID(unsigned long unique_id); + +// ************************************************************************ +// SSM Specific options + +// Get unique ID of plugin identified by given library filename and label. +// This is for backwards compatibility with older versions of SSM where the +// path and label of the plugin was stored in the configuration - current +// versions store the Unique ID + unsigned long GetIDFromFilenameAndLabel(std::string filename, + std::string label); + +// Struct for plugin information returned by queries + struct PluginEntry + { + unsigned int Depth; + unsigned long UniqueID; + std::string Name; + + bool operator<(const PluginEntry& pe) + { + return (Name GetMenuList(void); + +// Get the index in the above list for given Unique ID +// If not found, this returns the size of the above list + unsigned long GetPluginListEntryByID(unsigned long unique_id); + +// Get the number of input ports for the plugin with the most +// input ports + unsigned long GetMaxInputPortCount(void) { return m_MaxInputPortCount; } + +private: +// See LADSPAInfo.C for comments on these functions + void DescendGroup(std::string prefix, + const std::string group, + unsigned int depth); + std::list GetSubGroups(const std::string group); + + void CleanUp(void); + void ScanPathList(const char *path_list, + void (LADSPAInfo::*ExamineFunc)(const std::string, + const std::string)); + void ExaminePluginLibrary(const std::string path, + const std::string basename); + + bool CheckPlugin(const LADSPA_Descriptor *desc); + LADSPA_Descriptor_Function GetDescriptorFunctionForLibrary(unsigned long library_index); +#ifdef HAVE_LIBLRDF + void ExamineRDFFile(const std::string path, + const std::string basename); + void MetadataRDFDescend(const char *uri, + unsigned long parent); +#endif + +// For cached library information + struct LibraryInfo + { + unsigned long PathIndex; // Index of path in m_Paths + std::string Basename; // Filename + unsigned long RefCount; // Count of descriptors requested + void *Handle; // DLL Handle, NULL + }; + +// For cached plugin information + struct PluginInfo + { + unsigned long LibraryIndex; // Index of library in m_Libraries + unsigned long Index; // Plugin index in library + unsigned long UniqueID; // Unique ID + std::string Label; // Plugin label + std::string Name; // Plugin Name + const LADSPA_Descriptor *Descriptor; // Descriptor, NULL + }; + +// For cached RDF uri information + struct RDFURIInfo + { + std::string URI; // Full URI for use with lrdf + std::string Label; // Label + std::vector Parents; // Index of parents in m_RDFURIs + std::vector Children; // Indices of children in m_RDFURIs + std::vector Plugins; // Indices of plugins in m_Plugins + }; + +// Lookup maps + typedef std::map > IDMap; + + typedef std::map > StringMap; + + bool m_LADSPAPathOverride; + char *m_ExtraPaths; + +// LADSPA Plugin information database + std::vector m_Paths; + std::vector m_Libraries; + std::vector m_Plugins; + +// Plugin lookup maps + IDMap m_IDLookup; + +// RDF URI database + std::vector m_RDFURIs; + +// RDF URI lookup map + StringMap m_RDFURILookup; + +// RDF Label lookup map + StringMap m_RDFLabelLookup; + +// SSM specific data + std::vector m_SSMMenuList; + StringMap m_FilenameLookup; + unsigned long m_MaxInputPortCount; +}; + +#endif // __ladspa_info_h__ diff --git a/Mixer/Meter.H b/Mixer/Meter.H new file mode 100644 index 0000000..b1e9229 --- /dev/null +++ b/Mixer/Meter.H @@ -0,0 +1,138 @@ + +/*******************************************************************************/ +/* Copyright (C) 2008 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. */ +/*******************************************************************************/ + + +/* Base class for all meters */ + +#include +#include +#include +class Meter : public Fl_Valuator +{ + + float _peak; + float _old_value; + float _value; + +protected: + + virtual void draw ( void ) = 0; + virtual int handle ( int m ) + { + + if ( m == FL_PUSH ) + { +// if ( Fl::event_button3() ) +// hide(); +// else + reset(); + return 1; + } + + return Fl_Widget::handle( m ); + } + + float + deflection ( float db ) + { + float def = 0.0f; + + if ( db < -70.0f ) + def = 0.0f; + else if ( db < -60.0f ) + def = ( db + 70.0f ) * 0.25f; + else if ( db < -50.0f ) + def = ( db + 60.0f ) * 0.5f + 2.5f; + else if ( db < -40.0f ) + def = ( db + 50.0f ) * 0.75f + 7.5f; + else if ( db < -30.0f ) + def = ( db + 40.0f ) * 1.5f + 15.0f; + else if ( db < -20.0f ) + def = ( db + 30.0f ) * 2.0f + 30.0f; + else if ( db < 6.0f ) + def = ( db + 20.0f ) * 2.5f + 50.0f; + else + def = 115.0f; + + return def / 115.0f; + } + + float old_value ( void ) const { return _old_value; } + +public: + + Meter ( int X, int Y, int W, int H, const char *L = 0 ) : + Fl_Valuator( X, Y, W, H, L ) + { + _peak = _value = -80.0f; + _old_value = 4.0f; + } + + virtual ~Meter ( ) { } + + void value ( float v ) + { + if ( _value != v ) + { + damage( FL_DAMAGE_USER1 ); + + _old_value = _value; + _value = v; + + if ( _value > _peak ) + _peak = _value; + } + } + + float value ( void ) const { return _value; } + float peak ( void ) const { return _peak; } + + void reset ( void ) { _peak = -80.0f; redraw(); } + +}; + +#include +#include + + +/* ... Extension methods for any group containing only meters. Access + * via a cast to (Meter_Pack *) */ + +class Meter_Pack : public Fl_Group +{ + +public: + +/** return a pointer to the meter for channel /c/ in group of meters /g/ */ + Meter * + channel ( int c ) + { + if ( c > children() ) + { + fprintf( stderr, "no such channel\n" ); + return NULL; + } + + return (Meter *)child( c ); + } + + int + channels ( void ) const { return children(); } + +}; diff --git a/Mixer/Meter_Indicator_Module.C b/Mixer/Meter_Indicator_Module.C new file mode 100644 index 0000000..97c9d99 --- /dev/null +++ b/Mixer/Meter_Indicator_Module.C @@ -0,0 +1,180 @@ + +/*******************************************************************************/ +/* Copyright (C) 2009 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 "Meter_Indicator_Module.H" + +#include +#include +#include +#include +#include "FL/Fl_Arc_Dial.H" +#include "FL/Fl_Light_Button.H" +#include "FL/Boxtypes.H" +#include +#include "FL/Fl_Labelpad_Group.H" +#include +#include "Engine/Engine.H" +#include "Chain.H" +#include "DPM.H" +#include "FL/Fl_Scalepack.H" + + + +const float CONTROL_UPDATE_FREQ = 0.1f; + + + +Meter_Indicator_Module::Meter_Indicator_Module ( int W, int H, const char *L ) + : Module ( W, 100, L ) +{ + box( FL_NO_BOX ); + + _pad = true; + control = 0; + control_value = 0; + + add_port( Port( this, Port::INPUT, Port::CONTROL ) ); + + dpm_pack = new Fl_Scalepack( x(), y(), w(), h() ); + dpm_pack->type( FL_HORIZONTAL ); + + control_value = new float[1]; + + end(); + + Fl::add_timeout( CONTROL_UPDATE_FREQ, update_cb, this ); +} + +Meter_Indicator_Module::~Meter_Indicator_Module ( ) +{ + +} + + + +void +Meter_Indicator_Module::update_cb ( void *v ) +{ + ((Meter_Indicator_Module*)v)->update_cb(); +} + +void +Meter_Indicator_Module::update_cb ( void ) +{ + Fl::repeat_timeout( CONTROL_UPDATE_FREQ, update_cb, this ); + + if ( control_input[0].connected() ) + { + // A little hack to detect that the connected module's number + // of control outs has changed. + Port *p = control_input[0].connected_port(); + + if ( dpm_pack->children() != p->hints.dimensions ) + { + engine->lock(); + + dpm_pack->clear(); + + control_value = new float[p->hints.dimensions]; + + for ( int i = p->hints.dimensions; i--; ) + { + + DPM *dpm = new DPM( x(), y(), w(), h() ); + dpm->type( FL_VERTICAL ); + align( (Fl_Align)(FL_ALIGN_CENTER | FL_ALIGN_INSIDE ) ); + + dpm_pack->add( dpm ); + + control_value[i] = -70.0f; + dpm->value( -70.0f ); + } + + engine->unlock(); + } + else + { + for ( int i = 0; i < dpm_pack->children(); ++i ) + { + ((DPM*)dpm_pack->child( i ))->value( control_value[i] ); + } + } + } + + + redraw(); +} + +void +Meter_Indicator_Module::connect_to ( Port *p ) +{ + control_input[0].connect_to( p ); + +/* else if ( p->hints.type == Module::Port::Hints::LOGARITHMIC ) */ +/* { */ + { + DPM *o = new DPM( x(), y(), this->w(), h() ); + o->type( FL_VERTICAL ); + align( (Fl_Align)(FL_ALIGN_CENTER | FL_ALIGN_INSIDE ) ); + + dpm_pack->add( o ); + } + +// control = o; +// w = o; + +// o->value( p->control_value() ); +/* } */ + +/* w->align(FL_ALIGN_TOP); */ +/* w->labelsize( 10 ); */ + +/* if ( _pad ) */ +/* { */ +/* Fl_Labelpad_Group *flg = new Fl_Labelpad_Group( w ); */ +/* size( flg->w(), flg->h() ); */ +/* add( flg ); */ +/* } */ +/* else */ +/* { */ +/* w->resize( x(), y(), this->w(), h() ); */ +/* add( w ); */ +/* resizable( w ); */ +/* } */ +} + +int +Meter_Indicator_Module::handle ( int m ) +{ + return Fl_Group::handle( m ); +} + + + +void +Meter_Indicator_Module::process ( void ) +{ + if ( control_input[0].connected() ) + { + Port *p = control_input[0].connected_port(); + + for ( int i = 0; i < p->hints.dimensions; ++i ) + control_value[i] = ((float*)control_input[0].buffer())[i]; + } +} diff --git a/Mixer/Meter_Indicator_Module.H b/Mixer/Meter_Indicator_Module.H new file mode 100644 index 0000000..902010e --- /dev/null +++ b/Mixer/Meter_Indicator_Module.H @@ -0,0 +1,75 @@ + +/*******************************************************************************/ +/* Copyright (C) 2009 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 "Module.H" +#include +#include "JACK/Port.H" + +class Fl_Valuator; +class Fl_Scalepack; + +class Meter_Indicator_Module : public Module +{ + Fl_Scalepack *dpm_pack; + + static void update_cb ( void *v ); + void update_cb ( void ); + + bool _pad; + + volatile float *control_value; + +public: + + Meter_Indicator_Module ( int W, int H, const char *L=0 ); + virtual ~Meter_Indicator_Module ( ); + + const char *name ( void ) const { return "Meter Indicator"; } + + int can_support_inputs ( int n ) { return 0; } + bool configure_inputs ( int n ) { return false; } + + void pad ( bool v ) { _pad = v; } + + static void cb_handle ( Fl_Widget *w, void *v ); + void cb_handle ( Fl_Widget *w ); + + void connect_to ( Port *p ); + +protected: + + + // virtual void draw ( void ); + virtual void process ( void ); + + virtual void draw ( void ) + { + draw_box(); + Fl_Group::draw(); + } + + virtual int handle ( int m ); + +private: + + Fl_Valuator *control; + +}; diff --git a/Mixer/Meter_Module.C b/Mixer/Meter_Module.C new file mode 100644 index 0000000..77bb5a0 --- /dev/null +++ b/Mixer/Meter_Module.C @@ -0,0 +1,201 @@ + +/*******************************************************************************/ +/* Copyright (C) 2009 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 "Meter_Module.H" +#include "DPM.H" +#include +#include +#include +#include "JACK/Port.H" +#include + + + +const float METER_UPDATE_FREQ = 0.1f; + + + +Meter_Module::Meter_Module ( int W, int H, const char *L ) + : Module ( W, 100, L ) +{ + box( FL_THIN_UP_FRAME ); + dpm_pack = new Fl_Scalepack( x(), y(), w(), h() ); + dpm_pack->type( FL_HORIZONTAL ); + + color( FL_BLACK ); + + end(); + + Port p( this, Port::OUTPUT, Port::CONTROL, "dB level" ); + p.hints.type = Port::Hints::LOGARITHMIC; + p.hints.ranged = true; + p.hints.maximum = 6.0f; + p.hints.minimum = -70.0f; + p.hints.dimensions = 1; + p.connect_to( new float[1] ); + p.control_value_no_callback( -70.0f ); + + add_port( p ); + + Fl::add_timeout( METER_UPDATE_FREQ, update_cb, this ); + +} + +Meter_Module::~Meter_Module ( ) +{ +} + +void +Meter_Module::update_cb ( void *v ) +{ + ((Meter_Module*)v)->update_cb(); +} + +void +Meter_Module::update_cb ( void ) +{ + Fl::repeat_timeout( METER_UPDATE_FREQ, update_cb, this ); + + for ( int i = dpm_pack->children(); i--; ) + dpm_pack->child( i )->redraw(); +} + +bool +Meter_Module::configure_inputs ( int n ) +{ + int tx, ty, tw, th; + bbox( tx,ty,tw,th ); + + int on = audio_input.size(); + + if ( n > on ) + { + for ( int i = on; i < n; ++i ) + { + DPM *dpm = new DPM( tx, ty, tw, th ); + dpm->type( FL_VERTICAL ); + align( (Fl_Align)(FL_ALIGN_CENTER | FL_ALIGN_INSIDE ) ); + + dpm_pack->add( dpm ); + + add_port( Port( this, Port::INPUT, Port::AUDIO ) ); + add_port( Port( this, Port::OUTPUT, Port::AUDIO ) ); + + } + } + else + { + for ( int i = on; i > n; --i ) + { + DPM *dpm = (DPM*)dpm_pack->child( dpm_pack->children() - 1 ); + dpm_pack->remove( dpm ); + delete dpm; + + audio_input.back().disconnect(); + audio_input.pop_back(); + audio_output.back().disconnect(); + audio_output.pop_back(); + control_output.back().disconnect(); + control_output.pop_back(); + } + } + + control_output[0].hints.dimensions = n; + delete[] (float*)control_output[0].buffer(); + { + float *f = new float[n]; + + for ( int i = n; i--; ) + f[i] = -70.0f; + + control_output[0].connect_to( f); + } + + return true; +} + +int +Meter_Module::handle ( int m ) +{ + switch ( m ) + { + case FL_PUSH: + { +/* Fl_Single_Window *win = new Fl_Single_Window( 0, 0, 400, 400 ); */ + +/* win->add( dpm ); */ +/* win->resizable( dpm ); */ + +/* win->end(); */ + +/* win->show(); */ +/* break; */ + } + } + + return Module::handle( m ); + } + + + +static float +get_peak_sample ( const sample_t* buf, nframes_t nframes ) +{ + float p = 0.0f; + + const sample_t *f = buf; + + for ( int j = nframes; j--; ++f ) + { + float s = *f; + + /* rectify */ + if ( s < 0.0f ) + s = 0 - s; + + if ( s > p ) + p = s; + } + + return p; +} + +void +Meter_Module::process ( void ) +{ + for ( int i = 0; i < audio_input.size(); ++i ) + { + DPM *dpm = (DPM*)dpm_pack->child( i ); + if ( audio_input[i].connected() ) + { + dpm->activate(); + + float dB = 20 * log10( get_peak_sample( (float*)audio_input[i].buffer(), nframes() ) / 2.0f ); + + dpm->value( dB ); + +/* if ( control_output[i].connected() ) */ +/* { */ + ((float*)control_output[0].buffer())[i] = dB; +/* } */ + } + else + dpm->deactivate(); + } +} diff --git a/Mixer/Meter_Module.H b/Mixer/Meter_Module.H new file mode 100644 index 0000000..a496fa2 --- /dev/null +++ b/Mixer/Meter_Module.H @@ -0,0 +1,51 @@ + +/*******************************************************************************/ +/* Copyright (C) 2009 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 "Module.H" + +class Fl_Scalepack; + +class Meter_Module : public Module +{ + Fl_Scalepack *dpm_pack; + +public: + + Meter_Module ( int W, int H, const char *L=0 ); + virtual ~Meter_Module ( ); + + const char *name ( void ) const { return "Meter"; } + + int can_support_inputs ( int n ) { return n > 0 ? n : -1; } + bool configure_inputs ( int n ); + + static void update_cb ( void *v ); + void update_cb ( void ); + +protected: + + + // virtual void draw ( void ); + virtual int handle ( int m ); + virtual void process ( void ); + + +}; diff --git a/Mixer/Mixer.C b/Mixer/Mixer.C new file mode 100644 index 0000000..afe9164 --- /dev/null +++ b/Mixer/Mixer.C @@ -0,0 +1,230 @@ + +/*******************************************************************************/ +/* Copyright (C) 2009 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. */ +/*******************************************************************************/ + +/* This is the main mixer group. It contains and manages Mixer_Strips. */ + +#include "Mixer.H" +#include "Mixer_Strip.H" + +#include +#include + +#include "Engine/Engine.H" + +#include +#include "debug.h" + +const double STATUS_UPDATE_FREQ = 0.2f; + +static Fl_Pack *mixer_strips; + + +#include "util/debug.h" + +static void update_cb( void *v ) { + Fl::repeat_timeout( STATUS_UPDATE_FREQ, update_cb, v ); + + ((Mixer*)v)->update(); +} + +Mixer::Mixer ( int X, int Y, int W, int H, const char *L ) : + Fl_Group( X, Y, W, H, L ) +{ + label( "Non-Mixer" ); + align( (Fl_Align)(FL_ALIGN_CENTER | FL_ALIGN_INSIDE) ); + labelsize( 96 ); + { + Fl_Scroll *o = scroll = new Fl_Scroll( 0, 0, W, H ); + o->type( Fl_Scroll::HORIZONTAL_ALWAYS ); + { + Fl_Pack *o = mixer_strips = new Fl_Pack( 0, 4, W, H - 24 ); + o->type( Fl_Pack::HORIZONTAL ); + o->spacing( 2 ); + o->end(); + Fl_Group::current()->resizable( o ); + } + o->end(); + Fl_Group::current()->resizable( o ); + } + + end(); + +// Fl::add_timeout( STATUS_UPDATE_FREQ, update_cb, this ); + + MESSAGE( "Scanning for plugins..." ); + +} + +Mixer::~Mixer ( ) +{ + /* FIXME: teardown */ + +} + +void Mixer::resize ( int X, int Y, int W, int H ) +{ + mixer_strips->size( W, H - 24 ); + + scroll->resize( X, Y, W, H ); + + Fl_Group::resize( X, Y, W, H ); +} + +void Mixer::add ( Mixer_Strip *ms ) +{ + MESSAGE( "Add mixer strip \"%s\"", ms->name() ); + + engine->lock(); + + mixer_strips->add( ms ); +// mixer_strips->insert( *ms, 0 ); + + engine->unlock(); + + redraw(); +} + +void Mixer::remove ( Mixer_Strip *ms ) +{ + MESSAGE( "Remove mixer strip \"%s\"", ms->name() ); + + engine->lock(); + + mixer_strips->remove( ms ); + + engine->unlock(); + + delete ms; + parent()->redraw(); +} + +bool +Mixer::contains ( Mixer_Strip *ms ) +{ + return ms->parent() == mixer_strips; +} + +void Mixer::update ( void ) +{ + THREAD_ASSERT( UI ); + + for ( int i = mixer_strips->children(); i--; ) + { + ((Mixer_Strip*)mixer_strips->child( i ))->update(); + } + // redraw(); +} + +void +Mixer::process ( unsigned int nframes ) +{ + THREAD_ASSERT( RT ); + + for ( int i = mixer_strips->children(); i--; ) + { + ((Mixer_Strip*)mixer_strips->child( i ))->process( nframes ); + } +} + +/** retrun a pointer to the track named /name/, or NULL if no track is named /name/ */ +Mixer_Strip * +Mixer::track_by_name ( const char *name ) +{ + for ( int i = mixer_strips->children(); i-- ; ) + { + Mixer_Strip *t = (Mixer_Strip*)mixer_strips->child( i ); + + if ( ! strcmp( name, t->name() ) ) + return t; + } + + return NULL; +} + +/** return a malloc'd string representing a unique name for a new track */ +char * +Mixer::get_unique_track_name ( const char *name ) +{ + char pat[256]; + + strcpy( pat, name ); + + for ( int i = 1; track_by_name( pat ); ++i ) + snprintf( pat, sizeof( pat ), "%s.%d", name, i ); + + return strdup( pat ); +} + + +void +Mixer::snapshot ( void ) +{ + for ( int i = 0; i < mixer_strips->children(); ++i ) + ((Mixer_Strip*)mixer_strips->child( i ))->log_create(); +} + + +void +Mixer::new_strip ( void ) +{ + engine->lock(); + + add( new Mixer_Strip( get_unique_track_name( "Unnamed" ), 1 ) ); + + engine->unlock(); + +// scroll->size( mixer_strips->w(), scroll->h() ); +} + +int +Mixer::handle ( int m ) +{ + int r = Fl_Group::handle( m ); + + switch ( m ) + { + case FL_ENTER: + case FL_LEAVE: + return 1; + case FL_SHORTCUT: + { + if ( Fl::event_key() == 'a' ) + { + new_strip(); + return 1; + } + else if ( Fl::event_ctrl() && Fl::event_key() == 's' ) + { + MESSAGE( "Saving state" ); + Loggable::snapshot_callback( &Mixer::snapshot, this ); + Loggable::snapshot( "save.mix" ); + return 1; + } + else + return r; + break; + } + default: + return r; + break; + } + + // return 0; + return r; +} diff --git a/Mixer/Mixer.H b/Mixer/Mixer.H new file mode 100644 index 0000000..662b042 --- /dev/null +++ b/Mixer/Mixer.H @@ -0,0 +1,63 @@ + +/*******************************************************************************/ +/* Copyright (C) 2009 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 +#include +#include +#include +#include +#include "Mixer_Strip.H" + +class Mixer : public Fl_Group +{ + +private: + + Mixer_Strip* track_by_name ( const char *name ); + char * get_unique_track_name ( const char *name ); + + void snapshot ( void ); + static void snapshot ( void *v ) { ((Mixer*)v)->snapshot(); } + + Fl_Scroll *scroll; + Fl_Pack *pack; + +protected: + + virtual int handle ( int m ); + +public: + + virtual void resize ( int X, int Y, int W, int H ); + + void update ( void ); + + void new_strip ( void ); + void process ( unsigned int nframes ); + void add ( Mixer_Strip *ms ); + void remove ( Mixer_Strip *ms ); + bool contains ( Mixer_Strip *ms ); + + Mixer ( int X, int Y, int W, int H, const char *L ); + virtual ~Mixer(); +}; + +extern Mixer* mixer; diff --git a/Mixer/Mixer_Strip.C b/Mixer/Mixer_Strip.C new file mode 100644 index 0000000..366de00 --- /dev/null +++ b/Mixer/Mixer_Strip.C @@ -0,0 +1,471 @@ + +/*******************************************************************************/ +/* Copyright (C) 2009 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. */ +/*******************************************************************************/ + +/* Mixer strip control. Handles GUI and control I/O for this strip. */ + +/* A mixer strip is home to some (JACK) input ports, a fader, some + * meters, and a filter chain which can terminate either at the input + * to the spacializer or some (JACK) output ports. Since mixer strips + * are not necessarily in a 1:1 association with Non-DAW tracks, there + * is no need for busses per se. If you want to route the output of + * several strips into a single fader or filter chain, then you just + * gives those strips JACK outputs and connect them to the common + * inputs. This mechanism can also do away with the need for 'sends' + * and 'inserts'. + + */ +/* Each mixer strip comprises a fader and a panner */ + +#include "Mixer_Strip.H" +#include "Engine/Engine.H" +#include +#include +#include "debug.h" + + +#include +#include "FL/Fl_Flowpack.H" +#include "Mixer.H" + +#include "Chain.H" +#include "Gain_Module.H" +#include "Meter_Module.H" +#include "Controller_Module.H" +#include "Meter_Indicator_Module.H" +#include "util/debug.h" + +extern Mixer *mixer; + + + +void +Mixer_Strip::get ( Log_Entry &e ) const +{ + e.add( ":name", name() ); +// e.add( ":controllable", controllable() ); +// e.add( ":inputs", _in.size() ); +/* e.add( ":gain", gain_slider->value() ); */ + e.add( ":meter_point", prepost_button->value() ? "pre" : "post" ); + e.add( ":color", (unsigned long)color()); + +} + +void +Mixer_Strip::set ( Log_Entry &e ) +{ + for ( int i = 0; i < e.size(); ++i ) + { + const char *s, *v; + + e.get( i, &s, &v ); + + if ( ! strcmp( s, ":name" ) ) + name( v ); +// else if ( ! strcmp( s, ":controllable" ) ) +// controllable( atoi( v ) ); + else if ( ! strcmp( s, ":inputs" ) ) + configure_ports( atoi( v ) ); +/* else if ( ! strcmp( s, ":gain" ) ) */ +/* gain_slider->value( atof( v ) ); */ + else if ( ! strcmp( s, ":meter_point" ) ) + prepost_button->value( strcmp( v, "pre" ) == 0 ); + else if ( ! strcmp( s, ":color" ) ) + { + color( (Fl_Color)atoll( v ) ); + redraw(); + } + } + + if ( ! mixer->contains( this ) ) + mixer->add( this ); +} + + +Mixer_Strip::Mixer_Strip( const char *strip_name, int channels ) : Fl_Group( 0, 0, 120, 600 ) +{ + + label( strdup( strip_name ) ); + + init(); + + color( (Fl_Color)rand() ); + +// name( strdup( strip_name ) ); + + configure_ports( channels ); + + log_create(); +} + +Mixer_Strip::Mixer_Strip() : Fl_Group( 0, 0, 120, 600 ) +{ + init(); + + log_create(); +} + +Mixer_Strip::~Mixer_Strip ( ) +{ + configure_ports( 0 ); +} + + + +void Mixer_Strip::cb_handle(Fl_Widget* o) { + // parent()->parent()->damage( FL_DAMAGE_ALL, x(), y(), w(), h() ); + if ( o == close_button ) + ((Mixer*)parent())->remove( this ); + else if ( o == inputs_counter ) + configure_ports( ((Fl_Counter*)o)->value() ); + else if ( o == name_field ) + name( name_field->value() ); +/* else if ( o == controllable_button ) */ +/* { */ +/* controllable( controllable_button->value() ); */ +/* // configure_ports( channels() ); */ +/* } */ + else if ( o == prepost_button ) + { + if ( ((Fl_Button*)o)->value() ) + size( 300, h() ); + else + size( 120, h() ); + + parent()->parent()->redraw(); + } +} + +void Mixer_Strip::cb_handle(Fl_Widget* o, void* v) { + ((Mixer_Strip*)(v))->cb_handle(o); +} + + +void +Mixer_Strip::name ( const char *name ) { + char *s = strdup( name ); + name_field->value( s ); + label( s ); + chain->name( s ); +} + +void +Mixer_Strip::configure_outputs ( Fl_Widget *o, void *v ) +{ + ((Mixer_Strip*)v)->configure_outputs(); +} + +void +Mixer_Strip::configure_outputs ( void ) +{ + DMESSAGE( "Got signal to configure outputs" ); +} + +bool +Mixer_Strip::configure_ports ( int n ) +{ +/* /\* figure out how many buffers we have to create *\/ */ +/* int required_buffers = chain->required_buffers(); */ + +/* engine->lock(); */ + +/* if ( chain_buffers > 0 ) */ +/* { */ +/* for ( int i = chain_buffers; --i; ) */ +/* { */ +/* delete chain_buffer[i]; */ +/* chain_buffer[i] = NULL; */ +/* } */ +/* delete chain_buffer; */ +/* chain_buffer = NULL; */ +/* chain_buffers = 0; */ +/* } */ + +/* sample_t **buf = new sample_t*[required_buffers]; */ +/* for ( int i = 0; i < required_buffers; ++i ) */ +/* buf[i] = new sample_t[nframes]; */ + +/* chain_buffers = required_buffers; */ +/* chain_buffer = buf; */ + +/* engine->unlock(); */ + +/* /\* FIXME: bogus *\/ */ +/* return true; */ + +} + + + +void +Mixer_Strip::process ( nframes_t nframes ) +{ + THREAD_ASSERT( RT ); + +/* sample_t *gain_buf = NULL; */ +/* float g = gain_slider->value(); */ + +/* if ( _control && _control->connected() ) */ +/* { */ +/* gain_buf = (sample_t*)_control->buffer( nframes ); */ + +/* /\* // bring it up to 0.0-2.0f *\/ */ +/* /\* for ( int i = nframes; i--; ) *\/ */ +/* /\* gain_buf[i] += 1.0f; *\/ */ + +/* // apply gain from slider */ +/* buffer_apply_gain( gain_buf, nframes, g ); */ + +/* /\* FIXME: bullshit! *\/ */ +/* _control_peak = gain_buf[0]; */ +/* } */ +/* else */ +/* { */ +/* _control_peak = 0; */ +/* } */ + +/* for ( int i = channels(); i--; ) */ +/* { */ +/* if ( _in[i].connected()) */ +/* { */ +/* if ( gain_buf ) */ +/* buffer_copy_and_apply_gain_buffer( (sample_t*)_out[i].buffer( nframes ), (sample_t*)_in[i].buffer( nframes ), gain_buf, nframes ); */ +/* else */ +/* buffer_copy_and_apply_gain( (sample_t*)_out[i].buffer( nframes ), (sample_t*)_in[i].buffer( nframes ), nframes, g ); */ + +/* sample_t *meter_buffer = prepost_button->value() == 1 ? (sample_t*)_in[i].buffer( nframes ) : (sample_t*)_out[i].buffer( nframes ); */ + +/* /\* set peak value (in dB) *\/ */ +/* _peak[i] = 20 * log10( get_peak_sample( meter_buffer, nframes ) / 2.0f ); */ +/* } */ +/* else */ +/* { */ +/* buffer_fill_with_silence( (sample_t*)_out[i].buffer( nframes ), nframes ); */ +/* } */ +/* } */ + + chain->process( nframes ); +} + +/* update GUI with values from RT thread */ +void +Mixer_Strip::update ( void ) +{ + THREAD_ASSERT( UI ); +} + +void +Mixer_Strip::init ( ) +{ + chain_buffers = 0; + chain_buffer = NULL; + + box(FL_THIN_UP_BOX); + clip_children( 1 ); + + Fl_Pack *gain_pack; + + { Fl_Pack *o = new Fl_Pack( 2, 2, 114, 100 ); + o->type( Fl_Pack::VERTICAL ); + o->spacing( 2 ); + { + Fl_Input *o = name_field = new Fl_Sometimes_Input( 2, 2, 144, 24 ); + o->color( color() ); + o->box( FL_FLAT_BOX ); + o->labeltype( FL_NO_LABEL ); + o->labelcolor( FL_GRAY0 ); + o->textcolor( FL_FOREGROUND_COLOR ); + o->value( name() ); + o->callback( cb_handle, (void*)this ); + + } + { Fl_Scalepack *o = new Fl_Scalepack( 7, 143, 110, 25 ); + o->type( Fl_Pack::HORIZONTAL ); + { Fl_Button* o = close_button = new Fl_Button(7, 143, 35, 25, "X"); + o->tooltip( "Remove strip" ); + o->type(0); + o->labeltype( FL_EMBOSSED_LABEL ); + o->color( FL_LIGHT1 ); + o->selection_color( FL_RED ); + o->labelsize(10); + o->callback( ((Fl_Callback*)cb_handle), this ); + } // Fl_Button* o + o->end(); + } // Fl_Group* o + { Fl_Flip_Button* o = prepost_button = new Fl_Flip_Button(61, 183, 45, 22, "narrow/wide"); + o->type(1); +// o->box(FL_ROUNDED_BOX); + o->box( FL_THIN_DOWN_BOX ); + o->color((Fl_Color)106); + o->selection_color((Fl_Color)65); + o->labeltype(FL_NORMAL_LABEL); + o->labelfont(0); + o->labelsize(14); + o->labelcolor(FL_FOREGROUND_COLOR); + o->align(FL_ALIGN_CLIP); + o->callback( ((Fl_Callback*)cb_handle), this ); + o->when(FL_WHEN_RELEASE); + } // Fl_Flip_Button* o +// { Fl_Pack* o = new Fl_Pack(8, 208, 103, 471); +// { Fl_Pack* o = gain_pack = new Fl_Pack(8, 208, 103, 516 ); + + o->end(); + } + + Fl_Pack *fader_pack; + + { Fl_Tabs *o = new Fl_Tabs( 4, 104, 110, 330 ); + o->clip_children( 1 ); + o->box( FL_NO_BOX ); + { Fl_Group *o = new Fl_Group( 4, 114, 110, 330, "Fader" ); + o->labelsize( 9 ); + o->box( FL_NO_BOX ); + { Fl_Pack* o = fader_pack = new Fl_Pack(4, 116, 103, 330 ); + o->spacing( 20 ); + o->type( Fl_Pack::HORIZONTAL ); + o->end(); + Fl_Group::current()->resizable(o); + } // Fl_Group* o + o->end(); + Fl_Group::current()->resizable(o); + } + { Fl_Group *o = new Fl_Group( 4, 114, 110, 330, "Signal" ); + o->labelsize( 9 ); + o->hide(); + { Chain *o = chain = new Chain( 4, 116, 110, 330 ); + o->labelsize( 10 ); + o->align( FL_ALIGN_TOP ); + o->color( FL_RED ); + o->configure_outputs_callback( configure_outputs, this ); + o->name( name() ); + o->initialize_with_default(); + Fl_Group::current()->resizable(o); + } + o->end(); + } + + o->end(); + Fl_Group::current()->resizable(o); + } + + { Fl_Pack *o = new Fl_Pack( 2, 440, 114, 40 ); + o->spacing( 2 ); + o->type( Fl_Pack::VERTICAL ); + + { Fl_Box *o = new Fl_Box( 0, 0, 100, 24 ); + o->align( (Fl_Align)(FL_ALIGN_BOTTOM | FL_ALIGN_INSIDE) ); + o->labelsize( 10 ); + o->label( "Pan" ); + } + { Panner* o = new Panner(0, 0, 110, 90); + o->box(FL_THIN_UP_BOX); + o->color(FL_GRAY0); + o->selection_color(FL_BACKGROUND_COLOR); + o->labeltype(FL_NORMAL_LABEL); + o->labelfont(0); + o->labelsize(11); + o->labelcolor(FL_FOREGROUND_COLOR); + o->align(FL_ALIGN_TOP); + o->when(FL_WHEN_RELEASE); + } // Panner* o + { + Controller_Module *m = new Controller_Module( 100, 24, "Inputs" ); + m->chain( chain ); + m->pad( false ); + m->connect_to( &chain->module( 0 )->control_input[1] ); + m->size( 33, 24 ); + } + o->end(); + } + + end(); + + color( FL_BLACK ); +// controllable( true ); + + { + Module *gain_module; + + { + Module *m = gain_module = new Gain_Module( 50, 50, "Gain" ); + m->initialize(); + chain->insert( chain->module( chain->modules() - 1 ), m ); + } + + { + Controller_Module *m = new Controller_Module( 100, 0, "Gain" ); + m->chain( chain ); + m->pad( false ); + m->connect_to( &gain_module->control_input[0] ); + m->size( 33, 0 ); + + fader_pack->add( m ); + } + + Module *meter_module; + + { + Module *m = meter_module = new Meter_Module( 50, 50, "Meter" ); + chain->insert( chain->module( chain->modules() - 1 ), m ); + } + { + Meter_Indicator_Module *m = new Meter_Indicator_Module( 100, 0, "" ); + m->chain( chain ); + m->pad( false ); + m->connect_to( &meter_module->control_output[0] ); + m->size( 58, 0 ); + m->clip_children( 0 ); + + fader_pack->add( m ); + + fader_pack->resizable( m ); + } + + chain->configure_ports(); + } + +} + + +int +Mixer_Strip::handle ( int m ) +{ + Logger log( this ); + + static Fl_Color orig_color; + + switch ( m ) + { + case FL_ENTER: +// orig_color = color(); +// color( FL_BLACK ); + redraw(); + return 1; + break; + case FL_LEAVE: +// color( orig_color ); + redraw(); + return 1; + break; + default: + return Fl_Group::handle( m ); + + } + + return 0; +} diff --git a/Mixer/Mixer_Strip.H b/Mixer/Mixer_Strip.H new file mode 100644 index 0000000..3a337e5 --- /dev/null +++ b/Mixer/Mixer_Strip.H @@ -0,0 +1,100 @@ +// generated by Fast Light User Interface Designer (fluid) version 1.0108 + +#ifndef Mixer_Strip_H +#define Mixer_Strip_H +#include +#include "DPM.H" +#include "Panner.H" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include "Fader.H" +#include + + +#include "../FL/Fl_Sometimes_Input.H" + +#include "Loggable.H" +class Chain; +class Fl_Flowpack; + +class Mixer_Strip : public Fl_Group, public Loggable { + +public: + + Mixer_Strip(int X, int Y, int W, int H, const char *L = 0); + Mixer_Strip( const char *name, int channels ); + Mixer_Strip(); /* for log create */ + ~Mixer_Strip(); + +// Fl_Value_Slider *gain_slider; + Fl_Flip_Button *prepost_button; + Fl_Button *close_button; + Fl_Input *name_field; + + Fl_Flowpack *controls_pack; + + Chain *chain; + + sample_t **chain_buffer; + int chain_buffers; + + nframes_t nframes; + +private: + + unsigned _color; + + void init ( ); + void cb_handle(Fl_Widget*); + static void cb_handle(Fl_Widget*, void*); + + void update_port_names ( void ); + +protected: + + void get ( Log_Entry &e ) const; + void set ( Log_Entry &e ); + + virtual int handle ( int m ); + +public: + + void color ( Fl_Color c ) + { + _color = c; + name_field->color( _color ); + name_field->redraw(); + } + + Fl_Color color ( void ) const + { + return name_field->color(); + } + + LOG_CREATE_FUNC( Mixer_Strip ); + + Fl_Counter *inputs_counter; + void process ( unsigned int nframes ); + + static void configure_outputs ( Fl_Widget *o, void *v ); + void configure_outputs ( void ); + + bool configure_ports ( int n ); + + void update ( void ); + + // int channels ( void ) const { return _in.size(); } + void name ( const char *name ); + const char *name ( void ) const { return label(); } +}; +#endif diff --git a/Mixer/Module.C b/Mixer/Module.C new file mode 100644 index 0000000..d97d888 --- /dev/null +++ b/Mixer/Module.C @@ -0,0 +1,169 @@ + +/*******************************************************************************/ +/* Copyright (C) 2009 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 "Module.H" +#include + +#include +#include + +#include "Module_Parameter_Editor.H" + + + +Module::~Module ( ) +{ + for ( unsigned int i = 0; i < audio_input.size(); ++i ) + audio_input[i].disconnect(); + for ( unsigned int i = 0; i < audio_output.size(); ++i ) + audio_output[i].disconnect(); + for ( unsigned int i = 0; i < control_input.size(); ++i ) + control_input[i].disconnect(); + for ( unsigned int i = 0; i < control_output.size(); ++i ) + control_output[i].disconnect(); + + audio_input.clear(); + audio_output.clear(); + control_input.clear(); + control_output.clear(); +} + + + +void +Module::draw_box ( void ) +{ + fl_color( FL_WHITE ); + + int tw, th, tx, ty; + + tw = w(); + th = h(); + ty = y(); + tx = x(); + +// bbox( tx, ty, tw, th ); + + fl_push_clip( tx, ty, tw, th ); + + int spacing = w() / instances(); + for ( int i = instances(); i--; ) + { + fl_draw_box( box(), tx + (spacing * i), ty, tw / instances(), th, Fl::belowmouse() == this ? fl_lighter( color() ) : color() ); + } + + if ( audio_input.size() && audio_output.size() ) + { + /* maybe draw control indicators */ + if ( control_input.size() ) + fl_draw_box( FL_ROUNDED_BOX, tx + 4, ty + 4, 5, 5, is_being_controlled() ? FL_YELLOW : fl_inactive( FL_YELLOW ) ); + if ( control_output.size() ) + fl_draw_box( FL_ROUNDED_BOX, tx + tw - 8, ty + 4, 5, 5, is_controlling() ? FL_YELLOW : fl_inactive( FL_YELLOW ) ); + } + + fl_pop_clip(); +// box( FL_NO_BOX ); + + Fl_Group::draw_children(); +} + +void +Module::draw_label ( void ) +{ + int tw, th, tx, ty; + + bbox( tx, ty, tw, th ); + + const char *lp = label(); + + int l = strlen( label() ); + + fl_color( FL_FOREGROUND_COLOR ); + char *s = NULL; + + if ( l > 10 ) + { + s = new char[l]; + char *sp = s; + + for ( ; *lp; ++lp ) + switch ( *lp ) + { + case 'i': case 'e': case 'o': case 'u': case 'a': + break; + default: + *(sp++) = *lp; + } + *sp = '\0'; + + } + + if ( l > 20 ) + fl_font( FL_HELVETICA, 10 ); + else + fl_font( FL_HELVETICA, 14 ); + + fl_draw( s ? s : lp, tx, ty, tw, th, (Fl_Align)(FL_ALIGN_CENTER | FL_ALIGN_INSIDE) ); + + + if ( s ) + delete[] s; +} + +#include "FL/test_press.H" + +int +Module::handle ( int m ) +{ + switch ( m ) + { + case FL_PUSH: + { + if ( test_press( FL_BUTTON1 ) ) + { + if ( _editor ) + { + _editor->show(); + } + else if ( ncontrol_inputs() ) + { + + DMESSAGE( "Opening module parameters for \"%s\"", label() ); + _editor = new Module_Parameter_Editor( this ); + + _editor->show(); + + do { Fl::wait(); } + while ( _editor->shown() ); + + DMESSAGE( "Module parameters for \"%s\" closed",label() ); + + delete _editor; + + _editor = NULL; + } + + return 1; + } + break; + } + } + + return Fl_Group::handle( m ); +} diff --git a/Mixer/Module.H b/Mixer/Module.H new file mode 100644 index 0000000..9a94aab --- /dev/null +++ b/Mixer/Module.H @@ -0,0 +1,321 @@ + +/*******************************************************************************/ +/* Copyright (C) 2009 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 +#include +#include +#include + +#include +#include "util/debug.h" +#include + +#include "util/Thread.H" + +class Chain; +class Module_Parameter_Editor; + +class Module : public Fl_Group { + + void init ( void ) + { + _editor = 0; + _chain = 0; + _instances = 1; + box( FL_UP_BOX ); + labeltype( FL_NO_LABEL ); + clip_children( 1 ); + } + + int _ins; + int _outs; + int _instances; + unsigned long _nframes; + Chain *_chain; + + Module_Parameter_Editor *_editor; + + void cb_handle(Fl_Widget*); + static void cb_handle(Fl_Widget*, void*); + + + +public: + + class Port + { + /* char *type_names[] = { "Audio", "Control" }; */ + /* char *direction_names[] = { "Input", "Output" }; */ + + + public: + + enum Direction { INPUT, OUTPUT }; + enum Type { AUDIO, CONTROL }; + + /* hints for control ports (specifically control inputs) */ + struct Hints + { + enum Type { LINEAR, LOGARITHMIC, BOOLEAN, INTEGER }; + + Type type; + bool ranged; + float minimum; + float maximum; + float default_value; + int dimensions; + + Hints ( ) + { + type = LINEAR; + ranged = false; + minimum = 0; + maximum = 0; + default_value = 0.0f; + dimensions = 1; + } + }; + + Hints hints; + + Port ( Module *module, Direction direction, Type type, const char *name = 0 ) + { + _name = name; + _direction = direction; + _type = type; + _buf = 0; + _nframes = 0; + _connected = 0; + _module = module; + } + + Port ( const Port& p ) + { + _name = p._name; + _direction = p._direction; + _type = p._type; + _buf = p._buf; + _nframes = p._nframes; + _connected = p._connected; + _module = p._module; + hints = p.hints; + } + + virtual ~Port ( ) + { + // disconnect(); + } + + const char *name ( void ) const { return _name; } + Type type ( void ) const { return _type; } + Direction direction ( void ) const { return _direction; } + + Module * module ( void ) const { return _module; } + unsigned long nframes ( void ) const { return _nframes; } + + void buffer ( void *buf, unsigned long nframes ) { _buf = buf; _nframes = nframes; }; + void *buffer ( void ) const { return _buf; } + + void control_value_no_callback ( float f ) + { + THREAD_ASSERT( UI ); + + if ( buffer() ) + { + *((float*)buffer()) = f; + } + } + + void control_value ( float f ) + { + control_value_no_callback( f ); + _module->handle_control_changed( this ); + } + + float control_value ( ) const + { + if ( buffer() ) + return *((float*)buffer()); + else + return 0.0f; + } + + bool connected ( void ) const { return _connected; } + + Port *connected_port ( void ) const + { + return _connected; + } + + void connect_to ( Port *to ) + { + _buf = to->_buf; + to->_connected = this; + _connected = to; + } + + void connect_to ( void *buf ) + { + _buf = buf; +// _connected = (Port*)0x01; + } + + void disconnect ( void ) + { + if ( _connected && _connected != (void*)0x01 ) + { + _connected->_connected = NULL; + _connected = NULL; + } + else + _connected = NULL; + + /* FIXME: do something! */ + } + + private: + + Port *_connected; + Type _type; + Direction _direction; + const char *_name; + void *_buf; + unsigned long _nframes; + Module *_module; + }; + + void bbox ( int &X, int &Y, int &W, int &H ) + { + X = x() + 5; + Y = y() + 5; + W = w() - 10; + H = h() - 10; + } + + Module ( int W, int H, const char *L = 0 ) : Fl_Group( 0, 0, W, H, L ) + { + init(); + } + Module ( ) : Fl_Group( 0, 0, 0, 50, "Unnamed" ) + { + init(); + } + virtual ~Module ( ); + +// static Module * pick_plugin ( void ); + + + unsigned long nframes ( void ) const { return _nframes; } + void nframes ( unsigned long v ) { _nframes = v; } + + + int instances ( void ) const { return _instances; } + + bool is_being_controlled ( void ) const + { + for ( unsigned int i = control_input.size(); i--; ) + if ( control_input[i].connected() ) + return true; + return false; + } + + bool is_controlling ( void ) const + { + for ( unsigned int i = control_output.size(); i--; ) + if ( control_output[i].connected() ) + return true; + return false; + } + + virtual const char *name ( void ) const = 0; + + std::vector audio_input; + std::vector audio_output; + std::vector control_input; + std::vector control_output; + + void add_port ( const Port &p ) + { + if ( p.type() == Port::AUDIO && p.direction() == Port::INPUT ) + audio_input.push_back( p ); + else if ( p.type() == Port::AUDIO && p.direction() == Port::OUTPUT ) + audio_output.push_back( p ); + else if ( p.type() == Port::CONTROL && p.direction() == Port::INPUT ) + control_input.push_back( p ); + else if ( p.type() == Port::CONTROL && p.direction() == Port::OUTPUT ) + control_output.push_back( p ); + } + + int noutputs ( void ) const + { + return audio_output.size(); + } + + int ninputs ( void ) const + { + return audio_input.size(); + } + + int ncontrol_inputs ( void ) const + { + return control_input.size(); + } + + int ncontrol_outputs ( void ) const + { + return control_output.size(); + } + + + Chain *chain ( void ) const { return _chain; } + void chain ( Chain * v ) { _chain = v; } + + virtual bool initialize ( void ) { return true; } + + /* for the given number of inputs, return how many outputs this + * plugin would have. -1 if this plugin can't support so many + * inputs. */ + virtual int can_support_inputs ( int n ) = 0; + /* called by the chain whenever we need to adjust our input + * channel configuration, but only if can_support_inputs() returns + * true */ + virtual bool configure_inputs ( int n ) = 0; + + virtual void process ( void ) { } + + /* called whenever the value of a control port is changed. + This can be used to take appropriate action from the GUI thread */ + virtual void handle_control_changed ( Port * ) { } + + /* called whenever the name of the chain changes (usually because + * the name of the mixer strip changed). */ + virtual void handle_chain_name_changed () {} + +protected: + + void draw_connections ( void ); + void draw_label ( void ); + void draw_box ( void ); + + virtual void draw ( void ) { Module::draw_box(); Module::draw_label(); } + virtual int handle ( int m ); + +}; diff --git a/Mixer/Module_Parameter_Editor.C b/Mixer/Module_Parameter_Editor.C new file mode 100644 index 0000000..bdcc974 --- /dev/null +++ b/Mixer/Module_Parameter_Editor.C @@ -0,0 +1,293 @@ + +/*******************************************************************************/ +/* Copyright (C) 2009 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 +#include +#include +#include "FL/Fl_Arc_Dial.H" +#include "FL/Boxtypes.H" +#include "FL/Fl_Flowpack.H" +#include "FL/Fl_Labelpad_Group.H" +#include "FL/Fl_Choice.H" +#include "Module.H" +#include "Module_Parameter_Editor.H" +#include "Controller_Module.H" +#include "Chain.H" +#include +#include +#include +#include + + + + +Module_Parameter_Editor::Module_Parameter_Editor ( Module *module ) : Fl_Double_Window( 0, 0, 800, 600 ) +{ + _module = module; + _resized = false; + + char lab[256]; + if ( strcmp( module->name(), module->label() ) ) + { + snprintf( lab, sizeof( lab ), "%s : %s", module->name(), module->label() ); + } + else + strcpy( lab, module->label() ); + + + char title[512]; + snprintf( title, sizeof( title ), "%s - %s - %s", "Mixer", module->chain()->name(), lab ); + + label( title ); + + { Fl_Pack *o = main_pack = new Fl_Pack( 0, y(), w(), h() - 10 ); + o->type( FL_VERTICAL ); + o->label( strdup( lab ) ); + o->labeltype( FL_SHADOW_LABEL ); + o->labelsize( 18 ); + o->align( (Fl_Align)(FL_ALIGN_TOP | FL_ALIGN_RIGHT | FL_ALIGN_INSIDE ) ); + + { Fl_Pack *o = new Fl_Pack( 0, 0, 50, 25 ); + o->type( FL_HORIZONTAL ); + o->spacing( 20 ); + { Fl_Choice *o = mode_choice = new Fl_Choice( 0, 0, 200, 25 ); + o->add( "Knobs" ); + o->add( "Horizontal Sliders" ); + o->add( "Vertical Sliders" ); + o->value( 0 ); + o->when( FL_WHEN_CHANGED ); + o->callback( cb_mode_handle, this ); + } +/* { Fl_Box *o = new Fl_Box( 0, 0, 300, 25 ); */ +/* o->box( FL_ROUNDED_BOX ); */ +/* o->color( FL_YELLOW ); */ +/* o->label( strdup( lab ) ); */ +/* o->labeltype( FL_SHADOW_LABEL ); */ +/* o->labelsize( 18 ); */ +/* o->align( (Fl_Align)(FL_ALIGN_TOP | FL_ALIGN_RIGHT | FL_ALIGN_INSIDE ) ); */ +/* // Fl_Group::current()->resizable( o ); */ +/* } */ + o->end(); + } + { Fl_Group *o = new Fl_Group( 0, 0, w(), h() ); + { Fl_Flowpack *o = control_pack = new Fl_Flowpack( 50, 0, w() - 100, h() ); +/* o->box( FL_ROUNDED_BOX ); */ +/* o->color( FL_GRAY ); */ + o->vspacing( 10 ); + o->hspacing( 10 ); + o->end(); + } + o->resizable( 0 ); + o->end(); + } + o->end(); + } + + end(); + +// draw(); + + make_controls(); +} + +Module_Parameter_Editor::~Module_Parameter_Editor ( ) +{ + + +} + + + +void +Module_Parameter_Editor::make_controls ( void ) +{ + Module *module = _module; + + control_pack->clear(); + + for ( unsigned int i = 0; i < module->control_input.size(); ++i ) + { + Fl_Widget *w; + + Module::Port *p = &module->control_input[i]; + + if ( p->hints.type == Module::Port::Hints::BOOLEAN ) + { + Fl_Button *o = new Fl_Button( 0, 0, 30, 30, p->name() ); + w = o; + o->selection_color( FL_GREEN ); + o->type( FL_TOGGLE_BUTTON ); + o->value( p->control_value() ); + + } + else if ( p->hints.type == Module::Port::Hints::INTEGER ) + { + + Fl_Counter *o = new Fl_Counter(0, 0, 58, 24, p->name() ); + w = o; + + o->type(1); + o->step(1); + + if ( p->hints.ranged ) + { + o->minimum( p->hints.minimum ); + o->maximum( p->hints.maximum ); + } + + o->value( p->control_value() ); + + } + else + { + if ( mode_choice->value() == 0 ) + { + Fl_Arc_Dial *o = new Fl_Arc_Dial( 0, 0, 50, 50, p->name() ); + w = o; + if ( p->hints.ranged ) + { + o->minimum( p->hints.minimum ); + o->maximum( p->hints.maximum ); + } + + o->box( FL_BURNISHED_OVAL_BOX ); + o->color( fl_darker( fl_darker( FL_GRAY ) ) ); + o->selection_color( FL_WHITE ); + o->value( p->control_value() ); + + } + else + { + Fl_Value_Slider *o = new Fl_Value_Slider( 0, 0, 120, 24, p->name() ); + w = o; + if ( p->hints.ranged ) + { + o->minimum( p->hints.minimum ); + o->maximum( p->hints.maximum ); + } + + if ( mode_choice->value() == 1 ) + { + o->type( FL_HORIZONTAL ); + o->size( 120, 24 ); + } + else + { + o->type( FL_VERTICAL ); + o->size( 24, 120 ); + } + + o->slider( FL_THIN_UP_BOX ); + o->color( fl_darker( fl_darker( FL_GRAY ) ) ); + o->selection_color( FL_WHITE ); + o->value( p->control_value() ); + } + + } + + Fl_Button *bound; + + w->align(FL_ALIGN_TOP); + w->labelsize( 10 ); + w->callback( cb_value_handle, new callback_data( this, i ) ); + + { Fl_Group *o = new Fl_Group( 0, 0, 50, 75 ); + { + Fl_Labelpad_Group *flg = new Fl_Labelpad_Group( w ); + + { Fl_Button *o = bound = new Fl_Button( 0, 50, 14, 14 ); + o->selection_color( FL_YELLOW ); + o->type( 0 ); + o->labelsize( 8 ); + + o->value( p->connected() ); + + o->callback( cb_bound_handle, new callback_data( this, i ) ); + } + + o->resizable( 0 ); + o->end(); + + flg->position( o->x(), o->y() ); + bound->position( o->x(), flg->y() + flg->h() ); + o->size( flg->w(), flg->h() + bound->h() ); + o->init_sizes(); + } + control_pack->add( o ); + } + + } + + main_pack->size( control_pack->max_width() + 100, control_pack->h() + 50 ); + size( control_pack->max_width() + 100, control_pack->h() + 50 ); +} + +void +Module_Parameter_Editor::cb_value_handle ( Fl_Widget *w, void *v ) +{ + callback_data *cd = (callback_data*)v; + + cd->base_widget->set_value( cd->port_number, ((Fl_Valuator*)w)->value() ); +} + +void +Module_Parameter_Editor::cb_mode_handle ( Fl_Widget *w, void *v ) +{ + ((Module_Parameter_Editor*)v)->make_controls(); +} + +void +Module_Parameter_Editor::cb_bound_handle ( Fl_Widget *w, void *v ) +{ + callback_data *cd = (callback_data*)v; + + Fl_Button *fv = (Fl_Button*)w; + + fv->value( 1 ); + + cd->base_widget->bind_control( cd->port_number ); +} + +void +Module_Parameter_Editor::bind_control ( int i ) +{ + Module::Port *p = &_module->control_input[i]; + + if ( p->connected() ) + /* can only bind once */ + return; + + Controller_Module *o = new Controller_Module( 50, 50, p->name() ); + o->chain( _module->chain() ); + + o->connect_to( p ); + + _module->chain()->add_control( o ); + _module->redraw(); +} + + +void +Module_Parameter_Editor::set_value (int i, float value ) +{ + _module->control_input[i].control_value( value ); + _module->handle_control_changed( &_module->control_input[i] ); +} diff --git a/Mixer/Module_Parameter_Editor.H b/Mixer/Module_Parameter_Editor.H new file mode 100644 index 0000000..471e302 --- /dev/null +++ b/Mixer/Module_Parameter_Editor.H @@ -0,0 +1,62 @@ + +/*******************************************************************************/ +/* Copyright (C) 2009 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 +class Fl_Pack; +class Fl_Flowpack; +class Fl_Choice; +class Module; + +class Module_Parameter_Editor : public Fl_Double_Window +{ + Module *_module; + + struct callback_data + { + Module_Parameter_Editor *base_widget; + int port_number; + + callback_data ( Module_Parameter_Editor *base_widget, int port_number ) + { + this->base_widget = base_widget; + this->port_number = port_number; + } + }; + + static void cb_value_handle ( Fl_Widget *w, void *v ); + static void cb_mode_handle ( Fl_Widget *w, void *v ); + static void cb_bound_handle ( Fl_Widget *w, void *v ); + void set_value (int i, float value ); + void bind_control ( int i ); + void make_controls ( void ); + + Fl_Pack *main_pack; + Fl_Flowpack *control_pack; + Fl_Choice *mode_choice; + bool _resized; + + + +public: + + Module_Parameter_Editor ( Module *module ); + virtual ~Module_Parameter_Editor ( ); +}; diff --git a/Mixer/Panner.C b/Mixer/Panner.C new file mode 100644 index 0000000..ea176b0 --- /dev/null +++ b/Mixer/Panner.C @@ -0,0 +1,283 @@ + +/*******************************************************************************/ +/* Copyright (C) 2008 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 "Panner.H" + +#include +#include + +/* 2D Panner widget. Supports various multichannel configurations. */ + + +/* multichannel layouts, in degrees */ +int Panner::_configs[][12] = +{ + /* none, error condition? */ + { NONE }, + /* mono, panner disabled */ + { NONE }, + /* stereo */ + { L, R }, + /* stereo + mono */ + { L, R, C }, + /* quad */ + { FL, FR, RL, RR }, + /* 5.1 */ + { FL, FR, RL, RR, C }, + /* no such config */ + { NONE }, + /* 7.1 */ + { FL, FR, RL, RR, C, L, R }, +}; + + +/* speaker symbol */ +#define BP fl_begin_polygon() +#define EP fl_end_polygon() +#define BCP fl_begin_complex_polygon() +#define ECP fl_end_complex_polygon() +#define BL fl_begin_line() +#define EL fl_end_line() +#define BC fl_begin_loop() +#define EC fl_end_loop() +#define vv(x,y) fl_vertex(x,y) + +static void draw_speaker ( Fl_Color col ) +{ + fl_color(col); + + BP; vv(0.2,0.4); vv(0.6,0.4); vv(0.6,-0.4); vv(0.2,-0.4); EP; + BP; vv(-0.6,0.8); vv(0.2,0.0); vv(-0.6,-0.8); EP; + + fl_color( fl_darker( col ) ); + + BC; vv(0.2,0.4); vv(0.6,0.4); vv(0.6,-0.4); vv(0.2,-0.4); EC; + BC; vv(-0.6,0.8); vv(0.2,0.0); vv(-0.6,-0.8); EC; +} + + +/** 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 +{ + int tx, ty, tw, th; + + bbox( tx, ty, tw, th ); + + tw -= pw(); + th -= ph(); + + float px, py; + + p->axes( &px, &py ); + + *X = tx + ((tw / 2) * px + (tw / 2)); + *Y = ty + ((th / 2) * py + (th / 2)); + + *W = pw(); + *H = ph(); +} + +Panner::Point * +Panner::event_point ( void ) +{ + for ( int i = _ins; i--; ) + { + int px, py, pw, ph; + + Point *p = &_points[ i ]; + + point_bbox( p, &px, &py, &pw, &ph ); + +// printf( "%d, %d -- %d %d %d %d\n", Fl::event_x(), Fl::event_y(), px, py, pw, ph ); + + if ( Fl::event_inside( px, py, pw, ph ) ) + return p; + } + + return NULL; +} + +void +Panner::draw ( void ) +{ + draw_box(); +// draw_box( FL_FLAT_BOX, x(), y(), w(), h(), FL_BLACK ); + draw_label(); + + + if ( _bypassed ) + { + fl_color( 0 ); + fl_font( FL_HELVETICA, 12 ); + fl_draw( "(bypass)", x(), y(), w(), h(), FL_ALIGN_CENTER ); + return; + } + + int tw, th, tx, ty; + + bbox( tx, ty, tw, th ); + + fl_push_clip( tx, ty, tw, th ); + + fl_color( FL_WHITE ); + + const int b = 10; + + tx += b; + ty += b; + tw -= b * 2; + th -= b * 2; + + fl_arc( tx, ty, tw, th, 0, 360 ); + + if ( _configs[ _outs ][0] >= 0 ) + { + for ( int i = _outs; i--; ) + { + int a = _configs[ _outs ][ i ]; + + Point p( 1.2f, (float)a ); + + float px, py; + + p.axes( &px, &py ); + + fl_push_matrix(); + + const int bx = tx + ((tw / 2) * px + (tw / 2)); + const int by = ty + ((th / 2) * py + (th / 2)); + + fl_translate( bx, by ); + + fl_scale( 5, 5 ); + + a = 90 - a; + + fl_rotate( a ); + + draw_speaker( FL_WHITE ); + + fl_rotate( -a ); + + fl_pop_matrix(); + + } + } + + /* ensure that points are drawn *inside* the circle */ + + for ( int i = _ins; i--; ) + { + Point *p = &_points[ i ]; + + Fl_Color c = (Fl_Color)(10 + i); + + int px, py, pw, ph; + point_bbox( p, &px, &py, &pw, &ph ); + + /* draw point */ + fl_color( c ); + fl_pie( px, py, pw, ph, 0, 360 ); + + /* draw echo */ + fl_color( c = fl_darker( c ) ); + fl_arc( px - 5, py - 5, pw + 10, ph + 10, 0, 360 ); + fl_color( c = fl_darker( c ) ); + fl_arc( px - 10, py - 10, pw + 20, ph + 20, 0, 360 ); + fl_color( c = fl_darker( c ) ); + fl_arc( px - 30, py - 30, pw + 60, ph + 60, 0, 360 ); + + /* draw number */ + char pat[4]; + snprintf( pat, 4, "%d", i + 1 ); + + fl_color( FL_BLACK ); + fl_font( FL_HELVETICA, ph + 2 ); + fl_draw( pat, px + 1, py + 1, pw - 1, ph - 1, FL_ALIGN_CENTER ); + + /* draw line */ + +/* fl_color( FL_WHITE ); */ +/* fl_line( bx + pw() / 2, by + ph() / 2, tx + (tw / 2), ty + (th / 2) ); */ + + } + + fl_pop_clip(); +} + +/* return the current gain setting for the path in/out */ +Panner::Point +Panner::point( int i ) +{ + return _points[ i ]; +} + +int +Panner::handle ( int m ) +{ + static Point *drag; + + int r = Fl_Widget::handle( m ); + + switch ( m ) + { + + case FL_PUSH: + + if ( Fl::event_button2() ) + { + _bypassed = ! _bypassed; + redraw(); + return 0; + } + else if ( Fl::event_button1() && ( drag = event_point() ) ) + return 1; + else + return 0; + case FL_RELEASE: + drag = NULL; + return 1; + case FL_DRAG: + { + float X = Fl::event_x() - x(); + float Y = Fl::event_y() - y(); + + int tx, ty, tw, th; + bbox( tx, ty, tw, th ); + +/* 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 ); + + + printf( "%f %f\n", drag->a, drag->d ); + + redraw(); + + return 1; + } + + } + + return r; + +// return 0; +} diff --git a/Mixer/Panner.H b/Mixer/Panner.H new file mode 100644 index 0000000..8581e57 --- /dev/null +++ b/Mixer/Panner.H @@ -0,0 +1,172 @@ + +/*******************************************************************************/ +/* Copyright (C) 2008 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 +#include +#include + +#include + +#include +using namespace std; + +class Panner : public Fl_Widget +{ + + struct Point + { + /* axes */ + + /* distance from center (from 0 to 1) */ + float d; + /* angle */ + float a; + + Point ( ) : d( 0.0f ), a( 0.0f ) { } + Point ( float D, float A ) : d( D ), a( A ) { } + + /** translate angle /a/ into x/y coords and place the result in /X/ and /Y/ */ + void + axes ( float *X, float *Y ) const + { + /* rotate */ + float A = ( 270 - a ) * ( M_PI / 180 ); + + *X = -d * cosf( A ); + *Y = d * sinf( A ); + } + + void + angle ( float X1, float Y1 ) + { + + float X2, Y2; + + 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; + } + + /** 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 ) ); + } + + }; + + + /* channel configuration */ + int _ins, + _outs; + + bool _bypassed; + + vector _points; + + static int pw ( void ) { return 12; } + static int ph ( void ) { return 12; } + + static int _configs[][12]; + + void bbox ( int &X, int &Y, int &W, int &H ) const + { + W = w() - Fl::box_dw( box() ); + H = h() - Fl::box_dh( box() ); + X = x() + Fl::box_dx( box() ); + Y = y() + Fl::box_dy( box() ); + } + + void point_bbox ( const Point *p, int *X, int *Y, int *W, int *H ) const; + + Point * event_point ( void ); + Point angle_to_axes ( float a ); + + enum { + NONE = -1, + R = 90, + L = 270, + C = 0, + FL = 315, + FR = 45, + RL = 225, + RR = 135, + }; + + +protected: + + virtual void draw ( void ); + virtual int handle ( int ); + +public: + + + Panner ( int X, int Y, int W, int H, const char *L = 0 ) : + Fl_Widget( X, Y, W, H, L ) + { + _bypassed = false; + + _ins = 1; + + _outs = 1; + +// _ins = _outs = 4; + +// _points.push_back( Point( 1, FL ) ); + _points.push_back( Point( 1, C ) ); + +/* _points.push_back( Point( 1, FR ) ); */ +/* _points.push_back( Point( 1, RL ) ); */ +/* _points.push_back( Point( 1, RR ) ); */ + + + } + + virtual ~Panner ( ) { } + + Panner::Point point ( int i ); + +}; diff --git a/Mixer/Plugin_Module.C b/Mixer/Plugin_Module.C new file mode 100644 index 0000000..b970718 --- /dev/null +++ b/Mixer/Plugin_Module.C @@ -0,0 +1,577 @@ + +/*******************************************************************************/ +/* Copyright (C) 2009 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. */ +/*******************************************************************************/ + +/* Filter module. Can host LADPSA Plugins, or can be inherited from to make internal + modules with special features and appearance. */ + +#include "Plugin_Module.H" + +#include +#include + +#include "util/debug.h" + +#include + +#define HAVE_LIBLRDF 1 +#include "LADSPAInfo.h" +#include +#include +#include +#include +#include + +#include "Engine/Engine.H" + + + +static LADSPAInfo *ladspainfo; + +/* keep this out of the header to avoid spreading ladspa.h dependency */ +struct Plugin_Module::ImplementationData +{ + const LADSPA_Descriptor *descriptor; +// std::vector m_LADSPABufVec; + LADSPA_Handle handle; +}; + + + + +Plugin_Module::Plugin_Module ( int , int , const char *L ) : Module( 50, 50, L ) +{ + init(); + + end(); +} + +Plugin_Module::~Plugin_Module ( ) +{ + +} + + + + +/* void */ +/* Plugin_Module::detect_plugins ( void ) */ +/* { */ +/* LADPSAInfo *li = new LADSPAInfo(); */ +/* } */ + +#include + +/* allow the user to pick a plugin */ +Plugin_Module * +Plugin_Module::pick_plugin ( void ) +{ + + /**************/ + /* build menu */ + /**************/ + + Fl_Menu_Button *menu = new Fl_Menu_Button( 0, 0, 400, 400 ); + menu->type( Fl_Menu_Button::POPUP3 ); + + Plugin_Module::Plugin_Info *pia = Plugin_Module::discover(); + + for ( Plugin_Module::Plugin_Info *pi = pia; pi->path; ++pi ) + { + menu->add(pi->path, 0, NULL, pi, 0 ); + } + + menu->popup(); + + if ( menu->value() <= 0 ) + return NULL; + + /************************/ + /* load selected plugin */ + /************************/ + + Plugin_Module::Plugin_Info *pi = (Plugin_Module::Plugin_Info*)menu->menu()[ menu->value() ].user_data(); + + Plugin_Module *m = new Plugin_Module( 50, 50 ); + +// Plugin_Module *plugin = new Plugin_Module(); + + m->load( pi ); + + const char *plugin_name = pi->path; + + char *label = strdup( rindex(plugin_name, '/') + 1 ); + + m->label( label ); + + delete[] pia; + + return m; +} + + +void +Plugin_Module::init ( void ) +{ + _idata = new Plugin_Module::ImplementationData(); + _active = false; + _crosswire = true; + + _instances = 1; +// box( FL_ROUNDED_BOX ); +// box( FL_NO_BOX ); + align( (Fl_Align)FL_ALIGN_CENTER | FL_ALIGN_INSIDE ); + color( (Fl_Color)fl_color_average( FL_BLUE, FL_GREEN, 0.5f ) ); + int tw, th, tx, ty; + + bbox( tx, ty, tw, th ); +} + +#include "FL/test_press.H" + +int +Plugin_Module::handle ( int m ) +{ + switch ( m ) + { + case FL_ENTER: + case FL_LEAVE: + redraw(); + return 1; + break; + default: + return Module::handle( m ); + } + + return 0; +} + +/* There are two possible adaptations that can be made at Plugin_Module input to account for a mismatch + between channel configurations. + + The two scenarios are as follows. + + 1. The preceding module has fewer outputs than this module has inputs. If + the preceding module has 1 output (MONO) then it will be duplicated + for this module's addition inputs. If the preceding module has more + than one output, then the chain is in error. + + 2. The preceding module has more outputs than this module has inputs + If this module has 1 output (MONO) then it will create the required number of + instances of its plugin. + + + Stereo plugins are never run with more than one instance. Mono + plugins will have their outputs brought up to stereo for plugins with + stereo input. + +*/ + +int +Plugin_Module::can_support_inputs ( int n ) +{ + /* this is the simple case */ + if ( plugin_ins() == n ) + return plugin_outs(); + /* e.g. MONO going into STEREO */ + /* we'll duplicate our inputs */ + else if ( n < plugin_ins() && + 1 == n ) + { + return plugin_outs(); + } + /* e.g. STEREO going into MONO */ + /* we'll run multiple instances of the plugin */ + else if ( n > plugin_ins() && + plugin_ins() == 1 && plugin_outs() == 1 ) + { + return plugin_outs() * n; +// instances( i ); + } + + return -1; +} + +bool +Plugin_Module::configure_inputs( int n ) +{ + if ( 1 == n && plugin_ins() > 1 ) + { + _crosswire = true; + audio_input.clear(); + audio_input.push_back( Port( this, Port::INPUT, Port::AUDIO ) ); + } + +/* audio_input.clear(); */ +/* audio_output.clear(); */ + +/* for ( int i = 0; i < n; ++i ) */ +/* { */ +/* add_port( Port( Port::INPUT, Port::AUDIO ) ); */ +/* } */ + +/* if ( n > plugin_ins() ) */ +/* { */ +/* /\* multiple instances *\/ */ +/* instances( n / plugin_ins() ); */ +/* } */ +/* else if ( n < plugin_ins() ) */ +/* { */ +/* /\* duplication of input *\/ */ + + +/* } */ + +/* for ( int i = 0; i < plugin_outs() * instances(); ++i ) */ +/* { */ +/* add_port( Port( this, Port::OUTPUT, Port::AUDIO ) ); */ +/* } */ + + if ( ! _active ) + activate(); +/* // _plugin->deactivate(); */ +/* /\* if ( _plugin->active() ) *\/ */ +/* /\* _plugin->activate(); *\/ */ + + /* FIXME: do controls */ + + return true; +} + +/* return a list of available plugins */ +Plugin_Module::Plugin_Info * +Plugin_Module::discover ( void ) +{ + if ( !ladspainfo ) + ladspainfo = new LADSPAInfo(); + + std::vector plugins = ladspainfo->GetMenuList(); + + Plugin_Info* pi = new Plugin_Info[plugins.size() + 1]; + + int j = 0; + for (std::vector::iterator i=plugins.begin(); + i!=plugins.end(); i++, j++) + { + pi[j].path = i->Name.c_str(); + pi[j].id = i->UniqueID; + } + + return pi; +} + +bool +Plugin_Module::load ( Plugin_Module::Plugin_Info *pi ) +{ + _idata->descriptor = ladspainfo->GetDescriptorByID( pi->id ); + + _plugin_ins = _plugin_outs = 0; + + if ( _idata->descriptor ) + { + if ( LADSPA_IS_INPLACE_BROKEN( _idata->descriptor->Properties ) ) + { + WARNING( "Cannot use this plugin because it is incapable of processing audio in-place" ); + return false; + } + else if ( ! LADSPA_IS_HARD_RT_CAPABLE( _idata->descriptor->Properties ) ) + { + WARNING( "Cannot use this plugin because it is incapable of hard real-time operation" ); + return false; + } + + /* FIXME: bogus rate */ + if ( ! (_idata->handle = _idata->descriptor->instantiate( _idata->descriptor, engine->sample_rate() ) ) ) + { + WARNING( "Failed to load plugin" ); + return false; + } +// _idata->descriptor->activate( _idata->handle ); + + MESSAGE( "Name: %s", _idata->descriptor->Name ); + + for ( unsigned int i = 0; i < _idata->descriptor->PortCount; ++i ) + { + if ( LADSPA_IS_PORT_AUDIO( _idata->descriptor->PortDescriptors[i] ) ) + { + if ( LADSPA_IS_PORT_INPUT( _idata->descriptor->PortDescriptors[i] ) ) + { + add_port( Port( this, Port::INPUT, Port::AUDIO, _idata->descriptor->PortNames[ i ] ) ); + _plugin_ins++; + } + else if (LADSPA_IS_PORT_OUTPUT(_idata->descriptor->PortDescriptors[i])) + { + _plugin_outs++; + add_port( Port( this, Port::OUTPUT, Port::AUDIO, _idata->descriptor->PortNames[ i ] ) ); + } + } + } + + MESSAGE( "Plugin has %i inputs and %i outputs", _plugin_ins, _plugin_outs); + + for ( unsigned int i = 0; i < _idata->descriptor->PortCount; ++i ) + { + if ( LADSPA_IS_PORT_CONTROL( _idata->descriptor->PortDescriptors[i] ) ) + { + Port::Direction d; + + if ( LADSPA_IS_PORT_INPUT( _idata->descriptor->PortDescriptors[i] ) ) + { + d = Port::INPUT; + } + else if ( LADSPA_IS_PORT_OUTPUT( _idata->descriptor->PortDescriptors[i] ) ) + { + d = Port::OUTPUT; + } + + Port p( this, d, Port::CONTROL, _idata->descriptor->PortNames[ i ] ); + + + LADSPA_PortRangeHintDescriptor hd = _idata->descriptor->PortRangeHints[i].HintDescriptor; + + if ( LADSPA_IS_HINT_BOUNDED_BELOW(hd) ) + { + p.hints.ranged = true; + p.hints.minimum = _idata->descriptor->PortRangeHints[i].LowerBound; + } + if ( LADSPA_IS_HINT_BOUNDED_ABOVE(hd) ) + { + p.hints.ranged = true; + p.hints.maximum = _idata->descriptor->PortRangeHints[i].UpperBound; + } + + if ( LADSPA_IS_HINT_HAS_DEFAULT(hd) ) + { + + float Max=1.0f, Min=-1.0f, Default=0.0f; + int Port=i; + + // Get the bounding hints for the port + LADSPA_PortRangeHintDescriptor HintDesc=_idata->descriptor->PortRangeHints[Port].HintDescriptor; + if (LADSPA_IS_HINT_BOUNDED_BELOW(HintDesc)) + { + Min=_idata->descriptor->PortRangeHints[Port].LowerBound; + if (LADSPA_IS_HINT_SAMPLE_RATE(HintDesc)) + { + Min*=engine->sample_rate(); + } + } + if (LADSPA_IS_HINT_BOUNDED_ABOVE(HintDesc)) + { + Max=_idata->descriptor->PortRangeHints[Port].UpperBound; + if (LADSPA_IS_HINT_SAMPLE_RATE(HintDesc)) + { + Max*=engine->sample_rate(); + } + } + +#ifdef LADSPA_VERSION +// We've got a version of the header that supports port defaults + if (LADSPA_IS_HINT_HAS_DEFAULT(HintDesc)) { + // LADSPA_HINT_DEFAULT_0 is assumed anyway, so we don't check for it + if (LADSPA_IS_HINT_DEFAULT_1(HintDesc)) { + Default = 1.0f; + } else if (LADSPA_IS_HINT_DEFAULT_100(HintDesc)) { + Default = 100.0f; + } else if (LADSPA_IS_HINT_DEFAULT_440(HintDesc)) { + Default = 440.0f; + } else { + // These hints may be affected by SAMPLERATE, LOGARITHMIC and INTEGER + if (LADSPA_IS_HINT_DEFAULT_MINIMUM(HintDesc) && + LADSPA_IS_HINT_BOUNDED_BELOW(HintDesc)) { + Default=_idata->descriptor->PortRangeHints[Port].LowerBound; + } else if (LADSPA_IS_HINT_DEFAULT_MAXIMUM(HintDesc) && + LADSPA_IS_HINT_BOUNDED_ABOVE(HintDesc)) { + Default=_idata->descriptor->PortRangeHints[Port].UpperBound; + } else if (LADSPA_IS_HINT_BOUNDED_BELOW(HintDesc) && + LADSPA_IS_HINT_BOUNDED_ABOVE(HintDesc)) { + // These hints require both upper and lower bounds + float lp = 0.0f, up = 0.0f; + float min = _idata->descriptor->PortRangeHints[Port].LowerBound; + float max = _idata->descriptor->PortRangeHints[Port].UpperBound; + if (LADSPA_IS_HINT_DEFAULT_LOW(HintDesc)) { + lp = 0.75f; + up = 0.25f; + } else if (LADSPA_IS_HINT_DEFAULT_MIDDLE(HintDesc)) { + lp = 0.5f; + up = 0.5f; + } else if (LADSPA_IS_HINT_DEFAULT_HIGH(HintDesc)) { + lp = 0.25f; + up = 0.75f; + } + + if (LADSPA_IS_HINT_LOGARITHMIC(HintDesc)) { + + p.hints.type = Port::Hints::LOGARITHMIC; + + if (min==0.0f || max==0.0f) { + // Zero at either end means zero no matter + // where hint is at, since: + // log(n->0) -> Infinity + Default = 0.0f; + } else { + // Catch negatives + bool neg_min = min < 0.0f ? true : false; + bool neg_max = max < 0.0f ? true : false; + + if (!neg_min && !neg_max) { + Default = exp(log(min) * lp + log(max) * up); + } else if (neg_min && neg_max) { + Default = -exp(log(-min) * lp + log(-max) * up); + } else { + // Logarithmic range has asymptote + // so just use linear scale + Default = min * lp + max * up; + } + } + } else { + Default = min * lp + max * up; + } + } + if (LADSPA_IS_HINT_SAMPLE_RATE(HintDesc)) { + Default *= engine->sample_rate(); + } + if (LADSPA_IS_HINT_INTEGER(HintDesc)) { + if ( p.hints.ranged && + 0 == p.hints.minimum && + 1 == p.hints.maximum ) + p.hints.type = Port::Hints::BOOLEAN; + else + p.hints.type = Port::Hints::INTEGER; + Default = floorf(Default); + } + if (LADSPA_IS_HINT_TOGGLED(HintDesc)){ + p.hints.type = Port::Hints::BOOLEAN; + } + } + } +#else + Default = 0.0f; +#endif + p.hints.default_value = Default; + } + + float *control_value = new float; + + *control_value = p.hints.default_value; + + p.connect_to( control_value ); + + add_port( p ); + + _idata->descriptor->connect_port( _idata->handle, i, (LADSPA_Data*)control_input.back().buffer() ); + + DMESSAGE( "Plugin has control port \"%s\" (default: %f)", _idata->descriptor->PortNames[ i ], p.hints.default_value ); + } + } + } + else + { + WARNING( "Failed to load plugin" ); + return false; + } + + + return true; +} + +/* const char * */ +/* Plugin_Module::name ( void ) const */ +/* { */ +/* return _idata->descriptor->Name; */ +/* } */ + +void +Plugin_Module::set_input_buffer ( int n, void *buf ) +{ + for ( unsigned int i = 0; i < _idata->descriptor->PortCount; ++i ) + if ( LADSPA_IS_PORT_INPUT( _idata->descriptor->PortDescriptors[i] ) && + LADSPA_IS_PORT_AUDIO( _idata->descriptor->PortDescriptors[i] ) ) + if ( n-- == 0 ) + _idata->descriptor->connect_port( _idata->handle, i, (LADSPA_Data*)buf ); +} + +void +Plugin_Module::set_output_buffer ( int n, void *buf ) +{ + for ( unsigned int i = 0; i < _idata->descriptor->PortCount; ++i ) + if ( LADSPA_IS_PORT_OUTPUT( _idata->descriptor->PortDescriptors[i] ) && + LADSPA_IS_PORT_AUDIO( _idata->descriptor->PortDescriptors[i] ) ) + if ( n-- == 0 ) + _idata->descriptor->connect_port( _idata->handle, i, (LADSPA_Data*)buf ); +} + +void +Plugin_Module::set_control_buffer ( int n, void *buf ) +{ + for ( unsigned int i = 0; i < _idata->descriptor->PortCount; ++i ) + if ( LADSPA_IS_PORT_INPUT( _idata->descriptor->PortDescriptors[i] ) && + LADSPA_IS_PORT_CONTROL( _idata->descriptor->PortDescriptors[i] ) ) + if ( n-- == 0 ) + _idata->descriptor->connect_port( _idata->handle, i, (LADSPA_Data*)buf ); +} + +void +Plugin_Module::activate ( void ) +{ + if ( _active ) + FATAL( "Attempt to activate already active plugin" ); + + if ( _idata->descriptor->activate ) + _idata->descriptor->activate( _idata->handle ); + _active = true; +} + +void +Plugin_Module::deactivate( void ) +{ + if ( _idata->descriptor->deactivate ) + _idata->descriptor->deactivate( _idata->handle ); + _active = false; +} + +void +Plugin_Module::process ( ) +{ + if ( _crosswire ) + { + for ( int i = 0; i < plugin_ins(); ++i ) + { + set_input_buffer( i, audio_input[0].buffer() ); + } + } + else + { + for ( unsigned int i = 0; i < audio_input.size(); ++i ) + { + set_input_buffer( i, audio_input[i].buffer() ); + } + } + + for ( unsigned int i = 0; i < audio_output.size(); ++i ) + { + set_output_buffer( i, audio_output[i].buffer() ); + } + + if ( _active ) + { + _idata->descriptor->run( _idata->handle, nframes() ); + } +} + + diff --git a/Mixer/Plugin_Module.H b/Mixer/Plugin_Module.H new file mode 100644 index 0000000..36a0684 --- /dev/null +++ b/Mixer/Plugin_Module.H @@ -0,0 +1,114 @@ + +/*******************************************************************************/ +/* Copyright (C) 2009 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 "Module.H" + +class Plugin_Module : Module { + + void init ( void ); + + int _ins; + int _outs; + int _instances; + + void bbox ( int &X, int &Y, int &W, int &H ) + { + X = x(); + Y = y() + 5; + W = w(); + H = h() - 10; + } + + void cb_handle(Fl_Widget*); + static void cb_handle(Fl_Widget*, void*); + + Fl_Button *close_button; + + struct ImplementationData; + + ImplementationData *_idata; + + bool _active; + + + int _plugin_ins; + int _plugin_outs; + bool _crosswire; + + struct Plugin_Info + { + const char *path; + unsigned long id; + + Plugin_Info ( ) + { + path = 0; + id = 0; + } + }; + + static Plugin_Info* discover ( void ); + + bool load ( Plugin_Info * ); + + void set_input_buffer ( int n, void *buf ); + void set_output_buffer ( int n, void *buf ); + void set_control_buffer ( int n, void *buf ); + void activate ( void ); + void deactivate ( void ); + void process ( unsigned long nframes ); + bool active ( void ) const { return _active; } + +public: + + Plugin_Module( int W, int H, const char *L = 0 ); + Plugin_Module ( ); + virtual ~Plugin_Module(); + + static Plugin_Module * pick_plugin ( void ); + + int plugin_ins ( void ) const { return _plugin_ins; } + int plugin_outs ( void ) const { return _plugin_outs; } + + bool ins ( int i ); + int ins ( void ) const { return _ins; } + int outs ( void ) const { return plugin_outs() * _instances; } + + bool controllable ( void ) const { return false; } + + int instances ( void ) const { return _instances; } + void instances ( int i ) { _instances = i; } + + void select_plugin ( unsigned long id ); + + const char *name ( void ) const { return "Plugin"; } + + int can_support_inputs ( int ); + bool configure_inputs ( int ); + + void process ( void ); + +protected: + +// virtual void draw ( void ); + virtual int handle ( int ); + +}; diff --git a/Mixer/main.C b/Mixer/main.C new file mode 100644 index 0000000..343920f --- /dev/null +++ b/Mixer/main.C @@ -0,0 +1,114 @@ + +/*******************************************************************************/ +/* Copyright (C) 2008 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 +#include +#include +#include "Mixer_Strip.H" + + +#include +#include + +#include "DPM.H" + +#include "Mixer.H" +#include "Engine/Engine.H" +#include "util/Thread.H" +#include "util/debug.h" + +Engine *engine; +Mixer *mixer; + +Fl_Single_Window *main_window; + +#include +#include "Loggable.H" +#include + +int +main ( int argc, char **argv ) +{ + Thread::init(); + + Thread thread( "UI" ); + thread.set(); + + Fl_Tooltip::color( FL_BLACK ); + Fl_Tooltip::textcolor( FL_YELLOW ); + Fl_Tooltip::size( 14 ); + Fl_Tooltip::hoverdelay( 0.1f ); + + Fl::visible_focus( 0 ); + + LOG_REGISTER_CREATE( Mixer_Strip ); + + init_boxtypes(); + + Fl::get_system_colors(); + Fl::scheme( "plastic" ); +// Fl::scheme( "gtk+" ); + +/* Fl::foreground( 0xFF, 0xFF, 0xFF ); */ +/* Fl::background( 0x10, 0x10, 0x10 ); */ + + MESSAGE( "Initializing JACK" ); + + engine = new Engine(); + + + engine->init( "Non-Mixer" ); + + Fl_Single_Window *o = main_window = new Fl_Single_Window( 1024, 768, "Mixer" ); + { + Fl_Widget *o = mixer = new Mixer( 0, 0, main_window->w(), main_window->h(), NULL ); + Fl_Group::current()->resizable(o); + } + o->end(); + + o->show( argc, argv ); + + { + engine->lock(); + + if ( argc > 1 ) + { + char name[1024]; + + snprintf( name, sizeof( name ), "%s/history", argv[1] ); + + Loggable::open( name ); + } + else + { + WARNING( "Running without a project--nothing will be saved." ); + } + + engine->unlock(); + } + + Fl::run(); + + delete engine; + + MESSAGE( "Your fun is over" ); + +} diff --git a/Mixer/makefile.inc b/Mixer/makefile.inc new file mode 100644 index 0000000..a927e73 --- /dev/null +++ b/Mixer/makefile.inc @@ -0,0 +1,21 @@ +# -*- mode: makefile; -*- + +all: Mixer + +Mixer_SRCS := $(wildcard Mixer/*.C Mixer/*.fl Mixer/Engine/*.C) +Mixer_SRCS += util/debug.C util/Thread.C util/file.C + +Mixer_SRCS:=$(Mixer_SRCS:.fl=.C) +Mixer_SRCS:=$(sort $(Mixer_SRCS)) +Mixer_OBJS:=$(Mixer_SRCS:.C=.o) + +Mixer_LIBS := $(FLTK_LIBS) $(JACK_LIBS) $(LASH_LIBS) -llrdf + +Mixer/mixer: $(Mixer_OBJS) FL + @ echo -n Linking mixer... + @ $(CXX) $(CXXFLAGS) $(Mixer_LIBS) $(Mixer_OBJS) -o $@ -LFL -lfl_widgets -Lnonlib -lnonlib && echo $(DONE) + +Mixer: Mixer/mixer + +Mixer_clean: + rm -f $(Mixer_OBJS) Mixer/mixer diff --git a/configure b/configure index d5301cf..0d4c5cb 100755 --- a/configure +++ b/configure @@ -27,5 +27,6 @@ require_command ar ar require_command makedepend makedepend require_package JACK 0.103.0 jack require_package sndfile 1.0.17 sndfile +require_package lrdf 0.1.0 lrdf end