Skip to content

Commit 5839e52

Browse files
committed
Add support for sending and receiving events
Fix conda-numpy import error on Windows
1 parent baa0a35 commit 5839e52

File tree

4 files changed

+112
-7
lines changed

4 files changed

+112
-7
lines changed

Modules/examples/bandpass_filter.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import numpy as np
22
from scipy import signal
3+
import oe_pyprocessor
34

45
def butter_bandpass(lowcut, highcut, sample_rate, order=2):
56
"""
@@ -33,11 +34,12 @@ def butter_bandpass_filter(sos, data):
3334

3435
class PyProcessor:
3536

36-
def __init__(self, num_channels, sample_rate):
37+
def __init__(self, processor, num_channels, sample_rate):
3738
""" A new bandpass filter is initialized whenever the plugin settings are updated """
3839

3940
self.num_chans = num_channels
4041
self.sample_rate = sample_rate
42+
self.processor = processor
4143

4244
self.sos = []
4345
sos_t = butter_bandpass(500, 2000, sample_rate)
@@ -67,6 +69,19 @@ def stop_acquisition(self):
6769
""" Called when acquisition is stopped """
6870
pass
6971

72+
def handle_ttl_event(self, source_node, channel, sample_number, line, state):
73+
"""
74+
Handle each incoming ttl event.
75+
76+
Parameters:
77+
source_node (int): id of the processor this event was generated from
78+
channel (str): name of the event channel
79+
sample_number (int): sample number of the event
80+
line (int): the line on which event was generated (0-255)
81+
state (bool): event state true (ON) or false (OFF)
82+
"""
83+
pass
84+
7085
def start_recording(self, recording_dir):
7186
"""
7287
Called when recording starts

Modules/template/processor_template.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
import numpy as np
2+
import oe_pyprocessor
23
# Add additional imports here
34

45
class PyProcessor:
56

