diff --git a/Builds/Linux/Makefile b/Builds/Linux/Makefile index 1ae639259f1f18b1eb130ca3d6080c81bb120444..398b848f09296e44d13bd7e8ec4cdfe4a968c47a 100644 --- a/Builds/Linux/Makefile +++ b/Builds/Linux/Makefile @@ -18,10 +18,10 @@ ifeq ($(CONFIG),Debug) TARGET_ARCH := -march=native endif - CPPFLAGS := $(DEPFLAGS) -D "LINUX=1" -D "DEBUG=1" -D "_DEBUG=1" -D "JUCER_LINUX_MAKE_7346DA2A=1" -D "JUCE_APP_VERSION=0.4.2" -D "JUCE_APP_VERSION_HEX=0x402" -I /usr/include -I /usr/include/freetype2 -I ~/SDKs/VST3\ SDK -I ../../JuceLibraryCode -I ../../JuceLibraryCode/modules -I JuceLibraryCode/ + CPPFLAGS := $(DEPFLAGS) -D "LINUX=1" -D "DEBUG=1" -D "_DEBUG=1" -D "JUCER_LINUX_MAKE_7346DA2A=1" -D "JUCE_APP_VERSION=0.4.2.1" -D "JUCE_APP_VERSION_HEX=0x40201" -I /usr/include -I /usr/include/freetype2 -I ~/SDKs/VST3\ SDK -I ../../JuceLibraryCode -I ../../JuceLibraryCode/modules -I JuceLibraryCode/ CFLAGS += $(CPPFLAGS) $(TARGET_ARCH) -g -ggdb -O3 -rdynamic -fvisibility=hidden CXXFLAGS += $(CFLAGS) -std=c++11 - LDFLAGS += $(TARGET_ARCH) -L$(BINDIR) -L$(LIBDIR) -L/usr/X11R6/lib/ -L/usr/local/include -lGL -lX11 -lXext -lXinerama -lasound -ldl -lfreetype -lpthread -lrt -ldl -lXext -lGLU -rdynamic -fPIC + LDFLAGS += $(TARGET_ARCH) -L$(BINDIR) -L$(LIBDIR) -L/usr/X11R6/lib/ -L/usr/local/include -lGL -lX11 -lXext -lXinerama -lasound -ldl -lfreetype -lpthread -lrt -ldl -lXext -lGLU -rdynamic -fPIC -Wl,-rpath,'$$ORIGIN' TARGET := open-ephys BLDCMD = $(CXX) -o $(OUTDIR)/$(TARGET) $(OBJECTS) $(LDFLAGS) $(RESOURCES) $(TARGET_ARCH) @@ -38,10 +38,10 @@ ifeq ($(CONFIG),Release) TARGET_ARCH := -march=native endif - CPPFLAGS := $(DEPFLAGS) -D "LINUX=1" -D "NDEBUG=1" -D "JUCER_LINUX_MAKE_7346DA2A=1" -D "JUCE_APP_VERSION=0.4.2" -D "JUCE_APP_VERSION_HEX=0x402" -I /usr/include -I /usr/include/freetype2 -I ~/SDKs/VST3\ SDK -I ../../JuceLibraryCode -I ../../JuceLibraryCode/modules -I JuceLibraryCode/ + CPPFLAGS := $(DEPFLAGS) -D "LINUX=1" -D "NDEBUG=1" -D "JUCER_LINUX_MAKE_7346DA2A=1" -D "JUCE_APP_VERSION=0.4.2.1" -D "JUCE_APP_VERSION_HEX=0x40201" -I /usr/include -I /usr/include/freetype2 -I ~/SDKs/VST3\ SDK -I ../../JuceLibraryCode -I ../../JuceLibraryCode/modules -I JuceLibraryCode/ CFLAGS += $(CPPFLAGS) $(TARGET_ARCH) -O3 -rdynamic -fvisibility=hidden CXXFLAGS += $(CFLAGS) -std=c++11 - LDFLAGS += $(TARGET_ARCH) -L$(BINDIR) -L$(LIBDIR) -fvisibility=hidden -L/usr/X11R6/lib/ -lGL -lX11 -lXext -lXinerama -lasound -ldl -lfreetype -lpthread -lrt -ldl -lXext -lGLU -rdynamic -fPIC + LDFLAGS += $(TARGET_ARCH) -L$(BINDIR) -L$(LIBDIR) -fvisibility=hidden -L/usr/X11R6/lib/ -lGL -lX11 -lXext -lXinerama -lasound -ldl -lfreetype -lpthread -lrt -ldl -lXext -lGLU -rdynamic -fPIC -Wl,-rpath,'$$ORIGIN' TARGET := open-ephys-release BLDCMD = $(CXX) -o $(OUTDIR)/$(TARGET) $(OBJECTS) $(LDFLAGS) $(RESOURCES) $(TARGET_ARCH) diff --git a/Builds/MacOSX/Info-App.plist b/Builds/MacOSX/Info-App.plist index d6c7290d7f5f5fa579e83afc7e663d3ffbc1358d..29f12d9086cc1628e0c03fdeaf32d19f415fed47 100644 --- a/Builds/MacOSX/Info-App.plist +++ b/Builds/MacOSX/Info-App.plist @@ -18,9 +18,9 @@ <key>CFBundleSignature</key> <string>????</string> <key>CFBundleShortVersionString</key> - <string>0.4.2</string> + <string>0.4.2.1</string> <key>CFBundleVersion</key> - <string>0.4.2</string> + <string>0.4.2.1</string> <key>NSHumanReadableCopyright</key> <string>Open Ephys</string> <key>NSHighResolutionCapable</key> diff --git a/Builds/MacOSX/OpenEphys.xcworkspace/contents.xcworkspacedata b/Builds/MacOSX/OpenEphys.xcworkspace/contents.xcworkspacedata index 0a9ef155f588a09c06f0262dbffa8d353a84a3a2..3a7b70872579064f8214d8a710b996952e8629c6 100644 --- a/Builds/MacOSX/OpenEphys.xcworkspace/contents.xcworkspacedata +++ b/Builds/MacOSX/OpenEphys.xcworkspace/contents.xcworkspacedata @@ -65,9 +65,6 @@ <FileRef location = "group:SerialInput/SerialInput.xcodeproj"> </FileRef> - <FileRef - location = "group:SpikeRaster/SpikeRaster.xcodeproj"> - </FileRef> <FileRef location = "group:SpikeSorter/SpikeSorter.xcodeproj"> </FileRef> diff --git a/Builds/MacOSX/Plugins/NWBFormat/NWBFormat.xcodeproj/project.pbxproj b/Builds/MacOSX/Plugins/NWBFormat/NWBFormat.xcodeproj/project.pbxproj index 4c60410d56661e3b3b1e64cce1b85f1d72d09936..921608e444f8a352d3b802313ca12245375d14a9 100644 --- a/Builds/MacOSX/Plugins/NWBFormat/NWBFormat.xcodeproj/project.pbxproj +++ b/Builds/MacOSX/Plugins/NWBFormat/NWBFormat.xcodeproj/project.pbxproj @@ -239,6 +239,7 @@ E1F91DEC1DBE670500FF13EA /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; diff --git a/Builds/MacOSX/open-ephys.xcodeproj/project.pbxproj b/Builds/MacOSX/open-ephys.xcodeproj/project.pbxproj index c7ec046745c24e7a999f8886f5951ad00c83c2d5..688922995b904eeaf72f3e8b721819ce474a81b3 100644 --- a/Builds/MacOSX/open-ephys.xcodeproj/project.pbxproj +++ b/Builds/MacOSX/open-ephys.xcodeproj/project.pbxproj @@ -3358,8 +3358,8 @@ "_DEBUG=1", "DEBUG=1", "JUCER_XCODE_MAC_F6D2F4CF=1", - "JUCE_APP_VERSION=0.4.2", - "JUCE_APP_VERSION_HEX=0x402", ); + "JUCE_APP_VERSION=0.4.2.1", + "JUCE_APP_VERSION_HEX=0x40201", ); GCC_VERSION = com.apple.compilers.llvm.clang.1_0; HEADER_SEARCH_PATHS = ("~/SDKs/vstsdk2.4", "../../JuceLibraryCode", "../../JuceLibraryCode/modules", "JuceLibraryCode/", "$(inherited)"); INFOPLIST_FILE = Info-App.plist; @@ -3382,8 +3382,8 @@ "_NDEBUG=1", "NDEBUG=1", "JUCER_XCODE_MAC_F6D2F4CF=1", - "JUCE_APP_VERSION=0.4.2", - "JUCE_APP_VERSION_HEX=0x402", ); + "JUCE_APP_VERSION=0.4.2.1", + "JUCE_APP_VERSION_HEX=0x40201", ); GCC_SYMBOLS_PRIVATE_EXTERN = YES; GCC_VERSION = com.apple.compilers.llvm.clang.1_0; HEADER_SEARCH_PATHS = ("~/SDKs/vstsdk2.4", "../../JuceLibraryCode", "../../JuceLibraryCode/modules", "JuceLibraryCode/", "$(inherited)"); diff --git a/Builds/VisualStudio2012/open-ephys.vcxproj b/Builds/VisualStudio2012/open-ephys.vcxproj index 4ca750dc9a351b7f3e778e67c666f874fa10fc3c..4ba7d96512184069544079c433247d9e6cac9fd0 100644 --- a/Builds/VisualStudio2012/open-ephys.vcxproj +++ b/Builds/VisualStudio2012/open-ephys.vcxproj @@ -85,7 +85,7 @@ <Optimization>Disabled</Optimization> <DebugInformationFormat>EditAndContinue</DebugInformationFormat> <AdditionalIncludeDirectories>~\SDKs\VST3 SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;JuceLibraryCode/;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;NOMINMAX;JUCER_VS2012_78A501F=1;JUCE_APP_VERSION=0.4.2;JUCE_APP_VERSION_HEX=0x402;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;NOMINMAX;JUCER_VS2012_78A501F=1;JUCE_APP_VERSION=0.4.2.1;JUCE_APP_VERSION_HEX=0x40201;%(PreprocessorDefinitions)</PreprocessorDefinitions> <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> <RuntimeTypeInfo>true</RuntimeTypeInfo> <PrecompiledHeader/> @@ -130,7 +130,7 @@ <ClCompile> <Optimization>Full</Optimization> <AdditionalIncludeDirectories>~\SDKs\VST3 SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;JuceLibraryCode/;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;NOMINMAX;JUCER_VS2012_78A501F=1;JUCE_APP_VERSION=0.4.2;JUCE_APP_VERSION_HEX=0x402;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;NOMINMAX;JUCER_VS2012_78A501F=1;JUCE_APP_VERSION=0.4.2.1;JUCE_APP_VERSION_HEX=0x40201;%(PreprocessorDefinitions)</PreprocessorDefinitions> <RuntimeLibrary>MultiThreaded</RuntimeLibrary> <RuntimeTypeInfo>true</RuntimeTypeInfo> <PrecompiledHeader/> @@ -177,7 +177,7 @@ <Optimization>Disabled</Optimization> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <AdditionalIncludeDirectories>~\SDKs\VST3 SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;JuceLibraryCode/;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;NOMINMAX;JUCER_VS2012_78A501F=1;JUCE_APP_VERSION=0.4.2;JUCE_APP_VERSION_HEX=0x402;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;NOMINMAX;JUCER_VS2012_78A501F=1;JUCE_APP_VERSION=0.4.2.1;JUCE_APP_VERSION_HEX=0x40201;%(PreprocessorDefinitions)</PreprocessorDefinitions> <RuntimeLibrary>MultiThreadedDebug</RuntimeLibrary> <RuntimeTypeInfo>true</RuntimeTypeInfo> <PrecompiledHeader/> @@ -220,7 +220,7 @@ <ClCompile> <Optimization>Full</Optimization> <AdditionalIncludeDirectories>~\SDKs\VST3 SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;JuceLibraryCode/;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;NOMINMAX;JUCER_VS2012_78A501F=1;JUCE_APP_VERSION=0.4.2;JUCE_APP_VERSION_HEX=0x402;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;NOMINMAX;JUCER_VS2012_78A501F=1;JUCE_APP_VERSION=0.4.2.1;JUCE_APP_VERSION_HEX=0x40201;%(PreprocessorDefinitions)</PreprocessorDefinitions> <RuntimeLibrary>MultiThreaded</RuntimeLibrary> <RuntimeTypeInfo>true</RuntimeTypeInfo> <PrecompiledHeader/> diff --git a/Builds/VisualStudio2012/resources.rc b/Builds/VisualStudio2012/resources.rc index 13bfc913a492ab1a9b507be55871002f2d99aaa7..8582bebd4f2d17cfd432bf1576b35c7ed7466274 100644 --- a/Builds/VisualStudio2012/resources.rc +++ b/Builds/VisualStudio2012/resources.rc @@ -7,7 +7,7 @@ #include <windows.h> VS_VERSION_INFO VERSIONINFO -FILEVERSION 0,4,2,0 +FILEVERSION 0,4,2,1 BEGIN BLOCK "StringFileInfo" BEGIN @@ -15,9 +15,9 @@ BEGIN BEGIN VALUE "CompanyName", "Open Ephys\0" VALUE "FileDescription", "open-ephys\0" - VALUE "FileVersion", "0.4.2\0" + VALUE "FileVersion", "0.4.2.1\0" VALUE "ProductName", "open-ephys\0" - VALUE "ProductVersion", "0.4.2\0" + VALUE "ProductVersion", "0.4.2.1\0" END END diff --git a/Builds/VisualStudio2013/Plugins/LFP_Viewer_Beta/LFP_Viewer_Beta.vcxproj b/Builds/VisualStudio2013/Plugins/LFP_Viewer_Beta/LFP_Viewer_Beta.vcxproj index 59325ff17864cec8c4c06081d723f0304c261340..94a294e10b5401f0e6edd897a7d1f9cbd3fa704a 100644 --- a/Builds/VisualStudio2013/Plugins/LFP_Viewer_Beta/LFP_Viewer_Beta.vcxproj +++ b/Builds/VisualStudio2013/Plugins/LFP_Viewer_Beta/LFP_Viewer_Beta.vcxproj @@ -74,7 +74,7 @@ <ClCompile> <WarningLevel>Level3</WarningLevel> <Optimization>Disabled</Optimization> - <SDLCheck>true</SDLCheck> + <SDLCheck>false</SDLCheck> </ClCompile> <Link> <GenerateDebugInformation>true</GenerateDebugInformation> @@ -95,7 +95,7 @@ <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> - <SDLCheck>true</SDLCheck> + <SDLCheck>false</SDLCheck> </ClCompile> <Link> <GenerateDebugInformation>true</GenerateDebugInformation> @@ -109,7 +109,7 @@ <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> - <SDLCheck>true</SDLCheck> + <SDLCheck>false</SDLCheck> </ClCompile> <Link> <GenerateDebugInformation>true</GenerateDebugInformation> diff --git a/Builds/VisualStudio2013/Plugins/NetworkEvents/NetworkEvents.vcxproj b/Builds/VisualStudio2013/Plugins/NetworkEvents/NetworkEvents.vcxproj index a1fb07bd496f0fa579a22857c4b40ea9cba95681..0f02f4cb78a89cc280c7211b8c7da950c3f46573 100644 --- a/Builds/VisualStudio2013/Plugins/NetworkEvents/NetworkEvents.vcxproj +++ b/Builds/VisualStudio2013/Plugins/NetworkEvents/NetworkEvents.vcxproj @@ -74,7 +74,7 @@ <ClCompile> <WarningLevel>Level3</WarningLevel> <Optimization>Disabled</Optimization> - <SDLCheck>true</SDLCheck> + <SDLCheck>false</SDLCheck> <PreprocessorDefinitions>ZEROMQ;OEPLUGIN;WIN32;_WINDOWS;DEBUG;_DEBUG;JUCE_API=__declspec(dllimport);JUCER_VS2013_78A5020=1;JUCE_APP_VERSION=0.3.5;JUCE_APP_VERSION_HEX=0x305;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> <AdditionalIncludeDirectories>../../../../Resources/windows-libs/ZeroMQ/include;..\..\..\..\JuceLibraryCode;..\..\..\..\JuceLibraryCode\modules;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> @@ -103,7 +103,7 @@ <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> - <SDLCheck>true</SDLCheck> + <SDLCheck>false</SDLCheck> <PreprocessorDefinitions>ZEROMQ;OEPLUGIN;WIN32;_WINDOWS;NDEBUG;JUCE_API=__declspec(dllimport);JUCER_VS2013_78A5020=1;JUCE_APP_VERSION=0.3.5;JUCE_APP_VERSION_HEX=0x305;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> <AdditionalIncludeDirectories>../../../../Resources/windows-libs/ZeroMQ/include;..\..\..\..\JuceLibraryCode;..\..\..\..\JuceLibraryCode\modules;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> @@ -121,7 +121,7 @@ <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> - <SDLCheck>true</SDLCheck> + <SDLCheck>false</SDLCheck> <PreprocessorDefinitions>ZEROMQ;OEPLUGIN;WIN32;_WINDOWS;NDEBUG;JUCE_API=__declspec(dllimport);JUCER_VS2013_78A5020=1;JUCE_APP_VERSION=0.3.5;JUCE_APP_VERSION_HEX=0x305;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)</PreprocessorDefinitions> <AdditionalIncludeDirectories>../../../../Resources/windows-libs/ZeroMQ/include;..\..\..\..\JuceLibraryCode;..\..\..\..\JuceLibraryCode\modules;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> </ClCompile> diff --git a/Builds/VisualStudio2013/Plugins/Phase Detector/Phase Detector.vcxproj b/Builds/VisualStudio2013/Plugins/Phase Detector/Phase Detector.vcxproj index 44484ebcdba2a4fc8f1378b538c42b51be40ba24..a2f4d733c8be16cccbcc5e3872bf1ba061299f5f 100644 --- a/Builds/VisualStudio2013/Plugins/Phase Detector/Phase Detector.vcxproj +++ b/Builds/VisualStudio2013/Plugins/Phase Detector/Phase Detector.vcxproj @@ -74,7 +74,7 @@ <ClCompile> <WarningLevel>Level3</WarningLevel> <Optimization>Disabled</Optimization> - <SDLCheck>true</SDLCheck> + <SDLCheck>false</SDLCheck> </ClCompile> <Link> <GenerateDebugInformation>true</GenerateDebugInformation> @@ -95,7 +95,7 @@ <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> - <SDLCheck>true</SDLCheck> + <SDLCheck>false</SDLCheck> </ClCompile> <Link> <GenerateDebugInformation>true</GenerateDebugInformation> @@ -109,7 +109,7 @@ <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> - <SDLCheck>true</SDLCheck> + <SDLCheck>false</SDLCheck> </ClCompile> <Link> <GenerateDebugInformation>true</GenerateDebugInformation> diff --git a/Builds/VisualStudio2013/Plugins/Plugins.sln b/Builds/VisualStudio2013/Plugins/Plugins.sln index c167ab1547bbdd6e92c4273a2bcb87966002aad4..d03b5fb137a3d480b0afb7599674fc5bfa965bbb 100644 --- a/Builds/VisualStudio2013/Plugins/Plugins.sln +++ b/Builds/VisualStudio2013/Plugins/Plugins.sln @@ -40,14 +40,10 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SpikeSorter", "SpikeSorter\ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ExampleProcessor", "ExampleProcessor\ExampleProcessor.vcxproj", "{767D282E-0BE5-4B35-874A-3B1ED925F06B}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PCIeRhythm", "PCIeRhythm\PCIeRhythm.vcxproj", "{8F019559-89D4-4C33-BA0F-FF330C57BA2B}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "BinaryWriter", "BinaryWriter\BinaryWriter.vcxproj", "{9DB31964-F7E8-49B0-92E9-BAB2C35AF4B5}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LFP_Viewer_Beta", "LFP_Viewer_Beta\LFP_Viewer_Beta.vcxproj", "{A6E2C4F0-63A3-496B-8929-1B2785FDBBFD}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SpikeRaster", "SpikeRaster\SpikeRaster.vcxproj", "{D6A842A4-AC32-4AD0-B6AF-01AF1FBC8661}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NWBFormat", "NWBFormat\NWBFormat.vcxproj", "{270F2CBD-EE26-44CF-B53C-068CA80BFBF7}" ProjectSection(ProjectDependencies) = postProject {F250DB70-6E3E-408C-BD9E-1483D574499D} = {F250DB70-6E3E-408C-BD9E-1483D574499D} @@ -261,18 +257,6 @@ Global {767D282E-0BE5-4B35-874A-3B1ED925F06B}.Release|Mixed Platforms.ActiveCfg = Release|x64 {767D282E-0BE5-4B35-874A-3B1ED925F06B}.Release|Win32.ActiveCfg = Release|Win32 {767D282E-0BE5-4B35-874A-3B1ED925F06B}.Release|x64.ActiveCfg = Release|x64 - {8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Debug|Mixed Platforms.ActiveCfg = Release|x64 - {8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Debug|Mixed Platforms.Build.0 = Release|x64 - {8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Debug|Win32.ActiveCfg = Debug|Win32 - {8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Debug|Win32.Build.0 = Debug|Win32 - {8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Debug|x64.ActiveCfg = Debug|x64 - {8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Debug|x64.Build.0 = Debug|x64 - {8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Release|Mixed Platforms.ActiveCfg = Release|x64 - {8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Release|Mixed Platforms.Build.0 = Release|x64 - {8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Release|Win32.ActiveCfg = Release|Win32 - {8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Release|Win32.Build.0 = Release|Win32 - {8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Release|x64.ActiveCfg = Release|x64 - {8F019559-89D4-4C33-BA0F-FF330C57BA2B}.Release|x64.Build.0 = Release|x64 {9DB31964-F7E8-49B0-92E9-BAB2C35AF4B5}.Debug|Mixed Platforms.ActiveCfg = Release|x64 {9DB31964-F7E8-49B0-92E9-BAB2C35AF4B5}.Debug|Mixed Platforms.Build.0 = Release|x64 {9DB31964-F7E8-49B0-92E9-BAB2C35AF4B5}.Debug|Win32.ActiveCfg = Debug|Win32 @@ -297,18 +281,6 @@ Global {A6E2C4F0-63A3-496B-8929-1B2785FDBBFD}.Release|Win32.Build.0 = Release|Win32 {A6E2C4F0-63A3-496B-8929-1B2785FDBBFD}.Release|x64.ActiveCfg = Release|x64 {A6E2C4F0-63A3-496B-8929-1B2785FDBBFD}.Release|x64.Build.0 = Release|x64 - {D6A842A4-AC32-4AD0-B6AF-01AF1FBC8661}.Debug|Mixed Platforms.ActiveCfg = Release|x64 - {D6A842A4-AC32-4AD0-B6AF-01AF1FBC8661}.Debug|Mixed Platforms.Build.0 = Release|x64 - {D6A842A4-AC32-4AD0-B6AF-01AF1FBC8661}.Debug|Win32.ActiveCfg = Debug|Win32 - {D6A842A4-AC32-4AD0-B6AF-01AF1FBC8661}.Debug|Win32.Build.0 = Debug|Win32 - {D6A842A4-AC32-4AD0-B6AF-01AF1FBC8661}.Debug|x64.ActiveCfg = Debug|x64 - {D6A842A4-AC32-4AD0-B6AF-01AF1FBC8661}.Debug|x64.Build.0 = Debug|x64 - {D6A842A4-AC32-4AD0-B6AF-01AF1FBC8661}.Release|Mixed Platforms.ActiveCfg = Release|x64 - {D6A842A4-AC32-4AD0-B6AF-01AF1FBC8661}.Release|Mixed Platforms.Build.0 = Release|x64 - {D6A842A4-AC32-4AD0-B6AF-01AF1FBC8661}.Release|Win32.ActiveCfg = Release|Win32 - {D6A842A4-AC32-4AD0-B6AF-01AF1FBC8661}.Release|Win32.Build.0 = Release|Win32 - {D6A842A4-AC32-4AD0-B6AF-01AF1FBC8661}.Release|x64.ActiveCfg = Release|x64 - {D6A842A4-AC32-4AD0-B6AF-01AF1FBC8661}.Release|x64.Build.0 = Release|x64 {270F2CBD-EE26-44CF-B53C-068CA80BFBF7}.Debug|Mixed Platforms.ActiveCfg = Debug|Win32 {270F2CBD-EE26-44CF-B53C-068CA80BFBF7}.Debug|Mixed Platforms.Build.0 = Debug|Win32 {270F2CBD-EE26-44CF-B53C-068CA80BFBF7}.Debug|Win32.ActiveCfg = Debug|Win32 @@ -326,11 +298,13 @@ Global {F250DB70-6E3E-408C-BD9E-1483D574499D}.Debug|Win32.ActiveCfg = Debug|Win32 {F250DB70-6E3E-408C-BD9E-1483D574499D}.Debug|Win32.Build.0 = Debug|Win32 {F250DB70-6E3E-408C-BD9E-1483D574499D}.Debug|x64.ActiveCfg = Debug|x64 + {F250DB70-6E3E-408C-BD9E-1483D574499D}.Debug|x64.Build.0 = Debug|x64 {F250DB70-6E3E-408C-BD9E-1483D574499D}.Release|Mixed Platforms.ActiveCfg = Release|Win32 {F250DB70-6E3E-408C-BD9E-1483D574499D}.Release|Mixed Platforms.Build.0 = Release|Win32 {F250DB70-6E3E-408C-BD9E-1483D574499D}.Release|Win32.ActiveCfg = Release|Win32 {F250DB70-6E3E-408C-BD9E-1483D574499D}.Release|Win32.Build.0 = Release|Win32 {F250DB70-6E3E-408C-BD9E-1483D574499D}.Release|x64.ActiveCfg = Release|x64 + {F250DB70-6E3E-408C-BD9E-1483D574499D}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Builds/VisualStudio2013/Plugins/PulsePalOutput/PulsePalOutput.vcxproj b/Builds/VisualStudio2013/Plugins/PulsePalOutput/PulsePalOutput.vcxproj index 0e5433566f1ff70ad13d0aafd805105baddf743c..72936ceb095449c7c1a35226aebed6c5157a4a38 100644 --- a/Builds/VisualStudio2013/Plugins/PulsePalOutput/PulsePalOutput.vcxproj +++ b/Builds/VisualStudio2013/Plugins/PulsePalOutput/PulsePalOutput.vcxproj @@ -74,7 +74,7 @@ <ClCompile> <WarningLevel>Level3</WarningLevel> <Optimization>Disabled</Optimization> - <SDLCheck>true</SDLCheck> + <SDLCheck>false</SDLCheck> </ClCompile> <Link> <GenerateDebugInformation>true</GenerateDebugInformation> @@ -95,7 +95,7 @@ <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> - <SDLCheck>true</SDLCheck> + <SDLCheck>false</SDLCheck> </ClCompile> <Link> <GenerateDebugInformation>true</GenerateDebugInformation> @@ -109,7 +109,7 @@ <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> - <SDLCheck>true</SDLCheck> + <SDLCheck>false</SDLCheck> </ClCompile> <Link> <GenerateDebugInformation>true</GenerateDebugInformation> diff --git a/Builds/VisualStudio2013/Plugins/RecordControl/RecordControl.vcxproj b/Builds/VisualStudio2013/Plugins/RecordControl/RecordControl.vcxproj index 52b6b4251f154a764a150294c971e8826b860369..20ee0e8df9acdacda7ed4727fdcb8d1217bd3394 100644 --- a/Builds/VisualStudio2013/Plugins/RecordControl/RecordControl.vcxproj +++ b/Builds/VisualStudio2013/Plugins/RecordControl/RecordControl.vcxproj @@ -74,7 +74,7 @@ <ClCompile> <WarningLevel>Level3</WarningLevel> <Optimization>Disabled</Optimization> - <SDLCheck>true</SDLCheck> + <SDLCheck>false</SDLCheck> </ClCompile> <Link> <GenerateDebugInformation>true</GenerateDebugInformation> @@ -95,7 +95,7 @@ <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> - <SDLCheck>true</SDLCheck> + <SDLCheck>false</SDLCheck> </ClCompile> <Link> <GenerateDebugInformation>true</GenerateDebugInformation> @@ -109,7 +109,7 @@ <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> - <SDLCheck>true</SDLCheck> + <SDLCheck>false</SDLCheck> </ClCompile> <Link> <GenerateDebugInformation>true</GenerateDebugInformation> diff --git a/Builds/VisualStudio2013/Plugins/Rectifier/Rectifier.vcxproj b/Builds/VisualStudio2013/Plugins/Rectifier/Rectifier.vcxproj index 62ab80991460930f4b301cb1247c171719e4fa74..52407b0bc1243d425d53aa54eb3f79632eba1628 100644 --- a/Builds/VisualStudio2013/Plugins/Rectifier/Rectifier.vcxproj +++ b/Builds/VisualStudio2013/Plugins/Rectifier/Rectifier.vcxproj @@ -74,7 +74,7 @@ <ClCompile> <WarningLevel>Level3</WarningLevel> <Optimization>Disabled</Optimization> - <SDLCheck>true</SDLCheck> + <SDLCheck>false</SDLCheck> </ClCompile> <Link> <GenerateDebugInformation>true</GenerateDebugInformation> @@ -95,7 +95,7 @@ <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> - <SDLCheck>true</SDLCheck> + <SDLCheck>false</SDLCheck> </ClCompile> <Link> <GenerateDebugInformation>true</GenerateDebugInformation> @@ -109,7 +109,7 @@ <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> - <SDLCheck>true</SDLCheck> + <SDLCheck>false</SDLCheck> </ClCompile> <Link> <GenerateDebugInformation>true</GenerateDebugInformation> diff --git a/Builds/VisualStudio2013/Plugins/SerialInput/SerialInput.vcxproj b/Builds/VisualStudio2013/Plugins/SerialInput/SerialInput.vcxproj index 9fcd277b7b269098b16cb8bbfe67efaad654eb67..b7bd2cc098f44920fcbf03493813e04f1396f174 100644 --- a/Builds/VisualStudio2013/Plugins/SerialInput/SerialInput.vcxproj +++ b/Builds/VisualStudio2013/Plugins/SerialInput/SerialInput.vcxproj @@ -74,7 +74,7 @@ <ClCompile> <WarningLevel>Level3</WarningLevel> <Optimization>Disabled</Optimization> - <SDLCheck>true</SDLCheck> + <SDLCheck>false</SDLCheck> </ClCompile> <Link> <GenerateDebugInformation>true</GenerateDebugInformation> @@ -95,7 +95,7 @@ <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> - <SDLCheck>true</SDLCheck> + <SDLCheck>false</SDLCheck> </ClCompile> <Link> <GenerateDebugInformation>true</GenerateDebugInformation> @@ -109,7 +109,7 @@ <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> - <SDLCheck>true</SDLCheck> + <SDLCheck>false</SDLCheck> </ClCompile> <Link> <GenerateDebugInformation>true</GenerateDebugInformation> diff --git a/Builds/VisualStudio2013/Plugins/SpikeSorter/SpikeSorter.vcxproj b/Builds/VisualStudio2013/Plugins/SpikeSorter/SpikeSorter.vcxproj index 693fe9028cfda94bc2ad2de74d098ab7a2c6c2d7..c6ec5249514f063577a441b2952cbb812de23d81 100644 --- a/Builds/VisualStudio2013/Plugins/SpikeSorter/SpikeSorter.vcxproj +++ b/Builds/VisualStudio2013/Plugins/SpikeSorter/SpikeSorter.vcxproj @@ -95,7 +95,7 @@ <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> - <SDLCheck>true</SDLCheck> + <SDLCheck>false</SDLCheck> </ClCompile> <Link> <GenerateDebugInformation>true</GenerateDebugInformation> @@ -109,7 +109,7 @@ <Optimization>MaxSpeed</Optimization> <FunctionLevelLinking>true</FunctionLevelLinking> <IntrinsicFunctions>true</IntrinsicFunctions> - <SDLCheck>true</SDLCheck> + <SDLCheck>false</SDLCheck> </ClCompile> <Link> <GenerateDebugInformation>true</GenerateDebugInformation> diff --git a/Builds/VisualStudio2013/open-ephys.vcxproj b/Builds/VisualStudio2013/open-ephys.vcxproj index 28b9e28dcbd726afc970f1f7173dd5ddcb9bfda6..611ed81bcb60c456cdc89b89e3073d634a3cedbd 100644 --- a/Builds/VisualStudio2013/open-ephys.vcxproj +++ b/Builds/VisualStudio2013/open-ephys.vcxproj @@ -85,7 +85,7 @@ <Optimization>Disabled</Optimization> <DebugInformationFormat>EditAndContinue</DebugInformationFormat> <AdditionalIncludeDirectories>~\SDKs\VST3 SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;JuceLibraryCode/;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;NOMINMAX;JUCE_API=__declspec(dllexport);JUCER_VS2013_78A5020=1;JUCE_APP_VERSION=0.4.2;JUCE_APP_VERSION_HEX=0x402;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;NOMINMAX;JUCE_API=__declspec(dllexport);JUCER_VS2013_78A5020=1;JUCE_APP_VERSION=0.4.2.1;JUCE_APP_VERSION_HEX=0x40201;%(PreprocessorDefinitions)</PreprocessorDefinitions> <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> <RuntimeTypeInfo>true</RuntimeTypeInfo> <PrecompiledHeader/> @@ -131,7 +131,7 @@ <ClCompile> <Optimization>Full</Optimization> <AdditionalIncludeDirectories>~\SDKs\VST3 SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;JuceLibraryCode/;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;NOMINMAX;JUCE_API=__declspec(dllexport);JUCER_VS2013_78A5020=1;JUCE_APP_VERSION=0.4.2;JUCE_APP_VERSION_HEX=0x402;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;NOMINMAX;JUCE_API=__declspec(dllexport);JUCER_VS2013_78A5020=1;JUCE_APP_VERSION=0.4.2.1;JUCE_APP_VERSION_HEX=0x40201;%(PreprocessorDefinitions)</PreprocessorDefinitions> <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> <RuntimeTypeInfo>true</RuntimeTypeInfo> <PrecompiledHeader/> @@ -179,7 +179,7 @@ <Optimization>Disabled</Optimization> <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> <AdditionalIncludeDirectories>~\SDKs\VST3 SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;JuceLibraryCode/;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;NOMINMAX;JUCE_API=__declspec(dllexport);JUCER_VS2013_78A5020=1;JUCE_APP_VERSION=0.4.2;JUCE_APP_VERSION_HEX=0x402;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;DEBUG;_DEBUG;NOMINMAX;JUCE_API=__declspec(dllexport);JUCER_VS2013_78A5020=1;JUCE_APP_VERSION=0.4.2.1;JUCE_APP_VERSION_HEX=0x40201;%(PreprocessorDefinitions)</PreprocessorDefinitions> <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> <RuntimeTypeInfo>true</RuntimeTypeInfo> <PrecompiledHeader/> @@ -223,7 +223,7 @@ <ClCompile> <Optimization>Full</Optimization> <AdditionalIncludeDirectories>~\SDKs\VST3 SDK;..\..\JuceLibraryCode;..\..\JuceLibraryCode\modules;JuceLibraryCode/;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> - <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;NOMINMAX;JUCE_API=__declspec(dllexport);JUCER_VS2013_78A5020=1;JUCE_APP_VERSION=0.4.2;JUCE_APP_VERSION_HEX=0x402;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <PreprocessorDefinitions>_CRT_SECURE_NO_WARNINGS;WIN32;_WINDOWS;NDEBUG;NOMINMAX;JUCE_API=__declspec(dllexport);JUCER_VS2013_78A5020=1;JUCE_APP_VERSION=0.4.2.1;JUCE_APP_VERSION_HEX=0x40201;%(PreprocessorDefinitions)</PreprocessorDefinitions> <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> <RuntimeTypeInfo>true</RuntimeTypeInfo> <PrecompiledHeader/> diff --git a/Builds/VisualStudio2013/resources.rc b/Builds/VisualStudio2013/resources.rc index 13bfc913a492ab1a9b507be55871002f2d99aaa7..8582bebd4f2d17cfd432bf1576b35c7ed7466274 100644 --- a/Builds/VisualStudio2013/resources.rc +++ b/Builds/VisualStudio2013/resources.rc @@ -7,7 +7,7 @@ #include <windows.h> VS_VERSION_INFO VERSIONINFO -FILEVERSION 0,4,2,0 +FILEVERSION 0,4,2,1 BEGIN BLOCK "StringFileInfo" BEGIN @@ -15,9 +15,9 @@ BEGIN BEGIN VALUE "CompanyName", "Open Ephys\0" VALUE "FileDescription", "open-ephys\0" - VALUE "FileVersion", "0.4.2\0" + VALUE "FileVersion", "0.4.2.1\0" VALUE "ProductName", "open-ephys\0" - VALUE "ProductVersion", "0.4.2\0" + VALUE "ProductVersion", "0.4.2.1\0" END END diff --git a/JuceLibraryCode/JuceHeader.h b/JuceLibraryCode/JuceHeader.h index 1cf20a1c5c7d43aeeabb48a43dc74f8f3c13823c..c0cf844233fd45f75d3b40fd499d4cc5e34b9217 100644 --- a/JuceLibraryCode/JuceHeader.h +++ b/JuceLibraryCode/JuceHeader.h @@ -42,8 +42,8 @@ namespace ProjectInfo { const char* const projectName = "open-ephys"; - const char* const versionString = "0.4.2"; - const int versionNumber = 0x402; + const char* const versionString = "0.4.2.1"; + const int versionNumber = 0x40201; } #endif diff --git a/Projucer/Builds/LinuxMakefile/Makefile b/Projucer/Builds/LinuxMakefile/Makefile index f6368828ac1fed29c4f46eade60fd3eb665a6fb9..f94c27832ed819ec3ad6299d061cf5211c828637 100644 --- a/Projucer/Builds/LinuxMakefile/Makefile +++ b/Projucer/Builds/LinuxMakefile/Makefile @@ -18,7 +18,7 @@ ifeq ($(CONFIG),Debug) TARGET_ARCH := -march=native endif - CPPFLAGS := $(DEPFLAGS) -D "LINUX=1" -D "DEBUG=1" -D "_DEBUG=1" -D "JUCER_LINUX_MAKE_6D53C8B4=1" -D "JUCE_APP_VERSION=4.2.1" -D "JUCE_APP_VERSION_HEX=0x40201" -I /usr/include -I /usr/include/freetype2 -I ../../JuceLibraryCode -I ../../../JuceLibraryCode/modules + CPPFLAGS := $(DEPFLAGS) -D "LINUX=1" -D "DEBUG=1" -D "_DEBUG=1" -D "JUCER_LINUX_MAKE_6D53C8B4=1" -D "JUCE_APP_VERSION=4.2.1" -D "JUCE_APP_VERSION_HEX=0x40201" -D "JUCE_DISABLE_NATIVE_FILECHOOSERS=1" -I /usr/include -I /usr/include/freetype2 -I ../../JuceLibraryCode -I ../../../JuceLibraryCode/modules CFLAGS += $(CPPFLAGS) $(TARGET_ARCH) -g -ggdb -O0 -std=c++11 CXXFLAGS += $(CFLAGS) -std=c++11 LDFLAGS += $(TARGET_ARCH) -L$(BINDIR) -L$(LIBDIR) -L/usr/X11R6/lib/ -lX11 -lXext -lXinerama -ldl -lfreetype -lpthread -lrt -lcurl diff --git a/Resources/Scripts/install_linux_dependencies.sh b/Resources/Scripts/install_linux_dependencies.sh index 070b596ddd11f0cbe1d421745b862ce7f08bee89..8b8c1c21a4b9f835640f3d0f6aa234f17bd9a61a 100755 --- a/Resources/Scripts/install_linux_dependencies.sh +++ b/Resources/Scripts/install_linux_dependencies.sh @@ -1,8 +1,12 @@ #!/bin/bash +# This script installs required packages using apt-get +# It must be run with sudo. Example: +# sudo bash install_linux_dependencies.sh + # install g++ apt-get -y install build-essential # install Juce dependencies -apt-get -y install freeglut3-dev libfreetype6-dev libxinerama-dev libxcursor-dev libasound2-dev +apt-get -y install freeglut3-dev libfreetype6-dev libxinerama-dev libxcursor-dev libasound2-dev libxrandr-dev diff --git a/Source/Plugins/BinaryWriter/BinaryRecording.cpp b/Source/Plugins/BinaryWriter/BinaryRecording.cpp index 986e90e72f9e5a4c145bc1efe1cf42f2b7ba4a69..9542f84937c0176e1d19b3e099fdfa19f8cb0c6d 100644 --- a/Source/Plugins/BinaryWriter/BinaryRecording.cpp +++ b/Source/Plugins/BinaryWriter/BinaryRecording.cpp @@ -49,13 +49,52 @@ void BinaryRecording::openFiles(File rootFolder, int experimentNumber, int recor //Open channel files int nProcessors = getNumRecordedProcessors(); - for (int i = 0; i < nProcessors; i++) + m_channelIndexes.insertMultiple(0, 0, getNumRecordedChannels()); + m_fileIndexes.insertMultiple(0, 0, getNumRecordedChannels()); + + int lastId = 0; + for (int proc = 0; proc < nProcessors; proc++) { - const RecordProcessorInfo& pInfo = getProcessorInfo(i); - File datFile(basepath + "_" + String(pInfo.processorId) + "_" + String(recordingNumber) + ".dat"); - ScopedPointer<SequentialBlockFile> bFile = new SequentialBlockFile(pInfo.recordedChannels.size(), samplesPerBlock); - if (bFile->openFile(datFile)) - m_DataFiles.add(bFile.release()); + const RecordProcessorInfo& pInfo = getProcessorInfo(proc); + int recChans = pInfo.recordedChannels.size(); + + for (int chan = 0; chan < recChans; chan++) + { + int recordedChan = pInfo.recordedChannels[chan]; + int realChan = getRealChannel(recordedChan); + const DataChannel* channelInfo = getDataChannel(realChan); + int sourceId = channelInfo->getSourceNodeID(); + int sourceSubIdx = channelInfo->getSubProcessorIdx(); + int nInfoArrays = m_dataChannels.size(); + bool found = false; + for (int i = lastId; i < nInfoArrays; i++) + { + if (sourceId == m_dataChannels.getReference(i)[0]->getSourceNodeID() && sourceSubIdx == m_dataChannels.getReference(i)[0]->getSubProcessorIdx()) + { + m_channelIndexes.set(recordedChan, m_dataChannels.getReference(i).size()); + m_fileIndexes.set(recordedChan, i); + m_dataChannels.getReference(i).add(getDataChannel(realChan)); + found = true; + break; + } + } + if (!found) + { + File datFile(basepath + "_" + String(pInfo.processorId) + "_" + String(sourceId) + "." + String(sourceSubIdx) + "_" + String(recordingNumber) + ".dat"); + ScopedPointer<SequentialBlockFile> bFile = new SequentialBlockFile(pInfo.recordedChannels.size(), samplesPerBlock); + if (bFile->openFile(datFile)) + m_DataFiles.add(bFile.release()); + else + m_DataFiles.add(nullptr); + + ContinuousGroup newGroup; + newGroup.add(getDataChannel(realChan)); + m_dataChannels.add(newGroup); + m_fileIndexes.set(recordedChan, nInfoArrays); + m_channelIndexes.set(recordedChan, 0); + + } + } } int nChans = getNumRecordedChannels(); //Origin Timestamp @@ -130,7 +169,7 @@ void BinaryRecording::writeData(int writeChannel, int realChannel, const float* FloatVectorOperations::copyWithMultiply(m_scaledBuffer.getData(), buffer, multFactor, size); AudioDataConverters::convertFloatToInt16LE(m_scaledBuffer.getData(), m_intBuffer.getData(), size); - m_DataFiles[getProcessorFromChannel(writeChannel)]->writeChannel(getTimestamp(writeChannel)-m_startTS[writeChannel],getChannelNumInProc(writeChannel),m_intBuffer.getData(),size); + m_DataFiles[m_fileIndexes[writeChannel]]->writeChannel(getTimestamp(writeChannel)-m_startTS[writeChannel],m_channelIndexes[writeChannel],m_intBuffer.getData(),size); } //Code below is copied from OriginalRecording, so it's not as clean as newer one diff --git a/Source/Plugins/BinaryWriter/BinaryRecording.h b/Source/Plugins/BinaryWriter/BinaryRecording.h index a92f2acd429f274a370618dacce59d75dfe73697..d710dd4dfa9bd8334c2a2e89b3d709694df93a66 100644 --- a/Source/Plugins/BinaryWriter/BinaryRecording.h +++ b/Source/Plugins/BinaryWriter/BinaryRecording.h @@ -56,7 +56,7 @@ namespace BinaryRecordingEngine static RecordEngineManager* getEngineManager(); private: - + typedef Array<const DataChannel*> ContinuousGroup; void openSpikeFile(String basepath, int spikeIndex, int recordingNumber); String generateSpikeHeader(const SpikeChannel* elec); String generateEventHeader(); @@ -71,6 +71,9 @@ namespace BinaryRecordingEngine int m_bufferSize; OwnedArray<SequentialBlockFile> m_DataFiles; + Array<unsigned int> m_channelIndexes; + Array<unsigned int> m_fileIndexes; + Array<ContinuousGroup> m_dataChannels; FILE* eventFile; FILE* messageFile; diff --git a/Source/Plugins/CommonLibs/OpenEphysHDF5Lib/HDF5FileFormat.cpp b/Source/Plugins/CommonLibs/OpenEphysHDF5Lib/HDF5FileFormat.cpp index bed007fa9132ff7e5f7d66dcafd8da025bb0a3e2..fc3b8a080e95945f1854806734a5b06a8e692ac3 100644 --- a/Source/Plugins/CommonLibs/OpenEphysHDF5Lib/HDF5FileFormat.cpp +++ b/Source/Plugins/CommonLibs/OpenEphysHDF5Lib/HDF5FileFormat.cpp @@ -111,13 +111,13 @@ void HDF5FileBase::close() opened = false; } -int HDF5FileBase::setAttribute(DataTypes type, const void* data, String path, String name) +int HDF5FileBase::setAttribute(BaseDataType type, const void* data, String path, String name) { return setAttributeArray(type, data, 1, path, name); } -int HDF5FileBase::setAttributeArray(DataTypes type, const void* data, int size, String path, String name) +int HDF5FileBase::setAttributeArray(BaseDataType type, const void* data, int size, String path, String name) { H5Location* loc; Group gloc; @@ -335,13 +335,13 @@ HDF5RecordingData* HDF5FileBase::getDataSet(String path) } } -HDF5RecordingData* HDF5FileBase::createDataSet(DataTypes type, int sizeX, int chunkX, String path) +HDF5RecordingData* HDF5FileBase::createDataSet(BaseDataType type, int sizeX, int chunkX, String path) { int chunks[3] = {chunkX, 0, 0}; return createDataSet(type,1,&sizeX,chunks,path); } -HDF5RecordingData* HDF5FileBase::createDataSet(DataTypes type, int sizeX, int sizeY, int chunkX, String path) +HDF5RecordingData* HDF5FileBase::createDataSet(BaseDataType type, int sizeX, int sizeY, int chunkX, String path) { int size[2]; int chunks[3] = {chunkX, 0, 0}; @@ -350,7 +350,7 @@ HDF5RecordingData* HDF5FileBase::createDataSet(DataTypes type, int sizeX, int si return createDataSet(type,2,size,chunks,path); } -HDF5RecordingData* HDF5FileBase::createDataSet(DataTypes type, int sizeX, int sizeY, int sizeZ, int chunkX, String path) +HDF5RecordingData* HDF5FileBase::createDataSet(BaseDataType type, int sizeX, int sizeY, int sizeZ, int chunkX, String path) { int size[3]; int chunks[3] = {chunkX, 0, 0}; @@ -360,7 +360,7 @@ HDF5RecordingData* HDF5FileBase::createDataSet(DataTypes type, int sizeX, int si return createDataSet(type,3,size,chunks,path); } -HDF5RecordingData* HDF5FileBase::createDataSet(DataTypes type, int sizeX, int sizeY, int sizeZ, int chunkX, int chunkY, String path) +HDF5RecordingData* HDF5FileBase::createDataSet(BaseDataType type, int sizeX, int sizeY, int sizeZ, int chunkX, int chunkY, String path) { int size[3]; int chunks[3] = {chunkX, chunkY, 0}; @@ -370,7 +370,7 @@ HDF5RecordingData* HDF5FileBase::createDataSet(DataTypes type, int sizeX, int si return createDataSet(type,3,size,chunks,path); } -HDF5RecordingData* HDF5FileBase::createDataSet(DataTypes type, int dimension, int* size, int* chunking, String path) +HDF5RecordingData* HDF5FileBase::createDataSet(BaseDataType type, int dimension, int* size, int* chunking, String path) { ScopedPointer<DataSet> data; DSetCreatPropList prop; @@ -426,88 +426,135 @@ HDF5RecordingData* HDF5FileBase::createDataSet(DataTypes type, int dimension, in } -H5::DataType HDF5FileBase::getNativeType(DataTypes type) +H5::DataType HDF5FileBase::getNativeType(BaseDataType type) { - switch (type) - { - case I8: - return PredType::NATIVE_INT8; - break; - case I16: - return PredType::NATIVE_INT16; - break; - case I32: - return PredType::NATIVE_INT32; - break; - case I64: - return PredType::NATIVE_INT64; - break; - case U8: - return PredType::NATIVE_UINT8; - break; - case U16: - return PredType::NATIVE_UINT16; - break; - case U32: - return PredType::NATIVE_UINT32; - break; - case U64: - return PredType::NATIVE_UINT64; - break; - case F32: - return PredType::NATIVE_FLOAT; - break; - case F64: - return PredType::NATIVE_DOUBLE; - break; - case STR: - return StrType(PredType::C_S1,MAX_STR_SIZE); - break; - } - return PredType::NATIVE_INT32; + H5::DataType baseType; + + switch (type.type) + { + case BaseDataType::Type::T_I8: + baseType = PredType::NATIVE_INT8; + break; + case BaseDataType::Type::T_I16: + baseType = PredType::NATIVE_INT16; + break; + case BaseDataType::Type::T_I32: + baseType = PredType::NATIVE_INT32; + break; + case BaseDataType::Type::T_I64: + baseType = PredType::NATIVE_INT64; + break; + case BaseDataType::Type::T_U8: + baseType = PredType::NATIVE_UINT8; + break; + case BaseDataType::Type::T_U16: + baseType = PredType::NATIVE_UINT16; + break; + case BaseDataType::Type::T_U32: + baseType = PredType::NATIVE_UINT32; + break; + case BaseDataType::Type::T_U64: + baseType = PredType::NATIVE_UINT64; + break; + case BaseDataType::Type::T_F32: + baseType = PredType::NATIVE_FLOAT; + break; + case BaseDataType::Type::T_F64: + baseType = PredType::NATIVE_DOUBLE; + break; + case BaseDataType::Type::T_STR: + return StrType(PredType::C_S1, type.typeSize); + break; + default: + baseType = PredType::NATIVE_INT32; + } + if (type.typeSize > 1) + { + hsize_t size = type.typeSize; + return ArrayType(baseType, 1, &size); + } + else return baseType; } -H5::DataType HDF5FileBase::getH5Type(DataTypes type) +H5::DataType HDF5FileBase::getH5Type(BaseDataType type) { - switch (type) - { - case I8: - return PredType::STD_I8LE; - break; - case I16: - return PredType::STD_I16LE; - break; - case I32: - return PredType::STD_I32LE; - break; - case I64: - return PredType::STD_I64LE; - break; - case U8: - return PredType::STD_U8LE; - break; - case U16: - return PredType::STD_U16LE; - break; - case U32: - return PredType::STD_U32LE; - break; - case U64: - return PredType::STD_U64LE; - break; - case F32: - return PredType::IEEE_F32LE; - break; - case F64: - return PredType::IEEE_F64LE; - break; - case STR: - return StrType(PredType::C_S1,MAX_STR_SIZE); - break; - } - return PredType::STD_I32LE; + H5::DataType baseType; + + switch (type.type) + { + case BaseDataType::Type::T_I8: + baseType = PredType::STD_I8LE; + break; + case BaseDataType::Type::T_I16: + baseType = PredType::STD_I16LE; + break; + case BaseDataType::Type::T_I32: + baseType = PredType::STD_I32LE; + break; + case BaseDataType::Type::T_I64: + baseType = PredType::STD_I64LE; + break; + case BaseDataType::Type::T_U8: + baseType = PredType::STD_U8LE; + break; + case BaseDataType::Type::T_U16: + baseType = PredType::STD_U16LE; + break; + case BaseDataType::Type::T_U32: + baseType = PredType::STD_U32LE; + break; + case BaseDataType::Type::T_U64: + baseType = PredType::STD_U64LE; + break; + case BaseDataType::Type::T_F32: + return PredType::IEEE_F32LE; + break; + case BaseDataType::Type::T_F64: + baseType = PredType::IEEE_F64LE; + break; + case BaseDataType::Type::T_STR: + return StrType(PredType::C_S1, type.typeSize); + break; + default: + return PredType::STD_I32LE; + } + if (type.typeSize > 1) + { + hsize_t size = type.typeSize; + return ArrayType(baseType, 1, &size); + } + else return baseType; } +//BaseDataType + +HDF5FileBase::BaseDataType::BaseDataType(HDF5FileBase::BaseDataType::Type t, size_t s) + : type(t), typeSize(s) +{} + +HDF5FileBase::BaseDataType::BaseDataType() + : type(T_I32), typeSize(1) +{} + +HDF5FileBase::BaseDataType HDF5FileBase::BaseDataType::STR(size_t size) +{ + return HDF5FileBase::BaseDataType(T_STR, size); +} + +const HDF5FileBase::BaseDataType HDF5FileBase::BaseDataType::U8 = HDF5FileBase::BaseDataType(T_U8, 1); +const HDF5FileBase::BaseDataType HDF5FileBase::BaseDataType::U16 = HDF5FileBase::BaseDataType(T_U16, 1); +const HDF5FileBase::BaseDataType HDF5FileBase::BaseDataType::U32 = HDF5FileBase::BaseDataType(T_U32, 1); +const HDF5FileBase::BaseDataType HDF5FileBase::BaseDataType::U64 = HDF5FileBase::BaseDataType(T_U64, 1); +const HDF5FileBase::BaseDataType HDF5FileBase::BaseDataType::I8 = HDF5FileBase::BaseDataType(T_I8, 1); +const HDF5FileBase::BaseDataType HDF5FileBase::BaseDataType::I16 = HDF5FileBase::BaseDataType(T_I16, 1); +const HDF5FileBase::BaseDataType HDF5FileBase::BaseDataType::I32 = HDF5FileBase::BaseDataType(T_I32, 1); +const HDF5FileBase::BaseDataType HDF5FileBase::BaseDataType::I64 = HDF5FileBase::BaseDataType(T_I64, 1); +const HDF5FileBase::BaseDataType HDF5FileBase::BaseDataType::F32 = HDF5FileBase::BaseDataType(T_F32, 1); +const HDF5FileBase::BaseDataType HDF5FileBase::BaseDataType::F64 = HDF5FileBase::BaseDataType(T_F64, 1); +const HDF5FileBase::BaseDataType HDF5FileBase::BaseDataType::DSTR = HDF5FileBase::BaseDataType(T_STR, DEFAULT_STR_SIZE); + +//H5RecordingData + HDF5RecordingData::HDF5RecordingData(DataSet* data) { DataSpace dSpace; @@ -543,12 +590,12 @@ HDF5RecordingData::~HDF5RecordingData() //Safety dSet->flush(H5F_SCOPE_GLOBAL); } -int HDF5RecordingData::writeDataBlock(int xDataSize, HDF5FileBase::DataTypes type, const void* data) +int HDF5RecordingData::writeDataBlock(int xDataSize, HDF5FileBase::BaseDataType type, const void* data) { return writeDataBlock(xDataSize,size[1],type,data); } -int HDF5RecordingData::writeDataBlock(int xDataSize, int yDataSize, HDF5FileBase::DataTypes type, const void* data) +int HDF5RecordingData::writeDataBlock(int xDataSize, int yDataSize, HDF5FileBase::BaseDataType type, const void* data) { hsize_t dim[3],offset[3]; DataSpace fSpace; @@ -602,7 +649,7 @@ int HDF5RecordingData::writeDataBlock(int xDataSize, int yDataSize, HDF5FileBase } -int HDF5RecordingData::writeDataRow(int yPos, int xDataSize, HDF5FileBase::DataTypes type, const void* data) +int HDF5RecordingData::writeDataRow(int yPos, int xDataSize, HDF5FileBase::BaseDataType type, const void* data) { hsize_t dim[2],offset[2]; DataSpace fSpace; diff --git a/Source/Plugins/CommonLibs/OpenEphysHDF5Lib/HDF5FileFormat.h b/Source/Plugins/CommonLibs/OpenEphysHDF5Lib/HDF5FileFormat.h index 7f0796bd5d75c5b7e718450daf95ca88fca59288..9f0aac6758a91129927b26231d4918a0667e6345 100644 --- a/Source/Plugins/CommonLibs/OpenEphysHDF5Lib/HDF5FileFormat.h +++ b/Source/Plugins/CommonLibs/OpenEphysHDF5Lib/HDF5FileFormat.h @@ -27,13 +27,13 @@ #include <CommonLibHeader.h> #define PROCESS_ERROR std::cerr << error.getCDetailMsg() << std::endl; return -1 -#define CHECK_ERROR(x) if (x) std::cerr << "Error at HDFRecording " << __LINE__ << std::endl; +#define CHECK_ERROR(x) if (x) std::cerr << "Error at " << __FILE__ " " << __LINE__ << std::endl; #ifndef CHUNK_XSIZE #define CHUNK_XSIZE 2048 #endif -#define MAX_STR_SIZE 256 +#define DEFAULT_STR_SIZE 256 namespace H5 { @@ -59,18 +59,39 @@ public: virtual String getFileName() = 0; bool isOpen() const; bool isReadyToOpen() const; - typedef enum DataTypes { U8, U16, U32, U64, I8, I16, I32, I64, F32, F64, STR} DataTypes; - - static H5::DataType getNativeType(DataTypes type); - static H5::DataType getH5Type(DataTypes type); + class COMMON_LIB BaseDataType { + public: + enum Type { T_U8, T_U16, T_U32, T_U64, T_I8, T_I16, T_I32, T_I64, T_F32, T_F64, T_STR }; + BaseDataType(); + BaseDataType(Type, size_t); + Type type; + size_t typeSize; + + //handy accessors + static const BaseDataType U8; + static const BaseDataType U16; + static const BaseDataType U32; + static const BaseDataType U64; + static const BaseDataType I8; + static const BaseDataType I16; + static const BaseDataType I32; + static const BaseDataType I64; + static const BaseDataType F32; + static const BaseDataType F64; + static const BaseDataType DSTR; + static BaseDataType STR(size_t size); + }; + + static H5::DataType getNativeType(BaseDataType type); + static H5::DataType getH5Type(BaseDataType type); protected: virtual int createFileStructure() = 0; - int setAttribute(DataTypes type, const void* data, String path, String name); + int setAttribute(BaseDataType type, const void* data, String path, String name); int setAttributeStr(const String& value, String path, String name); - int setAttributeArray(DataTypes type, const void* data, int size, String path, String name); + int setAttributeArray(BaseDataType type, const void* data, int size, String path, String name); int setAttributeStrArray(const StringArray& data, String path, String name); int createGroup(String path); int createGroupIfDoesNotExist(String path); @@ -78,10 +99,10 @@ protected: HDF5RecordingData* getDataSet(String path); //aliases for createDataSet - HDF5RecordingData* createDataSet(DataTypes type, int sizeX, int chunkX, String path); - HDF5RecordingData* createDataSet(DataTypes type, int sizeX, int sizeY, int chunkX, String path); - HDF5RecordingData* createDataSet(DataTypes type, int sizeX, int sizeY, int sizeZ, int chunkX, String path); - HDF5RecordingData* createDataSet(DataTypes type, int sizeX, int sizeY, int sizeZ, int chunkX, int chunkY, String path); + HDF5RecordingData* createDataSet(BaseDataType type, int sizeX, int chunkX, String path); + HDF5RecordingData* createDataSet(BaseDataType type, int sizeX, int sizeY, int chunkX, String path); + HDF5RecordingData* createDataSet(BaseDataType type, int sizeX, int sizeY, int sizeZ, int chunkX, String path); + HDF5RecordingData* createDataSet(BaseDataType type, int sizeX, int sizeY, int sizeZ, int chunkX, int chunkY, String path); bool readyToOpen; @@ -89,7 +110,7 @@ private: int setAttributeStrArray(Array<const char*>& data, int maxSize, String path, String name); //create an extendable dataset - HDF5RecordingData* createDataSet(DataTypes type, int dimension, int* size, int* chunking, String path); + HDF5RecordingData* createDataSet(BaseDataType type, int dimension, int* size, int* chunking, String path); int open(bool newfile, int nChans); ScopedPointer<H5::H5File> file; bool opened; @@ -103,10 +124,10 @@ public: HDF5RecordingData(H5::DataSet* data); ~HDF5RecordingData(); - int writeDataBlock(int xDataSize, HDF5FileBase::DataTypes type, const void* data); - int writeDataBlock(int xDataSize, int yDataSize, HDF5FileBase::DataTypes type, const void* data); + int writeDataBlock(int xDataSize, HDF5FileBase::BaseDataType type, const void* data); + int writeDataBlock(int xDataSize, int yDataSize, HDF5FileBase::BaseDataType type, const void* data); - int writeDataRow(int yPos, int xDataSize, HDF5FileBase::DataTypes type, const void* data); + int writeDataRow(int yPos, int xDataSize, HDF5FileBase::BaseDataType type, const void* data); void getRowXPositions(Array<uint32>& rows); diff --git a/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.cpp b/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.cpp index 19d7f8f4b92e565640b8816ed4dff78122c9a77f..185f885ed155fa6782e13885ae8a2e58c0c1e34f 100644 --- a/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.cpp +++ b/Source/Plugins/KWIKFormat/RecordEngine/HDF5Recording.cpp @@ -281,8 +281,8 @@ void HDF5Recording::writeSpike(int electrodeIndex, const SpikeEvent* spike) void HDF5Recording::startAcquisition() { eventFile = new KWEFile(); - eventFile->addEventType("TTL",HDF5FileBase::U8,"event_channels"); - eventFile->addEventType("Messages",HDF5FileBase::STR,"Text"); + eventFile->addEventType("TTL",HDF5FileBase::BaseDataType::U8,"event_channels"); + eventFile->addEventType("Messages", HDF5FileBase::BaseDataType::DSTR, "Text"); spikesFile = new KWXFile(); } diff --git a/Source/Plugins/KWIKFormat/RecordEngine/KWIKFormat.cpp b/Source/Plugins/KWIKFormat/RecordEngine/KWIKFormat.cpp index 2bf8593d367e778fea0e048de2996f81b2a7cac8..110d86cf3c315347dab062f3ede100d28fe27206 100644 --- a/Source/Plugins/KWIKFormat/RecordEngine/KWIKFormat.cpp +++ b/Source/Plugins/KWIKFormat/RecordEngine/KWIKFormat.cpp @@ -69,31 +69,31 @@ void KWDFile::startNewRecording(int recordingNumber, int nChannels, KWIKRecordin String recordPath = String("/recordings/")+String(recordingNumber); CHECK_ERROR(createGroup(recordPath)); CHECK_ERROR(setAttributeStr(info->name,recordPath,String("name"))); - CHECK_ERROR(setAttribute(U64,&(info->start_time),recordPath,String("start_time"))); - CHECK_ERROR(setAttribute(U32,&(info->start_sample),recordPath,String("start_sample"))); - CHECK_ERROR(setAttribute(F32,&(info->sample_rate),recordPath,String("sample_rate"))); - CHECK_ERROR(setAttribute(U32,&(info->bit_depth),recordPath,String("bit_depth"))); + CHECK_ERROR(setAttribute(BaseDataType::U64, &(info->start_time), recordPath, String("start_time"))); + CHECK_ERROR(setAttribute(BaseDataType::U32, &(info->start_sample), recordPath, String("start_sample"))); + CHECK_ERROR(setAttribute(BaseDataType::F32, &(info->sample_rate), recordPath, String("sample_rate"))); + CHECK_ERROR(setAttribute(BaseDataType::U32, &(info->bit_depth), recordPath, String("bit_depth"))); CHECK_ERROR(createGroup(recordPath+"/application_data")); // CHECK_ERROR(setAttributeArray(F32,info->bitVolts.getRawDataPointer(),info->bitVolts.size(),recordPath+"/application_data",String("channel_bit_volts"))); - bitVoltsSet = createDataSet(F32, info->bitVolts.size(), 0, recordPath + "/application_data/channel_bit_volts"); + bitVoltsSet = createDataSet(BaseDataType::F32, info->bitVolts.size(), 0, recordPath + "/application_data/channel_bit_volts"); if (bitVoltsSet.get()) - bitVoltsSet->writeDataBlock(info->bitVolts.size(), F32, info->bitVolts.getRawDataPointer()); + bitVoltsSet->writeDataBlock(info->bitVolts.size(), BaseDataType::F32, info->bitVolts.getRawDataPointer()); else std::cerr << "Error creating bitvolts data set" << std::endl; - CHECK_ERROR(setAttribute(U8,&mSample,recordPath+"/application_data",String("is_multiSampleRate_data"))); + CHECK_ERROR(setAttribute(BaseDataType::U8, &mSample, recordPath + "/application_data", String("is_multiSampleRate_data"))); //CHECK_ERROR(setAttributeArray(F32,info->channelSampleRates.getRawDataPointer(),info->channelSampleRates.size(),recordPath+"/application_data",String("channel_sample_rates"))); - sampleRateSet = createDataSet(F32, info->channelSampleRates.size(), 0, recordPath + "/application_data/channel_sample_rates"); + sampleRateSet = createDataSet(BaseDataType::F32, info->channelSampleRates.size(), 0, recordPath + "/application_data/channel_sample_rates"); if (sampleRateSet.get()) - sampleRateSet->writeDataBlock(info->channelSampleRates.size(), F32, info->channelSampleRates.getRawDataPointer()); + sampleRateSet->writeDataBlock(info->channelSampleRates.size(), BaseDataType::F32, info->channelSampleRates.getRawDataPointer()); else std::cerr << "Error creating sample rates data set" << std::endl; - recdata = createDataSet(I16,0,nChannels,CHUNK_XSIZE,recordPath+"/data"); + recdata = createDataSet(BaseDataType::I16, 0, nChannels, CHUNK_XSIZE, recordPath + "/data"); if (!recdata.get()) std::cerr << "Error creating data set" << std::endl; - tsData = createDataSet(I64, 0, nChannels, TIMESTAMP_CHUNK_SIZE, recordPath + "/application_data/timestamps"); + tsData = createDataSet(BaseDataType::I64, 0, nChannels, TIMESTAMP_CHUNK_SIZE, recordPath + "/application_data/timestamps"); if (!tsData.get()) std::cerr << "Error creating timestamps data set" << std::endl; @@ -106,7 +106,7 @@ void KWDFile::stopRecording() String path = String("/recordings/")+String(recordingNumber)+String("/data"); recdata->getRowXPositions(samples); - CHECK_ERROR(setAttributeArray(U32,samples.getRawDataPointer(),samples.size(),path,"valid_samples")); + CHECK_ERROR(setAttributeArray(BaseDataType::U32, samples.getRawDataPointer(), samples.size(), path, "valid_samples")); //ScopedPointer does the deletion and destructors the closings recdata = nullptr; tsData = nullptr; @@ -116,13 +116,13 @@ int KWDFile::createFileStructure() { const uint16 ver = 2; if (createGroup("/recordings")) return -1; - if (setAttribute(U16,(void*)&ver,"/","kwik_version")) return -1; + if (setAttribute(BaseDataType::U16, (void*)&ver, "/", "kwik_version")) return -1; return 0; } void KWDFile::writeBlockData(int16* data, int nSamples) { - CHECK_ERROR(recdata->writeDataBlock(nSamples,I16,data)); + CHECK_ERROR(recdata->writeDataBlock(nSamples, BaseDataType::I16, data)); } void KWDFile::writeRowData(int16* data, int nSamples) @@ -131,7 +131,7 @@ void KWDFile::writeRowData(int16* data, int nSamples) { curChan=0; } - CHECK_ERROR(recdata->writeDataRow(curChan,nSamples,I16,data)); + CHECK_ERROR(recdata->writeDataRow(curChan, nSamples, BaseDataType::I16, data)); curChan++; } @@ -139,7 +139,7 @@ void KWDFile::writeRowData(int16* data, int nSamples, int channel) { if (channel >= 0 && channel < nChannels) { - CHECK_ERROR(recdata->writeDataRow(channel, nSamples, I16, data)); + CHECK_ERROR(recdata->writeDataRow(channel, nSamples, BaseDataType::I16, data)); curChan = channel; } } @@ -148,7 +148,7 @@ void KWDFile::writeTimestamps(int64* ts, int nTs, int channel) { if (channel >= 0 && channel < nChannels) { - CHECK_ERROR(tsData->writeDataRow(channel, nTs, I64, ts)); + CHECK_ERROR(tsData->writeDataRow(channel, nTs, BaseDataType::I64, ts)); } } @@ -190,20 +190,21 @@ int KWEFile::createFileStructure() if (createGroup(path)) return -1; path += "/events"; if (createGroup(path)) return -1; - dSet = createDataSet(U64,0,EVENT_CHUNK_SIZE,path + "/time_samples"); + dSet = createDataSet(BaseDataType::U64, 0, EVENT_CHUNK_SIZE, path + "/time_samples"); if (!dSet) return -1; - dSet = createDataSet(U16,0,EVENT_CHUNK_SIZE,path + "/recording"); + dSet = createDataSet(BaseDataType::U16, 0, EVENT_CHUNK_SIZE, path + "/recording"); if (!dSet) return -1; path += "/user_data"; if (createGroup(path)) return -1; - dSet = createDataSet(U8,0,EVENT_CHUNK_SIZE,path + "/eventID"); + dSet = createDataSet(BaseDataType::U8, 0, EVENT_CHUNK_SIZE, path + "/eventID"); if (!dSet) return -1; - dSet = createDataSet(U8,0,EVENT_CHUNK_SIZE,path + "/nodeID"); + dSet = createDataSet(BaseDataType::U8, 0, EVENT_CHUNK_SIZE, path + "/nodeID"); if (!dSet) return -1; dSet = createDataSet(eventTypes[i],0,EVENT_CHUNK_SIZE,path + "/" + eventDataNames[i]); if (!dSet) return -1; } - if (setAttribute(U16,(void*)&ver,"/","kwik_version")) return -1; + if (setAttribute(BaseDataType::U16, (void*)&ver, "/", "kwik_version")) return -1; + return 0; } @@ -214,10 +215,10 @@ void KWEFile::startNewRecording(int recordingNumber, KWIKRecordingInfo* info) String recordPath = String("/recordings/")+String(recordingNumber); CHECK_ERROR(createGroup(recordPath)); CHECK_ERROR(setAttributeStr(info->name,recordPath,String("name"))); - CHECK_ERROR(setAttribute(U64,&(info->start_time),recordPath,String("start_time"))); + CHECK_ERROR(setAttribute(BaseDataType::U64, &(info->start_time), recordPath, String("start_time"))); // CHECK_ERROR(setAttribute(U32,&(info->start_sample),recordPath,String("start_sample"))); - CHECK_ERROR(setAttribute(F32,&(info->sample_rate),recordPath,String("sample_rate"))); - CHECK_ERROR(setAttribute(U32,&(info->bit_depth),recordPath,String("bit_depth"))); + CHECK_ERROR(setAttribute(BaseDataType::F32, &(info->sample_rate), recordPath, String("sample_rate"))); + CHECK_ERROR(setAttribute(BaseDataType::U32, &(info->bit_depth), recordPath, String("bit_depth"))); // CHECK_ERROR(createGroup(recordPath + "/raw")); // CHECK_ERROR(createGroup(recordPath + "/raw/hdf5_paths")); @@ -264,10 +265,10 @@ void KWEFile::writeEvent(int type, uint8 id, uint8 processor, void* data, int64 std::cerr << "HDF5::writeEvent Invalid event type " << type << std::endl; return; } - CHECK_ERROR(timeStamps[type]->writeDataBlock(1,U64,×tamp)); - CHECK_ERROR(recordings[type]->writeDataBlock(1,I32,&recordingNumber)); - CHECK_ERROR(eventID[type]->writeDataBlock(1,U8,&id)); - CHECK_ERROR(nodeID[type]->writeDataBlock(1,U8,&processor)); + CHECK_ERROR(timeStamps[type]->writeDataBlock(1, BaseDataType::U64, ×tamp)); + CHECK_ERROR(recordings[type]->writeDataBlock(1, BaseDataType::I32, &recordingNumber)); + CHECK_ERROR(eventID[type]->writeDataBlock(1, BaseDataType::U8, &id)); + CHECK_ERROR(nodeID[type]->writeDataBlock(1, BaseDataType::U8, &processor)); CHECK_ERROR(eventData[type]->writeDataBlock(1,eventTypes[type],data)); } @@ -283,7 +284,7 @@ void KWEFile::writeEvent(int type, uint8 id, uint8 processor, void* data, int64 kwdIndex++; }*/ -void KWEFile::addEventType(String name, DataTypes type, String dataName) +void KWEFile::addEventType(String name, BaseDataType type, String dataName) { eventNames.add(name); eventTypes.add(type); @@ -325,7 +326,7 @@ int KWXFile::createFileStructure() { const uint16 ver = 2; if (createGroup("/channel_groups")) return -1; - if (setAttribute(U16,(void*)&ver,"/","kwik_version")) return -1; + if (setAttribute(BaseDataType::U16, (void*)&ver, "/", "kwik_version")) return -1; for (int i=0; i < channelArray.size(); i++) { int res = createChannelGroup(i); @@ -346,11 +347,11 @@ int KWXFile::createChannelGroup(int index) int nChannels = channelArray[index]; String path("/channel_groups/"+String(index)); CHECK_ERROR(createGroup(path)); - dSet = createDataSet(I16,0,0,nChannels,SPIKE_CHUNK_XSIZE,SPIKE_CHUNK_YSIZE,path+"/waveforms_filtered"); + dSet = createDataSet(BaseDataType::I16, 0, 0, nChannels, SPIKE_CHUNK_XSIZE, SPIKE_CHUNK_YSIZE, path + "/waveforms_filtered"); if (!dSet) return -1; - dSet = createDataSet(U64,0,SPIKE_CHUNK_XSIZE,path+"/time_samples"); + dSet = createDataSet(BaseDataType::U64, 0, SPIKE_CHUNK_XSIZE, path + "/time_samples"); if (!dSet) return -1; - dSet = createDataSet(U16,0,SPIKE_CHUNK_XSIZE,path+"/recordings"); + dSet = createDataSet(BaseDataType::U16, 0, SPIKE_CHUNK_XSIZE, path + "/recordings"); if (!dSet) return -1; return 0; } @@ -412,7 +413,7 @@ void KWXFile::writeSpike(int groupIndex, int nSamples, const float* data, Array< } } - CHECK_ERROR(spikeArray[groupIndex]->writeDataBlock(1,nSamples,I16,transformVector)); - CHECK_ERROR(recordingArray[groupIndex]->writeDataBlock(1,I32,&recordingNumber)); - CHECK_ERROR(timeStamps[groupIndex]->writeDataBlock(1,U64,×tamp)); + CHECK_ERROR(spikeArray[groupIndex]->writeDataBlock(1, nSamples, BaseDataType::I16, transformVector)); + CHECK_ERROR(recordingArray[groupIndex]->writeDataBlock(1, BaseDataType::I32, &recordingNumber)); + CHECK_ERROR(timeStamps[groupIndex]->writeDataBlock(1, BaseDataType::U64, ×tamp)); } diff --git a/Source/Plugins/KWIKFormat/RecordEngine/KWIKFormat.h b/Source/Plugins/KWIKFormat/RecordEngine/KWIKFormat.h index 5a467b68f7f4ef7b3278b90fd90246d18392e767..0ffefea2d0ca8ad945af942c4b69b7f704b24423 100644 --- a/Source/Plugins/KWIKFormat/RecordEngine/KWIKFormat.h +++ b/Source/Plugins/KWIKFormat/RecordEngine/KWIKFormat.h @@ -80,7 +80,7 @@ public: void stopRecording(); void writeEvent(int type, uint8 id, uint8 processor, void* data, int64 timestamp); // void addKwdFile(String filename); - void addEventType(String name, DataTypes type, String dataName); + void addEventType(String name, BaseDataType type, String dataName); String getFileName(); protected: @@ -95,7 +95,7 @@ private: OwnedArray<HDF5RecordingData> nodeID; OwnedArray<HDF5RecordingData> eventData; Array<String> eventNames; - Array<DataTypes> eventTypes; + Array<BaseDataType> eventTypes; Array<String> eventDataNames; int kwdIndex; diff --git a/Source/Plugins/NWBFormat/NWBFormat.cpp b/Source/Plugins/NWBFormat/NWBFormat.cpp index 4288c4dddd82ef1b5f8c87ff1180c4327cd92562..dea14b24618a0741263cbb04c9b022a32884f58b 100644 --- a/Source/Plugins/NWBFormat/NWBFormat.cpp +++ b/Source/Plugins/NWBFormat/NWBFormat.cpp @@ -36,10 +36,16 @@ #define SPIKE_CHUNK_YSIZE 40 #endif + #define MAX_BUFFER_SIZE 40960 + NWBFile::NWBFile(String fName, String ver, String idText) : HDF5FileBase(), filename(fName), identifierText(idText), GUIVersion(ver) { //Init stuff readyToOpen=true; //In KWIK this is in initFile, but the new recordEngine methods make it safe for it to be here + + scaledBuffer.malloc(MAX_BUFFER_SIZE); + intBuffer.malloc(MAX_BUFFER_SIZE); + bufferSize = MAX_BUFFER_SIZE; } NWBFile::~NWBFile() @@ -65,10 +71,7 @@ int NWBFile::createFileStructure() if (createGroup("/stimulus")) return -1; if (createGroup("/acquisition/timeseries")) return -1; - if (createGroup("/acquisition/timeseries/continuous")) return -1; - if (createGroup("/acquisition/timeseries/spikes")) return -1; - if (createGroup("/acquisition/timeseries/messages")) return -1; - if (createGroup("/acquisition/timeseries/events")) return -1; + if (createGroup("/general/data_collection")) return -1; @@ -87,167 +90,279 @@ int NWBFile::createFileStructure() return 0; } - bool NWBFile::startNewRecording(int recordingNumber, const Array<NWBRecordingInfo>& continuousInfo, const Array<NWBRecordingInfo>& spikeInfo) +bool NWBFile::startNewRecording(int recordingNumber, const Array<ContinuousGroup>& continuousArray, + const Array<const EventChannel*>& eventArray, const Array<const SpikeChannel*>& electrodeArray) { //Created each time a new recording is started. Creates the specific file structures and attributes //for that specific recording - HDF5RecordingData* dSet; String basePath; - int nCont = continuousInfo.size(); + StringArray ancestry; + String rootPath = "/acquisition/timeseries/recording" + String(recordingNumber); + if (createGroup(rootPath)) return false; + if (createGroupIfDoesNotExist(rootPath + "/continuous")) return false; + if (createGroupIfDoesNotExist(rootPath + "/spikes")) return false; + if (createGroupIfDoesNotExist(rootPath + "/events")) return false; + + //just in case + continuousDataSets.clearQuick(true); + spikeDataSets.clearQuick(true); + eventDataSets.clearQuick(true); + + ScopedPointer<TimeSeries> tsStruct; + ScopedPointer<HDF5RecordingData> dSet; + + int nCont; + nCont = continuousArray.size(); for (int i = 0; i < nCont; i++) { - basePath = "/acquisition/timeseries/continuous"; - basePath = basePath + "/processor" + String(continuousInfo[i].processorId) + "_" + String(continuousInfo[i].sourceId) + "." + String(continuousInfo[i].sourceSubIdx); - if (createGroupIfDoesNotExist(basePath)) return false; - basePath = basePath + "/recording" + String(recordingNumber); - if (createGroup(basePath)) return false; - dSet = createRecordingStructures(basePath, continuousInfo[i], "Stores acquired voltage data from extracellular recordings", CHUNK_XSIZE, "ElectricalSeries"); - continuousDataSetsTS.add(dSet); - basePath += "/data"; - continuousBasePaths.add(basePath); - dSet = createDataSet(I16, 0, continuousInfo[i].nChannels, CHUNK_XSIZE, basePath); - continuousDataSets.add(dSet); //even if it's null, to not break ordering. - if (!dSet) - std::cerr << "Error creating data dataset for " << continuousInfo[i].processorId << std::endl; + //All channels in a group will share the same source information (any caller to this method MUST assure this happen + //so we just pick the first channel. + const DataChannel* info = continuousArray.getReference(i)[0]; + basePath = rootPath + "/continuous/processor" + String(info->getCurrentNodeID()) + "_" + String(info->getSourceNodeID()); + if (info->getSourceSubprocessorCount() > 1) basePath += "." + String(info->getSubProcessorIdx()); + String name = info->getCurrentNodeName() + " (" + String(info->getCurrentNodeID()) + ") From " + info->getSourceName() + " (" + String(info->getSourceNodeID()); + if (info->getSourceSubprocessorCount() > 1) name += "." + String(info->getSubProcessorIdx()); + name += ")"; + ancestry.clearQuick(); + ancestry.add("Timeseries"); + ancestry.add("ElectricalSeries"); + if (!createTimeSeriesBase(basePath, name, "Stores acquired voltage data from extracellular recordings", "", ancestry)) return false; + tsStruct = new TimeSeries(); + tsStruct->basePath = basePath; + dSet = createDataSet(BaseDataType::I16, 0, continuousArray.getReference(i).size(), CHUNK_XSIZE, basePath + "/data"); + if (dSet == nullptr) + { + std::cerr << "Error creating dataset for " << name << std::endl; + return false; + } else { - float bV = continuousInfo[i].bitVolts; - CHECK_ERROR(setAttribute(F32, &bV, basePath, "resolution")); - bV = bV / float(65536); - CHECK_ERROR(setAttribute(F32, &bV, basePath, "resolution")); - CHECK_ERROR(setAttributeStr("volt", basePath, "unit")); + createDataAttributes(basePath, info->getBitVolts(), info->getBitVolts() / 65536, info->getDataUnits()); } - continuousInfoStructs.add(continuousInfo[i]); - } + tsStruct->baseDataSet = dSet; + + dSet = createTimestampDataSet(basePath, CHUNK_XSIZE); + if (dSet == nullptr) return false; + tsStruct->timestampDataSet = dSet; - nCont = spikeInfo.size(); + basePath = basePath + "/oe_extra_info"; + if (createGroup(basePath)) return false; + int nChans = continuousArray.getReference(i).size(); + for (int j = 0; j < nChans; j++) + { + String channelPath = basePath + "/channel" + String(j + 1); + const DataChannel* chan = continuousArray.getReference(i)[j]; + createExtraInfo(channelPath, chan->getName(), chan->getDescription(), chan->getIdentifier(), chan->getSourceIndex(), chan->getSourceTypeIndex()); + createChannelMetaDataSets(channelPath + "/channel_metadata", chan); + } + continuousDataSets.add(tsStruct.release()); + } + + nCont = electrodeArray.size(); for (int i = 0; i < nCont; i++) { - basePath = "/acquisition/timeseries/spikes"; - basePath = basePath + "/electrode" + String(i); - if (createGroupIfDoesNotExist(basePath)) return false; - basePath = basePath + "/recording" + String(recordingNumber); - if (createGroup(basePath)) return false; - dSet = createRecordingStructures(basePath, spikeInfo[i], "Snapshorts of spike events from data", SPIKE_CHUNK_XSIZE, "SpikeEventSeries"); - spikeDataSetsTS.add(dSet); - basePath += "/data"; - spikeBasePaths.add(basePath); - dSet = createDataSet(I16, 0, spikeInfo[i].nChannels, spikeInfo[i].nSamplesPerSpike, SPIKE_CHUNK_XSIZE, basePath); - spikeDataSets.add(dSet); //even if it's null, to not break ordering. - if (!dSet) - std::cerr << "Error creating spike dataset for " << spikeInfo[i].processorId << std::endl; + basePath = rootPath + "/spikes/electrode" + String(i + 1); + const SpikeChannel* info = electrodeArray[i]; + String sourceName = info->getSourceName() + "_" + String(info->getSourceNodeID()); + if (info->getSourceSubprocessorCount() > 1) sourceName = sourceName + "." + String(info->getSubProcessorIdx()); + ancestry.clearQuick(); + ancestry.add("Timeseries"); + ancestry.add("SpikeEventSeries"); + if (!createTimeSeriesBase(basePath, sourceName, "Snapshorts of spike events from data", info->getName(), ancestry)) return false; + + tsStruct = new TimeSeries(); + tsStruct->basePath = basePath; + + dSet = createDataSet(BaseDataType::I16, 0, info->getNumChannels(), info->getTotalSamples(), SPIKE_CHUNK_XSIZE, basePath + "/data"); + if (dSet == nullptr) + { + std::cerr << "Error creating dataset for electrode " << i << std::endl; + return false; + } else { - float bV = spikeInfo[i].bitVolts; - CHECK_ERROR(setAttribute(F32, &bV, basePath, "resolution")); - bV = bV / float(65536); - CHECK_ERROR(setAttribute(F32, &bV, basePath, "resolution")); - CHECK_ERROR(setAttributeStr("volt", basePath, "unit")); + createDataAttributes(basePath, info->getChannelBitVolts(0), info->getChannelBitVolts(0) / 65536, "volt"); } - spikeInfoStructs.add(spikeInfo[i]); - numSpikes.add(0); + tsStruct->baseDataSet = dSet; + dSet = createTimestampDataSet(basePath, SPIKE_CHUNK_XSIZE); + if (dSet == nullptr) return false; + tsStruct->timestampDataSet = dSet; + + basePath = basePath + "/oe_extra_info"; + createExtraInfo(basePath, info->getName(), info->getDescription(), info->getIdentifier(), info->getSourceIndex(), info->getSourceTypeIndex()); + createChannelMetaDataSets(basePath + "/channel_metadata", info); + createEventMetaDataSets(basePath + "/spike_metadata", tsStruct, info); + + spikeDataSets.add(tsStruct.release()); + } + + nCont = eventArray.size(); + int nTTL = 0; + int nTXT = 0; + int nBIN = 0; + for (int i = 0; i < nCont; i++) + { + basePath = rootPath + "/events"; + const EventChannel* info = eventArray[i]; + String sourceName = info->getSourceName() + "_" + String(info->getSourceNodeID()); + if (info->getSourceSubprocessorCount() > 1) sourceName = sourceName + "." + String(info->getSubProcessorIdx()); + ancestry.clearQuick(); + ancestry.add("Timeseries"); - NWBRecordingInfo singleInfo; - singleInfo.bitVolts = NAN; - singleInfo.nChannels = 0; - singleInfo.nSamplesPerSpike = 0; - singleInfo.processorId = 0; - singleInfo.sampleRate = 0; - singleInfo.sourceId = 0; - singleInfo.spikeElectrodeName = " "; - singleInfo.sourceName = "All processors"; - - basePath = "/acquisition/timeseries/events"; - basePath = basePath + "/recording" + String(recordingNumber); - if (createGroup(basePath)) return false; - dSet = createRecordingStructures(basePath, singleInfo, "Stores the start and stop times for events",EVENT_CHUNK_SIZE, "IntervalSeries"); - eventsDataSetTS = dSet; - eventsControlDataSet = createDataSet(U8, 0, EVENT_CHUNK_SIZE, basePath + "/control"); - if (!eventsControlDataSet) - std::cerr << "Error creating events control dataset" << std::endl; - - basePath += "/data"; - eventsBasePath = basePath; - dSet = createDataSet(I8, 0, EVENT_CHUNK_SIZE, basePath); - eventsDataSet = dSet; //even if it's null, to not break ordering. - if (!dSet) - std::cerr << "Error creating events dataset " << std::endl; - else + String helpText; + + switch (info->getChannelType()) + { + case EventChannel::TTL: + nTTL += 1; + basePath = basePath + "/ttl" + String(nTTL); + ancestry.add("IntervalSeries"); + ancestry.add("TTLSeries"); + helpText = "Stores the start and stop times for TTL events"; + break; + case EventChannel::TEXT: + nTXT += 1; + basePath = basePath + "/text" + String(nTXT); + ancestry.add("AnnotationSeries"); + helpText = "Time-stamped annotations about an experiment"; + break; + default: + nBIN += 1; + basePath = basePath + "/binary" + String(nBIN); + ancestry.add("BinarySeries"); + helpText = "Stores arbitrary binary data"; + break; + } + + if (!createTimeSeriesBase(basePath, sourceName, helpText, info->getDescription(), ancestry)) return false; + + tsStruct = new TimeSeries(); + tsStruct->basePath = basePath; + + if (info->getChannelType() >= EventChannel::BINARY_BASE_VALUE) //only binary events have length greater than 1 + { + dSet = createDataSet(getEventH5Type(info->getChannelType(), info->getLength()), 0, info->getLength(), EVENT_CHUNK_SIZE, basePath + "/data");; + } + else + { + dSet = createDataSet(getEventH5Type(info->getChannelType(), info->getLength()), 0, EVENT_CHUNK_SIZE, basePath + "/data"); + } + + if (dSet == nullptr) + { + std::cerr << "Error creating dataset for event " << info->getName() << std::endl; + return false; + } + else + { + createDataAttributes(basePath, NAN, NAN, "n/a"); + } + + + tsStruct->baseDataSet = dSet; + dSet = createTimestampDataSet(basePath, EVENT_CHUNK_SIZE); + if (dSet == nullptr) return false; + tsStruct->timestampDataSet = dSet; + + dSet = createDataSet(BaseDataType::U8, 0, EVENT_CHUNK_SIZE, basePath + "/control"); + if (dSet == nullptr) return false; + tsStruct->controlDataSet = dSet; + + if (info->getChannelType() == EventChannel::TTL) + { + dSet = createDataSet(BaseDataType::U8, 0, info->getDataSize(), EVENT_CHUNK_SIZE, basePath + "/full_word"); + if (dSet == nullptr) return false; + tsStruct->ttlWordDataSet = dSet; + } + + basePath = basePath + "/oe_extra_info"; + createExtraInfo(basePath, info->getName(), info->getDescription(), info->getIdentifier(), info->getSourceIndex(), info->getSourceTypeIndex()); + createChannelMetaDataSets(basePath + "/channel_metadata", info); + createEventMetaDataSets(basePath + "/event_metadata", tsStruct, info); + eventDataSets.add(tsStruct.release()); + + } + basePath = rootPath + "/events/sync_messages"; + ancestry.clearQuick(); + ancestry.add("Timeseries"); + ancestry.add("AnnotationSeries"); + String desc = "Stores recording start timestamps for each processor in text format"; + if (!createTimeSeriesBase(basePath, "Autogenerated messages", desc, desc, ancestry)) return false; + tsStruct = new TimeSeries(); + tsStruct->basePath = basePath; + dSet = createDataSet(BaseDataType::STR(100), 0, 1, basePath + "/data"); + if (dSet == nullptr) { - float bV = NAN; - CHECK_ERROR(setAttribute(F32, &bV, basePath, "resolution")); - CHECK_ERROR(setAttribute(F32, &bV, basePath, "resolution")); - CHECK_ERROR(setAttributeStr("n/a", basePath, "unit")); + std::cerr << "Error creating dataset for sync messages" << std::endl; + return false; } - numEvents = 0; - - basePath = "/acquisition/timeseries/messages"; - basePath = basePath + "/recording" + String(recordingNumber); - if (createGroup(basePath)) return false; - dSet = createRecordingStructures(basePath, singleInfo, "Time-stamped annotations about an experiment", EVENT_CHUNK_SIZE, "AnnotationSeries"); - messagesDataSetTS = dSet; - basePath += "/data"; - messagesBasePath = basePath; - dSet = createDataSet(STR, 0, EVENT_CHUNK_SIZE, basePath); - messagesDataSet = dSet; //even if it's null, to not break ordering. - if (!dSet) - std::cerr << "Error creating messages dataset " << std::endl; else { - float bV = NAN; - CHECK_ERROR(setAttribute(F32, &bV, basePath, "resolution")); - CHECK_ERROR(setAttribute(F32, &bV, basePath, "resolution")); - CHECK_ERROR(setAttributeStr("n/a", basePath, "unit")); + createDataAttributes(basePath, NAN, NAN, "n/a"); } - numMessages = 0; + tsStruct->baseDataSet = dSet; + dSet = createTimestampDataSet(basePath, 1); + if (dSet == nullptr) return false; + tsStruct->timestampDataSet = dSet; + + dSet = createDataSet(BaseDataType::U8, 0, 1, basePath + "/control"); + if (dSet == nullptr) return false; + tsStruct->controlDataSet = dSet; + syncMsgDataSet = tsStruct; + return true; } void NWBFile::stopRecording() { - uint64 n; int nObjs = continuousDataSets.size(); + const TimeSeries* tsStruct; for (int i = 0; i < nObjs; i++) { - n = numContinuousSamples[i]; - CHECK_ERROR(setAttribute(U64, &n, continuousBasePaths[i], "num_samples")); + tsStruct = continuousDataSets[i]; + CHECK_ERROR(setAttribute(BaseDataType::U64, &(tsStruct->numSamples), tsStruct->basePath, "num_samples")); } nObjs = spikeDataSets.size(); for (int i = 0; i < nObjs; i++) { - n = numSpikes[i]; - CHECK_ERROR(setAttribute(I32, &n, spikeBasePaths[i], "num_samples")); + tsStruct = spikeDataSets[i]; + CHECK_ERROR(setAttribute(BaseDataType::U64, &(tsStruct->numSamples), tsStruct->basePath, "num_samples")); } - CHECK_ERROR(setAttribute(I32, &numEvents, eventsBasePath, "num_samples")); - CHECK_ERROR(setAttribute(I32, &numMessages, messagesBasePath, "num_samples")); + nObjs = eventDataSets.size(); + for (int i = 0; i < nObjs; i++) + { + tsStruct = eventDataSets[i]; + CHECK_ERROR(setAttribute(BaseDataType::U64, &(tsStruct->numSamples), tsStruct->basePath, "num_samples")); + } + + CHECK_ERROR(setAttribute(BaseDataType::U64, &(syncMsgDataSet->numSamples), syncMsgDataSet->basePath, "num_samples")); continuousDataSets.clear(); - continuousDataSetsTS.clear(); - continuousBasePaths.clear(); - numContinuousSamples.clear(); - spikeDataSets.clear(); - spikeDataSetsTS.clear(); - spikeBasePaths.clear(); - numSpikes.clear(); - - eventsDataSet = nullptr; - eventsDataSetTS = nullptr; - eventsControlDataSet = nullptr; - - messagesDataSet = nullptr; - messagesDataSetTS = nullptr; - + eventDataSets.clear(); + syncMsgDataSet = nullptr; } - void NWBFile::writeData(int datasetID, int channel, int nSamples, const int16* data) + void NWBFile::writeData(int datasetID, int channel, int nSamples, const float* data, float bitVolts) { if (!continuousDataSets[datasetID]) return; - CHECK_ERROR(continuousDataSets[datasetID]->writeDataRow(channel, nSamples, I16, data)); + if (nSamples > bufferSize) //Shouldn't happen, and if it happens it'll be slow, but better this than crashing. Will be reset on file close and reset. + { + std::cerr << "Write buffer overrun, resizing to" << nSamples << std::endl; + bufferSize = nSamples; + scaledBuffer.malloc(nSamples); + intBuffer.malloc(nSamples); + } + + double multFactor = 1 / (float(0x7fff) * bitVolts); + FloatVectorOperations::copyWithMultiply(scaledBuffer.getData(), data, multFactor, nSamples); + AudioDataConverters::convertFloatToInt16LE(scaledBuffer.getData(), intBuffer.getData(), nSamples); + + CHECK_ERROR(continuousDataSets[datasetID]->baseDataSet->writeDataRow(channel, nSamples, BaseDataType::I16, intBuffer)); /* Since channels are filled asynchronouysly by the Record Thread, there is no guarantee that at a any point in time all channels in a dataset have the same number of filled samples. @@ -256,80 +371,304 @@ int NWBFile::createFileStructure() an arbitrary channel, and at the end all channels will be the same. */ if (channel == 0) //there will always be a first channel or there wouldn't be dataset - numContinuousSamples.set(datasetID, numContinuousSamples[datasetID] + nSamples); + continuousDataSets[datasetID]->numSamples += nSamples; } void NWBFile::writeTimestamps(int datasetID, int nSamples, const double* data) { - if (!continuousDataSetsTS[datasetID]) + if (!continuousDataSets[datasetID]) return; - CHECK_ERROR(continuousDataSetsTS[datasetID]->writeDataBlock(nSamples, F64, data)); + CHECK_ERROR(continuousDataSets[datasetID]->timestampDataSet->writeDataBlock(nSamples, BaseDataType::F64, data)); } - void NWBFile::writeSpike(int electrodeId, const int16* data, double timestampSec) + void NWBFile::writeSpike(int electrodeId, const SpikeChannel* channel, const SpikeEvent* event) { if (!spikeDataSets[electrodeId]) return; - int totValues = spikeInfoStructs[electrodeId].nChannels * spikeInfoStructs[electrodeId].nSamplesPerSpike; - CHECK_ERROR(spikeDataSets[electrodeId]->writeDataBlock(1, I16, data)); - CHECK_ERROR(spikeDataSetsTS[electrodeId]->writeDataBlock(1, F64, ×tampSec)); - numSpikes.set(electrodeId, numSpikes[electrodeId] + 1); + int nSamples = channel->getTotalSamples() * channel->getNumChannels(); + + if (nSamples > bufferSize) //Shouldn't happen, and if it happens it'll be slow, but better this than crashing. Will be reset on file close and reset. + { + std::cerr << "Write buffer overrun, resizing to" << nSamples << std::endl; + bufferSize = nSamples; + scaledBuffer.malloc(nSamples); + intBuffer.malloc(nSamples); + } + + double multFactor = 1 / (float(0x7fff) * channel->getChannelBitVolts(0)); + FloatVectorOperations::copyWithMultiply(scaledBuffer.getData(), event->getDataPointer(), multFactor, nSamples); + AudioDataConverters::convertFloatToInt16LE(scaledBuffer.getData(), intBuffer.getData(), nSamples); + + double timestampSec = event->getTimestamp() / channel->getSampleRate(); + + CHECK_ERROR(spikeDataSets[electrodeId]->baseDataSet->writeDataBlock(1, BaseDataType::I16, intBuffer)); + CHECK_ERROR(spikeDataSets[electrodeId]->timestampDataSet->writeDataBlock(1, BaseDataType::F64, ×tampSec)); + writeEventMetaData(spikeDataSets[electrodeId], channel, event); + + spikeDataSets[electrodeId]->numSamples += 1; } - void NWBFile::writeTTLEvent(int channel, int id, uint8 source, double timestampSec) + void NWBFile::writeEvent(int eventID, const EventChannel* channel, const Event* event) { - int8 data = id != 0 ? channel : -channel; - CHECK_ERROR(eventsDataSet->writeDataBlock(1, I8, &data)); - CHECK_ERROR(eventsDataSetTS->writeDataBlock(1, F64, ×tampSec)); - CHECK_ERROR(eventsControlDataSet->writeDataBlock(1, U8, &source)); - numEvents += 1; + if (!eventDataSets[eventID]) + return; + + const void* dataSrc; + BaseDataType type; + int8 ttlVal; + String text; + + switch (event->getEventType()) + { + case EventChannel::TTL: + ttlVal = (static_cast<const TTLEvent*>(event)->getState() ? 1 : -1) * (event->getChannel() + 1); + dataSrc = &ttlVal; + type = BaseDataType::I8; + break; + case EventChannel::TEXT: + text = static_cast<const TextEvent*>(event)->getText(); + dataSrc = text.toUTF8().getAddress(); + type = BaseDataType::STR(text.length()); + break; + default: + dataSrc = static_cast<const BinaryEvent*>(event)->getBinaryDataPointer(); + type = getEventH5Type(event->getEventType()); + break; + } + CHECK_ERROR(eventDataSets[eventID]->baseDataSet->writeDataBlock(1, type, dataSrc)); + + double timeSec = event->getTimestamp() / channel->getSampleRate(); + + CHECK_ERROR(eventDataSets[eventID]->timestampDataSet->writeDataBlock(1, BaseDataType::F64, &timeSec)); + + uint8 controlValue = event->getChannel() + 1; + + CHECK_ERROR(eventDataSets[eventID]->controlDataSet->writeDataBlock(1, BaseDataType::U8, &controlValue)); + + if (event->getEventType() == EventChannel::TTL) + { + CHECK_ERROR(eventDataSets[eventID]->ttlWordDataSet->writeDataBlock(1, BaseDataType::U8, static_cast<const TTLEvent*>(event)->getTTLWordPointer())); + } + + eventDataSets[eventID]->numSamples += 1; } - void NWBFile::writeMessage(const char* msg, double timestampSec) + void NWBFile::writeTimestampSyncText(uint16 sourceID, int64 timestamp, float sourceSampleRate, String text) { - CHECK_ERROR(messagesDataSet->writeDataBlock(1, STR, msg)); - CHECK_ERROR(messagesDataSetTS->writeDataBlock(1, F64, ×tampSec)); - numMessages += 1; + CHECK_ERROR(syncMsgDataSet->baseDataSet->writeDataBlock(1, BaseDataType::STR(text.length()), text.toUTF8())); + double timeSec = timestamp / sourceSampleRate; + CHECK_ERROR(syncMsgDataSet->timestampDataSet->writeDataBlock(1, BaseDataType::F64, &timeSec)); + CHECK_ERROR(syncMsgDataSet->controlDataSet->writeDataBlock(1, BaseDataType::U8, &sourceID)); + syncMsgDataSet->numSamples += 1; } + String NWBFile::getFileName() { return filename; } - HDF5RecordingData* NWBFile::createRecordingStructures(String basePath, const NWBRecordingInfo& info, String helpText, int chunk_size, String ancestry) + bool NWBFile::createTimeSeriesBase(String basePath, String source, String helpText, String description, StringArray ancestry) { - StringArray ancestryStrings; - ancestryStrings.add("TimeSeries"); - ancestryStrings.add(ancestry); - CHECK_ERROR(setAttributeStrArray(ancestryStrings, basePath, "ancestry")); + if (createGroup(basePath)) return false; + CHECK_ERROR(setAttributeStrArray(ancestry, basePath, "ancestry")); CHECK_ERROR(setAttributeStr(" ", basePath, "comments")); - CHECK_ERROR(setAttributeStr(info.spikeElectrodeName, basePath, "description")); + CHECK_ERROR(setAttributeStr(description, basePath, "description")); CHECK_ERROR(setAttributeStr("TimeSeries", basePath, "neurodata_type")); - CHECK_ERROR(setAttributeStr(info.sourceName, basePath, "source")); + CHECK_ERROR(setAttributeStr(source, basePath, "source")); CHECK_ERROR(setAttributeStr(helpText, basePath, "help")); - HDF5RecordingData* tsSet = createDataSet(HDF5FileBase::F64, 0, chunk_size, basePath + "/timestamps"); - if (!tsSet) - std::cerr << "Error creating timestamp dataset for " << info.processorId << std::endl; - else - { - const int32 one = 1; - CHECK_ERROR(setAttribute(HDF5FileBase::I32, &one, basePath + "/timestamps", "interval")); - CHECK_ERROR(setAttributeStr("seconds", basePath + "/timestamps", "unit")); - } - return tsSet; - + return true; } + + void NWBFile::createDataAttributes(String basePath, float conversion, float resolution, String unit) + { + CHECK_ERROR(setAttribute(BaseDataType::F32, &conversion, basePath + "/data", "conversion")); + CHECK_ERROR(setAttribute(BaseDataType::F32, &resolution, basePath + "/data", "resolution")); + CHECK_ERROR(setAttributeStr(unit, basePath + "/data", "unit")); + } + + HDF5RecordingData* NWBFile::createTimestampDataSet(String basePath, int chunk_size) + { + HDF5RecordingData* tsSet = createDataSet(BaseDataType::F64, 0, chunk_size, basePath + "/timestamps"); + if (!tsSet) + std::cerr << "Error creating timestamp dataset in " << basePath << std::endl; + else + { + const int32 one = 1; + CHECK_ERROR(setAttribute(BaseDataType::I32, &one, basePath + "/timestamps", "interval")); + CHECK_ERROR(setAttributeStr("seconds", basePath + "/timestamps", "unit")); + } + return tsSet; + } + + bool NWBFile::createExtraInfo(String basePath, String name, String desc, String id, uint16 index, uint16 typeIndex) + { + if (createGroup(basePath)) return false; + CHECK_ERROR(setAttributeStr("openephys:<channel_info>/", basePath, "schema_id")); + CHECK_ERROR(setAttributeStr(name, basePath, "name")); + CHECK_ERROR(setAttributeStr(desc, basePath, "description")); + CHECK_ERROR(setAttributeStr(id, basePath, "identifier")); + CHECK_ERROR(setAttribute(BaseDataType::U16, &index, basePath, "source_index")); + CHECK_ERROR(setAttribute(BaseDataType::U16, &typeIndex, basePath, "source_type_index")); + return true; + } + + bool NWBFile::createChannelMetaDataSets(String basePath, const MetaDataInfoObject* info) + { + if (!info) return false; + if (createGroup(basePath)) return false; + CHECK_ERROR(setAttributeStr("openephys:<metadata>/", basePath, "schema_id")); + int nMetaData = info->getMetaDataCount(); + + for (int i = 0; i < nMetaData; i++) + { + const MetaDataDescriptor* desc = info->getMetaDataDescriptor(i); + String fieldName = "Field_" + String(i+1); + String name = desc->getName(); + String description = desc->getDescription(); + String identifier = desc->getIdentifier(); + BaseDataType type = getMetaDataH5Type(desc->getType(), desc->getLength()); //only string types use length, for others is always set to 1. If array types are implemented, change this + int length = desc->getType() == MetaDataDescriptor::CHAR ? 1 : desc->getLength(); //strings are a single element of length set in the type (see above) while other elements are saved a + HeapBlock<char> data(desc->getDataSize()); + info->getMetaDataValue(i)->getValue(static_cast<void*>(data.getData())); + createBinaryDataSet(basePath, fieldName, type, length, data.getData()); + String fullPath = basePath + "/" + fieldName; + CHECK_ERROR(setAttributeStr("openephys:<metadata>/", fullPath, "schema_id")); + CHECK_ERROR(setAttributeStr(name, fullPath, "name")); + CHECK_ERROR(setAttributeStr(description, fullPath, "description")); + CHECK_ERROR(setAttributeStr(identifier, fullPath, "identifier")); + } + return true; + } + + + bool NWBFile::createEventMetaDataSets(String basePath, TimeSeries* timeSeries, const MetaDataEventObject* info) + { + if (!info) return false; + if (createGroup(basePath)) return false; + CHECK_ERROR(setAttributeStr("openephys:<metadata>/", basePath, "schema_id")); + int nMetaData = info->getEventMetaDataCount(); + + timeSeries->metaDataSet.clear(); //just in case + for (int i = 0; i < nMetaData; i++) + { + const MetaDataDescriptor* desc = info->getEventMetaDataDescriptor(i); + String fieldName = "Field_" + String(i+1); + String name = desc->getName(); + String description = desc->getDescription(); + String identifier = desc->getIdentifier(); + BaseDataType type = getMetaDataH5Type(desc->getType(), desc->getLength()); //only string types use length, for others is always set to 1. If array types are implemented, change this + int length = desc->getType() == MetaDataDescriptor::CHAR ? 1 : desc->getLength(); //strings are a single element of length set in the type (see above) while other elements are saved as arrays + String fullPath = basePath + "/" + fieldName; + HDF5RecordingData* dSet = createDataSet(type, 0, length, EVENT_CHUNK_SIZE, fullPath); + if (!dSet) return false; + timeSeries->metaDataSet.add(dSet); + + CHECK_ERROR(setAttributeStr("openephys:<metadata>/", fullPath, "schema_id")); + CHECK_ERROR(setAttributeStr(name, fullPath, "name")); + CHECK_ERROR(setAttributeStr(description, fullPath, "description")); + CHECK_ERROR(setAttributeStr(identifier, fullPath, "identifier")); + } + return true; + } + + void NWBFile::writeEventMetaData(TimeSeries* timeSeries, const MetaDataEventObject* info, const MetaDataEvent* event) + { + jassert(timeSeries->metaDataSet.size() == event->getMetadataValueCount()); + jassert(info->getEventMetaDataCount() == event->getMetadataValueCount()); + int nMetaData = event->getMetadataValueCount(); + for (int i = 0; i < nMetaData; i++) + { + BaseDataType type = getMetaDataH5Type(info->getEventMetaDataDescriptor(i)->getType(), info->getEventMetaDataDescriptor(i)->getLength()); + timeSeries->metaDataSet[i]->writeDataBlock(1, type, event->getMetaDataValue(i)->getRawValuePointer()); + } + + } void NWBFile::createTextDataSet(String path, String name, String text) { ScopedPointer<HDF5RecordingData> dSet; if (text.isEmpty()) text = " "; //to avoid 0-length strings, which cause errors + BaseDataType type = BaseDataType::STR(text.length()); - dSet = createDataSet(STR, 1, 0, path + "/" + name); + dSet = createDataSet(type, 1, 0, path + "/" + name); if (!dSet) return; - dSet->writeDataBlock(1, STR, text.toUTF8()); + dSet->writeDataBlock(1, type, text.toUTF8()); } + + void NWBFile::createBinaryDataSet(String path, String name, BaseDataType type, int length, void* data) + { + ScopedPointer<HDF5RecordingData> dSet; + if ((length < 1) || !data) return; + + dSet = createDataSet(type, 1, length, 1, path + "/" + name); + if (!dSet) return; + dSet->writeDataBlock(1, type, data); + } + + + //These two methods whould be easy to adapt to support array types for all base types, for now + //length is only used for string types. + NWBFile::BaseDataType NWBFile::getEventH5Type(EventChannel::EventChannelTypes type, int length) + { + switch (type) + { + case EventChannel::INT8_ARRAY: + return BaseDataType::I8; + case EventChannel::UINT8_ARRAY: + return BaseDataType::U8; + case EventChannel::INT16_ARRAY: + return BaseDataType::I16; + case EventChannel::UINT16_ARRAY: + return BaseDataType::U16; + case EventChannel::INT32_ARRAY: + return BaseDataType::I32; + case EventChannel::UINT32_ARRAY: + return BaseDataType::U32; + case EventChannel::INT64_ARRAY: + return BaseDataType::I64; + case EventChannel::UINT64_ARRAY: + return BaseDataType::U64; + case EventChannel::FLOAT_ARRAY: + return BaseDataType::F32; + case EventChannel::DOUBLE_ARRAY: + return BaseDataType::F64; + case EventChannel::TEXT: + return BaseDataType::STR(length); + default: + return BaseDataType::I8; + } + } + NWBFile::BaseDataType NWBFile::getMetaDataH5Type(MetaDataDescriptor::MetaDataTypes type, int length) + { + switch (type) + { + case MetaDataDescriptor::INT8: + return BaseDataType::I8; + case MetaDataDescriptor::UINT8: + return BaseDataType::U8; + case MetaDataDescriptor::INT16: + return BaseDataType::I16; + case MetaDataDescriptor::UINT16: + return BaseDataType::U16; + case MetaDataDescriptor::INT32: + return BaseDataType::I32; + case MetaDataDescriptor::UINT32: + return BaseDataType::U32; + case MetaDataDescriptor::INT64: + return BaseDataType::I64; + case MetaDataDescriptor::UINT64: + return BaseDataType::U64; + case MetaDataDescriptor::FLOAT: + return BaseDataType::F32; + case MetaDataDescriptor::DOUBLE: + return BaseDataType::F64; + case MetaDataDescriptor::CHAR: + return BaseDataType::STR(length); + default: + return BaseDataType::I8; + } + } \ No newline at end of file diff --git a/Source/Plugins/NWBFormat/NWBFormat.h b/Source/Plugins/NWBFormat/NWBFormat.h index b64491c38164d86793950c4763a56389521be413..bf5fb79b26a3caeeeb8741146e2c20b09d8e3627 100644 --- a/Source/Plugins/NWBFormat/NWBFormat.h +++ b/Source/Plugins/NWBFormat/NWBFormat.h @@ -25,22 +25,22 @@ #define NWBFORMAT_H #include <OpenEphysHDF5Lib/HDF5FileFormat.h> +#include <RecordingLib.h> using namespace OpenEphysHDF5; namespace NWBRecording { - - struct NWBRecordingInfo + typedef Array<const DataChannel*> ContinuousGroup; + class TimeSeries { - String sourceName; - float bitVolts; - int processorId; - int sourceId; - int sourceSubIdx; - int nChannels; - int nSamplesPerSpike; - float sampleRate; - String spikeElectrodeName; + public: + ScopedPointer<HDF5RecordingData> baseDataSet; + ScopedPointer<HDF5RecordingData> timestampDataSet; + ScopedPointer<HDF5RecordingData> controlDataSet; //for all but spikes + ScopedPointer<HDF5RecordingData> ttlWordDataSet; //just for ttl events + OwnedArray<HDF5RecordingData> metaDataSet; + String basePath; + uint64 numSamples{ 0 }; }; class NWBFile : public HDF5FileBase @@ -48,13 +48,14 @@ namespace NWBRecording public: NWBFile(String fName, String ver, String idText); //with whatever arguments it's necessary ~NWBFile(); - bool startNewRecording(int recordingNumber, const Array<NWBRecordingInfo>& continuousArray, const Array<NWBRecordingInfo>& electrodeArray); + bool startNewRecording(int recordingNumber, const Array<ContinuousGroup>& continuousArray, + const Array<const EventChannel*>& eventArray, const Array<const SpikeChannel*>& electrodeArray); void stopRecording(); - void writeData(int datasetID, int channel, int nSamples, const int16* data); + void writeData(int datasetID, int channel, int nSamples, const float* data, float bitVolts); void writeTimestamps(int datasetID, int nSamples, const double* data); - void writeSpike(int electrodeId, const int16* data, double timestampSec); - void writeTTLEvent(int channel, int id, uint8 source, double timestampSec); - void writeMessage(const char* msg, double timestampSec); + void writeSpike(int electrodeId, const SpikeChannel* channel, const SpikeEvent* event); + void writeEvent(int eventID, const EventChannel* channel, const Event* event); + void writeTimestampSyncText(uint16 sourceID, int64 timestamp, float sourceSampleRate, String text); String getFileName() override; void setXmlText(const String& xmlText); @@ -62,36 +63,37 @@ namespace NWBRecording int createFileStructure() override; private: - HDF5RecordingData* createRecordingStructures(String basePath, const NWBRecordingInfo& info, String helpText, int chunk_size, String ancestry); + void createTextDataSet(String path, String name, String text); + void createBinaryDataSet(String path, String name, HDF5FileBase::BaseDataType type, int length, void* data); + static HDF5FileBase::BaseDataType getEventH5Type(EventChannel::EventChannelTypes type, int length = 1); + static HDF5FileBase::BaseDataType getMetaDataH5Type(MetaDataDescriptor::MetaDataTypes type, int length = 1); + + bool createTimeSeriesBase(String basePath, String source, String helpText, String description, StringArray ancestry); + bool createExtraInfo(String basePath, String name, String desc, String id, uint16 index, uint16 typeIndex); + HDF5RecordingData* createTimestampDataSet(String basePath, int chunk_size); + void createDataAttributes(String basePath, float conversion, float resolution, String unit); + bool createChannelMetaDataSets(String basePath, const MetaDataInfoObject* info); + bool createEventMetaDataSets(String basePath, TimeSeries* timeSeries, const MetaDataEventObject* info); + + void writeEventMetaData(TimeSeries* timeSeries, const MetaDataEventObject* info, const MetaDataEvent* event); + const String filename; const String GUIVersion; - OwnedArray<HDF5RecordingData> continuousDataSets; - OwnedArray<HDF5RecordingData> continuousDataSetsTS; - StringArray continuousBasePaths; - Array<uint64> numContinuousSamples; - Array<NWBRecordingInfo> continuousInfoStructs; - OwnedArray<HDF5RecordingData> spikeDataSets; - OwnedArray<HDF5RecordingData> spikeDataSetsTS; - StringArray spikeBasePaths; - Array<uint64> numSpikes; - Array<NWBRecordingInfo> spikeInfoStructs; - ScopedPointer<HDF5RecordingData> eventsDataSet; - ScopedPointer<HDF5RecordingData> eventsDataSetTS; - String eventsBasePath; - uint64 numEvents; - ScopedPointer<HDF5RecordingData> messagesDataSet; - ScopedPointer<HDF5RecordingData> messagesDataSetTS; - String messagesBasePath; - uint64 numMessages; - - ScopedPointer<HDF5RecordingData> eventsControlDataSet; + OwnedArray<TimeSeries> continuousDataSets; + OwnedArray<TimeSeries> spikeDataSets; + OwnedArray<TimeSeries> eventDataSets; + ScopedPointer<TimeSeries> syncMsgDataSet; const String* xmlText; const String identifierText; + HeapBlock<float> scaledBuffer; + HeapBlock<int16> intBuffer; + size_t bufferSize; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(NWBFile); }; diff --git a/Source/Plugins/NWBFormat/NWBRecording.cpp b/Source/Plugins/NWBFormat/NWBRecording.cpp index 1533888dd74d348040860997304190c7d58729a0..081a4ff9b487fff244ebcaed34403e83a86dfc4d 100644 --- a/Source/Plugins/NWBFormat/NWBRecording.cpp +++ b/Source/Plugins/NWBFormat/NWBRecording.cpp @@ -26,10 +26,9 @@ using namespace NWBRecording; - NWBRecordEngine::NWBRecordEngine() : bufferSize(MAX_BUFFER_SIZE) + NWBRecordEngine::NWBRecordEngine() { - scaledBuffer.malloc(MAX_BUFFER_SIZE); - intBuffer.malloc(MAX_BUFFER_SIZE); + tsBuffer.malloc(MAX_BUFFER_SIZE); } @@ -55,12 +54,12 @@ datasetIndexes.insertMultiple(0, 0, getNumRecordedChannels()); writeChannelIndexes.insertMultiple(0, 0, getNumRecordedChannels()); - + //Generate the continuous datasets info array, seeking for different combinations of recorded processor and source processor int lastId = 0; for (int proc = 0; proc < recProcs; proc++) { - const RecordProcessorInfo procInfo = getProcessorInfo(proc); + const RecordProcessorInfo& procInfo = getProcessorInfo(proc); int recChans = procInfo.recordedChannels.size(); for (int chan = 0; chan < recChans; chan++) { @@ -69,15 +68,15 @@ const DataChannel* channelInfo = getDataChannel(realChan); int sourceId = channelInfo->getSourceNodeID(); int sourceSubIdx = channelInfo->getSubProcessorIdx(); - int nInfoArrays = continuousInfo.size(); + int nInfoArrays = continuousChannels.size(); bool found = false; for (int i = lastId; i < nInfoArrays; i++) { - if (sourceId == continuousInfo[i].sourceId && sourceSubIdx == continuousInfo[i].sourceSubIdx) + if (sourceId == continuousChannels.getReference(i)[0]->getSourceNodeID() && sourceSubIdx == continuousChannels.getReference(i)[0]->getSubProcessorIdx()) { //A dataset for the current processor from the current source is already present - writeChannelIndexes.set(recordedChan, continuousInfo[i].nChannels); - continuousInfo.getReference(i).nChannels += 1; + writeChannelIndexes.set(recordedChan, continuousChannels.getReference(i).size()); + continuousChannels.getReference(i).add(getDataChannel(realChan)); datasetIndexes.set(recordedChan, i); found = true; break; @@ -85,31 +84,26 @@ } if (!found) //a new dataset must be created { - NWBRecordingInfo recInfo; - recInfo.bitVolts = channelInfo->getBitVolts(); - recInfo.nChannels = 1; - recInfo.processorId = procInfo.processorId; - recInfo.sampleRate = channelInfo->getSampleRate(); - recInfo.sourceName = "processor: " + channelInfo->getSourceName() + " (" + String(sourceId) + "." + String(sourceSubIdx) + ")"; - recInfo.sourceId = sourceId; - recInfo.sourceSubIdx = sourceSubIdx; - recInfo.spikeElectrodeName = " "; - recInfo.nSamplesPerSpike = 0; - continuousInfo.add(recInfo); + ContinuousGroup newGroup; + newGroup.add(getDataChannel(realChan)); + continuousChannels.add(newGroup); datasetIndexes.set(recordedChan, nInfoArrays); writeChannelIndexes.set(recordedChan, 0); } } - lastId = continuousInfo.size(); + lastId = continuousChannels.size(); } + int nEvents = getNumRecordedEvents(); + for (int i = 0; i < nEvents; i++) + eventChannels.add(getEventChannel(i)); //open the file - recordFile->open(getNumRecordedChannels() + continuousInfo.size()); //total channels + timestamp arrays, to create a big enough buffer + recordFile->open(getNumRecordedChannels() + continuousChannels.size() + eventChannels.size() + spikeChannels.size()); //total channels + timestamp arrays, to create a big enough buffer //create the recording - recordFile->startNewRecording(recordingNumber, continuousInfo, spikeInfo); - + recordFile->startNewRecording(recordingNumber, continuousChannels, eventChannels, spikeChannels); + } @@ -126,38 +120,24 @@ { resetChannels(true); } + void NWBRecordEngine::resetChannels(bool resetSpikes) { - //Called at various points, should reset everything. - - if (resetSpikes) //only clear this at actual reset, not when closing files. - spikeInfo.clear(); - continuousInfo.clear(); + if (resetSpikes) + spikeChannels.clear(); + eventChannels.clear(); + continuousChannels.clear(); datasetIndexes.clear(); writeChannelIndexes.clear(); - scaledBuffer.malloc(MAX_BUFFER_SIZE); - intBuffer.malloc(MAX_BUFFER_SIZE); tsBuffer.malloc(MAX_BUFFER_SIZE); bufferSize = MAX_BUFFER_SIZE; } void NWBRecordEngine::writeData(int writeChannel, int realChannel, const float* buffer, int size) { - if (size > bufferSize) //Shouldn't happen, and if it happens it'll be slow, but better this than crashing. Will be reset on file close and reset. - { - std::cerr << "Write buffer overrun, resizing to" << size << std::endl; - bufferSize = size; - scaledBuffer.malloc(size); - intBuffer.malloc(size); - tsBuffer.malloc(size); - } - double multFactor = 1 / (float(0x7fff) * getDataChannel(realChannel)->getBitVolts()); - FloatVectorOperations::copyWithMultiply(scaledBuffer.getData(), buffer, multFactor, size); - AudioDataConverters::convertFloatToInt16LE(scaledBuffer.getData(), intBuffer.getData(), size); - - recordFile->writeData(datasetIndexes[writeChannel], writeChannelIndexes[writeChannel], size, intBuffer.getData()); + recordFile->writeData(datasetIndexes[writeChannel], writeChannelIndexes[writeChannel], size, buffer, getDataChannel(realChannel)->getBitVolts()); /* All channels in a dataset have the same number of samples and share timestamps. But since this method is called asynchronously, the timestamps might not be in sync during acquisition, so we chose a channel and write the @@ -178,51 +158,27 @@ void NWBRecordEngine::writeEvent(int eventIndex, const MidiMessage& event) { - if (Event::getEventType(event) == EventChannel::TTL) - { - TTLEventPtr ttl = TTLEvent::deserializeFromMessage(event, getEventChannel(eventIndex)); - recordFile->writeTTLEvent(ttl->getChannel(), ttl->getState() ? 1 : 0, ttl->getSourceID() , double(ttl->getTimestamp()) / getEventChannel(eventIndex)->getSampleRate()); - } - else if (Event::getEventType(event) == EventChannel::TEXT) - { - TextEventPtr text = TextEvent::deserializeFromMessage(event, getEventChannel(eventIndex)); - recordFile->writeMessage(text->getText().toUTF8(), double(text->getTimestamp()) / getEventChannel(eventIndex)->getSampleRate()); - } + const EventChannel* channel = getEventChannel(eventIndex); + EventPtr eventStruct = Event::deserializeFromMessage(event, channel); + + recordFile->writeEvent(eventIndex, channel, eventStruct); } void NWBRecordEngine::writeTimestampSyncText(uint16 sourceID, uint16 sourceIdx, int64 timestamp, float sourceSampleRate, String text) { - recordFile->writeMessage(text.toUTF8(), double(timestamp) / sourceSampleRate); + recordFile->writeTimestampSyncText(sourceID, timestamp, sourceSampleRate, text); } void NWBRecordEngine::addSpikeElectrode(int index,const SpikeChannel* elec) { - //Called during chain update by a processor that records spikes. Allows the RecordEngine to gather data about the electrode, which will usually - //be used in openfiles to be sent to startNewRecording so the electrode info is stored into the file. - NWBRecordingInfo info; - info.bitVolts = elec->getChannelBitVolts(0); - info.nChannels = elec->getNumChannels(); - info.nSamplesPerSpike = elec->getTotalSamples(); - info.processorId = elec->getCurrentNodeID(); - info.sourceId = elec->getSourceNodeID(); - info.sourceSubIdx = elec->getSubProcessorIdx(); - info.sampleRate = elec->getSampleRate(); - info.sourceName = elec->getSourceName() + " (" + String(info.sourceId) + "." + String(info.sourceSubIdx) + ")"; - info.spikeElectrodeName = elec->getName(); - spikeInfo.add(info); + spikeChannels.add(elec); } + void NWBRecordEngine::writeSpike(int electrodeIndex, const SpikeEvent* spike) { const SpikeChannel* channel = getSpikeChannel(electrodeIndex); - int totalSamples = channel->getTotalSamples() * channel->getNumChannels(); - double timestamp = double(spike->getTimestamp()) / channel->getSampleRate(); - - double multFactor = 1 / (float(0x7fff) * channel->getChannelBitVolts(0)); - FloatVectorOperations::copyWithMultiply(scaledBuffer.getData(), spike->getDataPointer(), multFactor, totalSamples); - AudioDataConverters::convertFloatToInt16LE(scaledBuffer.getData(), intBuffer.getData(), totalSamples); - - recordFile->writeSpike(electrodeIndex, intBuffer.getData(), timestamp); + recordFile->writeSpike(electrodeIndex, channel, spike); } RecordEngineManager* NWBRecordEngine::getEngineManager() diff --git a/Source/Plugins/NWBFormat/NWBRecording.h b/Source/Plugins/NWBFormat/NWBRecording.h index 58864e061acb9a09ff3a72b04e2816a4d36cf02a..e521a34c35dbb2a4627a318bce94c7bd6d33d534 100644 --- a/Source/Plugins/NWBFormat/NWBRecording.h +++ b/Source/Plugins/NWBFormat/NWBRecording.h @@ -49,17 +49,16 @@ private: void resetChannels(bool resetSpikes); - Array<NWBRecordingInfo> continuousInfo; - Array<NWBRecordingInfo> spikeInfo; - ScopedPointer<NWBFile> recordFile; Array<int> datasetIndexes; Array<int> writeChannelIndexes; - HeapBlock<float> scaledBuffer; - HeapBlock<int16> intBuffer; + Array<ContinuousGroup> continuousChannels; + Array<const EventChannel*> eventChannels; + Array<const SpikeChannel*> spikeChannels; + HeapBlock<double> tsBuffer; - int bufferSize; + size_t bufferSize; String identifierText; diff --git a/Source/Processors/Channel/InfoObjects.cpp b/Source/Processors/Channel/InfoObjects.cpp index fe6a1302b65a44c3c0b2c14daf136de8df47930d..c49d4b8923f15e113ef2c3cd05f69599bdd73f2c 100644 --- a/Source/Processors/Channel/InfoObjects.cpp +++ b/Source/Processors/Channel/InfoObjects.cpp @@ -42,6 +42,16 @@ unsigned int NodeInfoBase::getCurrentNodeID() const return m_nodeID; } +String NodeInfoBase::getCurrentNodeType() const +{ + return m_currentNodeType; +} + +String NodeInfoBase::getCurrentNodeName() const +{ + return m_currentNodeName; +} + //History Object HistoryObject::~HistoryObject() {} @@ -64,7 +74,8 @@ SourceProcessorInfo::SourceProcessorInfo(const GenericProcessor* source, uint16 : m_sourceNodeID(source->getNodeId()), m_sourceSubNodeIndex(subproc), m_sourceType(source->getName()), - m_sourceName(source->getName()) //TODO: fix those two when we have the ability to rename processors + m_sourceName(source->getName()), //TODO: fix those two when we have the ability to rename processors + m_sourceSubProcessorCount(source->getNumSubProcessors()) { } @@ -91,6 +102,11 @@ String SourceProcessorInfo::getSourceName() const return m_sourceName; } +uint16 SourceProcessorInfo::getSourceSubprocessorCount() const +{ + return m_sourceSubProcessorCount; +} + //NamedInfoObject NamedInfoObject::~NamedInfoObject() {} @@ -110,7 +126,7 @@ void NamedInfoObject::setIdentifier(String identifier) m_identifier = identifier; } -String NamedInfoObject::getDescriptor() const +String NamedInfoObject::getIdentifier() const { return m_identifier; } diff --git a/Source/Processors/Channel/InfoObjects.h b/Source/Processors/Channel/InfoObjects.h index a9c44fd9f0a4dd3cf3e6cdcc9bc7bc34ccb5691f..a3e1679b52941ab288494aa98d7b47c58ca6248b 100644 --- a/Source/Processors/Channel/InfoObjects.h +++ b/Source/Processors/Channel/InfoObjects.h @@ -50,10 +50,16 @@ public: virtual ~NodeInfoBase(); /** Gets the ID of the processor which currently owns this copy of the info object */ unsigned int getCurrentNodeID() const; + /** Gets the type of the processor which currently owns this copy of the info object */ + String getCurrentNodeType() const; + /** Gets the name of the processor which currently owns this copy of the info object */ + String getCurrentNodeName() const; protected: NodeInfoBase() = delete; NodeInfoBase(uint16 id); uint16 m_nodeID{ 0 }; + String m_currentNodeType; + String m_currentNodeName; }; /** This class allows creating a string with an historic of all the data a node has gone through */ @@ -92,13 +98,17 @@ public: /** Gets the name of the processor which created this object */ String getSourceName() const; + /** Gets the number of subprocessors the source processor has. + Useful to determine if a processor has multiple subprocessors and label things accordingly*/ + uint16 getSourceSubprocessorCount() const; + private: SourceProcessorInfo() = delete; const uint16 m_sourceNodeID; const uint16 m_sourceSubNodeIndex; const String m_sourceType; const String m_sourceName; - + const uint16 m_sourceSubProcessorCount; }; class PLUGIN_API NamedInfoObject @@ -120,7 +130,7 @@ public: /** Sets a machine-readable data identifier (eg.: sourcedata.continuous ) */ void setIdentifier(String identifier); - String getDescriptor() const; + String getIdentifier() const; protected: NamedInfoObject(); virtual void setDefaultNameAndDescription() = 0; diff --git a/Source/Processors/Channel/MetaData.cpp b/Source/Processors/Channel/MetaData.cpp index 5945ebd9fd1092fc4190b1f8685ddcfa77c39c1f..5dca331398785c7bcdfb1643bddcd620919b5858 100644 --- a/Source/Processors/Channel/MetaData.cpp +++ b/Source/Processors/Channel/MetaData.cpp @@ -39,6 +39,8 @@ bool checkMetaDataType(MetaDataDescriptor::MetaDataTypes baseType) case MetaDataDescriptor::UINT32: return std::is_same<uint32, T>::value; case MetaDataDescriptor::INT64: return std::is_same<int64, T>::value; case MetaDataDescriptor::UINT64: return std::is_same<uint64, T>::value; + case MetaDataDescriptor::FLOAT: return std::is_same<float, T>::value; + case MetaDataDescriptor::DOUBLE: return std::is_same<double, T>::value; default: return false; } } @@ -162,7 +164,7 @@ size_t MetaDataValue::getDataSize() const void MetaDataValue::allocSpace() { - m_data.malloc(m_size); + m_data.calloc(m_size); } MetaDataValue::MetaDataValue(const MetaDataValue& v) @@ -239,6 +241,19 @@ void MetaDataValue::getValue(T* data) const memcpy(data, m_data.getData(), m_size); } +//Using this version of the method in normal processor operation is not recommended +//However it is useful for format-agnostic processes. +template<> +void MetaDataValue::getValue<void>(void* data) const +{ + memcpy(data, m_data.getData(), m_size); +} + +const void* MetaDataValue::getRawValuePointer() const +{ + return m_data.getData(); +} + template <typename T> void MetaDataValue::setValue(const Array<T>& data) { @@ -335,7 +350,10 @@ void MetaDataEventObject::addEventMetaData(MetaDataDescriptor* desc) return; } m_eventMetaDataDescriptorArray.add(desc); - m_totalSize += desc->getDataSize(); + size_t size = desc->getDataSize(); + m_totalSize += size; + if (m_maxSize < size) + m_maxSize = size; } void MetaDataEventObject::addEventMetaData(const MetaDataDescriptor& desc) @@ -347,7 +365,10 @@ void MetaDataEventObject::addEventMetaData(const MetaDataDescriptor& desc) return; } m_eventMetaDataDescriptorArray.add(new MetaDataDescriptor(desc)); - m_totalSize += desc.getDataSize(); + size_t size = desc.getDataSize(); + m_totalSize += size; + if (m_maxSize < size) + m_maxSize = size; } size_t MetaDataEventObject::getTotalEventMetaDataSize() const @@ -360,7 +381,7 @@ const MetaDataDescriptor* MetaDataEventObject::getEventMetaDataDescriptor(int in return m_eventMetaDataDescriptorArray[index]; } -const int MetaDataEventObject::getEventMetaDataCount() const +int MetaDataEventObject::getEventMetaDataCount() const { return m_eventMetaDataDescriptorArray.size(); } @@ -377,16 +398,31 @@ int MetaDataEventObject::findEventMetaData(MetaDataDescriptor::MetaDataTypes typ return -1; } +size_t MetaDataEventObject::getMaxEventMetaDataSize() const +{ + return m_maxSize; +} + //MetaDataEvent MetaDataEvent::MetaDataEvent() {} MetaDataEvent::~MetaDataEvent() {} +int MetaDataEvent::getMetadataValueCount() const +{ + return m_metaDataValues.size(); +} + +const MetaDataValue* MetaDataEvent::getMetaDataValue(int index) const +{ + return m_metaDataValues[index]; +} + void MetaDataEvent::serializeMetaData(void* dstBuffer) const { int metaDataSize = m_metaDataValues.size(); char* buffer = static_cast<char*>(dstBuffer); - int ptrIndex = 0; + size_t ptrIndex = 0; for (int i = 0; i < metaDataSize; i++) { @@ -405,7 +441,7 @@ bool MetaDataEvent::deserializeMetaData(const MetaDataEventObject* info, const v { const MetaDataDescriptor* desc = info->getEventMetaDataDescriptor(i); size_t dataSize = desc->getDataSize(); - if ((memIndex + dataSize) < size) return false; //check for buffer boundaries + if ((memIndex + dataSize) > size) return false; //check for buffer boundaries metaData.add(new MetaDataValue(*desc, (static_cast<const char*>(srcBuffer) + memIndex))); memIndex += dataSize; @@ -495,6 +531,8 @@ template PLUGIN_API void MetaDataValue::getValue<uint64>(Array<uint64>&) const; template PLUGIN_API void MetaDataValue::getValue<float>(Array<float>&) const; template PLUGIN_API void MetaDataValue::getValue<double>(Array<double>&) const; +template PLUGIN_API void MetaDataValue::getValue<void>(void*) const; + //Helper function to compare identifier strings bool compareIdentifierStrings(const String& identifier, const String& compareWith) { diff --git a/Source/Processors/Channel/MetaData.h b/Source/Processors/Channel/MetaData.h index 0c799cd7653ba1648551444c0c8ee3f3de8b0a52..7ecbb0b164667dd86e86ef57eebf310a3cea54f8 100644 --- a/Source/Processors/Channel/MetaData.h +++ b/Source/Processors/Channel/MetaData.h @@ -149,6 +149,11 @@ public: template <typename T> void getValue(Array<T>& data) const; + /** It is generally not advised to use this method, which can, however, be of use in performance critical modules. + It is usually preferable to use the strongly typed copy getter methods, when speed is not an issue. + Keep in mind that any pointer returned by this will become invalid after the block execution.*/ + const void* getRawValuePointer() const; + private: MetaDataValue() = delete; void setValue(const void* data); @@ -207,11 +212,14 @@ public: const MetaDataDescriptor* getEventMetaDataDescriptor(int index) const; int findEventMetaData(MetaDataDescriptor::MetaDataTypes type, unsigned int length, String identifier = String::empty) const; size_t getTotalEventMetaDataSize() const; - const int getEventMetaDataCount() const; + int getEventMetaDataCount() const; + //gets the largest metadata size, which can be useful to reserve buffers in advance + size_t getMaxEventMetaDataSize() const; protected: MetaDataDescriptorArray m_eventMetaDataDescriptorArray; MetaDataEventObject(); size_t m_totalSize{ 0 }; + size_t m_maxSize{ 0 }; }; //And the base from which event objects can hold their metadata before serializing @@ -219,6 +227,8 @@ class PLUGIN_API MetaDataEvent { public: virtual ~MetaDataEvent(); + int getMetadataValueCount() const; + const MetaDataValue* getMetaDataValue(int index) const; protected: void serializeMetaData(void* dstBuffer) const; bool deserializeMetaData(const MetaDataEventObject* info, const void* srcBuffer, int size); diff --git a/Source/Processors/GenericProcessor/GenericProcessor.cpp b/Source/Processors/GenericProcessor/GenericProcessor.cpp index b82fc42251808729dbce6bfde59354d385bd4b3e..b0f11fd1ea3f1ac4f200b4f4cdad0a055f86f7ec 100755 --- a/Source/Processors/GenericProcessor/GenericProcessor.cpp +++ b/Source/Processors/GenericProcessor/GenericProcessor.cpp @@ -333,19 +333,20 @@ void GenericProcessor::update() dataChannelArray.add (ch); } - for (int i = 0; i < sourceNode->eventChannelArray.size(); ++i) - { - EventChannel* sourceChan = sourceNode->eventChannelArray[i]; - EventChannel* ch = new EventChannel (*sourceChan); + for (int i = 0; i < sourceNode->eventChannelArray.size(); ++i) + { + EventChannel* sourceChan = sourceNode->eventChannelArray[i]; + EventChannel* ch = new EventChannel(*sourceChan); ch->eventMetaDataLock = true; - eventChannelArray.add (ch); - } + eventChannelArray.add(ch); + } for (int i = 0; i < sourceNode->spikeChannelArray.size(); ++i) { SpikeChannel* sourceChan = sourceNode->spikeChannelArray[i]; SpikeChannel* ch = new SpikeChannel(*sourceChan); ch->eventMetaDataLock = true; spikeChannelArray.add(ch); + } for (int i = 0; i < sourceNode->configurationObjectArray.size(); ++i) { @@ -409,7 +410,11 @@ void GenericProcessor::updateChannelIndexes(bool updateNodeID) { DataChannel* channel = dataChannelArray[i]; if (updateNodeID) + { channel->m_nodeID = nodeId; + channel->m_currentNodeName = getName(); + channel->m_currentNodeType = getName(); //Fix when the ability to name individual processors is implemented + } uint32 sourceID = getProcessorFullId(channel->getSourceNodeID(), channel->getSubProcessorIdx()); dataChannelMap[sourceID][channel->getSourceIndex()] = i; } @@ -417,7 +422,11 @@ void GenericProcessor::updateChannelIndexes(bool updateNodeID) { EventChannel* channel = eventChannelArray[i]; if (updateNodeID) + { channel->m_nodeID = nodeId; + channel->m_currentNodeName = getName(); + channel->m_currentNodeType = getName(); //Fix when the ability to name individual processors is implemented + } uint32 sourceID = getProcessorFullId(channel->getSourceNodeID(), channel->getSubProcessorIdx()); eventChannelMap[sourceID][channel->getSourceIndex()] = i; } @@ -425,7 +434,11 @@ void GenericProcessor::updateChannelIndexes(bool updateNodeID) { SpikeChannel* channel = spikeChannelArray[i]; if (updateNodeID) + { channel->m_nodeID = nodeId; + channel->m_currentNodeName = getName(); + channel->m_currentNodeType = getName(); //Fix when the ability to name individual processors is implemented + } uint32 sourceID = getProcessorFullId(channel->getSourceNodeID(), channel->getSubProcessorIdx()); spikeChannelMap[sourceID][channel->getSourceIndex()] = i; } diff --git a/Source/Processors/RecordNode/EngineConfigWindow.cpp b/Source/Processors/RecordNode/EngineConfigWindow.cpp index 3882d27532e496d14d0800a1f28c45e01a7db07b..6fb06d7c12ae92d68ca98f87c3f2c4e7285efa9d 100644 --- a/Source/Processors/RecordNode/EngineConfigWindow.cpp +++ b/Source/Processors/RecordNode/EngineConfigWindow.cpp @@ -30,7 +30,7 @@ EngineParameterComponent::EngineParameterComponent(EngineParameter& param) { ToggleButton* but = new ToggleButton(); but->setToggleState(param.boolParam.value,dontSendNotification); - but->setBounds(120,0,40,20); + but->setBounds(120,0,100,20); addAndMakeVisible(but); control = but; } diff --git a/Source/Processors/RecordNode/EventQueue.h b/Source/Processors/RecordNode/EventQueue.h index a83e6d45b0abe37fe9e8d0ba65909d6e56289df4..771bbc67ea22874692bd876064edea2550f88fe1 100644 --- a/Source/Processors/RecordNode/EventQueue.h +++ b/Source/Processors/RecordNode/EventQueue.h @@ -130,7 +130,8 @@ private: JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(EventQueue); }; - +//NOTE: Events are sent as midimessages while spikes as spike objects due to the difference on how they are passed to the record node. +//Once the probe system is implemented, this will be normalized typedef EventQueue<MidiMessage> EventMsgQueue; typedef EventQueue<SpikeEvent> SpikeMsgQueue; typedef ReferenceCountedObjectPtr<AsyncEventMessage<MidiMessage>> EventMessagePtr; diff --git a/Source/Processors/RecordNode/OriginalRecording.cpp b/Source/Processors/RecordNode/OriginalRecording.cpp index fc5b8ac333d3edfe737f4de9e8d293803b0ac3e7..f3163358b07af7d90a3cea1e010a6430ca42b050 100644 --- a/Source/Processors/RecordNode/OriginalRecording.cpp +++ b/Source/Processors/RecordNode/OriginalRecording.cpp @@ -67,21 +67,6 @@ String OriginalRecording::getEngineID() const return "OPENEPHYS"; } -void OriginalRecording::registerProcessor(const GenericProcessor* proc) -{ - procIndex = 0; -} - -void OriginalRecording::addDataChannel(int index, const DataChannel* chan) -{ - //Just populate the file array with null so we can address it by index afterwards - fileArray.add(nullptr); - blockIndex.add(0); - samplesSinceLastTimestamp.add(0); - originalChannelIndexes.add(procIndex); - procIndex++; -} - void OriginalRecording::addSpikeElectrode(int index, const SpikeChannel* elec) { spikeFileArray.add(nullptr); @@ -109,16 +94,15 @@ void OriginalRecording::openFiles(File rootFolder, int experimentNumber, int rec openFile(rootFolder,getEventChannel(0), 0); openMessageFile(rootFolder); - for (int i = 0; i < fileArray.size(); i++) - { - if (getDataChannel(i)->getRecordState()) - { - openFile(rootFolder,getDataChannel(i),i); - blockIndex.set(i,0); - samplesSinceLastTimestamp.set(i,0); - } + int nChannels = getNumRecordedChannels(); - } + for (int i = 0; i < nChannels; i++) + { + const DataChannel* ch = getDataChannel(getRealChannel(i)); + openFile(rootFolder, ch, getRealChannel(i)); + blockIndex.add(0); + samplesSinceLastTimestamp.add(0); + } for (int i = 0; i < spikeFileArray.size(); i++) { openSpikeFile(rootFolder,getSpikeChannel(i),i); @@ -184,7 +168,7 @@ void OriginalRecording::openFile(File rootFolder, const InfoObjectCommon* ch, in eventFile = chFile; else { - fileArray.set(channelIndex,chFile); + fileArray.add(chFile); if (ch->getCurrentNodeID() != lastProcId) { lastProcId = ch->getCurrentNodeID(); @@ -465,10 +449,7 @@ void OriginalRecording::writeData(int writeChannel, int realChannel, const float { int samplesWritten = 0; - //int sourceNodeId = getChannel(realChannel)->sourceNodeId; - - //TODO: optimize. Now we use realchannel, we should optimize the whole thing to only use recorded channels - samplesSinceLastTimestamp.set(realChannel, 0); + samplesSinceLastTimestamp.set(writeChannel, 0); int nSamples = size; @@ -476,7 +457,7 @@ void OriginalRecording::writeData(int writeChannel, int realChannel, const float { int numSamplesToWrite = nSamples - samplesWritten; - if (blockIndex[realChannel] + numSamplesToWrite < BLOCK_LENGTH) // we still have space in this block + if (blockIndex[writeChannel] + numSamplesToWrite < BLOCK_LENGTH) // we still have space in this block { // write buffer to disk! @@ -485,15 +466,15 @@ void OriginalRecording::writeData(int writeChannel, int realChannel, const float writeChannel); //timestamp += numSamplesToWrite; - samplesSinceLastTimestamp.set(realChannel, samplesSinceLastTimestamp[realChannel] + numSamplesToWrite); - blockIndex.set(realChannel, blockIndex[realChannel] + numSamplesToWrite); + samplesSinceLastTimestamp.set(writeChannel, samplesSinceLastTimestamp[writeChannel] + numSamplesToWrite); + blockIndex.set(writeChannel, blockIndex[writeChannel] + numSamplesToWrite); samplesWritten += numSamplesToWrite; } else // there's not enough space left in this block for all remaining samples { - numSamplesToWrite = BLOCK_LENGTH - blockIndex[realChannel]; + numSamplesToWrite = BLOCK_LENGTH - blockIndex[writeChannel]; // write buffer to disk! writeContinuousBuffer(buffer + samplesWritten, @@ -503,8 +484,8 @@ void OriginalRecording::writeData(int writeChannel, int realChannel, const float // update our variables samplesWritten += numSamplesToWrite; //timestamp += numSamplesToWrite; - samplesSinceLastTimestamp.set(realChannel, samplesSinceLastTimestamp[realChannel] + numSamplesToWrite); - blockIndex.set(realChannel,0); // back to the beginning of the block + samplesSinceLastTimestamp.set(writeChannel, samplesSinceLastTimestamp[writeChannel] + numSamplesToWrite); + blockIndex.set(writeChannel, 0); // back to the beginning of the block } } @@ -513,13 +494,12 @@ void OriginalRecording::writeData(int writeChannel, int realChannel, const float 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) + if (fileArray[writeChannel] == nullptr) return; // scale the data back into the range of int16 - float scaleFactor = float(0x7fff) * getDataChannel(channel)->getBitVolts(); + float scaleFactor = float(0x7fff) * getDataChannel(getRealChannel(writeChannel))->getBitVolts(); for (int n = 0; n < nSamples; n++) { @@ -527,9 +507,9 @@ void OriginalRecording::writeContinuousBuffer(const float* data, int nSamples, i } AudioDataConverters::convertFloatToInt16BE(continuousDataFloatBuffer, continuousDataIntegerBuffer, nSamples); - if (blockIndex[channel] == 0) + if (blockIndex[writeChannel] == 0) { - writeTimestampAndSampleCount(fileArray[channel], writeChannel); + writeTimestampAndSampleCount(fileArray[writeChannel], writeChannel); } diskWriteLock.enter(); @@ -537,7 +517,7 @@ void OriginalRecording::writeContinuousBuffer(const float* data, int nSamples, i size_t count = fwrite(continuousDataIntegerBuffer, // ptr 2, // size of each element nSamples, // count - fileArray[channel]); // ptr to FILE object + fileArray[writeChannel]); // ptr to FILE object //std::cout << channel << " : " << nSamples << " : " << count << std::endl; @@ -546,9 +526,9 @@ void OriginalRecording::writeContinuousBuffer(const float* data, int nSamples, i diskWriteLock.exit(); - if (blockIndex[channel] + nSamples == BLOCK_LENGTH) + if (blockIndex[writeChannel] + nSamples == BLOCK_LENGTH) { - writeRecordMarker(fileArray[channel]); + writeRecordMarker(fileArray[writeChannel]); } } @@ -605,13 +585,13 @@ void OriginalRecording::closeFiles() writeContinuousBuffer(zeroBuffer.getReadPointer(0), BLOCK_LENGTH - blockIndex[i], i); diskWriteLock.enter(); fclose(fileArray[i]); - fileArray.set(i,nullptr); diskWriteLock.exit(); } } - - blockIndex.set(i,0); } + fileArray.clear(); + blockIndex.clear(); + samplesSinceLastTimestamp.clear(); for (int i = 0; i < spikeFileArray.size(); i++) { if (spikeFileArray[i] != nullptr) diff --git a/Source/Processors/RecordNode/OriginalRecording.h b/Source/Processors/RecordNode/OriginalRecording.h index e417a1b708a9010918753dd7f09917b679205dc5..3730992ec2d91d1e0aef513f8eedc5b6fb7849aa 100644 --- a/Source/Processors/RecordNode/OriginalRecording.h +++ b/Source/Processors/RecordNode/OriginalRecording.h @@ -50,8 +50,6 @@ public: void closeFiles() override; void writeData(int writeChannel, int realChannel, const float* buffer, int size) override; void writeEvent(int eventIndex, const MidiMessage& event) override; - void registerProcessor(const GenericProcessor* proc) override; - void addDataChannel(int index, const DataChannel* chan) override; void resetChannels() override; void addSpikeElectrode(int index, const SpikeChannel* elec) override; void writeSpike(int electrodeIndex, const SpikeEvent* spike) override; diff --git a/Source/Processors/RecordNode/RecordEngine.cpp b/Source/Processors/RecordNode/RecordEngine.cpp index ed88af8b1facf06141a6a8c4058fb4e900002340..6aaef305fd4b5b3db8118d256a01200f92536645 100644 --- a/Source/Processors/RecordNode/RecordEngine.cpp +++ b/Source/Processors/RecordNode/RecordEngine.cpp @@ -103,6 +103,11 @@ int RecordEngine::getNumRecordedChannels() const return channelMap.size(); } +int RecordEngine::getNumRecordedEvents() const +{ + return AccessClass::getProcessorGraph()->getRecordNode()->getTotalEventChannels(); +} + void RecordEngine::registerSpikeSource (const GenericProcessor* processor) {} int RecordEngine::getNumRecordedProcessors() const diff --git a/Source/Processors/RecordNode/RecordEngine.h b/Source/Processors/RecordNode/RecordEngine.h index a87fa260f35704685923379b0cc6df697c8c1a4e..f6864ec06021f644be3c7c78aaa81274926f5f7b 100644 --- a/Source/Processors/RecordNode/RecordEngine.h +++ b/Source/Processors/RecordNode/RecordEngine.h @@ -156,6 +156,7 @@ protected: /** Gets the specified channel from the channel array stored in RecordNode */ const DataChannel* getDataChannel (int index) const; + /** Gets the specified event channel from the channel array stored in RecordNode */ const EventChannel* getEventChannel(int index) const; /** Gets the specified channel group info structure from the array stored in RecordNode */ @@ -173,6 +174,12 @@ protected: /** Gets the number of recorded channels */ int getNumRecordedChannels() const; + /** Gets the number of recorded event channels + (right now all channels are recorded) */ + int getNumRecordedEvents() const; + + /** TODO: to fill when the probe system is implemented*/ + //int getNumRecordedSpikes() const; /** Gets the number of processors being recorded */ diff --git a/Source/Processors/RecordNode/RecordNode.cpp b/Source/Processors/RecordNode/RecordNode.cpp index 592f1cf8a782562a6ac1426f579a067c9085b21e..b0c04492bcc98b29eef9d9a1066a9f5d0201d578 100755 --- a/Source/Processors/RecordNode/RecordNode.cpp +++ b/Source/Processors/RecordNode/RecordNode.cpp @@ -159,8 +159,10 @@ void RecordNode::addInputChannel(const GenericProcessor* sourceNode, int chan) for (int n = 0; n < sourceNode->getTotalEventChannels(); n++) { - - eventChannelArray.add(new EventChannel(*sourceNode->getEventChannel(n))); + const EventChannel* orig = sourceNode->getEventChannel(n); + //only add to the record node the events originating from this processor, to avoid duplicates + if (orig->getSourceNodeID() == sourceNode->getNodeId()) + eventChannelArray.add(new EventChannel(*orig)); } diff --git a/open-ephys.jucer b/open-ephys.jucer index 3c996776dbf473a246555534a308e89edb04460b..80a6aa56695f4153c7e2d28fcf20b8cf47f8d394 100644 --- a/open-ephys.jucer +++ b/open-ephys.jucer @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="UTF-8"?> -<JUCERPROJECT id="ynSYIrr" name="open-ephys" projectType="guiapp" version="0.4.2" +<JUCERPROJECT id="ynSYIrr" name="open-ephys" projectType="guiapp" version="0.4.2.1" juceLinkage="amalg_multi" buildVST="1" buildRTAS="0" buildAU="1" pluginName="Juce Project" pluginDesc="Juce Project" pluginManufacturer="yourcompany" pluginManufacturerCode="Manu" pluginCode="Plug" pluginChannelConfigs="{1, 1}, {2, 2}" @@ -11,7 +11,7 @@ companyName="Open Ephys" userNotes="The Open Ephys GUI was designed to provide a fast and flexible interface for acquiring, processing, and visualizing data from extracellular electrodes. See open-ephys.org for more information." includeBinaryInAppConfig="1"> <EXPORTFORMATS> - <LINUX_MAKE targetFolder="Builds/Linux" vstFolder="" extraLinkerFlags="-ldl -lXext -lGLU -rdynamic -fPIC" + <LINUX_MAKE targetFolder="Builds/Linux" vstFolder="" extraLinkerFlags="-ldl -lXext -lGLU -rdynamic -fPIC -Wl,-rpath,'$$ORIGIN'" extraCompilerFlags="-rdynamic -fvisibility=hidden" extraDefs="" smallIcon="nFMauU" bigIcon="nFMauU" cppLanguageStandard="-std=c++11"> <CONFIGURATIONS>