X-Git-Url: https://main.carlh.net/gitweb/?a=blobdiff_plain;f=libs%2Fardour%2Fsession.cc;h=edae568645e8e92634b445eb054a7567b76952e7;hb=f269e39115934c8e48dbc66e495d8cfdba1e70f0;hp=879bd7653960b4c799a4c95b65cf5db880bed412;hpb=22b07e0233a29d9633ffa825a79503befaf2e16e;p=ardour.git diff --git a/libs/ardour/session.cc b/libs/ardour/session.cc index 879bd76539..edae568645 100644 --- a/libs/ardour/session.cc +++ b/libs/ardour/session.cc @@ -23,7 +23,6 @@ #include #include #include -#include #include /* sprintf(3) ... grrr */ #include #include @@ -72,9 +71,12 @@ #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 "ardour/midi_track.h" #include "ardour/midi_ui.h" #include "ardour/operations.h" @@ -105,6 +107,8 @@ #include "midi++/port.h" #include "midi++/mmc.h" +#include "LuaBridge/LuaBridge.h" + #include "i18n.h" #include @@ -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 */ @@ -556,6 +573,8 @@ Session::destroy () ControlProtocolManager::instance().drop_protocols (); + MIDI::Name::MidiPatchManager::instance().remove_search_path(session_directory().midi_patch_path()); + _engine.remove_session (); #ifdef USE_TRACKS_CODE_FEATURES @@ -579,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 (); @@ -723,8 +753,12 @@ void Session::setup_click () { _clicking = false; + + boost::shared_ptr gl (new AutomationList (Evoral::Parameter (GainAutomation))); + boost::shared_ptr gain_control = boost::shared_ptr (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()); @@ -1013,6 +1047,7 @@ Session::remove_monitor_section () if (auditioner) { auditioner->connect (); } + Config->ParameterChanged ("use-monitor-bus"); } void @@ -1164,6 +1199,7 @@ Session::add_monitor_section () if (auditioner) { auditioner->connect (); } + Config->ParameterChanged ("use-monitor-bus"); } void @@ -1815,6 +1851,19 @@ Session::enable_record () } } +void +Session::set_all_tracks_record_enabled (bool enable ) +{ + boost::shared_ptr rl = routes.reader(); + for (RouteList::iterator i = rl->begin(); i != rl->end(); ++i) { + boost::shared_ptr tr = boost::dynamic_pointer_cast (*i); + if (tr) { + tr->set_record_enabled (enable, Controllable::NoGroup); + } + } +} + + void Session::disable_record (bool rt_context, bool force) { @@ -2065,6 +2114,10 @@ Session::resort_routes () return; } + if (_route_deletion_in_progress) { + return; + } + { RCUWriter writer (routes); boost::shared_ptr r = writer.get_copy (); @@ -2208,9 +2261,14 @@ Session::find_route_name (string const & base, uint32_t& id, string& name, bool for (vector::const_iterator reserved = reserved_io_names.begin(); reserved != reserved_io_names.end(); ++reserved) { if (base == *reserved) { - definitely_add_number = true; - if (id < 1) { - id = 1; + /* Check if this reserved name already exists, and if + so, disallow it without a numeric suffix. + */ + if (route_by_name (*reserved)) { + definitely_add_number = true; + if (id < 1) { + id = 1; + } } break; } @@ -2377,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 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 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 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 wmt) { @@ -2854,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); } } @@ -3021,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; @@ -3073,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 (); @@ -3111,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; + + if ((track = boost::dynamic_pointer_cast (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 */ @@ -3209,8 +3388,8 @@ Session::add_routes_inner (RouteList& new_routes, bool input_auto_connect, bool 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, _1, wpr)); - r->mute_changed.connect_same_thread (*this, boost::bind (&Session::route_mute_changed, this, _1)); + 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)); @@ -3282,7 +3461,7 @@ Session::globally_set_send_gains_to_zero (boost::shared_ptr 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); } } } @@ -3295,7 +3474,7 @@ Session::globally_set_send_gains_to_unity (boost::shared_ptr 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); } } } @@ -3308,7 +3487,7 @@ Session::globally_set_send_gains_from_track(boost::shared_ptr 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); } } } @@ -3364,9 +3543,8 @@ Session::add_internal_send (boost::shared_ptr dest, boost::shared_ptr routes_to_remove) { - PBD::Unwinder uw_flag (_route_deletion_in_progress, true); - { // RCU Writer scope + PBD::Unwinder uw_flag (_route_deletion_in_progress, true); RCUWriter writer (routes); boost::shared_ptr rs = writer.get_copy (); @@ -3377,7 +3555,7 @@ Session::remove_routes (boost::shared_ptr routes_to_remove) continue; } - (*iter)->set_solo (false, this); + (*iter)->set_solo (false, Controllable::NoGroup); rs->remove (*iter); @@ -3485,32 +3663,49 @@ Session::remove_route (boost::shared_ptr route) } void -Session::route_mute_changed (void* /*src*/) +Session::route_mute_changed () { set_dirty (); } void -Session::route_listen_changed (void* /*src*/, boost::weak_ptr wpr) +Session::route_listen_changed (Controllable::GroupControlDisposition group_override, boost::weak_ptr wpr) { boost::shared_ptr route = wpr.lock(); if (!route) { - error << string_compose (_("programming error: %1"), X_("invalid route weak ptr passed to route_solo_changed")) << endmsg; + error << string_compose (_("programming error: %1"), X_("invalid route weak ptr passed to route_listen_changed")) << endmsg; return; } 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()); + const bool group_already_accounted_for = route->use_group (group_override, &RouteGroup::is_solo); + boost::shared_ptr 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; + } + + 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, this); + (*i)->set_listen (false, Controllable::NoGroup); } } @@ -3524,7 +3719,7 @@ Session::route_listen_changed (void* /*src*/, boost::weak_ptr wpr) update_route_solo_state (); } void -Session::route_solo_isolated_changed (void* /*src*/, boost::weak_ptr wpr) +Session::route_solo_isolated_changed (boost::weak_ptr wpr) { boost::shared_ptr route = wpr.lock (); @@ -3554,7 +3749,7 @@ Session::route_solo_isolated_changed (void* /*src*/, boost::weak_ptr wpr) } void -Session::route_solo_changed (bool self_solo_change, void* /*src*/, boost::weak_ptr wpr) +Session::route_solo_changed (bool self_solo_change, Controllable::GroupControlDisposition group_override, boost::weak_ptr wpr) { DEBUG_TRACE (DEBUG::Solo, string_compose ("route solo change, self = %1\n", self_solo_change)); @@ -3575,19 +3770,51 @@ Session::route_solo_changed (bool self_solo_change, void* /*src*/, boost::weak_p 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()); + 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)->solo_isolated() || (*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); + + 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); } } @@ -3601,8 +3828,22 @@ Session::route_solo_changed (bool self_solo_change, void* /*src*/, boost::weak_p bool via_sends_only; bool in_signal_flow; - 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; + } + + 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; } @@ -3615,6 +3856,8 @@ Session::route_solo_changed (bool self_solo_change, void* /*src*/, boost::weak_p if (!via_sends_only) { if (!route->soloed_by_others_upstream()) { (*i)->mod_solo_by_others_downstream (delta); + } else { + DEBUG_TRACE (DEBUG::Solo, "\talready soloed by others upstream\n"); } } else { DEBUG_TRACE (DEBUG::Solo, string_compose ("\tthere is a send-only feed from %1\n", (*i)->name())); @@ -3639,12 +3882,9 @@ Session::route_solo_changed (bool self_solo_change, void* /*src*/, boost::weak_p route->soloed_by_others_downstream(), route->soloed_by_others_upstream())); if (!via_sends_only) { - if (!route->soloed_by_others_downstream()) { - DEBUG_TRACE (DEBUG::Solo, string_compose ("\tmod %1 by %2\n", (*i)->name(), delta)); - (*i)->mod_solo_by_others_upstream (delta); - } else { - DEBUG_TRACE (DEBUG::Solo, "\talready soloed by others downstream\n"); - } + //NB. Triggers Invert Push, which handles soloed by downstream + DEBUG_TRACE (DEBUG::Solo, string_compose ("\tmod %1 by %2\n", (*i)->name(), delta)); + (*i)->mod_solo_by_others_upstream (delta); } else { DEBUG_TRACE (DEBUG::Solo, string_compose ("\tfeed to %1 ignored, sends-only\n", (*i)->name())); } @@ -3669,7 +3909,7 @@ Session::route_solo_changed (bool self_solo_change, void* /*src*/, boost::weak_p 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 */ @@ -3682,6 +3922,7 @@ Session::update_route_solo_state (boost::shared_ptr 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; @@ -3697,8 +3938,9 @@ Session::update_route_solo_state (boost::shared_ptr 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); } } @@ -3712,6 +3954,11 @@ Session::update_route_solo_state (boost::shared_ptr 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) { @@ -3744,6 +3991,11 @@ Session::io_name_is_legal (const std::string& name) for (vector::const_iterator reserved = reserved_io_names.begin(); reserved != reserved_io_names.end(); ++reserved) { if (name == *reserved) { + if (!route_by_name (*reserved)) { + /* first instance of a reserved name is allowed */ + return true; + } + /* all other instances of a reserved name are not allowed */ return false; } } @@ -3869,6 +4121,21 @@ Session::route_by_id (PBD::ID id) return boost::shared_ptr ((Route*) 0); } +boost::shared_ptr +Session::processor_by_id (PBD::ID id) const +{ + boost::shared_ptr r = routes.reader (); + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + boost::shared_ptr p = (*i)->Route::processor_by_id (id); + if (p) { + return p; + } + } + + return boost::shared_ptr (); +} + boost::shared_ptr Session::track_by_diskstream_id (PBD::ID id) { @@ -3899,6 +4166,19 @@ Session::route_by_remote_id (uint32_t id) } +boost::shared_ptr +Session::route_by_selected_count (uint32_t id) +{ + boost::shared_ptr r = routes.reader (); + + for (RouteList::iterator i = r->begin(); i != r->end(); ++i) { + /* NOT IMPLEMENTED */ + } + + return boost::shared_ptr ((Route*) 0); +} + + void Session::reassign_track_numbers () { @@ -4721,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 +Session::registered_lua_functions () +{ + Glib::Threads::Mutex::Lock lm (lua_lock); + std::vector 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 ()); + } + } 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 (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 () { @@ -5086,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; } @@ -5435,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) {