From c8724a8814b1a262edec33d2f8552ee0b7dec253 Mon Sep 17 00:00:00 2001 From: myd7349 Date: Sun, 11 Aug 2024 11:48:18 +0800 Subject: [PATCH] Update --- Source/SharpLSL/Interop/Inlet.Interop.cs | 26 +- Source/SharpLSL/LSL.cs | 32 +- Source/SharpLSL/StreamInlet.cs | 568 ++++++++++++++++++++++- 3 files changed, 598 insertions(+), 28 deletions(-) diff --git a/Source/SharpLSL/Interop/Inlet.Interop.cs b/Source/SharpLSL/Interop/Inlet.Interop.cs index 2770831..9e65379 100644 --- a/Source/SharpLSL/Interop/Inlet.Interop.cs +++ b/Source/SharpLSL/Interop/Inlet.Interop.cs @@ -43,24 +43,40 @@ public static unsafe partial class LSL [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 double lsl_pull_sample_buf(lsl_inlet @in, IntPtr[] buffer, uint[] buffer_lengths, int buffer_elements, double timeout, ref int ec); + + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern double lsl_pull_sample_v(lsl_inlet @in, byte[] buffer, int buffer_bytes, double timeout, ref int ec); + [DllImport("lsl", CallingConvention = CallingConvention.Cdecl, ExactSpelling = true)] + public static extern uint lsl_pull_chunk_c(lsl_inlet @in, sbyte[] 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(lsl_inlet @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_i(lsl_inlet @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_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); + public static extern uint lsl_pull_chunk_l(lsl_inlet @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_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); + public static extern uint lsl_pull_chunk_f(lsl_inlet @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_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); + public static extern uint lsl_pull_chunk_d(lsl_inlet @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_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); + public static extern uint lsl_pull_chunk_str(lsl_inlet @in, IntPtr[] 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); + public static extern uint lsl_pull_chunk_buf(lsl_inlet @in, IntPtr[] data_buffer, uint[] lengths_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); diff --git a/Source/SharpLSL/LSL.cs b/Source/SharpLSL/LSL.cs index 2a44ccc..97acd95 100644 --- a/Source/SharpLSL/LSL.cs +++ b/Source/SharpLSL/LSL.cs @@ -1,8 +1,10 @@ using System; +using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using SharpLSL.Interop; + using static SharpLSL.Interop.LSL; namespace SharpLSL @@ -156,7 +158,7 @@ internal static string PtrToXmlString(IntPtr ptr) { return Marshal.PtrToStringAnsi(ptr); } - + #if !NET35 [MethodImpl(MethodImplOptions.AggressiveInlining)] #endif @@ -201,5 +203,33 @@ internal static void CheckSampleBuffer(T[] sample, int channelCount) CheckChannelCount(channelCount, sample.Length); } + +#if !NET35 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + internal static int CheckChunkBuffer(T[] chunk, int channelCount) + { + if (chunk == null) + throw new ArgumentNullException(nameof(chunk)); + + if (chunk.Length == 0 || (chunk.Length % channelCount) != 0) + throw new ArgumentException(nameof(chunk)); + + return chunk.Length / channelCount; + } + +#if !NET35 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + internal static void CheckTimestampBuffer(double[] timestamps, int samples) + { + Debug.Assert(samples > 0); + + if (timestamps != null) + { + if (timestamps.Length != samples) + throw new ArgumentException($"Timestamps buffer size ({timestamps.Length}) does not match the number of samples ({samples})."); + } + } } } diff --git a/Source/SharpLSL/StreamInlet.cs b/Source/SharpLSL/StreamInlet.cs index 5e753a9..046f977 100644 --- a/Source/SharpLSL/StreamInlet.cs +++ b/Source/SharpLSL/StreamInlet.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using SharpLSL.Interop; @@ -129,7 +128,7 @@ public StreamInlet(IntPtr handle, bool ownsHandle = true) 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 @@ -169,7 +168,7 @@ public StreamInfo GetStreamInfo(double timeout = Forever) 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); @@ -191,7 +190,7 @@ public void OpenStream(double timeout = Forever) public void CloseStream() { ThrowIfInvalid(); - + lsl_close_stream(handle); } @@ -238,7 +237,7 @@ public void CloseStream() 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); @@ -279,7 +278,7 @@ public double TimeCorrection(double timeout = Forever) 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); @@ -322,6 +321,7 @@ public void SetPostProcessingOptions( CheckError(lsl_set_postprocessing(handle, (uint)postProcessingOptions)); } +#if false /// /// Pulls a sample from the inlet and read it into an array of values. Handles /// type checking & conversion if necessary. @@ -368,6 +368,7 @@ public double PullSample(byte[] sample, double timeout = Forever) CheckError(errorCode); return timestamp; } +#endif /// /// Pulls a sample from the inlet and read it into an array of values. Handles @@ -503,7 +504,7 @@ 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); @@ -597,7 +598,7 @@ 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); @@ -644,7 +645,7 @@ 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); @@ -694,66 +695,583 @@ public double PullSample(string[] sample, double timeout = Forever) 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) + try + { + 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]); + + return timestamp; + } + finally { - sample[i] = PtrToString(buffer[i]); - lsl_destroy_string(buffer[i]); + for (int i = 0; i < buffer.Length; ++i) + lsl_destroy_string(buffer[i]); } + } + + /// + /// 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 buffer = new IntPtr[ChannelCount]; + var lengths = new uint[ChannelCount]; + + try + { + var timestamp = lsl_pull_sample_buf(handle, buffer, lengths, buffer.Length, timeout, ref errorCode); + CheckError(errorCode); + + for (int i = 0; i < buffer.Length; ++i) + { + if (sample[i] == null || sample[i].Length != lengths[i]) + { +#if NET35 + sample[i] = new byte[lengths[i]]; +#else + sample[i] = lengths[i] > 0 ? new byte[lengths[i]] : Array.Empty(); +#endif + } + + if (sample[i].Length > 0) + { + unsafe + { + fixed (byte* dest = sample[i]) + { +#if NET35 + byte* src = (byte*)buffer[i].ToPointer(); + for (int c = 0; c < lengths[i]; ++c) + dest[c] = src[c]; +#else + Buffer.MemoryCopy( + buffer[i].ToPointer(), + dest, + lengths[i], + lengths[i] + ); +#endif + } + } + } + } + + return timestamp; + } + finally + { + for (int i = 0; i < buffer.Length; ++i) + lsl_destroy_string(buffer[i]); + } + } + + /// + /// Pulls a sample from the inlet and read it into a custom buffer. Overall + /// size checking but no type checking or conversion are done. + /// + /// + /// 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 is too small to hold the data of + /// a sample. + /// + /// + /// 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. + /// + /// + /// Do not use this method for variable size or string-formatted streams. + /// + /// + public double PullSample(byte[] sample, double timeout = Forever) + { + ThrowIfInvalid(); + if (sample == null) + throw new ArgumentNullException(nameof(sample)); + + var errorCode = (int)lsl_error_code_t.lsl_no_error; + var timestamp = lsl_pull_sample_v(handle, sample, sample.Length, timeout, ref errorCode); + CheckError(errorCode); return timestamp; } - public double PullSample(List[] sample, double timeout = Forever) + /// + /// Pulls a chunk of samples from the inlet and read it into a buffer. Handles + /// type checking & conversion if necessary. + /// + /// + /// The buffer where the returned data chunk shall be stored. + /// + /// + /// The buffer where the returned timestamps shall be stored. If it is null, + /// no timestamps will be returned. + /// + /// + /// Specifies the timeout of the operation in seconds, if any. When the timeout + /// expires, the function may return before the entire buffer is filled. The + /// default value of 0.0 will retrieve only data available for immediate pickup. + /// + /// + /// The number of channel data elements written to the data buffer. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// Thrown if the provided buffer is null. + /// + /// + /// Thrown if size of the provided buffer is too small. + /// + /// + /// Thrown if the stream source has been lost. + /// + /// + /// If the timeout expires before a new sample was received, the function + /// returns 0; this case is not considered an error condition. + /// + public uint PullChunk(sbyte[] chunk, double[] timestamps, double timeout = 0.0) { ThrowIfInvalid(); - CheckSampleBuffer(sample, ChannelCount); + + var samples = CheckChunkBuffer(chunk, ChannelCount); + CheckTimestampBuffer(timestamps, samples); - return 0.0; + var errorCode = (int)lsl_error_code_t.lsl_no_error; + var result = lsl_pull_chunk_c(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) + /// + /// Pulls a chunk of samples from the inlet and read it into a buffer. Handles + /// type checking & conversion if necessary. + /// + /// + /// The buffer where the returned data chunk shall be stored. + /// + /// + /// The buffer where the returned timestamps shall be stored. If it is null, + /// no timestamps will be returned. + /// + /// + /// Specifies the timeout of the operation in seconds, if any. When the timeout + /// expires, the function may return before the entire buffer is filled. The + /// default value of 0.0 will retrieve only data available for immediate pickup. + /// + /// + /// The number of channel data elements written to the data buffer. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// Thrown if the provided buffer is null. + /// + /// + /// Thrown if size of the provided buffer is too small. + /// + /// + /// Thrown if the stream source has been lost. + /// + /// + /// If the timeout expires before a new sample was received, the function + /// returns 0; this case is not considered an error condition. + /// + public uint PullChunk(short[] chunk, double[] timestamps, double timeout = 0.0) { + ThrowIfInvalid(); + + var samples = CheckChunkBuffer(chunk, ChannelCount); + CheckTimestampBuffer(timestamps, samples); + 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) + /// + /// Pulls a chunk of samples from the inlet and read it into a buffer. Handles + /// type checking & conversion if necessary. + /// + /// + /// The buffer where the returned data chunk shall be stored. + /// + /// + /// The buffer where the returned timestamps shall be stored. If it is null, + /// no timestamps will be returned. + /// + /// + /// Specifies the timeout of the operation in seconds, if any. When the timeout + /// expires, the function may return before the entire buffer is filled. The + /// default value of 0.0 will retrieve only data available for immediate pickup. + /// + /// + /// The number of channel data elements written to the data buffer. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// Thrown if the provided buffer is null. + /// + /// + /// Thrown if size of the provided buffer is too small. + /// + /// + /// Thrown if the stream source has been lost. + /// + /// + /// If the timeout expires before a new sample was received, the function + /// returns 0; this case is not considered an error condition. + /// + public uint PullChunk(int[] chunk, double[] timestamps, double timeout = 0.0) { + ThrowIfInvalid(); + + var samples = CheckChunkBuffer(chunk, ChannelCount); + CheckTimestampBuffer(timestamps, samples); + 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) + /// + /// Pulls a chunk of samples from the inlet and read it into a buffer. Handles + /// type checking & conversion if necessary. + /// + /// + /// The buffer where the returned data chunk shall be stored. + /// + /// + /// The buffer where the returned timestamps shall be stored. If it is null, + /// no timestamps will be returned. + /// + /// + /// Specifies the timeout of the operation in seconds, if any. When the timeout + /// expires, the function may return before the entire buffer is filled. The + /// default value of 0.0 will retrieve only data available for immediate pickup. + /// + /// + /// The number of channel data elements written to the data buffer. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// Thrown if the provided buffer is null. + /// + /// + /// Thrown if size of the provided buffer is too small. + /// + /// + /// Thrown if the stream source has been lost. + /// + /// + /// If the timeout expires before a new sample was received, the function + /// returns 0; this case is not considered an error condition. + /// + public uint PullChunk(long[] chunk, double[] timestamps, double timeout = 0.0) { + ThrowIfInvalid(); + + var samples = CheckChunkBuffer(chunk, ChannelCount); + CheckTimestampBuffer(timestamps, samples); + 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) + /// + /// Pulls a chunk of samples from the inlet and read it into a buffer. Handles + /// type checking & conversion if necessary. + /// + /// + /// The buffer where the returned data chunk shall be stored. + /// + /// + /// The buffer where the returned timestamps shall be stored. If it is null, + /// no timestamps will be returned. + /// + /// + /// Specifies the timeout of the operation in seconds, if any. When the timeout + /// expires, the function may return before the entire buffer is filled. The + /// default value of 0.0 will retrieve only data available for immediate pickup. + /// + /// + /// The number of channel data elements written to the data buffer. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// Thrown if the provided buffer is null. + /// + /// + /// Thrown if size of the provided buffer is too small. + /// + /// + /// Thrown if the stream source has been lost. + /// + /// + /// If the timeout expires before a new sample was received, the function + /// returns 0; this case is not considered an error condition. + /// + public uint PullChunk(float[] chunk, double[] timestamps, double timeout = 0.0) { + ThrowIfInvalid(); + + var samples = CheckChunkBuffer(chunk, ChannelCount); + CheckTimestampBuffer(timestamps, samples); + 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) + /// + /// Pulls a chunk of samples from the inlet and read it into a buffer. Handles + /// type checking & conversion if necessary. + /// + /// + /// The buffer where the returned data chunk shall be stored. + /// + /// + /// The buffer where the returned timestamps shall be stored. If it is null, + /// no timestamps will be returned. + /// + /// + /// Specifies the timeout of the operation in seconds, if any. When the timeout + /// expires, the function may return before the entire buffer is filled. The + /// default value of 0.0 will retrieve only data available for immediate pickup. + /// + /// + /// The number of channel data elements written to the data buffer. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// Thrown if the provided buffer is null. + /// + /// + /// Thrown if size of the provided buffer is too small. + /// + /// + /// Thrown if the stream source has been lost. + /// + /// + /// If the timeout expires before a new sample was received, the function + /// returns 0; this case is not considered an error condition. + /// + public uint PullChunk(double[] chunk, double[] timestamps, double timeout = 0.0) { + ThrowIfInvalid(); + + var samples = CheckChunkBuffer(chunk, ChannelCount); + CheckTimestampBuffer(timestamps, samples); + 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; } + /// + /// Pulls a chunk of samples from the inlet and read it into a buffer. Handles + /// type checking & conversion if necessary. + /// + /// + /// The buffer where the returned data chunk shall be stored. + /// + /// + /// The buffer where the returned timestamps shall be stored. If it is null, + /// no timestamps will be returned. + /// + /// + /// Specifies the timeout of the operation in seconds, if any. When the timeout + /// expires, the function may return before the entire buffer is filled. The + /// default value of 0.0 will retrieve only data available for immediate pickup. + /// + /// + /// The number of channel data elements written to the data buffer. + /// + /// + /// Thrown if this object is invalid. + /// + /// + /// Thrown if the provided buffer is null. + /// + /// + /// Thrown if size of the provided buffer is too small. + /// + /// + /// Thrown if the stream source has been lost. + /// + /// + /// If the timeout expires before a new sample was received, the function + /// returns 0; this case is not considered an error condition. + /// + public uint PullChunk(string[] chunk, double[] timestamps, double timeout = 0.0) + { + ThrowIfInvalid(); + + var samples = CheckChunkBuffer(chunk, ChannelCount); + CheckTimestampBuffer(timestamps, samples); + + var errorCode = (int)lsl_error_code_t.lsl_no_error; + var buffer = new IntPtr[chunk.Length]; + uint result = 0; + + try + { + result = lsl_pull_chunk_str(handle, buffer, timestamps, (uint)chunk.Length, (uint)timestamps.Length, timeout, ref errorCode); + CheckError(errorCode); + + for (int i = 0; i < result; ++i) + chunk[i] = PtrToString(buffer[i]); + + return result; + } + finally + { + for (int i = 0; i < result; ++i) + lsl_destroy_string(buffer[i]); + } + } + + + public uint PullChunk(byte[][] chunk, double[] timestamps, double timeout = 0.0) + { + ThrowIfInvalid(); + + var samples = CheckChunkBuffer(chunk, ChannelCount); + CheckTimestampBuffer(timestamps, samples); + + var errorCode = (int)lsl_error_code_t.lsl_no_error; + var buffer = new IntPtr[chunk.Length]; + var lengths = new uint[ChannelCount]; + uint result = 0; + + try + { + result = lsl_pull_chunk_buf(handle, buffer, lengths, timestamps, (uint)chunk.Length, (uint)timestamps.Length, timeout, ref errorCode); + CheckError(errorCode); + + for (int i = 0; i < result; ++i) + { + if (chunk[i] == null || chunk[i].Length != lengths[i]) + { +#if NET35 + chunk[i] = new byte[lengths[i]]; +#else + chunk[i] = lengths[i] > 0 ? new byte[lengths[i]] : Array.Empty(); +#endif + } + + if (chunk[i].Length > 0) + { + unsafe + { + fixed (byte* dest = chunk[i]) + { +#if NET35 + byte* src = (byte*)buffer[i].ToPointer(); + for (int c = 0; c < lengths[i]; ++c) + dest[c] = src[c]; +#else + Buffer.MemoryCopy( + buffer[i].ToPointer(), + dest, + lengths[i], + lengths[i] + ); +#endif + } + } + } + } + + return result; + } + finally + { + for (int i = 0; i < result; ++i) + lsl_destroy_string(buffer[i]); + } + } + public uint PullChunk(short[,] chunk, double[] timestamps, double timeout) { var errorCode = (int)lsl_error_code_t.lsl_no_error; @@ -814,3 +1332,9 @@ protected override void DestroyLSLObject() } } } + + +// References: +// [Array versus List: When to use which?](https://stackoverflow.com/questions/434761/array-versus-listt-when-to-use-which) +// https://learn.microsoft.com/en-us/dotnet/api/system.runtime.compilerservices.unsafe.copyblock?view=net-8.0 +// [How to Convert IntPtr to native c++ object](https://stackoverflow.com/questions/4277681/how-to-convert-intptr-to-native-c-object)