UNPKG

node-labstreaminglayer

Version:
374 lines 24.2 kB
/** * @fileoverview Low-level FFI (Foreign Function Interface) bindings for Lab Streaming Layer (LSL). * * This module provides direct access to the LSL C library functions through Koffi FFI. * Most users should use the high-level classes (StreamInfo, StreamOutlet, StreamInlet) instead * of calling these functions directly. * * @module lib/index * @see {@link https://github.com/sccn/labstreaminglayer} - LSL Documentation * @see {@link https://koffi.dev/} - Koffi FFI Documentation */ import koffi from 'koffi'; import { platform, arch } from 'os'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; // Get the directory of this module for resolving library paths const __dirname = dirname(fileURLToPath(import.meta.url)); /** * Determines the correct LSL library file based on the current platform and architecture. * * Supported platforms: * - Windows: Uses lsl_amd64.dll (x64) or lsl_i386.dll (x86) * - macOS: Uses lsl.dylib (universal binary for x64/ARM64) * - Linux: Uses lsl.so (requires system installation) * * @returns {string} Absolute path to the platform-specific LSL library * @throws {Error} If the platform is not supported */ function getLibraryPath() { const platformName = platform(); const archName = arch(); let libName; if (platformName === 'win32') { // Windows: Select DLL based on architecture // x64 (64-bit) uses amd64, x86 (32-bit) uses i386 libName = archName === 'x64' ? 'lsl_amd64.dll' : 'lsl_i386.dll'; } else if (platformName === 'darwin') { // macOS: Universal binary supports both Intel and Apple Silicon libName = 'lsl.dylib'; } else if (platformName === 'linux') { // Linux: Shared object file (requires liblsl to be installed) libName = 'lsl.so'; } else { throw new Error(`Unsupported platform: ${platformName}`); } // Resolve to prebuild directory containing platform binaries return join(__dirname, '..', '..', 'prebuild', libName); } // Load the LSL library using Koffi FFI const libPath = getLibraryPath(); export const lib = koffi.load(libPath); /* ============================================================================ * CHANNEL FORMAT CONSTANTS * * These constants define the data type of channels in a stream. * They map directly to the lsl_channel_format_t enum in the C library. * ============================================================================ */ /** @const {number} Undefined format (0) - Should not be used for data streams */ export const cf_undefined = 0; /** @const {number} 32-bit IEEE floating point (1) - Standard single precision */ export const cf_float32 = 1; /** @const {number} 64-bit IEEE floating point (2) - Double precision */ export const cf_double64 = 2; /** @const {number} Variable-length string (3) - UTF-8 encoded text */ export const cf_string = 3; /** @const {number} 32-bit signed integer (4) */ export const cf_int32 = 4; /** @const {number} 16-bit signed integer (5) */ export const cf_int16 = 5; /** @const {number} 8-bit signed integer (6) */ export const cf_int8 = 6; /** @const {number} 64-bit signed integer (7) - May have limited support */ export const cf_int64 = 7; /* ============================================================================ * TYPE CONVERSION MAPPINGS * * These mappings facilitate conversion between string representations and * numeric channel format constants. * ============================================================================ */ /** * Maps string format names to numeric channel format constants. * Used when creating streams with string-based format specification. * @example * const format = string2fmt['float32']; // Returns cf_float32 (1) */ export const string2fmt = { 'float32': cf_float32, 'double64': cf_double64, 'string': cf_string, 'int32': cf_int32, 'int16': cf_int16, 'int8': cf_int8, 'int64': cf_int64, }; /** * Array mapping numeric format constants to string names. * Index corresponds to the channel format constant value. * @example * const name = fmt2string[cf_float32]; // Returns 'float32' */ export const fmt2string = [ 'undefined', // 0: cf_undefined 'float32', // 1: cf_float32 'double64', // 2: cf_double64 'string', // 3: cf_string 'int32', // 4: cf_int32 'int16', // 5: cf_int16 'int8', // 6: cf_int8 'int64', // 7: cf_int64 ]; /* ============================================================================ * OPAQUE POINTER TYPES * * These represent C pointers to LSL structures. They are opaque because * JavaScript doesn't need to access their internal structure directly. * ============================================================================ */ /** Opaque pointer to lsl_streaminfo structure - Contains stream metadata */ const lsl_streaminfo = koffi.opaque('lsl_streaminfo'); /** Opaque pointer to lsl_outlet structure - Broadcasts data to network */ const lsl_outlet = koffi.opaque('lsl_outlet'); /** Opaque pointer to lsl_inlet structure - Receives data from network */ const lsl_inlet = koffi.opaque('lsl_inlet'); /** Opaque pointer to XML element - Used for stream descriptions */ const lsl_xml_ptr = koffi.opaque('lsl_xml_ptr'); /** Opaque pointer to continuous resolver - Monitors available streams */ const lsl_continuous_resolver = koffi.opaque('lsl_continuous_resolver'); /* ============================================================================ * STREAMINFO FUNCTIONS * * Functions for creating and managing stream metadata. * StreamInfo objects describe the properties of a data stream. * ============================================================================ */ export const lsl_create_streaminfo = lib.func('void* lsl_create_streaminfo(str name, str type, int32 channel_count, double nominal_srate, int32 channel_format, str source_id)'); export const lsl_destroy_streaminfo = lib.func('void lsl_destroy_streaminfo(void* info)'); export const lsl_get_name = lib.func('str lsl_get_name(void* info)'); export const lsl_get_type = lib.func('str lsl_get_type(void* info)'); export const lsl_get_channel_count = lib.func('int32 lsl_get_channel_count(void* info)'); export const lsl_get_nominal_srate = lib.func('double lsl_get_nominal_srate(void* info)'); export const lsl_get_channel_format = lib.func('int32 lsl_get_channel_format(void* info)'); export const lsl_get_source_id = lib.func('str lsl_get_source_id(void* info)'); export const lsl_get_version = lib.func('int32 lsl_get_version(void* info)'); export const lsl_get_created_at = lib.func('double lsl_get_created_at(void* info)'); export const lsl_get_uid = lib.func('str lsl_get_uid(void* info)'); export const lsl_get_session_id = lib.func('str lsl_get_session_id(void* info)'); export const lsl_get_hostname = lib.func('str lsl_get_hostname(void* info)'); export const lsl_get_desc = lib.func('void* lsl_get_desc(void* info)'); export const lsl_get_xml = lib.func('str lsl_get_xml(void* info)'); /* ============================================================================ * STREAMOUTLET FUNCTIONS * * Functions for broadcasting data streams to the network. * Outlets push samples to any connected inlets. * ============================================================================ */ export const lsl_create_outlet = lib.func('void* lsl_create_outlet(void* info, int32 chunk_size, int32 max_buffered)'); export const lsl_destroy_outlet = lib.func('void lsl_destroy_outlet(void* outlet)'); export const lsl_have_consumers = lib.func('int32 lsl_have_consumers(void* outlet)'); export const lsl_wait_for_consumers = lib.func('int32 lsl_wait_for_consumers(void* outlet, double timeout)'); export const lsl_get_info = lib.func('void* lsl_get_info(void* outlet)'); /* ============================================================================ * PUSH SAMPLE FUNCTIONS * * Type-specific functions for pushing single samples. * The suffix indicates the data type: f=float32, d=double64, i=int32, etc. * The 'tp' suffix means timestamp and pushthrough parameters are included. * ============================================================================ */ export const lsl_push_sample_f = lib.func('int32 lsl_push_sample_ftp(void* outlet, float* sample, double timestamp, int32 pushthrough)'); export const lsl_push_sample_d = lib.func('int32 lsl_push_sample_dtp(void* outlet, double* sample, double timestamp, int32 pushthrough)'); export const lsl_push_sample_i = lib.func('int32 lsl_push_sample_itp(void* outlet, int32* sample, double timestamp, int32 pushthrough)'); export const lsl_push_sample_s = lib.func('int32 lsl_push_sample_stp(void* outlet, int16* sample, double timestamp, int32 pushthrough)'); export const lsl_push_sample_c = lib.func('int32 lsl_push_sample_ctp(void* outlet, int8* sample, double timestamp, int32 pushthrough)'); export const lsl_push_sample_l = lib.func('int32 lsl_push_sample_ltp(void* outlet, int64* sample, double timestamp, int32 pushthrough)'); export const lsl_push_sample_str = lib.func('int32 lsl_push_sample_strtp(void* outlet, char** sample, double timestamp, int32 pushthrough)'); export const lsl_push_sample_v = lib.func('int32 lsl_push_sample_vtp(void* outlet, void* sample, double timestamp, int32 pushthrough)'); /* ============================================================================ * PUSH CHUNK FUNCTIONS * * Type-specific functions for pushing multiple samples at once. * More efficient than pushing samples individually. * Suffix 't' versions accept individual timestamps for each sample. * ============================================================================ */ export const lsl_push_chunk_f = lib.func('int32 lsl_push_chunk_ftp(void* outlet, float* data, ulong data_elements, double timestamp, int32 pushthrough)'); export const lsl_push_chunk_ft = lib.func('int32 lsl_push_chunk_ftnp(void* outlet, float* data, ulong data_elements, double* timestamps, int32 pushthrough)'); export const lsl_push_chunk_d = lib.func('int32 lsl_push_chunk_dtp(void* outlet, double* data, ulong data_elements, double timestamp, int32 pushthrough)'); export const lsl_push_chunk_dt = lib.func('int32 lsl_push_chunk_dtnp(void* outlet, double* data, ulong data_elements, double* timestamps, int32 pushthrough)'); export const lsl_push_chunk_i = lib.func('int32 lsl_push_chunk_itp(void* outlet, int32* data, ulong data_elements, double timestamp, int32 pushthrough)'); export const lsl_push_chunk_it = lib.func('int32 lsl_push_chunk_itnp(void* outlet, int32* data, ulong data_elements, double* timestamps, int32 pushthrough)'); export const lsl_push_chunk_s = lib.func('int32 lsl_push_chunk_stp(void* outlet, int16* data, ulong data_elements, double timestamp, int32 pushthrough)'); export const lsl_push_chunk_st = lib.func('int32 lsl_push_chunk_stnp(void* outlet, int16* data, ulong data_elements, double* timestamps, int32 pushthrough)'); export const lsl_push_chunk_c = lib.func('int32 lsl_push_chunk_ctp(void* outlet, int8* data, ulong data_elements, double timestamp, int32 pushthrough)'); export const lsl_push_chunk_ct = lib.func('int32 lsl_push_chunk_ctnp(void* outlet, int8* data, ulong data_elements, double* timestamps, int32 pushthrough)'); export const lsl_push_chunk_l = lib.func('int32 lsl_push_chunk_ltp(void* outlet, int64* data, ulong data_elements, double timestamp, int32 pushthrough)'); export const lsl_push_chunk_lt = lib.func('int32 lsl_push_chunk_ltnp(void* outlet, int64* data, ulong data_elements, double* timestamps, int32 pushthrough)'); export const lsl_push_chunk_str = lib.func('int32 lsl_push_chunk_strtp(void* outlet, char** data, ulong data_elements, double timestamp, int32 pushthrough)'); export const lsl_push_chunk_strt = lib.func('int32 lsl_push_chunk_strtnp(void* outlet, char** data, ulong data_elements, double* timestamps, int32 pushthrough)'); /* ============================================================================ * STREAMINLET FUNCTIONS * * Functions for receiving data streams from the network. * Inlets pull samples from connected outlets. * ============================================================================ */ export const lsl_create_inlet = lib.func('void* lsl_create_inlet(void* info, int32 max_buflen, int32 max_chunklen, int32 recover)'); export const lsl_destroy_inlet = lib.func('void lsl_destroy_inlet(void* inlet)'); export const lsl_get_fullinfo = lib.func('void* lsl_get_fullinfo(void* inlet, double timeout, _Out_ int32* errcode)'); export const lsl_open_stream = lib.func('void lsl_open_stream(void* inlet, double timeout, _Out_ int32* errcode)'); export const lsl_close_stream = lib.func('void lsl_close_stream(void* inlet)'); export const lsl_time_correction = lib.func('double lsl_time_correction(void* inlet, double timeout, _Out_ int32* errcode)'); export const lsl_set_postprocessing = lib.func('int32 lsl_set_postprocessing(void* inlet, uint32 flags)'); export const lsl_samples_available = lib.func('uint32 lsl_samples_available(void* inlet)'); export const lsl_inlet_flush = lib.func('uint32 lsl_inlet_flush(void* inlet)'); export const lsl_was_clock_reset = lib.func('uint32 lsl_was_clock_reset(void* inlet)'); /* ============================================================================ * PULL SAMPLE FUNCTIONS * * Type-specific functions for pulling single samples. * Returns timestamp of the sample (0 if no sample available within timeout). * ============================================================================ */ export const lsl_pull_sample_f = lib.func('double lsl_pull_sample_f(void* inlet, _Out_ float* sample, int32 buffer_elements, double timeout, _Out_ int32* errcode)'); export const lsl_pull_sample_d = lib.func('double lsl_pull_sample_d(void* inlet, _Out_ double* sample, int32 buffer_elements, double timeout, _Out_ int32* errcode)'); export const lsl_pull_sample_i = lib.func('double lsl_pull_sample_i(void* inlet, _Out_ int32* sample, int32 buffer_elements, double timeout, _Out_ int32* errcode)'); export const lsl_pull_sample_s = lib.func('double lsl_pull_sample_s(void* inlet, _Out_ int16* sample, int32 buffer_elements, double timeout, _Out_ int32* errcode)'); export const lsl_pull_sample_c = lib.func('double lsl_pull_sample_c(void* inlet, _Out_ int8* sample, int32 buffer_elements, double timeout, _Out_ int32* errcode)'); export const lsl_pull_sample_l = lib.func('double lsl_pull_sample_l(void* inlet, _Out_ int64* sample, int32 buffer_elements, double timeout, _Out_ int32* errcode)'); export const lsl_pull_sample_str = lib.func('double lsl_pull_sample_str(void* inlet, _Out_ char** sample, int32 buffer_elements, double timeout, _Out_ int32* errcode)'); export const lsl_pull_sample_v = lib.func('double lsl_pull_sample_v(void* inlet, _Out_ void* sample, int32 buffer_elements, double timeout, _Out_ int32* errcode)'); /* ============================================================================ * PULL CHUNK FUNCTIONS * * Type-specific functions for pulling multiple samples at once. * Returns the number of samples actually pulled. * ============================================================================ */ export const lsl_pull_chunk_f = lib.func('ulong lsl_pull_chunk_f(void* inlet, _Out_ float* data_buffer, _Out_ double* timestamp_buffer, ulong data_buffer_elements, ulong timestamp_buffer_elements, double timeout, _Out_ int32* errcode)'); export const lsl_pull_chunk_d = lib.func('ulong lsl_pull_chunk_d(void* inlet, _Out_ double* data_buffer, _Out_ double* timestamp_buffer, ulong data_buffer_elements, ulong timestamp_buffer_elements, double timeout, _Out_ int32* errcode)'); export const lsl_pull_chunk_i = lib.func('ulong lsl_pull_chunk_i(void* inlet, _Out_ int32* data_buffer, _Out_ double* timestamp_buffer, ulong data_buffer_elements, ulong timestamp_buffer_elements, double timeout, _Out_ int32* errcode)'); export const lsl_pull_chunk_s = lib.func('ulong lsl_pull_chunk_s(void* inlet, _Out_ int16* data_buffer, _Out_ double* timestamp_buffer, ulong data_buffer_elements, ulong timestamp_buffer_elements, double timeout, _Out_ int32* errcode)'); export const lsl_pull_chunk_c = lib.func('ulong lsl_pull_chunk_c(void* inlet, _Out_ int8* data_buffer, _Out_ double* timestamp_buffer, ulong data_buffer_elements, ulong timestamp_buffer_elements, double timeout, _Out_ int32* errcode)'); export const lsl_pull_chunk_l = lib.func('ulong lsl_pull_chunk_l(void* inlet, _Out_ int64* data_buffer, _Out_ double* timestamp_buffer, ulong data_buffer_elements, ulong timestamp_buffer_elements, double timeout, _Out_ int32* errcode)'); export const lsl_pull_chunk_str = lib.func('ulong lsl_pull_chunk_str(void* inlet, _Out_ char** data_buffer, _Out_ double* timestamp_buffer, ulong data_buffer_elements, ulong timestamp_buffer_elements, double timeout, _Out_ int32* errcode)'); /* ============================================================================ * RESOLVER FUNCTIONS * * Functions for discovering available streams on the network. * Resolvers use multicast to find streams matching specific criteria. * ============================================================================ */ export const lsl_resolve_all = lib.func('int32 lsl_resolve_all(_Out_ void** buffer, uint32 buffer_elements, double wait_time)'); export const lsl_resolve_byprop = lib.func('int32 lsl_resolve_byprop(_Out_ void** buffer, uint32 buffer_elements, str prop, str value, int32 minimum, double timeout)'); export const lsl_resolve_bypred = lib.func('int32 lsl_resolve_bypred(_Out_ void** buffer, uint32 buffer_elements, str predicate, int32 minimum, double timeout)'); /* ============================================================================ * CONTINUOUS RESOLVER FUNCTIONS * * Functions for continuously monitoring stream availability. * Unlike one-shot resolvers, these track streams as they appear/disappear. * ============================================================================ */ export const lsl_create_continuous_resolver = lib.func('void* lsl_create_continuous_resolver(double forget_after)'); export const lsl_create_continuous_resolver_byprop = lib.func('void* lsl_create_continuous_resolver_byprop(str prop, str value, double forget_after)'); export const lsl_create_continuous_resolver_bypred = lib.func('void* lsl_create_continuous_resolver_bypred(str predicate, double forget_after)'); export const lsl_destroy_continuous_resolver = lib.func('void lsl_destroy_continuous_resolver(void* resolver)'); export const lsl_resolver_results = lib.func('int32 lsl_resolver_results(void* resolver, _Out_ void** buffer, uint32 buffer_elements)'); /* ============================================================================ * UTILITY FUNCTIONS * * General utility functions for LSL operations. * Includes timing, version info, and memory management. * ============================================================================ */ export const lsl_local_clock = lib.func('double lsl_local_clock()'); export const lsl_protocol_version = lib.func('int32 lsl_protocol_version()'); export const lsl_library_version = lib.func('int32 lsl_library_version()'); export const lsl_library_info = lib.func('str lsl_library_info()'); export const lsl_destroy_string = lib.func('void lsl_destroy_string(void* str)'); /* ============================================================================ * XML ELEMENT FUNCTIONS * * Functions for manipulating XML stream descriptions. * LSL uses XML to store structured metadata about streams. * ============================================================================ */ export const lsl_first_child = lib.func('void* lsl_first_child(void* e)'); export const lsl_last_child = lib.func('void* lsl_last_child(void* e)'); export const lsl_next_sibling = lib.func('void* lsl_next_sibling(void* e)'); export const lsl_next_sibling_n = lib.func('void* lsl_next_sibling_n(void* e, str name)'); export const lsl_previous_sibling = lib.func('void* lsl_previous_sibling(void* e)'); export const lsl_previous_sibling_n = lib.func('void* lsl_previous_sibling_n(void* e, str name)'); export const lsl_parent = lib.func('void* lsl_parent(void* e)'); export const lsl_child = lib.func('void* lsl_child(void* e, str name)'); export const lsl_empty = lib.func('int32 lsl_empty(void* e)'); export const lsl_is_text = lib.func('int32 lsl_is_text(void* e)'); export const lsl_name = lib.func('str lsl_name(void* e)'); export const lsl_value = lib.func('str lsl_value(void* e)'); export const lsl_child_value = lib.func('str lsl_child_value(void* e)'); export const lsl_child_value_n = lib.func('str lsl_child_value_n(void* e, str name)'); export const lsl_append_child_value = lib.func('void* lsl_append_child_value(void* e, str name, str value)'); export const lsl_prepend_child_value = lib.func('void* lsl_prepend_child_value(void* e, str name, str value)'); export const lsl_set_child_value = lib.func('int32 lsl_set_child_value(void* e, str name, str value)'); export const lsl_set_name = lib.func('int32 lsl_set_name(void* e, str name)'); export const lsl_set_value = lib.func('int32 lsl_set_value(void* e, str value)'); export const lsl_append_child = lib.func('void* lsl_append_child(void* e, str name)'); export const lsl_prepend_child = lib.func('void* lsl_prepend_child(void* e, str name)'); export const lsl_append_copy = lib.func('void* lsl_append_copy(void* e, void* target)'); export const lsl_prepend_copy = lib.func('void* lsl_prepend_copy(void* e, void* target)'); export const lsl_remove_child = lib.func('void lsl_remove_child(void* e, void* target)'); export const lsl_remove_child_n = lib.func('void lsl_remove_child_n(void* e, str name)'); /* ============================================================================ * HELPER FUNCTION MAPPINGS * * These dictionaries map channel format constants to their corresponding * C functions. This allows dynamic function selection based on data type. * ============================================================================ */ /** * Maps channel format to corresponding push_sample function. * Used internally by StreamOutlet to select the correct function. */ export const fmt2push_sample = { [cf_float32]: lsl_push_sample_f, [cf_double64]: lsl_push_sample_d, [cf_string]: lsl_push_sample_str, [cf_int32]: lsl_push_sample_i, [cf_int16]: lsl_push_sample_s, [cf_int8]: lsl_push_sample_c, [cf_int64]: lsl_push_sample_l, }; /** * Maps channel format to corresponding push_chunk function (single timestamp). * Used for pushing chunks where all samples share the same timestamp. */ export const fmt2push_chunk = { [cf_float32]: lsl_push_chunk_f, [cf_double64]: lsl_push_chunk_d, [cf_string]: lsl_push_chunk_str, [cf_int32]: lsl_push_chunk_i, [cf_int16]: lsl_push_chunk_s, [cf_int8]: lsl_push_chunk_c, [cf_int64]: lsl_push_chunk_l, }; /** * Maps channel format to corresponding push_chunk function (multiple timestamps). * Used for pushing chunks where each sample has its own timestamp. */ export const fmt2push_chunk_n = { [cf_float32]: lsl_push_chunk_ft, [cf_double64]: lsl_push_chunk_dt, [cf_string]: lsl_push_chunk_strt, [cf_int32]: lsl_push_chunk_it, [cf_int16]: lsl_push_chunk_st, [cf_int8]: lsl_push_chunk_ct, [cf_int64]: lsl_push_chunk_lt, }; /** * Maps channel format to corresponding pull_sample function. * Used internally by StreamInlet to select the correct function. */ export const fmt2pull_sample = { [cf_float32]: lsl_pull_sample_f, [cf_double64]: lsl_pull_sample_d, [cf_string]: lsl_pull_sample_str, [cf_int32]: lsl_pull_sample_i, [cf_int16]: lsl_pull_sample_s, [cf_int8]: lsl_pull_sample_c, [cf_int64]: lsl_pull_sample_l, }; /** * Maps channel format to corresponding pull_chunk function. * Used for pulling multiple samples efficiently. */ export const fmt2pull_chunk = { [cf_float32]: lsl_pull_chunk_f, [cf_double64]: lsl_pull_chunk_d, [cf_string]: lsl_pull_chunk_str, [cf_int32]: lsl_pull_chunk_i, [cf_int16]: lsl_pull_chunk_s, [cf_int8]: lsl_pull_chunk_c, [cf_int64]: lsl_pull_chunk_l, }; //# sourceMappingURL=index.js.map