public LSLStream(LSLBridgeStreamInfo streamInfo) { StreamInfo = streamInfo; liblsl.channel_format_t channelFormat; if (streamInfo.ChannelDataType == LSLBridgeDataType.FLOAT) { channelFormat = liblsl.channel_format_t.cf_float32; } else if (streamInfo.ChannelDataType == LSLBridgeDataType.DOUBLE) { channelFormat = liblsl.channel_format_t.cf_double64; } else if (streamInfo.ChannelDataType == LSLBridgeDataType.INT) { channelFormat = liblsl.channel_format_t.cf_int32; } else if (streamInfo.ChannelDataType == LSLBridgeDataType.STRING) { channelFormat = liblsl.channel_format_t.cf_string; } else { throw new InvalidOperationException("Unsupported channel data type."); } var lslStreamInfo = new liblsl.StreamInfo(streamInfo.StreamName, streamInfo.StreamType, streamInfo.ChannelCount, streamInfo.NominalSRate, channelFormat, Application.ResourceAssembly.GetName().Name); lslStreamInfo.desc().append_child_value("manufacturer", streamInfo.DeviceManufacturer); lslStreamInfo.desc().append_child_value("device", streamInfo.DeviceName); lslStreamInfo.desc().append_child_value("type", streamInfo.StreamType); var channels = lslStreamInfo.desc().append_child("channels"); foreach (var c in streamInfo.Channels) { channels.append_child("channel") .append_child_value("label", c.Label) .append_child_value("unit", c.Unit) .append_child_value("type", c.Type); } OnPropertyChanged(nameof(StreamDisplayInfo)); lslStream = new liblsl.StreamOutlet(lslStreamInfo, streamInfo.ChunkSize, streamInfo.BufferLength); stopWatch = new Stopwatch(); stopWatch.Restart(); }
private void LSLService_RequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args) { lastMessageTime = DateTime.UtcNow; ValueSet message = args.Request.Message; if (message.TryGetValue(Constants.LSL_MESSAGE_TYPE, out object value)) { string commandType = (string)value; switch (commandType) { case Constants.LSL_MESSAGE_TYPE_KEEP_ACTIVE: return; case Constants.LSL_MESSAGE_TYPE_OPEN_STREAM: { LSLBridgeStreamInfo streamInfo = JsonConvert.DeserializeObject <LSLBridgeStreamInfo>((string)message[Constants.LSL_MESSAGE_STREAM_INFO]); if (streamInfo.SendSecondaryTimestamp) { if (streamInfo.ChannelDataType == LSLBridgeDataType.FLOAT) { streamInfo.Channels.Add(new LSLBridgeChannelInfo { Label = "Secondary Timestamp (Base)", Type = "timestamp", Unit = "seconds" }); streamInfo.Channels.Add(new LSLBridgeChannelInfo { Label = "Secondary Timestamp (Remainder)", Type = "timestamp", Unit = "seconds" }); streamInfo.ChannelCount += 2; } else if (streamInfo.ChannelDataType == LSLBridgeDataType.DOUBLE) { streamInfo.Channels.Add(new LSLBridgeChannelInfo { Label = "Secondary Timestamp", Type = "timestamp", Unit = "seconds" }); streamInfo.ChannelCount += 1; } } if (!streams.Any(x => x.StreamInfo.StreamName == streamInfo.StreamName)) { streams.Add(new LSLStream(streamInfo)); streamCountSetter(streams.Count); } } break; case Constants.LSL_MESSAGE_TYPE_CLOSE_STREAM: { string streamName = (string)message[Constants.LSL_MESSAGE_STREAM_NAME]; var stream = streams.FirstOrDefault(x => x.StreamInfo.StreamName == streamName); if (stream != null) { streams.Remove(stream); stream.Dispose(); streamCountSetter(streams.Count); } } break; case Constants.LSL_MESSAGE_TYPE_SEND_CHUNK: { string streamName = (string)message[Constants.LSL_MESSAGE_STREAM_NAME]; var stream = streams.FirstOrDefault(x => x.StreamInfo.StreamName == streamName); if (stream != null) { var streamInfo = stream.StreamInfo; // Get our stream timestamps. double[] nativeTimestamps = null; double[] timestamps = ((double[])message[Constants.LSL_MESSAGE_CHUNK_TIMESTAMPS]); if (double.IsNegativeInfinity(timestamps[0])) // Hack since main app can't call native lsl_local_clock(). { nativeTimestamps = StreamHelper.GenerateLSLNativeTimestamps(streamInfo); timestamps = nativeTimestamps; } // Potentially add in secondary timestamps to be pushed with data chunk if we are using float/double format. double[] timestamps2 = null; if (streamInfo.SendSecondaryTimestamp) { timestamps2 = ((double[])message[Constants.LSL_MESSAGE_CHUNK_TIMESTAMPS2]); if (double.IsNegativeInfinity(timestamps2[0])) // Hack since main app can't call native lsl_local_clock(). { timestamps2 = nativeTimestamps ?? StreamHelper.GenerateLSLNativeTimestamps(streamInfo); } } // Get our stream data, figure out data type, get secondary timestamps if needed, and push chunk. if (streamInfo.ChannelDataType == LSLBridgeDataType.FLOAT) { float[] data1D = (float[])message[Constants.LSL_MESSAGE_CHUNK_DATA]; float[,] data2D = data1D.To2DArray(streamInfo.ChunkSize, streamInfo.ChannelCount - (streamInfo.SendSecondaryTimestamp ? 2 : 0)); // Two extra channels for float timestamp, so subtract 2 from length to get actual data part. stream.PushChunkLSL(data2D, timestamps, timestamps2); } else if (streamInfo.ChannelDataType == LSLBridgeDataType.DOUBLE) { double[] data1D = (double[])message[Constants.LSL_MESSAGE_CHUNK_DATA]; double[,] data2D = data1D.To2DArray(streamInfo.ChunkSize, streamInfo.ChannelCount - (streamInfo.SendSecondaryTimestamp ? 1 : 0)); stream.PushChunkLSL(data2D, timestamps, timestamps2); } else if (streamInfo.ChannelDataType == LSLBridgeDataType.INT) { int[] data1D = (int[])message[Constants.LSL_MESSAGE_CHUNK_DATA]; int[,] data2D = data1D.To2DArray(streamInfo.ChunkSize, streamInfo.ChannelCount); stream.PushChunkLSL(data2D, timestamps); } else if (streamInfo.ChannelDataType == LSLBridgeDataType.STRING) { string[] data1D = (string[])message[Constants.LSL_MESSAGE_CHUNK_DATA]; string[,] data2D = data1D.To2DArray(streamInfo.ChunkSize, streamInfo.ChannelCount); stream.PushChunkLSL(data2D, timestamps); } stream.UpdateSampleRate(timestamps.Length); } } break; // Should not be called until application is closing. case Constants.LSL_MESSAGE_TYPE_CLOSE_BRIDGE: { Log.Information("Closing LSL bridge (requested by BlueMuse)."); CloseBridge(); } break; } } }