Mercurial > hg > vampy
diff PyPlugin.cpp @ 0:e20e214bdfb5
Added VAMP-Python binding project vampy
author | fazekasgy |
---|---|
date | Tue, 11 Mar 2008 19:47:34 +0000 |
parents | |
children | 134313c59d82 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/PyPlugin.cpp Tue Mar 11 19:47:34 2008 +0000 @@ -0,0 +1,985 @@ +/* + Vamp + + An API for audio analysis and feature extraction plugins. + + Centre for Digital Music, Queen Mary, University of London. + Copyright 2006 Chris Cannam. + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, copy, + modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR + ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF + CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + Except as contained in this notice, the names of the Centre for + Digital Music; Queen Mary, University of London; and Chris Cannam + shall not be used in advertising or otherwise to promote the sale, + use or other dealings in this Software without prior written + authorization. +*/ + + + +/** + * This VAMP plugin is a wrapper for Python Scripts. (vampy) + * Centre for Digital Music, Queen Mary, University of London. + * Copyright 2008, George Fazekas. + +TODO: needs more complete error checking + needs correct implementation of Python threading + more efficient data conversion using the buffering interface or ctypes + VAMP programs not implemented + support multiple plugins per script in scanner + ensure proper cleanup, host do a good job though + +*/ + +//#include "Python.h" +#include "/usr/include/python/Python.h" +#include "PyPlugin.h" + +#ifdef _WIN32 +#define pathsep ('\\') +#else +#define pathsep ('/') +#endif + +#define _DEBUG + +using std::string; +using std::vector; +using std::cerr; +using std::endl; +using std::map; + +extern volatile bool mutex; + +// Maps to associate strings with enum values +static std::map<std::string, eOutDescriptors> outKeys; +static std::map<std::string, eSampleTypes> sampleKeys; +static std::map<std::string, eFeatureFields> ffKeys; +static std::map<std::string, p::eParmDescriptors> parmKeys; + +void initMaps() +{ + outKeys["identifier"] = identifier; + outKeys["name"] = name; + outKeys["description"] = description; + outKeys["unit"] = unit; + outKeys["hasFixedBinCount"] = hasFixedBinCount; + outKeys["binCount"] = binCount; + outKeys["binNames"] = binNames; + outKeys["hasKnownExtents"] = hasKnownExtents; + outKeys["minValue"] = minValue; + outKeys["maxValue"] = maxValue; + outKeys["isQuantized"] = isQuantized; + outKeys["quantizeStep"] = quantizeStep; + outKeys["sampleType"] = sampleType; + outKeys["sampleRate"] = sampleRate; + + sampleKeys["OneSamplePerStep"] = OneSamplePerStep; + sampleKeys["FixedSampleRate"] = FixedSampleRate; + sampleKeys["VariableSampleRate"] = VariableSampleRate; + + ffKeys["hasTimestamp"] = hasTimestamp; + ffKeys["timeStamp"] = timeStamp; + ffKeys["values"] = values; + ffKeys["label"] = label; + + parmKeys["identifier"] = p::identifier; + parmKeys["name"] = p::name; + parmKeys["description"] = p::description; + parmKeys["unit"] = p::unit; + parmKeys["minValue"] = p::minValue; + parmKeys["maxValue"] = p::maxValue; + parmKeys["defaultValue"] = p::defaultValue; + parmKeys["isQuantized"] = p::isQuantized; + +} + + +//missing API helper: convert Python list to C++ vector of strings +//TODO: these could be templates if we need more of this kind +std::vector<std::string> PyList_To_StringVector (PyObject *inputList) { + + std::vector<std::string> Output; + std::string ListElement; + PyObject *pyString = NULL; + + if (!PyList_Check(inputList)) return Output; + + for (Py_ssize_t i = 0; i < PyList_GET_SIZE(inputList); ++i) { + //Get next list item (Borrowed Reference) + pyString = PyList_GET_ITEM(inputList,i); + ListElement = (string) PyString_AsString(PyObject_Str(pyString)); + Output.push_back(ListElement); + } + return Output; +} + +//missing API helper: convert Python list to C++ vector of floats +std::vector<float> PyList_As_FloatVector (PyObject *inputList) { + + std::vector<float> Output; + float ListElement; + PyObject *pyFloat = NULL; + + if (!PyList_Check(inputList)) return Output; + + for (Py_ssize_t k = 0; k < PyList_GET_SIZE(inputList); ++k) { + //Get next list item (Borrowed Reference) + pyFloat = PyList_GET_ITEM(inputList,k); + ListElement = (float) PyFloat_AS_DOUBLE(pyFloat); + Output.push_back(ListElement); + } + + return Output; +} + +/* TODO: find out why this produces error, also + do sg more clever about handling RealTime +Vamp::RealTime +PyFrame_As_RealTime (PyObject *frameNo,size_t inputSampleRate) { +Vamp::RealTime result = +Vamp::RealTime::frame2RealTime((size_t)PyInt_AS_LONG(frameNo), inputSampleRate); +return result; +} +*/ + + +PyPlugin::PyPlugin(std::string pluginKey,float inputSampleRate, PyObject *pyInstance) : + Plugin(inputSampleRate), + m_pyInstance(pyInstance), + m_stepSize(0), + m_previousSample(0.0f), + m_plugin(pluginKey), + m_class(pluginKey.substr(pluginKey.rfind(':')+1,pluginKey.size()-1)), + m_path((pluginKey.substr(0,pluginKey.rfind(pathsep)))) +{ + +/*TODO: this is a nasty way of ensuring the process is +finished before we create a new instance accessing the Python/C API. +The Python/C API is not fully thread safe. +Calling into a python class while the process is doing heavy +computation may cause segmentation fault. +Manipulating the GIL and thread states only gave me a grief so far.*/ + + if (mutex) { + cerr << "PyPlugin::PyPlugin:" << m_class + << " Warning: Waiting for clear signal from parallel process." << endl; + while (mutex) { } + } +} + +PyPlugin::~PyPlugin() +{ + cerr << "PyPlugin::PyPlugin:" << m_class + << " Instance deleted." << endl; + //for safety only. has been cleared after process. + mutex = false; +} + + +string +PyPlugin::getIdentifier() const +{ + char method[]="getIdentifier"; + cerr << "[call] " << method << endl; + string rString="VampPy-x"; + + if ( PyObject_HasAttrString(m_pyInstance,method) ) { + + //Call the method + PyObject *pyString = + PyObject_CallMethod(m_pyInstance, method, NULL); + + //Check return value + if (!PyString_Check(pyString)) { + Py_CLEAR(pyString); + cerr << "ERROR: In Python plugin [" << m_class << "::" << method + << "] Expected String return value." << endl; + return rString; + } + + rString=PyString_AsString(pyString); + Py_CLEAR(pyString); + } + cerr << "Warning: Plugin must return a unique identifier." << endl; + return rString; +} + + +string +PyPlugin::getName() const +{ + + char method[]="getName"; + cerr << "[call] " << method << endl; + string rString="VamPy Plugin (Noname)"; + + if ( PyObject_HasAttrString(m_pyInstance,method) ) { + + //Call the method + PyObject *pyString = + PyObject_CallMethod(m_pyInstance, method, NULL); + + //Check return value + if (!PyString_Check(pyString)) { + Py_CLEAR(pyString); + cerr << "ERROR: In Python plugin [" << m_class << "::" << method + << "] Expected String return value." << endl; + return rString; + } + + rString=PyString_AsString(pyString); + Py_CLEAR(pyString); + } + return rString; +} + +string +PyPlugin::getDescription() const +{ + char method[]="getDescription"; + cerr << "[call] " << method << endl; + string rString="Not given. (Hint: Implement getDescription method.)"; + + if ( PyObject_HasAttrString(m_pyInstance,method) ) { + + //Call the method + PyObject *pyString = + PyObject_CallMethod(m_pyInstance, method, NULL); + + //Check return value + if (!PyString_Check(pyString)) { + Py_CLEAR(pyString); + cerr << "ERROR: In Python plugin [" << m_class << "::" << method + << "] Expected String return value." << endl; + return rString; + } + + rString=PyString_AsString(pyString); + Py_CLEAR(pyString); + } + return rString; +} + +string +PyPlugin::getMaker() const +{ + char method[]="getMaker"; + cerr << "[call] " << method << endl; + string rString="Generic VamPy Plugin."; + + if ( PyObject_HasAttrString(m_pyInstance,method) ) { + + //Call the method + PyObject *pyString = + PyObject_CallMethod(m_pyInstance, method, NULL); + + //Check return value + if (!PyString_Check(pyString)) { + Py_CLEAR(pyString); + cerr << "ERROR: In Python plugin [" << m_class << "::" << method + << "] Expected String return value." << endl; + return rString; + } + + rString=PyString_AsString(pyString); + Py_CLEAR(pyString); + } + return rString; +} + +int +PyPlugin::getPluginVersion() const +{ + return 2; +} + +string +PyPlugin::getCopyright() const +{ + char method[]="getCopyright"; + cerr << "[call] " << method << endl; + string rString="BSD License"; + + if ( PyObject_HasAttrString(m_pyInstance,method) ) { + + //Call the method + PyObject *pyString = + PyObject_CallMethod(m_pyInstance, method, NULL); + + //Check return value + if (!PyString_Check(pyString)) { + Py_CLEAR(pyString); + cerr << "ERROR: In Python plugin [" << m_class << "::" << method + << "] Expected String return value." << endl; + return rString; + } + + + rString=PyString_AsString(pyString); + Py_CLEAR(pyString); + } + return rString; +} + + +bool +PyPlugin::initialise(size_t channels, size_t stepSize, size_t blockSize) +{ + if (channels < getMinChannelCount() || + channels > getMaxChannelCount()) return false; + + m_stepSize = std::min(stepSize, blockSize); + char method[]="initialise"; + cerr << "[call] " << method << endl; + + //Check if the method is implemented in Python else return false + if (PyObject_HasAttrString(m_pyInstance,method)) { + + PyObject *pyMethod = PyString_FromString(method); + PyObject *pyChannels = PyInt_FromSsize_t((Py_ssize_t)channels); + PyObject *pyStepSize = PyInt_FromSsize_t((Py_ssize_t)m_stepSize); + PyObject *pyBlockSize = PyInt_FromSsize_t((Py_ssize_t)blockSize); + PyObject *pyInputSampleRate = PyFloat_FromDouble((double)m_inputSampleRate); + + //Call the method + PyObject *pyBool = + PyObject_CallMethodObjArgs(m_pyInstance,pyMethod,pyChannels,pyStepSize,pyBlockSize,pyInputSampleRate,NULL); + + Py_DECREF(pyMethod); + Py_DECREF(pyChannels); + Py_DECREF(pyStepSize); + Py_DECREF(pyBlockSize); + Py_DECREF(pyInputSampleRate); + + //Check return value + if (!PyBool_Check(pyBool)) { + Py_CLEAR(pyBool); + cerr << "ERROR: In Python plugin [" << m_class << "::" << method + << "] Expected Bool return value." << endl; + return false; + } + + if (pyBool == Py_True) { + Py_CLEAR(pyBool); + return true; + } else { + Py_CLEAR(pyBool); + return false;} + } + return true; +} + +void +PyPlugin::reset() +{ + m_previousSample = 0.0f; +} + +PyPlugin::InputDomain PyPlugin::getInputDomain() const +{ + char method[]="getInputDomain"; + cerr << "[call] " << method << endl; + PyPlugin::InputDomain rValue = TimeDomain; // TimeDomain + + if ( PyObject_HasAttrString(m_pyInstance,method) ) { + + PyObject *pyString = PyObject_CallMethod(m_pyInstance, method, NULL); + + //Check return value + if (!PyString_Check(pyString)) { + Py_CLEAR(pyString); + cerr << "ERROR: In Python plugin [" << m_class << "::" << method + << "] Expected String return value." << endl; + return rValue; + } + + string domain = (string) PyString_AsString(pyString); + if (domain == "FrequencyDomain") rValue = FrequencyDomain; + Py_CLEAR(pyString); + } + return rValue; +} + +size_t PyPlugin::getPreferredBlockSize() const +{ + char method[]="getPreferredBlockSize"; + cerr << "[call] " << method << endl; + size_t rValue=0; //not set by default + if ( PyObject_HasAttrString(m_pyInstance,method) ) { + PyObject *pyInt = PyObject_CallMethod(m_pyInstance, method, NULL); + + //Check return value + if (!PyInt_Check(pyInt)) { + Py_CLEAR(pyInt); + cerr << "ERROR: In Python plugin [" << m_class << "::" << method + << "] Expected Integer return value." << endl; + return rValue; + } + + rValue=(size_t)PyInt_AS_LONG(pyInt); + Py_CLEAR(pyInt); + } + return rValue; +} + +//size_t PyPlugin::getPreferredStepSize() const { return 0; } +size_t PyPlugin::getPreferredStepSize() const +{ + char method[]="getPreferredStepSize"; + cerr << "[call] " << method << endl; + size_t rValue=0; //not set by default + if ( PyObject_HasAttrString(m_pyInstance,method) ) { + PyObject *pyInt = PyObject_CallMethod(m_pyInstance, method, NULL); + + //Check return value + if (!PyInt_Check(pyInt)) { + Py_CLEAR(pyInt); + cerr << "ERROR: In Python plugin [" << m_class << "::" << method + << "] Expected Integer return value." << endl; + return rValue; + } + + rValue=(size_t)PyInt_AS_LONG(pyInt); + Py_CLEAR(pyInt); + } + return rValue; +} + +size_t PyPlugin::getMinChannelCount() const +{ + char method[]="getMinChannelCount"; + cerr << "[call] " << method << endl; + size_t rValue=1; //default value + if ( PyObject_HasAttrString(m_pyInstance,method) ) { + PyObject *pyInt = PyObject_CallMethod(m_pyInstance, method, NULL); + + //Check return value + if (!PyInt_Check(pyInt)) { + Py_CLEAR(pyInt); + cerr << "ERROR: In Python plugin [" << m_class << "::" << method + << "] Expected String return value." << endl; + return rValue; + } + + rValue=(size_t)PyInt_AS_LONG(pyInt); + Py_CLEAR(pyInt); + } + return rValue; +} + +size_t PyPlugin::getMaxChannelCount() const +{ + char method[]="getMaxChannelCount"; + cerr << "[call] " << method << endl; + size_t rValue=1; //default value + if ( PyObject_HasAttrString(m_pyInstance,method) ) { + PyObject *pyInt = PyObject_CallMethod(m_pyInstance, method, NULL); + + //Check return value + if (!PyInt_Check(pyInt)) { + Py_CLEAR(pyInt); + cerr << "ERROR: In Python plugin [" << m_class << "::" << method + << "] Expected String return value." << endl; + return rValue; + } + + rValue=(size_t)PyInt_AS_LONG(pyInt); + Py_CLEAR(pyInt); + } + return rValue; +} + + +PyPlugin::OutputList +PyPlugin::getOutputDescriptors() const +{ + //PyEval_AcquireThread(newThreadState); + OutputList list; + OutputDescriptor od; + char method[]="getOutputDescriptors"; + cerr << "[call] " << method << endl; + + //Check if the method is implemented in Python + if ( PyObject_HasAttrString(m_pyInstance,method) ) { + + //Call the method: must return list object (new reference) + PyObject *pyList = + PyObject_CallMethod(m_pyInstance,method, NULL); + + //Check return type + if (! PyList_Check(pyList) ) { + Py_CLEAR(pyList); + cerr << "ERROR: In Python plugin [" << m_class << "::" << method + << "] Expected List return type." << endl; + return list; + } + + //These will all be borrowed references (no need to DECREF) + PyObject *pyDict, *pyKey, *pyValue; + + //Parse Output List + for (Py_ssize_t i = 0; i < PyList_GET_SIZE(pyList); ++i) { + + //Get i-th VAMP output descriptor (Borrowed Reference) + pyDict = PyList_GET_ITEM(pyList,i); + + //We only care about dictionaries holding output descriptors + if ( !PyDict_Check(pyDict) ) continue; + + Py_ssize_t pyPos = NULL; + initMaps(); + + //Python Sequence Iterator + while (PyDict_Next(pyDict, &pyPos, &pyKey, &pyValue)) + { + switch (outKeys[PyString_AsString(pyKey)]) + { + case not_found : + cerr << "Unknown key in VAMP OutputDescriptor: " << PyString_AsString(pyKey) << endl; + break; + case identifier: + od.identifier = PyString_AsString(pyValue); + break; + case name: + od.name = PyString_AsString(pyValue); + break; + case description: + od.description = PyString_AsString(pyValue); + break; + case unit: + od.unit = PyString_AsString(pyValue); + break; + case hasFixedBinCount: + od.hasFixedBinCount = (bool) PyInt_AS_LONG(pyValue); + break; + case binCount: + od.binCount = (size_t) PyInt_AS_LONG(pyValue); + break; + case binNames: + od.binNames = PyList_To_StringVector(pyValue); + break; + case hasKnownExtents: + od.hasKnownExtents = (bool) PyInt_AS_LONG(pyValue); + break; + case minValue: + od.minValue = (float) PyFloat_AS_DOUBLE(pyValue); + break; + case maxValue: + od.maxValue = (float) PyFloat_AS_DOUBLE(pyValue); + break; + case isQuantized: + od.isQuantized = (bool) PyInt_AS_LONG(pyValue); + break; + case quantizeStep: + od.quantizeStep = (float) PyFloat_AS_DOUBLE(pyValue); + break; + case sampleType: + od.sampleType = (OutputDescriptor::SampleType) sampleKeys[PyString_AsString(pyValue)]; + break; + case sampleRate: + od.sampleRate = (float) PyFloat_AS_DOUBLE(pyValue); + break; + default : + cerr << "Invalid key in VAMP OutputDescriptor: " << PyString_AsString(pyKey) << endl; + } // switch + } // while dict keys + list.push_back(od); + } // for each memeber in outputlist + Py_CLEAR(pyList); + } // if method is implemented + //PyEval_ReleaseThread(newThreadState); + return list; +} + +PyPlugin::ParameterList +PyPlugin::getParameterDescriptors() const +{ + ParameterList list; + ParameterDescriptor pd; + char method[]="getParameterDescriptors"; + cerr << "[call] " << method << endl; + + //Check if the method is implemented in Python + if ( PyObject_HasAttrString(m_pyInstance,method) ) { + + //Call the method: must return list object (new reference) + PyObject *pyList = + PyObject_CallMethod(m_pyInstance,method, NULL); + + //Check return type + if (! PyList_Check(pyList) ) { + Py_CLEAR(pyList); + cerr << "ERROR: In Python plugin [" << m_class << "::" << method + << "] Expected List return type." << endl; + return list; + } + + + //These will all be borrowed references (no need to DECREF) + PyObject *pyDict, *pyKey, *pyValue; + + //Parse Output List + for (Py_ssize_t i = 0; i < PyList_GET_SIZE(pyList); ++i) { + + //Get i-th VAMP output descriptor (Borrowed Reference) + pyDict = PyList_GET_ITEM(pyList,i); + + //We only care about dictionaries holding output descriptors + if ( !PyDict_Check(pyDict) ) continue; + + Py_ssize_t pyPos = NULL; + initMaps(); + + //Python Sequence Iterator + while (PyDict_Next(pyDict, &pyPos, &pyKey, &pyValue)) + { + switch (parmKeys[PyString_AsString(pyKey)]) + { + case not_found : + cerr << "Unknown key in VAMP OutputDescriptor: " << PyString_AsString(pyKey) << endl; + break; + case p::identifier: + pd.identifier = PyString_AsString(pyValue); + break; + case p::name: + pd.name = PyString_AsString(pyValue); + break; + case p::description: + pd.description = PyString_AsString(pyValue); + break; + case p::unit: + pd.unit = PyString_AsString(pyValue); + break; + case p::minValue: + pd.minValue = (float) PyFloat_AS_DOUBLE(pyValue); + break; + case p::maxValue: + pd.maxValue = (float) PyFloat_AS_DOUBLE(pyValue); + break; + case p::defaultValue: + pd.defaultValue = (float) PyFloat_AS_DOUBLE(pyValue); + break; + case p::isQuantized: + pd.isQuantized = (bool) PyInt_AS_LONG(pyValue); + break; + default : + cerr << "Invalid key in VAMP OutputDescriptor: " << PyString_AsString(pyKey) << endl; + } // switch + } // while dict keys + list.push_back(pd); + } // for each memeber in outputlist + Py_CLEAR(pyList); + } // if + return list; +} + +void PyPlugin::setParameter(std::string paramid, float newval) +{ + char method[]="setParameter"; + cerr << "[call] " << method << endl; + + //Check if the method is implemented in Python + if (PyObject_HasAttrString(m_pyInstance,method)) { + + PyObject *pyMethod = PyString_FromString(method); + PyObject *pyParamid = PyString_FromString(paramid.c_str()); + PyObject *pyNewval = PyFloat_FromDouble((double)newval); + + //Call the method + PyObject *pyBool = + PyObject_CallMethodObjArgs(m_pyInstance,pyMethod,pyParamid,pyNewval,NULL); + + //This only happens if there is a syntax error or so + if (pyBool == NULL) { + cerr << "ERROR: In Python plugin [" << m_class << "::" << method + << "] Error setting parameter: " << paramid << endl; + if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } + } + + Py_DECREF(pyMethod); + Py_DECREF(pyParamid); + Py_DECREF(pyNewval); + } +} + +float PyPlugin::getParameter(std::string paramid) const +{ + char method[]="getParameter"; + cerr << "[call] " << method << endl; + float rValue = 0.0f; + + //Check if the method is implemented in Python + if (PyObject_HasAttrString(m_pyInstance,method)) { + + PyObject *pyMethod = PyString_FromString(method); + PyObject *pyParamid = PyString_FromString(paramid.c_str()); + + //Call the method + PyObject *pyFloat = + PyObject_CallMethodObjArgs(m_pyInstance,pyMethod,pyParamid,NULL); + + //Check return type + if (! PyFloat_Check(pyFloat) ) { + cerr << "ERROR: In Python plugin [" << m_class << "::" << method + << "] Expected Float return type." << endl; + if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } + Py_CLEAR(pyFloat); + return rValue; + } + + rValue = (float) PyFloat_AS_DOUBLE(pyFloat); + + Py_DECREF(pyMethod); + Py_DECREF(pyParamid); + Py_DECREF(pyFloat); + } + + return rValue; +} + +#ifdef _DEBUG +static int proccounter = 0; +#endif + +PyPlugin::FeatureSet +PyPlugin::process(const float *const *inputBuffers, + Vamp::RealTime timestamp) +{ + if (m_stepSize == 0) { + cerr << "ERROR: PyPlugin::process: " + << "Plugin has not been initialised" << endl; + return FeatureSet(); + } + mutex = true; + static char method[]="process"; + +#ifdef _DEBUG + cerr << "[call] " << method << " frame:" << proccounter << endl; + proccounter++; + //cerr << "C: proc..." << proccounter << " " << endl; +#endif + + //proces::Check if the method is implemented in Python + if ( PyObject_HasAttrString(m_pyInstance,method) ) + { + + //Pack method name into Python Object + PyObject *pyMethod = PyString_FromString(method); + + //Declare new list object + PyObject *pyFloat, *pyList; + pyList = PyList_New((Py_ssize_t) m_stepSize); + + //Pack samples into a Python List Object + //pyFloat will always be new references, + //these will be discarded when the list is deallocated + for (size_t i = 0; i < m_stepSize; ++i) { + pyFloat=PyFloat_FromDouble((double) inputBuffers[0][i]); + PyList_SET_ITEM(pyList, (Py_ssize_t) i, pyFloat); + } + + //Call python process (returns new reference) + PyObject *pyOutputList = + PyObject_CallMethodObjArgs(m_pyInstance,pyMethod,pyList,NULL); + + //Check return type + if (pyOutputList == NULL || !PyList_Check(pyOutputList) ) { + if (pyOutputList == NULL) { + cerr << "ERROR: In Python plugin [" << m_class << "::" << method + << "] Unexpected result." << endl; + if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } + } else { + cerr << "ERROR: In Python plugin [" << m_class << "::" << method + << "] Expected List return type." << endl; + } + Py_CLEAR(pyMethod); + Py_CLEAR(pyOutputList); + mutex = false; + return FeatureSet(); + } + + Py_DECREF(pyMethod); + // Py_DECREF(pyList); + // This appears to be tracked by the cyclic garbage collector + // hence decrefing produces GC error +#ifdef _DEBUG + cerr << "Process Returned Features" << endl; +#endif + // These will ALL be borrowed references + PyObject *pyFeatureList, *pyDict, *pyKey, *pyValue; + + FeatureSet returnFeatures; + + //Parse Output List for each element (FeatureSet) + for (Py_ssize_t i = 0; + i < PyList_GET_SIZE(pyOutputList); ++i) { + //cerr << "output (FeatureSet): " << i << endl; + + //Get i-th FeatureList (Borrowed Reference) + pyFeatureList = PyList_GET_ITEM(pyOutputList,i); + + //Parse FeatureList for each element (Feature) + for (Py_ssize_t j = 0; j < PyList_GET_SIZE(pyFeatureList); ++j) { + //cerr << "element (FeatureList): " << j << endl; + + //Get j-th Feature (Borrowed Reference) + pyDict = PyList_GET_ITEM(pyFeatureList,j); + + //We only care about dictionaries holding a Feature struct + if ( !PyDict_Check(pyDict) ) continue; + + Py_ssize_t pyPos = NULL; + bool emptyFeature = true; + Feature feature; + + //process::Python Sequence Iterator for dictionary + while (PyDict_Next(pyDict, &pyPos, &pyKey, &pyValue)) + { + emptyFeature = false; + switch (ffKeys[PyString_AsString(pyKey)]) + { + case not_found : + cerr << "Unknown key in VAMP FeatureSet: " + << PyString_AsString(pyKey) << endl; + break; + case hasTimestamp: + feature.hasTimestamp = (bool) PyInt_AS_LONG(pyValue); + break; + case timeStamp: + feature.timestamp = timestamp + + Vamp::RealTime::frame2RealTime + ((size_t)PyInt_AS_LONG(pyValue), (size_t)m_inputSampleRate); + break; + case values: + feature.values = PyList_As_FloatVector(pyValue); + break; + case label: + feature.label = PyString_AsString(pyValue); + break; + default : + cerr << "Invalid key in VAMP FeatureSet: " + << PyString_AsString(pyKey) << endl; + } // switch + + } // while + if (emptyFeature) cerr << "Warning: This feature is empty or badly formatted." << endl; + else returnFeatures[i].push_back(feature); + + }// for j = FeatureList + + }//for i = FeatureSet + Py_CLEAR(pyOutputList); + mutex = false; + return returnFeatures; + + }//if (has_attribute) + mutex = false; + return FeatureSet(); +} + + + +PyPlugin::FeatureSet +PyPlugin::getRemainingFeatures() +{ + static char method[]="getRemainingFeatures"; + cerr << "[call] " << method << endl; + + //check if the method is implemented + mutex = true; + if ( ! PyObject_HasAttrString(m_pyInstance,method) ) { + return FeatureSet(); + } + + PyObject *pyMethod = PyString_FromString(method); + + PyObject *pyOutputList = + PyObject_CallMethod(m_pyInstance,method, NULL); + + //Check return type + if (pyOutputList == NULL || !PyList_Check(pyOutputList) ) { + if (pyOutputList == NULL) { + cerr << "ERROR: In Python plugin [" << m_class << "::" << method + << "] Unexpected result." << endl; + if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } + } else { + cerr << "ERROR: In Python plugin [" << m_class << "::" << method + << "] Expected List return type." << endl; + } + Py_CLEAR(pyMethod); + Py_CLEAR(pyOutputList); + mutex = false; + return FeatureSet(); + } + Py_DECREF(pyMethod); + + PyObject *pyFeatureList, *pyDict, *pyKey, *pyValue; + FeatureSet returnFeatures; + + for (Py_ssize_t i = 0; i < PyList_GET_SIZE(pyOutputList); ++i) { + + pyFeatureList = PyList_GET_ITEM(pyOutputList,i); + + for (Py_ssize_t j = 0; j < PyList_GET_SIZE(pyFeatureList); ++j) { + + pyDict = PyList_GET_ITEM(pyFeatureList,j); + + if ( !PyDict_Check(pyDict) ) continue; + + Py_ssize_t pyPos = NULL; + bool emptyFeature = true; + Feature feature; + + while (PyDict_Next(pyDict, &pyPos, &pyKey, &pyValue)) + { + emptyFeature = false; + switch (ffKeys[PyString_AsString(pyKey)]) + { + case not_found : + cerr << "Unknown key in VAMP FeatureSet: " + << PyString_AsString(pyKey) << endl; + break; + case hasTimestamp: + feature.hasTimestamp = (bool) PyInt_AS_LONG(pyValue); + break; + // TODO: clarify what to do here + case timeStamp: + feature.timestamp = + Vamp::RealTime::frame2RealTime + ((size_t)PyInt_AS_LONG(pyValue), (size_t)m_inputSampleRate); + break; + case values: + feature.values = PyList_As_FloatVector(pyValue); + break; + case label: + feature.label = PyString_AsString(pyValue); + break; + } // switch + } // while + if (emptyFeature) cerr << "Warning: This feature is empty or badly formatted." << endl; + else returnFeatures[i].push_back(feature); + }// for j + }//for i + Py_CLEAR(pyOutputList); + mutex = false; + return returnFeatures; +} +