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:
Paul Davis 2011-01-27 01:31:03 +00:00
parent 1385643131
commit 15b5fce904
78 changed files with 1382 additions and 3528 deletions

View File

@ -165,6 +165,7 @@ static const char* authors[] = {
N_("Petter Sundlöf"),
N_("Mike Täht"),
N_("Thorsten Wilms"),
N_("Robin Gareus"),
0
};

View File

@ -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

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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) {

View File

@ -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);

View File

@ -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")))));

View File

@ -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;

View File

@ -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)
{

View File

@ -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);

View File

@ -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()) {

View File

@ -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"

View File

@ -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 ();
}

View File

@ -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));
}

View File

@ -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();

View File

@ -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;

View File

@ -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);
}

View File

@ -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;

View File

@ -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',

View File

@ -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();

View File

@ -48,6 +48,7 @@ namespace PBD {
extern uint64_t Monitor;
extern uint64_t Solo;
extern uint64_t AudioPlayback;
extern uint64_t Panning;
}
}

View File

@ -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;

View File

@ -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;
};

View File

@ -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;

View File

@ -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

View File

@ -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__ */

View File

@ -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);

View File

@ -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;

View File

@ -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; }

View File

@ -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;

View File

@ -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 {

View File

@ -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__ */

View File

@ -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__ */

View File

@ -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);

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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 */
}
}

View File

@ -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");

View File

@ -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();
}

View File

@ -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");
}

View File

@ -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));

View File

@ -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);

View File

@ -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) {

View File

@ -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;
}

View File

@ -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) {

View File

@ -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

View File

@ -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 */
}

View File

@ -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;

View File

@ -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>

View File

@ -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));

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -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;
}
}

View File

@ -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'
]

View File

@ -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);

View File

@ -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);

View File

@ -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__ */

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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__ */

View File

@ -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()

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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' ]

View File

@ -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);
}
}

View File

@ -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);
};

View File

@ -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;

35
libs/panners/vbap/wscript Normal file
View File

@ -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()

View File

@ -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)

View File

@ -39,6 +39,7 @@ public:
Recenable,
PanDirection,
PanWidth,
PanElevation,
Balance,
SendGain,
PluginParameter

View File

@ -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())
{

View File

@ -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);

View File

@ -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);
}
}

View File

@ -25,6 +25,7 @@ children = [
'libs/taglib',
'libs/rubberband',
'libs/surfaces',
'libs/panners',
'libs/timecode',
'libs/ardour',
'libs/gtkmm2ext',