merge 3.0-panexp (pan experiments) branch, revisions 8534-8585 into 3.0, thus ending 3.0-panexp. THIS COMMIT WILL BREAK ALL EXISTING 3.0 SESSIONS IN SOME WAY (possibly not fatally).
git-svn-id: svn://localhost/ardour2/branches/3.0@8586 d708f5d6-7413-0410-9779-e7cbd77b26cf
This commit is contained in:
parent
1385643131
commit
15b5fce904
|
@ -165,6 +165,7 @@ static const char* authors[] = {
|
|||
N_("Petter Sundlöf"),
|
||||
N_("Mike Täht"),
|
||||
N_("Thorsten Wilms"),
|
||||
N_("Robin Gareus"),
|
||||
0
|
||||
};
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ libs=$TOP/@LIBS@
|
|||
|
||||
export ARDOUR_PATH=$TOP/gtk2_ardour/icons:$TOP/gtk2_ardour/pixmaps:$TOP/build/default/gtk2_ardour:$TOP/gtk2_ardour:.
|
||||
export ARDOUR_SURFACES_PATH=$libs/surfaces/osc:$libs/surfaces/generic_midi:$libs/surfaces/tranzport:$libs/surfaces/powermate:$libs/surfaces/mackie
|
||||
export ARDOUR_PANNER_PATH=$libs/panners/2in2out:$libs/panners/1in2out:$libs/panners/vbap
|
||||
export ARDOUR_DATA_PATH=$TOP/gtk2_ardour:build/default/gtk2_ardour:.
|
||||
|
||||
if test -d $HOME/gtk/inst ; then
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
#include "ardour/audioplaylist.h"
|
||||
#include "ardour/event_type_map.h"
|
||||
#include "ardour/location.h"
|
||||
#include "ardour/pannable.h"
|
||||
#include "ardour/panner.h"
|
||||
#include "ardour/playlist.h"
|
||||
#include "ardour/processor.h"
|
||||
|
@ -191,7 +192,9 @@ AudioTimeAxisView::create_automation_child (const Evoral::Parameter& param, bool
|
|||
|
||||
create_gain_automation_child (param, show);
|
||||
|
||||
} else if (param.type() == PanAutomation) {
|
||||
} else if (param.type() == PanWidthAutomation ||
|
||||
param.type() == PanElevationAutomation ||
|
||||
param.type() == PanAzimuthAutomation) {
|
||||
|
||||
ensure_xml_node ();
|
||||
ensure_pan_views (show);
|
||||
|
@ -217,13 +220,11 @@ AudioTimeAxisView::ensure_pan_views (bool show)
|
|||
return;
|
||||
}
|
||||
|
||||
const set<Evoral::Parameter>& params = _route->panner()->what_can_be_automated();
|
||||
set<Evoral::Parameter> params = _route->panner()->what_can_be_automated();
|
||||
set<Evoral::Parameter>::iterator p;
|
||||
|
||||
for (p = params.begin(); p != params.end(); ++p) {
|
||||
boost::shared_ptr<ARDOUR::AutomationControl> pan_control
|
||||
= boost::dynamic_pointer_cast<ARDOUR::AutomationControl>(
|
||||
_route->panner()->control(*p));
|
||||
boost::shared_ptr<ARDOUR::AutomationControl> pan_control = _route->pannable()->automation_control(*p);
|
||||
|
||||
if (pan_control->parameter().type() == NullAutomation) {
|
||||
error << "Pan control has NULL automation type!" << endmsg;
|
||||
|
@ -238,7 +239,9 @@ AudioTimeAxisView::ensure_pan_views (bool show)
|
|||
|
||||
boost::shared_ptr<AutomationTimeAxisView> t (
|
||||
new AutomationTimeAxisView (_session,
|
||||
_route, _route->panner(), pan_control,
|
||||
_route,
|
||||
_route->pannable(),
|
||||
pan_control,
|
||||
_editor,
|
||||
*this,
|
||||
false,
|
||||
|
@ -442,7 +445,7 @@ AudioTimeAxisView::build_automation_action_menu ()
|
|||
pan_automation_item = dynamic_cast<CheckMenuItem*> (&automation_items.back ());
|
||||
pan_automation_item->set_active (pan_tracks.front()->marked_for_display ());
|
||||
|
||||
set<Evoral::Parameter> const & params = _route->panner()->what_can_be_automated ();
|
||||
set<Evoral::Parameter> const & params = _route->pannable()->what_can_be_automated ();
|
||||
for (set<Evoral::Parameter>::iterator p = params.begin(); p != params.end(); ++p) {
|
||||
_main_automation_menu_map[*p] = pan_automation_item;
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "ardour/event_type_map.h"
|
||||
#include "ardour/automatable.h"
|
||||
#include "ardour/panner.h"
|
||||
#include "ardour/pan_controllable.h"
|
||||
#include "ardour/session.h"
|
||||
|
||||
#include "ardour_ui.h"
|
||||
|
@ -90,8 +91,6 @@ AutomationController::get_label (int&)
|
|||
// Hack to display CC rounded to int
|
||||
if (_controllable->parameter().type() == MidiCCAutomation) {
|
||||
s << (int)_controllable->get_value();
|
||||
} else if (_controllable->parameter().type() == PanAutomation) {
|
||||
s << Panner::value_as_string (_controllable->get_value ());
|
||||
} else {
|
||||
s << std::fixed << std::setprecision(3) << _controllable->get_value();
|
||||
}
|
||||
|
|
|
@ -1204,8 +1204,9 @@ AutomationLine::view_to_model_coord_y (double& y) const
|
|||
y = slider_position_to_gain (y);
|
||||
y = max (0.0, y);
|
||||
y = min (2.0, y);
|
||||
} else if (alist->parameter().type() == PanAutomation) {
|
||||
// vertical coordinate axis reversal
|
||||
} else if (alist->parameter().type() == PanAzimuthAutomation ||
|
||||
alist->parameter().type() == PanElevationAutomation ||
|
||||
alist->parameter().type() == PanWidthAutomation) {
|
||||
y = 1.0 - y;
|
||||
} else if (alist->parameter().type() == PluginAutomation) {
|
||||
y = y * (double)(alist->get_max_y()- alist->get_min_y()) + alist->get_min_y();
|
||||
|
@ -1221,7 +1222,9 @@ AutomationLine::model_to_view_coord (double& x, double& y) const
|
|||
if (alist->parameter().type() == GainAutomation ||
|
||||
alist->parameter().type() == EnvelopeAutomation) {
|
||||
y = gain_to_slider_position (y);
|
||||
} else if (alist->parameter().type() == PanAutomation) {
|
||||
} else if (alist->parameter().type() == PanAzimuthAutomation ||
|
||||
alist->parameter().type() == PanElevationAutomation ||
|
||||
alist->parameter().type() == PanWidthAutomation) {
|
||||
// vertical coordinate axis reversal
|
||||
y = 1.0 - y;
|
||||
} else if (alist->parameter().type() == PluginAutomation) {
|
||||
|
|
|
@ -639,6 +639,7 @@ class Editor : public PublicEditor, public PBD::ScopedConnectionList, public ARD
|
|||
|
||||
void set_selected_track (TimeAxisView&, Selection::Operation op = Selection::Set, bool no_remove=false);
|
||||
void select_all_tracks ();
|
||||
void select_all_internal_edit (Selection::Operation);
|
||||
|
||||
bool set_selected_control_point_from_click (Selection::Operation op = Selection::Set, bool no_remove=false);
|
||||
void set_selected_track_from_click (bool press, Selection::Operation op = Selection::Set, bool no_remove=false);
|
||||
|
|
|
@ -2628,11 +2628,6 @@ Editor::set_internal_edit (bool yn)
|
|||
ARDOUR_UI::instance()->set_tip (mouse_select_button, _("Draw/Edit MIDI Notes"));
|
||||
mouse_mode_toggled (mouse_mode);
|
||||
|
||||
/* deselect everything to avoid confusion when e.g. we can't now cut a previously selected
|
||||
region because cut means "cut note" rather than "cut region".
|
||||
*/
|
||||
selection->clear ();
|
||||
|
||||
} else {
|
||||
|
||||
mouse_select_button.set_image (*(manage (new Image (::get_icon("tool_range")))));
|
||||
|
|
|
@ -37,6 +37,7 @@
|
|||
#include "control_point.h"
|
||||
#include "editor_regions.h"
|
||||
#include "editor_cursors.h"
|
||||
#include "midi_region_view.h"
|
||||
|
||||
#include "i18n.h"
|
||||
|
||||
|
@ -1212,11 +1213,27 @@ Editor::select_all_in_track (Selection::Operation op)
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
Editor::select_all_internal_edit (Selection::Operation op)
|
||||
{
|
||||
/* currently limited to MIDI only */
|
||||
|
||||
for (MidiRegionSelection::iterator i = selection->midi_regions.begin(); i != selection->midi_regions.end(); ++i) {
|
||||
MidiRegionView* mrv = *i;
|
||||
mrv->select_all_notes ();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Editor::select_all (Selection::Operation op)
|
||||
{
|
||||
list<Selectable *> touched;
|
||||
|
||||
if (_internal_editing) {
|
||||
select_all_internal_edit (op);
|
||||
return;
|
||||
}
|
||||
|
||||
for (TrackViewList::iterator iter = track_views.begin(); iter != track_views.end(); ++iter) {
|
||||
if ((*iter)->hidden()) {
|
||||
continue;
|
||||
|
|
|
@ -1873,6 +1873,16 @@ MidiRegionView::unique_select(ArdourCanvas::CanvasNoteEvent* ev)
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
MidiRegionView::select_all_notes ()
|
||||
{
|
||||
clear_selection ();
|
||||
|
||||
for (Events::iterator i = _events.begin(); i != _events.end(); ++i) {
|
||||
add_to_selection (*i);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
MidiRegionView::select_matching_notes (uint8_t notenum, uint16_t channel_mask, bool add, bool extend)
|
||||
{
|
||||
|
|
|
@ -191,6 +191,7 @@ class MidiRegionView : public RegionView
|
|||
void delete_selection();
|
||||
void delete_note (boost::shared_ptr<NoteType>);
|
||||
size_t selection_size() { return _selection.size(); }
|
||||
void select_all_notes ();
|
||||
|
||||
void move_selection(double dx, double dy, double cumulative_dy);
|
||||
void note_dropped (ArdourCanvas::CanvasNoteEvent* ev, ARDOUR::frameoffset_t, int8_t d_note);
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
#include "ardour/route.h"
|
||||
#include "ardour/route_group.h"
|
||||
#include "ardour/audio_track.h"
|
||||
#include "ardour/pannable.h"
|
||||
#include "ardour/panner.h"
|
||||
#include "ardour/send.h"
|
||||
#include "ardour/processor.h"
|
||||
|
@ -967,14 +968,10 @@ MixerStrip::connect_to_pan ()
|
|||
return;
|
||||
}
|
||||
|
||||
boost::shared_ptr<ARDOUR::AutomationControl> pan_control
|
||||
= boost::dynamic_pointer_cast<ARDOUR::AutomationControl>(
|
||||
_route->panner()->control(Evoral::Parameter(PanAutomation)));
|
||||
boost::shared_ptr<Pannable> p = _route->pannable ();
|
||||
|
||||
if (pan_control) {
|
||||
pan_control->alist()->automation_state_changed.connect (panstate_connection, invalidator (*this), boost::bind (&PannerUI::pan_automation_state_changed, &panners), gui_context());
|
||||
pan_control->alist()->automation_style_changed.connect (panstyle_connection, invalidator (*this), boost::bind (&PannerUI::pan_automation_style_changed, &panners), gui_context());
|
||||
}
|
||||
p->automation_state_changed.connect (panstate_connection, invalidator (*this), boost::bind (&PannerUI::pan_automation_state_changed, &panners), gui_context());
|
||||
p->automation_style_changed.connect (panstyle_connection, invalidator (*this), boost::bind (&PannerUI::pan_automation_style_changed, &panners), gui_context());
|
||||
|
||||
panners.panner_changed (this);
|
||||
}
|
||||
|
@ -1598,10 +1595,10 @@ MixerStrip::reset_strip_style ()
|
|||
if (is_midi_track()) {
|
||||
if (_route->active()) {
|
||||
set_name ("MidiTrackStripBase");
|
||||
gpm.set_meter_strip_name ("MidiTrackStripBase");
|
||||
gpm.set_meter_strip_name ("MidiTrackMetrics");
|
||||
} else {
|
||||
set_name ("MidiTrackStripBaseInactive");
|
||||
gpm.set_meter_strip_name ("MidiTrackStripBaseInactive");
|
||||
gpm.set_meter_strip_name ("MidiTrackMetricsInactive");
|
||||
}
|
||||
gpm.set_fader_name ("MidiTrackFader");
|
||||
} else if (is_audio_track()) {
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "gtkmm2ext/gtk_ui.h"
|
||||
#include "gtkmm2ext/keyboard.h"
|
||||
|
||||
#include "ardour/panner.h"
|
||||
#include "ardour/panner.h"
|
||||
|
||||
#include "ardour_ui.h"
|
||||
|
|
|
@ -78,7 +78,7 @@ Panner2d::~Panner2d()
|
|||
void
|
||||
Panner2d::reset (uint32_t n_inputs)
|
||||
{
|
||||
Targets::size_type existing_pucks = pucks.size();
|
||||
uint32_t nouts = panner->out().n_audio();
|
||||
|
||||
/* pucks */
|
||||
|
||||
|
@ -120,36 +120,40 @@ Panner2d::reset (uint32_t n_inputs)
|
|||
break;
|
||||
}
|
||||
|
||||
#ifdef PANNER_HACKS
|
||||
for (uint32_t i = existing_pucks; i < n_inputs; ++i) {
|
||||
pucks[i]->position = panner->streampanner (i).get_position ();
|
||||
pucks[i]->visible = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* add all outputs */
|
||||
|
||||
while (targets.size() < panner->nouts()) {
|
||||
while (targets.size() < nouts) {
|
||||
add_target (AngularVector());
|
||||
}
|
||||
|
||||
if (targets.size() > panner->nouts()) {
|
||||
for (uint32_t i = panner->nouts(); i < targets.size(); ++i) {
|
||||
if (targets.size() > nouts) {
|
||||
for (uint32_t i = nouts; i < targets.size(); ++i) {
|
||||
delete targets[i];
|
||||
}
|
||||
|
||||
targets.resize (panner->nouts ());
|
||||
targets.resize (nouts);
|
||||
}
|
||||
|
||||
for (Targets::iterator x = targets.begin(); x != targets.end(); ++x) {
|
||||
(*x)->visible = false;
|
||||
}
|
||||
|
||||
for (uint32_t n = 0; n < panner->nouts(); ++n) {
|
||||
for (uint32_t n = 0; n < nouts; ++n) {
|
||||
char buf[16];
|
||||
|
||||
snprintf (buf, sizeof (buf), "%d", n+1);
|
||||
targets[n]->set_text (buf);
|
||||
#ifdef PANNER_HACKS
|
||||
targets[n]->position = panner->output(n).position;
|
||||
targets[n]->visible = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
queue_draw ();
|
||||
|
@ -201,6 +205,7 @@ Panner2d::handle_state_change ()
|
|||
void
|
||||
Panner2d::handle_position_change ()
|
||||
{
|
||||
#ifdef PANNER_HACKS
|
||||
uint32_t n;
|
||||
ENSURE_GUI_THREAD (*this, &Panner2d::handle_position_change)
|
||||
|
||||
|
@ -213,6 +218,7 @@ Panner2d::handle_position_change ()
|
|||
}
|
||||
|
||||
queue_draw ();
|
||||
#endif
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -528,7 +534,9 @@ Panner2d::handle_motion (gint evx, gint evy, GdkModifierType state)
|
|||
|
||||
cp.angular (drag_target->position); /* sets drag target position */
|
||||
|
||||
#ifdef PANNER_HACKS
|
||||
panner->streampanner (drag_index).set_position (drag_target->position);
|
||||
#endif
|
||||
|
||||
queue_draw ();
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include "ardour/delivery.h"
|
||||
#include "ardour/session.h"
|
||||
#include "ardour/panner.h"
|
||||
#include "ardour/pannable.h"
|
||||
#include "ardour/route.h"
|
||||
|
||||
#include "i18n.h"
|
||||
|
@ -50,13 +51,7 @@ const int PannerUI::pan_bar_height = 40;
|
|||
|
||||
PannerUI::PannerUI (Session* s)
|
||||
: _current_nouts (-1)
|
||||
, _current_npans (-1)
|
||||
, hAdjustment(0.0, 0.0, 0.0)
|
||||
, vAdjustment(0.0, 0.0, 0.0)
|
||||
, panning_viewport(hAdjustment, vAdjustment)
|
||||
, panning_up_arrow (Gtk::ARROW_UP, Gtk::SHADOW_OUT)
|
||||
, panning_down_arrow (Gtk::ARROW_DOWN, Gtk::SHADOW_OUT)
|
||||
, panning_link_button (_("link"))
|
||||
, _current_nins (-1)
|
||||
, pan_automation_style_button ("")
|
||||
, pan_automation_state_button ("")
|
||||
{
|
||||
|
@ -80,52 +75,13 @@ PannerUI::PannerUI (Session* s)
|
|||
//set_size_request_to_display_given_text (pan_automation_state_button, X_("O"), 2, 2);
|
||||
//set_size_request_to_display_given_text (pan_automation_style_button, X_("0"), 2, 2);
|
||||
|
||||
panning_viewport.set_name (X_("BaseFrame"));
|
||||
|
||||
ARDOUR_UI::instance()->set_tip (panning_link_button,
|
||||
_("panning link control"));
|
||||
ARDOUR_UI::instance()->set_tip (panning_link_direction_button,
|
||||
_("panning link direction"));
|
||||
|
||||
pan_automation_style_button.unset_flags (Gtk::CAN_FOCUS);
|
||||
pan_automation_state_button.unset_flags (Gtk::CAN_FOCUS);
|
||||
|
||||
pan_automation_style_button.signal_button_press_event().connect (sigc::mem_fun(*this, &PannerUI::pan_automation_style_button_event), false);
|
||||
pan_automation_state_button.signal_button_press_event().connect (sigc::mem_fun(*this, &PannerUI::pan_automation_state_button_event), false);
|
||||
|
||||
panning_link_button.set_name (X_("PanningLinkButton"));
|
||||
panning_link_direction_button.set_name (X_("PanningLinkDirectionButton"));
|
||||
|
||||
panning_link_box.pack_start (panning_link_button, true, true);
|
||||
panning_link_box.pack_start (panning_link_direction_button, true, true);
|
||||
panning_link_box.pack_start (pan_automation_state_button, true, true);
|
||||
|
||||
/* the pixmap will be reset at some point, but the key thing is that
|
||||
we need a pixmap in the button just to get started.
|
||||
*/
|
||||
panning_link_direction_button.add (*(manage (new Image (get_xpm("forwardblarrow.xpm")))));
|
||||
|
||||
panning_link_direction_button.signal_clicked().connect
|
||||
(sigc::mem_fun(*this, &PannerUI::panning_link_direction_clicked));
|
||||
|
||||
panning_link_button.signal_button_press_event().connect
|
||||
(sigc::mem_fun(*this, &PannerUI::panning_link_button_press), false);
|
||||
panning_link_button.signal_button_release_event().connect
|
||||
(sigc::mem_fun(*this, &PannerUI::panning_link_button_release), false);
|
||||
|
||||
panning_up.set_border_width (3);
|
||||
panning_down.set_border_width (3);
|
||||
panning_up.add (panning_up_arrow);
|
||||
panning_down.add (panning_down_arrow);
|
||||
panning_up.set_name (X_("PanScrollerBase"));
|
||||
panning_down.set_name (X_("PanScrollerBase"));
|
||||
panning_up_arrow.set_name (X_("PanScrollerArrow"));
|
||||
panning_down_arrow.set_name (X_("PanScrollerArrow"));
|
||||
|
||||
pan_vbox.set_spacing (2);
|
||||
pan_vbox.pack_start (panning_viewport, Gtk::PACK_SHRINK);
|
||||
pan_vbox.pack_start (panning_link_box, Gtk::PACK_SHRINK);
|
||||
|
||||
pack_start (pan_vbox, true, true);
|
||||
|
||||
twod_panner = 0;
|
||||
|
@ -158,18 +114,16 @@ PannerUI::set_panner (boost::shared_ptr<Panner> p)
|
|||
}
|
||||
|
||||
_panner->Changed.connect (connections, invalidator (*this), boost::bind (&PannerUI::panner_changed, this, this), gui_context());
|
||||
_panner->LinkStateChanged.connect (connections, invalidator (*this), boost::bind (&PannerUI::update_pan_linkage, this), gui_context());
|
||||
_panner->StateChanged.connect (connections, invalidator (*this), boost::bind (&PannerUI::update_pan_state, this), gui_context());
|
||||
|
||||
/* new panner object, force complete reset of panner GUI
|
||||
*/
|
||||
|
||||
_current_nouts = 0;
|
||||
_current_npans = 0;
|
||||
_current_nins = 0;
|
||||
|
||||
panner_changed (0);
|
||||
update_pan_sensitive ();
|
||||
update_pan_linkage ();
|
||||
pan_automation_state_changed ();
|
||||
|
||||
}
|
||||
|
@ -224,61 +178,6 @@ PannerUI::get_controllable()
|
|||
return pan_bars[0]->get_controllable();
|
||||
}
|
||||
|
||||
bool
|
||||
PannerUI::panning_link_button_press (GdkEventButton*)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
PannerUI::panning_link_button_release (GdkEventButton*)
|
||||
{
|
||||
if (!ignore_toggle) {
|
||||
_panner->set_linked (!_panner->linked());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
PannerUI::panning_link_direction_clicked()
|
||||
{
|
||||
switch (_panner->link_direction()) {
|
||||
case Panner::SameDirection:
|
||||
_panner->set_link_direction (Panner::OppositeDirection);
|
||||
break;
|
||||
default:
|
||||
_panner->set_link_direction (Panner::SameDirection);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PannerUI::update_pan_linkage ()
|
||||
{
|
||||
ENSURE_GUI_THREAD (*this, &PannerUI::update_pan_linkage)
|
||||
|
||||
bool const x = _panner->linked();
|
||||
bool const bx = panning_link_button.get_active();
|
||||
|
||||
if (x != bx) {
|
||||
|
||||
ignore_toggle = true;
|
||||
panning_link_button.set_active (x);
|
||||
ignore_toggle = false;
|
||||
}
|
||||
|
||||
panning_link_direction_button.set_sensitive (x);
|
||||
|
||||
switch (_panner->link_direction()) {
|
||||
case Panner::SameDirection:
|
||||
panning_link_direction_button.set_image (*(manage (new Image (get_xpm ("forwardblarrow.xpm")))));
|
||||
break;
|
||||
default:
|
||||
panning_link_direction_button.set_image (*(manage (new Image (get_xpm("revdblarrow.xpm")))));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PannerUI::on_size_allocate (Allocation& a)
|
||||
{
|
||||
|
@ -288,19 +187,9 @@ PannerUI::on_size_allocate (Allocation& a)
|
|||
void
|
||||
PannerUI::set_width (Width w)
|
||||
{
|
||||
switch (w) {
|
||||
case Wide:
|
||||
panning_link_button.set_label (_("link"));
|
||||
break;
|
||||
case Narrow:
|
||||
panning_link_button.set_label (_("L"));
|
||||
break;
|
||||
}
|
||||
|
||||
_width = w;
|
||||
}
|
||||
|
||||
|
||||
PannerUI::~PannerUI ()
|
||||
{
|
||||
for (vector<MonoPanner*>::iterator i = pan_bars.begin(); i != pan_bars.end(); ++i) {
|
||||
|
@ -319,50 +208,13 @@ PannerUI::~PannerUI ()
|
|||
void
|
||||
PannerUI::panner_changed (void* src)
|
||||
{
|
||||
ENSURE_GUI_THREAD (*this, &PannerUI::panner_changed)
|
||||
|
||||
setup_pan ();
|
||||
|
||||
if (src == this) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (_panner->npanners()) {
|
||||
case 0:
|
||||
panning_link_direction_button.set_sensitive (false);
|
||||
panning_link_button.set_sensitive (false);
|
||||
return;
|
||||
case 1:
|
||||
panning_link_direction_button.set_sensitive (false);
|
||||
panning_link_button.set_sensitive (false);
|
||||
break;
|
||||
default:
|
||||
panning_link_direction_button.set_sensitive (_panner->linked ());
|
||||
panning_link_button.set_sensitive (true);
|
||||
}
|
||||
|
||||
uint32_t const nouts = _panner->nouts();
|
||||
|
||||
switch (nouts) {
|
||||
case 0:
|
||||
case 1:
|
||||
/* relax */
|
||||
break;
|
||||
|
||||
case 2:
|
||||
break;
|
||||
|
||||
default:
|
||||
// panner->move_puck (pan_value (pans[0], pans[1]), 0.5);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PannerUI::update_pan_state ()
|
||||
{
|
||||
/* currently nothing to do */
|
||||
// ENSURE_GUI_THREAD (*this, &PannerUI::update_panner_state)
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -370,101 +222,81 @@ PannerUI::setup_pan ()
|
|||
{
|
||||
if (!_panner) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
uint32_t const nouts = _panner->out().n_audio();
|
||||
uint32_t const nins = _panner->in().n_audio();
|
||||
|
||||
uint32_t const nouts = _panner->nouts();
|
||||
uint32_t const npans = _panner->npanners();
|
||||
|
||||
if (int32_t (nouts) == _current_nouts && int32_t (npans) == _current_npans) {
|
||||
if (int32_t (nouts) == _current_nouts && int32_t (nins) == _current_nins) {
|
||||
return;
|
||||
}
|
||||
|
||||
_current_nouts = nouts;
|
||||
_current_npans = npans;
|
||||
|
||||
panning_viewport.remove ();
|
||||
container_clear (pan_vbox);
|
||||
|
||||
delete twod_panner;
|
||||
twod_panner = 0;
|
||||
delete _stereo_panner;
|
||||
_stereo_panner = 0;
|
||||
|
||||
|
||||
if (nouts == 0 || nouts == 1) {
|
||||
|
||||
while (!pan_bars.empty()) {
|
||||
delete pan_bars.back();
|
||||
pan_bars.pop_back ();
|
||||
}
|
||||
delete _stereo_panner;
|
||||
delete twod_panner;
|
||||
|
||||
/* stick something into the panning viewport so that it redraws */
|
||||
|
||||
EventBox* eb = manage (new EventBox());
|
||||
panning_viewport.add (*eb);
|
||||
pan_vbox.pack_start (*eb, false, false);
|
||||
|
||||
} else if (nouts == 2) {
|
||||
|
||||
vector<Adjustment*>::size_type p;
|
||||
|
||||
while (!pan_bars.empty()) {
|
||||
delete pan_bars.back();
|
||||
pan_bars.pop_back ();
|
||||
}
|
||||
|
||||
if (npans == 2) {
|
||||
if (nins == 2) {
|
||||
|
||||
/* add integrated 2in/2out panner GUI */
|
||||
|
||||
_stereo_panner = new StereoPanner (_panner->direction_control(),
|
||||
_panner->width_control());
|
||||
boost::shared_ptr<Pannable> pannable = _panner->pannable();
|
||||
|
||||
_stereo_panner = new StereoPanner (_panner);
|
||||
_stereo_panner->set_size_request (-1, pan_bar_height);
|
||||
panning_viewport.add (*_stereo_panner);
|
||||
pan_vbox.pack_start (*_stereo_panner, false, false);
|
||||
|
||||
boost::shared_ptr<AutomationControl> ac;
|
||||
|
||||
ac = _panner->direction_control();
|
||||
ac = pannable->pan_azimuth_control;
|
||||
_stereo_panner->StartPositionGesture.connect (sigc::bind (sigc::mem_fun (*this, &PannerUI::start_touch),
|
||||
boost::weak_ptr<AutomationControl> (ac)));
|
||||
_stereo_panner->StopPositionGesture.connect (sigc::bind (sigc::mem_fun (*this, &PannerUI::stop_touch),
|
||||
boost::weak_ptr<AutomationControl>(ac)));
|
||||
|
||||
ac = _panner->width_control();
|
||||
ac = pannable->pan_width_control;
|
||||
_stereo_panner->StartWidthGesture.connect (sigc::bind (sigc::mem_fun (*this, &PannerUI::start_touch),
|
||||
boost::weak_ptr<AutomationControl> (ac)));
|
||||
_stereo_panner->StopWidthGesture.connect (sigc::bind (sigc::mem_fun (*this, &PannerUI::stop_touch),
|
||||
boost::weak_ptr<AutomationControl>(ac)));
|
||||
|
||||
} else {
|
||||
} else if (nins == 1) {
|
||||
/* 1-in/2out */
|
||||
|
||||
/* N-in/2out - just use a set of single-channel panners */
|
||||
|
||||
while ((p = pan_bars.size()) < npans) {
|
||||
|
||||
MonoPanner* mp;
|
||||
boost::shared_ptr<AutomationControl> ac = _panner->pan_control (p);
|
||||
|
||||
mp = new MonoPanner (_panner->pan_control (p));
|
||||
|
||||
mp->StartGesture.connect (sigc::bind (sigc::mem_fun (*this, &PannerUI::start_touch),
|
||||
MonoPanner* mp;
|
||||
boost::shared_ptr<Pannable> pannable = _panner->pannable();
|
||||
boost::shared_ptr<AutomationControl> ac = pannable->pan_azimuth_control;
|
||||
|
||||
mp = new MonoPanner (ac);
|
||||
|
||||
mp->StartGesture.connect (sigc::bind (sigc::mem_fun (*this, &PannerUI::start_touch),
|
||||
boost::weak_ptr<AutomationControl> (ac)));
|
||||
mp->StopGesture.connect (sigc::bind (sigc::mem_fun (*this, &PannerUI::stop_touch),
|
||||
boost::weak_ptr<AutomationControl>(ac)));
|
||||
|
||||
mp->signal_button_release_event().connect
|
||||
(sigc::bind (sigc::mem_fun(*this, &PannerUI::pan_button_event), (uint32_t) p));
|
||||
|
||||
mp->set_size_request (-1, pan_bar_height);
|
||||
|
||||
pan_bars.push_back (mp);
|
||||
pan_bar_packer.pack_start (*mp, false, false);
|
||||
}
|
||||
mp->StopGesture.connect (sigc::bind (sigc::mem_fun (*this, &PannerUI::stop_touch),
|
||||
boost::weak_ptr<AutomationControl>(ac)));
|
||||
|
||||
/* now that we actually have the pan bars,
|
||||
set their sensitivity based on current
|
||||
automation state.
|
||||
*/
|
||||
mp->signal_button_release_event().connect (sigc::mem_fun(*this, &PannerUI::pan_button_event));
|
||||
|
||||
mp->set_size_request (-1, pan_bar_height);
|
||||
|
||||
update_pan_sensitive ();
|
||||
panning_viewport.add (pan_bar_packer);
|
||||
pan_vbox.pack_start (*mp, false, false);
|
||||
|
||||
} else {
|
||||
warning << string_compose (_("No panner user interface is currently available for %1-in/2out tracks/busses"),
|
||||
nins) << endmsg;
|
||||
}
|
||||
|
||||
|
||||
|
@ -474,24 +306,22 @@ PannerUI::setup_pan ()
|
|||
twod_panner = new Panner2d (_panner, 61);
|
||||
twod_panner->set_name ("MixerPanZone");
|
||||
twod_panner->show ();
|
||||
|
||||
twod_panner->signal_button_press_event().connect
|
||||
(sigc::bind (sigc::mem_fun(*this, &PannerUI::pan_button_event), (uint32_t) 0), false);
|
||||
twod_panner->signal_button_press_event().connect (sigc::mem_fun(*this, &PannerUI::pan_button_event), false);
|
||||
}
|
||||
|
||||
update_pan_sensitive ();
|
||||
twod_panner->reset (npans);
|
||||
twod_panner->reset (nins);
|
||||
if (big_window) {
|
||||
big_window->reset (npans);
|
||||
big_window->reset (nins);
|
||||
}
|
||||
twod_panner->set_size_request (-1, 61);
|
||||
|
||||
/* and finally, add it to the panner frame */
|
||||
|
||||
panning_viewport.add (*twod_panner);
|
||||
pan_vbox.pack_start (*twod_panner, false, false);
|
||||
}
|
||||
|
||||
panning_viewport.show_all ();
|
||||
pan_vbox.show_all ();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -515,13 +345,13 @@ PannerUI::stop_touch (boost::weak_ptr<AutomationControl> wac)
|
|||
}
|
||||
|
||||
bool
|
||||
PannerUI::pan_button_event (GdkEventButton* ev, uint32_t which)
|
||||
PannerUI::pan_button_event (GdkEventButton* ev)
|
||||
{
|
||||
switch (ev->button) {
|
||||
case 1:
|
||||
if (twod_panner && ev->type == GDK_2BUTTON_PRESS) {
|
||||
if (!big_window) {
|
||||
big_window = new Panner2dWindow (_panner, 400, _panner->npanners());
|
||||
big_window = new Panner2dWindow (_panner, 400, _panner->in().n_audio());
|
||||
}
|
||||
big_window->show ();
|
||||
return true;
|
||||
|
@ -533,7 +363,7 @@ PannerUI::pan_button_event (GdkEventButton* ev, uint32_t which)
|
|||
pan_menu = manage (new Menu);
|
||||
pan_menu->set_name ("ArdourContextMenu");
|
||||
}
|
||||
build_pan_menu (which);
|
||||
build_pan_menu ();
|
||||
pan_menu->popup (1, ev->time);
|
||||
return true;
|
||||
break;
|
||||
|
@ -545,21 +375,13 @@ PannerUI::pan_button_event (GdkEventButton* ev, uint32_t which)
|
|||
}
|
||||
|
||||
void
|
||||
PannerUI::build_pan_menu (uint32_t which)
|
||||
PannerUI::build_pan_menu ()
|
||||
{
|
||||
using namespace Menu_Helpers;
|
||||
MenuList& items (pan_menu->items());
|
||||
|
||||
items.clear ();
|
||||
|
||||
items.push_back (CheckMenuElem (_("Mute")));
|
||||
|
||||
/* set state first, connect second */
|
||||
|
||||
(dynamic_cast<CheckMenuItem*> (&items.back()))->set_active (_panner->streampanner(which).muted());
|
||||
(dynamic_cast<CheckMenuItem*> (&items.back()))->signal_toggled().connect
|
||||
(sigc::bind (sigc::mem_fun(*this, &PannerUI::pan_mute), which));
|
||||
|
||||
items.push_back (CheckMenuElem (_("Bypass"), sigc::mem_fun(*this, &PannerUI::pan_bypass_toggle)));
|
||||
bypass_menu_item = static_cast<CheckMenuItem*> (&items.back());
|
||||
|
||||
|
@ -568,16 +390,7 @@ PannerUI::build_pan_menu (uint32_t which)
|
|||
bypass_menu_item->set_active (_panner->bypassed());
|
||||
bypass_menu_item->signal_toggled().connect (sigc::mem_fun(*this, &PannerUI::pan_bypass_toggle));
|
||||
|
||||
items.push_back (MenuElem (_("Reset"), sigc::bind (sigc::mem_fun (*this, &PannerUI::pan_reset), which)));
|
||||
items.push_back (SeparatorElem());
|
||||
items.push_back (MenuElem (_("Reset all"), sigc::mem_fun (*this, &PannerUI::pan_reset_all)));
|
||||
}
|
||||
|
||||
void
|
||||
PannerUI::pan_mute (uint32_t which)
|
||||
{
|
||||
StreamPanner& sp = _panner->streampanner(which);
|
||||
sp.set_muted (!sp.muted());
|
||||
items.push_back (MenuElem (_("Reset"), sigc::mem_fun (*this, &PannerUI::pan_reset)));
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -589,15 +402,9 @@ PannerUI::pan_bypass_toggle ()
|
|||
}
|
||||
|
||||
void
|
||||
PannerUI::pan_reset (uint32_t which)
|
||||
PannerUI::pan_reset ()
|
||||
{
|
||||
_panner->reset_streampanner (which);
|
||||
}
|
||||
|
||||
void
|
||||
PannerUI::pan_reset_all ()
|
||||
{
|
||||
_panner->reset_to_default ();
|
||||
_panner->reset ();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -617,26 +424,14 @@ PannerUI::effective_pan_display ()
|
|||
void
|
||||
PannerUI::update_pan_sensitive ()
|
||||
{
|
||||
bool const sensitive = !(_panner->mono()) && !(_panner->automation_state() & Play);
|
||||
bool const sensitive = !(_panner->is_mono()) && !(_panner->pannable()->automation_state() & Play);
|
||||
|
||||
switch (_panner->nouts()) {
|
||||
case 0:
|
||||
case 1:
|
||||
break;
|
||||
case 2:
|
||||
for (vector<MonoPanner*>::iterator i = pan_bars.begin(); i != pan_bars.end(); ++i) {
|
||||
(*i)->set_sensitive (sensitive);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
if (twod_panner) {
|
||||
twod_panner->set_sensitive (sensitive);
|
||||
}
|
||||
if (big_window) {
|
||||
big_window->set_sensitive (sensitive);
|
||||
}
|
||||
break;
|
||||
}
|
||||
#ifdef PANNER_HACKS
|
||||
pan_vbox.set_sensitive (sensitive);
|
||||
#endif
|
||||
if (big_window) {
|
||||
big_window->set_sensitive (sensitive);
|
||||
}
|
||||
}
|
||||
|
||||
gint
|
||||
|
@ -700,42 +495,31 @@ PannerUI::pan_automation_style_changed ()
|
|||
void
|
||||
PannerUI::pan_automation_state_changed ()
|
||||
{
|
||||
ENSURE_GUI_THREAD (*this, &PannerUI::pan_automation_state_changed)
|
||||
|
||||
bool x;
|
||||
boost::shared_ptr<Pannable> pannable (_panner->pannable());
|
||||
|
||||
switch (_width) {
|
||||
case Wide:
|
||||
pan_automation_state_button.set_label (astate_string(_panner->automation_state()));
|
||||
pan_automation_state_button.set_label (astate_string(pannable->automation_state()));
|
||||
break;
|
||||
case Narrow:
|
||||
pan_automation_state_button.set_label (short_astate_string(_panner->automation_state()));
|
||||
pan_automation_state_button.set_label (short_astate_string(pannable->automation_state()));
|
||||
break;
|
||||
}
|
||||
|
||||
/* when creating a new session, we get to create busses (and
|
||||
sometimes tracks) with no outputs by the time they get
|
||||
here.
|
||||
*/
|
||||
|
||||
if (_panner->empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
x = (_panner->streampanner(0).pan_control()->alist()->automation_state() != Off);
|
||||
|
||||
bool x = (pannable->automation_state() != Off);
|
||||
|
||||
if (pan_automation_state_button.get_active() != x) {
|
||||
ignore_toggle = true;
|
||||
ignore_toggle = true;
|
||||
pan_automation_state_button.set_active (x);
|
||||
ignore_toggle = false;
|
||||
}
|
||||
|
||||
update_pan_sensitive ();
|
||||
|
||||
|
||||
/* start watching automation so that things move */
|
||||
|
||||
|
||||
pan_watching.disconnect();
|
||||
|
||||
|
||||
if (x) {
|
||||
pan_watching = ARDOUR_UI::RapidScreenUpdate.connect (sigc::mem_fun (*this, &PannerUI::effective_pan_display));
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@
|
|||
|
||||
#include <gtkmm/box.h>
|
||||
#include <gtkmm/adjustment.h>
|
||||
#include <gtkmm/viewport.h>
|
||||
#include <gtkmm/eventbox.h>
|
||||
#include <gtkmm/arrow.h>
|
||||
#include <gtkmm/togglebutton.h>
|
||||
|
@ -95,7 +94,7 @@ class PannerUI : public Gtk::HBox, public ARDOUR::SessionHandlePtr
|
|||
bool ignore_toggle;
|
||||
bool in_pan_update;
|
||||
int _current_nouts;
|
||||
int _current_npans;
|
||||
int _current_nins;
|
||||
|
||||
static const int pan_bar_height;
|
||||
|
||||
|
@ -103,13 +102,6 @@ class PannerUI : public Gtk::HBox, public ARDOUR::SessionHandlePtr
|
|||
Panner2dWindow* big_window;
|
||||
|
||||
Gtk::VBox pan_bar_packer;
|
||||
Gtk::Adjustment hAdjustment;
|
||||
Gtk::Adjustment vAdjustment;
|
||||
Gtk::Viewport panning_viewport;
|
||||
Gtk::EventBox panning_up;
|
||||
Gtk::Arrow panning_up_arrow;
|
||||
Gtk::EventBox panning_down;
|
||||
Gtk::Arrow panning_down_arrow;
|
||||
Gtk::VBox pan_vbox;
|
||||
Gtk::VBox poswidth_box;
|
||||
Width _width;
|
||||
|
@ -122,25 +114,15 @@ class PannerUI : public Gtk::HBox, public ARDOUR::SessionHandlePtr
|
|||
void position_adjusted ();
|
||||
void show_position ();
|
||||
|
||||
Gtk::ToggleButton panning_link_button;
|
||||
Gtk::Button panning_link_direction_button;
|
||||
Gtk::HBox panning_link_box;
|
||||
|
||||
bool panning_link_button_press (GdkEventButton*);
|
||||
bool panning_link_button_release (GdkEventButton*);
|
||||
|
||||
Gtk::Menu* pan_astate_menu;
|
||||
Gtk::Menu* pan_astyle_menu;
|
||||
|
||||
Gtk::Button pan_automation_style_button;
|
||||
Gtk::ToggleButton pan_automation_state_button;
|
||||
|
||||
void panning_link_direction_clicked ();
|
||||
|
||||
std::vector<MonoPanner*> pan_bars;
|
||||
|
||||
void pan_value_changed (uint32_t which);
|
||||
void update_pan_linkage ();
|
||||
void update_pan_state ();
|
||||
void build_astate_menu ();
|
||||
void build_astyle_menu ();
|
||||
|
@ -153,14 +135,12 @@ class PannerUI : public Gtk::HBox, public ARDOUR::SessionHandlePtr
|
|||
gint start_pan_touch (GdkEventButton*);
|
||||
gint end_pan_touch (GdkEventButton*);
|
||||
|
||||
bool pan_button_event (GdkEventButton*, uint32_t which);
|
||||
bool pan_button_event (GdkEventButton*);
|
||||
|
||||
Gtk::Menu* pan_menu;
|
||||
Gtk::CheckMenuItem* bypass_menu_item;
|
||||
void build_pan_menu (uint32_t which);
|
||||
void pan_mute (uint32_t which);
|
||||
void pan_reset (uint32_t);
|
||||
void pan_reset_all ();
|
||||
void build_pan_menu ();
|
||||
void pan_reset ();
|
||||
void pan_bypass_toggle ();
|
||||
|
||||
void pan_automation_state_changed();
|
||||
|
|
|
@ -898,7 +898,7 @@ ProcessorBox::weird_plugin_dialog (Plugin& p, Route::ProcessorStreams streams)
|
|||
void
|
||||
ProcessorBox::choose_insert ()
|
||||
{
|
||||
boost::shared_ptr<Processor> processor (new PortInsert (*_session, _route->mute_master()));
|
||||
boost::shared_ptr<Processor> processor (new PortInsert (*_session, _route->pannable(), _route->mute_master()));
|
||||
_route->add_processor (processor, _placement);
|
||||
}
|
||||
|
||||
|
@ -906,7 +906,7 @@ ProcessorBox::choose_insert ()
|
|||
void
|
||||
ProcessorBox::choose_send ()
|
||||
{
|
||||
boost::shared_ptr<Send> send (new Send (*_session, _route->mute_master()));
|
||||
boost::shared_ptr<Send> send (new Send (*_session, _route->pannable(), _route->mute_master()));
|
||||
|
||||
/* make an educated guess at the initial number of outputs for the send */
|
||||
ChanCount outs = (_session->master_out())
|
||||
|
@ -1510,7 +1510,7 @@ ProcessorBox::paste_processor_state (const XMLNodeList& nlist, boost::shared_ptr
|
|||
|
||||
XMLNode n (**niter);
|
||||
Send::make_unique (n, *_session);
|
||||
Send* s = new Send (*_session, _route->mute_master());
|
||||
Send* s = new Send (*_session, _route->pannable(), _route->mute_master());
|
||||
if (s->set_state (n, Stateful::loading_state_version)) {
|
||||
delete s;
|
||||
return;
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "gtkmm2ext/gtk_ui.h"
|
||||
#include "gtkmm2ext/keyboard.h"
|
||||
|
||||
#include "ardour/pannable.h"
|
||||
#include "ardour/panner.h"
|
||||
|
||||
#include "ardour_ui.h"
|
||||
|
@ -53,9 +54,12 @@ static const int top_step = 2;
|
|||
StereoPanner::ColorScheme StereoPanner::colors[3];
|
||||
bool StereoPanner::have_colors = false;
|
||||
|
||||
StereoPanner::StereoPanner (boost::shared_ptr<PBD::Controllable> position, boost::shared_ptr<PBD::Controllable> width)
|
||||
: position_control (position)
|
||||
, width_control (width)
|
||||
using namespace ARDOUR;
|
||||
|
||||
StereoPanner::StereoPanner (boost::shared_ptr<Panner> panner)
|
||||
: _panner (panner)
|
||||
, position_control (_panner->pannable()->pan_azimuth_control)
|
||||
, width_control (_panner->pannable()->pan_width_control)
|
||||
, dragging (false)
|
||||
, dragging_position (false)
|
||||
, dragging_left (false)
|
||||
|
@ -66,8 +70,8 @@ StereoPanner::StereoPanner (boost::shared_ptr<PBD::Controllable> position, boost
|
|||
, detented (false)
|
||||
, drag_data_window (0)
|
||||
, drag_data_label (0)
|
||||
, position_binder (position)
|
||||
, width_binder (width)
|
||||
, position_binder (position_control)
|
||||
, width_binder (width_control)
|
||||
{
|
||||
if (!have_colors) {
|
||||
set_colors ();
|
||||
|
@ -326,8 +330,9 @@ StereoPanner::on_button_press_event (GdkEventButton* ev)
|
|||
/* 2ndary-double click on right, collapse to hard right */
|
||||
width_control->set_value (0);
|
||||
position_control->set_value (1.0);
|
||||
} else {
|
||||
position_control->set_value (max_pos);
|
||||
}
|
||||
position_control->set_value (max_pos);
|
||||
} else {
|
||||
position_control->set_value (0.5);
|
||||
}
|
||||
|
|
|
@ -31,10 +31,14 @@ namespace PBD {
|
|||
class Controllable;
|
||||
}
|
||||
|
||||
namespace ARDOUR {
|
||||
class Panner;
|
||||
}
|
||||
|
||||
class StereoPanner : public Gtk::DrawingArea
|
||||
{
|
||||
public:
|
||||
StereoPanner (boost::shared_ptr<PBD::Controllable> pos, boost::shared_ptr<PBD::Controllable> width);
|
||||
StereoPanner (boost::shared_ptr<ARDOUR::Panner>);
|
||||
~StereoPanner ();
|
||||
|
||||
sigc::signal<void> StartPositionGesture;
|
||||
|
@ -54,6 +58,7 @@ class StereoPanner : public Gtk::DrawingArea
|
|||
bool on_leave_notify_event (GdkEventCrossing* ev);
|
||||
|
||||
private:
|
||||
boost::shared_ptr<ARDOUR::Panner> _panner;
|
||||
boost::shared_ptr<PBD::Controllable> position_control;
|
||||
boost::shared_ptr<PBD::Controllable> width_control;
|
||||
PBD::ScopedConnectionList connections;
|
||||
|
|
|
@ -156,7 +156,6 @@ gtk2_ardour_sources = [
|
|||
'note_player.cc',
|
||||
'option_editor.cc',
|
||||
'opts.cc',
|
||||
'panner.cc',
|
||||
'panner2d.cc',
|
||||
'panner_ui.cc',
|
||||
'piano_roll_header.cc',
|
||||
|
|
|
@ -44,7 +44,6 @@ class Automatable : virtual public Evoral::ControlSet
|
|||
public:
|
||||
Automatable(Session&);
|
||||
Automatable (const Automatable& other);
|
||||
Automatable();
|
||||
|
||||
virtual ~Automatable() {}
|
||||
|
||||
|
@ -93,6 +92,8 @@ public:
|
|||
|
||||
typedef Evoral::ControlSet::Controls Controls;
|
||||
|
||||
static const std::string xml_node_name;
|
||||
|
||||
int set_automation_xml_state (const XMLNode&, Evoral::Parameter default_param);
|
||||
XMLNode& get_automation_xml_state();
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ namespace PBD {
|
|||
extern uint64_t Monitor;
|
||||
extern uint64_t Solo;
|
||||
extern uint64_t AudioPlayback;
|
||||
extern uint64_t Panning;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,7 +30,9 @@ namespace ARDOUR {
|
|||
class BufferSet;
|
||||
class IO;
|
||||
class MuteMaster;
|
||||
class PannerShell;
|
||||
class Panner;
|
||||
class Pannable;
|
||||
|
||||
class Delivery : public IOProcessor
|
||||
{
|
||||
|
@ -52,11 +54,11 @@ public:
|
|||
|
||||
/* Delivery to an existing output */
|
||||
|
||||
Delivery (Session& s, boost::shared_ptr<IO> io, boost::shared_ptr<MuteMaster> mm, const std::string& name, Role);
|
||||
Delivery (Session& s, boost::shared_ptr<IO> io, boost::shared_ptr<Pannable>, boost::shared_ptr<MuteMaster> mm, const std::string& name, Role);
|
||||
|
||||
/* Delivery to a new output owned by this object */
|
||||
|
||||
Delivery (Session& s, boost::shared_ptr<MuteMaster> mm, const std::string& name, Role);
|
||||
Delivery (Session& s, boost::shared_ptr<Pannable>, boost::shared_ptr<MuteMaster> mm, const std::string& name, Role);
|
||||
~Delivery ();
|
||||
|
||||
bool set_name (const std::string& name);
|
||||
|
@ -90,15 +92,14 @@ public:
|
|||
static int disable_panners (void);
|
||||
static int reset_panners (void);
|
||||
|
||||
boost::shared_ptr<Panner> panner() const { return _panner; }
|
||||
boost::shared_ptr<PannerShell> panner_shell() const { return _panshell; }
|
||||
boost::shared_ptr<Panner> panner() const;
|
||||
|
||||
void reset_panner ();
|
||||
void defer_pan_reset ();
|
||||
void allow_pan_reset ();
|
||||
|
||||
uint32_t pans_required() const { return _configured_input.n_audio(); }
|
||||
void start_pan_touch (uint32_t which, double when);
|
||||
void end_pan_touch (uint32_t which, bool mark, double when);
|
||||
|
||||
protected:
|
||||
Role _role;
|
||||
|
@ -108,7 +109,7 @@ public:
|
|||
bool _no_outs_cuz_we_no_monitor;
|
||||
boost::shared_ptr<MuteMaster> _mute_master;
|
||||
bool no_panner_reset;
|
||||
boost::shared_ptr<Panner> _panner;
|
||||
boost::shared_ptr<PannerShell> _panshell;
|
||||
|
||||
static bool panners_legal;
|
||||
static PBD::Signal0<int> PannersLegal;
|
||||
|
|
|
@ -21,6 +21,7 @@ extern const char* const route_templates_dir_name;
|
|||
extern const char* const surfaces_dir_name;
|
||||
extern const char* const user_config_dir_name;
|
||||
extern const char* const stub_dir_name;
|
||||
extern const char* const panner_dir_name;
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ namespace ARDOUR {
|
|||
class InternalSend : public Send
|
||||
{
|
||||
public:
|
||||
InternalSend (Session&, boost::shared_ptr<MuteMaster>, boost::shared_ptr<Route> send_to, Delivery::Role role);
|
||||
InternalSend (Session&, boost::shared_ptr<Pannable>, boost::shared_ptr<MuteMaster>, boost::shared_ptr<Route> send_to, Delivery::Role role);
|
||||
virtual ~InternalSend ();
|
||||
|
||||
std::string display_name() const;
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
#include <boost/shared_ptr.hpp>
|
||||
|
||||
#include "pbd/stateful.h"
|
||||
#include "evoral/Parameter.hpp"
|
||||
|
||||
#include "ardour/automatable.h"
|
||||
|
@ -33,8 +34,9 @@ namespace ARDOUR {
|
|||
|
||||
class Session;
|
||||
class AutomationControl;
|
||||
class Panner;
|
||||
|
||||
struct Pannable : public Automatable, public SessionHandleRef {
|
||||
struct Pannable : public PBD::Stateful, public Automatable, public SessionHandleRef {
|
||||
Pannable (Session& s);
|
||||
|
||||
boost::shared_ptr<AutomationControl> pan_azimuth_control;
|
||||
|
@ -42,6 +44,9 @@ struct Pannable : public Automatable, public SessionHandleRef {
|
|||
boost::shared_ptr<AutomationControl> pan_width_control;
|
||||
boost::shared_ptr<AutomationControl> pan_frontback_control;
|
||||
boost::shared_ptr<AutomationControl> pan_lfe_control;
|
||||
|
||||
boost::shared_ptr<Panner> panner() const { return _panner; }
|
||||
void set_panner(boost::shared_ptr<Panner>);
|
||||
|
||||
Session& session() { return _session; }
|
||||
|
||||
|
@ -66,10 +71,21 @@ struct Pannable : public Automatable, public SessionHandleRef {
|
|||
bool writing() const { return _auto_state == Write; }
|
||||
bool touch_enabled() const { return _auto_state == Touch; }
|
||||
|
||||
XMLNode& get_state ();
|
||||
XMLNode& state (bool full_state);
|
||||
int set_state (const XMLNode&, int version);
|
||||
|
||||
bool has_state() const { return _has_state; }
|
||||
|
||||
protected:
|
||||
boost::shared_ptr<Panner> _panner;
|
||||
AutoState _auto_state;
|
||||
AutoStyle _auto_style;
|
||||
gint _touching;
|
||||
bool _has_state;
|
||||
uint32_t _responding_to_control_auto_state_change;
|
||||
|
||||
void control_auto_state_changed (AutoState);
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright (C) 2004 Paul Davis
|
||||
Copyright (C) 2004-2011 Paul Davis
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
|
@ -26,116 +26,75 @@
|
|||
#include <string>
|
||||
#include <iostream>
|
||||
|
||||
#include "pbd/stateful.h"
|
||||
#include "pbd/controllable.h"
|
||||
#include "pbd/cartesian.h"
|
||||
#include "pbd/signals.h"
|
||||
#include "pbd/stateful.h"
|
||||
|
||||
#include "ardour/types.h"
|
||||
#include "ardour/automation_control.h"
|
||||
#include "ardour/processor.h"
|
||||
#include "ardour/automatable.h"
|
||||
|
||||
namespace ARDOUR {
|
||||
|
||||
class Session;
|
||||
class Panner;
|
||||
class Pannable;
|
||||
class BufferSet;
|
||||
class AudioBuffer;
|
||||
class Speakers;
|
||||
|
||||
class StreamPanner : public PBD::Stateful
|
||||
class Panner : public PBD::Stateful, public PBD::ScopedConnectionList
|
||||
{
|
||||
public:
|
||||
StreamPanner (Panner& p, Evoral::Parameter param);
|
||||
~StreamPanner ();
|
||||
Panner (boost::shared_ptr<Pannable>);
|
||||
~Panner ();
|
||||
|
||||
void set_muted (bool yn);
|
||||
bool muted() const { return _muted; }
|
||||
virtual ChanCount in() const = 0;
|
||||
virtual ChanCount out() const = 0;
|
||||
|
||||
const PBD::AngularVector& get_position() const { return _angles; }
|
||||
const PBD::AngularVector& get_effective_position() const { return _effective_angles; }
|
||||
void set_position (const PBD::AngularVector&, bool link_call = false);
|
||||
void set_diffusion (double);
|
||||
|
||||
void distribute (AudioBuffer &, BufferSet &, gain_t, pframes_t);
|
||||
void distribute_automated (AudioBuffer &, BufferSet &, framepos_t, framepos_t, pframes_t, pan_t **);
|
||||
|
||||
/* the basic StreamPanner API */
|
||||
|
||||
/**
|
||||
* Pan some input samples to a number of output buffers.
|
||||
*
|
||||
* @param src Input buffer.
|
||||
* @param obufs Output buffers (one per panner output).
|
||||
* @param gain_coeff Gain coefficient to apply to output samples.
|
||||
* @param nframes Number of frames in the input.
|
||||
*/
|
||||
virtual void do_distribute (AudioBuffer& src, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes) = 0;
|
||||
virtual void do_distribute_automated (AudioBuffer& src, BufferSet& obufs,
|
||||
framepos_t start, framepos_t end, pframes_t nframes,
|
||||
pan_t** buffers) = 0;
|
||||
|
||||
boost::shared_ptr<AutomationControl> pan_control() { return _control; }
|
||||
|
||||
PBD::Signal0<void> Changed; /* for position or diffusion */
|
||||
PBD::Signal0<void> StateChanged; /* for mute, mono */
|
||||
|
||||
int set_state (const XMLNode&, int version);
|
||||
virtual XMLNode& state (bool full_state) = 0;
|
||||
|
||||
Panner & get_parent() { return parent; }
|
||||
|
||||
/* old school automation loading */
|
||||
virtual int load (std::istream&, std::string path, uint32_t&) = 0;
|
||||
|
||||
struct PanControllable : public AutomationControl {
|
||||
PanControllable (Session& s, std::string name, StreamPanner* p, Evoral::Parameter param)
|
||||
: AutomationControl (s, param,
|
||||
boost::shared_ptr<AutomationList>(new AutomationList(param)), name)
|
||||
, streampanner (p)
|
||||
{ assert (param.type() == PanAutomation); }
|
||||
|
||||
AutomationList* alist() { return (AutomationList*)_list.get(); }
|
||||
StreamPanner* streampanner;
|
||||
|
||||
void set_value (double);
|
||||
double get_value (void) const;
|
||||
double lower () const;
|
||||
};
|
||||
|
||||
protected:
|
||||
friend class Panner;
|
||||
Panner& parent;
|
||||
|
||||
void set_mono (bool);
|
||||
|
||||
PBD::AngularVector _angles;
|
||||
PBD::AngularVector _effective_angles;
|
||||
double _diffusion;
|
||||
|
||||
bool _muted;
|
||||
bool _mono;
|
||||
virtual void configure_io (ARDOUR::ChanCount in, ARDOUR::ChanCount out) {}
|
||||
|
||||
boost::shared_ptr<AutomationControl> _control;
|
||||
/* derived implementations of these methods must indicate
|
||||
whether it is legal for a Controllable to use the
|
||||
value of the argument (post-call) in a call to
|
||||
Controllable::set_value().
|
||||
|
||||
they have a choice of:
|
||||
|
||||
XMLNode& get_state ();
|
||||
* return true, leave argument unchanged
|
||||
* return true, modify argument
|
||||
* return false
|
||||
|
||||
/* Update internal parameters based on this.angles */
|
||||
virtual void update () = 0;
|
||||
};
|
||||
*/
|
||||
|
||||
class BaseStereoPanner : public StreamPanner
|
||||
{
|
||||
public:
|
||||
BaseStereoPanner (Panner&, Evoral::Parameter param);
|
||||
~BaseStereoPanner ();
|
||||
virtual bool clamp_position (double&) { return true; }
|
||||
virtual bool clamp_width (double&) { return true; }
|
||||
virtual bool clamp_elevation (double&) { return true; }
|
||||
|
||||
/* this class just leaves the pan law itself to be defined
|
||||
by the update(), do_distribute_automated()
|
||||
methods. derived classes also need a factory method
|
||||
and a type name. See EqualPowerStereoPanner as an example.
|
||||
*/
|
||||
virtual void set_position (double) { }
|
||||
virtual void set_width (double) { }
|
||||
virtual void set_elevation (double) { }
|
||||
|
||||
virtual double position () const { return 0.0; }
|
||||
virtual double width () const { return 0.0; }
|
||||
virtual double elevation () const { return 0.0; }
|
||||
|
||||
void do_distribute (AudioBuffer& src, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes);
|
||||
virtual void reset() {}
|
||||
|
||||
virtual bool bypassed() const { return _bypassed; }
|
||||
virtual void set_bypassed (bool yn);
|
||||
|
||||
virtual bool is_mono () const { return _mono; }
|
||||
virtual void set_mono (bool);
|
||||
|
||||
void set_automation_state (AutoState);
|
||||
AutoState automation_state() const;
|
||||
void set_automation_style (AutoStyle);
|
||||
AutoStyle automation_style() const;
|
||||
|
||||
virtual std::set<Evoral::Parameter> what_can_be_automated() const;
|
||||
virtual std::string describe_parameter (Evoral::Parameter);
|
||||
|
||||
bool touching() const;
|
||||
|
||||
static double azimuth_to_lr_fract (double azi) {
|
||||
/* 180.0 degrees=> left => 0.0 */
|
||||
|
@ -159,176 +118,70 @@ class BaseStereoPanner : public StreamPanner
|
|||
return rint (180.0 - (fract * 180.0));
|
||||
}
|
||||
|
||||
/* old school automation loading */
|
||||
/**
|
||||
* Pan some input buffers to a number of output buffers.
|
||||
*
|
||||
* @param ibufs Input buffers (one per panner input)
|
||||
* @param obufs Output buffers (one per panner output).
|
||||
* @param gain_coeff fixed, additional gain coefficient to apply to output samples.
|
||||
* @param nframes Number of frames in the input.
|
||||
*
|
||||
* Derived panners can choose to implement these if they need to gain more control over the panning algorithm.
|
||||
* the default is to (1) check if _mono is true, and if so, just deliver .. (2) otherwise, call
|
||||
* distribute_one() or distribute_one_automated() on each input buffer to deliver it to each output
|
||||
* buffer.
|
||||
*
|
||||
* If a panner does not need to override this default behaviour, it can just implement
|
||||
* distribute_one() and distribute_one_automated() (below).
|
||||
*/
|
||||
virtual void distribute (BufferSet& ibufs, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes);
|
||||
virtual void distribute_automated (BufferSet& ibufs, BufferSet& obufs,
|
||||
framepos_t start, framepos_t end, pframes_t nframes,
|
||||
pan_t** buffers);
|
||||
|
||||
int load (std::istream&, std::string path, uint32_t&);
|
||||
PBD::Signal0<void> Changed; /* for positional info */
|
||||
PBD::Signal0<void> StateChanged; /* for mute, mono */
|
||||
|
||||
protected:
|
||||
float left;
|
||||
float right;
|
||||
float desired_left;
|
||||
float desired_right;
|
||||
float left_interp;
|
||||
float right_interp;
|
||||
};
|
||||
int set_state (const XMLNode&, int version);
|
||||
virtual XMLNode& state (bool full_state) = 0;
|
||||
|
||||
class EqualPowerStereoPanner : public BaseStereoPanner
|
||||
{
|
||||
public:
|
||||
EqualPowerStereoPanner (Panner&, Evoral::Parameter param);
|
||||
~EqualPowerStereoPanner ();
|
||||
boost::shared_ptr<Pannable> pannable() const { return _pannable; }
|
||||
|
||||
void do_distribute_automated (AudioBuffer& src, BufferSet& obufs,
|
||||
framepos_t start, framepos_t end, pframes_t nframes,
|
||||
pan_t** buffers);
|
||||
|
||||
void get_current_coefficients (pan_t*) const;
|
||||
void get_desired_coefficients (pan_t*) const;
|
||||
|
||||
static StreamPanner* factory (Panner&, Evoral::Parameter param, Speakers&);
|
||||
static std::string name;
|
||||
|
||||
XMLNode& state (bool full_state);
|
||||
XMLNode& get_state (void);
|
||||
int set_state (const XMLNode&, int version);
|
||||
|
||||
private:
|
||||
void update ();
|
||||
};
|
||||
|
||||
/** Class to pan from some number of inputs to some number of outputs.
|
||||
* This class has a number of StreamPanners, one for each input.
|
||||
*/
|
||||
class Panner : public SessionObject, public Automatable
|
||||
{
|
||||
public:
|
||||
struct Output {
|
||||
PBD::AngularVector position;
|
||||
pan_t current_pan;
|
||||
pan_t desired_pan;
|
||||
|
||||
Output (const PBD::AngularVector& a)
|
||||
: position (a), current_pan (0), desired_pan (0) {}
|
||||
|
||||
};
|
||||
|
||||
Panner (std::string name, Session&);
|
||||
virtual ~Panner ();
|
||||
|
||||
void clear_panners ();
|
||||
bool empty() const { return _streampanners.empty(); }
|
||||
|
||||
void set_automation_state (AutoState);
|
||||
AutoState automation_state() const;
|
||||
void set_automation_style (AutoStyle);
|
||||
AutoStyle automation_style() const;
|
||||
bool touching() const;
|
||||
|
||||
std::string describe_parameter (Evoral::Parameter param);
|
||||
|
||||
bool can_support_io_configuration (const ChanCount& /*in*/, ChanCount& /*out*/) const { return true; };
|
||||
|
||||
/// The fundamental Panner function
|
||||
void run (BufferSet& src, BufferSet& dest, framepos_t start_frame, framepos_t end_frames, pframes_t nframes);
|
||||
|
||||
bool bypassed() const { return _bypassed; }
|
||||
void set_bypassed (bool yn);
|
||||
bool mono () const { return _mono; }
|
||||
void set_mono (bool);
|
||||
|
||||
StreamPanner* add ();
|
||||
void remove (uint32_t which);
|
||||
void reset (uint32_t noutputs, uint32_t npans);
|
||||
void reset_streampanner (uint32_t which_panner);
|
||||
void reset_to_default ();
|
||||
|
||||
XMLNode& get_state (void);
|
||||
XMLNode& state (bool full);
|
||||
int set_state (const XMLNode&, int version);
|
||||
//virtual std::string describe_parameter (Evoral::Parameter);
|
||||
//virtual std::string value_as_string (Evoral::Parameter, double val);
|
||||
|
||||
static bool equivalent (pan_t a, pan_t b) {
|
||||
return fabsf (a - b) < 0.002; // about 1 degree of arc for a stereo panner
|
||||
}
|
||||
|
||||
static bool equivalent (const PBD::AngularVector& a, const PBD::AngularVector& b) {
|
||||
/* XXX azimuth only, at present */
|
||||
return fabs (a.azi - b.azi) < 1.0;
|
||||
}
|
||||
|
||||
void move_output (uint32_t, float x, float y);
|
||||
uint32_t nouts() const { return outputs.size(); }
|
||||
Output& output (uint32_t n) { return outputs[n]; }
|
||||
protected:
|
||||
boost::shared_ptr<Pannable> _pannable;
|
||||
bool _mono;
|
||||
bool _bypassed;
|
||||
|
||||
enum LinkDirection {
|
||||
SameDirection,
|
||||
OppositeDirection
|
||||
};
|
||||
XMLNode& get_state ();
|
||||
|
||||
LinkDirection link_direction() const { return _link_direction; }
|
||||
void set_link_direction (LinkDirection);
|
||||
|
||||
bool linked() const { return _linked; }
|
||||
void set_linked (bool yn);
|
||||
|
||||
StreamPanner &streampanner( uint32_t n ) const { assert( n < _streampanners.size() ); return *_streampanners[n]; }
|
||||
uint32_t npanners() const { return _streampanners.size(); }
|
||||
|
||||
PBD::Signal0<void> Changed; /* panner and/or outputs count changed */
|
||||
PBD::Signal0<void> LinkStateChanged;
|
||||
PBD::Signal0<void> StateChanged; /* for bypass */
|
||||
|
||||
/* only StreamPanner should call these */
|
||||
|
||||
void set_position (const PBD::AngularVector&, StreamPanner& orig);
|
||||
|
||||
/* old school automation */
|
||||
|
||||
int load ();
|
||||
|
||||
boost::shared_ptr<AutomationControl> pan_control (int id, uint32_t chan=0) {
|
||||
return automation_control (Evoral::Parameter (PanAutomation, chan, id));
|
||||
}
|
||||
|
||||
boost::shared_ptr<const AutomationControl> pan_control (int id, uint32_t chan=0) const {
|
||||
return automation_control (Evoral::Parameter (PanAutomation, chan, id));
|
||||
}
|
||||
|
||||
boost::shared_ptr<AutomationControl> direction_control () {
|
||||
return automation_control (Evoral::Parameter (PanAutomation, 0, 100));
|
||||
}
|
||||
|
||||
boost::shared_ptr<AutomationControl> width_control () {
|
||||
return automation_control (Evoral::Parameter (PanAutomation, 0, 200));
|
||||
}
|
||||
|
||||
void set_stereo_position (double);
|
||||
void set_stereo_width (double);
|
||||
bool set_stereo_pan (double pos, double width);
|
||||
|
||||
static std::string value_as_string (double);
|
||||
|
||||
private:
|
||||
/* disallow copy construction */
|
||||
Panner (Panner const &);
|
||||
|
||||
void distribute_no_automation(BufferSet& src, BufferSet& dest, pframes_t nframes, gain_t gain_coeff);
|
||||
std::vector<StreamPanner*> _streampanners; ///< one StreamPanner per input
|
||||
std::vector<Output> outputs;
|
||||
uint32_t current_outs;
|
||||
bool _linked;
|
||||
bool _bypassed;
|
||||
bool _mono;
|
||||
LinkDirection _link_direction;
|
||||
|
||||
static float current_automation_version_number;
|
||||
|
||||
void setup_speakers (uint32_t nouts);
|
||||
void setup_meta_controls ();
|
||||
|
||||
/* old school automation handling */
|
||||
|
||||
std::string automation_path;
|
||||
virtual void distribute_one (AudioBuffer&, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t which) = 0;
|
||||
virtual void distribute_one_automated (AudioBuffer&, BufferSet& obufs,
|
||||
framepos_t start, framepos_t end, pframes_t nframes,
|
||||
pan_t** buffers, uint32_t which) = 0;
|
||||
};
|
||||
|
||||
} // namespace ARDOUR
|
||||
} // namespace
|
||||
|
||||
#endif /*__ardour_panner_h__ */
|
||||
extern "C" {
|
||||
struct PanPluginDescriptor {
|
||||
std::string name;
|
||||
int32_t in;
|
||||
int32_t out;
|
||||
ARDOUR::Panner* (*factory)(boost::shared_ptr<ARDOUR::Pannable>, ARDOUR::Speakers&);
|
||||
};
|
||||
}
|
||||
|
||||
#endif /* __ardour_panner_h__ */
|
||||
|
|
|
@ -37,13 +37,14 @@ class Session;
|
|||
class IO;
|
||||
class Delivery;
|
||||
class MuteMaster;
|
||||
class Pannable;
|
||||
|
||||
/** Port inserts: send output to a Jack port, pick up input at a Jack port
|
||||
*/
|
||||
class PortInsert : public IOProcessor
|
||||
{
|
||||
public:
|
||||
PortInsert (Session&, boost::shared_ptr<MuteMaster> mm);
|
||||
PortInsert (Session&, boost::shared_ptr<Pannable>, boost::shared_ptr<MuteMaster> mm);
|
||||
~PortInsert ();
|
||||
|
||||
XMLNode& state(bool full);
|
||||
|
|
|
@ -54,11 +54,13 @@ class Amp;
|
|||
class Delivery;
|
||||
class IOProcessor;
|
||||
class Panner;
|
||||
class PannerShell;
|
||||
class Processor;
|
||||
class RouteGroup;
|
||||
class Send;
|
||||
class InternalReturn;
|
||||
class MonitorProcessor;
|
||||
class Pannable;
|
||||
class CapturingProcessor;
|
||||
|
||||
class Route : public SessionObject, public Automatable, public RouteGroupMember, public GraphNode
|
||||
|
@ -365,8 +367,10 @@ class Route : public SessionObject, public Automatable, public RouteGroupMember,
|
|||
here.
|
||||
*/
|
||||
|
||||
boost::shared_ptr<Panner> panner() const;
|
||||
boost::shared_ptr<Panner> panner() const; /* may return null */
|
||||
boost::shared_ptr<PannerShell> panner_shell() const;
|
||||
boost::shared_ptr<AutomationControl> gain_control() const;
|
||||
boost::shared_ptr<Pannable> pannable() const;
|
||||
|
||||
void automation_snapshot (framepos_t now, bool force=false);
|
||||
void protect_automation ();
|
||||
|
@ -423,6 +427,7 @@ class Route : public SessionObject, public Automatable, public RouteGroupMember,
|
|||
boost::shared_ptr<Delivery> _monitor_send;
|
||||
boost::shared_ptr<InternalReturn> _intreturn;
|
||||
boost::shared_ptr<MonitorProcessor> _monitor_control;
|
||||
boost::shared_ptr<Pannable> _pannable;
|
||||
|
||||
Flag _flags;
|
||||
int _pending_declick;
|
||||
|
|
|
@ -36,7 +36,7 @@ class Amp;
|
|||
class Send : public Delivery
|
||||
{
|
||||
public:
|
||||
Send (Session&, boost::shared_ptr<MuteMaster>, Delivery::Role r = Delivery::Send);
|
||||
Send (Session&, boost::shared_ptr<Pannable> pannable, boost::shared_ptr<MuteMaster>, Delivery::Role r = Delivery::Send);
|
||||
virtual ~Send ();
|
||||
|
||||
uint32_t bit_slot() const { return _bitslot; }
|
||||
|
|
|
@ -23,12 +23,15 @@
|
|||
#include <iostream>
|
||||
|
||||
#include <pbd/signals.h>
|
||||
#include <pbd/stateful.h>
|
||||
|
||||
#include "ardour/speaker.h"
|
||||
|
||||
class XMLNode;
|
||||
|
||||
namespace ARDOUR {
|
||||
|
||||
class Speakers {
|
||||
class Speakers : public PBD::Stateful {
|
||||
public:
|
||||
Speakers ();
|
||||
virtual ~Speakers ();
|
||||
|
@ -38,12 +41,17 @@ public:
|
|||
virtual void move_speaker (int id, const PBD::AngularVector& new_position);
|
||||
virtual void clear_speakers ();
|
||||
|
||||
void setup_default_speakers (uint32_t nspeakers);
|
||||
|
||||
std::vector<Speaker>& speakers() { return _speakers; }
|
||||
|
||||
void dump_speakers (std::ostream&);
|
||||
|
||||
PBD::Signal0<void> Changed;
|
||||
XMLNode& get_state ();
|
||||
int set_state (const XMLNode&, int version);
|
||||
|
||||
PBD::Signal0<void> Changed;
|
||||
|
||||
protected:
|
||||
std::vector<Speaker> _speakers;
|
||||
|
||||
|
|
|
@ -122,24 +122,27 @@ namespace ARDOUR {
|
|||
InsertMergeExtend // extend new (or old) to the range of old+new
|
||||
};
|
||||
|
||||
/** See parameter.h
|
||||
* XXX: I don't think/hope these hex values matter anymore.
|
||||
/** See evoral/Parameter.hpp
|
||||
*/
|
||||
enum AutomationType {
|
||||
NullAutomation = 0x0,
|
||||
GainAutomation = 0x1,
|
||||
PanAutomation = 0x2,
|
||||
PluginAutomation = 0x4,
|
||||
SoloAutomation = 0x8,
|
||||
MuteAutomation = 0x10,
|
||||
MidiCCAutomation = 0x20,
|
||||
MidiPgmChangeAutomation = 0x21,
|
||||
MidiPitchBenderAutomation = 0x22,
|
||||
MidiChannelPressureAutomation = 0x23,
|
||||
MidiSystemExclusiveAutomation = 0x24,
|
||||
FadeInAutomation = 0x40,
|
||||
FadeOutAutomation = 0x80,
|
||||
EnvelopeAutomation = 0x100
|
||||
NullAutomation,
|
||||
GainAutomation,
|
||||
PanAzimuthAutomation,
|
||||
PanElevationAutomation,
|
||||
PanWidthAutomation,
|
||||
PanFrontBackAutomation,
|
||||
PanLFEAutomation,
|
||||
PluginAutomation,
|
||||
SoloAutomation,
|
||||
MuteAutomation,
|
||||
MidiCCAutomation,
|
||||
MidiPgmChangeAutomation,
|
||||
MidiPitchBenderAutomation,
|
||||
MidiChannelPressureAutomation,
|
||||
MidiSystemExclusiveAutomation,
|
||||
FadeInAutomation,
|
||||
FadeOutAutomation,
|
||||
EnvelopeAutomation
|
||||
};
|
||||
|
||||
enum AutoState {
|
||||
|
|
|
@ -1,70 +0,0 @@
|
|||
/*
|
||||
Copyright (C) 2010 Paul Davis
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#ifndef __libardour_vbap_h__
|
||||
#define __libardour_vbap_h__
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
||||
#include "ardour/panner.h"
|
||||
#include "ardour/vbap_speakers.h"
|
||||
|
||||
namespace ARDOUR {
|
||||
|
||||
class Speakers;
|
||||
|
||||
class VBAPanner : public StreamPanner {
|
||||
public:
|
||||
VBAPanner (Panner& parent, Evoral::Parameter param, Speakers& s);
|
||||
~VBAPanner ();
|
||||
|
||||
static StreamPanner* factory (Panner& parent, Evoral::Parameter param, Speakers& s);
|
||||
static std::string name;
|
||||
|
||||
void do_distribute (AudioBuffer&, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes);
|
||||
void do_distribute_automated (AudioBuffer& src, BufferSet& obufs,
|
||||
framepos_t start, framepos_t end, pframes_t nframes, pan_t** buffers);
|
||||
|
||||
void set_azimuth_elevation (double azimuth, double elevation);
|
||||
|
||||
XMLNode& state (bool full_state);
|
||||
XMLNode& get_state ();
|
||||
int set_state (const XMLNode&, int version);
|
||||
|
||||
/* there never was any old-school automation */
|
||||
|
||||
int load (std::istream&, std::string path, uint32_t&) { return 0; }
|
||||
|
||||
private:
|
||||
bool _dirty;
|
||||
double gains[3];
|
||||
double desired_gains[3];
|
||||
int outputs[3];
|
||||
int desired_outputs[3];
|
||||
|
||||
VBAPSpeakers& _speakers;
|
||||
|
||||
void compute_gains (double g[3], int ls[3], int azi, int ele);
|
||||
|
||||
void update ();
|
||||
};
|
||||
|
||||
} /* namespace */
|
||||
|
||||
#endif /* __libardour_vbap_h__ */
|
|
@ -1,107 +0,0 @@
|
|||
/*
|
||||
Copyright (C) 2010 Paul Davis
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#ifndef __libardour_vbap_speakers_h__
|
||||
#define __libardour_vbap_speakers_h__
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/utility.hpp>
|
||||
|
||||
#include <pbd/signals.h>
|
||||
|
||||
#include "ardour/panner.h"
|
||||
#include "ardour/speakers.h"
|
||||
|
||||
namespace ARDOUR {
|
||||
|
||||
class Speakers;
|
||||
|
||||
class VBAPSpeakers : public boost::noncopyable {
|
||||
public:
|
||||
typedef std::vector<double> dvector;
|
||||
|
||||
const dvector matrix (int tuple) const { return _matrices[tuple]; }
|
||||
int speaker_for_tuple (int tuple, int which) const { return _speaker_tuples[tuple][which]; }
|
||||
|
||||
int n_tuples () const { return _matrices.size(); }
|
||||
int dimension() const { return _dimension; }
|
||||
|
||||
static VBAPSpeakers& instance (Speakers&);
|
||||
|
||||
~VBAPSpeakers ();
|
||||
|
||||
private:
|
||||
static VBAPSpeakers* _instance;
|
||||
static const double MIN_VOL_P_SIDE_LGTH = 0.01;
|
||||
int _dimension;
|
||||
std::vector<Speaker>& _speakers;
|
||||
PBD::ScopedConnection speaker_connection;
|
||||
|
||||
VBAPSpeakers (Speakers&);
|
||||
|
||||
struct azimuth_sorter {
|
||||
bool operator() (const Speaker& s1, const Speaker& s2) {
|
||||
return s1.angles().azi < s2.angles().azi;
|
||||
}
|
||||
};
|
||||
|
||||
struct twoDmatrix : public dvector {
|
||||
twoDmatrix() : dvector (4, 0.0) {}
|
||||
};
|
||||
|
||||
struct threeDmatrix : public dvector {
|
||||
threeDmatrix() : dvector (9, 0.0) {}
|
||||
};
|
||||
|
||||
struct tmatrix : public dvector {
|
||||
tmatrix() : dvector (3, 0.0) {}
|
||||
};
|
||||
|
||||
std::vector<dvector> _matrices; /* holds matrices for a given speaker combinations */
|
||||
std::vector<tmatrix> _speaker_tuples; /* holds speakers IDs for a given combination */
|
||||
|
||||
/* A struct for all loudspeakers */
|
||||
struct ls_triplet_chain {
|
||||
int ls_nos[3];
|
||||
float inv_mx[9];
|
||||
struct ls_triplet_chain *next;
|
||||
};
|
||||
|
||||
static float vec_angle(PBD::CartesianVector v1, PBD::CartesianVector v2);
|
||||
static float vec_length(PBD::CartesianVector v1);
|
||||
static float vec_prod(PBD::CartesianVector v1, PBD::CartesianVector v2);
|
||||
static float vol_p_side_lgth(int i, int j,int k, const std::vector<Speaker>&);
|
||||
static void cross_prod(PBD::CartesianVector v1,PBD::CartesianVector v2, PBD::CartesianVector *res);
|
||||
|
||||
void update ();
|
||||
int any_ls_inside_triplet (int a, int b, int c);
|
||||
void add_ldsp_triplet (int i, int j, int k, struct ls_triplet_chain **ls_triplets);
|
||||
int lines_intersect (int i,int j,int k,int l);
|
||||
void calculate_3x3_matrixes (struct ls_triplet_chain *ls_triplets);
|
||||
void choose_speaker_triplets (struct ls_triplet_chain **ls_triplets);
|
||||
void choose_speaker_pairs ();
|
||||
void sort_2D_lss (int* sorted_lss);
|
||||
int calc_2D_inv_tmatrix (double azi1,double azi2, double* inv_mat);
|
||||
|
||||
};
|
||||
|
||||
} /* namespace */
|
||||
|
||||
#endif /* __libardour_vbap_speakers_h__ */
|
|
@ -99,7 +99,9 @@ AudioEngine::AudioEngine (string client_name, string session_uuid)
|
|||
Evoral::Parameter p(NullAutomation);
|
||||
p = EventTypeMap::instance().new_parameter(NullAutomation);
|
||||
p = EventTypeMap::instance().new_parameter(GainAutomation);
|
||||
p = EventTypeMap::instance().new_parameter(PanAutomation);
|
||||
p = EventTypeMap::instance().new_parameter(PanAzimuthAutomation);
|
||||
p = EventTypeMap::instance().new_parameter(PanElevationAutomation);
|
||||
p = EventTypeMap::instance().new_parameter(PanWidthAutomation);
|
||||
p = EventTypeMap::instance().new_parameter(PluginAutomation);
|
||||
p = EventTypeMap::instance().new_parameter(SoloAutomation);
|
||||
p = EventTypeMap::instance().new_parameter(MuteAutomation);
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
#include "ardour/auditioner.h"
|
||||
#include "ardour/audioplaylist.h"
|
||||
#include "ardour/audio_port.h"
|
||||
#include "ardour/panner_shell.h"
|
||||
#include "ardour/panner.h"
|
||||
#include "ardour/data_type.h"
|
||||
#include "ardour/region_factory.h"
|
||||
|
@ -139,7 +140,8 @@ Auditioner::audition_current_playlist ()
|
|||
|
||||
/* force a panner reset now that we have all channels */
|
||||
|
||||
_main_outs->panner()->reset (n_outputs().n_audio(), _diskstream->n_channels().n_audio());
|
||||
_main_outs->panner_shell()->configure_io (ChanCount (DataType::AUDIO, _diskstream->n_channels().n_audio()),
|
||||
ChanCount (DataType::AUDIO, n_outputs().n_audio()));
|
||||
|
||||
g_atomic_int_set (&_auditioning, 1);
|
||||
}
|
||||
|
|
|
@ -35,7 +35,9 @@
|
|||
#include "ardour/amp.h"
|
||||
#include "ardour/event_type_map.h"
|
||||
#include "ardour/midi_track.h"
|
||||
#include "ardour/pannable.h"
|
||||
#include "ardour/panner.h"
|
||||
#include "ardour/pan_controllable.h"
|
||||
#include "ardour/plugin_insert.h"
|
||||
#include "ardour/session.h"
|
||||
|
||||
|
@ -46,6 +48,7 @@ using namespace ARDOUR;
|
|||
using namespace PBD;
|
||||
|
||||
framecnt_t Automatable::_automation_interval = 0;
|
||||
const string Automatable::xml_node_name = X_("Automation");
|
||||
|
||||
Automatable::Automatable(Session& session)
|
||||
: _a_session(session)
|
||||
|
@ -182,9 +185,6 @@ Automatable::describe_parameter (Evoral::Parameter param)
|
|||
|
||||
if (param == Evoral::Parameter(GainAutomation)) {
|
||||
return _("Fader");
|
||||
} else if (param.type() == PanAutomation) {
|
||||
/* ID's are zero-based, present them as 1-based */
|
||||
return (string_compose(_("Pan %1"), param.id() + 1));
|
||||
} else if (param.type() == MidiCCAutomation) {
|
||||
return string_compose("%1: %2 [%3]",
|
||||
param.id() + 1, midi_name(param.id()), int(param.channel()) + 1);
|
||||
|
@ -255,19 +255,21 @@ Automatable::set_automation_xml_state (const XMLNode& node, Evoral::Parameter le
|
|||
continue;
|
||||
}
|
||||
|
||||
boost::shared_ptr<AutomationList> al (new AutomationList(**niter, param));
|
||||
|
||||
|
||||
if (!id_prop) {
|
||||
warning << "AutomationList node without automation-id property, "
|
||||
<< "using default: " << EventTypeMap::instance().to_symbol(legacy_param) << endmsg;
|
||||
}
|
||||
|
||||
boost::shared_ptr<Evoral::Control> existing = control(param);
|
||||
boost::shared_ptr<AutomationControl> existing = automation_control (param);
|
||||
|
||||
if (existing) {
|
||||
existing->set_list(al);
|
||||
existing->alist()->set_state (**niter, 3000);
|
||||
} else {
|
||||
boost::shared_ptr<Evoral::Control> newcontrol = control_factory(param);
|
||||
add_control(newcontrol);
|
||||
boost::shared_ptr<Evoral::Control> newcontrol = control_factory(param);
|
||||
add_control (newcontrol);
|
||||
boost::shared_ptr<AutomationList> al (new AutomationList(**niter, param));
|
||||
newcontrol->set_list(al);
|
||||
}
|
||||
|
||||
|
@ -285,7 +287,7 @@ XMLNode&
|
|||
Automatable::get_automation_xml_state ()
|
||||
{
|
||||
Glib::Mutex::Lock lm (control_lock());
|
||||
XMLNode* node = new XMLNode (X_("Automation"));
|
||||
XMLNode* node = new XMLNode (Automatable::xml_node_name);
|
||||
|
||||
if (controls().empty()) {
|
||||
return *node;
|
||||
|
@ -455,13 +457,12 @@ Automatable::control_factory(const Evoral::Parameter& param)
|
|||
} else {
|
||||
warning << "GainAutomation for non-Amp" << endl;
|
||||
}
|
||||
} else if (param.type() == PanAutomation) {
|
||||
Panner* panner = dynamic_cast<Panner*>(this);
|
||||
if (panner) {
|
||||
StreamPanner& sp (panner->streampanner (param.channel()));
|
||||
control = new StreamPanner::PanControllable (_a_session, X_("direction"), &sp, param);
|
||||
} else if (param.type() == PanAzimuthAutomation || param.type() == PanWidthAutomation || param.type() == PanElevationAutomation) {
|
||||
Pannable* pannable = dynamic_cast<Pannable*>(this);
|
||||
if (pannable) {
|
||||
control = new PanControllable (_a_session, pannable->describe_parameter (param), pannable, param);
|
||||
} else {
|
||||
warning << "PanAutomation for non-Panner" << endl;
|
||||
warning << "PanAutomation for non-Pannable" << endl;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -124,7 +124,9 @@ AutomationList::create_curve_if_necessary()
|
|||
{
|
||||
switch (_parameter.type()) {
|
||||
case GainAutomation:
|
||||
case PanAutomation:
|
||||
case PanAzimuthAutomation:
|
||||
case PanElevationAutomation:
|
||||
case PanWidthAutomation:
|
||||
case FadeInAutomation:
|
||||
case FadeOutAutomation:
|
||||
case EnvelopeAutomation:
|
||||
|
@ -184,7 +186,6 @@ AutomationList::set_automation_state (AutoState s)
|
|||
Glib::Mutex::Lock lm (ControlList::_lock);
|
||||
nascent.push_back (new NascentInfo (false));
|
||||
}
|
||||
|
||||
automation_state_changed (s); /* EMIT SIGNAL */
|
||||
}
|
||||
}
|
||||
|
|
|
@ -45,4 +45,5 @@ uint64_t PBD::DEBUG::MidiClock = PBD::new_debug_bit ("midiclock");
|
|||
uint64_t PBD::DEBUG::Monitor = PBD::new_debug_bit ("monitor");
|
||||
uint64_t PBD::DEBUG::Solo = PBD::new_debug_bit ("solo");
|
||||
uint64_t PBD::DEBUG::AudioPlayback = PBD::new_debug_bit ("audioplayback");
|
||||
uint64_t PBD::DEBUG::Panning = PBD::new_debug_bit ("panning");
|
||||
|
||||
|
|
|
@ -33,6 +33,8 @@
|
|||
#include "ardour/meter.h"
|
||||
#include "ardour/mute_master.h"
|
||||
#include "ardour/panner.h"
|
||||
#include "ardour/panner_shell.h"
|
||||
#include "ardour/pannable.h"
|
||||
#include "ardour/port.h"
|
||||
#include "ardour/session.h"
|
||||
#include "ardour/audioengine.h"
|
||||
|
@ -49,7 +51,8 @@ bool Delivery::panners_legal = false;
|
|||
|
||||
/* deliver to an existing IO object */
|
||||
|
||||
Delivery::Delivery (Session& s, boost::shared_ptr<IO> io, boost::shared_ptr<MuteMaster> mm, const string& name, Role r)
|
||||
Delivery::Delivery (Session& s, boost::shared_ptr<IO> io, boost::shared_ptr<Pannable> pannable,
|
||||
boost::shared_ptr<MuteMaster> mm, const string& name, Role r)
|
||||
: IOProcessor(s, boost::shared_ptr<IO>(), (role_requires_output_ports (r) ? io : boost::shared_ptr<IO>()), name)
|
||||
, _role (r)
|
||||
, _output_buffers (new BufferSet())
|
||||
|
@ -59,7 +62,7 @@ Delivery::Delivery (Session& s, boost::shared_ptr<IO> io, boost::shared_ptr<Mute
|
|||
, _mute_master (mm)
|
||||
, no_panner_reset (false)
|
||||
{
|
||||
_panner = boost::shared_ptr<Panner>(new Panner (_name, _session));
|
||||
_panshell = boost::shared_ptr<PannerShell>(new PannerShell (_name, _session, pannable));
|
||||
_display_to_user = false;
|
||||
|
||||
if (_output) {
|
||||
|
@ -71,7 +74,7 @@ Delivery::Delivery (Session& s, boost::shared_ptr<IO> io, boost::shared_ptr<Mute
|
|||
|
||||
/* deliver to a new IO object */
|
||||
|
||||
Delivery::Delivery (Session& s, boost::shared_ptr<MuteMaster> mm, const string& name, Role r)
|
||||
Delivery::Delivery (Session& s, boost::shared_ptr<Pannable> pannable, boost::shared_ptr<MuteMaster> mm, const string& name, Role r)
|
||||
: IOProcessor(s, false, (role_requires_output_ports (r) ? true : false), name)
|
||||
, _role (r)
|
||||
, _output_buffers (new BufferSet())
|
||||
|
@ -81,7 +84,7 @@ Delivery::Delivery (Session& s, boost::shared_ptr<MuteMaster> mm, const string&
|
|||
, _mute_master (mm)
|
||||
, no_panner_reset (false)
|
||||
{
|
||||
_panner = boost::shared_ptr<Panner>(new Panner (_name, _session));
|
||||
_panshell = boost::shared_ptr<PannerShell>(new PannerShell (_name, _session, pannable));
|
||||
_display_to_user = false;
|
||||
|
||||
if (_output) {
|
||||
|
@ -228,6 +231,8 @@ Delivery::configure_io (ChanCount in, ChanCount out)
|
|||
void
|
||||
Delivery::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame, pframes_t nframes, bool result_required)
|
||||
{
|
||||
boost::shared_ptr<Panner> panner;
|
||||
|
||||
assert (_output);
|
||||
|
||||
PortSet& ports (_output->ports());
|
||||
|
@ -279,11 +284,13 @@ Delivery::run (BufferSet& bufs, framepos_t start_frame, framepos_t end_frame, pf
|
|||
Amp::apply_simple_gain (bufs, nframes, tgain);
|
||||
}
|
||||
|
||||
if (_panner && _panner->npanners() && !_panner->bypassed()) {
|
||||
panner = _panshell->panner();
|
||||
|
||||
if (panner && !panner->bypassed()) {
|
||||
|
||||
// Use the panner to distribute audio to output port buffers
|
||||
|
||||
_panner->run (bufs, output_buffers(), start_frame, end_frame, nframes);
|
||||
_panshell->run (bufs, output_buffers(), start_frame, end_frame, nframes);
|
||||
|
||||
if (result_required) {
|
||||
bufs.read_from (output_buffers (), nframes);
|
||||
|
@ -322,7 +329,7 @@ Delivery::state (bool full_state)
|
|||
}
|
||||
|
||||
node.add_property("role", enum_2_string(_role));
|
||||
node.add_child_nocopy (_panner->state (full_state));
|
||||
node.add_child_nocopy (_panshell->state (full_state));
|
||||
|
||||
return node;
|
||||
}
|
||||
|
@ -346,7 +353,7 @@ Delivery::set_state (const XMLNode& node, int version)
|
|||
XMLNode* pan_node = node.child (X_("Panner"));
|
||||
|
||||
if (pan_node) {
|
||||
_panner->set_state (*pan_node, version);
|
||||
_panshell->set_state (*pan_node, version);
|
||||
}
|
||||
|
||||
reset_panner ();
|
||||
|
@ -368,7 +375,7 @@ Delivery::reset_panner ()
|
|||
ntargets = _configured_output.n_audio();
|
||||
}
|
||||
|
||||
_panner->reset (ntargets, pans_required());
|
||||
_panshell->configure_io (ChanCount (DataType::AUDIO, pans_required()), ChanCount (DataType::AUDIO, ntargets));
|
||||
}
|
||||
} else {
|
||||
panner_legal_c.disconnect ();
|
||||
|
@ -387,8 +394,10 @@ Delivery::panners_became_legal ()
|
|||
ntargets = _configured_output.n_audio();
|
||||
}
|
||||
|
||||
_panner->reset (ntargets, pans_required());
|
||||
_panshell->configure_io (ChanCount (DataType::AUDIO, pans_required()), ChanCount (DataType::AUDIO, ntargets));
|
||||
#ifdef PANNER_HACKS
|
||||
_panner->load (); // automation
|
||||
#endif
|
||||
panner_legal_c.disconnect ();
|
||||
return 0;
|
||||
}
|
||||
|
@ -421,25 +430,6 @@ Delivery::reset_panners ()
|
|||
return *PannersLegal ();
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Delivery::start_pan_touch (uint32_t which, double when)
|
||||
{
|
||||
if (which < _panner->npanners()) {
|
||||
_panner->pan_control(which)->start_touch(when);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Delivery::end_pan_touch (uint32_t which, bool mark, double when)
|
||||
{
|
||||
if (which < _panner->npanners()) {
|
||||
_panner->pan_control(which)->stop_touch(mark, when);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
Delivery::flush_buffers (framecnt_t nframes, framepos_t time)
|
||||
{
|
||||
|
@ -456,8 +446,7 @@ void
|
|||
Delivery::transport_stopped (framepos_t now)
|
||||
{
|
||||
Processor::transport_stopped (now);
|
||||
|
||||
_panner->transport_stopped (now);
|
||||
_panshell->pannable()->transport_stopped (now);
|
||||
|
||||
if (_output) {
|
||||
PortSet& ports (_output->ports());
|
||||
|
@ -533,7 +522,7 @@ Delivery::set_name (const std::string& name)
|
|||
bool ret = IOProcessor::set_name (name);
|
||||
|
||||
if (ret) {
|
||||
ret = _panner->set_name (name);
|
||||
ret = _panshell->set_name (name);
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
@ -547,3 +536,9 @@ Delivery::output_changed (IOChange change, void* /*src*/)
|
|||
_output_buffers->attach_buffers (_output->ports ());
|
||||
}
|
||||
}
|
||||
|
||||
boost::shared_ptr<Panner>
|
||||
Delivery::panner () const
|
||||
{
|
||||
return _panshell->panner();
|
||||
}
|
||||
|
|
|
@ -18,5 +18,6 @@ const char* const route_templates_dir_name = X_("route_templates");
|
|||
const char* const surfaces_dir_name = X_("surfaces");
|
||||
const char* const user_config_dir_name = X_("ardour3");
|
||||
const char* const stub_dir_name = X_(".stubs");
|
||||
const char* const panner_dir_name = X_("panners");
|
||||
|
||||
}
|
||||
|
|
|
@ -46,6 +46,8 @@
|
|||
#include "ardour/configuration.h"
|
||||
#include "ardour/audiofilesource.h"
|
||||
#include "ardour/send.h"
|
||||
#include "ardour/pannable.h"
|
||||
#include "ardour/panner_shell.h"
|
||||
#include "ardour/playlist.h"
|
||||
#include "ardour/cycle_timer.h"
|
||||
#include "ardour/region.h"
|
||||
|
@ -458,18 +460,23 @@ Diskstream::playlist_ranges_moved (list< Evoral::RangeMove<framepos_t> > const &
|
|||
}
|
||||
|
||||
/* move panner automation */
|
||||
boost::shared_ptr<Panner> p = _track->main_outs()->panner ();
|
||||
if (p) {
|
||||
for (uint32_t i = 0; i < p->npanners (); ++i) {
|
||||
boost::shared_ptr<AutomationList> pan_alist = p->streampanner(i).pan_control()->alist();
|
||||
XMLNode & before = pan_alist->get_state ();
|
||||
bool const things_moved = pan_alist->move_ranges (movements);
|
||||
if (things_moved) {
|
||||
_session.add_command (new MementoCommand<AutomationList> (
|
||||
*pan_alist.get(), &before, &pan_alist->get_state ()));
|
||||
}
|
||||
}
|
||||
}
|
||||
boost::shared_ptr<Pannable> pannable = _track->pannable();
|
||||
Evoral::ControlSet::Controls& c (pannable->controls());
|
||||
|
||||
for (Evoral::ControlSet::Controls::iterator ci = c.begin(); ci != c.end(); ++ci) {
|
||||
boost::shared_ptr<AutomationControl> ac = boost::dynamic_pointer_cast<AutomationControl>(ci->second);
|
||||
if (!ac) {
|
||||
continue;
|
||||
}
|
||||
boost::shared_ptr<AutomationList> alist = ac->alist();
|
||||
|
||||
XMLNode & before = alist->get_state ();
|
||||
bool const things_moved = alist->move_ranges (movements);
|
||||
if (things_moved) {
|
||||
_session.add_command (new MementoCommand<AutomationList> (
|
||||
*alist.get(), &before, &alist->get_state ()));
|
||||
}
|
||||
}
|
||||
|
||||
/* move processor automation */
|
||||
_track->foreach_processor (boost::bind (&Diskstream::move_processor_automation, this, _1, movements_frames));
|
||||
|
|
|
@ -83,7 +83,6 @@ setup_enum_writer ()
|
|||
TimecodeFormat _Session_TimecodeFormat;
|
||||
Session::PullupFormat _Session_PullupFormat;
|
||||
FadeShape _FadeShape;
|
||||
Panner::LinkDirection _Panner_LinkDirection;
|
||||
IOChange _IOChange;
|
||||
AutomationType _AutomationType;
|
||||
AutoState _AutoState;
|
||||
|
@ -135,7 +134,9 @@ setup_enum_writer ()
|
|||
REGISTER (_OverlapType);
|
||||
|
||||
REGISTER_ENUM (GainAutomation);
|
||||
REGISTER_ENUM (PanAutomation);
|
||||
REGISTER_ENUM (PanAzimuthAutomation);
|
||||
REGISTER_ENUM (PanElevationAutomation);
|
||||
REGISTER_ENUM (PanWidthAutomation);
|
||||
REGISTER_ENUM (PluginAutomation);
|
||||
REGISTER_ENUM (SoloAutomation);
|
||||
REGISTER_ENUM (MuteAutomation);
|
||||
|
@ -410,10 +411,6 @@ setup_enum_writer ()
|
|||
REGISTER_CLASS_ENUM (Location, IsRangeMarker);
|
||||
REGISTER_BITS (_Location_Flags);
|
||||
|
||||
REGISTER_CLASS_ENUM (Panner, SameDirection);
|
||||
REGISTER_CLASS_ENUM (Panner, OppositeDirection);
|
||||
REGISTER (_Panner_LinkDirection);
|
||||
|
||||
REGISTER_CLASS_ENUM (Track, NoFreeze);
|
||||
REGISTER_CLASS_ENUM (Track, Frozen);
|
||||
REGISTER_CLASS_ENUM (Track, UnFrozen);
|
||||
|
|
|
@ -140,15 +140,25 @@ EventTypeMap::new_parameter(uint32_t type, uint8_t channel, uint32_t id) const
|
|||
double min = 0.0f;
|
||||
double max = 1.0f;
|
||||
double normal = 0.0f;
|
||||
|
||||
switch((AutomationType)type) {
|
||||
case NullAutomation:
|
||||
case GainAutomation:
|
||||
max = 2.0f;
|
||||
normal = 1.0f;
|
||||
break;
|
||||
case PanAutomation:
|
||||
normal = 0.5f;
|
||||
break;
|
||||
case PanAzimuthAutomation:
|
||||
normal = 0.5f; // there really is no normal but this works for stereo, sort of
|
||||
break;
|
||||
case PanWidthAutomation:
|
||||
min = -1.0;
|
||||
max = 1.0;
|
||||
normal = 0.0f;
|
||||
break;
|
||||
case PanElevationAutomation:
|
||||
case PanFrontBackAutomation:
|
||||
case PanLFEAutomation:
|
||||
break;
|
||||
case PluginAutomation:
|
||||
case SoloAutomation:
|
||||
case MuteAutomation:
|
||||
|
@ -191,11 +201,16 @@ EventTypeMap::new_parameter(const string& str) const
|
|||
p_type = FadeOutAutomation;
|
||||
} else if (str == "envelope") {
|
||||
p_type = EnvelopeAutomation;
|
||||
} else if (str == "pan") {
|
||||
p_type = PanAutomation;
|
||||
} else if (str.length() > 4 && str.substr(0, 4) == "pan-") {
|
||||
p_type = PanAutomation;
|
||||
p_id = atoi(str.c_str()+4);
|
||||
} else if (str == "pan-azimuth") {
|
||||
p_type = PanAzimuthAutomation;
|
||||
} else if (str == "pan-width") {
|
||||
p_type = PanWidthAutomation;
|
||||
} else if (str == "pan-elevation") {
|
||||
p_type = PanElevationAutomation;
|
||||
} else if (str == "pan-frontback") {
|
||||
p_type = PanFrontBackAutomation;
|
||||
} else if (str == "pan-lfe") {
|
||||
p_type = PanLFEAutomation;
|
||||
} else if (str.length() > 10 && str.substr(0, 10) == "parameter-") {
|
||||
p_type = PluginAutomation;
|
||||
p_id = atoi(str.c_str()+10);
|
||||
|
@ -243,8 +258,16 @@ EventTypeMap::to_symbol(const Evoral::Parameter& param) const
|
|||
|
||||
if (t == GainAutomation) {
|
||||
return "gain";
|
||||
} else if (t == PanAutomation) {
|
||||
return string_compose("pan-%1", param.id());
|
||||
} else if (t == PanAzimuthAutomation) {
|
||||
return "pan-azimuth";
|
||||
} else if (t == PanElevationAutomation) {
|
||||
return "pan-elevation";
|
||||
} else if (t == PanWidthAutomation) {
|
||||
return "pan-width";
|
||||
} else if (t == PanFrontBackAutomation) {
|
||||
return "pan-frontback";
|
||||
} else if (t == PanLFEAutomation) {
|
||||
return "pan-lfe";
|
||||
} else if (t == SoloAutomation) {
|
||||
return "solo";
|
||||
} else if (t == MuteAutomation) {
|
||||
|
|
|
@ -70,6 +70,7 @@
|
|||
#include "ardour/midi_region.h"
|
||||
#include "ardour/mix.h"
|
||||
#include "ardour/audioplaylist.h"
|
||||
#include "ardour/panner_manager.h"
|
||||
#include "ardour/plugin_manager.h"
|
||||
#include "ardour/process_thread.h"
|
||||
#include "ardour/profile.h"
|
||||
|
@ -323,6 +324,8 @@ ARDOUR::init (bool use_vst, bool try_optimization)
|
|||
ProcessThread::init ();
|
||||
BufferManager::init (10); // XX should be num_processors_for_dsp
|
||||
|
||||
PannerManager::instance().discover_panners();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -32,8 +32,8 @@ using namespace PBD;
|
|||
using namespace ARDOUR;
|
||||
using namespace std;
|
||||
|
||||
InternalSend::InternalSend (Session& s, boost::shared_ptr<MuteMaster> mm, boost::shared_ptr<Route> sendto, Delivery::Role role)
|
||||
: Send (s, mm, role)
|
||||
InternalSend::InternalSend (Session& s, boost::shared_ptr<Pannable> p, boost::shared_ptr<MuteMaster> mm, boost::shared_ptr<Route> sendto, Delivery::Role role)
|
||||
: Send (s, p, mm, role)
|
||||
, target (0)
|
||||
{
|
||||
if (sendto) {
|
||||
|
|
|
@ -1,31 +1,87 @@
|
|||
/*
|
||||
Copyright (C) 2011 Paul Davis
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
|
||||
*/
|
||||
|
||||
#include "pbd/error.h"
|
||||
#include "pbd/convert.h"
|
||||
|
||||
#include "ardour/automation_control.h"
|
||||
#include "ardour/automation_list.h"
|
||||
#include "ardour/pannable.h"
|
||||
#include "ardour/pan_controllable.h"
|
||||
#include "ardour/session.h"
|
||||
|
||||
using namespace PBD;
|
||||
using namespace ARDOUR;
|
||||
|
||||
Pannable::Pannable (Session& s)
|
||||
: Automatable (s)
|
||||
, SessionHandleRef (s)
|
||||
, pan_azimuth_control (new AutomationControl (s, PanAzimuthAutomation,
|
||||
boost::shared_ptr<AutomationList>(new AutomationList(PanAzimuthAutomation)), ""))
|
||||
, pan_elevation_control (new AutomationControl (s, PanElevationAutomation,
|
||||
boost::shared_ptr<AutomationList>(new AutomationList(PanElevationAutomation)), ""))
|
||||
, pan_width_control (new AutomationControl (s, PanWidthAutomation,
|
||||
boost::shared_ptr<AutomationList>(new AutomationList(PanWidthAutomation)), ""))
|
||||
, pan_frontback_control (new AutomationControl (s, PanFrontBackAutomation,
|
||||
boost::shared_ptr<AutomationList>(new AutomationList(PanFrontBackAutomation)), ""))
|
||||
, pan_lfe_control (new AutomationControl (s, PanLFEAutomation,
|
||||
boost::shared_ptr<AutomationList>(new AutomationList(PanLFEAutomation)), ""))
|
||||
, pan_azimuth_control (new PanControllable (s, "", this, PanAzimuthAutomation))
|
||||
, pan_elevation_control (new PanControllable (s, "", this, PanElevationAutomation))
|
||||
, pan_width_control (new PanControllable (s, "", this, PanWidthAutomation))
|
||||
, pan_frontback_control (new PanControllable (s, "", this, PanFrontBackAutomation))
|
||||
, pan_lfe_control (new PanControllable (s, "", this, PanLFEAutomation))
|
||||
, _auto_state (Off)
|
||||
, _auto_style (Absolute)
|
||||
, _has_state (false)
|
||||
, _responding_to_control_auto_state_change (0)
|
||||
{
|
||||
add_control (pan_azimuth_control);
|
||||
add_control (pan_elevation_control);
|
||||
add_control (pan_width_control);
|
||||
add_control (pan_frontback_control);
|
||||
add_control (pan_lfe_control);
|
||||
|
||||
/* all controls change state together */
|
||||
|
||||
pan_azimuth_control->alist()->automation_state_changed.connect_same_thread (*this, boost::bind (&Pannable::control_auto_state_changed, this, _1));
|
||||
pan_elevation_control->alist()->automation_state_changed.connect_same_thread (*this, boost::bind (&Pannable::control_auto_state_changed, this, _1));
|
||||
pan_width_control->alist()->automation_state_changed.connect_same_thread (*this, boost::bind (&Pannable::control_auto_state_changed, this, _1));
|
||||
pan_frontback_control->alist()->automation_state_changed.connect_same_thread (*this, boost::bind (&Pannable::control_auto_state_changed, this, _1));
|
||||
pan_lfe_control->alist()->automation_state_changed.connect_same_thread (*this, boost::bind (&Pannable::control_auto_state_changed, this, _1));
|
||||
}
|
||||
|
||||
void
|
||||
Pannable::control_auto_state_changed (AutoState new_state)
|
||||
{
|
||||
if (_responding_to_control_auto_state_change) {
|
||||
return;
|
||||
}
|
||||
|
||||
_responding_to_control_auto_state_change++;
|
||||
|
||||
pan_azimuth_control->set_automation_state (new_state);
|
||||
pan_width_control->set_automation_state (new_state);
|
||||
pan_elevation_control->set_automation_state (new_state);
|
||||
pan_frontback_control->set_automation_state (new_state);
|
||||
pan_lfe_control->set_automation_state (new_state);
|
||||
|
||||
_responding_to_control_auto_state_change--;
|
||||
|
||||
_auto_state = new_state;
|
||||
automation_state_changed (new_state); /* EMIT SIGNAL */
|
||||
}
|
||||
|
||||
void
|
||||
Pannable::set_panner (boost::shared_ptr<Panner> p)
|
||||
{
|
||||
_panner = p;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -95,3 +151,100 @@ Pannable::stop_touch (bool mark, double when)
|
|||
}
|
||||
g_atomic_int_set (&_touching, 0);
|
||||
}
|
||||
|
||||
XMLNode&
|
||||
Pannable::get_state ()
|
||||
{
|
||||
return state (true);
|
||||
}
|
||||
|
||||
XMLNode&
|
||||
Pannable::state (bool full)
|
||||
{
|
||||
XMLNode* node = new XMLNode (X_("Pannable"));
|
||||
XMLNode* control_node;
|
||||
char buf[32];
|
||||
|
||||
control_node = new XMLNode (X_("azimuth"));
|
||||
snprintf (buf, sizeof(buf), "%.12g", pan_azimuth_control->get_value());
|
||||
control_node->add_property (X_("value"), buf);
|
||||
node->add_child_nocopy (*control_node);
|
||||
|
||||
control_node = new XMLNode (X_("width"));
|
||||
snprintf (buf, sizeof(buf), "%.12g", pan_width_control->get_value());
|
||||
control_node->add_property (X_("value"), buf);
|
||||
node->add_child_nocopy (*control_node);
|
||||
|
||||
control_node = new XMLNode (X_("elevation"));
|
||||
snprintf (buf, sizeof(buf), "%.12g", pan_elevation_control->get_value());
|
||||
control_node->add_property (X_("value"), buf);
|
||||
node->add_child_nocopy (*control_node);
|
||||
|
||||
control_node = new XMLNode (X_("frontback"));
|
||||
snprintf (buf, sizeof(buf), "%.12g", pan_frontback_control->get_value());
|
||||
control_node->add_property (X_("value"), buf);
|
||||
node->add_child_nocopy (*control_node);
|
||||
|
||||
control_node = new XMLNode (X_("lfe"));
|
||||
snprintf (buf, sizeof(buf), "%.12g", pan_lfe_control->get_value());
|
||||
control_node->add_property (X_("value"), buf);
|
||||
node->add_child_nocopy (*control_node);
|
||||
|
||||
node->add_child_nocopy (get_automation_xml_state ());
|
||||
|
||||
return *node;
|
||||
}
|
||||
|
||||
int
|
||||
Pannable::set_state (const XMLNode& root, int /*version - not used*/)
|
||||
{
|
||||
if (root.name() != X_("Pannable")) {
|
||||
warning << string_compose (_("Pannable given XML data for %1 - ignored"), root.name()) << endmsg;
|
||||
return -1;
|
||||
}
|
||||
|
||||
XMLNodeList nlist;
|
||||
XMLNodeConstIterator niter;
|
||||
const XMLProperty *prop;
|
||||
|
||||
nlist = root.children();
|
||||
|
||||
for (niter = nlist.begin(); niter != nlist.end(); ++niter) {
|
||||
if ((*niter)->name() == X_("azimuth")) {
|
||||
prop = (*niter)->property (X_("value"));
|
||||
if (prop) {
|
||||
pan_azimuth_control->set_value (atof (prop->value()));
|
||||
}
|
||||
} else if ((*niter)->name() == X_("width")) {
|
||||
prop = (*niter)->property (X_("value"));
|
||||
if (prop) {
|
||||
pan_width_control->set_value (atof (prop->value()));
|
||||
}
|
||||
} else if ((*niter)->name() == X_("elevation")) {
|
||||
prop = (*niter)->property (X_("value"));
|
||||
if (prop) {
|
||||
pan_elevation_control->set_value (atof (prop->value()));
|
||||
}
|
||||
} else if ((*niter)->name() == X_("azimuth")) {
|
||||
prop = (*niter)->property (X_("value"));
|
||||
if (prop) {
|
||||
pan_frontback_control->set_value (atof (prop->value()));
|
||||
}
|
||||
} else if ((*niter)->name() == X_("lfe")) {
|
||||
prop = (*niter)->property (X_("value"));
|
||||
if (prop) {
|
||||
pan_lfe_control->set_value (atof (prop->value()));
|
||||
}
|
||||
} else if ((*niter)->name() == Automatable::xml_node_name) {
|
||||
set_automation_xml_state (**niter, PanAzimuthAutomation);
|
||||
}
|
||||
}
|
||||
|
||||
_has_state = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -102,6 +102,12 @@ PannerShell::configure_io (ChanCount in, ChanCount out)
|
|||
}
|
||||
|
||||
_panner.reset (pi->descriptor.factory (_pannable, _session.get_speakers()));
|
||||
_panner->configure_io (in, out);
|
||||
|
||||
/* PANNER_HACKS: only the real owner should be able to claim the pannable
|
||||
*/
|
||||
|
||||
_pannable->set_panner (_panner);
|
||||
|
||||
Changed (); /* EMIT SIGNAL */
|
||||
}
|
||||
|
|
|
@ -41,9 +41,9 @@ using namespace std;
|
|||
using namespace ARDOUR;
|
||||
using namespace PBD;
|
||||
|
||||
PortInsert::PortInsert (Session& s, boost::shared_ptr<MuteMaster> mm)
|
||||
PortInsert::PortInsert (Session& s, boost::shared_ptr<Pannable> pannable, boost::shared_ptr<MuteMaster> mm)
|
||||
: IOProcessor (s, true, true, string_compose (_("insert %1"), (bitslot = s.next_insert_id()) + 1), "")
|
||||
, _out (new Delivery (s, _output, mm, _name, Delivery::Insert))
|
||||
, _out (new Delivery (s, _output, pannable, mm, _name, Delivery::Insert))
|
||||
{
|
||||
_mtdm = 0;
|
||||
_latency_detect = false;
|
||||
|
|
|
@ -46,7 +46,9 @@
|
|||
#include "ardour/meter.h"
|
||||
#include "ardour/mix.h"
|
||||
#include "ardour/monitor_processor.h"
|
||||
#include "ardour/pannable.h"
|
||||
#include "ardour/panner.h"
|
||||
#include "ardour/panner_shell.h"
|
||||
#include "ardour/plugin_insert.h"
|
||||
#include "ardour/port.h"
|
||||
#include "ardour/port_insert.h"
|
||||
|
@ -106,7 +108,7 @@ Route::Route (Session& sess, string name, Flag flg, DataType default_type)
|
|||
int
|
||||
Route::init ()
|
||||
{
|
||||
/* add standard controls */
|
||||
/* add standard controls */
|
||||
|
||||
_solo_control->set_flags (Controllable::Flag (_solo_control->flags() | Controllable::Toggle));
|
||||
_mute_control->set_flags (Controllable::Flag (_mute_control->flags() | Controllable::Toggle));
|
||||
|
@ -114,6 +116,10 @@ Route::init ()
|
|||
add_control (_solo_control);
|
||||
add_control (_mute_control);
|
||||
|
||||
/* panning */
|
||||
|
||||
_pannable.reset (new Pannable (_session));
|
||||
|
||||
/* input and output objects */
|
||||
|
||||
_input.reset (new IO (_session, _name, IO::Input, _default_type));
|
||||
|
@ -136,7 +142,7 @@ Route::init ()
|
|||
|
||||
add_processor (_meter, PostFader);
|
||||
|
||||
_main_outs.reset (new Delivery (_session, _output, _mute_master, _name, Delivery::Main));
|
||||
_main_outs.reset (new Delivery (_session, _output, _pannable, _mute_master, _name, Delivery::Main));
|
||||
|
||||
add_processor (_main_outs, PostFader);
|
||||
|
||||
|
@ -163,7 +169,9 @@ Route::init ()
|
|||
|
||||
/* no panning on the monitor main outs */
|
||||
|
||||
#ifdef PANNER_HACKS
|
||||
_main_outs->panner()->set_bypassed (true);
|
||||
#endif
|
||||
}
|
||||
|
||||
if (is_master() || is_monitor() || is_hidden()) {
|
||||
|
@ -975,14 +983,14 @@ Route::add_processor_from_xml_2X (const XMLNode& node, int version)
|
|||
|
||||
} else {
|
||||
|
||||
processor.reset (new PortInsert (_session, _mute_master));
|
||||
processor.reset (new PortInsert (_session, _pannable, _mute_master));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else if (node.name() == "Send") {
|
||||
|
||||
processor.reset (new Send (_session, _mute_master));
|
||||
processor.reset (new Send (_session, _pannable, _mute_master));
|
||||
|
||||
} else {
|
||||
|
||||
|
@ -1852,6 +1860,8 @@ Route::state(bool full_state)
|
|||
cmt->add_content (_comment);
|
||||
}
|
||||
|
||||
node->add_child_nocopy (_pannable->state (full_state));
|
||||
|
||||
for (i = _processors.begin(); i != _processors.end(); ++i) {
|
||||
node->add_child_nocopy((*i)->state (full_state));
|
||||
}
|
||||
|
@ -1928,6 +1938,11 @@ Route::_set_state (const XMLNode& node, int version, bool /*call_base*/)
|
|||
if (child->name() == X_("Processor")) {
|
||||
processor_state.add_child_copy (*child);
|
||||
}
|
||||
|
||||
|
||||
if (child->name() == X_("Pannable")) {
|
||||
_pannable->set_state (*child, version);
|
||||
}
|
||||
}
|
||||
|
||||
set_processor_state (processor_state);
|
||||
|
@ -2233,7 +2248,7 @@ Route::_set_state_2X (const XMLNode& node, int version)
|
|||
io_child = *io_niter;
|
||||
|
||||
if (io_child->name() == X_("Panner")) {
|
||||
_main_outs->panner()->set_state(*io_child, version);
|
||||
_main_outs->panner_shell()->set_state(*io_child, version);
|
||||
} else if (io_child->name() == X_("Automation")) {
|
||||
/* IO's automation is for the fader */
|
||||
_amp->set_automation_xml_state (*io_child, Evoral::Parameter (GainAutomation));
|
||||
|
@ -2370,7 +2385,7 @@ Route::set_processor_state (const XMLNode& node)
|
|||
|
||||
if (prop->value() == "intsend") {
|
||||
|
||||
processor.reset (new InternalSend (_session, _mute_master, boost::shared_ptr<Route>(), Delivery::Role (0)));
|
||||
processor.reset (new InternalSend (_session, _pannable, _mute_master, boost::shared_ptr<Route>(), Delivery::Role (0)));
|
||||
|
||||
} else if (prop->value() == "ladspa" || prop->value() == "Ladspa" ||
|
||||
prop->value() == "lv2" ||
|
||||
|
@ -2381,11 +2396,11 @@ Route::set_processor_state (const XMLNode& node)
|
|||
|
||||
} else if (prop->value() == "port") {
|
||||
|
||||
processor.reset (new PortInsert (_session, _mute_master));
|
||||
processor.reset (new PortInsert (_session, _pannable, _mute_master));
|
||||
|
||||
} else if (prop->value() == "send") {
|
||||
|
||||
processor.reset (new Send (_session, _mute_master));
|
||||
processor.reset (new Send (_session, _pannable, _mute_master));
|
||||
|
||||
} else {
|
||||
error << string_compose(_("unknown Processor type \"%1\"; ignored"), prop->value()) << endmsg;
|
||||
|
@ -2540,11 +2555,11 @@ Route::listen_via (boost::shared_ptr<Route> route, Placement placement, bool /*a
|
|||
/* master never sends to control outs */
|
||||
return 0;
|
||||
} else {
|
||||
listener.reset (new InternalSend (_session, _mute_master, route, (aux ? Delivery::Aux : Delivery::Listen)));
|
||||
listener.reset (new InternalSend (_session, _pannable, _mute_master, route, (aux ? Delivery::Aux : Delivery::Listen)));
|
||||
}
|
||||
|
||||
} else {
|
||||
listener.reset (new InternalSend (_session, _mute_master, route, (aux ? Delivery::Aux : Delivery::Listen)));
|
||||
listener.reset (new InternalSend (_session, _pannable, _mute_master, route, (aux ? Delivery::Aux : Delivery::Listen)));
|
||||
}
|
||||
|
||||
} catch (failed_constructor& err) {
|
||||
|
@ -3141,8 +3156,7 @@ Route::set_latency_delay (framecnt_t longest_session_latency)
|
|||
void
|
||||
Route::automation_snapshot (framepos_t now, bool force)
|
||||
{
|
||||
panner()->automation_snapshot (now, force);
|
||||
|
||||
_pannable->automation_snapshot (now, force);
|
||||
for (ProcessorList::iterator i = _processors.begin(); i != _processors.end(); ++i) {
|
||||
(*i)->automation_snapshot (now, force);
|
||||
}
|
||||
|
@ -3271,11 +3285,10 @@ Route::shift (framepos_t pos, framecnt_t frames)
|
|||
|
||||
/* pan automation */
|
||||
{
|
||||
boost::shared_ptr<AutomationControl> pc;
|
||||
uint32_t npans = _main_outs->panner()->npanners();
|
||||
|
||||
for (uint32_t p = 0; p < npans; ++p) {
|
||||
pc = _main_outs->panner()->pan_control (0, p);
|
||||
ControlSet::Controls& c (_pannable->controls());
|
||||
|
||||
for (ControlSet::Controls::const_iterator ci = c.begin(); ci != c.end(); ++ci) {
|
||||
boost::shared_ptr<AutomationControl> pc = boost::dynamic_pointer_cast<AutomationControl> (ci->second);
|
||||
if (pc) {
|
||||
boost::shared_ptr<AutomationList> al = pc->alist();
|
||||
XMLNode& before = al->get_state ();
|
||||
|
@ -3461,10 +3474,23 @@ Route::meter ()
|
|||
}
|
||||
}
|
||||
|
||||
boost::shared_ptr<Pannable>
|
||||
Route::pannable() const
|
||||
{
|
||||
return _pannable;
|
||||
}
|
||||
|
||||
boost::shared_ptr<Panner>
|
||||
Route::panner() const
|
||||
{
|
||||
return _main_outs->panner();
|
||||
/* may be null ! */
|
||||
return _main_outs->panner_shell()->panner();
|
||||
}
|
||||
|
||||
boost::shared_ptr<PannerShell>
|
||||
Route::panner_shell() const
|
||||
{
|
||||
return _main_outs->panner_shell();
|
||||
}
|
||||
|
||||
boost::shared_ptr<AutomationControl>
|
||||
|
|
|
@ -38,8 +38,8 @@ using namespace ARDOUR;
|
|||
using namespace PBD;
|
||||
using namespace std;
|
||||
|
||||
Send::Send (Session& s, boost::shared_ptr<MuteMaster> mm, Role r)
|
||||
: Delivery (s, mm, string_compose (_("send %1"), (_bitslot = s.next_send_id()) + 1), r)
|
||||
Send::Send (Session& s, boost::shared_ptr<Pannable> p, boost::shared_ptr<MuteMaster> mm, Role r)
|
||||
: Delivery (s, p, mm, string_compose (_("send %1"), (_bitslot = s.next_send_id()) + 1), r)
|
||||
, _metering (false)
|
||||
{
|
||||
_amp.reset (new Amp (_session));
|
||||
|
|
|
@ -99,7 +99,7 @@
|
|||
#include "ardour/tempo.h"
|
||||
#include "ardour/utils.h"
|
||||
#include "ardour/graph.h"
|
||||
#include "ardour/vbap_speakers.h"
|
||||
#include "ardour/speakers.h"
|
||||
#include "ardour/operations.h"
|
||||
|
||||
#include "midi++/port.h"
|
||||
|
@ -4207,10 +4207,6 @@ Session::ensure_search_path_includes (const string& path, DataType type)
|
|||
Speakers&
|
||||
Session::get_speakers()
|
||||
{
|
||||
if (!_speakers) {
|
||||
_speakers = new Speakers;
|
||||
}
|
||||
|
||||
return *_speakers;
|
||||
}
|
||||
|
||||
|
|
|
@ -92,7 +92,7 @@
|
|||
#include "ardour/midi_source.h"
|
||||
#include "ardour/midi_track.h"
|
||||
#include "ardour/named_selection.h"
|
||||
#include "ardour/panner.h"
|
||||
#include "ardour/pannable.h"
|
||||
#include "ardour/processor.h"
|
||||
#include "ardour/port.h"
|
||||
#include "ardour/region_factory.h"
|
||||
|
@ -220,7 +220,7 @@ Session::first_stage_init (string fullpath, string snapshot_name)
|
|||
midi_control_ui = 0;
|
||||
_step_editors = 0;
|
||||
no_questions_about_missing_files = false;
|
||||
_speakers = 0;
|
||||
_speakers = new Speakers;
|
||||
|
||||
AudioDiskstream::allocate_working_buffers();
|
||||
|
||||
|
@ -1174,6 +1174,8 @@ Session::state(bool full_state)
|
|||
}
|
||||
}
|
||||
|
||||
node->add_child_nocopy (_speakers->get_state());
|
||||
|
||||
node->add_child_nocopy (_tempo_map->get_state());
|
||||
|
||||
node->add_child_nocopy (get_control_protocol_state());
|
||||
|
@ -1294,6 +1296,13 @@ Session::set_state (const XMLNode& node, int version)
|
|||
goto out;
|
||||
}
|
||||
|
||||
if ((child = find_named_node (node, X_("Speakers"))) == 0) {
|
||||
warning << _("Session: XML state has no speakers section - assuming simple stereo") << endmsg;
|
||||
_speakers->setup_default_speakers (2);
|
||||
} else {
|
||||
_speakers->set_state (*child, version);
|
||||
}
|
||||
|
||||
Location* location;
|
||||
|
||||
if ((location = _locations->auto_loop_location()) != 0) {
|
||||
|
@ -2990,19 +2999,19 @@ Session::controllable_by_descriptor (const ControllableDescriptor& desc)
|
|||
|
||||
case ControllableDescriptor::PanDirection:
|
||||
{
|
||||
boost::shared_ptr<Panner> p = r->panner();
|
||||
if (p) {
|
||||
c = p->direction_control();
|
||||
}
|
||||
c = r->pannable()->pan_azimuth_control;
|
||||
break;
|
||||
}
|
||||
|
||||
case ControllableDescriptor::PanWidth:
|
||||
{
|
||||
boost::shared_ptr<Panner> p = r->panner();
|
||||
if (p) {
|
||||
c = p->width_control();
|
||||
}
|
||||
c = r->pannable()->pan_width_control;
|
||||
break;
|
||||
}
|
||||
|
||||
case ControllableDescriptor::PanElevation:
|
||||
{
|
||||
c = r->pannable()->pan_elevation_control;
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -16,9 +16,15 @@
|
|||
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
|
||||
*/
|
||||
|
||||
#include "pbd/error.h"
|
||||
#include "pbd/convert.h"
|
||||
#include "pbd/locale_guard.h"
|
||||
|
||||
#include "ardour/speaker.h"
|
||||
#include "ardour/speakers.h"
|
||||
|
||||
#include "i18n.h"
|
||||
|
||||
using namespace ARDOUR;
|
||||
using namespace PBD;
|
||||
using namespace std;
|
||||
|
@ -103,3 +109,119 @@ Speakers::move_speaker (int id, const AngularVector& new_position)
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Speakers::setup_default_speakers (uint32_t n)
|
||||
{
|
||||
/* default assignment of speaker position for n speakers */
|
||||
|
||||
assert (n>0);
|
||||
|
||||
switch (n) {
|
||||
case 1:
|
||||
add_speaker (AngularVector (0.0, 0.0));
|
||||
break;
|
||||
|
||||
case 2:
|
||||
add_speaker (AngularVector (0.0, 0.0));
|
||||
add_speaker (AngularVector (180.0, 0,0));
|
||||
break;
|
||||
|
||||
case 3:
|
||||
/* top, bottom kind-of-left & bottom kind-of-right */
|
||||
add_speaker (AngularVector (90.0, 0.0));
|
||||
add_speaker (AngularVector (215.0, 0,0));
|
||||
add_speaker (AngularVector (335.0, 0,0));
|
||||
break;
|
||||
case 4:
|
||||
/* clockwise from top left */
|
||||
add_speaker (AngularVector (135.0, 0.0));
|
||||
add_speaker (AngularVector (45.0, 0.0));
|
||||
add_speaker (AngularVector (335.0, 0.0));
|
||||
add_speaker (AngularVector (215.0, 0.0));
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
double degree_step = 360.0 / n;
|
||||
double deg;
|
||||
uint32_t i;
|
||||
|
||||
/* even number of speakers? make sure the top two are either side of "top".
|
||||
otherwise, just start at the "top" (90.0 degrees) and rotate around
|
||||
*/
|
||||
|
||||
if (n % 2) {
|
||||
deg = 90.0 - degree_step;
|
||||
} else {
|
||||
deg = 90.0;
|
||||
}
|
||||
for (i = 0; i < n; ++i, deg += degree_step) {
|
||||
add_speaker (AngularVector (deg, 0.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
XMLNode&
|
||||
Speakers::get_state ()
|
||||
{
|
||||
XMLNode* node = new XMLNode (X_("Speakers"));
|
||||
char buf[32];
|
||||
LocaleGuard lg (X_("POSIX"));
|
||||
|
||||
for (vector<Speaker>::const_iterator i = _speakers.begin(); i != _speakers.end(); ++i) {
|
||||
XMLNode* speaker = new XMLNode (X_("Speaker"));
|
||||
|
||||
snprintf (buf, sizeof (buf), "%.12g", (*i).angles().azi);
|
||||
speaker->add_property (X_("azimuth"), buf);
|
||||
snprintf (buf, sizeof (buf), "%.12g", (*i).angles().ele);
|
||||
speaker->add_property (X_("elevation"), buf);
|
||||
snprintf (buf, sizeof (buf), "%.12g", (*i).angles().length);
|
||||
speaker->add_property (X_("distance"), buf);
|
||||
|
||||
node->add_child_nocopy (*speaker);
|
||||
}
|
||||
|
||||
return *node;
|
||||
}
|
||||
|
||||
int
|
||||
Speakers::set_state (const XMLNode& node, int /*version*/)
|
||||
{
|
||||
XMLNodeConstIterator i;
|
||||
const XMLProperty* prop;
|
||||
double a, e, d;
|
||||
LocaleGuard lg (X_("POSIX"));
|
||||
int n = 0;
|
||||
|
||||
_speakers.clear ();
|
||||
|
||||
for (i = node.children().begin(); i != node.children().end(); ++i, ++n) {
|
||||
if ((*i)->name() == X_("Speaker")) {
|
||||
if ((prop = (*i)->property (X_("azimuth"))) == 0) {
|
||||
warning << _("Speaker information is missing azimuth - speaker ignored") << endmsg;
|
||||
continue;
|
||||
}
|
||||
a = atof (prop->value());
|
||||
|
||||
if ((prop = (*i)->property (X_("elevation"))) == 0) {
|
||||
warning << _("Speaker information is missing elevation - speaker ignored") << endmsg;
|
||||
continue;
|
||||
}
|
||||
e = atof (prop->value());
|
||||
|
||||
if ((prop = (*i)->property (X_("distance"))) == 0) {
|
||||
warning << _("Speaker information is missing distance - speaker ignored") << endmsg;
|
||||
continue;
|
||||
}
|
||||
d = atof (prop->value());
|
||||
|
||||
add_speaker (AngularVector (a, e, d));
|
||||
}
|
||||
}
|
||||
|
||||
update ();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
#include <iostream>
|
||||
#include <algorithm>
|
||||
|
||||
#include "ardour/audioengine.h"
|
||||
#include "ardour/buffer_set.h"
|
||||
|
@ -68,6 +69,10 @@ ThreadBuffers::ensure_buffers (ChanCount howmany)
|
|||
void
|
||||
ThreadBuffers::allocate_pan_automation_buffers (framecnt_t nframes, uint32_t howmany, bool force)
|
||||
{
|
||||
/* we always need at least 2 pan buffers */
|
||||
|
||||
howmany = max (2U, howmany);
|
||||
|
||||
if (!force && howmany <= npan_buffers) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,240 +0,0 @@
|
|||
|
||||
/*
|
||||
This software is being provided to you, the licensee, by Ville Pulkki,
|
||||
under the following license. By obtaining, using and/or copying this
|
||||
software, you agree that you have read, understood, and will comply
|
||||
with these terms and conditions: Permission to use, copy, modify and
|
||||
distribute, including the right to grant others rights to distribute
|
||||
at any tier, this software and its documentation for any purpose and
|
||||
without fee or royalty is hereby granted, provided that you agree to
|
||||
comply with the following copyright notice and statements, including
|
||||
the disclaimer, and that the same appear on ALL copies of the software
|
||||
and documentation, including modifications that you make for internal
|
||||
use or for distribution:
|
||||
|
||||
Copyright 1998 by Ville Pulkki, Helsinki University of Technology. All
|
||||
rights reserved.
|
||||
|
||||
The software may be used, distributed, and included to commercial
|
||||
products without any charges. When included to a commercial product,
|
||||
the method "Vector Base Amplitude Panning" and its developer Ville
|
||||
Pulkki must be referred to in documentation.
|
||||
|
||||
This software is provided "as is", and Ville Pulkki or Helsinki
|
||||
University of Technology make no representations or warranties,
|
||||
expressed or implied. By way of example, but not limitation, Helsinki
|
||||
University of Technology or Ville Pulkki make no representations or
|
||||
warranties of merchantability or fitness for any particular purpose or
|
||||
that the use of the licensed software or documentation will not
|
||||
infringe any third party patents, copyrights, trademarks or other
|
||||
rights. The name of Ville Pulkki or Helsinki University of Technology
|
||||
may not be used in advertising or publicity pertaining to distribution
|
||||
of the software.
|
||||
*/
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
|
||||
#include "pbd/cartesian.h"
|
||||
|
||||
#include "ardour/speakers.h"
|
||||
#include "ardour/vbap.h"
|
||||
#include "ardour/vbap_speakers.h"
|
||||
#include "ardour/audio_buffer.h"
|
||||
#include "ardour/buffer_set.h"
|
||||
|
||||
using namespace PBD;
|
||||
using namespace ARDOUR;
|
||||
using namespace std;
|
||||
|
||||
string VBAPanner::name = X_("VBAP");
|
||||
|
||||
VBAPanner::VBAPanner (Panner& parent, Evoral::Parameter param, Speakers& s)
|
||||
: StreamPanner (parent, param)
|
||||
, _dirty (true)
|
||||
, _speakers (VBAPSpeakers::instance (s))
|
||||
{
|
||||
}
|
||||
|
||||
VBAPanner::~VBAPanner ()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
VBAPanner::update ()
|
||||
{
|
||||
/* force 2D for now */
|
||||
_angles.ele = 0.0;
|
||||
_dirty = true;
|
||||
|
||||
Changed ();
|
||||
}
|
||||
|
||||
void
|
||||
VBAPanner::compute_gains (double gains[3], int speaker_ids[3], int azi, int ele)
|
||||
{
|
||||
/* calculates gain factors using loudspeaker setup and given direction */
|
||||
double cartdir[3];
|
||||
double power;
|
||||
int i,j,k;
|
||||
double small_g;
|
||||
double big_sm_g, gtmp[3];
|
||||
|
||||
azi_ele_to_cart (azi,ele, cartdir[0], cartdir[1], cartdir[2]);
|
||||
big_sm_g = -100000.0;
|
||||
|
||||
gains[0] = gains[1] = gains[2] = 0;
|
||||
speaker_ids[0] = speaker_ids[1] = speaker_ids[2] = 0;
|
||||
|
||||
for (i = 0; i < _speakers.n_tuples(); i++) {
|
||||
|
||||
small_g = 10000000.0;
|
||||
|
||||
for (j = 0; j < _speakers.dimension(); j++) {
|
||||
|
||||
gtmp[j] = 0.0;
|
||||
|
||||
for (k = 0; k < _speakers.dimension(); k++) {
|
||||
gtmp[j] += cartdir[k] * _speakers.matrix(i)[j*_speakers.dimension()+k];
|
||||
}
|
||||
|
||||
if (gtmp[j] < small_g) {
|
||||
small_g = gtmp[j];
|
||||
}
|
||||
}
|
||||
|
||||
if (small_g > big_sm_g) {
|
||||
|
||||
big_sm_g = small_g;
|
||||
|
||||
gains[0] = gtmp[0];
|
||||
gains[1] = gtmp[1];
|
||||
|
||||
speaker_ids[0] = _speakers.speaker_for_tuple (i, 0);
|
||||
speaker_ids[1] = _speakers.speaker_for_tuple (i, 1);
|
||||
|
||||
if (_speakers.dimension() == 3) {
|
||||
gains[2] = gtmp[2];
|
||||
speaker_ids[2] = _speakers.speaker_for_tuple (i, 2);
|
||||
} else {
|
||||
gains[2] = 0.0;
|
||||
speaker_ids[2] = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
power = sqrt (gains[0]*gains[0] + gains[1]*gains[1] + gains[2]*gains[2]);
|
||||
|
||||
if (power > 0) {
|
||||
gains[0] /= power;
|
||||
gains[1] /= power;
|
||||
gains[2] /= power;
|
||||
}
|
||||
|
||||
_dirty = false;
|
||||
}
|
||||
|
||||
void
|
||||
VBAPanner::do_distribute (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes)
|
||||
{
|
||||
if (_muted) {
|
||||
return;
|
||||
}
|
||||
|
||||
Sample* const src = srcbuf.data();
|
||||
Sample* dst;
|
||||
pan_t pan;
|
||||
uint32_t n_audio = obufs.count().n_audio();
|
||||
bool was_dirty;
|
||||
|
||||
if ((was_dirty = _dirty)) {
|
||||
compute_gains (desired_gains, desired_outputs, _angles.azi, _angles.ele);
|
||||
cerr << " @ " << _angles.azi << " /= " << _angles.ele
|
||||
<< " Outputs: "
|
||||
<< desired_outputs[0] + 1 << ' '
|
||||
<< desired_outputs[1] + 1 << ' '
|
||||
<< " Gains "
|
||||
<< desired_gains[0] << ' '
|
||||
<< desired_gains[1] << ' '
|
||||
<< endl;
|
||||
}
|
||||
|
||||
bool todo[n_audio];
|
||||
|
||||
for (uint32_t o = 0; o < n_audio; ++o) {
|
||||
todo[o] = true;
|
||||
}
|
||||
|
||||
|
||||
/* VBAP may distribute the signal across up to 3 speakers depending on
|
||||
the configuration of the speakers.
|
||||
*/
|
||||
|
||||
for (int o = 0; o < 3; ++o) {
|
||||
if (desired_outputs[o] != -1) {
|
||||
|
||||
pframes_t n = 0;
|
||||
|
||||
/* XXX TODO: interpolate across changes in gain and/or outputs
|
||||
*/
|
||||
|
||||
dst = obufs.get_audio(desired_outputs[o]).data();
|
||||
|
||||
pan = gain_coefficient * desired_gains[o];
|
||||
mix_buffers_with_gain (dst+n,src+n,nframes-n,pan);
|
||||
|
||||
todo[o] = false;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint32_t o = 0; o < n_audio; ++o) {
|
||||
if (todo[o]) {
|
||||
/* VBAP decided not to deliver any audio to this output, so we write silence */
|
||||
dst = obufs.get_audio(o).data();
|
||||
memset (dst, 0, sizeof (Sample) * nframes);
|
||||
}
|
||||
}
|
||||
|
||||
if (was_dirty) {
|
||||
memcpy (gains, desired_gains, sizeof (gains));
|
||||
memcpy (outputs, desired_outputs, sizeof (outputs));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
VBAPanner::do_distribute_automated (AudioBuffer& src, BufferSet& obufs,
|
||||
framepos_t start, framepos_t end, pframes_t nframes, pan_t** buffers)
|
||||
{
|
||||
}
|
||||
|
||||
XMLNode&
|
||||
VBAPanner::get_state ()
|
||||
{
|
||||
return state (true);
|
||||
}
|
||||
|
||||
XMLNode&
|
||||
VBAPanner::state (bool full_state)
|
||||
{
|
||||
XMLNode& node (StreamPanner::get_state());
|
||||
node.add_property (X_("type"), VBAPanner::name);
|
||||
return node;
|
||||
}
|
||||
|
||||
int
|
||||
VBAPanner::set_state (const XMLNode& node, int /*version*/)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
StreamPanner*
|
||||
VBAPanner::factory (Panner& parent, Evoral::Parameter param, Speakers& s)
|
||||
{
|
||||
return new VBAPanner (parent, param, s);
|
||||
}
|
||||
|
|
@ -1,658 +0,0 @@
|
|||
/*
|
||||
This software is being provided to you, the licensee, by Ville Pulkki,
|
||||
under the following license. By obtaining, using and/or copying this
|
||||
software, you agree that you have read, understood, and will comply
|
||||
with these terms and conditions: Permission to use, copy, modify and
|
||||
distribute, including the right to grant others rights to distribute
|
||||
at any tier, this software and its documentation for any purpose and
|
||||
without fee or royalty is hereby granted, provided that you agree to
|
||||
comply with the following copyright notice and statements, including
|
||||
the disclaimer, and that the same appear on ALL copies of the software
|
||||
and documentation, including modifications that you make for internal
|
||||
use or for distribution:
|
||||
|
||||
Copyright 1998 by Ville Pulkki, Helsinki University of Technology. All
|
||||
rights reserved.
|
||||
|
||||
The software may be used, distributed, and included to commercial
|
||||
products without any charges. When included to a commercial product,
|
||||
the method "Vector Base Amplitude Panning" and its developer Ville
|
||||
Pulkki must be referred to in documentation.
|
||||
|
||||
This software is provided "as is", and Ville Pulkki or Helsinki
|
||||
University of Technology make no representations or warranties,
|
||||
expressed or implied. By way of example, but not limitation, Helsinki
|
||||
University of Technology or Ville Pulkki make no representations or
|
||||
warranties of merchantability or fitness for any particular purpose or
|
||||
that the use of the licensed software or documentation will not
|
||||
infringe any third party patents, copyrights, trademarks or other
|
||||
rights. The name of Ville Pulkki or Helsinki University of Technology
|
||||
may not be used in advertising or publicity pertaining to distribution
|
||||
of the software.
|
||||
*/
|
||||
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "pbd/cartesian.h"
|
||||
#include "ardour/vbap_speakers.h"
|
||||
|
||||
using namespace ARDOUR;
|
||||
using namespace PBD;
|
||||
using namespace std;
|
||||
|
||||
VBAPSpeakers* VBAPSpeakers::_instance = 0;
|
||||
|
||||
VBAPSpeakers&
|
||||
VBAPSpeakers::instance (Speakers& s)
|
||||
{
|
||||
if (_instance == 0) {
|
||||
_instance = new VBAPSpeakers (s);
|
||||
}
|
||||
|
||||
return *_instance;
|
||||
}
|
||||
|
||||
VBAPSpeakers::VBAPSpeakers (Speakers& s)
|
||||
: _dimension (2)
|
||||
, _speakers (s.speakers())
|
||||
{
|
||||
s.Changed.connect_same_thread (speaker_connection, boost::bind (&VBAPSpeakers::update, this));
|
||||
}
|
||||
|
||||
VBAPSpeakers::~VBAPSpeakers ()
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
VBAPSpeakers::update ()
|
||||
{
|
||||
int dim = 2;
|
||||
|
||||
for (vector<Speaker>::const_iterator i = _speakers.begin(); i != _speakers.end(); ++i) {
|
||||
if ((*i).angles().ele != 0.0) {
|
||||
cerr << "\n\n\nSPEAKER " << (*i).id << " has ele = " << (*i).angles().ele << "\n\n\n\n";
|
||||
dim = 3;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_dimension = dim;
|
||||
|
||||
cerr << "update with dimension = " << dim << " speakers = " << _speakers.size() << endl;
|
||||
|
||||
if (_speakers.size() < 2) {
|
||||
/* nothing to be done with less than two speakers */
|
||||
return;
|
||||
}
|
||||
|
||||
if (_dimension == 3) {
|
||||
ls_triplet_chain *ls_triplets = 0;
|
||||
choose_speaker_triplets (&ls_triplets);
|
||||
if (ls_triplets) {
|
||||
calculate_3x3_matrixes (ls_triplets);
|
||||
free (ls_triplets);
|
||||
}
|
||||
} else {
|
||||
choose_speaker_pairs ();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
VBAPSpeakers::choose_speaker_triplets(struct ls_triplet_chain **ls_triplets)
|
||||
{
|
||||
/* Selects the loudspeaker triplets, and
|
||||
calculates the inversion matrices for each selected triplet.
|
||||
A line (connection) is drawn between each loudspeaker. The lines
|
||||
denote the sides of the triangles. The triangles should not be
|
||||
intersecting. All crossing connections are searched and the
|
||||
longer connection is erased. This yields non-intesecting triangles,
|
||||
which can be used in panning.
|
||||
*/
|
||||
|
||||
int i,j,k,l,table_size;
|
||||
int n_speakers = _speakers.size ();
|
||||
int connections[n_speakers][n_speakers];
|
||||
float distance_table[((n_speakers * (n_speakers - 1)) / 2)];
|
||||
int distance_table_i[((n_speakers * (n_speakers - 1)) / 2)];
|
||||
int distance_table_j[((n_speakers * (n_speakers - 1)) / 2)];
|
||||
float distance;
|
||||
struct ls_triplet_chain *trip_ptr, *prev, *tmp_ptr;
|
||||
|
||||
if (n_speakers == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = 0; i < n_speakers; i++) {
|
||||
for (j = i+1; j < n_speakers; j++) {
|
||||
for(k=j+1;k<n_speakers;k++) {
|
||||
if (vol_p_side_lgth(i,j, k, _speakers) > MIN_VOL_P_SIDE_LGTH){
|
||||
connections[i][j]=1;
|
||||
connections[j][i]=1;
|
||||
connections[i][k]=1;
|
||||
connections[k][i]=1;
|
||||
connections[j][k]=1;
|
||||
connections[k][j]=1;
|
||||
add_ldsp_triplet(i,j,k,ls_triplets);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*calculate distancies between all speakers and sorting them*/
|
||||
table_size =(((n_speakers - 1) * (n_speakers)) / 2);
|
||||
for (i = 0; i < table_size; i++) {
|
||||
distance_table[i] = 100000.0;
|
||||
}
|
||||
|
||||
for (i = 0;i < n_speakers; i++) {
|
||||
for (j = i+1; j < n_speakers; j++) {
|
||||
if (connections[i][j] == 1) {
|
||||
distance = fabs(vec_angle(_speakers[i].coords(),_speakers[j].coords()));
|
||||
k=0;
|
||||
while(distance_table[k] < distance) {
|
||||
k++;
|
||||
}
|
||||
for (l = table_size - 1; l > k ; l--) {
|
||||
distance_table[l] = distance_table[l-1];
|
||||
distance_table_i[l] = distance_table_i[l-1];
|
||||
distance_table_j[l] = distance_table_j[l-1];
|
||||
}
|
||||
distance_table[k] = distance;
|
||||
distance_table_i[k] = i;
|
||||
distance_table_j[k] = j;
|
||||
} else
|
||||
table_size--;
|
||||
}
|
||||
}
|
||||
|
||||
/* disconnecting connections which are crossing shorter ones,
|
||||
starting from shortest one and removing all that cross it,
|
||||
and proceeding to next shortest */
|
||||
for (i = 0; i < table_size; i++) {
|
||||
int fst_ls = distance_table_i[i];
|
||||
int sec_ls = distance_table_j[i];
|
||||
if (connections[fst_ls][sec_ls] == 1) {
|
||||
for (j = 0; j < n_speakers; j++) {
|
||||
for (k = j+1; k < n_speakers; k++) {
|
||||
if ((j!=fst_ls) && (k != sec_ls) && (k!=fst_ls) && (j != sec_ls)){
|
||||
if (lines_intersect(fst_ls, sec_ls, j,k) == 1){
|
||||
connections[j][k] = 0;
|
||||
connections[k][j] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* remove triangles which had crossing sides
|
||||
with smaller triangles or include loudspeakers*/
|
||||
trip_ptr = *ls_triplets;
|
||||
prev = 0;
|
||||
while (trip_ptr != 0){
|
||||
i = trip_ptr->ls_nos[0];
|
||||
j = trip_ptr->ls_nos[1];
|
||||
k = trip_ptr->ls_nos[2];
|
||||
if (connections[i][j] == 0 ||
|
||||
connections[i][k] == 0 ||
|
||||
connections[j][k] == 0 ||
|
||||
any_ls_inside_triplet(i,j,k) == 1 ){
|
||||
if (prev != 0) {
|
||||
prev->next = trip_ptr->next;
|
||||
tmp_ptr = trip_ptr;
|
||||
trip_ptr = trip_ptr->next;
|
||||
free(tmp_ptr);
|
||||
} else {
|
||||
*ls_triplets = trip_ptr->next;
|
||||
tmp_ptr = trip_ptr;
|
||||
trip_ptr = trip_ptr->next;
|
||||
free(tmp_ptr);
|
||||
}
|
||||
} else {
|
||||
prev = trip_ptr;
|
||||
trip_ptr = trip_ptr->next;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
VBAPSpeakers::any_ls_inside_triplet(int a, int b, int c)
|
||||
{
|
||||
/* returns 1 if there is loudspeaker(s) inside given ls triplet */
|
||||
float invdet;
|
||||
const CartesianVector* lp1;
|
||||
const CartesianVector* lp2;
|
||||
const CartesianVector* lp3;
|
||||
float invmx[9];
|
||||
int i,j;
|
||||
float tmp;
|
||||
bool any_ls_inside;
|
||||
bool this_inside;
|
||||
int n_speakers = _speakers.size();
|
||||
|
||||
lp1 = &(_speakers[a].coords());
|
||||
lp2 = &(_speakers[b].coords());
|
||||
lp3 = &(_speakers[c].coords());
|
||||
|
||||
/* matrix inversion */
|
||||
invdet = 1.0 / ( lp1->x * ((lp2->y * lp3->z) - (lp2->z * lp3->y))
|
||||
- lp1->y * ((lp2->x * lp3->z) - (lp2->z * lp3->x))
|
||||
+ lp1->z * ((lp2->x * lp3->y) - (lp2->y * lp3->x)));
|
||||
|
||||
invmx[0] = ((lp2->y * lp3->z) - (lp2->z * lp3->y)) * invdet;
|
||||
invmx[3] = ((lp1->y * lp3->z) - (lp1->z * lp3->y)) * -invdet;
|
||||
invmx[6] = ((lp1->y * lp2->z) - (lp1->z * lp2->y)) * invdet;
|
||||
invmx[1] = ((lp2->x * lp3->z) - (lp2->z * lp3->x)) * -invdet;
|
||||
invmx[4] = ((lp1->x * lp3->z) - (lp1->z * lp3->x)) * invdet;
|
||||
invmx[7] = ((lp1->x * lp2->z) - (lp1->z * lp2->x)) * -invdet;
|
||||
invmx[2] = ((lp2->x * lp3->y) - (lp2->y * lp3->x)) * invdet;
|
||||
invmx[5] = ((lp1->x * lp3->y) - (lp1->y * lp3->x)) * -invdet;
|
||||
invmx[8] = ((lp1->x * lp2->y) - (lp1->y * lp2->x)) * invdet;
|
||||
|
||||
any_ls_inside = false;
|
||||
for (i = 0; i < n_speakers; i++) {
|
||||
if (i != a && i!=b && i != c) {
|
||||
this_inside = true;
|
||||
for (j = 0; j < 3; j++) {
|
||||
tmp = _speakers[i].coords().x * invmx[0 + j*3];
|
||||
tmp += _speakers[i].coords().y * invmx[1 + j*3];
|
||||
tmp += _speakers[i].coords().z * invmx[2 + j*3];
|
||||
if (tmp < -0.001) {
|
||||
this_inside = false;
|
||||
}
|
||||
}
|
||||
if (this_inside) {
|
||||
any_ls_inside = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return any_ls_inside;
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
VBAPSpeakers::add_ldsp_triplet(int i, int j, int k, struct ls_triplet_chain **ls_triplets)
|
||||
{
|
||||
/* adds i,j,k triplet to triplet chain*/
|
||||
|
||||
struct ls_triplet_chain *trip_ptr, *prev;
|
||||
trip_ptr = *ls_triplets;
|
||||
prev = 0;
|
||||
|
||||
while (trip_ptr != 0){
|
||||
prev = trip_ptr;
|
||||
trip_ptr = trip_ptr->next;
|
||||
}
|
||||
|
||||
trip_ptr = (struct ls_triplet_chain*) malloc (sizeof (struct ls_triplet_chain));
|
||||
|
||||
if (prev == 0) {
|
||||
*ls_triplets = trip_ptr;
|
||||
} else {
|
||||
prev->next = trip_ptr;
|
||||
}
|
||||
|
||||
trip_ptr->next = 0;
|
||||
trip_ptr->ls_nos[0] = i;
|
||||
trip_ptr->ls_nos[1] = j;
|
||||
trip_ptr->ls_nos[2] = k;
|
||||
}
|
||||
|
||||
float
|
||||
VBAPSpeakers::vec_angle(CartesianVector v1, CartesianVector v2)
|
||||
{
|
||||
float inner= ((v1.x*v2.x + v1.y*v2.y + v1.z*v2.z)/
|
||||
(vec_length(v1) * vec_length(v2)));
|
||||
|
||||
if (inner > 1.0) {
|
||||
inner= 1.0;
|
||||
}
|
||||
|
||||
if (inner < -1.0) {
|
||||
inner = -1.0;
|
||||
}
|
||||
|
||||
return fabsf((float) acos((double) inner));
|
||||
}
|
||||
|
||||
float
|
||||
VBAPSpeakers::vec_length(CartesianVector v1)
|
||||
{
|
||||
return (sqrt(v1.x*v1.x + v1.y*v1.y + v1.z*v1.z));
|
||||
}
|
||||
|
||||
float
|
||||
VBAPSpeakers::vec_prod(CartesianVector v1, CartesianVector v2)
|
||||
{
|
||||
return (v1.x*v2.x + v1.y*v2.y + v1.z*v2.z);
|
||||
}
|
||||
|
||||
float
|
||||
VBAPSpeakers::vol_p_side_lgth(int i, int j,int k, const vector<Speaker>& speakers)
|
||||
{
|
||||
/* calculate volume of the parallelepiped defined by the loudspeaker
|
||||
direction vectors and divide it with total length of the triangle sides.
|
||||
This is used when removing too narrow triangles. */
|
||||
|
||||
float volper, lgth;
|
||||
CartesianVector xprod;
|
||||
|
||||
cross_prod (speakers[i].coords(), speakers[j].coords(), &xprod);
|
||||
volper = fabsf (vec_prod(xprod, speakers[k].coords()));
|
||||
lgth = (fabsf (vec_angle(speakers[i].coords(), speakers[j].coords()))
|
||||
+ fabsf (vec_angle(speakers[i].coords(), speakers[k].coords()))
|
||||
+ fabsf (vec_angle(speakers[j].coords(), speakers[k].coords())));
|
||||
|
||||
if (lgth > 0.00001) {
|
||||
return volper / lgth;
|
||||
} else {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
VBAPSpeakers::cross_prod(CartesianVector v1,CartesianVector v2, CartesianVector *res)
|
||||
{
|
||||
float length;
|
||||
|
||||
res->x = (v1.y * v2.z ) - (v1.z * v2.y);
|
||||
res->y = (v1.z * v2.x ) - (v1.x * v2.z);
|
||||
res->z = (v1.x * v2.y ) - (v1.y * v2.x);
|
||||
|
||||
length = vec_length(*res);
|
||||
res->x /= length;
|
||||
res->y /= length;
|
||||
res->z /= length;
|
||||
}
|
||||
|
||||
int
|
||||
VBAPSpeakers::lines_intersect (int i, int j, int k, int l)
|
||||
{
|
||||
/* checks if two lines intersect on 3D sphere
|
||||
see theory in paper Pulkki, V. Lokki, T. "Creating Auditory Displays
|
||||
with Multiple Loudspeakers Using VBAP: A Case Study with
|
||||
DIVA Project" in International Conference on
|
||||
Auditory Displays -98. E-mail Ville.Pulkki@hut.fi
|
||||
if you want to have that paper.
|
||||
*/
|
||||
|
||||
CartesianVector v1;
|
||||
CartesianVector v2;
|
||||
CartesianVector v3, neg_v3;
|
||||
float dist_ij,dist_kl,dist_iv3,dist_jv3,dist_inv3,dist_jnv3;
|
||||
float dist_kv3,dist_lv3,dist_knv3,dist_lnv3;
|
||||
|
||||
cross_prod(_speakers[i].coords(),_speakers[j].coords(),&v1);
|
||||
cross_prod(_speakers[k].coords(),_speakers[l].coords(),&v2);
|
||||
cross_prod(v1,v2,&v3);
|
||||
|
||||
neg_v3.x= 0.0 - v3.x;
|
||||
neg_v3.y= 0.0 - v3.y;
|
||||
neg_v3.z= 0.0 - v3.z;
|
||||
|
||||
dist_ij = (vec_angle(_speakers[i].coords(),_speakers[j].coords()));
|
||||
dist_kl = (vec_angle(_speakers[k].coords(),_speakers[l].coords()));
|
||||
dist_iv3 = (vec_angle(_speakers[i].coords(),v3));
|
||||
dist_jv3 = (vec_angle(v3,_speakers[j].coords()));
|
||||
dist_inv3 = (vec_angle(_speakers[i].coords(),neg_v3));
|
||||
dist_jnv3 = (vec_angle(neg_v3,_speakers[j].coords()));
|
||||
dist_kv3 = (vec_angle(_speakers[k].coords(),v3));
|
||||
dist_lv3 = (vec_angle(v3,_speakers[l].coords()));
|
||||
dist_knv3 = (vec_angle(_speakers[k].coords(),neg_v3));
|
||||
dist_lnv3 = (vec_angle(neg_v3,_speakers[l].coords()));
|
||||
|
||||
/* if one of loudspeakers is close to crossing point, don't do anything*/
|
||||
|
||||
|
||||
if(fabsf(dist_iv3) <= 0.01 || fabsf(dist_jv3) <= 0.01 ||
|
||||
fabsf(dist_kv3) <= 0.01 || fabsf(dist_lv3) <= 0.01 ||
|
||||
fabsf(dist_inv3) <= 0.01 || fabsf(dist_jnv3) <= 0.01 ||
|
||||
fabsf(dist_knv3) <= 0.01 || fabsf(dist_lnv3) <= 0.01 ) {
|
||||
return(0);
|
||||
}
|
||||
|
||||
if (((fabsf(dist_ij - (dist_iv3 + dist_jv3)) <= 0.01 ) &&
|
||||
(fabsf(dist_kl - (dist_kv3 + dist_lv3)) <= 0.01)) ||
|
||||
((fabsf(dist_ij - (dist_inv3 + dist_jnv3)) <= 0.01) &&
|
||||
(fabsf(dist_kl - (dist_knv3 + dist_lnv3)) <= 0.01 ))) {
|
||||
return (1);
|
||||
} else {
|
||||
return (0);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
VBAPSpeakers::calculate_3x3_matrixes(struct ls_triplet_chain *ls_triplets)
|
||||
{
|
||||
/* Calculates the inverse matrices for 3D */
|
||||
float invdet;
|
||||
const CartesianVector* lp1;
|
||||
const CartesianVector* lp2;
|
||||
const CartesianVector* lp3;
|
||||
float *invmx;
|
||||
struct ls_triplet_chain *tr_ptr = ls_triplets;
|
||||
int triplet_count = 0;
|
||||
int triplet;
|
||||
|
||||
assert (tr_ptr);
|
||||
|
||||
/* counting triplet amount */
|
||||
|
||||
while (tr_ptr != 0) {
|
||||
triplet_count++;
|
||||
tr_ptr = tr_ptr->next;
|
||||
}
|
||||
|
||||
cerr << "@@@ triplets generate " << triplet_count << " of speaker tuples\n";
|
||||
|
||||
triplet = 0;
|
||||
|
||||
_matrices.clear ();
|
||||
_speaker_tuples.clear ();
|
||||
|
||||
for (int n = 0; n < triplet_count; ++n) {
|
||||
_matrices.push_back (threeDmatrix());
|
||||
_speaker_tuples.push_back (tmatrix());
|
||||
}
|
||||
|
||||
while (tr_ptr != 0) {
|
||||
lp1 = &(_speakers[tr_ptr->ls_nos[0]].coords());
|
||||
lp2 = &(_speakers[tr_ptr->ls_nos[1]].coords());
|
||||
lp3 = &(_speakers[tr_ptr->ls_nos[2]].coords());
|
||||
|
||||
/* matrix inversion */
|
||||
invmx = tr_ptr->inv_mx;
|
||||
invdet = 1.0 / ( lp1->x * ((lp2->y * lp3->z) - (lp2->z * lp3->y))
|
||||
- lp1->y * ((lp2->x * lp3->z) - (lp2->z * lp3->x))
|
||||
+ lp1->z * ((lp2->x * lp3->y) - (lp2->y * lp3->x)));
|
||||
|
||||
invmx[0] = ((lp2->y * lp3->z) - (lp2->z * lp3->y)) * invdet;
|
||||
invmx[3] = ((lp1->y * lp3->z) - (lp1->z * lp3->y)) * -invdet;
|
||||
invmx[6] = ((lp1->y * lp2->z) - (lp1->z * lp2->y)) * invdet;
|
||||
invmx[1] = ((lp2->x * lp3->z) - (lp2->z * lp3->x)) * -invdet;
|
||||
invmx[4] = ((lp1->x * lp3->z) - (lp1->z * lp3->x)) * invdet;
|
||||
invmx[7] = ((lp1->x * lp2->z) - (lp1->z * lp2->x)) * -invdet;
|
||||
invmx[2] = ((lp2->x * lp3->y) - (lp2->y * lp3->x)) * invdet;
|
||||
invmx[5] = ((lp1->x * lp3->y) - (lp1->y * lp3->x)) * -invdet;
|
||||
invmx[8] = ((lp1->x * lp2->y) - (lp1->y * lp2->x)) * invdet;
|
||||
|
||||
/* copy the matrix */
|
||||
|
||||
_matrices[triplet][0] = invmx[0];
|
||||
_matrices[triplet][1] = invmx[1];
|
||||
_matrices[triplet][2] = invmx[2];
|
||||
_matrices[triplet][3] = invmx[3];
|
||||
_matrices[triplet][4] = invmx[4];
|
||||
_matrices[triplet][5] = invmx[5];
|
||||
_matrices[triplet][6] = invmx[6];
|
||||
_matrices[triplet][7] = invmx[7];
|
||||
_matrices[triplet][8] = invmx[8];
|
||||
|
||||
_speaker_tuples[triplet][0] = tr_ptr->ls_nos[0];
|
||||
_speaker_tuples[triplet][1] = tr_ptr->ls_nos[1];
|
||||
_speaker_tuples[triplet][2] = tr_ptr->ls_nos[2];
|
||||
|
||||
cerr << "Triplet[" << triplet << "] = "
|
||||
<< tr_ptr->ls_nos[0] << " + "
|
||||
<< tr_ptr->ls_nos[1] << " + "
|
||||
<< tr_ptr->ls_nos[2] << endl;
|
||||
|
||||
triplet++;
|
||||
|
||||
tr_ptr = tr_ptr->next;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
VBAPSpeakers::choose_speaker_pairs (){
|
||||
|
||||
/* selects the loudspeaker pairs, calculates the inversion
|
||||
matrices and stores the data to a global array
|
||||
*/
|
||||
const int n_speakers = _speakers.size();
|
||||
const double AZIMUTH_DELTA_THRESHOLD_DEGREES = (180.0/M_PI) * (M_PI - 0.175);
|
||||
int sorted_speakers[n_speakers];
|
||||
bool exists[n_speakers];
|
||||
double inverse_matrix[n_speakers][4];
|
||||
int expected_pairs = 0;
|
||||
int pair;
|
||||
int speaker;
|
||||
|
||||
cerr << "CHOOSE PAIRS\n";
|
||||
|
||||
if (n_speakers == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (speaker = 0; speaker < n_speakers; ++speaker) {
|
||||
exists[speaker] = false;
|
||||
}
|
||||
|
||||
/* sort loudspeakers according their aximuth angle */
|
||||
sort_2D_lss (sorted_speakers);
|
||||
|
||||
/* adjacent loudspeakers are the loudspeaker pairs to be used.*/
|
||||
for (speaker = 0; speaker < n_speakers-1; speaker++) {
|
||||
|
||||
cerr << "Looking at "
|
||||
<< _speakers[sorted_speakers[speaker]].id << " @ " << _speakers[sorted_speakers[speaker]].angles().azi
|
||||
<< " and "
|
||||
<< _speakers[sorted_speakers[speaker+1]].id << " @ " << _speakers[sorted_speakers[speaker+1]].angles().azi
|
||||
<< " delta = "
|
||||
<< _speakers[sorted_speakers[speaker+1]].angles().azi - _speakers[sorted_speakers[speaker]].angles().azi
|
||||
<< endl;
|
||||
|
||||
if ((_speakers[sorted_speakers[speaker+1]].angles().azi -
|
||||
_speakers[sorted_speakers[speaker]].angles().azi) <= AZIMUTH_DELTA_THRESHOLD_DEGREES) {
|
||||
if (calc_2D_inv_tmatrix( _speakers[sorted_speakers[speaker]].angles().azi,
|
||||
_speakers[sorted_speakers[speaker+1]].angles().azi,
|
||||
inverse_matrix[speaker]) != 0){
|
||||
exists[speaker] = true;
|
||||
expected_pairs++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (((6.283 - _speakers[sorted_speakers[n_speakers-1]].angles().azi)
|
||||
+_speakers[sorted_speakers[0]].angles().azi) <= AZIMUTH_DELTA_THRESHOLD_DEGREES) {
|
||||
if (calc_2D_inv_tmatrix(_speakers[sorted_speakers[n_speakers-1]].angles().azi,
|
||||
_speakers[sorted_speakers[0]].angles().azi,
|
||||
inverse_matrix[n_speakers-1]) != 0) {
|
||||
exists[n_speakers-1] = true;
|
||||
expected_pairs++;
|
||||
}
|
||||
}
|
||||
|
||||
pair = 0;
|
||||
|
||||
_matrices.clear ();
|
||||
_speaker_tuples.clear ();
|
||||
|
||||
for (int n = 0; n < expected_pairs; ++n) {
|
||||
_matrices.push_back (twoDmatrix());
|
||||
_speaker_tuples.push_back (tmatrix());
|
||||
}
|
||||
|
||||
for (speaker = 0; speaker < n_speakers - 1; speaker++) {
|
||||
if (exists[speaker]) {
|
||||
_matrices[pair][0] = inverse_matrix[speaker][0];
|
||||
_matrices[pair][1] = inverse_matrix[speaker][1];
|
||||
_matrices[pair][2] = inverse_matrix[speaker][2];
|
||||
_matrices[pair][3] = inverse_matrix[speaker][3];
|
||||
|
||||
_speaker_tuples[pair][0] = sorted_speakers[speaker];
|
||||
_speaker_tuples[pair][1] = sorted_speakers[speaker+1];
|
||||
|
||||
cerr << "PAIR[" << pair << "] = " << sorted_speakers[speaker] << " + " << sorted_speakers[speaker+1] << endl;
|
||||
|
||||
pair++;
|
||||
}
|
||||
}
|
||||
|
||||
if (exists[n_speakers-1]) {
|
||||
_matrices[pair][0] = inverse_matrix[speaker][0];
|
||||
_matrices[pair][1] = inverse_matrix[speaker][1];
|
||||
_matrices[pair][2] = inverse_matrix[speaker][2];
|
||||
_matrices[pair][3] = inverse_matrix[speaker][3];
|
||||
|
||||
_speaker_tuples[pair][0] = sorted_speakers[n_speakers-1];
|
||||
_speaker_tuples[pair][1] = sorted_speakers[0];
|
||||
|
||||
cerr << "PAIR[" << pair << "] = " << sorted_speakers[n_speakers-1] << " + " << sorted_speakers[0] << endl;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
VBAPSpeakers::sort_2D_lss (int* sorted_speakers)
|
||||
{
|
||||
vector<Speaker> tmp = _speakers;
|
||||
vector<Speaker>::iterator s;
|
||||
azimuth_sorter sorter;
|
||||
int n;
|
||||
|
||||
sort (tmp.begin(), tmp.end(), sorter);
|
||||
|
||||
for (n = 0, s = tmp.begin(); s != tmp.end(); ++s, ++n) {
|
||||
sorted_speakers[n] = (*s).id;
|
||||
cerr << "Sorted[" << n << "] = " << (*s).id << endl;
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
VBAPSpeakers::calc_2D_inv_tmatrix (double azi1, double azi2, double* inverse_matrix)
|
||||
{
|
||||
double x1,x2,x3,x4;
|
||||
double det;
|
||||
|
||||
x1 = cos (azi1);
|
||||
x2 = sin (azi1);
|
||||
x3 = cos (azi2);
|
||||
x4 = sin (azi2);
|
||||
det = (x1 * x4) - ( x3 * x2 );
|
||||
|
||||
if (fabs(det) <= 0.001) {
|
||||
|
||||
inverse_matrix[0] = 0.0;
|
||||
inverse_matrix[1] = 0.0;
|
||||
inverse_matrix[2] = 0.0;
|
||||
inverse_matrix[3] = 0.0;
|
||||
|
||||
return 0;
|
||||
|
||||
} else {
|
||||
|
||||
inverse_matrix[0] = x4 / det;
|
||||
inverse_matrix[1] = -x3 / det;
|
||||
inverse_matrix[2] = -x2 / det;
|
||||
inverse_matrix[3] = x1 / det;
|
||||
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -137,7 +137,12 @@ libardour_sources = [
|
|||
'named_selection.cc',
|
||||
'onset_detector.cc',
|
||||
'operations.cc',
|
||||
'panner.cc',
|
||||
'pan_controllable.cc',
|
||||
'pannable.cc',
|
||||
'panner.cc',
|
||||
'panner_manager.cc',
|
||||
'panner_search_path.cc',
|
||||
'panner_shell.cc',
|
||||
'pcm_utils.cc',
|
||||
'pi_controller.cc',
|
||||
'playlist.cc',
|
||||
|
@ -205,8 +210,6 @@ libardour_sources = [
|
|||
'unknown_processor.cc',
|
||||
'user_bundle.cc',
|
||||
'utils.cc',
|
||||
'vbap.cc',
|
||||
'vbap_speakers.cc',
|
||||
'version.cc'
|
||||
]
|
||||
|
||||
|
|
|
@ -115,6 +115,8 @@ public:
|
|||
void slide (iterator before, double distance);
|
||||
void shift (double before, double distance);
|
||||
|
||||
virtual bool clamp_value (double& when, double& value) const { return true; }
|
||||
|
||||
void rt_add (double when, double value);
|
||||
void add (double when, double value);
|
||||
void fast_simple_add (double when, double value);
|
||||
|
|
|
@ -394,6 +394,10 @@ ControlList::add (double when, double value)
|
|||
control surface (GUI, MIDI, OSC etc)
|
||||
*/
|
||||
|
||||
if (!clamp_value (when, value)) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
Glib::Mutex::Lock lm (_lock);
|
||||
ControlEvent cp (when, 0.0f);
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <string>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <gtkmm/container.h>
|
||||
#include <gtkmm/treeview.h>
|
||||
#include <gdkmm/window.h> /* for WMDecoration */
|
||||
#include <gdkmm/pixbuf.h>
|
||||
|
@ -86,6 +87,8 @@ namespace Gtkmm2ext {
|
|||
|
||||
int physical_screen_height (Glib::RefPtr<Gdk::Window>);
|
||||
int physical_screen_width (Glib::RefPtr<Gdk::Window>);
|
||||
|
||||
void container_clear (Gtk::Container&);
|
||||
};
|
||||
|
||||
#endif /* __gtkmm2ext_utils_h__ */
|
||||
|
|
|
@ -377,3 +377,12 @@ Gtkmm2ext::physical_screen_width (Glib::RefPtr<Gdk::Window> win)
|
|||
return gdk_screen_get_width (scr);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Gtkmm2ext::container_clear (Gtk::Container& c)
|
||||
{
|
||||
list<Gtk::Widget*> children = c.get_children();
|
||||
for (list<Gtk::Widget*>::iterator child = children.begin(); child != children.end(); ++child) {
|
||||
c.remove (**child);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,16 +43,17 @@
|
|||
|
||||
#include "ardour/session.h"
|
||||
#include "ardour/panner.h"
|
||||
#include "ardour/panner_1in2out.h"
|
||||
#include "ardour/utils.h"
|
||||
#include "ardour/audio_buffer.h"
|
||||
|
||||
#include "ardour/debug.h"
|
||||
#include "ardour/runtime_functions.h"
|
||||
#include "ardour/buffer_set.h"
|
||||
#include "ardour/audio_buffer.h"
|
||||
#include "ardour/vbap.h"
|
||||
#include "ardour/pannable.h"
|
||||
|
||||
#include "i18n.h"
|
||||
#include "panner_1in2out.h"
|
||||
|
||||
#include "pbd/mathfix.h"
|
||||
|
||||
|
@ -62,22 +63,27 @@ using namespace PBD;
|
|||
|
||||
static PanPluginDescriptor _descriptor = {
|
||||
"Mono to Stereo Panner",
|
||||
1, 1, 2, 2,
|
||||
1, 2,
|
||||
Panner1in2out::factory
|
||||
};
|
||||
|
||||
extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; }
|
||||
extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } }
|
||||
|
||||
Panner1in2out::Panner1in2out (PannerShell& p)
|
||||
Panner1in2out::Panner1in2out (boost::shared_ptr<Pannable> p)
|
||||
: Panner (p)
|
||||
, _position (new PanControllable (parent.session(), _("position"), this, Evoral::Parameter(PanAzimuthAutomation, 0, 0)))
|
||||
, left (0.5)
|
||||
, right (0.5)
|
||||
, left_interp (left)
|
||||
, right_interp (right)
|
||||
{
|
||||
desired_left = left;
|
||||
desired_right = right;
|
||||
if (!_pannable->has_state()) {
|
||||
_pannable->pan_azimuth_control->set_value (0.5);
|
||||
}
|
||||
|
||||
update ();
|
||||
|
||||
left = desired_left;
|
||||
right = desired_right;
|
||||
left_interp = left;
|
||||
right_interp = right;
|
||||
|
||||
_pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&Panner1in2out::update, this));
|
||||
}
|
||||
|
||||
Panner1in2out::~Panner1in2out ()
|
||||
|
@ -85,14 +91,44 @@ Panner1in2out::~Panner1in2out ()
|
|||
}
|
||||
|
||||
void
|
||||
Panner1in2out::set_position (double p)
|
||||
Panner1in2out::update ()
|
||||
{
|
||||
_desired_right = p;
|
||||
_desired_left = 1 - p;
|
||||
float panR, panL;
|
||||
float const pan_law_attenuation = -3.0f;
|
||||
float const scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
|
||||
|
||||
panR = _pannable->pan_azimuth_control->get_value();
|
||||
panL = 1 - panR;
|
||||
|
||||
desired_left = panL * (scale * panL + 1.0f - scale);
|
||||
desired_right = panR * (scale * panR + 1.0f - scale);
|
||||
}
|
||||
|
||||
void
|
||||
Panner1in2out::do_distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t /* not used */)
|
||||
Panner1in2out::set_position (double p)
|
||||
{
|
||||
if (clamp_position (p)) {
|
||||
_pannable->pan_azimuth_control->set_value (p);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
Panner1in2out::clamp_position (double& p)
|
||||
{
|
||||
/* any position between 0.0 and 1.0 is legal */
|
||||
DEBUG_TRACE (DEBUG::Panning, string_compose ("want to move panner to %1 - always allowed in 0.0-1.0 range\n", p));
|
||||
p = max (min (p, 1.0), 0.0);
|
||||
return true;
|
||||
}
|
||||
|
||||
double
|
||||
Panner1in2out::position () const
|
||||
{
|
||||
return _pannable->pan_azimuth_control->get_value ();
|
||||
}
|
||||
|
||||
void
|
||||
Panner1in2out::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t /* not used */)
|
||||
{
|
||||
assert (obufs.count().n_audio() == 2);
|
||||
|
||||
|
@ -100,17 +136,13 @@ Panner1in2out::do_distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t
|
|||
Sample* dst;
|
||||
pan_t pan;
|
||||
|
||||
if (_muted) {
|
||||
return;
|
||||
}
|
||||
|
||||
Sample* const src = srcbuf.data();
|
||||
|
||||
/* LEFT OUTPUT */
|
||||
|
||||
dst = obufs.get_audio(0).data();
|
||||
|
||||
if (fabsf ((delta = (left[which] - desired_left[which]))) > 0.002) { // about 1 degree of arc
|
||||
if (fabsf ((delta = (left - desired_left))) > 0.002) { // about 1 degree of arc
|
||||
|
||||
/* we've moving the pan by an appreciable amount, so we must
|
||||
interpolate over 64 frames or nframes, whichever is smaller */
|
||||
|
@ -121,23 +153,23 @@ Panner1in2out::do_distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t
|
|||
delta = -(delta / (float) (limit));
|
||||
|
||||
for (n = 0; n < limit; n++) {
|
||||
left_interp[which] = left_interp[which] + delta;
|
||||
left = left_interp[which] + 0.9 * (left[which] - left_interp[which]);
|
||||
dst[n] += src[n] * left[which] * gain_coeff;
|
||||
left_interp = left_interp + delta;
|
||||
left = left_interp + 0.9 * (left - left_interp);
|
||||
dst[n] += src[n] * left * gain_coeff;
|
||||
}
|
||||
|
||||
/* then pan the rest of the buffer; no need for interpolation for this bit */
|
||||
|
||||
pan = left[which] * gain_coeff;
|
||||
pan = left * gain_coeff;
|
||||
|
||||
mix_buffers_with_gain (dst+n,src+n,nframes-n,pan);
|
||||
|
||||
} else {
|
||||
|
||||
left[which] = desired_left[which];
|
||||
left_interp[which] = left[which];
|
||||
left = desired_left;
|
||||
left_interp = left;
|
||||
|
||||
if ((pan = (left[which] * gain_coeff)) != 1.0f) {
|
||||
if ((pan = (left * gain_coeff)) != 1.0f) {
|
||||
|
||||
if (pan != 0.0f) {
|
||||
|
||||
|
@ -165,7 +197,7 @@ Panner1in2out::do_distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t
|
|||
|
||||
dst = obufs.get_audio(1).data();
|
||||
|
||||
if (fabsf ((delta = (right[which] - desired_right[which]))) > 0.002) { // about 1 degree of arc
|
||||
if (fabsf ((delta = (right - desired_right))) > 0.002) { // about 1 degree of arc
|
||||
|
||||
/* we're moving the pan by an appreciable amount, so we must
|
||||
interpolate over 64 frames or nframes, whichever is smaller */
|
||||
|
@ -176,14 +208,14 @@ Panner1in2out::do_distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t
|
|||
delta = -(delta / (float) (limit));
|
||||
|
||||
for (n = 0; n < limit; n++) {
|
||||
right_interp[which] = right_interp[which] + delta;
|
||||
right[which] = right_interp[which] + 0.9 * (right[which] - right_interp[which]);
|
||||
dst[n] += src[n] * right[which] * gain_coeff;
|
||||
right_interp = right_interp + delta;
|
||||
right = right_interp + 0.9 * (right - right_interp);
|
||||
dst[n] += src[n] * right * gain_coeff;
|
||||
}
|
||||
|
||||
/* then pan the rest of the buffer, no need for interpolation for this bit */
|
||||
|
||||
pan = right[which] * gain_coeff;
|
||||
pan = right * gain_coeff;
|
||||
|
||||
mix_buffers_with_gain(dst+n,src+n,nframes-n,pan);
|
||||
|
||||
|
@ -191,10 +223,10 @@ Panner1in2out::do_distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t
|
|||
|
||||
} else {
|
||||
|
||||
right[which] = desired_right[which];
|
||||
right_interp[which] = right[which];
|
||||
right = desired_right;
|
||||
right_interp = right;
|
||||
|
||||
if ((pan = (right[which] * gain_coeff)) != 1.0f) {
|
||||
if ((pan = (right * gain_coeff)) != 1.0f) {
|
||||
|
||||
if (pan != 0.0f) {
|
||||
|
||||
|
@ -217,19 +249,116 @@ Panner1in2out::do_distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t
|
|||
|
||||
}
|
||||
|
||||
string
|
||||
Panner1in2out::describe_parameter (Evoral::Parameter param)
|
||||
void
|
||||
Panner1in2out::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs,
|
||||
framepos_t start, framepos_t end, pframes_t nframes,
|
||||
pan_t** buffers, uint32_t which)
|
||||
{
|
||||
switch (param.type()) {
|
||||
case PanWidthAutomation:
|
||||
return "Pan:width";
|
||||
case PanAzimuthAutomation:
|
||||
return "Pan:position";
|
||||
case PanElevationAutomation:
|
||||
error << X_("stereo panner should not have elevation control") << endmsg;
|
||||
return "Pan:elevation";
|
||||
}
|
||||
|
||||
return Automatable::describe_parameter (param);
|
||||
assert (obufs.count().n_audio() == 2);
|
||||
|
||||
Sample* dst;
|
||||
pan_t* pbuf;
|
||||
Sample* const src = srcbuf.data();
|
||||
pan_t* const position = buffers[0];
|
||||
|
||||
/* fetch positional data */
|
||||
|
||||
if (!_pannable->pan_azimuth_control->list()->curve().rt_safe_get_vector (start, end, position, nframes)) {
|
||||
/* fallback */
|
||||
distribute_one (srcbuf, obufs, 1.0, nframes, which);
|
||||
return;
|
||||
}
|
||||
|
||||
/* apply pan law to convert positional data into pan coefficients for
|
||||
each buffer (output)
|
||||
*/
|
||||
|
||||
const float pan_law_attenuation = -3.0f;
|
||||
const float scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f);
|
||||
|
||||
for (pframes_t n = 0; n < nframes; ++n) {
|
||||
|
||||
float panR = position[n];
|
||||
const float panL = 1 - panR;
|
||||
|
||||
/* note that are overwriting buffers, but its OK
|
||||
because we're finished with their old contents
|
||||
(position automation data) and are
|
||||
replacing it with panning/gain coefficients
|
||||
that we need to actually process the data.
|
||||
*/
|
||||
|
||||
buffers[0][n] = panL * (scale * panL + 1.0f - scale);
|
||||
buffers[1][n] = panR * (scale * panR + 1.0f - scale);
|
||||
}
|
||||
|
||||
/* LEFT OUTPUT */
|
||||
|
||||
dst = obufs.get_audio(0).data();
|
||||
pbuf = buffers[0];
|
||||
|
||||
for (pframes_t n = 0; n < nframes; ++n) {
|
||||
dst[n] += src[n] * pbuf[n];
|
||||
}
|
||||
|
||||
/* XXX it would be nice to mark the buffer as written to */
|
||||
|
||||
/* RIGHT OUTPUT */
|
||||
|
||||
dst = obufs.get_audio(1).data();
|
||||
pbuf = buffers[1];
|
||||
|
||||
for (pframes_t n = 0; n < nframes; ++n) {
|
||||
dst[n] += src[n] * pbuf[n];
|
||||
}
|
||||
|
||||
/* XXX it would be nice to mark the buffer as written to */
|
||||
}
|
||||
|
||||
|
||||
Panner*
|
||||
Panner1in2out::factory (boost::shared_ptr<Pannable> p, Speakers& /* ignored */)
|
||||
{
|
||||
return new Panner1in2out (p);
|
||||
}
|
||||
|
||||
XMLNode&
|
||||
Panner1in2out::get_state (void)
|
||||
{
|
||||
return state (true);
|
||||
}
|
||||
|
||||
XMLNode&
|
||||
Panner1in2out::state (bool /*full_state*/)
|
||||
{
|
||||
XMLNode& root (Panner::get_state ());
|
||||
root.add_property (X_("type"), _descriptor.name);
|
||||
return root;
|
||||
}
|
||||
|
||||
int
|
||||
Panner1in2out::set_state (const XMLNode& node, int version)
|
||||
{
|
||||
LocaleGuard lg (X_("POSIX"));
|
||||
Panner::set_state (node, version);
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::set<Evoral::Parameter>
|
||||
Panner1in2out::what_can_be_automated() const
|
||||
{
|
||||
set<Evoral::Parameter> s;
|
||||
s.insert (Evoral::Parameter (PanAzimuthAutomation));
|
||||
return s;
|
||||
}
|
||||
|
||||
string
|
||||
Panner1in2out::describe_parameter (Evoral::Parameter p)
|
||||
{
|
||||
switch (p.type()) {
|
||||
case PanAzimuthAutomation:
|
||||
return _("L/R");
|
||||
default:
|
||||
return _pannable->describe_parameter (p);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -31,40 +31,56 @@
|
|||
#include "pbd/cartesian.h"
|
||||
|
||||
#include "ardour/types.h"
|
||||
#include "ardour/automation_control.h"
|
||||
#include "ardour/automatable.h"
|
||||
#include "ardour/panner.h"
|
||||
|
||||
namespace ARDOUR {
|
||||
|
||||
class PannerStereoBase : public class Panner
|
||||
class Panner1in2out : public Panner
|
||||
{
|
||||
public:
|
||||
PannerStereoBase (Panner&);
|
||||
~PannerStereoBase ();
|
||||
Panner1in2out (boost::shared_ptr<Pannable>);
|
||||
~Panner1in2out ();
|
||||
|
||||
void set_position (double);
|
||||
bool clamp_position (double&);
|
||||
|
||||
double position() const;
|
||||
|
||||
ChanCount in() const { return ChanCount (DataType::AUDIO, 1); }
|
||||
ChanCount out() const { return ChanCount (DataType::AUDIO, 2); }
|
||||
|
||||
std::set<Evoral::Parameter> what_can_be_automated() const;
|
||||
|
||||
/* this class just leaves the pan law itself to be defined
|
||||
by the update(), do_distribute_automated()
|
||||
methods. derived classes also need a factory method
|
||||
and a type name. See EqualPowerStereoPanner as an example.
|
||||
*/
|
||||
|
||||
void do_distribute (AudioBuffer& src, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes);
|
||||
static Panner* factory (boost::shared_ptr<Pannable>, Speakers&);
|
||||
|
||||
std::string describe_parameter (Evoral::Parameter);
|
||||
|
||||
XMLNode& state (bool full_state);
|
||||
XMLNode& get_state (void);
|
||||
int set_state (const XMLNode&, int version);
|
||||
|
||||
protected:
|
||||
boost::shared_ptr<AutomationControl> _position;
|
||||
float left;
|
||||
float right;
|
||||
float desired_left;
|
||||
float desired_right;
|
||||
float left_interp;
|
||||
float right_interp;
|
||||
|
||||
void distribute_one (AudioBuffer& src, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t which);
|
||||
void distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs,
|
||||
framepos_t start, framepos_t end, pframes_t nframes,
|
||||
pan_t** buffers, uint32_t which);
|
||||
|
||||
void update ();
|
||||
};
|
||||
|
||||
}
|
||||
} // namespace
|
||||
|
||||
#endif /* __ardour_panner_1in2out_h__ */
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env python
|
||||
import autowaf
|
||||
import os
|
||||
|
||||
# Library version (UNIX style major, minor, micro)
|
||||
# major increment <=> incompatible changes
|
||||
# minor increment <=> compatible changes (additions)
|
||||
# micro increment <=> no interface changes
|
||||
LIBARDOUR_PAN1IN2OUT_LIB_VERSION = '1.0.0'
|
||||
|
||||
# Mandatory variables
|
||||
srcdir = '.'
|
||||
blddir = 'build'
|
||||
|
||||
def set_options(opt):
|
||||
autowaf.set_options(opt)
|
||||
|
||||
def configure(conf):
|
||||
autowaf.configure(conf)
|
||||
|
||||
def build(bld):
|
||||
obj = bld.new_task_gen('cxx', 'shlib')
|
||||
obj.source = [ 'panner_1in2out.cc' ]
|
||||
obj.export_incdirs = ['.']
|
||||
obj.cxxflags = '-DPACKAGE="libardour_pan1in2out"'
|
||||
obj.includes = ['.']
|
||||
obj.name = 'libardour_pan1in2out'
|
||||
obj.target = 'pan1in2out'
|
||||
obj.uselib_local = 'libardour libardour_cp libpbd'
|
||||
obj.vnum = LIBARDOUR_PAN1IN2OUT_LIB_VERSION
|
||||
obj.install_path = os.path.join(bld.env['LIBDIR'], 'ardour3', 'panners')
|
||||
|
||||
def shutdown():
|
||||
autowaf.shutdown()
|
||||
|
|
@ -71,21 +71,21 @@ extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; }
|
|||
Panner2in2out::Panner2in2out (boost::shared_ptr<Pannable> p)
|
||||
: Panner (p)
|
||||
{
|
||||
_pannable->pan_azimuth_control->set_value (0.5);
|
||||
_pannable->pan_width_control->set_value (1.0);
|
||||
|
||||
/* LEFT SIGNAL, panned hard left */
|
||||
left[0] = 1.0;
|
||||
right[0] = 0.0;
|
||||
desired_left[0] = left_interp[0] = left[0];
|
||||
desired_right[0] = right_interp[0] = right[0];
|
||||
|
||||
/* RIGHT SIGNAL, panned hard right */
|
||||
left[1] = 0;
|
||||
right[1] = 1.0;
|
||||
desired_left[1] = left_interp[1] = left[1];
|
||||
desired_right[1] = right_interp[1] = right[1];
|
||||
|
||||
if (!_pannable->has_state()) {
|
||||
_pannable->pan_azimuth_control->set_value (0.5);
|
||||
_pannable->pan_width_control->set_value (1.0);
|
||||
}
|
||||
|
||||
update ();
|
||||
|
||||
/* LEFT SIGNAL */
|
||||
left_interp[0] = left[0] = desired_left[0];
|
||||
right_interp[0] = right[0] = desired_right[0];
|
||||
|
||||
/* RIGHT SIGNAL */
|
||||
left_interp[1] = left[1] = desired_left[1];
|
||||
right_interp[1] = right[1] = desired_right[1];
|
||||
|
||||
_pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&Panner2in2out::update, this));
|
||||
_pannable->pan_width_control->Changed.connect_same_thread (*this, boost::bind (&Panner2in2out::update, this));
|
||||
}
|
||||
|
@ -141,8 +141,6 @@ Panner2in2out::update ()
|
|||
const double width = _pannable->pan_width_control->get_value();
|
||||
const double direction_as_lr_fract = _pannable->pan_azimuth_control->get_value();
|
||||
|
||||
cerr << "new pan values width=" << width << " LR = " << direction_as_lr_fract << endl;
|
||||
|
||||
if (width < 0.0) {
|
||||
pos[0] = direction_as_lr_fract + (width/2.0); // left signal lr_fract
|
||||
pos[1] = direction_as_lr_fract - (width/2.0); // right signal lr_fract
|
||||
|
@ -190,20 +188,14 @@ Panner2in2out::clamp_width (double& w)
|
|||
bool
|
||||
Panner2in2out::clamp_stereo_pan (double& direction_as_lr_fract, double& width)
|
||||
{
|
||||
double r_pos = direction_as_lr_fract + (width/2.0);
|
||||
double l_pos = direction_as_lr_fract - (width/2.0);
|
||||
bool can_move_left = true;
|
||||
bool can_move_right = true;
|
||||
double r_pos;
|
||||
double l_pos;
|
||||
|
||||
cerr << "Clamp pos = " << direction_as_lr_fract << " w = " << width << endl;
|
||||
width = max (min (width, 1.0), -1.0);
|
||||
direction_as_lr_fract = max (min (direction_as_lr_fract, 1.0), 0.0);
|
||||
|
||||
if (width > 1.0 || width < 1.0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (direction_as_lr_fract > 1.0 || direction_as_lr_fract < 0.0) {
|
||||
return false;
|
||||
}
|
||||
r_pos = direction_as_lr_fract + (width/2.0);
|
||||
l_pos = direction_as_lr_fract - (width/2.0);
|
||||
|
||||
if (width < 0.0) {
|
||||
swap (r_pos, l_pos);
|
||||
|
@ -213,19 +205,20 @@ Panner2in2out::clamp_stereo_pan (double& direction_as_lr_fract, double& width)
|
|||
is already there, we're not moving the left signal.
|
||||
*/
|
||||
|
||||
if (l_pos <= 0.0 && desired_left[0] <= 0.0) {
|
||||
can_move_left = false;
|
||||
if (l_pos < 0.0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* if the new right position is less than or equal to 1.0 (hard right) and the right panner
|
||||
is already there, we're not moving the right signal.
|
||||
*/
|
||||
|
||||
if (r_pos >= 1.0 && desired_right[1] >= 1.0) {
|
||||
can_move_right = false;
|
||||
if (r_pos > 1.0) {
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
return can_move_left && can_move_right;
|
||||
return true;
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -459,3 +452,24 @@ Panner2in2out::set_state (const XMLNode& node, int version)
|
|||
return 0;
|
||||
}
|
||||
|
||||
std::set<Evoral::Parameter>
|
||||
Panner2in2out::what_can_be_automated() const
|
||||
{
|
||||
set<Evoral::Parameter> s;
|
||||
s.insert (Evoral::Parameter (PanAzimuthAutomation));
|
||||
s.insert (Evoral::Parameter (PanWidthAutomation));
|
||||
return s;
|
||||
}
|
||||
|
||||
string
|
||||
Panner2in2out::describe_parameter (Evoral::Parameter p)
|
||||
{
|
||||
switch (p.type()) {
|
||||
case PanAzimuthAutomation:
|
||||
return _("L/R");
|
||||
case PanWidthAutomation:
|
||||
return _("Width");
|
||||
default:
|
||||
return _pannable->describe_parameter (p);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,8 +55,12 @@ class Panner2in2out : public Panner
|
|||
double position () const;
|
||||
double width () const;
|
||||
|
||||
std::set<Evoral::Parameter> what_can_be_automated() const;
|
||||
|
||||
static Panner* factory (boost::shared_ptr<Pannable>, Speakers&);
|
||||
|
||||
std::string describe_parameter (Evoral::Parameter);
|
||||
|
||||
XMLNode& state (bool full_state);
|
||||
XMLNode& get_state (void);
|
||||
int set_state (const XMLNode&, int version);
|
||||
|
|
|
@ -12,6 +12,12 @@ LIBARDOUR_PAN2IN2OUT_LIB_VERSION = '1.0.0'
|
|||
srcdir = '.'
|
||||
blddir = 'build'
|
||||
|
||||
def set_options(opt):
|
||||
autowaf.set_options(opt)
|
||||
|
||||
def configure(conf):
|
||||
autowaf.configure(conf)
|
||||
|
||||
def build(bld):
|
||||
obj = bld.new_task_gen('cxx', 'shlib')
|
||||
obj.source = [ 'panner_2in2out.cc' ]
|
||||
|
|
|
@ -10,27 +10,26 @@
|
|||
|
||||
#include "ardour/pannable.h"
|
||||
#include "ardour/speakers.h"
|
||||
#include "ardour/vbap.h"
|
||||
#include "ardour/vbap_speakers.h"
|
||||
#include "ardour/audio_buffer.h"
|
||||
#include "ardour/buffer_set.h"
|
||||
#include "ardour/pan_controllable.h"
|
||||
|
||||
#include "vbap.h"
|
||||
#include "vbap_speakers.h"
|
||||
|
||||
using namespace PBD;
|
||||
using namespace ARDOUR;
|
||||
using namespace std;
|
||||
|
||||
static PanPluginDescriptor _descriptor = {
|
||||
"VBAP 2D panner",
|
||||
1, -1, 2, -1,
|
||||
-1, -1,
|
||||
VBAPanner::factory
|
||||
};
|
||||
|
||||
extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } }
|
||||
|
||||
VBAPanner::Signal::Signal (Session& session, VBAPanner& p, uint32_t n)
|
||||
: azimuth_control (new PanControllable (session, string_compose (_("azimuth %1"), n+1), &p, Evoral::Parameter (PanAzimuthAutomation, 0, n)))
|
||||
, elevation_control (new PanControllable (session, string_compose (_("elevation %1"), n+1), &p, Evoral::Parameter (PanElevationAutomation, 0, n)))
|
||||
{
|
||||
gains[0] = gains[1] = gains[2] = 0;
|
||||
desired_gains[0] = desired_gains[1] = desired_gains[2] = 0;
|
||||
|
@ -40,44 +39,86 @@ VBAPanner::Signal::Signal (Session& session, VBAPanner& p, uint32_t n)
|
|||
|
||||
VBAPanner::VBAPanner (boost::shared_ptr<Pannable> p, Speakers& s)
|
||||
: Panner (p)
|
||||
, _dirty (true)
|
||||
, _speakers (VBAPSpeakers::instance (s))
|
||||
{
|
||||
_pannable->pan_azimuth_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
|
||||
_pannable->pan_width_control->Changed.connect_same_thread (*this, boost::bind (&VBAPanner::update, this));
|
||||
|
||||
update ();
|
||||
}
|
||||
|
||||
VBAPanner::~VBAPanner ()
|
||||
{
|
||||
for (vector<Signal*>::iterator i = _signals.begin(); i != _signals.end(); ++i) {
|
||||
delete *i;
|
||||
}
|
||||
clear_signals ();
|
||||
}
|
||||
|
||||
void
|
||||
VBAPanner::configure_io (const ChanCount& in, const ChanCount& /* ignored - we use Speakers */)
|
||||
VBAPanner::clear_signals ()
|
||||
{
|
||||
for (vector<Signal*>::iterator i = _signals.begin(); i != _signals.end(); ++i) {
|
||||
delete *i;
|
||||
}
|
||||
_signals.clear ();
|
||||
}
|
||||
|
||||
void
|
||||
VBAPanner::configure_io (ChanCount in, ChanCount /* ignored - we use Speakers */)
|
||||
{
|
||||
uint32_t n = in.n_audio();
|
||||
|
||||
/* 2d panning: spread signals equally around a circle */
|
||||
|
||||
double degree_step = 360.0 / _speakers.n_speakers();
|
||||
double deg;
|
||||
|
||||
/* even number of signals? make sure the top two are either side of "top".
|
||||
otherwise, just start at the "top" (90.0 degrees) and rotate around
|
||||
*/
|
||||
|
||||
if (n % 2) {
|
||||
deg = 90.0 - degree_step;
|
||||
} else {
|
||||
deg = 90.0;
|
||||
}
|
||||
clear_signals ();
|
||||
|
||||
_signals.clear ();
|
||||
|
||||
for (uint32_t i = 0; i < n; ++i) {
|
||||
_signals.push_back (new Signal (_pannable->session(), *this, i));
|
||||
_signals[i]->direction = AngularVector (deg, 0.0);
|
||||
deg += degree_step;
|
||||
}
|
||||
|
||||
update ();
|
||||
}
|
||||
|
||||
void
|
||||
VBAPanner::update ()
|
||||
{
|
||||
/* recompute signal directions based on panner azimuth and width (diffusion) parameters)
|
||||
*/
|
||||
|
||||
/* panner azimuth control is [0 .. 1.0] which we interpret as [0 .. 360] degrees
|
||||
*/
|
||||
|
||||
double center = _pannable->pan_azimuth_control->get_value() * 360.0;
|
||||
|
||||
/* panner width control is [-1.0 .. 1.0]; we ignore sign, and map to [0 .. 360] degrees
|
||||
so that a width of 1 corresponds to a signal equally present from all directions,
|
||||
and a width of zero corresponds to a point source from the "center" (above)
|
||||
*/
|
||||
|
||||
double w = fabs (_pannable->pan_width_control->get_value()) * 360.0;
|
||||
|
||||
double min_dir = center - w;
|
||||
min_dir = max (min (min_dir, 360.0), 0.0);
|
||||
|
||||
double max_dir = center + w;
|
||||
max_dir = max (min (max_dir, 360.0), 0.0);
|
||||
|
||||
double degree_step_per_signal = (max_dir - min_dir) / _signals.size();
|
||||
double signal_direction = min_dir;
|
||||
|
||||
for (vector<Signal*>::iterator s = _signals.begin(); s != _signals.end(); ++s) {
|
||||
|
||||
Signal* signal = *s;
|
||||
|
||||
signal->direction = AngularVector (signal_direction, 0.0);
|
||||
|
||||
compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
|
||||
cerr << " @ " << signal->direction.azi << " /= " << signal->direction.ele
|
||||
<< " Outputs: "
|
||||
<< signal->desired_outputs[0] + 1 << ' '
|
||||
<< signal->desired_outputs[1] + 1 << ' '
|
||||
<< " Gains "
|
||||
<< signal->desired_gains[0] << ' '
|
||||
<< signal->desired_gains[1] << ' '
|
||||
<< endl;
|
||||
|
||||
signal_direction += degree_step_per_signal;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -141,49 +182,29 @@ VBAPanner::compute_gains (double gains[3], int speaker_ids[3], int azi, int ele)
|
|||
gains[1] /= power;
|
||||
gains[2] /= power;
|
||||
}
|
||||
|
||||
_dirty = false;
|
||||
}
|
||||
|
||||
void
|
||||
VBAPanner::do_distribute (BufferSet& inbufs, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes)
|
||||
VBAPanner::distribute (BufferSet& inbufs, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes)
|
||||
{
|
||||
bool was_dirty = _dirty;
|
||||
uint32_t n;
|
||||
vector<Signal*>::iterator s;
|
||||
|
||||
assert (inbufs.count().n_audio() == _signals.size());
|
||||
|
||||
/* XXX need to handle mono case */
|
||||
|
||||
for (s = _signals.begin(), n = 0; s != _signals.end(); ++s, ++n) {
|
||||
|
||||
Signal* signal (*s);
|
||||
|
||||
if (was_dirty) {
|
||||
compute_gains (signal->desired_gains, signal->desired_outputs, signal->direction.azi, signal->direction.ele);
|
||||
cerr << " @ " << signal->direction.azi << " /= " << signal->direction.ele
|
||||
<< " Outputs: "
|
||||
<< signal->desired_outputs[0] + 1 << ' '
|
||||
<< signal->desired_outputs[1] + 1 << ' '
|
||||
<< " Gains "
|
||||
<< signal->desired_gains[0] << ' '
|
||||
<< signal->desired_gains[1] << ' '
|
||||
<< endl;
|
||||
}
|
||||
|
||||
do_distribute_one (inbufs.get_audio (n), obufs, gain_coefficient, nframes, n);
|
||||
distribute_one (inbufs.get_audio (n), obufs, gain_coefficient, nframes, n);
|
||||
|
||||
if (was_dirty) {
|
||||
memcpy (signal->gains, signal->desired_gains, sizeof (signal->gains));
|
||||
memcpy (signal->outputs, signal->desired_outputs, sizeof (signal->outputs));
|
||||
}
|
||||
memcpy (signal->gains, signal->desired_gains, sizeof (signal->gains));
|
||||
memcpy (signal->outputs, signal->desired_outputs, sizeof (signal->outputs));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
VBAPanner::do_distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes, uint32_t which)
|
||||
VBAPanner::distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coefficient, pframes_t nframes, uint32_t which)
|
||||
{
|
||||
Sample* const src = srcbuf.data();
|
||||
Sample* dst;
|
||||
|
@ -228,8 +249,8 @@ VBAPanner::do_distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain
|
|||
}
|
||||
|
||||
void
|
||||
VBAPanner::do_distribute_one_automated (AudioBuffer& src, BufferSet& obufs,
|
||||
framepos_t start, framepos_t end, pframes_t nframes, pan_t** buffers, uint32_t which)
|
||||
VBAPanner::distribute_one_automated (AudioBuffer& src, BufferSet& obufs,
|
||||
framepos_t start, framepos_t end, pframes_t nframes, pan_t** buffers, uint32_t which)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -253,46 +274,12 @@ VBAPanner::set_state (const XMLNode& node, int /*version*/)
|
|||
return 0;
|
||||
}
|
||||
|
||||
boost::shared_ptr<AutomationControl>
|
||||
VBAPanner::azimuth_control (uint32_t n)
|
||||
{
|
||||
if (n >= _signals.size()) {
|
||||
return boost::shared_ptr<AutomationControl>();
|
||||
}
|
||||
return _signals[n]->azimuth_control;
|
||||
}
|
||||
|
||||
boost::shared_ptr<AutomationControl>
|
||||
VBAPanner::evelation_control (uint32_t n)
|
||||
{
|
||||
if (n >= _signals.size()) {
|
||||
return boost::shared_ptr<AutomationControl>();
|
||||
}
|
||||
return _signals[n]->elevation_control;
|
||||
}
|
||||
|
||||
Panner*
|
||||
VBAPanner::factory (boost::shared_ptr<Pannable> p, Speakers& s)
|
||||
{
|
||||
return new VBAPanner (p, s);
|
||||
}
|
||||
|
||||
string
|
||||
VBAPanner::describe_parameter (Evoral::Parameter param)
|
||||
{
|
||||
stringstream ss;
|
||||
switch (param.type()) {
|
||||
case PanElevationAutomation:
|
||||
return string_compose ( _("Pan:elevation %1"), param.id() + 1);
|
||||
case PanWidthAutomation:
|
||||
return string_compose ( _("Pan:diffusion %1"), param.id() + 1);
|
||||
case PanAzimuthAutomation:
|
||||
return string_compose ( _("Pan:azimuth %1"), param.id() + 1);
|
||||
}
|
||||
|
||||
return Automatable::describe_parameter (param);
|
||||
}
|
||||
|
||||
ChanCount
|
||||
VBAPanner::in() const
|
||||
{
|
||||
|
@ -304,3 +291,25 @@ VBAPanner::out() const
|
|||
{
|
||||
return ChanCount (DataType::AUDIO, _speakers.n_speakers());
|
||||
}
|
||||
|
||||
std::set<Evoral::Parameter>
|
||||
VBAPanner::what_can_be_automated() const
|
||||
{
|
||||
set<Evoral::Parameter> s;
|
||||
s.insert (Evoral::Parameter (PanAzimuthAutomation));
|
||||
s.insert (Evoral::Parameter (PanWidthAutomation));
|
||||
return s;
|
||||
}
|
||||
|
||||
string
|
||||
VBAPanner::describe_parameter (Evoral::Parameter p)
|
||||
{
|
||||
switch (p.type()) {
|
||||
case PanAzimuthAutomation:
|
||||
return _("Direction");
|
||||
case PanWidthAutomation:
|
||||
return _("Diffusion");
|
||||
default:
|
||||
return _pannable->describe_parameter (p);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,8 @@
|
|||
|
||||
#include "ardour/panner.h"
|
||||
#include "ardour/panner_shell.h"
|
||||
#include "ardour/vbap_speakers.h"
|
||||
|
||||
#include "vbap_speakers.h"
|
||||
|
||||
namespace ARDOUR {
|
||||
|
||||
|
@ -39,26 +40,24 @@ public:
|
|||
VBAPanner (boost::shared_ptr<Pannable>, Speakers& s);
|
||||
~VBAPanner ();
|
||||
|
||||
void configure_io (const ChanCount& in, const ChanCount& /* ignored - we use Speakers */);
|
||||
void configure_io (ChanCount in, ChanCount /* ignored - we use Speakers */);
|
||||
ChanCount in() const;
|
||||
ChanCount out() const;
|
||||
|
||||
std::set<Evoral::Parameter> what_can_be_automated() const;
|
||||
|
||||
static Panner* factory (boost::shared_ptr<Pannable>, Speakers& s);
|
||||
|
||||
void do_distribute (BufferSet& ibufs, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes);
|
||||
void do_distribute_automated (BufferSet& ibufs, BufferSet& obufs,
|
||||
framepos_t start, framepos_t end, pframes_t nframes, pan_t** buffers);
|
||||
void distribute (BufferSet& ibufs, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes);
|
||||
|
||||
void set_azimuth_elevation (double azimuth, double elevation);
|
||||
|
||||
std::string describe_parameter (Evoral::Parameter);
|
||||
|
||||
XMLNode& state (bool full_state);
|
||||
XMLNode& get_state ();
|
||||
int set_state (const XMLNode&, int version);
|
||||
|
||||
boost::shared_ptr<AutomationControl> azimuth_control (uint32_t signal);
|
||||
boost::shared_ptr<AutomationControl> evelation_control (uint32_t signal);
|
||||
|
||||
std::string describe_parameter (Evoral::Parameter param);
|
||||
|
||||
private:
|
||||
struct Signal {
|
||||
|
@ -67,20 +66,19 @@ private:
|
|||
double desired_gains[3];
|
||||
int outputs[3];
|
||||
int desired_outputs[3];
|
||||
boost::shared_ptr<AutomationControl> azimuth_control;
|
||||
boost::shared_ptr<AutomationControl> elevation_control;
|
||||
|
||||
Signal (Session&, VBAPanner&, uint32_t which);
|
||||
};
|
||||
|
||||
std::vector<Signal*> _signals;
|
||||
bool _dirty;
|
||||
VBAPSpeakers& _speakers;
|
||||
|
||||
void compute_gains (double g[3], int ls[3], int azi, int ele);
|
||||
void update ();
|
||||
void clear_signals ();
|
||||
|
||||
void do_distribute_one (AudioBuffer& src, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t which);
|
||||
void do_distribute_one_automated (AudioBuffer& src, BufferSet& obufs,
|
||||
void distribute_one (AudioBuffer& src, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t which);
|
||||
void distribute_one_automated (AudioBuffer& src, BufferSet& obufs,
|
||||
framepos_t start, framepos_t end, pframes_t nframes,
|
||||
pan_t** buffers, uint32_t which);
|
||||
};
|
||||
|
|
|
@ -36,7 +36,8 @@
|
|||
#include <stdlib.h>
|
||||
|
||||
#include "pbd/cartesian.h"
|
||||
#include "ardour/vbap_speakers.h"
|
||||
|
||||
#include "vbap_speakers.h"
|
||||
|
||||
using namespace ARDOUR;
|
||||
using namespace PBD;
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
#!/usr/bin/env python
|
||||
import autowaf
|
||||
import os
|
||||
|
||||
# Library version (UNIX style major, minor, micro)
|
||||
# major increment <=> incompatible changes
|
||||
# minor increment <=> compatible changes (additions)
|
||||
# micro increment <=> no interface changes
|
||||
LIBARDOUR_PANVBAP_LIB_VERSION = '1.0.0'
|
||||
|
||||
# Mandatory variables
|
||||
srcdir = '.'
|
||||
blddir = 'build'
|
||||
|
||||
def set_options(opt):
|
||||
autowaf.set_options(opt)
|
||||
|
||||
def configure(conf):
|
||||
autowaf.configure(conf)
|
||||
|
||||
def build(bld):
|
||||
obj = bld.new_task_gen('cxx', 'shlib')
|
||||
obj.source = [ 'vbap_speakers.cc', 'vbap.cc' ]
|
||||
obj.export_incdirs = ['.']
|
||||
obj.cxxflags = '-DPACKAGE="libardour_panvbap"'
|
||||
obj.includes = ['.']
|
||||
obj.name = 'libardour_panvbap'
|
||||
obj.target = 'panvbap'
|
||||
obj.uselib_local = 'libardour libardour_cp libpbd'
|
||||
obj.vnum = LIBARDOUR_PANVBAP_LIB_VERSION
|
||||
obj.install_path = os.path.join(bld.env['LIBDIR'], 'ardour3', 'panners')
|
||||
|
||||
def shutdown():
|
||||
autowaf.shutdown()
|
||||
|
|
@ -6,13 +6,22 @@ import os
|
|||
srcdir = '.'
|
||||
blddir = 'build'
|
||||
|
||||
#panners = [ '2in2out', 'vbap', '1in1out' ]
|
||||
panners = [ '2in2out' ]
|
||||
panners = [ '2in2out', '1in2out', 'vbap' ]
|
||||
|
||||
def set_options(opt):
|
||||
autowaf.set_options(opt)
|
||||
|
||||
def sub_config_and_use(conf, name, has_objects = True):
|
||||
conf.sub_config(name)
|
||||
autowaf.set_local_lib(conf, name, has_objects)
|
||||
|
||||
def configure(conf):
|
||||
autowaf.set_recursive()
|
||||
autowaf.configure(conf)
|
||||
|
||||
for i in panners:
|
||||
sub_config_and_use(conf, i)
|
||||
|
||||
def build(bld):
|
||||
for i in panners:
|
||||
bld.add_subdirs(i)
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ public:
|
|||
Recenable,
|
||||
PanDirection,
|
||||
PanWidth,
|
||||
PanElevation,
|
||||
Balance,
|
||||
SendGain,
|
||||
PluginParameter
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
#include "ardour/location.h"
|
||||
#include "ardour/midi_ui.h"
|
||||
#include "ardour/panner.h"
|
||||
#include "ardour/panner_shell.h"
|
||||
#include "ardour/route.h"
|
||||
#include "ardour/session.h"
|
||||
#include "ardour/tempo.h"
|
||||
|
@ -832,19 +833,17 @@ MackieControlProtocol::handle_control_event (SurfacePort & port, Control & contr
|
|||
// pot (jog wheel, external control)
|
||||
case Control::type_pot:
|
||||
if (control.group().is_strip()) {
|
||||
if (route != 0 && route->panner())
|
||||
{
|
||||
if (route) {
|
||||
boost::shared_ptr<Panner> panner = route->panner_shell()->panner();
|
||||
// pan for mono input routes, or stereo linked panners
|
||||
if (route->panner()->npanners() == 1 || (route->panner()->npanners() == 2 && route->panner()->linked()))
|
||||
{
|
||||
// assume pan for now
|
||||
AngularVector a = route->panner()->streampanner (0).get_effective_position ();
|
||||
if (panner) {
|
||||
double p = panner->position ();
|
||||
|
||||
// calculate new value, and adjust
|
||||
a.azi += 180.0 * state.delta * state.sign;
|
||||
a.azi = min (180.0, a.azi);
|
||||
a.azi = max (0.0, a.azi);
|
||||
route->panner()->streampanner (0).set_position (a);
|
||||
p += state.delta * state.sign;
|
||||
p = min (1.0, p);
|
||||
p = max (0.0, p);
|
||||
panner->set_position (p);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -1000,15 +999,13 @@ MackieControlProtocol::notify_panner_changed (RouteSignal * route_signal, bool f
|
|||
{
|
||||
Pot & pot = route_signal->strip().vpot();
|
||||
boost::shared_ptr<Panner> panner = route_signal->route()->panner();
|
||||
if ((panner && panner->npanners() == 1) || (panner->npanners() == 2 && panner->linked()))
|
||||
{
|
||||
AngularVector pos = route_signal->route()->panner()->streampanner(0).get_effective_position ();
|
||||
float fract = 1.0 - (pos.azi / 180.0); /* 1.0 = 0 degrees = right; 0.0 = 180 degrees = left */
|
||||
if (panner) {
|
||||
double pos = panner->position ();
|
||||
|
||||
// cache the MidiByteArray here, because the mackie led control is much lower
|
||||
// resolution than the panner control. So we save lots of byte
|
||||
// sends in spite of more work on the comparison
|
||||
MidiByteArray bytes = builder.build_led_ring (pot, ControlState (on, fract), MackieMidiBuilder::midi_pot_mode_dot);
|
||||
MidiByteArray bytes = builder.build_led_ring (pot, ControlState (on, pos), MackieMidiBuilder::midi_pot_mode_dot);
|
||||
// check that something has actually changed
|
||||
if (force_update || bytes != route_signal->last_pan_written())
|
||||
{
|
||||
|
|
|
@ -52,10 +52,6 @@ void RouteSignal::connect()
|
|||
|
||||
if (_route->panner()) {
|
||||
_route->panner()->Changed.connect(connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_panner_changed, &_mcp, this, false), midi_ui_context());
|
||||
|
||||
for ( unsigned int i = 0; i < _route->panner()->npanners(); ++i ) {
|
||||
_route->panner()->streampanner(i).Changed.connect (connections, MISSING_INVALIDATOR, ui_bind (&MackieControlProtocol::notify_panner_changed, &_mcp, this, false), midi_ui_context());
|
||||
}
|
||||
}
|
||||
|
||||
boost::shared_ptr<Track> trk = boost::dynamic_pointer_cast<ARDOUR::Track>(_route);
|
||||
|
|
|
@ -870,7 +870,7 @@ OSC::route_set_pan_stereo_position (int rid, float pos)
|
|||
if (r) {
|
||||
boost::shared_ptr<Panner> panner = r->panner();
|
||||
if (panner) {
|
||||
panner->set_stereo_position (pos);
|
||||
panner->set_position (pos);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -888,7 +888,7 @@ OSC::route_set_pan_stereo_width (int rid, float pos)
|
|||
if (r) {
|
||||
boost::shared_ptr<Panner> panner = r->panner();
|
||||
if (panner) {
|
||||
panner->set_stereo_width (pos);
|
||||
panner->set_width (pos);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue