You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
579 lines
17 KiB
579 lines
17 KiB
/*
|
|
Calf Box, an open source musical instrument.
|
|
Copyright (C) 2010-2011 Krzysztof Foltman
|
|
|
|
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 3 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, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include "app.h"
|
|
#include "blob.h"
|
|
#include "engine.h"
|
|
#include "errors.h"
|
|
#include "module.h"
|
|
#include "scripting.h"
|
|
#include <assert.h>
|
|
#include <glib.h>
|
|
|
|
#include "config-api.h"
|
|
#include "tarfile.h"
|
|
#include "wavebank.h"
|
|
#include "scene.h"
|
|
|
|
static gboolean audio_running = FALSE;
|
|
static gboolean engine_initialised = FALSE;
|
|
|
|
gboolean cbox_embed_init_engine(const char *config_file, GError **error)
|
|
{
|
|
if (engine_initialised)
|
|
{
|
|
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Engine already initialized");
|
|
return FALSE;
|
|
}
|
|
|
|
cbox_dom_init();
|
|
app.tarpool = cbox_tarpool_new();
|
|
app.document = cbox_document_new();
|
|
app.rt = cbox_rt_new(app.document);
|
|
app.engine = cbox_engine_new(app.document, app.rt);
|
|
app.rt->engine = app.engine;
|
|
cbox_config_init(config_file);
|
|
cbox_wavebank_init();
|
|
engine_initialised = 1;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean cbox_embed_shutdown_engine(GError **error)
|
|
{
|
|
if (!engine_initialised)
|
|
{
|
|
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Engine not initialized");
|
|
return FALSE;
|
|
}
|
|
|
|
CBOX_DELETE(app.engine);
|
|
CBOX_DELETE(app.rt);
|
|
cbox_tarpool_destroy(app.tarpool);
|
|
cbox_document_destroy(app.document);
|
|
cbox_wavebank_close();
|
|
cbox_config_close();
|
|
cbox_dom_close();
|
|
engine_initialised = FALSE;
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean cbox_embed_start_audio(struct cbox_command_target *target, GError **error)
|
|
{
|
|
if (!engine_initialised)
|
|
{
|
|
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Engine not initialized");
|
|
return FALSE;
|
|
}
|
|
if (audio_running)
|
|
{
|
|
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Audio already started");
|
|
return FALSE;
|
|
}
|
|
|
|
struct cbox_open_params params;
|
|
|
|
GError *error2 = NULL;
|
|
if (!cbox_io_init(&app.io, ¶ms, target, &error2))
|
|
{
|
|
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Cannot initialise sound I/O: %s", (error2 && error2->message ? error2->message : "Unknown error"));
|
|
g_error_free(error2);
|
|
return FALSE;
|
|
}
|
|
|
|
const char *effect_preset_name = cbox_config_get_string("master", "effect");
|
|
|
|
cbox_rt_set_io(app.rt, &app.io);
|
|
cbox_scene_new(app.document, app.engine);
|
|
cbox_rt_start(app.rt, target);
|
|
if (effect_preset_name && *effect_preset_name)
|
|
{
|
|
app.engine->effect = cbox_module_new_from_fx_preset(effect_preset_name, app.document, app.rt, app.engine, error);
|
|
if (!app.engine->effect)
|
|
{
|
|
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Cannot load master effect preset %s: %s", effect_preset_name, (error2 && error2->message ? error2->message : "Unknown error"));
|
|
g_error_free(error2);
|
|
return FALSE;
|
|
}
|
|
}
|
|
audio_running = TRUE;
|
|
return TRUE;
|
|
}
|
|
|
|
gboolean cbox_embed_stop_audio(GError **error)
|
|
{
|
|
if (!engine_initialised)
|
|
{
|
|
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Engine not initialised");
|
|
return FALSE;
|
|
}
|
|
if (!audio_running)
|
|
{
|
|
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "Audio not running");
|
|
return FALSE;
|
|
}
|
|
|
|
while(app.engine->scene_count > 0)
|
|
CBOX_DELETE(app.engine->scenes[0]);
|
|
cbox_rt_stop(app.rt);
|
|
cbox_io_close(&app.io);
|
|
audio_running = FALSE;
|
|
return TRUE;
|
|
}
|
|
|
|
struct cbox_command_target *cbox_embed_get_cmd_root()
|
|
{
|
|
return &app.cmd_target;
|
|
}
|
|
|
|
#if USE_PYTHON
|
|
|
|
// This is a workaround for what I consider a defect in pyconfig.h
|
|
#undef _XOPEN_SOURCE
|
|
#undef _POSIX_C_SOURCE
|
|
|
|
#include <Python.h>
|
|
|
|
struct PyCboxCallback
|
|
{
|
|
PyObject_HEAD
|
|
struct cbox_command_target *target;
|
|
};
|
|
|
|
static PyObject *
|
|
PyCboxCallback_New(PyTypeObject *type, PyObject *args, PyObject *kwds)
|
|
{
|
|
struct PyCboxCallback *self;
|
|
|
|
self = (struct PyCboxCallback *)type->tp_alloc(type, 0);
|
|
if (self != NULL) {
|
|
self->target = NULL;
|
|
}
|
|
|
|
return (PyObject *)self;
|
|
}
|
|
|
|
static int
|
|
PyCboxCallback_Init(struct PyCboxCallback *self, PyObject *args, PyObject *kwds)
|
|
{
|
|
PyObject *cobj = NULL;
|
|
if (!PyArg_ParseTuple(args, "O!:init", &PyCapsule_Type, &cobj))
|
|
return -1;
|
|
|
|
self->target = PyCapsule_GetPointer(cobj, NULL);
|
|
return 0;
|
|
}
|
|
|
|
static PyObject *cbox_python_do_cmd_on(struct cbox_command_target *ct, PyObject *self, PyObject *args);
|
|
|
|
static PyObject *
|
|
PyCboxCallback_Call(PyObject *_self, PyObject *args, PyObject *kwds)
|
|
{
|
|
struct PyCboxCallback *self = (struct PyCboxCallback *)_self;
|
|
|
|
return cbox_python_do_cmd_on(self->target, _self, args);
|
|
}
|
|
|
|
PyTypeObject CboxCallbackType = {
|
|
PyVarObject_HEAD_INIT(NULL, 0)
|
|
.tp_name = "_cbox.Callback",
|
|
.tp_basicsize = sizeof(struct PyCboxCallback),
|
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
|
.tp_doc = "Callback for feedback channel to Cbox C code",
|
|
.tp_init = (initproc)PyCboxCallback_Init,
|
|
.tp_new = PyCboxCallback_New,
|
|
.tp_call = PyCboxCallback_Call
|
|
};
|
|
|
|
static gboolean set_error_from_python(GError **error)
|
|
{
|
|
PyObject *ptype = NULL, *pvalue = NULL, *ptraceback = NULL;
|
|
PyErr_Fetch(&ptype, &pvalue, &ptraceback);
|
|
PyObject *ptypestr = PyObject_Str(ptype);
|
|
PyObject *pvaluestr = PyObject_Str(pvalue);
|
|
PyObject *ptypestr_unicode = PyUnicode_AsUTF8String(ptypestr);
|
|
PyObject *pvaluestr_unicode = PyUnicode_AsUTF8String(pvaluestr);
|
|
g_set_error(error, CBOX_MODULE_ERROR, CBOX_MODULE_ERROR_FAILED, "%s: %s", PyBytes_AsString(ptypestr_unicode), PyBytes_AsString(pvaluestr_unicode));
|
|
Py_DECREF(pvaluestr_unicode);
|
|
Py_DECREF(ptypestr_unicode);
|
|
//g_error("%s:%s", PyString_AsString(ptypestr), PyString_AsString(pvaluestr));
|
|
Py_DECREF(ptypestr);
|
|
Py_DECREF(pvaluestr);
|
|
Py_DECREF(ptype);
|
|
Py_XDECREF(pvalue);
|
|
Py_XDECREF(ptraceback);
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean bridge_to_python_callback(struct cbox_command_target *ct, struct cbox_command_target *fb, struct cbox_osc_command *cmd, GError **error)
|
|
{
|
|
PyObject *callback = ct->user_data;
|
|
|
|
int argc = strlen(cmd->arg_types);
|
|
PyObject *arg_values = PyList_New(argc);
|
|
for (int i = 0; i < argc; i++)
|
|
{
|
|
if (cmd->arg_types[i] == 's')
|
|
{
|
|
PyList_SetItem(arg_values, i, PyUnicode_FromString(cmd->arg_values[i]));
|
|
}
|
|
else
|
|
if (cmd->arg_types[i] == 'o')
|
|
{
|
|
struct cbox_objhdr *oh = cmd->arg_values[i];
|
|
char buf[40];
|
|
cbox_uuid_tostring(&oh->instance_uuid, buf);
|
|
PyList_SetItem(arg_values, i, PyUnicode_FromString(buf));
|
|
}
|
|
else
|
|
if (cmd->arg_types[i] == 'u')
|
|
{
|
|
struct cbox_uuid *uuid = cmd->arg_values[i];
|
|
char buf[40];
|
|
cbox_uuid_tostring(uuid, buf);
|
|
PyList_SetItem(arg_values, i, PyUnicode_FromString(buf));
|
|
}
|
|
else
|
|
if (cmd->arg_types[i] == 'i')
|
|
{
|
|
PyList_SetItem(arg_values, i, PyLong_FromLong(*(int *)cmd->arg_values[i]));
|
|
}
|
|
else
|
|
if (cmd->arg_types[i] == 'f')
|
|
{
|
|
PyList_SetItem(arg_values, i, PyFloat_FromDouble(*(double *)cmd->arg_values[i]));
|
|
}
|
|
else
|
|
if (cmd->arg_types[i] == 'b')
|
|
{
|
|
struct cbox_blob *blob = cmd->arg_values[i];
|
|
PyList_SetItem(arg_values, i, PyByteArray_FromStringAndSize(blob->data, blob->size));
|
|
}
|
|
else
|
|
{
|
|
PyList_SetItem(arg_values, i, Py_None);
|
|
Py_INCREF(Py_None);
|
|
}
|
|
}
|
|
struct PyCboxCallback *fbcb = NULL;
|
|
|
|
PyObject *args = PyTuple_New(3);
|
|
PyTuple_SetItem(args, 0, PyUnicode_FromString(cmd->command));
|
|
PyObject *pyfb = NULL;
|
|
if (fb)
|
|
{
|
|
struct PyCboxCallback *fbcb = PyObject_New(struct PyCboxCallback, &CboxCallbackType);
|
|
fbcb->target = fb;
|
|
pyfb = (PyObject *)fbcb;
|
|
}
|
|
else
|
|
{
|
|
pyfb = Py_None;
|
|
Py_INCREF(Py_None);
|
|
}
|
|
PyTuple_SetItem(args, 1, pyfb);
|
|
PyTuple_SetItem(args, 2, arg_values);
|
|
|
|
PyObject *result = PyObject_Call(callback, args, NULL);
|
|
Py_DECREF(args);
|
|
|
|
if (fbcb)
|
|
fbcb->target = NULL;
|
|
|
|
if (result)
|
|
{
|
|
Py_DECREF(result);
|
|
return TRUE;
|
|
}
|
|
|
|
return set_error_from_python(error);
|
|
}
|
|
|
|
static PyObject *cbox_python_do_cmd_on(struct cbox_command_target *ct, PyObject *self, PyObject *args)
|
|
{
|
|
const char *command = NULL;
|
|
PyObject *callback = NULL;
|
|
PyObject *list = NULL;
|
|
if (!PyArg_ParseTuple(args, "sOO!:do_cmd", &command, &callback, &PyList_Type, &list))
|
|
return NULL;
|
|
|
|
int len = PyList_Size(list);
|
|
void *extra = malloc(len * sizeof(double));
|
|
struct cbox_osc_command cmd;
|
|
GError *error = NULL;
|
|
char *arg_types = malloc(len + 1);
|
|
void **arg_values = malloc(2 * len * sizeof(void *));
|
|
void **arg_extra = &arg_values[len];
|
|
cmd.command = command;
|
|
cmd.arg_types = arg_types;
|
|
cmd.arg_values = arg_values;
|
|
double *arg_space = extra;
|
|
gboolean free_blobs = FALSE;
|
|
for (int i = 0; i < len; i++)
|
|
{
|
|
cmd.arg_values[i] = &arg_space[i];
|
|
PyObject *value = PyList_GetItem(list, i);
|
|
|
|
if (PyLong_Check(value))
|
|
{
|
|
arg_types[i] = 'i';
|
|
*(int *)arg_values[i] = PyLong_AsLong(value);
|
|
}
|
|
else
|
|
if (PyFloat_Check(value))
|
|
{
|
|
arg_types[i] = 'f';
|
|
*(double *)arg_values[i] = PyFloat_AsDouble(value);
|
|
}
|
|
else
|
|
if (PyUnicode_Check(value))
|
|
{
|
|
PyObject *utf8str = PyUnicode_AsUTF8String(value);
|
|
arg_types[i] = 's';
|
|
arg_extra[i] = utf8str;
|
|
arg_values[i] = PyBytes_AsString(utf8str);
|
|
free_blobs = TRUE;
|
|
}
|
|
else
|
|
if (PyByteArray_Check(value))
|
|
{
|
|
const void *buf = PyByteArray_AsString(value);
|
|
ssize_t len = PyByteArray_Size(value);
|
|
|
|
if (buf)
|
|
{
|
|
// note: this is not really acquired, the blob is freed using free and not cbox_blob_destroy
|
|
struct cbox_blob *blob = cbox_blob_new_acquire_data((void *)buf, len);
|
|
arg_types[i] = 'b';
|
|
arg_values[i] = blob;
|
|
free_blobs = TRUE;
|
|
}
|
|
else
|
|
arg_types[i] = 'N';
|
|
}
|
|
else
|
|
{
|
|
PyObject *ob_type = (PyObject *)value->ob_type;
|
|
PyObject *typename_unicode = PyObject_Str(ob_type);
|
|
PyObject *typename_bytes = PyUnicode_AsUTF8String(typename_unicode);
|
|
PyObject *exc = PyErr_Format(PyExc_ValueError, "Cannot decode Python type '%s' to execute '%s'", PyBytes_AsString(typename_bytes), command);
|
|
Py_DECREF(typename_bytes);
|
|
Py_DECREF(typename_unicode);
|
|
|
|
return exc;
|
|
}
|
|
}
|
|
arg_types[len] = '\0';
|
|
|
|
struct cbox_command_target target;
|
|
cbox_command_target_init(&target, bridge_to_python_callback, callback);
|
|
|
|
// cbox_osc_command_dump(&cmd);
|
|
Py_INCREF(callback);
|
|
gboolean result = ct->process_cmd(ct, callback != Py_None ? &target : NULL, &cmd, &error);
|
|
Py_DECREF(callback);
|
|
|
|
if (free_blobs)
|
|
{
|
|
for (int i = 0; i < len; i++)
|
|
{
|
|
if (arg_types[i] == 'b')
|
|
free(arg_values[i]);
|
|
if (arg_types[i] == 's')
|
|
Py_DECREF((PyObject *)arg_extra[i]);
|
|
}
|
|
}
|
|
free(arg_space);
|
|
free(arg_values);
|
|
free(arg_types);
|
|
|
|
if (!result)
|
|
return PyErr_Format(PyExc_Exception, "%s", error ? error->message : "Unknown error");
|
|
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
static PyObject *cbox_python_do_cmd(PyObject *self, PyObject *args)
|
|
{
|
|
if (!engine_initialised)
|
|
return PyErr_Format(PyExc_Exception, "Engine not initialised");
|
|
return cbox_python_do_cmd_on(&app.cmd_target, self, args);
|
|
}
|
|
|
|
#if CALFBOX_AS_MODULE
|
|
|
|
#include "config-api.h"
|
|
#include "tarfile.h"
|
|
#include "wavebank.h"
|
|
#include "scene.h"
|
|
|
|
static PyObject *pyerror_from_gerror(PyObject *exception, GError *error)
|
|
{
|
|
PyObject *pyerr = PyErr_Format(exception, "%s", error ? error->message : "Unknown error");
|
|
g_error_free(error);
|
|
return pyerr;
|
|
}
|
|
|
|
static PyObject *cbox_python_init_engine(PyObject *self, PyObject *args)
|
|
{
|
|
const char *config_file = NULL;
|
|
if (!PyArg_ParseTuple(args, "|z:init_engine", &config_file))
|
|
return NULL;
|
|
|
|
GError *error = NULL;
|
|
if (!cbox_embed_init_engine(config_file, &error))
|
|
return pyerror_from_gerror(PyExc_Exception, error);
|
|
Py_INCREF(Py_None);
|
|
return Py_None;
|
|
}
|
|
|
|
static PyObject *cbox_python_shutdown_engine(PyObject *self, PyObject *args)
|
|
{
|
|
if (!PyArg_ParseTuple(args, ":shutdown_engine"))
|
|
return NULL;
|
|
|
|
GError *error = NULL;
|
|
if (!cbox_embed_shutdown_engine(&error))
|
|
return pyerror_from_gerror(PyExc_Exception, error);
|
|
Py_INCREF(Py_None);
|
|
return Py_None;
|
|
}
|
|
|
|
static PyObject *cbox_python_start_audio(PyObject *self, PyObject *args)
|
|
{
|
|
PyObject *callback = NULL;
|
|
if (!PyArg_ParseTuple(args, "|O:start_audio", &callback))
|
|
return NULL;
|
|
|
|
struct cbox_command_target target;
|
|
gboolean has_target = callback && callback != Py_None;
|
|
if (has_target)
|
|
cbox_command_target_init(&target, bridge_to_python_callback, callback);
|
|
|
|
GError *error = NULL;
|
|
if (!cbox_embed_start_audio(has_target ? &target : NULL, &error))
|
|
return pyerror_from_gerror(PyExc_Exception, error);
|
|
|
|
Py_INCREF(Py_None);
|
|
return Py_None;
|
|
}
|
|
|
|
static PyObject *cbox_python_start_noaudio(PyObject *self, PyObject *args)
|
|
{
|
|
PyObject *callback = NULL;
|
|
int sample_rate = 0;
|
|
if (!PyArg_ParseTuple(args, "i|O:start_noaudio", &sample_rate, &callback))
|
|
return NULL;
|
|
if (!engine_initialised)
|
|
return PyErr_Format(PyExc_Exception, "Engine not initialised");
|
|
if (audio_running)
|
|
return PyErr_Format(PyExc_Exception, "Audio already started");
|
|
|
|
struct cbox_command_target target;
|
|
if (callback && callback != Py_None)
|
|
cbox_command_target_init(&target, bridge_to_python_callback, callback);
|
|
|
|
cbox_rt_set_offline(app.rt, sample_rate, 1024);
|
|
cbox_scene_new(app.document, app.engine);
|
|
cbox_rt_start(app.rt, (callback && callback != Py_None) ? &target : NULL);
|
|
audio_running = TRUE;
|
|
|
|
Py_INCREF(Py_None);
|
|
return Py_None;
|
|
}
|
|
|
|
static PyObject *cbox_python_stop_audio(PyObject *self, PyObject *args)
|
|
{
|
|
if (!PyArg_ParseTuple(args, ":stop_audio"))
|
|
return NULL;
|
|
|
|
GError *error = NULL;
|
|
if (!cbox_embed_stop_audio(&error))
|
|
return pyerror_from_gerror(PyExc_Exception, error);
|
|
Py_INCREF(Py_None);
|
|
return Py_None;
|
|
}
|
|
|
|
#endif
|
|
|
|
static PyMethodDef CboxMethods[] = {
|
|
{"do_cmd", cbox_python_do_cmd, METH_VARARGS, "Execute a CalfBox command using a global path."},
|
|
#if CALFBOX_AS_MODULE
|
|
{"init_engine", cbox_python_init_engine, METH_VARARGS, "Initialise the CalfBox engine using optional config file."},
|
|
{"shutdown_engine", cbox_python_shutdown_engine, METH_VARARGS, "Shutdown the CalfBox engine."},
|
|
{"start_audio", cbox_python_start_audio, METH_VARARGS, "Start real-time audio processing using I/O settings from the current config."},
|
|
{"start_noaudio", cbox_python_start_noaudio, METH_VARARGS, "Start dummy audio processing using sample rate specified as argument."},
|
|
{"stop_audio", cbox_python_stop_audio, METH_VARARGS, "Stop real-time audio processing."},
|
|
#endif
|
|
{NULL, NULL, 0, NULL}
|
|
};
|
|
|
|
static PyModuleDef CboxModule = {
|
|
PyModuleDef_HEAD_INIT, "_cbox", NULL, -1, CboxMethods,
|
|
NULL, NULL, NULL, NULL
|
|
};
|
|
|
|
#if CALFBOX_AS_MODULE
|
|
|
|
static void cbox_python_atexit()
|
|
{
|
|
if (audio_running) {
|
|
cbox_rt_stop(app.rt);
|
|
cbox_io_close(&app.io);
|
|
audio_running = FALSE;
|
|
}
|
|
if (engine_initialised) {
|
|
cbox_tarpool_destroy(app.tarpool);
|
|
cbox_document_destroy(app.document);
|
|
cbox_wavebank_close();
|
|
cbox_config_close();
|
|
cbox_dom_close();
|
|
engine_initialised = FALSE;
|
|
}
|
|
}
|
|
|
|
PyMODINIT_FUNC
|
|
PyInit__cbox(void)
|
|
{
|
|
PyObject *m = PyModule_Create(&CboxModule);
|
|
if (!m)
|
|
return NULL;
|
|
Py_INCREF(&CboxCallbackType);
|
|
if (PyType_Ready(&CboxCallbackType) < 0)
|
|
return NULL;
|
|
PyModule_AddObject(m, "Callback", (PyObject *)&CboxCallbackType);
|
|
atexit(cbox_python_atexit);
|
|
|
|
return m;
|
|
}
|
|
|
|
#else
|
|
|
|
PyObject*
|
|
PyInit_cbox(void)
|
|
{
|
|
PyObject *m = PyModule_Create(&CboxModule);
|
|
PyModule_AddObject(m, "Callback", (PyObject *)&CboxCallbackType);
|
|
return m;
|
|
}
|
|
|
|
#endif
|
|
#endif
|
|
|