diff --git a/Builds/Linux/Makefile b/Builds/Linux/Makefile index a45f74ab9a643ed11e3d69c19006bd64b5036d3a..b00bec1330aed6503cd834f272de0ad03f5d17e4 100644 --- a/Builds/Linux/Makefile +++ b/Builds/Linux/Makefile @@ -106,6 +106,8 @@ OBJECTS := \ $(OBJDIR)/ParameterEditor_112258eb.o \ $(OBJDIR)/Parameter_b3e5ac9e.o \ $(OBJDIR)/ProcessorGraph_8c3a250a.o \ + $(OBJDIR)/DataQueue_d6cc297a.o \ + $(OBJDIR)/RecordThread_fb797372.o \ $(OBJDIR)/EngineConfigWindow_4fd44ceb.o \ $(OBJDIR)/OriginalRecording_d6dc3293.o \ $(OBJDIR)/RecordEngine_97ef83aa.o \ @@ -444,6 +446,16 @@ $(OBJDIR)/ProcessorGraph_8c3a250a.o: ../../Source/Processors/ProcessorGraph/Proc @echo "Compiling ProcessorGraph.cpp" @$(CXX) $(CXXFLAGS) -o "$@" -c "$<" +$(OBJDIR)/DataQueue_d6cc297a.o: ../../Source/Processors/RecordNode/DataQueue.cpp + -@mkdir -p $(OBJDIR) + @echo "Compiling DataQueue.cpp" + @$(CXX) $(CXXFLAGS) -o "$@" -c "$<" + +$(OBJDIR)/RecordThread_fb797372.o: ../../Source/Processors/RecordNode/RecordThread.cpp + -@mkdir -p $(OBJDIR) + @echo "Compiling RecordThread.cpp" + @$(CXX) $(CXXFLAGS) -o "$@" -c "$<" + $(OBJDIR)/EngineConfigWindow_4fd44ceb.o: ../../Source/Processors/RecordNode/EngineConfigWindow.cpp -@mkdir -p $(OBJDIR) @echo "Compiling EngineConfigWindow.cpp" diff --git a/Builds/MacOSX/open-ephys.xcodeproj/project.pbxproj b/Builds/MacOSX/open-ephys.xcodeproj/project.pbxproj index ef8805846618a18d72d1b24facd7356ac8f0ae57..ca65fc7961e97d1afff5167acda3b4773d8e1287 100644 --- a/Builds/MacOSX/open-ephys.xcodeproj/project.pbxproj +++ b/Builds/MacOSX/open-ephys.xcodeproj/project.pbxproj @@ -76,6 +76,8 @@ F2586A2DCEF44961AEA247E8 = {isa = PBXBuildFile; fileRef = 934B37E2BECD69E6E27051F6; }; 3E7939ABAA984EE8BFC8CEDD = {isa = PBXBuildFile; fileRef = 4F5D51C5F8174E3824EF8B42; }; BAC379C03C2E7995F2393EF5 = {isa = PBXBuildFile; fileRef = 4CB63EE1552BBFDEB1DADB0A; }; + 0326A368BA8F70C74A8A12A7 = {isa = PBXBuildFile; fileRef = 74E31DA11A4C1244B78A077A; }; + F7E069E1FC1BB7EF856AA083 = {isa = PBXBuildFile; fileRef = 699B3251715DE04674E0E0C4; }; E1247DDF1C88D99691499E52 = {isa = PBXBuildFile; fileRef = 7DB22AC6407EEA88F3FFA16D; }; 0A8D8C2D02858F0F08356EA9 = {isa = PBXBuildFile; fileRef = E39CC410838072043E3C30DC; }; AEDA8F23648EABF79215B566 = {isa = PBXBuildFile; fileRef = F716728550EBD8FA7B9CA7EF; }; @@ -148,6 +150,7 @@ 06072EC6BCD3B7D8C17C2402 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_AudioProcessor.cpp"; path = "../../JuceLibraryCode/modules/juce_audio_processors/processors/juce_AudioProcessor.cpp"; sourceTree = "SOURCE_ROOT"; }; 0618303B4E1BF577974A03FE = {isa = PBXFileReference; lastKnownFileType = file.icns; name = Icon.icns; path = Icon.icns; sourceTree = "SOURCE_ROOT"; }; 0646A83E4EE738EE5D914DA6 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Visualizer.cpp; path = ../../Source/Processors/Visualization/Visualizer.cpp; sourceTree = "SOURCE_ROOT"; }; + 066A1CD777247BC8142A7DAA = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = EventQueue.h; path = ../../Source/Processors/RecordNode/EventQueue.h; sourceTree = "SOURCE_ROOT"; }; 078625CF5C083AD538D23401 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_AudioCDReader.cpp"; path = "../../JuceLibraryCode/modules/juce_audio_devices/audio_cd/juce_AudioCDReader.cpp"; sourceTree = "SOURCE_ROOT"; }; 0790CCE2FCFDFA6944DFC402 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_PopupMenu.cpp"; path = "../../JuceLibraryCode/modules/juce_gui_basics/menus/juce_PopupMenu.cpp"; sourceTree = "SOURCE_ROOT"; }; 07B84F46CF90D04BB6B673C5 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Merger.cpp; path = ../../Source/Processors/Merger/Merger.cpp; sourceTree = "SOURCE_ROOT"; }; @@ -577,6 +580,7 @@ 693E9C5C9A435F791921DAAE = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_AudioDeviceManager.cpp"; path = "../../JuceLibraryCode/modules/juce_audio_devices/audio_io/juce_AudioDeviceManager.cpp"; sourceTree = "SOURCE_ROOT"; }; 696F2DC49934E6F01A2DF9FE = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_FileTreeComponent.cpp"; path = "../../JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_FileTreeComponent.cpp"; sourceTree = "SOURCE_ROOT"; }; 698B0EC670DA47934444381B = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_win32_Network.cpp"; path = "../../JuceLibraryCode/modules/juce_core/native/juce_win32_Network.cpp"; sourceTree = "SOURCE_ROOT"; }; + 699B3251715DE04674E0E0C4 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = RecordThread.cpp; path = ../../Source/Processors/RecordNode/RecordThread.cpp; sourceTree = "SOURCE_ROOT"; }; 6A559D9595A54EF52BF0773A = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_Range.h"; path = "../../JuceLibraryCode/modules/juce_core/maths/juce_Range.h"; sourceTree = "SOURCE_ROOT"; }; 6A63308EBE68478531604BA4 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_DirectoryContentsList.cpp"; path = "../../JuceLibraryCode/modules/juce_gui_basics/filebrowser/juce_DirectoryContentsList.cpp"; sourceTree = "SOURCE_ROOT"; }; 6ABF91320A2EB6D307091AEE = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "juce_mac_CameraDevice.mm"; path = "../../JuceLibraryCode/modules/juce_video/native/juce_mac_CameraDevice.mm"; sourceTree = "SOURCE_ROOT"; }; @@ -624,12 +628,14 @@ 74A81014471CC0EB0D5E6571 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_ValueTree.cpp"; path = "../../JuceLibraryCode/modules/juce_data_structures/values/juce_ValueTree.cpp"; sourceTree = "SOURCE_ROOT"; }; 74BAC33D6BC1D961F04DCC72 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Channel.h; path = ../../Source/Processors/Channel/Channel.h; sourceTree = "SOURCE_ROOT"; }; 74DE857CEFA10BC49FF591DB = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_Synthesiser.h"; path = "../../JuceLibraryCode/modules/juce_audio_basics/synthesisers/juce_Synthesiser.h"; sourceTree = "SOURCE_ROOT"; }; + 74E31DA11A4C1244B78A077A = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = DataQueue.cpp; path = ../../Source/Processors/RecordNode/DataQueue.cpp; sourceTree = "SOURCE_ROOT"; }; 753B81CCB5A6B6929679E7B7 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_Application.h"; path = "../../JuceLibraryCode/modules/juce_gui_basics/application/juce_Application.h"; sourceTree = "SOURCE_ROOT"; }; 7555A13E69B99B1B6C7295FD = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_InputStream.cpp"; path = "../../JuceLibraryCode/modules/juce_core/streams/juce_InputStream.cpp"; sourceTree = "SOURCE_ROOT"; }; 75A4EEE127FAB86D65FF5F6E = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_RelativeCoordinatePositioner.cpp"; path = "../../JuceLibraryCode/modules/juce_gui_basics/positioning/juce_RelativeCoordinatePositioner.cpp"; sourceTree = "SOURCE_ROOT"; }; 75E0C433EC27CFB712CD9F75 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_PluginListComponent.h"; path = "../../JuceLibraryCode/modules/juce_audio_processors/scanning/juce_PluginListComponent.h"; sourceTree = "SOURCE_ROOT"; }; 75FCE8908DD9055F90E93716 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_ResizableBorderComponent.cpp"; path = "../../JuceLibraryCode/modules/juce_gui_basics/layout/juce_ResizableBorderComponent.cpp"; sourceTree = "SOURCE_ROOT"; }; 76140C0485FDDA98C3D98E2A = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_OldSchoolLookAndFeel.cpp"; path = "../../JuceLibraryCode/modules/juce_gui_extra/lookandfeel/juce_OldSchoolLookAndFeel.cpp"; sourceTree = "SOURCE_ROOT"; }; + 762A0D03A828BA95B3B9C209 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RecordThread.h; path = ../../Source/Processors/RecordNode/RecordThread.h; sourceTree = "SOURCE_ROOT"; }; 766923F74E30FF5D6B12E7CE = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_DrawableComposite.h"; path = "../../JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawableComposite.h"; sourceTree = "SOURCE_ROOT"; }; 76E89CBE70BF8F2476B7AA34 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_SortedSet.h"; path = "../../JuceLibraryCode/modules/juce_core/containers/juce_SortedSet.h"; sourceTree = "SOURCE_ROOT"; }; 7719FB81DDF23CF0164B131D = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_BlowFish.h"; path = "../../JuceLibraryCode/modules/juce_cryptography/encryption/juce_BlowFish.h"; sourceTree = "SOURCE_ROOT"; }; @@ -823,6 +829,7 @@ 9F845E950F19FEC4E6C88F91 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_Typeface.h"; path = "../../JuceLibraryCode/modules/juce_graphics/fonts/juce_Typeface.h"; sourceTree = "SOURCE_ROOT"; }; 9FC97A1CFD250F7215B4E397 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "juce_mac_AudioCDBurner.mm"; path = "../../JuceLibraryCode/modules/juce_audio_devices/native/juce_mac_AudioCDBurner.mm"; sourceTree = "SOURCE_ROOT"; }; 9FDCF1E2B4651E58240400B9 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_TextEditor.h"; path = "../../JuceLibraryCode/modules/juce_gui_basics/widgets/juce_TextEditor.h"; sourceTree = "SOURCE_ROOT"; }; + A010F4CC42989CB1E73A8A94 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = DataQueue.h; path = ../../Source/Processors/RecordNode/DataQueue.h; sourceTree = "SOURCE_ROOT"; }; A0434BD0EE742DF9089E2750 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RHD2000Editor.h; path = ../../Source/Processors/DataThreads/RhythmNode/RHD2000Editor.h; sourceTree = "SOURCE_ROOT"; }; A0D768F1B92568344DAC9F0B = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_win32_Fonts.cpp"; path = "../../JuceLibraryCode/modules/juce_graphics/native/juce_win32_Fonts.cpp"; sourceTree = "SOURCE_ROOT"; }; A15596CDCC27B86FC070D7FA = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_Desktop.cpp"; path = "../../JuceLibraryCode/modules/juce_gui_basics/components/juce_Desktop.cpp"; sourceTree = "SOURCE_ROOT"; }; @@ -1151,27 +1158,27 @@ E7ACE8C1456403A574236451 = {isa = PBXFileReference; lastKnownFileType = file; name = "cpmono-bold-serialized"; path = "../../Resources/Fonts/cpmono-bold-serialized"; sourceTree = "SOURCE_ROOT"; }; E7EE416EF527C7506B499070 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_BigInteger.h"; path = "../../JuceLibraryCode/modules/juce_core/maths/juce_BigInteger.h"; sourceTree = "SOURCE_ROOT"; }; E835BEB3C42E4B241804BE13 = {isa = PBXFileReference; lastKnownFileType = file; name = "cpmono-light-serialized"; path = "../../Resources/Fonts/cpmono-light-serialized"; sourceTree = "SOURCE_ROOT"; }; - E8964C0BE264A55753BC6B7B = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_linux_Midi.cpp"; path = "../../JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_Midi.cpp"; sourceTree = "SOURCE_ROOT"; }; E8D51D470C9955D7D03D5469 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ChebyshevII.h; path = ../../Source/Processors/Dsp/ChebyshevII.h; sourceTree = "SOURCE_ROOT"; }; - E91923510CB2280C3A3B9E9C = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_LocalisedStrings.h"; path = "../../JuceLibraryCode/modules/juce_core/text/juce_LocalisedStrings.h"; sourceTree = "SOURCE_ROOT"; }; - EA354D7D8E48D461415D52D8 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_JPEGLoader.cpp"; path = "../../JuceLibraryCode/modules/juce_graphics/image_formats/juce_JPEGLoader.cpp"; sourceTree = "SOURCE_ROOT"; }; + E946426F95E0240683CB3337 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_DrawablePath.h"; path = "../../JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawablePath.h"; sourceTree = "SOURCE_ROOT"; }; EA9518CDEA7049C21D5CE2D5 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_Process.h"; path = "../../JuceLibraryCode/modules/juce_core/threads/juce_Process.h"; sourceTree = "SOURCE_ROOT"; }; EAB6A66678B122C578B16445 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_HighResolutionTimer.h"; path = "../../JuceLibraryCode/modules/juce_core/threads/juce_HighResolutionTimer.h"; sourceTree = "SOURCE_ROOT"; }; + EAC262A83CD2BEA14542AE89 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_StringPool.h"; path = "../../JuceLibraryCode/modules/juce_core/text/juce_StringPool.h"; sourceTree = "SOURCE_ROOT"; }; + EAEA49B9394D802B79CA8164 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_StringPairArray.h"; path = "../../JuceLibraryCode/modules/juce_core/text/juce_StringPairArray.h"; sourceTree = "SOURCE_ROOT"; }; EF3F9AA8D70E1D4D55F13182 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_AudioThumbnail.cpp"; path = "../../JuceLibraryCode/modules/juce_audio_utils/gui/juce_AudioThumbnail.cpp"; sourceTree = "SOURCE_ROOT"; }; F5A00ACFA3D76168F22F1205 = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 99E1BC08B886CFDD2CCFD462 = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "open-ephys.app"; sourceTree = "BUILT_PRODUCTS_DIR"; }; E39CC410838072043E3C30DC = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = OriginalRecording.cpp; path = ../../Source/Processors/RecordNode/OriginalRecording.cpp; sourceTree = "SOURCE_ROOT"; }; + E8964C0BE264A55753BC6B7B = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_linux_Midi.cpp"; path = "../../JuceLibraryCode/modules/juce_audio_devices/native/juce_linux_Midi.cpp"; sourceTree = "SOURCE_ROOT"; }; + E91923510CB2280C3A3B9E9C = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_LocalisedStrings.h"; path = "../../JuceLibraryCode/modules/juce_core/text/juce_LocalisedStrings.h"; sourceTree = "SOURCE_ROOT"; }; E91A272EF06892937CB4B9CE = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_ComponentDragger.cpp"; path = "../../JuceLibraryCode/modules/juce_gui_basics/mouse/juce_ComponentDragger.cpp"; sourceTree = "SOURCE_ROOT"; }; E93BE115650B1CB80EACB841 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = EditorViewportButtons.h; path = ../../Source/UI/EditorViewportButtons.h; sourceTree = "SOURCE_ROOT"; }; - E946426F95E0240683CB3337 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_DrawablePath.h"; path = "../../JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawablePath.h"; sourceTree = "SOURCE_ROOT"; }; E97684DCE824DEDA6683C6CD = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_Synthesiser.cpp"; path = "../../JuceLibraryCode/modules/juce_audio_basics/synthesisers/juce_Synthesiser.cpp"; sourceTree = "SOURCE_ROOT"; }; EA2FC92CECD1EDA1F07DC59C = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_TooltipWindow.h"; path = "../../JuceLibraryCode/modules/juce_gui_basics/windows/juce_TooltipWindow.h"; sourceTree = "SOURCE_ROOT"; }; + EA354D7D8E48D461415D52D8 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_JPEGLoader.cpp"; path = "../../JuceLibraryCode/modules/juce_graphics/image_formats/juce_JPEGLoader.cpp"; sourceTree = "SOURCE_ROOT"; }; EA73332E3D5AEC04ADDFBB2A = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_AudioDataConverters.h"; path = "../../JuceLibraryCode/modules/juce_audio_basics/buffers/juce_AudioDataConverters.h"; sourceTree = "SOURCE_ROOT"; }; EAB2319C7AA57E06A2247CDF = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_BorderSize.h"; path = "../../JuceLibraryCode/modules/juce_graphics/geometry/juce_BorderSize.h"; sourceTree = "SOURCE_ROOT"; }; EAB637B566FEBBDADA654262 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_VSTMidiEventList.h"; path = "../../JuceLibraryCode/modules/juce_audio_processors/format_types/juce_VSTMidiEventList.h"; sourceTree = "SOURCE_ROOT"; }; - EAC262A83CD2BEA14542AE89 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_StringPool.h"; path = "../../JuceLibraryCode/modules/juce_core/text/juce_StringPool.h"; sourceTree = "SOURCE_ROOT"; }; EAC7A64301F0BF2C5E33A1F9 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = "juce_InterprocessConnectionServer.cpp"; path = "../../JuceLibraryCode/modules/juce_events/interprocess/juce_InterprocessConnectionServer.cpp"; sourceTree = "SOURCE_ROOT"; }; - EAEA49B9394D802B79CA8164 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_StringPairArray.h"; path = "../../JuceLibraryCode/modules/juce_core/text/juce_StringPairArray.h"; sourceTree = "SOURCE_ROOT"; }; EB5F9A50EB53A57D6AE303C2 = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = "juce_mac_QuickTimeMovieComponent.mm"; path = "../../JuceLibraryCode/modules/juce_video/native/juce_mac_QuickTimeMovieComponent.mm"; sourceTree = "SOURCE_ROOT"; }; EBD8622EAEF10558809888B7 = {isa = PBXFileReference; lastKnownFileType = image.png; name = "RadioButtons_selected_over-01.png"; path = "../../Resources/Images/Icons/RadioButtons_selected_over-01.png"; sourceTree = "SOURCE_ROOT"; }; EC95A2CF4B33EA37DA5FC1AC = {isa = PBXFileReference; lastKnownFileType = file.ttf; name = nordic.ttf; path = ../../Resources/Fonts/nordic.ttf; sourceTree = "SOURCE_ROOT"; }; @@ -1467,6 +1474,11 @@ 4CB63EE1552BBFDEB1DADB0A, B695B24906116ADEFC9D9B5C, ); name = ProcessorGraph; sourceTree = "<group>"; }; 0E7092A11A3C96E5ECA71CDA = {isa = PBXGroup; children = ( + 74E31DA11A4C1244B78A077A, + A010F4CC42989CB1E73A8A94, + 066A1CD777247BC8142A7DAA, + 699B3251715DE04674E0E0C4, + 762A0D03A828BA95B3B9C209, 7DB22AC6407EEA88F3FFA16D, 398BF0B03B719107E6093F98, E39CC410838072043E3C30DC, @@ -2807,6 +2819,8 @@ F2586A2DCEF44961AEA247E8, 3E7939ABAA984EE8BFC8CEDD, BAC379C03C2E7995F2393EF5, + 0326A368BA8F70C74A8A12A7, + F7E069E1FC1BB7EF856AA083, E1247DDF1C88D99691499E52, 0A8D8C2D02858F0F08356EA9, AEDA8F23648EABF79215B566, diff --git a/Builds/VisualStudio2012/open-ephys.vcxproj b/Builds/VisualStudio2012/open-ephys.vcxproj index 36d5935abbe1ec78356b38e4a1cf3995cfcea13e..0c0301996c320dbea3b6c93fc7714c1c3f8c7a4e 100644 --- a/Builds/VisualStudio2012/open-ephys.vcxproj +++ b/Builds/VisualStudio2012/open-ephys.vcxproj @@ -310,6 +310,8 @@ <ClCompile Include="..\..\Source\Processors\Parameter\ParameterEditor.cpp"/> <ClCompile Include="..\..\Source\Processors\Parameter\Parameter.cpp"/> <ClCompile Include="..\..\Source\Processors\ProcessorGraph\ProcessorGraph.cpp"/> + <ClCompile Include="..\..\Source\Processors\RecordNode\DataQueue.cpp"/> + <ClCompile Include="..\..\Source\Processors\RecordNode\RecordThread.cpp"/> <ClCompile Include="..\..\Source\Processors\RecordNode\EngineConfigWindow.cpp"/> <ClCompile Include="..\..\Source\Processors\RecordNode\OriginalRecording.cpp"/> <ClCompile Include="..\..\Source\Processors\RecordNode\RecordEngine.cpp"/> @@ -1527,6 +1529,9 @@ <ClInclude Include="..\..\Source\Processors\Parameter\ParameterEditor.h"/> <ClInclude Include="..\..\Source\Processors\Parameter\Parameter.h"/> <ClInclude Include="..\..\Source\Processors\ProcessorGraph\ProcessorGraph.h"/> + <ClInclude Include="..\..\Source\Processors\RecordNode\DataQueue.h"/> + <ClInclude Include="..\..\Source\Processors\RecordNode\EventQueue.h"/> + <ClInclude Include="..\..\Source\Processors\RecordNode\RecordThread.h"/> <ClInclude Include="..\..\Source\Processors\RecordNode\EngineConfigWindow.h"/> <ClInclude Include="..\..\Source\Processors\RecordNode\OriginalRecording.h"/> <ClInclude Include="..\..\Source\Processors\RecordNode\RecordEngine.h"/> diff --git a/Builds/VisualStudio2012/open-ephys.vcxproj.filters b/Builds/VisualStudio2012/open-ephys.vcxproj.filters index cdfdb6dd81f05ae25b15b88d4cb1cd3c8c3afc4b..426497b4220dc8a3017291cc97f20dbd61b82d8b 100644 --- a/Builds/VisualStudio2012/open-ephys.vcxproj.filters +++ b/Builds/VisualStudio2012/open-ephys.vcxproj.filters @@ -571,6 +571,12 @@ <ClCompile Include="..\..\Source\Processors\ProcessorGraph\ProcessorGraph.cpp"> <Filter>open-ephys\Source\Processors\ProcessorGraph</Filter> </ClCompile> + <ClCompile Include="..\..\Source\Processors\RecordNode\DataQueue.cpp"> + <Filter>open-ephys\Source\Processors\RecordNode</Filter> + </ClCompile> + <ClCompile Include="..\..\Source\Processors\RecordNode\RecordThread.cpp"> + <Filter>open-ephys\Source\Processors\RecordNode</Filter> + </ClCompile> <ClCompile Include="..\..\Source\Processors\RecordNode\EngineConfigWindow.cpp"> <Filter>open-ephys\Source\Processors\RecordNode</Filter> </ClCompile> @@ -2079,6 +2085,15 @@ <ClInclude Include="..\..\Source\Processors\ProcessorGraph\ProcessorGraph.h"> <Filter>open-ephys\Source\Processors\ProcessorGraph</Filter> </ClInclude> + <ClInclude Include="..\..\Source\Processors\RecordNode\DataQueue.h"> + <Filter>open-ephys\Source\Processors\RecordNode</Filter> + </ClInclude> + <ClInclude Include="..\..\Source\Processors\RecordNode\EventQueue.h"> + <Filter>open-ephys\Source\Processors\RecordNode</Filter> + </ClInclude> + <ClInclude Include="..\..\Source\Processors\RecordNode\RecordThread.h"> + <Filter>open-ephys\Source\Processors\RecordNode</Filter> + </ClInclude> <ClInclude Include="..\..\Source\Processors\RecordNode\EngineConfigWindow.h"> <Filter>open-ephys\Source\Processors\RecordNode</Filter> </ClInclude> diff --git a/Builds/VisualStudio2013/open-ephys.vcxproj b/Builds/VisualStudio2013/open-ephys.vcxproj index 70f120980976b9870847b4261581d61db486c953..9f63c1204dbeb10b41b1d9e00f6323bd93882e75 100644 --- a/Builds/VisualStudio2013/open-ephys.vcxproj +++ b/Builds/VisualStudio2013/open-ephys.vcxproj @@ -314,6 +314,8 @@ <ClCompile Include="..\..\Source\Processors\Parameter\ParameterEditor.cpp"/> <ClCompile Include="..\..\Source\Processors\Parameter\Parameter.cpp"/> <ClCompile Include="..\..\Source\Processors\ProcessorGraph\ProcessorGraph.cpp"/> + <ClCompile Include="..\..\Source\Processors\RecordNode\DataQueue.cpp"/> + <ClCompile Include="..\..\Source\Processors\RecordNode\RecordThread.cpp"/> <ClCompile Include="..\..\Source\Processors\RecordNode\EngineConfigWindow.cpp"/> <ClCompile Include="..\..\Source\Processors\RecordNode\OriginalRecording.cpp"/> <ClCompile Include="..\..\Source\Processors\RecordNode\RecordEngine.cpp"/> @@ -1531,6 +1533,9 @@ <ClInclude Include="..\..\Source\Processors\Parameter\ParameterEditor.h"/> <ClInclude Include="..\..\Source\Processors\Parameter\Parameter.h"/> <ClInclude Include="..\..\Source\Processors\ProcessorGraph\ProcessorGraph.h"/> + <ClInclude Include="..\..\Source\Processors\RecordNode\DataQueue.h"/> + <ClInclude Include="..\..\Source\Processors\RecordNode\EventQueue.h"/> + <ClInclude Include="..\..\Source\Processors\RecordNode\RecordThread.h"/> <ClInclude Include="..\..\Source\Processors\RecordNode\EngineConfigWindow.h"/> <ClInclude Include="..\..\Source\Processors\RecordNode\OriginalRecording.h"/> <ClInclude Include="..\..\Source\Processors\RecordNode\RecordEngine.h"/> diff --git a/Builds/VisualStudio2013/open-ephys.vcxproj.filters b/Builds/VisualStudio2013/open-ephys.vcxproj.filters index 52b9b68af1e7f94f3e8ff975c632e42ebcd793ed..f8f2090e35b7ca5a7505fb05e4ad517e1e98feb9 100644 --- a/Builds/VisualStudio2013/open-ephys.vcxproj.filters +++ b/Builds/VisualStudio2013/open-ephys.vcxproj.filters @@ -571,6 +571,12 @@ <ClCompile Include="..\..\Source\Processors\ProcessorGraph\ProcessorGraph.cpp"> <Filter>open-ephys\Source\Processors\ProcessorGraph</Filter> </ClCompile> + <ClCompile Include="..\..\Source\Processors\RecordNode\DataQueue.cpp"> + <Filter>open-ephys\Source\Processors\RecordNode</Filter> + </ClCompile> + <ClCompile Include="..\..\Source\Processors\RecordNode\RecordThread.cpp"> + <Filter>open-ephys\Source\Processors\RecordNode</Filter> + </ClCompile> <ClCompile Include="..\..\Source\Processors\RecordNode\EngineConfigWindow.cpp"> <Filter>open-ephys\Source\Processors\RecordNode</Filter> </ClCompile> @@ -2079,6 +2085,15 @@ <ClInclude Include="..\..\Source\Processors\ProcessorGraph\ProcessorGraph.h"> <Filter>open-ephys\Source\Processors\ProcessorGraph</Filter> </ClInclude> + <ClInclude Include="..\..\Source\Processors\RecordNode\DataQueue.h"> + <Filter>open-ephys\Source\Processors\RecordNode</Filter> + </ClInclude> + <ClInclude Include="..\..\Source\Processors\RecordNode\EventQueue.h"> + <Filter>open-ephys\Source\Processors\RecordNode</Filter> + </ClInclude> + <ClInclude Include="..\..\Source\Processors\RecordNode\RecordThread.h"> + <Filter>open-ephys\Source\Processors\RecordNode</Filter> + </ClInclude> <ClInclude Include="..\..\Source\Processors\RecordNode\EngineConfigWindow.h"> <Filter>open-ephys\Source\Processors\RecordNode</Filter> </ClInclude> diff --git a/Source/CoreServices.cpp b/Source/CoreServices.cpp index 84a668fd697378d7d4e042cf2d1ea5aef302b787..f17620830644cc9276486889f22a137a99c862ed 100644 --- a/Source/CoreServices.cpp +++ b/Source/CoreServices.cpp @@ -107,6 +107,16 @@ void setAppendTextToRecordingDir(String text) getControlPanel()->setAppendText(text); } +String getSelectedRecordEngineId() +{ + return getControlPanel()->getSelectedRecordEngineId(); +} + +bool setSelectedRecordEngineId(String id) +{ + return getControlPanel()->setSelectedRecordEngineId(id); +} + namespace RecordNode { void createNewrecordingDir() @@ -145,7 +155,7 @@ int addSpikeElectrode(SpikeRecordInfo* elec) } }; -PLUGIN_API const char* getApplicationResource(const char* name, int& size) +const char* getApplicationResource(const char* name, int& size) { return BinaryData::getNamedResource(name, size); } diff --git a/Source/CoreServices.h b/Source/CoreServices.h index 4b46d403afc7ba1d76aefeeecc221147d6487962..b8e254d4a6bdcd85e5e3001b685077b25d145614 100644 --- a/Source/CoreServices.h +++ b/Source/CoreServices.h @@ -78,6 +78,14 @@ PLUGIN_API void setPrependTextToRecordingDir(String text); /** Manually set the text to be appended to the recording directory */ PLUGIN_API void setAppendTextToRecordingDir(String text); +/** Gets the ID fo the selected Record Engine*/ +PLUGIN_API String getSelectedRecordEngineId(); + +/** Sets a specific RecordEngine to be used based on its id. +Return true if there is an engine with the specified ID and it's possible to +change the current engine or false otherwise. */ +PLUGIN_API bool setSelectedRecordEngineId(String id); + namespace RecordNode { /** Forces creation of new directory on recording */ diff --git a/Source/Plugins/Headers/CoreServicesHeader.h b/Source/Plugins/Headers/CoreServicesHeader.h new file mode 100644 index 0000000000000000000000000000000000000000..49871820b0fb990c587922671f3c341c6b0a977a --- /dev/null +++ b/Source/Plugins/Headers/CoreServicesHeader.h @@ -0,0 +1,29 @@ +/* +------------------------------------------------------------------ + +This file is part of the Open Ephys GUI +Copyright (C) 2013 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/>. + +*/ + +/* +This file references the CoreServices namespace header, in case a plugin type that usually doesn't +access this namespace (FileSource, for example) needs, as a particular case, use its functionality +*/ + +#include "../../CoreServices.h" diff --git a/Source/Plugins/KWIKFormat/FileSource/KwikFileSource.cpp b/Source/Plugins/KWIKFormat/FileSource/KwikFileSource.cpp index af9c7951489da38569facb3e9714fe33f05eef15..7a626fc82a617e2f2f48ceeb5898e987f07f5a23 100644 --- a/Source/Plugins/KWIKFormat/FileSource/KwikFileSource.cpp +++ b/Source/Plugins/KWIKFormat/FileSource/KwikFileSource.cpp @@ -22,14 +22,14 @@ */ #include <H5Cpp.h> #include "KwikFileSource.h" - +#include <CoreServicesHeader.h> using namespace H5; #define PROCESS_ERROR std::cerr << "KwikFilesource exception: " << error.getCDetailMsg() << std::endl -KWIKFileSource::KWIKFileSource() : samplePos(0) +KWIKFileSource::KWIKFileSource() : samplePos(0), skipRecordEngineCheck(false) { } @@ -247,4 +247,32 @@ void KWIKFileSource::processChannelData(int16* inBuffer, float* outBuffer, int c *(outBuffer+i) = *(inBuffer+(n*i)+channel) * bitVolts; } +} + +bool KWIKFileSource::isReady() +{ + //HDF5 is by default not thread-safe, so we must warn the user. + if ((!skipRecordEngineCheck) && (CoreServices::getSelectedRecordEngineId() == "KWIK")) + { + int res = AlertWindow::showYesNoCancelBox(AlertWindow::WarningIcon, "Record format conflict", + "Both the selected input file for the File Reader and the output file format for recording use the HDF5 library.\n" + "This library is, by default, not thread safe, so running both at the same time might cause unexpected crashes (chances increase with signal complexity and number of recorded channels).\n\n" + "If you have a custom-built hdf5 library with the thread safe features turned on, you can safely continue, but performance will be reduced.\n" + "More information on:\n" + "https://www.hdfgroup.org/HDF5/doc/TechNotes/ThreadSafeLibrary.html\n" + "https://www.hdfgroup.org/hdf5-quest.html\n\n" + "Do you want to continue acquisition?", "Yes", "Yes and don't ask again", "No"); + switch (res) + { + case 2: + skipRecordEngineCheck = true; + case 1: + return true; + break; + default: + return false; + } + } + else + return true; } \ No newline at end of file diff --git a/Source/Plugins/KWIKFormat/FileSource/KwikFileSource.h b/Source/Plugins/KWIKFormat/FileSource/KwikFileSource.h index ba92857e831e7ccb2c86d0b6f1461311b1e665f1..7bacf4964b506f484270b414252f1be35bb6cb1e 100644 --- a/Source/Plugins/KWIKFormat/FileSource/KwikFileSource.h +++ b/Source/Plugins/KWIKFormat/FileSource/KwikFileSource.h @@ -43,20 +43,23 @@ public: KWIKFileSource(); ~KWIKFileSource(); - int readData(int16* buffer, int nSamples); + int readData(int16* buffer, int nSamples) override; - void seekTo(int64 sample); + void seekTo(int64 sample) override; - void processChannelData(int16* inBuffer, float* outBuffer, int channel, int64 numSamples); + void processChannelData(int16* inBuffer, float* outBuffer, int channel, int64 numSamples) override; + + bool isReady() override; private: ScopedPointer<H5::H5File> sourceFile; ScopedPointer<H5::DataSet> dataSet; - bool Open(File file); - void fillRecordInfo(); - void updateActiveRecord(); + bool Open(File file) override; + void fillRecordInfo() override; + void updateActiveRecord() override; int64 samplePos; Array<int> availableDataSets; + bool skipRecordEngineCheck; }; diff --git a/Source/Plugins/KWIKFormat/RecordEngine/HDF5FileFormat.cpp b/Source/Plugins/KWIKFormat/RecordEngine/HDF5FileFormat.cpp index 46ac97f76d04f2eaeeb361938bbab2939d72830a..31eabc8f4b6afd674a7185407d7e156bb2d4c9ec 100644 --- a/Source/Plugins/KWIKFormat/RecordEngine/HDF5FileFormat.cpp +++ b/Source/Plugins/KWIKFormat/RecordEngine/HDF5FileFormat.cpp @@ -25,7 +25,7 @@ #include "HDF5FileFormat.h" #ifndef CHUNK_XSIZE -#define CHUNK_XSIZE 640 +#define CHUNK_XSIZE 2048 #endif #ifndef EVENT_CHUNK_SIZE @@ -97,7 +97,7 @@ int HDF5FileBase::open(bool newfile, int nChans) FileAccPropList props = FileAccPropList::DEFAULT; if (nChans > 0) { - props.setCache(0, 809, 8 * 2 * 640 * nChans, 1); + props.setCache(0, 809, 8 * 2 * CHUNK_XSIZE * nChans, 1); //std::cout << "opening HDF5 " << getFileName() << " with nchans: " << nChans << std::endl; } @@ -506,6 +506,8 @@ HDF5RecordingData::HDF5RecordingData(DataSet* data) HDF5RecordingData::~HDF5RecordingData() { + //Safety + dSet->flush(H5F_SCOPE_GLOBAL); } int HDF5RecordingData::writeDataBlock(int xDataSize, HDF5FileBase::DataTypes type, void* data) { @@ -712,6 +714,15 @@ void KWDFile::writeRowData(int16* data, int nSamples) curChan++; } +void KWDFile::writeRowData(int16* data, int nSamples, int channel) +{ + if (channel >= 0 && channel < nChannels) + { + CHECK_ERROR(recdata->writeDataRow(channel, nSamples, I16, data)); + curChan = channel; + } +} + //KWE File KWEFile::KWEFile(String basename) : HDF5FileBase() diff --git a/Source/Plugins/KWIKFormat/RecordEngine/HDF5FileFormat.h b/Source/Plugins/KWIKFormat/RecordEngine/HDF5FileFormat.h index 1be0343c6c3307ad7567a11c7555cbef9656a5d7..ca0f190f349f0ebfd422f8d91bf002220bd35095 100644 --- a/Source/Plugins/KWIKFormat/RecordEngine/HDF5FileFormat.h +++ b/Source/Plugins/KWIKFormat/RecordEngine/HDF5FileFormat.h @@ -127,6 +127,7 @@ public: void stopRecording(); void writeBlockData(int16* data, int nSamples); void writeRowData(int16* data, int nSamples); + void writeRowData(int16* data, int nSamples, int channel); String getFileName(); protected: diff --git a/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.cpp b/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.cpp index e3b415a5697b9ba7528ae1a0b35b0e951cece675..3806adcccf3df29f69ba1aaad7813d1c92ca7319 100644 --- a/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.cpp +++ b/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.cpp @@ -47,10 +47,12 @@ String HDF5Recording::getEngineID() // this->timestamp = timestamp; // } -void HDF5Recording::registerProcessor(GenericProcessor* proc) +void HDF5Recording::registerProcessor(const GenericProcessor* proc) { HDF5RecordingInfo* info = new HDF5RecordingInfo(); - info->sample_rate = proc->getSampleRate(); + //This is a VERY BAD thig to do. temporary only until we fix const-correctness on GenericEditor methods + //(which implies modifying all the plugins and processors) + info->sample_rate = const_cast<GenericProcessor*>(proc)->getSampleRate(); info->bit_depth = 16; info->multiSample = false; infoArray.add(info); @@ -70,11 +72,12 @@ void HDF5Recording::resetChannels() sampleRatesArray.clear(); processorMap.clear(); infoArray.clear(); + recordedChanToKWDChan.clear(); if (spikesFile) spikesFile->resetChannels(); } -void HDF5Recording::addChannel(int index, Channel* chan) +void HDF5Recording::addChannel(int index,const Channel* chan) { processorMap.add(processorIndex); } @@ -94,15 +97,40 @@ void HDF5Recording::openFiles(File rootFolder, int experimentNumber, int recordi //Let's just put the first processor (usually the source node) on the KWIK for now infoArray[0]->name = String("Open Ephys Recording #") + String(recordingNumber); - if (hasAcquired) + /* if (hasAcquired) infoArray[0]->start_time = (*timestamps)[getChannel(0)->sourceNodeId]; //(*timestamps).begin()->first; else - infoArray[0]->start_time = 0; + infoArray[0]->start_time = 0;*/ + infoArray[0]->start_time = getTimestamp(0); infoArray[0]->start_sample = 0; eventFile->startNewRecording(recordingNumber,infoArray[0]); //KWD files + recordedChanToKWDChan.clear(); + Array<int> processorRecPos; + processorRecPos.insertMultiple(0, 0, fileArray.size()); + for (int i = 0; i < getNumRecordedChannels(); i++) + { + int index = processorMap[getRealChannel(i)]; + if (!fileArray[index]->isOpen()) + { + fileArray[index]->initFile(getChannel(getRealChannel(i))->nodeId, basepath); + infoArray[index]->start_time = getTimestamp(i); + } + + channelsPerProcessor.set(index, channelsPerProcessor[index] + 1); + bitVoltsArray[index]->add(getChannel(getRealChannel(i))->bitVolts); + sampleRatesArray[index]->add(getChannel(getRealChannel(i))->sampleRate); + if (getChannel(getRealChannel(i))->sampleRate != infoArray[index]->sample_rate) + { + infoArray[index]->multiSample = true; + } + int procPos = processorRecPos[index]; + recordedChanToKWDChan.add(procPos); + processorRecPos.set(index, procPos+1); + } +#if 0 for (int i = 0; i < processorMap.size(); i++) { int index = processorMap[i]; @@ -125,6 +153,7 @@ void HDF5Recording::openFiles(File rootFolder, int experimentNumber, int recordi } } } +#endif for (int i = 0; i < fileArray.size(); i++) { if ((!fileArray[i]->isOpen()) && (fileArray[i]->isReadyToOpen())) @@ -169,42 +198,32 @@ void HDF5Recording::closeFiles() } } -void HDF5Recording::writeData(AudioSampleBuffer& buffer) +void HDF5Recording::writeData(int writeChannel, int realChannel, const float* buffer, int size) { // int64 t1 = Time::getHighResolutionTicks(); - for (int i = 0; i < buffer.getNumChannels(); i++) - { - if (getChannel(i)->getRecordState()) - { - - int sourceNodeId = getChannel(i)->sourceNodeId; - int nSamples = (*numSamples)[sourceNodeId]; - - double multFactor = 1/(float(0x7fff) * getChannel(i)->bitVolts); - int index = processorMap[getChannel(i)->recordIndex]; - FloatVectorOperations::copyWithMultiply(scaledBuffer,buffer.getReadPointer(i,0),multFactor,nSamples); - AudioDataConverters::convertFloatToInt16LE(scaledBuffer,intBuffer,nSamples); - fileArray[index]->writeRowData(intBuffer,nSamples); - } - } + double multFactor = 1 / (float(0x7fff) * getChannel(realChannel)->bitVolts); + int index = processorMap[getChannel(realChannel)->recordIndex]; + FloatVectorOperations::copyWithMultiply(scaledBuffer, buffer, multFactor, size); + AudioDataConverters::convertFloatToInt16LE(scaledBuffer, intBuffer, size); + fileArray[index]->writeRowData(intBuffer, size, recordedChanToKWDChan[writeChannel]); // int64 t2 = Time::getHighResolutionTicks(); // std::cout << "record time: " << float(t2 - t1) / float(Time::getHighResolutionTicksPerSecond()) << std::endl; } -void HDF5Recording::writeEvent(int eventType, MidiMessage& event, int samplePosition) +void HDF5Recording::writeEvent(int eventType, const MidiMessage& event, int64 timestamp) { const uint8* dataptr = event.getRawData(); if (eventType == GenericProcessor::TTL) - eventFile->writeEvent(0,*(dataptr+2),*(dataptr+1),(void*)(dataptr+3),(*timestamps)[*(dataptr+1)]+samplePosition); + eventFile->writeEvent(0,*(dataptr+2),*(dataptr+1),(void*)(dataptr+3),timestamp); else if (eventType == GenericProcessor::MESSAGE) - eventFile->writeEvent(1,*(dataptr+2),*(dataptr+1),(void*)(dataptr+6),(*timestamps)[*(dataptr+1)]+samplePosition); + eventFile->writeEvent(1,*(dataptr+2),*(dataptr+1),(void*)(dataptr+6),timestamp); } -void HDF5Recording::addSpikeElectrode(int index, SpikeRecordInfo* elec) +void HDF5Recording::addSpikeElectrode(int index, const SpikeRecordInfo* elec) { spikesFile->addChannelGroup(elec->numChannels); } -void HDF5Recording::writeSpike(const SpikeObject& spike, int electrodeIndex) +void HDF5Recording::writeSpike(int electrodeIndex, const SpikeObject& spike, int64 /*timestamp*/) { spikesFile->writeSpike(electrodeIndex,spike.nSamples,spike.data,spike.timestamp); } diff --git a/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.h b/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.h index dc27f26ff37934a868f8f37b0ea677da827410ea..a40f5f1e903885975d74fe3d9cf878951e473562 100644 --- a/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.h +++ b/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.h @@ -33,17 +33,16 @@ public: HDF5Recording(); ~HDF5Recording(); String getEngineID(); - void openFiles(File rootFolder, int experimentNumber, int recordingNumber); - void closeFiles(); - void writeData(AudioSampleBuffer& buffer); - void writeEvent(int eventType, MidiMessage& event, int samplePosition); - void addChannel(int index, Channel* chan); - void addSpikeElectrode(int index, SpikeRecordInfo* elec); - void writeSpike(const SpikeObject& spike, int electrodeIndex); - void registerProcessor(GenericProcessor* processor); - void resetChannels(); - //oid updateTimeStamp(int64 timestamp); - void startAcquisition(); + void openFiles(File rootFolder, int experimentNumber, int recordingNumber) override; + void closeFiles() override; + void writeData(int writeChannel, int realChannel, const float* buffer, int size) override; + void writeEvent(int eventType, const MidiMessage& event, int64 timestamp) override; + void addChannel(int index, const Channel* chan) override; + void addSpikeElectrode(int index,const SpikeRecordInfo* elec) override; + void writeSpike(int electrodeIndex, const SpikeObject& spike, int64 timestamp) override; + void registerProcessor(const GenericProcessor* processor) override; + void resetChannels() override; + void startAcquisition() override; static RecordEngineManager* getEngineManager(); private: @@ -52,6 +51,7 @@ private: Array<int> processorMap; Array<int> channelsPerProcessor; + Array<int> recordedChanToKWDChan; OwnedArray<Array<float>> bitVoltsArray; OwnedArray<Array<float>> sampleRatesArray; OwnedArray<KWDFile> fileArray; diff --git a/Source/Processors/FileReader/FileReader.cpp b/Source/Processors/FileReader/FileReader.cpp index 727c27de4c9b0248dca04fe01b61c153a4db3c54..e19275f84e9361f47dbbb20d9668bb3c51208e19 100644 --- a/Source/Processors/FileReader/FileReader.cpp +++ b/Source/Processors/FileReader/FileReader.cpp @@ -80,7 +80,7 @@ bool FileReader::isReady() /* const */ } else { - return true; + return input->isReady(); } } diff --git a/Source/Processors/FileReader/FileSource.cpp b/Source/Processors/FileReader/FileSource.cpp index e506976a80b1b0def90e488699df0b0f04e47678..7657f27c7375096577636a4e3952e4463d65bd52 100644 --- a/Source/Processors/FileReader/FileSource.cpp +++ b/Source/Processors/FileReader/FileSource.cpp @@ -140,3 +140,8 @@ bool FileSource::OpenFile (File file) return fileOpened; } + +bool FileSource::isReady() +{ + return true; +} diff --git a/Source/Processors/FileReader/FileSource.h b/Source/Processors/FileReader/FileSource.h index 43871a43ca81e363a2623c086ee363ae935ef6c8..68c6c6a07f40a208d1b879ef439eb5647fc49e15 100644 --- a/Source/Processors/FileReader/FileSource.h +++ b/Source/Processors/FileReader/FileSource.h @@ -67,6 +67,7 @@ public: virtual void processChannelData (int16* inBuffer, float* outBuffer, int channel, int64 numSamples) = 0; virtual void seekTo (int64 sample) = 0; + virtual bool isReady(); protected: struct RecordInfo diff --git a/Source/Processors/RecordNode/DataQueue.cpp b/Source/Processors/RecordNode/DataQueue.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4a1f4b0cf5d2c520dacaa8a5fc95897cc0491512 --- /dev/null +++ b/Source/Processors/RecordNode/DataQueue.cpp @@ -0,0 +1,218 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2014 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 "../../../JuceLibraryCode/JuceHeader.h" +#include "DataQueue.h" + +DataQueue::DataQueue(int blockSize, int nBlocks) : +m_blockSize(blockSize), +m_numBlocks(nBlocks), +m_maxSize(blockSize*nBlocks), +m_readInProgress(false), +m_numChans(0), +m_buffer(0, blockSize*nBlocks) +{} + +DataQueue::~DataQueue() +{} + +void DataQueue::setChannels(int nChans) +{ + if (m_readInProgress) + return; + + m_fifos.clear(); + m_readSamples.clear(); + m_numChans = nChans; + m_timestamps.clear(); + m_lastReadTimestamps.clear(); + + for (int i = 0; i < nChans; ++i) + { + m_fifos.add(new AbstractFifo(m_maxSize)); + m_readSamples.add(0); + m_timestamps.add(new Array<int64>()); + m_timestamps.getLast()->resize(m_numBlocks); + m_lastReadTimestamps.add(0); + } + m_buffer.setSize(nChans, m_maxSize); +} + +void DataQueue::resize(int nBlocks) +{ + if (m_readInProgress) + return; + + int size = m_blockSize*nBlocks; + m_maxSize = size; + m_numBlocks = nBlocks; + + for (int i = 0; i < m_numChans; ++i) + { + m_fifos[i]->setTotalSize(size); + m_fifos[i]->reset(); + m_readSamples.set(i, 0); + m_timestamps[i]->resize(nBlocks); + m_lastReadTimestamps.set(i, 0); + } + m_buffer.setSize(m_numChans, size); +} + +void DataQueue::fillTimestamps(int channel, int index, int size, int64 timestamp) +{ + //Search for the next block start. + int blockMod = index % m_blockSize; + int blockIdx = index / m_blockSize; + int64 startTimestamp; + int blockStartPos; + + if (blockMod == 0) //block starts here + { + startTimestamp = timestamp; + blockStartPos = index; + } + else //we're in the middle of a block, correct to jump to the start of the next- + { + startTimestamp = timestamp + (m_blockSize - blockMod); + blockStartPos = index + (m_blockSize - blockMod); + blockIdx++; + } + + //check that the block is in range + for (int i = 0; i < size; i += m_blockSize) + { + if ((blockStartPos + i) < (index + size)) + { + int64 ts = startTimestamp + (i*m_blockSize); + m_timestamps[channel]->set(blockIdx, ts); + } + + } +} + +void DataQueue::writeChannel(const AudioSampleBuffer& buffer, int channel, int sourceChannel, int nSamples, int64 timestamp) +{ + int index1, size1, index2, size2; + m_fifos[channel]->prepareToWrite(nSamples, index1, size1, index2, size2); + if ((size1 + size2) < nSamples) + { //TODO: turn this into a proper notification. Probably returning a bool. + std::cerr << "Recording Data Queue Overflow" << std::endl; + } + m_buffer.copyFrom(channel, + index1, + buffer, + sourceChannel, + 0, + size1); + + fillTimestamps(channel, index1, size1, timestamp); + + if (size2 > 0) + { + m_buffer.copyFrom(channel, + index2, + buffer, + sourceChannel, + size1, + size2); + + fillTimestamps(channel, index2, size2, timestamp + size1); + } + m_fifos[channel]->finishedWrite(size1 + size2); +} + +/* +We could copy the internal circular buffer to an external one, as DataBuffer does. This class +is, however, intended for disk writing, which is one of the most CPU-critical systems. Just +allowing the record subsytem to access the internal buffer is way faster, altough it has to be +done with special care and manually finish the read process. +*/ + +const AudioSampleBuffer& DataQueue::getAudioBufferReference() const +{ + return m_buffer; +} + +bool DataQueue::startRead(Array<CircularBufferIndexes>& indexes, Array<int64>& timestamps, int nMax) +{ + //This should never happen, but it never hurts to be on the safe side. + if (m_readInProgress) + return false; + + m_readInProgress = true; + indexes.clear(); //Just in case it's not empty already + timestamps.clear(); + + for (int chan = 0; chan < m_numChans; ++chan) + { + CircularBufferIndexes idx; + int readyToRead = m_fifos[chan]->getNumReady(); + int samplesToRead = ((readyToRead > nMax) && (nMax > 0)) ? nMax : readyToRead; + + m_fifos[chan]->prepareToRead(samplesToRead, idx.index1, idx.size1, idx.index2, idx.size2); + indexes.add(idx); + m_readSamples.set(chan, idx.size1 + idx.size2); + + int blockMod = idx.index1 % m_blockSize; + int blockDiff = (blockMod == 0) ? 0 : (m_blockSize - blockMod); + + //If the next timestamp block is within the data we're reading, include the translated timestamp in the output + if (blockDiff < (idx.size1 + idx.size2)) + { + int blockIdx = ((idx.index1 + blockDiff) / m_blockSize) % m_numBlocks; + int64 ts = m_timestamps[chan]->getUnchecked(blockIdx) - blockDiff; + timestamps.add(ts); + //update to the end of the block + m_lastReadTimestamps.set(chan, ts+ idx.size1 + idx.size2); + } + //If not, copy the last sent again + else + { + int64 ts = m_lastReadTimestamps[chan]; + timestamps.add(ts); + m_lastReadTimestamps.set(chan, ts + idx.size1 + idx.size2); + } + } + return true; +} + +void DataQueue::stopRead() +{ + if (!m_readInProgress) + return; + + for (int i = 0; i < m_numChans; ++i) + { + m_fifos[i]->finishedRead(m_readSamples[i]); + m_readSamples.set(i, 0); + } + m_readInProgress = false; +} + +void DataQueue::getTimestampsForBlock(int idx, Array<int64>& timestamps) const +{ + timestamps.clear(); + for (int chan = 0; chan < m_numChans; ++chan) + { + timestamps.add((*m_timestamps[chan])[idx]); + } +} \ No newline at end of file diff --git a/Source/Processors/RecordNode/DataQueue.h b/Source/Processors/RecordNode/DataQueue.h new file mode 100644 index 0000000000000000000000000000000000000000..1d52d5fc22998157f3bbadf75bc1b6dc93852783 --- /dev/null +++ b/Source/Processors/RecordNode/DataQueue.h @@ -0,0 +1,73 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2014 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 DATAQUEUE_H_INCLUDED +#define DATAQUEUE_H_INCLUDED + +#include "../../../JuceLibraryCode/JuceHeader.h" + +struct CircularBufferIndexes +{ + int index1; + int size1; + int index2; + int size2; +}; + +class DataQueue +{ +public: + DataQueue(int blockSize, int nBlocks); + ~DataQueue(); + void setChannels(int nChans); + void resize(int nBlocks); + void getTimestampsForBlock(int idx, Array<int64>& timestamps) const; + + //Only the methods after this comment are considered thread-safe. + //Caution must be had to avoid calling more than one of the methods above simulatenously + void writeChannel(const AudioSampleBuffer& buffer, int channel, int sourceChannel, int nSamples, int64 timestamp); + bool startRead(Array<CircularBufferIndexes>& indexes, Array<int64>& timestamps, int nMax); + const AudioSampleBuffer& getAudioBufferReference() const; + void stopRead(); + + +private: + void fillTimestamps(int channel, int index, int size, int64 timestamp); + + OwnedArray<AbstractFifo> m_fifos; + AudioSampleBuffer m_buffer; + Array<int> m_readSamples; + OwnedArray<Array<int64>> m_timestamps; + Array<int64> m_lastReadTimestamps; + + int m_numChans; + const int m_blockSize; + bool m_readInProgress; + int m_numBlocks; + int m_maxSize; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(DataQueue); +}; + + +#endif // DATAQUEUE_H_INCLUDED diff --git a/Source/Processors/RecordNode/EngineConfigWindow.cpp b/Source/Processors/RecordNode/EngineConfigWindow.cpp index e86beb6240f5ef28ce1344ce704c75701c494b04..c0f9601ac14ec9b4770f1ae70499e50c4ee55b73 100644 --- a/Source/Processors/RecordNode/EngineConfigWindow.cpp +++ b/Source/Processors/RecordNode/EngineConfigWindow.cpp @@ -1,25 +1,25 @@ /* - ------------------------------------------------------------------ + ------------------------------------------------------------------ - This file is part of the Open Ephys GUI - Copyright (C) 2014 Florian Franzen + This file is part of the Open Ephys GUI + Copyright (C) 2014 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 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. + 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/>. + 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 "EngineConfigWindow.h" diff --git a/Source/Processors/RecordNode/EngineConfigWindow.h b/Source/Processors/RecordNode/EngineConfigWindow.h index e0f0bf823cac72e9e0fe99accd5bec3fdfc44c8c..61d22f0b572b6b913e7f81adb2454fddc6f3e05a 100644 --- a/Source/Processors/RecordNode/EngineConfigWindow.h +++ b/Source/Processors/RecordNode/EngineConfigWindow.h @@ -1,25 +1,25 @@ /* - ------------------------------------------------------------------ + ------------------------------------------------------------------ - This file is part of the Open Ephys GUI - Copyright (C) 2014 Florian Franzen + This file is part of the Open Ephys GUI + Copyright (C) 2014 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 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. + 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/>. + 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 ENGINECONFIGWINDOW_H_INCLUDED #define ENGINECONFIGWINDOW_H_INCLUDED diff --git a/Source/Processors/RecordNode/EventQueue.h b/Source/Processors/RecordNode/EventQueue.h new file mode 100644 index 0000000000000000000000000000000000000000..22dd238fc57847f7292f26d373ec7e4567cffef6 --- /dev/null +++ b/Source/Processors/RecordNode/EventQueue.h @@ -0,0 +1,141 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2014 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 EVENTQUEUE_H_INCLUDED +#define EVENTQUEUE_H_INCLUDED + +#include "../../../JuceLibraryCode/JuceHeader.h" +#include <vector> + +struct SpikeObject; + +template <class MsgContainer> +class AsyncEventMessage : + public ReferenceCountedObject +{ +public: + AsyncEventMessage(const MsgContainer& m, int64 t, int extra) : + ReferenceCountedObject(), + m_data(m), + m_timestamp(t), + m_extra(extra) + {} + + ~AsyncEventMessage() {}; + + const MsgContainer& getData() const { return m_data; } + const int64& getTimestamp() const { return m_timestamp; } + const int& getExtra() const { return m_extra; } + +private: + const MsgContainer m_data; + const int64 m_timestamp; + const int m_extra; + +}; + +template <class EventClass> +class EventQueue +{ +public: + typedef AsyncEventMessage<EventClass> EventContainer; + typedef ReferenceCountedObjectPtr<EventContainer> EventClassPtr; + + EventQueue(int size) : + m_fifo(size) + { + m_data.resize(size); + } + + ~EventQueue() + {} + + int getRemainingEvents() const + { + return m_fifo.getNumReady(); + } + + void reset() + { + m_data.clear(); + m_data.resize(m_fifo.getTotalSize()); + } + + void resize(int size) + { + m_data.clear(); + m_fifo.setTotalSize(size); + m_data.resize(size); + } + + void addEvent(const EventClass& ev, int64 t, int extra = 0) + { + int pos1, size1, pos2, size2; + size1 = 0; + m_fifo.prepareToWrite(1, pos1, size1, pos2, size2); + + /* This means there is a buffer overrun. Instead of overwritting the existing data and risking a collision of both threads + we just skip the incoming samples. TODO: use this to notify of the overrun and act consequently */ + if (size1 > 0) + { + m_data[pos1] = new EventContainer(ev, t, extra); + m_fifo.finishedWrite(1); + } + } + + int getEvents(std::vector<EventClassPtr>& vec, int max) + { + int pos1, size1, pos2, size2; + int numAvailable = m_fifo.getNumReady(); + int numToRead = ((max < numAvailable) && (max > 0)) ? max : numAvailable; + m_fifo.prepareToRead(numToRead, pos1, size1, pos2, size2); + vec.resize(numToRead); + for (int i = 0; i < size1; ++i) + { + vec[i] = m_data[pos1 + i]; + } + if (size2 > 0) + { + for (int i = 0; i < size2; ++i) + { + vec[size1 + i] = m_data[pos2 + i]; + } + } + m_fifo.finishedRead(numToRead); + return numToRead; + } + +private: + std::vector<EventClassPtr> m_data; + AbstractFifo m_fifo; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EventQueue); +}; + +typedef EventQueue<MidiMessage> EventMsgQueue; +typedef EventQueue<SpikeObject> SpikeMsgQueue; +typedef ReferenceCountedObjectPtr<AsyncEventMessage<MidiMessage>> EventMessagePtr; +typedef ReferenceCountedObjectPtr<AsyncEventMessage<SpikeObject>> SpikeMessagePtr; + +#endif // EVENTQUEUE_H_INCLUDED + diff --git a/Source/Processors/RecordNode/OriginalRecording.cpp b/Source/Processors/RecordNode/OriginalRecording.cpp index 864514ae6ed08e8bb97579bbe4d4a0a5f378ee4d..5205d144e8cf5b6a8f374f4e007352fd6149b036 100644 --- a/Source/Processors/RecordNode/OriginalRecording.cpp +++ b/Source/Processors/RecordNode/OriginalRecording.cpp @@ -1,23 +1,23 @@ /* ------------------------------------------------------------------- + ------------------------------------------------------------------ -This file is part of the Open Ephys GUI -Copyright (C) 2013 Florian Franzen + This file is part of the Open Ephys GUI + Copyright (C) 2014 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 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. + 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/>. + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. */ @@ -67,7 +67,7 @@ String OriginalRecording::getEngineID() return "OPENEPHYS"; } -void OriginalRecording::addChannel(int index, Channel* chan) +void OriginalRecording::addChannel(int index, const Channel* chan) { //Just populate the file array with null so we can address it by index afterwards fileArray.add(nullptr); @@ -75,7 +75,7 @@ void OriginalRecording::addChannel(int index, Channel* chan) samplesSinceLastTimestamp.add(0); } -void OriginalRecording::addSpikeElectrode(int index, SpikeRecordInfo* elec) +void OriginalRecording::addSpikeElectrode(int index, const SpikeRecordInfo* elec) { spikeFileArray.add(nullptr); } @@ -382,27 +382,23 @@ String OriginalRecording::generateSpikeHeader(SpikeRecordInfo* elec) return header; } -void OriginalRecording::writeEvent(int eventType, MidiMessage& event, int samplePosition) +void OriginalRecording::writeEvent(int eventType, const MidiMessage& event, int64 timestamp) { if (isWritableEvent(eventType)) - writeTTLEvent(event,samplePosition); + writeTTLEvent(event, timestamp); if (eventType == GenericProcessor::MESSAGE) - writeMessage(event,samplePosition); + writeMessage(event, timestamp); } -void OriginalRecording::writeMessage(MidiMessage& event, int samplePosition) +void OriginalRecording::writeMessage(const MidiMessage& event, int64 timestamp) { if (messageFile == nullptr) return; - uint64 samplePos = (uint64) samplePosition; - uint8 sourceNodeId = event.getNoteNumber(); - - int64 eventTimestamp = (*timestamps)[sourceNodeId] + samplePos; int msgLength = event.getRawDataSize() - 6; const char* dataptr = (const char*)event.getRawData() + 6; - String timestampText(eventTimestamp); + String timestampText(timestamp); diskWriteLock.enter(); fwrite(timestampText.toUTF8(),1,timestampText.length(),messageFile); @@ -413,7 +409,7 @@ void OriginalRecording::writeMessage(MidiMessage& event, int samplePosition) } -void OriginalRecording::writeTTLEvent(MidiMessage& event, int samplePosition) +void OriginalRecording::writeTTLEvent(const MidiMessage& event, int64 timestamp) { // find file and write samples to disk // std::cout << "Received event!" << std::endl; @@ -423,15 +419,12 @@ void OriginalRecording::writeTTLEvent(MidiMessage& event, int samplePosition) const uint8* dataptr = event.getRawData(); - uint64 samplePos = (uint64) samplePosition; - - uint8 sourceNodeId = event.getNoteNumber(); - - int64 eventTimestamp = (*timestamps)[sourceNodeId] + samplePos; // add the sample position to the buffer timestamp + //With the new external recording thread, this field has no sense. + int16 samplePos = 0; diskWriteLock.enter(); - fwrite(&eventTimestamp, // ptr + fwrite(×tamp, // ptr 8, // size of each element 1, // count eventFile); // ptr to FILE object @@ -453,64 +446,59 @@ void OriginalRecording::writeTTLEvent(MidiMessage& event, int samplePosition) diskWriteLock.exit(); } -void OriginalRecording::writeData(AudioSampleBuffer& buffer) +void OriginalRecording::writeData(int writeChannel, int realChannel, const float* buffer, int size) { + int samplesWritten = 0; - for (int i = 0; i < buffer.getNumChannels(); i++) - { - if (getChannel(i)->getRecordState()) - { - int samplesWritten = 0; - - int sourceNodeId = getChannel(i)->sourceNodeId; + int sourceNodeId = getChannel(realChannel)->sourceNodeId; - samplesSinceLastTimestamp.set(i,0); + //TODO: optimize. Now we use realchannel, we should optimize the whole thing to only use recorded channels + samplesSinceLastTimestamp.set(realChannel, 0); - int nSamples = (*numSamples)[sourceNodeId]; + int nSamples = size; while (samplesWritten < nSamples) // there are still unwritten samples in this buffer { int numSamplesToWrite = nSamples - samplesWritten; - if (blockIndex[i] + numSamplesToWrite < BLOCK_LENGTH) // we still have space in this block + if (blockIndex[realChannel] + numSamplesToWrite < BLOCK_LENGTH) // we still have space in this block { // write buffer to disk! - writeContinuousBuffer(buffer.getReadPointer(i,samplesWritten), + writeContinuousBuffer(buffer + samplesWritten, numSamplesToWrite, - i); + writeChannel); //timestamp += numSamplesToWrite; - samplesSinceLastTimestamp.set(i, samplesSinceLastTimestamp[i] + numSamplesToWrite); - blockIndex.set(i, blockIndex[i] + numSamplesToWrite); + samplesSinceLastTimestamp.set(realChannel, samplesSinceLastTimestamp[realChannel] + numSamplesToWrite); + blockIndex.set(realChannel, blockIndex[realChannel] + numSamplesToWrite); samplesWritten += numSamplesToWrite; } else // there's not enough space left in this block for all remaining samples { - numSamplesToWrite = BLOCK_LENGTH - blockIndex[i]; + numSamplesToWrite = BLOCK_LENGTH - blockIndex[realChannel]; // write buffer to disk! - writeContinuousBuffer(buffer.getReadPointer(i,samplesWritten), + writeContinuousBuffer(buffer + samplesWritten, numSamplesToWrite, - i); + writeChannel); // update our variables samplesWritten += numSamplesToWrite; //timestamp += numSamplesToWrite; - samplesSinceLastTimestamp.set(i, samplesSinceLastTimestamp[i] + numSamplesToWrite); - blockIndex.set(i,0); // back to the beginning of the block + samplesSinceLastTimestamp.set(realChannel, samplesSinceLastTimestamp[realChannel] + numSamplesToWrite); + blockIndex.set(realChannel,0); // back to the beginning of the block } } - } - } } -void OriginalRecording::writeContinuousBuffer(const float* data, int nSamples, int channel) +void OriginalRecording::writeContinuousBuffer(const float* data, int nSamples, int writeChannel) { + int channel = getRealChannel(writeChannel); // check to see if the file exists if (fileArray[channel] == nullptr) return; @@ -526,7 +514,7 @@ void OriginalRecording::writeContinuousBuffer(const float* data, int nSamples, i if (blockIndex[channel] == 0) { - writeTimestampAndSampleCount(fileArray[channel], channel); + writeTimestampAndSampleCount(fileArray[channel], writeChannel); } diskWriteLock.enter(); @@ -554,9 +542,9 @@ void OriginalRecording::writeTimestampAndSampleCount(FILE* file, int channel) uint16 samps = BLOCK_LENGTH; - int sourceNodeId = getChannel(channel)->sourceNodeId; + // int sourceNodeId = getChannel(channel)->sourceNodeId; - int64 ts = (*timestamps)[sourceNodeId] + samplesSinceLastTimestamp[channel]; + int64 ts = getTimestamp(channel) + samplesSinceLastTimestamp[channel]; fwrite(&ts, // ptr 8, // size of each element @@ -642,7 +630,7 @@ void OriginalRecording::closeFiles() // this->timestamp = timestamp; // } -void OriginalRecording::writeSpike(const SpikeObject& spike, int electrodeIndex) +void OriginalRecording::writeSpike(int electrodeIndex, const SpikeObject& spike, int64 timestamp) { uint8_t spikeBuffer[MAX_SPIKE_BUFFER_LEN]; diff --git a/Source/Processors/RecordNode/OriginalRecording.h b/Source/Processors/RecordNode/OriginalRecording.h index 82cd89092cbce28a99f3335e6deb65561f25fc1b..8a7e7dd3f887630eae1c3bdac922dbe465ac6bf7 100644 --- a/Source/Processors/RecordNode/OriginalRecording.h +++ b/Source/Processors/RecordNode/OriginalRecording.h @@ -1,26 +1,25 @@ /* - ------------------------------------------------------------------ + ------------------------------------------------------------------ - This file is part of the Open Ephys GUI - Copyright (C) 2013 Florian Franzen + This file is part of the Open Ephys GUI + Copyright (C) 2014 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 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. + 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/>. - - */ + 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 ORIGINALRECORDING_H_INCLUDED #define ORIGINALRECORDING_H_INCLUDED @@ -47,15 +46,14 @@ public: void setParameter(EngineParameter& parameter); String getEngineID(); - void openFiles(File rootFolder, int experimentNumber, int recordingNumber); - void closeFiles(); - void writeData(AudioSampleBuffer& buffer); - void writeEvent(int eventType, MidiMessage& event, int samplePosition); - void addChannel(int index, Channel* chan); - void resetChannels(); - //void updateTimeStamp(int64 timestamp); - void addSpikeElectrode(int index, SpikeRecordInfo* elec); - void writeSpike(const SpikeObject& spike, int electrodeIndex); + void openFiles(File rootFolder, int experimentNumber, int recordingNumber) override; + void closeFiles() override; + void writeData(int writeChannel, int realChannel, const float* buffer, int size) override; + void writeEvent(int eventType, const MidiMessage& event, int64 timestamp) override; + void addChannel(int index, const Channel* chan) override; + void resetChannels() override; + void addSpikeElectrode(int index, const SpikeRecordInfo* elec) override; + void writeSpike(int electrodeIndex, const SpikeObject& spike, int64 timestamp) override; static RecordEngineManager* getEngineManager(); @@ -71,8 +69,8 @@ private: String generateSpikeHeader(SpikeRecordInfo* elec); void openMessageFile(File rootFolder); - void writeTTLEvent(MidiMessage& event, int samplePosition); - void writeMessage(MidiMessage& event, int samplePosition); + void writeTTLEvent(const MidiMessage& event, int64 timestamp); + void writeMessage(const MidiMessage& event, int64 timestamp); void writeXml(); diff --git a/Source/Processors/RecordNode/RecordEngine.cpp b/Source/Processors/RecordNode/RecordEngine.cpp index e62c06b4811c1dd8dc255f5a91305e78efcf32e4..4fb307ad0ab3229a58607631d7290dad836fc726 100644 --- a/Source/Processors/RecordNode/RecordEngine.cpp +++ b/Source/Processors/RecordNode/RecordEngine.cpp @@ -1,25 +1,25 @@ /* - ------------------------------------------------------------------ + ------------------------------------------------------------------ - This file is part of the Open Ephys GUI - Copyright (C) 2014 Florian Franzen + This file is part of the Open Ephys GUI + Copyright (C) 2014 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 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. + 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/>. + 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 "RecordEngine.h" #include "RecordNode.h" @@ -40,33 +40,52 @@ void RecordEngine::setParameter(EngineParameter& parameter) {} void RecordEngine::resetChannels() {} -void RecordEngine::registerProcessor(GenericProcessor* processor) {} +void RecordEngine::registerProcessor(const GenericProcessor* processor) {} -Channel* RecordEngine::getChannel(int index) +void RecordEngine::addChannel(int index, const Channel* chan) {} + +Channel* RecordEngine::getChannel(int index) const { return AccessClass::getProcessorGraph()->getRecordNode()->getDataChannel(index); } -String RecordEngine::generateDateString() +String RecordEngine::generateDateString() const { return AccessClass::getProcessorGraph()->getRecordNode()->generateDateString(); } -SpikeRecordInfo* RecordEngine::getSpikeElectrode(int index) +SpikeRecordInfo* RecordEngine::getSpikeElectrode(int index) const { return AccessClass::getProcessorGraph()->getRecordNode()->getSpikeElectrode(index); } -void RecordEngine::updateTimestamps(std::map<uint8, int64>* ts) +void RecordEngine::updateTimestamps(const Array<int64>& ts, int channel) { - timestamps = ts; + if (channel < 0) + timestamps = ts; + else + timestamps.set(channel, ts[channel]); } -void RecordEngine::updateNumSamples(std::map<uint8, int>* ns) +void RecordEngine::setChannelMapping(const Array<int>& chans) { - numSamples = ns; + channelMap = chans; } +int64 RecordEngine::getTimestamp(int channel) const +{ + return timestamps[channel]; +} + +int RecordEngine::getRealChannel(int channel) const +{ + return channelMap[channel]; +} + +int RecordEngine::getNumRecordedChannels() const +{ + return channelMap.size(); +} void RecordEngine::registerSpikeSource(GenericProcessor* processor) {} diff --git a/Source/Processors/RecordNode/RecordEngine.h b/Source/Processors/RecordNode/RecordEngine.h index e9fc8585fd9f1acdea83a530a987f362a23f9484..328967230378030ada630d83ca3a9bd3cd826778 100644 --- a/Source/Processors/RecordNode/RecordEngine.h +++ b/Source/Processors/RecordNode/RecordEngine.h @@ -1,25 +1,25 @@ /* - ------------------------------------------------------------------ + ------------------------------------------------------------------ - This file is part of the Open Ephys GUI - Copyright (C) 2014 Florian Franzen + This file is part of the Open Ephys GUI + Copyright (C) 2014 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 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. + 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/>. + 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 RECORDENGINE_H_INCLUDED #define RECORDENGINE_H_INCLUDED @@ -61,21 +61,27 @@ public: virtual ~RecordEngine(); virtual String getEngineID() =0; - /** All the public methods (except registerManager) are called by RecordNode: + /** All the public methods (except registerManager) are called by RecordNode or RecordingThread: When acquisition starts (in the specified order): 1-resetChannels 2-registerProcessor, addChannel, registerSpikeSource, addspikeelectrode 3-configureEngine (which calls setParameter) 3-startAcquisition - During acquisition: - updateTimeStamps When recording starts (in the specified order): 1-directoryChanged (if needed) - 2-openFiles - During recording: - writeData, writeEvent, writeSpike + 2-(setChannelMapping) + 3-(updateTimestamps*) + 4-openFiles* + During recording: (RecordThread loop) + 1-(updateTimestamps*) + 2-writeData* + 3-writeEvent* (if needed) + 4-writeSpike* (if needed) When recording stops: - closeFiles + closeFiles* + + Methods marked with a * are called via the RecordThread thread. + Methods marked with parenthesis are not overloaded methods */ /** Called for registering parameters @@ -91,25 +97,23 @@ public: */ virtual void closeFiles() = 0; - /** Write continuous data. - This method gets the full data buffer, it must query getRecordState for - each registered channel to determine which channels to actually write to disk. - The number of samples to write will be found in the numSamples object. + /** Write continuous data for a channel. The raw buffer pointer is passed for speed, + care must be taken to only read the specified number of bytes. */ - virtual void writeData(AudioSampleBuffer& buffer) = 0; + virtual void writeData(int writeChannel, int realChannel, const float* buffer, int size) = 0; /** Write a single event to disk. */ - virtual void writeEvent(int eventType, MidiMessage& event, int samplePosition) = 0; + virtual void writeEvent(int eventType, const MidiMessage& event, int64 timestamp) = 0; /** Called when acquisition starts once for each processor that might record continuous data */ - virtual void registerProcessor(GenericProcessor* processor); + virtual void registerProcessor(const GenericProcessor* processor); /** Called after registerProcessor, once for each output channel of the processor */ - virtual void addChannel(int index, Channel* chan) = 0; + virtual void addChannel(int index, const Channel* chan); /** Called when acquisition starts once for each processor that might record spikes */ @@ -117,23 +121,25 @@ public: /** Called after registerSpikesource, once for each channel group */ - virtual void addSpikeElectrode(int index, SpikeRecordInfo* elec) = 0; + virtual void addSpikeElectrode(int index, const SpikeRecordInfo* elec) = 0; /** Write a spike to disk */ - virtual void writeSpike(const SpikeObject& spike, int electrodeIndex) = 0; + virtual void writeSpike(int electrodeIndex, const SpikeObject& spike, int64 timestamp) = 0; /** Called when a new acquisition starts, to clean all channel data before registering the processors */ virtual void resetChannels(); - /** Called every time a new timestamp event is received + /** Called at the start of every write block */ - void updateTimestamps(std::map<uint8, int64>* timestamps); + void updateTimestamps(const Array<int64>& timestamps, int channel = -1); - /** Called every time a new numSamples event is received */ - void updateNumSamples(std::map<uint8, int>* numSamples); + /** Called prior to opening files, to set the map between recorded + channels and actual channel numbers + */ + void setChannelMapping(const Array<int>& channels); /** Called after all channels and spike groups have been registered, just before acquisition starts @@ -157,20 +163,31 @@ protected: /** Gets the specified channel from the channel array stored in RecordNode */ - Channel* getChannel(int index); + Channel* getChannel(int index) const; /** Gets the specified channel group info structure from the array stored in RecordNode */ - SpikeRecordInfo* getSpikeElectrode(int index); + SpikeRecordInfo* getSpikeElectrode(int index) const; /** Generate a Matlab-compatible datestring */ - String generateDateString(); + String generateDateString() const; + + /** Gets the current block's first timestamp for a given channel + */ + int64 getTimestamp(int channel) const; + + /** Gets the actual channel number from a recorded channel index + */ + int getRealChannel(int channel) const; - std::map<uint8, int>* numSamples; - std::map<uint8, int64>* timestamps; + /** Gets the number of recorded channels + */ + int getNumRecordedChannels() const; private: + Array<int64> timestamps; + Array<int> channelMap; RecordEngineManager* manager; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RecordEngine); diff --git a/Source/Processors/RecordNode/RecordNode.cpp b/Source/Processors/RecordNode/RecordNode.cpp index 83a80bef5a0d233a74b718b56d0238b311439257..b41604eab08cfdc03cf1ab72789d4190ae33547d 100755 --- a/Source/Processors/RecordNode/RecordNode.cpp +++ b/Source/Processors/RecordNode/RecordNode.cpp @@ -27,6 +27,8 @@ #include "../../UI/ControlPanel.h" #include "../../AccessClass.h" #include "RecordEngine.h" +#include "RecordThread.h" +#include "DataQueue.h" #define EVERY_ENGINE for(int eng = 0; eng < engineArray.size(); eng++) engineArray[eng] @@ -41,15 +43,11 @@ RecordNode::RecordNode() isProcessing = false; isRecording = false; - allFilesOpened = false; - signalFilesShouldClose = false; - - signalFilesShouldClose = false; + setFirstBlock = false; settings.numInputs = 2048; settings.numOutputs = 0; - eventChannel = new Channel(this, 0, EVENT_CHANNEL); recordingNumber = -1; spikeElectrodeIndex = 0; @@ -60,13 +58,16 @@ RecordNode::RecordNode() // 128 inputs, 0 outputs setPlayConfigDetails(getNumInputs(),getNumOutputs(),44100.0,128); - + m_recordThread = new RecordThread(engineArray); + m_dataQueue = new DataQueue(WRITE_BLOCK_LENGTH, DATA_BUFFER_NBLOCKS); + m_eventQueue = new EventMsgQueue(EVENT_BUFFER_NEVENTS); + m_spikeQueue = new SpikeMsgQueue(SPIKE_BUFFER_NSPIKES); + m_recordThread->setQueuePointers(m_dataQueue, m_eventQueue, m_spikeQueue); } RecordNode::~RecordNode() { - delete eventChannel; // Memory leak fixed by Michael Borisov } void RecordNode::setChannel(Channel* ch) @@ -117,19 +118,6 @@ void RecordNode::filenameComponentChanged(FilenameComponent* fnc) } -void RecordNode::updateChannelName(int channelIndex, String newname) -{ - /* if (channelPointers[channelIndex] != nullptr && channelIndex < channelPointers.size()) - { - channelPointers[channelIndex]->name = newname; - updateFileName(channelPointers[channelIndex]); - } else*/ - { - // keep name and do the change when the pointer actually points to something... ? - modifiedChannelNames.add(newname); - modifiedChannelInd.add(channelIndex); - } -} void RecordNode::getChannelNamesAndRecordingStatus(StringArray& names, Array<bool>& recording) { @@ -186,17 +174,6 @@ void RecordNode::addInputChannel(GenericProcessor* sourceNode, int chan) } -void RecordNode::updateTrialNumber() -{ - trialNum++; -} - -void RecordNode::appendTrialNumber(bool t) -{ - appendTrialNum = t; -} - - void RecordNode::createNewDirectory() { std::cout << "Creating new directory." << std::endl; @@ -299,9 +276,8 @@ void RecordNode::setParameter(int parameterIndex, float newValue) if (parameterIndex == 1) { - isRecording = true; - hasRecorded = true; - // std::cout << "START RECORDING." << std::endl; + + // std::cout << "START RECORDING." << std::endl; if (newDirectoryNeeded) { @@ -327,9 +303,31 @@ void RecordNode::setParameter(int parameterIndex, float newValue) settingsNeeded = false; } - EVERY_ENGINE->openFiles(rootFolder, experimentNumber, recordingNumber); + m_recordThread->setFileComponents(rootFolder, experimentNumber, recordingNumber); + + channelMap.clear(); + int totChans = channelPointers.size(); + for (int ch = 0; ch < totChans; ++ch) + { + if (channelPointers[ch]->getRecordState()) + { + channelMap.add(ch); + } + } + int numRecordedChannels = channelMap.size(); + + EVERY_ENGINE->setChannelMapping(channelMap); + m_recordThread->setChannelMap(channelMap); + m_dataQueue->setChannels(numRecordedChannels); + m_eventQueue->reset(); + m_spikeQueue->reset(); + m_recordThread->setFirstBlockFlag(false); + + setFirstBlock = false; + m_recordThread->startThread(); - allFilesOpened = true; + isRecording = true; + hasRecorded = true; } else if (parameterIndex == 0) @@ -340,15 +338,29 @@ void RecordNode::setParameter(int parameterIndex, float newValue) if (isRecording) { - - // close necessary files - signalFilesShouldClose = true; + isRecording = false; + + // close the writing thread. + m_recordThread->signalThreadShouldExit(); + m_recordThread->waitForThreadToExit(2000); + while (m_recordThread->isThreadRunning()) + { + + if (AlertWindow::showOkCancelBox(AlertWindow::WarningIcon, "Record Thread timeout", + "The recording thread is taking too long to close.\nThis could mean there is still data waiting to be written in the buffer, but it normally " + "shouldn't take this long.\nYou can either wait a bit more or forcefully close the thread. Note that data might be lost or corrupted" + "if forcibly closing the thread.", "Stop the thread", "Wait a bit more")) + { + m_recordThread->stopThread(100); + m_recordThread->forceCloseFiles(); + } + else + { + m_recordThread->waitForThreadToExit(2000); + } + } } - - isRecording = false; - - } else if (parameterIndex == 2) { @@ -379,15 +391,6 @@ void RecordNode::setParameter(int parameterIndex, float newValue) } } -void RecordNode::closeAllFiles() -{ - if (allFilesOpened) - { - EVERY_ENGINE->closeFiles(); - allFilesOpened = false; - } -} - bool RecordNode::enable() { if (hasRecorded) @@ -411,9 +414,6 @@ bool RecordNode::disable() // close files if necessary setParameter(0, 10.0f); - if (isProcessing) - closeAllFiles(); - isProcessing = false; return true; @@ -427,13 +427,15 @@ float RecordNode::getFreeSpace() void RecordNode::handleEvent(int eventType, MidiMessage& event, int samplePosition) { - if (isRecording && allFilesOpened) + if (isRecording) { if (isWritableEvent(eventType)) { if (*(event.getRawData()+4) > 0) // saving flag > 0 (i.e., event has not already been processed) { - EVERY_ENGINE->writeEvent(eventType, event, samplePosition); + uint8 sourceNodeId = event.getNoteNumber(); + int64 timestamp = timestamps[sourceNodeId] + samplePosition; + m_eventQueue->addEvent(event, timestamp, eventType); } } } @@ -442,34 +444,32 @@ void RecordNode::handleEvent(int eventType, MidiMessage& event, int samplePositi void RecordNode::process(AudioSampleBuffer& buffer, MidiBuffer& events) { - //update timstamp data even if we're not recording yet - EVERY_ENGINE->updateTimestamps(×tamps); - EVERY_ENGINE->updateNumSamples(&numSamples); // FIRST: cycle through events -- extract the TTLs and the timestamps checkForEvents(events); - if (isRecording && allFilesOpened) + if (isRecording) { // SECOND: write channel data - if (channelPointers.size() > 0) - { - EVERY_ENGINE->writeData(buffer); - } + int recordChans = channelMap.size(); + for (int chan = 0; chan < recordChans; ++chan) + { + int realChan = channelMap[chan]; + int sourceNodeId = channelPointers[realChan]->sourceNodeId; + int nSamples = numSamples.at(sourceNodeId); + int timestamp = timestamps.at(sourceNodeId); + m_dataQueue->writeChannel(buffer, chan, realChan, nSamples, timestamp); + } // std::cout << nSamples << " " << samplesWritten << " " << blockIndex << std::endl; - - return; - + if (!setFirstBlock) + { + m_recordThread->setFirstBlockFlag(true); + setFirstBlock = true; + } + } - // this is intended to prevent parameter changes from closing files - // before recording stops - if (signalFilesShouldClose) - { - closeAllFiles(); - signalFilesShouldClose = false; - } } @@ -488,7 +488,7 @@ void RecordNode::registerRecordEngine(RecordEngine* engine) engineArray.add(engine); } -void RecordNode::registerSpikeSource(GenericProcessor* processor) +void RecordNode::registerSpikeSource(GenericProcessor* processor) { EVERY_ENGINE->registerSpikeSource(processor); } @@ -503,7 +503,10 @@ int RecordNode::addSpikeElectrode(SpikeRecordInfo* elec) void RecordNode::writeSpike(SpikeObject& spike, int electrodeIndex) { - EVERY_ENGINE->writeSpike(spike,electrodeIndex); + if (isRecording) + { + m_spikeQueue->addEvent(spike, spike.timestamp, electrodeIndex); + } } SpikeRecordInfo* RecordNode::getSpikeElectrode(int index) diff --git a/Source/Processors/RecordNode/RecordNode.h b/Source/Processors/RecordNode/RecordNode.h index 70facf9f34c5780e95188806916f3842f31ae964..92af93e5920e4f1e46f0e4e45ad417c5211a875e 100755 --- a/Source/Processors/RecordNode/RecordNode.h +++ b/Source/Processors/RecordNode/RecordNode.h @@ -26,18 +26,23 @@ #include "../../../JuceLibraryCode/JuceHeader.h" #include <stdio.h> #include <map> +#include <atomic> #include "../GenericProcessor/GenericProcessor.h" #include "../Channel/Channel.h" +#include "EventQueue.h" - -#define HEADER_SIZE 1024 -#define BLOCK_LENGTH 1024 +#define WRITE_BLOCK_LENGTH 1024 +#define DATA_BUFFER_NBLOCKS 300 +#define EVENT_BUFFER_NEVENTS 512 +#define SPIKE_BUFFER_NSPIKES 512 struct SpikeRecordInfo; struct SpikeObject; class RecordEngine; +class RecordThread; +class DataQueue; /** @@ -92,9 +97,6 @@ public: /** returns channel names and whether we record them */ void getChannelNamesAndRecordingStatus(StringArray& names, Array<bool>& recording); - /** update channel name */ - void updateChannelName(int channelIndex, String newname); - /** Get channel stored in channelPointers array */ Channel* getDataChannel(int index); @@ -132,10 +134,6 @@ public: return rootFolder; } - void appendTrialNumber(bool); - - void updateTrialNumber(); - /** Adds a Record Engine to use */ void registerRecordEngine(RecordEngine* engine); @@ -163,8 +161,7 @@ public: /** Signals when to create a new data directory when recording starts.*/ bool newDirectoryNeeded; - bool isRecording; - bool allFilesOpened; + std::atomic<bool> isRecording; /** Generate a Matlab-compatible datestring */ String generateDateString(); @@ -173,7 +170,7 @@ private: /** Keep the RecordNode informed of acquisition and record states. */ - bool isProcessing, signalFilesShouldClose; + bool isProcessing; /** User-selectable directory for saving data files. Currently defaults to the user's home directory. @@ -196,16 +193,14 @@ private: */ Time timer; - /** Closes all open files after recording has finished. - */ - void closeAllFiles(); - /** Pointers to all continuous channels */ Array<Channel*> channelPointers; /** Pointers to all event channels */ Array<Channel*> eventChannelPointers; + Array<int> channelMap; + OwnedArray<SpikeRecordInfo> spikeElectrodePointers; int spikeElectrodeIndex; @@ -213,41 +208,22 @@ private: int experimentNumber; bool hasRecorded; bool settingsNeeded; - + std::atomic<bool> setFirstBlock; /** Generates a default directory name, based on the current date and time */ String generateDirectoryName(); /** Cycle through the event buffer, looking for data to save */ void handleEvent(int eventType, MidiMessage& event, int samplePos); - /** Object for holding information about the events file */ - Channel* eventChannel; - - /** Method for writing continuous buffers to disk. - */ - void writeContinuousBuffer(const float* data, int nSamples, int channel); - - /** Method for writing event buffers to disk. - */ - void writeEventBuffer(MidiMessage& event, int samplePos); - - void writeRecordMarker(FILE*); - void writeTimestampAndSampleCount(FILE*); - - /** Used to indicate the end of each record */ - char* recordMarker; - - CriticalSection diskWriteLock; - - Array<String> modifiedChannelNames; - Array<int> modifiedChannelInd; - - bool appendTrialNum; - int trialNum; - /**RecordEngines loaded**/ OwnedArray<RecordEngine> engineArray; + ScopedPointer<RecordThread> m_recordThread; + ScopedPointer<DataQueue> m_dataQueue; + ScopedPointer<EventMsgQueue> m_eventQueue; + ScopedPointer<SpikeMsgQueue> m_spikeQueue; + + Array<int> m_recordedChannelMap; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RecordNode); diff --git a/Source/Processors/RecordNode/RecordThread.cpp b/Source/Processors/RecordNode/RecordThread.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fcf7cff7400c5ebadfc8f7bd9507b80725aea7e7 --- /dev/null +++ b/Source/Processors/RecordNode/RecordThread.cpp @@ -0,0 +1,154 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2014 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 "RecordThread.h" +#include "../Visualization/SpikeObject.h" +#include "RecordEngine.h" + +#define EVERY_ENGINE for(int eng = 0; eng < m_engineArray.size(); eng++) m_engineArray[eng] + + +RecordThread::RecordThread(const OwnedArray<RecordEngine>& engines) : +Thread("Record Thread"), +m_engineArray(engines), +m_receivedFirstBlock(false), +m_cleanExit(true) +{ +} + +RecordThread::~RecordThread() +{ +} + +void RecordThread::setFileComponents(File rootFolder, int experimentNumber, int recordingNumber) +{ + if (isThreadRunning()) + return; + + m_rootFolder = rootFolder; + m_experimentNumber = experimentNumber; + m_recordingNumber = recordingNumber; +} + +void RecordThread::setChannelMap(const Array<int>& channels) +{ + if (isThreadRunning()) + return; + m_channelArray = channels; + m_numChannels = channels.size(); +} + +void RecordThread::setQueuePointers(DataQueue* data, EventMsgQueue* events, SpikeMsgQueue* spikes) +{ + m_dataQueue = data; + m_eventQueue = events; + m_spikeQueue = spikes; +} + +void RecordThread::setFirstBlockFlag(bool state) +{ + m_receivedFirstBlock = state; + this->notify(); +} + +void RecordThread::run() +{ + const AudioSampleBuffer& dataBuffer = m_dataQueue->getAudioBufferReference(); + bool closeEarly = true; + //1-Wait until the first block has arrived, so we can align the timestamps + while (!m_receivedFirstBlock && !threadShouldExit()) + { + wait(100); + } + + //2-Open Files + if (!threadShouldExit()) + { + m_cleanExit = false; + closeEarly = false; + Array<int64> timestamps; + m_dataQueue->getTimestampsForBlock(0, timestamps); + EVERY_ENGINE->updateTimestamps(timestamps); + EVERY_ENGINE->openFiles(m_rootFolder, m_experimentNumber, m_recordingNumber); + } + //3-Normal loop + while (!threadShouldExit()) + { + writeData(dataBuffer, BLOCK_MAX_WRITE_SAMPLES, BLOCK_MAX_WRITE_EVENTS, BLOCK_MAX_WRITE_SPIKES); + } + //4-Before closing the thread, try to write the remaining samples + if (!closeEarly) + { + writeData(dataBuffer, -1, -1, -1); + + //5-Close files + EVERY_ENGINE->closeFiles(); + } + m_cleanExit = true; + m_receivedFirstBlock = false; +} + +void RecordThread::writeData(const AudioSampleBuffer& dataBuffer, int maxSamples, int maxEvents, int maxSpikes) +{ + Array<int64> timestamps; + Array<CircularBufferIndexes> idx; + m_dataQueue->startRead(idx, timestamps, maxSamples); + EVERY_ENGINE->updateTimestamps(timestamps); + for (int chan = 0; chan < m_numChannels; ++chan) + { + if (idx[chan].size1 > 0) + { + EVERY_ENGINE->writeData(chan, m_channelArray[chan], dataBuffer.getReadPointer(chan, idx[chan].index1), idx[chan].size1); + if (idx[chan].size2 > 0) + { + timestamps.set(chan, timestamps[chan] + idx[chan].size1); + EVERY_ENGINE->updateTimestamps(timestamps, chan); + EVERY_ENGINE->writeData(chan, m_channelArray[chan], dataBuffer.getReadPointer(chan, idx[chan].index2), idx[chan].size2); + } + } + } + m_dataQueue->stopRead(); + + std::vector<EventMessagePtr> events; + int nEvents = m_eventQueue->getEvents(events, maxEvents); + for (int ev = 0; ev < nEvents; ++ev) + { + EVERY_ENGINE->writeEvent(events[ev]->getExtra(), events[ev]->getData(), events[ev]->getTimestamp()); + } + + std::vector<SpikeMessagePtr> spikes; + int nSpikes = m_spikeQueue->getEvents(spikes, maxSpikes); + for (int sp = 0; sp < nSpikes; ++sp) + { + EVERY_ENGINE->writeSpike(spikes[sp]->getExtra(), spikes[sp]->getData(), spikes[sp]->getTimestamp()); + } +} + +void RecordThread::forceCloseFiles() +{ + if (isThreadRunning() || m_cleanExit) + return; + + EVERY_ENGINE->closeFiles(); + m_cleanExit = true; +} \ No newline at end of file diff --git a/Source/Processors/RecordNode/RecordThread.h b/Source/Processors/RecordNode/RecordThread.h new file mode 100644 index 0000000000000000000000000000000000000000..30c2c28057741260b632b8f4637d17edcc7c3ef2 --- /dev/null +++ b/Source/Processors/RecordNode/RecordThread.h @@ -0,0 +1,74 @@ +/* + ------------------------------------------------------------------ + + This file is part of the Open Ephys GUI + Copyright (C) 2014 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 RECORDTHREAD_H_INCLUDED +#define RECORDTHREAD_H_INCLUDED + +#include "../../../JuceLibraryCode/JuceHeader.h" +#include "EventQueue.h" +#include "DataQueue.h" +#include <atomic> + +#define BLOCK_MAX_WRITE_SAMPLES 4096 +#define BLOCK_MAX_WRITE_EVENTS 32 +#define BLOCK_MAX_WRITE_SPIKES 32 + +class Channel; +class RecordEngine; + + +class RecordThread : public Thread +{ +public: + RecordThread(const OwnedArray<RecordEngine>& engines); + ~RecordThread(); + void setFileComponents(File rootFolder, int experimentNumber, int recordingNumber); + void setChannelMap(const Array<int>& channels); + void setQueuePointers(DataQueue* data, EventMsgQueue* events, SpikeMsgQueue* spikes); + + void run() override; + + void setFirstBlockFlag(bool state); + void forceCloseFiles(); + +private: + void writeData(const AudioSampleBuffer& buffer, int maxSamples, int maxEvents, int maxSpikes); + + const OwnedArray<RecordEngine>& m_engineArray; + Array<int> m_channelArray; + + DataQueue* m_dataQueue; + EventMsgQueue* m_eventQueue; + SpikeMsgQueue *m_spikeQueue; + + std::atomic<bool> m_receivedFirstBlock; + std::atomic<bool> m_cleanExit; + + File m_rootFolder; + int m_experimentNumber; + int m_recordingNumber; + int m_numChannels; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(RecordThread); +}; + +#endif // RECORDTHREAD_H_INCLUDED diff --git a/Source/UI/ControlPanel.cpp b/Source/UI/ControlPanel.cpp index 987121fc30c86e2740f82e1dfd6e2076ac082bf7..b5328948a98abd20e0f53e1328070471e36d7bce 100755 --- a/Source/UI/ControlPanel.cpp +++ b/Source/UI/ControlPanel.cpp @@ -552,6 +552,30 @@ void ControlPanel::updateRecordEngineList() recordSelector->setSelectedId(selectedEngine, sendNotification); } +String ControlPanel::getSelectedRecordEngineId() +{ + return recordEngines[recordSelector->getSelectedId() - 1]->getID(); +} + +bool ControlPanel::setSelectedRecordEngineId(String id) +{ + if (getAcquisitionState()) + { + return false; + } + + int nEngines = recordEngines.size(); + for (int i = 0; i < nEngines; ++i) + { + if (recordEngines[i]->getID() == id) + { + recordSelector->setSelectedId(i + 1, sendNotificationSync); + return true; + } + } + return false; +} + void ControlPanel::createPaths() { /* int w = getWidth() - 325; diff --git a/Source/UI/ControlPanel.h b/Source/UI/ControlPanel.h index cec8bc11898f033caea0ed8bc4087bc965136579..f63296ecf6761813a3bc7548691443b6b1384e2d 100755 --- a/Source/UI/ControlPanel.h +++ b/Source/UI/ControlPanel.h @@ -365,6 +365,10 @@ public: void updateRecordEngineList(); + String getSelectedRecordEngineId(); + + bool setSelectedRecordEngineId(String id); + ScopedPointer<RecordButton> recordButton; private: ScopedPointer<PlayButton> playButton; diff --git a/open-ephys.jucer b/open-ephys.jucer index 62023b4f198eee86f8f6732aafa8ef24a6f24770..6ff0cb146a8fd6465e81d48c30172d11db7c28d5 100644 --- a/open-ephys.jucer +++ b/open-ephys.jucer @@ -474,6 +474,12 @@ file="Source/Processors/ProcessorGraph/ProcessorGraph.h"/> </GROUP> <GROUP id="{72D807AC-44A0-1F7A-8699-22225876FE9A}" name="RecordNode"> + <FILE id="WQxge0" name="DataQueue.cpp" compile="1" resource="0" file="Source/Processors/RecordNode/DataQueue.cpp"/> + <FILE id="cZPfsG" name="DataQueue.h" compile="0" resource="0" file="Source/Processors/RecordNode/DataQueue.h"/> + <FILE id="mcvfV8" name="EventQueue.h" compile="0" resource="0" file="Source/Processors/RecordNode/EventQueue.h"/> + <FILE id="r8K6Sh" name="RecordThread.cpp" compile="1" resource="0" + file="Source/Processors/RecordNode/RecordThread.cpp"/> + <FILE id="Q8yVpr" name="RecordThread.h" compile="0" resource="0" file="Source/Processors/RecordNode/RecordThread.h"/> <FILE id="deQ9TU" name="EngineConfigWindow.cpp" compile="1" resource="0" file="Source/Processors/RecordNode/EngineConfigWindow.cpp"/> <FILE id="iSAT0P" name="EngineConfigWindow.h" compile="0" resource="0"