fazekasgy@37
|
1 /*
|
fazekasgy@37
|
2
|
fazekasgy@37
|
3 * Vampy : This plugin is a wrapper around the Vamp plugin API.
|
fazekasgy@37
|
4 * It allows for writing Vamp plugins in Python.
|
fazekasgy@37
|
5
|
fazekasgy@37
|
6 * Centre for Digital Music, Queen Mary University of London.
|
fazekasgy@37
|
7 * Copyright (C) 2008-2009 Gyorgy Fazekas, QMUL. (See Vamp sources
|
fazekasgy@37
|
8 * for licence information.)
|
fazekasgy@37
|
9
|
fazekasgy@37
|
10 */
|
fazekasgy@37
|
11
|
fazekasgy@37
|
12 #include <Python.h>
|
fazekasgy@37
|
13
|
fazekasgy@37
|
14 #ifdef HAVE_NUMPY
|
fazekasgy@37
|
15 #define PY_ARRAY_UNIQUE_SYMBOL VAMPY_ARRAY_API
|
fazekasgy@37
|
16 #include "numpy/arrayobject.h"
|
fazekasgy@37
|
17 #endif
|
fazekasgy@37
|
18
|
fazekasgy@37
|
19 #include "vamp/vamp.h"
|
fazekasgy@37
|
20 #include "vamp-sdk/PluginAdapter.h"
|
fazekasgy@37
|
21 #include "PyPlugScanner.h"
|
fazekasgy@37
|
22 #include "PyPlugin.h"
|
fazekasgy@37
|
23 #include "PyExtensionModule.h"
|
fazekasgy@37
|
24 #include "PyExtensionManager.h"
|
fazekasgy@37
|
25
|
fazekasgy@37
|
26
|
fazekasgy@37
|
27 #ifdef _WIN32
|
fazekasgy@37
|
28 #define pathsep ('\\')
|
fazekasgy@37
|
29 #include <windows.h>
|
fazekasgy@37
|
30 #include <tchar.h>
|
fazekasgy@37
|
31 #else
|
fazekasgy@37
|
32 #define pathsep ('/')
|
fazekasgy@37
|
33 #include <dirent.h>
|
fazekasgy@37
|
34 #include <dlfcn.h>
|
fazekasgy@37
|
35 #endif
|
fazekasgy@37
|
36
|
fazekasgy@37
|
37 using std::cerr;
|
fazekasgy@37
|
38 using std::endl;
|
fazekasgy@37
|
39 using std::string;
|
fazekasgy@37
|
40 using std::vector;
|
fazekasgy@37
|
41
|
fazekasgy@37
|
42 static int adinstcount;
|
fazekasgy@37
|
43 static int totinstcount;
|
fazekasgy@37
|
44
|
fazekasgy@37
|
45 class PyPluginAdapter : public Vamp::PluginAdapterBase
|
fazekasgy@37
|
46 {
|
fazekasgy@37
|
47 public:
|
fazekasgy@37
|
48 PyPluginAdapter(std::string pyPlugId, PyObject* pyClass) :
|
fazekasgy@37
|
49 PluginAdapterBase(),
|
fazekasgy@37
|
50 m_plug(pyPlugId),
|
fazekasgy@37
|
51 m_pyClass(pyClass),
|
fazekasgy@37
|
52 m_failed(false)
|
fazekasgy@37
|
53 {
|
fazekasgy@37
|
54 cerr << "PyPluginAdapter:ctor:"<< adinstcount << ": " << m_plug << endl;
|
fazekasgy@37
|
55 adinstcount++;
|
fazekasgy@37
|
56 }
|
fazekasgy@37
|
57
|
fazekasgy@37
|
58 ~PyPluginAdapter()
|
fazekasgy@37
|
59 {
|
fazekasgy@37
|
60 }
|
fazekasgy@37
|
61
|
fazekasgy@37
|
62 bool failed() { return m_failed; }
|
fazekasgy@37
|
63 std::string getPlugKey() { return m_plug; }
|
fazekasgy@37
|
64
|
fazekasgy@37
|
65 protected:
|
fazekasgy@37
|
66 Vamp::Plugin *createPlugin(float inputSampleRate)
|
fazekasgy@37
|
67 {
|
fazekasgy@37
|
68 try {
|
fazekasgy@37
|
69 PyPlugin *plugin = new PyPlugin(m_plug, inputSampleRate, m_pyClass, totinstcount);
|
fazekasgy@37
|
70 return plugin;
|
fazekasgy@37
|
71 } catch (...) {
|
fazekasgy@37
|
72 cerr << "PyPluginAdapter::createPlugin: Failed to construct PyPlugin" << endl;
|
fazekasgy@37
|
73 // any plugin with syntax errors will fail to construct
|
fazekasgy@37
|
74 m_failed = true;
|
fazekasgy@37
|
75 return 0;
|
fazekasgy@37
|
76 }
|
fazekasgy@37
|
77 }
|
fazekasgy@37
|
78
|
fazekasgy@37
|
79 std::string m_plug;
|
fazekasgy@37
|
80 PyObject *m_pyClass;
|
fazekasgy@37
|
81 bool m_failed;
|
fazekasgy@37
|
82 };
|
fazekasgy@37
|
83
|
fazekasgy@37
|
84 static void array_API_initialiser()
|
fazekasgy@37
|
85 {
|
fazekasgy@37
|
86 /// numpy C-API requirement
|
fazekasgy@37
|
87 #ifdef HAVE_NUMPY
|
fazekasgy@37
|
88 import_array();
|
fazekasgy@37
|
89 if(NPY_VERSION != PyArray_GetNDArrayCVersion())
|
fazekasgy@37
|
90 cerr << "Warning: Numpy ABI version mismatch. (Build version: "
|
fazekasgy@37
|
91 << NPY_VERSION << " Runtime version: " << PyArray_GetNDArrayCVersion() << ")" << endl;
|
fazekasgy@37
|
92 #endif
|
fazekasgy@37
|
93 }
|
fazekasgy@37
|
94
|
fazekasgy@37
|
95
|
fazekasgy@37
|
96 static std::vector<PyPluginAdapter *> adapters;
|
fazekasgy@37
|
97 static bool haveScannedPlugins = false;
|
fazekasgy@37
|
98
|
fazekasgy@37
|
99 static bool tryPreload(string name)
|
fazekasgy@37
|
100 {
|
fazekasgy@37
|
101 #ifdef _WIN32
|
fazekasgy@37
|
102 void *lib = LoadLibrary(name.c_str());
|
fazekasgy@37
|
103 if (!lib) {
|
fazekasgy@37
|
104 return false;
|
fazekasgy@37
|
105 }
|
fazekasgy@37
|
106 #else
|
fazekasgy@37
|
107 void *lib = dlopen(name.c_str(), RTLD_NOW | RTLD_GLOBAL);
|
fazekasgy@37
|
108 if (!lib) {
|
fazekasgy@37
|
109 return false;
|
fazekasgy@37
|
110 }
|
fazekasgy@37
|
111 #endif
|
fazekasgy@37
|
112 return true;
|
fazekasgy@37
|
113 }
|
fazekasgy@37
|
114
|
fazekasgy@37
|
115 static bool preloadPython()
|
fazekasgy@37
|
116 {
|
fazekasgy@37
|
117 #ifdef _WIN32
|
fazekasgy@37
|
118 // this doesn't seem to be necessary at all on Windows
|
fazekasgy@37
|
119 return true;
|
fazekasgy@37
|
120 #endif
|
fazekasgy@37
|
121
|
fazekasgy@37
|
122 string pyver = Py_GetVersion();
|
fazekasgy@37
|
123 int dots = 2;
|
fazekasgy@37
|
124 string shortver;
|
fazekasgy@37
|
125 for (size_t i = 0; i < pyver.length(); ++i) {
|
fazekasgy@37
|
126 if (pyver[i] == '.') {
|
fazekasgy@37
|
127 if (--dots == 0) {
|
fazekasgy@37
|
128 shortver = pyver.substr(0, i);
|
fazekasgy@37
|
129 break;
|
fazekasgy@37
|
130 }
|
fazekasgy@37
|
131 }
|
fazekasgy@37
|
132 }
|
fazekasgy@37
|
133 cerr << "Short version: " << shortver << endl;
|
fazekasgy@37
|
134 // this is useful to find out where the loaded library might be loaded from
|
fazekasgy@37
|
135 cerr << "Python exec prefix: " << Py_GetExecPrefix() << endl;
|
fazekasgy@37
|
136
|
fazekasgy@37
|
137 vector<string> pfxs;
|
fazekasgy@37
|
138 pfxs.push_back("");
|
fazekasgy@37
|
139 pfxs.push_back(string(Py_GetExecPrefix()) + "/lib/");
|
fazekasgy@37
|
140 pfxs.push_back(string(Py_GetExecPrefix()) + "/");
|
fazekasgy@37
|
141 pfxs.push_back("/usr/lib/");
|
fazekasgy@37
|
142 pfxs.push_back("/usr/local/lib/");
|
fazekasgy@37
|
143 char buffer[5];
|
fazekasgy@37
|
144
|
fazekasgy@37
|
145 // hahaha! grossness is like a brother to us
|
fazekasgy@37
|
146 #ifdef __APPLE__
|
fazekasgy@37
|
147 for (size_t pfxidx = 0; pfxidx < pfxs.size(); ++pfxidx) {
|
fazekasgy@37
|
148 for (int minor = 8; minor >= 0; --minor) {
|
fazekasgy@37
|
149 sprintf(buffer, "%d", minor);
|
fazekasgy@37
|
150 if (tryPreload(pfxs[pfxidx] + string("libpython") + shortver + ".dylib." + buffer)) return true;
|
fazekasgy@37
|
151 }
|
fazekasgy@37
|
152 if (tryPreload(pfxs[pfxidx] + string("libpython") + shortver + ".dylib")) return true;
|
fazekasgy@37
|
153 if (tryPreload(pfxs[pfxidx] + string("libpython.dylib"))) return true;
|
fazekasgy@37
|
154 }
|
fazekasgy@37
|
155 #else
|
fazekasgy@37
|
156 for (size_t pfxidx = 0; pfxidx < pfxs.size(); ++pfxidx) {
|
fazekasgy@37
|
157 for (int minor = 8; minor >= 0; --minor) {
|
fazekasgy@37
|
158 sprintf(buffer, "%d", minor);
|
fazekasgy@37
|
159 if (tryPreload(pfxs[pfxidx] + string("libpython") + shortver + ".so." + buffer)) return true;
|
fazekasgy@37
|
160 }
|
fazekasgy@37
|
161 if (tryPreload(pfxs[pfxidx] + string("libpython") + shortver + ".so")) return true;
|
fazekasgy@37
|
162 if (tryPreload(pfxs[pfxidx] + string("libpython.so"))) return true;
|
fazekasgy@37
|
163 }
|
fazekasgy@37
|
164 #endif
|
fazekasgy@37
|
165
|
fazekasgy@37
|
166 return false;
|
fazekasgy@37
|
167 }
|
fazekasgy@37
|
168
|
fazekasgy@37
|
169
|
fazekasgy@37
|
170 static PyExtensionManager pyExtensionManager;
|
fazekasgy@37
|
171
|
fazekasgy@37
|
172 const VampPluginDescriptor
|
fazekasgy@37
|
173 *vampGetPluginDescriptor(unsigned int version,unsigned int index)
|
fazekasgy@37
|
174 {
|
fazekasgy@37
|
175 if (version < 1) return 0;
|
fazekasgy@37
|
176
|
fazekasgy@37
|
177 int isPythonInitialized = Py_IsInitialized();
|
fazekasgy@37
|
178 cerr << "# isPythonInitialized: " << isPythonInitialized << endl;
|
fazekasgy@37
|
179 cerr << "# haveScannedPlugins: " << haveScannedPlugins << endl;
|
fazekasgy@37
|
180
|
fazekasgy@37
|
181 if (!haveScannedPlugins) {
|
fazekasgy@37
|
182
|
fazekasgy@37
|
183 if (!isPythonInitialized){
|
fazekasgy@37
|
184
|
fazekasgy@37
|
185 if (!preloadPython())
|
fazekasgy@37
|
186 cerr << "Warning: Could not preload Python. Dynamic loading in scripts will fail." << endl;
|
fazekasgy@37
|
187 if (PyImport_AppendInittab("vampy",initvampy) != 0)
|
fazekasgy@37
|
188 cerr << "Warning: Extension module could not be added to module inittab." << endl;
|
fazekasgy@37
|
189 Py_Initialize();
|
fazekasgy@37
|
190 initvampy();
|
fazekasgy@37
|
191 #ifdef _DEBUG
|
fazekasgy@37
|
192 cerr << "# isPythonInitialized after initialize: " << Py_IsInitialized() << endl;
|
fazekasgy@37
|
193 #endif
|
fazekasgy@37
|
194 }
|
fazekasgy@37
|
195
|
fazekasgy@37
|
196 vector<string> pyPlugs;
|
fazekasgy@37
|
197 vector<string> pyPath;
|
fazekasgy@37
|
198 vector<PyObject *> pyClasses;
|
fazekasgy@37
|
199 static PyPlugScanner *scanner;
|
fazekasgy@37
|
200
|
fazekasgy@37
|
201 //Scanning Plugins
|
fazekasgy@37
|
202 cerr << "Scanning Vampy Plugins" << endl;
|
fazekasgy@37
|
203 scanner = PyPlugScanner::getInstance();
|
fazekasgy@37
|
204
|
fazekasgy@37
|
205 // added env. varable support VAMPY_EXTPATH
|
fazekasgy@37
|
206 pyPath=scanner->getAllValidPath();
|
fazekasgy@37
|
207 scanner->setPath(pyPath);
|
fazekasgy@37
|
208
|
fazekasgy@37
|
209 // added env. variable support:
|
fazekasgy@37
|
210 // VAMPY_COMPILED=1 to recognise .pyc files (default is 1)
|
fazekasgy@37
|
211 pyPlugs = scanner->getPyPlugs();
|
fazekasgy@37
|
212
|
fazekasgy@37
|
213 cerr << "Found " << pyPlugs.size() << " Scripts." << endl;
|
fazekasgy@37
|
214 //TODO: should this support multiple classes per script (?)
|
fazekasgy@37
|
215 pyClasses = scanner->getPyClasses();
|
fazekasgy@37
|
216 cerr << "Found " << pyClasses.size() << " Classes." << endl;
|
fazekasgy@37
|
217
|
fazekasgy@37
|
218 for (size_t i = 0; i < pyPlugs.size(); ++i) {
|
fazekasgy@37
|
219 adapters.push_back( new PyPluginAdapter(pyPlugs[i],pyClasses[i]));
|
fazekasgy@37
|
220 }
|
fazekasgy@37
|
221 pyExtensionManager.setPlugModuleNames(pyPlugs);
|
fazekasgy@37
|
222 pyExtensionManager.initExtension();
|
fazekasgy@37
|
223 array_API_initialiser();
|
fazekasgy@37
|
224 haveScannedPlugins=true;
|
fazekasgy@37
|
225 }
|
fazekasgy@37
|
226
|
fazekasgy@37
|
227 #ifdef _DEBUG
|
fazekasgy@37
|
228 cerr << "Accessing adapter index: " << index << " (adapters: " << adapters.size() << ")" << endl;
|
fazekasgy@37
|
229 #endif
|
fazekasgy@37
|
230
|
fazekasgy@37
|
231 if (index<adapters.size()) {
|
fazekasgy@37
|
232
|
fazekasgy@37
|
233 const VampPluginDescriptor *tmp = adapters[index]->getDescriptor();
|
fazekasgy@37
|
234
|
fazekasgy@37
|
235 if (adapters[index]->failed()) {
|
fazekasgy@37
|
236 cerr << "\nERROR: [in vampGetPluginDescriptor] Removing adapter of: \n'"
|
fazekasgy@37
|
237 << adapters[index]->getPlugKey() << "'\n"
|
fazekasgy@37
|
238 << "The plugin has failed to construct. Hint: Check __init__() function." << endl;
|
fazekasgy@37
|
239 pyExtensionManager.deleteModuleName(adapters[index]->getPlugKey());
|
fazekasgy@37
|
240 delete adapters[index];
|
fazekasgy@37
|
241 adapters.erase(adapters.begin()+index);
|
fazekasgy@37
|
242 return 0;
|
fazekasgy@37
|
243 }
|
fazekasgy@37
|
244
|
fazekasgy@37
|
245 return tmp;
|
fazekasgy@37
|
246
|
fazekasgy@37
|
247 } else return 0;
|
fazekasgy@37
|
248 }
|
fazekasgy@37
|
249
|
fazekasgy@37
|
250
|
fazekasgy@37
|
251
|
fazekasgy@37
|
252
|
fazekasgy@37
|
253
|
fazekasgy@37
|
254
|
fazekasgy@37
|
255
|
fazekasgy@37
|
256
|