Chris@66: /* -*- c-basic-offset: 8 indent-tabs-mode: t -*- */ fazekasgy@37: /* fazekasgy@37: fazekasgy@37: * Vampy : This plugin is a wrapper around the Vamp plugin API. fazekasgy@37: * It allows for writing Vamp plugins in Python. fazekasgy@37: fazekasgy@37: * Centre for Digital Music, Queen Mary University of London. fazekasgy@37: * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources fazekasgy@37: * for licence information.) fazekasgy@37: fazekasgy@37: */ fazekasgy@37: fazekasgy@37: #include fazekasgy@37: fazekasgy@37: #ifdef HAVE_NUMPY fazekasgy@51: fazekasgy@51: // define a unique API pointer Chris@66: #define PY_ARRAY_UNIQUE_SYMBOL VAMPY_ARRAY_API Chris@66: #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION fazekasgy@37: #include "numpy/arrayobject.h" fazekasgy@51: fazekasgy@51: // prevent building with very old versions of numpy fazekasgy@51: #ifndef NPY_VERSION fazekasgy@51: #undef HAVE_NUMPY fazekasgy@51: #endif fazekasgy@51: fazekasgy@51: #endif fazekasgy@51: fazekasgy@51: // this is not part of the API, but we will require it for a bug workaround fazekasgy@51: // define this symbol if you use another version of numpy in the makefile fazekasgy@51: // Vampy will not attempt to load a lower version than specified fazekasgy@51: #ifdef HAVE_NUMPY Chris@81: #ifndef NUMPY_MAJORVERSION Chris@81: #define NUMPY_MAJORVERSION 1 fazekasgy@51: #endif Chris@81: #ifndef NUMPY_MINORVERSION Chris@81: #define NUMPY_MINORVERSION 9 Chris@81: #endif Chris@81: fazekasgy@37: #endif fazekasgy@37: fazekasgy@37: #include "vamp/vamp.h" fazekasgy@37: #include "vamp-sdk/PluginAdapter.h" fazekasgy@37: #include "PyPlugScanner.h" fazekasgy@37: #include "PyPlugin.h" fazekasgy@37: #include "PyExtensionModule.h" fazekasgy@37: #include "PyExtensionManager.h" Chris@67: #include "Debug.h" gyorgyf@63: #include fazekasgy@37: fazekasgy@37: #ifdef _WIN32 fazekasgy@37: #define pathsep ('\\') fazekasgy@37: #include fazekasgy@37: #include fazekasgy@37: #else fazekasgy@37: #define pathsep ('/') fazekasgy@37: #include fazekasgy@37: #include fazekasgy@37: #endif fazekasgy@37: fazekasgy@37: using std::cerr; fazekasgy@37: using std::endl; fazekasgy@37: using std::string; fazekasgy@37: using std::vector; fazekasgy@37: fazekasgy@37: static int adinstcount; fazekasgy@37: static int totinstcount; fazekasgy@51: static bool numpyInstalled = false; fazekasgy@51: static bool arrayApiInitialised = false; fazekasgy@37: fazekasgy@37: class PyPluginAdapter : public Vamp::PluginAdapterBase fazekasgy@37: { fazekasgy@37: public: fazekasgy@37: PyPluginAdapter(std::string pyPlugId, PyObject* pyClass) : fazekasgy@37: PluginAdapterBase(), fazekasgy@37: m_plug(pyPlugId), fazekasgy@37: m_pyClass(pyClass), fazekasgy@37: m_failed(false) fazekasgy@37: { Chris@67: DSTREAM << "PyPluginAdapter:ctor:"<< adinstcount << ": " << m_plug << endl; fazekasgy@37: adinstcount++; fazekasgy@37: } fazekasgy@37: fazekasgy@37: ~PyPluginAdapter() fazekasgy@37: { fazekasgy@37: } fazekasgy@37: fazekasgy@37: bool failed() { return m_failed; } fazekasgy@37: std::string getPlugKey() { return m_plug; } fazekasgy@37: fazekasgy@37: protected: fazekasgy@37: Vamp::Plugin *createPlugin(float inputSampleRate) fazekasgy@37: { fazekasgy@37: try { fazekasgy@51: PyPlugin *plugin = new PyPlugin(m_plug, inputSampleRate, m_pyClass, totinstcount, numpyInstalled); fazekasgy@37: return plugin; fazekasgy@37: } catch (...) { Chris@67: cerr << "ERROR: PyPluginAdapter::createPlugin: Failed to construct PyPlugin" << endl; Chris@67: // any plugin with syntax errors will fail to construct Chris@67: m_failed = true; fazekasgy@37: return 0; fazekasgy@37: } fazekasgy@37: } fazekasgy@37: fazekasgy@37: std::string m_plug; fazekasgy@37: PyObject *m_pyClass; fazekasgy@37: bool m_failed; fazekasgy@37: }; fazekasgy@37: fazekasgy@51: fazekasgy@37: static void array_API_initialiser() fazekasgy@37: { fazekasgy@51: if (arrayApiInitialised) return; fazekasgy@51: fazekasgy@51: /* Numpy 1.3 build note: there seems to be a bug fazekasgy@51: in this version (at least on OS/X) which will cause memory fazekasgy@51: access error in the array API import function if an earlier runtime fazekasgy@51: version of Numpy is used when loading the library. fazekasgy@51: (below is a horrible workaround) fazekasgy@51: */ fazekasgy@51: fazekasgy@37: #ifdef HAVE_NUMPY fazekasgy@51: Chris@81: string ver, majorver, minorver; gyorgyf@63: std::istringstream verStream; Chris@81: int numpyVersionMajor, numpyVersionMinor; fazekasgy@51: fazekasgy@51: /// attmept to test numpy version before importing the array API Chris@67: DSTREAM << "Numpy build information: ABI level: " << NPY_VERSION Chris@81: << " Numpy version: " << NUMPY_MAJORVERSION << "." << NUMPY_MINORVERSION << endl; fazekasgy@51: fazekasgy@51: PyObject *pyModule, *pyDict, *pyVer; fazekasgy@51: fazekasgy@51: pyModule = PyImport_ImportModule("numpy"); //numpy.core.multiarray fazekasgy@51: if (!pyModule) { Chris@67: cerr << "ERROR: Vampy was compiled with Numpy support but Numpy does not seem to be installed." << endl; fazekasgy@51: #ifdef __APPLE__ fazekasgy@51: cerr << "Hint: Check if Numpy is installed for the particular setup of Python used by Vampy (given by Python exec prefix)." << endl; fazekasgy@51: #endif fazekasgy@51: goto numpyFailure; fazekasgy@51: } fazekasgy@51: fazekasgy@51: pyDict = PyModule_GetDict(pyModule); // borrowed ref fazekasgy@51: if (!pyDict) { Chris@67: cerr << "ERROR: Can not access Numpy module dictionary." << endl; fazekasgy@51: goto numpyFailure; fazekasgy@51: } fazekasgy@51: fazekasgy@51: pyVer = PyDict_GetItemString(pyDict,"__version__"); //borrowed ref fazekasgy@51: if (!pyVer) { Chris@67: cerr << "ERROR: Can not access Numpy version information." << endl; fazekasgy@51: goto numpyFailure; fazekasgy@51: } fazekasgy@51: fazekasgy@51: ver = PyString_AsString(pyVer); fazekasgy@51: ver = ver.substr(0,ver.rfind(".")); Chris@81: majorver = ver.substr(0,ver.rfind(".")); Chris@81: minorver = ver.substr(ver.rfind(".")+1); Chris@81: Chris@81: // parse version string to float Chris@81: verStream.str(majorver); Chris@81: verStream >> numpyVersionMajor; Chris@81: verStream.str(minorver); Chris@81: verStream >> numpyVersionMinor; Chris@67: Chris@81: DSTREAM << "Numpy runtime version: " << numpyVersionMajor << "." << numpyVersionMinor << endl; Chris@81: Chris@81: if(numpyVersionMajor < NUMPY_MAJORVERSION || Chris@81: (numpyVersionMajor < NUMPY_MAJORVERSION && Chris@81: numpyVersionMinor < NUMPY_MINORVERSION)) { Chris@81: cerr << "ERROR: Incompatible Numpy version found: " << ver << endl; fazekasgy@51: goto numpyFailure; fazekasgy@51: } fazekasgy@51: fazekasgy@51: Py_DECREF(pyModule); fazekasgy@51: fazekasgy@51: // At least we catch import errors, but if binary compatibility fazekasgy@51: // has changed without notice, this would still fail. fazekasgy@51: // However, we should never get to this point now anyway. fazekasgy@37: import_array(); fazekasgy@51: if (PyErr_Occurred()) { Chris@67: cerr << "ERROR: Import error while loading the Numpy Array API." << endl; fazekasgy@51: PyErr_Print(); PyErr_Clear(); fazekasgy@51: goto numpyFailure; fazekasgy@51: } fazekasgy@51: else { fazekasgy@51: numpyInstalled = true; fazekasgy@51: arrayApiInitialised = true; fazekasgy@51: return; fazekasgy@51: } fazekasgy@51: fazekasgy@51: fazekasgy@51: numpyFailure: Chris@81: cerr << "Please make sure you have Numpy " << NUMPY_MAJORVERSION << "." << NUMPY_MINORVERSION << " or greater installed." << endl; fazekasgy@51: cerr << "Vampy: Numpy support disabled." << endl; fazekasgy@51: numpyInstalled = false; fazekasgy@51: arrayApiInitialised = true; fazekasgy@51: if (pyModule) Py_XDECREF(pyModule); fazekasgy@51: return; fazekasgy@51: fazekasgy@51: /*HAVE_NUMPY*/ fazekasgy@51: #endif fazekasgy@51: fazekasgy@51: numpyInstalled = false; fazekasgy@51: arrayApiInitialised = true; fazekasgy@51: return; fazekasgy@37: } fazekasgy@37: fazekasgy@37: fazekasgy@37: static std::vector adapters; fazekasgy@37: static bool haveScannedPlugins = false; fazekasgy@37: fazekasgy@37: static bool tryPreload(string name) fazekasgy@37: { cannam@53: // cerr << "tryPreload: " << name << endl; fazekasgy@37: #ifdef _WIN32 fazekasgy@37: void *lib = LoadLibrary(name.c_str()); fazekasgy@37: if (!lib) { fazekasgy@37: return false; fazekasgy@37: } fazekasgy@37: #else fazekasgy@37: void *lib = dlopen(name.c_str(), RTLD_NOW | RTLD_GLOBAL); fazekasgy@37: if (!lib) { cannam@56: //perror("dlopen"); fazekasgy@37: return false; fazekasgy@37: } fazekasgy@37: #endif Chris@67: DSTREAM << "Preloaded Python from " << name << endl; fazekasgy@37: return true; fazekasgy@37: } fazekasgy@37: fazekasgy@37: static bool preloadPython() fazekasgy@37: { fazekasgy@37: #ifdef _WIN32 fazekasgy@37: // this doesn't seem to be necessary at all on Windows fazekasgy@37: return true; fazekasgy@37: #endif fazekasgy@37: fazekasgy@37: string pyver = Py_GetVersion(); fazekasgy@37: int dots = 2; fazekasgy@37: string shortver; fazekasgy@37: for (size_t i = 0; i < pyver.length(); ++i) { fazekasgy@37: if (pyver[i] == '.') { fazekasgy@37: if (--dots == 0) { fazekasgy@37: shortver = pyver.substr(0, i); fazekasgy@37: break; fazekasgy@37: } fazekasgy@37: } fazekasgy@37: } Chris@67: DSTREAM << "Short version: " << shortver << endl; Chris@67: // this is useful to find out where the loaded library might be loaded from Chris@67: DSTREAM << "Python exec prefix: " << Py_GetExecPrefix() << endl; fazekasgy@37: cannam@55: char *pylib = getenv("VAMPY_PYLIB"); cannam@54: if (pylib && *pylib) { Chris@67: DSTREAM << "Trying to preload Python from specified location " << pylib Chris@67: << "..." << endl; cannam@54: return tryPreload(string(pylib)); cannam@54: } cannam@54: fazekasgy@37: vector pfxs; cannam@53: pfxs.push_back(string(Py_GetExecPrefix()) + "/"); cannam@53: pfxs.push_back(string(Py_GetExecPrefix()) + "/lib/"); fazekasgy@37: pfxs.push_back(""); fazekasgy@37: pfxs.push_back("/usr/lib/"); fazekasgy@37: pfxs.push_back("/usr/local/lib/"); fazekasgy@37: char buffer[5]; fazekasgy@37: fazekasgy@37: // hahaha! grossness is like a brother to us fazekasgy@37: #ifdef __APPLE__ fazekasgy@37: for (size_t pfxidx = 0; pfxidx < pfxs.size(); ++pfxidx) { cannam@53: // cerr << "prefix: " << pfxs[pfxidx] << endl; cannam@53: if (tryPreload(pfxs[pfxidx] + string("Python"))) return true; fazekasgy@37: for (int minor = 8; minor >= 0; --minor) { fazekasgy@37: sprintf(buffer, "%d", minor); fazekasgy@37: if (tryPreload(pfxs[pfxidx] + string("libpython") + shortver + ".dylib." + buffer)) return true; fazekasgy@37: } fazekasgy@37: if (tryPreload(pfxs[pfxidx] + string("libpython") + shortver + ".dylib")) return true; fazekasgy@37: if (tryPreload(pfxs[pfxidx] + string("libpython.dylib"))) return true; fazekasgy@37: } fazekasgy@37: #else fazekasgy@37: for (size_t pfxidx = 0; pfxidx < pfxs.size(); ++pfxidx) { fazekasgy@37: for (int minor = 8; minor >= 0; --minor) { fazekasgy@37: sprintf(buffer, "%d", minor); fazekasgy@37: if (tryPreload(pfxs[pfxidx] + string("libpython") + shortver + ".so." + buffer)) return true; fazekasgy@37: } fazekasgy@37: if (tryPreload(pfxs[pfxidx] + string("libpython") + shortver + ".so")) return true; fazekasgy@37: if (tryPreload(pfxs[pfxidx] + string("libpython.so"))) return true; fazekasgy@37: } fazekasgy@37: #endif fazekasgy@37: fazekasgy@37: return false; fazekasgy@37: } fazekasgy@37: fazekasgy@37: fazekasgy@37: static PyExtensionManager pyExtensionManager; fazekasgy@37: fazekasgy@37: const VampPluginDescriptor fazekasgy@37: *vampGetPluginDescriptor(unsigned int version,unsigned int index) Chris@79: { Chris@79: DSTREAM << "# vampGetPluginDescriptor(" << version << "," << index << ")" << endl; Chris@79: fazekasgy@37: if (version < 1) return 0; fazekasgy@37: fazekasgy@37: int isPythonInitialized = Py_IsInitialized(); Chris@67: DSTREAM << "# isPythonInitialized: " << isPythonInitialized << endl; Chris@67: DSTREAM << "# haveScannedPlugins: " << haveScannedPlugins << endl; fazekasgy@37: fazekasgy@37: if (!haveScannedPlugins) { fazekasgy@37: fazekasgy@37: if (!isPythonInitialized){ fazekasgy@37: fazekasgy@37: if (!preloadPython()) fazekasgy@37: cerr << "Warning: Could not preload Python. Dynamic loading in scripts will fail." << endl; fazekasgy@37: if (PyImport_AppendInittab("vampy",initvampy) != 0) fazekasgy@37: cerr << "Warning: Extension module could not be added to module inittab." << endl; fazekasgy@37: Py_Initialize(); fazekasgy@51: array_API_initialiser(); fazekasgy@37: initvampy(); Chris@67: DSTREAM << "# isPythonInitialized after initialize: " << Py_IsInitialized() << endl; fazekasgy@37: } fazekasgy@37: fazekasgy@37: vector pyPlugs; fazekasgy@37: vector pyPath; fazekasgy@37: vector pyClasses; fazekasgy@37: static PyPlugScanner *scanner; fazekasgy@37: fazekasgy@37: //Scanning Plugins Chris@67: DSTREAM << "Scanning Vampy Plugins" << endl; fazekasgy@37: scanner = PyPlugScanner::getInstance(); fazekasgy@37: fazekasgy@37: // added env. varable support VAMPY_EXTPATH fazekasgy@37: pyPath=scanner->getAllValidPath(); fazekasgy@37: scanner->setPath(pyPath); fazekasgy@37: fazekasgy@37: // added env. variable support: fazekasgy@37: // VAMPY_COMPILED=1 to recognise .pyc files (default is 1) fazekasgy@37: pyPlugs = scanner->getPyPlugs(); fazekasgy@37: Chris@67: DSTREAM << "Found " << pyPlugs.size() << " Scripts." << endl; fazekasgy@37: //TODO: should this support multiple classes per script (?) fazekasgy@37: pyClasses = scanner->getPyClasses(); Chris@67: DSTREAM << "Found " << pyClasses.size() << " Classes." << endl; fazekasgy@37: fazekasgy@37: for (size_t i = 0; i < pyPlugs.size(); ++i) { fazekasgy@37: adapters.push_back( new PyPluginAdapter(pyPlugs[i],pyClasses[i])); fazekasgy@37: } fazekasgy@37: pyExtensionManager.setPlugModuleNames(pyPlugs); fazekasgy@37: pyExtensionManager.initExtension(); fazekasgy@37: array_API_initialiser(); fazekasgy@37: haveScannedPlugins=true; fazekasgy@37: } fazekasgy@37: Chris@67: DSTREAM << "Accessing adapter index: " << index << " (adapters: " << adapters.size() << ")" << endl; fazekasgy@37: Chris@84: while (index < adapters.size()) { fazekasgy@37: fazekasgy@37: const VampPluginDescriptor *tmp = adapters[index]->getDescriptor(); fazekasgy@37: fazekasgy@37: if (adapters[index]->failed()) { fazekasgy@37: cerr << "\nERROR: [in vampGetPluginDescriptor] Removing adapter of: \n'" fazekasgy@37: << adapters[index]->getPlugKey() << "'\n" fazekasgy@37: << "The plugin has failed to construct. Hint: Check __init__() function." << endl; fazekasgy@37: pyExtensionManager.deleteModuleName(adapters[index]->getPlugKey()); fazekasgy@37: delete adapters[index]; fazekasgy@37: adapters.erase(adapters.begin()+index); Chris@84: continue; fazekasgy@37: } fazekasgy@37: fazekasgy@37: return tmp; Chris@84: } fazekasgy@37: Chris@84: return 0; fazekasgy@37: } fazekasgy@37: fazekasgy@37: fazekasgy@37: fazekasgy@37: fazekasgy@37: fazekasgy@37: fazekasgy@37: fazekasgy@37: