| @@ -0,0 +1,179 @@ | |||
| /*******************************************************************************/ | |||
| /* Copyright (C) 2013 Jonathan Moore Liles */ | |||
| /* */ | |||
| /* This program is free software; you can redistribute it and/or modify it */ | |||
| /* under the terms of the GNU General Public License as published by the */ | |||
| /* Free Software Foundation; either version 2 of the License, or (at your */ | |||
| /* option) any later version. */ | |||
| /* */ | |||
| /* This program is distributed in the hope that it will be useful, but WITHOUT */ | |||
| /* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ | |||
| /* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for */ | |||
| /* more details. */ | |||
| /* */ | |||
| /* You should have received a copy of the GNU General Public License along */ | |||
| /* with This program; see the file COPYING. If not,write to the Free Software */ | |||
| /* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ | |||
| /*******************************************************************************/ | |||
| #include <FL/fl_draw.H> | |||
| #include "AUX_Module.H" | |||
| #include "dsp.h" | |||
| /* The purpose of this module is to provide auxiliary outputs, with | |||
| * gain. This allows one to create a 'send' type topology without | |||
| * having to use an extra strip to control the 'send' gain. */ | |||
| AUX_Module::AUX_Module ( ) : JACK_Module ( false ) | |||
| { | |||
| is_default( false ); | |||
| _number = 0; | |||
| { | |||
| Port p( this, Port::INPUT, Port::CONTROL, "Gain (dB)" ); | |||
| p.hints.type = Port::Hints::LOGARITHMIC; | |||
| p.hints.ranged = true; | |||
| p.hints.minimum = -70.0f; | |||
| p.hints.maximum = 6.0f; | |||
| p.hints.default_value = 0.0f; | |||
| p.connect_to( new float ); | |||
| p.control_value( p.hints.default_value ); | |||
| add_port( p ); | |||
| } | |||
| log_create(); | |||
| copy_label( "Aux" ); | |||
| } | |||
| AUX_Module::~AUX_Module ( ) | |||
| { | |||
| configure_outputs(0); | |||
| delete (float*)control_input[0].buffer(); | |||
| } | |||
| void | |||
| AUX_Module::get ( Log_Entry &e ) const | |||
| { | |||
| e.add( ":number", number() ); | |||
| JACK_Module::get(e); | |||
| } | |||
| void | |||
| AUX_Module::set ( Log_Entry &e ) | |||
| { | |||
| for ( int i = 0; i < e.size(); ++i ) | |||
| { | |||
| const char *s, *v; | |||
| e.get( i, &s, &v ); | |||
| if ( ! ( strcmp( s, ":number" ) ) ) | |||
| { | |||
| number( atoi(v) ); | |||
| } | |||
| } | |||
| JACK_Module::set(e); | |||
| } | |||
| void | |||
| AUX_Module::number ( int n ) | |||
| { | |||
| _number = n; | |||
| char s[10]; | |||
| snprintf( s, sizeof(s), "aux-%c", 'A' + n ); | |||
| prefix( s ); | |||
| snprintf( s, sizeof(s), "Aux (%c)", 'A' + n ); | |||
| copy_label( s ); | |||
| } | |||
| void | |||
| AUX_Module::process ( nframes_t nframes ) | |||
| { | |||
| if ( !bypass() ) | |||
| { | |||
| float g = DB_CO( control_input[0].control_value() ); | |||
| for ( unsigned int i = 0; i < audio_input.size(); ++i ) | |||
| { | |||
| if ( audio_input[i].connected() ) | |||
| buffer_copy_and_apply_gain( (sample_t*)jack_output[i].buffer( nframes ), (sample_t*)audio_input[i].buffer(), nframes, g ); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| for ( unsigned int i = 0; i < audio_input.size(); ++i ) | |||
| { | |||
| if ( audio_input[i].connected() ) | |||
| buffer_fill_with_silence( (sample_t*)jack_output[i].buffer( nframes ), nframes ); | |||
| } | |||
| } | |||
| } | |||
| void | |||
| AUX_Module::draw ( void ) | |||
| { | |||
| int W = 5; | |||
| Module::draw_box(x(),y(),w() - W,h()); | |||
| Module::draw_label(x(),y(),w() - W,h()); | |||
| Module *m = this; | |||
| fl_color( fl_darker( FL_FOREGROUND_COLOR ) ); | |||
| int spacing, offset; | |||
| spacing = h() / m->ninputs(); | |||
| offset = spacing / 2; | |||
| for ( int i = m->ninputs(); i--; ) | |||
| { | |||
| int xi = offset + ( spacing * i ); | |||
| fl_rectf( m->x() + m->w() - W, m->y() + xi, W, 2 ); | |||
| } | |||
| } | |||
| bool | |||
| AUX_Module::configure_outputs ( int n ) | |||
| { | |||
| int on = audio_output.size(); | |||
| if ( n > on ) | |||
| { | |||
| for ( int i = on; i < n; ++i ) | |||
| { | |||
| add_port( Port( this, Port::OUTPUT, Port::AUDIO ) ); | |||
| } | |||
| } | |||
| else | |||
| { | |||
| for ( int i = on; i > n; --i ) | |||
| { | |||
| audio_output.back().disconnect(); | |||
| audio_output.pop_back(); | |||
| } | |||
| } | |||
| return true; | |||
| } | |||
| bool | |||
| AUX_Module::configure_inputs ( int n ) | |||
| { | |||
| bool b = JACK_Module::configure_inputs( n ); | |||
| if ( b ) | |||
| { | |||
| return configure_outputs( n ); | |||
| } | |||
| return false; | |||
| } | |||
| @@ -0,0 +1,54 @@ | |||
| /*******************************************************************************/ | |||
| /* Copyright (C) 2013 Jonathan Moore Liles */ | |||
| /* */ | |||
| /* This program is free software; you can redistribute it and/or modify it */ | |||
| /* under the terms of the GNU General Public License as published by the */ | |||
| /* Free Software Foundation; either version 2 of the License, or (at your */ | |||
| /* option) any later version. */ | |||
| /* */ | |||
| /* This program is distributed in the hope that it will be useful, but WITHOUT */ | |||
| /* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or */ | |||
| /* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for */ | |||
| /* more details. */ | |||
| /* */ | |||
| /* You should have received a copy of the GNU General Public License along */ | |||
| /* with This program; see the file COPYING. If not,write to the Free Software */ | |||
| /* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ | |||
| /*******************************************************************************/ | |||
| #pragma once | |||
| #include "JACK_Module.H" | |||
| class AUX_Module : public JACK_Module | |||
| { | |||
| int _number; | |||
| protected: | |||
| virtual void get ( Log_Entry &e ) const; | |||
| virtual void set ( Log_Entry &e ); | |||
| public: | |||
| virtual const char *name ( void ) const { return "AUX"; } | |||
| virtual bool configure_outputs ( int n ); | |||
| virtual bool configure_inputs ( int n ); | |||
| void number ( int n ); | |||
| int number ( void ) const { return _number; } | |||
| AUX_Module ( ); | |||
| virtual ~AUX_Module ( ); | |||
| LOG_CREATE_FUNC( AUX_Module ); | |||
| protected: | |||
| virtual void draw ( void ); | |||
| virtual void process ( nframes_t nframes ); | |||
| }; | |||
| @@ -91,7 +91,7 @@ public: | |||
| void draw ( void ) | |||
| { | |||
| draw_box(); | |||
| draw_box(x(),y(),w(),h()); | |||
| Fl_Group::draw(); | |||
| } | |||
| @@ -32,41 +32,47 @@ | |||
| JACK_Module::JACK_Module ( ) | |||
| JACK_Module::JACK_Module ( bool log ) | |||
| : Module ( 50, 24, name() ) | |||
| { | |||
| /* FIXME: how do Controls find out that a connected value has changed? How does this work in ladspa? */ | |||
| _prefix = 0; | |||
| if ( log ) | |||
| { | |||
| Port p( this, Port::INPUT, Port::CONTROL, "Inputs" ); | |||
| p.hints.type = Port::Hints::INTEGER; | |||
| p.hints.minimum = 0; | |||
| p.hints.maximum = 16; | |||
| p.hints.ranged = true; | |||
| /* 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 = 16; | |||
| p.hints.ranged = true; | |||
| p.hints.visible = false; | |||
| p.connect_to( new float ); | |||
| p.control_value_no_callback( 0 ); | |||
| p.connect_to( new float ); | |||
| p.control_value_no_callback( 0 ); | |||
| add_port( p ); | |||
| } | |||
| add_port( p ); | |||
| } | |||
| { | |||
| Port p( this, Port::INPUT, Port::CONTROL, "Outputs" ); | |||
| p.hints.type = Port::Hints::INTEGER; | |||
| p.hints.minimum = 0; | |||
| p.hints.maximum = 16; | |||
| p.hints.ranged = true; | |||
| { | |||
| Port p( this, Port::INPUT, Port::CONTROL, "Outputs" ); | |||
| p.hints.type = Port::Hints::INTEGER; | |||
| p.hints.minimum = 0; | |||
| p.hints.maximum = 16; | |||
| p.hints.ranged = true; | |||
| p.hints.visible = false; | |||
| p.connect_to( new float ); | |||
| p.control_value_no_callback( 0 ); | |||
| p.connect_to( new float ); | |||
| p.control_value_no_callback( 0 ); | |||
| add_port( p ); | |||
| } | |||
| add_port( p ); | |||
| } | |||
| color( FL_BLACK ); | |||
| end(); | |||
| color( FL_BLACK ); | |||
| log_create(); | |||
| } | |||
| log_create(); | |||
| end(); | |||
| } | |||
| JACK_Module::~JACK_Module ( ) | |||
| @@ -74,6 +80,8 @@ JACK_Module::~JACK_Module ( ) | |||
| log_destroy(); | |||
| configure_inputs( 0 ); | |||
| configure_outputs( 0 ); | |||
| if ( _prefix ) | |||
| free( _prefix ); | |||
| } | |||
| @@ -93,19 +101,26 @@ JACK_Module::configure_inputs ( int n ) | |||
| { | |||
| for ( int i = on; i < n; ++i ) | |||
| { | |||
| JACK::Port po( chain()->engine(), JACK::Port::Output, i ); | |||
| JACK::Port *po = NULL; | |||
| if ( ! po.activate() ) | |||
| if ( !_prefix ) | |||
| po = new JACK::Port( chain()->engine(), JACK::Port::Output, i ); | |||
| else | |||
| po = new JACK::Port( chain()->engine(), JACK::Port::Output, _prefix, i ); | |||
| if ( ! po->activate() ) | |||
| { | |||
| jack_port_activation_error( &po ); | |||
| jack_port_activation_error( po ); | |||
| return false; | |||
| } | |||
| if ( po.valid() ) | |||
| if ( po->valid() ) | |||
| { | |||
| add_port( Port( this, Port::INPUT, Port::AUDIO ) ); | |||
| jack_output.push_back( po ); | |||
| jack_output.push_back( *po ); | |||
| } | |||
| delete po; | |||
| } | |||
| } | |||
| else | |||
| @@ -119,7 +134,8 @@ JACK_Module::configure_inputs ( int n ) | |||
| } | |||
| } | |||
| control_input[0].control_value_no_callback( n ); | |||
| if ( is_default() ) | |||
| control_input[0].control_value_no_callback( n ); | |||
| return true; | |||
| } | |||
| @@ -139,19 +155,26 @@ JACK_Module::configure_outputs ( int n ) | |||
| { | |||
| for ( int i = on; i < n; ++i ) | |||
| { | |||
| JACK::Port po( chain()->engine(), JACK::Port::Input, i ); | |||
| JACK::Port *po = NULL; | |||
| if ( ! po.activate() ) | |||
| if ( !_prefix ) | |||
| po = new JACK::Port( chain()->engine(), JACK::Port::Input, i ); | |||
| else | |||
| po = new JACK::Port( chain()->engine(), JACK::Port::Input, _prefix, i ); | |||
| if ( ! po->activate() ) | |||
| { | |||
| jack_port_activation_error( &po ); | |||
| jack_port_activation_error( po ); | |||
| return false; | |||
| } | |||
| if ( po.valid() ) | |||
| if ( po->valid() ) | |||
| { | |||
| add_port( Port( this, Port::OUTPUT, Port::AUDIO ) ); | |||
| jack_input.push_back( po ); | |||
| jack_input.push_back( *po ); | |||
| } | |||
| delete po; | |||
| } | |||
| } | |||
| else | |||
| @@ -165,7 +188,8 @@ JACK_Module::configure_outputs ( int n ) | |||
| } | |||
| } | |||
| control_input[1].control_value_no_callback( n ); | |||
| if ( is_default() ) | |||
| control_input[1].control_value_no_callback( n ); | |||
| return true; | |||
| } | |||
| @@ -230,10 +254,19 @@ void | |||
| JACK_Module::process ( nframes_t nframes ) | |||
| { | |||
| for ( unsigned 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 ); | |||
| buffer_copy( (sample_t*)jack_output[i].buffer( nframes ), | |||
| (sample_t*)audio_input[i].buffer(), | |||
| nframes ); | |||
| } | |||
| for ( unsigned 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 ); | |||
| buffer_copy( (sample_t*)audio_output[i].buffer(), | |||
| (sample_t*)jack_input[i].buffer( nframes ), | |||
| nframes ); | |||
| } | |||
| } | |||
| @@ -19,13 +19,27 @@ | |||
| #pragma once | |||
| #include "Module.H" | |||
| #include "JACK/Port.H" | |||
| #include <vector> | |||
| class JACK_Module : public Module | |||
| { | |||
| char *_prefix; | |||
| protected: | |||
| void prefix ( const char *s ) | |||
| { | |||
| if ( _prefix ) | |||
| free( _prefix ); | |||
| _prefix = NULL; | |||
| if ( s ) | |||
| _prefix = strdup( s ); | |||
| } | |||
| std::vector<JACK::Port> jack_input; | |||
| std::vector<JACK::Port> jack_output; | |||
| @@ -33,21 +47,19 @@ class JACK_Module : public Module | |||
| public: | |||
| JACK_Module ( ); | |||
| JACK_Module ( bool log = true ); | |||
| virtual ~JACK_Module ( ); | |||
| const char *name ( void ) const { return "JACK"; } | |||
| bool initialize ( void ); | |||
| virtual const char *name ( void ) const { return "JACK"; } | |||
| int can_support_inputs ( int ); | |||
| bool configure_inputs ( int n ); | |||
| bool configure_outputs ( int n ); | |||
| virtual bool initialize ( void ); | |||
| void add_output ( void ); | |||
| virtual int can_support_inputs ( int ); | |||
| virtual bool configure_inputs ( int n ); | |||
| virtual bool configure_outputs ( int n ); | |||
| void handle_control_changed ( Port *p ); | |||
| void handle_chain_name_changed (); | |||
| virtual void handle_control_changed ( Port *p ); | |||
| virtual void handle_chain_name_changed (); | |||
| LOG_CREATE_FUNC( JACK_Module ); | |||
| @@ -48,5 +48,5 @@ protected: | |||
| virtual int handle ( int m ); | |||
| virtual void process ( nframes_t nframes ); | |||
| virtual void draw ( void ) { draw_box(); } | |||
| virtual void draw ( void ) { draw_box(x(),y(),w(),h()); } | |||
| }; | |||
| @@ -33,6 +33,7 @@ | |||
| #include "Mono_Pan_Module.H" | |||
| #include "Meter_Module.H" | |||
| #include "Plugin_Module.H" | |||
| #include "AUX_Module.H" | |||
| #include <FL/Fl_Menu_Button.H> | |||
| #include "FL/test_press.H" | |||
| @@ -395,7 +396,11 @@ Module::set ( Log_Entry &e ) | |||
| e.get( i, &s, &v ); | |||
| if ( ! strcmp( s, ":chain" ) ) | |||
| if ( ! ( strcmp( s, ":is_default" ) ) ) | |||
| { | |||
| is_default( atoi( v ) ); | |||
| } | |||
| else if ( ! strcmp( s, ":chain" ) ) | |||
| { | |||
| /* This trickiness is because we may need to know the name of | |||
| our chain before we actually get added to it. */ | |||
| @@ -421,10 +426,6 @@ Module::set ( Log_Entry &e ) | |||
| { | |||
| set_parameters( v ); | |||
| } | |||
| else if ( ! ( strcmp( s, ":is_default" ) ) ) | |||
| { | |||
| is_default( atoi( v ) ); | |||
| } | |||
| else if ( ! ( strcmp( s, ":active" ) ) ) | |||
| { | |||
| bypass( ! atoi( v ) ); | |||
| @@ -527,17 +528,10 @@ Module::set_parameters ( const char *parameters ) | |||
| void | |||
| Module::draw_box ( void ) | |||
| Module::draw_box ( int tx, int ty, int tw, int th ) | |||
| { | |||
| fl_color( fl_contrast( FL_FOREGROUND_COLOR, color() ) ); | |||
| int tw, th, tx, ty; | |||
| tw = w(); | |||
| th = h(); | |||
| ty = y(); | |||
| tx = x(); | |||
| fl_push_clip( tx, ty, tw, th ); | |||
| Fl_Color c = color(); | |||
| @@ -575,10 +569,8 @@ Module::draw_box ( void ) | |||
| } | |||
| void | |||
| Module::draw_label ( void ) | |||
| Module::draw_label ( int tx, int ty, int tw, int th ) | |||
| { | |||
| int tw, th, tx, ty; | |||
| bbox( tx, ty, tw, th ); | |||
| const char *lp = label(); | |||
| @@ -616,47 +608,53 @@ Module::draw_label ( void ) | |||
| void | |||
| Module::insert_menu_cb ( const Fl_Menu_ *m ) | |||
| { | |||
| unsigned long id = Plugin_Chooser::plugin_chooser( this->ninputs() ); | |||
| const char * picked = m->mvalue()->label(); | |||
| DMESSAGE("picked = %s", picked ); | |||
| Module *mod = NULL; | |||
| switch ( id ) | |||
| if ( !strcmp( picked, "Aux" ) ) | |||
| { | |||
| case 0: | |||
| return; | |||
| case 1: | |||
| mod = new JACK_Module(); | |||
| break; | |||
| case 2: | |||
| mod = new Gain_Module(); | |||
| break; | |||
| case 3: | |||
| mod = new Meter_Module(); | |||
| break; | |||
| case 4: | |||
| mod = new Mono_Pan_Module(); | |||
| break; | |||
| default: | |||
| int n = 0; | |||
| for ( int i = 0; i < chain()->modules(); i++ ) | |||
| { | |||
| Plugin_Module *m = new Plugin_Module(); | |||
| m->load( id ); | |||
| mod = m; | |||
| if ( !strcmp( chain()->module(i)->name(), "AUX" ) ) | |||
| n++; | |||
| } | |||
| AUX_Module *jm = new AUX_Module(); | |||
| jm->chain( chain() ); | |||
| jm->number( n ); | |||
| jm->configure_inputs( ninputs() ); | |||
| jm->configure_outputs( ninputs() ); | |||
| jm->initialize(); | |||
| mod = jm; | |||
| } | |||
| else if ( !strcmp( picked, "Gain" ) ) | |||
| mod = new Gain_Module(); | |||
| else if ( !strcmp( picked, "Meter" ) ) | |||
| mod = new Meter_Module(); | |||
| else if ( !strcmp( picked, "Mono Pan" )) | |||
| mod = new Mono_Pan_Module(); | |||
| else if ( !strcmp(picked, "Plugin" )) | |||
| { | |||
| unsigned long id = Plugin_Chooser::plugin_chooser( this->ninputs() ); | |||
| if ( id == 0 ) | |||
| return; | |||
| Plugin_Module *m = new Plugin_Module(); | |||
| m->load( id ); | |||
| mod = m; | |||
| } | |||
| if ( mod ) | |||
| { | |||
| if ( !strcmp( mod->name(), "JACK" ) ) | |||
| { | |||
| DMESSAGE( "Special casing JACK module" ); | |||
| JACK_Module *jm = (JACK_Module*)mod; | |||
| jm->chain( chain() ); | |||
| jm->configure_inputs( ninputs() ); | |||
| jm->configure_outputs( ninputs() ); | |||
| } | |||
| if ( ! chain()->insert( this, mod ) ) | |||
| { | |||
| fl_alert( "Cannot insert this module at this point in the chain" ); | |||
| @@ -737,16 +735,12 @@ Module::menu ( void ) const | |||
| { | |||
| insert_menu = new Fl_Menu_Button( 0, 0, 0, 0 ); | |||
| insert_menu->add( "Gain", 0, 0, new unsigned long(2) ); | |||
| insert_menu->add( "Meter", 0, 0, new unsigned long(3) ); | |||
| insert_menu->add( "Mono Pan", 0, 0, new unsigned long(4) ); | |||
| insert_menu->add( "Plugin", 0, 0, new unsigned long(4) ); | |||
| /* Plugin_Module::add_plugins_to_menu( insert_menu ); */ | |||
| insert_menu->add( "Gain", 0, 0 ); | |||
| insert_menu->add( "Meter", 0, 0 ); | |||
| insert_menu->add( "Mono Pan", 0, 0 ); | |||
| insert_menu->add( "Aux", 0, 0 ); | |||
| insert_menu->add( "Plugin", 0, 0 ); | |||
| // menu_set_callback( insert_menu, &Module::insert_menu_cb, (void*)this ); | |||
| insert_menu->callback( &Module::insert_menu_cb, (void*)this ); | |||
| } | |||
| @@ -269,10 +269,10 @@ public: | |||
| void bbox ( int &X, int &Y, int &W, int &H ) | |||
| { | |||
| X = x() + 5; | |||
| Y = y() + 5; | |||
| W = w() - 10; | |||
| H = h() - 10; | |||
| X += + 5; | |||
| Y += 5; | |||
| W -= 10; | |||
| H -= 10; | |||
| } | |||
| Module ( int W, int H, const char *L = 0 ); | |||
| @@ -435,10 +435,10 @@ public: | |||
| protected: | |||
| void draw_connections ( void ); | |||
| void draw_label ( void ); | |||
| void draw_box ( void ); | |||
| void draw_label ( int X, int Y, int W, int H ); | |||
| void draw_box ( int X, int Y, int W, int H ); | |||
| virtual void draw ( void ) { Module::draw_box(); Module::draw_label(); } | |||
| virtual void draw ( void ) { Module::draw_box(x(),y(),w(),h()); Module::draw_label(x(),y(),w(),h()); } | |||
| virtual int handle ( int m ); | |||
| virtual void get ( Log_Entry &e ) const; | |||
| @@ -51,7 +51,7 @@ | |||
| #include "Mono_Pan_Module.H" | |||
| #include "Chain.H" | |||
| #include "Mixer_Strip.H" | |||
| #include "AUX_Module.H" | |||
| #include "NSM.H" | |||
| #include <signal.h> | |||
| @@ -156,6 +156,7 @@ main ( int argc, char **argv ) | |||
| LOG_REGISTER_CREATE( Mono_Pan_Module ); | |||
| LOG_REGISTER_CREATE( Meter_Indicator_Module ); | |||
| LOG_REGISTER_CREATE( Controller_Module ); | |||
| LOG_REGISTER_CREATE( AUX_Module ); | |||
| signal( SIGPIPE, SIG_IGN ); | |||
| @@ -49,6 +49,7 @@ src/DPM.C | |||
| src/Engine/Engine.C | |||
| src/Gain_Module.C | |||
| src/JACK_Module.C | |||
| src/AUX_Module.C | |||
| src/LADSPAInfo.C | |||
| src/Meter_Indicator_Module.C | |||
| src/Meter_Module.C | |||