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

/*
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, &params, 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