6-
def __init__(self, num_channels, sample_rate):
7+
def __init__(self, processor, num_channels, sample_rate):
78
"""
89
A new processor is initialized whenever the plugin settings are updated
910
1011
Parameters:
12+
processor (object): Python Processor class object used for adding events from python.
1113
num_channels (int): number of input channels in the selected stream.
1214
sample_rate (float): sample rate of the selected stream
1315
"""
@@ -43,7 +45,7 @@ def handle_ttl_event(self, source_node, channel, sample_number, line, state):
4345
channel (str): name of the event channel
4446
sample_number (int): sample number of the event
4547
line (int): the line on which event was generated (0-255)
46-
state (int): event state 0 (OFF) or 1 (ON)
48+
state (bool): event state True (ON) or False (OFF)
4749
"""
4850
pass
4951

Source/PythonProcessor.cpp

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
2929

3030
namespace py = pybind11;
3131

32+
PYBIND11_EMBEDDED_MODULE(oe_pyprocessor, module){
33+
34+
py::class_<PythonProcessor> (module, "PythonProcessor")
35+
.def("add_python_event", &PythonProcessor::addPythonEvent);
36+
}
3237

3338
PythonProcessor::PythonProcessor()
3439
: GenericProcessor("Python Processor")
@@ -74,6 +79,30 @@ void PythonProcessor::updateSettings()
7479
{
7580
if (getDataStreams().size() == 0)
7681
currentStream = 0;
82+
83+
localEventChannels.clear();
84+
85+
for (auto stream : getDataStreams())
86+
{
87+
const uint16 streamId = stream->getStreamId();
88+
89+
// TTL Channel
90+
EventChannel* ttlChan;
91+
EventChannel::Settings ttlChannelSettings{
92+
EventChannel::Type::TTL,
93+
"Python Processor Output",
94+
"TTL event triggerd by the python module.",
95+
"pythonprocessor.ttl",
96+
getDataStream(stream->getStreamId())
97+
};
98+
99+
ttlChan = new EventChannel(ttlChannelSettings);
100+
ttlChan->addProcessor(processorInfo.get());
101+
eventChannels.add(ttlChan);
102+
103+
localEventChannels[streamId] = eventChannels.getLast();
104+
105+
}
77106
}
78107

79108
void PythonProcessor::initialize(bool signalChainIsLoading)
@@ -93,12 +122,13 @@ void PythonProcessor::process(AudioBuffer<float>& buffer)
93122

94123
checkForEvents(true);
95124

125+
int64 sampleNum = getFirstSampleNumberForBlock(currentStream);
126+
96127
for (auto stream : getDataStreams())
97128
{
98129

99130
if (stream->getStreamId() == currentStream)
100131
{
101-
102132
const uint16 streamId = stream->getStreamId();
103133

104134
const int numSamples = getNumSamplesInBlock(streamId);
@@ -135,6 +165,16 @@ void PythonProcessor::process(AudioBuffer<float>& buffer)
135165
// py::gil_scoped_release release;
136166

137167
}
168+
169+
{
170+
ScopedLock TTLlock(TTLqueueLock);
171+
while (!TTLQueue.empty())
172+
{
173+
const StringTTL& TTLmsg = TTLQueue.front();
174+
triggerTTLEvent(TTLmsg, sampleNum);
175+
TTLQueue.pop();
176+
}
177+
}
138178
}
139179
}
140180

@@ -148,7 +188,7 @@ void PythonProcessor::handleTTLEvent(TTLEventPtr event)
148188
const int sourceNodeId = chanInfo->getSourceNodeId();
149189
const int64 sampleNumber = event->getSampleNumber();
150190
const uint8 line = event->getLine();
151-
const int state = event->getState() ? 1 : 0;
191+
const bool state = event->getState();
152192

153193
// Give to python
154194
// py::gil_scoped_acquire acquire;
@@ -180,6 +220,29 @@ void PythonProcessor::handleTTLEvent(TTLEventPtr event)
180220

181221
// }
182222

223+
void PythonProcessor::addPythonEvent(int line, bool state)
224+
{
225+
// LOGC("[PYTHON] Event received!! Line: ", line, " | State: ", state);
226+
{
227+
ScopedLock TTLlock(TTLqueueLock);
228+
if (CoreServices::getAcquisitionStatus())
229+
{
230+
TTLQueue.push({ line, state });
231+
}
232+
}
233+
}
234+
235+
void PythonProcessor::triggerTTLEvent(StringTTL TTLmsg, juce::int64 sampleNum)
236+
{
237+
TTLEventPtr event =
238+
TTLEvent::createTTLEvent(localEventChannels[currentStream],
239+
sampleNum,
240+
TTLmsg.eventLine,
241+
TTLmsg.state);
242+
addEvent(event, 0);
243+
244+
}
245+
183246
bool PythonProcessor::startAcquisition()
184247
{
185248
if (moduleReady)
@@ -346,13 +409,20 @@ bool PythonProcessor::initInterpreter(String pythonHome)
346409
py::gil_scoped_acquire acquire;
347410

348411
if(Py_IsInitialized() > 0)
412+
{
349413
LOGC("Python Interpreter initialized successfully! Python Home: ", String(Py_GetPythonHome()));
414+
415+
#if JUCE_WINDOWS
416+
py::module_ os = py::module_::import("os");
417+
os.attr("add_dll_directory")
418+
(targetFolder.getChildFile("Library\\bin").getFullPathName().toWideCharPointer());
419+
#endif
420+
}
350421

351422
py::module sys = py::module::import("sys");
352423
py::list path = sys.attr("path");
353424

354425
LOGD("Python sys paths:")
355-
// Check if the path was added successfully
356426
for (auto p : path) {
357427
LOGD(p.cast<std::string>());
358428
}
@@ -472,7 +542,7 @@ void PythonProcessor::initModule()
472542
}
473543

474544
try {
475-
pyObject = new py::object(pyModule->attr("PyProcessor")(numChans, sampleRate));
545+
pyObject = new py::object(pyModule->attr("PyProcessor")(this, numChans, sampleRate));
476546
}
477547

478548
catch (std::exception& exc)

Source/PythonProcessor.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,12 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
2424
#define PYTHONPROCESSOR_H_DEFINED
2525

2626
#include <ProcessorHeaders.h>
27+
#include <pybind11/pybind11.h>
2728
#include <pybind11/embed.h>
2829
#include <pybind11/numpy.h>
2930

31+
#include <queue>
32+
3033
#include "PythonProcessorEditor.h"
3134

3235
namespace py = pybind11;
@@ -60,6 +63,17 @@ class PythonProcessor : public GenericProcessor
6063
/** Stream to process*/
6164
uint16 currentStream;
6265

66+
std::map<uint16, EventChannel*> localEventChannels;
67+
68+
struct StringTTL
69+
{
70+
int eventLine;
71+
bool state;
72+
};
73+
74+
std::queue<StringTTL> TTLQueue;
75+
CriticalSection TTLqueueLock;
76+
6377
/**Check whether data stream exists */
6478
bool streamExists(uint16 streamId);
6579

@@ -101,6 +115,10 @@ class PythonProcessor : public GenericProcessor
101115
// Called automatically whenever a broadcast message is sent through the signal chain */
102116
// void handleBroadcastMessage(String message) override;
103117

118+
void addPythonEvent(int line, bool state);
119+
120+
void triggerTTLEvent(StringTTL TTLmsg, juce::int64 sampleNum);
121+
104122
/** Called at the start of acquisition.*/
105123
bool startAcquisition() override;
106124

0 commit comments

Comments
 (0)