strict i/o: limit output channels.
[ardour.git] / libs / ardour / session.cc
index f57731b2454ce9bdc3cbd4c2ab758c4ad5213fcd..edae568645e8e92634b445eb054a7567b76952e7 100644 (file)
@@ -71,7 +71,9 @@
 #include "ardour/engine_state_controller.h"
 #endif
 #include "ardour/filename_extensions.h"
+#include "ardour/gain_control.h"
 #include "ardour/graph.h"
+#include "ardour/luabindings.h"
 #include "ardour/midiport_manager.h"
 #include "ardour/scene_changer.h"
 #include "ardour/midi_patch_manager.h"
 #include "midi++/port.h"
 #include "midi++/mmc.h"
 
+#include "LuaBridge/LuaBridge.h"
+
 #include "i18n.h"
 
 #include <glibmm/checksum.h>
@@ -180,9 +184,10 @@ Session::Session (AudioEngine &eng,
        , current_block_size (0)
        , _worst_output_latency (0)
        , _worst_input_latency (0)
-       , _worst_track_latency (0)
+       , _worst_track_latency (0)
        , _have_captured (false)
        , _non_soloed_outs_muted (false)
+       , _listening (false)
        , _listen_cnt (0)
        , _solo_isolated_cnt (0)
        , _writable (false)
@@ -199,8 +204,8 @@ Session::Session (AudioEngine &eng,
        , post_export_sync (false)
        , post_export_position (0)
        , _exporting (false)
-       , _export_started (false)
        , _export_rolling (false)
+       , _export_preroll (0)
        , _pre_export_mmc_enabled (false)
        , _name (snapshot_name)
        , _is_new (true)
@@ -225,6 +230,9 @@ Session::Session (AudioEngine &eng,
        , pending_locate_flush (false)
        , pending_abort (false)
        , pending_auto_loop (false)
+       , _mempool ("Session", 1048576)
+       , lua (lua_newstate (&PBD::ReallocPool::lalloc, &_mempool))
+       , _n_lua_scripts (0)
        , _butler (new Butler (*this))
        , _post_transport_work (0)
        ,  cumulative_rf_motion (0)
@@ -305,6 +313,8 @@ Session::Session (AudioEngine &eng,
 
        pre_engine_init (fullpath);
 
+       setup_lua ();
+
        if (_is_new) {
 
                Stateful::loading_state_version = CURRENT_SESSION_FILE_VERSION;
@@ -317,6 +327,11 @@ Session::Session (AudioEngine &eng,
                        throw SessionException (_("Cannot connect to audio/midi engine"));
                }
 
+               // set samplerate for plugins added early
+               // e.g from templates or MB channelstrip
+               set_block_size (_engine.samples_per_cycle());
+               set_frame_rate (_engine.sample_rate());
+
                if (create (mix_template, bus_profile)) {
                        destroy ();
                        throw SessionException (_("Session initialization failed"));
@@ -544,6 +559,8 @@ Session::destroy ()
 
        remove_pending_capture_state ();
 
+       Analyser::flush ();
+
        _state_of_the_state = StateOfTheState (CannotSave|Deletion);
 
        /* disconnect from any and all signals that we are connected to */
@@ -581,8 +598,19 @@ Session::destroy ()
        delete state_tree;
        state_tree = 0;
 
-       /* reset dynamic state version back to default */
+       // unregister all lua functions, drop held references (if any)
+       (*_lua_cleanup)();
+       lua.do_command ("Session = nil");
+       delete _lua_run;
+       delete _lua_add;
+       delete _lua_del;
+       delete _lua_list;
+       delete _lua_save;
+       delete _lua_load;
+       delete _lua_cleanup;
+       lua.collect_garbage ();
 
+       /* reset dynamic state version back to default */
        Stateful::loading_state_version = 0;
 
        _butler->drop_references ();
@@ -725,8 +753,12 @@ void
 Session::setup_click ()
 {
        _clicking = false;
+
+       boost::shared_ptr<AutomationList> gl (new AutomationList (Evoral::Parameter (GainAutomation)));
+       boost::shared_ptr<GainControl> gain_control = boost::shared_ptr<GainControl> (new GainControl (*this, Evoral::Parameter(GainAutomation), gl));
+
        _click_io.reset (new ClickIO (*this, X_("Click")));
-       _click_gain.reset (new Amp (*this));
+       _click_gain.reset (new Amp (*this, _("Fader"), gain_control, true));
        _click_gain->activate ();
        if (state_tree) {
                setup_click_state (state_tree->root());
@@ -1015,6 +1047,7 @@ Session::remove_monitor_section ()
        if (auditioner) {
                auditioner->connect ();
        }
+       Config->ParameterChanged ("use-monitor-bus");
 }
 
 void
@@ -1166,6 +1199,7 @@ Session::add_monitor_section ()
        if (auditioner) {
                auditioner->connect ();
        }
+       Config->ParameterChanged ("use-monitor-bus");
 }
 
 void
@@ -1817,6 +1851,19 @@ Session::enable_record ()
        }
 }
 
+void
+Session::set_all_tracks_record_enabled (bool enable )
+{
+       boost::shared_ptr<RouteList> rl = routes.reader();
+       for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) {
+               boost::shared_ptr<Track> tr = boost::dynamic_pointer_cast<Track> (*i);
+               if (tr) {
+                       tr->set_record_enabled (enable, Controllable::NoGroup);
+               }
+       }
+}
+
+
 void
 Session::disable_record (bool rt_context, bool force)
 {
@@ -2388,6 +2435,92 @@ Session::new_midi_track (const ChanCount& input, const ChanCount& output, boost:
        return ret;
 }
 
+RouteList
+Session::new_midi_route (RouteGroup* route_group, uint32_t how_many, string name_template, boost::shared_ptr<PluginInfo> instrument)
+{
+       string bus_name;
+       uint32_t bus_id = 0;
+       string port;
+       RouteList ret;
+
+       bool const use_number = (how_many != 1) || name_template.empty () || name_template == _("Midi Bus");
+
+       while (how_many) {
+               if (!find_route_name (name_template.empty () ? _("Midi Bus") : name_template, ++bus_id, bus_name, use_number)) {
+                       error << "cannot find name for new midi bus" << endmsg;
+                       goto failure;
+               }
+
+               try {
+                       boost::shared_ptr<Route> bus (new Route (*this, bus_name, Route::Flag(0), DataType::AUDIO)); // XXX Editor::add_routes is not ready for ARDOUR::DataType::MIDI
+
+                       if (bus->init ()) {
+                               goto failure;
+                       }
+
+#ifdef BOOST_SP_ENABLE_DEBUG_HOOKS
+                       // boost_debug_shared_ptr_mark_interesting (bus.get(), "Route");
+#endif
+                       {
+                               Glib::Threads::Mutex::Lock lm (AudioEngine::instance()->process_lock ());
+
+                               if (bus->input()->ensure_io (ChanCount(DataType::MIDI, 1), false, this)) {
+                                       error << _("cannot configure new midi bus input") << endmsg;
+                                       goto failure;
+                               }
+
+
+                               if (bus->output()->ensure_io (ChanCount(DataType::MIDI, 1), false, this)) {
+                                       error << _("cannot configure new midi bus output") << endmsg;
+                                       goto failure;
+                               }
+                       }
+
+                       if (route_group) {
+                               route_group->add (bus);
+                       }
+                       if (Config->get_remote_model() == UserOrdered) {
+                               bus->set_remote_control_id (next_control_id());
+                       }
+
+                       ret.push_back (bus);
+                       RouteAddedOrRemoved (true); /* EMIT SIGNAL */
+                       ARDOUR::GUIIdle ();
+               }
+
+               catch (failed_constructor &err) {
+                       error << _("Session: could not create new audio route.") << endmsg;
+                       goto failure;
+               }
+
+               catch (AudioEngine::PortRegistrationFailure& pfe) {
+                       error << pfe.what() << endmsg;
+                       goto failure;
+               }
+
+
+               --how_many;
+       }
+
+  failure:
+       if (!ret.empty()) {
+               StateProtector sp (this);
+               add_routes (ret, false, false, false);
+
+               if (instrument) {
+                       for (RouteList::iterator r = ret.begin(); r != ret.end(); ++r) {
+                               PluginPtr plugin = instrument->load (*this);
+                               boost::shared_ptr<Processor> p (new PluginInsert (*this, plugin));
+                               (*r)->add_processor (p, PreFader);
+                       }
+               }
+       }
+
+       return ret;
+
+}
+
+
 void
 Session::midi_output_change_handler (IOChange change, void * /*src*/, boost::weak_ptr<Route> wmt)
 {
@@ -2865,7 +2998,7 @@ Session::new_audio_track (int input_channels, int output_channels, TrackMode mod
                                //  0 for Stereo Out mode
                                //  0 Multi Out mode
                                if (Config->get_output_auto_connect() & AutoConnectMaster) {
-                                       track->set_gain (dB_to_coefficient (0), 0);
+                                       track->set_gain (dB_to_coefficient (0), Controllable::NoGroup);
                                }
                        }
 
@@ -3032,30 +3165,39 @@ Session::new_audio_route (int input_channels, int output_channels, RouteGroup* r
 }
 
 RouteList
-Session::new_route_from_template (uint32_t how_many, const std::string& template_path, const std::string& name_base)
+Session::new_route_from_template (uint32_t how_many, const std::string& template_path, const std::string& name_base, PlaylistDisposition pd)
 {
-       RouteList ret;
-       uint32_t control_id;
        XMLTree tree;
-       uint32_t number = 0;
-       const uint32_t being_added = how_many;
 
        if (!tree.read (template_path.c_str())) {
-               return ret;
+               return RouteList();
        }
 
-       XMLNode* node = tree.root();
+       return new_route_from_template (how_many, *tree.root(), name_base, pd);
+}
 
+RouteList
+Session::new_route_from_template (uint32_t how_many, XMLNode& node, const std::string& name_base, PlaylistDisposition pd)
+{
+       RouteList ret;
+       uint32_t control_id;
+       uint32_t number = 0;
+       const uint32_t being_added = how_many;
+       /* This will prevent the use of any existing XML-provided PBD::ID
+          values by Stateful.
+       */
+       Stateful::ForceIDRegeneration force_ids;
        IO::disable_connecting ();
 
        control_id = next_control_id ();
 
        while (how_many) {
 
-               XMLNode node_copy (*node);
+               /* We're going to modify the node contents a bit so take a
+                * copy. The node may be re-used when duplicating more than once.
+                */
 
-               /* Remove IDs of everything so that new ones are used */
-               node_copy.remove_property_recursively (X_("id"));
+               XMLNode node_copy (node);
 
                try {
                        string name;
@@ -3084,7 +3226,18 @@ Session::new_route_from_template (uint32_t how_many, const std::string& template
                        }
 
                        /* set this name in the XML description that we are about to use */
-                       Route::set_name_in_state (node_copy, name);
+
+                       bool rename_playlist;
+                       switch (pd) {
+                       case NewPlaylist:
+                               rename_playlist = true;
+                               break;
+                       case CopyPlaylist:
+                       case SharePlaylist:
+                               rename_playlist = false;
+                       }
+
+                       Route::set_name_in_state (node_copy, name, rename_playlist);
 
                        /* trim bitslots from listen sends so that new ones are used */
                        XMLNodeList children = node_copy.children ();
@@ -3122,6 +3275,21 @@ Session::new_route_from_template (uint32_t how_many, const std::string& template
                        route->set_remote_control_id (control_id);
                        ++control_id;
 
+                       boost::shared_ptr<Track> track;
+
+                       if ((track = boost::dynamic_pointer_cast<Track> (route))) {
+                               switch (pd) {
+                               case NewPlaylist:
+                                       track->use_new_playlist ();
+                                       break;
+                               case CopyPlaylist:
+                                       track->use_copy_playlist ();
+                                       break;
+                               case SharePlaylist:
+                                       break;
+                               }
+                       };
+
                        ret.push_back (route);
 
                        RouteAddedOrRemoved (true); /* EMIT SIGNAL */
@@ -3218,10 +3386,10 @@ Session::add_routes_inner (RouteList& new_routes, bool input_auto_connect, bool
                boost::weak_ptr<Route> wpr (*x);
                boost::shared_ptr<Route> r (*x);
 
-               r->listen_changed.connect_same_thread (*this, boost::bind (&Session::route_listen_changed, this, _2, wpr));
-               r->solo_changed.connect_same_thread (*this, boost::bind (&Session::route_solo_changed, this, _1, _3, wpr));
-               r->solo_isolated_changed.connect_same_thread (*this, boost::bind (&Session::route_solo_isolated_changed, this, _1, wpr));
-               r->mute_changed.connect_same_thread (*this, boost::bind (&Session::route_mute_changed, this, _1));
+               r->listen_changed.connect_same_thread (*this, boost::bind (&Session::route_listen_changed, this, _1, wpr));
+               r->solo_changed.connect_same_thread (*this, boost::bind (&Session::route_solo_changed, this, _1, _2, wpr));
+               r->solo_isolated_changed.connect_same_thread (*this, boost::bind (&Session::route_solo_isolated_changed, this, wpr));
+               r->mute_changed.connect_same_thread (*this, boost::bind (&Session::route_mute_changed, this));
                r->output()->changed.connect_same_thread (*this, boost::bind (&Session::set_worst_io_latencies_x, this, _1, _2));
                r->processors_changed.connect_same_thread (*this, boost::bind (&Session::route_processors_changed, this, _1));
 
@@ -3293,7 +3461,7 @@ Session::globally_set_send_gains_to_zero (boost::shared_ptr<Route> dest)
 
        for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
                if ((s = (*i)->internal_send_for (dest)) != 0) {
-                       s->amp()->gain_control()->set_value (GAIN_COEFF_ZERO);
+                       s->amp()->gain_control()->set_value (GAIN_COEFF_ZERO, Controllable::NoGroup);
                }
        }
 }
@@ -3306,7 +3474,7 @@ Session::globally_set_send_gains_to_unity (boost::shared_ptr<Route> dest)
 
        for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
                if ((s = (*i)->internal_send_for (dest)) != 0) {
-                       s->amp()->gain_control()->set_value (GAIN_COEFF_UNITY);
+                       s->amp()->gain_control()->set_value (GAIN_COEFF_UNITY, Controllable::NoGroup);
                }
        }
 }
@@ -3319,7 +3487,7 @@ Session::globally_set_send_gains_from_track(boost::shared_ptr<Route> dest)
 
        for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
                if ((s = (*i)->internal_send_for (dest)) != 0) {
-                       s->amp()->gain_control()->set_value ((*i)->gain_control()->get_value());
+                       s->amp()->gain_control()->set_value ((*i)->gain_control()->get_value(), Controllable::NoGroup);
                }
        }
 }
@@ -3387,7 +3555,7 @@ Session::remove_routes (boost::shared_ptr<RouteList> routes_to_remove)
                                continue;
                        }
 
-                       (*iter)->set_solo (false, this);
+                       (*iter)->set_solo (false, Controllable::NoGroup);
 
                        rs->remove (*iter);
 
@@ -3495,13 +3663,13 @@ Session::remove_route (boost::shared_ptr<Route> route)
 }
 
 void
-Session::route_mute_changed (void* /*src*/)
+Session::route_mute_changed ()
 {
        set_dirty ();
 }
 
 void
-Session::route_listen_changed (bool group_override, boost::weak_ptr<Route> wpr)
+Session::route_listen_changed (Controllable::GroupControlDisposition group_override, boost::weak_ptr<Route> wpr)
 {
        boost::shared_ptr<Route> route = wpr.lock();
        if (!route) {
@@ -3512,18 +3680,32 @@ Session::route_listen_changed (bool group_override, boost::weak_ptr<Route> wpr)
        if (route->listening_via_monitor ()) {
 
                if (Config->get_exclusive_solo()) {
-                       /* new listen: disable all other listen, except solo-grouped channels */
+
                        RouteGroup* rg = route->route_group ();
-                       bool leave_group_alone = (rg && rg->is_active() && rg->is_solo());
-                       if (group_override && rg) {
-                               leave_group_alone = !leave_group_alone;
-                       }
+                       const bool group_already_accounted_for = route->use_group (group_override, &RouteGroup::is_solo);
+
                        boost::shared_ptr<RouteList> r = routes.reader ();
+
                        for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
-                               if ((*i) == route || (*i)->solo_isolated() || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_auditioner() || (leave_group_alone && ((*i)->route_group() == rg))) {
+                               if ((*i) == route) {
+                                       /* already changed */
                                        continue;
                                }
-                               (*i)->set_listen (false, this, group_override);
+
+                               if ((*i)->solo_isolated() || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_auditioner()) {
+                                       /* route does not get solo propagated to it */
+                                       continue;
+                               }
+
+                               if ((group_already_accounted_for && (*i)->route_group() && (*i)->route_group() == rg)) {
+                                       /* this route is a part of the same solo group as the route
+                                        * that was changed. Changing that route did change or will
+                                        * change all group members appropriately, so we can ignore it
+                                        * here
+                                        */
+                                       continue;
+                               }
+                               (*i)->set_listen (false, Controllable::NoGroup);
                        }
                }
 
@@ -3537,7 +3719,7 @@ Session::route_listen_changed (bool group_override, boost::weak_ptr<Route> wpr)
        update_route_solo_state ();
 }
 void
-Session::route_solo_isolated_changed (void* /*src*/, boost::weak_ptr<Route> wpr)
+Session::route_solo_isolated_changed (boost::weak_ptr<Route> wpr)
 {
        boost::shared_ptr<Route> route = wpr.lock ();
 
@@ -3567,7 +3749,7 @@ Session::route_solo_isolated_changed (void* /*src*/, boost::weak_ptr<Route> wpr)
 }
 
 void
-Session::route_solo_changed (bool self_solo_change, bool group_override,  boost::weak_ptr<Route> wpr)
+Session::route_solo_changed (bool self_solo_change, Controllable::GroupControlDisposition group_override,  boost::weak_ptr<Route> wpr)
 {
        DEBUG_TRACE (DEBUG::Solo, string_compose ("route solo change, self = %1\n", self_solo_change));
 
@@ -3588,21 +3770,51 @@ Session::route_solo_changed (bool self_solo_change, bool group_override,  boost:
                delta = -1;
        }
 
+       /* the route may be a member of a group that has shared-solo
+        * semantics. If so, then all members of that group should follow the
+        * solo of the changed route. But ... this is optional, controlled by a
+        * Controllable::GroupControlDisposition.
+        *
+        * The first argument to the signal that this method is connected to is the
+        * GroupControlDisposition value that was used to change solo.
+        *
+        * If the solo change was done with group semantics (either InverseGroup
+        * (force the entire group to change even if the group shared solo is
+        * disabled) or UseGroup (use the group, which may or may not have the
+        * shared solo property enabled)) then as we propagate the change to
+        * the entire session we should IGNORE THE GROUP that the changed route
+        * belongs to.
+        */
+
        RouteGroup* rg = route->route_group ();
-       bool leave_group_alone = (rg && rg->is_active() && rg->is_solo());
-       if (group_override && rg) {
-               leave_group_alone = !leave_group_alone;
-       }
+       const bool group_already_accounted_for = (group_override == Controllable::ForGroup);
+
        if (delta == 1 && Config->get_exclusive_solo()) {
 
                /* new solo: disable all other solos, but not the group if its solo-enabled */
 
                for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
-                       if ((*i) == route || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_auditioner() ||
-                           (leave_group_alone && ((*i)->route_group() == rg))) {
+
+                       if ((*i) == route) {
+                               /* already changed */
+                               continue;
+                       }
+
+                       if ((*i)->solo_isolated() || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_auditioner()) {
+                               /* route does not get solo propagated to it */
                                continue;
                        }
-                       (*i)->set_solo (false, this, group_override);
+
+                       if ((group_already_accounted_for && (*i)->route_group() && (*i)->route_group() == rg)) {
+                               /* this route is a part of the same solo group as the route
+                                * that was changed. Changing that route did change or will
+                                * change all group members appropriately, so we can ignore it
+                                * here
+                                */
+                               continue;
+                       }
+
+                       (*i)->set_solo (false, group_override);
                }
        }
 
@@ -3616,8 +3828,22 @@ Session::route_solo_changed (bool self_solo_change, bool group_override,  boost:
                bool via_sends_only;
                bool in_signal_flow;
 
-               if ((*i) == route || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_auditioner() ||
-                   (leave_group_alone && ((*i)->route_group() == rg))) {
+               if ((*i) == route) {
+                       /* already changed */
+                       continue;
+               }
+
+               if ((*i)->solo_isolated() || (*i)->is_master() || (*i)->is_monitor() || (*i)->is_auditioner()) {
+                       /* route does not get solo propagated to it */
+                       continue;
+               }
+
+               if ((group_already_accounted_for && (*i)->route_group() && (*i)->route_group() == rg)) {
+                       /* this route is a part of the same solo group as the route
+                        * that was changed. Changing that route did change or will
+                        * change all group members appropriately, so we can ignore it
+                        * here
+                        */
                        continue;
                }
 
@@ -3683,7 +3909,7 @@ Session::route_solo_changed (bool self_solo_change, bool group_override,  boost:
        for (RouteList::iterator i = uninvolved.begin(); i != uninvolved.end(); ++i) {
                DEBUG_TRACE (DEBUG::Solo, string_compose ("mute change for %1, which neither feeds or is fed by %2\n", (*i)->name(), route->name()));
                (*i)->act_on_mute ();
-               (*i)->mute_changed (this);
+               (*i)->mute_changed ();
        }
 
        SoloChanged (); /* EMIT SIGNAL */
@@ -3696,6 +3922,7 @@ Session::update_route_solo_state (boost::shared_ptr<RouteList> r)
        /* now figure out if anything that matters is soloed (or is "listening")*/
 
        bool something_soloed = false;
+       bool something_listening = false;
        uint32_t listeners = 0;
        uint32_t isolated = 0;
 
@@ -3711,8 +3938,9 @@ Session::update_route_solo_state (boost::shared_ptr<RouteList> r)
                if (!(*i)->is_auditioner() && (*i)->listening_via_monitor()) {
                        if (Config->get_solo_control_is_listen_control()) {
                                listeners++;
+                               something_listening = true;
                        } else {
-                               (*i)->set_listen (false, this);
+                               (*i)->set_listen (false, Controllable::NoGroup);
                        }
                }
 
@@ -3726,6 +3954,11 @@ Session::update_route_solo_state (boost::shared_ptr<RouteList> r)
                SoloActive (_non_soloed_outs_muted); /* EMIT SIGNAL */
        }
 
+       if (something_listening != _listening) {
+               _listening = something_listening;
+               SoloActive (_listening);
+       }
+
        _listen_cnt = listeners;
 
        if (isolated != _solo_isolated_cnt) {
@@ -3888,6 +4121,21 @@ Session::route_by_id (PBD::ID id)
        return boost::shared_ptr<Route> ((Route*) 0);
 }
 
+boost::shared_ptr<Processor>
+Session::processor_by_id (PBD::ID id) const
+{
+       boost::shared_ptr<RouteList> r = routes.reader ();
+
+       for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
+               boost::shared_ptr<Processor> p = (*i)->Route::processor_by_id (id);
+               if (p) {
+                       return p;
+               }
+       }
+
+       return boost::shared_ptr<Processor> ();
+}
+
 boost::shared_ptr<Track>
 Session::track_by_diskstream_id (PBD::ID id)
 {
@@ -3918,6 +4166,19 @@ Session::route_by_remote_id (uint32_t id)
 }
 
 
+boost::shared_ptr<Route>
+Session::route_by_selected_count (uint32_t id)
+{
+       boost::shared_ptr<RouteList> r = routes.reader ();
+
+       for (RouteList::iterator i = r->begin(); i != r->end(); ++i) {
+               /* NOT IMPLEMENTED */
+       }
+
+       return boost::shared_ptr<Route> ((Route*) 0);
+}
+
+
 void
 Session::reassign_track_numbers ()
 {
@@ -4740,6 +5001,228 @@ Session::audition_playlist ()
        queue_event (ev);
 }
 
+
+void
+Session::register_lua_function (
+               const std::string& name,
+               const std::string& script,
+               const LuaScriptParamList& args
+               )
+{
+       Glib::Threads::Mutex::Lock lm (lua_lock);
+
+       lua_State* L = lua.getState();
+
+       const std::string& bytecode = LuaScripting::get_factory_bytecode (script);
+       luabridge::LuaRef tbl_arg (luabridge::newTable(L));
+       for (LuaScriptParamList::const_iterator i = args.begin(); i != args.end(); ++i) {
+               if ((*i)->optional && !(*i)->is_set) { continue; }
+               tbl_arg[(*i)->name] = (*i)->value;
+       }
+       (*_lua_add)(name, bytecode, tbl_arg); // throws luabridge::LuaException
+       set_dirty();
+}
+
+void
+Session::unregister_lua_function (const std::string& name)
+{
+       Glib::Threads::Mutex::Lock lm (lua_lock);
+       (*_lua_del)(name); // throws luabridge::LuaException
+       lua.collect_garbage ();
+       set_dirty();
+}
+
+std::vector<std::string>
+Session::registered_lua_functions ()
+{
+       Glib::Threads::Mutex::Lock lm (lua_lock);
+       std::vector<std::string> rv;
+
+       try {
+               luabridge::LuaRef list ((*_lua_list)());
+               for (luabridge::Iterator i (list); !i.isNil (); ++i) {
+                       if (!i.key ().isString ()) { assert(0); continue; }
+                       rv.push_back (i.key ().cast<std::string> ());
+               }
+       } catch (luabridge::LuaException const& e) { }
+       return rv;
+}
+
+#ifndef NDEBUG
+static void _lua_print (std::string s) {
+       std::cout << "SessionLua: " << s << "\n";
+}
+#endif
+
+void
+Session::try_run_lua (pframes_t nframes)
+{
+       if (_n_lua_scripts == 0) return;
+       Glib::Threads::Mutex::Lock tm (lua_lock, Glib::Threads::TRY_LOCK);
+       if (tm.locked ()) {
+               try { (*_lua_run)(nframes); } catch (luabridge::LuaException const& e) { }
+       }
+}
+
+void
+Session::setup_lua ()
+{
+#ifndef NDEBUG
+       lua.Print.connect (&_lua_print);
+#endif
+       lua.do_command (
+                       "function ArdourSession ()"
+                       "  local self = { scripts = {}, instances = {} }"
+                       ""
+                       "  local remove = function (n)"
+                       "   self.scripts[n] = nil"
+                       "   self.instances[n] = nil"
+                       "   Session:scripts_changed()" // call back
+                       "  end"
+                       ""
+                       "  local addinternal = function (n, f, a)"
+                       "   assert(type(n) == 'string', 'function-name must be string')"
+                       "   assert(type(f) == 'function', 'Given script is a not a function')"
+                       "   assert(type(a) == 'table' or type(a) == 'nil', 'Given argument is invalid')"
+                       "   assert(self.scripts[n] == nil, 'Callback \"'.. n ..'\" already exists.')"
+                       "   self.scripts[n] = { ['f'] = f, ['a'] = a }"
+                       "   local env = _ENV;  env.f = nil env.io = nil env.os = nil env.loadfile = nil env.require = nil env.dofile = nil env.package = nil env.debug = nil"
+                       "   local env = { print = print, Session = Session, tostring = tostring, assert = assert, ipairs = ipairs, error = error, select = select, string = string, type = type, tonumber = tonumber, collectgarbage = collectgarbage, pairs = pairs, math = math, table = table, pcall = pcall }"
+                       "   self.instances[n] = load (string.dump(f, true), nil, nil, env)(a)"
+                       "   Session:scripts_changed()" // call back
+                       "  end"
+                       ""
+                       "  local add = function (n, b, a)"
+                       "   assert(type(b) == 'string', 'ByteCode must be string')"
+                       "   load (b)()" // assigns f
+                       "   assert(type(f) == 'string', 'Assigned ByteCode must be string')"
+                       "   addinternal (n, load(f), a)"
+                       "  end"
+                       ""
+                       "  local run = function (...)"
+                       "   for n, s in pairs (self.instances) do"
+                       "     local status, err = pcall (s, ...)"
+                       "     if not status then"
+                       "       print ('fn \"'.. n .. '\": ', err)"
+                       "       remove (n)"
+                       "      end"
+                       "   end"
+                       "   collectgarbage()"
+                       "  end"
+                       ""
+                       "  local cleanup = function ()"
+                       "   self.scripts = nil"
+                       "   self.instances = nil"
+                       "  end"
+                       ""
+                       "  local list = function ()"
+                       "   local rv = {}"
+                       "   for n, _ in pairs (self.scripts) do"
+                       "     rv[n] = true"
+                       "   end"
+                       "   return rv"
+                       "  end"
+                       ""
+                       "  local function basic_serialize (o)"
+                       "    if type(o) == \"number\" then"
+                       "     return tostring(o)"
+                       "    else"
+                       "     return string.format(\"%q\", o)"
+                       "    end"
+                       "  end"
+                       ""
+                       "  local function serialize (name, value)"
+                       "   local rv = name .. ' = '"
+                       "   collectgarbage()"
+                       "   if type(value) == \"number\" or type(value) == \"string\" or type(value) == \"nil\" then"
+                       "    return rv .. basic_serialize(value) .. ' '"
+                       "   elseif type(value) == \"table\" then"
+                       "    rv = rv .. '{} '"
+                       "    for k,v in pairs(value) do"
+                       "     local fieldname = string.format(\"%s[%s]\", name, basic_serialize(k))"
+                       "     rv = rv .. serialize(fieldname, v) .. ' '"
+                       "     collectgarbage()" // string concatenation allocates a new string :(
+                       "    end"
+                       "    return rv;"
+                       "   elseif type(value) == \"function\" then"
+                       "     return rv .. string.format(\"%q\", string.dump(value, true))"
+                       "   else"
+                       "    error('cannot save a ' .. type(value))"
+                       "   end"
+                       "  end"
+                       ""
+                       ""
+                       "  local save = function ()"
+                       "   return (serialize('scripts', self.scripts))"
+                       "  end"
+                       ""
+                       "  local restore = function (state)"
+                       "   self.scripts = {}"
+                       "   load (state)()"
+                       "   for n, s in pairs (scripts) do"
+                       "    addinternal (n, load(s['f']), s['a'])"
+                       "   end"
+                       "  end"
+                       ""
+                       " return { run = run, add = add, remove = remove,"
+                 "          list = list, restore = restore, save = save, cleanup = cleanup}"
+                       " end"
+                       " "
+                       " sess = ArdourSession ()"
+                       " ArdourSession = nil"
+                       " "
+                       "function ardour () end"
+                       );
+
+       lua_State* L = lua.getState();
+
+       try {
+               luabridge::LuaRef lua_sess = luabridge::getGlobal (L, "sess");
+               lua.do_command ("sess = nil"); // hide it.
+               lua.do_command ("collectgarbage()");
+
+               _lua_run = new luabridge::LuaRef(lua_sess["run"]);
+               _lua_add = new luabridge::LuaRef(lua_sess["add"]);
+               _lua_del = new luabridge::LuaRef(lua_sess["remove"]);
+               _lua_list = new luabridge::LuaRef(lua_sess["list"]);
+               _lua_save = new luabridge::LuaRef(lua_sess["save"]);
+               _lua_load = new luabridge::LuaRef(lua_sess["restore"]);
+               _lua_cleanup = new luabridge::LuaRef(lua_sess["cleanup"]);
+       } catch (luabridge::LuaException const& e) {
+               fatal << string_compose (_("programming error: %1"),
+                               X_("Failed to setup Lua interpreter"))
+                       << endmsg;
+               abort(); /*NOTREACHED*/
+       }
+
+       LuaBindings::stddef (L);
+       LuaBindings::common (L);
+       LuaBindings::dsp (L);
+       luabridge::push <Session *> (L, this);
+       lua_setglobal (L, "Session");
+}
+
+void
+Session::scripts_changed ()
+{
+       assert (!lua_lock.trylock()); // must hold lua_lock
+
+       try {
+               luabridge::LuaRef list ((*_lua_list)());
+               int cnt = 0;
+               for (luabridge::Iterator i (list); !i.isNil (); ++i) {
+                       if (!i.key ().isString ()) { assert(0); continue; }
+                       ++cnt;
+               }
+               _n_lua_scripts = cnt;
+       } catch (luabridge::LuaException const& e) {
+               fatal << string_compose (_("programming error: %1"),
+                               X_("Indexing Lua Session Scripts failed."))
+                       << endmsg;
+               abort(); /*NOTREACHED*/
+       }
+}
+
 void
 Session::non_realtime_set_audition ()
 {
@@ -5105,6 +5588,7 @@ Session::unmark_aux_send_id (uint32_t id)
 void
 Session::unmark_return_id (uint32_t id)
 {
+       if (_state_of_the_state & Deletion) { return; }
        if (id < return_bitset.size()) {
                return_bitset[id] = false;
        }
@@ -5454,6 +5938,12 @@ Session::get_scratch_buffers (ChanCount count, bool silence)
        return ProcessThread::get_scratch_buffers (count, silence);
 }
 
+BufferSet&
+Session::get_noinplace_buffers (ChanCount count)
+{
+       return ProcessThread::get_noinplace_buffers (count);
+}
+
 BufferSet&
 Session::get_route_buffers (ChanCount count, bool silence)
 {