diff --git a/Builds/MacOSX/Info.plist b/Builds/MacOSX/Info.plist index 148cb10fe1c0290781bbdaa6473adfa3baf907c8..52711770d36a1a9273f881e6894f46fc86f3f798 100644 --- a/Builds/MacOSX/Info.plist +++ b/Builds/MacOSX/Info.plist @@ -16,9 +16,9 @@ <key>CFBundleSignature</key> <string>????</string> <key>CFBundleShortVersionString</key> - <string>0.3.5</string> + <string>0.3.6</string> <key>CFBundleVersion</key> - <string>0.3.5</string> + <string>0.3.6</string> <key>NSHumanReadableCopyright</key> <string>Open Ephys</string> <key>NSHighResolutionCapable</key> diff --git a/Builds/VisualStudio2012/resources.rc b/Builds/VisualStudio2012/resources.rc index f8e9fb7b27f1fe31f745a58a2ab83b88b2b29263..2ec7e37977549fe56273d5725bb997199bd8b7b8 100644 --- a/Builds/VisualStudio2012/resources.rc +++ b/Builds/VisualStudio2012/resources.rc @@ -7,7 +7,7 @@ #include <windows.h> VS_VERSION_INFO VERSIONINFO -FILEVERSION 0,3,5,0 +FILEVERSION 0,3,6,0 BEGIN BLOCK "StringFileInfo" BEGIN @@ -15,9 +15,9 @@ BEGIN BEGIN VALUE "CompanyName", "Open Ephys\0" VALUE "FileDescription", "open-ephys\0" - VALUE "FileVersion", "0.3.5\0" + VALUE "FileVersion", "0.3.6\0" VALUE "ProductName", "open-ephys\0" - VALUE "ProductVersion", "0.3.5\0" + VALUE "ProductVersion", "0.3.6\0" END END diff --git a/Builds/VisualStudio2013/resources.rc b/Builds/VisualStudio2013/resources.rc index f8e9fb7b27f1fe31f745a58a2ab83b88b2b29263..2ec7e37977549fe56273d5725bb997199bd8b7b8 100644 --- a/Builds/VisualStudio2013/resources.rc +++ b/Builds/VisualStudio2013/resources.rc @@ -7,7 +7,7 @@ #include <windows.h> VS_VERSION_INFO VERSIONINFO -FILEVERSION 0,3,5,0 +FILEVERSION 0,3,6,0 BEGIN BLOCK "StringFileInfo" BEGIN @@ -15,9 +15,9 @@ BEGIN BEGIN VALUE "CompanyName", "Open Ephys\0" VALUE "FileDescription", "open-ephys\0" - VALUE "FileVersion", "0.3.5\0" + VALUE "FileVersion", "0.3.6\0" VALUE "ProductName", "open-ephys\0" - VALUE "ProductVersion", "0.3.5\0" + VALUE "ProductVersion", "0.3.6\0" END END diff --git a/JuceLibraryCode/JuceHeader.h b/JuceLibraryCode/JuceHeader.h index 23beaaa63e64634b9d631992026c569f9529f71e..23180453ee27c07c99c7288b8ce1c817b8114347 100644 --- a/JuceLibraryCode/JuceHeader.h +++ b/JuceLibraryCode/JuceHeader.h @@ -39,8 +39,8 @@ namespace ProjectInfo { const char* const projectName = "open-ephys"; - const char* const versionString = "0.3.5"; - const int versionNumber = 0x305; + const char* const versionString = "0.3.6"; + const int versionNumber = 0x306; } #endif // __APPHEADERFILE_YNSYIRR__ diff --git a/JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp b/JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp index e51dd7c0a4656e429e4790df06e537718dcb394b..d70c3ff58f33c494e5a06f0842b46c16b9f4fe10 100755 --- a/JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp +++ b/JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_CoreAudio.cpp @@ -29,6 +29,9 @@ #endif //============================================================================== +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#pragma GCC diagnostic ignored "-Wnonnull" struct SystemVol { SystemVol (AudioObjectPropertySelector selector) @@ -123,6 +126,7 @@ private: && isSettable; } }; +#pragma GCC diagnostic pop #define JUCE_SYSTEMAUDIOVOL_IMPLEMENTED 1 float JUCE_CALLTYPE SystemAudioVolume::getGain() { return SystemVol (kAudioHardwareServiceDeviceProperty_VirtualMasterVolume).getGain(); } diff --git a/JuceLibraryCode/modules/juce_core/native/juce_mac_Files.mm b/JuceLibraryCode/modules/juce_core/native/juce_mac_Files.mm index 0a00e9212450f7eb2d1bbaeb47a119cec8a02f72..45520d4981deb817c65504bb7a7580336da4fef1 100755 --- a/JuceLibraryCode/modules/juce_core/native/juce_mac_Files.mm +++ b/JuceLibraryCode/modules/juce_core/native/juce_mac_Files.mm @@ -311,12 +311,15 @@ bool File::moveToTrash() const { NSString* p = juceStringToNS (getFullPathName()); +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" return [[NSWorkspace sharedWorkspace] performFileOperation: NSWorkspaceRecycleOperation source: [p stringByDeletingLastPathComponent] destination: nsEmptyString() files: [NSArray arrayWithObject: [p lastPathComponent]] tag: nil ]; +#pragma GCC diagnostic pop } #endif } diff --git a/JuceLibraryCode/modules/juce_core/native/juce_mac_Network.mm b/JuceLibraryCode/modules/juce_core/native/juce_mac_Network.mm index 130759e6d67e4acbea60f278d6e478b2df5d54c4..c019d2c88e8a1b7d4402936ef7a4f600df1d4137 100755 --- a/JuceLibraryCode/modules/juce_core/native/juce_mac_Network.mm +++ b/JuceLibraryCode/modules/juce_core/native/juce_mac_Network.mm @@ -243,8 +243,11 @@ public: void run() override { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" connection = [[NSURLConnection alloc] initWithRequest: request delegate: delegate]; +#pragma GCC diagnostic pop while (! threadShouldExit()) { JUCE_AUTORELEASEPOOL diff --git a/JuceLibraryCode/modules/juce_graphics/native/juce_mac_Fonts.mm b/JuceLibraryCode/modules/juce_graphics/native/juce_mac_Fonts.mm index 81a29a8047c455ad78a07e9eb420423cb2e05c37..f56683ecde6bf56d15e99613ec739e6d3d96be23 100755 --- a/JuceLibraryCode/modules/juce_graphics/native/juce_mac_Fonts.mm +++ b/JuceLibraryCode/modules/juce_graphics/native/juce_mac_Fonts.mm @@ -272,16 +272,22 @@ namespace CoreTextTypeLayout } // Paragraph Attributes +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" CTTextAlignment ctTextAlignment = kCTLeftTextAlignment; +#pragma GCC diagnostic pop CTLineBreakMode ctLineBreakMode = kCTLineBreakByWordWrapping; const CGFloat ctLineSpacing = text.getLineSpacing(); switch (text.getJustification().getOnlyHorizontalFlags()) { case Justification::left: break; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" case Justification::right: ctTextAlignment = kCTRightTextAlignment; break; case Justification::horizontallyCentred: ctTextAlignment = kCTCenterTextAlignment; break; case Justification::horizontallyJustified: ctTextAlignment = kCTJustifiedTextAlignment; break; +#pragma GCC diagnostic pop default: jassertfalse; break; // Illegal justification flags } diff --git a/JuceLibraryCode/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm b/JuceLibraryCode/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm index 6b863280f57c6d34f4e8137ef9842aec659d0228..cbf27f7a08e08aaa0c1ec9fa8e7bb24b97223144 100755 --- a/JuceLibraryCode/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm +++ b/JuceLibraryCode/modules/juce_gui_extra/native/juce_mac_WebBrowserComponent.mm @@ -160,9 +160,9 @@ public: static DownloadClickDetectorClass cls; clickListener = [cls.createInstance() init]; DownloadClickDetectorClass::setOwner (clickListener, owner); - [webView setPolicyDelegate: clickListener]; - [webView setFrameLoadDelegate: clickListener]; - [webView setUIDelegate: clickListener]; + // [webView setPolicyDelegate: (id <WebPolicyDelegate>)clickListener]; + // [webView setFrameLoadDelegate: (id <WebFrameLoadDelegate>)clickListener]; + // [webView setUIDelegate: (id <WebUIDelegate>)clickListener]; #else webView = [[UIWebView alloc] initWithFrame: CGRectMake (0, 0, 1.0f, 1.0f)]; setView (webView); diff --git a/Resources/Python/event_listener.py b/Resources/Python/event_listener.py new file mode 100644 index 0000000000000000000000000000000000000000..9304b266c4a00822c66845caad2da02bbcbaf68c --- /dev/null +++ b/Resources/Python/event_listener.py @@ -0,0 +1,109 @@ +from __future__ import print_function, unicode_literals +from collections import OrderedDict +from itertools import chain, repeat +try: + from itertools import izip +except ImportError: + izip = zip # Python 3 +import struct + +import zmq + + +# Event types +TTL = 3 +SPIKE = 4 +MESSAGE = 5 +BINARY_MSG = 6 + + +def unpacker(format, *fields): + s = struct.Struct(format) + + def unpack(data): + values = s.unpack(data[:s.size]) + if (len(values) == 1) and (not fields): + assert len(data) == s.size + return values[0], '' + assert len(values) <= len(fields) + return (OrderedDict(izip(fields, chain(values, repeat(None)))), + data[s.size:]) + + return unpack + + +unpack_standard = unpacker('3BxB', + 'node_id', + 'event_id', + 'event_channel', + 'source_node_id', + ) + + +unpack_ttl = unpacker('<Q') + + +unpack_spike = unpacker('<2q2x5H3B2fH', + 'timestamp', + 'timestamp_software', + 'n_channels', + 'n_samples', + 'sorted_id', + 'electrode_id', + 'channel', + 'color_r', 'color_g', 'color_b', + 'pc_proj_x', 'pc_proj_y', + 'sampling_frequency_hz', + 'data', + 'gain', + 'threshold', + ) + + +def run(hostname='localhost', port=5557): + with zmq.Context() as ctx: + with ctx.socket(zmq.SUB) as sock: + sock.connect('tcp://%s:%d' % (hostname, port)) + + for etype in (TTL, SPIKE, MESSAGE): + sock.setsockopt(zmq.SUBSCRIBE, chr(etype).encode('utf-8')) + + while True: + try: + parts = sock.recv_multipart() + assert len(parts) == 3 + + etype = ord(parts[0]) + timestamp_seconds = struct.unpack('d', parts[1])[0] + body = parts[2] + + if etype == SPIKE: + spike, body = unpack_spike(body) + print('%g: Spike: %s' % (timestamp_seconds, spike)) + body = '' # TODO: unpack other data + + else: + header, body = unpack_standard(body) + + if etype == TTL: + word, body = unpack_ttl(body) + print('%g: TTL: Channel %d: %s' % + (timestamp_seconds, + header['event_channel'] + 1, + 'ON' if header['event_id'] else 'OFF')) + + elif etype == MESSAGE: + msg, body = body.decode('utf-8'), '' + print('%g: Message: %s' % (timestamp_seconds, msg)) + + + # Check that all data was consumed + assert len(body) == 0 + + except KeyboardInterrupt: + print() # Add final newline + break + + +if __name__ == '__main__': + run() diff --git a/Resources/Python/record_control_example_client.py b/Resources/Python/record_control_example_client.py new file mode 100644 index 0000000000000000000000000000000000000000..9b4c0f899366ac26378629073787faf58c076883 --- /dev/null +++ b/Resources/Python/record_control_example_client.py @@ -0,0 +1,69 @@ +""" + A zmq client to test remote control of open-ephys GUI +""" + +import zmq +import os +import time + + +def run_client(): + + # Basic start/stop commands + start_cmd = 'StartRecord' + stop_cmd = 'StopRecord' + + # Example settings + rec_dir = os.path.join(os.getcwd(), 'Output_RecordControl') + print "Saving data to:", rec_dir + + # Some commands + commands = [start_cmd + ' RecDir=%s' % rec_dir, + start_cmd + ' PrependText=Session01 AppendText=Condition01', + start_cmd + ' PrependText=Session01 AppendText=Condition02', + start_cmd + ' PrependText=Session02 AppendText=Condition01', + start_cmd, + start_cmd + ' CreateNewDir=1'] + + # Connect network handler + ip = '127.0.0.1' + port = 5556 + timeout = 1. + + url = "tcp://%s:%d" % (ip, port) + + with zmq.Context() as context: + with context.socket(zmq.REQ) as socket: + socket.RCVTIMEO = int(timeout * 1000) # timeout in milliseconds + socket.connect(url) + + # Finally, start data acquisition + socket.send('StartAcquisition') + answer = socket.recv() + print answer + time.sleep(5) + + for start_cmd in commands: + + for cmd in [start_cmd, stop_cmd]: + socket.send(cmd) + answer = socket.recv() + print answer + + if 'StartRecord' in cmd: + # Record data for 5 seconds + time.sleep(5) + else: + # Stop for 1 second + time.sleep(1) + + # Finally, stop data acquisition; it might be a good idea to + # wait a little bit until all data have been written to hard drive + time.sleep(0.5) + socket.send('StopAcquisition') + answer = socket.recv() + print answer + + +if __name__ == '__main__': + run_client() diff --git a/Source/CoreServices.cpp b/Source/CoreServices.cpp index 11b541ddd7ab1e1975d30829b417a0db261cf03e..b58d8936136d47edf638dd8efe51d9853b68c5c0 100644 --- a/Source/CoreServices.cpp +++ b/Source/CoreServices.cpp @@ -52,6 +52,16 @@ void setRecordingStatus(bool enable) getControlPanel()->setRecordState(enable); } +bool getAcquisitionStatus() +{ + return getControlPanel()->getAcquisitionState(); +} + +void setAcquisitionStatus(bool enable) +{ + getControlPanel()->setAcquisitionState(enable); +} + void sendStatusMessage(const String& text) { getBroadcaster()->sendActionMessage(text); @@ -77,6 +87,26 @@ int64 getSoftwareTimestamp() return getMessageCenter()->getTimestamp(true); } +void setRecordingDirectory(String dir) +{ + getControlPanel()->setRecordingDirectory(dir); +} + +void createNewRecordingDir() +{ + getControlPanel()->labelTextChanged(NULL); +} + +void setPrependTextToRecordingDir(String text) +{ + getControlPanel()->setPrependText(text); +} + +void setAppendTextToRecordingDir(String text) +{ + getControlPanel()->setAppendText(text); +} + namespace RecordNode { void createNewrecordingDir() diff --git a/Source/CoreServices.h b/Source/CoreServices.h index 19fd631924cd76d13e8103260e137f6f8997ed85..1350f4ea6d77bb0b584b15678c5aa5e0fb9fe10f 100644 --- a/Source/CoreServices.h +++ b/Source/CoreServices.h @@ -43,6 +43,12 @@ PLUGIN_API bool getRecordingStatus(); /** Activated or deactivates recording */ PLUGIN_API void setRecordingStatus(bool enable); +/** Returns true if the GUI is acquiring data */ +bool getAcquisitionStatus(); + +/** Activates or deactivates data acquisition */ +void setAcquisitionStatus(bool enable); + /** Sends a string to the message bar */ PLUGIN_API void sendStatusMessage(const String& text); @@ -60,6 +66,18 @@ PLUGIN_API int64 getGlobalTimestamp(); /** Gets the software timestamp based on a high resolution timer aligned to the start of each processing block */ PLUGIN_API int64 getSoftwareTimestamp(); +/** Set new recording directory */ +void setRecordingDirectory(String dir); + +/** Create new recording directory */ +void createNewRecordingDir(); + +/** Manually set the text to be prepended to the recording directory */ +void setPrependTextToRecordingDir(String text); + +/** Manually set the text to be appended to the recording directory */ +void setAppendTextToRecordingDir(String text); + namespace RecordNode { /** Forces creation of new directory on recording */ diff --git a/Source/Plugins/ArduinoOutput/ArduinoOutput.cpp b/Source/Plugins/ArduinoOutput/ArduinoOutput.cpp index 4e175655330f2c7c2d853c04ba461976df17895a..0c2317d04e4418cecb8f6b737c46b535e7f5aedc 100644 --- a/Source/Plugins/ArduinoOutput/ArduinoOutput.cpp +++ b/Source/Plugins/ArduinoOutput/ArduinoOutput.cpp @@ -27,7 +27,7 @@ #include <stdio.h> ArduinoOutput::ArduinoOutput() - : GenericProcessor("Arduino Output"), outputChannel(13), inputChannel(-1), state(true), deviceSelected(false) + : GenericProcessor("Arduino Output"), outputChannel(13), inputChannel(-1), state(true), deviceSelected(false), acquisitionIsActive(false) { } diff --git a/Source/Plugins/EcubeSource/EcubeThread.h b/Source/Plugins/EcubeSource/EcubeThread.h index 8b654620f463a179ad0efee1092b60ba602b81d3..13b43aac20619518374ea8228cac50c104f57458 100644 --- a/Source/Plugins/EcubeSource/EcubeThread.h +++ b/Source/Plugins/EcubeSource/EcubeThread.h @@ -32,8 +32,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>. #include <stdio.h> #include <string.h> -#define MAX_NUM_DATA_STREAMS 8 - class SourceNode; #if JUCE_WINDOWS diff --git a/Source/Plugins/NetworkEvents/NetworkEvents.cpp b/Source/Plugins/NetworkEvents/NetworkEvents.cpp index 7d53afb775e5bbfe82160a2e31d9b6640a26617c..1c2cc76dd1dfa90c83baa829f2bd237b08696257 100644 --- a/Source/Plugins/NetworkEvents/NetworkEvents.cpp +++ b/Source/Plugins/NetworkEvents/NetworkEvents.cpp @@ -403,7 +403,86 @@ String NetworkEvents::handleSpecialMessages(StringTS msg) } */ - return String("NotHandled"); + + /** Start/stop data acquisition */ + String s = msg.getString(); + + /** Command is first substring */ + StringArray inputs = StringArray::fromTokens(s, " "); + String cmd = String(inputs[0]); + + const MessageManagerLock mmLock; + if (cmd.compareIgnoreCase("StartAcquisition") == 0) + { + if (!CoreServices::getAcquisitionStatus()) + { + CoreServices::setAcquisitionStatus(true); + } + return String("StartedAcquisition"); + } + else if (cmd.compareIgnoreCase("StopAcquisition") == 0) + { + if (CoreServices::getAcquisitionStatus()) + { + CoreServices::setAcquisitionStatus(false); + } + return String("StoppedAcquisition"); + } + else if (String("StartRecord").compareIgnoreCase(cmd) == 0) + { + if (!CoreServices::getRecordingStatus() && CoreServices::getAcquisitionStatus()) + { + /** First set optional parameters (name/value pairs)*/ + if (s.contains("=")) + { + String params = s.substring(cmd.length()); + StringPairArray dict = parseNetworkMessage(params); + + StringArray keys = dict.getAllKeys(); + for (int i = 0; i<keys.size(); i++) + { + String key = keys[i]; + String value = dict[key]; + + if (key.compareIgnoreCase("CreateNewDir") == 0) + { + if (value.compareIgnoreCase("1") == 0) + { + CoreServices::createNewRecordingDir(); + } + } + else if (key.compareIgnoreCase("RecDir") == 0) + { + CoreServices::setRecordingDirectory(value); + } + else if (key.compareIgnoreCase("PrependText") == 0) + { + CoreServices::setPrependTextToRecordingDir(value); + } + else if (key.compareIgnoreCase("AppendText") == 0) + { + CoreServices::setAppendTextToRecordingDir(value); + } + } + } + + /** Start recording */ + CoreServices::setRecordingStatus(true); + return String("StartedRecording"); + } + } + else if (String("StopRecord").compareIgnoreCase(cmd) == 0) + { + if (CoreServices::getRecordingStatus()) + { + CoreServices::setRecordingStatus(false); + return String("StoppedRecording"); + } + } + else + { + return String("NotHandled"); + } } void NetworkEvents::process(AudioSampleBuffer& buffer, @@ -564,3 +643,51 @@ void NetworkEvents::createZmqContext() zmqcontext = zmq_ctx_new(); //<-- this is only available in version 3+ #endif } + +StringPairArray NetworkEvents::parseNetworkMessage(String msg) +{ + StringArray splitted; + splitted.addTokens(msg, "=", ""); + + StringPairArray dict = StringPairArray(); + String key = ""; + String value = ""; + for (int i = 0; i<splitted.size() - 1; i++) + { + String s1 = splitted[i]; + String s2 = splitted[i + 1]; + + /** Get key */ + if (!key.isEmpty()) + { + if (s1.contains(" ")) + { + int i1 = s1.lastIndexOf(" "); + key = s1.substring(i1 + 1); + } + else + { + key = s1; + } + } + else + { + key = s1.trim(); + } + + /** Get value */ + if (i < splitted.size() - 2) + { + int i1 = s2.lastIndexOf(" "); + value = s2.substring(0, i1); + } + else + { + value = s2; + } + + dict.set(key, value); + } + + return dict; +} diff --git a/Source/Plugins/NetworkEvents/NetworkEvents.h b/Source/Plugins/NetworkEvents/NetworkEvents.h index cb1d9db3ade119a561cb0edd746906841654f32f..b053a09f855456fdeee211cb8953e115cd087ca5 100644 --- a/Source/Plugins/NetworkEvents/NetworkEvents.h +++ b/Source/Plugins/NetworkEvents/NetworkEvents.h @@ -115,6 +115,9 @@ private: void handleEvent(int eventType, MidiMessage& event, int samplePos); void createZmqContext(); + //* Split network message into name/value pairs (name1=val1 name2=val2 etc) */ + StringPairArray parseNetworkMessage(String msg); + StringTS createStringTS(String S, int64 t); static void* zmqcontext; diff --git a/Source/Plugins/RecordControl/RecordControl.cpp b/Source/Plugins/RecordControl/RecordControl.cpp index 110b89e84f8665475c7875fcc7f5c439569b65ef..612435be73fa7c7faf17a929b9752b183b0c72d0 100644 --- a/Source/Plugins/RecordControl/RecordControl.cpp +++ b/Source/Plugins/RecordControl/RecordControl.cpp @@ -109,8 +109,8 @@ void RecordControl::handleEvent(int eventType, MidiMessage& event, int) { CoreServices::setRecordingStatus(!CoreServices::getRecordingStatus()); } + } +} - } -} \ No newline at end of file diff --git a/Source/Plugins/RecordControl/RecordControl.h b/Source/Plugins/RecordControl/RecordControl.h index 9b6546a018e9e40d098b5d45326d4690fcd556dc..75601632b6c45f1df7a725c5785b657a2dae47a9 100644 --- a/Source/Plugins/RecordControl/RecordControl.h +++ b/Source/Plugins/RecordControl/RecordControl.h @@ -45,6 +45,7 @@ public: void setParameter(int, float); void updateTriggerChannel(int newChannel); void handleEvent(int eventType, MidiMessage& event, int); + bool enable(); bool isUtility() @@ -65,4 +66,4 @@ private: }; -#endif \ No newline at end of file +#endif diff --git a/Source/Processors/DataThreads/RhythmNode/RHD2000Thread.cpp b/Source/Processors/DataThreads/RhythmNode/RHD2000Thread.cpp index 81edfd38d6a67a58647031e9b9e81fb3e63f2d6e..767a377cbd01c8e4ba7ff244a218d68553022fb4 100644 --- a/Source/Processors/DataThreads/RhythmNode/RHD2000Thread.cpp +++ b/Source/Processors/DataThreads/RhythmNode/RHD2000Thread.cpp @@ -318,7 +318,9 @@ bool RHD2000Thread::uploadBitfile(String bitfilename) bool response = AlertWindow::showOkCancelBox(AlertWindow::NoIcon, "FPGA bitfile not found.", - "The rhd2000.bit file was not found in the directory of the executable. Would you like to browse for it?", + (evalBoard->isUSB3() ? + "The rhd2000_usb3.bit file was not found in the directory of the executable. Would you like to browse for it?" : + "The rhd2000.bit file was not found in the directory of the executable. Would you like to browse for it?"), "Yes", "No", 0, 0); if (response) { @@ -1495,7 +1497,7 @@ bool RHD2000Thread::stopAcquisition() bool RHD2000Thread::updateBuffer() { - int chOffset; + //int chOffset; unsigned char* bufferPtr; //cout << "Number of 16-bit words in FIFO: " << evalBoard->numWordsInFifo() << endl; //cout << "Block size: " << blockSize << endl; diff --git a/Source/Processors/DataThreads/RhythmNode/RHD2000Thread.h b/Source/Processors/DataThreads/RhythmNode/RHD2000Thread.h index fbeb3302ac26d9e8f28c1d5230bfae6b547bb005..63e0df6fce1d5309ed891507b1fdfb0d6248a9bb 100644 --- a/Source/Processors/DataThreads/RhythmNode/RHD2000Thread.h +++ b/Source/Processors/DataThreads/RhythmNode/RHD2000Thread.h @@ -43,7 +43,6 @@ #define MAX_NUM_DATA_STREAMS_USB3 16 #define MAX_NUM_HEADSTAGES 8 -#define MAX_NUM_DATA_STREAMS(u3) (u3 ? MAX_NUM_DATA_STREAMS_USB3 : MAX_NUM_DATA_STREAMS_USB2) #define MAX_NUM_CHANNELS MAX_NUM_DATA_STREAMS_USB3*35 class SourceNode; diff --git a/Source/Processors/DataThreads/RhythmNode/rhythm-api/rhd2000evalboard.cpp b/Source/Processors/DataThreads/RhythmNode/rhythm-api/rhd2000evalboard.cpp index 45c1c3a1f6bcaab22e80ad3a6cf2dab56ffa137e..832b80e26fa3798efb0d7ffbabefd325f47fbd63 100644 --- a/Source/Processors/DataThreads/RhythmNode/rhythm-api/rhd2000evalboard.cpp +++ b/Source/Processors/DataThreads/RhythmNode/rhythm-api/rhd2000evalboard.cpp @@ -1389,15 +1389,15 @@ void Rhd2000EvalBoard::flush() { dev->SetWireInValue(WireInResetRun, 1 << 16, 1 << 16); //Override pipeout block throttle dev->UpdateWireIns(); - cout << "Pre-Flush: " << numWordsInFifo() << endl; + //cout << "Pre-Flush: " << numWordsInFifo() << endl; while (numWordsInFifo() >= USB_BUFFER_SIZE / 2) { dev->ReadFromBlockPipeOut(PipeOutData, USB3_BLOCK_SIZE, USB_BUFFER_SIZE, usbBuffer); - cout << "Flush phase A: " << numWordsInFifo() << endl; + // cout << "Flush phase A: " << numWordsInFifo() << endl; } while (numWordsInFifo() > 0) { dev->ReadFromBlockPipeOut(PipeOutData, USB3_BLOCK_SIZE, USB3_BLOCK_SIZE *max(2 * numWordsInFifo() / USB3_BLOCK_SIZE, (unsigned int)1), usbBuffer); - cout << "Flush phase B: " << numWordsInFifo() << endl; - printFIFOmetrics(); + // cout << "Flush phase B: " << numWordsInFifo() << endl; + // printFIFOmetrics(); } dev->SetWireInValue(WireInResetRun, 0, 1 << 16); dev->UpdateWireIns(); @@ -1488,6 +1488,7 @@ bool Rhd2000EvalBoard::readDataBlocks(int numBlocks, queue<Rhd2000DataBlock> &da unsigned int numWordsToRead, numBytesToRead; int i; Rhd2000DataBlock *dataBlock; + long res; numWordsToRead = numBlocks * dataBlock->calculateDataBlockSizeInWords(numDataStreams, usb3); @@ -1502,7 +1503,18 @@ bool Rhd2000EvalBoard::readDataBlocks(int numBlocks, queue<Rhd2000DataBlock> &da return false; } - dev->ReadFromPipeOut(PipeOutData, numBytesToRead, usbBuffer); + if (usb3) + { + res = dev->ReadFromBlockPipeOut(PipeOutData, USB3_BLOCK_SIZE, numBytesToRead, usbBuffer); + } + else + { + res = dev->ReadFromPipeOut(PipeOutData, numBytesToRead, usbBuffer); + } + if (res == ok_Timeout) + { + cerr << "CRITICAL: Timeout on pipe read. Check block and buffer sizes." << endl; + } dataBlock = new Rhd2000DataBlock(numDataStreams, usb3); for (i = 0; i < numBlocks; ++i) { diff --git a/Source/Processors/DataThreads/RhythmNode/rhythm-api/rhd2000evalboard.h b/Source/Processors/DataThreads/RhythmNode/rhythm-api/rhd2000evalboard.h index fffb8404c1e5bcc2b6504de03ef54eb9dcf7704a..440cee2251188f27a3496991726e3133fc85b795 100644 --- a/Source/Processors/DataThreads/RhythmNode/rhythm-api/rhd2000evalboard.h +++ b/Source/Processors/DataThreads/RhythmNode/rhythm-api/rhd2000evalboard.h @@ -30,7 +30,7 @@ #define MAX_NUM_DATA_STREAMS(u3) ( u3 ? MAX_NUM_DATA_STREAMS_USB3 : MAX_NUM_DATA_STREAMS_USB2 ) -#define USB3_BLOCK_SIZE 2048 +#define USB3_BLOCK_SIZE 1024 #define DDR_BLOCK_SIZE 32 #include <queue> diff --git a/Source/Processors/EventBroadcaster/EventBroadcaster.cpp b/Source/Processors/EventBroadcaster/EventBroadcaster.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0527dcff471db91b1e0963b72bcb7ff2340fc34e --- /dev/null +++ b/Source/Processors/EventBroadcaster/EventBroadcaster.cpp @@ -0,0 +1,153 @@ +/* + ============================================================================== + + EventBroadcaster.cpp + Created: 22 May 2015 3:31:50pm + Author: Christopher Stawarz + + ============================================================================== +*/ + +#include "EventBroadcaster.h" +#include "EventBroadcasterEditor.h" + + +std::shared_ptr<void> EventBroadcaster::getZMQContext() { + // Note: C++11 guarantees that initialization of static local variables occurs exactly once, even + // if multiple threads attempt to initialize the same static local variable concurrently. +#ifdef ZEROMQ + static const std::shared_ptr<void> ctx(zmq_ctx_new(), zmq_ctx_destroy); +#else + static const std::shared_ptr<void> ctx; +#endif + return ctx; +} + + +void EventBroadcaster::closeZMQSocket(void* socket) +{ +#ifdef ZEROMQ + zmq_close(socket); +#endif +} + + +EventBroadcaster::EventBroadcaster() + : GenericProcessor("Event Broadcaster"), + zmqContext(getZMQContext()), + zmqSocket(nullptr, &closeZMQSocket), + listeningPort(0), + currentSampleRate(0) +{ + setListeningPort(5557); +} + + +AudioProcessorEditor* EventBroadcaster::createEditor() +{ + editor = new EventBroadcasterEditor(this, true); + return editor; +} + + +int EventBroadcaster::getListeningPort() const +{ + return listeningPort; +} + + +void EventBroadcaster::setListeningPort(int port, bool forceRestart) +{ + if ((listeningPort != port) || forceRestart) + { +#ifdef ZEROMQ + zmqSocket.reset(zmq_socket(zmqContext.get(), ZMQ_PUB)); + if (!zmqSocket) + { + std::cout << "Failed to create socket: " << zmq_strerror(zmq_errno()) << std::endl; + return; + } + + String url = String("tcp://*:") + String(port); + if (0 != zmq_bind(zmqSocket.get(), url.toRawUTF8())) + { + std::cout << "Failed to open socket: " << zmq_strerror(zmq_errno()) << std::endl; + return; + } +#endif + + listeningPort = port; + } +} + + +void EventBroadcaster::process(AudioSampleBuffer& continuousBuffer, MidiBuffer& eventBuffer) +{ + currentSampleRate = getSampleRate(); + checkForEvents(eventBuffer); +} + + +bool EventBroadcaster::isSink() +{ + return true; +} + + +void EventBroadcaster::handleEvent(int eventType, MidiMessage& event, int samplePosition) +{ + const uint8_t* buffer = event.getRawData(); + uint8_t type = buffer[0]; + int64_t timestamp; + + switch (type) { + case TTL: + case MESSAGE: + case BINARY_MSG: { + uint8_t nodeID = buffer[1]; + timestamp = timestamps.at(nodeID) + samplePosition; + break; + } + + case SPIKE: + std::copy_n(buffer + 1, sizeof(timestamp), reinterpret_cast<uint8_t *>(×tamp)); + break; + + default: + // Don't broadcast other event types + return; + } + + double timestampSeconds = double(timestamp) / currentSampleRate; + +#ifdef ZEROMQ + if (-1 == zmq_send(zmqSocket.get(), &type, sizeof(type), ZMQ_SNDMORE) || + -1 == zmq_send(zmqSocket.get(), ×tampSeconds, sizeof(timestampSeconds), ZMQ_SNDMORE) || + -1 == zmq_send(zmqSocket.get(), buffer + 1, event.getRawDataSize() - 1, 0) /* Omit event type */) + { + std::cout << "Failed to send message: " << zmq_strerror(zmq_errno()) << std::endl; + } +#endif +} + + +void EventBroadcaster::saveCustomParametersToXml(XmlElement* parentElement) +{ + XmlElement* mainNode = parentElement->createNewChildElement("EVENTBROADCASTER"); + mainNode->setAttribute("port", listeningPort); +} + + +void EventBroadcaster::loadCustomParametersFromXml() +{ + if (parametersAsXml) + { + forEachXmlChildElement(*parametersAsXml, mainNode) + { + if (mainNode->hasTagName("EVENTBROADCASTER")) + { + setListeningPort(mainNode->getIntAttribute("port")); + } + } + } +} diff --git a/Source/Processors/EventBroadcaster/EventBroadcaster.h b/Source/Processors/EventBroadcaster/EventBroadcaster.h new file mode 100644 index 0000000000000000000000000000000000000000..6e7424a472d41149c5e32dc1179ddb82f478f455 --- /dev/null +++ b/Source/Processors/EventBroadcaster/EventBroadcaster.h @@ -0,0 +1,58 @@ +/* + ============================================================================== + + EventBroadcaster.h + Created: 22 May 2015 3:31:50pm + Author: Christopher Stawarz + + ============================================================================== +*/ + +#ifndef EVENTBROADCASTER_H_INCLUDED +#define EVENTBROADCASTER_H_INCLUDED + +#include "../GenericProcessor/GenericProcessor.h" + +#ifdef ZEROMQ + +#ifdef WIN32 +#include "../../../Resources/windows-libs/ZeroMQ/include/zmq.h" +#include "../../../Resources/windows-libs/ZeroMQ/include/zmq_utils.h" +#else +#include <zmq.h> +#endif + +#endif +#include <memory> + +class EventBroadcaster : public GenericProcessor +{ +public: + EventBroadcaster(); + + AudioProcessorEditor* createEditor() override; + + int getListeningPort() const; + void setListeningPort(int port, bool forceRestart = false); + + void process(AudioSampleBuffer& continuousBuffer, MidiBuffer& eventBuffer) override; + bool isSink() override; + void handleEvent(int eventType, MidiMessage& event, int samplePosition = 0) override; + + void saveCustomParametersToXml(XmlElement* parentElement) override; + void loadCustomParametersFromXml() override; + +private: + static std::shared_ptr<void> getZMQContext(); + static void closeZMQSocket(void* socket); + + const std::shared_ptr<void> zmqContext; + std::unique_ptr<void, decltype(&closeZMQSocket)> zmqSocket; + int listeningPort; + + float currentSampleRate; + +}; + + +#endif // EVENTBROADCASTER_H_INCLUDED diff --git a/Source/Processors/EventBroadcaster/EventBroadcasterEditor.cpp b/Source/Processors/EventBroadcaster/EventBroadcasterEditor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9519f7e1b4f4db0a6ac017ccb0b497f7fa35267f --- /dev/null +++ b/Source/Processors/EventBroadcaster/EventBroadcasterEditor.cpp @@ -0,0 +1,63 @@ +/* + ============================================================================== + + EventBroadcasterEditor.cpp + Created: 22 May 2015 3:34:30pm + Author: Christopher Stawarz + + ============================================================================== +*/ + +#include "EventBroadcasterEditor.h" +#include "EventBroadcaster.h" + + +EventBroadcasterEditor::EventBroadcasterEditor(GenericProcessor* parentNode, bool useDefaultParameterEditors) + : GenericEditor(parentNode, useDefaultParameterEditors) + +{ + desiredWidth = 180; + + urlLabel = new Label("Port", "Port:"); + urlLabel->setBounds(20,80,140,25); + addAndMakeVisible(urlLabel); + EventBroadcaster* p = (EventBroadcaster*)getProcessor(); + + restartConnection = new UtilityButton("Restart Connection",Font("Default", 15, Font::plain)); + restartConnection->setBounds(20,45,150,18); + restartConnection->addListener(this); + addAndMakeVisible(restartConnection); + + portLabel = new Label("Port", String(p->getListeningPort())); + portLabel->setBounds(70,85,80,18); + portLabel->setFont(Font("Default", 15, Font::plain)); + portLabel->setColour(Label::textColourId, Colours::white); + portLabel->setColour(Label::backgroundColourId, Colours::grey); + portLabel->setEditable(true); + portLabel->addListener(this); + addAndMakeVisible(portLabel); + + setEnabledState(false); +} + + +void EventBroadcasterEditor::buttonEvent(Button* button) +{ + if (button == restartConnection) + { + EventBroadcaster* p = (EventBroadcaster*)getProcessor(); + p->setListeningPort(p->getListeningPort(), true); + } +} + + +void EventBroadcasterEditor::labelTextChanged(juce::Label* label) +{ + if (label == portLabel) + { + Value val = label->getTextValue(); + + EventBroadcaster* p = (EventBroadcaster*)getProcessor(); + p->setListeningPort(val.getValue()); + } +} diff --git a/Source/Processors/EventBroadcaster/EventBroadcasterEditor.h b/Source/Processors/EventBroadcaster/EventBroadcasterEditor.h new file mode 100644 index 0000000000000000000000000000000000000000..59533658e6a48527286cecd2c585a6c49d57de69 --- /dev/null +++ b/Source/Processors/EventBroadcaster/EventBroadcasterEditor.h @@ -0,0 +1,43 @@ +/* + ============================================================================== + + EventBroadcasterEditor.h + Created: 22 May 2015 3:34:30pm + Author: Christopher Stawarz + + ============================================================================== +*/ + +#ifndef EVENTBROADCASTEREDITOR_H_INCLUDED +#define EVENTBROADCASTEREDITOR_H_INCLUDED + +#include "../Editors/GenericEditor.h" + + +/** + + User interface for the "EventBroadcaster" source node. + + @see EventBroadcaster + + */ + +class EventBroadcasterEditor : public GenericEditor, public Label::Listener +{ +public: + EventBroadcasterEditor(GenericProcessor* parentNode, bool useDefaultParameterEditors); + + void buttonEvent(Button* button) override; + void labelTextChanged(juce::Label* label) override; + +private: + ScopedPointer<UtilityButton> restartConnection; + ScopedPointer<Label> urlLabel; + ScopedPointer<Label> portLabel; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EventBroadcasterEditor); + +}; + + +#endif // EVENTBROADCASTEREDITOR_H_INCLUDED diff --git a/Source/Processors/GenericProcessor/GenericProcessor.cpp b/Source/Processors/GenericProcessor/GenericProcessor.cpp index 4717d0fd919ded44746d6f5091308a5b3fe5b79b..ffc990e41ca02b85cdca1eb19bb4686462a081d4 100755 --- a/Source/Processors/GenericProcessor/GenericProcessor.cpp +++ b/Source/Processors/GenericProcessor/GenericProcessor.cpp @@ -769,7 +769,8 @@ void GenericProcessor::addEvent(MidiBuffer& eventBuffer, if (!isTimestamp && !timestampSet && !isSource() && !generatesTimestamps()) setTimestamp(eventBuffer, getTimestamp(0)); - uint8* data = new uint8[6+numBytes]; + HeapBlock<uint8> data(static_cast<const size_t>(6 + numBytes)); + //uint8* data = new uint8[6+numBytes]; data[0] = type; // event type data[1] = nodeId; // processor ID automatically added diff --git a/Source/Processors/SourceNode/SourceNode.cpp b/Source/Processors/SourceNode/SourceNode.cpp index f1a855e6d3f06b30fb727f0ffdd6081974e9547f..359c60bf08681ef805c34e2bd2a7a45215817b06 100755 --- a/Source/Processors/SourceNode/SourceNode.cpp +++ b/Source/Processors/SourceNode/SourceNode.cpp @@ -43,7 +43,8 @@ SourceNode::SourceNode(const String& name_, DataThreadCreator dt) } numEventChannels = dataThread->getNumEventChannels(); - eventChannelState = new int[numEventChannels]; + //eventChannelState = new int[numEventChannels]; + eventChannelState.malloc(numEventChannels); for (int i = 0; i < numEventChannels; i++) { eventChannelState[i] = 0; @@ -53,7 +54,7 @@ SourceNode::SourceNode(const String& name_, DataThreadCreator dt) else { enabledState(false); - eventChannelState = 0; + // eventChannelState = 0; numEventChannels = 0; } @@ -61,7 +62,8 @@ SourceNode::SourceNode(const String& name_, DataThreadCreator dt) startTimer(sourceCheckInterval); timestamp = 0; - eventCodeBuffer = new uint64[10000]; //10000 samples per buffer max? + //eventCodeBuffer = new uint64[10000]; //10000 samples per buffer max? + eventCodeBuffer.malloc(10000); } @@ -76,8 +78,8 @@ SourceNode::~SourceNode() } - if (eventChannelState) - delete[] eventChannelState; + //if (eventChannelState) + // delete[] eventChannelState; } DataThread* SourceNode::getThread() @@ -379,7 +381,9 @@ void SourceNode::process(AudioSampleBuffer& buffer, TTL, // eventType i, // sampleNum 0, // eventID - c // eventChannel + c, // eventChannel + 8, + (uint8*)(&eventCodeBuffer[i]) ); } else @@ -393,7 +397,9 @@ void SourceNode::process(AudioSampleBuffer& buffer, TTL, // eventType i, // sampleNum 1, // eventID - c // eventChannel + c, // eventChannel + 8, + (uint8*)(&eventCodeBuffer[i]) ); diff --git a/Source/Processors/SourceNode/SourceNode.h b/Source/Processors/SourceNode/SourceNode.h index 0c4781a6e28ebf7e6104caa62f7e893f40be0471..88febeba150e1d7a2a16bdfd2b163e58e2bb9e9a 100755 --- a/Source/Processors/SourceNode/SourceNode.h +++ b/Source/Processors/SourceNode/SourceNode.h @@ -118,8 +118,10 @@ private: DataBuffer* inputBuffer; uint64 timestamp; - uint64* eventCodeBuffer; - int* eventChannelState; + //uint64* eventCodeBuffer; + //int* eventChannelState; + HeapBlock<uint64> eventCodeBuffer; + HeapBlock<int> eventChannelState; int ttlState; diff --git a/Source/UI/ControlPanel.cpp b/Source/UI/ControlPanel.cpp index 2bca3db57e9e3384dc16c35d372d76027c77bd48..a41c544bb51cc809b6b93c33c5e937aff1e48d15 100755 --- a/Source/UI/ControlPanel.cpp +++ b/Source/UI/ControlPanel.cpp @@ -485,6 +485,33 @@ void ControlPanel::setRecordState(bool t) } +bool ControlPanel::getRecordingState() +{ + + return recordButton->getToggleState(); + +} + +void ControlPanel::setRecordingDirectory(String path) +{ + File newFile(path); + filenameComponent->setCurrentFile(newFile, true, sendNotificationSync); + + graph->getRecordNode()->newDirectoryNeeded = true; + masterClock->resetRecordTime(); +} + +bool ControlPanel::getAcquisitionState() +{ + return playButton->getToggleState(); +} + +void ControlPanel::setAcquisitionState(bool state) +{ + playButton->setToggleState(state, sendNotification); +} + + void ControlPanel::updateChildComponents() { @@ -994,6 +1021,16 @@ String ControlPanel::getTextToPrepend() } } +void ControlPanel::setPrependText(String t) +{ + prependText->setText(t, sendNotificationSync); +} + +void ControlPanel::setAppendText(String t) +{ + appendText->setText(t, sendNotificationSync); +} + void ControlPanel::setDateText(String t) { dateText->setText(t, dontSendNotification); @@ -1077,4 +1114,4 @@ StringArray ControlPanel::getRecentlyUsedFilenames() void ControlPanel::setRecentlyUsedFilenames(const StringArray& filenames) { filenameComponent->setRecentlyUsedFilenames(filenames); -} \ No newline at end of file +} diff --git a/Source/UI/ControlPanel.h b/Source/UI/ControlPanel.h index 8d81f9d9f0f710d97bf7d17716d664b7f259b426..8b733c6dd26a9e0988d2255dde47c9b1fa890e87 100755 --- a/Source/UI/ControlPanel.h +++ b/Source/UI/ControlPanel.h @@ -301,6 +301,19 @@ public: /** Used to manually turn recording on and off.*/ void setRecordState(bool isRecording); + + /** Return current recording state.*/ + bool getRecordingState(); + + /** Set recording directory and update FilenameComponent */ + void setRecordingDirectory(String path); + + /** Return current acquisition state.*/ + bool getAcquisitionState(); + + /** Used to manually turn recording on and off.*/ + void setAcquisitionState(bool state); + /** Returns a boolean that indicates whether or not the FilenameComponet is visible. */ bool isOpen() @@ -317,6 +330,12 @@ public: /** Used by RecordNode to set the filename. */ String getTextToAppend(); + /** Manually set the text to be prepended to the recording directory */ + void setPrependText(String text); + + /** Manually set the text to be appended to the recording directory */ + void setAppendText(String text); + /** Set date text. */ void setDateText(String);