2011-01-17 12:53:34 -05:00
/*
2019-08-02 22:01:25 -04:00
* Copyright ( C ) 2011 - 2012 David Robillard < d @ drobilla . net >
* Copyright ( C ) 2011 - 2017 Paul Davis < paul @ linuxaudiosystems . com >
* Copyright ( C ) 2011 Carl Hetherington < carl @ carlh . net >
* Copyright ( C ) 2014 - 2017 Robin Gareus < robin @ gareus . org >
* Copyright ( C ) 2015 Ben Loftis < ben @ harrisonconsoles . com >
* Copyright ( C ) 2016 Tim Mayberry < mojofunk @ gmail . com >
*
* 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 . ,
* 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA .
*/
2011-01-17 12:53:34 -05:00
# 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"
# include "pbd/convert.h"
2020-03-15 16:39:53 -04:00
# include "pbd/enumwriter.h"
2011-01-17 12:53:34 -05:00
# include "pbd/error.h"
# include "pbd/failed_constructor.h"
2020-03-15 16:39:53 -04:00
# include "pbd/stateful.h"
2011-01-17 12:53:34 -05:00
# include "pbd/xml++.h"
2019-10-25 15:13:51 -04:00
# include "evoral/Curve.h"
2011-01-17 12:53:34 -05:00
# include "ardour/audio_buffer.h"
2014-01-13 09:13:37 -05:00
# include "ardour/audioengine.h"
2016-05-07 13:32:31 -04:00
# include "ardour/boost_debug.h"
2011-01-17 12:53:34 -05:00
# include "ardour/buffer_set.h"
2011-02-10 13:13:15 -05:00
# include "ardour/debug.h"
2014-01-13 09:13:37 -05:00
# include "ardour/pannable.h"
2011-01-17 12:53:34 -05:00
# include "ardour/panner.h"
# include "ardour/panner_manager.h"
# include "ardour/panner_shell.h"
2016-04-20 15:44:28 -04:00
# include "ardour/profile.h"
2011-01-17 12:53:34 -05:00
# include "ardour/session.h"
2011-10-18 23:34:02 -04:00
# include "ardour/speakers.h"
2011-01-17 12:53:34 -05:00
2016-07-14 14:44:52 -04:00
# include "pbd/i18n.h"
2011-01-17 12:53:34 -05:00
# include "pbd/mathfix.h"
using namespace std ;
using namespace ARDOUR ;
using namespace PBD ;
2023-07-21 11:56:42 -04:00
PannerShell : : PannerShell ( string name , Session & s , std : : shared_ptr < Pannable > p , Temporal : : TimeDomainProvider const & tdp , bool is_send )
2011-01-17 12:53:34 -05:00
: SessionObject ( s , name )
2014-01-13 17:21:30 -05:00
, _pannable_route ( p )
, _is_send ( is_send )
, _panlinked ( true )
2011-07-14 18:17:43 -04:00
, _bypassed ( false )
2014-01-08 18:18:29 -05:00
, _current_panner_uri ( " " )
, _user_selected_panner_uri ( " " )
, _panner_gui_uri ( " " )
, _force_reselect ( false )
2011-01-17 12:53:34 -05:00
{
2014-01-13 17:21:30 -05:00
if ( is_send ) {
2023-07-21 11:56:42 -04:00
_pannable_internal . reset ( new Pannable ( s , tdp ) ) ;
2020-03-23 12:45:38 -04:00
if ( Config - > get_link_send_and_route_panner ( ) ) {
2014-01-13 17:21:30 -05:00
_panlinked = true ;
} else {
_panlinked = false ;
}
}
2011-01-17 12:53:34 -05:00
set_name ( name ) ;
}
PannerShell : : ~ PannerShell ( )
{
2014-01-13 17:21:30 -05: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 12:53:34 -05:00
}
void
PannerShell : : configure_io ( ChanCount in , ChanCount out )
{
2011-06-01 21:01:07 -04:00
uint32_t nouts = out . n_audio ( ) ;
uint32_t nins = in . n_audio ( ) ;
2011-01-17 12:53:34 -05:00
/* if new and old config don't need panning, or if
the config hasn ' t changed , we ' re done .
*/
2014-01-08 18:18:29 -05:00
if ( ! _force_reselect & & _panner & & ( _panner - > in ( ) . n_audio ( ) = = nins ) & & ( _panner - > out ( ) . n_audio ( ) = = nouts ) ) {
2011-06-01 21:01:07 -04:00
return ;
2011-01-17 12:53:34 -05:00
}
2014-01-14 20:50:17 -05:00
_force_reselect = false ;
2011-01-17 12:53:34 -05:00
if ( nouts < 2 | | nins = = 0 ) {
/* no need for panning with less than 2 outputs or no inputs */
if ( _panner ) {
2011-06-01 21:01:07 -04:00
_panner . reset ( ) ;
2014-01-08 18:18:29 -05:00
_current_panner_uri = " " ;
_panner_gui_uri = " " ;
2014-01-14 20:50:17 -05:00
if ( ! _is_send | | ! _panlinked ) {
pannable ( ) - > set_panner ( _panner ) ;
}
2011-01-17 12:53:34 -05:00
Changed ( ) ; /* EMIT SIGNAL */
}
return ;
}
2014-01-08 18:18:29 -05:00
PannerInfo * pi = PannerManager : : instance ( ) . select_panner ( in , out , _user_selected_panner_uri ) ;
2011-07-12 09:33:35 -04:00
if ( ! pi ) {
2014-03-22 22:10:00 -04:00
fatal < < _ ( " No panner found: check that panners are being discovered correctly during startup. " ) < < endmsg ;
2014-11-14 04:47:43 -05:00
abort ( ) ; /*NOTREACHED*/
2011-07-12 09:33:35 -04:00
}
2020-03-15 16:39:53 -04:00
if ( Stateful : : loading_state_version < 6000 & & pi - > descriptor . in = = 2 ) {
_user_selected_panner_uri = pi - > descriptor . panner_uri ;
}
2011-01-17 12:53:34 -05:00
2014-01-08 18:18:29 -05:00
DEBUG_TRACE ( DEBUG : : Panning , string_compose ( _ ( " select panner: %1 \n " ) , pi - > descriptor . name . c_str ( ) ) ) ;
2023-02-16 18:33:28 -05:00
std : : shared_ptr < Speakers > speakers = _session . get_speakers ( ) ;
2011-02-17 11:43:55 -05:00
2011-06-01 21:01:07 -04: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 11:43:55 -05:00
2014-01-13 17:21:30 -05: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 11:52:38 -04:00
// boost_debug_shared_ptr_mark_interesting (p, "Panner");
2011-06-01 21:01:07 -04:00
_panner . reset ( p ) ;
_panner - > configure_io ( in , out ) ;
2014-01-08 18:18:29 -05:00
_current_panner_uri = pi - > descriptor . panner_uri ;
_panner_gui_uri = pi - > descriptor . gui_uri ;
2011-01-26 20:31:03 -05:00
2014-01-14 20:50:17 -05:00
if ( ! _is_send | | ! _panlinked ) {
pannable ( ) - > set_panner ( _panner ) ;
}
2011-06-01 21:01:07 -04:00
Changed ( ) ; /* EMIT SIGNAL */
2011-01-17 12:53:34 -05:00
}
XMLNode &
2022-04-06 23:56:32 -04:00
PannerShell : : get_state ( ) const
2011-01-17 12:53:34 -05:00
{
XMLNode * node = new XMLNode ( " PannerShell " ) ;
2016-08-27 02:03:42 -04:00
node - > set_property ( X_ ( " bypassed " ) , _bypassed ) ;
node - > set_property ( X_ ( " user-panner " ) , _user_selected_panner_uri ) ;
node - > set_property ( X_ ( " linked-to-route " ) , _panlinked ) ;
2011-07-14 18:17:43 -04:00
2014-01-13 17:21:30 -05:00
if ( _panner & & _is_send ) {
2011-07-14 18:17:43 -04:00
node - > add_child_nocopy ( _panner - > get_state ( ) ) ;
2011-01-17 12:53:34 -05:00
}
return * node ;
}
int
PannerShell : : set_state ( const XMLNode & node , int version )
{
XMLNodeList nlist = node . children ( ) ;
XMLNodeConstIterator niter ;
2016-08-27 02:03:42 -04:00
bool yn ;
std : : string str ;
2011-01-17 12:53:34 -05:00
2016-08-27 02:03:42 -04:00
if ( node . get_property ( X_ ( " bypassed " ) , yn ) ) {
set_bypassed ( yn ) ;
2011-07-14 18:17:43 -04:00
}
2016-08-27 02:03:42 -04:00
if ( node . get_property ( X_ ( " linked-to-route " ) , yn ) ) {
2020-03-23 12:45:38 -04:00
_panlinked = yn ;
2014-01-13 17:21:30 -05:00
}
2016-08-27 02:03:42 -04:00
node . get_property ( X_ ( " user-panner " ) , _user_selected_panner_uri ) ;
2014-01-08 18:18:29 -05:00
2011-06-01 21:01:07 -04:00
_panner . reset ( ) ;
2015-10-05 10:17:49 -04:00
2011-01-17 12:53:34 -05:00
for ( niter = nlist . begin ( ) ; niter ! = nlist . end ( ) ; + + niter ) {
if ( ( * niter ) - > name ( ) = = X_ ( " Panner " ) ) {
2016-08-27 02:03:42 -04:00
if ( ( * niter ) - > get_property ( X_ ( " uri " ) , str ) ) {
PannerInfo * p = PannerManager : : instance ( ) . get_by_uri ( str ) ;
2014-01-08 18:18:29 -05:00
if ( p ) {
2024-04-02 20:06:38 -04:00
_panner . reset ( p - > descriptor . factory ( _is_send ? _pannable_internal : _pannable_route , _session . get_speakers ( ) ) ) ;
2014-01-08 18:18:29 -05:00
_current_panner_uri = p - > descriptor . panner_uri ;
_panner_gui_uri = p - > descriptor . gui_uri ;
2014-01-14 20:50:17 -05:00
if ( _is_send ) {
if ( ! _panlinked ) {
_pannable_internal - > set_panner ( _panner ) ;
} else {
_force_reselect = true ;
}
} else {
_pannable_route - > set_panner ( _panner ) ;
}
2014-01-08 18:18:29 -05:00
if ( _panner - > set_state ( * * niter , version ) = = 0 ) {
return - 1 ;
}
}
}
else /* backwards compatibility */
2016-08-27 02:03:42 -04:00
if ( ( * niter ) - > get_property ( X_ ( " type " ) , str ) ) {
2011-01-17 12:53:34 -05:00
2011-06-01 21:01:07 -04:00
list < PannerInfo * > : : iterator p ;
PannerManager & pm ( PannerManager : : instance ( ) ) ;
2011-01-17 12:53:34 -05:00
for ( p = pm . panner_info . begin ( ) ; p ! = pm . panner_info . end ( ) ; + + p ) {
2016-08-27 02:03:42 -04:00
if ( str = = ( * p ) - > descriptor . name ) {
2011-01-17 12:53:34 -05:00
/* 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 12:50:12 -04:00
2014-01-13 17:21:30 -05:00
_panner . reset ( ( * p ) - > descriptor . factory (
_is_send ? _pannable_internal : _pannable_route , _session . get_speakers ( ) ) ) ;
2014-01-08 18:18:29 -05:00
_current_panner_uri = ( * p ) - > descriptor . panner_uri ;
_panner_gui_uri = ( * p ) - > descriptor . gui_uri ;
2011-06-01 12:50:12 -04:00
2014-05-29 21:54:15 -04:00
if ( _is_send ) {
if ( ! _panlinked ) {
_pannable_internal - > set_panner ( _panner ) ;
} else {
_force_reselect = true ;
}
} else {
_pannable_route - > set_panner ( _panner ) ;
}
2011-01-17 12:53:34 -05:00
if ( _panner - > set_state ( * * niter , version ) = = 0 ) {
2011-06-01 21:01:07 -04:00
return - 1 ;
}
2011-01-17 12:53:34 -05:00
break ;
}
}
if ( p = = pm . panner_info . end ( ) ) {
error < < string_compose ( _ ( " Unknown panner plugin \" %1 \" found in pan state - ignored " ) ,
2016-08-27 02:03:42 -04:00
str )
2011-01-17 12:53:34 -05: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-01 21:01:07 -04:00
/* just one output: no real panning going on */
2011-01-17 12:53:34 -05:00
AudioBuffer & dst = outbufs . get_audio ( 0 ) ;
2015-03-25 17:47:25 -04:00
if ( gain_coeff = = GAIN_COEFF_ZERO ) {
2011-01-17 12:53:34 -05:00
/* gain was zero, so make it silent */
dst . silence ( nframes ) ;
2015-03-25 17:47:25 -04:00
} else if ( gain_coeff = = GAIN_COEFF_UNITY ) {
2011-01-17 12:53:34 -05: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-01 21:01:07 -04:00
/* multiple outputs ... we must have a panner */
2011-01-17 12:53:34 -05:00
2011-06-01 21:01:07 -04:00
assert ( _panner ) ;
2011-01-17 12:53:34 -05:00
/* setup silent buffers so that we can mix into the outbuffers (slightly suboptimal -
2011-06-01 21:01:07 -04:00
better to copy the first set of data then mix after that , but hey , its 2011 )
*/
2011-01-17 12:53:34 -05:00
for ( BufferSet : : audio_iterator b = outbufs . audio_begin ( ) ; b ! = outbufs . audio_end ( ) ; + + b ) {
( * b ) . silence ( nframes ) ;
}
2011-06-01 21:01:07 -04:00
_panner - > distribute ( inbufs , outbufs , gain_coeff , nframes ) ;
2011-01-17 12:53:34 -05:00
}
void
2017-09-18 12:39:17 -04:00
PannerShell : : run ( BufferSet & inbufs , BufferSet & outbufs , samplepos_t start_sample , samplepos_t end_sample , pframes_t nframes )
2011-01-17 12:53:34 -05:00
{
2011-10-07 12:30:27 -04: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 12:49:05 -05:00
point with no audio because there is no preceding instrument )
2011-10-07 12:30:27 -04:00
*/
outbufs . silence ( nframes , 0 ) ;
return ;
}
2011-01-17 12:53:34 -05: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-01 21:01:07 -04:00
/* one output only: no panner */
2011-01-17 12:53:34 -05: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
2020-03-23 11:41:22 -04:00
AutoState as = pannable ( ) - > automation_state ( ) ;
2011-01-17 12:53:34 -05:00
// If we shouldn't play automation defer to distribute_no_automation
2020-03-23 11:41:22 -04:00
if ( ! ( ( as & Play ) | | ( ( as & ( Touch | Latch ) ) & & ! pannable ( ) - > touching ( ) ) ) ) {
2011-01-17 12:53:34 -05:00
2015-09-15 17:01:12 -04:00
distribute_no_automation ( inbufs , outbufs , nframes , 1.0 ) ;
2011-01-17 12:53:34 -05:00
} else {
2011-06-01 21:01:07 -04: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 12:50:12 -04:00
2017-09-18 12:39:17 -04:00
_panner - > distribute_automated ( inbufs , outbufs , start_sample , end_sample , nframes , _session . pan_automation_buffer ( ) ) ;
2011-06-01 21:01:07 -04:00
}
2011-01-17 12:53:34 -05:00
}
2011-07-14 18:17:43 -04:00
void
PannerShell : : set_bypassed ( bool yn )
{
if ( yn = = _bypassed ) {
return ;
}
2015-10-05 10:17:49 -04:00
2011-07-14 18:17:43 -04:00
_bypassed = yn ;
2014-01-18 07:19:14 -05:00
_session . set_dirty ( ) ;
2011-07-14 18:17:43 -04:00
Changed ( ) ; /* EMIT SIGNAL */
}
bool
PannerShell : : bypassed ( ) const
{
return _bypassed ;
}
2014-01-08 18:18:29 -05: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 09:13:37 -05: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-14 20:50:17 -05:00
if ( ! _is_send | | ! _panlinked ) {
pannable ( ) - > set_panner ( _panner ) ;
}
2014-01-13 09:13:37 -05:00
_session . set_dirty ( ) ;
}
return true ;
}
2014-01-13 17:21:30 -05:00
void
PannerShell : : set_linked_to_route ( bool onoff )
{
2014-01-14 20:50:17 -05:00
assert ( _is_send ) ;
if ( onoff = = _panlinked ) {
2014-01-13 17:21:30 -05:00
return ;
}
2014-01-14 20:50:17 -05: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 ( ) ;
2024-04-02 20:06:58 -04:00
pannable ( ) - > set_state ( state , Stateful : : loading_state_version ) ;
2014-01-14 20:50:17 -05:00
}
2014-01-13 17:21:30 -05: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-14 20:50:17 -05:00
if ( ! _panlinked ) {
pannable ( ) - > set_panner ( _panner ) ;
}
2014-01-13 17:21:30 -05:00
_session . set_dirty ( ) ;
}
PannableChanged ( ) ;
}