diff --git a/Builds/Linux/Makefile b/Builds/Linux/Makefile
index 7539a54cb..ee4be351e 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 \
@@ -442,6 +444,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 8809d9d99..a63803ce5 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; };
@@ -146,6 +148,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"; };
@@ -574,6 +577,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"; };
@@ -620,12 +624,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"; };
@@ -819,6 +825,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"; };
@@ -1145,24 +1152,24 @@
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"; };
- E93BE115650B1CB80EACB841 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = EditorViewportButtons.h; path = ../../Source/UI/EditorViewportButtons.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"; };
+ E946426F95E0240683CB3337 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_DrawablePath.h"; path = "../../JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawablePath.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"; };
EA9518CDEA7049C21D5CE2D5 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_Process.h"; path = "../../JuceLibraryCode/modules/juce_core/threads/juce_Process.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"; };
+ 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"; };
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"; };
- E946426F95E0240683CB3337 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "juce_DrawablePath.h"; path = "../../JuceLibraryCode/modules/juce_gui_basics/drawables/juce_DrawablePath.h"; sourceTree = "SOURCE_ROOT"; };
+ E93BE115650B1CB80EACB841 = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = EditorViewportButtons.h; path = ../../Source/UI/EditorViewportButtons.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"; };
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"; };
- 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"; };
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"; };
@@ -1461,6 +1468,11 @@
4CB63EE1552BBFDEB1DADB0A,
B695B24906116ADEFC9D9B5C, ); name = ProcessorGraph; sourceTree = ""; };
0E7092A11A3C96E5ECA71CDA = {isa = PBXGroup; children = (
+ 74E31DA11A4C1244B78A077A,
+ A010F4CC42989CB1E73A8A94,
+ 066A1CD777247BC8142A7DAA,
+ 699B3251715DE04674E0E0C4,
+ 762A0D03A828BA95B3B9C209,
7DB22AC6407EEA88F3FFA16D,
398BF0B03B719107E6093F98,
E39CC410838072043E3C30DC,
@@ -2797,6 +2809,8 @@
F2586A2DCEF44961AEA247E8,
3E7939ABAA984EE8BFC8CEDD,
BAC379C03C2E7995F2393EF5,
+ 0326A368BA8F70C74A8A12A7,
+ F7E069E1FC1BB7EF856AA083,
E1247DDF1C88D99691499E52,
0A8D8C2D02858F0F08356EA9,
AEDA8F23648EABF79215B566,
diff --git a/Builds/VisualStudio2012/open-ephys.vcxproj b/Builds/VisualStudio2012/open-ephys.vcxproj
index 5a4bc3cca..088662525 100644
--- a/Builds/VisualStudio2012/open-ephys.vcxproj
+++ b/Builds/VisualStudio2012/open-ephys.vcxproj
@@ -310,6 +310,8 @@
+
+
@@ -1525,6 +1527,9 @@
+
+
+
diff --git a/Builds/VisualStudio2012/open-ephys.vcxproj.filters b/Builds/VisualStudio2012/open-ephys.vcxproj.filters
index ad441ad8d..ad84cb0ca 100644
--- a/Builds/VisualStudio2012/open-ephys.vcxproj.filters
+++ b/Builds/VisualStudio2012/open-ephys.vcxproj.filters
@@ -571,6 +571,12 @@
open-ephys\Source\Processors\ProcessorGraph
+
+ open-ephys\Source\Processors\RecordNode
+
+
+ open-ephys\Source\Processors\RecordNode
+
open-ephys\Source\Processors\RecordNode
@@ -2073,6 +2079,15 @@
open-ephys\Source\Processors\ProcessorGraph
+
+ open-ephys\Source\Processors\RecordNode
+
+
+ open-ephys\Source\Processors\RecordNode
+
+
+ open-ephys\Source\Processors\RecordNode
+
open-ephys\Source\Processors\RecordNode
diff --git a/Builds/VisualStudio2013/open-ephys.sln b/Builds/VisualStudio2013/open-ephys.sln
index 59a9d10ba..8387e176d 100644
--- a/Builds/VisualStudio2013/open-ephys.sln
+++ b/Builds/VisualStudio2013/open-ephys.sln
@@ -1,21 +1,31 @@
-Microsoft Visual Studio Solution File, Format Version 11.00
+Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 2013
-Project("{5A05F353-1D63-394C-DFB0-981BB2309002}") = "open-ephys", "open-ephys.vcxproj", "{9C924D66-7DEC-1AEF-B375-DB8666BFB909}"
+VisualStudioVersion = 12.0.40629.0
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "open-ephys", "open-ephys.vcxproj", "{9C924D66-7DEC-1AEF-B375-DB8666BFB909}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Win32 = Debug|Win32
- Release|Win32 = Release|Win32
+ Debug|x64 = Debug|x64
+ Debug64|Win32 = Debug64|Win32
Debug64|x64 = Debug64|x64
+ Release|Win32 = Release|Win32
+ Release|x64 = Release|x64
+ Release64|Win32 = Release64|Win32
Release64|x64 = Release64|x64
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{9C924D66-7DEC-1AEF-B375-DB8666BFB909}.Debug|Win32.ActiveCfg = Debug|Win32
{9C924D66-7DEC-1AEF-B375-DB8666BFB909}.Debug|Win32.Build.0 = Debug|Win32
- {9C924D66-7DEC-1AEF-B375-DB8666BFB909}.Release|Win32.ActiveCfg = Release|Win32
- {9C924D66-7DEC-1AEF-B375-DB8666BFB909}.Release|Win32.Build.0 = Release|Win32
+ {9C924D66-7DEC-1AEF-B375-DB8666BFB909}.Debug|x64.ActiveCfg = Debug|Win32
+ {9C924D66-7DEC-1AEF-B375-DB8666BFB909}.Debug64|Win32.ActiveCfg = Debug64|x64
{9C924D66-7DEC-1AEF-B375-DB8666BFB909}.Debug64|x64.ActiveCfg = Debug64|x64
{9C924D66-7DEC-1AEF-B375-DB8666BFB909}.Debug64|x64.Build.0 = Debug64|x64
+ {9C924D66-7DEC-1AEF-B375-DB8666BFB909}.Release|Win32.ActiveCfg = Release|Win32
+ {9C924D66-7DEC-1AEF-B375-DB8666BFB909}.Release|Win32.Build.0 = Release|Win32
+ {9C924D66-7DEC-1AEF-B375-DB8666BFB909}.Release|x64.ActiveCfg = Release|Win32
+ {9C924D66-7DEC-1AEF-B375-DB8666BFB909}.Release64|Win32.ActiveCfg = Release64|x64
{9C924D66-7DEC-1AEF-B375-DB8666BFB909}.Release64|x64.ActiveCfg = Release64|x64
{9C924D66-7DEC-1AEF-B375-DB8666BFB909}.Release64|x64.Build.0 = Release64|x64
EndGlobalSection
diff --git a/Builds/VisualStudio2013/open-ephys.vcxproj b/Builds/VisualStudio2013/open-ephys.vcxproj
index 86e2060ea..5817b29f3 100644
--- a/Builds/VisualStudio2013/open-ephys.vcxproj
+++ b/Builds/VisualStudio2013/open-ephys.vcxproj
@@ -314,6 +314,8 @@
+
+
@@ -1529,6 +1531,9 @@
+
+
+
diff --git a/Builds/VisualStudio2013/open-ephys.vcxproj.filters b/Builds/VisualStudio2013/open-ephys.vcxproj.filters
index 01a8cd756..0ccff7b33 100644
--- a/Builds/VisualStudio2013/open-ephys.vcxproj.filters
+++ b/Builds/VisualStudio2013/open-ephys.vcxproj.filters
@@ -571,6 +571,12 @@
open-ephys\Source\Processors\ProcessorGraph
+
+ open-ephys\Source\Processors\RecordNode
+
+
+ open-ephys\Source\Processors\RecordNode
+
open-ephys\Source\Processors\RecordNode
@@ -2073,6 +2079,15 @@
open-ephys\Source\Processors\ProcessorGraph
+
+ open-ephys\Source\Processors\RecordNode
+
+
+ open-ephys\Source\Processors\RecordNode
+
+
+ open-ephys\Source\Processors\RecordNode
+
open-ephys\Source\Processors\RecordNode
diff --git a/Source/CoreServices.cpp b/Source/CoreServices.cpp
index 84a668fd6..f17620830 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 4b46d403a..b8e254d4a 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 000000000..49871820b
--- /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 .
+
+*/
+
+/*
+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 af9c79514..7a626fc82 100644
--- a/Source/Plugins/KWIKFormat/FileSource/KwikFileSource.cpp
+++ b/Source/Plugins/KWIKFormat/FileSource/KwikFileSource.cpp
@@ -22,14 +22,14 @@
*/
#include
#include "KwikFileSource.h"
-
+#include
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 ba92857e8..7bacf4964 100644
--- a/Source/Plugins/KWIKFormat/FileSource/KwikFileSource.h
+++ b/Source/Plugins/KWIKFormat/FileSource/KwikFileSource.h
@@ -43,20 +43,23 @@ class KWIKFileSource : public FileSource
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 sourceFile;
ScopedPointer dataSet;
- bool Open(File file);
- void fillRecordInfo();
- void updateActiveRecord();
+ bool Open(File file) override;
+ void fillRecordInfo() override;
+ void updateActiveRecord() override;
int64 samplePos;
Array availableDataSets;
+ bool skipRecordEngineCheck;
};
diff --git a/Source/Plugins/KWIKFormat/RecordEngine/HDF5FileFormat.cpp b/Source/Plugins/KWIKFormat/RecordEngine/HDF5FileFormat.cpp
index 46ac97f76..31eabc8f4 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 1be0343c6..ca0f190f3 100644
--- a/Source/Plugins/KWIKFormat/RecordEngine/HDF5FileFormat.h
+++ b/Source/Plugins/KWIKFormat/RecordEngine/HDF5FileFormat.h
@@ -127,6 +127,7 @@ class KWDFile : public HDF5FileBase
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 e3b415a56..3806adccc 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(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 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 dc27f26ff..a40f5f1e9 100644
--- a/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.h
+++ b/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.h
@@ -33,17 +33,16 @@ class HDF5Recording : public RecordEngine
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 @@ class HDF5Recording : public RecordEngine
Array processorMap;
Array channelsPerProcessor;
+ Array recordedChanToKWDChan;
OwnedArray> bitVoltsArray;
OwnedArray> sampleRatesArray;
OwnedArray fileArray;
diff --git a/Source/Processors/FileReader/FileReader.cpp b/Source/Processors/FileReader/FileReader.cpp
index 727c27de4..e19275f84 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 e506976a8..7657f27c7 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 43871a43c..68c6c6a07 100644
--- a/Source/Processors/FileReader/FileSource.h
+++ b/Source/Processors/FileReader/FileSource.h
@@ -67,6 +67,7 @@ class PLUGIN_API FileSource
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 000000000..4a1f4b0cf
--- /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 .
+
+*/
+#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());
+ 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& indexes, Array& 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& 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 000000000..1d52d5fc2
--- /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 .
+
+*/
+
+#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& 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& indexes, Array& timestamps, int nMax);
+ const AudioSampleBuffer& getAudioBufferReference() const;
+ void stopRead();
+
+
+private:
+ void fillTimestamps(int channel, int index, int size, int64 timestamp);
+
+ OwnedArray m_fifos;
+ AudioSampleBuffer m_buffer;
+ Array m_readSamples;
+ OwnedArray> m_timestamps;
+ Array 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 e86beb624..c0f9601ac 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 .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
- */
+*/
#include "EngineConfigWindow.h"
diff --git a/Source/Processors/RecordNode/EngineConfigWindow.h b/Source/Processors/RecordNode/EngineConfigWindow.h
index e0f0bf823..61d22f0b5 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 .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
- */
+*/
#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 000000000..22dd238fc
--- /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 .
+
+*/
+
+#ifndef EVENTQUEUE_H_INCLUDED
+#define EVENTQUEUE_H_INCLUDED
+
+#include "../../../JuceLibraryCode/JuceHeader.h"
+#include
+
+struct SpikeObject;
+
+template
+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 EventQueue
+{
+public:
+ typedef AsyncEventMessage EventContainer;
+ typedef ReferenceCountedObjectPtr 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& 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 m_data;
+ AbstractFifo m_fifo;
+
+ JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EventQueue);
+};
+
+typedef EventQueue EventMsgQueue;
+typedef EventQueue SpikeMsgQueue;
+typedef ReferenceCountedObjectPtr> EventMessagePtr;
+typedef ReferenceCountedObjectPtr> SpikeMessagePtr;
+
+#endif // EVENTQUEUE_H_INCLUDED
+
diff --git a/Source/Processors/RecordNode/OriginalRecording.cpp b/Source/Processors/RecordNode/OriginalRecording.cpp
index 864514ae6..5205d144e 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 .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
*/
@@ -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 82cd89092..8a7e7dd3f 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 .
-
- */
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
+*/
#ifndef ORIGINALRECORDING_H_INCLUDED
#define ORIGINALRECORDING_H_INCLUDED
@@ -47,15 +46,14 @@ class OriginalRecording : public RecordEngine
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 @@ class OriginalRecording : public RecordEngine
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 e62c06b48..4fb307ad0 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 .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
- */
+*/
#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* ts)
+void RecordEngine::updateTimestamps(const Array& ts, int channel)
{
- timestamps = ts;
+ if (channel < 0)
+ timestamps = ts;
+ else
+ timestamps.set(channel, ts[channel]);
}
-void RecordEngine::updateNumSamples(std::map* ns)
+void RecordEngine::setChannelMapping(const Array& 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 e9fc8585f..328967230 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 .
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see .
- */
+*/
#ifndef RECORDENGINE_H_INCLUDED
#define RECORDENGINE_H_INCLUDED
@@ -61,21 +61,27 @@ class PLUGIN_API RecordEngine
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 @@ class PLUGIN_API RecordEngine
*/
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 @@ class PLUGIN_API RecordEngine
/** 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* timestamps);
+ void updateTimestamps(const Array& timestamps, int channel = -1);
- /** Called every time a new numSamples event is received */
- void updateNumSamples(std::map* numSamples);
+ /** Called prior to opening files, to set the map between recorded
+ channels and actual channel numbers
+ */
+ void setChannelMapping(const Array& channels);
/** Called after all channels and spike groups have been registered,
just before acquisition starts
@@ -157,20 +163,31 @@ class PLUGIN_API RecordEngine
/** 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* numSamples;
- std::map* timestamps;
+ /** Gets the number of recorded channels
+ */
+ int getNumRecordedChannels() const;
private:
+ Array timestamps;
+ Array 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 83a80bef5..b41604eab 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& 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 70facf9f3..92af93e59 100755
--- a/Source/Processors/RecordNode/RecordNode.h
+++ b/Source/Processors/RecordNode/RecordNode.h
@@ -26,18 +26,23 @@
#include "../../../JuceLibraryCode/JuceHeader.h"
#include
#include
+
+
+
+
+