non/mixer/src/Chain.C

868 lines
20 KiB
C
Raw Blame History

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

/*******************************************************************************/
/* 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 "const.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "Chain.H"
#include "Module.H"
#include "Meter_Module.H"
#include "JACK_Module.H"
#include "Gain_Module.H"
#include "Plugin_Module.H"
#include "Controller_Module.H"
#include <FL/Fl_Box.H>
#include <FL/Fl_Menu.H>
#include <FL/fl_ask.H>
#include <FL/Fl_Flip_Button.H>
#include <FL/Fl_Tabs.H>
#include "FL/Fl_Flowpack.H"
#include "FL/Fl_Scroll.H"
#include "FL/Fl_Packscroller.H"
#include <FL/fl_draw.H>
#include "FL/menu_popup.H"
#include "FL/test_press.H"
#include "debug.h"
#include "Engine/Engine.H"
#include "Mixer_Strip.H"
#include <dsp.h>
extern char *instance_name;
/* Chain::Chain ( int X, int Y, int W, int H, const char *L ) : */
/* Fl_Group( X, Y, W, H, L) */
Chain::Chain ( ) : Fl_Group( 0, 0, 100, 100, "")
{
_engine = NULL;
int X = 0;
int Y = 0;
int W = 100;
int H = 100;
/* _outs = 1; */
/* _ins = 1; */
_configure_outputs_callback = NULL;
_strip = NULL;
_name = NULL;
labelsize( 10 );
align( FL_ALIGN_TOP );
{ Fl_Flip_Button* o = tab_button = new Fl_Flip_Button( X, Y, W, 16, "chain/controls");
o->type(1);
o->labelsize( 12 );
o->callback( cb_handle, this );
}
Y += 18;
H -= 18;
{ Fl_Group *o = chain_tab = new Fl_Group( X, Y, W, H, "" );
o->labeltype( FL_NO_LABEL );
o->box( FL_FLAT_BOX );
// o->color( fl_darker( FL_BACKGROUND_COLOR ) );
// o->color( FL_BACKGROUND_COLOR );
// o->box( FL_NO_BOX );
{ Fl_Packscroller *o = new Fl_Packscroller( X, Y, W, H );
o->color( FL_BACKGROUND_COLOR );
// o->box( FL_FLAT_BOX );
o->box( FL_THIN_UP_BOX );
o->type( Fl_Scroll::VERTICAL );
{ Fl_Pack *o = modules_pack = new Fl_Pack( X, Y, W, H );
o->type( Fl_Pack::VERTICAL );
o->spacing( 6 );
o->end();
Fl_Group::current()->resizable( o );
}
o->end();
}
o->end();
}
{ Fl_Group *o = control_tab = new Fl_Group( X, Y, W, H, "" );
o->box( FL_FLAT_BOX );
o->color( FL_BACKGROUND_COLOR );
o->labeltype( FL_NO_LABEL );
o->hide();
{ Fl_Scroll *o = new Fl_Scroll( X, Y, W, H );
o->color( FL_BACKGROUND_COLOR );
o->box( FL_NO_BOX );
o->type( Fl_Scroll::VERTICAL );
{ Fl_Flowpack *o = controls_pack = new Fl_Flowpack( X, Y, W, H );
o->type( FL_VERTICAL );
o->hspacing( 10 );
o->vspacing( 10 );
o->box( FL_NO_BOX );
// o->color( FL_RED );
o->end();
Fl_Group::current()->resizable( o );
}
o->end();
Fl_Group::current()->resizable( o );
}
o->end();
o->hide();
Fl_Group::current()->resizable( o );
}
end();
log_create();
}
Chain::~Chain ( )
{
DMESSAGE( "Destroying chain" );
log_destroy();
engine()->lock();
/* if we leave this up to FLTK, it will happen after we've
already destroyed the engine */
modules_pack->clear();
controls_pack->clear();
delete _engine;
_engine = NULL;
}
void
Chain::get ( Log_Entry &e ) const
{
e.add( ":strip", strip() );
e.add( ":tab", tab_button->value() ? "controls" : "chain" );
}
void
Chain::set ( Log_Entry &e )
{
for ( int i = 0; i < e.size(); ++i )
{
const char *s, *v;
e.get( i, &s, &v );
if ( ! strcmp( s, ":tab" ) )
{
tab_button->value( strcmp( v, "controls" ) == 0 );
tab_button->do_callback();
}
else if ( ! strcmp( s, ":strip" ) )
{
int i;
sscanf( v, "%X", &i );
Mixer_Strip *t = (Mixer_Strip*)Loggable::find( i );
assert( t );
t->chain( this );
}
}
}
void
Chain::log_children ( void ) const
{
log_create();
for ( int i = 0; i < modules(); ++i )
{
module(i)->log_create();
}
for ( int i = 0; i < controls_pack->children(); ++i )
{
Controller_Module *cm = (Controller_Module*)controls_pack->child( i );
cm->log_create();
}
}
/* Fill this chain with JACK I/O, Gain, and Meter modules. */
void
Chain::initialize_with_default ( void )
{
{ JACK_Module *m = new JACK_Module();
m->is_default( true );
m->chain( this );
m->configure_outputs( 1 );
m->initialize();
add( m );
}
{ Module *m = new Gain_Module();
m->is_default( true );
m->initialize();
add( m );
}
{ Module *m = new Meter_Module();
m->is_default( true );
add( m );
}
{ JACK_Module *m = new JACK_Module();
m->is_default( true );
m->chain( this );
m->initialize();
add( m );
}
}
void Chain::cb_handle(Fl_Widget* o) {
if ( o == tab_button )
{
Fl_Flip_Button *fb = (Fl_Flip_Button*)o;
if ( fb->value() == 0 )
{
control_tab->hide();
chain_tab->show();
}
else
{
chain_tab->hide();
control_tab->show();
}
}
}
void Chain::cb_handle(Fl_Widget* o, void* v) {
((Chain*)(v))->cb_handle(o);
}
void
Chain::remove ( Controller_Module *m )
{
DMESSAGE( "Removing controller module from chain" );
engine()->lock();
m->disconnect();
controls_pack->remove( m );
modules_pack->remove( m );
build_process_queue();
engine()->unlock();
redraw();
}
/* 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" );
}
engine()->lock();
strip()->handle_module_removed( m );
modules_pack->remove( m );
configure_ports();
engine()->unlock();
}
/* determine number of output ports, signal if changed. */
void
Chain::configure_ports ( void )
{
int nouts = 0;
engine()->lock();
for ( int i = 0; i < modules(); ++i )
{
module( i )->configure_inputs( nouts );
nouts = module( i )->noutputs();
}
unsigned int req_buffers = required_buffers();
DMESSAGE( "required_buffers = %i", req_buffers );
if ( scratch_port.size() < req_buffers )
{
for ( unsigned int i = scratch_port.size(); i--; )
delete[] (sample_t*)scratch_port[i].buffer();
scratch_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()] );
buffer_fill_with_silence( (sample_t*)p.buffer(), engine()->nframes() );
scratch_port.push_back( p );
}
}
build_process_queue();
engine()->unlock();
parent()->redraw();
}
int
Chain::get_module_instance_number ( Module *m )
{
int n = 0;
for ( int i = 0; i < modules() && module(i) != m; ++i )
if ( ! strcmp( module(i)->label(), m->label() ) )
n++;
return n;
}
/* 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;
}
unsigned int
Chain::maximum_name_length ( void )
{
return JACK::Client::maximum_name_length() - ( strlen( instance_name ) + 1 );
}
/* 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 )
{
char ename[512];
snprintf( ename, sizeof(ename), "%s/%s", instance_name, name );
if ( ! _engine )
{
_engine = new Engine( &Chain::process, this );
engine()->buffer_size_callback( &Chain::buffer_size, this );
engine()->port_connect_callback( &Chain::port_connect, this );
const char *jack_name = engine()->init( ename );
if ( ! jack_name )
{
_engine = NULL;
fl_alert( "Could not create JACK client. Perhaps the sound device already in use. In any event, now I'll die." );
exit( 1 );
return;
}
}
else
{
DMESSAGE( "Renaming JACK client from \"%s\" to \"%s\"", _name, ename );
_name = engine()->name( ename );
/* FIXME: discarding the name jack picked is technically wrong! */
}
_name = name;
for ( int i = 0; i < modules(); ++i )
{
module( i )->handle_chain_name_changed();
}
}
bool
Chain::add ( Module *m )
{
/* FIXME: hacky */
if ( !strcmp( m->name(), "Controller" ) )
return false;
else
return insert( NULL, m );
}
bool
Chain::add ( Controller_Module *m )
{
DMESSAGE( "Adding control" );
add_control(m);
return true;
}
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
{
DMESSAGE( "Module says it can't support %i inputs", module( modules() - 1 )->noutputs() );
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 );
}
strip()->handle_module_added( n );
configure_ports();
engine()->unlock();
DMESSAGE( "Module \"%s\" has %i:%i audio and %i:%i control ports",
n->name(),
n->ninputs(),
n->noutputs(),
n->ncontrol_inputs(),
n->ncontrol_outputs() );
return true;
err:
engine()->unlock();
DMESSAGE( "Insert failed" );
return false;
}
/* add a control to the control strip. Assumed to already be connected! */
void
Chain::add_control ( Controller_Module *m )
{
engine()->lock();
controls_pack->add( m );
configure_ports();
engine()->unlock();
controls_pack->redraw();
}
void
Chain::draw_connections ( Module *m )
{
int spacing;
int offset;
int X, Y, W, H;
((Fl_Packscroller*)chain_tab->child( 0 ))->bbox( X, Y, W, H );
fl_push_clip( X, Y, W, H );
Fl_Color c = FL_FOREGROUND_COLOR;
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() - 3, 2, 3 );
}
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, 3 );
}
fl_pop_clip();
}
void
Chain::add_to_process_queue ( Module *m )
{
for ( std::list<Module*>::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( &scratch_port[j] );
}
for ( unsigned int j = 0; j < m->audio_output.size(); ++j )
{
m->audio_output[j].connect_to( &scratch_port[j] );
}
m->handle_port_connection_change();
}
/* DMESSAGE( "Process queue looks like:" ); */
/* for ( std::list<Module*>::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() ); */
/* { */
/* char *s = m->get_parameters(); */
/* DMESSAGE( "(%s)", s ); */
/* delete[] s; */
/* } */
/* } */
}
void
Chain::strip ( Mixer_Strip * ms )
{
_strip = ms;
}
void
Chain::draw ( void )
{
Fl_Group::draw();
/* if ( 0 == strcmp( "Chain", tabs->value()->label() ) ) */
if ( chain_tab->visible() )
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() );
}
/*****************/
/* Import/Export */
/*****************/
void
Chain::snapshot ( void *v )
{
((Chain*)v)->snapshot();
}
void
Chain::snapshot ( void )
{
log_children();
}
bool
Chain::do_export ( const char *filename )
{
MESSAGE( "Exporting chain state" );
Loggable::snapshot_callback( &Chain::snapshot, this );
Loggable::snapshot( filename );
return true;
}
/**********/
/* Engine */
/**********/
void
Chain::process ( nframes_t nframes, void *v )
{
((Chain*)v)->process( nframes );
}
void
Chain::process ( nframes_t nframes )
{
for ( std::list<Module*>::const_iterator i = process_queue.begin(); i != process_queue.end(); ++i )
{
Module *m = *i;
m->process( nframes );
}
}
void
Chain::buffer_size ( nframes_t nframes, void *v )
{
((Chain*)v)->buffer_size( nframes );
}
void
Chain::buffer_size ( nframes_t nframes )
{
for ( unsigned int i = scratch_port.size(); i--; )
delete[] (sample_t*)scratch_port[i].buffer();
scratch_port.clear();
configure_ports();
for ( int i = 0; i < modules(); ++i )
{
Module *m = module(i);
m->resize_buffers( nframes );
}
}
void
Chain::port_connect ( jack_port_id_t a, jack_port_id_t b, int connect, void *v )
{
((Chain*)v)->port_connect( a, b, connect );
}
/* handle jack port connection change */
void
Chain::port_connect ( jack_port_id_t a, jack_port_id_t b, int connect )
{
/* this is called from JACK non-RT thread... */
if ( jack_port_is_mine( engine()->jack_client(), jack_port_by_id( engine()->jack_client(), a ) ) ||
jack_port_is_mine( engine()->jack_client(), jack_port_by_id( engine()->jack_client(), b ) ))
{
Fl::awake( Chain::update_connection_status, this );
}
}
void
Chain::update_connection_status ( void *v )
{
((Chain*)v)->update_connection_status();
}
void
Chain::update_connection_status ( void )
{
for ( int i = 0; i < modules(); i++ )
{
Module *m = module(i);
if ( !strcmp( m->name(), "JACK" ) ||
!strcmp( m->name(), "AUX" ))
{
((JACK_Module*)m)->update_connection_status();
}
}
}