diff --git a/libs/ardour/ardour/plugin_insert.h b/libs/ardour/ardour/plugin_insert.h index 973a47b503..d8be4b6d7a 100644 --- a/libs/ardour/ardour/plugin_insert.h +++ b/libs/ardour/ardour/plugin_insert.h @@ -37,6 +37,16 @@ #include "ardour/sidechain.h" #include "ardour/automation_control.h" +#include "ardour/luascripting.h" +#include "pbd/reallocpool.h" +#include "lua/luastate.h" + +namespace luabridge { + class LuaRef; +} + +class PluginModulateScriptProxy; + class XMLNode; namespace ARDOUR { @@ -111,6 +121,16 @@ class LIBARDOUR_API PluginInsert : public Processor bool is_channelstrip () const; #endif + bool load_modulation_script (const std::string&); + void unload_modulation_script (); + bool modulation_script_loaded () const; + void clear_modulation (); + std::string modulation_script () const; + PBD::Signal0 ModulationScriptChanged; + + PluginModulateScriptProxy* modscript_proxy () const { return _modscript_proxy; } + void set_modscript_proxy (PluginModulateScriptProxy* wp) { _modscript_proxy = wp ; } + void set_input_map (uint32_t, ChanMapping); void set_output_map (uint32_t, ChanMapping); void set_thru_map (ChanMapping); @@ -382,6 +402,15 @@ class LIBARDOUR_API PluginInsert : public Processor uint32_t _bypass_port; void preset_load_set_value (uint32_t, float); + + void reinit_lua(); + PBD::ReallocPool _mempool; + LuaState* _lua; + luabridge::LuaRef* _lua_modulate; + std::string _script; + Glib::Threads::Mutex _lua_lock; + void lua_print (std::string s); + PluginModulateScriptProxy *_modscript_proxy; }; } // namespace ARDOUR diff --git a/libs/ardour/luabindings.cc b/libs/ardour/luabindings.cc index 84a2610a44..821343cb33 100644 --- a/libs/ardour/luabindings.cc +++ b/libs/ardour/luabindings.cc @@ -508,6 +508,7 @@ LuaBindings::common (lua_State* L) .endClass () .beginWSPtrClass ("Control") + .addCast ("to_automationcontrol") .addFunction ("list", (boost::shared_ptr(Evoral::Control::*)())&Evoral::Control::list) .endClass () @@ -519,6 +520,9 @@ LuaBindings::common (lua_State* L) .addData ("toggled", &Evoral::ParameterDescriptor::toggled) .endClass () + .beginStdMap > ("ControlMap") + .endClass () + .beginClass > ("Range") .addConstructor () .addData ("from", &Evoral::Range::from) @@ -1295,10 +1299,15 @@ LuaBindings::common (lua_State* L) .addFunction ("natural_output_streams", &PluginInsert::natural_output_streams) .addFunction ("natural_input_streams", &PluginInsert::natural_input_streams) .addFunction ("reset_parameters_to_default", &PluginInsert::reset_parameters_to_default) + .addFunction ("load_modulation_script", &PluginInsert::load_modulation_script) + .addFunction ("unload_modulation_script", &PluginInsert::unload_modulation_script) + .addFunction ("modulation_script_loaded", &PluginInsert::modulation_script_loaded) + .addFunction ("modulation_script", &PluginInsert::modulation_script) .endClass () .deriveWSPtrClass ("AutomationControl") .addCast ("to_ctrl") + .addCast ("to_plugincontrol") .addFunction ("automation_state", &AutomationControl::automation_state) .addFunction ("automation_style", &AutomationControl::automation_style) .addFunction ("set_automation_state", &AutomationControl::set_automation_state) @@ -1393,6 +1402,11 @@ LuaBindings::common (lua_State* L) .endClass () .deriveWSPtrClass ("PluginControl") + .addFunction ("modulate_to", &PluginInsert::PluginControl::modulate_to) + .addFunction ("modulate_by", &PluginInsert::PluginControl::modulate_by) + .addFunction ("get_value", &PluginInsert::PluginControl::get_value) + .addFunction ("modulation_delta", &PluginInsert::PluginControl::modulation_delta) + .addFunction ("modulated_value", &PluginInsert::PluginControl::modulated_value) .endClass () .deriveWSPtrClass ("AudioSource") diff --git a/libs/ardour/plugin_insert.cc b/libs/ardour/plugin_insert.cc index cee15a0fc8..2dcdbccb60 100644 --- a/libs/ardour/plugin_insert.cc +++ b/libs/ardour/plugin_insert.cc @@ -38,6 +38,9 @@ #include "ardour/plugin_insert.h" #include "ardour/port.h" +#include "LuaBridge/LuaBridge.h" +#include "ardour/luabindings.h" + #ifdef LV2_SUPPORT #include "ardour/lv2_plugin.h" #endif @@ -83,6 +86,10 @@ PluginInsert::PluginInsert (Session& s, boost::shared_ptr plug) , _maps_from_state (false) , _latency_changed (false) , _bypass_port (UINT32_MAX) + , _mempool ("LuaModulator", 3145728) + , _lua (0) + , _lua_modulate (0) + , _modscript_proxy (0) { /* the first is the master */ @@ -94,10 +101,157 @@ PluginInsert::PluginInsert (Session& s, boost::shared_ptr plug) add_sidechain (sc.n_audio (), sc.n_midi ()); } } + reinit_lua (); } PluginInsert::~PluginInsert () { + delete _lua_modulate; + delete _lua; +} + +void +PluginInsert::reinit_lua () +{ + delete _lua_modulate; + delete _lua; +#ifdef RAP_WITH_CALL_STATS + assert (_mempool->mem_used () == 0); +#endif + _lua_modulate = 0; + _lua = new LuaState (lua_newstate (&PBD::ReallocPool::lalloc, &_mempool)); + + + _lua->tweak_rt_gc (); + _lua->Print.connect (sigc::mem_fun (*this, &PluginInsert::lua_print)); + + // register session object + lua_State* L = _lua->getState (); + LuaBindings::stddef (L); + LuaBindings::common (L); + LuaBindings::dsp (L); + + luabridge::push (L, &_session); + lua_setglobal (L, "Session"); + + // sandbox + _lua->do_command ("io = nil os = nil loadfile = nil require = nil dofile = nil package = nil debug = nil"); +} + +void +PluginInsert::lua_print (std::string s) { + std::cout <<"PluginInsert: " << s << "\n"; + PBD::error << "PluginInsert: " << s << "\n"; +} + +bool +PluginInsert::load_modulation_script (const std::string& s) +{ + std::string bytecode = + LuaScripting::get_factory_bytecode (s, "dsp_modulate", "f"); + + if (bytecode.empty ()) { + return false; + } + + Glib::Threads::Mutex::Lock lm (_lua_lock); + reinit_lua (); + + lua_State* L = _lua->getState (); + + luabridge::LuaRef load = luabridge::getGlobal (L, "load"); + _lua->do_command ("f = nil"); + load (bytecode)(); // assigns f = "bytecode" + _lua->do_command ("assert (type (f) == 'string') dsp_modulate = load(f) f = nil"); // assigns dsp_modulate + // consider just loading the script (globals and all) + luabridge::LuaRef lua_modulate = luabridge::getGlobal (L, "dsp_modulate"); + if (lua_modulate.type () != LUA_TFUNCTION) { + return false; + } + + _lua_modulate = new luabridge::LuaRef (lua_modulate); + _script = s; + + lm.release (); + clear_modulation (); + ModulationScriptChanged (); /* EMIT SIGNAL */ + + return true; +} + +void +PluginInsert::unload_modulation_script () +{ + { + Glib::Threads::Mutex::Lock lm (_lua_lock); + _lua_modulate = 0; + } + _script = ""; + clear_modulation (); + ModulationScriptChanged (); /* EMIT SIGNAL */ +} + +bool +PluginInsert::modulation_script_loaded () const +{ + return _lua_modulate ? true : false; +} + +void +PluginInsert::clear_modulation () +{ + for (Controls::iterator li = controls().begin(); li != controls().end(); ++li) { + boost::shared_ptr c + = boost::dynamic_pointer_cast(li->second); + if (c) { + c->modulate_by (0); + } + } +} + +std::string +PluginInsert::modulation_script () const +{ + if (_lua_modulate) { + return _script; + } + return + "-- EXAMPLE MODULATION SCRIPT --\n" + "\n" + "-- modulation callback, this function is called every process\n" + "-- cycle befor running the plugin\n" + "function dsp_modulate (ctrl, bufs, n_samples, offset, start)\n" + "\n" + " init = init or 0 if init == 0 then init = 1\n" + " -- one time initialization code, persistent setup\n" + "\n" + " -- query session sample-rate, store in global variable\n" + " samplerate = Session:nominal_frame_rate ()\n" + "\n" + " -- create an envelope follower, 100ms rise, 2sec fall time\n" + " env = ARDOUR.DSP.EnvFollower (samplerate, 100, 2000)\n" + "\n" + " -- initialize a counter for a LFO\n" + " lfo = 0\n" + "\n" + " end\n" + "\n" + "\n" + " -- feed the envelope follower with data from the 1st audio input\n" + " env:process_bufs (bufs, 0 --[[ input buffer 0..N ]], n_samples, offset)\n" + "\n" + " -- set the plugin's 1st input to 10 times the envelope-value\n" + " ARDOUR.LuaAPI.modulate_to (ctrl, 0 --[[plugin control port 0..N]], 10 * env:value())\n" + "\n" + " -- LFO 1 second period (1 sec = sample-rate number of samples)\n" + " lfo = lfo + n_samples\n" + " if lfo > samplerate then lfo = lfo - samplerate end -- wrap around\n" + " local val = math.sin (2 * math.pi * lfo / samplerate); -- use a sine OSC [-1...1]\n" + "\n" + " -- modulate the plugin's 2nd input over 50% of its total range\n" + " ARDOUR.LuaAPI.modulate_range (ctrl, 1 --[[plugin control port 0..N]], val * 0.5)\n" + "\n" + "end"; } void @@ -257,11 +411,11 @@ PluginInsert::control_list_automation_state_changed (Evoral::Parameter which, Au if (which.type() != PluginAutomation) return; - boost::shared_ptr c - = boost::dynamic_pointer_cast(control (which)); + boost::shared_ptr c + = boost::dynamic_pointer_cast(control (which)); if (c && s != Off) { - _plugins[0]->set_parameter (which.id(), c->list()->eval (_session.transport_frame())); + _plugins[0]->set_parameter (which.id(), c->modulation_delta() + c->list()->eval (_session.transport_frame())); } } @@ -790,6 +944,26 @@ PluginInsert::connect_and_run (BufferSet& bufs, framepos_t start, framepos_t end } } + if (_lua_modulate) { + Glib::Threads::Mutex::Lock lm (_lua_lock, Glib::Threads::TRY_LOCK); + if (lm.locked()) { + try { + (*_lua_modulate) (controls(), &bufs, nframes, offset, start); + _lua->collect_garbage_step (); + } catch (luabridge::LuaException const& e) { + _lua_modulate = 0; + _script = ""; + + lm.release (); + clear_modulation (); + ModulationScriptChanged (); /* EMIT SIGNAL */ +#ifndef NDEBUG + std::cerr << "LuaException: " << e.what () << "\n"; +#endif + } + } + } + /* Calculate if, and how many frames we need to collect for analysis */ framecnt_t collect_signal_nframes = (_signal_analysis_collect_nframes_max - _signal_analysis_collected_nframes); @@ -2323,6 +2497,16 @@ PluginInsert::state (bool full) node.add_child_nocopy (_sidechain->state (full)); } + if (!_script.empty()) { + gchar* b64 = g_base64_encode ((const guchar*)_script.c_str (), _script.size ()); + std::string b64s (b64); + g_free (b64); + XMLNode* script_node = new XMLNode (X_("script")); + script_node->add_property (X_("lua"), LUA_VERSION); + script_node->add_content (b64s); + node.add_child_nocopy (*script_node); + } + _plugins[0]->set_insert_id(this->id()); node.add_child_nocopy (_plugins[0]->get_state()); @@ -2628,6 +2812,17 @@ PluginInsert::set_state(const XMLNode& node, int version) _sidechain->set_state (**i, version); } } + + if ((*i)->name () == X_("script")) { + for (XMLNodeList::const_iterator n = (*i)->children ().begin (); n != (*i)->children ().end (); ++n) { + if (!(*n)->is_content ()) { continue; } + gsize size; + guchar* buf = g_base64_decode ((*n)->content ().c_str (), &size); + load_modulation_script (std::string ((const char*)buf, size)); + g_free (buf); + break; + } + } } if (in_maps == out_maps && out_maps >0 && out_maps == get_count()) {