From 15b5fce90480490455237da917167b0bcb5ce946 Mon Sep 17 00:00:00 2001 From: Paul Davis Date: Thu, 27 Jan 2011 01:31:03 +0000 Subject: [PATCH] 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 --- gtk2_ardour/about.cc | 1 + gtk2_ardour/ardev_common.sh.in | 1 + gtk2_ardour/audio_time_axis.cc | 17 +- gtk2_ardour/automation_controller.cc | 3 +- gtk2_ardour/automation_line.cc | 9 +- gtk2_ardour/editor.h | 1 + gtk2_ardour/editor_mouse.cc | 5 - gtk2_ardour/editor_selection.cc | 17 + gtk2_ardour/midi_region_view.cc | 10 + gtk2_ardour/midi_region_view.h | 1 + gtk2_ardour/mixer_strip.cc | 15 +- gtk2_ardour/mono_panner.cc | 1 + gtk2_ardour/panner2d.cc | 20 +- gtk2_ardour/panner_ui.cc | 348 +--- gtk2_ardour/panner_ui.h | 28 +- gtk2_ardour/processor_box.cc | 6 +- gtk2_ardour/stereo_panner.cc | 17 +- gtk2_ardour/stereo_panner.h | 7 +- gtk2_ardour/wscript | 1 - libs/ardour/ardour/automatable.h | 3 +- libs/ardour/ardour/debug.h | 1 + libs/ardour/ardour/delivery.h | 13 +- libs/ardour/ardour/directory_names.h | 1 + libs/ardour/ardour/internal_send.h | 2 +- libs/ardour/ardour/pannable.h | 18 +- libs/ardour/ardour/panner.h | 339 ++-- libs/ardour/ardour/port_insert.h | 3 +- libs/ardour/ardour/route.h | 7 +- libs/ardour/ardour/send.h | 2 +- libs/ardour/ardour/speakers.h | 12 +- libs/ardour/ardour/types.h | 35 +- libs/ardour/ardour/vbap.h | 70 - libs/ardour/ardour/vbap_speakers.h | 107 -- libs/ardour/audioengine.cc | 4 +- libs/ardour/auditioner.cc | 4 +- libs/ardour/automatable.cc | 31 +- libs/ardour/automation_list.cc | 5 +- libs/ardour/debug.cc | 1 + libs/ardour/delivery.cc | 59 +- libs/ardour/directory_names.cc | 1 + libs/ardour/diskstream.cc | 31 +- libs/ardour/enums.cc | 9 +- libs/ardour/event_type_map.cc | 43 +- libs/ardour/globals.cc | 3 + libs/ardour/internal_send.cc | 4 +- libs/ardour/pannable.cc | 173 +- libs/ardour/panner.cc | 1533 +---------------- libs/ardour/panner_shell.cc | 6 + libs/ardour/port_insert.cc | 4 +- libs/ardour/route.cc | 62 +- libs/ardour/send.cc | 4 +- libs/ardour/session.cc | 6 +- libs/ardour/session_state.cc | 29 +- libs/ardour/speakers.cc | 122 ++ libs/ardour/thread_buffers.cc | 5 + libs/ardour/vbap.cc | 240 --- libs/ardour/vbap_speakers.cc | 658 ------- libs/ardour/wscript | 9 +- libs/evoral/evoral/ControlList.hpp | 2 + libs/evoral/src/ControlList.cpp | 4 + libs/gtkmm2ext/gtkmm2ext/utils.h | 3 + libs/gtkmm2ext/utils.cc | 9 + libs/panners/1in2out/panner_1in2out.cc | 223 ++- libs/panners/1in2out/panner_1in2out.h | 32 +- libs/panners/1in2out/wscript | 35 + libs/panners/2in2out/panner_2in2out.cc | 82 +- libs/panners/2in2out/panner_2in2out.h | 4 + libs/panners/2in2out/wscript | 6 + libs/panners/vbap/vbap.cc | 183 +- libs/panners/vbap/vbap.h | 26 +- libs/panners/vbap/vbap_speakers.cc | 3 +- libs/panners/vbap/wscript | 35 + libs/panners/wscript | 15 +- libs/pbd/pbd/controllable_descriptor.h | 1 + .../mackie/mackie_control_protocol.cc | 27 +- libs/surfaces/mackie/route_signal.cc | 4 - libs/surfaces/osc/osc.cc | 4 +- wscript | 1 + 78 files changed, 1360 insertions(+), 3506 deletions(-) delete mode 100644 libs/ardour/ardour/vbap.h delete mode 100644 libs/ardour/ardour/vbap_speakers.h delete mode 100644 libs/ardour/vbap.cc delete mode 100644 libs/ardour/vbap_speakers.cc create mode 100644 libs/panners/1in2out/wscript create mode 100644 libs/panners/vbap/wscript diff --git a/gtk2_ardour/about.cc b/gtk2_ardour/about.cc index 7eb33f02df..8f4d6e0526 100644 --- a/gtk2_ardour/about.cc +++ b/gtk2_ardour/about.cc @@ -165,6 +165,7 @@ static const char* authors[] = { N_("Petter Sundlöf"), N_("Mike Täht"), N_("Thorsten Wilms"), + N_("Robin Gareus"), 0 }; diff --git a/gtk2_ardour/ardev_common.sh.in b/gtk2_ardour/ardev_common.sh.in index 44dc3445c7..116a6d88fc 100644 --- a/gtk2_ardour/ardev_common.sh.in +++ b/gtk2_ardour/ardev_common.sh.in @@ -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 diff --git a/gtk2_ardour/audio_time_axis.cc b/gtk2_ardour/audio_time_axis.cc index d791290562..fcdc4bbdfa 100644 --- a/gtk2_ardour/audio_time_axis.cc +++ b/gtk2_ardour/audio_time_axis.cc @@ -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& params = _route->panner()->what_can_be_automated(); + set params = _route->panner()->what_can_be_automated(); set::iterator p; for (p = params.begin(); p != params.end(); ++p) { - boost::shared_ptr pan_control - = boost::dynamic_pointer_cast( - _route->panner()->control(*p)); + boost::shared_ptr 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 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 (&automation_items.back ()); pan_automation_item->set_active (pan_tracks.front()->marked_for_display ()); - set const & params = _route->panner()->what_can_be_automated (); + set const & params = _route->pannable()->what_can_be_automated (); for (set::iterator p = params.begin(); p != params.end(); ++p) { _main_automation_menu_map[*p] = pan_automation_item; } diff --git a/gtk2_ardour/automation_controller.cc b/gtk2_ardour/automation_controller.cc index 3af5f34445..f09fae905b 100644 --- a/gtk2_ardour/automation_controller.cc +++ b/gtk2_ardour/automation_controller.cc @@ -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(); } diff --git a/gtk2_ardour/automation_line.cc b/gtk2_ardour/automation_line.cc index a8125c0818..45b9573f71 100644 --- a/gtk2_ardour/automation_line.cc +++ b/gtk2_ardour/automation_line.cc @@ -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) { diff --git a/gtk2_ardour/editor.h b/gtk2_ardour/editor.h index b14c5f20e7..aca4015083 100644 --- a/gtk2_ardour/editor.h +++ b/gtk2_ardour/editor.h @@ -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); diff --git a/gtk2_ardour/editor_mouse.cc b/gtk2_ardour/editor_mouse.cc index 0c8da2bf1b..d1d9afc985 100644 --- a/gtk2_ardour/editor_mouse.cc +++ b/gtk2_ardour/editor_mouse.cc @@ -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"))))); diff --git a/gtk2_ardour/editor_selection.cc b/gtk2_ardour/editor_selection.cc index 482a778ae7..3212c93bb7 100644 --- a/gtk2_ardour/editor_selection.cc +++ b/gtk2_ardour/editor_selection.cc @@ -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 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; diff --git a/gtk2_ardour/midi_region_view.cc b/gtk2_ardour/midi_region_view.cc index fd2d629a09..60e07c17e0 100644 --- a/gtk2_ardour/midi_region_view.cc +++ b/gtk2_ardour/midi_region_view.cc @@ -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) { diff --git a/gtk2_ardour/midi_region_view.h b/gtk2_ardour/midi_region_view.h index 846b1651a1..edf418afb4 100644 --- a/gtk2_ardour/midi_region_view.h +++ b/gtk2_ardour/midi_region_view.h @@ -191,6 +191,7 @@ class MidiRegionView : public RegionView void delete_selection(); void delete_note (boost::shared_ptr); 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); diff --git a/gtk2_ardour/mixer_strip.cc b/gtk2_ardour/mixer_strip.cc index 8c6d612012..245b5c0072 100644 --- a/gtk2_ardour/mixer_strip.cc +++ b/gtk2_ardour/mixer_strip.cc @@ -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 pan_control - = boost::dynamic_pointer_cast( - _route->panner()->control(Evoral::Parameter(PanAutomation))); + boost::shared_ptr 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()) { diff --git a/gtk2_ardour/mono_panner.cc b/gtk2_ardour/mono_panner.cc index 93f635d970..52c417a9fd 100644 --- a/gtk2_ardour/mono_panner.cc +++ b/gtk2_ardour/mono_panner.cc @@ -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" diff --git a/gtk2_ardour/panner2d.cc b/gtk2_ardour/panner2d.cc index 0700685056..1d98ca0093 100644 --- a/gtk2_ardour/panner2d.cc +++ b/gtk2_ardour/panner2d.cc @@ -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 (); } diff --git a/gtk2_ardour/panner_ui.cc b/gtk2_ardour/panner_ui.cc index 52ae16a648..79a7259b32 100644 --- a/gtk2_ardour/panner_ui.cc +++ b/gtk2_ardour/panner_ui.cc @@ -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 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::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->nouts(); - uint32_t const npans = _panner->npanners(); + } + uint32_t const nouts = _panner->out().n_audio(); + uint32_t const nins = _panner->in().n_audio(); - 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::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 = _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 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 (ac))); _stereo_panner->StopPositionGesture.connect (sigc::bind (sigc::mem_fun (*this, &PannerUI::stop_touch), boost::weak_ptr(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 (ac))); _stereo_panner->StopWidthGesture.connect (sigc::bind (sigc::mem_fun (*this, &PannerUI::stop_touch), boost::weak_ptr(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 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 = _panner->pannable(); + boost::shared_ptr ac = pannable->pan_azimuth_control; + + mp = new MonoPanner (ac); + + mp->StartGesture.connect (sigc::bind (sigc::mem_fun (*this, &PannerUI::start_touch), boost::weak_ptr (ac))); - mp->StopGesture.connect (sigc::bind (sigc::mem_fun (*this, &PannerUI::stop_touch), - boost::weak_ptr(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(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 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 (&items.back()))->set_active (_panner->streampanner(which).muted()); - (dynamic_cast (&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 (&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) -{ - _panner->reset_streampanner (which); -} - -void -PannerUI::pan_reset_all () +PannerUI::pan_reset () { - _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::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 (_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)); } diff --git a/gtk2_ardour/panner_ui.h b/gtk2_ardour/panner_ui.h index 50fc6b228d..e64419b457 100644 --- a/gtk2_ardour/panner_ui.h +++ b/gtk2_ardour/panner_ui.h @@ -24,7 +24,6 @@ #include #include -#include #include #include #include @@ -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 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(); diff --git a/gtk2_ardour/processor_box.cc b/gtk2_ardour/processor_box.cc index 05f49a4e4e..2eaeeb83f7 100644 --- a/gtk2_ardour/processor_box.cc +++ b/gtk2_ardour/processor_box.cc @@ -898,7 +898,7 @@ ProcessorBox::weird_plugin_dialog (Plugin& p, Route::ProcessorStreams streams) void ProcessorBox::choose_insert () { - boost::shared_ptr processor (new PortInsert (*_session, _route->mute_master())); + boost::shared_ptr 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 (new Send (*_session, _route->mute_master())); + boost::shared_ptr 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; diff --git a/gtk2_ardour/stereo_panner.cc b/gtk2_ardour/stereo_panner.cc index 4e715c49ee..8442b45e67 100644 --- a/gtk2_ardour/stereo_panner.cc +++ b/gtk2_ardour/stereo_panner.cc @@ -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 position, boost::shared_ptr width) - : position_control (position) - , width_control (width) +using namespace ARDOUR; + +StereoPanner::StereoPanner (boost::shared_ptr 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 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); } diff --git a/gtk2_ardour/stereo_panner.h b/gtk2_ardour/stereo_panner.h index 86a53eccf7..294f86babb 100644 --- a/gtk2_ardour/stereo_panner.h +++ b/gtk2_ardour/stereo_panner.h @@ -31,10 +31,14 @@ namespace PBD { class Controllable; } +namespace ARDOUR { + class Panner; +} + class StereoPanner : public Gtk::DrawingArea { public: - StereoPanner (boost::shared_ptr pos, boost::shared_ptr width); + StereoPanner (boost::shared_ptr); ~StereoPanner (); sigc::signal StartPositionGesture; @@ -54,6 +58,7 @@ class StereoPanner : public Gtk::DrawingArea bool on_leave_notify_event (GdkEventCrossing* ev); private: + boost::shared_ptr _panner; boost::shared_ptr position_control; boost::shared_ptr width_control; PBD::ScopedConnectionList connections; diff --git a/gtk2_ardour/wscript b/gtk2_ardour/wscript index 43b45e1039..38700fcbc3 100644 --- a/gtk2_ardour/wscript +++ b/gtk2_ardour/wscript @@ -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', diff --git a/libs/ardour/ardour/automatable.h b/libs/ardour/ardour/automatable.h index 541e9d562d..c9e900e6a9 100644 --- a/libs/ardour/ardour/automatable.h +++ b/libs/ardour/ardour/automatable.h @@ -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(); diff --git a/libs/ardour/ardour/debug.h b/libs/ardour/ardour/debug.h index 43fb468b7a..69421662f3 100644 --- a/libs/ardour/ardour/debug.h +++ b/libs/ardour/ardour/debug.h @@ -48,6 +48,7 @@ namespace PBD { extern uint64_t Monitor; extern uint64_t Solo; extern uint64_t AudioPlayback; + extern uint64_t Panning; } } diff --git a/libs/ardour/ardour/delivery.h b/libs/ardour/ardour/delivery.h index e1aa389df7..99893976ba 100644 --- a/libs/ardour/ardour/delivery.h +++ b/libs/ardour/ardour/delivery.h @@ -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, boost::shared_ptr mm, const std::string& name, Role); + Delivery (Session& s, boost::shared_ptr io, boost::shared_ptr, boost::shared_ptr mm, const std::string& name, Role); /* Delivery to a new output owned by this object */ - Delivery (Session& s, boost::shared_ptr mm, const std::string& name, Role); + Delivery (Session& s, boost::shared_ptr, boost::shared_ptr 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() const { return _panner; } + boost::shared_ptr panner_shell() const { return _panshell; } + boost::shared_ptr 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 _mute_master; bool no_panner_reset; - boost::shared_ptr _panner; + boost::shared_ptr _panshell; static bool panners_legal; static PBD::Signal0 PannersLegal; diff --git a/libs/ardour/ardour/directory_names.h b/libs/ardour/ardour/directory_names.h index 9cce077ad0..a5fd6dc754 100644 --- a/libs/ardour/ardour/directory_names.h +++ b/libs/ardour/ardour/directory_names.h @@ -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; }; diff --git a/libs/ardour/ardour/internal_send.h b/libs/ardour/ardour/internal_send.h index 903e4673df..53c6686887 100644 --- a/libs/ardour/ardour/internal_send.h +++ b/libs/ardour/ardour/internal_send.h @@ -28,7 +28,7 @@ namespace ARDOUR { class InternalSend : public Send { public: - InternalSend (Session&, boost::shared_ptr, boost::shared_ptr send_to, Delivery::Role role); + InternalSend (Session&, boost::shared_ptr, boost::shared_ptr, boost::shared_ptr send_to, Delivery::Role role); virtual ~InternalSend (); std::string display_name() const; diff --git a/libs/ardour/ardour/pannable.h b/libs/ardour/ardour/pannable.h index e6ed2e41c1..dff64cec4b 100644 --- a/libs/ardour/ardour/pannable.h +++ b/libs/ardour/ardour/pannable.h @@ -24,6 +24,7 @@ #include +#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 pan_azimuth_control; @@ -42,6 +44,9 @@ struct Pannable : public Automatable, public SessionHandleRef { boost::shared_ptr pan_width_control; boost::shared_ptr pan_frontback_control; boost::shared_ptr pan_lfe_control; + + boost::shared_ptr panner() const { return _panner; } + void set_panner(boost::shared_ptr); 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; 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 diff --git a/libs/ardour/ardour/panner.h b/libs/ardour/ardour/panner.h index b07167862e..f43be2dbbc 100644 --- a/libs/ardour/ardour/panner.h +++ b/libs/ardour/ardour/panner.h @@ -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 #include -#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 (); - - void set_muted (bool yn); - bool muted() const { return _muted; } - - 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 pan_control() { return _control; } - - PBD::Signal0 Changed; /* for position or diffusion */ - PBD::Signal0 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(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; + Panner (boost::shared_ptr); + ~Panner (); - void set_mono (bool); - - PBD::AngularVector _angles; - PBD::AngularVector _effective_angles; - double _diffusion; + virtual ChanCount in() const = 0; + virtual ChanCount out() const = 0; - bool _muted; - bool _mono; + virtual void configure_io (ARDOUR::ChanCount in, ARDOUR::ChanCount out) {} + + /* 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: + + * return true, leave argument unchanged + * return true, modify argument + * return false + + */ + + virtual bool clamp_position (double&) { return true; } + virtual bool clamp_width (double&) { return true; } + virtual bool clamp_elevation (double&) { return true; } + + virtual void set_position (double) { } + virtual void set_width (double) { } + virtual void set_elevation (double) { } - boost::shared_ptr _control; + virtual double position () const { return 0.0; } + virtual double width () const { return 0.0; } + virtual double elevation () const { return 0.0; } - XMLNode& get_state (); + virtual void reset() {} - /* Update internal parameters based on this.angles */ - virtual void update () = 0; -}; + virtual bool bypassed() const { return _bypassed; } + virtual void set_bypassed (bool yn); -class BaseStereoPanner : public StreamPanner -{ - public: - BaseStereoPanner (Panner&, Evoral::Parameter param); - ~BaseStereoPanner (); + 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; - /* 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 std::set what_can_be_automated() const; + virtual std::string describe_parameter (Evoral::Parameter); - void do_distribute (AudioBuffer& src, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes); + 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 */ - - int load (std::istream&, std::string path, uint32_t&); - - protected: - float left; - float right; - float desired_left; - float desired_right; - float left_interp; - float right_interp; -}; - -class EqualPowerStereoPanner : public BaseStereoPanner -{ - public: - EqualPowerStereoPanner (Panner&, Evoral::Parameter param); - ~EqualPowerStereoPanner (); - - 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; }; + /** + * 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); - /// The fundamental Panner function - void run (BufferSet& src, BufferSet& dest, framepos_t start_frame, framepos_t end_frames, pframes_t nframes); + PBD::Signal0 Changed; /* for positional info */ + PBD::Signal0 StateChanged; /* for mute, mono */ - bool bypassed() const { return _bypassed; } - void set_bypassed (bool yn); - bool mono () const { return _mono; } - void set_mono (bool); + int set_state (const XMLNode&, int version); + virtual XMLNode& state (bool full_state) = 0; - 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 (); + boost::shared_ptr pannable() const { return _pannable; } - 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]; } - - enum LinkDirection { - SameDirection, - OppositeDirection - }; - - 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 Changed; /* panner and/or outputs count changed */ - PBD::Signal0 LinkStateChanged; - PBD::Signal0 StateChanged; /* for bypass */ - - /* only StreamPanner should call these */ - - void set_position (const PBD::AngularVector&, StreamPanner& orig); - - /* old school automation */ - - int load (); - - boost::shared_ptr pan_control (int id, uint32_t chan=0) { - return automation_control (Evoral::Parameter (PanAutomation, chan, id)); - } - - boost::shared_ptr pan_control (int id, uint32_t chan=0) const { - return automation_control (Evoral::Parameter (PanAutomation, chan, id)); - } - - boost::shared_ptr direction_control () { - return automation_control (Evoral::Parameter (PanAutomation, 0, 100)); - } - - boost::shared_ptr 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 _streampanners; ///< one StreamPanner per input - std::vector outputs; - uint32_t current_outs; - bool _linked; - bool _bypassed; - bool _mono; - LinkDirection _link_direction; - - static float current_automation_version_number; + protected: + boost::shared_ptr _pannable; + bool _mono; + bool _bypassed; - void setup_speakers (uint32_t nouts); - void setup_meta_controls (); + XMLNode& get_state (); - /* 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 + +extern "C" { + struct PanPluginDescriptor { + std::string name; + int32_t in; + int32_t out; + ARDOUR::Panner* (*factory)(boost::shared_ptr, ARDOUR::Speakers&); + }; +} -#endif /*__ardour_panner_h__ */ +#endif /* __ardour_panner_h__ */ diff --git a/libs/ardour/ardour/port_insert.h b/libs/ardour/ardour/port_insert.h index d460aa5546..959e46b4a5 100644 --- a/libs/ardour/ardour/port_insert.h +++ b/libs/ardour/ardour/port_insert.h @@ -37,13 +37,14 @@ class Session; class IO; class Delivery; class MuteMaster; +class Pannable; /** Port inserts: send output to a Jack port, pick up input at a Jack port */ class PortInsert : public IOProcessor { public: - PortInsert (Session&, boost::shared_ptr mm); + PortInsert (Session&, boost::shared_ptr, boost::shared_ptr mm); ~PortInsert (); XMLNode& state(bool full); diff --git a/libs/ardour/ardour/route.h b/libs/ardour/ardour/route.h index 90f5664eba..f90ec77b7e 100644 --- a/libs/ardour/ardour/route.h +++ b/libs/ardour/ardour/route.h @@ -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() const; + boost::shared_ptr panner() const; /* may return null */ + boost::shared_ptr panner_shell() const; boost::shared_ptr gain_control() const; + boost::shared_ptr 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 _monitor_send; boost::shared_ptr _intreturn; boost::shared_ptr _monitor_control; + boost::shared_ptr _pannable; Flag _flags; int _pending_declick; diff --git a/libs/ardour/ardour/send.h b/libs/ardour/ardour/send.h index bf5c5d7370..40045d6b85 100644 --- a/libs/ardour/ardour/send.h +++ b/libs/ardour/ardour/send.h @@ -36,7 +36,7 @@ class Amp; class Send : public Delivery { public: - Send (Session&, boost::shared_ptr, Delivery::Role r = Delivery::Send); + Send (Session&, boost::shared_ptr pannable, boost::shared_ptr, Delivery::Role r = Delivery::Send); virtual ~Send (); uint32_t bit_slot() const { return _bitslot; } diff --git a/libs/ardour/ardour/speakers.h b/libs/ardour/ardour/speakers.h index bae8cb96db..f6e6e22848 100644 --- a/libs/ardour/ardour/speakers.h +++ b/libs/ardour/ardour/speakers.h @@ -23,12 +23,15 @@ #include #include +#include #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& speakers() { return _speakers; } void dump_speakers (std::ostream&); - PBD::Signal0 Changed; + XMLNode& get_state (); + int set_state (const XMLNode&, int version); + PBD::Signal0 Changed; + protected: std::vector _speakers; diff --git a/libs/ardour/ardour/types.h b/libs/ardour/ardour/types.h index 78e21d5a9d..744c06f552 100644 --- a/libs/ardour/ardour/types.h +++ b/libs/ardour/ardour/types.h @@ -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 { diff --git a/libs/ardour/ardour/vbap.h b/libs/ardour/ardour/vbap.h deleted file mode 100644 index 419930c574..0000000000 --- a/libs/ardour/ardour/vbap.h +++ /dev/null @@ -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 -#include - -#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__ */ diff --git a/libs/ardour/ardour/vbap_speakers.h b/libs/ardour/ardour/vbap_speakers.h deleted file mode 100644 index 80989646bc..0000000000 --- a/libs/ardour/ardour/vbap_speakers.h +++ /dev/null @@ -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 -#include - -#include - -#include - -#include "ardour/panner.h" -#include "ardour/speakers.h" - -namespace ARDOUR { - -class Speakers; - -class VBAPSpeakers : public boost::noncopyable { -public: - typedef std::vector 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& _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 _matrices; /* holds matrices for a given speaker combinations */ - std::vector _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&); - 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__ */ diff --git a/libs/ardour/audioengine.cc b/libs/ardour/audioengine.cc index ef94d0d740..f6557fbc4f 100644 --- a/libs/ardour/audioengine.cc +++ b/libs/ardour/audioengine.cc @@ -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); diff --git a/libs/ardour/auditioner.cc b/libs/ardour/auditioner.cc index d7605f26e1..209d26dc30 100644 --- a/libs/ardour/auditioner.cc +++ b/libs/ardour/auditioner.cc @@ -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); } diff --git a/libs/ardour/automatable.cc b/libs/ardour/automatable.cc index 03e5ad97c3..0d24135710 100644 --- a/libs/ardour/automatable.cc +++ b/libs/ardour/automatable.cc @@ -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 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 existing = control(param); + boost::shared_ptr existing = automation_control (param); + if (existing) { - existing->set_list(al); + existing->alist()->set_state (**niter, 3000); } else { - boost::shared_ptr newcontrol = control_factory(param); - add_control(newcontrol); + boost::shared_ptr newcontrol = control_factory(param); + add_control (newcontrol); + boost::shared_ptr 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(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(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; } } diff --git a/libs/ardour/automation_list.cc b/libs/ardour/automation_list.cc index f4f4f95238..5f18bbcebb 100644 --- a/libs/ardour/automation_list.cc +++ b/libs/ardour/automation_list.cc @@ -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 */ } } diff --git a/libs/ardour/debug.cc b/libs/ardour/debug.cc index 7ddb6ddcc8..2c413f407e 100644 --- a/libs/ardour/debug.cc +++ b/libs/ardour/debug.cc @@ -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"); diff --git a/libs/ardour/delivery.cc b/libs/ardour/delivery.cc index 80135baf28..5c535b8918 100644 --- a/libs/ardour/delivery.cc +++ b/libs/ardour/delivery.cc @@ -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, boost::shared_ptr mm, const string& name, Role r) +Delivery::Delivery (Session& s, boost::shared_ptr io, boost::shared_ptr pannable, + boost::shared_ptr mm, const string& name, Role r) : IOProcessor(s, boost::shared_ptr(), (role_requires_output_ports (r) ? io : boost::shared_ptr()), name) , _role (r) , _output_buffers (new BufferSet()) @@ -59,7 +62,7 @@ Delivery::Delivery (Session& s, boost::shared_ptr io, boost::shared_ptr(new Panner (_name, _session)); + _panshell = boost::shared_ptr(new PannerShell (_name, _session, pannable)); _display_to_user = false; if (_output) { @@ -71,7 +74,7 @@ Delivery::Delivery (Session& s, boost::shared_ptr io, boost::shared_ptr mm, const string& name, Role r) +Delivery::Delivery (Session& s, boost::shared_ptr pannable, boost::shared_ptr 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 mm, const string& , _mute_master (mm) , no_panner_reset (false) { - _panner = boost::shared_ptr(new Panner (_name, _session)); + _panshell = boost::shared_ptr(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; + 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 +Delivery::panner () const +{ + return _panshell->panner(); +} diff --git a/libs/ardour/directory_names.cc b/libs/ardour/directory_names.cc index d6e2f94455..18d43f7ec8 100644 --- a/libs/ardour/directory_names.cc +++ b/libs/ardour/directory_names.cc @@ -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"); } diff --git a/libs/ardour/diskstream.cc b/libs/ardour/diskstream.cc index bf77d7d6ad..df9d1a9b4d 100644 --- a/libs/ardour/diskstream.cc +++ b/libs/ardour/diskstream.cc @@ -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 > const & } /* move panner automation */ - boost::shared_ptr p = _track->main_outs()->panner (); - if (p) { - for (uint32_t i = 0; i < p->npanners (); ++i) { - boost::shared_ptr 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 ( - *pan_alist.get(), &before, &pan_alist->get_state ())); - } - } - } + boost::shared_ptr pannable = _track->pannable(); + Evoral::ControlSet::Controls& c (pannable->controls()); + + for (Evoral::ControlSet::Controls::iterator ci = c.begin(); ci != c.end(); ++ci) { + boost::shared_ptr ac = boost::dynamic_pointer_cast(ci->second); + if (!ac) { + continue; + } + boost::shared_ptr alist = ac->alist(); + + XMLNode & before = alist->get_state (); + bool const things_moved = alist->move_ranges (movements); + if (things_moved) { + _session.add_command (new MementoCommand ( + *alist.get(), &before, &alist->get_state ())); + } + } /* move processor automation */ _track->foreach_processor (boost::bind (&Diskstream::move_processor_automation, this, _1, movements_frames)); diff --git a/libs/ardour/enums.cc b/libs/ardour/enums.cc index cde3f418fd..5987c874fb 100644 --- a/libs/ardour/enums.cc +++ b/libs/ardour/enums.cc @@ -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); diff --git a/libs/ardour/event_type_map.cc b/libs/ardour/event_type_map.cc index d88e5afd26..c8c48c5b86 100644 --- a/libs/ardour/event_type_map.cc +++ b/libs/ardour/event_type_map.cc @@ -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) { diff --git a/libs/ardour/globals.cc b/libs/ardour/globals.cc index d5579b47da..afcaa4dfcd 100644 --- a/libs/ardour/globals.cc +++ b/libs/ardour/globals.cc @@ -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; } diff --git a/libs/ardour/internal_send.cc b/libs/ardour/internal_send.cc index 43276516dd..4c3b385304 100644 --- a/libs/ardour/internal_send.cc +++ b/libs/ardour/internal_send.cc @@ -32,8 +32,8 @@ using namespace PBD; using namespace ARDOUR; using namespace std; -InternalSend::InternalSend (Session& s, boost::shared_ptr mm, boost::shared_ptr sendto, Delivery::Role role) - : Send (s, mm, role) +InternalSend::InternalSend (Session& s, boost::shared_ptr p, boost::shared_ptr mm, boost::shared_ptr sendto, Delivery::Role role) + : Send (s, p, mm, role) , target (0) { if (sendto) { diff --git a/libs/ardour/pannable.cc b/libs/ardour/pannable.cc index e2f8ccc30e..4243ab45df 100644 --- a/libs/ardour/pannable.cc +++ b/libs/ardour/pannable.cc @@ -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(new AutomationList(PanAzimuthAutomation)), "")) - , pan_elevation_control (new AutomationControl (s, PanElevationAutomation, - boost::shared_ptr(new AutomationList(PanElevationAutomation)), "")) - , pan_width_control (new AutomationControl (s, PanWidthAutomation, - boost::shared_ptr(new AutomationList(PanWidthAutomation)), "")) - , pan_frontback_control (new AutomationControl (s, PanFrontBackAutomation, - boost::shared_ptr(new AutomationList(PanFrontBackAutomation)), "")) - , pan_lfe_control (new AutomationControl (s, PanLFEAutomation, - boost::shared_ptr(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 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; +} + + + + diff --git a/libs/ardour/panner.cc b/libs/ardour/panner.cc index e4dc417c83..bfee68a342 100644 --- a/libs/ardour/panner.cc +++ b/libs/ardour/panner.cc @@ -1,6 +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 @@ -18,1539 +17,147 @@ */ -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "pbd/cartesian.h" -#include "pbd/convert.h" -#include "pbd/error.h" -#include "pbd/failed_constructor.h" -#include "pbd/xml++.h" -#include "pbd/enumwriter.h" - -#include "evoral/Curve.hpp" - -#include "ardour/session.h" -#include "ardour/panner.h" -#include "ardour/utils.h" #include "ardour/audio_buffer.h" - -#include "ardour/runtime_functions.h" #include "ardour/buffer_set.h" -#include "ardour/audio_buffer.h" -#include "ardour/vbap.h" - -#include "i18n.h" - -#include "pbd/mathfix.h" +#include "ardour/panner.h" +#include "ardour/pannable.h" +#include "ardour/session.h" +#include "ardour/utils.h" using namespace std; using namespace ARDOUR; -using namespace PBD; -float Panner::current_automation_version_number = 1.0; - -string EqualPowerStereoPanner::name = "Equal Power Stereo"; - -/* this is a default mapper of control values to a pan position - others can be imagined. -*/ - -static double direct_control_to_stereo_pan (double fract) +Panner::Panner (boost::shared_ptr p) + : _pannable (p) + , _mono (0) { - return BaseStereoPanner::lr_fract_to_azimuth (fract); -} - -StreamPanner::StreamPanner (Panner& p, Evoral::Parameter param) - : parent (p) - , _control (new PanControllable (parent.session(), _("direction"), this, param)) -{ - assert (param.type() != NullAutomation); - - _muted = false; - _mono = false; - - p.add_control (_control); } -StreamPanner::~StreamPanner () +Panner::~Panner () { } void -StreamPanner::set_mono (bool yn) +Panner::set_bypassed (bool yn) { - if (yn != _mono) { - _mono = yn; + if (yn != _bypassed) { + _bypassed = yn; StateChanged (); } } -double -StreamPanner::PanControllable::lower () const -{ - switch (parameter().id()) { - case 200: /* width */ - return -1.0; - default: - return 0.0; - } -} - -void -StreamPanner::PanControllable::set_value (double val) -{ - Panner& p (streampanner->get_parent()); - switch (parameter().id()) { - case 100: - /* position */ - val = max (min (val, 1.0), 0.0); - if (p.set_stereo_pan (val, p.width_control()->get_value())) { - AutomationControl::set_value(val); - p.session().set_dirty (); - } - break; - - case 200: - /* width */ - val = max (min (val, 1.0), -1.0); - if (p.set_stereo_pan (p.direction_control()->get_value(), val)) { - AutomationControl::set_value(val); - p.session().set_dirty (); - } - break; - - default: /* positional */ - val = max (min (val, 1.0), 0.0); - streampanner->set_position (AngularVector (direct_control_to_stereo_pan (val), 0.0)); - AutomationControl::set_value(val); - p.session().set_dirty (); - break; - } - -} - -double -StreamPanner::PanControllable::get_value (void) const -{ - return AutomationControl::get_value(); -} - void -StreamPanner::set_muted (bool yn) +Panner::set_mono (bool yn) { - if (yn != _muted) { - _muted = yn; + if (yn != _mono) { + _mono = yn; StateChanged (); } } -void -StreamPanner::set_position (const AngularVector& av, bool link_call) -{ - if (!link_call && parent.linked()) { - parent.set_position (av, *this); - } - - if (_angles != av) { - _angles = av; - update (); - Changed (); - _control->Changed (); - } -} - int -StreamPanner::set_state (const XMLNode& node, int version) +Panner::set_state (const XMLNode& node, int version) { const XMLProperty* prop; XMLNodeConstIterator iter; - if ((prop = node.property (X_("muted")))) { - set_muted (string_is_affirmative (prop->value())); - } - if ((prop = node.property (X_("mono")))) { set_mono (string_is_affirmative (prop->value())); } - for (XMLNodeConstIterator iter = node.children().begin(); iter != node.children().end(); ++iter) { - if ((*iter)->name() == Controllable::xml_node_name) { - if ((prop = (*iter)->property ("name")) != 0 && prop->value() == "direction") { - _control->set_state (**iter, version); - } - } - } + if ((prop = node.property (X_("bypassed"))) != 0) { + set_bypassed (string_is_affirmative (prop->value())); + } return 0; } XMLNode& -StreamPanner::get_state () +Panner::get_state () { - XMLNode* node = new XMLNode (X_("StreamPanner")); - node->add_property (X_("muted"), (muted() ? "yes" : "no")); + XMLNode* node = new XMLNode (X_("Panner")); + node->add_property (X_("mono"), (_mono ? "yes" : "no")); - node->add_child_nocopy (_control->get_state ()); - return *node; -} + node->add_property (X_("bypassed"), (bypassed() ? "yes" : "no")); -void -StreamPanner::distribute (AudioBuffer& src, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes) -{ - if (_mono) { - /* we're in mono mode, so just pan the input to all outputs equally */ - int const N = parent.nouts (); - for (int i = 0; i < N; ++i) { - mix_buffers_with_gain (obufs.get_audio(i).data(), src.data(), nframes, gain_coeff); - } - } else { - /* normal mode, call the `real' distribute method */ - do_distribute (src, obufs, gain_coeff, nframes); - } + return *node; } void -StreamPanner::distribute_automated (AudioBuffer& src, BufferSet& obufs, - framepos_t start, framepos_t end, pframes_t nframes, pan_t** buffers) -{ - if (_mono) { - /* we're in mono mode, so just pan the input to all outputs equally */ - int const N = parent.nouts (); - for (int i = 0; i < N; ++i) { - mix_buffers_with_gain (obufs.get_audio(i).data(), src.data(), nframes, 1.0); - } - } else { - /* normal mode, call the `real' distribute method */ - do_distribute_automated (src, obufs, start, end, nframes, buffers); - } - -} - - -/*---------------------------------------------------------------------- */ - -BaseStereoPanner::BaseStereoPanner (Panner& p, Evoral::Parameter param) - : StreamPanner (p, param) - , left (0.5) - , right (0.5) - , left_interp (left) - , right_interp (right) +Panner::distribute (BufferSet& ibufs, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes) { -} - -BaseStereoPanner::~BaseStereoPanner () -{ -} - -int -BaseStereoPanner::load (istream& in, string path, uint32_t& linecnt) -{ - char line[128]; - LocaleGuard lg (X_("POSIX")); - - _control->list()->clear (); - - while (in.getline (line, sizeof (line), '\n')) { - framepos_t when; - double value; - - ++linecnt; - - if (strcmp (line, "end") == 0) { - break; - } - - if (sscanf (line, "%" PRIu64 " %lf", &when, &value) != 2) { - warning << string_compose(_("badly formatted pan automation event record at line %1 of %2 (ignored) [%3]"), linecnt, path, line) << endmsg; - continue; - } - - _control->list()->fast_simple_add (when, value); - } - - /* now that we are done loading */ - - ((AutomationList*)_control->list().get())->StateChanged (); + uint32_t which = 0; - return 0; + for (BufferSet::audio_iterator src = ibufs.audio_begin(); src != ibufs.audio_end(); ++src, ++which) { + if (_mono) { + /* we're in mono mode, so just pan the input to all outputs equally (XXX should we scale?) */ + for (BufferSet::audio_iterator o = obufs.audio_begin(); o != obufs.audio_end(); ++o) { + mix_buffers_with_gain ((*o).data(), (*src).data(), nframes, gain_coeff); + } + } else { + /* normal mode, call the `real' distribute method */ + distribute_one (*src, obufs, gain_coeff, nframes, which); + } + } } void -BaseStereoPanner::do_distribute (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes) -{ - assert(obufs.count().n_audio() == 2); - - pan_t delta; - Sample* dst; - pan_t pan; - - if (_muted) { - return; - } - - Sample* const src = srcbuf.data(); - - /* LEFT */ - - dst = obufs.get_audio(0).data(); - - 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 */ - - pframes_t const limit = min ((pframes_t) 64, nframes); - pframes_t n; - - delta = -(delta / (float) (limit)); - - for (n = 0; n < limit; n++) { - 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 * gain_coeff; - - mix_buffers_with_gain (dst+n,src+n,nframes-n,pan); - - } else { - - left = desired_left; - left_interp = left; - - if ((pan = (left * gain_coeff)) != 1.0f) { - - if (pan != 0.0f) { - - /* pan is 1 but also not 0, so we must do it "properly" */ - - mix_buffers_with_gain(dst,src,nframes,pan); - - /* mark that we wrote into the buffer */ - - // obufs[0] = 0; - - } - - } else { - - /* pan is 1 so we can just copy the input samples straight in */ - - mix_buffers_no_gain(dst,src,nframes); - - /* mark that we wrote into the buffer */ - - // obufs[0] = 0; - } - } - - /* RIGHT */ - - dst = obufs.get_audio(1).data(); - - 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 */ - - pframes_t const limit = min ((pframes_t) 64, nframes); - pframes_t n; - - delta = -(delta / (float) (limit)); - - for (n = 0; n < limit; n++) { - 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 * gain_coeff; - - mix_buffers_with_gain(dst+n,src+n,nframes-n,pan); - - /* XXX it would be nice to mark the buffer as written to */ - - } else { - - right = desired_right; - right_interp = right; - - if ((pan = (right * gain_coeff)) != 1.0f) { - - if (pan != 0.0f) { - - /* pan is not 1 but also not 0, so we must do it "properly" */ - - mix_buffers_with_gain(dst,src,nframes,pan); - - /* XXX it would be nice to mark the buffer as written to */ - } - - } else { - - /* pan is 1 so we can just copy the input samples straight in */ - - mix_buffers_no_gain(dst,src,nframes); - - /* XXX it would be nice to mark the buffer as written to */ - } - } -} - -/*---------------------------------------------------------------------- */ - -EqualPowerStereoPanner::EqualPowerStereoPanner (Panner& p, Evoral::Parameter param) - : BaseStereoPanner (p, param) +Panner::distribute_automated (BufferSet& ibufs, BufferSet& obufs, + framepos_t start, framepos_t end, pframes_t nframes, pan_t** buffers) { - update (); - - left = desired_left; - right = desired_right; - left_interp = left; - right_interp = right; -} + uint32_t which = 0; -EqualPowerStereoPanner::~EqualPowerStereoPanner () -{ + for (BufferSet::audio_iterator src = ibufs.audio_begin(); src != ibufs.audio_end(); ++src, ++which) { + if (_mono) { + /* we're in mono mode, so just pan the input to all outputs equally (XXX should we scale?) */ + for (BufferSet::audio_iterator o = obufs.audio_begin(); o != obufs.audio_end(); ++o) { + mix_buffers_with_gain ((*o).data(), (*src).data(), nframes, 1.0); + } + } else { + /* normal mode, call the `real' distribute method */ + distribute_one_automated (*src, obufs, start, end, nframes, buffers, which); + } + } } void -EqualPowerStereoPanner::update () +Panner::set_automation_style (AutoStyle style) { - /* it would be very nice to split this out into a virtual function - that can be accessed from BaseStereoPanner and used in do_distribute_automated(). - - but the place where its used in do_distribute_automated() is a tight inner loop, - and making "nframes" virtual function calls to compute values is an absurd - overhead. - */ - - /* x == 0 => hard left = 180.0 degrees - x == 1 => hard right = 0.0 degrees - */ - - double _x = BaseStereoPanner::azimuth_to_lr_fract (_angles.azi); - - float const panR = _x; - float const panL = 1 - panR; - - float const pan_law_attenuation = -3.0f; - float const scale = 2.0f - 4.0f * powf (10.0f,pan_law_attenuation/20.0f); - - desired_left = panL * (scale * panL + 1.0f - scale); - desired_right = panR * (scale * panR + 1.0f - scale); - - _effective_angles = _angles; - //_control->set_value(x); + _pannable->set_automation_style (style); } void -EqualPowerStereoPanner::do_distribute_automated (AudioBuffer& srcbuf, BufferSet& obufs, - framepos_t start, framepos_t end, pframes_t nframes, - pan_t** buffers) -{ - assert (obufs.count().n_audio() == 2); - - Sample* dst; - pan_t* pbuf; - Sample* const src = srcbuf.data(); - - /* fetch positional data */ - - if (!_control->list()->curve().rt_safe_get_vector (start, end, buffers[0], nframes)) { - /* fallback */ - if (!_muted) { - do_distribute (srcbuf, obufs, 1.0, nframes); - } - return; - } - - /* store effective pan position. do this even if we are muted */ - - if (nframes > 0) { - _effective_angles.azi = BaseStereoPanner::lr_fract_to_azimuth (buffers[0][nframes-1]); - } - - if (_muted) { - 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 const panR = buffers[0][n]; - float const panL = 1 - panR; - - buffers[0][n] = panL * (scale * panL + 1.0f - scale); - buffers[1][n] = panR * (scale * panR + 1.0f - scale); - } - - /* LEFT */ - - 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 */ - - 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 */ -} - -StreamPanner* -EqualPowerStereoPanner::factory (Panner& parent, Evoral::Parameter param, Speakers& /* ignored */) -{ - return new EqualPowerStereoPanner (parent, param); -} - -XMLNode& -EqualPowerStereoPanner::get_state (void) -{ - return state (true); -} - -XMLNode& -EqualPowerStereoPanner::state (bool /*full_state*/) -{ - XMLNode& root (StreamPanner::get_state ()); - root.add_property (X_("type"), EqualPowerStereoPanner::name); - return root; -} - -int -EqualPowerStereoPanner::set_state (const XMLNode& node, int version) -{ - LocaleGuard lg (X_("POSIX")); - - StreamPanner::set_state (node, version); - - for (XMLNodeConstIterator iter = node.children().begin(); iter != node.children().end(); ++iter) { - - if ((*iter)->name() == X_("Automation")) { - - _control->alist()->set_state (*((*iter)->children().front()), version); - - if (_control->alist()->automation_state() != Off) { - double degrees = BaseStereoPanner::lr_fract_to_azimuth (_control->list()->eval (parent.session().transport_frame())); - set_position (AngularVector (degrees, 0.0)); - } - } - } - - return 0; -} - -Panner::Panner (string name, Session& s) - : SessionObject (s, name) - , Automatable (s) -{ - //set_name_old_auto (name); - set_name (name); - - _linked = false; - _link_direction = SameDirection; - _bypassed = false; - _mono = false; -} - -Panner::~Panner () +Panner::set_automation_state (AutoState state) { + _pannable->set_automation_state (state); } -void -Panner::set_linked (bool yn) +AutoState +Panner::automation_state () const { - if (yn != _linked) { - _linked = yn; - _session.set_dirty (); - LinkStateChanged (); /* EMIT SIGNAL */ - } + return _pannable->automation_state(); } -void -Panner::set_link_direction (LinkDirection ld) +AutoStyle +Panner::automation_style () const { - if (ld != _link_direction) { - _link_direction = ld; - _session.set_dirty (); - LinkStateChanged (); /* EMIT SIGNAL */ - } + return _pannable->automation_style (); } - -void -Panner::set_bypassed (bool yn) +bool +Panner::touching () const { - if (yn != _bypassed) { - _bypassed = yn; - StateChanged (); - } + return _pannable->touching (); } - -void -Panner::reset_to_default () +set +Panner::what_can_be_automated() const { - vector positions; - - switch (outputs.size()) { - case 0: - case 1: - return; - } - - if (outputs.size() == 2) { - AngularVector a; - switch (_streampanners.size()) { - case 1: - a.azi = 90.0; /* "front" or "top", in degrees */ - _streampanners.front()->set_position (a); - _streampanners.front()->pan_control()->list()->reset_default (0.5); - return; - break; - case 2: - a.azi = 180.0; /* "left", in degrees */ - _streampanners.front()->set_position (a); - _streampanners.front()->pan_control()->list()->reset_default (0.0); - a.azi = 0.0; /* "right", in degrees */ - _streampanners.back()->set_position (a); - _streampanners.back()->pan_control()->list()->reset_default (1.0); - return; - default: - break; - } - } - - vector::iterator o; - vector::iterator p; - - for (o = outputs.begin(), p = _streampanners.begin(); o != outputs.end() && p != _streampanners.end(); ++o, ++p) { - (*p)->set_position ((*o).position); - } + return _pannable->what_can_be_automated (); } -void -Panner::reset_streampanner (uint32_t which) +string +Panner::describe_parameter (Evoral::Parameter p) { - AngularVector a; - - if (which >= _streampanners.size() || which >= outputs.size()) { - return; - } - - switch (outputs.size()) { - case 0: - case 1: - return; - - case 2: - switch (_streampanners.size()) { - case 1: - /* stereo out, 1 stream, default = middle */ - a.azi = 90.0; /* "front" or "top", in degrees */ - _streampanners.front()->set_position (a); - _streampanners.front()->pan_control()->list()->reset_default (0.5); - break; - case 2: - /* stereo out, 2 streams, default = hard left/right */ - if (which == 0) { - a.azi = 180.0; /* "left", in degrees */ - _streampanners.front()->set_position (a); - _streampanners.front()->pan_control()->list()->reset_default (0.0); - } else { - a.azi = 0.0; /* "right", in degrees */ - _streampanners.back()->set_position (a); - _streampanners.back()->pan_control()->list()->reset_default (1.0); - } - break; - } - return; - - default: - _streampanners[which]->set_position (outputs[which].position); - } -} - -/** - * Reset the panner with a given number of outs and panners (and hence inputs) - * - * \param nouts Number of outputs. - * \param npans Number of panners. - */ -void -Panner::reset (uint32_t nouts, uint32_t npans) -{ - uint32_t n; - bool changed = false; - bool do_not_and_did_not_need_panning = ((nouts < 2) && (outputs.size() < 2)); - - /* if new and old config don't need panning, or if - the config hasn't changed, we're done. - */ - - if (do_not_and_did_not_need_panning || - ((nouts == outputs.size()) && (npans == _streampanners.size()))) { - return; - } - - n = _streampanners.size(); - clear_panners (); - - if (n != npans) { - changed = true; - } - - n = outputs.size(); - outputs.clear (); - - if (n != nouts) { - changed = true; - } - - if (nouts < 2) { - /* no need for panning with less than 2 outputs */ - if (changed) { - Changed (); /* EMIT SIGNAL */ - } - return; - } - - switch (nouts) { - case 0: - /* XXX: this can never happen */ - break; - - case 1: - /* XXX: this can never happen */ - fatal << _("programming error:") - << X_("Panner::reset() called with a single output") - << endmsg; - /*NOTREACHED*/ - break; - - case 2: // line - outputs.push_back (Output (AngularVector (180.0, 0.0))); - outputs.push_back (Output (AngularVector (0.0, 0,0))); - for (n = 0; n < npans; ++n) { - _streampanners.push_back (new EqualPowerStereoPanner (*this, Evoral::Parameter(PanAutomation, 0, n))); - } - break; - - default: - setup_speakers (nouts); - for (n = 0; n < npans; ++n) { - _streampanners.push_back (new VBAPanner (*this, Evoral::Parameter(PanAutomation, 0, n), _session.get_speakers())); - } - break; - } - - for (std::vector::iterator x = _streampanners.begin(); x != _streampanners.end(); ++x) { - (*x)->update (); - } - - setup_meta_controls (); - - /* must emit Changed here, otherwise the changes to the pan_control below raise further - signals which the GUI is not prepared for until it has seen the Changed here. - */ - - if (changed) { - Changed (); /* EMIT SIGNAL */ - } - - /* force hard left/right panning in a common case: 2in/2out - */ - - if (npans == 2 && outputs.size() == 2) { - - /* Do this only if we changed configuration, or our configuration - appears to be the default set up (zero degrees) - */ - - AngularVector left; - AngularVector right; - - left = _streampanners.front()->get_position (); - right = _streampanners.back()->get_position (); - - if (changed || ((left.azi == 0.0) && (right.azi == 0.0))) { - - _streampanners.front()->set_position (AngularVector (180.0, 0.0)); - _streampanners.front()->pan_control()->list()->reset_default (0.0); - - _streampanners.back()->set_position (AngularVector (0.0, 0.0)); - _streampanners.back()->pan_control()->list()->reset_default (1.0); - } - - } else if (npans > 1 && outputs.size() > 2) { - - /* 2d panning: spread signals equally around a circle */ - - double degree_step = 360.0 / nouts; - 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 (npans % 2) { - deg = 90.0 - degree_step; - } else { - deg = 90.0; - } - - for (std::vector::iterator x = _streampanners.begin(); x != _streampanners.end(); ++x) { - (*x)->set_position (AngularVector (deg, 0.0)); - deg += degree_step; - } - } -} - -void -Panner::remove (uint32_t which) -{ - vector::iterator i; - for (i = _streampanners.begin(); i != _streampanners.end() && which; ++i, --which) {} - - if (i != _streampanners.end()) { - delete *i; - _streampanners.erase (i); - } -} - - -/** Remove all our StreamPanners */ -void -Panner::clear_panners () -{ - for (vector::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { - delete *i; - } - - _streampanners.clear (); -} - -void -Panner::set_automation_style (AutoStyle style) -{ - for (vector::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { - ((AutomationList*)(*i)->pan_control()->list().get())->set_automation_style (style); - } - _session.set_dirty (); -} - -void -Panner::set_automation_state (AutoState state) -{ - for (vector::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { - ((AutomationList*)(*i)->pan_control()->list().get())->set_automation_state (state); - } - _session.set_dirty (); -} - -AutoState -Panner::automation_state () const -{ - boost::shared_ptr l; - if (!empty()) { - boost::shared_ptr control = _streampanners.front()->pan_control(); - if (control) { - l = boost::dynamic_pointer_cast(control->list()); - } - } - - return l ? l->automation_state() : Off; -} - -AutoStyle -Panner::automation_style () const -{ - boost::shared_ptr l; - if (!empty()) { - boost::shared_ptr control = _streampanners.front()->pan_control(); - if (control) { - l = boost::dynamic_pointer_cast(control->list()); - } - } - - return l ? l->automation_style() : Absolute; -} - -struct PanPlugins { - string name; - uint32_t nouts; - StreamPanner* (*factory)(Panner&, Evoral::Parameter, Speakers&); -}; - -PanPlugins pan_plugins[] = { - { EqualPowerStereoPanner::name, 2, EqualPowerStereoPanner::factory }, - { VBAPanner::name, 3, VBAPanner::factory }, - { string (""), 0, 0 } -}; - -XMLNode& -Panner::get_state (void) -{ - return state (true); -} - -XMLNode& -Panner::state (bool full) -{ - XMLNode* node = new XMLNode ("Panner"); - - char buf[32]; - - node->add_property (X_("linked"), (_linked ? "yes" : "no")); - node->add_property (X_("link_direction"), enum_2_string (_link_direction)); - node->add_property (X_("bypassed"), (bypassed() ? "yes" : "no")); - snprintf (buf, sizeof (buf), "%zd", outputs.size()); - node->add_property (X_("outputs"), buf); - - for (vector::const_iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { - node->add_child_nocopy ((*i)->state (full)); - } - - node->add_child_nocopy (get_automation_xml_state ()); - - return *node; -} - -int -Panner::set_state (const XMLNode& node, int version) -{ - XMLNodeList nlist = node.children (); - XMLNodeConstIterator niter; - const XMLProperty *prop; - uint32_t i; - uint32_t num_panners = 0; - StreamPanner* sp; - LocaleGuard lg (X_("POSIX")); - - clear_panners (); - - ChanCount ins = ChanCount::ZERO; - ChanCount outs = ChanCount::ZERO; - - // XXX: this might not be necessary anymore - outputs.clear (); - - if ((prop = node.property (X_("linked"))) != 0) { - set_linked (string_is_affirmative (prop->value())); - } - - if ((prop = node.property (X_("bypassed"))) != 0) { - set_bypassed (string_is_affirmative (prop->value())); - } - - if ((prop = node.property (X_("link_direction"))) != 0) { - LinkDirection ld; /* here to provide type information */ - set_link_direction (LinkDirection (string_2_enum (prop->value(), ld))); - } - - if ((prop = node.property (X_("outputs"))) != 0) { - uint32_t n = atoi (prop->value()); - - while (n--) { - AngularVector a; // value is irrelevant - outputs.push_back (Output (a)); - } - - } else { - - /* old school */ - - for (niter = nlist.begin(); niter != nlist.end(); ++niter) { - if ((*niter)->name() == X_("Output")) { - - AngularVector a; - - if ((prop = (*niter)->property (X_("azimuth")))) { - sscanf (prop->value().c_str(), "%lg", &a.azi); - } else if ((prop = (*niter)->property (X_("x")))) { - /* old school cartesian */ - a.azi = BaseStereoPanner::lr_fract_to_azimuth (atof (prop->value().c_str())); - } - - if ((prop = (*niter)->property (X_("elevation")))) { - sscanf (prop->value().c_str(), "%lg", &a.ele); - } - - outputs.push_back (Output (a)); - } - } - } - - for (niter = nlist.begin(); niter != nlist.end(); ++niter) { - - if ((*niter)->name() == X_("StreamPanner")) { - - if ((prop = (*niter)->property (X_("type")))) { - - for (i = 0; pan_plugins[i].factory; ++i) { - if (prop->value() == pan_plugins[i].name) { - - - /* note that we assume that all the stream panners - are of the same type. pretty good - assumption, but it's still an assumption. - */ - - sp = pan_plugins[i].factory (*this, Evoral::Parameter(PanAutomation, 0, num_panners), _session.get_speakers ()); - num_panners++; - - if (sp->set_state (**niter, version) == 0) { - _streampanners.push_back (sp); - } - - break; - } - } - - if (!pan_plugins[i].factory) { - error << string_compose (_("Unknown panner plugin \"%1\" found in pan state - ignored"), - prop->value()) - << endmsg; - } - - } else { - error << _("panner plugin node has no type information!") - << endmsg; - return -1; - } - - } - } - - setup_meta_controls (); - - reset (outputs.size (), num_panners); - - /* don't try to do old-school automation loading if it wasn't marked as existing */ - - if ((prop = node.property (X_("automation")))) { - - /* automation path is relative */ - - automation_path = Glib::build_filename(_session.automation_dir(), prop->value ()); - } - - for (niter = nlist.begin(); niter != nlist.end(); ++niter) { - if ((*niter)->name() == X_("Automation")) { - set_automation_xml_state (**niter, Evoral::Parameter (PanAutomation)); - } - } - - return 0; -} - -bool -Panner::touching () const -{ - for (vector::const_iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { - if (((AutomationList*)(*i)->pan_control()->list().get())->touching ()) { - return true; - } - } - - return false; -} - -void -Panner::set_position (const AngularVector& a, StreamPanner& orig) -{ - AngularVector delta; - AngularVector new_position; - - delta = orig.get_position() - a; - - if (_link_direction == SameDirection) { - - for (vector::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { - if (*i == &orig) { - (*i)->set_position (a, true); - } else { - new_position = (*i)->get_position() + delta; - (*i)->set_position (new_position, true); - } - } - - } else { - - for (vector::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { - if (*i == &orig) { - (*i)->set_position (a, true); - } else { - new_position = (*i)->get_position() - delta; - (*i)->set_position (new_position, true); - } - } - } -} - -void -Panner::distribute_no_automation (BufferSet& inbufs, BufferSet& outbufs, pframes_t nframes, gain_t gain_coeff) -{ - if (outbufs.count().n_audio() == 0) { - // Don't want to lose audio... - assert(inbufs.count().n_audio() == 0); - return; - } - - // We shouldn't be called in the first place... - assert(!bypassed()); - assert(!empty()); - - - if (outbufs.count().n_audio() == 1) { - - AudioBuffer& dst = outbufs.get_audio(0); - - if (gain_coeff == 0.0f) { - - /* only one output, and gain was zero, so make it silent */ - - dst.silence (nframes); - - } else if (gain_coeff == 1.0f){ - - /* mix all buffers into the output */ - - // copy the first - dst.read_from(inbufs.get_audio(0), nframes); - - // accumulate starting with the second - if (inbufs.count().n_audio() > 0) { - BufferSet::audio_iterator i = inbufs.audio_begin(); - for (++i; i != inbufs.audio_end(); ++i) { - dst.merge_from(*i, nframes); - } - } - - } else { - - /* mix all buffers into the output, scaling them all by the gain */ - - // copy the first - dst.read_from(inbufs.get_audio(0), nframes); - - // accumulate (with gain) starting with the second - if (inbufs.count().n_audio() > 0) { - BufferSet::audio_iterator i = inbufs.audio_begin(); - for (++i; i != inbufs.audio_end(); ++i) { - dst.accumulate_with_gain_from(*i, nframes, gain_coeff); - } - } - - } - - return; - } - - /* the terrible silence ... */ - for (BufferSet::audio_iterator i = outbufs.audio_begin(); i != outbufs.audio_end(); ++i) { - i->silence(nframes); - } - - BufferSet::audio_iterator i = inbufs.audio_begin(); - - for (vector::iterator pan = _streampanners.begin(); pan != _streampanners.end() && i != inbufs.audio_end(); ++pan, ++i) { - (*pan)->distribute (*i, outbufs, gain_coeff, nframes); - } -} - -void -Panner::run (BufferSet& inbufs, BufferSet& outbufs, framepos_t start_frame, framepos_t end_frame, pframes_t nframes) -{ - if (outbufs.count().n_audio() == 0) { - // Failing to deliver audio we were asked to deliver is a bug - assert(inbufs.count().n_audio() == 0); - return; - } - - // We shouldn't be called in the first place... - assert(!bypassed()); - assert(!empty()); - - // If we shouldn't play automation defer to distribute_no_automation - if (!(automation_state() & Play || ((automation_state() & Touch) && !touching()))) { - - // Speed quietning - gain_t gain_coeff = 1.0; - - if (fabsf(_session.transport_speed()) > 1.5f && Config->get_quieten_at_speed ()) { - gain_coeff = speed_quietning; - } - - distribute_no_automation (inbufs, outbufs, nframes, gain_coeff); - return; - } - - // Otherwise.. let the automation flow, baby - - if (outbufs.count().n_audio() == 1) { - - AudioBuffer& dst = outbufs.get_audio(0); - - // FIXME: apply gain automation? - - // copy the first - dst.read_from(inbufs.get_audio(0), nframes); - - // accumulate starting with the second - BufferSet::audio_iterator i = inbufs.audio_begin(); - for (++i; i != inbufs.audio_end(); ++i) { - dst.merge_from(*i, nframes); - } - - return; - } - - // More than 1 output, we should have 1 panner for each input - //assert(_streampanners.size() == inbufs.count().n_audio()); - - /* the terrible silence ... */ - for (BufferSet::audio_iterator i = outbufs.audio_begin(); i != outbufs.audio_end(); ++i) { - i->silence(nframes); - } - - BufferSet::audio_iterator i = inbufs.audio_begin(); - for (vector::iterator pan = _streampanners.begin(); pan != _streampanners.end(); ++pan, ++i) { - (*pan)->distribute_automated (*i, outbufs, start_frame, end_frame, nframes, _session.pan_automation_buffer()); - } -} - -/* old school automation handling */ - -/* -void -Panner::set_name (string str) -{ - automation_path = Glib::build_filename(_session.automation_dir(), - _session.snap_name() + "-pan-" + legalize_for_path (str) + ".automation"); -} -*/ - -int -Panner::load () -{ - char line[128]; - uint32_t linecnt = 0; - float version; - vector::iterator sp; - LocaleGuard lg (X_("POSIX")); - - if (automation_path.length() == 0) { - return 0; - } - - if (access (automation_path.c_str(), F_OK)) { - return 0; - } - - ifstream in (automation_path.c_str()); - - if (!in) { - error << string_compose (_("cannot open pan automation file %1 (%2)"), - automation_path, strerror (errno)) - << endmsg; - return -1; - } - - sp = _streampanners.begin(); - - while (in.getline (line, sizeof(line), '\n')) { - - if (++linecnt == 1) { - if (memcmp (line, X_("version"), 7) == 0) { - if (sscanf (line, "version %f", &version) != 1) { - error << string_compose(_("badly formed version number in pan automation event file \"%1\""), automation_path) << endmsg; - return -1; - } - } else { - error << string_compose(_("no version information in pan automation event file \"%1\" (first line = %2)"), - automation_path, line) << endmsg; - return -1; - } - - continue; - } - - if (strlen (line) == 0 || line[0] == '#') { - continue; - } - - if (strcmp (line, "begin") == 0) { - - if (sp == _streampanners.end()) { - error << string_compose (_("too many panner states found in pan automation file %1"), - automation_path) - << endmsg; - return -1; - } - - if ((*sp)->load (in, automation_path, linecnt)) { - return -1; - } - - ++sp; - } - } - - return 0; -} - -void -Panner::set_mono (bool yn) -{ - if (yn != _mono) { - _mono = yn; - StateChanged (); - } - - for (vector::iterator i = _streampanners.begin(); i != _streampanners.end(); ++i) { - (*i)->set_mono (yn); - } -} - -string -Panner::value_as_string (double v) -{ - if (Panner::equivalent (v, 0.5)) { - return _("C"); - } else if (equivalent (v, 0)) { - return _("L"); - } else if (equivalent (v, 1)) { - return _("R"); - } else if (v < 0.5) { - stringstream s; - s << fixed << setprecision (0) << _("L") << ((0.5 - v) * 200) << "%"; - return s.str(); - } else { - stringstream s; - s << fixed << setprecision (0) << _("R") << ((v -0.5) * 200) << "%"; - return s.str (); - } - - return ""; -} - -void -Panner::setup_speakers (uint32_t nouts) -{ - switch (nouts) { - case 3: - /* top, bottom kind-of-left & bottom kind-of-right */ - outputs.push_back (AngularVector (90.0, 0.0)); - outputs.push_back (AngularVector (215.0, 0,0)); - outputs.push_back (AngularVector (335.0, 0,0)); - break; - case 4: - /* clockwise from top left */ - outputs.push_back (AngularVector (135.0, 0.0)); - outputs.push_back (AngularVector (45.0, 0.0)); - outputs.push_back (AngularVector (335.0, 0.0)); - outputs.push_back (AngularVector (215.0, 0.0)); - break; - - default: - { - double degree_step = 360.0 / nouts; - double deg; - uint32_t n; - - /* 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 (nouts % 2) { - deg = 90.0 - degree_step; - } else { - deg = 90.0; - } - for (n = 0; n < nouts; ++n, deg += degree_step) { - outputs.push_back (Output (AngularVector (deg, 0.0))); - } - } - } - - Speakers& speakers (_session.get_speakers()); - - speakers.clear_speakers (); - - for (vector::iterator o = outputs.begin(); o != outputs.end(); ++o) { - speakers.add_speaker ((*o).position); - } -} - -void -Panner::set_stereo_width (double val) -{ - boost::shared_ptr dc = direction_control(); - if (dc) { - dc->set_value (val); - } -} - -void -Panner::set_stereo_position (double val) -{ - boost::shared_ptr wc = width_control(); - if (wc) { - wc->set_value (val); - } -} - -bool -Panner::set_stereo_pan (double direction_as_lr_fract, double width) -{ - AngularVector p (BaseStereoPanner::lr_fract_to_azimuth (direction_as_lr_fract), 0.0); - /* width parameter ranges from -1..+1 with 0.0 at center. we want 0..+1 plus knowing - whether its "reversed" (i.e. left signal pans right and right signal pans left). - full width = 180 degrees - */ - double spread = 2.0 * fabs(width/2.0) * 180.0; - double l_pos = p.azi + (spread/2.0); /* more left is "increasing degrees" */ - double r_pos = p.azi - (spread/2.0); /* more right is "decreasing degrees" */ - bool move_left = true; - bool move_right = true; - int l_index = 0; - int r_index = 1; - - assert (_streampanners.size() > 1); - - if (width < 0.0) { - swap (l_index, r_index); - } - - l_pos = max (min (l_pos, 180.0), 0.0); - r_pos = max (min (r_pos, 180.0), 0.0); - - /* if the new left position is less than or equal to 180 (hard left) and the left panner - is already there, we're not moving the left signal. - */ - - if (l_pos >= 180.0 &&_streampanners[l_index]->get_position().azi == 180.0) { - move_left = false; - } - - /* if the new right position is less than or equal to zero (hard right) and the right panner - is already there, we're not moving the right signal. - */ - - if (r_pos <= 0.0 && _streampanners[r_index]->get_position().azi == 0.0) { - move_right = false; - } - - if (move_left && move_right) { - _streampanners[l_index]->set_position (AngularVector (l_pos, 0.0)); - _streampanners[r_index]->set_position (AngularVector (r_pos, 0.0)); - } - - return move_left && move_right; -} - -void -Panner::setup_meta_controls () -{ - if (_streampanners.size() != 2 || outputs.size() != 2) { - return; - } - - /* 2 signals to 2 outputs: provide "classic" controls for easier manipulation. - - The ID numbers used here don't really matter that much, because Parameters are scoped by owner, - but they keep us out of the ordinary range of pan-related parameters. - */ - - Evoral::Parameter lr_param (PanAutomation, 0, 100); - Evoral::Parameter width_param (PanAutomation, 0, 200); - boost::shared_ptr dc = automation_control (lr_param); - boost::shared_ptr wc = automation_control (width_param); - - if (dc) { - /* reset parent StreamPanner as the current one may have been deleted */ - boost::shared_ptr p = boost::dynamic_pointer_cast (dc); - assert (p); - p->streampanner = _streampanners.front (); - } else { - dc.reset (new StreamPanner::PanControllable (_session, _("lr"), _streampanners.front(), lr_param)); - add_control (dc); - } - - if (wc) { - /* reset parent as above */ - boost::shared_ptr p = boost::dynamic_pointer_cast (wc); - assert (p); - p->streampanner = _streampanners.front (); - } else { - wc.reset (new StreamPanner::PanControllable (_session, _("width"), _streampanners.front(), width_param)); - add_control (wc); - } - - dc->set_value (0.5); - wc->set_value (1.0); // full width -} - -string -Panner::describe_parameter (Evoral::Parameter param) -{ - if (param.type() == PanAutomation) { - switch (param.id()) { - case 100: - return "Pan:position"; - case 200: - return "Pan:width"; - default: - if (_streampanners.size() == 2) { - switch (param.id()) { - case 0: - return "Pan L"; - default: - return "Pan R"; - } - } else { - stringstream ss; - ss << "Pan " << param.id() + 1; - return ss.str (); - } - } - } - - return Automatable::describe_parameter (param); + return _pannable->describe_parameter (p); } diff --git a/libs/ardour/panner_shell.cc b/libs/ardour/panner_shell.cc index c6cbbe36ab..7963682bcf 100644 --- a/libs/ardour/panner_shell.cc +++ b/libs/ardour/panner_shell.cc @@ -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 */ } diff --git a/libs/ardour/port_insert.cc b/libs/ardour/port_insert.cc index f0940670d4..df758e2230 100644 --- a/libs/ardour/port_insert.cc +++ b/libs/ardour/port_insert.cc @@ -41,9 +41,9 @@ using namespace std; using namespace ARDOUR; using namespace PBD; -PortInsert::PortInsert (Session& s, boost::shared_ptr mm) +PortInsert::PortInsert (Session& s, boost::shared_ptr pannable, boost::shared_ptr 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; diff --git a/libs/ardour/route.cc b/libs/ardour/route.cc index c1004a18f9..b732fd09e8 100644 --- a/libs/ardour/route.cc +++ b/libs/ardour/route.cc @@ -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(), Delivery::Role (0))); + processor.reset (new InternalSend (_session, _pannable, _mute_master, boost::shared_ptr(), 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, 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 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 pc = boost::dynamic_pointer_cast (ci->second); if (pc) { boost::shared_ptr al = pc->alist(); XMLNode& before = al->get_state (); @@ -3461,10 +3474,23 @@ Route::meter () } } +boost::shared_ptr +Route::pannable() const +{ + return _pannable; +} + boost::shared_ptr Route::panner() const { - return _main_outs->panner(); + /* may be null ! */ + return _main_outs->panner_shell()->panner(); +} + +boost::shared_ptr +Route::panner_shell() const +{ + return _main_outs->panner_shell(); } boost::shared_ptr diff --git a/libs/ardour/send.cc b/libs/ardour/send.cc index 33b4277d93..57c8500454 100644 --- a/libs/ardour/send.cc +++ b/libs/ardour/send.cc @@ -38,8 +38,8 @@ using namespace ARDOUR; using namespace PBD; using namespace std; -Send::Send (Session& s, boost::shared_ptr mm, Role r) - : Delivery (s, mm, string_compose (_("send %1"), (_bitslot = s.next_send_id()) + 1), r) +Send::Send (Session& s, boost::shared_ptr p, boost::shared_ptr 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)); diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index 9ac7dcc908..e569188a38 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -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; } diff --git a/libs/ardour/session_state.cc b/libs/ardour/session_state.cc index 4f11291637..54c36a3459 100644 --- a/libs/ardour/session_state.cc +++ b/libs/ardour/session_state.cc @@ -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 p = r->panner(); - if (p) { - c = p->direction_control(); - } + c = r->pannable()->pan_azimuth_control; break; } case ControllableDescriptor::PanWidth: { - boost::shared_ptr 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; } diff --git a/libs/ardour/speakers.cc b/libs/ardour/speakers.cc index 7d0461ff52..4229e77c65 100644 --- a/libs/ardour/speakers.cc +++ b/libs/ardour/speakers.cc @@ -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::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; +} diff --git a/libs/ardour/thread_buffers.cc b/libs/ardour/thread_buffers.cc index 2436b6d365..f30edee572 100644 --- a/libs/ardour/thread_buffers.cc +++ b/libs/ardour/thread_buffers.cc @@ -18,6 +18,7 @@ */ #include +#include #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; } diff --git a/libs/ardour/vbap.cc b/libs/ardour/vbap.cc deleted file mode 100644 index 96fc1336fb..0000000000 --- a/libs/ardour/vbap.cc +++ /dev/null @@ -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 -#include -#include -#include - -#include -#include - -#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); -} - diff --git a/libs/ardour/vbap_speakers.cc b/libs/ardour/vbap_speakers.cc deleted file mode 100644 index 9090ed65e1..0000000000 --- a/libs/ardour/vbap_speakers.cc +++ /dev/null @@ -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 -#include -#include - -#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::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 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& 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 tmp = _speakers; - vector::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; - } -} - - diff --git a/libs/ardour/wscript b/libs/ardour/wscript index 0a81c306c3..1a3296d4d5 100644 --- a/libs/ardour/wscript +++ b/libs/ardour/wscript @@ -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' ] diff --git a/libs/evoral/evoral/ControlList.hpp b/libs/evoral/evoral/ControlList.hpp index 833fae4440..cfb7564ab6 100644 --- a/libs/evoral/evoral/ControlList.hpp +++ b/libs/evoral/evoral/ControlList.hpp @@ -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); diff --git a/libs/evoral/src/ControlList.cpp b/libs/evoral/src/ControlList.cpp index cc8ba45001..6738e8c1ac 100644 --- a/libs/evoral/src/ControlList.cpp +++ b/libs/evoral/src/ControlList.cpp @@ -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); diff --git a/libs/gtkmm2ext/gtkmm2ext/utils.h b/libs/gtkmm2ext/gtkmm2ext/utils.h index aea4ff11f5..3528435ed1 100644 --- a/libs/gtkmm2ext/gtkmm2ext/utils.h +++ b/libs/gtkmm2ext/gtkmm2ext/utils.h @@ -24,6 +24,7 @@ #include #include +#include #include #include /* for WMDecoration */ #include @@ -86,6 +87,8 @@ namespace Gtkmm2ext { int physical_screen_height (Glib::RefPtr); int physical_screen_width (Glib::RefPtr); + + void container_clear (Gtk::Container&); }; #endif /* __gtkmm2ext_utils_h__ */ diff --git a/libs/gtkmm2ext/utils.cc b/libs/gtkmm2ext/utils.cc index afff784ae8..df79e535a7 100644 --- a/libs/gtkmm2ext/utils.cc +++ b/libs/gtkmm2ext/utils.cc @@ -377,3 +377,12 @@ Gtkmm2ext::physical_screen_width (Glib::RefPtr win) return gdk_screen_get_width (scr); } } + +void +Gtkmm2ext::container_clear (Gtk::Container& c) +{ + list children = c.get_children(); + for (list::iterator child = children.begin(); child != children.end(); ++child) { + c.remove (**child); + } +} diff --git a/libs/panners/1in2out/panner_1in2out.cc b/libs/panners/1in2out/panner_1in2out.cc index 2851aec095..7a59aa04cc 100644 --- a/libs/panners/1in2out/panner_1in2out.cc +++ b/libs/panners/1in2out/panner_1in2out.cc @@ -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,37 +63,72 @@ 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 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 () { } +void +Panner1in2out::update () +{ + 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::set_position (double p) { - _desired_right = p; - _desired_left = 1 - 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::do_distribute_one (AudioBuffer& srcbuf, BufferSet& obufs, gain_t gain_coeff, pframes_t nframes, uint32_t /* not used */) +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 } +void +Panner1in2out::distribute_one_automated (AudioBuffer& srcbuf, BufferSet& obufs, + framepos_t start, framepos_t end, pframes_t nframes, + pan_t** buffers, uint32_t which) +{ + 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 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 +Panner1in2out::what_can_be_automated() const +{ + set s; + s.insert (Evoral::Parameter (PanAzimuthAutomation)); + return s; +} + string -Panner1in2out::describe_parameter (Evoral::Parameter param) +Panner1in2out::describe_parameter (Evoral::Parameter p) { - switch (param.type()) { - case PanWidthAutomation: - return "Pan:width"; + switch (p.type()) { 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); + return _("L/R"); + default: + return _pannable->describe_parameter (p); + } } - diff --git a/libs/panners/1in2out/panner_1in2out.h b/libs/panners/1in2out/panner_1in2out.h index 152eb7156a..ced467c11b 100644 --- a/libs/panners/1in2out/panner_1in2out.h +++ b/libs/panners/1in2out/panner_1in2out.h @@ -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); + ~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 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, 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 _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__ */ diff --git a/libs/panners/1in2out/wscript b/libs/panners/1in2out/wscript new file mode 100644 index 0000000000..e7e4d29928 --- /dev/null +++ b/libs/panners/1in2out/wscript @@ -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() + diff --git a/libs/panners/2in2out/panner_2in2out.cc b/libs/panners/2in2out/panner_2in2out.cc index 6bc0f93a8f..2c2856361c 100644 --- a/libs/panners/2in2out/panner_2in2out.cc +++ b/libs/panners/2in2out/panner_2in2out.cc @@ -71,21 +71,21 @@ extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } Panner2in2out::Panner2in2out (boost::shared_ptr 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; - - cerr << "Clamp pos = " << direction_as_lr_fract << " w = " << width << endl; + double r_pos; + double l_pos; - if (width > 1.0 || width < 1.0) { - return false; - } + width = max (min (width, 1.0), -1.0); + direction_as_lr_fract = max (min (direction_as_lr_fract, 1.0), 0.0); - 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 +Panner2in2out::what_can_be_automated() const +{ + set 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); + } +} diff --git a/libs/panners/2in2out/panner_2in2out.h b/libs/panners/2in2out/panner_2in2out.h index 8d8d57d709..0bb38fa27f 100644 --- a/libs/panners/2in2out/panner_2in2out.h +++ b/libs/panners/2in2out/panner_2in2out.h @@ -55,8 +55,12 @@ class Panner2in2out : public Panner double position () const; double width () const; + std::set what_can_be_automated() const; + static Panner* factory (boost::shared_ptr, Speakers&); + std::string describe_parameter (Evoral::Parameter); + XMLNode& state (bool full_state); XMLNode& get_state (void); int set_state (const XMLNode&, int version); diff --git a/libs/panners/2in2out/wscript b/libs/panners/2in2out/wscript index 509848e5a6..22d5c03726 100644 --- a/libs/panners/2in2out/wscript +++ b/libs/panners/2in2out/wscript @@ -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' ] diff --git a/libs/panners/vbap/vbap.cc b/libs/panners/vbap/vbap.cc index 1876f4cf44..6088a2d0d6 100644 --- a/libs/panners/vbap/vbap.cc +++ b/libs/panners/vbap/vbap.cc @@ -10,27 +10,26 @@ #include "ardour/pannable.h" #include "ardour/speakers.h" -#include "ardour/vbap.h" -#include "ardour/vbap_speakers.h" #include "ardour/audio_buffer.h" #include "ardour/buffer_set.h" #include "ardour/pan_controllable.h" +#include "vbap.h" +#include "vbap_speakers.h" + using namespace PBD; using namespace ARDOUR; using namespace std; static PanPluginDescriptor _descriptor = { "VBAP 2D panner", - 1, -1, 2, -1, + -1, -1, VBAPanner::factory }; extern "C" { PanPluginDescriptor* panner_descriptor () { return &_descriptor; } } VBAPanner::Signal::Signal (Session& session, VBAPanner& p, uint32_t n) - : azimuth_control (new PanControllable (session, string_compose (_("azimuth %1"), n+1), &p, Evoral::Parameter (PanAzimuthAutomation, 0, n))) - , elevation_control (new PanControllable (session, string_compose (_("elevation %1"), n+1), &p, Evoral::Parameter (PanElevationAutomation, 0, n))) { gains[0] = gains[1] = gains[2] = 0; desired_gains[0] = desired_gains[1] = desired_gains[2] = 0; @@ -40,44 +39,86 @@ VBAPanner::Signal::Signal (Session& session, VBAPanner& p, uint32_t n) VBAPanner::VBAPanner (boost::shared_ptr 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 () +{ + clear_signals (); +} + +void +VBAPanner::clear_signals () { for (vector::iterator i = _signals.begin(); i != _signals.end(); ++i) { delete *i; } + _signals.clear (); } void -VBAPanner::configure_io (const ChanCount& in, const ChanCount& /* ignored - we use Speakers */) +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::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::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); - - if (was_dirty) { - memcpy (signal->gains, signal->desired_gains, sizeof (signal->gains)); - memcpy (signal->outputs, signal->desired_outputs, sizeof (signal->outputs)); - } + distribute_one (inbufs.get_audio (n), obufs, gain_coefficient, nframes, n); + + 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 -VBAPanner::azimuth_control (uint32_t n) -{ - if (n >= _signals.size()) { - return boost::shared_ptr(); - } - return _signals[n]->azimuth_control; -} - -boost::shared_ptr -VBAPanner::evelation_control (uint32_t n) -{ - if (n >= _signals.size()) { - return boost::shared_ptr(); - } - return _signals[n]->elevation_control; -} - Panner* VBAPanner::factory (boost::shared_ptr 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 +VBAPanner::what_can_be_automated() const +{ + set 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); + } +} diff --git a/libs/panners/vbap/vbap.h b/libs/panners/vbap/vbap.h index aacff8894c..2b80b032cb 100644 --- a/libs/panners/vbap/vbap.h +++ b/libs/panners/vbap/vbap.h @@ -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, 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 what_can_be_automated() const; + static Panner* factory (boost::shared_ptr, 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 azimuth_control (uint32_t signal); - boost::shared_ptr 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 azimuth_control; - boost::shared_ptr elevation_control; Signal (Session&, VBAPanner&, uint32_t which); }; std::vector _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); }; diff --git a/libs/panners/vbap/vbap_speakers.cc b/libs/panners/vbap/vbap_speakers.cc index 9090ed65e1..7e70e5df66 100644 --- a/libs/panners/vbap/vbap_speakers.cc +++ b/libs/panners/vbap/vbap_speakers.cc @@ -36,7 +36,8 @@ #include #include "pbd/cartesian.h" -#include "ardour/vbap_speakers.h" + +#include "vbap_speakers.h" using namespace ARDOUR; using namespace PBD; diff --git a/libs/panners/vbap/wscript b/libs/panners/vbap/wscript new file mode 100644 index 0000000000..1399039483 --- /dev/null +++ b/libs/panners/vbap/wscript @@ -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() + diff --git a/libs/panners/wscript b/libs/panners/wscript index 63b547d093..192f27b7db 100644 --- a/libs/panners/wscript +++ b/libs/panners/wscript @@ -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) - diff --git a/libs/pbd/pbd/controllable_descriptor.h b/libs/pbd/pbd/controllable_descriptor.h index 1bf7705595..6b0d733656 100644 --- a/libs/pbd/pbd/controllable_descriptor.h +++ b/libs/pbd/pbd/controllable_descriptor.h @@ -39,6 +39,7 @@ public: Recenable, PanDirection, PanWidth, + PanElevation, Balance, SendGain, PluginParameter diff --git a/libs/surfaces/mackie/mackie_control_protocol.cc b/libs/surfaces/mackie/mackie_control_protocol.cc index ce2b2088d2..005d28f7aa 100644 --- a/libs/surfaces/mackie/mackie_control_protocol.cc +++ b/libs/surfaces/mackie/mackie_control_protocol.cc @@ -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 = 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 = 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()) { diff --git a/libs/surfaces/mackie/route_signal.cc b/libs/surfaces/mackie/route_signal.cc index 2f6a6f7c5f..f6db125b8c 100644 --- a/libs/surfaces/mackie/route_signal.cc +++ b/libs/surfaces/mackie/route_signal.cc @@ -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 trk = boost::dynamic_pointer_cast(_route); diff --git a/libs/surfaces/osc/osc.cc b/libs/surfaces/osc/osc.cc index 93b56a8a97..280959ae09 100644 --- a/libs/surfaces/osc/osc.cc +++ b/libs/surfaces/osc/osc.cc @@ -870,7 +870,7 @@ OSC::route_set_pan_stereo_position (int rid, float pos) if (r) { boost::shared_ptr 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 = r->panner(); if (panner) { - panner->set_stereo_width (pos); + panner->set_width (pos); } } diff --git a/wscript b/wscript index 4c86cc37c9..9882898eb4 100644 --- a/wscript +++ b/wscript @@ -25,6 +25,7 @@ children = [ 'libs/taglib', 'libs/rubberband', 'libs/surfaces', + 'libs/panners', 'libs/timecode', 'libs/ardour', 'libs/gtkmm2ext', -- 2.30.2