fazekasgy@31: #include fazekasgy@31: #include "PyExtensionModule.h" fazekasgy@31: #include "PyRealTime.h" fazekasgy@31: #include "PyFeature.h" fazekasgy@31: #include "PyFeatureSet.h" fazekasgy@31: #include "PyParameterDescriptor.h" fazekasgy@31: #include "PyOutputDescriptor.h" fazekasgy@31: #include "vamp-sdk/Plugin.h" fazekasgy@31: fazekasgy@31: using namespace std; fazekasgy@31: using namespace Vamp; fazekasgy@31: using Vamp::Plugin; fazekasgy@31: using Vamp::RealTime; fazekasgy@31: fazekasgy@31: /* Simple Example Function */ fazekasgy@31: fazekasgy@31: static int five=5; fazekasgy@31: fazekasgy@31: PyObject* fazekasgy@31: get_five(PyObject *self, PyObject *args) fazekasgy@31: { fazekasgy@31: if(!PyArg_ParseTuple(args, ":five")) return NULL; fazekasgy@31: return Py_BuildValue("i", five); fazekasgy@31: } fazekasgy@31: fazekasgy@31: fazekasgy@31: /* Functions Exposed by Vampy */ fazekasgy@31: fazekasgy@31: /* Creating PyRealTime Objects */ fazekasgy@31: fazekasgy@31: fazekasgy@31: /* New RealTime object from Frame (with given samplerate) */ fazekasgy@31: static PyObject * fazekasgy@31: RealTime_frame2RealTime(PyObject *ignored, PyObject *args) fazekasgy@31: { fazekasgy@31: long frame; fazekasgy@31: unsigned int sampleRate; fazekasgy@31: fazekasgy@31: if (!PyArg_ParseTuple(args, "lI:realtime.frame2RealTime ", fazekasgy@31: &frame,&sampleRate)) fazekasgy@31: return NULL; fazekasgy@31: fazekasgy@31: RealTimeObject *self; fazekasgy@31: self = PyObject_New(RealTimeObject, &RealTime_Type); fazekasgy@31: if (self == NULL) return NULL; fazekasgy@31: fazekasgy@31: self->rt = new RealTime::RealTime( fazekasgy@31: RealTime::frame2RealTime(frame,sampleRate)); fazekasgy@31: fazekasgy@31: return (PyObject *) self; fazekasgy@31: } fazekasgy@31: fazekasgy@31: /* Creating PyParameterDescriptor Objects */ fazekasgy@31: fazekasgy@31: /* New ParameterDescriptor object fazekasgy@31: static PyObject * fazekasgy@31: ParameterDescriptor_new(PyObject *ignored, PyObject *args) fazekasgy@31: { fazekasgy@31: fazekasgy@31: if (!PyArg_ParseTuple(args, ":ParameterDescriptor")) { fazekasgy@31: PyErr_SetString(PyExc_TypeError, fazekasgy@31: "Error: ParameterDescriptor initialised with arguments."); fazekasgy@31: return NULL; fazekasgy@31: } fazekasgy@31: fazekasgy@31: ParameterDescriptorObject *self = fazekasgy@31: PyObject_New(ParameterDescriptorObject, &ParameterDescriptor_Type); fazekasgy@31: if (self == NULL) return NULL; fazekasgy@31: self->dict = PyDict_New(); fazekasgy@31: if (self->dict == NULL) return NULL; fazekasgy@31: return (PyObject *) self; fazekasgy@31: } fazekasgy@31: */ fazekasgy@31: fazekasgy@31: /* Creating PyOutputDescriptor Objects */ fazekasgy@31: fazekasgy@31: /* New OutputDescriptor object fazekasgy@31: static PyObject * fazekasgy@31: OutputDescriptor_new(PyObject *ignored, PyObject *args) fazekasgy@31: { fazekasgy@31: fazekasgy@31: if (!PyArg_ParseTuple(args, ":OutputDescriptor")) { fazekasgy@31: PyErr_SetString(PyExc_TypeError, fazekasgy@31: "Error: OutputDescriptor initialised with arguments."); fazekasgy@31: return NULL; fazekasgy@31: } fazekasgy@31: fazekasgy@31: OutputDescriptorObject *self = fazekasgy@31: PyObject_New(OutputDescriptorObject, &OutputDescriptor_Type); fazekasgy@31: if (self == NULL) return NULL; fazekasgy@31: self->dict = PyDict_New(); fazekasgy@31: if (self->dict == NULL) return NULL; fazekasgy@31: return (PyObject *) self; fazekasgy@31: } fazekasgy@31: */ fazekasgy@31: fazekasgy@31: /* Creating PyOutputList Objects */ fazekasgy@31: fazekasgy@31: /* New OutputList object */ fazekasgy@31: static PyObject * fazekasgy@31: OutputList_new(PyObject *ignored, PyObject *args) fazekasgy@31: { fazekasgy@31: if (!PyArg_ParseTuple(args, ":OutputList")) { fazekasgy@31: PyErr_SetString(PyExc_TypeError, fazekasgy@31: "Error: OutputList initialised with arguments."); fazekasgy@31: return NULL; fazekasgy@31: } fazekasgy@31: fazekasgy@31: return (PyObject *) PyList_New(0); fazekasgy@31: } fazekasgy@31: fazekasgy@31: fazekasgy@31: /* Creating PyParameterList Objects */ fazekasgy@31: fazekasgy@31: /* New ParameterList object */ fazekasgy@31: static PyObject * fazekasgy@31: ParameterList_new(PyObject *ignored, PyObject *args) fazekasgy@31: { fazekasgy@31: if (!PyArg_ParseTuple(args, ":ParameterList")) { fazekasgy@31: PyErr_SetString(PyExc_TypeError, fazekasgy@31: "Error: ParameterList initialised with arguments."); fazekasgy@31: return NULL; fazekasgy@31: } fazekasgy@31: return (PyObject *) PyList_New(0); fazekasgy@31: } fazekasgy@31: fazekasgy@31: fazekasgy@31: /* Creating PyFeatureList Objects */ fazekasgy@31: fazekasgy@31: /* New FeatureList object fazekasgy@31: static PyObject * fazekasgy@31: FeatureList_new(PyObject *ignored, PyObject *args) fazekasgy@31: { fazekasgy@31: if (!PyArg_ParseTuple(args, ":FeatureList")) { fazekasgy@31: PyErr_SetString(PyExc_TypeError, fazekasgy@31: "Error: FeatureList initialised with arguments."); fazekasgy@31: return NULL; fazekasgy@31: } fazekasgy@31: return (PyObject *) PyList_New(0); fazekasgy@31: } fazekasgy@31: */ fazekasgy@31: fazekasgy@31: /* Creating PyFeatureSet Objects */ fazekasgy@31: fazekasgy@31: /* New FeatureSet object fazekasgy@31: static PyObject * fazekasgy@31: FeatureSet_new(PyObject *ignored, PyObject *args) fazekasgy@31: { fazekasgy@31: if (!PyArg_ParseTuple(args, ":FeatureSet")) { fazekasgy@31: PyErr_SetString(PyExc_TypeError, fazekasgy@31: "Error: FeatureSet initialised with arguments."); fazekasgy@31: return NULL; fazekasgy@31: } fazekasgy@31: return (PyObject *) PyDict_New(); fazekasgy@31: } fazekasgy@31: */ fazekasgy@31: fazekasgy@31: fazekasgy@31: /* Declare the methods exposed by the vampy module */ fazekasgy@31: fazekasgy@31: fazekasgy@31: PyMethodDef VampyMethods[] = { fazekasgy@31: /*NOTE: This is conventionally static, but limiting the scope fazekasgy@31: here will cause seg fault if the declared functions are fazekasgy@31: called back from a Python function wrapped in a C++ class.*/ fazekasgy@31: {"five", get_five, METH_VARARGS, "Return a number."}, fazekasgy@31: fazekasgy@31: {"frame2RealTime", (PyCFunction)RealTime_frame2RealTime, METH_VARARGS, fazekasgy@31: PyDoc_STR("frame2RealTime((int64)frame, (uint32)sampleRate ) -> returns new RealTime object from frame.")}, fazekasgy@31: fazekasgy@31: /*{"RealTime", RealTime_new, METH_VARARGS, fazekasgy@31: PyDoc_STR("RealTime() -> returns new RealTime object")},*/ fazekasgy@31: fazekasgy@31: /*{"Feature", Feature_new, METH_VARARGS, fazekasgy@31: PyDoc_STR("Feature() -> returns new Feature object")},*/ fazekasgy@31: fazekasgy@31: /*{"ParameterDescriptor", ParameterDescriptor_new, METH_VARARGS, fazekasgy@31: PyDoc_STR("ParameterDescriptor() -> returns new ParameterDescriptor object")}, fazekasgy@31: fazekasgy@31: {"OutputDescriptor", OutputDescriptor_new, METH_VARARGS, fazekasgy@31: PyDoc_STR("OutputDescriptor() -> returns new OutputDescriptor object")}, fazekasgy@31: fazekasgy@31: {"FeatureList", FeatureList_new, METH_VARARGS, fazekasgy@31: PyDoc_STR("FeatureList() -> returns new FeatureList object")},*/ fazekasgy@31: fazekasgy@31: {"OutputList", OutputList_new, METH_VARARGS, fazekasgy@31: PyDoc_STR("OutputList() -> returns new OutputList object")}, fazekasgy@31: fazekasgy@31: {"ParameterList", ParameterList_new, METH_VARARGS, fazekasgy@31: PyDoc_STR("ParameterList() -> returns new ParameterList object")}, fazekasgy@31: fazekasgy@31: {NULL, NULL, 0, NULL} fazekasgy@31: }; fazekasgy@31: fazekasgy@31: /* Module Documentation */ fazekasgy@31: // PyDoc_STRVAR(vampy_doc,"This module exposes Vamp plugin data type wrappers."); fazekasgy@31: fazekasgy@31: PyMODINIT_FUNC fazekasgy@31: initvampy(void) fazekasgy@31: { fazekasgy@31: PyObject* module; fazekasgy@31: fazekasgy@31: // if (PyType_Ready(&Feature_Type) < 0) return; fazekasgy@31: /// Why do we get a segfault if this is initialised here? fazekasgy@31: /*PyType_Ready adds these object to the GC. fazekasgy@31: This is OK for an extension module, but it is a mistake here, fazekasgy@31: because the reference count will be decremented in the Vamp fazekasgy@31: wrapper plugin outside the interpreter. fazekasgy@31: When the GC tries to visit a deallocated object, it will throw up.*/ fazekasgy@31: fazekasgy@31: RealTime_Type.ob_type = &PyType_Type; fazekasgy@31: Feature_Type.ob_type = &PyType_Type; fazekasgy@31: OutputDescriptor_Type.ob_type = &PyType_Type; fazekasgy@31: ParameterDescriptor_Type.ob_type = &PyType_Type; fazekasgy@31: initFeatureSetType(); /// this is derived from the builtin dict fazekasgy@31: fazekasgy@31: PyImport_AddModule("vampy"); fazekasgy@31: module = Py_InitModule("vampy", VampyMethods); fazekasgy@31: if (!module) return; fazekasgy@31: fazekasgy@31: Py_INCREF(&RealTime_Type); fazekasgy@31: PyModule_AddObject(module,"RealTime",(PyObject*)&RealTime_Type); fazekasgy@31: // Py_INCREF(&RealTime_Type); fazekasgy@31: fazekasgy@31: Py_INCREF((PyObject*)&Feature_Type); fazekasgy@31: PyModule_AddObject(module,"Feature",(PyObject*)&Feature_Type); fazekasgy@31: // Py_INCREF((PyObject*)&Feature_Type); fazekasgy@31: fazekasgy@31: Py_INCREF((PyObject*)&FeatureSet_Type); fazekasgy@31: PyModule_AddObject(module,"FeatureSet",(PyObject*)&FeatureSet_Type); fazekasgy@31: // Py_INCREF((PyObject*)&FeatureSet_Type); fazekasgy@31: fazekasgy@31: Py_INCREF((PyObject*)&OutputDescriptor_Type); fazekasgy@31: PyModule_AddObject(module,"OutputDescriptor",(PyObject*)&OutputDescriptor_Type); fazekasgy@31: fazekasgy@31: Py_INCREF((PyObject*)&ParameterDescriptor_Type); fazekasgy@31: PyModule_AddObject(module,"ParameterDescriptor",(PyObject*)&ParameterDescriptor_Type); fazekasgy@31: fazekasgy@31: cerr << "Vampy: extension module initialised." << endl; fazekasgy@31: } fazekasgy@31: fazekasgy@31: /* fazekasgy@31: NOTE: Why do we need to clean up the module? fazekasgy@31: fazekasgy@31: The module exposed by Vampy to the embedded interpreter fazekasgy@31: contains callback functions. These functions are accessed fazekasgy@31: via function pointers stored in the extension module dictionary. fazekasgy@31: fazekasgy@31: Unfortunately, when the Vampy shared library is unloaded and fazekasgy@31: reloaded again during a host session, these addresses might fazekasgy@31: change. Therefore, we reinitialise the module dict before fazekasgy@31: each use. However, this will cause garbage collection errors fazekasgy@31: or segmentation faults, when elements of the dict of the fazekasgy@31: previous session are attempted to free. Therefore, we hold fazekasgy@31: a global reference count to all initialised Vampy plugins, fazekasgy@31: and when this reaches zero, we clean up the module dict. fazekasgy@31: fazekasgy@31: This is an attempt to catch the moment when the shared lib fazekasgy@31: is finally unloaded and the references are still point to valid fazekasgy@31: memory addresses. fazekasgy@31: fazekasgy@31: Why doesn't the GC clean this up correctly? fazekasgy@31: fazekasgy@31: In a normal Python session the GC would deallocate the module fazekasgy@31: dict at the end. In embedded python, although the GC appears fazekasgy@31: to be called when the shared lib is unloaded, the interpreter fazekasgy@31: is reused. Since there is no C/API call to unload modules, fazekasgy@31: and at the time of unloading vampy the wrapped function pointers fazekasgy@31: are still valid, the GC doesn't collect them, nor are they freed fazekasgy@31: by the interpreter. When vampy is reloaded however, the module fazekasgy@31: dict will contain invalid addresses. The above procedure solves fazekasgy@31: this problem. fazekasgy@31: fazekasgy@31: fazekasgy@31: */ fazekasgy@31: fazekasgy@31: void cleanModule(void) fazekasgy@31: { fazekasgy@31: PyObject *m = PyImport_AddModule("vampy"); fazekasgy@31: if (!m) cerr << "Destr: PyImport_AddModule returned NULL!" << endl; fazekasgy@31: else { fazekasgy@31: // cerr << "Destr: Add module found existing." << endl; fazekasgy@31: PyObject *dict = PyModule_GetDict(m); fazekasgy@31: Py_ssize_t ln = PyDict_Size(dict); fazekasgy@31: cerr << "Destr: Size of module dict = " << (int) ln << endl; fazekasgy@31: /// Clean the module dictionary. fazekasgy@31: PyDict_Clear(dict); fazekasgy@31: ln = PyDict_Size(dict); fazekasgy@31: cerr << "Destr: Cleaned size of module dict = " << (int) ln << endl; fazekasgy@31: } fazekasgy@31: } fazekasgy@31: