From a7a594c4c3ca74eab4ae88fe2829df49b99dba0c Mon Sep 17 00:00:00 2001 From: jsiegle <jsiegle@mit.edu> Date: Sat, 16 Jun 2012 16:33:24 -0400 Subject: [PATCH] IntanThread now handles TTL inputs and generates its own timestamps. The IntanThread can report TTL events on its 6 input channels. This was tested with an Arduino; input detection is fast and reliable. The IntanThread also generates its own timestamps, allowing the software to move toward a framework in which timestamps come from input sources, rather than from the RecordNode. Finally, an ArduinoOutput module was added, which allows the software to communicate with an Arduino via serial output. --- Builds/Linux/Makefile | 6 + .../open-ephys.xcodeproj/project.pbxproj | 10 +- Builds/VisualStudio2010/open-ephys.vcxproj | 2 + .../open-ephys.vcxproj.filters | 6 + Source/Processors/ArduinoOutput.cpp | 131 ++++++++++++++++++ Source/Processors/ArduinoOutput.h | 75 ++++++++++ Source/Processors/DataThreads/DataBuffer.cpp | 26 +++- Source/Processors/DataThreads/DataBuffer.h | 8 +- Source/Processors/DataThreads/DataThread.h | 8 ++ Source/Processors/DataThreads/FPGAThread.cpp | 8 +- .../DataThreads/FileReaderThread.cpp | 5 +- Source/Processors/DataThreads/IntanThread.cpp | 36 +++-- Source/Processors/DataThreads/IntanThread.h | 3 +- Source/Processors/ProcessorGraph.cpp | 5 + Source/Processors/SourceNode.cpp | 40 +++++- Source/Processors/SourceNode.h | 6 + Source/UI/ProcessorList.cpp | 1 + open-ephys.jucer | 4 + 18 files changed, 358 insertions(+), 22 deletions(-) create mode 100644 Source/Processors/ArduinoOutput.cpp create mode 100644 Source/Processors/ArduinoOutput.h diff --git a/Builds/Linux/Makefile b/Builds/Linux/Makefile index 66d62dd8d..e92783d4d 100644 --- a/Builds/Linux/Makefile +++ b/Builds/Linux/Makefile @@ -63,6 +63,7 @@ OBJECTS := \ $(OBJDIR)/RootFinder_239a995f.o \ $(OBJDIR)/State_22979684.o \ $(OBJDIR)/AudioComponent_521bd9c9.o \ + $(OBJDIR)/ArduinoOutput_391e90c4.o \ $(OBJDIR)/Parameter_ae008024.o \ $(OBJDIR)/SpikeDisplayNode_9c52e4ad.o \ $(OBJDIR)/WiFiOutput_fa464ec5.o \ @@ -248,6 +249,11 @@ $(OBJDIR)/AudioComponent_521bd9c9.o: ../../Source/Audio/AudioComponent.cpp @echo "Compiling AudioComponent.cpp" @$(CXX) $(CXXFLAGS) -o "$@" -c "$<" +$(OBJDIR)/ArduinoOutput_391e90c4.o: ../../Source/Processors/ArduinoOutput.cpp + -@mkdir -p $(OBJDIR) + @echo "Compiling ArduinoOutput.cpp" + @$(CXX) $(CXXFLAGS) -o "$@" -c "$<" + $(OBJDIR)/Parameter_ae008024.o: ../../Source/Processors/Parameter.cpp -@mkdir -p $(OBJDIR) @echo "Compiling Parameter.cpp" diff --git a/Builds/MacOSX/open-ephys.xcodeproj/project.pbxproj b/Builds/MacOSX/open-ephys.xcodeproj/project.pbxproj index 1d6614e00..974c112c0 100644 --- a/Builds/MacOSX/open-ephys.xcodeproj/project.pbxproj +++ b/Builds/MacOSX/open-ephys.xcodeproj/project.pbxproj @@ -37,6 +37,7 @@ 8E138283FC265B58D252AAC3 = { isa = PBXBuildFile; fileRef = F4A53064BA75472765338C1D; }; EE1DC0B09AE0727BC7A5A99C = { isa = PBXBuildFile; fileRef = 0D20C3399D0492771F7A808A; }; 4ACF816CB5CDB285D8005AB8 = { isa = PBXBuildFile; fileRef = F74662D3D82975EDB5AD42E0; }; + AEB65E53845FA668D89CE15E = { isa = PBXBuildFile; fileRef = C42446F8ABB3627870E9677D; }; 717D108DC8B2379D556C4B2F = { isa = PBXBuildFile; fileRef = 751C52F2BEA7F1328ED13333; }; 1F67A9ACD509FB4DC5A633DF = { isa = PBXBuildFile; fileRef = 4AEDD076CCA918481C6F9CF2; }; B992DDBFF8928A985EEE1557 = { isa = PBXBuildFile; fileRef = 268005410FB62BCB9099A762; }; @@ -219,6 +220,8 @@ E27B5891A52FDAB2B00901A0 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Utilities.h; path = ../../Source/Dsp/Utilities.h; sourceTree = SOURCE_ROOT; }; F74662D3D82975EDB5AD42E0 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = AudioComponent.cpp; path = ../../Source/Audio/AudioComponent.cpp; sourceTree = SOURCE_ROOT; }; FA55B9FDE138CCB1F16BA905 = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AudioComponent.h; path = ../../Source/Audio/AudioComponent.h; sourceTree = SOURCE_ROOT; }; + C42446F8ABB3627870E9677D = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ArduinoOutput.cpp; path = ../../Source/Processors/ArduinoOutput.cpp; sourceTree = SOURCE_ROOT; }; + 5779673F042A62E02C4AC06B = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ArduinoOutput.h; path = ../../Source/Processors/ArduinoOutput.h; sourceTree = SOURCE_ROOT; }; 751C52F2BEA7F1328ED13333 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Parameter.cpp; path = ../../Source/Processors/Parameter.cpp; sourceTree = SOURCE_ROOT; }; 7B825983F25D8984E02F6FFB = { isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Parameter.h; path = ../../Source/Processors/Parameter.h; sourceTree = SOURCE_ROOT; }; 4AEDD076CCA918481C6F9CF2 = { isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = SpikeDisplayNode.cpp; path = ../../Source/Processors/SpikeDisplayNode.cpp; sourceTree = SOURCE_ROOT; }; @@ -552,6 +555,8 @@ 72123888A7DD78159AA032AF, 2164BFCDF57A5AA752CAA3A2 ); name = DataThreads; sourceTree = "<group>"; }; 33A88A7C3FF426F051834D6A = { isa = PBXGroup; children = ( + C42446F8ABB3627870E9677D, + 5779673F042A62E02C4AC06B, 751C52F2BEA7F1328ED13333, 7B825983F25D8984E02F6FFB, 4AEDD076CCA918481C6F9CF2, @@ -652,7 +657,7 @@ C3E8FB47D6069235EA9D6FD7 = { isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_BIT)"; PREBINDING = NO; - HEADER_SEARCH_PATHS = "/usr/local/include /usr/local/include/freetype2 $(inherited)"; + HEADER_SEARCH_PATHS = " $(inherited)"; GCC_OPTIMIZATION_LEVEL = 0; INFOPLIST_FILE = Info.plist; INSTALL_PATH = "$(HOME)/Applications"; @@ -670,7 +675,7 @@ 5D7484BAF16E272FF0E9EEAE = { isa = XCBuildConfiguration; buildSettings = { ARCHS = "$(ARCHS_STANDARD_32_BIT)"; PREBINDING = NO; - HEADER_SEARCH_PATHS = "/usr/local/include /usr/local/include/freetype2 $(inherited)"; + HEADER_SEARCH_PATHS = " $(inherited)"; GCC_OPTIMIZATION_LEVEL = 3; INFOPLIST_FILE = Info.plist; INSTALL_PATH = "$(HOME)/Applications"; @@ -741,6 +746,7 @@ 8E138283FC265B58D252AAC3, EE1DC0B09AE0727BC7A5A99C, 4ACF816CB5CDB285D8005AB8, + AEB65E53845FA668D89CE15E, 717D108DC8B2379D556C4B2F, 1F67A9ACD509FB4DC5A633DF, B992DDBFF8928A985EEE1557, diff --git a/Builds/VisualStudio2010/open-ephys.vcxproj b/Builds/VisualStudio2010/open-ephys.vcxproj index 3b1150b35..ad6aaeaa2 100644 --- a/Builds/VisualStudio2010/open-ephys.vcxproj +++ b/Builds/VisualStudio2010/open-ephys.vcxproj @@ -142,6 +142,7 @@ <ClCompile Include="..\..\Source\Dsp\RootFinder.cpp"/> <ClCompile Include="..\..\Source\Dsp\State.cpp"/> <ClCompile Include="..\..\Source\Audio\AudioComponent.cpp"/> + <ClCompile Include="..\..\Source\Processors\ArduinoOutput.cpp"/> <ClCompile Include="..\..\Source\Processors\Parameter.cpp"/> <ClCompile Include="..\..\Source\Processors\SpikeDisplayNode.cpp"/> <ClCompile Include="..\..\Source\Processors\WiFiOutput.cpp"/> @@ -239,6 +240,7 @@ <ClInclude Include="..\..\Source\Dsp\Types.h"/> <ClInclude Include="..\..\Source\Dsp\Utilities.h"/> <ClInclude Include="..\..\Source\Audio\AudioComponent.h"/> + <ClInclude Include="..\..\Source\Processors\ArduinoOutput.h"/> <ClInclude Include="..\..\Source\Processors\Parameter.h"/> <ClInclude Include="..\..\Source\Processors\SpikeDisplayNode.h"/> <ClInclude Include="..\..\Source\Processors\WiFiOutput.h"/> diff --git a/Builds/VisualStudio2010/open-ephys.vcxproj.filters b/Builds/VisualStudio2010/open-ephys.vcxproj.filters index a5a0e0bac..4ba9e2891 100644 --- a/Builds/VisualStudio2010/open-ephys.vcxproj.filters +++ b/Builds/VisualStudio2010/open-ephys.vcxproj.filters @@ -283,6 +283,9 @@ <ClCompile Include="..\..\Source\Audio\AudioComponent.cpp"> <Filter>open-ephys\Source\Audio</Filter> </ClCompile> + <ClCompile Include="..\..\Source\Processors\ArduinoOutput.cpp"> + <Filter>open-ephys\Source\Processors</Filter> + </ClCompile> <ClCompile Include="..\..\Source\Processors\Parameter.cpp"> <Filter>open-ephys\Source\Processors</Filter> </ClCompile> @@ -570,6 +573,9 @@ <ClInclude Include="..\..\Source\Audio\AudioComponent.h"> <Filter>open-ephys\Source\Audio</Filter> </ClInclude> + <ClInclude Include="..\..\Source\Processors\ArduinoOutput.h"> + <Filter>open-ephys\Source\Processors</Filter> + </ClInclude> <ClInclude Include="..\..\Source\Processors\Parameter.h"> <Filter>open-ephys\Source\Processors</Filter> </ClInclude> diff --git a/Source/Processors/ArduinoOutput.cpp b/Source/Processors/ArduinoOutput.cpp new file mode 100644 index 000000000..f2c2841e7 --- /dev/null +++ b/Source/Processors/ArduinoOutput.cpp @@ -0,0 +1,131 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2012 Open Ephys + + ------------------------------------------------------------------ + + 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 <http://www.gnu.org/licenses/>. + +*/ + +#include "ArduinoOutput.h" + +#include <stdio.h> +#include <unistd.h> /*UNIX standard function definitions */ +#include <termios.h> /*POSIX terminal control definitions */ +#include <fcntl.h> /*File control definitions */ + + +ArduinoOutput::ArduinoOutput() + : GenericProcessor("Arduino Output"), serialport("/dev/ttyACM0") +{ + +} + +ArduinoOutput::~ArduinoOutput() +{ + +} + +// AudioProcessorEditor* ArduinoOutput::createEditor() +// { +// editor = new ArduinoOutputEditor(this); +// return editor; +// } + +void ArduinoOutput::handleEvent(int eventType, MidiMessage& event) +{ + if (eventType == TTL) + { + std::cout << "Received event!" << std::endl; + + const char byte = 0; + write(handle, &byte, 1); + //startTimer((int) float(event.getTimeStamp())/getSampleRate()*1000.0); + } + +} + +void ArduinoOutput::setParameter (int parameterIndex, float newValue) +{ + +} + +bool ArduinoOutput::enable() +{ + struct termios toptions; + int fd; + + handle = open(serialport, O_RDWR | O_NOCTTY | O_NDELAY); + + if (handle == -1) + { + std::cout << "Arduino Output unable to open port." << std::endl; + return false; + } + + if (tcgetattr(handle, &toptions) < 0) + { + std::cout << "Arduino Output couldn't get term attributes" << std::endl; + return false; + } + + speed_t brate = B9600; + + cfsetispeed(&toptions, brate); + cfsetospeed(&toptions, brate); + + // 8N1 + toptions.c_cflag &= ~PARENB; + toptions.c_cflag &= ~CSTOPB; + toptions.c_cflag &= ~CSIZE; + toptions.c_cflag |= CS8; + // no flow control + toptions.c_cflag &= ~CRTSCTS; + + toptions.c_cflag |= CREAD | CLOCAL; // turn on READ & ignore ctrl lines + toptions.c_iflag &= ~(IXON | IXOFF | IXANY); // turn off s/w flow ctrl + + toptions.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); // make raw + toptions.c_oflag &= ~OPOST; // make raw + + // see: http://unixwiz.net/techtips/termios-vmin-vtime.html + toptions.c_cc[VMIN] = 0; + toptions.c_cc[VTIME] = 20; + + if( tcsetattr(handle, TCSANOW, &toptions) < 0) { + std::cout << "Arduino Output couldn't set term attributes" << std::endl; + return false; + } +} + +bool ArduinoOutput::disable() +{ + + + +} + +void ArduinoOutput::process(AudioSampleBuffer &buffer, + MidiBuffer &events, + int& nSamples) +{ + + + checkForEvents(events); + + +} \ No newline at end of file diff --git a/Source/Processors/ArduinoOutput.h b/Source/Processors/ArduinoOutput.h new file mode 100644 index 000000000..0d42717e6 --- /dev/null +++ b/Source/Processors/ArduinoOutput.h @@ -0,0 +1,75 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2012 Open Ephys + + ------------------------------------------------------------------ + + 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 <http://www.gnu.org/licenses/>. + +*/ + +#ifndef __ARDUINOOUTPUT_H_F7BDA585__ +#define __ARDUINOOUTPUT_H_F7BDA585__ + +#include "../../JuceLibraryCode/JuceHeader.h" + +#include "GenericProcessor.h" + +/** + + Provides a serial interface to an Arduino board. + + Based on arduino-serial.c (http://todbot.com/blog/2006/12/06/arduino-serial-c-code-to-talk-to-arduino/) + + @see GenericProcessor + +*/ + +class ArduinoOutput : public GenericProcessor +{ +public: + + ArduinoOutput(); + ~ArduinoOutput(); + + void process(AudioSampleBuffer &buffer, MidiBuffer &events, int& nSamples); + + void setParameter (int parameterIndex, float newValue); + + void handleEvent(int eventType, MidiMessage& event); + + bool enable(); + bool disable(); + + //AudioProcessorEditor* createEditor(); + + bool isSink() {return true;} + +private: + + //void timerCallback(); + int handle; + + const char* serialport; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ArduinoOutput); + +}; + + + + +#endif // __ARDUINOOUTPUT_H_F7BDA585__ diff --git a/Source/Processors/DataThreads/DataBuffer.cpp b/Source/Processors/DataThreads/DataBuffer.cpp index 0fa864fc6..acb6e8f79 100644 --- a/Source/Processors/DataThreads/DataBuffer.cpp +++ b/Source/Processors/DataThreads/DataBuffer.cpp @@ -24,7 +24,12 @@ #include "DataBuffer.h" DataBuffer::DataBuffer(int chans, int size) - : abstractFifo (size), buffer(chans, size), numChans(chans) {} + : abstractFifo (size), buffer(chans, size), numChans(chans) +{ + timestampBuffer = new int64[size]; + eventCodeBuffer = new int16[size]; + +} DataBuffer::~DataBuffer() {} @@ -33,7 +38,7 @@ void DataBuffer::clear() { buffer.clear(); } -void DataBuffer::addToBuffer(float* data, int numItems) { +void DataBuffer::addToBuffer(float* data, int64* timestamps, int16* eventCodes, int numItems) { // writes one sample for all channels int startIndex1, blockSize1, startIndex2, blockSize2; abstractFifo.prepareToWrite(numItems, startIndex1, blockSize1, startIndex2, blockSize2); @@ -45,6 +50,10 @@ void DataBuffer::addToBuffer(float* data, int numItems) { data + chan, // const float* source 1); // int num samples } + + *(timestampBuffer + startIndex1) = *timestamps; + *(eventCodeBuffer + startIndex1) = *eventCodes; + abstractFifo.finishedWrite(numItems); } @@ -53,7 +62,7 @@ int DataBuffer::getNumSamples() { } -int DataBuffer::readAllFromBuffer (AudioSampleBuffer& data, int maxSize) +int DataBuffer::readAllFromBuffer (AudioSampleBuffer& data, int64* timestamps, int16* eventCodes, int maxSize) { // check to see if the maximum size is smaller than the total number of available ints int numItems = (maxSize < abstractFifo.getNumReady()) ? @@ -63,6 +72,7 @@ int DataBuffer::readAllFromBuffer (AudioSampleBuffer& data, int maxSize) abstractFifo.prepareToRead(numItems, startIndex1, blockSize1, startIndex2, blockSize2); if (blockSize1 > 0) { + for (int chan = 0; chan < data.getNumChannels(); chan++) { data.copyFrom(chan, // destChan 0, // destStartSample @@ -71,11 +81,14 @@ int DataBuffer::readAllFromBuffer (AudioSampleBuffer& data, int maxSize) startIndex1, // sourceStartSample blockSize1); // numSamples } + + memcpy(timestamps, timestampBuffer+startIndex1, blockSize1*4); + memcpy(eventCodes, eventCodeBuffer+startIndex1, blockSize1*2); } - if (blockSize2 > 0) { + if (blockSize2 > 0) { - for (int chan = 0; chan < data.getNumChannels(); chan++) { + for (int chan = 0; chan < data.getNumChannels(); chan++) { data.copyFrom(chan, // destChan blockSize1, // destStartSample buffer, // source @@ -83,6 +96,9 @@ int DataBuffer::readAllFromBuffer (AudioSampleBuffer& data, int maxSize) startIndex2, // sourceStartSample blockSize2); // numSamples } + + memcpy(timestamps + blockSize1, timestampBuffer+startIndex2, blockSize2*4); + memcpy(eventCodes + blockSize1, eventCodeBuffer+startIndex2, blockSize2*2); } abstractFifo.finishedRead(numItems); diff --git a/Source/Processors/DataThreads/DataBuffer.h b/Source/Processors/DataThreads/DataBuffer.h index 290309b70..4992331ef 100644 --- a/Source/Processors/DataThreads/DataBuffer.h +++ b/Source/Processors/DataThreads/DataBuffer.h @@ -39,13 +39,17 @@ public: DataBuffer(int chans, int size); ~DataBuffer(); void clear(); - void addToBuffer(float* data, int numItems); + void addToBuffer(float* data, int64* ts, int16* eventCodes, int numItems); int getNumSamples(); - int readAllFromBuffer(AudioSampleBuffer& data, int maxSize); + int readAllFromBuffer(AudioSampleBuffer& data, int64* ts, int16* eventCodes, int maxSize); private: AbstractFifo abstractFifo; AudioSampleBuffer buffer; + + int64* timestampBuffer; + int16* eventCodeBuffer; + int numChans; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DataBuffer); diff --git a/Source/Processors/DataThreads/DataThread.h b/Source/Processors/DataThreads/DataThread.h index d5d9084e1..1c607cba3 100644 --- a/Source/Processors/DataThreads/DataThread.h +++ b/Source/Processors/DataThreads/DataThread.h @@ -60,11 +60,19 @@ public: virtual int getNumChannels() = 0; virtual float getSampleRate() = 0; virtual float getBitVolts() = 0; + virtual int getNumEventChannels() {return 0;} SourceNode* sn; + int16 eventCode; + int64 timestamp; + + Time timer; + private: + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (DataThread); diff --git a/Source/Processors/DataThreads/FPGAThread.cpp b/Source/Processors/DataThreads/FPGAThread.cpp index fba327097..4ccf07c43 100644 --- a/Source/Processors/DataThreads/FPGAThread.cpp +++ b/Source/Processors/DataThreads/FPGAThread.cpp @@ -59,6 +59,8 @@ FPGAThread::FPGAThread(SourceNode* sn) : DataThread(sn), dataBuffer = new DataBuffer(32, 10000); + eventCode = 0; + } @@ -173,7 +175,11 @@ bool FPGAThread::updateBuffer() { thisSample[n] = (float(samp) - 32768.0f)/92768.0f; } - dataBuffer->addToBuffer(thisSample,1); + + // should actually be converting timecode to timestamp: + timestamp = timer.getHighResolutionTicks(); + + dataBuffer->addToBuffer(thisSample, ×tamp, &eventCode, 1); } j++; // keep scanning for timecodes } diff --git a/Source/Processors/DataThreads/FileReaderThread.cpp b/Source/Processors/DataThreads/FileReaderThread.cpp index 91f1dc20c..be0eb49b3 100644 --- a/Source/Processors/DataThreads/FileReaderThread.cpp +++ b/Source/Processors/DataThreads/FileReaderThread.cpp @@ -43,7 +43,7 @@ FileReaderThread::FileReaderThread(SourceNode* sn) : DataThread(sn) dataBuffer = new DataBuffer(16, bufferSize*3); - + eventCode = 0; std::cout << "File Reader Thread initialized." << std::endl; @@ -117,7 +117,8 @@ bool FileReaderThread::updateBuffer() if (chan == 15) { - dataBuffer->addToBuffer(thisSample,1); + timestamp = timer.getHighResolutionTicks(); + dataBuffer->addToBuffer(thisSample, ×tamp, &eventCode, 1); chan = 0; } else { chan++; diff --git a/Source/Processors/DataThreads/IntanThread.cpp b/Source/Processors/DataThreads/IntanThread.cpp index 0d1d130ea..98efed098 100644 --- a/Source/Processors/DataThreads/IntanThread.cpp +++ b/Source/Processors/DataThreads/IntanThread.cpp @@ -34,10 +34,12 @@ IntanThread::IntanThread(SourceNode* sn) : DataThread(sn), { - dataBuffer = new DataBuffer(16,4096); + dataBuffer = new DataBuffer(17,4096); deviceFound = initializeUSB(true); + eventCode = 0; + } IntanThread::~IntanThread() @@ -51,6 +53,11 @@ int IntanThread::getNumChannels() return 16; } +int IntanThread::getNumEventChannels() +{ + return 6; +} + float IntanThread::getSampleRate() { return 25000.0; @@ -208,13 +215,13 @@ bool IntanThread::updateBuffer() ++ch; - for (int n = 0; n < 1; n++) { // + // for (int n = 0; n < 1; n++) { // // after accounting for bit volts: - thisSample[ch%16+n*16] = float((buffer[index] & 127) + + thisSample[ch%16] = float((buffer[index] & 127) + ((buffer[index+1] & 127) << 7) + ((buffer[index+2] & 3) << 14)) * 0.1907f - 6175.0f; - // these samples should now be in microvolts! + // these samples should now be in microvolts! // bit volt calculation: // 2.5 V range / 2^16 = 38.14 uV @@ -227,14 +234,27 @@ bool IntanThread::updateBuffer() // ((buffer[index+1] & 127) << 7) + // ((buffer[index+2] & 3) << 14) - 32768)/32768; - } - - TTLval = (buffer[index+2] & 4) >> 2; // extract TTL value (bit 3) + //} + + if (ch > 0 && ch < 7) // event channels + { + TTLval = (buffer[index+2] & 4) >> 2; // extract TTL value (bit 3) + + eventCode += (TTLval << (ch-1)); + + } + channelVal = buffer[index+2] & 60; // extract channel value if (channelVal == 60) { - dataBuffer->addToBuffer(thisSample,1); + + timestamp = timer.getHighResolutionTicks(); + + dataBuffer->addToBuffer(thisSample, ×tamp, &eventCode, 1); + + // reset values ch = -1; + eventCode = 0; } } diff --git a/Source/Processors/DataThreads/IntanThread.h b/Source/Processors/DataThreads/IntanThread.h index 254e2bbb3..4a7b17c5e 100644 --- a/Source/Processors/DataThreads/IntanThread.h +++ b/Source/Processors/DataThreads/IntanThread.h @@ -50,6 +50,7 @@ public: int getNumChannels(); float getSampleRate(); float getBitVolts(); + int getNumEventChannels(); private: @@ -68,7 +69,7 @@ private: unsigned char startCode, stopCode; unsigned char buffer[240]; // should be 5 samples per channel - float thisSample[16]; + float thisSample[17]; // 17 continuous channels and one event channel int ch; diff --git a/Source/Processors/ProcessorGraph.cpp b/Source/Processors/ProcessorGraph.cpp index f2c20aff0..b00bcc2d4 100644 --- a/Source/Processors/ProcessorGraph.cpp +++ b/Source/Processors/ProcessorGraph.cpp @@ -37,6 +37,7 @@ #include "SourceNode.h" #include "SpikeDetector.h" #include "WiFiOutput.h" +#include "ArduinoOutput.h" #include "Utilities/Splitter.h" #include "Utilities/Merger.h" #include "../UI/UIComponent.h" @@ -455,6 +456,10 @@ GenericProcessor* ProcessorGraph::createProcessorFromDescription(String& descrip std::cout << "Creating a WiFi node." << std::endl; processor = new WiFiOutput(); } + else if (subProcessorType.equalsIgnoreCase("Arduino Output")) { + std::cout << "Creating an Arduino node." << std::endl; + processor = new ArduinoOutput(); + } //sendActionMessage("New sink created."); } diff --git a/Source/Processors/SourceNode.cpp b/Source/Processors/SourceNode.cpp index 4e1e5aa0f..0a4f33b66 100644 --- a/Source/Processors/SourceNode.cpp +++ b/Source/Processors/SourceNode.cpp @@ -47,13 +47,26 @@ SourceNode::SourceNode(const String& name_) { enabledState(false); } + + numEventChannels = dataThread->getNumEventChannels(); + eventChannelState = new int[numEventChannels]; + for (int i = 0; i < numEventChannels; i++) + { + eventChannelState[i] = 0; + } + } else { enabledState(false); + numEventChannels = 0; } // check for input source every few seconds startTimer(sourceCheckInterval); + timestampBuffer = new int64[10000]; // 10000 samples per buffer max? + eventCodeBuffer = new int16[10000]; + + } SourceNode::~SourceNode() @@ -213,8 +226,33 @@ void SourceNode::process(AudioSampleBuffer &buffer, //std::cout << "SOURCE NODE" << std::endl; buffer.clear(); - nSamples = inputBuffer->readAllFromBuffer(buffer,buffer.getNumSamples()); + nSamples = inputBuffer->readAllFromBuffer(buffer, timestampBuffer, eventCodeBuffer, buffer.getNumSamples()); + // generate timestamp + + // generate events + + for (int i = 0; i < nSamples; i++) + { + for (int c = 0; c < numEventChannels; c++) + { + int state = eventCodeBuffer[i] & (1 << c); + + if (eventChannelState[c] != state) + { + if (state == 0) + std::cout << "Channel " << c << " off!" << std::endl; + else + addEvent(events, TTL, state); + //std::cout << "Channel " << c << " on!" << std::endl; + + eventChannelState[c] = state; + } + } + } + + //std::cout << *eventCodeBuffer << std::endl; + } diff --git a/Source/Processors/SourceNode.h b/Source/Processors/SourceNode.h index be72bcd8b..3a0250029 100644 --- a/Source/Processors/SourceNode.h +++ b/Source/Processors/SourceNode.h @@ -77,6 +77,8 @@ public: private: + int numEventChannels; + int sourceCheckInterval; bool wasDisabled; @@ -86,6 +88,10 @@ private: ScopedPointer<DataThread> dataThread; DataBuffer* inputBuffer; + int64* timestampBuffer; + int16* eventCodeBuffer; + int* eventChannelState; + void updateSettings(); int* numSamplesInThisBuffer; diff --git a/Source/UI/ProcessorList.cpp b/Source/UI/ProcessorList.cpp index 5f135a2c0..fa34aae64 100644 --- a/Source/UI/ProcessorList.cpp +++ b/Source/UI/ProcessorList.cpp @@ -59,6 +59,7 @@ ProcessorList::ProcessorList() : isDragging(false), sinks->addSubItem(new ProcessorListItem("LFP Viewer")); sinks->addSubItem(new ProcessorListItem("Spike Viewer")); sinks->addSubItem(new ProcessorListItem("WiFi Output")); + sinks->addSubItem(new ProcessorListItem("Arduino Output")); ProcessorListItem* utilities = new ProcessorListItem("Utilities"); utilities->addSubItem(new ProcessorListItem("Splitter")); diff --git a/open-ephys.jucer b/open-ephys.jucer index 314b3828c..7d7ee7dba 100644 --- a/open-ephys.jucer +++ b/open-ephys.jucer @@ -187,6 +187,10 @@ file="Source/Audio/AudioComponent.h"/> </GROUP> <GROUP id="yQmqZWk" name="Processors"> + <FILE id="wBm4y2" name="ArduinoOutput.cpp" compile="1" resource="0" + file="Source/Processors/ArduinoOutput.cpp"/> + <FILE id="1sbSwS7" name="ArduinoOutput.h" compile="0" resource="0" + file="Source/Processors/ArduinoOutput.h"/> <FILE id="pQaYQiE" name="Parameter.cpp" compile="1" resource="0" file="Source/Processors/Parameter.cpp"/> <FILE id="0jxvc4H" name="Parameter.h" compile="0" resource="0" file="Source/Processors/Parameter.h"/> <FILE id="arRy5R" name="SpikeDisplayNode.cpp" compile="1" resource="0" -- GitLab