mirror of
https://github.com/Ardour/ardour.git
synced 2025-12-06 14:54:56 +01:00
Vapor: Implement Surround Send/Return
This commit is contained in:
parent
ec32d22cb3
commit
b84cd4fb5a
17 changed files with 1752 additions and 17 deletions
|
|
@ -134,7 +134,6 @@ CONFIG_VARIABLE (ListenPosition, listen_position, "listen-position", AfterFaderL
|
|||
CONFIG_VARIABLE (PFLPosition, pfl_position, "pfl-position", PFLFromAfterProcessors)
|
||||
CONFIG_VARIABLE (AFLPosition, afl_position, "afl-position", AFLFromAfterProcessors)
|
||||
CONFIG_VARIABLE (bool, use_monitor_bus, "use-monitor-bus", false)
|
||||
CONFIG_VARIABLE (bool, use_surround_master, "use-surround-master", false)
|
||||
|
||||
CONFIG_VARIABLE (bool, solo_control_is_listen_control, "solo-control-is-listen-control", false)
|
||||
CONFIG_VARIABLE (bool, exclusive_solo, "exclusive-solo", false)
|
||||
|
|
|
|||
|
|
@ -97,6 +97,8 @@ class SoloIsolateControl;
|
|||
class PhaseControl;
|
||||
class MonitorControl;
|
||||
class TriggerBox;
|
||||
class SurroundReturn;
|
||||
class SurroundSend;
|
||||
|
||||
class LIBARDOUR_API Route : public Stripable,
|
||||
public GraphNode,
|
||||
|
|
@ -141,6 +143,8 @@ public:
|
|||
static void set_name_in_state (XMLNode &, const std::string &);
|
||||
|
||||
std::shared_ptr<MonitorControl> monitoring_control() const { return _monitoring_control; }
|
||||
std::shared_ptr<SurroundSend> surround_send() const { return _surround_send; }
|
||||
std::shared_ptr<SurroundReturn> surround_return() const { return _surround_return; }
|
||||
|
||||
MonitorState monitoring_state () const;
|
||||
virtual MonitorState get_input_monitoring_state (bool recording, bool talkback) const { return MonitoringSilence; }
|
||||
|
|
@ -183,7 +187,7 @@ public:
|
|||
void push_solo_upstream (int32_t delta);
|
||||
void push_solo_isolate_upstream (int32_t delta);
|
||||
bool can_solo () const {
|
||||
return !(is_master() || is_monitor() || is_auditioner() || is_foldbackbus());
|
||||
return !(is_singleton() || is_auditioner() || is_foldbackbus());
|
||||
}
|
||||
bool is_safe () const {
|
||||
return _solo_safe_control->get_value();
|
||||
|
|
@ -192,6 +196,7 @@ public:
|
|||
return can_solo() || is_foldbackbus ();
|
||||
}
|
||||
void enable_monitor_send ();
|
||||
void enable_surround_send ();
|
||||
|
||||
void set_denormal_protection (bool yn);
|
||||
bool denormal_protection() const;
|
||||
|
|
@ -260,7 +265,11 @@ public:
|
|||
|
||||
std::shared_ptr<AutomationControl> automation_control_recurse (PBD::ID const & id) const;
|
||||
|
||||
void automatables (PBD::ControllableSet&) const;
|
||||
void automatables (PBD::ControllableSet&) const;
|
||||
|
||||
void queue_surround_processors_changed () {
|
||||
_pending_surround_send.store (1);
|
||||
}
|
||||
|
||||
/* special processors */
|
||||
|
||||
|
|
@ -433,6 +442,7 @@ public:
|
|||
int add_aux_send (std::shared_ptr<Route>, std::shared_ptr<Processor>);
|
||||
int add_foldback_send (std::shared_ptr<Route>, bool post_fader);
|
||||
void remove_monitor_send ();
|
||||
void remove_surround_send ();
|
||||
|
||||
/**
|
||||
* return true if this route feeds the first argument directly, via
|
||||
|
|
@ -669,6 +679,8 @@ protected:
|
|||
std::shared_ptr<BeatBox> _beatbox;
|
||||
#endif
|
||||
std::shared_ptr<MonitorControl> _monitoring_control;
|
||||
std::shared_ptr<SurroundSend> _surround_send;
|
||||
std::shared_ptr<SurroundReturn> _surround_return;
|
||||
|
||||
DiskIOPoint _disk_io_point;
|
||||
|
||||
|
|
@ -676,13 +688,15 @@ protected:
|
|||
EmitNone = 0x00,
|
||||
EmitMeterChanged = 0x01,
|
||||
EmitMeterVisibilityChange = 0x02,
|
||||
EmitRtProcessorChange = 0x04
|
||||
EmitRtProcessorChange = 0x04,
|
||||
EmitSendReturnChange = 0x08
|
||||
};
|
||||
|
||||
ProcessorList _pending_processor_order;
|
||||
std::atomic<int> _pending_process_reorder; // atomic
|
||||
std::atomic<int> _pending_listen_change; // atomic
|
||||
std::atomic<int> _pending_signals; // atomic
|
||||
ProcessorList _pending_processor_order;
|
||||
std::atomic<int> _pending_process_reorder;
|
||||
std::atomic<int> _pending_listen_change;
|
||||
std::atomic<int> _pending_surround_send;
|
||||
std::atomic<int> _pending_signals;
|
||||
|
||||
MeterPoint _meter_point;
|
||||
MeterPoint _pending_meter_point;
|
||||
|
|
|
|||
|
|
@ -986,6 +986,7 @@ public:
|
|||
PBD::Signal0<void> IsolatedChanged;
|
||||
PBD::Signal0<void> MonitorChanged;
|
||||
PBD::Signal0<void> MonitorBusAddedOrRemoved;
|
||||
PBD::Signal0<void> SurroundMasterAddedOrRemoved;
|
||||
|
||||
PBD::Signal0<void> session_routes_reconnected;
|
||||
|
||||
|
|
@ -998,6 +999,7 @@ public:
|
|||
std::shared_ptr<Route> monitor_out() const { return _monitor_out; }
|
||||
std::shared_ptr<Route> master_out() const { return _master_out; }
|
||||
std::shared_ptr<GainControl> master_volume () const;
|
||||
std::shared_ptr<Route> surround_master() const { return _surround_master; }
|
||||
|
||||
PresentationInfo::order_t master_order_key () const { return _master_out ? _master_out->presentation_info ().order () : -1; }
|
||||
bool ensure_stripable_sort_order ();
|
||||
|
|
@ -1024,18 +1026,24 @@ public:
|
|||
}
|
||||
|
||||
uint32_t next_send_id();
|
||||
uint32_t next_surround_send_id();
|
||||
uint32_t next_aux_send_id();
|
||||
uint32_t next_return_id();
|
||||
uint32_t next_insert_id();
|
||||
void mark_send_id (uint32_t);
|
||||
void mark_surround_send_id (uint32_t);
|
||||
void mark_aux_send_id (uint32_t);
|
||||
void mark_return_id (uint32_t);
|
||||
void mark_insert_id (uint32_t);
|
||||
void unmark_send_id (uint32_t);
|
||||
void unmark_surround_send_id (uint32_t);
|
||||
void unmark_aux_send_id (uint32_t);
|
||||
void unmark_return_id (uint32_t);
|
||||
void unmark_insert_id (uint32_t);
|
||||
|
||||
bool vapor_barrier ();
|
||||
bool vapor_export_barrier ();
|
||||
|
||||
/* s/w "RAID" management */
|
||||
|
||||
boost::optional<samplecnt_t> available_capture_duration();
|
||||
|
|
@ -1485,6 +1493,12 @@ private:
|
|||
void add_monitor_section ();
|
||||
void remove_monitor_section ();
|
||||
|
||||
void add_surround_master ();
|
||||
void remove_surround_master ();
|
||||
|
||||
boost::optional<bool> _vapor_available;
|
||||
boost::optional<bool> _vapor_exportable;
|
||||
|
||||
void update_latency (bool playback);
|
||||
void set_owned_port_public_latency (bool playback);
|
||||
bool update_route_latency (bool reverse, bool apply_to_delayline, bool* delayline_update_needed);
|
||||
|
|
@ -2060,6 +2074,7 @@ private:
|
|||
/* INSERT AND SEND MANAGEMENT */
|
||||
|
||||
boost::dynamic_bitset<uint32_t> send_bitset;
|
||||
boost::dynamic_bitset<uint32_t> surround_send_bitset;
|
||||
boost::dynamic_bitset<uint32_t> aux_send_bitset;
|
||||
boost::dynamic_bitset<uint32_t> return_bitset;
|
||||
boost::dynamic_bitset<uint32_t> insert_bitset;
|
||||
|
|
@ -2198,13 +2213,16 @@ private:
|
|||
|
||||
std::shared_ptr<Route> _master_out;
|
||||
std::shared_ptr<Route> _monitor_out;
|
||||
std::shared_ptr<Route> _surround_master;
|
||||
|
||||
friend class PortManager;
|
||||
void auto_connect_master_bus ();
|
||||
void auto_connect_monitor_bus ();
|
||||
void auto_connect_surround_master ();
|
||||
void auto_connect_io (std::shared_ptr<IO>);
|
||||
|
||||
void setup_route_monitor_sends (bool enable, bool need_process_lock);
|
||||
void setup_route_surround_sends (bool enable, bool need_process_lock);
|
||||
|
||||
int find_all_sources (std::string path, std::set<std::string>& result);
|
||||
int find_all_sources_across_snapshots (std::set<std::string>& result, bool exclude_this_snapshot);
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ CONFIG_VARIABLE (std::string, timecode_generator_offset, "timecode-generator-off
|
|||
CONFIG_VARIABLE (bool, midi_copy_is_fork, "midi-copy-is-fork", true)
|
||||
CONFIG_VARIABLE (bool, tracks_follow_session_time, "tracks-follow-session-time", false)
|
||||
CONFIG_VARIABLE (bool, realtime_export, "realtime-export", false)
|
||||
CONFIG_VARIABLE (bool, use_surround_master, "use-surround-master", false)
|
||||
|
||||
/* Video-settings are saved with the session and belong to the session.
|
||||
* headless ardour could remote control xjadeo for example.
|
||||
|
|
|
|||
79
libs/ardour/ardour/surround_pannable.h
Normal file
79
libs/ardour/ardour/surround_pannable.h
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* Copyright (C) 2023 Robin Gareus <robin@gareus.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef _ardour_surround_pannable_h_
|
||||
#define _ardour_surround_pannable_h_
|
||||
|
||||
#include "pbd/stateful.h"
|
||||
|
||||
#include "ardour/automatable.h"
|
||||
#include "ardour/automation_control.h"
|
||||
#include "ardour/session_handle.h"
|
||||
|
||||
namespace ARDOUR
|
||||
{
|
||||
|
||||
class LIBARDOUR_API SurroundControllable : public AutomationControl
|
||||
{
|
||||
public:
|
||||
SurroundControllable (Session&, Evoral::Parameter, Temporal::TimeDomainProvider const&);
|
||||
std::string get_user_string () const;
|
||||
|
||||
private:
|
||||
};
|
||||
|
||||
class LIBARDOUR_API SurroundPannable : public Automatable, public PBD::Stateful, public SessionHandleRef
|
||||
{
|
||||
public:
|
||||
SurroundPannable (Session& s, uint32_t chn, Temporal::TimeDomainProvider const &);
|
||||
~SurroundPannable ();
|
||||
|
||||
std::shared_ptr<AutomationControl> pan_pos_x;
|
||||
std::shared_ptr<AutomationControl> pan_pos_y;
|
||||
std::shared_ptr<AutomationControl> pan_pos_z;
|
||||
std::shared_ptr<AutomationControl> pan_size;
|
||||
std::shared_ptr<AutomationControl> pan_snap;
|
||||
std::shared_ptr<AutomationControl> binaural_render_mode;
|
||||
|
||||
void set_automation_state (AutoState);
|
||||
AutoState automation_state() const { return _auto_state; }
|
||||
PBD::Signal1<void, AutoState> automation_state_changed;
|
||||
|
||||
bool automation_playback() const {
|
||||
return (_auto_state & Play) || ((_auto_state & (Touch | Latch)) && !touching());
|
||||
}
|
||||
|
||||
bool touching() const;
|
||||
|
||||
XMLNode& get_state () const;
|
||||
int set_state (const XMLNode&, int version);
|
||||
|
||||
protected:
|
||||
void control_auto_state_changed (AutoState);
|
||||
virtual XMLNode& state () const;
|
||||
|
||||
AutoState _auto_state;
|
||||
uint32_t _responding_to_control_auto_state_change;
|
||||
|
||||
private:
|
||||
void value_changed ();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
100
libs/ardour/ardour/surround_return.h
Normal file
100
libs/ardour/ardour/surround_return.h
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright (C) 2023 Paul Davis <paul@linuxaudiosystems.com>
|
||||
* Copyright (C) 2023 Robin Gareus <robin@gareus.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __ardour_surround_return_h__
|
||||
#define __ardour_surround_return_h__
|
||||
|
||||
#ifdef HAVE_LV2_1_18_6
|
||||
#include <lv2/atom/atom.h>
|
||||
#include <lv2/atom/forge.h>
|
||||
#else
|
||||
#include <lv2/lv2plug.in/ns/ext/atom/atom.h>
|
||||
#include <lv2/lv2plug.in/ns/ext/atom/forge.h>
|
||||
#endif
|
||||
|
||||
#include "ardour/chan_mapping.h"
|
||||
#include "ardour/lufs_meter.h"
|
||||
#include "ardour/processor.h"
|
||||
|
||||
namespace ARDOUR
|
||||
{
|
||||
class Session;
|
||||
class SurroundSend;
|
||||
class SurroundPannable;
|
||||
class LV2Plugin;
|
||||
|
||||
class LIBARDOUR_API SurroundReturn : public Processor
|
||||
{
|
||||
public:
|
||||
SurroundReturn (Session&);
|
||||
virtual ~SurroundReturn ();
|
||||
|
||||
bool can_support_io_configuration (const ChanCount& in, ChanCount& out);
|
||||
void run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample, double speed, pframes_t nframes, bool);
|
||||
int set_block_size (pframes_t);
|
||||
void flush ();
|
||||
void set_playback_offset (samplecnt_t cnt);
|
||||
bool display_to_user () const { return false; }
|
||||
|
||||
void setup_export (std::string const&, samplepos_t, samplepos_t);
|
||||
void finalize_export ();
|
||||
|
||||
std::shared_ptr<LV2Plugin> surround_processor () const {
|
||||
return _surround_processor;
|
||||
}
|
||||
|
||||
/* a value <= -200 indicates that no data is available */
|
||||
float integrated_loudness () const;
|
||||
float max_dbtp () const;
|
||||
|
||||
samplecnt_t signal_latency () const;
|
||||
|
||||
protected:
|
||||
XMLNode& state () const;
|
||||
|
||||
private:
|
||||
static const size_t max_object_id = 128; // happens to be the same as a constant in a well known surround system
|
||||
static const size_t num_pan_parameters = 5; // X, Y, Z, Size, Snap
|
||||
|
||||
void forge_int_msg (uint32_t obj_id, uint32_t key, int val, uint32_t key2 = 0, int val2 = 0);
|
||||
void maybe_send_metadata (size_t id, pframes_t frame, pan_t const v[num_pan_parameters]);
|
||||
void evaluate (size_t id, std::shared_ptr<SurroundPannable> const&, timepos_t const& , pframes_t);
|
||||
|
||||
std::shared_ptr<LV2Plugin> _surround_processor;
|
||||
|
||||
LUFSMeter _lufs_meter;
|
||||
|
||||
LV2_Atom_Forge _forge;
|
||||
uint8_t _atom_buf[8192];
|
||||
pan_t _current_value[max_object_id][num_pan_parameters];
|
||||
int _current_render_mode[max_object_id];
|
||||
size_t _current_n_objects;
|
||||
BufferSet _surround_bufs;
|
||||
ChanMapping _in_map;
|
||||
ChanMapping _out_map;
|
||||
bool _exporting;
|
||||
samplepos_t _export_start;
|
||||
samplepos_t _export_end;
|
||||
bool _rolling;
|
||||
std::atomic<int> _flush;
|
||||
};
|
||||
|
||||
} // namespace ARDOUR
|
||||
|
||||
#endif /* __ardour_surround_return_h__ */
|
||||
101
libs/ardour/ardour/surround_send.h
Normal file
101
libs/ardour/ardour/surround_send.h
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
/*
|
||||
* Copyright (C) 2023 Paul Davis <paul@linuxaudiosystems.com>
|
||||
* Copyright (C) 2023 Robin Gareus <robin@gareus.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#ifndef __ardour_surround_send_h__
|
||||
#define __ardour_surround_send_h__
|
||||
|
||||
#include "ardour/processor.h"
|
||||
#include "ardour/send.h"
|
||||
|
||||
namespace ARDOUR
|
||||
{
|
||||
class Amp;
|
||||
class SurroundPannable;
|
||||
class MuteMaster;
|
||||
class GainControl;
|
||||
|
||||
class LIBARDOUR_API SurroundSend : public Processor, public LatentSend
|
||||
{
|
||||
public:
|
||||
SurroundSend (Session&, std::shared_ptr<MuteMaster>);
|
||||
virtual ~SurroundSend ();
|
||||
|
||||
/* methods for the UI to access SurroundSend controls */
|
||||
std::shared_ptr<GainControl> gain_control () const { return _gain_control; }
|
||||
std::shared_ptr<SurroundPannable> pannable (size_t chn = 0) const;
|
||||
|
||||
uint32_t n_pannables () const;
|
||||
|
||||
PBD::Signal0<void> NPannablesChanged;
|
||||
PBD::Signal0<void> PanChanged;
|
||||
|
||||
/* Route/processor interface */
|
||||
bool can_support_io_configuration (const ChanCount& in, ChanCount& out) { return in == out; }
|
||||
bool configure_io (ChanCount in, ChanCount out);
|
||||
int set_block_size (pframes_t);
|
||||
void run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample, double speed, pframes_t nframes, bool);
|
||||
bool display_to_user() const;
|
||||
bool does_routing() const { return true; }
|
||||
|
||||
std::string describe_parameter(Evoral::Parameter param);
|
||||
|
||||
/* Latent Send */
|
||||
void set_delay_in (samplecnt_t);
|
||||
void set_delay_out (samplecnt_t, size_t bus = 0);
|
||||
void update_delaylines (bool rt_ok);
|
||||
samplecnt_t get_delay_in () const { return _delay_in; }
|
||||
samplecnt_t get_delay_out () const { return _delay_out; }
|
||||
samplecnt_t signal_latency () const;
|
||||
|
||||
/* These may only be called by a SurroundReturn (to which we are attached) from within its ::run() * method */
|
||||
BufferSet const& bufs () const { return _mixbufs; }
|
||||
|
||||
std::shared_ptr<SurroundPannable> const& pan_param (size_t chn, timepos_t& s, timepos_t& e) const;
|
||||
|
||||
protected:
|
||||
int set_state (const XMLNode&, int version);
|
||||
XMLNode& state () const;
|
||||
|
||||
private:
|
||||
void ensure_mixbufs ();
|
||||
gain_t target_gain () const;
|
||||
void cycle_start (pframes_t);
|
||||
void add_pannable ();
|
||||
|
||||
BufferSet _mixbufs;
|
||||
int32_t _surround_id;
|
||||
timepos_t _cycle_start;
|
||||
timepos_t _cycle_end;
|
||||
gain_t _current_gain;
|
||||
bool _has_state;
|
||||
|
||||
std::vector<std::shared_ptr<SurroundPannable>> _pannable;
|
||||
|
||||
std::shared_ptr<GainControl> _gain_control;
|
||||
std::shared_ptr<Amp> _amp;
|
||||
std::shared_ptr<MuteMaster> _mute_master;
|
||||
std::shared_ptr<DelayLine> _send_delay;
|
||||
std::shared_ptr<DelayLine> _thru_delay;
|
||||
|
||||
PBD::ScopedConnectionList _change_connections;
|
||||
};
|
||||
|
||||
} // namespace ARDOUR
|
||||
|
||||
#endif /* __ardour_surround_send_h__ */
|
||||
|
|
@ -46,6 +46,7 @@
|
|||
#include "ardour/plugin_insert.h"
|
||||
#include "ardour/record_enable_control.h"
|
||||
#include "ardour/session.h"
|
||||
#include "ardour/surround_pannable.h"
|
||||
#include "ardour/uri_map.h"
|
||||
#include "ardour/value_as_string.h"
|
||||
|
||||
|
|
@ -589,6 +590,9 @@ Automatable::control_factory(const Evoral::Parameter& param)
|
|||
control = new GainControl(_a_session, param);
|
||||
} else if (param.type() == BusSendLevel) {
|
||||
control = new GainControl(_a_session, param);
|
||||
} else if (param.type() == PanSurroundX || param.type() == PanSurroundY || param.type() == PanSurroundZ || param.type() == PanSurroundSize || param.type() == PanSurroundSnap || param.type() == BinauralRenderMode) {
|
||||
assert (0);
|
||||
control = new SurroundControllable (_a_session, param.type(), *this);
|
||||
} else if (param.type() == PanAzimuthAutomation || param.type() == PanWidthAutomation || param.type() == PanElevationAutomation) {
|
||||
Pannable* pannable = dynamic_cast<Pannable*>(this);
|
||||
if (pannable) {
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
#include "ardour/export_format_specification.h"
|
||||
#include "ardour/export_filename.h"
|
||||
#include "ardour/soundcloud_upload.h"
|
||||
#include "ardour/surround_return.h"
|
||||
#include "ardour/system_exec.h"
|
||||
#include "pbd/openuri.h"
|
||||
#include "pbd/basename.h"
|
||||
|
|
@ -227,6 +228,11 @@ ExportHandler::start_timespan ()
|
|||
post_processing = false;
|
||||
session.ProcessExport.connect_same_thread (process_connection, boost::bind (&ExportHandler::process, this, _1));
|
||||
process_position = current_timespan->get_start();
|
||||
|
||||
if (!region_export && !current_timespan->vapor ().empty () && session.surround_master ()) {
|
||||
session.surround_master ()->surround_return ()->setup_export (current_timespan->vapor (), current_timespan->get_start (), current_timespan->get_end ());
|
||||
}
|
||||
|
||||
// TODO check if it's a RegionExport.. set flag to skip process_without_events()
|
||||
return session.start_audio_export (process_position, realtime, region_export);
|
||||
}
|
||||
|
|
@ -377,6 +383,10 @@ ExportHandler::start_timespan_bg (void* eh)
|
|||
void
|
||||
ExportHandler::finish_timespan ()
|
||||
{
|
||||
if (/*!region_export &&*/ !current_timespan->vapor ().empty () && session.surround_master ()) {
|
||||
session.surround_master ()->surround_return ()->finalize_export ();
|
||||
}
|
||||
|
||||
graph_builder->get_analysis_results (export_status->result_map);
|
||||
|
||||
/* work-around: split-channel will produce several files
|
||||
|
|
|
|||
|
|
@ -748,6 +748,7 @@ ARDOUR::init (bool try_optimization, const char* localedir, bool with_gui)
|
|||
|
||||
reserved_io_names[_("Monitor")] = true;
|
||||
reserved_io_names[_("Master")] = true;
|
||||
reserved_io_names[_("Surround")] = true;
|
||||
reserved_io_names[X_("auditioner")] = true; // auditioner.cc Track (s, "auditioner",...)
|
||||
reserved_io_names[X_("x-virtual-keyboard")] = false;
|
||||
reserved_io_names[X_("MIDI Tracer 1")] = false;
|
||||
|
|
|
|||
|
|
@ -95,6 +95,8 @@
|
|||
#include "ardour/session.h"
|
||||
#include "ardour/solo_control.h"
|
||||
#include "ardour/solo_isolate_control.h"
|
||||
#include "ardour/surround_return.h"
|
||||
#include "ardour/surround_send.h"
|
||||
#include "ardour/triggerbox.h"
|
||||
#include "ardour/types_convert.h"
|
||||
#include "ardour/unknown_processor.h"
|
||||
|
|
@ -143,6 +145,7 @@ Route::Route (Session& sess, string name, PresentationInfo::Flag flag, DataType
|
|||
|
||||
_pending_process_reorder.store (0);
|
||||
_pending_listen_change.store (0);
|
||||
_pending_surround_send.store (0);
|
||||
_pending_signals.store (0);
|
||||
}
|
||||
|
||||
|
|
@ -214,9 +217,12 @@ Route::init ()
|
|||
_amp->set_owner (this);
|
||||
|
||||
_polarity.reset (new PolarityProcessor (_session, _phase_control));
|
||||
_polarity->activate();
|
||||
_polarity->set_owner (this);
|
||||
|
||||
if (!is_surround_master ()) {
|
||||
_polarity->activate();
|
||||
}
|
||||
|
||||
if (is_monitor ()) {
|
||||
_amp->set_display_name (_("Monitor"));
|
||||
}
|
||||
|
|
@ -230,7 +236,9 @@ Route::init ()
|
|||
_trim.reset (new Amp (_session, X_("Trim"), _trim_control, false));
|
||||
_trim->set_display_to_user (false);
|
||||
|
||||
if (dynamic_cast<AudioTrack*>(this)) {
|
||||
if (is_surround_master ()) {
|
||||
_trim->deactivate ();
|
||||
} else if (dynamic_cast<AudioTrack*>(this)) {
|
||||
/* we can't do this in the AudioTrack's constructor
|
||||
* because _trim does not exit then
|
||||
*/
|
||||
|
|
@ -286,6 +294,17 @@ Route::init ()
|
|||
_monitor_control.reset (new MonitorProcessor (_session));
|
||||
_monitor_control->activate ();
|
||||
}
|
||||
|
||||
if (is_surround_master ()) {
|
||||
_meter_point = _pending_meter_point = MeterPreFader;
|
||||
_surround_return.reset (new SurroundReturn (_session));
|
||||
_surround_return->activate ();
|
||||
panner_shell()->set_bypassed (true);
|
||||
|
||||
_monitor_control.reset (new MonitorProcessor (_session));
|
||||
_monitor_control->activate ();
|
||||
}
|
||||
|
||||
if (_presentation_info.flags() & PresentationInfo::FoldbackBus) {
|
||||
panner_shell()->select_panner_by_uri ("http://ardour.org/plugin/panner_balance");
|
||||
}
|
||||
|
|
@ -1431,7 +1450,7 @@ Route::clear_processors (Placement p)
|
|||
bool
|
||||
Route::is_internal_processor (std::shared_ptr<Processor> p) const
|
||||
{
|
||||
if (p == _amp || p == _meter || p == _main_outs || p == _delayline || p == _trim || p == _polarity || (_volume && p == _volume) || (_triggerbox && p == _triggerbox)) {
|
||||
if (p == _amp || p == _meter || p == _main_outs || p == _delayline || p == _trim || p == _polarity || (_volume && p == _volume) || (_triggerbox && p == _triggerbox) || (_surround_return && p == _surround_return) || (_surround_send && p == _surround_send)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
@ -3174,7 +3193,7 @@ Route::set_processor_state (const XMLNode& node, int version)
|
|||
must_configure = true;
|
||||
}
|
||||
_intreturn->set_state (**niter, version);
|
||||
} else if (is_monitor() && prop->value() == "monitor") {
|
||||
} else if ((is_monitor() || is_surround_master ()) && prop->value() == "monitor") {
|
||||
if (!_monitor_control) {
|
||||
_monitor_control.reset (new MonitorProcessor (_session));
|
||||
must_configure = true;
|
||||
|
|
@ -3310,6 +3329,10 @@ Route::set_processor_state (XMLNode const& node, int version, XMLProperty const*
|
|||
send->output()->changed.connect_same_thread (*send, boost::bind (&Route::output_change_handler, this, _1, _2));
|
||||
}
|
||||
|
||||
} else if (prop->value() == "sursend") {
|
||||
_surround_send.reset (new SurroundSend (_session, _mute_master));
|
||||
_surround_send->set_owner (this);
|
||||
processor = _surround_send;
|
||||
} else {
|
||||
warning << string_compose(_("unknown Processor type \"%1\"; ignored"), prop->value()) << endmsg;
|
||||
return false;
|
||||
|
|
@ -3437,6 +3460,7 @@ Route::enable_monitor_send ()
|
|||
/* master never sends to monitor section via the normal mechanism */
|
||||
assert (!is_master ());
|
||||
assert (!is_monitor ());
|
||||
assert (!is_surround_master ());
|
||||
|
||||
/* make sure we have one */
|
||||
if (!_monitor_send) {
|
||||
|
|
@ -3617,6 +3641,14 @@ Route::direct_feeds_according_to_reality (std::shared_ptr<GraphNode> node, bool*
|
|||
|
||||
Glib::Threads::RWLock::ReaderLock lm (_processor_lock);
|
||||
|
||||
/* our surround send always feeds the surround master */
|
||||
if (other->is_surround_master () && _surround_send) {
|
||||
if (via_send_only) {
|
||||
*via_send_only = true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
for (ProcessorList::iterator r = _processors.begin(); r != _processors.end(); ++r) {
|
||||
|
||||
std::shared_ptr<IOProcessor> iop = std::dynamic_pointer_cast<IOProcessor>(*r);
|
||||
|
|
@ -4141,6 +4173,15 @@ Route::apply_processor_changes_rt ()
|
|||
*/
|
||||
update_signal_latency (true);
|
||||
}
|
||||
|
||||
if (_pending_surround_send.load ()) {
|
||||
Glib::Threads::RWLock::WriterLock pwl (_processor_lock, Glib::Threads::TRY_LOCK);
|
||||
if (pwl.locked()) {
|
||||
_pending_surround_send.store (0);
|
||||
emissions |= EmitSendReturnChange;
|
||||
}
|
||||
}
|
||||
|
||||
if (emissions != 0) {
|
||||
_pending_signals.store (emissions);
|
||||
return true;
|
||||
|
|
@ -4164,6 +4205,9 @@ Route::emit_pending_signals ()
|
|||
if (sig & EmitRtProcessorChange) {
|
||||
processors_changed (RouteProcessorChange (RouteProcessorChange::RealTimeChange)); /* EMIT SIGNAL */
|
||||
}
|
||||
if (sig & EmitSendReturnChange) {
|
||||
processors_changed (RouteProcessorChange (RouteProcessorChange::SendReturnChange, false)); /* EMIT SIGNAL */
|
||||
}
|
||||
|
||||
/* this would be a job for the butler.
|
||||
* Conceptually we should not take processe/processor locks here.
|
||||
|
|
@ -4372,6 +4416,9 @@ Route::update_signal_latency (bool apply_to_delayline, bool* delayline_update_ne
|
|||
if (std::shared_ptr<InternalReturn> rtn = std::dynamic_pointer_cast<InternalReturn> (*i)) {
|
||||
rtn->set_playback_offset (0);
|
||||
}
|
||||
if (std::shared_ptr<SurroundReturn> rtn = std::dynamic_pointer_cast<SurroundReturn> (*i)) {
|
||||
rtn->set_playback_offset (0);
|
||||
}
|
||||
// TODO sidechain inputs?!
|
||||
}
|
||||
return 0;
|
||||
|
|
@ -4451,7 +4498,7 @@ Route::update_signal_latency (bool apply_to_delayline, bool* delayline_update_ne
|
|||
*delayline_update_needed = true;
|
||||
}
|
||||
}
|
||||
} else if (!apply_to_delayline && std::dynamic_pointer_cast<InternalReturn> (*i)) {
|
||||
} else if (!apply_to_delayline && (std::dynamic_pointer_cast<InternalReturn> (*i) || std::dynamic_pointer_cast<SurroundReturn> (*i))) {
|
||||
/* InternalReturn::set_playback_offset() calls set_delay_out(), requires process lock */
|
||||
const samplecnt_t poff = _signal_latency + _output_latency;
|
||||
if (delayline_update_needed && (*i)->playback_offset () != poff) {
|
||||
|
|
@ -5181,7 +5228,11 @@ Route::setup_invisible_processors ()
|
|||
/* find visible processors */
|
||||
|
||||
for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
|
||||
std::shared_ptr<Send> auxsnd = std::dynamic_pointer_cast<Send> ((*i));
|
||||
std::shared_ptr<Send> auxsnd = std::dynamic_pointer_cast<Send> (*i);
|
||||
|
||||
if (std::dynamic_pointer_cast<SurroundSend> (*i)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef HAVE_BEATBOX
|
||||
/* XXX temporary hack while we decide on visibility */
|
||||
|
|
@ -5216,6 +5267,11 @@ Route::setup_invisible_processors ()
|
|||
new_processors.insert (amp, _meter);
|
||||
}
|
||||
|
||||
/* SURROUND SEND */
|
||||
if (_surround_send) {
|
||||
new_processors.push_back (_surround_send);
|
||||
}
|
||||
|
||||
/* MAIN OUTS */
|
||||
|
||||
assert (_main_outs);
|
||||
|
|
@ -5318,6 +5374,15 @@ Route::setup_invisible_processors ()
|
|||
new_processors.push_front (_intreturn);
|
||||
}
|
||||
|
||||
/* SURROUND RETURN */
|
||||
if (_surround_return) {
|
||||
assert (_surround_return && is_surround_master ());
|
||||
new_processors.push_front (_monitor_control);
|
||||
|
||||
assert (!_surround_return->display_to_user ());
|
||||
new_processors.push_front (_surround_return);
|
||||
}
|
||||
|
||||
/* DISK READER & WRITER (for Track objects) */
|
||||
|
||||
if (_disk_reader || _disk_writer) {
|
||||
|
|
@ -5371,8 +5436,8 @@ Route::setup_invisible_processors ()
|
|||
}
|
||||
}
|
||||
|
||||
/* Polarity Invert (always present) */
|
||||
if (_polarity) {
|
||||
/* Polarity Invert */
|
||||
if (_polarity->active ()) {
|
||||
ProcessorList::iterator reader_pos = find (new_processors.begin(), new_processors.end(), _disk_reader);
|
||||
ProcessorList::iterator polarity_pos;
|
||||
if (reader_pos != new_processors.end()) {
|
||||
|
|
@ -6354,3 +6419,49 @@ Route::tempo_map_changed ()
|
|||
_triggerbox->tempo_map_changed ();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Route::enable_surround_send ()
|
||||
{
|
||||
if (is_main_bus ()) {
|
||||
/* no surround sends for you */
|
||||
return;
|
||||
}
|
||||
|
||||
/* Caller must hold process lock */
|
||||
assert (!AudioEngine::instance()->process_lock().trylock());
|
||||
|
||||
/* make sure we have one */
|
||||
if (!_surround_send) {
|
||||
_surround_send.reset (new SurroundSend (_session, _mute_master));
|
||||
_surround_send->set_owner (this);
|
||||
_surround_send->activate ();
|
||||
}
|
||||
|
||||
Glib::Threads::RWLock::WriterLock lm (_processor_lock);
|
||||
configure_processors_unlocked (0, &lm);
|
||||
/* We cannot emit `processors_changed` while holing the `process lock`
|
||||
* This can lead to deadlock in ARDOUR::Session::route_processors_changed
|
||||
*/
|
||||
_pending_surround_send.store (1);
|
||||
}
|
||||
|
||||
void
|
||||
Route::remove_surround_send ()
|
||||
{
|
||||
/* Caller must hold process lock */
|
||||
assert (!AudioEngine::instance()->process_lock().trylock());
|
||||
|
||||
if (!_surround_send) {
|
||||
return;
|
||||
}
|
||||
|
||||
_surround_send.reset ();
|
||||
|
||||
Glib::Threads::RWLock::WriterLock lm (_processor_lock);
|
||||
configure_processors_unlocked (0, &lm);
|
||||
/* We cannot emit `processors_changed` while holing the `process lock`
|
||||
* This can lead to deadlock in ARDOUR::Session::route_processors_changed
|
||||
*/
|
||||
_pending_surround_send.store (1);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -86,6 +86,7 @@
|
|||
#include "ardour/graph.h"
|
||||
#include "ardour/io_plug.h"
|
||||
#include "ardour/luabindings.h"
|
||||
#include "ardour/lv2_plugin.h"
|
||||
#include "ardour/midiport_manager.h"
|
||||
#include "ardour/scene_changer.h"
|
||||
#include "ardour/midi_patch_manager.h"
|
||||
|
|
@ -122,6 +123,7 @@
|
|||
#include "ardour/solo_isolate_control.h"
|
||||
#include "ardour/source_factory.h"
|
||||
#include "ardour/speakers.h"
|
||||
#include "ardour/surround_return.h"
|
||||
#include "ardour/tempo.h"
|
||||
#include "ardour/ticker.h"
|
||||
#include "ardour/transport_fsm.h"
|
||||
|
|
@ -785,6 +787,7 @@ Session::destroy ()
|
|||
|
||||
_master_out.reset ();
|
||||
_monitor_out.reset ();
|
||||
_surround_master.reset ();
|
||||
|
||||
{
|
||||
RCUWriter<RouteList> writer (routes);
|
||||
|
|
@ -1375,6 +1378,199 @@ Session::reset_monitor_section ()
|
|||
setup_route_monitor_sends (true, false);
|
||||
}
|
||||
|
||||
void
|
||||
Session::remove_surround_master ()
|
||||
{
|
||||
if (!_surround_master) {
|
||||
return;
|
||||
}
|
||||
|
||||
/* allow deletion when session is unloaded */
|
||||
if (!_engine.running() && !deletion_in_progress ()) {
|
||||
error << _("Cannot remove monitor section while the engine is offline.") << endmsg;
|
||||
return;
|
||||
}
|
||||
|
||||
/* if we are auditioning, cancel it ... this is a workaround
|
||||
to a problem (auditioning does not execute the process graph,
|
||||
which is needed to remove routes when using >1 core for processing)
|
||||
*/
|
||||
cancel_audition ();
|
||||
|
||||
if (!deletion_in_progress ()) {
|
||||
setup_route_surround_sends (false, true);
|
||||
_engine.monitor_port().clear_ports (true);
|
||||
}
|
||||
|
||||
remove_route (_surround_master);
|
||||
if (deletion_in_progress ()) {
|
||||
return;
|
||||
}
|
||||
|
||||
SurroundMasterAddedOrRemoved (); /* EMIT SIGNAL */
|
||||
}
|
||||
|
||||
bool
|
||||
Session::vapor_barrier ()
|
||||
{
|
||||
#if !(defined (LV2_EXTENDED) && defined (HAVE_LV2_1_10_0))
|
||||
return false;
|
||||
#endif
|
||||
if (_vapor_available.has_value ()) {
|
||||
return _vapor_available.value ();
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
bool ex = false;
|
||||
|
||||
if (nominal_sample_rate () == 48000 || nominal_sample_rate () == 96000) {
|
||||
std::shared_ptr<LV2Plugin> p;
|
||||
|
||||
if (_surround_master) {
|
||||
p = _surround_master->surround_return ()->surround_processor ();
|
||||
} else {
|
||||
PluginManager& mgr (PluginManager::instance ());
|
||||
for (auto const& i : mgr.lv2_plugin_info ()) {
|
||||
if ("urn:ardour:a-vapor" != i->unique_id) {
|
||||
continue;
|
||||
}
|
||||
p = std::dynamic_pointer_cast<LV2Plugin> (i->load (*this));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (p) {
|
||||
ok = true;
|
||||
ex = p->can_export ();
|
||||
}
|
||||
}
|
||||
|
||||
_vapor_exportable = ex;
|
||||
_vapor_available = ok;
|
||||
|
||||
return ok;
|
||||
}
|
||||
|
||||
bool
|
||||
Session::vapor_export_barrier ()
|
||||
{
|
||||
#if !(defined (LV2_EXTENDED) && defined (HAVE_LV2_1_10_0))
|
||||
return false;
|
||||
#endif
|
||||
if (!_vapor_exportable.has_value ()) {
|
||||
vapor_barrier ();
|
||||
}
|
||||
assert (_vapor_exportable.has_value ());
|
||||
return _vapor_exportable.value ();
|
||||
}
|
||||
|
||||
void
|
||||
Session::add_surround_master ()
|
||||
{
|
||||
RouteList rl;
|
||||
|
||||
if (_surround_master) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_engine.running()) {
|
||||
error << _("Cannot create surround master while the engine is offline.") << endmsg;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!vapor_barrier()) {
|
||||
error << _("Some surround sound systems require a sample-rate of 48kHz or 96kHz.") << endmsg;
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<Route> r (new Route (*this, _("Surround"), PresentationInfo::SurroundMaster, DataType::AUDIO));
|
||||
|
||||
if (r->init ()) {
|
||||
return;
|
||||
}
|
||||
|
||||
BOOST_MARK_ROUTE(r);
|
||||
|
||||
try {
|
||||
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
|
||||
r->input()->ensure_io (ChanCount (), false, this);
|
||||
r->output()->ensure_io (ChanCount (DataType::AUDIO, 16), false, this);
|
||||
} catch (...) {
|
||||
error << _("Cannot create surround master. 'Surround' Port name is not unique.") << endmsg;
|
||||
return;
|
||||
}
|
||||
|
||||
rl.push_back (r);
|
||||
add_routes (rl, false, false, 0);
|
||||
|
||||
assert (_surround_master);
|
||||
|
||||
auto_connect_surround_master ();
|
||||
|
||||
/* Hold process lock while doing this so that we don't hear bits and
|
||||
* pieces of audio as we work on each route.
|
||||
*/
|
||||
|
||||
setup_route_surround_sends (true, true);
|
||||
|
||||
SurroundMasterAddedOrRemoved (); /* EMIT SIGNAL */
|
||||
}
|
||||
|
||||
void
|
||||
Session::auto_connect_surround_master ()
|
||||
{
|
||||
/* compare to auto_connect_io */
|
||||
vector<string> outputs;
|
||||
_engine.get_physical_outputs (DataType::AUDIO, outputs);
|
||||
|
||||
std::shared_ptr<IO> io = _surround_master->output ();
|
||||
uint32_t limit = io->n_ports ().n_audio ();
|
||||
|
||||
Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
|
||||
/* connect binaural outputs, port 12, 13 */
|
||||
for (uint32_t n = 12, p = 0; n < limit && outputs.size () > p; ++n, ++p) {
|
||||
std::shared_ptr<AudioPort> ap = io->audio (n);
|
||||
|
||||
if (io->connect (ap, outputs[p], this)) {
|
||||
error << string_compose (_("cannot connect %1 output %2 to %3"), io->name(), n, outputs[p]) << endmsg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
lm.release ();
|
||||
|
||||
/* Mute non-surround path */
|
||||
if (_monitor_out) {
|
||||
_monitor_out->monitor_control ()->set_mono (true);
|
||||
} else if (_master_out) {
|
||||
_master_out->mute_control ()->set_value (true, PBD::Controllable::NoGroup);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void
|
||||
Session::setup_route_surround_sends (bool enable, bool need_process_lock)
|
||||
{
|
||||
Glib::Threads::Mutex::Lock lx (AudioEngine::instance()->process_lock (), Glib::Threads::NOT_LOCK);
|
||||
if (need_process_lock) {
|
||||
/* Hold process lock while doing this so that we don't hear bits and
|
||||
* pieces of audio as we work on each route.
|
||||
*/
|
||||
lx.acquire();
|
||||
}
|
||||
|
||||
std::shared_ptr<RouteList const> rl = routes.reader ();
|
||||
ProcessorChangeBlocker pcb (this, false /* XXX */);
|
||||
|
||||
for (auto const& x : *rl) {
|
||||
if (x->can_monitor ()) {
|
||||
if (enable) {
|
||||
x->enable_surround_send ();
|
||||
} else {
|
||||
x->remove_surround_send ();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
Session::add_master_bus (ChanCount const& count)
|
||||
{
|
||||
|
|
@ -3222,6 +3418,11 @@ Session::new_route_from_template (uint32_t how_many, PresentationInfo::order_t i
|
|||
(*x)->remove_monitor_send ();
|
||||
}
|
||||
}
|
||||
if (_surround_master) {
|
||||
(*x)->enable_surround_send();
|
||||
} else {
|
||||
(*x)->remove_surround_send();
|
||||
}
|
||||
/* reconnect ports using information from state */
|
||||
for (auto const& wio : (*x)->all_inputs ()) {
|
||||
std::shared_ptr<IO> io = wio.lock();
|
||||
|
|
@ -3339,6 +3540,10 @@ Session::add_routes_inner (RouteList& new_routes, bool input_auto_connect, bool
|
|||
_monitor_out = r;
|
||||
}
|
||||
|
||||
if (r->is_surround_master()) {
|
||||
_surround_master = r;
|
||||
}
|
||||
|
||||
std::shared_ptr<Track> tr = std::dynamic_pointer_cast<Track> (r);
|
||||
if (tr) {
|
||||
tr->PlaylistChanged.connect_same_thread (*this, boost::bind (&Session::track_playlist_changed, this, std::weak_ptr<Track> (tr)));
|
||||
|
|
@ -3406,6 +3611,13 @@ Session::add_routes_inner (RouteList& new_routes, bool input_auto_connect, bool
|
|||
}
|
||||
}
|
||||
|
||||
if (_surround_master && !loading()) {
|
||||
Glib::Threads::Mutex::Lock lm (_engine.process_lock());
|
||||
for (auto & r : new_routes) {
|
||||
r->enable_surround_send ();
|
||||
}
|
||||
}
|
||||
|
||||
reassign_track_numbers ();
|
||||
}
|
||||
|
||||
|
|
@ -3579,6 +3791,10 @@ Session::remove_routes (std::shared_ptr<RouteList> routes_to_remove)
|
|||
_monitor_out.reset ();
|
||||
}
|
||||
|
||||
if (*iter == _surround_master) {
|
||||
_surround_master.reset ();
|
||||
}
|
||||
|
||||
// We need to disconnect the route's inputs and outputs
|
||||
|
||||
(*iter)->input()->disconnect (0);
|
||||
|
|
@ -5677,6 +5893,26 @@ Session::next_send_id ()
|
|||
}
|
||||
}
|
||||
|
||||
uint32_t
|
||||
Session::next_surround_send_id ()
|
||||
{
|
||||
/* this doesn't really loop forever. just think about it */
|
||||
|
||||
while (true) {
|
||||
for (boost::dynamic_bitset<uint32_t>::size_type n = 1; n < surround_send_bitset.size(); ++n) {
|
||||
if (!surround_send_bitset[n]) {
|
||||
surround_send_bitset[n] = true;
|
||||
return n;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/* none available, so resize and try again */
|
||||
|
||||
surround_send_bitset.resize (surround_send_bitset.size() + 16, false);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t
|
||||
Session::next_aux_send_id ()
|
||||
{
|
||||
|
|
@ -5741,6 +5977,18 @@ Session::mark_aux_send_id (uint32_t id)
|
|||
aux_send_bitset[id] = true;
|
||||
}
|
||||
|
||||
void
|
||||
Session::mark_surround_send_id (uint32_t id)
|
||||
{
|
||||
if (id >= surround_send_bitset.size()) {
|
||||
surround_send_bitset.resize (id+16, false);
|
||||
}
|
||||
if (surround_send_bitset[id]) {
|
||||
warning << string_compose (_("surround send ID %1 appears to be in use already"), id) << endmsg;
|
||||
}
|
||||
surround_send_bitset[id] = true;
|
||||
}
|
||||
|
||||
void
|
||||
Session::mark_return_id (uint32_t id)
|
||||
{
|
||||
|
|
@ -5787,6 +6035,17 @@ Session::unmark_aux_send_id (uint32_t id)
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
Session::unmark_surround_send_id (uint32_t id)
|
||||
{
|
||||
if (deletion_in_progress ()) {
|
||||
return;
|
||||
}
|
||||
if (id < surround_send_bitset.size()) {
|
||||
surround_send_bitset[id] = false;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Session::unmark_return_id (uint32_t id)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -657,8 +657,9 @@ Session::create (const string& session_template, BusProfile const * bus_profile,
|
|||
return rv;
|
||||
}
|
||||
|
||||
if (Config->get_use_monitor_bus())
|
||||
if (Config->get_use_monitor_bus()) {
|
||||
add_monitor_section ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -4582,6 +4583,35 @@ Session::config_changed (std::string p, bool ours)
|
|||
remove_monitor_section ();
|
||||
}
|
||||
}
|
||||
} else if (p == "use-surround-master") {
|
||||
/* NB. This is always called when constructing a session,
|
||||
* after restoring session state (if any),
|
||||
* via post_engine_init() -> Config->map_parameters()
|
||||
*/
|
||||
bool want_sm = config.get_use_surround_master();
|
||||
bool have_sm = _surround_master ? true : false;
|
||||
if (loading ()) {
|
||||
/* When loading an existing session, the config "use-surround-master"
|
||||
* is ignored. Instead the sesion-state (xml) will have added the
|
||||
* "surround-master" and restored its state (and connections)
|
||||
* if the session has a surround master..
|
||||
* Update the config to reflect this.
|
||||
*/
|
||||
if (want_sm != have_sm) {
|
||||
config.set_use_surround_master (have_sm);
|
||||
}
|
||||
SurroundMasterAddedOrRemoved (); /* EMIT SIGNAL */
|
||||
} else {
|
||||
/* Otherwise, Config::set_use_surround_master() does
|
||||
* control the the presence of the monitor-section
|
||||
* (new sessions, user initiated change)
|
||||
*/
|
||||
if (want_sm && !have_sm) {
|
||||
add_surround_master ();
|
||||
} else if (!want_sm && have_sm) {
|
||||
remove_surround_master ();
|
||||
}
|
||||
}
|
||||
} else if (p == "loop-fade-choice") {
|
||||
last_loopend = 0; /* force locate to refill buffers with new loop boundary data */
|
||||
auto_loop_changed (_locations->auto_loop_location());
|
||||
|
|
|
|||
223
libs/ardour/surround_pannable.cc
Normal file
223
libs/ardour/surround_pannable.cc
Normal file
|
|
@ -0,0 +1,223 @@
|
|||
/*
|
||||
* Copyright (C) 2023 Robin Gareus <robin@gareus.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "pbd/error.h"
|
||||
|
||||
#include "ardour/automation_list.h"
|
||||
#include "ardour/surround_pannable.h"
|
||||
#include "ardour/session.h"
|
||||
#include "ardour/value_as_string.h"
|
||||
|
||||
#include "pbd/i18n.h"
|
||||
|
||||
using namespace std;
|
||||
using namespace PBD;
|
||||
using namespace ARDOUR;
|
||||
|
||||
SurroundControllable::SurroundControllable (Session& s, Evoral::Parameter param, Temporal::TimeDomainProvider const& tdp)
|
||||
: AutomationControl (s,
|
||||
param,
|
||||
ParameterDescriptor(param),
|
||||
std::shared_ptr<AutomationList>(new AutomationList(param, tdp)))
|
||||
{
|
||||
}
|
||||
|
||||
std::string
|
||||
SurroundControllable::get_user_string () const
|
||||
{
|
||||
float v = get_value ();
|
||||
char buf[32];
|
||||
switch (parameter ().type ()) {
|
||||
case PanSurroundX:
|
||||
if (v == 0.5) {
|
||||
return _("Center");
|
||||
}
|
||||
snprintf(buf, sizeof(buf), "L%3d R%3d", (int)rint (100.0 * (1.0 - v)), (int)rint (100.0 * v));
|
||||
break;
|
||||
case PanSurroundY:
|
||||
snprintf(buf, sizeof(buf), "F%3d B%3d", (int)rint (100.0 * (1.0 - v)), (int)rint (100.0 * v));
|
||||
break;
|
||||
case PanSurroundSize:
|
||||
snprintf(buf, sizeof(buf), "%.0f%%", 100.f * v);
|
||||
break;
|
||||
default:
|
||||
return value_as_string (desc(), v);
|
||||
}
|
||||
return buf;
|
||||
}
|
||||
|
||||
SurroundPannable::SurroundPannable (Session& s, uint32_t chn, Temporal::TimeDomainProvider const & tdp)
|
||||
: Automatable (s, tdp)
|
||||
, SessionHandleRef (s)
|
||||
, pan_pos_x (new SurroundControllable (s, Evoral::Parameter (PanSurroundX, 0, chn), tdp))
|
||||
, pan_pos_y (new SurroundControllable (s, Evoral::Parameter (PanSurroundY, 0, chn), tdp))
|
||||
, pan_pos_z (new SurroundControllable (s, Evoral::Parameter (PanSurroundZ, 0, chn), tdp))
|
||||
, pan_size (new SurroundControllable (s, Evoral::Parameter (PanSurroundSize, 0, chn), tdp))
|
||||
, pan_snap (new SurroundControllable (s, Evoral::Parameter (PanSurroundSnap, 0, chn), tdp))
|
||||
, binaural_render_mode (new SurroundControllable (s, Evoral::Parameter (BinauralRenderMode, 0, chn), tdp))
|
||||
, _auto_state (Off)
|
||||
, _responding_to_control_auto_state_change (0)
|
||||
{
|
||||
binaural_render_mode->set_flag (Controllable::NotAutomatable);
|
||||
|
||||
add_control (pan_pos_x);
|
||||
add_control (pan_pos_y);
|
||||
add_control (pan_pos_z);
|
||||
add_control (pan_size);
|
||||
add_control (pan_snap);
|
||||
add_control (binaural_render_mode); // not automatable
|
||||
|
||||
/* all controls change state together */
|
||||
pan_pos_x->alist()->automation_state_changed.connect_same_thread (*this, boost::bind (&SurroundPannable::control_auto_state_changed, this, _1));
|
||||
pan_pos_y->alist()->automation_state_changed.connect_same_thread (*this, boost::bind (&SurroundPannable::control_auto_state_changed, this, _1));
|
||||
pan_pos_z->alist()->automation_state_changed.connect_same_thread (*this, boost::bind (&SurroundPannable::control_auto_state_changed, this, _1));
|
||||
pan_size->alist()->automation_state_changed.connect_same_thread (*this, boost::bind (&SurroundPannable::control_auto_state_changed, this, _1));
|
||||
pan_snap->alist()->automation_state_changed.connect_same_thread (*this, boost::bind (&SurroundPannable::control_auto_state_changed, this, _1));
|
||||
|
||||
pan_pos_x->Changed.connect_same_thread (*this, boost::bind (&SurroundPannable::value_changed, this));
|
||||
pan_pos_y->Changed.connect_same_thread (*this, boost::bind (&SurroundPannable::value_changed, this));
|
||||
pan_pos_z->Changed.connect_same_thread (*this, boost::bind (&SurroundPannable::value_changed, this));
|
||||
pan_size->Changed.connect_same_thread (*this, boost::bind (&SurroundPannable::value_changed, this));
|
||||
pan_snap->Changed.connect_same_thread (*this, boost::bind (&SurroundPannable::value_changed, this));
|
||||
}
|
||||
|
||||
SurroundPannable::~SurroundPannable ()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
SurroundPannable::control_auto_state_changed (AutoState new_state)
|
||||
{
|
||||
if (_responding_to_control_auto_state_change) {
|
||||
return;
|
||||
}
|
||||
|
||||
_responding_to_control_auto_state_change++;
|
||||
|
||||
pan_pos_x->set_automation_state (new_state);
|
||||
pan_pos_y->set_automation_state (new_state);
|
||||
pan_pos_z->set_automation_state (new_state);
|
||||
pan_size->set_automation_state (new_state);
|
||||
pan_snap->set_automation_state (new_state);
|
||||
|
||||
_responding_to_control_auto_state_change--;
|
||||
|
||||
_auto_state = new_state;
|
||||
automation_state_changed (new_state); /* EMIT SIGNAL */
|
||||
}
|
||||
|
||||
void
|
||||
SurroundPannable::value_changed ()
|
||||
{
|
||||
_session.set_dirty ();
|
||||
}
|
||||
|
||||
void
|
||||
SurroundPannable::set_automation_state (AutoState state)
|
||||
{
|
||||
if (state == _auto_state) {
|
||||
return;
|
||||
}
|
||||
_auto_state = state;
|
||||
|
||||
const Controls& c (controls());
|
||||
|
||||
for (Controls::const_iterator ci = c.begin(); ci != c.end(); ++ci) {
|
||||
std::shared_ptr<AutomationControl> ac = std::dynamic_pointer_cast<AutomationControl>(ci->second);
|
||||
if (ac) {
|
||||
ac->alist()->set_automation_state (state);
|
||||
}
|
||||
}
|
||||
|
||||
_session.set_dirty ();
|
||||
automation_state_changed (_auto_state); /* EMIT SIGNAL */
|
||||
}
|
||||
|
||||
bool
|
||||
SurroundPannable::touching () const
|
||||
{
|
||||
const Controls& c (controls());
|
||||
|
||||
for (auto const& i : c) {
|
||||
std::shared_ptr<AutomationControl> ac = std::dynamic_pointer_cast<AutomationControl>(i.second);
|
||||
if (ac && ac->touching ()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
XMLNode&
|
||||
SurroundPannable::get_state () const
|
||||
{
|
||||
return state ();
|
||||
}
|
||||
|
||||
XMLNode&
|
||||
SurroundPannable::state () const
|
||||
{
|
||||
XMLNode* node = new XMLNode (X_("SurroundPannable"));
|
||||
node->set_property ("channel", pan_pos_x->parameter ().id ());
|
||||
|
||||
node->add_child_nocopy (pan_pos_x->get_state());
|
||||
node->add_child_nocopy (pan_pos_y->get_state());
|
||||
node->add_child_nocopy (pan_pos_z->get_state());
|
||||
node->add_child_nocopy (pan_size->get_state());
|
||||
node->add_child_nocopy (pan_snap->get_state());
|
||||
node->add_child_nocopy (binaural_render_mode->get_state());
|
||||
|
||||
return *node;
|
||||
}
|
||||
|
||||
int
|
||||
SurroundPannable::set_state (const XMLNode& root, int version)
|
||||
{
|
||||
if (root.name() != X_("SurroundPannable")) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const XMLNodeList& nlist (root.children());
|
||||
XMLNodeConstIterator niter;
|
||||
|
||||
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
|
||||
if ((*niter)->name() != Controllable::xml_node_name) {
|
||||
continue;
|
||||
}
|
||||
std::string control_name;
|
||||
|
||||
if (!(*niter)->get_property (X_("name"), control_name)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (control_name == pan_pos_x->name()) {
|
||||
pan_pos_x->set_state (**niter, version);
|
||||
} else if (control_name == pan_pos_y->name()) {
|
||||
pan_pos_y->set_state (**niter, version);
|
||||
} else if (control_name == pan_pos_z->name()) {
|
||||
pan_pos_z->set_state (**niter, version);
|
||||
} else if (control_name == pan_size->name()) {
|
||||
pan_size->set_state (**niter, version);
|
||||
} else if (control_name == pan_snap->name()) {
|
||||
pan_snap->set_state (**niter, version);
|
||||
} else if (control_name == binaural_render_mode->name()) {
|
||||
binaural_render_mode->set_state (**niter, version);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
380
libs/ardour/surround_return.cc
Normal file
380
libs/ardour/surround_return.cc
Normal file
|
|
@ -0,0 +1,380 @@
|
|||
/*
|
||||
* Copyright (C) 2023 Robin Gareus <robin@gareus.org>
|
||||
* Copyright (C) 2023 Paul Davis <paul@linuxaudiosystems.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.
|
||||
*/
|
||||
|
||||
#include "ardour/surround_return.h"
|
||||
#include "ardour/audio_buffer.h"
|
||||
#include "ardour/lv2_plugin.h"
|
||||
#include "ardour/route.h"
|
||||
#include "ardour/session.h"
|
||||
#include "ardour/surround_pannable.h"
|
||||
#include "ardour/surround_send.h"
|
||||
#include "ardour/uri_map.h"
|
||||
#include "pbd/i18n.h"
|
||||
|
||||
using namespace ARDOUR;
|
||||
|
||||
SurroundReturn::SurroundReturn (Session& s)
|
||||
: Processor (s, _("SurrReturn"), Temporal::TimeDomainProvider (Temporal::AudioTime))
|
||||
, _lufs_meter (s.nominal_sample_rate (), 5)
|
||||
, _current_n_objects (max_object_id)
|
||||
, _in_map (ChanCount (DataType::AUDIO, 128))
|
||||
, _out_map (ChanCount (DataType::AUDIO, 14 + 6 /* Loudness Meter */))
|
||||
, _exporting (false)
|
||||
, _export_start (0)
|
||||
, _export_end (0)
|
||||
{
|
||||
#if !(defined(LV2_EXTENDED) && defined(HAVE_LV2_1_10_0))
|
||||
throw failed_constructor ();
|
||||
#endif
|
||||
|
||||
_surround_processor = std::dynamic_pointer_cast<LV2Plugin> (find_plugin (_session, "urn:ardour:a-vapor", ARDOUR::LV2));
|
||||
|
||||
if (!_surround_processor) {
|
||||
throw ProcessorException (_("Required Atmos/Vapor Processor not found."));
|
||||
}
|
||||
|
||||
_flush.store (0);
|
||||
_surround_processor->activate ();
|
||||
_surround_bufs.ensure_buffers (DataType::AUDIO, 128, s.get_block_size ());
|
||||
_surround_bufs.set_count (ChanCount (DataType::AUDIO, 128));
|
||||
|
||||
lv2_atom_forge_init (&_forge, URIMap::instance ().urid_map ());
|
||||
|
||||
for (size_t i = 0; i < max_object_id; ++i) {
|
||||
_current_render_mode[i] = -1;
|
||||
for (size_t p = 0; p < num_pan_parameters; ++p) {
|
||||
_current_value[i][p] = -1111; /* some invalid data that forces an update */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SurroundReturn::~SurroundReturn ()
|
||||
{
|
||||
}
|
||||
|
||||
int
|
||||
SurroundReturn::set_block_size (pframes_t nframes)
|
||||
{
|
||||
_surround_bufs.ensure_buffers (DataType::AUDIO, 128, nframes);
|
||||
_surround_processor->set_block_size (nframes);
|
||||
return 0;
|
||||
}
|
||||
|
||||
samplecnt_t
|
||||
SurroundReturn::signal_latency () const
|
||||
{
|
||||
return _surround_processor->signal_latency ();
|
||||
}
|
||||
|
||||
void
|
||||
SurroundReturn::flush ()
|
||||
{
|
||||
_flush.store (1);
|
||||
}
|
||||
|
||||
void
|
||||
SurroundReturn::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample, double speed, pframes_t nframes, bool)
|
||||
{
|
||||
if (!check_active ()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int canderef (1);
|
||||
if (_flush.compare_exchange_strong (canderef, 0)) {
|
||||
_surround_processor->flush ();
|
||||
}
|
||||
|
||||
bufs.set_count (_configured_output);
|
||||
_surround_bufs.silence (nframes, 0);
|
||||
|
||||
RouteList rl = *_session.get_routes (); // XXX this allocates memory
|
||||
rl.sort (Stripable::Sorter (true));
|
||||
|
||||
size_t id = 10; // First 10 IDs are reseved for bed mixes
|
||||
|
||||
for (auto const& r : rl) {
|
||||
std::shared_ptr<SurroundSend> ss;
|
||||
if (!r->active ()) {
|
||||
continue;
|
||||
}
|
||||
if (!(ss = r->surround_send ()) || !ss->active ()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
timepos_t start, end;
|
||||
|
||||
for (uint32_t s = 0; s < ss->bufs ().count ().n_audio () && id < max_object_id; ++s, ++id) {
|
||||
|
||||
std::shared_ptr<SurroundPannable> const& p (ss->pan_param (s, start, end));
|
||||
AutoState const as = p->automation_state ();
|
||||
bool const automated = (as & Play) || ((as & (Touch | Latch)) && !p->touching ());
|
||||
|
||||
AudioBuffer& dst_ab (_surround_bufs.get_audio (id));
|
||||
AudioBuffer const& src_ab (ss->bufs ().get_audio (s));
|
||||
if (id > 9) {
|
||||
/* object */
|
||||
dst_ab.read_from (src_ab, nframes);
|
||||
if (!automated || start_sample >= end_sample) {
|
||||
pan_t const v[num_pan_parameters] =
|
||||
{
|
||||
(pan_t)p->pan_pos_x->get_value (),
|
||||
(pan_t)p->pan_pos_y->get_value (),
|
||||
(pan_t)p->pan_pos_z->get_value (),
|
||||
(pan_t)p->pan_size->get_value (),
|
||||
(pan_t)p->pan_snap->get_value ()
|
||||
};
|
||||
maybe_send_metadata (id, 0, v);
|
||||
} else {
|
||||
/* Evaluate Automation
|
||||
*
|
||||
* Note, exclusive end: range = [start_sample, end_sample[
|
||||
* nframes == end_sample - start_sample
|
||||
* IOW: end_sample == next cycle's start_sample;
|
||||
*/
|
||||
if (nframes < 2) {
|
||||
evaluate (id, p, timepos_t (start_sample), 0);
|
||||
} else {
|
||||
timepos_t start (start_sample);
|
||||
timepos_t end (end_sample - 1);
|
||||
while (true) {
|
||||
Evoral::ControlEvent next_event (timepos_t (Temporal::AudioTime), 0.0f);
|
||||
if (!p->find_next_event (start, end, next_event)) {
|
||||
break;
|
||||
}
|
||||
samplecnt_t pos = std::min (timepos_t (start_sample).distance (next_event.when).samples(), (samplecnt_t) nframes - 1);
|
||||
evaluate (id, p, next_event.when, pos);
|
||||
start = next_event.when;
|
||||
}
|
||||
/* end */
|
||||
evaluate (id, p, end, nframes - 1);
|
||||
}
|
||||
}
|
||||
/* configure near/mid/far - not sample-accurate */
|
||||
int const brm = p->binaural_render_mode->get_value ();
|
||||
if (brm!= _current_render_mode[id]) {
|
||||
_current_render_mode[id] = brm;
|
||||
#if defined(LV2_EXTENDED) && defined(HAVE_LV2_1_10_0)
|
||||
URIMap::URIDs const& urids = URIMap::instance ().urids;
|
||||
forge_int_msg (urids.surr_Settings, urids.surr_Channel, id, urids.surr_BinauralRenderMode, brm);
|
||||
#endif
|
||||
}
|
||||
|
||||
} else {
|
||||
/* bed mix */
|
||||
dst_ab.merge_from (src_ab, nframes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (id >= max_object_id) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_current_n_objects != id) {
|
||||
_current_n_objects = id;
|
||||
#if defined(LV2_EXTENDED) && defined(HAVE_LV2_1_10_0)
|
||||
URIMap::URIDs const& urids = URIMap::instance ().urids;
|
||||
forge_int_msg (urids.surr_Settings, urids.surr_ChannelCount, _current_n_objects);
|
||||
#endif
|
||||
}
|
||||
|
||||
uint32_t meter_nframes = nframes;
|
||||
uint32_t meter_offset = 0;
|
||||
|
||||
if (_exporting && _export_start >= start_sample && _export_start < end_sample && start_sample != end_sample) {
|
||||
_lufs_meter.reset ();
|
||||
meter_offset = _export_start - start_sample;
|
||||
meter_nframes -= meter_offset;
|
||||
#if defined(LV2_EXTENDED) && defined(HAVE_LV2_1_10_0)
|
||||
//std::cout << "SURR START EXPORT " << start_sample << " <= " << _export_start << " < " << end_sample << "\n";
|
||||
URIMap::URIDs const& urids = URIMap::instance ().urids;
|
||||
forge_int_msg (urids.surr_ExportStart, urids.time_frame, _export_start - start_sample);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (_exporting && _export_end >= start_sample && _export_end < end_sample) {
|
||||
meter_nframes = _export_end - start_sample;
|
||||
#if defined(LV2_EXTENDED) && defined(HAVE_LV2_1_10_0)
|
||||
//std::cout << "SURR START EXPORT " << start_sample << " <= " << _export_end << " < " << end_sample << "\n";
|
||||
URIMap::URIDs const& urids = URIMap::instance ().urids;
|
||||
forge_int_msg (urids.surr_ExportStop, urids.time_frame, _export_end - start_sample);
|
||||
#endif
|
||||
}
|
||||
|
||||
_surround_processor->connect_and_run (_surround_bufs, start_sample, end_sample, speed, _in_map, _out_map, nframes, 0);
|
||||
|
||||
BufferSet::iterator i = _surround_bufs.begin (DataType::AUDIO);
|
||||
for (BufferSet::iterator o = bufs.begin (DataType::AUDIO); o != bufs.end (DataType::AUDIO); ++i, ++o) {
|
||||
o->read_from (*i, nframes);
|
||||
}
|
||||
|
||||
if (_exporting) {
|
||||
_rolling = true;
|
||||
} else if (_rolling && start_sample == end_sample) {
|
||||
_rolling = false;
|
||||
} else if (!_rolling && start_sample != end_sample) {
|
||||
_rolling = true;
|
||||
_lufs_meter.reset ();
|
||||
}
|
||||
|
||||
float const* data[5] = {
|
||||
_surround_bufs.get_audio (14).data (meter_offset),
|
||||
_surround_bufs.get_audio (15).data (meter_offset),
|
||||
_surround_bufs.get_audio (16).data (meter_offset),
|
||||
_surround_bufs.get_audio (18).data (meter_offset),
|
||||
_surround_bufs.get_audio (19).data (meter_offset)
|
||||
};
|
||||
|
||||
if (_rolling && (!_exporting || _export_end >= end_sample)) {
|
||||
_lufs_meter.run (data, meter_nframes);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SurroundReturn::forge_int_msg (uint32_t obj_id, uint32_t key, int val, uint32_t key2, int val2)
|
||||
{
|
||||
URIMap::URIDs const& urids = URIMap::instance ().urids;
|
||||
LV2_Atom_Forge_Frame frame;
|
||||
lv2_atom_forge_set_buffer (&_forge, _atom_buf, sizeof(_atom_buf));
|
||||
lv2_atom_forge_frame_time (&_forge, 0);
|
||||
LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object (&_forge, &frame, 1, obj_id);
|
||||
lv2_atom_forge_key (&_forge, key);
|
||||
lv2_atom_forge_int (&_forge, val);
|
||||
if (key2 > 0) {
|
||||
lv2_atom_forge_key (&_forge, key2);
|
||||
lv2_atom_forge_int (&_forge, val2);
|
||||
}
|
||||
lv2_atom_forge_pop (&_forge, &frame);
|
||||
_surround_processor->write_from_ui (0, urids.atom_eventTransfer, lv2_atom_total_size (msg), (const uint8_t*)msg);
|
||||
}
|
||||
|
||||
void
|
||||
SurroundReturn::maybe_send_metadata (size_t id, pframes_t sample, pan_t const v[num_pan_parameters])
|
||||
{
|
||||
bool changed = false;
|
||||
for (size_t i = 0; i < num_pan_parameters; ++i) {
|
||||
if (_current_value[id][i] != v[i]) {
|
||||
changed = true;
|
||||
}
|
||||
_current_value[id][i] = v[i];
|
||||
}
|
||||
if (!changed) {
|
||||
return;
|
||||
}
|
||||
URIMap::URIDs const& urids = URIMap::instance ().urids;
|
||||
|
||||
#if defined(LV2_EXTENDED) && defined(HAVE_LV2_1_10_0)
|
||||
LV2_Atom_Forge_Frame frame;
|
||||
lv2_atom_forge_set_buffer (&_forge, _atom_buf, sizeof(_atom_buf));
|
||||
lv2_atom_forge_frame_time (&_forge, 0);
|
||||
LV2_Atom* msg = (LV2_Atom*)lv2_atom_forge_object (&_forge, &frame, 1, urids.surr_MetaData);
|
||||
lv2_atom_forge_key (&_forge, urids.time_frame);
|
||||
lv2_atom_forge_int (&_forge, sample);
|
||||
lv2_atom_forge_key (&_forge, urids.surr_Channel);
|
||||
lv2_atom_forge_int (&_forge, id);
|
||||
lv2_atom_forge_key (&_forge, urids.surr_PosX);
|
||||
lv2_atom_forge_float (&_forge, v[0]);
|
||||
lv2_atom_forge_key (&_forge, urids.surr_PosY);
|
||||
lv2_atom_forge_float (&_forge, v[1]);
|
||||
lv2_atom_forge_key (&_forge, urids.surr_PosZ);
|
||||
lv2_atom_forge_float (&_forge, v[2]);
|
||||
lv2_atom_forge_key (&_forge, urids.surr_Size);
|
||||
lv2_atom_forge_float (&_forge, v[3]);
|
||||
lv2_atom_forge_key (&_forge, urids.surr_Snap);
|
||||
lv2_atom_forge_bool (&_forge, v[4]> 0 ? true : false);
|
||||
lv2_atom_forge_pop (&_forge, &frame);
|
||||
|
||||
_surround_processor->write_from_ui (0, urids.atom_eventTransfer, lv2_atom_total_size (msg), (const uint8_t*)msg);
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
SurroundReturn::evaluate (size_t id, std::shared_ptr<SurroundPannable> const& p, timepos_t const& when, pframes_t sample)
|
||||
{
|
||||
bool ok[num_pan_parameters];
|
||||
pan_t const v[num_pan_parameters] =
|
||||
{
|
||||
(pan_t)p->pan_pos_x->list()->rt_safe_eval (when, ok[0]),
|
||||
(pan_t)p->pan_pos_y->list()->rt_safe_eval (when, ok[1]),
|
||||
(pan_t)p->pan_pos_z->list()->rt_safe_eval (when, ok[2]),
|
||||
(pan_t)p->pan_size->list()->rt_safe_eval (when, ok[3]),
|
||||
(pan_t)p->pan_snap->list()->rt_safe_eval (when, ok[4])
|
||||
};
|
||||
if (ok[0] && ok[1] && ok[2] && ok[3] && ok[4]) {
|
||||
maybe_send_metadata (id, sample, v);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
SurroundReturn::can_support_io_configuration (const ChanCount& in, ChanCount& out)
|
||||
{
|
||||
out = ChanCount (DataType::AUDIO, 14); // 7.1.4 + binaural
|
||||
return in.n_total () == 0;
|
||||
}
|
||||
|
||||
void
|
||||
SurroundReturn::set_playback_offset (samplecnt_t cnt)
|
||||
{
|
||||
Processor::set_playback_offset (cnt);
|
||||
std::shared_ptr<RouteList const> rl (_session.get_routes ());
|
||||
for (auto const& r : *rl) {
|
||||
std::shared_ptr<SurroundSend> ss = r->surround_send ();
|
||||
if (ss) {
|
||||
ss->set_delay_out (cnt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SurroundReturn::setup_export (std::string const& fn, samplepos_t ss, samplepos_t es)
|
||||
{
|
||||
if (0 == _surround_processor->setup_export (fn.c_str())) {
|
||||
_exporting = true;
|
||||
_export_start = ss - effective_latency ();
|
||||
_export_end = es - effective_latency ();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
SurroundReturn::finalize_export ()
|
||||
{
|
||||
_surround_processor->finalize_export ();
|
||||
_exporting = false;
|
||||
_export_start = _export_end = 0;
|
||||
}
|
||||
|
||||
float
|
||||
SurroundReturn::integrated_loudness () const
|
||||
{
|
||||
return _lufs_meter.integrated_loudness ();
|
||||
}
|
||||
|
||||
float
|
||||
SurroundReturn::max_dbtp () const
|
||||
{
|
||||
return _lufs_meter.dbtp ();
|
||||
}
|
||||
|
||||
XMLNode&
|
||||
SurroundReturn::state () const
|
||||
{
|
||||
XMLNode* node = new XMLNode (X_("SurroundReturn"));
|
||||
return *node;
|
||||
}
|
||||
402
libs/ardour/surround_send.cc
Normal file
402
libs/ardour/surround_send.cc
Normal file
|
|
@ -0,0 +1,402 @@
|
|||
/*
|
||||
* Copyright (C) 2023 Paul Davis <paul@linuxaudiosystems.com>
|
||||
* Copyright (C) 2023 Robin Gareus <robin@gareus.org>
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
#include "ardour/surround_send.h"
|
||||
#include "ardour/amp.h"
|
||||
#include "ardour/audioengine.h"
|
||||
#include "ardour/buffer.h"
|
||||
#include "ardour/delayline.h"
|
||||
#include "ardour/gain_control.h"
|
||||
#include "ardour/internal_send.h"
|
||||
#include "ardour/surround_pannable.h"
|
||||
#include "ardour/route.h"
|
||||
#include "ardour/session.h"
|
||||
|
||||
#include "pbd/i18n.h"
|
||||
|
||||
using namespace ARDOUR;
|
||||
|
||||
SurroundSend::SurroundSend (Session& s, std::shared_ptr<MuteMaster> mm)
|
||||
: Processor (s, _("Surround"), Temporal::TimeDomainProvider (Temporal::AudioTime))
|
||||
, _surround_id (s.next_surround_send_id ())
|
||||
, _current_gain (GAIN_COEFF_ZERO)
|
||||
, _has_state (false)
|
||||
, _mute_master (mm)
|
||||
|
||||
{
|
||||
_send_delay.reset (new DelayLine (_session, "Send-" + name ()));
|
||||
_thru_delay.reset (new DelayLine (_session, "Thru-" + name ()));
|
||||
|
||||
std::shared_ptr<AutomationList> gl (new AutomationList (Evoral::Parameter (BusSendLevel), *this));
|
||||
_gain_control = std::shared_ptr<GainControl> (new GainControl (_session, Evoral::Parameter (BusSendLevel), gl));
|
||||
_amp.reset (new Amp (_session, _("Surround"), _gain_control, false));
|
||||
_amp->activate ();
|
||||
|
||||
_gain_control->set_flag (PBD::Controllable::InlineControl);
|
||||
//_gain_control->set_value (GAIN_COEFF_ZERO, PBD::Controllable::NoGroup);
|
||||
|
||||
add_control (_gain_control);
|
||||
|
||||
InternalSend::CycleStart.connect_same_thread (*this, boost::bind (&SurroundSend::cycle_start, this, _1));
|
||||
}
|
||||
|
||||
SurroundSend::~SurroundSend ()
|
||||
{
|
||||
}
|
||||
|
||||
std::shared_ptr<SurroundPannable>
|
||||
SurroundSend::pannable (size_t chn) const
|
||||
{
|
||||
return _pannable[chn];
|
||||
}
|
||||
|
||||
std::shared_ptr<SurroundPannable> const&
|
||||
SurroundSend::pan_param (size_t chn, timepos_t& s, timepos_t& e) const
|
||||
{
|
||||
s = _cycle_start;
|
||||
e = _cycle_end;
|
||||
return _pannable[chn];
|
||||
}
|
||||
|
||||
|
||||
gain_t
|
||||
SurroundSend::target_gain () const
|
||||
{
|
||||
return _mute_master->mute_gain_at (MuteMaster::SurroundSend);
|
||||
}
|
||||
|
||||
void
|
||||
SurroundSend::run (BufferSet& bufs, samplepos_t start_sample, samplepos_t end_sample, double speed, pframes_t nframes, bool)
|
||||
{
|
||||
automation_run (start_sample, nframes);
|
||||
|
||||
if (!check_active ()) {
|
||||
_mixbufs.silence (nframes, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
/* Copy inputs to mixbufs, since (a) we may need to adjust gain (b) the
|
||||
* contents need to be available for the Surround return (later)
|
||||
*/
|
||||
|
||||
BufferSet::iterator o = _mixbufs.begin (DataType::AUDIO);
|
||||
BufferSet::iterator i = bufs.begin (DataType::AUDIO);
|
||||
|
||||
for (; i != bufs.end (DataType::AUDIO) && o != _mixbufs.end (DataType::AUDIO); ++i, ++o) {
|
||||
o->read_from (*i, nframes);
|
||||
}
|
||||
|
||||
/* main gain control: * mute & bypass/enable */
|
||||
gain_t tgain = target_gain ();
|
||||
|
||||
if (tgain != _current_gain) {
|
||||
/* target gain has changed, fade in/out */
|
||||
_current_gain = Amp::apply_gain (_mixbufs, _session.nominal_sample_rate (), nframes, _current_gain, tgain);
|
||||
} else if (tgain == GAIN_COEFF_ZERO) {
|
||||
/* we were quiet last time, and we're still supposed to be quiet. */
|
||||
Amp::apply_simple_gain (_mixbufs, nframes, GAIN_COEFF_ZERO);
|
||||
return;
|
||||
} else if (tgain != GAIN_COEFF_UNITY) {
|
||||
/* target gain has not changed, but is not zero or unity */
|
||||
Amp::apply_simple_gain (_mixbufs, nframes, tgain);
|
||||
}
|
||||
|
||||
/* apply fader gain automation */
|
||||
_amp->set_gain_automation_buffer (_session.send_gain_automation_buffer ());
|
||||
_amp->setup_gain_automation (start_sample, end_sample, nframes);
|
||||
_amp->run (_mixbufs, start_sample, end_sample, speed, nframes, true);
|
||||
|
||||
_send_delay->run (_mixbufs, start_sample, end_sample, speed, nframes, true);
|
||||
|
||||
for (uint32_t chn = 0; chn < n_pannables (); ++ chn) {
|
||||
_pannable[chn]->automation_run (start_sample, nframes);
|
||||
}
|
||||
|
||||
_cycle_start = timepos_t (start_sample);
|
||||
_cycle_end = timepos_t (end_sample);
|
||||
|
||||
_thru_delay->run (bufs, start_sample, end_sample, speed, nframes, true);
|
||||
}
|
||||
|
||||
void
|
||||
SurroundSend::set_delay_in (samplecnt_t delay)
|
||||
{
|
||||
if (_delay_in == delay) {
|
||||
return;
|
||||
}
|
||||
_delay_in = delay;
|
||||
update_delaylines (false);
|
||||
}
|
||||
|
||||
void
|
||||
SurroundSend::set_delay_out (samplecnt_t delay, size_t /*bus*/)
|
||||
{
|
||||
if (_delay_out == delay) {
|
||||
return;
|
||||
}
|
||||
_delay_out = delay;
|
||||
update_delaylines (true);
|
||||
}
|
||||
|
||||
void
|
||||
SurroundSend::update_delaylines (bool rt_ok)
|
||||
{
|
||||
if (!rt_ok && AudioEngine::instance ()->running () && AudioEngine::instance ()->in_process_thread ()) {
|
||||
if (_delay_out > _delay_in) {
|
||||
if (_send_delay->delay () != 0 || _thru_delay->delay () != _delay_out - _delay_in) {
|
||||
QueueUpdate (); /* EMIT SIGNAL */
|
||||
}
|
||||
} else {
|
||||
if (_thru_delay->delay () != 0 || _send_delay->delay () != _delay_in - _delay_out) {
|
||||
QueueUpdate (); /* EMIT SIGNAL */
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool changed;
|
||||
if (_delay_out > _delay_in) {
|
||||
changed = _thru_delay->set_delay (_delay_out - _delay_in);
|
||||
_send_delay->set_delay (0);
|
||||
} else {
|
||||
changed = _thru_delay->set_delay (0);
|
||||
_send_delay->set_delay (_delay_in - _delay_out);
|
||||
}
|
||||
|
||||
if (changed && !AudioEngine::instance ()->in_process_thread ()) {
|
||||
ChangedLatency (); /* EMIT SIGNAL */
|
||||
}
|
||||
}
|
||||
|
||||
samplecnt_t
|
||||
SurroundSend::signal_latency () const
|
||||
{
|
||||
if (!_pending_active) {
|
||||
return 0;
|
||||
}
|
||||
if (_delay_out > _delay_in) {
|
||||
return _delay_out - _delay_in;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool
|
||||
SurroundSend::display_to_user() const
|
||||
{
|
||||
#ifdef MIXBUS
|
||||
return false;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
SurroundSend::n_pannables () const
|
||||
{
|
||||
/* do not use _pannable.size(),
|
||||
* if we would do so, state of removed pannables would be saved.
|
||||
*/
|
||||
#ifdef MIXBUS
|
||||
return std::min<uint32_t> (2, _configured_input.n_audio ());
|
||||
#endif
|
||||
return _configured_input.n_audio ();
|
||||
}
|
||||
|
||||
void
|
||||
SurroundSend::add_pannable ()
|
||||
{
|
||||
std::shared_ptr<SurroundPannable> p = std::shared_ptr<SurroundPannable> (new SurroundPannable (_session, _pannable.size (), Temporal::TimeDomainProvider (Temporal::AudioTime)));
|
||||
|
||||
add_control (p->pan_pos_x);
|
||||
add_control (p->pan_pos_y);
|
||||
add_control (p->pan_pos_z);
|
||||
add_control (p->pan_size);
|
||||
add_control (p->pan_snap);
|
||||
add_control (p->binaural_render_mode);
|
||||
_pannable.push_back (p);
|
||||
|
||||
_change_connections.drop_connections ();
|
||||
for (auto const& c: _controls) {
|
||||
std::shared_ptr<AutomationControl> ac = std::dynamic_pointer_cast<AutomationControl>(c.second);
|
||||
ac->Changed.connect_same_thread (_change_connections, [this](bool, PBD::Controllable::GroupControlDisposition) { PanChanged (); /* EMIT SIGNAL*/});
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
SurroundSend::configure_io (ChanCount in, ChanCount out)
|
||||
{
|
||||
bool changed = false;
|
||||
uint32_t n_audio = in.n_audio ();
|
||||
|
||||
#ifdef MIXBUS
|
||||
n_audio = std::min<uint32_t> (2, n_audio);
|
||||
#endif
|
||||
|
||||
if (_configured) {
|
||||
changed = n_audio != n_pannables ();
|
||||
}
|
||||
|
||||
while (_pannable.size () < n_audio) {
|
||||
add_pannable ();
|
||||
}
|
||||
|
||||
if (!_configured && !_has_state) {
|
||||
switch (n_audio) {
|
||||
case 2:
|
||||
_pannable[0]->pan_pos_x->set_value (0.0, PBD::Controllable::NoGroup);
|
||||
_pannable[1]->pan_pos_x->set_value (1.0, PBD::Controllable::NoGroup);
|
||||
break;
|
||||
case 3:
|
||||
_pannable[0]->pan_pos_x->set_value (0.0, PBD::Controllable::NoGroup);
|
||||
_pannable[1]->pan_pos_x->set_value (1.0, PBD::Controllable::NoGroup);
|
||||
_pannable[2]->pan_pos_x->set_value (0.5, PBD::Controllable::NoGroup);
|
||||
break;
|
||||
case 5:
|
||||
_pannable[0]->pan_pos_x->set_value (0.0, PBD::Controllable::NoGroup);
|
||||
_pannable[1]->pan_pos_x->set_value (1.0, PBD::Controllable::NoGroup);
|
||||
_pannable[2]->pan_pos_x->set_value (0.5, PBD::Controllable::NoGroup);
|
||||
_pannable[3]->pan_pos_x->set_value (0.0, PBD::Controllable::NoGroup);
|
||||
_pannable[4]->pan_pos_x->set_value (1.0, PBD::Controllable::NoGroup);
|
||||
_pannable[3]->pan_pos_y->set_value (1.0, PBD::Controllable::NoGroup);
|
||||
_pannable[4]->pan_pos_y->set_value (1.0, PBD::Controllable::NoGroup);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ChanCount ca (DataType::AUDIO, n_audio);
|
||||
_amp->configure_io (ca, ca);
|
||||
|
||||
if (!_send_delay->configure_io (ca, ca)) {
|
||||
return false;
|
||||
}
|
||||
if (!_thru_delay->configure_io (in, out)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_configured && changed) {
|
||||
/* We cannot emit `processors_changed` while holing the `process lock` */
|
||||
dynamic_cast<Route*> (_owner)->queue_surround_processors_changed (); /* EMIT SIGNAL */
|
||||
}
|
||||
|
||||
Processor::configure_io (in, out); /* may EMIT SIGNAL ConfigurationChanged */
|
||||
|
||||
set_block_size (_session.get_block_size ());
|
||||
|
||||
if (changed) {
|
||||
NPannablesChanged (); /* EMIT SIGNAL */
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
SurroundSend::ensure_mixbufs ()
|
||||
{
|
||||
_mixbufs.ensure_buffers (DataType::AUDIO, n_pannables (), _session.get_block_size ());
|
||||
}
|
||||
|
||||
int
|
||||
SurroundSend::set_block_size (pframes_t)
|
||||
{
|
||||
ensure_mixbufs ();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void
|
||||
SurroundSend::cycle_start (pframes_t /*nframes*/)
|
||||
{
|
||||
for (BufferSet::audio_iterator b = _mixbufs.audio_begin (); b != _mixbufs.audio_end (); ++b) {
|
||||
b->prepare ();
|
||||
}
|
||||
}
|
||||
|
||||
std::string
|
||||
SurroundSend::describe_parameter (Evoral::Parameter param)
|
||||
{
|
||||
if (n_pannables () < 2) {
|
||||
/* Use default names */
|
||||
return Automatable::describe_parameter (param);
|
||||
}
|
||||
|
||||
std::string prefix;
|
||||
if (n_pannables () == 2) {
|
||||
prefix = string_compose ("[%1]", param.id() == 0 ? S_("Panner|L") : S_("Panner|R"));
|
||||
} else {
|
||||
prefix = string_compose ("[%1]", 1 + param.id());
|
||||
}
|
||||
|
||||
if (param.type() == PanSurroundX) {
|
||||
return string_compose("%1 %2", prefix, _("Left/Right"));
|
||||
} else if (param.type() == PanSurroundY) {
|
||||
return string_compose("%1 %2", prefix, _("Front/Back"));
|
||||
} else if (param.type() == PanSurroundZ) {
|
||||
return string_compose("%1 %2", prefix, _("Elevation"));
|
||||
} else if (param.type() == PanSurroundSize) {
|
||||
return string_compose("%1 %2", prefix, _("Object Size"));
|
||||
} else if (param.type() == PanSurroundSnap) {
|
||||
return string_compose("%1 %2", prefix, _("Snap to Speaker"));
|
||||
} else if (param.type() == BinauralRenderMode) {
|
||||
return string_compose("%1 %2", prefix, _("Binaural Render mode"));
|
||||
}
|
||||
|
||||
return Automatable::describe_parameter (param);
|
||||
}
|
||||
|
||||
int
|
||||
SurroundSend::set_state (const XMLNode& node, int version)
|
||||
{
|
||||
XMLNode* gainnode = node.child (PBD::Controllable::xml_node_name.c_str());
|
||||
_gain_control->set_state (*gainnode, version);
|
||||
|
||||
uint32_t npan;
|
||||
if (!node.get_property("n-pannables", npan)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
while (_pannable.size () < npan) {
|
||||
add_pannable ();
|
||||
}
|
||||
|
||||
XMLNodeList pans = node.children (X_("SurroundPannable"));
|
||||
for (auto const& c: pans) {
|
||||
uint32_t chn;
|
||||
if (!c->get_property("channel", chn)) {
|
||||
continue;
|
||||
}
|
||||
_pannable[chn]->set_state (*c, version);
|
||||
}
|
||||
|
||||
_has_state = true;
|
||||
|
||||
return Processor::set_state (node, version);
|
||||
}
|
||||
|
||||
XMLNode&
|
||||
SurroundSend::state () const
|
||||
{
|
||||
XMLNode& node (Processor::state ());
|
||||
node.set_property ("type", "sursend");
|
||||
node.set_property ("n-pannables", n_pannables ());
|
||||
|
||||
node.add_child_nocopy (_gain_control->get_state());
|
||||
for (uint32_t chn = 0; chn < n_pannables (); ++ chn) {
|
||||
node.add_child_nocopy (_pannable[chn]->get_state ());
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
|
@ -249,6 +249,9 @@ libardour_sources = [
|
|||
'stripable.cc',
|
||||
# 'step_sequencer.cc',
|
||||
'strip_silence.cc',
|
||||
'surround_pannable.cc',
|
||||
'surround_return.cc',
|
||||
'surround_send.cc',
|
||||
'system_exec.cc',
|
||||
'revision.cc',
|
||||
'rt_midibuffer.cc',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue