/*
------------------------------------------------------------------
Python Plugin
Copyright (C) 2016 FP Battaglia
based on
Open Ephys GUI
Copyright (C) 2013, 2015 Open Ephys
------------------------------------------------------------------
v
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
/*
==============================================================================
PythonPlugin.cpp
Created: 13 Jun 2014 5:56:17pm
Author: fpbatta
==============================================================================
*/
#include "PythonPlugin.h"
#include "PythonPlugin.h"
#include "PythonEditor.h"
#include
#include
#include
#ifdef DEBUG
#define PYTHON_DEBUG
#endif
#ifdef PYTHON_DEBUG
#if defined(__linux__)
#include
#include
#else
#include
#endif
#endif
PythonPlugin::PythonPlugin(const String &processorName)
: GenericProcessor(processorName) //, threshold(200.0), state(true)
{
//parameters.add(Parameter("thresh", 0.0, 500.0, 200.0, 0));
filePath = "";
plugin = 0;
#define QUOTE(name) #name
#define STR(macro) QUOTE(macro)
#define PYTHON_HOME_NAME STR(PYTHON_HOME)
char * old_python_home = getenv("PYTHONHOME");
if (old_python_home == NULL)
{
#ifdef PYTHON_DEBUG
std::cout << "setting PYTHONHOME" << std::endl;
#endif
setenv("PYTHONHOME", PYTHON_HOME_NAME, 1);
}
// setenv("PYTHONHOME", "/usr/local/anaconda", 1); // FIXME hardcoded PYTHONHOME!
#ifdef PYTHON_DEBUG
std::cout << "PYTHONHOME: " << getenv("PYTHONHOME") << std::endl;
#endif
#ifdef PYTHON_DEBUG
#if defined(__linux__)
pid_t tid;
tid = syscall(SYS_gettid);
#else
uint64_t tid;
pthread_threadid_np(NULL, &tid);
#endif
std::cout << "in constructor pthread_threadid_np()=" << tid << std::endl;
#endif
#if PY_MAJOR_VERSION==3
Py_SetProgramName ((wchar_t *)"PythonPlugin");
#else
Py_SetProgramName ((char *)"PythonPlugin");
#endif
Py_Initialize ();
PyEval_InitThreads();
PyRun_SimpleString("import sys");
PyRun_SimpleString("sys.setcheckinterval(10000)");
#ifdef PYTHON_DEBUG
std::cout << Py_GetPrefix() << std::endl;
std::cout << Py_GetVersion() << std::endl;
#endif
GUIThreadState = PyEval_SaveThread();
}
PythonPlugin::~PythonPlugin()
{
dlclose(plugin);
}
void PythonPlugin::createEventChannels()
{
EventChannel* ev = new EventChannel(EventChannel::TTL, 8, 1, CoreServices::getGlobalSampleRate(), this);
ev->setName("Python events");
ev->setDescription("Events generated by a Python plugin");
String identifier = "dataderived.python";
ev->setIdentifier(identifier);
MetaDataDescriptor md(MetaDataDescriptor::CHAR, 25, "Plugin type", "Description of the plugin", "channelInfo.extra");
MetaDataValue mv(md);
mv.setValue("Python plugin");
ev->addMetaData(md, mv);
eventChannelArray.add(ev);
ttlChannel = ev;
std::cout << "Python event channel created" << std::endl;
}
AudioProcessorEditor* PythonPlugin::createEditor()
{
// std::cout << "in PythonEditor::createEditor()" << std::endl;
editor = new PythonEditor(this, true);
return editor;
}
bool PythonPlugin::isReady()
{
#ifdef PYTHON_DEBUG
#if defined(__linux__)
pid_t tid;
tid = syscall(SYS_gettid);
#else
uint64_t tid;
pthread_threadid_np(NULL, &tid);
#endif
std::cout << "in isReady pthread_threadid_np()=" << tid << std::endl;
#endif
bool ret;
PyEval_RestoreThread(GUIThreadState);
if (plugin == 0 )
{
// sendActionMessage("No plugin selected in Python Plugin."); // FIXME how to send error message?
ret = false;
}
else if (pluginIsReady && !(*pluginIsReady)())
{
// sendActionMessage("Plugin is not ready"); // FIXME how to send error message?
ret = false;
}
else
{
ret = true;
}
GUIThreadState = PyEval_SaveThread();
return ret;
}
void PythonPlugin::setParameter(int parameterIndex, float newValue)
{
editor->updateParameterButtons(parameterIndex);
//Parameter& p = parameters.getReference(parameterIndex);
//p.setValue(newValue, 0);
//threshold = newValue;
//std::cout << float(p[0]) << std::endl;
editor->updateParameterButtons(parameterIndex);
}
void PythonPlugin::resetConnections()
{
#ifdef PYTHON_DEBUG
#if defined(__linux__)
pid_t tid;
tid = syscall(SYS_gettid);
#else
uint64_t tid;
pthread_threadid_np(NULL, &tid);
#endif
std::cout << "in resetConnection pthread_threadid_np()=" << tid << std::endl;
#endif
nextAvailableChannel = 0;
wasConnected = false;
#ifdef PYTHON_DEBUG
std::cout << "resetting ThreadState, which was " << processThreadState << std::endl;
#endif
processThreadState = 0;
}
void PythonPlugin::process(AudioSampleBuffer& buffer)
{
checkForEvents();
#ifdef PYTHON_DEBUG
#if defined(__linux__)
pid_t tid;
tid = syscall(SYS_gettid);
#else
uint64_t tid;
pthread_threadid_np(NULL, &tid);
#endif
// std::cout << "in process pthread_threadid_np()=" << tid << std::endl;
#endif
if(!processThreadState)
{
//DEBUG
PyThreadState *nowState;
nowState = PyGILState_GetThisThreadState();
#ifdef PYTHON_DEBUG
std::cout << "currentState: " << nowState << std::endl;
std::cout << "initialiting ThreadState" << std::endl;
#endif
if(nowState) //UGLY HACK!!!
{
processThreadState = nowState;
}
else
{
processThreadState = PyThreadState_New(GUIThreadState->interp);
}
if(!processThreadState)
std::cout << "ThreadState is Null!" << std::endl;
}
PyEval_RestoreThread(processThreadState);
PythonEvent *pyEvents = (PythonEvent *)calloc(1, sizeof(PythonEvent));
pyEvents->type = 0; // this marks an empty event
#ifdef PYTHON_DEBUG
// std::cout << "in process, trying to acquire lock" << std::endl;
#endif
// PyEval_InitThreads();
//
// std::cout << "in process, threadstate: " << PyGILState_GetThisThreadState() << std::endl;
// PyGILState_STATE gstate;
// gstate = PyGILState_Ensure();
// std::cout << "in process, lock acquired" << std::endl;
(*pluginFunction)(*(buffer.getArrayOfWritePointers()), buffer.getNumChannels(), buffer.getNumSamples(), getNumSamples(0), pyEvents);
// PyGILState_Release(gstate);
// std::cout << "in process, lock released" << std::endl;
if(wasTriggered)
{
uint8 ttlData = 0;
// std::cout << "in Python plugin resetting channel: " << lastChan << std::endl;
TTLEventPtr event = TTLEvent::createTTLEvent(ttlChannel, getTimestamp(0),
&ttlData, sizeof(uint8), lastChan);
addEvent(ttlChannel, event, 0);
wasTriggered = false;
}
if(pyEvents->type != 0)
{
#ifdef PYTHON_DEBUG
// std::cout << "Event emitted " << (int)pyEvents->type << std::endl;
#endif
// uint8 ttlData = 1 << module.outputChan;
// TTLEventPtr event = TTLEvent::createTTLEvent(moduleEventChannels[m], getTimestamp(module.inputChan) + i, &ttlData, sizeof(uint8), module.outputChan);
// addEvent(moduleEventChannels[m], event, i);
lastChan = (uint16)pyEvents->eventId;
uint8 ttlData = 1 << lastChan;
// std::cout << "in Python plugin ts is " << getTimestamp(0) + pyEvents->sampleNum << " and sampleNum is " <<
// pyEvents->sampleNum << " eventId: " << uint16(pyEvents->eventId) << " ttlData: " << int(ttlData) << std::endl;
// FIXME now we set the ts at the first samble in the block
TTLEventPtr event = TTLEvent::createTTLEvent(ttlChannel, getTimestamp(0) + pyEvents->sampleNum,
&ttlData, sizeof(uint8), lastChan);
addEvent(ttlChannel, event, pyEvents->sampleNum);
PythonEvent *lastEvent = pyEvents;
PythonEvent *nextEvent = lastEvent->nextEvent;
free((void *)lastEvent);
wasTriggered = true;
// std::cout << "lastChan is " << lastChan << std::endl;
while (nextEvent) {
lastChan = (uint16)nextEvent->eventId;
uint8 ttlData = 1 << lastChan;
// std::cout << "in Python plugin ts is " << getTimestamp(0) << " and sampleNum is " <<
// nextEvent->sampleNum << " ttlData: " << ttlData << std::endl;
TTLEventPtr event = TTLEvent::createTTLEvent(ttlChannel, getTimestamp(0) + nextEvent->sampleNum,
&ttlData, sizeof(uint8), lastChan);
addEvent(ttlChannel, event, nextEvent->sampleNum);
lastEvent = nextEvent;
nextEvent = nextEvent->nextEvent;
free((void *)lastEvent);
wasTriggered = true;
}
}
processThreadState = PyEval_SaveThread();
#ifdef PYTHON_DEBUG
// std::cout << "Thread saved" << std::endl;
#endif
}
/* The complete API that the Cython plugin has to expose is
void pluginStartup(void): a function to initialize the plugin data structures prior to start ACQ
int isReady(void): a boolean function telling the processor whether the plugin is ready to receive data
int getParamNum(void) get the number of parameters that the plugin takes
ParamConfig *getParamConfig(void) this will allow generating the editor GUI TODO
void setIntParameter(char *name, int value) set integer parameter
void set FloatParameter(char *name, float value) set float parameter
*/
void PythonPlugin::setFile(String fullpath)
{
#ifdef PYTHON_DEBUG
#if defined(__linux__)
pid_t tid;
tid = syscall(SYS_gettid);
#else
uint64_t tid;
pthread_threadid_np(NULL, &tid);
#endif
std::cout << "in setFile pthread_threadid_np()=" << tid << std::endl;
#endif
filePath = fullpath;
const char* path = filePath.getCharPointer();
plugin = dlopen(path, RTLD_LAZY);
if (!plugin)
{
std::cout << "Can't open plugin "
<< '"' << path << "\""
<< dlerror()
<< std::endl;
return;
}
String initPlugin = filePath.fromLastOccurrenceOf(String("/"), false, true);
initPlugin = initPlugin.upToFirstOccurrenceOf(String("."), false, true);
#if PY_MAJOR_VERSION>=3
String initPluginName = String("PyInit_");
#else
String initPluginName = String("init");
#endif
initPluginName.append(initPlugin, 200);
std::cout << "init function is: " << initPluginName << std::endl;
void *initializer = dlsym(plugin,initPluginName.getCharPointer());
#ifdef PYTHON_DEBUG
std::cout << "initializer: " << initializer << std::endl;
#endif
if (!initializer)
{
std::cout << "Can't find init function in plugin "
<< '"' << path << "\"" << std::endl
<< dlerror()
<< std::endl;
plugin = 0;
return;
}
initfunc_t initF = (initfunc_t) initializer;
void *cfunc = dlsym(plugin,"pluginisready");
if (!cfunc)
{
std::cout << "Can't find ready function in plugin "
<< '"' << path << "\"" << std::endl
<< dlerror()
<< std::endl;
plugin = 0;
return;
}
pluginIsReady = (isreadyfunc_t)cfunc;
cfunc = dlsym(plugin,"pluginStartup");
if (!cfunc)
{
std::cout << "Can't find startup function in plugin "
<< '"' << path << "\"" << std::endl
<< dlerror()
<< std::endl;
plugin = 0;
return;
}
pluginStartupFunction = (startupfunc_t)cfunc;
cfunc = dlsym(plugin,"getParamNum");
if (!cfunc)
{
std::cout << "Can't find getParamNum function in plugin "
<< '"' << path << "\"" << std::endl
<< dlerror()
<< std::endl;
plugin = 0;
return;
}
getParamNumFunction = (getparamnumfunc_t)cfunc;
cfunc = dlsym(plugin,"getParamConfig");
if (!cfunc)
{
std::cout << "Can't find getParamNum function in plugin "
<< '"' << path << "\"" << std::endl
<< dlerror()
<< std::endl;
// plugin = 0;
// return;
}
getParamConfigFunction = (getparamconfigfunc_t)cfunc;
cfunc = dlsym(plugin,"pluginFunction");
// std::cout << "plugin: " << cfunc << std::endl;
if (!cfunc)
{
std::cout << "Can't find plugin function in plugin "
<< '"' << path << "\"" << std::endl
<< dlerror()
<< std::endl;
plugin = 0;
return;
}
pluginFunction = (pluginfunc_t)cfunc;
cfunc = dlsym(plugin,"setIntParam");
// std::cout << "plugin: " << cfunc << std::endl;
if (!cfunc)
{
std::cout << "Can't find setIntParam function in plugin "
<< '"' << path << "\"" << std::endl
<< dlerror()
<< std::endl;
plugin = 0;
return;
}
setIntParamFunction = (setintparamfunc_t)cfunc;
cfunc = dlsym(plugin,"setFloatParam");
// std::cout << "plugin: " << cfunc << std::endl;
if (!cfunc)
{
std::cout << "Can't find setFloatParam function in plugin "
<< '"' << path << "\"" << std::endl
<< dlerror()
<< std::endl;
plugin = 0;
return;
}
setFloatParamFunction = (setfloatparamfunc_t)cfunc;
cfunc = dlsym(plugin, "getIntParam");
// std::cout << "plugin: " << cfunc << std::endl;
if (!cfunc)
{
std::cout << "Can't find getIntParam function in plugin "
<< '"' << path << "\"" << std::endl
<< dlerror()
<< std::endl;
plugin = 0;
return;
}
getIntParamFunction = (getintparamfunc_t)cfunc;
cfunc = dlsym(plugin, "getFloatParam");
// std::cout << "plugin: " << cfunc << std::endl;
if (!cfunc)
{
std::cout << "Can't find getFloatParam function in plugin "
<< '"' << path << "\"" << std::endl
<< dlerror()
<< std::endl;
plugin = 0;
return;
}
getFloatParamFunction = (getfloatparamfunc_t)cfunc;
// now the API should be fully loaded
PyEval_RestoreThread(GUIThreadState);
// initialize the plugin
#ifdef PYTHON_DEBUG
std::cout << "before initplugin" << std::endl; // DEBUG
#endif
(*initF)();
#ifdef PYTHON_DEBUG
std::cout << "after initplugin" << std::endl; // DEBUG
#endif
(*pluginStartupFunction)(getSampleRate());
// load the parameter configuration
numPythonParams = (*getParamNumFunction)();
#ifdef PYTHON_DEBUG
std::cout << "the plugin wants " << numPythonParams
<< " parameters" << std::endl;
#endif
params = (ParamConfig *)calloc(numPythonParams, sizeof(ParamConfig));
paramsControl = (Component **)calloc(numPythonParams, sizeof(Component *));
(*getParamConfigFunction)(params);
#ifdef PYTHON_DEBUG
std::cout << "release paramconfig" << std::endl;
#endif
for(int i = 0; i < numPythonParams; i++)
{
#ifdef PYTHON_DEBUG
std::cout << "param " << i << " is a " << params[i].type << std::endl;
std::cout << "it is named: " << params[i].name << std::endl << std::endl;
#endif
switch (params[i].type) {
case TOGGLE:
paramsControl[i] = dynamic_cast(getEditor())->addToggleButton(String(params[i].name), params[i].isEnabled);
break;
case INT_SET:
paramsControl[i] = dynamic_cast(getEditor())->addComboBox(String(params[i].name), params[i].nEntries, params[i].entries);
break;
case FLOAT_RANGE:
paramsControl[i] = dynamic_cast(getEditor())->addSlider(String(params[i].name), params[i].rangeMin, params[i].rangeMax, params[i].startValue);
break;
default:
break;
}
}
GUIThreadState = PyEval_SaveThread();
}
String PythonPlugin::getFile()
{
return filePath;
}
void PythonPlugin::updateSettings()
{
}
void PythonPlugin::setIntPythonParameter(String name, int value)
{
#ifdef PYTHON_DEBUG
#if defined(__linux__)
pid_t tid;
tid = syscall(SYS_gettid);
#else
uint64_t tid;
pthread_threadid_np(NULL, &tid);
#endif
std::cout << "in setintparam pthread_threadid_np()=" << tid << std::endl;
#endif
PyEval_RestoreThread(GUIThreadState);
(*setIntParamFunction)(name.getCharPointer().getAddress(), value);
GUIThreadState = PyEval_SaveThread();
}
void PythonPlugin::setFloatPythonParameter(String name, float value)
{
#ifdef PYTHON_DEBUG
#if defined(__linux__)
pid_t tid;
tid = syscall(SYS_gettid);
#else
uint64_t tid;
pthread_threadid_np(NULL, &tid);
#endif
std::cout << "in setfloatparam pthread_threadid_np()=" << tid << std::endl;
#endif
PyEval_RestoreThread(GUIThreadState);
(*setFloatParamFunction)(name.getCharPointer().getAddress(), value);
GUIThreadState = PyEval_SaveThread();
}
int PythonPlugin::getIntPythonParameter(String name)
{
#ifdef PYTHON_DEBUG
#if defined(__linux__)
pid_t tid;
tid = syscall(SYS_gettid);
#else
uint64_t tid;
pthread_threadid_np(NULL, &tid);
#endif
std::cout << "in getintparam pthread_threadid_np()=" << tid << std::endl;
#endif
int value;
PyEval_RestoreThread(GUIThreadState);
value = (*getIntParamFunction)(name.getCharPointer().getAddress());
GUIThreadState = PyEval_SaveThread();
return value;
}
float PythonPlugin::getFloatPythonParameter(String name)
{
#ifdef PYTHON_DEBUG
#if defined(__linux__)
pid_t tid;
tid = syscall(SYS_gettid);
#else
uint64_t tid;
pthread_threadid_np(NULL, &tid);
#endif
std::cout << "in getfloatparam pthread_threadid_np()=" << tid << std::endl;
#endif
PyEval_RestoreThread(GUIThreadState);
float value;
value = (*getFloatParamFunction)(name.getCharPointer().getAddress());
GUIThreadState = PyEval_SaveThread();
return value;
}