2011-01-17 17:53:34 +00:00
/*
Copyright ( C ) 2004 - 2011 Paul Davis
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 . , 675 Mass Ave , Cambridge , MA 0213 9 , USA .
*/
# include <inttypes.h>
# include <cmath>
# include <cerrno>
# include <cstdlib>
# include <string>
# include <cstdio>
# include <locale.h>
# include <unistd.h>
# include <float.h>
# include <iomanip>
# include <glibmm.h>
# include "pbd/cartesian.h"
2011-02-10 18:13:15 +00:00
# include "pbd/boost_debug.h"
2011-01-17 17:53:34 +00:00
# include "pbd/convert.h"
# include "pbd/error.h"
# include "pbd/failed_constructor.h"
# include "pbd/xml++.h"
# include "pbd/enumwriter.h"
# include "evoral/Curve.hpp"
# include "ardour/audio_buffer.h"
2014-01-13 15:13:37 +01:00
# include "ardour/audioengine.h"
2011-01-17 17:53:34 +00:00
# include "ardour/buffer_set.h"
2011-02-10 18:13:15 +00:00
# include "ardour/debug.h"
2014-01-13 15:13:37 +01:00
# include "ardour/pannable.h"
2011-01-17 17:53:34 +00:00
# include "ardour/panner.h"
# include "ardour/panner_manager.h"
# include "ardour/panner_shell.h"
# include "ardour/session.h"
2011-10-19 03:34:02 +00:00
# include "ardour/speakers.h"
2011-01-17 17:53:34 +00:00
# include "i18n.h"
# include "pbd/mathfix.h"
using namespace std ;
using namespace ARDOUR ;
using namespace PBD ;
2014-01-13 23:21:30 +01:00
PannerShell : : PannerShell ( string name , Session & s , boost : : shared_ptr < Pannable > p , bool is_send )
2011-01-17 17:53:34 +00:00
: SessionObject ( s , name )
2014-01-13 23:21:30 +01:00
, _pannable_route ( p )
, _is_send ( is_send )
, _panlinked ( true )
2011-07-14 22:17:43 +00:00
, _bypassed ( false )
2014-01-09 00:18:29 +01:00
, _current_panner_uri ( " " )
, _user_selected_panner_uri ( " " )
, _panner_gui_uri ( " " )
, _force_reselect ( false )
2011-01-17 17:53:34 +00:00
{
2014-01-13 23:21:30 +01:00
if ( is_send ) {
_pannable_internal . reset ( new Pannable ( s ) ) ;
if ( Config - > get_link_send_and_route_panner ( ) ) {
_panlinked = true ;
} else {
_panlinked = false ;
}
}
2011-01-17 17:53:34 +00:00
set_name ( name ) ;
}
PannerShell : : ~ PannerShell ( )
{
2014-01-13 23:21:30 +01:00
DEBUG_TRACE ( DEBUG : : Destruction , string_compose ( " panner shell %3 for %1 destructor, panner is %4, pannable is %2 \n " , _name , _pannable_route , this , _panner ) ) ;
2011-01-17 17:53:34 +00:00
}
void
PannerShell : : configure_io ( ChanCount in , ChanCount out )
{
2011-06-02 01:01:07 +00:00
uint32_t nouts = out . n_audio ( ) ;
uint32_t nins = in . n_audio ( ) ;
2011-01-17 17:53:34 +00:00
/* if new and old config don't need panning, or if
the config hasn ' t changed , we ' re done .
*/
2014-01-09 00:18:29 +01:00
if ( ! _force_reselect & & _panner & & ( _panner - > in ( ) . n_audio ( ) = = nins ) & & ( _panner - > out ( ) . n_audio ( ) = = nouts ) ) {
2011-06-02 01:01:07 +00:00
return ;
2011-01-17 17:53:34 +00:00
}
2014-01-15 02:50:17 +01:00
_force_reselect = false ;
2011-01-17 17:53:34 +00:00
if ( nouts < 2 | | nins = = 0 ) {
/* no need for panning with less than 2 outputs or no inputs */
if ( _panner ) {
2011-06-02 01:01:07 +00:00
_panner . reset ( ) ;
2014-01-09 00:18:29 +01:00
_current_panner_uri = " " ;
_panner_gui_uri = " " ;
2014-01-15 02:50:17 +01:00
if ( ! _is_send | | ! _panlinked ) {
pannable ( ) - > set_panner ( _panner ) ;
}
2011-01-17 17:53:34 +00:00
Changed ( ) ; /* EMIT SIGNAL */
}
return ;
}
2014-01-09 00:18:29 +01:00
PannerInfo * pi = PannerManager : : instance ( ) . select_panner ( in , out , _user_selected_panner_uri ) ;
2011-07-12 13:33:35 +00:00
if ( ! pi ) {
2014-03-23 03:10:00 +01:00
fatal < < _ ( " No panner found: check that panners are being discovered correctly during startup. " ) < < endmsg ;
2014-11-14 10:47:43 +01:00
abort ( ) ; /*NOTREACHED*/
2011-07-12 13:33:35 +00:00
}
2011-01-17 17:53:34 +00:00
2014-01-09 00:18:29 +01:00
DEBUG_TRACE ( DEBUG : : Panning , string_compose ( _ ( " select panner: %1 \n " ) , pi - > descriptor . name . c_str ( ) ) ) ;
2011-06-02 01:01:07 +00:00
boost : : shared_ptr < Speakers > speakers = _session . get_speakers ( ) ;
2011-02-17 16:43:55 +00:00
2011-06-02 01:01:07 +00:00
if ( nouts ! = speakers - > size ( ) ) {
/* hmm, output count doesn't match session speaker count so
create a new speaker set .
*/
Speakers * s = new Speakers ( ) ;
s - > setup_default_speakers ( nouts ) ;
speakers . reset ( s ) ;
}
2011-02-17 16:43:55 +00:00
2014-01-13 23:21:30 +01:00
/* TODO don't allow to link _is_send if internal & route panners are different types */
Panner * p = pi - > descriptor . factory ( pannable ( ) , speakers ) ;
2011-10-29 15:52:38 +00:00
// boost_debug_shared_ptr_mark_interesting (p, "Panner");
2011-06-02 01:01:07 +00:00
_panner . reset ( p ) ;
_panner - > configure_io ( in , out ) ;
2014-01-09 00:18:29 +01:00
_current_panner_uri = pi - > descriptor . panner_uri ;
_panner_gui_uri = pi - > descriptor . gui_uri ;
2011-01-27 01:31:03 +00:00
2014-01-15 02:50:17 +01:00
if ( ! _is_send | | ! _panlinked ) {
pannable ( ) - > set_panner ( _panner ) ;
}
2011-06-02 01:01:07 +00:00
Changed ( ) ; /* EMIT SIGNAL */
2011-01-17 17:53:34 +00:00
}
XMLNode &
2011-07-14 22:17:43 +00:00
PannerShell : : get_state ( )
2011-01-17 17:53:34 +00:00
{
XMLNode * node = new XMLNode ( " PannerShell " ) ;
2011-07-14 22:17:43 +00:00
node - > add_property ( X_ ( " bypassed " ) , _bypassed ? X_ ( " yes " ) : X_ ( " no " ) ) ;
2014-01-09 00:18:29 +01:00
node - > add_property ( X_ ( " user-panner " ) , _user_selected_panner_uri ) ;
2014-01-13 23:21:30 +01:00
node - > add_property ( X_ ( " linked-to-route " ) , _panlinked ? X_ ( " yes " ) : X_ ( " no " ) ) ;
2011-07-14 22:17:43 +00:00
2014-01-13 23:21:30 +01:00
if ( _panner & & _is_send ) {
2011-07-14 22:17:43 +00:00
node - > add_child_nocopy ( _panner - > get_state ( ) ) ;
2011-01-17 17:53:34 +00:00
}
return * node ;
}
int
PannerShell : : set_state ( const XMLNode & node , int version )
{
XMLNodeList nlist = node . children ( ) ;
XMLNodeConstIterator niter ;
const XMLProperty * prop ;
2015-01-19 07:29:25 -06:00
LocaleGuard lg ( X_ ( " C " ) ) ;
2011-01-17 17:53:34 +00:00
2011-07-14 22:17:43 +00:00
if ( ( prop = node . property ( X_ ( " bypassed " ) ) ) ! = 0 ) {
set_bypassed ( string_is_affirmative ( prop - > value ( ) ) ) ;
}
2014-01-13 23:21:30 +01:00
if ( ( prop = node . property ( X_ ( " linked-to-route " ) ) ) ! = 0 ) {
_panlinked = string_is_affirmative ( prop - > value ( ) ) ;
}
2014-01-09 00:18:29 +01:00
if ( ( prop = node . property ( X_ ( " user-panner " ) ) ) ! = 0 ) {
_user_selected_panner_uri = prop - > value ( ) ;
}
2011-06-02 01:01:07 +00:00
_panner . reset ( ) ;
2015-10-05 16:17:49 +02:00
2011-01-17 17:53:34 +00:00
for ( niter = nlist . begin ( ) ; niter ! = nlist . end ( ) ; + + niter ) {
if ( ( * niter ) - > name ( ) = = X_ ( " Panner " ) ) {
2014-01-09 00:18:29 +01:00
if ( ( prop = ( * niter ) - > property ( X_ ( " uri " ) ) ) ) {
PannerInfo * p = PannerManager : : instance ( ) . get_by_uri ( prop - > value ( ) ) ;
if ( p ) {
2014-01-13 23:21:30 +01:00
_panner . reset ( p - > descriptor . factory (
_is_send ? _pannable_internal : _pannable_route , _session . get_speakers ( ) ) ) ;
2014-01-09 00:18:29 +01:00
_current_panner_uri = p - > descriptor . panner_uri ;
_panner_gui_uri = p - > descriptor . gui_uri ;
2014-01-15 02:50:17 +01:00
if ( _is_send ) {
if ( ! _panlinked ) {
_pannable_internal - > set_panner ( _panner ) ;
} else {
_force_reselect = true ;
}
} else {
_pannable_route - > set_panner ( _panner ) ;
}
2014-01-09 00:18:29 +01:00
if ( _panner - > set_state ( * * niter , version ) = = 0 ) {
return - 1 ;
}
}
}
else /* backwards compatibility */
2011-01-17 17:53:34 +00:00
if ( ( prop = ( * niter ) - > property ( X_ ( " type " ) ) ) ) {
2011-06-02 01:01:07 +00:00
list < PannerInfo * > : : iterator p ;
PannerManager & pm ( PannerManager : : instance ( ) ) ;
2011-01-17 17:53:34 +00:00
for ( p = pm . panner_info . begin ( ) ; p ! = pm . panner_info . end ( ) ; + + p ) {
if ( prop - > value ( ) = = ( * p ) - > descriptor . name ) {
/* note that we assume that all the stream panners
are of the same type . pretty good
assumption , but it ' s still an assumption .
*/
2011-06-01 16:50:12 +00:00
2014-01-13 23:21:30 +01:00
_panner . reset ( ( * p ) - > descriptor . factory (
_is_send ? _pannable_internal : _pannable_route , _session . get_speakers ( ) ) ) ;
2014-01-09 00:18:29 +01:00
_current_panner_uri = ( * p ) - > descriptor . panner_uri ;
_panner_gui_uri = ( * p ) - > descriptor . gui_uri ;
2011-06-01 16:50:12 +00:00
2014-05-30 03:54:15 +02:00
if ( _is_send ) {
if ( ! _panlinked ) {
_pannable_internal - > set_panner ( _panner ) ;
} else {
_force_reselect = true ;
}
} else {
_pannable_route - > set_panner ( _panner ) ;
}
2011-01-17 17:53:34 +00:00
if ( _panner - > set_state ( * * niter , version ) = = 0 ) {
2011-06-02 01:01:07 +00:00
return - 1 ;
}
2011-01-17 17:53:34 +00:00
break ;
}
}
if ( p = = pm . panner_info . end ( ) ) {
error < < string_compose ( _ ( " Unknown panner plugin \" %1 \" found in pan state - ignored " ) ,
2011-06-02 01:01:07 +00:00
prop - > value ( ) )
2011-01-17 17:53:34 +00:00
< < endmsg ;
}
} else {
error < < _ ( " panner plugin node has no type information! " )
< < endmsg ;
return - 1 ;
}
}
}
return 0 ;
}
void
PannerShell : : distribute_no_automation ( BufferSet & inbufs , BufferSet & outbufs , pframes_t nframes , gain_t gain_coeff )
{
if ( outbufs . count ( ) . n_audio ( ) = = 0 ) {
// Don't want to lose audio...
assert ( inbufs . count ( ) . n_audio ( ) = = 0 ) ;
return ;
}
if ( outbufs . count ( ) . n_audio ( ) = = 1 ) {
2011-06-02 01:01:07 +00:00
/* just one output: no real panning going on */
2011-01-17 17:53:34 +00:00
AudioBuffer & dst = outbufs . get_audio ( 0 ) ;
2015-03-25 16:47:25 -05:00
if ( gain_coeff = = GAIN_COEFF_ZERO ) {
2011-01-17 17:53:34 +00:00
/* gain was zero, so make it silent */
dst . silence ( nframes ) ;
2015-03-25 16:47:25 -05:00
} else if ( gain_coeff = = GAIN_COEFF_UNITY ) {
2011-01-17 17:53:34 +00:00
/* mix all input buffers into the output */
// copy the first
dst . read_from ( inbufs . get_audio ( 0 ) , nframes ) ;
// accumulate starting with the second
if ( inbufs . count ( ) . n_audio ( ) > 0 ) {
BufferSet : : audio_iterator i = inbufs . audio_begin ( ) ;
for ( + + i ; i ! = inbufs . audio_end ( ) ; + + i ) {
dst . merge_from ( * i , nframes ) ;
}
}
} else {
/* mix all buffers into the output, scaling them all by the gain */
// copy the first
dst . read_from ( inbufs . get_audio ( 0 ) , nframes ) ;
// accumulate (with gain) starting with the second
if ( inbufs . count ( ) . n_audio ( ) > 0 ) {
BufferSet : : audio_iterator i = inbufs . audio_begin ( ) ;
for ( + + i ; i ! = inbufs . audio_end ( ) ; + + i ) {
dst . accumulate_with_gain_from ( * i , nframes , gain_coeff ) ;
}
}
}
return ;
}
2011-06-02 01:01:07 +00:00
/* multiple outputs ... we must have a panner */
2011-01-17 17:53:34 +00:00
2011-06-02 01:01:07 +00:00
assert ( _panner ) ;
2011-01-17 17:53:34 +00:00
/* setup silent buffers so that we can mix into the outbuffers (slightly suboptimal -
2011-06-02 01:01:07 +00:00
better to copy the first set of data then mix after that , but hey , its 2011 )
*/
2011-01-17 17:53:34 +00:00
for ( BufferSet : : audio_iterator b = outbufs . audio_begin ( ) ; b ! = outbufs . audio_end ( ) ; + + b ) {
( * b ) . silence ( nframes ) ;
}
2011-06-02 01:01:07 +00:00
_panner - > distribute ( inbufs , outbufs , gain_coeff , nframes ) ;
2011-01-17 17:53:34 +00:00
}
void
PannerShell : : run ( BufferSet & inbufs , BufferSet & outbufs , framepos_t start_frame , framepos_t end_frame , pframes_t nframes )
{
2011-10-07 16:30:27 +00:00
if ( inbufs . count ( ) . n_audio ( ) = = 0 ) {
/* Input has no audio buffers (e.g. Aux Send in a MIDI track at a
2011-11-20 17:49:05 +00:00
point with no audio because there is no preceding instrument )
2011-10-07 16:30:27 +00:00
*/
outbufs . silence ( nframes , 0 ) ;
return ;
}
2011-01-17 17:53:34 +00:00
if ( outbufs . count ( ) . n_audio ( ) = = 0 ) {
// Failing to deliver audio we were asked to deliver is a bug
assert ( inbufs . count ( ) . n_audio ( ) = = 0 ) ;
return ;
}
if ( outbufs . count ( ) . n_audio ( ) = = 1 ) {
2011-06-02 01:01:07 +00:00
/* one output only: no panner */
2011-01-17 17:53:34 +00:00
AudioBuffer & dst = outbufs . get_audio ( 0 ) ;
// FIXME: apply gain automation?
// copy the first
dst . read_from ( inbufs . get_audio ( 0 ) , nframes ) ;
// accumulate starting with the second
BufferSet : : audio_iterator i = inbufs . audio_begin ( ) ;
for ( + + i ; i ! = inbufs . audio_end ( ) ; + + i ) {
dst . merge_from ( * i , nframes ) ;
}
return ;
}
// More than 1 output
2011-06-02 01:01:07 +00:00
AutoState as = _panner - > automation_state ( ) ;
2011-01-17 17:53:34 +00:00
// If we shouldn't play automation defer to distribute_no_automation
if ( ! ( as & Play | | ( ( as & Touch ) & & ! _panner - > touching ( ) ) ) ) {
2015-09-15 17:01:12 -04:00
distribute_no_automation ( inbufs , outbufs , nframes , 1.0 ) ;
2011-01-17 17:53:34 +00:00
} else {
2011-06-02 01:01:07 +00:00
/* setup the terrible silence so that we can mix into the outbuffers (slightly suboptimal -
better to copy the first set of data then mix after that , but hey , its 2011 )
*/
for ( BufferSet : : audio_iterator i = outbufs . audio_begin ( ) ; i ! = outbufs . audio_end ( ) ; + + i ) {
i - > silence ( nframes ) ;
}
2011-06-01 16:50:12 +00:00
2011-06-02 01:01:07 +00:00
_panner - > distribute_automated ( inbufs , outbufs , start_frame , end_frame , nframes , _session . pan_automation_buffer ( ) ) ;
}
2011-01-17 17:53:34 +00:00
}
2011-07-14 22:17:43 +00:00
void
PannerShell : : set_bypassed ( bool yn )
{
if ( yn = = _bypassed ) {
return ;
}
2015-10-05 16:17:49 +02:00
2011-07-14 22:17:43 +00:00
_bypassed = yn ;
2014-01-18 13:19:14 +01:00
_session . set_dirty ( ) ;
2011-07-14 22:17:43 +00:00
Changed ( ) ; /* EMIT SIGNAL */
}
bool
PannerShell : : bypassed ( ) const
{
return _bypassed ;
}
2014-01-09 00:18:29 +01:00
/* set custom-panner config
*
* This function is intended to be only called from
* Route : : set_custom_panner ( )
* which will trigger IO - reconfigutaion if this fn return true
*/
bool
PannerShell : : set_user_selected_panner_uri ( std : : string const uri )
{
if ( uri = = _user_selected_panner_uri ) return false ;
_user_selected_panner_uri = uri ;
if ( uri = = _current_panner_uri ) return false ;
_force_reselect = true ;
return true ;
}
2014-01-13 15:13:37 +01:00
bool
PannerShell : : select_panner_by_uri ( std : : string const uri )
{
if ( uri = = _user_selected_panner_uri ) return false ;
_user_selected_panner_uri = uri ;
if ( uri = = _current_panner_uri ) return false ;
_force_reselect = true ;
if ( _panner ) {
Glib : : Threads : : Mutex : : Lock lx ( AudioEngine : : instance ( ) - > process_lock ( ) ) ;
ChanCount in = _panner - > in ( ) ;
ChanCount out = _panner - > out ( ) ;
configure_io ( in , out ) ;
2014-01-15 02:50:17 +01:00
if ( ! _is_send | | ! _panlinked ) {
pannable ( ) - > set_panner ( _panner ) ;
}
2014-01-13 15:13:37 +01:00
_session . set_dirty ( ) ;
}
return true ;
}
2014-01-13 23:21:30 +01:00
void
PannerShell : : set_linked_to_route ( bool onoff )
{
2014-01-15 02:50:17 +01:00
assert ( _is_send ) ;
if ( onoff = = _panlinked ) {
2014-01-13 23:21:30 +01:00
return ;
}
2014-01-15 02:50:17 +01:00
/* set _pannable-_has_state = true
* this way the panners will pick it up
* when it is re - created
*/
if ( pannable ( ) ) {
XMLNode state = pannable ( ) - > get_state ( ) ;
pannable ( ) - > set_state ( state , 3000 ) ;
}
2014-01-13 23:21:30 +01:00
_panlinked = onoff ;
_force_reselect = true ;
if ( _panner ) {
Glib : : Threads : : Mutex : : Lock lx ( AudioEngine : : instance ( ) - > process_lock ( ) ) ;
ChanCount in = _panner - > in ( ) ;
ChanCount out = _panner - > out ( ) ;
configure_io ( in , out ) ;
2014-01-15 02:50:17 +01:00
if ( ! _panlinked ) {
pannable ( ) - > set_panner ( _panner ) ;
}
2014-01-13 23:21:30 +01:00
_session . set_dirty ( ) ;
}
PannableChanged ( ) ;
}