From e219ba9ebc326781cdcd7683f5a3d686fbc048f5 Mon Sep 17 00:00:00 2001 From: myd7349 Date: Tue, 30 Jul 2024 23:10:55 +0800 Subject: [PATCH] Update --- .gitattributes | 1 + Binding/generate.ps1 | 9 +- Examples/LSLVer/LSLVer.csproj | 2 +- Examples/LSLVer/Program.cs | 7 +- Source/SharpLSL/ChannelFormat.cs | 96 +++ Source/SharpLSL/ContinuousResolver.cs | 91 ++ Source/SharpLSL/Interop/Inlet.Interop.cs | 130 +++ Source/SharpLSL/Interop/Outlet.Interop.cs | 326 +++++++ Source/SharpLSL/Interop/Resolver.Interop.cs | 26 + Source/SharpLSL/Interop/StreamInfo.Interop.cs | 35 + Source/SharpLSL/Interop/XML.Interop.cs | 58 ++ .../SharpLSL/Interop/{Xml.g.cs => XML.g.cs} | 0 Source/SharpLSL/LSL.cs | 205 +++++ Source/SharpLSL/LSLException.cs | 51 ++ Source/SharpLSL/LSLInternalException.cs | 50 ++ Source/SharpLSL/LSLObject.cs | 114 +++ Source/SharpLSL/Lsl.cs | 30 - Source/SharpLSL/LslChannelFormat.cs | 16 - Source/SharpLSL/LslException.cs | 21 - Source/SharpLSL/LslObject.cs | 19 - Source/SharpLSL/LslProcessingOptions.cs | 17 - Source/SharpLSL/LslStreamInfo.cs | 82 -- Source/SharpLSL/LslTransportOptions.cs | 11 - Source/SharpLSL/PostProcessingOptions.cs | 68 ++ Source/SharpLSL/StreamInfo.cs | 531 ++++++++++++ Source/SharpLSL/StreamInlet.cs | 816 ++++++++++++++++++ Source/SharpLSL/StreamLostException.cs | 52 ++ Source/SharpLSL/StreamOutlet.cs | 461 ++++++++++ Source/SharpLSL/TransportOptions.cs | 31 + Source/SharpLSL/XMLElement.cs | 578 +++++++++++++ Tests/SharpLSL.Test/CommonTest.cs | 13 +- Tests/SharpLSL.Test/ResolverTest.cs | 19 + Tests/SharpLSL.Test/SharpLSL.Test.csproj | 4 + 33 files changed, 3764 insertions(+), 206 deletions(-) create mode 100644 Source/SharpLSL/ChannelFormat.cs create mode 100644 Source/SharpLSL/ContinuousResolver.cs create mode 100644 Source/SharpLSL/Interop/Inlet.Interop.cs create mode 100644 Source/SharpLSL/Interop/Outlet.Interop.cs create mode 100644 Source/SharpLSL/Interop/Resolver.Interop.cs create mode 100644 Source/SharpLSL/Interop/StreamInfo.Interop.cs create mode 100644 Source/SharpLSL/Interop/XML.Interop.cs rename Source/SharpLSL/Interop/{Xml.g.cs => XML.g.cs} (100%) create mode 100644 Source/SharpLSL/LSL.cs create mode 100644 Source/SharpLSL/LSLException.cs create mode 100644 Source/SharpLSL/LSLInternalException.cs create mode 100644 Source/SharpLSL/LSLObject.cs delete mode 100644 Source/SharpLSL/Lsl.cs delete mode 100644 Source/SharpLSL/LslChannelFormat.cs delete mode 100644 Source/SharpLSL/LslException.cs delete mode 100644 Source/SharpLSL/LslObject.cs delete mode 100644 Source/SharpLSL/LslProcessingOptions.cs delete mode 100644 Source/SharpLSL/LslStreamInfo.cs delete mode 100644 Source/SharpLSL/LslTransportOptions.cs create mode 100644 Source/SharpLSL/PostProcessingOptions.cs create mode 100644 Source/SharpLSL/StreamInfo.cs create mode 100644 Source/SharpLSL/StreamInlet.cs create mode 100644 Source/SharpLSL/StreamLostException.cs create mode 100644 Source/SharpLSL/StreamOutlet.cs create mode 100644 Source/SharpLSL/TransportOptions.cs create mode 100644 Source/SharpLSL/XMLElement.cs create mode 100644 Tests/SharpLSL.Test/ResolverTest.cs diff --git a/.gitattributes b/.gitattributes index 55ed421..2a578f4 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,2 @@ Binding/SharpLSL.Header.cs text eol=lf +Source/SharpLSL/Interop/*.g.cs text eol=lf diff --git a/Binding/generate.ps1 b/Binding/generate.ps1 index 4202acd..fe65430 100644 --- a/Binding/generate.ps1 +++ b/Binding/generate.ps1 @@ -9,6 +9,7 @@ ClangSharpPInvokeGenerator ` --language c++ ` --methodClassName Common ` --namespace SharpLSL.Interop ` + --additional -m64 ` --output ../Source/SharpLSL/Interop Move-Item -Path ../Source/SharpLSL/Interop/Common.cs -Destination ../Source/SharpLSL/Interop/Common.g.cs -Force @@ -29,6 +30,7 @@ ClangSharpPInvokeGenerator ` --language c++ ` --methodClassName LSL ` --namespace SharpLSL.Interop ` + --additional -m64 ` --output ../Source/SharpLSL/Interop/Common.g.cs ` --remap ` sbyte*=IntPtr ` @@ -49,6 +51,7 @@ ClangSharpPInvokeGenerator ` --language c++ ` --methodClassName LSL ` --namespace SharpLSL.Interop ` + --additional -m64 ` --output ../Source/SharpLSL/Interop/Inlet.g.cs ` --remap ` lsl_inlet=IntPtr ` @@ -65,6 +68,7 @@ ClangSharpPInvokeGenerator ` --language c++ ` --methodClassName LSL ` --namespace SharpLSL.Interop ` + --additional -m64 ` --output ../Source/SharpLSL/Interop/Outlet.g.cs ` --remap ` lsl_outlet=IntPtr ` @@ -81,6 +85,7 @@ ClangSharpPInvokeGenerator ` --language c++ ` --methodClassName LSL ` --namespace SharpLSL.Interop ` + --additional -m64 ` --output ../Source/SharpLSL/Interop/Resolver.g.cs ` --remap ` lsl_continuous_resolver=IntPtr ` @@ -97,6 +102,7 @@ ClangSharpPInvokeGenerator ` --language c++ ` --methodClassName LSL ` --namespace SharpLSL.Interop ` + --additional -m64 ` --output ../Source/SharpLSL/Interop/StreamInfo.g.cs ` --remap ` lsl_streaminfo=IntPtr ` @@ -113,7 +119,8 @@ ClangSharpPInvokeGenerator ` --language c++ ` --methodClassName LSL ` --namespace SharpLSL.Interop ` - --output ../Source/SharpLSL/Interop/Xml.g.cs ` + --additional -m64 ` + --output ../Source/SharpLSL/Interop/XML.g.cs ` --remap ` lsl_xml_ptr=IntPtr diff --git a/Examples/LSLVer/LSLVer.csproj b/Examples/LSLVer/LSLVer.csproj index 988bdb9..8c6c0f6 100644 --- a/Examples/LSLVer/LSLVer.csproj +++ b/Examples/LSLVer/LSLVer.csproj @@ -8,7 +8,7 @@ - + diff --git a/Examples/LSLVer/Program.cs b/Examples/LSLVer/Program.cs index 29ef49c..e2db962 100644 --- a/Examples/LSLVer/Program.cs +++ b/Examples/LSLVer/Program.cs @@ -1,6 +1,5 @@ // Port of https://github.com/sccn/liblsl/blob/master/testing/lslver.c using SharpLSL; -using SharpLSL.Interop; namespace LSLVer { @@ -8,9 +7,9 @@ internal class Program { static void Main(string[] args) { - Console.WriteLine($"LSL version: {LSL.lsl_library_version()}"); - Console.WriteLine(Lsl.GetLibraryInfo()); - Console.WriteLine(Lsl.GetLocalClock()); + Console.WriteLine($"LSL version: {LSL.GetLibraryVersion()}"); + Console.WriteLine(LSL.GetLibraryInfo()); + Console.WriteLine(LSL.GetLocalClock()); } } } \ No newline at end of file diff --git a/Source/SharpLSL/ChannelFormat.cs b/Source/SharpLSL/ChannelFormat.cs new file mode 100644 index 0000000..dac6171 --- /dev/null +++ b/Source/SharpLSL/ChannelFormat.cs @@ -0,0 +1,96 @@ +using SharpLSL.Interop; + +namespace SharpLSL +{ + /// + /// Data format of a channel. + /// + /// + /// This enumeration specifies the format of data transmitted for each channel. + /// Each transmitted sample contains an array of channels, and this enum describes + /// the format of the data sent over the wire. + /// + /// + public enum ChannelFormat : int + { + /// + /// Represents a 32-bit floating-point format. + /// + /// + /// This format is used for measurements that require up to 24-bit precision, + /// such as physical quantities measured in microvolts. Integers within the + /// range of -16,777,216 to 16,777,216 are represented accurately using this + /// format. + /// + Float = lsl_channel_format_t.cft_float32, + + /// + /// Represents a 64-bit double-precision floating-point format. + /// + /// + /// This format is used for representing numerical data with high precision + /// for universal numeric data as long as permitted by network & disk budget. + /// The largest representable integer is 53-bit. + /// + Double = lsl_channel_format_t.cft_double64, + + /// + /// Represents variable-length ASCII strings or data blobs. + /// + /// + /// This format is suitable for data that cannot be easily represented as + /// numeric values, such as video frames, or complex event descriptions. + /// + String = lsl_channel_format_t.cft_string, + + /// + /// Represents a 32-bit signed integer format. + /// + /// + /// This format is used for transmitting data that requires 32-bit integer + /// precision. It is suitable for high-rate digitized formats and cases + /// where the data is represented as discrete numeric values, such as + /// application event codes or other coded data. + /// + Int32 = lsl_channel_format_t.cft_int32, + + /// + /// Represents a 16-bit signed integer format. + /// + /// + /// This format is used for transmitting data with 16-bit integer precision. + /// It is ideal for very high-rate signals (40kHz+) or consumer-grade audio. + /// For professional audio, is recommended. + /// + Int16 = lsl_channel_format_t.cft_int16, + + /// + /// Represents an 8-bit signed integer format. + /// + /// + /// This format is used for transmitting data with 8-bit integer precision. + /// It is suitable for binary signals or other coded data. It is not + /// recommended for encoding string data. + /// + Int8 = lsl_channel_format_t.cft_int8, + + /// + /// Represents a 64-bit signed integer format. + /// + /// + /// This format is used for transmitting data that requires 64-bit integer + /// precision. Note that support for `Int64` is not yet exposed in all languages. + /// Also, some builds of liblsl will not be able to send or receive data of this type. + /// + Int64 = lsl_channel_format_t.cft_int64, + + /// + /// Represents an undefined or unsupported data format. + /// + /// + /// This format indicates that the data format is either not defined or + /// not supported for transmission. + /// + Undefined = lsl_channel_format_t.cft_undefined, + } +} diff --git a/Source/SharpLSL/ContinuousResolver.cs b/Source/SharpLSL/ContinuousResolver.cs new file mode 100644 index 0000000..ce87dfd --- /dev/null +++ b/Source/SharpLSL/ContinuousResolver.cs @@ -0,0 +1,91 @@ +using System; + +using static SharpLSL.Interop.LSL; +using static SharpLSL.LSL; + +namespace SharpLSL +{ + public class ContinuousResolver : LSLObject + { + public ContinuousResolver(double forgetAfter = 5.0) + : base(lsl_create_continuous_resolver(forgetAfter)) + { + } + + public ContinuousResolver(string property, string value, double forgetAfter = 5.0) + : base(lsl_create_continuous_resolver_byprop(property, value, forgetAfter)) + { + } + + public ContinuousResolver(string predicate, double forgetAfter = 5.0) + : base(lsl_create_continuous_resolver_bypred(predicate, forgetAfter)) + { + } + + public ContinuousResolver(IntPtr handle, bool ownsHandle = true) + : base(handle, ownsHandle) + { + } + + public StreamInfo[] Resolve(int maxCount = 1024) + { + var streamInfoPointers = new IntPtr[maxCount]; + + var result = lsl_resolver_results(handle, streamInfoPointers, (uint)streamInfoPointers.Length); + CheckError(result); + + var streamInfos = new StreamInfo[result]; + for (int i = 0; i < result; ++i) + streamInfos[i] = new StreamInfo(streamInfoPointers[i], true); + + return streamInfos; + } + + public StreamInfo[] ResolveAll(int maxCount = 1024, double waitTime = 1.0) + { + var streamInfoPointers = new IntPtr[maxCount]; + + var result = lsl_resolve_all(streamInfoPointers, (uint)streamInfoPointers.Length, waitTime); + CheckError(result); + + var streamInfos = new StreamInfo[result]; + for (int i = 0; i < result; ++i) + streamInfos[i] = new StreamInfo(streamInfoPointers[i], true); + + return streamInfos; + } + + public StreamInfo[] Resolve(string property, string value, int minCount, int maxCount = 1024, double timeout = Forever) + { + var streamInfoPointers = new IntPtr[maxCount]; + + var result = lsl_resolve_byprop(streamInfoPointers, (uint)streamInfoPointers.Length, property, value, minCount, timeout); + CheckError(result); + + var streamInfos = new StreamInfo[result]; + for (int i = 0; i < result; ++i) + streamInfos[i] = new StreamInfo(streamInfoPointers[i], true); + + return streamInfos; + } + + public StreamInfo[] Resolve(string predicate, int minCount, int maxCount = 1024, double timeout = Forever) + { + var streamInfoPointers = new IntPtr[maxCount]; + + var result = lsl_resolve_bypred(streamInfoPointers, (uint)streamInfoPointers.Length, predicate, minCount, timeout); + CheckError(result); + + var streamInfos = new StreamInfo[result]; + for (int i = 0; i < result; ++i) + streamInfos[i] = new StreamInfo(streamInfoPointers[i], true); + + return streamInfos; + } + + protected override void DestroyLSLObject() + { + lsl_destroy_continuous_resolver(handle); + } + } +} diff --git a/Source/SharpLSL/Interop/Inlet.Interop.cs b/Source/SharpLSL/Interop/Inlet.Interop.cs new file mode 100644 index 0000000..2770831 --- /dev/null +++ b/Source/SharpLSL/Interop/Inlet.Interop.cs @@ -0,0 +1,130 @@ +using System; +using System.Runtime.InteropServices; + +using lsl_inlet = System.IntPtr; + +namespace SharpLSL.Interop +{ + public static unsafe partial class LSL + { + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern lsl_inlet lsl_get_fullinfo(lsl_inlet @in, double timeout, ref int ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern void lsl_open_stream(lsl_inlet @in, double timeout, ref int ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern double lsl_time_correction(lsl_inlet @in, double timeout, ref int ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern double lsl_time_correction_ex(lsl_inlet @in, ref double remote_time, ref double uncertainty, double timeout, ref int ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern double lsl_pull_sample_c(lsl_inlet @in, sbyte[] buffer, int buffer_elements, double timeout, ref int ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern double lsl_pull_sample_c(lsl_inlet @in, byte[] buffer, int buffer_elements, double timeout, ref int ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern double lsl_pull_sample_s(lsl_inlet @in, short[] buffer, int buffer_elements, double timeout, ref int ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern double lsl_pull_sample_i(lsl_inlet @in, int[] buffer, int buffer_elements, double timeout, ref int ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern double lsl_pull_sample_l(lsl_inlet @in, long[] buffer, int buffer_elements, double timeout, ref int ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern double lsl_pull_sample_f(lsl_inlet @in, float[] buffer, int buffer_elements, double timeout, ref int ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern double lsl_pull_sample_d(lsl_inlet @in, double[] buffer, int buffer_elements, double timeout, ref int ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern double lsl_pull_sample_str(lsl_inlet @in, IntPtr[] buffer, int buffer_elements, double timeout, ref int ec); + + + + + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern uint lsl_pull_chunk_f([NativeTypeName("lsl_inlet")] IntPtr @in, float[] data_buffer, double[] timestamp_buffer, uint data_buffer_elements, uint timestamp_buffer_elements, double timeout, ref int ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern uint lsl_pull_chunk_d([NativeTypeName("lsl_inlet")] IntPtr @in, double[] data_buffer, double[] timestamp_buffer, uint data_buffer_elements, uint timestamp_buffer_elements, double timeout, ref int ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern uint lsl_pull_chunk_l([NativeTypeName("lsl_inlet")] IntPtr @in, long[] data_buffer, double[] timestamp_buffer, uint data_buffer_elements, uint timestamp_buffer_elements, double timeout, ref int ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern uint lsl_pull_chunk_i([NativeTypeName("lsl_inlet")] IntPtr @in, int[] data_buffer, double[] timestamp_buffer, uint data_buffer_elements, uint timestamp_buffer_elements, double timeout, ref int ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern uint lsl_pull_chunk_s([NativeTypeName("lsl_inlet")] IntPtr @in, short[] data_buffer, double[] timestamp_buffer, uint data_buffer_elements, uint timestamp_buffer_elements, double timeout, ref int ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern uint lsl_pull_chunk_f([NativeTypeName("lsl_inlet")] IntPtr @in, float[,] data_buffer, double[] timestamp_buffer, uint data_buffer_elements, uint timestamp_buffer_elements, double timeout, ref int ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern uint lsl_pull_chunk_d([NativeTypeName("lsl_inlet")] IntPtr @in, double[,] data_buffer, double[] timestamp_buffer, uint data_buffer_elements, uint timestamp_buffer_elements, double timeout, ref int ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern uint lsl_pull_chunk_l([NativeTypeName("lsl_inlet")] IntPtr @in, long[,] data_buffer, double[] timestamp_buffer, uint data_buffer_elements, uint timestamp_buffer_elements, double timeout, ref int ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern uint lsl_pull_chunk_i([NativeTypeName("lsl_inlet")] IntPtr @in, int[,] data_buffer, double[] timestamp_buffer, uint data_buffer_elements, uint timestamp_buffer_elements, double timeout, ref int ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern uint lsl_pull_chunk_s([NativeTypeName("lsl_inlet")] IntPtr @in, short[,] data_buffer, double[] timestamp_buffer, uint data_buffer_elements, uint timestamp_buffer_elements, double timeout, ref int ec); + + /* + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("lsl_inlet")] + public static extern IntPtr lsl_create_inlet([NativeTypeName("lsl_streaminfo")] IntPtr info, [NativeTypeName("int32_t")] int max_buflen, [NativeTypeName("int32_t")] int max_chunklen, [NativeTypeName("int32_t")] int recover); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("lsl_streaminfo")] + public static extern IntPtr lsl_get_fullinfo([NativeTypeName("lsl_inlet")] IntPtr @in, double timeout, [NativeTypeName("int32_t *")] int* ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern double lsl_pull_sample_c([NativeTypeName("lsl_inlet")] IntPtr @in, [NativeTypeName("char *")] sbyte* buffer, [NativeTypeName("int32_t")] int buffer_elements, double timeout, [NativeTypeName("int32_t *")] int* ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern double lsl_pull_sample_str([NativeTypeName("lsl_inlet")] IntPtr @in, [NativeTypeName("char **")] sbyte** buffer, [NativeTypeName("int32_t")] int buffer_elements, double timeout, [NativeTypeName("int32_t *")] int* ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern double lsl_pull_sample_buf([NativeTypeName("lsl_inlet")] IntPtr @in, [NativeTypeName("char **")] sbyte** buffer, [NativeTypeName("uint32_t *")] uint* buffer_lengths, [NativeTypeName("int32_t")] int buffer_elements, double timeout, [NativeTypeName("int32_t *")] int* ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern double lsl_pull_sample_v([NativeTypeName("lsl_inlet")] IntPtr @in, void* buffer, [NativeTypeName("int32_t")] int buffer_bytes, double timeout, [NativeTypeName("int32_t *")] int* ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("unsigned long")] + public static extern uint lsl_pull_chunk_c([NativeTypeName("lsl_inlet")] IntPtr @in, [NativeTypeName("char *")] sbyte* data_buffer, double* timestamp_buffer, [NativeTypeName("unsigned long")] uint data_buffer_elements, [NativeTypeName("unsigned long")] uint timestamp_buffer_elements, double timeout, [NativeTypeName("int32_t *")] int* ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("unsigned long")] + public static extern uint lsl_pull_chunk_str([NativeTypeName("lsl_inlet")] IntPtr @in, [NativeTypeName("char **")] sbyte** data_buffer, double* timestamp_buffer, [NativeTypeName("unsigned long")] uint data_buffer_elements, [NativeTypeName("unsigned long")] uint timestamp_buffer_elements, double timeout, [NativeTypeName("int32_t *")] int* ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("unsigned long")] + public static extern uint lsl_pull_chunk_buf([NativeTypeName("lsl_inlet")] IntPtr @in, [NativeTypeName("char **")] sbyte** data_buffer, [NativeTypeName("uint32_t *")] uint* lengths_buffer, double* timestamp_buffer, [NativeTypeName("unsigned long")] uint data_buffer_elements, [NativeTypeName("unsigned long")] uint timestamp_buffer_elements, double timeout, [NativeTypeName("int32_t *")] int* ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("uint32_t")] + public static extern uint lsl_samples_available([NativeTypeName("lsl_inlet")] IntPtr @in); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("uint32_t")] + public static extern uint lsl_inlet_flush([NativeTypeName("lsl_inlet")] IntPtr @in); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("uint32_t")] + public static extern uint lsl_was_clock_reset([NativeTypeName("lsl_inlet")] IntPtr @in); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_smoothing_halftime([NativeTypeName("lsl_inlet")] IntPtr @in, float value); + */ + } +} diff --git a/Source/SharpLSL/Interop/Outlet.Interop.cs b/Source/SharpLSL/Interop/Outlet.Interop.cs new file mode 100644 index 0000000..1e26cec --- /dev/null +++ b/Source/SharpLSL/Interop/Outlet.Interop.cs @@ -0,0 +1,326 @@ +using System.Runtime.InteropServices; + +using lsl_outlet = System.IntPtr; + +namespace SharpLSL.Interop +{ + public static unsafe partial class LSL + { + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_sample_f(lsl_outlet @out, float[] data); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_sample_d(lsl_outlet @out, double[] data); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_sample_l(lsl_outlet @out, long[] data); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_sample_i(lsl_outlet @out, int[] data); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_sample_s(lsl_outlet @out, short[] data); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_sample_ft(lsl_outlet @out, float[] data, double timestamp); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_sample_dt(lsl_outlet @out, double[] data, double timestamp); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_sample_lt(lsl_outlet @out, long[] data, double timestamp); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_sample_it(lsl_outlet @out, int[] data, double timestamp); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_sample_st(lsl_outlet @out, short[] data, double timestamp); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_sample_ftp(lsl_outlet @out, float[] data, double timestamp, int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_sample_dtp(lsl_outlet @out, double[] data, double timestamp, int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_sample_ltp(lsl_outlet @out, long[] data, double timestamp, int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_sample_itp(lsl_outlet @out, int[] data, double timestamp, int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_sample_stp(lsl_outlet @out, short[] data, double timestamp, int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_f(lsl_outlet @out, float[] data, uint data_elements); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_d(lsl_outlet @out, double[] data, uint data_elements); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_l(lsl_outlet @out, long[] data, uint data_elements); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_i(lsl_outlet @out, int[] data, uint data_elements); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_s(lsl_outlet @out, short[] data, uint data_elements); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_f(lsl_outlet @out, float[,] data, uint data_elements); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_d(lsl_outlet @out, double[,] data, uint data_elements); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_l(lsl_outlet @out, long[,] data, uint data_elements); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_i(lsl_outlet @out, int[,] data, uint data_elements); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_s(lsl_outlet @out, short[,] data, uint data_elements); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_ft(lsl_outlet @out, float[] data, uint data_elements, double timestamp); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_dt(lsl_outlet @out, double[] data, uint data_elements, double timestamp); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_lt(lsl_outlet @out, long[] data, uint data_elements, double timestamp); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_it(lsl_outlet @out, int[] data, uint data_elements, double timestamp); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_st(lsl_outlet @out, short[] data, uint data_elements, double timestamp); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_ft(lsl_outlet @out, float[,] data, uint data_elements, double timestamp); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_dt(lsl_outlet @out, double[,] data, uint data_elements, double timestamp); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_lt(lsl_outlet @out, long[,] data, uint data_elements, double timestamp); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_it(lsl_outlet @out, int[,] data, uint data_elements, double timestamp); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_st(lsl_outlet @out, short[,] data, uint data_elements, double timestamp); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_ftp(lsl_outlet @out, float[] data, uint data_elements, double timestamp, int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_dtp(lsl_outlet @out, double[] data, uint data_elements, double timestamp, int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_ltp(lsl_outlet @out, long[] data, uint data_elements, double timestamp, int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_itp(lsl_outlet @out, int[] data, uint data_elements, double timestamp, int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_stp(lsl_outlet @out, short[] data, uint data_elements, double timestamp, int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_ftp(lsl_outlet @out, float[,] data, uint data_elements, double timestamp, int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_dtp(lsl_outlet @out, double[,] data, uint data_elements, double timestamp, int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_ltp(lsl_outlet @out, long[,] data, uint data_elements, double timestamp, int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_itp(lsl_outlet @out, int[,] data, uint data_elements, double timestamp, int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_stp(lsl_outlet @out, short[,] data, uint data_elements, double timestamp, int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_ftn(lsl_outlet @out, float[] data, uint data_elements, double[] timestamps); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_dtn(lsl_outlet @out, double[] data, uint data_elements, double[] timestamps); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_ltn(lsl_outlet @out, long[] data, uint data_elements, double[] timestamps); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_itn(lsl_outlet @out, int[] data, uint data_elements, double[] timestamps); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_stn(lsl_outlet @out, short[] data, uint data_elements, double[] timestamps); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_ftn(lsl_outlet @out, float[,] data, uint data_elements, double[] timestamps); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_dtn(lsl_outlet @out, double[,] data, uint data_elements, double[] timestamps); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_ltn(lsl_outlet @out, long[,] data, uint data_elements, double[] timestamps); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_itn(lsl_outlet @out, int[,] data, uint data_elements, double[] timestamps); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_stn(lsl_outlet @out, short[,] data, uint data_elements, double[] timestamps); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_ftnp(lsl_outlet @out, float[] data, uint data_elements, double[] timestamps, int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_dtnp(lsl_outlet @out, double[] data, uint data_elements, double[] timestamps, int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_ltnp(lsl_outlet @out, long[] data, uint data_elements, double[] timestamps, int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_itnp(lsl_outlet @out, int[] data, uint data_elements, double[] timestamps, int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_stnp(lsl_outlet @out, short[] data, uint data_elements, double[] timestamps, int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_ftnp(lsl_outlet @out, float[,] data, uint data_elements, double[] timestamps, int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_dtnp(lsl_outlet @out, double[,] data, uint data_elements, double[] timestamps, int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_ltnp(lsl_outlet @out, long[,] data, uint data_elements, double[] timestamps, int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_itnp(lsl_outlet @out, int[,] data, uint data_elements, double[] timestamps, int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_push_chunk_stnp(lsl_outlet @out, short[,] data, uint data_elements, double[] timestamps, int pushthrough); + + /* + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_sample_c(lsl_outlet @out, [NativeTypeName("const char *")] sbyte* data); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_sample_str(lsl_outlet @out, [NativeTypeName("const char **")] sbyte** data); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_sample_v(lsl_outlet @out, [NativeTypeName("const void *")] void* data); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_sample_ct(lsl_outlet @out, [NativeTypeName("const char *")] sbyte* data, double timestamp); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_sample_strt(lsl_outlet @out, [NativeTypeName("const char **")] sbyte** data, double timestamp); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_sample_vt(lsl_outlet @out, [NativeTypeName("const void *")] void* data, double timestamp); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_sample_ctp(lsl_outlet @out, [NativeTypeName("const char *")] sbyte* data, double timestamp, [NativeTypeName("int32_t")] int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_sample_strtp(lsl_outlet @out, [NativeTypeName("const char **")] sbyte** data, double timestamp, [NativeTypeName("int32_t")] int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_sample_vtp(lsl_outlet @out, [NativeTypeName("const void *")] void* data, double timestamp, [NativeTypeName("int32_t")] int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_sample_buf(lsl_outlet @out, [NativeTypeName("const char **")] sbyte** data, [NativeTypeName("const uint32_t *")] uint* lengths); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_sample_buft(lsl_outlet @out, [NativeTypeName("const char **")] sbyte** data, [NativeTypeName("const uint32_t *")] uint* lengths, double timestamp); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_sample_buftp(lsl_outlet @out, [NativeTypeName("const char **")] sbyte** data, [NativeTypeName("const uint32_t *")] uint* lengths, double timestamp, [NativeTypeName("int32_t")] int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_chunk_c(lsl_outlet @out, [NativeTypeName("const char *")] sbyte* data, [NativeTypeName("unsigned long")] uint data_elements); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_chunk_str(lsl_outlet @out, [NativeTypeName("const char **")] sbyte** data, [NativeTypeName("unsigned long")] uint data_elements); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_chunk_ct(lsl_outlet @out, [NativeTypeName("const char *")] sbyte* data, [NativeTypeName("unsigned long")] uint data_elements, double timestamp); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_chunk_strt(lsl_outlet @out, [NativeTypeName("const char **")] sbyte** data, [NativeTypeName("unsigned long")] uint data_elements, double timestamp); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_chunk_ctp(lsl_outlet @out, [NativeTypeName("const char *")] sbyte* data, [NativeTypeName("unsigned long")] uint data_elements, double timestamp, [NativeTypeName("int32_t")] int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_chunk_strtp(lsl_outlet @out, [NativeTypeName("const char **")] sbyte** data, [NativeTypeName("unsigned long")] uint data_elements, double timestamp, [NativeTypeName("int32_t")] int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_chunk_ctn(lsl_outlet @out, [NativeTypeName("const char *")] sbyte* data, [NativeTypeName("unsigned long")] uint data_elements, [NativeTypeName("const double *")] double* timestamps); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_chunk_strtn(lsl_outlet @out, [NativeTypeName("const char **")] sbyte** data, [NativeTypeName("unsigned long")] uint data_elements, [NativeTypeName("const double *")] double* timestamps); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_chunk_ctnp(lsl_outlet @out, [NativeTypeName("const char *")] sbyte* data, [NativeTypeName("unsigned long")] uint data_elements, [NativeTypeName("const double *")] double* timestamps, [NativeTypeName("int32_t")] int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_chunk_strtnp(lsl_outlet @out, [NativeTypeName("const char **")] sbyte** data, [NativeTypeName("unsigned long")] uint data_elements, [NativeTypeName("const double *")] double* timestamps, [NativeTypeName("int32_t")] int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_chunk_buf(lsl_outlet @out, [NativeTypeName("const char **")] sbyte** data, [NativeTypeName("const uint32_t *")] uint* lengths, [NativeTypeName("unsigned long")] uint data_elements); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_chunk_buft(lsl_outlet @out, [NativeTypeName("const char **")] sbyte** data, [NativeTypeName("const uint32_t *")] uint* lengths, [NativeTypeName("unsigned long")] uint data_elements, double timestamp); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_chunk_buftp(lsl_outlet @out, [NativeTypeName("const char **")] sbyte** data, [NativeTypeName("const uint32_t *")] uint* lengths, [NativeTypeName("unsigned long")] uint data_elements, double timestamp, [NativeTypeName("int32_t")] int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_chunk_buftn(lsl_outlet @out, [NativeTypeName("const char **")] sbyte** data, [NativeTypeName("const uint32_t *")] uint* lengths, [NativeTypeName("unsigned long")] uint data_elements, [NativeTypeName("const double *")] double* timestamps); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_push_chunk_buftnp(lsl_outlet @out, [NativeTypeName("const char **")] sbyte** data, [NativeTypeName("const uint32_t *")] uint* lengths, [NativeTypeName("unsigned long")] uint data_elements, [NativeTypeName("const double *")] double* timestamps, [NativeTypeName("int32_t")] int pushthrough); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_have_consumers(lsl_outlet @out); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("int32_t")] + public static extern int lsl_wait_for_consumers(lsl_outlet @out, double timeout); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + [return: NativeTypeName("lsl_streaminfo")] + public static extern IntPtr lsl_get_info(lsl_outlet @out); + */ + } +} diff --git a/Source/SharpLSL/Interop/Resolver.Interop.cs b/Source/SharpLSL/Interop/Resolver.Interop.cs new file mode 100644 index 0000000..39ebac8 --- /dev/null +++ b/Source/SharpLSL/Interop/Resolver.Interop.cs @@ -0,0 +1,26 @@ +using System; +using System.Runtime.InteropServices; + +namespace SharpLSL.Interop +{ + public static unsafe partial class LSL + { + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern IntPtr lsl_create_continuous_resolver_byprop(string prop, string value, double forget_after); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern IntPtr lsl_create_continuous_resolver_bypred(string pred, double forget_after); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_resolver_results([NativeTypeName("lsl_continuous_resolver")] IntPtr res, IntPtr[] buffer, [NativeTypeName("uint32_t")] uint buffer_elements); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_resolve_all(IntPtr[] buffer, [NativeTypeName("uint32_t")] uint buffer_elements, double wait_time); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_resolve_byprop(IntPtr[] buffer, uint buffer_elements, string prop, string value, int minimum, double timeout); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_resolve_bypred(IntPtr[] buffer, uint buffer_elements, string pred, int minimum, double timeout); + } +} diff --git a/Source/SharpLSL/Interop/StreamInfo.Interop.cs b/Source/SharpLSL/Interop/StreamInfo.Interop.cs new file mode 100644 index 0000000..e914db3 --- /dev/null +++ b/Source/SharpLSL/Interop/StreamInfo.Interop.cs @@ -0,0 +1,35 @@ +using System; +using System.Runtime.InteropServices; + +using lsl_streaminfo = System.IntPtr; + +namespace SharpLSL.Interop +{ + public static partial class LSL + { + // TODO: Encoding + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern lsl_streaminfo lsl_create_streaminfo( + [MarshalAs(UnmanagedType.LPStr)] string name, + [MarshalAs(UnmanagedType.LPStr)] string type, + int channel_count, + double nominal_srate, + ChannelFormat channel_format, + [MarshalAs(UnmanagedType.LPStr)] string source_id); + + // TODO: Encoding + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern lsl_streaminfo lsl_streaminfo_from_xml( + [MarshalAs(UnmanagedType.LPStr)] string xml); + + // TODO: Encoding + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_stream_info_matches_query( + lsl_streaminfo info, + [MarshalAs(UnmanagedType.LPStr)] string query); + } +} + + +// References: +// [Refactor your code using alias any type](https://devblogs.microsoft.com/dotnet/refactor-your-code-using-alias-any-type/) diff --git a/Source/SharpLSL/Interop/XML.Interop.cs b/Source/SharpLSL/Interop/XML.Interop.cs new file mode 100644 index 0000000..7d65561 --- /dev/null +++ b/Source/SharpLSL/Interop/XML.Interop.cs @@ -0,0 +1,58 @@ +using System.Runtime.InteropServices; + +using lsl_xml_ptr = System.IntPtr; + +namespace SharpLSL.Interop +{ + // TODO: Encoding + public static unsafe partial class LSL + { + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_set_name( + lsl_xml_ptr e, [MarshalAs(UnmanagedType.LPStr)] string rhs); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_set_value( + lsl_xml_ptr e, [MarshalAs(UnmanagedType.LPStr)] string rhs); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern lsl_xml_ptr lsl_child( + lsl_xml_ptr e, [MarshalAs(UnmanagedType.LPStr)] string name); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern lsl_xml_ptr lsl_child_value_n( + lsl_xml_ptr e, [MarshalAs(UnmanagedType.LPStr)] string name); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern int lsl_set_child_value( + lsl_xml_ptr e, [MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string value); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern lsl_xml_ptr lsl_next_sibling_n( + lsl_xml_ptr e, [MarshalAs(UnmanagedType.LPStr)] string name); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern lsl_xml_ptr lsl_previous_sibling_n( + lsl_xml_ptr e, [MarshalAs(UnmanagedType.LPStr)] string name); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern lsl_xml_ptr lsl_append_child( + lsl_xml_ptr e, [MarshalAs(UnmanagedType.LPStr)] string name); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern lsl_xml_ptr lsl_append_child_value( + lsl_xml_ptr e, [MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string value); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern lsl_xml_ptr lsl_prepend_child( + lsl_xml_ptr e, [MarshalAs(UnmanagedType.LPStr)] string name); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern lsl_xml_ptr lsl_prepend_child_value( + lsl_xml_ptr e, [MarshalAs(UnmanagedType.LPStr)] string name, [MarshalAs(UnmanagedType.LPStr)] string value); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern void lsl_remove_child_n( + lsl_xml_ptr e, [MarshalAs(UnmanagedType.LPStr)] string name); + } +} diff --git a/Source/SharpLSL/Interop/Xml.g.cs b/Source/SharpLSL/Interop/XML.g.cs similarity index 100% rename from Source/SharpLSL/Interop/Xml.g.cs rename to Source/SharpLSL/Interop/XML.g.cs diff --git a/Source/SharpLSL/LSL.cs b/Source/SharpLSL/LSL.cs new file mode 100644 index 0000000..2a44ccc --- /dev/null +++ b/Source/SharpLSL/LSL.cs @@ -0,0 +1,205 @@ +using System; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +using SharpLSL.Interop; +using static SharpLSL.Interop.LSL; + +namespace SharpLSL +{ + public static class LSL + { + /// + /// Constant to indicate that a stream has variable sampling rate. + /// + /// + public const double IrregularRate = LSL_IRREGULAR_RATE; + + /// + /// Constant to indicate that a sample has the next successive timestamp. + /// + /// + /// This is an optional optimization to transmit less data per sample. + /// The timestamp is then deduced from the preceding one according to the stream's + /// sampling rate. If the sampling rate is irregular, the time stamp will be assumed + /// to be the same as the previous sample's timestamp. + /// + public const double DeducedTimestamp = LSL_DEDUCED_TIMESTAMP; + + /// + /// Constant to indicate a very large time value, approximately equivalent to 1 year. + /// This constant can be used for specifying timeouts where you want to effectively + /// indicate an indefinite or very long duration. + /// + public const double Forever = LSL_FOREVER; + + /// + /// Constant to indicate that there is no preference about how a data stream shall be + /// chunked for transmission. + /// + /// + /// This constant can be used for the chunking parameters in the inlet or the outlet. + /// + public const int NoPreference = LSL_NO_PREFERENCE; + + /// + /// Constant to indicate the LSL version the binary was compiled against. + /// + /// + /// This constant is used either to check if the same version is used: + /// + /// if (LSL.GetProtocolVersion() != LSL.CompileHeaderVersion) + /// { + /// // Do stuff... + /// } + /// + /// or to require a certain set of features:: + /// + /// if (LSL.CompileHeaderVersion > 113) + /// { + /// // Do stuff... + /// } + /// + /// + /// + public const int CompileHeaderVersion = LIBLSL_COMPILE_HEADER_VERSION; + + /// + /// Gets the last error message from the LSL (Lab Streaming Layer) library. + /// + /// + /// A string containing the most recent error message from the LSL library. + /// + public static string GetLastError() => PtrToString(lsl_last_error()); + + /// + /// Gets the LSL protocol version encoded as a single integer. + /// + /// The LSL protocol version encoded as a single integer. + /// + /// The protocol version is encoded as a single integer, where: + /// + /// The major version can be obtained by dividing the protocol version by 100 (i.e., LSL.GetProtocolVersion() / 100). + /// The minor version can be obtained by taking the remainder of the protocol version divided by 100 (i.e., LSL.GetProtocolVersion() % 100). + /// + /// Clients with different minor versions are considered protocol-compatible, + /// while clients with different major versions are not compatible and will + /// refuse to work together. + /// + /// + /// + public static int GetProtocolVersion() => lsl_protocol_version(); + + /// + /// Gets the underlying liblsl version number encoded as a single integer. + /// + /// The liblsl version number encoded as a single integer. + /// + /// The liblsl version number is encoded as a single integer, where: + /// + /// The major version can be obtained by dividing the version by 100 (i.e., LSL.GetLibraryVersion() / 100). + /// The minor version can be obtained by taking the remainder of the version divided by 100 (i.e., LSL.GetLibraryVersion() % 100). + /// + /// + public static int GetLibraryVersion() => lsl_library_version(); + + /// + /// Gets a string containing library information. + /// + /// The string containing library information. + /// + /// The format of the string shouldn't be used for anything important except + /// giving a debugging person a good idea which exact library version is used. + /// + public static string GetLibraryInfo() => PtrToString(lsl_library_info()); + + /// + /// Gets the current local system timestamp in seconds with high resolution. + /// + /// The current local system timestamp in seconds. + /// + /// This function returns a local system timestamp in seconds which has a + /// high resolution better than a milliseconds. The returned timestamp can + /// be used to assign timestamps to samples as they are being acquired. + /// If the "age" of a sample is known at a particular time (e.g., from USB + /// transmission delays), it can be used as an offset to + /// to obtain a better estimate of when a sample was actually captured. + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + /// + // TODO: Update seealso + public static double GetLocalClock() => lsl_local_clock(); + + + // TODO: Encoding +#if !NET35 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + internal static string PtrToString(IntPtr ptr) + { + return Marshal.PtrToStringAnsi(ptr); + } + + // TODO: Encoding +#if !NET35 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + internal static string PtrToXmlString(IntPtr ptr) + { + return Marshal.PtrToStringAnsi(ptr); + } + +#if !NET35 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + internal static void CheckError(int errorCode) + { + if (errorCode < 0) + { + switch ((lsl_error_code_t)errorCode) + { + //case lsl_error_code_t.lsl_no_error: + // break; + case lsl_error_code_t.lsl_timeout_error: + throw new TimeoutException("The operation failed due to a timeout."); + case lsl_error_code_t.lsl_lost_error: + throw new StreamLostException("The stream has been lost."); + case lsl_error_code_t.lsl_argument_error: + throw new ArgumentException("An argument was incorrectly specified."); + case lsl_error_code_t.lsl_internal_error: + throw new LSLInternalException("An internal error has occurred."); + default: + throw new LSLException("An unknown error has occurred."); + } + } + } + +#if !NET35 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + internal static void CheckChannelCount(int expected, int actual) + { + if (actual != expected) + throw new ArgumentException($"Provided buffer's channel count ({actual}) doesn't match the stream's channel count ({expected})."); + } + +#if !NET35 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + internal static void CheckSampleBuffer(T[] sample, int channelCount) + { + if (sample == null) + throw new ArgumentNullException(nameof(sample)); + + CheckChannelCount(channelCount, sample.Length); + } + } +} diff --git a/Source/SharpLSL/LSLException.cs b/Source/SharpLSL/LSLException.cs new file mode 100644 index 0000000..ef88a92 --- /dev/null +++ b/Source/SharpLSL/LSLException.cs @@ -0,0 +1,51 @@ +using System; + +namespace SharpLSL +{ + /// + /// Represents errors that occur within the LSL (Lab Streaming Layer) framework. + /// + /// + /// This exception is used to signal issues specific to LSL operations, such as + /// problems with data streams or communication errors. It extends the base + /// class to provide additional context for LSL-related + /// exceptions. + /// + public class LSLException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public LSLException() + { + } + + /// + /// Initializes a new instance of the class with + /// a specified error message. + /// + /// + /// The error message that describes the reason for the exception. + /// + public LSLException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class with + /// a specified error message and a reference to the inner exception. + /// + /// + /// The error message that describes the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null + /// reference if no inner exception is specified. + /// + public LSLException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/Source/SharpLSL/LSLInternalException.cs b/Source/SharpLSL/LSLInternalException.cs new file mode 100644 index 0000000..6c53ea8 --- /dev/null +++ b/Source/SharpLSL/LSLInternalException.cs @@ -0,0 +1,50 @@ +using System; + +namespace SharpLSL +{ + /// + /// Represents an internal error within the LSL (Lab Streaming Layer) framework. + /// + /// + /// This exception is used to indicate errors that occur within the internal of LSL. + /// This class extends to provide a more specific exception + /// type for internal LSL errors. + /// + public class LSLInternalException : LSLException + { + /// + /// Initializes a new instance of the class. + /// + public LSLInternalException() + { + } + + /// + /// Initializes a new instance of the class with + /// a specified error message. + /// + /// + /// The error message that describes the reason for the exception. + /// + public LSLInternalException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class with + /// a specified error message and a reference to the inner exception. + /// + /// + /// The error message that describes the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null + /// reference if no inner exception is specified. + /// + public LSLInternalException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/Source/SharpLSL/LSLObject.cs b/Source/SharpLSL/LSLObject.cs new file mode 100644 index 0000000..851fc44 --- /dev/null +++ b/Source/SharpLSL/LSLObject.cs @@ -0,0 +1,114 @@ +using System; +using System.Runtime.InteropServices; + +namespace SharpLSL +{ + /// + /// Represents a base class for LSL (Lab Streaming Layer) objects, which wraps + /// a native handle to manage the underlying native resources. + /// + /// + /// This abstract class extends to provide a safe and + /// managed way to interact with LSL objects. It ensures that the native resources + /// associated with the LSL objects are properly released when no longer needed. + /// Derived classes should implement specific functionality for different types + /// of LSL objects, such as inlets or outlets. + /// + public abstract class LSLObject : SafeHandle + { + /// + /// Initializes a new instance of the class. + /// + /// + /// Speciies whether the wrapped handle should be released during the finalization + /// phase. + /// + protected LSLObject(bool ownsHandle = true) + : base(IntPtr.Zero, ownsHandle) + { + } + + /// + /// Initializes a new instance of the class with the + /// specified LSL native handle value. + /// + /// + /// Specifies the handle to be wrapped. + /// + /// + /// Speciies whether the wrapped handle should be released during the finalization phase. + /// + /// + /// Thrown if the handle to be wrapped is invalid. + /// + protected LSLObject(IntPtr handle, bool ownsHandle = true) + : base(IntPtr.Zero, ownsHandle) + { + if (handle == IntPtr.Zero) + throw new LSLException($"Failed to create {GetType().Name} object: {LSL.GetLastError()}."); + + SetHandle(handle); + } + + /// + /// Gets a value indicating whether the handle value is invalid. + /// + public override bool IsInvalid => handle == IntPtr.Zero; + + /// + /// Frees the wrapped LSL native handle by calling . + /// + /// A value indicates if the handle is released successfully. + protected override bool ReleaseHandle() + { + DestroyLSLObject(); + // Without this line, IsInvalid == false; + // SetHandleAsInvalid() won't change the value of handle. + SetHandle(IntPtr.Zero); + return true; + } + + /// + /// When overridden in a derived class, ensures that the appropriate LSL + /// function is called to release the handle of the corresponding LSL object. + /// + /// + protected abstract void DestroyLSLObject(); + + /// + /// Throws an if the handle is invalid. + /// + /// + /// Thrown when the handle is invalid (i.e., is true). + /// + protected void ThrowIfInvalid() + { + if (IsInvalid) + throw new InvalidOperationException("The handle is invalid."); + } + } +} + + +// References: +// [SafeHandle Class](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.safehandle?view=net-8.0) +// [System.Runtime.InteropServices.SafeHandle class](https://learn.microsoft.com/en-us/dotnet/fundamentals/runtime-libraries/system-runtime-interopservices-safehandle) +// [SafeHandle.SetHandleAsInvalid Method](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.safehandle.sethandleasinvalid?view=net-8.0) +// > As with the SetHandle method, use SetHandleAsInvalid only if you need to support a pre-existing handle. +// https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/SafeHandle.cs +// https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/System/Net/DebugSafeHandleZeroOrMinusOneIsInvalid.cs +// https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeHandleZeroOrMinusOneIsInvalid.cs +// https://github.com/dotnet/runtime/blob/main/src/tests/Interop/PInvoke/SafeHandles/SafeHandles.cs +// https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/Microsoft/Win32/SafeHandles/SafeCertStoreHandle.cs +// [Cannot pass SafeHandle instance in ReleaseHandle to native method](https://stackoverflow.com/questions/47934248/cannot-pass-safehandle-instance-in-releasehandle-to-native-method) +// https://github.com/dotnet/runtime/blob/main/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles/SafeFileHandle.cs +// > SetHandle(...); +// https://github.com/dotnet/runtime/tree/main/src/libraries/System.Private.CoreLib/src/Microsoft/Win32/SafeHandles +// [SafeHandle used to wrap a non-owned pointer](https://stackoverflow.com/questions/31691183/safehandle-used-to-wrap-a-non-owned-pointer) +// [Are there any benefits for using SafeFileHandle with FileStream constructor](https://stackoverflow.com/questions/58576214/are-there-any-benefits-for-using-safefilehandle-with-filestream-constructor) +// [What is SafeFileHandle in C# and when should I use it?](https://stackoverflow.com/questions/58568415/what-is-safefilehandle-in-c-sharp-and-when-should-i-use-it) +// [How to Close SafeFile Handle properly](https://stackoverflow.com/questions/20827222/how-to-close-safefile-handle-properly) +// https://github.com/dotnet/runtime/blob/main/src/libraries/Common/src/Interop/OSX/Interop.CoreFoundation.CFDate.cs +// > SetHandle(IntPtr.Zero); +// https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Quic/src/System/Net/Quic/Internal/MsQuicSafeHandle.cs +// > SetHandle(IntPtr.Zero); diff --git a/Source/SharpLSL/Lsl.cs b/Source/SharpLSL/Lsl.cs deleted file mode 100644 index a299d53..0000000 --- a/Source/SharpLSL/Lsl.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -using SharpLSL.Interop; - -namespace SharpLSL -{ - public static class Lsl - { - public static string GetLastError() - { - var ptr = LSL.lsl_last_error(); - return Marshal.PtrToStringAnsi(ptr); - } - - public static int GetProtocolVersion() => LSL.lsl_protocol_version(); - - public static int GetLibraryVersion() => LSL.lsl_library_version(); - - public static string GetLibraryInfo() - { - var ptr = LSL.lsl_library_info(); - return Marshal.PtrToStringAnsi(ptr); - } - - public static double GetLocalClock() => LSL.lsl_local_clock(); - - public static void DestroyString(IntPtr str) => LSL.lsl_destroy_string(str); - } -} diff --git a/Source/SharpLSL/LslChannelFormat.cs b/Source/SharpLSL/LslChannelFormat.cs deleted file mode 100644 index 9866579..0000000 --- a/Source/SharpLSL/LslChannelFormat.cs +++ /dev/null @@ -1,16 +0,0 @@ -using SharpLSL.Interop; - -namespace SharpLSL -{ - public enum LslChannelFormat : int - { - Float = lsl_channel_format_t.cft_float32, - Double = lsl_channel_format_t.cft_double64, - String = lsl_channel_format_t.cft_string, - Int32 = lsl_channel_format_t.cft_int32, - Int16 = lsl_channel_format_t.cft_int16, - Int8 = lsl_channel_format_t.cft_int8, - Int64 = lsl_channel_format_t.cft_int64, - Undefined = lsl_channel_format_t.cft_undefined, - } -} diff --git a/Source/SharpLSL/LslException.cs b/Source/SharpLSL/LslException.cs deleted file mode 100644 index 80b9f7a..0000000 --- a/Source/SharpLSL/LslException.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; - -namespace SharpLSL -{ - public class LSLException : Exception - { - public LSLException() - { - } - - public LSLException(string message) - : base(message) - { - } - - public LSLException(string message, Exception innerException) - : base(message, innerException) - { - } - } -} diff --git a/Source/SharpLSL/LslObject.cs b/Source/SharpLSL/LslObject.cs deleted file mode 100644 index 5147e0d..0000000 --- a/Source/SharpLSL/LslObject.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -namespace SharpLSL -{ - public abstract class LslObject : SafeHandle - { - protected LslObject(IntPtr lslHandle, bool ownsHandle = true) - : base(IntPtr.Zero, ownsHandle) - { - if (lslHandle == IntPtr.Zero) - throw new LSLException($"Failed to create {GetType().Name} object: {Lsl.GetLastError()}."); - - SetHandle(lslHandle); - } - - public override bool IsInvalid => handle != IntPtr.Zero; - } -} diff --git a/Source/SharpLSL/LslProcessingOptions.cs b/Source/SharpLSL/LslProcessingOptions.cs deleted file mode 100644 index 1e61e7d..0000000 --- a/Source/SharpLSL/LslProcessingOptions.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; - -using SharpLSL.Interop; - -namespace SharpLSL -{ - [Flags] - public enum LslProcessingOptions - { - None = lsl_processing_options_t.proc_none, - ClockSync = lsl_processing_options_t.proc_clocksync, - Dejitter = lsl_processing_options_t.proc_dejitter, - Monotonize = lsl_processing_options_t.proc_monotonize, - ThreadSafe = lsl_processing_options_t.proc_threadsafe, - All = ClockSync | Dejitter | Monotonize | ThreadSafe, - } -} diff --git a/Source/SharpLSL/LslStreamInfo.cs b/Source/SharpLSL/LslStreamInfo.cs deleted file mode 100644 index b802cb2..0000000 --- a/Source/SharpLSL/LslStreamInfo.cs +++ /dev/null @@ -1,82 +0,0 @@ -using System; -using System.Runtime.InteropServices; - -using SharpLSL.Interop; - -namespace SharpLSL -{ - public class LslStreamInfo : LslObject - { - public LslStreamInfo( - string name, - string type, - int channelCount = 1, - double nominalSrate = LSL.LSL_IRREGULAR_RATE, - LslChannelFormat channelFormat = LslChannelFormat.Float, - string sourceId = "") - : this(CreateStreamInfo( - name, - type, - channelCount, - nominalSrate, - channelFormat, - sourceId), true) - { - } - - public LslStreamInfo(IntPtr handle, bool ownsHandle = true) - : base(handle, ownsHandle) - { - } - - protected override bool ReleaseHandle() - { - LSL.lsl_destroy_streaminfo(handle); - return true; - } - - private static IntPtr CreateStreamInfo( - string name, - string type, - int channelCount, - double nominalSrate, - LslChannelFormat channelFormat, - string sourceId) - { - var namePtr = IntPtr.Zero; - var typePtr = IntPtr.Zero; - var sourceIdPtr = IntPtr.Zero; - - try - { - namePtr = Marshal.StringToHGlobalAnsi(name); - typePtr = Marshal.StringToHGlobalAnsi(type); - sourceIdPtr = Marshal.StringToHGlobalAnsi(sourceId); - - return LSL.lsl_create_streaminfo( - namePtr, - typePtr, - channelCount, - nominalSrate, - (lsl_channel_format_t)channelFormat, - sourceIdPtr); - } - finally - { - if (namePtr != IntPtr.Zero) - Marshal.FreeHGlobal(namePtr); - - if (typePtr != IntPtr.Zero) - Marshal.FreeHGlobal(typePtr); - - if (sourceIdPtr != IntPtr.Zero) - Marshal.FreeHGlobal(sourceIdPtr); - } - } - } -} - - -// References: -// https://github.com/labstreaminglayer/liblsl-Csharp/blob/master/LSL.cs -// [make IntPtr in C#.NET point to string value](https://stackoverflow.com/questions/11090427/make-intptr-in-c-net-point-to-string-value) diff --git a/Source/SharpLSL/LslTransportOptions.cs b/Source/SharpLSL/LslTransportOptions.cs deleted file mode 100644 index 680ef6c..0000000 --- a/Source/SharpLSL/LslTransportOptions.cs +++ /dev/null @@ -1,11 +0,0 @@ -using SharpLSL.Interop; - -namespace SharpLSL -{ - public enum LslTransportOptions - { - Default = lsl_transport_options_t.transp_default, - BufferSizeSamples = lsl_transport_options_t.transp_bufsize_samples, - BufferSizeThousandths = lsl_transport_options_t.transp_bufsize_thousandths, - } -} diff --git a/Source/SharpLSL/PostProcessingOptions.cs b/Source/SharpLSL/PostProcessingOptions.cs new file mode 100644 index 0000000..f03845e --- /dev/null +++ b/Source/SharpLSL/PostProcessingOptions.cs @@ -0,0 +1,68 @@ +using System; + +using SharpLSL.Interop; + +namespace SharpLSL +{ + /// + /// Post-processing options for stream inlets. + /// + /// + [Flags] + public enum PostProcessingOptions + { + /// + /// No automatic post-processing is applied. + /// + /// + /// This option returns the ground-truth timestamps as they are, allowing + /// for manual post-processing. It is the default behavior of the inlet. + /// + None = lsl_processing_options_t.proc_none, + + /// + /// Performs clock synchronization automatically. + /// + /// + /// This option indicates a time correction to the timestamps will be performed + /// automatically. It is equivalent to manually adding the + /// value to the received timestamps. + /// + /// + /// + ClockSync = lsl_processing_options_t.proc_clocksync, + + /// + /// Removes jitter from timestamps. + /// + /// + /// This will apply a smoothing algorithm to the received timestamps; the + /// smoothing needs to see a minimum number of samples (30-120 seconds + /// worst-case) until the remaining jitter is consistently below 1ms. + /// + Dejitter = lsl_processing_options_t.proc_dejitter, + + /// + /// Forces the timestamps to be monotonically ascending. + /// + /// + /// This option only makes sense if timestamps are dejittered. + /// + Monotonize = lsl_processing_options_t.proc_monotonize, + + /// + /// Indicates post-processing is thread-safe. + /// + /// + /// This option ensures that post-processing is thread-safe, allowing multiple + /// threads to read from the same inlet concurrently. Note that this flag may + /// increase CPU usage. + /// + ThreadSafe = lsl_processing_options_t.proc_threadsafe, + + /// + /// The combination of all possible post-processing options. + /// + All = ClockSync | Dejitter | Monotonize | ThreadSafe, + } +} diff --git a/Source/SharpLSL/StreamInfo.cs b/Source/SharpLSL/StreamInfo.cs new file mode 100644 index 0000000..509a191 --- /dev/null +++ b/Source/SharpLSL/StreamInfo.cs @@ -0,0 +1,531 @@ +using System; + +using static SharpLSL.Interop.LSL; +using static SharpLSL.LSL; + +namespace SharpLSL +{ + /// + /// Represents a `lsl_streaminfo` object which keeps a stream's meta data and + /// connection settings. + /// + /// + /// Whenever a program wants to provide a new stream on the lab network it will + /// typically first create a object to describe its + /// properties and then construct a with it to create + /// the stream on the network. Recipients who discover the outlet can query the + /// ; it is also written to disk when recording the stream + /// (playing a similar role as a file header). + /// + public class StreamInfo : LSLObject + { + /// + /// Constructs a new instance of object. + /// + /// + /// The stream name. + /// + /// The stream name describes the device (or product series) that this stream + /// makes available (for use by programs, experimenters or data analysts). + /// The stream name cannot be empty. + /// + /// + /// + /// The content type of the stream. + /// + /// Please see https://github.com/sccn/xdf/wiki/Meta-Data for pre-defined + /// content-type names. Note that you can also make up your own stream type + /// The stream content type is the preferred way to find streams (as opposed + /// to searching stream by name). + /// + /// + /// + /// Number of channels per sample. + /// + /// This stays constant during the lifetime of the stream. + /// + /// + /// + /// The nominal sampling rate(in Hz) as advertised by the data source, + /// if regular(otherwise set to ). + /// + /// + /// The data format of each channel. + /// + /// If your channels have different formats, consider supplying multiple + /// streams or use the largest type that can hold them all(such as ). + /// + /// + /// + /// Unique identifier of the device or source of the data, if available + /// (such as the serial number). + /// + /// This is critical for system robustness since it allows recipients to + /// recover from failure even after the serving app, device or computer + /// crashes(just by finding a stream with the same source id on the network + /// again). Therefore, it is highly recommended to always try to provide + /// whatever information can uniquely identify the data source itself. + /// + /// + /// + /// Thrown when creating a new instance of fails. + /// + public StreamInfo( + string name, + string type, + int channelCount = 1, + double nominalSrate = IrregularRate, + ChannelFormat channelFormat = ChannelFormat.Float, + string sourceId = "") + : this(lsl_create_streaminfo( + name, + type, + channelCount, + nominalSrate, + channelFormat, + sourceId), true) + { + } + + /// + /// Constructs a new instance of object which wraps + /// a pre-existing `lsl_streaminfo` handle. + /// + /// + /// Specifies the handle to be wrapped. + /// + /// + /// Speciies whether the wrapped handle should be released during the finalization phase. + /// + /// + /// Thrown if the handle is invalid. + /// + public StreamInfo(IntPtr handle, bool ownsHandle = true) + : base(handle, ownsHandle) + { + } + + /// + /// Gets the name of the stream. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// + /// The stream name is a human-readable name. + /// + /// + /// For streams offered by device modules, it refers to the type of device + /// or product series that is generating the data of the stream. If the + /// source is an application, the name may be a more generic or specific + /// identifier. Multiple streams with the same name can coexist, though + /// potentially at the cost of ambiguity (for the recording app or experimenter). + /// + /// + public string Name + { + get + { + ThrowIfInvalid(); + return PtrToString(lsl_get_name(handle)); + } + } + + /// + /// Gets the content type of the stream. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// The content type is a short string such as "EEG", "Gaze" which describes + /// the content carried by the channel (if known). If a stream contains mixed + /// content this value need not be assigned but may instead be stored in the + /// description of channel types. To be useful to applications and automated + /// processing systems using the recommended content types is preferred. Content + /// types usually follow those pre-defined in the [wiki](https://github.com/sccn/xdf/wiki/Meta-Data). + /// + public string Type + { + get + { + ThrowIfInvalid(); + return PtrToString(lsl_get_type(handle)); + } + } + + /// + /// Gets number of channels of the stream. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// A stream has at least one channels, and the channel count stays constant + /// for all samples. + /// + public int ChannelCount + { + get + { + ThrowIfInvalid(); + return lsl_get_channel_count(handle); + } + } + + /// + /// Gets the nominal sampling rate (in Hz) announced by the data source. + /// If the stream is irregularly sampled, this should be set to . + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// Note that no data will be lost even if this sampling rate is incorrect + /// or if a device has temporary hiccups, since all samples will be recorded + /// anyway (except for those dropped by the device itself). However, when + /// the recording is imported into an application, a good importer may correct + /// such errors more accurately if the advertised sampling rate was close to + /// the specs of the device. + /// + public double NominalSrate + { + get + { + ThrowIfInvalid(); + return lsl_get_nominal_srate(handle); + } + } + + /// + /// Gets the channel format of the stream. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// All channels in a stream have the same format. However, a device might + /// offer multiple time-synched streams each with its own format. + /// + /// + public ChannelFormat ChannelFormat + { + get + { + ThrowIfInvalid(); + return (ChannelFormat)lsl_get_channel_format(handle); + } + } + + /// + /// Gets number of bytes occupied by a channel (0 for string-typed channels). + /// + /// + /// Thrown if this object is invalid. + /// + public int ChannelBytes + { + get + { + ThrowIfInvalid(); + return lsl_get_channel_bytes(handle); + } + } + + /// + /// Gets number of bytes occupied by a sample (0 for string-typed channels). + /// + /// + /// Thrown if this object is invalid. + /// + public int SampleBytes + { + get + { + ThrowIfInvalid(); + return lsl_get_sample_bytes(handle); + } + } + + /// + /// Gets the unique identifier of the stream's source, if available. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// The unique source (or device) identifier is an optional piece of + /// information that, if available, allows that endpoints (such as the + /// recording program) can re-acquire a stream automatically once it is + /// back online. + /// + public string SourceId + { + get + { + ThrowIfInvalid(); + return PtrToString(lsl_get_source_id(handle)); + } + } + + /// + /// Gets the protocol version used to deliver the stream. + /// + /// + /// Thrown if this object is invalid. + /// + /// + public int Version + { + get + { + ThrowIfInvalid(); + return lsl_get_version(handle); + } + } + + /// + /// Gets the creation timestamp of the stream. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// This is the time stamp when the stream was first created(as determined + /// via on the providing machine). + /// + public double CreatedAt + { + get + { + ThrowIfInvalid(); + return lsl_get_created_at(handle); + } + } + + /// + /// Gets the unique ID of the stream outlet (once assigned). + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// This is a unique identifier of the stream outlet, and is guaranteed + /// to be different across multiple instantiations of the same outlet + /// (e.g., after a re-start). + /// + public string Uid + { + get + { + ThrowIfInvalid(); + return PtrToString(lsl_get_uid(handle)); + } + } + + /// + /// Gets the session ID of the stream. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// The session id is an optional human-assigned identifier of the recording + /// session. While it is rarely used, it can be used to prevent concurrent + /// recording activitites on the same sub-network (e.g., in multiple experiment + /// areas) from seeing each other's streams (assigned via a configuration file + /// by the experimenter, see Network Connectivity in the LSL wiki). + /// + public string SessionId + { + get + { + ThrowIfInvalid(); + return PtrToString(lsl_get_session_id(handle)); + } + } + + /// + /// Gets the host name of providing machine (once bound to an outlet). + /// + /// + /// Thrown if this object is invalid. + /// + public string HostName + { + get + { + ThrowIfInvalid(); + return PtrToString(lsl_get_hostname(handle)); + } + } + + /// + /// Gets the extended description of the stream. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// Thrown if getting extended description fails. + /// + /// + /// + /// The extended description may contain extra meta-data of the stream, such as: + /// channel labels, amplifier settings, measurement units, setup information, + /// subject information, etc. + /// + /// + /// Meta-data recommendations follow the XDF file format project. See: + /// https://github.com/sccn/xdf/wiki/Meta-Data for more details. + /// + /// + /// If you use a stream content type for which meta-data recommendations + /// exist, please try to lay out your meta-data in agreement with these + /// recommendations for compatibility with other applications. + /// + /// + /// + public XMLElement Description + { + get + { + ThrowIfInvalid(); + // TODO: memory free? + return new XMLElement(lsl_get_desc(handle)); + } + } + + /// + /// Creates a copy of the current instance. + /// + /// + /// A new instance of that is a copy of the current instance. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// Thrown when creating a copy of current instance of fails. + /// + /// + /// This method is rarely used. + /// + public StreamInfo Clone() + { + ThrowIfInvalid(); + return new StreamInfo(lsl_copy_streaminfo(handle), true); + } + + /// + /// Tests whether current object matches the given + /// query string. + /// + /// The query string. + /// Whether stream info is matched by the query string. + /// + /// Thrown if this object is invalid. + /// + /// + /// Thrown if the query string is null or empty. + /// + /// + /// The represents an XPath 1.0 query string. + /// Here are some examples: + /// + /// channel_count>5 and type='EEG' + /// type='TestStream' or contains(name,'Brain') + /// name='ExampleStream' + /// + /// + public bool MatchesQuery(string query) + { + ThrowIfInvalid(); + + if (string.IsNullOrEmpty(query)) + throw new ArgumentException(nameof(query)); + + return Convert.ToBoolean(lsl_stream_info_matches_query(handle, query)); + } + + /// + /// Retrieves the entire in XML format. + /// + /// + /// The XML representation of the object as a string. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// Thrown when retrieving the entire in XML format fails. + /// + /// + /// This yields an XML document (in string form) whose top-level element is + /// ``. element contains one element for each field of the streaminfo + /// class, including: + /// - the core elements ``, ``, ``, ``, + /// ``, `` + /// - the misc elements ``, ``, ``, ``, + /// ``, ``, ``, ``, + /// ``, `` + /// - the extended description element `` with user-defined sub-elements. + /// + /// + /// + public string ToXML() + { + ThrowIfInvalid(); + + var xml = lsl_get_xml(handle); + if (xml != IntPtr.Zero) + { + var xmlString = PtrToXmlString(xml); + lsl_destroy_string(xml); + return xmlString; + } + + return null; + } + + /// + /// Constructs a new instance of object from an + /// XML representation. + /// + /// + /// The XML representation of the object as a string. + /// + /// + /// A new instance of corresponding to the XML + /// representation. + /// + /// + /// Thrown if the is null or empty. + /// + /// + /// Thrown when creating a new instance of fails. + /// + /// + public static StreamInfo FromXML(string xml) + { + if (string.IsNullOrEmpty(xml)) + throw new ArgumentException(nameof(xml)); + + return new StreamInfo(lsl_streaminfo_from_xml(xml), true); + } + + /// + /// Destroys the underlying `lsl_streaminfo` handle associated with this instance. + /// + protected override void DestroyLSLObject() + { + lsl_destroy_streaminfo(handle); + } + } +} + + +// References: +// https://github.com/labstreaminglayer/liblsl-Csharp/blob/master/LSL.cs +// [make IntPtr in C#.NET point to string value](https://stackoverflow.com/questions/11090427/make-intptr-in-c-net-point-to-string-value) diff --git a/Source/SharpLSL/StreamInlet.cs b/Source/SharpLSL/StreamInlet.cs new file mode 100644 index 0000000..5e753a9 --- /dev/null +++ b/Source/SharpLSL/StreamInlet.cs @@ -0,0 +1,816 @@ +using System; +using System.Collections.Generic; + +using SharpLSL.Interop; + +using static SharpLSL.Interop.LSL; +using static SharpLSL.LSL; + +namespace SharpLSL +{ + /// + /// Represents a stream inlet object for receiving streaming data (and meta-data) + /// from the lab network. + /// + public class StreamInlet : LSLObject + { + /// + /// Constructs a new stream inlet from a resolved stream info. + /// + /// + /// + /// A resolved object (as coming from one of the + /// resolver functions). + /// + /// + /// The inlet will makes a copy of the object + /// at its construction. + /// + /// + /// The may also be constructed with a fully specified + /// , if the desired channel format and count is already + /// known up-front, but this is strongly discouraged and should only ever be done + /// if there is no time to resolve the stream up-front (e.g., due to limitations + /// in the client program). + /// + /// + /// + /// + /// Specifies the maximum amount of the data to buffer (in seconds if there + /// is a nominal sampling rate, otherwise x100 in samples). + /// + /// + /// Recording applications want to use a fairly large buffer size here, while + /// real-time applications would only buffer as much as they need to perform + /// their next calculation. A good default is 360, which corresponds to 6 + /// minutes of data. + /// + /// + /// + /// Specifies the maximum size, in samples, at which chunks are transmitted. + /// If set as 0, the chunk sizes preferred by the sender are used. Recording + /// applications can use a generous size here (leaving it to the network how + /// to pack things), while real time applications may want a finer (perhaps + /// 1-sample) granularity. + /// + /// + /// Specifies whether try to silently recover lost streams that are recoverable + /// (those that have a source id set). In all other cases (recover is false or + /// the stream is not recoverable) functions may throw a + /// if the stream's source is lost (e.g., due to an app or computer crash). + /// + /// + /// Specifies the transport options. + /// + /// + /// Thrown when creating a new instance of fails. + /// + public StreamInlet( + StreamInfo streamInfo, int maxBufferLength = 360, int maxChunkLength = 0, + bool recover = true, TransportOptions transportOptions = TransportOptions.Default) + : base(lsl_create_inlet_ex( + streamInfo.DangerousGetHandle(), + maxBufferLength, maxChunkLength, + recover ? 1 : 0, + (lsl_transport_options_t)transportOptions)) + { + ChannelCount = streamInfo.ChannelCount; + } + + /// + /// Constructs a new instance of object which wraps + /// a pre-existing stream inlet handle. + /// + /// + /// Specifies the handle to be wrapped. + /// + /// + /// Speciies whether the wrapped handle should be released during the finalization phase. + /// + /// + /// Thrown if the handle is invalid. + /// + public StreamInlet(IntPtr handle, bool ownsHandle = true) + : base(handle, ownsHandle) + { + var streamInfo = GetStreamInfo(); + ChannelCount = streamInfo.ChannelCount; + } + + /// + /// Gets number of channels of the stream. + /// + public int ChannelCount { get; } + + /// + /// Retrieves the complete information of the given stream, including the + /// extended description. + /// + /// + /// Specifies the timeout of the operation in seconds. Default value is + /// which indicates no timeout. + /// + /// + /// An instance of containing detailed information + /// about the stream, including its extended description. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// Thrown if the stream source has been lost. + /// + /// + /// Thrown if the timeout has expired. + /// + /// + /// This method can be invoked at any time of the stream's lifetime. + /// + public StreamInfo GetStreamInfo(double timeout = Forever) + { + ThrowIfInvalid(); + + var errorCode = (int)lsl_error_code_t.lsl_no_error; + var streamInfo = lsl_get_fullinfo(handle, timeout, ref errorCode); +#if false + if (streamInfo != IntPtr.Zero) + return new StreamInfo(streamInfo, true); + + CheckError(errorCode); + // [Is there something like [[noreturn]] in C# to indicate the compiler that the method will never return a value?](https://stackoverflow.com/questions/48232105/is-there-something-like-noreturn-in-c-sharp-to-indicate-the-compiler-that-th) +#else + CheckError(errorCode); + return new StreamInfo(streamInfo, true); +#endif + } + + /// + /// Subscribes to the data stream. + /// + /// + /// Specifies the timeout of the operation in seconds. Default value is + /// which indicates no timeout. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// Thrown if the stream source has been lost. + /// + /// + /// Thrown if the timeout has expired. + /// + /// + /// All samples pushed in at the other end from this moment onwards will be + /// queued and eventually be delivered in response to PullSample() or + /// PullChunk() calls. Pulling a sample without some preceding OpenStream() + /// is permitted (the stream will then be opened implicitly). + /// + public void OpenStream(double timeout = Forever) + { + ThrowIfInvalid(); + + var errorCode = (int)lsl_error_code_t.lsl_no_error; + lsl_open_stream(handle, timeout, ref errorCode); + CheckError(errorCode); + } + + /// + /// Drops the current data stream. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// All samples that are still buffered or in flight will be dropped and + /// transmission and buffering of data for this inlet will be stopped. If + /// an application stops being interested in data from a source (temporarily + /// or not) but keeps the outlet alive, it should call CloseStream() to not + /// waste unnecessary system and network resources. + /// + public void CloseStream() + { + ThrowIfInvalid(); + + lsl_close_stream(handle); + } + + /// + /// Retrieves an estimated time correction offset for the given stream. + /// + /// + /// Specifies the timeout to acquire the first time correction estimate + /// in seconds. Default value is which indicates + /// no timeout. + /// + /// + /// The time correction estimate. This is the number that needs to be added + /// to a timestamp that was remotely generated via + /// to map it into the local clock domain of this machine. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// Thrown if the stream source has been lost. + /// + /// + /// Thrown if the timeout has expired. + /// + /// + /// + /// The first call to this function takes several milliseconds until a reliable + /// first estimate is obtained. Subsequent calls are instantaneous (and rely + /// on periodic background updates). On a well-behaved network, the precision + /// of these estimates should be below 1 ms(empirically it is within +/-0.2 ms). + /// + /// + /// To get a measure of whether the network is well-behaved, use the extended + /// version and + /// check uncertainty (i.e. the round-trip-time). + /// + /// + /// 0.2 ms is typical of wired networks. 2 ms is typical of wireless networks. + /// The number can be much higher on poor networks. + /// + /// + /// + public double TimeCorrection(double timeout = Forever) + { + ThrowIfInvalid(); + + var errorCode = (int)lsl_error_code_t.lsl_no_error; + var result = lsl_time_correction(handle, timeout, ref errorCode); + CheckError(errorCode); + return result; + } + + /// + /// Retrieves an estimated time correction offset for the given stream. + /// + /// + /// The current time of the remote computer that was used to generate this + /// time correction. If desired, the client can fit time correction vs remote + /// time to improve the real-time time correction further. + /// + /// + /// The maximum uncertainty of the given time correction. + /// + /// + /// Specifies the timeout to acquire the first time correction estimate + /// in seconds. Default value is which indicates + /// no timeout. + /// + /// + /// The time correction estimate. This is the number that needs to be added + /// to a timestamp that was remotely generated via + /// to map it into the local clock domain of this machine. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// Thrown if the stream source has been lost. + /// + /// + /// Thrown if the timeout has expired. + /// + /// + public double TimeCorrection(ref double remoteTime, ref double uncertainty, double timeout = Forever) + { + ThrowIfInvalid(); + + var errorCode = (int)lsl_error_code_t.lsl_no_error; + var result = lsl_time_correction_ex(handle, ref remoteTime, ref uncertainty, timeout, ref errorCode); + CheckError(errorCode); + return result; + } + + /// + /// Sets post-processing flags to use. + /// + /// + /// The post-processing flags to use, this is the result of bitwise OR'ing + /// one or more options from together; + /// a good setting is to use . + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// Thrown if the specified post-processing flags are invalid. + /// + /// + /// + /// By default, the inlet performs NO post-processing and returns the ground + /// truth timestamps, which can then be manually synchronized using , + /// and then smoothed/dejittered if desired. + /// + /// + /// This function allows automating these two and possibly more operations. + /// + /// + /// When you enable this, you will no longer receive or be able to recover + /// the original timestamps. + /// + /// + public void SetPostProcessingOptions( + PostProcessingOptions postProcessingOptions = PostProcessingOptions.All) + { + ThrowIfInvalid(); + + CheckError(lsl_set_postprocessing(handle, (uint)postProcessingOptions)); + } + + /// + /// Pulls a sample from the inlet and read it into an array of values. Handles + /// type checking & conversion if necessary. + /// + /// + /// The buffer to hold the resulting values. + /// + /// + /// Specifies the timeout of the operation in seconds, if any. Default value + /// is which indicates no timeout. Use 0.0 to make a + /// non-blocking call, in this case a sample is only returned if one is + /// currently buffered. + /// + /// + /// The capture time of the sample on the remote machine. Returns 0.0 if no + /// new sample was available or the timeout expires. To remap this timestamp + /// to the local clock, add the value returned by + /// to it. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// Thrown if the provided buffer is null. + /// + /// + /// Thrown if size of the provided buffer does not + /// the channel count () of the stream inlet. + /// + /// + /// Thrown if the stream source has been lost. + /// + /// + /// If the timeout expires before a new sample was received, the function + /// returns 0.0; this case is not considered an error condition. + /// + public double PullSample(byte[] sample, double timeout = Forever) + { + ThrowIfInvalid(); + CheckSampleBuffer(sample, ChannelCount); + + var errorCode = (int)lsl_error_code_t.lsl_no_error; + var timestamp = lsl_pull_sample_c(handle, sample, sample.Length, timeout, ref errorCode); + CheckError(errorCode); + return timestamp; + } + + /// + /// Pulls a sample from the inlet and read it into an array of values. Handles + /// type checking & conversion if necessary. + /// + /// + /// The buffer to hold the resulting values. + /// + /// + /// Specifies the timeout of the operation in seconds, if any. Default value + /// is which indicates no timeout. Use 0.0 to make a + /// non-blocking call, in this case a sample is only returned if one is + /// currently buffered. + /// + /// + /// The capture time of the sample on the remote machine. Returns 0.0 if no + /// new sample was available or the timeout expires. To remap this timestamp + /// to the local clock, add the value returned by + /// to it. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// Thrown if the provided buffer is null. + /// + /// + /// Thrown if size of the provided buffer does not + /// the channel count () of the stream inlet. + /// + /// + /// Thrown if the stream source has been lost. + /// + /// + /// If the timeout expires before a new sample was received, the function + /// returns 0.0; this case is not considered an error condition. + /// + public double PullSample(sbyte[] sample, double timeout = Forever) + { + ThrowIfInvalid(); + CheckSampleBuffer(sample, ChannelCount); + + var errorCode = (int)lsl_error_code_t.lsl_no_error; + var timestamp = lsl_pull_sample_c(handle, sample, sample.Length, timeout, ref errorCode); + CheckError(errorCode); + return timestamp; + } + + /// + /// Pulls a sample from the inlet and read it into an array of values. Handles + /// type checking & conversion if necessary. + /// + /// + /// The buffer to hold the resulting values. + /// + /// + /// Specifies the timeout of the operation in seconds, if any. Default value + /// is which indicates no timeout. Use 0.0 to make a + /// non-blocking call, in this case a sample is only returned if one is + /// currently buffered. + /// + /// + /// The capture time of the sample on the remote machine. Returns 0.0 if no + /// new sample was available or the timeout expires. To remap this timestamp + /// to the local clock, add the value returned by + /// to it. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// Thrown if the provided buffer is null. + /// + /// + /// Thrown if size of the provided buffer does not + /// the channel count () of the stream inlet. + /// + /// + /// Thrown if the stream source has been lost. + /// + /// + /// If the timeout expires before a new sample was received, the function + /// returns 0.0; this case is not considered an error condition. + /// + public double PullSample(short[] sample, double timeout = Forever) + { + ThrowIfInvalid(); + CheckSampleBuffer(sample, ChannelCount); + + var errorCode = (int)lsl_error_code_t.lsl_no_error; + var timestamp = lsl_pull_sample_s(handle, sample, sample.Length, timeout, ref errorCode); + CheckError(errorCode); + return timestamp; + } + + /// + /// Pulls a sample from the inlet and read it into an array of values. Handles + /// type checking & conversion if necessary. + /// + /// + /// The buffer to hold the resulting values. + /// + /// + /// Specifies the timeout of the operation in seconds, if any. Default value + /// is which indicates no timeout. Use 0.0 to make a + /// non-blocking call, in this case a sample is only returned if one is + /// currently buffered. + /// + /// + /// The capture time of the sample on the remote machine. Returns 0.0 if no + /// new sample was available or the timeout expires. To remap this timestamp + /// to the local clock, add the value returned by + /// to it. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// Thrown if the provided buffer is null. + /// + /// + /// Thrown if size of the provided buffer does not + /// the channel count () of the stream inlet. + /// + /// + /// Thrown if the stream source has been lost. + /// + /// + /// If the timeout expires before a new sample was received, the function + /// returns 0.0; this case is not considered an error condition. + /// + public double PullSample(int[] sample, double timeout = Forever) + { + ThrowIfInvalid(); + CheckSampleBuffer(sample, ChannelCount); + + var errorCode = (int)lsl_error_code_t.lsl_no_error; + var timestamp = lsl_pull_sample_i(handle, sample, sample.Length, timeout, ref errorCode); + CheckError(errorCode); + return timestamp; + } + + /// + /// Pulls a sample from the inlet and read it into an array of values. Handles + /// type checking & conversion if necessary. + /// + /// + /// The buffer to hold the resulting values. + /// + /// + /// Specifies the timeout of the operation in seconds, if any. Default value + /// is which indicates no timeout. Use 0.0 to make a + /// non-blocking call, in this case a sample is only returned if one is + /// currently buffered. + /// + /// + /// The capture time of the sample on the remote machine. Returns 0.0 if no + /// new sample was available or the timeout expires. To remap this timestamp + /// to the local clock, add the value returned by + /// to it. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// Thrown if the provided buffer is null. + /// + /// + /// Thrown if size of the provided buffer does not + /// the channel count () of the stream inlet. + /// + /// + /// Thrown if the stream source has been lost. + /// + /// + /// If the timeout expires before a new sample was received, the function + /// returns 0.0; this case is not considered an error condition. + /// + public double PullSample(long[] sample, double timeout = Forever) + { + ThrowIfInvalid(); + CheckSampleBuffer(sample, ChannelCount); + + var errorCode = (int)lsl_error_code_t.lsl_no_error; + var timestamp = lsl_pull_sample_l(handle, sample, sample.Length, timeout, ref errorCode); + CheckError(errorCode); + return timestamp; + } + + /// + /// Pulls a sample from the inlet and read it into an array of values. Handles + /// type checking & conversion if necessary. + /// + /// + /// The buffer to hold the resulting values. + /// + /// + /// Specifies the timeout of the operation in seconds, if any. Default value + /// is which indicates no timeout. Use 0.0 to make a + /// non-blocking call, in this case a sample is only returned if one is + /// currently buffered. + /// + /// + /// The capture time of the sample on the remote machine. Returns 0.0 if no + /// new sample was available or the timeout expires. To remap this timestamp + /// to the local clock, add the value returned by + /// to it. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// Thrown if the provided buffer is null. + /// + /// + /// Thrown if size of the provided buffer does not + /// the channel count () of the stream inlet. + /// + /// + /// Thrown if the stream source has been lost. + /// + /// + /// If the timeout expires before a new sample was received, the function + /// returns 0.0; this case is not considered an error condition. + /// + public double PullSample(float[] sample, double timeout = Forever) + { + ThrowIfInvalid(); + CheckSampleBuffer(sample, ChannelCount); + + var errorCode = (int)lsl_error_code_t.lsl_no_error; + var timestamp = lsl_pull_sample_f(handle, sample, sample.Length, timeout, ref errorCode); + CheckError(errorCode); + return timestamp; + } + + /// + /// Pulls a sample from the inlet and read it into an array of values. Handles + /// type checking & conversion if necessary. + /// + /// + /// The buffer to hold the resulting values. + /// + /// + /// Specifies the timeout of the operation in seconds, if any. Default value + /// is which indicates no timeout. Use 0.0 to make a + /// non-blocking call, in this case a sample is only returned if one is + /// currently buffered. + /// + /// + /// The capture time of the sample on the remote machine. Returns 0.0 if no + /// new sample was available or the timeout expires. To remap this timestamp + /// to the local clock, add the value returned by + /// to it. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// Thrown if the provided buffer is null. + /// + /// + /// Thrown if size of the provided buffer does not + /// the channel count () of the stream inlet. + /// + /// + /// Thrown if the stream source has been lost. + /// + /// + /// If the timeout expires before a new sample was received, the function + /// returns 0.0; this case is not considered an error condition. + /// + public double PullSample(double[] sample, double timeout = Forever) + { + ThrowIfInvalid(); + CheckSampleBuffer(sample, ChannelCount); + + var errorCode = (int)lsl_error_code_t.lsl_no_error; + var timestamp = lsl_pull_sample_d(handle, sample, sample.Length, timeout, ref errorCode); + CheckError(errorCode); + return timestamp; + } + + /// + /// Pulls a sample from the inlet and read it into an array of values. Handles + /// type checking & conversion if necessary. + /// + /// + /// The buffer to hold the resulting values. + /// + /// + /// Specifies the timeout of the operation in seconds, if any. Default value + /// is which indicates no timeout. Use 0.0 to make a + /// non-blocking call, in this case a sample is only returned if one is + /// currently buffered. + /// + /// + /// The capture time of the sample on the remote machine. Returns 0.0 if no + /// new sample was available or the timeout expires. To remap this timestamp + /// to the local clock, add the value returned by + /// to it. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// Thrown if the provided buffer is null. + /// + /// + /// Thrown if size of the provided buffer does not + /// the channel count () of the stream inlet. + /// + /// + /// Thrown if the stream source has been lost. + /// + /// + /// If the timeout expires before a new sample was received, the function + /// returns 0.0; this case is not considered an error condition. + /// + public double PullSample(string[] sample, double timeout = Forever) + { + ThrowIfInvalid(); + CheckSampleBuffer(sample, ChannelCount); + + var errorCode = (int)lsl_error_code_t.lsl_no_error; + var buffer = new IntPtr[ChannelCount]; + var timestamp = lsl_pull_sample_str(handle, buffer, buffer.Length, timeout, ref errorCode); + CheckError(errorCode); + + for (int i = 0; i < buffer.Length; ++i) + { + sample[i] = PtrToString(buffer[i]); + lsl_destroy_string(buffer[i]); + } + + return timestamp; + } + + public double PullSample(List[] sample, double timeout = Forever) + { + ThrowIfInvalid(); + CheckSampleBuffer(sample, ChannelCount); + + return 0.0; + } + + public uint PullChunk(short[] chunk, double[] timestamps, double timeout) + { + var errorCode = (int)lsl_error_code_t.lsl_no_error; + var result = lsl_pull_chunk_s(handle, chunk, timestamps, (uint)chunk.Length, (uint)timestamps.Length, timeout, ref errorCode); + CheckError(errorCode); + return result; + } + + public uint PullChunk(int[] chunk, double[] timestamps, double timeout) + { + var errorCode = (int)lsl_error_code_t.lsl_no_error; + var result = lsl_pull_chunk_i(handle, chunk, timestamps, (uint)chunk.Length, (uint)timestamps.Length, timeout, ref errorCode); + CheckError(errorCode); + return result; + } + + public uint PullChunk(long[] chunk, double[] timestamps, double timeout) + { + var errorCode = (int)lsl_error_code_t.lsl_no_error; + var result = lsl_pull_chunk_l(handle, chunk, timestamps, (uint)chunk.Length, (uint)timestamps.Length, timeout, ref errorCode); + CheckError(errorCode); + return result; + } + + public uint PullChunk(float[] chunk, double[] timestamps, double timeout) + { + var errorCode = (int)lsl_error_code_t.lsl_no_error; + var result = lsl_pull_chunk_f(handle, chunk, timestamps, (uint)chunk.Length, (uint)timestamps.Length, timeout, ref errorCode); + CheckError(errorCode); + return result; + } + + public uint PullChunk(double[] chunk, double[] timestamps, double timeout) + { + var errorCode = (int)lsl_error_code_t.lsl_no_error; + var result = lsl_pull_chunk_d(handle, chunk, timestamps, (uint)chunk.Length, (uint)timestamps.Length, timeout, ref errorCode); + CheckError(errorCode); + return result; + } + + public uint PullChunk(short[,] chunk, double[] timestamps, double timeout) + { + var errorCode = (int)lsl_error_code_t.lsl_no_error; + var result = lsl_pull_chunk_s(handle, chunk, timestamps, (uint)chunk.Length, (uint)timestamps.Length, timeout, ref errorCode); + CheckError(errorCode); + return result; + } + + public uint PullChunk(int[,] chunk, double[] timestamps, double timeout) + { + var errorCode = (int)lsl_error_code_t.lsl_no_error; + var result = lsl_pull_chunk_i(handle, chunk, timestamps, (uint)chunk.Length, (uint)timestamps.Length, timeout, ref errorCode); + CheckError(errorCode); + return result; + } + + public uint PullChunk(long[,] chunk, double[] timestamps, double timeout) + { + var errorCode = (int)lsl_error_code_t.lsl_no_error; + var result = lsl_pull_chunk_l(handle, chunk, timestamps, (uint)chunk.Length, (uint)timestamps.Length, timeout, ref errorCode); + CheckError(errorCode); + return result; + } + + public uint PullChunk(float[,] chunk, double[] timestamps, double timeout) + { + var errorCode = (int)lsl_error_code_t.lsl_no_error; + var result = lsl_pull_chunk_f(handle, chunk, timestamps, (uint)chunk.Length, (uint)timestamps.Length, timeout, ref errorCode); + CheckError(errorCode); + return result; + } + + public uint PullChunk(double[,] chunk, double[] timestamps, double timeout) + { + var errorCode = (int)lsl_error_code_t.lsl_no_error; + var result = lsl_pull_chunk_d(handle, chunk, timestamps, (uint)chunk.Length, (uint)timestamps.Length, timeout, ref errorCode); + CheckError(errorCode); + return result; + } + + public bool SamplesAvailable() => lsl_samples_available(handle) > 0; + + public uint Flush() => lsl_inlet_flush(handle); + + public uint WasClockReset() => lsl_was_clock_reset(handle); + + public void SetSmoothingHalfTime(float value) + { + CheckError(lsl_smoothing_halftime(handle, value)); + } + + /// + /// Disconnects the inlet and destroys the underlying native resource. + /// + protected override void DestroyLSLObject() + { + lsl_destroy_inlet(handle); + } + } +} diff --git a/Source/SharpLSL/StreamLostException.cs b/Source/SharpLSL/StreamLostException.cs new file mode 100644 index 0000000..232cc40 --- /dev/null +++ b/Source/SharpLSL/StreamLostException.cs @@ -0,0 +1,52 @@ +using System; + +namespace SharpLSL +{ + /// + /// Represents an error that occurs when a LSL stream is unexpectedly lost or disconnected. + /// + /// + /// This exception is thrown when an application attempts to access a LSL stream + /// that is no longer available. It indicates that the connection to the stream + /// has been lost, which may be due to network issues, stream termination, or other + /// unforeseen circumstances. This exception extends the base class + /// to provide specific information about stream loss errors. + /// + public class StreamLostException : LSLException + { + /// + /// Initializes a new instance of the class. + /// + public StreamLostException() + { + } + + /// + /// Initializes a new instance of the class with + /// a specified error message. + /// + /// + /// The error message that describes the reason for the exception. + /// + public StreamLostException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class with + /// a specified error message and a reference to the inner exception. + /// + /// + /// The error message that describes the reason for the exception. + /// + /// + /// The exception that is the cause of the current exception, or a null + /// reference if no inner exception is specified. + /// + public StreamLostException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/Source/SharpLSL/StreamOutlet.cs b/Source/SharpLSL/StreamOutlet.cs new file mode 100644 index 0000000..682e357 --- /dev/null +++ b/Source/SharpLSL/StreamOutlet.cs @@ -0,0 +1,461 @@ +using System; +using System.Runtime.CompilerServices; + +using SharpLSL.Interop; + +using static SharpLSL.Interop.LSL; +using static SharpLSL.LSL; + +namespace SharpLSL +{ + public class StreamOutlet : LSLObject + { + public StreamOutlet(StreamInfo streamInfo, int chunkSize = 0, int maxBuffered = 360, TransportOptions transportOptions = TransportOptions.Default) + : base(lsl_create_outlet_ex(streamInfo.DangerousGetHandle(), chunkSize, maxBuffered, (lsl_transport_options_t)transportOptions)) + { + channelCount_ = streamInfo.ChannelCount; + } + + public StreamOutlet(IntPtr handle, bool ownsHandle = true) + : base(handle, ownsHandle) + { + } + + public void PushSample(short[] sampleData) + { + CheckSampleData(sampleData); + CheckError(lsl_push_sample_s(handle, sampleData)); + } + + public void PushSample(int[] sampleData) + { + CheckSampleData(sampleData); + CheckError(lsl_push_sample_i(handle, sampleData)); + } + + public void PushSample(long[] sampleData) + { + CheckSampleData(sampleData); + CheckError(lsl_push_sample_l(handle, sampleData)); + } + + public void PushSample(float[] sampleData) + { + CheckSampleData(sampleData); + CheckError(lsl_push_sample_f(handle, sampleData)); + } + + public void PushSample(double[] sampleData) + { + CheckSampleData(sampleData); + CheckError(lsl_push_sample_d(handle, sampleData)); + } + + public void PushSample(short[] sampleData, double timestamp) + { + CheckSampleData(sampleData); + CheckError(lsl_push_sample_st(handle, sampleData, timestamp)); + } + + public void PushSample(int[] sampleData, double timestamp) + { + CheckSampleData(sampleData); + CheckError(lsl_push_sample_it(handle, sampleData, timestamp)); + } + + public void PushSample(long[] sampleData, double timestamp) + { + CheckSampleData(sampleData); + CheckError(lsl_push_sample_lt(handle, sampleData, timestamp)); + } + + public void PushSample(float[] sampleData, double timestamp) + { + CheckSampleData(sampleData); + CheckError(lsl_push_sample_ft(handle, sampleData, timestamp)); + } + + public void PushSample(double[] sampleData, double timestamp) + { + CheckSampleData(sampleData); + CheckError(lsl_push_sample_dt(handle, sampleData, timestamp)); + } + + public void PushSample(short[] sampleData, double timestamp, bool pushThrough) + { + CheckSampleData(sampleData); + CheckError(lsl_push_sample_stp(handle, sampleData, timestamp, pushThrough ? 1 : 0)); + } + + public void PushSample(int[] sampleData, double timestamp, bool pushThrough) + { + CheckSampleData(sampleData); + CheckError(lsl_push_sample_itp(handle, sampleData, timestamp, pushThrough ? 1 : 0)); + } + + public void PushSample(long[] sampleData, double timestamp, bool pushThrough) + { + CheckSampleData(sampleData); + CheckError(lsl_push_sample_ltp(handle, sampleData, timestamp, pushThrough ? 1 : 0)); + } + + public void PushSample(float[] sampleData, double timestamp, bool pushThrough) + { + CheckSampleData(sampleData); + CheckError(lsl_push_sample_ftp(handle, sampleData, timestamp, pushThrough ? 1 : 0)); + } + + public void PushSample(double[] sampleData, double timestamp, bool pushThrough) + { + CheckSampleData(sampleData); + CheckError(lsl_push_sample_dtp(handle, sampleData, timestamp, pushThrough ? 1 : 0)); + } + + public void PushChunk(short[] data) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_s(handle, data, (uint)data.Length)); + } + + public void PushChunk(int[] data) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_i(handle, data, (uint)data.Length)); + } + + public void PushChunk(long[] data) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_l(handle, data, (uint)data.Length)); + } + + public void PushChunk(float[] data) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_f(handle, data, (uint)data.Length)); + } + + public void PushChunk(double[] data) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_d(handle, data, (uint)data.Length)); + } + + public void PushChunk(short[,] data) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_s(handle, data, (uint)data.Length)); + } + + public void PushChunk(int[,] data) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_i(handle, data, (uint)data.Length)); + } + + public void PushChunk(long[,] data) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_l(handle, data, (uint)data.Length)); + } + + public void PushChunk(float[,] data) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_f(handle, data, (uint)data.Length)); + } + + public void PushChunk(double[,] data) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_d(handle, data, (uint)data.Length)); + } + + public void PushChunk(short[] data, double timestamp) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_st(handle, data, (uint)data.Length, timestamp)); + } + + public void PushChunk(int[] data, double timestamp) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_it(handle, data, (uint)data.Length, timestamp)); + } + + public void PushChunk(long[] data, double timestamp) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_lt(handle, data, (uint)data.Length, timestamp)); + } + + public void PushChunk(float[] data, double timestamp) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_ft(handle, data, (uint)data.Length, timestamp)); + } + + public void PushChunk(double[] data, double timestamp) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_dt(handle, data, (uint)data.Length, timestamp)); + } + + public void PushChunk(short[,] data, double timestamp) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_st(handle, data, (uint)data.Length, timestamp)); + } + + public void PushChunk(int[,] data, double timestamp) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_it(handle, data, (uint)data.Length, timestamp)); + } + + public void PushChunk(long[,] data, double timestamp) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_lt(handle, data, (uint)data.Length, timestamp)); + } + + public void PushChunk(float[,] data, double timestamp) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_ft(handle, data, (uint)data.Length, timestamp)); + } + + public void PushChunk(double[,] data, double timestamp) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_dt(handle, data, (uint)data.Length, timestamp)); + } + + public void PushChunk(short[] data, double timestamp, bool pushThrough) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_stp(handle, data, (uint)data.Length, timestamp, pushThrough ? 1 : 0)); + } + + public void PushChunk(int[] data, double timestamp, bool pushThrough) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_itp(handle, data, (uint)data.Length, timestamp, pushThrough ? 1 : 0)); + } + + public void PushChunk(long[] data, double timestamp, bool pushThrough) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_ltp(handle, data, (uint)data.Length, timestamp, pushThrough ? 1 : 0)); + } + + public void PushChunk(float[] data, double timestamp, bool pushThrough) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_ftp(handle, data, (uint)data.Length, timestamp, pushThrough ? 1 : 0)); + } + + public void PushChunk(double[] data, double timestamp, bool pushThrough) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_dtp(handle, data, (uint)data.Length, timestamp, pushThrough ? 1 : 0)); + } + + public void PushChunk(short[,] data, double timestamp, bool pushThrough) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_stp(handle, data, (uint)data.Length, timestamp, pushThrough ? 1 : 0)); + } + + public void PushChunk(int[,] data, double timestamp, bool pushThrough) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_itp(handle, data, (uint)data.Length, timestamp, pushThrough ? 1 : 0)); + } + + public void PushChunk(long[,] data, double timestamp, bool pushThrough) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_ltp(handle, data, (uint)data.Length, timestamp, pushThrough ? 1 : 0)); + } + + public void PushChunk(float[,] data, double timestamp, bool pushThrough) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_ftp(handle, data, (uint)data.Length, timestamp, pushThrough ? 1 : 0)); + } + + public void PushChunk(double[,] data, double timestamp, bool pushThrough) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_dtp(handle, data, (uint)data.Length, timestamp, pushThrough ? 1 : 0)); + } + + public void PushChunk(short[] data, double[] timestamp) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_stn(handle, data, (uint)data.Length, timestamp)); + } + + public void PushChunk(int[] data, double[] timestamp) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_itn(handle, data, (uint)data.Length, timestamp)); + } + + public void PushChunk(long[] data, double[] timestamp) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_ltn(handle, data, (uint)data.Length, timestamp)); + } + + public void PushChunk(float[] data, double[] timestamp) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_ftn(handle, data, (uint)data.Length, timestamp)); + } + + public void PushChunk(double[] data, double[] timestamp) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_dtn(handle, data, (uint)data.Length, timestamp)); + } + + public void PushChunk(short[,] data, double[] timestamp) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_stn(handle, data, (uint)data.Length, timestamp)); + } + + public void PushChunk(int[,] data, double[] timestamp) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_itn(handle, data, (uint)data.Length, timestamp)); + } + + public void PushChunk(long[,] data, double[] timestamp) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_ltn(handle, data, (uint)data.Length, timestamp)); + } + + public void PushChunk(float[,] data, double[] timestamp) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_ftn(handle, data, (uint)data.Length, timestamp)); + } + + public void PushChunk(double[,] data, double[] timestamp) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_dtn(handle, data, (uint)data.Length, timestamp)); + } + + public void PushChunk(short[] data, double[] timestamp, bool pushThrough) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_stnp(handle, data, (uint)data.Length, timestamp, pushThrough ? 1 : 0)); + } + + public void PushChunk(int[] data, double[] timestamp, bool pushThrough) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_itnp(handle, data, (uint)data.Length, timestamp, pushThrough ? 1 : 0)); + } + + public void PushChunk(long[] data, double[] timestamp, bool pushThrough) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_ltnp(handle, data, (uint)data.Length, timestamp, pushThrough ? 1 : 0)); + } + + public void PushChunk(float[] data, double[] timestamp, bool pushThrough) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_ftnp(handle, data, (uint)data.Length, timestamp, pushThrough ? 1 : 0)); + } + + public void PushChunk(double[] data, double[] timestamp, bool pushThrough) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_dtnp(handle, data, (uint)data.Length, timestamp, pushThrough ? 1 : 0)); + } + + public void PushChunk(short[,] data, double[] timestamp, bool pushThrough) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_stnp(handle, data, (uint)data.Length, timestamp, pushThrough ? 1 : 0)); + } + + public void PushChunk(int[,] data, double[] timestamp, bool pushThrough) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_itnp(handle, data, (uint)data.Length, timestamp, pushThrough ? 1 : 0)); + } + + public void PushChunk(long[,] data, double[] timestamp, bool pushThrough) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_ltnp(handle, data, (uint)data.Length, timestamp, pushThrough ? 1 : 0)); + } + + public void PushChunk(float[,] data, double[] timestamp, bool pushThrough) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_ftnp(handle, data, (uint)data.Length, timestamp, pushThrough ? 1 : 0)); + } + + public void PushChunk(double[,] data, double[] timestamp, bool pushThrough) + { + CheckChunkData(data); + CheckError(lsl_push_chunk_dtnp(handle, data, (uint)data.Length, timestamp, pushThrough ? 1 : 0)); + } + + public bool HaveConsumers() => lsl_have_consumers(handle) > 0; + + public bool WaitForConsumers(double timeout) => lsl_wait_for_consumers(handle, timeout) > 0; + + protected override void DestroyLSLObject() + { + lsl_destroy_outlet(handle); + } + +#if !NET35 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + private void CheckSampleData(T[] sampleData) => + CheckSampleBuffer(sampleData, channelCount_); + +#if !NET35 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + private void CheckChunkData(T[] chunkData) + { + if (chunkData == null) + throw new ArgumentNullException(nameof(chunkData)); + + if (chunkData.Length == 0 || chunkData.Length % channelCount_ != 0) + throw new ArgumentException(nameof(chunkData)); + } + +#if !NET35 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + private void CheckChunkData(T[,] chunkData) + { + if (chunkData == null) + throw new ArgumentNullException(nameof(chunkData)); + + if (chunkData.GetLength(0) == 0 || chunkData.GetLength(1) != channelCount_) + throw new ArgumentException(nameof(chunkData)); + } + +#if !NET35 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + private void CheckChannelCount(int channelCount) => + LSL.CheckChannelCount(channelCount_, channelCount); + + private readonly int channelCount_; + } +} diff --git a/Source/SharpLSL/TransportOptions.cs b/Source/SharpLSL/TransportOptions.cs new file mode 100644 index 0000000..b8b9aca --- /dev/null +++ b/Source/SharpLSL/TransportOptions.cs @@ -0,0 +1,31 @@ +using System; + +using SharpLSL.Interop; + +namespace SharpLSL +{ + /// + /// Specifies the transport options for stream inlets and outlets. + /// + [Flags] + public enum TransportOptions + { + // TODO: argument name & see also + /// + /// Default behavior: `max_buffered` / `max_buflen` is interpreted as time in seconds, and asynchronous transfer is used. + /// + Default = lsl_transport_options_t.transp_default, + + // TODO: argument name & see also + /// + /// Specifies the supplied `max_buf` value is in samples. + /// + BufferSizeInSamples = lsl_transport_options_t.transp_bufsize_samples, + + // TODO: argument name & see also + /// + /// Specifies the supplied `max_buf` value should be scaled by 0.001. + /// + BufferSizeThousandths = lsl_transport_options_t.transp_bufsize_thousandths, + } +} diff --git a/Source/SharpLSL/XMLElement.cs b/Source/SharpLSL/XMLElement.cs new file mode 100644 index 0000000..3c49b9c --- /dev/null +++ b/Source/SharpLSL/XMLElement.cs @@ -0,0 +1,578 @@ +using System; +using System.Diagnostics; + +using static SharpLSL.Interop.LSL; +using static SharpLSL.LSL; + +namespace SharpLSL +{ + /// + /// Represents a lightweight XML element tree, modeling the + /// field of . + /// + /// + /// Each element has a name and can contain multiple named child elements or + /// have text content as its value; attributes are omitted. The interface is + /// modeled after a subset of pugixml's node type and is compatible with it. + /// See https://pugixml.org/docs/manual.html#access for more details. + /// + public class XMLElement // TODO: IEnumerable, ToString, C# XML libraries + { + /// + /// Constructs a new instance of the class which + /// wraps a pre-existing native XML node handle. + /// + /// + /// Specifies the handle to be wrapped. + /// + internal XMLElement(IntPtr handle) + { + handle_ = handle; + } + + /// + /// Gets a value indicating whether the element is null. + /// + /// + public bool IsNull => handle_ == IntPtr.Zero; + + /// + /// Gets a value indicating whether the element is empty. + /// + /// + /// Thrown when the handle is invalid. + /// + public bool IsEmpty + { + get + { + ThrowIfInvalid(); + return Convert.ToBoolean(lsl_empty(handle_)); + } + } + + /// + /// Gets a value indicating whether this element is a text body (instead of + /// an XML element). True both for plain char data and CData. + /// + /// + /// Thrown when the handle is invalid. + /// + public bool IsText + { + get + { + ThrowIfInvalid(); + return Convert.ToBoolean(lsl_is_text(handle_)); + } + } + + /// + /// Gets or sets the name of the element. + /// + /// + /// Thrown when the handle is invalid. + /// + /// + /// Thrown if setting name of the element fails. + /// + public string Name + { + get + { + ThrowIfInvalid(); + + unsafe + { + return PtrToXmlString((IntPtr)lsl_name(handle_)); + } + } + + set + { + ThrowIfInvalid(); + + var result = Convert.ToBoolean(lsl_set_name(handle_, value)); + if (!result) + throw new LSLException($"Failed to set name of element to {value}."); + } + } + + /// + /// Gets or sets the value of the element. + /// + /// + /// Thrown when the handle is invalid. + /// + /// + /// Thrown if setting value of the element fails. + /// + public string Value + { + get + { + ThrowIfInvalid(); + + unsafe + { + return PtrToXmlString((IntPtr)lsl_value(handle_)); + } + } + + set + { + ThrowIfInvalid(); + + var result = Convert.ToBoolean(lsl_set_value(handle_, value)); + if (!result) + throw new LSLException($"Failed to set value of element to {value}."); + } + } + + /// + /// Gets the parent node of the element. + /// + /// + /// The parent node of the element, or if the parent node + /// doesn't exist. + /// + /// + /// Thrown if current element is invalid. + /// + public XMLElement GetParent() + { + ThrowIfInvalid(); + + var parent = lsl_parent(handle_); + if (parent != IntPtr.Zero) + return new XMLElement(parent); + + return Null; + } + + /// + /// Finds the first child of the element. + /// + /// + /// The first child of the element, or if the element has + /// no children. + /// + /// + /// Thrown if current element is invalid. + /// + public XMLElement FindFirstChild() + { + ThrowIfInvalid(); + + var node = lsl_first_child(handle_); + if (node != IntPtr.Zero) + return new XMLElement(node); + + return Null; + } + + /// + /// Finds the last child of the element. + /// + /// + /// The last child of the element, or if the element has + /// no children. + /// + /// + /// Thrown if current element is invalid. + /// + public XMLElement FindLastChild() + { + ThrowIfInvalid(); + + var node = lsl_last_child(handle_); + if (node != IntPtr.Zero) + return new XMLElement(node); + + return Null; + } + + /// + /// Finds a child of the element with the specified name. + /// + /// The name of the child. + /// + /// A child of the element with the specified name, or + /// if no such child exists. + /// + /// + /// Thrown if current element is invalid. + /// + public XMLElement FindChild(string name) + { + ThrowIfInvalid(); + + var node = lsl_child(handle_, name); + if (node != IntPtr.Zero) + return new XMLElement(node); + + return Null; + } + + /// + /// Gets the value of the first child that is text. + /// + /// + /// The value of the first child that is text, or null if no such child exists. + /// + /// + /// Thrown if current element is invalid. + /// + public string GetChildValue() + { + ThrowIfInvalid(); + + unsafe + { + var str = (IntPtr)lsl_child_value(handle_); + if (str != IntPtr.Zero) // TODO: + return PtrToXmlString(str); + } + + return null; + } + + /// + /// Gets the value of the first child element with the specified name that + /// contains text. + /// + /// The name of the child. + /// + /// The value of the first child that is text, or null if no such child exists. + /// + /// + /// Thrown if current element is invalid. + /// + public string GetChildValue(string name) + { + ThrowIfInvalid(); + + var str = lsl_child_value_n(handle_, name); + if (str != IntPtr.Zero) // TODO: + return PtrToXmlString(str); + + return null; + } + + /// + /// Sets the text value of the (nameless) plain-text child of a named child node. + /// + /// The name of the child. + /// The value of the child. + /// + /// Thrown if current element is invalid. + /// + /// + /// Thrown if setting child value fails. + /// + public void SetChildValue(string name, string value) + { + ThrowIfInvalid(); + + var result = Convert.ToBoolean(lsl_set_child_value(handle_, name, value)); + if (!result) + throw new LSLException($"Failed to set child value: {name}={value}."); + } + + /// + /// Finds the next sibling of the element. + /// + /// + /// The next sibling of the element, or if the element + /// is the last node in the list. + /// + /// + /// Thrown if current element is invalid. + /// + public XMLElement FindNextSibling() + { + ThrowIfInvalid(); + + var node = lsl_next_sibling(handle_); + if (node != IntPtr.Zero) + return new XMLElement(node); + + return Null; + } + + /// + /// Finds the next sibling of the element with the specified name. + /// + /// The name of the sibling. + /// + /// The next sibling of the element with the specified name, or + /// if no such sibling exists. + /// + /// + /// Thrown if current element is invalid. + /// + public XMLElement FindNextSibling(string name) + { + ThrowIfInvalid(); + + var node = lsl_next_sibling_n(handle_, name); + if (node != IntPtr.Zero) + return new XMLElement(node); + + return Null; + } + + /// + /// Finds the previous sibling of the element. + /// + /// + /// The previous sibling of the element, or if the element + /// is the first node in the list. + /// + /// + /// Thrown if current element is invalid. + /// + public XMLElement FindPreviousSibling() + { + ThrowIfInvalid(); + + var node = lsl_previous_sibling(handle_); + if (node != IntPtr.Zero) + return new XMLElement(node); + + return Null; + } + + /// + /// Finds the previous sibling of the element with the specified name. + /// + /// The name of the sibling. + /// + /// The previous sibling of the element with the specified name, or + /// if no such sibling exists. + /// + /// + /// Thrown if current element is invalid. + /// + public XMLElement FindPreviousSibling(string name) + { + ThrowIfInvalid(); + + var node = lsl_previous_sibling_n(handle_, name); + if (node != IntPtr.Zero) + return new XMLElement(node); + + return Null; + } + + /// + /// Appends a new child element with the specified name to the current element. + /// + /// The name of the child. + /// + /// An representing the newly created child element. + /// + /// + /// Thrown if current element is invalid. + /// + public XMLElement AppendChild(string name) + { + ThrowIfInvalid(); + + var node = lsl_append_child(handle_, name); + if (node != IntPtr.Zero) + return new XMLElement(node); + + throw new LSLException($"Failed to append child with name {name}."); + } + + /// + /// Appends a child element with a given name, which has a (nameless) plain-text + /// child with the given text value. + /// + /// The name of the child. + /// The value of the child. + /// The current element. + /// + /// Thrown if current element is invalid. + /// + public XMLElement AppendChild(string name, string value) + { + ThrowIfInvalid(); + + var node = lsl_append_child_value(handle_, name, value); + Debug.Assert(node == handle_); // TODO: + return this; // TODO: + } + + /// + /// Appends a copy of the specified element as a child. + /// + /// The original element to be copied. + /// + /// An representing the newly created child element. + /// + /// + /// Thrown if the element to be copied is null. + /// + /// + /// Thrown if current element or the element to be copied is invalid. + /// + /// + /// Thrown if appending a copy of the specified element as child fails. + /// + public XMLElement AppendChild(XMLElement element) + { + ThrowIfInvalid(); + + if (element == null) + throw new ArgumentNullException(nameof(element)); + + element.ThrowIfInvalid(); + + var node = lsl_append_copy(handle_, element.handle_); + if (node != IntPtr.Zero) // TODO: + return new XMLElement(node); + + throw new LSLException("Failed to append a copy of the specified element as a child."); + } + + /// + /// Prepends a new child element with the specified name to the current element. + /// + /// The name of the child. + /// + /// An representing the newly created child element. + /// + /// + /// Thrown if current element is invalid. + /// + public XMLElement PrependChild(string name) + { + ThrowIfInvalid(); + + var node = lsl_prepend_child(handle_, name); + if (node != IntPtr.Zero) + return new XMLElement(node); + + throw new LSLException($"Failed to prepend child with name {name}."); + } + + /// + /// Prepends a child element with a given name, which has a (nameless) plain-text + /// child with the given text value. + /// + /// The name of the child. + /// The value of the child. + /// The current element. + /// + /// Thrown if current element is invalid. + /// + public XMLElement PrependChild(string name, string value) + { + ThrowIfInvalid(); + + var node = lsl_prepend_child_value(handle_, name, value); + Debug.Assert(node == handle_); // TODO: + return this; // TODO: + } + + /// + /// Prepends a copy of the specified element as a child. + /// + /// The original element to be copied. + /// + /// An representing the newly created child element. + /// + /// + /// Thrown if the element to be copied is null. + /// + /// + /// Thrown if current element or the element to be copied is invalid. + /// + /// + /// Thrown if prepending a copy of the specified element as child fails. + /// + public XMLElement PrependChild(XMLElement element) + { + ThrowIfInvalid(); + + if (element == null) + throw new ArgumentNullException(nameof(element)); + + element.ThrowIfInvalid(); + + var node = lsl_prepend_copy(handle_, element.handle_); + if (node != IntPtr.Zero) // TODO: + return new XMLElement(node); + + throw new LSLException("Failed to prepend a copy of the specified element as a child."); + } + + /// + /// Removes a child of the element with the specified name. + /// + /// The name of the child. + /// + /// Thrown when the handle is invalid. + /// + public void RemoveChild(string name) + { + ThrowIfInvalid(); + + lsl_remove_child_n(handle_, name); + } + + /// + /// Remove a specified child element. + /// + /// The child element to be removed. + /// + /// Thrown if the element to be removed is null. + /// + /// + /// Thrown if current element or the element to be removed is invalid. + /// + public void RemoveChild(XMLElement element) + { + ThrowIfInvalid(); + + if (element == null) + throw new ArgumentNullException(nameof(element)); + + element.ThrowIfInvalid(); + + lsl_remove_child(handle_, element.handle_); + } + + /// + /// Represents a null XML element. + /// + /// + /// The traversal functions provided by may return + /// this value if no element is found. + /// + /// + public static readonly XMLElement Null = new XMLElement(IntPtr.Zero); + + /// + /// Throws an if the handle is invalid. + /// + /// + /// Thrown when the handle is invalid. + /// + protected void ThrowIfInvalid() + { + if (handle_ == IntPtr.Zero) + throw new InvalidOperationException("The XML node handle is invalid."); + } + + private readonly IntPtr handle_; + } +} + + +// References: +// https://github.com/labstreaminglayer/liblsl-Csharp/blob/master/LSL.cs +// https://pugixml.org/docs/manual.html#access +// https://github.com/zeux/pugixml/blob/master/src/pugixml.hpp diff --git a/Tests/SharpLSL.Test/CommonTest.cs b/Tests/SharpLSL.Test/CommonTest.cs index 4ecb136..3b04c06 100644 --- a/Tests/SharpLSL.Test/CommonTest.cs +++ b/Tests/SharpLSL.Test/CommonTest.cs @@ -1,15 +1,20 @@ using Xunit; -using SharpLSL.Interop; +using static SharpLSL.Interop.LSL; namespace SharpLSL.Test; public class CommonTest { [Fact] - public void Given_When_Then() + public void TestVersion() { - Assert.Equal(4, sizeof(LslChannelFormat)); - Assert.Equal(114, LSL.LIBLSL_COMPILE_HEADER_VERSION); + Assert.Equal(114, LIBLSL_COMPILE_HEADER_VERSION); + } + + [Fact] + public void TestChannelFormat() + { + Assert.Equal(4, sizeof(ChannelFormat)); } } diff --git a/Tests/SharpLSL.Test/ResolverTest.cs b/Tests/SharpLSL.Test/ResolverTest.cs new file mode 100644 index 0000000..67f9bc1 --- /dev/null +++ b/Tests/SharpLSL.Test/ResolverTest.cs @@ -0,0 +1,19 @@ +using Xunit; + +namespace SharpLSL.Test; + +public class ResolverTest +{ + [Fact] + public void TestDispose() + { + var resolver = new ContinuousResolver(); + Assert.False(resolver.IsInvalid); + + resolver.Dispose(); + Assert.True(resolver.IsInvalid); + } +} + +// References: +// https://github.com/dotnet/runtime/blob/main/src/libraries/System.Runtime/tests/System.Runtime.Tests/Microsoft/Win32/SafeHandles/SafeHandleZeroOrMinusOneIsInvalid.cs diff --git a/Tests/SharpLSL.Test/SharpLSL.Test.csproj b/Tests/SharpLSL.Test/SharpLSL.Test.csproj index 543e563..2889be3 100644 --- a/Tests/SharpLSL.Test/SharpLSL.Test.csproj +++ b/Tests/SharpLSL.Test/SharpLSL.Test.csproj @@ -5,6 +5,9 @@ + @@ -14,6 +17,7 @@ + runtime; build; native; contentfiles; analyzers; buildtransitive