Mercurial > hg > vampy
view PyPlugin.cpp @ 8:3af6b5990ad8
more examples and some bug fixes
author | fazekasgy |
---|---|
date | Fri, 13 Jun 2008 16:50:00 +0000 |
parents | a4c955e9a70b |
children | e9cf443b18f5 |
line wrap: on
line source
/* 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 "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; // 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; Mutex PyPlugin::m_pythonInterpreterMutex; static bool isMapInitialised = false; PyPlugin::PyPlugin(std::string pluginKey,float inputSampleRate, PyObject *pyInstance) : Plugin(inputSampleRate), m_pyInstance(pyInstance), m_stepSize(0), m_blockSize(0), m_channels(0), m_plugin(pluginKey), m_class(pluginKey.substr(pluginKey.rfind(':')+1,pluginKey.size()-1)), m_path((pluginKey.substr(0,pluginKey.rfind(pathsep)))), m_processType(0), m_pyProcess(NULL), m_inputDomain(TimeDomain) { } PyPlugin::~PyPlugin() { Py_CLEAR(m_pyProcess); #ifdef _DEBUG cerr << "PyPlugin::PyPlugin:" << m_class << " Instance deleted." << endl; #endif } string PyPlugin::getIdentifier() const { MutexLocker locker(&m_pythonInterpreterMutex); char method[]="getIdentifier"; cerr << "[call] " << method << endl; string rString="vampy-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); return rString; } cerr << "Warning: Plugin must return a unique identifier." << endl; return rString; } string PyPlugin::getName() const { MutexLocker locker(&m_pythonInterpreterMutex); 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 { MutexLocker locker(&m_pythonInterpreterMutex); 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 { MutexLocker locker(&m_pythonInterpreterMutex); 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 { MutexLocker locker(&m_pythonInterpreterMutex); 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) { //useful for debugging Python plugins char method[]="initialise"; cerr << "[call] " << method << endl; //placing Mutex before these calls causes deadlock if (channels < getMinChannelCount() || channels > getMaxChannelCount()) return false; m_inputDomain = getInputDomain(); MutexLocker locker(&m_pythonInterpreterMutex); initMaps(); m_stepSize = stepSize; m_blockSize = blockSize; m_channels = channels; //quering process implementation type char legacyMethod[]="process"; char numpyMethod[]="processN"; if (PyObject_HasAttrString(m_pyInstance,legacyMethod) & m_processType == 0) { m_processType = legacyProcess; m_pyProcess = PyString_FromString(legacyMethod); } if (PyObject_HasAttrString(m_pyInstance,numpyMethod) & m_processType == 0) { m_processType = numpyProcess; m_pyProcess = PyString_FromString(numpyMethod); } if (!m_processType) { m_processType = not_implemented; m_pyProcess = NULL; cerr << "Warning: Python plugin [" << m_class << "::" << method << "] No process implementation found. Plugin will do nothing." << 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 (PyErr_Occurred() || !PyBool_Check(pyBool)) { PyErr_Print(); PyErr_Clear(); 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 false; } void PyPlugin::reset() { MutexLocker locker(&m_pythonInterpreterMutex); char method[]="reset"; cerr << "[call] " << method << endl; if ( PyObject_HasAttrString(m_pyInstance,method) ) { PyObject_CallMethod(m_pyInstance, method, NULL); if (PyErr_Occurred()) { PyErr_Print(); PyErr_Clear(); } } } PyPlugin::InputDomain PyPlugin::getInputDomain() const { MutexLocker locker(&m_pythonInterpreterMutex); 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 { MutexLocker locker(&m_pythonInterpreterMutex); 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 { MutexLocker locker(&m_pythonInterpreterMutex); char method[]="getPreferredStepSize"; cerr << "[call] " << method << endl; size_t rValue=1024; //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 { MutexLocker locker(&m_pythonInterpreterMutex); 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 { MutexLocker locker(&m_pythonInterpreterMutex); 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 { MutexLocker locker(&m_pythonInterpreterMutex); //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) ) return list; //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); // od.sampleRate = m_inputSampleRate / m_stepSize; cerr << od.sampleRate << endl; break; default : cerr << "Invalid key in VAMP OutputDescriptor: " << PyString_AsString(pyKey) << endl; } } // while dict list.push_back(od); } // for list Py_CLEAR(pyList); return list; } PyPlugin::ParameterList PyPlugin::getParameterDescriptors() const { MutexLocker locker(&m_pythonInterpreterMutex); 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) ) return list; //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; } } // while dict list.push_back(pd); } // for list Py_CLEAR(pyList); return list; } void PyPlugin::setParameter(std::string paramid, float newval) { MutexLocker locker(&m_pythonInterpreterMutex); 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 { MutexLocker locker(&m_pythonInterpreterMutex); 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) { MutexLocker locker(&m_pythonInterpreterMutex); #ifdef _DEBUG cerr << "[call] process, frame:" << proccounter << endl; proccounter++; #endif if (m_blockSize == 0 || m_channels == 0) { cerr << "ERROR: PyPlugin::process: " << "Plugin has not been initialised" << endl; return FeatureSet(); } if (m_processType == not_implemented) { cerr << "ERROR: In Python plugin [" << m_class << "] No process implementation found. Returning empty feature set." << endl; return FeatureSet(); } string method=PyString_AsString(m_pyProcess); PyObject *pyOutputList = NULL; /*new numPy support*/ if (m_processType == numpyProcess) { //create a list of buffers PyObject *pyChannelList = PyList_New((Py_ssize_t) m_channels); for (size_t i=0; i < m_channels; ++i) { //Expose memory using the Buffer Interface of C/API //This will virtually pass a pointer which can be //recasted in Python code as float or complex array PyObject *pyBuffer = PyBuffer_FromMemory ((void *) (float *) inputBuffers[i], (Py_ssize_t) sizeof(float) * m_blockSize); PyList_SET_ITEM(pyChannelList, (Py_ssize_t) i, pyBuffer); } //pass RealTime as frameCount PyObject *pyLongSample = PyLong_FromLong ( Vamp::RealTime::realTime2Frame (timestamp, (unsigned int) m_inputSampleRate)); //Call python process (returns new reference) pyOutputList = PyObject_CallMethodObjArgs (m_pyInstance,m_pyProcess,pyChannelList,pyLongSample,NULL); Py_DECREF(pyChannelList); Py_DECREF(pyLongSample); } if (m_processType == legacyProcess) { //create a list of lists PyObject *pyChannelList = PyList_New((Py_ssize_t) m_channels); for (size_t i=0; i < m_channels; ++i) { //Declare new list object PyObject *pyFloat, *pyList; pyList = PyList_New((Py_ssize_t) m_blockSize); //Pack samples into a Python List Object //pyFloat types will always be new references, //these will be discarded when the list is deallocated for (size_t j = 0; j < m_blockSize; ++j) { pyFloat=PyFloat_FromDouble( (double) inputBuffers[i][j]); PyList_SET_ITEM(pyList, (Py_ssize_t) j, pyFloat); } PyList_SET_ITEM(pyChannelList, (Py_ssize_t) i, pyList); } //pass RealTime as frameCount PyObject *pyLongSample = PyLong_FromLong ( Vamp::RealTime::realTime2Frame (timestamp, (unsigned int) m_inputSampleRate)); //Call python process (returns new reference) pyOutputList = PyObject_CallMethodObjArgs (m_pyInstance,m_pyProcess,pyChannelList,pyLongSample,NULL); Py_DECREF(pyChannelList); Py_DECREF(pyLongSample); } //return nothing //Py_CLEAR(pyOutputList); //return FeatureSet(); //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(pyOutputList); return FeatureSet(); } // 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 = Vamp::RealTime::frame2RealTime( PyLong_AsLong(pyValue), (unsigned int) m_inputSampleRate ); #ifdef _DEBUG cerr << "Timestamp: " << (long)PyLong_AsLong(pyValue) << ", ->" << feature.timestamp.toString() << endl; #endif 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); return returnFeatures; } PyPlugin::FeatureSet PyPlugin::getRemainingFeatures() { MutexLocker locker(&m_pythonInterpreterMutex); static char method[]="getRemainingFeatures"; cerr << "[call] " << method << endl; //check if the method is implemented 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); return FeatureSet(); } Py_DECREF(pyMethod); PyObject *pyFeatureList, *pyDict, *pyKey, *pyValue; FeatureSet returnFeatures; //iterate through list of outputs for (Py_ssize_t i = 0; i < PyList_GET_SIZE(pyOutputList); ++i) { pyFeatureList = PyList_GET_ITEM(pyOutputList,i); //iterate list of Features for (Py_ssize_t j = 0; j < PyList_GET_SIZE(pyFeatureList); ++j) { #ifdef _DEBUG cerr << "feature: " << j << endl; #endif 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; case timeStamp: feature.timestamp = Vamp::RealTime::frame2RealTime( PyLong_AsLong(pyValue), (unsigned int) m_inputSampleRate ); #ifdef _DEBUG cerr << "Timestamp: " << (long)PyLong_AsLong(pyValue) << ", ->" << feature.timestamp.toString() << endl; #endif 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); return returnFeatures; } bool PyPlugin::initMaps() const { if (isMapInitialised) return true; 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; isMapInitialised = true; return true; } //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> PyPlugin::PyList_To_StringVector (PyObject *inputList) const { 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> PyPlugin::PyList_As_FloatVector (PyObject *inputList) const { 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); #ifdef _DEBUG cerr << "value: " << ListElement << endl; #endif 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; } */