Files
ardour/libs/ardour/luaproc.cc
David Robillard 08fffeffec Remove Evoral::MIDIEvent
It is slightly questionable whether type specific methods like
velocity() belong on Event at all, these may be better off as free
functions.  However the code currently uses them as methods in many
places, and it seems like a step in the right direction, since, for
example, we might some day have events that have a velocity but aren't
stored as MIDI messages (e.g. if Ardour uses an internal musical model
that is more expressive).

In any case, the former inheritance and plethora of sloppy casts is
definitely not the right thing.
2016-12-03 15:18:21 -05:00

1267 lines
35 KiB
C++

/*
Copyright (C) 2016 Robin Gareus <robin@gareus.org>
Copyright (C) 2006 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 <glib.h>
#include <glibmm/miscutils.h>
#include <glibmm/fileutils.h>
#include "pbd/gstdio_compat.h"
#include "pbd/locale_guard.h"
#include "pbd/pthread_utils.h"
#include "ardour/audio_buffer.h"
#include "ardour/buffer_set.h"
#include "ardour/filesystem_paths.h"
#include "ardour/luabindings.h"
#include "ardour/luaproc.h"
#include "ardour/luascripting.h"
#include "ardour/midi_buffer.h"
#include "ardour/plugin.h"
#include "ardour/session.h"
#include "LuaBridge/LuaBridge.h"
#include "pbd/i18n.h"
using namespace ARDOUR;
using namespace PBD;
LuaProc::LuaProc (AudioEngine& engine,
Session& session,
const std::string &script)
: Plugin (engine, session)
, _mempool ("LuaProc", 3145728)
#ifdef USE_TLSF
, lua (lua_newstate (&PBD::TLSF::lalloc, &_mempool))
#elif defined USE_MALLOC
, lua ()
#else
, lua (lua_newstate (&PBD::ReallocPool::lalloc, &_mempool))
#endif
, _lua_dsp (0)
, _script (script)
, _lua_does_channelmapping (false)
, _lua_has_inline_display (false)
, _designated_bypass_port (UINT32_MAX)
, _control_data (0)
, _shadow_data (0)
, _configured (false)
, _has_midi_input (false)
, _has_midi_output (false)
{
init ();
/* when loading a session, or pasing a processor,
* the script is set during set_state();
*/
if (!_script.empty () && load_script ()) {
throw failed_constructor ();
}
}
LuaProc::LuaProc (const LuaProc &other)
: Plugin (other)
, _mempool ("LuaProc", 3145728)
#ifdef USE_TLSF
, lua (lua_newstate (&PBD::TLSF::lalloc, &_mempool))
#elif defined USE_MALLOC
, lua ()
#else
, lua (lua_newstate (&PBD::ReallocPool::lalloc, &_mempool))
#endif
, _lua_dsp (0)
, _script (other.script ())
, _lua_does_channelmapping (false)
, _lua_has_inline_display (false)
, _designated_bypass_port (UINT32_MAX)
, _control_data (0)
, _shadow_data (0)
, _configured (false)
, _has_midi_input (false)
, _has_midi_output (false)
{
init ();
if (load_script ()) {
throw failed_constructor ();
}
for (uint32_t i = 0; i < parameter_count (); ++i) {
_control_data[i] = other._shadow_data[i];
_shadow_data[i] = other._shadow_data[i];
}
}
LuaProc::~LuaProc () {
#ifdef WITH_LUAPROC_STATS
if (_info && _stats_cnt > 0) {
printf ("LuaProc: '%s' run() avg: %.3f max: %.3f [ms]\n",
_info->name.c_str (),
0.0001f * _stats_avg[0] / (float) _stats_cnt,
0.0001f * _stats_max[0]);
printf ("LuaProc: '%s' gc() avg: %.3f max: %.3f [ms]\n",
_info->name.c_str (),
0.0001f * _stats_avg[1] / (float) _stats_cnt,
0.0001f * _stats_max[1]);
}
#endif
lua.do_command ("collectgarbage();");
delete (_lua_dsp);
delete [] _control_data;
delete [] _shadow_data;
}
void
LuaProc::init ()
{
#ifdef WITH_LUAPROC_STATS
_stats_avg[0] = _stats_avg[1] = _stats_max[0] = _stats_max[1] = _stats_cnt = 0;
#endif
lua.tweak_rt_gc ();
lua.Print.connect (sigc::mem_fun (*this, &LuaProc::lua_print));
// register session object
lua_State* L = lua.getState ();
LuaBindings::stddef (L);
LuaBindings::common (L);
LuaBindings::dsp (L);
luabridge::getGlobalNamespace (L)
.beginNamespace ("Ardour")
.deriveClass <LuaProc, PBD::StatefulDestructible> ("LuaProc")
.addFunction ("queue_draw", &LuaProc::queue_draw)
.addFunction ("shmem", &LuaProc::instance_shm)
.addFunction ("table", &LuaProc::instance_ref)
.addFunction ("route", &LuaProc::route)
.addFunction ("unique_id", &LuaProc::unique_id)
.addFunction ("name", &LuaProc::name)
.endClass ()
.endNamespace ();
// add session to global lua namespace
luabridge::push <Session *> (L, &_session);
lua_setglobal (L, "Session");
// instance
luabridge::push <LuaProc *> (L, this);
lua_setglobal (L, "self");
// sandbox
lua.do_command ("io = nil os = nil loadfile = nil require = nil dofile = nil package = nil debug = nil");
#if 0
lua.do_command ("for n in pairs(_G) do print(n) end print ('----')"); // print global env
#endif
lua.do_command ("function ardour () end");
}
boost::weak_ptr<Route>
LuaProc::route () const
{
return static_cast<Route*>(_owner)->weakroute ();
}
void
LuaProc::lua_print (std::string s) {
std::cout <<"LuaProc: " << s << "\n";
PBD::error << "LuaProc: " << s << "\n";
}
bool
LuaProc::load_script ()
{
assert (!_lua_dsp); // don't allow to re-initialize
LuaPluginInfoPtr lpi;
// TODO: refine APIs; function arguments..
// - perform channel-map in ardour (silent/scratch buffers) ?
// - control-port API (explicit get/set functions ??)
// - latency reporting (global var? ctrl-port? set-function ?)
// - MIDI -> sparse table of events
// { [sample] => { Event }, .. }
// or { { sample, Event }, .. }
try {
LuaScriptInfoPtr lsi = LuaScripting::script_info (_script);
lpi = LuaPluginInfoPtr (new LuaPluginInfo (lsi));
assert (lpi);
set_info (lpi);
_mempool.set_name ("LuaProc: " + lsi->name);
_docs = lsi->description;
} catch (failed_constructor& err) {
return true;
}
lua_State* L = lua.getState ();
lua.do_command (_script);
// check if script has a DSP callback
luabridge::LuaRef lua_dsp_run = luabridge::getGlobal (L, "dsp_run");
luabridge::LuaRef lua_dsp_map = luabridge::getGlobal (L, "dsp_runmap");
if ((lua_dsp_run.type () != LUA_TFUNCTION) == (lua_dsp_map.type () != LUA_TFUNCTION)) {
return true;
}
if (lua_dsp_run.type () == LUA_TFUNCTION) {
_lua_dsp = new luabridge::LuaRef (lua_dsp_run);
}
else if (lua_dsp_map.type () == LUA_TFUNCTION) {
_lua_dsp = new luabridge::LuaRef (lua_dsp_map);
_lua_does_channelmapping = true;
}
else {
assert (0);
}
// initialize the DSP if needed
luabridge::LuaRef lua_dsp_init = luabridge::getGlobal (L, "dsp_init");
if (lua_dsp_init.type () == LUA_TFUNCTION) {
try {
lua_dsp_init (_session.nominal_frame_rate ());
} catch (luabridge::LuaException const& e) {
;
}
}
_ctrl_params.clear ();
luabridge::LuaRef lua_render = luabridge::getGlobal (L, "render_inline");
if (lua_render.isFunction ()) {
_lua_has_inline_display = true;
}
luabridge::LuaRef lua_params = luabridge::getGlobal (L, "dsp_params");
if (lua_params.isFunction ()) {
// call function // add try {} catch (luabridge::LuaException const& e)
luabridge::LuaRef params = lua_params ();
if (params.isTable ()) {
for (luabridge::Iterator i (params); !i.isNil (); ++i) {
// required fields
if (!i.key ().isNumber ()) { return false; }
if (!i.value ().isTable ()) { return false; }
if (!i.value ()["type"].isString ()) { return false; }
if (!i.value ()["name"].isString ()) { return false; }
if (!i.value ()["min"].isNumber ()) { return false; }
if (!i.value ()["max"].isNumber ()) { return false; }
int pn = i.key ().cast<int> ();
std::string type = i.value ()["type"].cast<std::string> ();
if (type == "input") {
if (!i.value ()["default"].isNumber ()) { return false; }
_ctrl_params.push_back (std::make_pair (false, pn));
}
else if (type == "output") {
_ctrl_params.push_back (std::make_pair (true, pn));
} else {
return false;
}
assert (pn == (int) _ctrl_params.size ());
//_param_desc[pn] = boost::shared_ptr<ParameterDescriptor> (new ParameterDescriptor());
luabridge::LuaRef lr = i.value ();
if (type == "input") {
_param_desc[pn].normal = lr["default"].cast<float> ();
} else {
_param_desc[pn].normal = lr["min"].cast<float> (); // output-port, no default
}
_param_desc[pn].lower = lr["min"].cast<float> ();
_param_desc[pn].upper = lr["max"].cast<float> ();
_param_desc[pn].toggled = lr["toggled"].isBoolean () && (lr["toggled"]).cast<bool> ();
_param_desc[pn].logarithmic = lr["logarithmic"].isBoolean () && (lr["logarithmic"]).cast<bool> ();
_param_desc[pn].integer_step = lr["integer"].isBoolean () && (lr["integer"]).cast<bool> ();
_param_desc[pn].sr_dependent = lr["ratemult"].isBoolean () && (lr["ratemult"]).cast<bool> ();
_param_desc[pn].enumeration = lr["enum"].isBoolean () && (lr["enum"]).cast<bool> ();
if (lr["bypass"].isBoolean () && (lr["bypass"]).cast<bool> ()) {
_designated_bypass_port = pn - 1; // lua table starts at 1.
}
if (lr["unit"].isString ()) {
std::string unit = lr["unit"].cast<std::string> ();
if (unit == "dB") { _param_desc[pn].unit = ParameterDescriptor::DB; }
else if (unit == "Hz") { _param_desc[pn].unit = ParameterDescriptor::HZ; }
else if (unit == "Midi Note") { _param_desc[pn].unit = ParameterDescriptor::MIDI_NOTE; }
}
_param_desc[pn].label = (lr["name"]).cast<std::string> ();
_param_desc[pn].scale_points = parse_scale_points (&lr);
luabridge::LuaRef doc = lr["doc"];
if (doc.isString ()) {
_param_doc[pn] = doc.cast<std::string> ();
} else {
_param_doc[pn] = "";
}
assert (!(_param_desc[pn].toggled && _param_desc[pn].logarithmic));
}
}
}
_control_data = new float[parameter_count ()];
_shadow_data = new float[parameter_count ()];
for (uint32_t i = 0; i < parameter_count (); ++i) {
if (parameter_is_input (i)) {
_control_data[i] = _shadow_data[i] = default_value (i);
}
}
// expose ctrl-ports to global lua namespace
luabridge::push <float *> (L, _control_data);
lua_setglobal (L, "CtrlPorts");
return false; // no error
}
bool
LuaProc::can_support_io_configuration (const ChanCount& in, ChanCount& out, ChanCount* imprecise)
{
// caller must hold process lock (no concurrent calls to interpreter
_output_configs.clear ();
lua_State* L = lua.getState ();
luabridge::LuaRef ioconfig = luabridge::getGlobal (L, "dsp_ioconfig");
luabridge::LuaRef *_iotable = NULL; // can't use reference :(
if (ioconfig.isFunction ()) {
try {
luabridge::LuaRef iotable = ioconfig ();
if (iotable.isTable ()) {
_iotable = new luabridge::LuaRef (iotable);
}
} catch (luabridge::LuaException const& e) {
_iotable = NULL;
}
}
if (!_iotable) {
/* empty table as default */
luabridge::LuaRef iotable = luabridge::newTable(L);
_iotable = new luabridge::LuaRef (iotable);
}
// now we can reference it.
luabridge::LuaRef iotable (*_iotable);
delete _iotable;
if ((iotable).length () < 1) {
/* empty table as only config, to get default values */
luabridge::LuaRef ioconf = luabridge::newTable(L);
iotable[1] = ioconf;
}
const int audio_in = in.n_audio ();
const int midi_in = in.n_midi ();
// preferred setting (provided by plugin_insert)
const int preferred_out = out.n_audio ();
const int preferred_midiout = out.n_midi ();
int midi_out = -1;
int audio_out = -1;
float penalty = 9999;
bool found = false;
#define FOUNDCFG_PENALTY(in, out, p) { \
_output_configs.insert (out); \
if (p < penalty) { \
audio_out = (out); \
midi_out = possible_midiout; \
if (imprecise) { \
imprecise->set (DataType::AUDIO, (in)); \
imprecise->set (DataType::MIDI, possible_midiin); \
} \
_has_midi_input = (possible_midiin > 0); \
_has_midi_output = (possible_midiout > 0); \
penalty = p; \
found = true; \
} \
}
#define FOUNDCFG_IMPRECISE(in, out) { \
const float p = fabsf ((float)(out) - preferred_out) * \
(((out) > preferred_out) ? 1.1 : 1) \
+ fabsf ((float)possible_midiout - preferred_midiout) * \
((possible_midiout - preferred_midiout) ? 0.6 : 0.5) \
+ fabsf ((float)(in) - audio_in) * \
(((in) > audio_in) ? 275 : 250) \
+ fabsf ((float)possible_midiin - midi_in) * \
((possible_midiin - midi_in) ? 100 : 110); \
FOUNDCFG_PENALTY(in, out, p); \
}
#define FOUNDCFG(out) \
FOUNDCFG_IMPRECISE(audio_in, out)
#define ANYTHINGGOES \
_output_configs.insert (0);
#define UPTO(nch) { \
for (int n = 1; n < nch; ++n) { \
_output_configs.insert (n); \
} \
}
if (imprecise) {
*imprecise = in;
}
for (luabridge::Iterator i (iotable); !i.isNil (); ++i) {
luabridge::LuaRef io (i.value ());
if (!io.isTable()) {
continue;
}
int possible_in = io["audio_in"].isNumber() ? io["audio_in"] : -1;
int possible_out = io["audio_out"].isNumber() ? io["audio_out"] : -1;
int possible_midiin = io["midi_in"].isNumber() ? io["midi_in"] : 0;
int possible_midiout = io["midi_out"].isNumber() ? io["midi_out"] : 0;
if (midi_in != possible_midiin && !imprecise) {
continue;
}
// exact match
if ((possible_in == audio_in) && (possible_out == preferred_out)) {
/* Set penalty so low that this output configuration
* will trump any other one */
FOUNDCFG_PENALTY(audio_in, preferred_out, -1);
}
if (possible_out == 0 && possible_midiout == 0) {
/* skip configurations with no output at all */
continue;
}
if (possible_in == -1 || possible_in == -2) {
/* wildcard for input */
if (possible_out == possible_in) {
/* either both -1 or both -2 (invalid and
* interpreted as both -1): out must match in */
FOUNDCFG (audio_in);
} else if (possible_out == -3 - possible_in) {
/* one is -1, the other is -2: any output configuration
* possible, pick what the insert prefers */
FOUNDCFG (preferred_out);
ANYTHINGGOES;
} else if (possible_out < -2) {
/* variable number of outputs up to -N,
* invalid if in == -2 but we accept it anyway */
FOUNDCFG (min (-possible_out, preferred_out));
UPTO (-possible_out)
} else {
/* exact number of outputs */
FOUNDCFG (possible_out);
}
}
if (possible_in < -2 || possible_in >= 0) {
/* specified number, exact or up to */
int desired_in;
if (possible_in >= 0) {
/* configuration can only match possible_in */
desired_in = possible_in;
} else {
/* configuration can match up to -possible_in */
desired_in = min (-possible_in, audio_in);
}
if (!imprecise && audio_in != desired_in) {
/* skip that configuration, it cannot match
* the required audio input count, and we
* cannot ask for change via \imprecise */
} else if (possible_out == -1 || possible_out == -2) {
/* any output configuration possible
* out == -2 is invalid, interpreted as out == -1.
* Really imprecise only if desired_in != audio_in */
FOUNDCFG_IMPRECISE (desired_in, preferred_out);
ANYTHINGGOES;
} else if (possible_out < -2) {
/* variable number of outputs up to -N
* not specified if in > 0, but we accept it anyway.
* Really imprecise only if desired_in != audio_in */
FOUNDCFG_IMPRECISE (desired_in, min (-possible_out, preferred_out));
UPTO (-possible_out)
} else {
/* exact number of outputs
* Really imprecise only if desired_in != audio_in */
FOUNDCFG_IMPRECISE (desired_in, possible_out);
}
}
}
if (!found) {
return false;
}
if (imprecise) {
_selected_in = *imprecise;
} else {
_selected_in = in;
}
out.set (DataType::MIDI, midi_out);
out.set (DataType::AUDIO, audio_out);
_selected_out = out;
return true;
}
bool
LuaProc::configure_io (ChanCount in, ChanCount out)
{
in.set (DataType::MIDI, _has_midi_input ? 1 : 0);
out.set (DataType::MIDI, _has_midi_output ? 1 : 0);
_info->n_inputs = _selected_in;
_info->n_outputs = _selected_out;
// configure the DSP if needed
if (in != _configured_in || out != _configured_out || !_configured) {
lua_State* L = lua.getState ();
luabridge::LuaRef lua_dsp_configure = luabridge::getGlobal (L, "dsp_configure");
if (lua_dsp_configure.type () == LUA_TFUNCTION) {
try {
luabridge::LuaRef io = lua_dsp_configure (&in, &out);
if (io.isTable ()) {
ChanCount lin (_selected_in);
ChanCount lout (_selected_out);
if (io["audio_in"].type() == LUA_TNUMBER) {
const int c = io["audio_in"].cast<int> ();
if (c >= 0) {
lin.set (DataType::AUDIO, c);
}
}
if (io["audio_out"].type() == LUA_TNUMBER) {
const int c = io["audio_out"].cast<int> ();
if (c >= 0) {
lout.set (DataType::AUDIO, c);
}
}
if (io["midi_in"].type() == LUA_TNUMBER) {
const int c = io["midi_in"].cast<int> ();
if (c >= 0) {
lin.set (DataType::MIDI, c);
}
}
if (io["midi_out"].type() == LUA_TNUMBER) {
const int c = io["midi_out"].cast<int> ();
if (c >= 0) {
lout.set (DataType::MIDI, c);
}
}
_info->n_inputs = lin;
_info->n_outputs = lout;
}
_configured = true;
} catch (luabridge::LuaException const& e) {
PBD::error << "LuaException: " << e.what () << "\n";
#ifndef NDEBUG
std::cerr << "LuaException: " << e.what () << "\n";
#endif
return false;
}
}
}
_configured_in = in;
_configured_out = out;
return true;
}
int
LuaProc::connect_and_run (BufferSet& bufs,
framepos_t start, framepos_t end, double speed,
ChanMapping in, ChanMapping out,
pframes_t nframes, framecnt_t offset)
{
if (!_lua_dsp) {
return 0;
}
Plugin::connect_and_run (bufs, start, end, speed, in, out, nframes, offset);
// This is needed for ARDOUR::Session requests :(
if (! SessionEvent::has_per_thread_pool ()) {
char name[64];
snprintf (name, 64, "Proc-%p", this);
pthread_set_name (name);
SessionEvent::create_per_thread_pool (name, 64);
PBD::notify_event_loops_about_thread_creation (pthread_self(), name, 64);
}
uint32_t const n = parameter_count ();
for (uint32_t i = 0; i < n; ++i) {
if (parameter_is_control (i) && parameter_is_input (i)) {
_control_data[i] = _shadow_data[i];
}
}
#ifdef WITH_LUAPROC_STATS
int64_t t0 = g_get_monotonic_time ();
#endif
try {
if (_lua_does_channelmapping) {
// run the DSP function
(*_lua_dsp)(&bufs, in, out, nframes, offset);
} else {
// map buffers
BufferSet& silent_bufs = _session.get_silent_buffers (ChanCount (DataType::AUDIO, 1));
BufferSet& scratch_bufs = _session.get_scratch_buffers (ChanCount (DataType::AUDIO, 1));
lua_State* L = lua.getState ();
luabridge::LuaRef in_map (luabridge::newTable (L));
luabridge::LuaRef out_map (luabridge::newTable (L));
const uint32_t audio_in = _configured_in.n_audio ();
const uint32_t audio_out = _configured_out.n_audio ();
const uint32_t midi_in = _configured_in.n_midi ();
for (uint32_t ap = 0; ap < audio_in; ++ap) {
bool valid;
const uint32_t buf_index = in.get(DataType::AUDIO, ap, &valid);
if (valid) {
in_map[ap + 1] = bufs.get_audio (buf_index).data (offset);
} else {
in_map[ap + 1] = silent_bufs.get_audio (0).data (offset);
}
}
for (uint32_t ap = 0; ap < audio_out; ++ap) {
bool valid;
const uint32_t buf_index = out.get(DataType::AUDIO, ap, &valid);
if (valid) {
out_map[ap + 1] = bufs.get_audio (buf_index).data (offset);
} else {
out_map[ap + 1] = scratch_bufs.get_audio (0).data (offset);
}
}
luabridge::LuaRef lua_midi_src_tbl (luabridge::newTable (L));
int e = 1; // > 1 port, we merge events (unsorted)
for (uint32_t mp = 0; mp < midi_in; ++mp) {
bool valid;
const uint32_t idx = in.get(DataType::MIDI, mp, &valid);
if (valid) {
for (MidiBuffer::iterator m = bufs.get_midi(idx).begin();
m != bufs.get_midi(idx).end(); ++m, ++e) {
const Evoral::Event<framepos_t> ev(*m, false);
luabridge::LuaRef lua_midi_data (luabridge::newTable (L));
const uint8_t* data = ev.buffer();
for (uint32_t i = 0; i < ev.size(); ++i) {
lua_midi_data [i + 1] = data[i];
}
luabridge::LuaRef lua_midi_event (luabridge::newTable (L));
lua_midi_event["time"] = 1 + (*m).time();
lua_midi_event["data"] = lua_midi_data;
lua_midi_event["bytes"] = data;
lua_midi_event["size"] = ev.size();
lua_midi_src_tbl[e] = lua_midi_event;
}
}
}
if (_has_midi_input) {
// XXX TODO This needs a better solution than global namespace
luabridge::push (L, lua_midi_src_tbl);
lua_setglobal (L, "midiin");
}
luabridge::LuaRef lua_midi_sink_tbl (luabridge::newTable (L));
if (_has_midi_output) {
luabridge::push (L, lua_midi_sink_tbl);
lua_setglobal (L, "midiout");
}
// run the DSP function
(*_lua_dsp)(in_map, out_map, nframes);
// copy back midi events
if (_has_midi_output && lua_midi_sink_tbl.isTable ()) {
bool valid;
const uint32_t idx = out.get(DataType::MIDI, 0, &valid);
if (valid && bufs.count().n_midi() > idx) {
MidiBuffer& mbuf = bufs.get_midi(idx);
mbuf.silence(0, 0);
for (luabridge::Iterator i (lua_midi_sink_tbl); !i.isNil (); ++i) {
if (!i.key ().isNumber ()) { continue; }
if (!i.value ()["time"].isNumber ()) { continue; }
if (!i.value ()["data"].isTable ()) { continue; }
luabridge::LuaRef data_tbl (i.value ()["data"]);
framepos_t tme = i.value ()["time"];
if (tme < 1 || tme > nframes) { continue; }
uint8_t data[64];
size_t size = 0;
for (luabridge::Iterator di (data_tbl); !di.isNil () && size < sizeof(data); ++di, ++size) {
data[size] = di.value ();
}
if (size > 0 && size < 64) {
mbuf.push_back(tme - 1, size, data);
}
}
}
}
}
} catch (luabridge::LuaException const& e) {
PBD::error << "LuaException: " << e.what () << "\n";
#ifndef NDEBUG
std::cerr << "LuaException: " << e.what () << "\n";
#endif
return -1;
}
#ifdef WITH_LUAPROC_STATS
int64_t t1 = g_get_monotonic_time ();
#endif
lua.collect_garbage_step ();
#ifdef WITH_LUAPROC_STATS
++_stats_cnt;
int64_t t2 = g_get_monotonic_time ();
int64_t ela0 = t1 - t0;
int64_t ela1 = t2 - t1;
if (ela0 > _stats_max[0]) _stats_max[0] = ela0;
if (ela1 > _stats_max[1]) _stats_max[1] = ela1;
_stats_avg[0] += ela0;
_stats_avg[1] += ela1;
#endif
return 0;
}
void
LuaProc::add_state (XMLNode* root) const
{
XMLNode* child;
char buf[32];
LocaleGuard lg;
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);
root->add_child_nocopy (*script_node);
for (uint32_t i = 0; i < parameter_count(); ++i) {
if (parameter_is_input(i) && parameter_is_control(i)) {
child = new XMLNode("Port");
snprintf(buf, sizeof(buf), "%u", i);
child->add_property("id", std::string(buf));
snprintf(buf, sizeof(buf), "%+f", _shadow_data[i]);
child->add_property("value", std::string(buf));
root->add_child_nocopy(*child);
}
}
}
int
LuaProc::set_script_from_state (const XMLNode& node)
{
XMLNode* child;
if (node.name () != state_node_name ()) {
return -1;
}
if ((child = node.child (X_("script"))) != 0) {
for (XMLNodeList::const_iterator n = child->children ().begin (); n != child->children ().end (); ++n) {
if (!(*n)->is_content ()) { continue; }
gsize size;
guchar* buf = g_base64_decode ((*n)->content ().c_str (), &size);
_script = std::string ((const char*)buf, size);
g_free (buf);
if (load_script ()) {
PBD::error << _("Failed to load Lua script from session state.") << endmsg;
#ifndef NDEBUG
std::cerr << "Failed Lua Script: " << _script << std::endl;
#endif
_script = "";
}
break;
}
}
if (_script.empty ()) {
PBD::error << _("Session State for LuaProcessor did not include a Lua script.") << endmsg;
return -1;
}
if (!_lua_dsp) {
PBD::error << _("Invalid/incompatible Lua script found for LuaProcessor.") << endmsg;
return -1;
}
return 0;
}
int
LuaProc::set_state (const XMLNode& node, int version)
{
#ifndef NO_PLUGIN_STATE
XMLNodeList nodes;
XMLProperty const * prop;
XMLNodeConstIterator iter;
XMLNode *child;
const char *value;
const char *port;
uint32_t port_id;
#endif
LocaleGuard lg;
if (_script.empty ()) {
if (set_script_from_state (node)) {
return -1;
}
}
#ifndef NO_PLUGIN_STATE
if (node.name() != state_node_name()) {
error << _("Bad node sent to LuaProc::set_state") << endmsg;
return -1;
}
nodes = node.children ("Port");
for (iter = nodes.begin(); iter != nodes.end(); ++iter) {
child = *iter;
if ((prop = child->property("id")) != 0) {
port = prop->value().c_str();
} else {
warning << _("LuaProc: port has no symbol, ignored") << endmsg;
continue;
}
if ((prop = child->property("value")) != 0) {
value = prop->value().c_str();
} else {
warning << _("LuaProc: port has no value, ignored") << endmsg;
continue;
}
sscanf (port, "%" PRIu32, &port_id);
set_parameter (port_id, atof(value));
}
#endif
return Plugin::set_state (node, version);
}
uint32_t
LuaProc::parameter_count () const
{
return _ctrl_params.size ();
}
float
LuaProc::default_value (uint32_t port)
{
if (_ctrl_params[port].first) {
assert (0);
return 0;
}
int lp = _ctrl_params[port].second;
return _param_desc[lp].normal;
}
void
LuaProc::set_parameter (uint32_t port, float val)
{
assert (port < parameter_count ());
if (get_parameter (port) == val) {
return;
}
_shadow_data[port] = val;
Plugin::set_parameter (port, val);
}
float
LuaProc::get_parameter (uint32_t port) const
{
if (parameter_is_input (port)) {
return _shadow_data[port];
} else {
return _control_data[port];
}
}
int
LuaProc::get_parameter_descriptor (uint32_t port, ParameterDescriptor& desc) const
{
assert (port <= parameter_count ());
int lp = _ctrl_params[port].second;
const ParameterDescriptor& d (_param_desc.find(lp)->second);
desc.lower = d.lower;
desc.upper = d.upper;
desc.normal = d.normal;
desc.toggled = d.toggled;
desc.logarithmic = d.logarithmic;
desc.integer_step = d.integer_step;
desc.sr_dependent = d.sr_dependent;
desc.enumeration = d.enumeration;
desc.unit = d.unit;
desc.label = d.label;
desc.scale_points = d.scale_points;
desc.update_steps ();
return 0;
}
std::string
LuaProc::get_parameter_docs (uint32_t port) const {
assert (port <= parameter_count ());
int lp = _ctrl_params[port].second;
return _param_doc.find(lp)->second;
}
uint32_t
LuaProc::nth_parameter (uint32_t port, bool& ok) const
{
if (port < _ctrl_params.size ()) {
ok = true;
return port;
}
ok = false;
return 0;
}
bool
LuaProc::parameter_is_input (uint32_t port) const
{
assert (port < _ctrl_params.size ());
return (!_ctrl_params[port].first);
}
bool
LuaProc::parameter_is_output (uint32_t port) const
{
assert (port < _ctrl_params.size ());
return (_ctrl_params[port].first);
}
std::set<Evoral::Parameter>
LuaProc::automatable () const
{
std::set<Evoral::Parameter> automatables;
for (uint32_t i = 0; i < _ctrl_params.size (); ++i) {
if (parameter_is_input (i)) {
automatables.insert (automatables.end (), Evoral::Parameter (PluginAutomation, 0, i));
}
}
return automatables;
}
std::string
LuaProc::describe_parameter (Evoral::Parameter param)
{
if (param.type () == PluginAutomation && param.id () < parameter_count ()) {
int lp = _ctrl_params[param.id ()].second;
return _param_desc[lp].label;
}
return "??";
}
void
LuaProc::print_parameter (uint32_t param, char* buf, uint32_t len) const
{
if (buf && len) {
if (param < parameter_count ()) {
snprintf (buf, len, "%.3f", get_parameter (param));
} else {
strcat (buf, "0");
}
}
}
boost::shared_ptr<ScalePoints>
LuaProc::parse_scale_points (luabridge::LuaRef* lr)
{
if (!(*lr)["scalepoints"].isTable()) {
return boost::shared_ptr<ScalePoints> ();
}
int cnt = 0;
boost::shared_ptr<ScalePoints> rv = boost::shared_ptr<ScalePoints>(new ScalePoints());
luabridge::LuaRef scalepoints ((*lr)["scalepoints"]);
for (luabridge::Iterator i (scalepoints); !i.isNil (); ++i) {
if (!i.key ().isString ()) { continue; }
if (!i.value ().isNumber ()) { continue; }
rv->insert(make_pair(i.key ().cast<std::string> (),
i.value ().cast<float> ()));
++cnt;
}
if (rv->size() > 0) {
return rv;
}
return boost::shared_ptr<ScalePoints> ();
}
boost::shared_ptr<ScalePoints>
LuaProc::get_scale_points (uint32_t port) const
{
int lp = _ctrl_params[port].second;
return _param_desc.find(lp)->second.scale_points;
}
void
LuaProc::setup_lua_inline_gui (LuaState *lua_gui)
{
lua_State* LG = lua_gui->getState ();
LuaBindings::stddef (LG);
LuaBindings::common (LG);
LuaBindings::dsp (LG);
LuaBindings::osc (LG);
lua_gui->Print.connect (sigc::mem_fun (*this, &LuaProc::lua_print));
lua_gui->do_command ("function ardour () end");
lua_gui->do_command (_script);
// TODO think: use a weak-pointer here ?
// (the GUI itself uses a shared ptr to this plugin, so we should be good)
luabridge::getGlobalNamespace (LG)
.beginNamespace ("Ardour")
.beginClass <LuaProc> ("LuaProc")
.addFunction ("shmem", &LuaProc::instance_shm)
.addFunction ("table", &LuaProc::instance_ref)
.endClass ()
.endNamespace ();
luabridge::push <LuaProc *> (LG, this);
lua_setglobal (LG, "self");
luabridge::push <float *> (LG, _control_data);
lua_setglobal (LG, "CtrlPorts");
}
////////////////////////////////////////////////////////////////////////////////
#include "ardour/search_paths.h"
#include "sha1.c"
std::string
LuaProc::preset_name_to_uri (const std::string& name) const
{
std::string uri ("urn:lua:");
char hash[41];
Sha1Digest s;
sha1_init (&s);
sha1_write (&s, (const uint8_t *) name.c_str(), name.size ());
sha1_write (&s, (const uint8_t *) _script.c_str(), _script.size ());
sha1_result_hash (&s, hash);
return uri + hash;
}
std::string
LuaProc::presets_file () const
{
return string_compose ("lua-%1", _info->unique_id);
}
XMLTree*
LuaProc::presets_tree () const
{
XMLTree* t = new XMLTree;
std::string p = Glib::build_filename (ARDOUR::user_config_directory (), "presets");
if (!Glib::file_test (p, Glib::FILE_TEST_IS_DIR)) {
if (g_mkdir_with_parents (p.c_str(), 0755) != 0) {
error << _("Unable to create LuaProc presets directory") << endmsg;
};
}
p = Glib::build_filename (p, presets_file ());
if (!Glib::file_test (p, Glib::FILE_TEST_EXISTS)) {
t->set_root (new XMLNode (X_("LuaPresets")));
return t;
}
t->set_filename (p);
if (!t->read ()) {
delete t;
return 0;
}
return t;
}
bool
LuaProc::load_preset (PresetRecord r)
{
boost::shared_ptr<XMLTree> t (presets_tree ());
if (t == 0) {
return false;
}
XMLNode* root = t->root ();
for (XMLNodeList::const_iterator i = root->children().begin(); i != root->children().end(); ++i) {
XMLProperty const * label = (*i)->property (X_("label"));
assert (label);
if (label->value() != r.label) {
continue;
}
for (XMLNodeList::const_iterator j = (*i)->children().begin(); j != (*i)->children().end(); ++j) {
if ((*j)->name() == X_("Parameter")) {
XMLProperty const * index = (*j)->property (X_("index"));
XMLProperty const * value = (*j)->property (X_("value"));
assert (index);
assert (value);
LocaleGuard lg;
set_parameter (atoi (index->value().c_str()), atof (value->value().c_str ()));
}
}
return Plugin::load_preset(r);
}
return false;
}
std::string
LuaProc::do_save_preset (std::string name) {
boost::shared_ptr<XMLTree> t (presets_tree ());
if (t == 0) {
return "";
}
std::string uri (preset_name_to_uri (name));
XMLNode* p = new XMLNode (X_("Preset"));
p->add_property (X_("uri"), uri);
p->add_property (X_("label"), name);
for (uint32_t i = 0; i < parameter_count(); ++i) {
if (parameter_is_input (i)) {
XMLNode* c = new XMLNode (X_("Parameter"));
c->add_property (X_("index"), string_compose ("%1", i));
c->add_property (X_("value"), string_compose ("%1", get_parameter (i)));
p->add_child_nocopy (*c);
}
}
t->root()->add_child_nocopy (*p);
std::string f = Glib::build_filename (ARDOUR::user_config_directory (), "presets");
f = Glib::build_filename (f, presets_file ());
t->write (f);
return uri;
}
void
LuaProc::do_remove_preset (std::string name)
{
boost::shared_ptr<XMLTree> t (presets_tree ());
if (t == 0) {
return;
}
t->root()->remove_nodes_and_delete (X_("label"), name);
std::string f = Glib::build_filename (ARDOUR::user_config_directory (), "presets");
f = Glib::build_filename (f, presets_file ());
t->write (f);
}
void
LuaProc::find_presets ()
{
boost::shared_ptr<XMLTree> t (presets_tree ());
if (t) {
XMLNode* root = t->root ();
for (XMLNodeList::const_iterator i = root->children().begin(); i != root->children().end(); ++i) {
XMLProperty const * uri = (*i)->property (X_("uri"));
XMLProperty const * label = (*i)->property (X_("label"));
assert (uri);
assert (label);
PresetRecord r (uri->value(), label->value(), true);
_presets.insert (make_pair (r.uri, r));
}
}
}
////////////////////////////////////////////////////////////////////////////////
LuaPluginInfo::LuaPluginInfo (LuaScriptInfoPtr lsi) {
if (lsi->type != LuaScriptInfo::DSP) {
throw failed_constructor ();
}
path = lsi->path;
name = lsi->name;
creator = lsi->author;
category = lsi->category;
unique_id = lsi->unique_id;
n_inputs.set (DataType::AUDIO, 1);
n_outputs.set (DataType::AUDIO, 1);
type = Lua;
_is_instrument = category == "Instrument";
}
PluginPtr
LuaPluginInfo::load (Session& session)
{
std::string script = "";
if (!Glib::file_test (path, Glib::FILE_TEST_EXISTS)) {
return PluginPtr ();
}
try {
script = Glib::file_get_contents (path);
} catch (Glib::FileError err) {
return PluginPtr ();
}
if (script.empty ()) {
return PluginPtr ();
}
try {
PluginPtr plugin (new LuaProc (session.engine (), session, script));
return plugin;
} catch (failed_constructor& err) {
;
}
return PluginPtr ();
}
std::vector<Plugin::PresetRecord>
LuaPluginInfo::get_presets (bool /*user_only*/) const
{
std::vector<Plugin::PresetRecord> p;
XMLTree* t = new XMLTree;
std::string pf = Glib::build_filename (ARDOUR::user_config_directory (), "presets", string_compose ("lua-%1", unique_id));
if (Glib::file_test (pf, Glib::FILE_TEST_EXISTS)) {
t->set_filename (pf);
if (t->read ()) {
XMLNode* root = t->root ();
for (XMLNodeList::const_iterator i = root->children().begin(); i != root->children().end(); ++i) {
XMLProperty const * uri = (*i)->property (X_("uri"));
XMLProperty const * label = (*i)->property (X_("label"));
p.push_back (Plugin::PresetRecord (uri->value(), label->value(), true));
}
}
}
delete t;
return p;
}