<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://www.bci2000.org/mediawiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Mellinger</id>
	<title>BCI2000 Wiki - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://www.bci2000.org/mediawiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Mellinger"/>
	<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php/Special:Contributions/Mellinger"/>
	<updated>2026-08-15T12:56:23Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.43.8</generator>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=Technical_Reference:Operator_Library&amp;diff=12564</id>
		<title>Technical Reference:Operator Library</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=Technical_Reference:Operator_Library&amp;diff=12564"/>
		<updated>2026-08-12T14:34:44Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* BCI_OnQuitRequest( void* refdata, const char** pMsg ) */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;The Operator Library is a shared library that encapsulates the Operator Module&#039;s core functionality into an interface that allows to embed Operator functionality into one&#039;s own application. This way, is is possible to write your own Operator module, or to control BCI2000 from your own application, without having to deal with details of the BCI2000 protocol. The BCI2000 Operator library can be used from any programming language that allows using a shared library (DLL). Although the Operator library is written in C++, its DLL interface is plain C, and can be used with compiled languages such as C, C++, C#, Pascal, but also with interpreted languages that can call functions in a DLL, such as Python, Matlab, VisualBasic, etc. Interfacing with Java requires a JNI (Java Native Interface) wrapper, which is currently not provided by BCI2000. &lt;br /&gt;
&lt;br /&gt;
For a C++ example that uses the Operator Library to implement a simple Operator Module, see [http://www.bci2000.org/tracproj/browser/trunk/src/contrib/OperatorLibApplications/OperatorLibDemo/main.cpp the OperatorLibDemo] program.&lt;br /&gt;
&lt;br /&gt;
==System State (State of Operation)==&lt;br /&gt;
BCI2000 system state may be queried using &amp;lt;tt&amp;gt;BCI_GetStateOfOperation()&amp;lt;/tt&amp;gt;, and modified using the &amp;lt;tt&amp;gt;BCI_SetConfig()&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;BCI_StartRun()&amp;lt;/tt&amp;gt;, and &amp;lt;tt&amp;gt;BCI_StopRun()&amp;lt;/tt&amp;gt; functions. These functions manipulate system state in a way that is analogous to the main buttons in the standard [[User Reference:Operator Module|Operator module]].&lt;br /&gt;
&lt;br /&gt;
System state is one of the following:&lt;br /&gt;
===BCI_StateUnavailable===&lt;br /&gt;
The system is not available for changes to system state.&lt;br /&gt;
&lt;br /&gt;
===BCI_StateIdle===&lt;br /&gt;
The system is initialized, but not started up, ie., not waiting for connections.&lt;br /&gt;
&lt;br /&gt;
===BCI_StateStartup===&lt;br /&gt;
The Operator module is waiting for core modules to connect.&lt;br /&gt;
===BCI_StateInitialization===&lt;br /&gt;
Core modules are connected, the system is waiting to be parameterized via &amp;lt;tt&amp;gt;SetConfig()&amp;lt;/tt&amp;gt;.&lt;br /&gt;
===BCI_StateResting===&lt;br /&gt;
The system has been parameterized and is waiting for a &amp;lt;tt&amp;gt;StartRun()&amp;lt;/tt&amp;gt; command.&lt;br /&gt;
===BCI_StateSuspended===&lt;br /&gt;
The system has been suspended via &amp;lt;tt&amp;gt;StopRun()&amp;lt;/tt&amp;gt;, or from one of the modules setting the &amp;quot;Running&amp;quot; state variable to 0.&lt;br /&gt;
===BCI_StateParamsModified===&lt;br /&gt;
The system has been suspended, and parameters have been modified from one or more of the core modules.&lt;br /&gt;
===BCI_StateRunning===&lt;br /&gt;
The system is running.&lt;br /&gt;
===BCI_StateBusy===&lt;br /&gt;
The system is currently busy, and not in one of the above states. It is not available for changes to system state.&lt;br /&gt;
&lt;br /&gt;
System state may be &#039;&#039;&#039;manipulated&#039;&#039;&#039; calling one of the following functions:&lt;br /&gt;
&lt;br /&gt;
===int BCI_GetStateOfOperation()===&lt;br /&gt;
Determines the externally visible state of the state machine, i.e.&lt;br /&gt;
the state of operation of the BCI2000 system.&lt;br /&gt;
===int BCI_SetConfig()===&lt;br /&gt;
Asynchronously applies current parameter settings to the BCI2000 system, i.e. initiates the parameterization process, and returns immediately, indicating whether parameterization has been initiated.&lt;br /&gt;
;returns:   1 if successful, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
===int BCI_StartRun()===&lt;br /&gt;
Asynchronously starts a new run.&lt;br /&gt;
;returns:   1 if successful, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
===int BCI_StopRun()===&lt;br /&gt;
Asynchronously stops the current run.&lt;br /&gt;
;returns:   1 if successful, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
==Parameterization==&lt;br /&gt;
Parameterization functions allow to populate and modify of the Operator Library&#039;s internal parameter list, state list, event list, and visproperty list. When calling &amp;lt;tt&amp;gt;SetConfig()&amp;lt;/tt&amp;gt;, parameter values are applied to the BCI2000 system.&lt;br /&gt;
&lt;br /&gt;
===int BCI_PutParameter( const char* parameterLine )===&lt;br /&gt;
Parses a BCI2000 parameter definition line, and adds the resulting parameter object to the internal parameter list, or changes the value of a parameter if it exists.&lt;br /&gt;
;arguments: Pointer to a null-terminated parameter line string.&lt;br /&gt;
;returns:   1 if successful, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
===const char* BCI_GetParameter( long index )===&lt;br /&gt;
Returns the parameter with the given index from the operator&#039;s internal parameter list, or NULL when the index is out of range.&lt;br /&gt;
;arguments: Parameter index.&lt;br /&gt;
;returns:   Pointer to a null-terminated string containing a parameter line, or NULL. The output buffer is allocated by the library, and should be released by the caller using &amp;lt;tt&amp;gt;BCI_ReleaseObject()&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===int BCI_PutState( const char* stateLine )===&lt;br /&gt;
Parses a BCI2000 state definition line, and adds the resulting state to the operator library&#039;s state list.&lt;br /&gt;
;arguments: Pointer to a null-terminated state line string.&lt;br /&gt;
;returns:   1 if successful, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
===const char* BCI_GetState( long index )===&lt;br /&gt;
Returns the state with the given index from the DLL&#039;s internal state list. , or NULL when the index is out of range.&lt;br /&gt;
;arguments: State index.&lt;br /&gt;
;returns:   Pointer to a null-terminated string containing a state line, or NULL. The output buffer is allocated by the library, and should be released by the caller using &amp;lt;tt&amp;gt;BCI_ReleaseObject()&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===int BCI_PutEvent( const char* eventLine )===&lt;br /&gt;
Parses a BCI2000 event definition line, and adds the resulting event to the operator library&#039;s event list.&lt;br /&gt;
;arguments: Pointer to a null-terminated event line string.&lt;br /&gt;
;returns:   1 if successful, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
===const char* BCI_GetEvent( long index )===&lt;br /&gt;
Returns the event with the given index from the DLL&#039;s internal event list. , or NULL when the index is out of range.&lt;br /&gt;
;arguments: Event index.&lt;br /&gt;
;returns:   Pointer to a null-terminated string containing an event line, or NULL. The output buffer is allocated by the library, and should be released by the caller using &amp;lt;tt&amp;gt;BCI_ReleaseObject()&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===int BCI_PutVisProperty( const char* visID, int cfgID, const char* value )===&lt;br /&gt;
Sets the a property to the given value, or adds the property to the property list if it is not present. A list of visualization properties is available at [[Technical Reference:Visualization Properties]].&lt;br /&gt;
;arguments: Pointer to a null-terminated vis ID string, numeric config ID, and a pointer to a null-terminated value string.&lt;br /&gt;
;returns:   1 if successful, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
===const char* BCI_GetVisProperty( const char* visID, int cfgID )===&lt;br /&gt;
Returns the property with the given cfgID from the DLL&#039;s internal property list. A list of visualization properties is available at [[Technical Reference:Visualization Properties]].&lt;br /&gt;
;arguments: Pointer to a null-terminated vis ID string, numeric config ID.&lt;br /&gt;
;returns:   Pointer to a null-terminated string containing a property line, or NULL. The output buffer is allocated by the library, and should be released by the caller using &amp;lt;tt&amp;gt;BCI_ReleaseObject()&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==Online Information==&lt;br /&gt;
These functions allow to access information processed when the system is running. Modifying states and setting events will affect what is recorded in the data file.&lt;br /&gt;
&lt;br /&gt;
===int BCI_SetStateValue( const char* stateName, long value )===&lt;br /&gt;
Sets the value of a state to a given value. Setting a state via &amp;lt;tt&amp;gt;BCI_SetStateValue()&amp;lt;/tt&amp;gt; may result in a change to BCI2000 system state.&lt;br /&gt;
;arguments: Pointer to a null-terminated state name string; new state value.&lt;br /&gt;
;returns:   1 if successful, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
===long BCI_GetStateValue( const char* stateName )===&lt;br /&gt;
Returns the value of a state. &#039;&#039;&#039;NOTE:&#039;&#039;&#039; During operation, state values will only be updated when sent by the last module in the chain of core modules. This is governed by the &amp;lt;tt&amp;gt;OperatorBackLink&amp;lt;/tt&amp;gt; parameter on the &amp;lt;tt&amp;gt;System&amp;lt;/tt&amp;gt; tab, which must be set to 1.&lt;br /&gt;
;arguments: Pointer to a null-terminated state name string.&lt;br /&gt;
;returns:   State value, or 0 if the state does not exist.&lt;br /&gt;
&lt;br /&gt;
===int BCI_SetEvent( const char* eventName, long value )===&lt;br /&gt;
Asynchronously sets the value of an event. In the data file, the point in time where SetEvent() was called will be associated with a single sample.&lt;br /&gt;
;arguments: Pointer to a null-terminated event name string; new event value.&lt;br /&gt;
;returns:   1 if successful, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
===int BCI_GetSignalChannels()===&lt;br /&gt;
Returns the number of channels in the control signal.&lt;br /&gt;
===int BCI_GetSignalElements()===&lt;br /&gt;
Returns the number of elements in the control signal.&lt;br /&gt;
&lt;br /&gt;
===float BCI_GetSignal( int channel, int element )===&lt;br /&gt;
Returns a value from the control signal. &#039;&#039;&#039;NOTE:&#039;&#039;&#039; During operation, control signal values will only be updated when sent by the last module in the chain of core modules. This is governed by the &amp;lt;tt&amp;gt;OperatorBackLink&amp;lt;/tt&amp;gt; parameter on the &amp;lt;tt&amp;gt;System&amp;lt;/tt&amp;gt; tab, which must be set to 1.&lt;br /&gt;
;arguments: Channel index, element index (zero-based).&lt;br /&gt;
;returns:   Signal value.&lt;br /&gt;
&lt;br /&gt;
==Watches==&lt;br /&gt;
Watches are expressions that are tied to &#039;&#039;callback events&#039;&#039; (see below). Whenever the expression&#039;s value changes, the associated callback event is triggered. Use &amp;lt;tt&amp;gt;BCI_AddWatch&amp;lt;/tt&amp;gt; to create a watch a receive a unique callback event ID, followed with &amp;lt;tt&amp;gt;BCI_SetCallback()&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;BCI_SetExternalCallback()&amp;lt;/tt&amp;gt; to register a callback function with the watch&#039;s event.&lt;br /&gt;
Whenever the watch is triggered, your callback function will be called with the expression&#039;s current value in string format.&lt;br /&gt;
&lt;br /&gt;
===long BCI_AddWatch( const char* expressions )===&lt;br /&gt;
Create a watch consisting of one or more expressions. An event is triggered whenever&lt;br /&gt;
one of the expressions changes its value. Expressions are evaluated as described for&lt;br /&gt;
the EVALUATE EXPRESSION scripting command.&lt;br /&gt;
You may associate a callback with the created watch by specifying its watch ID as an&lt;br /&gt;
event ID to any of the callback registration functions. To the callback function, an&lt;br /&gt;
additional argument of type const char* will be provided, which contains a string&lt;br /&gt;
representation of all current expression values, separated by tab characters, and terminated&lt;br /&gt;
with a Windows newline sequence (CRLF).&lt;br /&gt;
The argument is a string containing a list of expressions, separated by tab characters.&lt;br /&gt;
The function returns a watch ID if successful, BCI_None otherwise. The function will fail if the list of expressions&lt;br /&gt;
contains an invalid expression.&lt;br /&gt;
&lt;br /&gt;
===int BCI_SetWatchDecimation( long watchID, int decimation )===&lt;br /&gt;
Set decimation for a watch. By default, decimation is 1, and expressions will be evaluated&lt;br /&gt;
for each sample in a data block. For higher values of decimation, every n-th sample will be evaluated.&lt;br /&gt;
The function returns 1 if successful, 0 if no watch was registered with the given ID.&lt;br /&gt;
&lt;br /&gt;
===int BCI_RemoveWatch( long watchID )===&lt;br /&gt;
Removes a watch that was created using BCI_AddWatch(), and unregisters its associated callback function.&lt;br /&gt;
The Watch ID argument is a watch ID as returned by BCI_AddWatch.&lt;br /&gt;
Returns 1 if successful, 0 if no watch was registered with the given ID.&lt;br /&gt;
&lt;br /&gt;
==Callbacks==&lt;br /&gt;
Whenever the Operator Library receives data, or its state is modified, a &#039;&#039;callback event&#039;&#039; is triggered. In response to this event, a callback function is called. Callback functions are registered with &amp;lt;tt&amp;gt;BCI_SetCallback()&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;BCI_SetExternalCallback()&amp;lt;/tt&amp;gt;. The difference between the two registration functions is that the first registers a callback function to be called within the context of an internal thread of the Operator Library, while the second registers a callback function to be executed in a thread owned by the application that uses the Operator library, i.e. a thread that is &amp;quot;external&amp;quot; to the Operator library. That &amp;quot;external&amp;quot; thread needs to call &amp;lt;tt&amp;gt;BCI_CheckPendingCallback()&amp;lt;/tt&amp;gt; regularly in order to execute callback functions. Typically, &amp;lt;tt&amp;gt;BCI_CheckPendingCallback()&amp;lt;/tt&amp;gt; would be called from a GUI application&#039;s main thread within its message handling loop.&lt;br /&gt;
&lt;br /&gt;
Callback events are listed with their event names, and their handlers&#039; arguments. A callback function has at least one argument, which is a void* named refdata. The refdata pointer is supplied by user code when registering the callback function, and provided to the callback function unchanged. Typically, it would be used to store an object pointer in C++, or to an application specific data structure in C.&lt;br /&gt;
&lt;br /&gt;
In Windows, callbacks must follow the &#039;&#039;stdcall&#039;&#039; calling convention. In the Operator Library&#039;s header file, a macro STDCALL is provided that should be inserted after a callback function&#039;s return type in the function declaration. This macro allows setting the calling convention without confusing compilers when compiling for non-Windows systems.&lt;br /&gt;
&lt;br /&gt;
When programming in C++, callback functions must be either global functions, or functions declared &amp;quot;static&amp;quot; within class scope. They may not be ordinary member functions of a class. In object oriented programming however, you will typically want an ordinary member function to be called as a callback. This is possible using the &amp;quot;refdata&amp;quot; pointer to store an object pointer, and then call a member function on that object from the callback function as in the following example:&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
class MyOperator&lt;br /&gt;
{&lt;br /&gt;
 public:&lt;br /&gt;
  MyOperator()&lt;br /&gt;
  {&lt;br /&gt;
    BCI_SetCallback( BCI_OnSystemStateChange, BCI_Function( &amp;amp;OnSystemStateChangeCallback ), this );&lt;br /&gt;
  }&lt;br /&gt;
  ~MyOperator()&lt;br /&gt;
  {&lt;br /&gt;
    BCI_SetCallback( BCI_OnSystemStateChange, NULL, NULL );&lt;br /&gt;
  }&lt;br /&gt;
 private:&lt;br /&gt;
  void OnSystemStateChange()&lt;br /&gt;
  {&lt;br /&gt;
    ... // ordinary member function that gets executed when system state changes&lt;br /&gt;
  }&lt;br /&gt;
  static void STDCALL OnSystemStateChangeCallback( void* refdata )&lt;br /&gt;
  {&lt;br /&gt;
    MyOperator* pObject = reinterpret_cast&amp;lt;MyOperator*&amp;gt;( refdata );&lt;br /&gt;
    pObject-&amp;gt;OnSystemStateChange();&lt;br /&gt;
  }&lt;br /&gt;
};&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
In the following, callbacks are listed as if they were ordinary functions. However, you will not use them this way. Rather, the &amp;quot;function name&amp;quot; is the name of the callback that is supplied as the first argument to &amp;lt;tt&amp;gt;BCI_SetCallback()&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;BCI_SetExternalCallback()&amp;lt;/tt&amp;gt;. Your own callback function will have a different name, must return &amp;quot;void&amp;quot; or &amp;quot;int&amp;quot; as indicated, and must take the arguments listed as arguments to the callback. A pointer to your own callback function will then be specified as the second argument to &amp;lt;tt&amp;gt;BCI_SetCallback()&amp;lt;/tt&amp;gt;, casted to the &amp;lt;tt&amp;gt;BCI_Function&amp;lt;/tt&amp;gt; type (see the example above).&lt;br /&gt;
&lt;br /&gt;
===Callback Constants===&lt;br /&gt;
;BCI_Handled&lt;br /&gt;
:Return this constant to indicate that your callback handled the event. Only used with callback functions listed with an &amp;quot;int&amp;quot; return type.&lt;br /&gt;
;BCI_NotHandled&lt;br /&gt;
:Return this constant to indicate that your callback did not handle the event in question. Only used with callback functions listed with an &amp;quot;int&amp;quot; return type.&lt;br /&gt;
&lt;br /&gt;
=== BCI_OnSystemStateChange( void* refdata )===&lt;br /&gt;
Occurs on any change to BCI2000 system state.&lt;br /&gt;
===BCI_OnCoreInput( void* refdata )===&lt;br /&gt;
Occurs on input from any of the BCI2000 core modules.&lt;br /&gt;
===BCI_OnConnect( void* refdata )===&lt;br /&gt;
Occurs when core modules have connected to the Operator Library.&lt;br /&gt;
===BCI_OnSetConfig( void* refdata )===&lt;br /&gt;
Triggered when &amp;lt;tt&amp;gt;SetConfig()&amp;lt;/tt&amp;gt; has been called.&lt;br /&gt;
===BCI_OnStart( void* refdata )===&lt;br /&gt;
Triggered when &amp;lt;tt&amp;gt;StartRun()&amp;lt;/tt&amp;gt; has been called.&lt;br /&gt;
===BCI_OnSuspend( void* refdata )===&lt;br /&gt;
Triggered when BCI2000 goes into suspended state.&lt;br /&gt;
===BCI_OnResume( void* refdata )===&lt;br /&gt;
Triggered when BCI2000 enters the running state again.&lt;br /&gt;
===BCI_OnShutdown( void* refdata )===&lt;br /&gt;
Triggered when &amp;lt;tt&amp;gt;BCI_Shutdown()&amp;lt;/tt&amp;gt; is called.&lt;br /&gt;
&lt;br /&gt;
===BCI_OnQuitRequest( void* refdata, const char** pMsg )===&lt;br /&gt;
Triggered when a script executes the QUIT command. The application may choose to ignore the quit request, and should set the &amp;quot;message&amp;quot; pointer to a constant or static C string in this case. That string should provide information why the quit request was ignored.&lt;br /&gt;
&lt;br /&gt;
===BCI_OnLogMessage( void* refdata, const char* msg )===&lt;br /&gt;
Triggered for each log message arriving from one of the modules, including the Operator module itself.&lt;br /&gt;
===BCI_OnWarningMessage( void* refdata, const char* msg )===&lt;br /&gt;
Triggered for warning messages. Note that also a &amp;lt;tt&amp;gt;BCI_OnLogMessage&amp;lt;/tt&amp;gt; event is triggered for warning messages.&lt;br /&gt;
===BCI_OnErrorMessage( void* refdata, const char* msg )===&lt;br /&gt;
Triggered for error messages. Note that also a &amp;lt;tt&amp;gt;BCI_OnLogMessage&amp;lt;/tt&amp;gt; event is triggered for error messages.&lt;br /&gt;
===BCI_OnDebugMessage( void* refdata, const char* msg )===&lt;br /&gt;
Triggered for each [[Programming_Reference:Debug_Output|debug message]] received by the Operator module.&lt;br /&gt;
&lt;br /&gt;
===BCI_OnParameter( void* refdata, const char* parameterline )===&lt;br /&gt;
Triggered whenever a parameter message arrives at the Operator module.&lt;br /&gt;
===BCI_OnState( void* refdata, const char* stateline )===&lt;br /&gt;
Triggered whenever a state message arrives at the Operator module.&lt;br /&gt;
===BCI_OnVisPropertyMessage( void* refdata, const char* visID, int cfgID, const char* value )===&lt;br /&gt;
Triggered whenever a visproperty message arrives at the Operator module. For a list of available visualization properties, see [[Technical Reference:Visualization Properties]]. Constants for the cfgID parameter are defined in the &amp;lt;tt&amp;gt;src/shared/types/CfgID.h&amp;lt;/tt&amp;gt; header file.&lt;br /&gt;
&lt;br /&gt;
===BCI_OnVisProperty( void* refdata, const char* visID, int cfgID, const char* value )===&lt;br /&gt;
Triggered when a visualization property is modified from a script. For a list of available visualization properties, see [[Technical Reference:Visualization Properties]]. Constants for the cfgID parameter are defined in the &amp;lt;tt&amp;gt;src/shared/types/CfgID.h&amp;lt;/tt&amp;gt; header file. The &amp;quot;value&amp;quot; parameter may be NULL to indicate that the property should be reset to default (e.g., by removing it from the property store).&lt;br /&gt;
&lt;br /&gt;
===BCI_OnInitializeVis( void* refdata, const char* visID, const char* kind )===&lt;br /&gt;
Triggered when the first data message for a visualization window arrives. The &amp;lt;tt&amp;gt;kind&amp;lt;/tt&amp;gt; argument contains a string describing the kind of visualization required for; it is one of &amp;quot;Graph&amp;quot;, &amp;quot;Memo&amp;quot;, &amp;quot;Bitmap&amp;quot;, or &amp;quot;VideoFrame&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
===BCI_OnVisMemo( void* refdata, const char* visID, const char* msg )===&lt;br /&gt;
Triggered for each memo data message to a visualization window. The &amp;lt;tt&amp;gt;visID&amp;lt;/tt&amp;gt; argument specifies the window&#039;s visualization ID, the &amp;lt;tt&amp;gt;msg&amp;lt;/tt&amp;gt; argument specifies the memo data.&lt;br /&gt;
===BCI_OnVisSignal( void* refdata, const char* visID, int channels, int elements, float* data )===&lt;br /&gt;
Triggered for each signal data message to a visualization window. Data are given such that the pair of indices &amp;lt;tt&amp;gt;(ch,el)&amp;lt;/tt&amp;gt; corresponds to the single index &amp;lt;tt&amp;gt;ch*elements+el&amp;lt;/tt&amp;gt;. Note that this is different from the layout of bitmap data.&lt;br /&gt;
&lt;br /&gt;
===BCI_OnVisSignalHandle( void* refdata, const char* visID, void* signalHandle )===&lt;br /&gt;
Triggered for each signal data message to a visualization window. Note that using this callback is more efficient than the previous &#039;&#039;BCI_OnVisSignal&#039;&#039; because it allows to access signal data received through shared memory without copying. To access signal data behind the opaque handle, use the &#039;&#039;BCI_HandleGetSignalData()&#039;&#039; function explained below.&lt;br /&gt;
&lt;br /&gt;
===BCI_OnVisBitmap( void* refdata, const char* visID, int width, int height, signed short* data )===&lt;br /&gt;
Triggered for each bitmap data message to a visualization window. Data are given in ARGB 1444 format, where the 12 bits with lowest significance represent color, and the four most significant of these 12 bits represent the R component. When any of the 3 bits with higher significance is set, the pixel is assumed to be transparent. &lt;br /&gt;
&lt;br /&gt;
In actual images, the most significant bit is never set but allows for transmission of difference images between frames.  Difference frames are computed by subtracting pixel values as if they were signed 16 bit integers, i.e. without regard to their interpretation as ARGB colors.&lt;br /&gt;
In transmission, empty bitmaps (width and height set to 0) are sent to indicate that the next bitmap will be a reference frame; between reference frames, difference frames are sent.&lt;br /&gt;
&lt;br /&gt;
When addressing pixels, the pair of indices &amp;lt;tt&amp;gt;(x,y)&amp;lt;/tt&amp;gt; corresponds to the single index &amp;lt;tt&amp;gt;y*width+x&amp;lt;/tt&amp;gt;. Note that this is different from the layout of signal messages.&lt;br /&gt;
&lt;br /&gt;
===BCI_OnVisVideoFrame( void* refdata, const char* visID, int width, int height, unsigned int* data )===&lt;br /&gt;
Triggered for each video frame message sent to the Operator module. Data are given in ARGB 8888 format.&lt;br /&gt;
&lt;br /&gt;
When addressing pixels, the pair of indices &amp;lt;tt&amp;gt;(x,y)&amp;lt;/tt&amp;gt; corresponds to the single index &amp;lt;tt&amp;gt;y*width+x&amp;lt;/tt&amp;gt;. Note that this is different from the layout of signal messages.&lt;br /&gt;
&lt;br /&gt;
===int BCI_OnUnknownCommand( void* refdata, const char* command, const char** output, const char** error )===&lt;br /&gt;
Triggered when the [[User Reference:Operator Module Scripting|Operator Module Scripting]] engine meets an unknown command. The event handler can be used to extend the set of commands by handling those commands that are unknown to the scripting engine. To report that a command has been handled, return &amp;lt;tt&amp;gt;BCI_Handled&amp;lt;/tt&amp;gt; from the callback function. Otherwise, return a value of &amp;lt;tt&amp;gt;BCI_NotHandled&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
If your command produces any output, use &amp;lt;tt&amp;gt;BCI_AllocateString()&amp;lt;/tt&amp;gt; to allocate a string, and assign the resulting &amp;lt;tt&amp;gt;const char*&amp;lt;/tt&amp;gt; to &amp;lt;tt&amp;gt;*output&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
If your command produces any error messages, use &amp;lt;tt&amp;gt;BCI_AllocateString()&amp;lt;/tt&amp;gt; to allocate a string, and assign the resulting &amp;lt;tt&amp;gt;const char*&amp;lt;/tt&amp;gt; to &amp;lt;tt&amp;gt;*error&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===BCI_OnScriptHelp( void* refdata, const char** pMsg )===&lt;br /&gt;
Triggered when the scripting engine&#039;s &amp;quot;Help&amp;quot; command is executed. Assign the address of a string constant to the memory location received in &amp;lt;tt&amp;gt;pMsg&amp;lt;/tt&amp;gt;. The contents of the string will be printed last in the list of commands, preceded with &amp;quot;Application-defined commands: &amp;quot;.&lt;br /&gt;
&lt;br /&gt;
===BCI_OnScriptError( void* refdata, const char* msg )===&lt;br /&gt;
Triggered when script execution results in an error condition. Note that unknown commands are reported through the &amp;lt;tt&amp;gt;BCI_OnUnknownCommand&amp;lt;/tt&amp;gt; event handler to allow handling additional commands.&lt;br /&gt;
&lt;br /&gt;
===BCI_OnQuitRequest( void* refdata, const char** pMsg )===&lt;br /&gt;
This callback is executed when a script executes the QUIT command. It is the responsibility of your client program to shutdown the BCI2000 Operator library, and terminate the application. If you choose not to terminate, you may abort the Quit command by returning a message through the pMsg pointer. Use the &amp;lt;tt&amp;gt;BCI_AllocateString()&amp;lt;/tt&amp;gt; function to allocate such an error message.&lt;br /&gt;
&lt;br /&gt;
===BCI_OnNextFilePart( void* refdata, int part )===&lt;br /&gt;
This callback is executed when the &#039;&#039;FilePart&#039;&#039; event state is incremented. It receives the number of the the new file part as an argument.&lt;br /&gt;
&lt;br /&gt;
==Functions that manage callbacks==&lt;br /&gt;
&lt;br /&gt;
===int BCI_SetCallback( long, BCI_Function, void* )===&lt;br /&gt;
Register a callback function. To clear a callback function, specify NULL as a function pointer.&lt;br /&gt;
;arguments: Event ID, callback function pointer, data pointer. The callback function pointer must be cast to the &amp;lt;tt&amp;gt;BCI_Function&amp;lt;/tt&amp;gt; function pointer type. The data pointer may be any data that should be available to the callback function.&lt;br /&gt;
;returns:   1 if successful, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
===int BCI_SetExternalCallback( long, BCI_Function, void* )===&lt;br /&gt;
Register a callback function to be executed in an external thread on execution of &amp;lt;tt&amp;gt;BCI_CheckPendingCallback()&amp;lt;/tt&amp;gt;. To clear a callback function, specify NULL as a function pointer.&lt;br /&gt;
;arguments: Event ID, callback function pointer, data pointer. The callback function pointer must be cast to the &amp;lt;tt&amp;gt;BCI_Function&amp;lt;/tt&amp;gt; function pointer type. The data pointer may point to any data that should be available to the callback function.&lt;br /&gt;
;returns:   1 if successful, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
===int BCI_CheckPendingCallback()===&lt;br /&gt;
Call this function regularly from within an external thread you want external callbacks to run in. Typically, is is called from a GUI application&#039;s main event loop.&lt;br /&gt;
;arguments: None.&lt;br /&gt;
;returns:   1 if a callback was executed, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
===BCI_Function BCI_GetCallbackFunction( long )===&lt;br /&gt;
Get a registered callback function pointer.&lt;br /&gt;
;arguments: Event ID.&lt;br /&gt;
;returns:   Callback function pointer, or NULL if no callback function has been registered for the specified event.&lt;br /&gt;
&lt;br /&gt;
===void* BCI_GetCallbackData( long )===&lt;br /&gt;
Get registered callback data. Callback data is the first argument to callback functions, and specified when calling &amp;lt;tt&amp;gt;SetCallback()&amp;lt;/tt&amp;gt; or &amp;lt;tt&amp;gt;SetExternalCallback()&amp;lt;/tt&amp;gt;.&lt;br /&gt;
;arguments: Event ID.&lt;br /&gt;
;returns:   Callback data, or NULL if no callback data has been registered for the specified event.&lt;br /&gt;
&lt;br /&gt;
===int BCI_GetCallbackIsExternal( long )===&lt;br /&gt;
Get information how callback was registered.&lt;br /&gt;
;arguments: Event ID.&lt;br /&gt;
;returns:   1 if the function was registered with &amp;lt;tt&amp;gt;BCI_SetExternalCallback()&amp;lt;/tt&amp;gt;, and 0 if it was registered with &amp;lt;tt&amp;gt;BCI_SetCallback()&amp;lt;/tt&amp;gt;, or when no callback was registered.&lt;br /&gt;
&lt;br /&gt;
==Miscellaneous Functions==&lt;br /&gt;
===const char* BCI_GetInfo( void )===&lt;br /&gt;
Reports build and source version information.&lt;br /&gt;
;arguments: None.&lt;br /&gt;
;returns:   Pointer to a null-terminated string holding the information requested. The output buffer is allocated by the library, and should be released by the caller using &amp;lt;tt&amp;gt;BCI_ReleaseObject()&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===const char* BCI_GetConnectionInfo( int index )===&lt;br /&gt;
Obtains information about a [[Technical Reference:Core Modules|core module]] connection.&lt;br /&gt;
;argument:  Zero-based index of core module connection.&lt;br /&gt;
;returns:   Pointer to a null-terminated string containing connection information, or NULL for an invalid index. The output buffer is allocated by the library, and should be released by the caller using &amp;lt;tt&amp;gt;BCI_ReleaseObject()&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===const char* BCI_GetCoreModuleStatus( int index )===&lt;br /&gt;
Obtains a [[Technical Reference:Core Modules|core module&#039;s]] current status message.&lt;br /&gt;
;argument:  Zero-based index of core module connection.&lt;br /&gt;
;returns:   Pointer to a null-terminated string containing status information, or NULL for an invalid index. The output buffer is allocated by the library, and should be released by the caller using &amp;lt;tt&amp;gt;BCI_ReleaseObject()&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===int BCI_Startup( const char* arguments )===&lt;br /&gt;
Listens on core module connection ports.&lt;br /&gt;
;arguments: A string defining a local IP address (such as &amp;quot;localhost&amp;quot;, or &amp;quot;134.2.123.151&amp;quot;, or &amp;quot;*&amp;quot; to listen on all local addresses), and [[Technical Reference:Core Modules|core module]] names and listening ports in the form &amp;quot;&amp;lt;name1&amp;gt;:&amp;lt;port1&amp;gt; &amp;lt;name2:port2&amp;gt; ... &amp;lt;nameN:portN&amp;gt;&amp;quot; If NULL or empty, a value of &amp;quot;Source:4000 SignalProcessing:4001 Application:4002&amp;quot; representing a standard BCI2000 configuration is used, and the Operator module listens on all addresses.&lt;br /&gt;
;returns:   1 if successful, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
===int BCI_Shutdown( void )===&lt;br /&gt;
Close connections to core modules, and go into idle state.&lt;br /&gt;
;returns:   1 if successful, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
=== int BCI_Initialize( void )===&lt;br /&gt;
Initialize the library. Must be called before any other library function is used. This function should be called from your application&#039;s main thread.&lt;br /&gt;
;returns:   1 if no error occurred, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
=== int BCI_Dispose( void )===&lt;br /&gt;
Dispose of all resources allocated by the library. This function must be called from the thread that called BCI_Initialize().&lt;br /&gt;
;returns:   1 if no error occurred, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
=== int BCI_SetPerThreadWD( const char* directory)===&lt;br /&gt;
Sets the per-thread working directory stored inside the operator library.&lt;br /&gt;
Expects a pointer to a null-terminated string defining the working directory, in UTF8-encoding.&lt;br /&gt;
Returns 1 if successful (i.e., the directory exists), and 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
===BCI_PipeListen(const char* name)===&lt;br /&gt;
Start a telnet server, listening at the given named pipe.&lt;br /&gt;
;arguments: Pipe name.&lt;br /&gt;
;returns:   1 if no error occurred, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
===BCI_PipeCloseOne(const char* name)===&lt;br /&gt;
Stop a named pipe server, keeping connections open.&lt;br /&gt;
;arguments: Pipe name.&lt;br /&gt;
;returns:   1 if no error occurred, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
===BCI_PipeClose()===&lt;br /&gt;
Stop all named pipe servers, keeping connections open.&lt;br /&gt;
;returns:   1 if no error occurred, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
=== int BCI_TelnetListen( const char* address )===&lt;br /&gt;
Start a telnet server, listening at the given address. By opening a telnet connection, it is possible to execute [[User_Reference:Operator_Module_Scripting|scripting commands]] interactively.&lt;br /&gt;
Any number of telnet servers may be started listening on different addresses. If a telnet server is already running for a given&lt;br /&gt;
address, the request will be ignored, and 0 will be returned.&lt;br /&gt;
;arguments: Address as a string in &amp;lt;IP&amp;gt;:&amp;lt;port&amp;gt; format. May be NULL, in which case it defaults to &amp;quot;localhost:3999&amp;quot;.&lt;br /&gt;
;returns:   1 if no error occurred, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
=== int BCI_TelnetCloseOne( const char* address )===&lt;br /&gt;
Stop the telnet server that is listening at the given address.&lt;br /&gt;
If no telnet server is listening at the given address, 0 is returned.&lt;br /&gt;
;arguments: Address as a string in &amp;lt;IP&amp;gt;:&amp;lt;port&amp;gt; format.&lt;br /&gt;
;returns:   1 if a telnet server was listening at the given address, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
=== int BCI_TelnetClose( void )===&lt;br /&gt;
Stop all telnet servers, while keeping open connections.&lt;br /&gt;
;returns:   1 if no error occurred, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
=== int BCI_WebsocketListen( const char* address )===&lt;br /&gt;
Start a [https://en.wikipedia.org/wiki/WebSocket WebSocket] server, listening at the given address. By opening a WebSocket connection to &#039;&#039;ws://ip:port/&#039;&#039;, it is possible to execute [[User_Reference:Operator_Module_Scripting|scripting commands]] and receive command results.&lt;br /&gt;
Any number of websocket servers may be started listening on different addresses. If a websocket server is already running for a given&lt;br /&gt;
address, the request will be ignored, and 0 will be returned.&lt;br /&gt;
;arguments: Address as a string in &amp;lt;IP&amp;gt;:&amp;lt;port&amp;gt; format. May be NULL, in which case it defaults to &amp;quot;localhost:3998&amp;quot;.&lt;br /&gt;
;returns:   1 if no error occurred, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
=== int BCI_WebsocketCloseOne( const char* address )===&lt;br /&gt;
Stop the websocket server that is listening at the given address.&lt;br /&gt;
If no websocket server is listening at the given address, 0 is returned.&lt;br /&gt;
;arguments: Address as a string in &amp;lt;IP&amp;gt;:&amp;lt;port&amp;gt; format.&lt;br /&gt;
;returns:   1 if a websocket server was listening at the given address, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
=== int BCI_WebsocketClose( void )===&lt;br /&gt;
Stop all WebSocket servers, while keeping open connections.&lt;br /&gt;
;returns:   1 if no error occurred, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
===int BCI_ExecuteScript( const char* script )===&lt;br /&gt;
Interprets and executes the specified script according to the [[User Reference:Operator Module Scripting|Operator scripting reference]].&lt;br /&gt;
;arguments: Null-terminated string specifying script commands.&lt;br /&gt;
;returns:   0 if a syntax error is present, 1 otherwise.&lt;br /&gt;
&lt;br /&gt;
===const char* BCI_ExecuteScriptWithResult( const char* script )===&lt;br /&gt;
Interprets and executes the specified script according to the [[User Reference:Operator Module Scripting|Operator scripting reference]].&lt;br /&gt;
;arguments: Null-terminated string specifying script commands.&lt;br /&gt;
;returns:   NULL if a syntax error is present, pointer to a null-terminated result string otherwise. The result string is allocated by the library, and should be released by the caller using BCI_ReleaseObject(). The result is always identical to the result of the last executed script command.&lt;br /&gt;
&lt;br /&gt;
===void BCI_Log( int messageType, const char* message )===&lt;br /&gt;
Acts as if receiving the respective type of message from a module, i.e. typically calls the handler installed for that type of message.&lt;br /&gt;
;arguments: Type of message, which is one of BCI_LogMessage, BCI_Warning, BCI_Error, BCI_DebugMessage; null-terminated message string.&lt;br /&gt;
&lt;br /&gt;
===int BCI_ReleaseObject( const char* )===&lt;br /&gt;
Indicate that an object that has been allocated by the library is no longer needed by the library&#039;s client.&lt;br /&gt;
;arguments: Object to be released, or NULL.&lt;br /&gt;
;returns:   1 if successful, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
===const char* BCI_AllocateString( const char* )===&lt;br /&gt;
Allocate a copy of the given string on the Operator Library&#039;s heap.&lt;br /&gt;
;arguments: Pointer to a null-terminated string.&lt;br /&gt;
;returns:   Pointer to a null-terminated copy of the argument string. The result is allocated by the library and should be released using &amp;lt;tt&amp;gt;BCI_ReleaseObject()&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===const char* BCI_GetPerThreadWD()===&lt;br /&gt;
Returns the current per-thread working directory stored inside the operator library, in UTF8-encoding.&lt;br /&gt;
The purpose of this command is to allow scripting commands implemented inside the library&#039;s caller to obtain the working directory that applies for the current script (see the &amp;lt;tt&amp;gt;BCI_OnUnknownCommand()&amp;lt;/tt&amp;gt; callback).&lt;br /&gt;
;returns:   Pointer to a null-terminated string containing the result. The result is allocated by the library, and should be released by the caller using &amp;lt;tt&amp;gt;BCI_ReleaseObject()&amp;lt;/tt&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
===int BCI_SetPerThreadWD(const char*)===&lt;br /&gt;
Set the per-thread working directory stored inside the operator library.&lt;br /&gt;
The purpose of this command is to allow scripting commands implemented inside the library&#039;s caller to modify the working directory that applies for the current script (see the &amp;lt;tt&amp;gt;BCI_OnUnknownCommand()&amp;lt;/tt&amp;gt; callback).&lt;br /&gt;
;arguments: Pointer to a null-terminated string defining the working directory, in UTF8-encoding.&lt;br /&gt;
;returns:   1 if successful, 0 otherwise.&lt;br /&gt;
&lt;br /&gt;
===BCI_HandleAddRef(void* handle)===&lt;br /&gt;
Increments the use counter of the object referenced by a handle.&lt;br /&gt;
;arguments: A handle received from BCI_OperatorLib, e.g. as an argument to BCI_OnVisSignalHandle().&lt;br /&gt;
&lt;br /&gt;
===BCI_HandleRelease(void* handle)===&lt;br /&gt;
Decrements the use counter of the object referenced by a handle, destroying the object when its use counter reaches zero.&lt;br /&gt;
;arguments: A handle received from BCI_OperatorLib, e.g. as an argument to BCI_OnVisSignalHandle().&lt;br /&gt;
&lt;br /&gt;
===BCI_HandleGetSignalData(void* handle, int* channels, int* elements, double** pData)===&lt;br /&gt;
Gives access to data managed by a signal handle.&lt;br /&gt;
;arguments: A handle received from BCI_OperatorLib, e.g. as an argument to BCI_OnVisSignalHandle().&lt;br /&gt;
;returns:   1 if successful, 0 if not (e.g. the handle is invalid).&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[User Reference:Operator Module Scripting]], [[Technical Reference:States of Operation]], [[Programming Reference:Debug Output]], [[Technical Reference:BCI2000Remote Library]]&lt;br /&gt;
&lt;br /&gt;
[[Category:External Interfaces]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Operator_Module_Scripting&amp;diff=12563</id>
		<title>User Reference:Operator Module Scripting</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Operator_Module_Scripting&amp;diff=12563"/>
		<updated>2026-08-09T16:04:41Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* START WEBSOCKET , STOP WEBSOCKET  */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Operator scripts automate actions that the otherwise would be performed by the user, e.g. starting or suspending system operation.&lt;br /&gt;
Scripts may be contained in script files, or given immediately in the operator module&#039;s preferences dialog.&lt;br /&gt;
There is also an option to specify scripts from the [[User Reference:Module Command Line Options#Operator Module|command line]] when starting the operator module. When using the [[User_Reference:BCI2000Shell|BCI2000Shell]], or the [[Technical_Reference:Operator_Library|Operator Library]] from your own application, you may execute scripts at any time.&lt;br /&gt;
&lt;br /&gt;
In addition, the operator scripting language may be used to control an operator module over a [[User_Reference:Module_Command_Line_Options#--Telnet|Telnet]] or [[User_Reference:Module_Command_Line_Options#--WebSocket|WebSocket]] connection.&lt;br /&gt;
===Syntax===&lt;br /&gt;
&#039;&#039;&#039;Command separation.&#039;&#039;&#039; Scripts consist of sequences of the commands listed below. A command must be terminated with either a newline, or a semicolon (;).&lt;br /&gt;
This allows to put multiple commands into one line, separated by semicolon characters.&lt;br /&gt;
Commands are case-insensitive, variables and values may be case-sensitive, depending on context.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Comments.&#039;&#039;&#039; Lines starting with a &#039;#&#039; character are ignored. Such lines may be used to hold comments. In addition, when any of the first two lines of a script contains &amp;quot;#!&amp;quot; (the Unix shell invocation sequence), it will be ignored. In conjunction with [[User Reference:BCI2000Shell|BCI2000Shell]], this may be used to write Operator scripts that may be treated as executables.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Escaping.&#039;&#039;&#039; In order to resolve ambiguity about command arguments that contain white space, they must be included in double quotes, or the white space must be encoded in URL-fashion, e.g. &#039;&#039;%20&#039;&#039; instead of a space character. Similarly, when an argument contains a semicolon (;), it must be included in double quotes, or the semicolon must be encoded in URL-fashion, i.e. as &#039;&#039;%3B&#039;&#039;. Also, &amp;quot;$&amp;quot; characters indicate command substitution, so they should be encoded as &#039;&#039;%24&#039;&#039; if substitution is not desired.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Variable Substitution.&#039;&#039;&#039; When a command contains a dollar sign, alphanumeric characters following the dollar sign will be interpreted as the name of a variable: $NAME. The name will be matched against Expression variable names first, followed with Local variable names, and finally Environment variable names. When a match is found, $NAME will be replaced with the content of the matching variable. When no match is found, $NAME will be resolved to an empty string, without triggering an error.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Command Substitution.&#039;&#039;&#039; When part of a command is enclosed with ${...}, this subexpression will be substituted with the result of its execution as a command. E.g.,&lt;br /&gt;
 LOG &amp;quot;Current system state is: ${GET SYSTEM STATE}&amp;quot;&lt;br /&gt;
 LOG &amp;quot;The path environment variable is: ${PATH}&amp;quot;&lt;br /&gt;
Note that the last example uses the short form of the GET command, which will return the value of a PATH parameter or a PATH state if such exists. To make sure that only environment variables are matched, use the long form of the GET command:&lt;br /&gt;
 LOG MESSAGE &amp;quot;The path environment variable is: ${GET VARIABLE PATH}&amp;quot;&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
Substitutions may be nested, i.e. the following will work as expected:&lt;br /&gt;
 SET MyVar &amp;quot;LIST STATES&amp;quot;; LOG &amp;quot;States are: ${$MyVar}&amp;quot;&lt;br /&gt;
 SET MyVar &amp;quot;LIST STATES&amp;quot;; LOG &amp;quot;States are: ${${GET VARIABLE MyVar}}&amp;quot;&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Mathematical Expressions.&#039;&#039;&#039; A command may consist of a single [[User Reference:Expression Syntax|mathematical expression]]. This expression is then evaluated, and its result is returned as the command&#039;s result. As a special case, this allows the use of expression variables in ${...} substitutions. Consider for example&lt;br /&gt;
 x:=0; WHILE x&amp;lt;10; LOG ${x:=x+1}; END&lt;br /&gt;
There, the first command creates and initializes the expression variable x. In the WHILE condition, an expression is allowed as well as any command. In the LOG command, an expression appears in ${...}, which executes the expression in braces, and substitutes the result of the expression as an argument into the LOG command, which adds an entry to the Operator log. This results in a sequence of 10 log entries, containing the numbers from 1 to 10.&lt;br /&gt;
&lt;br /&gt;
===Commands===&lt;br /&gt;
====Control commands====&lt;br /&gt;
These commands allow conditional execution of parts of a script. When a condition is expected, any other scripting command may be given. Its result will be considered to represent a boolean value of &amp;quot;true&amp;quot; if it is empty, a nonzero number, or the string &amp;quot;true&amp;quot;. It will be taken to represent a boolean value of &amp;quot;false&amp;quot; if it contains a numeric value of zero, or any string that does not evaluate to a nonzero number. Note that identification of an empty value with &amp;quot;true&amp;quot; differs from string handling in the EVALUATE CONDITION command. This is because most scripting commands return nothing on success, but an error message on failure.&lt;br /&gt;
&lt;br /&gt;
The output of the SYSTEM and START EXECUTABLE commands is handled specially. There, the result code of the created child process is translated into a boolean value in the ordinary manner, treating a result code of zero as &amp;quot;true&amp;quot;, and any other result code as &amp;quot;false&amp;quot;. This allows to use external commands in the same way as in a native shell.&lt;br /&gt;
&lt;br /&gt;
=====IF &amp;lt;condition&amp;gt;; &amp;lt;if commands&amp;gt;; [ ELSEIF &amp;lt;condition&amp;gt;; &amp;lt;elseif commands&amp;gt;;] ... [ ELSE; &amp;lt;else commands&amp;gt;;] END=====&lt;br /&gt;
Executes &#039;&#039;if commands&#039;&#039; if &#039;&#039;condition&#039;&#039; evaluates to &amp;quot;true&amp;quot;. Otherwise, the &#039;&#039;elseif commands&#039;&#039; of the first matching &#039;&#039;elseif condition&#039;&#039; are executed. When none of the &#039;&#039;elseif conditions&#039;&#039; evaluates to &amp;quot;true&amp;quot;,  &#039;&#039;else commands&#039;&#039; are executed. ELSEIF and ELSE blocks may be omitted.&lt;br /&gt;
&lt;br /&gt;
=====WHILE &amp;lt;condition&amp;gt;; &amp;lt;loop commands&amp;gt;; END=====&lt;br /&gt;
Executes &#039;&#039;loop commands&#039;&#039; while &#039;&#039;condition&#039;&#039; evaluates to &#039;&#039;true&#039;&#039;.&lt;br /&gt;
=====DO; &amp;lt;loop commands&amp;gt;; UNTIL &amp;lt;condition&amp;gt;=====&lt;br /&gt;
Executes &#039;&#039;loop commands&#039;&#039; until &#039;&#039;condition&#039;&#039; evaluates to &#039;&#039;true&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=====FOR &amp;lt;name&amp;gt; IN &amp;lt;item1&amp;gt; &amp;lt;item2&amp;gt; ... ; &amp;lt;loop commands&amp;gt;; END=====&lt;br /&gt;
Creates a local variable with the specified name. Then, sequentially assigns each &#039;&#039;item&#039;&#039; to that variable, and executes &#039;&#039;loop commands&#039;&#039;. If an &#039;&#039;item&#039;&#039; contains newline characters, it is split up into multiple items, corresponding to the lines contained in the &#039;&#039;item&#039;&#039;. E.g.,&lt;br /&gt;
 FOR i IN top ${LIST FILES} bottom; LOG ${i}; END&lt;br /&gt;
will first write a log entry &amp;quot;top&amp;quot;. Then, it will create a log entry for each file in the current directory, and finally, it will create a log entry &amp;quot;bottom&amp;quot;.&lt;br /&gt;
=====RETURN [&amp;lt;value&amp;gt;]=====&lt;br /&gt;
Finishes execution of the current script, and optionally returns a value to the caller.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on Conditions====&lt;br /&gt;
=====EVALUATE CONDITION &amp;lt;left&amp;gt; [&amp;lt;op&amp;gt; [&amp;lt;right&amp;gt;]]=====&lt;br /&gt;
Evaluates a comparison between the &#039;&#039;left&#039;&#039; and &#039;&#039;right&#039;&#039; operands. As a comparison operator, the following may be specified: ==, !=, ~=, &amp;lt;, &amp;gt;, &amp;lt;=, &amp;gt;=. There, the != operator behaves identically to the ~= operator. When a test for equality is performed, the two operands are treated as strings, and compared in a case-insensitive manner. When any of the inequality tests is performed, the two operands are converted into floating-point numbers before comparison.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;right&#039;&#039; operand may be omitted, in which case it is treated as if an empty string were specified. Also, the &#039;&#039;op&#039;&#039; operator may be omitted, in which case the following rules apply regarding the remaining operand: If it is an empty string, or equal to the string &amp;quot;false&amp;quot; in case-insensitive comparison, the result is &amp;quot;false&amp;quot;. If entirely consists of the text representation of a floating-point number, the result is &amp;quot;false&amp;quot; if the number is 0, and &amp;quot;true&amp;quot; if the number is not 0.&lt;br /&gt;
&lt;br /&gt;
Inspired by the unix sh shells&#039; &amp;lt;tt&amp;gt;test&amp;lt;/tt&amp;gt; command, there exists a short form of the EVALUATE CONDITION command, where the arguments of EVALUATE CONDITION may appear within square brackets. This allows constructs such as&lt;br /&gt;
 IF [ ${MyVar} == MyValue ]; LOG Is equal; ELSE; LOG Is different; END&lt;br /&gt;
Note that the arguments to EVALUATE CONDITION must always be separated by white space, no matter whether its long or short form is used.&lt;br /&gt;
&lt;br /&gt;
Conditions may be combined logically using the operators &amp;amp;&amp;amp; and ||.   These go &#039;&#039;outside&#039;&#039; the square brackets.  Note also that whitespace around the comparison operators is critical, otherwise the conditional will be treated as a single string (which will always evaluate to &amp;quot;true&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
 IF [ ${foo} == foo ] || [ ${bar} == bar ]; LOG got a match; END&lt;br /&gt;
&lt;br /&gt;
====Commands operating on Local Variables====&lt;br /&gt;
Besides environment variables, there exist local variables in scripts. Local variables are inherited by sub-scripts executed with the EXECUTE SCRIPT command, but changes to the variable&#039;s values will not be propagated to the calling script.&lt;br /&gt;
=====SET VARIABLE &amp;lt;name&amp;gt; &amp;lt;value&amp;gt;=====&lt;br /&gt;
Sets the named variable to the specified value.&lt;br /&gt;
=====CLEAR VARIABLE &amp;lt;name&amp;gt;=====&lt;br /&gt;
Removes the named variable from memory.&lt;br /&gt;
=====GET VARIABLE &amp;lt;name&amp;gt;=====&lt;br /&gt;
Returns the variable&#039;s current value. When the variable does not exist, an empty value is returned rather than an error message generated.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on Environment Variables====&lt;br /&gt;
These scripting commands allow to read and modify environment variables. Changes to environment variables will be visible to child processes started with SYSTEM or START EXECUTABLE. Variable values are stored as strings. Variable names may not contain the equals sign.&lt;br /&gt;
=====SET ENVIRONMENT &amp;lt;name&amp;gt; &amp;lt;value&amp;gt;=====&lt;br /&gt;
Sets the named variable to the specified value.&lt;br /&gt;
=====CLEAR ENVIRONMENT &amp;lt;name&amp;gt;=====&lt;br /&gt;
Removes the named variable from memory.&lt;br /&gt;
=====GET ENVIRONMENT &amp;lt;name&amp;gt;=====&lt;br /&gt;
Returns the variable&#039;s current value. When the variable does not exist, an empty value is returned rather than an error message.&lt;br /&gt;
=====WRITE ENVIRONMENT &amp;lt;fileName&amp;gt; &amp;lt;variableName1&amp;gt; &amp;lt;variableName2&amp;gt; ...=====&lt;br /&gt;
Writes a series of SET ENVIRONMENT statements to the specified file, overwriting any previous file contents.  This saves the values of the named variables in such a way that you can later load them back by calling EXECUTE SCRIPT &amp;lt;filename&amp;gt;. The variable names may refer to either local or environment variables at the time of writing, but when you execute the script they will all be loaded as environment variables.&lt;br /&gt;
=====APPEND ENVIRONMENT &amp;lt;fileName&amp;gt; &amp;lt;variableName1&amp;gt; &amp;lt;variableName2&amp;gt; ...=====&lt;br /&gt;
This is the same as WRITE ENVIRONMENT, except that the SET ENVIRONMENT statements are appended to the named file without overwriting its previous contents.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on Scripts====&lt;br /&gt;
=====SET SCRIPT &amp;lt;handler names&amp;gt; &amp;lt;scripting commands&amp;gt;=====&lt;br /&gt;
Associates a sequence of scripting commands with the named handler. Handlers are specified by names as given [[#Handlers|below]]. Multiple handlers may be specified by concatenating their names with a pipe character, e.g. &amp;quot;OnStart|OnResume&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Scripting commands must be included in double quotes, unless they consist of a single word.&lt;br /&gt;
When specifying a sequence of scripting commands, they must be separated with a semicolon character: &amp;quot;SetConfig; Start&amp;quot;. In order to use double quotes or semicolons within the commands themselves, encode these as you would in a URL, i.e. replace a double quote character with &#039;&#039;%22&#039;&#039;, and a semicolon with &#039;&#039;%3B&#039;&#039;: &amp;quot;Load Parameters %22my file%22&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
To use a script file rather than a literal script, use the EXECUTE SCRIPT command:&lt;br /&gt;
 SET SCRIPT OnConnect &amp;quot;EXECUTE SCRIPT myscript.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
=====GET SCRIPT &amp;lt;handler name&amp;gt;=====&lt;br /&gt;
Returns the script associated with the specified handler.&lt;br /&gt;
&lt;br /&gt;
=====CLEAR SCRIPT &amp;lt;handler names&amp;gt;=====&lt;br /&gt;
Clears scripts for the given handlers. Equivalent to calling SET SCRIPT with an empty script.&lt;br /&gt;
&lt;br /&gt;
=====EXECUTE SCRIPT &amp;lt;file or handler name&amp;gt; [&amp;lt;Arg1&amp;gt; &amp;lt;Arg2&amp;gt; ... &amp;lt;Arg9&amp;gt;]=====&lt;br /&gt;
Executes a script contained in a file, and optionally sets the script&#039;s local variables &#039;&#039;1&#039;&#039; to &#039;&#039;9&#039;&#039; to the specified values. To execute script commands already associated with a handler, provide a handler name rather than a file. When the script is executed successfully, the result of the last executed script command becomes the result of the EXECUTE SCRIPT command itself. Use &amp;quot;RETURN &amp;lt;value&amp;gt;&amp;quot; anywhere in a script in order to finish execution, and return a certain value.&lt;br /&gt;
&lt;br /&gt;
When you run a script through EXECUTE SCRIPT, it will inherit copies of variables from its calling script/command line. Modifications of variables will be local to the script, and will be lost when script execution is complete.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;AbortOnError&#039;&#039; variable determines whether a script is aborted when any of its commands result in an error message. &#039;&#039;AbortOnError&#039;&#039; is not inherited but defaults to 1 in any script.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on Parameters====&lt;br /&gt;
=====LOAD PARAMETERFILE &amp;lt;file&amp;gt;, LOAD PARAMETERS &amp;lt;file&amp;gt;=====&lt;br /&gt;
Loads a parameter file specified by its path and name. Relative paths are interpreted relative to the operator module&#039;s working directory at startup. Usually, this matches the executable&#039;s location in the &amp;lt;tt&amp;gt;prog&amp;lt;/tt&amp;gt; directory.&lt;br /&gt;
As the parameter file name must not contain white space, please use HTML-type encoding for white space characters, such as &amp;lt;tt&amp;gt;Documents%20and%20Settings&amp;lt;/tt&amp;gt; when referring to a user&#039;s &amp;quot;Documents and Settings&amp;quot; folder.&lt;br /&gt;
&lt;br /&gt;
NOTE: Only those parameters will be loaded that do exist at the time when this command is called. In idle state, no parameters exist other than created by ADD PARAMETER. After modules are connected and have published their parameters to the Operator module, a full set of parameters exists.&lt;br /&gt;
&lt;br /&gt;
=====ADD PARAMETER &amp;lt;parameter definition&amp;gt;=====&lt;br /&gt;
Adds a parameter to the system. The parameter is specified as a [[Technical_Reference:Parameter_Definition#Parameter_Lines|parameter line]]. This command may not be used after system initialization has completed, i.e. its use is restricted to the &amp;quot;Idle&amp;quot; and &amp;quot;Publishing&amp;quot; [[Technical_Reference:States_of_Operation#Publishing_Phase|phases of system operation]]. In terms of handlers, its use is restricted to the &#039;&#039;OnConnect&#039;&#039; handler.&lt;br /&gt;
=====EXISTS PARAMETER &amp;lt;name&amp;gt;=====&lt;br /&gt;
Returns &amp;quot;true&amp;quot; when the specified parameter exists in the system, and &amp;quot;false&amp;quot; otherwise.&lt;br /&gt;
&lt;br /&gt;
=====ISEMPTY PARAMETER &amp;lt;name&amp;gt;=====&lt;br /&gt;
Returns &amp;quot;true&amp;quot; when the specified parameter exists and has no values, &amp;quot;false&amp;quot; if it exists and has values.&lt;br /&gt;
&lt;br /&gt;
=====SET PARAMETER &amp;lt;name&amp;gt;[( idx1, idx2 )] &amp;lt;value&amp;gt;=====&lt;br /&gt;
Sets the named parameter to the specified value. Values that contain special characters, or whitespace must use the [[Technical_Reference:Parameter_Definition#Special_Characters|parameter value encoding]]. Use parentheses to specify indices or labels. Omitted indices default to 1.&lt;br /&gt;
&lt;br /&gt;
=====SET PARAMETER &amp;lt;parameter line&amp;gt;=====&lt;br /&gt;
Replace a parameter&#039;s value and definition with the information given in the [[Technical_Reference:Parameter_Definition#Parameter_Lines|parameter line]]. The parameter must exist in the system when this command is executed.&lt;br /&gt;
&lt;br /&gt;
=====GET PARAMETER &amp;lt;name&amp;gt;[( idx1, idx2 )]=====&lt;br /&gt;
Prints the value of the named parameter. Use parentheses to specify indices or labels.&lt;br /&gt;
&lt;br /&gt;
=====SET PARAMETERROWS &amp;lt;parameter name&amp;gt; &amp;lt;rows&amp;gt;=====&lt;br /&gt;
Sets the number of rows in the named parameter.&lt;br /&gt;
&lt;br /&gt;
=====GET PARAMETERROWS &amp;lt;parameter name&amp;gt;=====&lt;br /&gt;
Prints the number of rows in the named parameter.&lt;br /&gt;
&lt;br /&gt;
=====SET PARAMETERCOLS &amp;lt;parameter name&amp;gt;=====&lt;br /&gt;
Sets the number of columns in the named parameter.&lt;br /&gt;
&lt;br /&gt;
=====GET PARAMETERCOLS &amp;lt;parameter name&amp;gt;=====&lt;br /&gt;
Prints the number of columns in the named parameter.&lt;br /&gt;
&lt;br /&gt;
=====LIST PARAMETER &amp;lt;wildcard expression&amp;gt;, LIST PARAMETERS=====&lt;br /&gt;
Prints all parameters with names matching the wildcard expression, in form of parameter lines.&lt;br /&gt;
=====CLEAR PARAMETERS=====&lt;br /&gt;
Clears the list of parameters in the system. May only be executed in &#039;&#039;Idle&#039;&#039; and &#039;&#039;Publishing&#039;&#039; system states.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on States====&lt;br /&gt;
In BCI2000, there are three types of States that differ in their alignment to brain signal data (see [[Technical_Reference:State_Definition#Kinds_of_States|Kinds of States]]). This section applies to both Stream States, and normal States.&lt;br /&gt;
For Event States, see the next section.&lt;br /&gt;
&lt;br /&gt;
=====ADD STATE &amp;lt;name&amp;gt; &amp;lt;bit width&amp;gt; &amp;lt;initial value&amp;gt;=====&lt;br /&gt;
Adds a state variable to the system. State variables are defined by name, bit width, and initial value (see [[Technical Reference:State Definition]]). This command may not be used after system initialization has completed, i.e. its use is restricted to the &amp;quot;Idle&amp;quot; and &amp;quot;Publishing&amp;quot; [[Technical_Reference:States_of_Operation#Publishing_Phase|phases of system operation]]. In terms of handlers, its use is restricted to the &#039;&#039;OnConnect&#039;&#039; handler.&lt;br /&gt;
=====EXISTS STATE &amp;lt;name&amp;gt;=====&lt;br /&gt;
Returns &amp;quot;true&amp;quot; when the specified state exists in the system, and &amp;quot;false&amp;quot; otherwise.&lt;br /&gt;
&lt;br /&gt;
=====SET STATE &amp;lt;name&amp;gt; &amp;lt;value&amp;gt;, SET STATES &amp;lt;name1&amp;gt; &amp;lt;value1&amp;gt; &amp;lt;name2&amp;gt; &amp;lt;value2&amp;gt; ...=====&lt;br /&gt;
Sets the named state variable to the specified integer value by sending a state message to the source module.&lt;br /&gt;
Setting the &#039;&#039;Running&#039;&#039; state to 1 will start system operation, setting it to 0 will suspend the system.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tt&amp;gt;SET STATES&amp;lt;/tt&amp;gt; will atomically set the values of multiple states.&lt;br /&gt;
&lt;br /&gt;
=====GET STATE[(&amp;lt;sample&amp;gt;)] &amp;lt;name&amp;gt;=====&lt;br /&gt;
Gets the value of the named state. Note that state values are not updated from the application module when the &#039;&#039;OperatorBackLink&#039;&#039; parameter is 0. In that case, GET STATE will return the state&#039;s initial value.&lt;br /&gt;
&lt;br /&gt;
An optional one-based sample index may be given to indicate that the state value should be retrieved at the given sample position. Sample positions range from 1 to the number of samples per block present in the state vector. The number of samples per block may be retrieved using GET STATEVECTOR SAMPLES.&lt;br /&gt;
&lt;br /&gt;
By default, the state&#039;s value is retrieved for sample index 1.&lt;br /&gt;
&lt;br /&gt;
=====LIST STATE &amp;lt;wildcard expression&amp;gt;, LIST STATES=====&lt;br /&gt;
Lists all states, or states with names matching the given wildcard expression, in form of state lines.&lt;br /&gt;
=====CLEAR STATES=====&lt;br /&gt;
Clears the list of states in the system. May only be executed in &#039;&#039;Idle&#039;&#039; and &#039;&#039;Publishing&#039;&#039; system states.&lt;br /&gt;
&lt;br /&gt;
=====FREEZE STATES, THAW STATES=====&lt;br /&gt;
Freezes/thaws the values of all states in the state vector, without interfering with system operation.  &amp;quot;Freezing&amp;quot; creates, and &amp;quot;thawing&amp;quot; discards, a frozen snapshot of the state vector. For the duration of its existence, GET STATE calls will be diverted to the snapshot.  This is useful for ensuring that multiple GET STATE commands actually retrieve mutually-consistent values from different state variables (i.e. values from the same sample-block).&lt;br /&gt;
&lt;br /&gt;
=====GET STATEVECTOR SAMPLES=====&lt;br /&gt;
Returns the number of samples in the state vector per block of data. In the system&#039;s &amp;quot;Running&amp;quot; state, this matches the number of samples per block in the source data. Prior to the &amp;quot;Running&amp;quot; state, this command returns 1 because the state vector has not yet been updated from the application module.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on Events====&lt;br /&gt;
Events are a special type of state, which are recorded asynchronously, at single-sample resolution. Events may only be added while the system is in &amp;quot;idle&amp;quot; state. This kind of events is not related to [[#Handlers|Operator Events]] as defined below.&lt;br /&gt;
=====ADD EVENT &amp;lt;name&amp;gt; &amp;lt;bit width&amp;gt; &amp;lt;initial value&amp;gt;=====&lt;br /&gt;
Adds an event to the system. Like state variables, events are defined by name, bit width, and initial value (see [[Technical Reference:State Definition]]). This command may not be used after the system has started up, so it is typically executed in a batch file before &#039;&#039;STARTUP&#039;&#039; has been called.&lt;br /&gt;
&lt;br /&gt;
=====EXISTS EVENT &amp;lt;name&amp;gt;=====&lt;br /&gt;
Returns &amp;quot;true&amp;quot; when the specified event exists in the system, and &amp;quot;false&amp;quot; otherwise.&lt;br /&gt;
&lt;br /&gt;
=====SET EVENT &amp;lt;name&amp;gt; &amp;lt;value&amp;gt;=====&lt;br /&gt;
Asynchronously sets an event to the given value. Recording events requires the EventLink logger component to be present in the source module.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTE:&#039;&#039;&#039; In versions prior to BCI2000 3.06, this command behaved as described for PULSE EVENT below, rather than as advertised. If you used SET EVENT in your scripts, it is recommended to replace it with PULSE EVENT in order to retain original behavior.&lt;br /&gt;
&lt;br /&gt;
=====PULSE EVENT &amp;lt;name&amp;gt; &amp;lt;value&amp;gt;=====&lt;br /&gt;
Asynchronously sets an event to the given value for a single sample duration. Recording events requires the EventLink logger component to be present in the source module.&lt;br /&gt;
&lt;br /&gt;
=====SET EVENTS [&amp;lt;name&amp;gt; &amp;lt;value&amp;gt;] [&amp;lt;name2&amp;gt; &amp;lt;value2&amp;gt;] ... =====&lt;br /&gt;
Asynchronously sets given events to their specified values. Using this command, rather than multiple SET EVENT commands in a row, ensures that all event changes are recorded at exactly the same point in time. Recording events requires the EventLink logger component to be present in the source module.&lt;br /&gt;
&lt;br /&gt;
=====PULSE EVENTS [&amp;lt;name&amp;gt; &amp;lt;value&amp;gt;] [&amp;lt;name2&amp;gt; &amp;lt;value2&amp;gt;] ... =====&lt;br /&gt;
Asynchronously sets given events to their specified values for a single sample duration. Using this command, rather than multiple PULSE EVENT commands in a row, ensures that all event changes are recorded at exactly the same point in time. Recording events requires the EventLink logger component to be present in the source module.&lt;br /&gt;
&lt;br /&gt;
=====GET EVENT[(&amp;lt;sample&amp;gt;)] &amp;lt;name&amp;gt;=====&lt;br /&gt;
Gets the value of the named event. Note that evemt values are not updated from the application module when the &#039;&#039;OperatorBackLink&#039;&#039; parameter is 0.&lt;br /&gt;
&lt;br /&gt;
An optional one-based sample index may be given to indicate that the event&#039;s value should be retrieved at the given sample position. Sample positions range from 1 to the number of samples per block present in the state vector. The number of samples per block may be retrieved using GET STATEVECTOR SAMPLES.&lt;br /&gt;
&lt;br /&gt;
By default, the event&#039;s value is retrieved for sample index 1.&lt;br /&gt;
&lt;br /&gt;
=====LIST EVENT &amp;lt;wildcard expression&amp;gt;, LIST EVENTS=====&lt;br /&gt;
Lists all events, or events with names matching the given wildcard expression, in form of state lines.&lt;br /&gt;
=====CLEAR EVENTS=====&lt;br /&gt;
Clears the list of events in the system. May only be executed in &#039;&#039;Idle&#039;&#039; state.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on VisProperties====&lt;br /&gt;
=====SET VISPROPERTY &amp;lt;visID&amp;gt;.&amp;lt;name&amp;gt; &amp;lt;value&amp;gt;=====&lt;br /&gt;
Sets the named [[Technical Reference:Visualization Properties|visualization property]] for the specified visualization ID to the given value. If the visualization ID contains a dot character, it must be encoded in [[Technical_Reference:Parameter_Definition#Special_Characters|parameter value encoding]]. E.g., setting the window width for the visualization ID &amp;quot;2.D1&amp;quot; would be written &amp;lt;code&amp;gt;SET VISPROPERTY 2%2ED1.Width 200&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=====RESET VISPROPERTY &amp;lt;visID&amp;gt;.&amp;lt;name&amp;gt;=====&lt;br /&gt;
Removes the named visualization property with the specified visualization ID from the property store, effectively resetting its value to its default.&lt;br /&gt;
&lt;br /&gt;
=====GET VISPROPERTY &amp;lt;visID&amp;gt;.&amp;lt;name&amp;gt;=====&lt;br /&gt;
Prints the value of the named [[Technical Reference:Visualization Properties|visualization property]] for the specified visualization ID.&lt;br /&gt;
&lt;br /&gt;
=====SET VISPROPERTIES &amp;lt;property set ID&amp;gt;=====&lt;br /&gt;
Applies a set of visualization property values as given in the [[#VisPropertySets|VisPropertySets]] parameter. In that matrix-valued parameter, row labels specify visualization properties such as &amp;quot;SRCD.Left&amp;quot;, and columns represent sets of property values. Column labels are IDs of the corresponding property sets.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on the Control Signal====&lt;br /&gt;
=====GET SIGNAL( &amp;lt;channel index&amp;gt;, &amp;lt;element index&amp;gt; )=====&lt;br /&gt;
Prints the value of the control signal at the given indices. Indices are 1-based.&lt;br /&gt;
=====GET SIGNAL CHANNELS=====&lt;br /&gt;
Prints the number of channels in the control signal.&lt;br /&gt;
=====GET SIGNAL ELEMENTS=====&lt;br /&gt;
Prints the number of elements (samples) in the control signal.&lt;br /&gt;
=====FREEZE SIGNAL, THAW SIGNAL=====&lt;br /&gt;
Freezes/thaws the contents of the control signal, without interfering with system operation. &amp;quot;Freezing&amp;quot; creates, and &amp;quot;thawing&amp;quot; discards, a frozen snapshot of the control signal. For the duration of its existence, GET SIGNAL calls will be diverted to the snapshot. This is useful for ensuring that multiple GET SIGNAL commands actually retrieve mutually-consistent values from different elements in the control signal (i.e. values from the same sample-block).&lt;br /&gt;
&lt;br /&gt;
====Commands operating on Expressions====&lt;br /&gt;
=====EVALUATE EXPRESSION &amp;lt;expression&amp;gt;=====&lt;br /&gt;
This command treats the remainder of the command as a literal mathematical expression (for a description, see [[User Reference:Expression Syntax]]). An expression may contain assignments to variables; such variables may then be used in later expressions. Note that expression variables are different from local and environment variables that may be accessed by GET/SET VARIABLE/ENVIRONMENT. Expression variables hold numerical values, while local and environment variables hold string values. Also, environment variables are accessible to child processes started with SYSTEM or START EXECUTABLE, while expression variables are accessible only to scripts. When a script is executed using the EXECUTE SCRIPT command, it will inherit a copy of all expression variables present. Changes to these variables from the executed script will not be visible in the parent script.&lt;br /&gt;
&lt;br /&gt;
=====CLEAR EXPRESSION VARIABLE &amp;lt;name&amp;gt;=====&lt;br /&gt;
Clears the named expression variable from storage.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on Watches====&lt;br /&gt;
A &amp;quot;Watch&amp;quot; is an object that consists of a set of expressions, and an action. Whenever the value of any of the expressions changes, the watch is &amp;quot;triggered&amp;quot;, and the action is executed. Watches allow client applications to respond to BCI2000 state changes in a reliable manner. Rather than polling information from BCI2000, a client may create a watch to be notified about changes of interest. This avoids the problem of missing short-lived changes, which is inherent in the polling approach.&lt;br /&gt;
&lt;br /&gt;
For watches created through operator scripting, the action consists of dumping values of all the expressions to a UDP port.&lt;br /&gt;
&lt;br /&gt;
The intended use of a watch from a client application is to create a separate thread that reads from the watch&#039;s UDP port in a blocking mode, and calls an appropriate handler function whenever it receives data.&lt;br /&gt;
The data sent will consist of a single UDP packet with a single line in ASCII format, terminated with a CRLF sequence. The line consists of tab-separated data fields, which contain the current values of the expressions specified when creating the watch. In addition, the first field contains a time stamp in milliseconds. This time stamp represents the point in time where the expression value changed, with an accuracy of a single sample.&lt;br /&gt;
&lt;br /&gt;
=====ADD WATCH [decimate &amp;lt;n&amp;gt;] &amp;lt;expression1&amp;gt; &amp;lt;expression2&amp;gt; ... [AT &amp;lt;ip:port&amp;gt;]=====&lt;br /&gt;
Adds a watch for the listed expressions. Each expression&#039;s value will be reported in a separate field. When an address is specified in &amp;lt;tt&amp;gt;ip:port&amp;lt;/tt&amp;gt; format, the watch tries to open that port for output, and creation fails if that port is taken. When no address is specified, a free port is chosen automatically. In both cases, successful creation of the watch is indicated by returning the output address in ASCII format. The address is also used to uniquely identify a watch in the context of a connection.&lt;br /&gt;
&lt;br /&gt;
When a &#039;&#039;decimate &amp;lt;n&amp;gt;&#039;&#039; clause is present, the watch will be created with decimation, i.e. it will only be evaluated for every &#039;&#039;n&#039;&#039;th&lt;br /&gt;
sample of the state vector.&lt;br /&gt;
&lt;br /&gt;
If a watch is created through a remote connection, it will use the remote host&#039;s external IP address for automatically chosen addresses. Otherwise, the output port will be associated with the machine&#039;s &amp;lt;tt&amp;gt;localhost&amp;lt;/tt&amp;gt; address.&lt;br /&gt;
&lt;br /&gt;
Watches may be created even if the system is currently running. In this case, the watch is triggered immediately at creation, and sends its current expression values to its output port.&lt;br /&gt;
&lt;br /&gt;
=====ADD WATCH SYSTEM STATE [AT &amp;lt;address&amp;gt;]=====&lt;br /&gt;
Similar to the first variant of ADD WATCH, but will watch the system&#039;s state as reported by GET SYSTEM STATE.&lt;br /&gt;
&lt;br /&gt;
=====CLEAR WATCH &amp;lt;address&amp;gt;, CLEAR WATCHES [&amp;lt;wildcard-expression&amp;gt;]=====&lt;br /&gt;
Removes the watches specified by address, or those with their addresses matching a wildcard expression. If CLEAR WATCHES is called without argument, all watches will be deleted.&lt;br /&gt;
&lt;br /&gt;
=====TRIGGER WATCH &amp;lt;address&amp;gt;, TRIGGER WATCHES  [&amp;lt;wildcard-expression&amp;gt;]=====&lt;br /&gt;
Forces dumping of the watches&#039; current expression values to their output ports. Mostly useful for testing purposes.&lt;br /&gt;
&lt;br /&gt;
=====LIST WATCHES  [&amp;lt;wildcard-expression&amp;gt;]=====&lt;br /&gt;
Displays a list of existing watches, and their addresses.&lt;br /&gt;
&lt;br /&gt;
=====COUNT WATCHES  [&amp;lt;wildcard-expression&amp;gt;]=====&lt;br /&gt;
Returns the number of existing watches, or the number of watches whose addresses match the optional wildcard expression.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on Files, Directories, and Paths====&lt;br /&gt;
=====EXTRACT DIRECTORY &amp;lt;path&amp;gt;, EXTRACT FILE &amp;lt;path&amp;gt;, EXTRACT FILE BASE &amp;lt;path&amp;gt;=====&lt;br /&gt;
Extracts the directory or file portion of a given path. When the path specifies a non-existing directory, the directory name must be followed with a separator (&amp;quot;/&amp;quot;) in order to be recognized as a directory. The EXTRACT DIRECTORY command always returns its result with a trailing separator. The EXTRACT FILE BASE command returns the file portion without extension.&lt;br /&gt;
&lt;br /&gt;
=====IS DIRECTORY &amp;lt;path&amp;gt;, IS FILE &amp;lt;path&amp;gt;, IS PATH &amp;lt;path&amp;gt;=====&lt;br /&gt;
Determines whether the specified path points to an existing directory, file, or any of the two. The result is returned as one of the strings &amp;quot;true&amp;quot; or &amp;quot;false&amp;quot;.&lt;br /&gt;
=====PARENT DIRECTORY &amp;lt;path&amp;gt;=====&lt;br /&gt;
Returns the parent directory of the specified path, independently of whether the path points to a directory, or to a file.&lt;br /&gt;
=====CURRENT DIRECTORY=====&lt;br /&gt;
Returns the current working directory. Note that working directories are per-script, i.e. multiple scripts running in parallel may have different working directories.&lt;br /&gt;
&lt;br /&gt;
=====CHANGE DIRECTORY &amp;lt;path&amp;gt;=====&lt;br /&gt;
Changes the script&#039;s working directory. Note that working directories are per-script, i.e. multiple scripts running in parallel may have different working directories. Also, this command will only affect the default directory of scripting commands, not the application-global working directory as seen by the OS.&lt;br /&gt;
&lt;br /&gt;
=====MAKE DIRECTORY &amp;lt;path&amp;gt;=====&lt;br /&gt;
Creates a new directory with the given path. The directory&#039;s parent must exist for the command to succeed.&lt;br /&gt;
=====LIST DIRECTORY [&amp;lt;path&amp;gt; or &amp;lt;wildcard expression&amp;gt;]=====&lt;br /&gt;
Returns a listing of the specified directory, or the current working directory if no path is specified. The listing is in long form. You may use wildcard expressions in order to restrict the output.&lt;br /&gt;
=====LIST FILE &amp;lt;wildcard expression&amp;gt;=====&lt;br /&gt;
Returns a list of file names matching &#039;&#039;wildcard expression&#039;&#039; in the current directory.&lt;br /&gt;
=====LIST FILES [&amp;lt;directory&amp;gt; [&amp;lt;wildcard expression&amp;gt;]]=====&lt;br /&gt;
Returns a list of file names from the specified directory, matching &#039;&#039;wildcard expression&#039;&#039;. When &#039;&#039;wildcard expression&#039;&#039; is missing, all files are listed. When &#039;&#039;directory&#039;&#039; is missing, files in the current directory are listed.&lt;br /&gt;
=====LIST DIRECTORIES [&amp;lt;directory&amp;gt; [&amp;lt;wildcard expression&amp;gt;]]=====&lt;br /&gt;
Returns a list of directory names from the specified directory, matching &#039;&#039;wildcard expression&#039;&#039;. When &#039;&#039;wildcard expression&#039;&#039; is missing, all directories are listed. When &#039;&#039;directory&#039;&#039; is missing, directories in the current directory are listed.&lt;br /&gt;
&lt;br /&gt;
=====RENAME FILE &amp;lt;current path&amp;gt; &amp;lt;new path&amp;gt;, RENAME DIRECTORY &amp;lt;current path&amp;gt; &amp;lt;new name&amp;gt;=====&lt;br /&gt;
Renames a file resp. a directory. For files, a different path may be given in the second argument, resulting in that the file is moved to the location specified by the new path. For directories, the path up to the directory&#039;s name must stay the same.&lt;br /&gt;
=====REMOVE FILE &amp;lt;path&amp;gt;, REMOVE DIRECTORY &amp;lt;path&amp;gt;=====&lt;br /&gt;
Removes the specified file or directory. This command cannot be undone. The directory must be empty for the command to succeed.&lt;br /&gt;
=====FORCEREMOVE DIRECTORY &amp;lt;path&amp;gt;=====&lt;br /&gt;
Removes the specified directory and its contents. Symbolic links are treated as ordinary files, i.e. they are not followed. This command cannot be undone.&lt;br /&gt;
=====NORMALIZED PATH &amp;lt;path&amp;gt;=====&lt;br /&gt;
Returns &amp;lt;path&amp;gt;, with the following transformations applied:&lt;br /&gt;
*Removes relative elements (&amp;lt;tt&amp;gt;..&amp;lt;/tt&amp;gt; &amp;lt;tt&amp;gt;.&amp;lt;/tt&amp;gt;) as far as possible. For absolute paths, the result will not contain any relative elements; for relative paths, double-dots may appear at the beginning of the result if necessary.&lt;br /&gt;
If a relative path simplifies to the empty string, &amp;lt;tt&amp;gt;./&amp;lt;/tt&amp;gt; is returned. Thus, the result of NORMALIZED PATH is never empty, unless its input was empty.&lt;br /&gt;
*Replaces backward slashes with forward slashes to achieve uniformity across platforms.&lt;br /&gt;
*On case-insensitive file systems, replaces the spelling of names with the one stored in the file system.&lt;br /&gt;
*On Win32, replaces short (8.3) names with long ones.&lt;br /&gt;
&lt;br /&gt;
=====CANONICAL PATH &amp;lt;path&amp;gt;=====&lt;br /&gt;
If &amp;lt;path&amp;gt; points to an existing file or directory, CANONICAL PATH returns a valid absolute file path, suitable as an unambiguous representation for the object pointed to. Especially, two non-empty canonical path strings will compare equal if and only if they refer to the same file system object.&lt;br /&gt;
&lt;br /&gt;
If &amp;lt;path&amp;gt; does not point to an existing file system object, construction of a canonical path is not possible due to lack of information about the named object, and CANONICAL PATH will return an empty string.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTES:&#039;&#039;&#039; The need for an unambiguous, or canonical, representation arises due to ambiguities in the string representation of paths, and in file systems themselves.&lt;br /&gt;
*Paths may contain relative elements: &amp;lt;tt&amp;gt;/mydir/../myfile&amp;lt;/tt&amp;gt; points to the same object as &amp;lt;tt&amp;gt;/myfile&amp;lt;/tt&amp;gt;.&lt;br /&gt;
*A path that involves symbolic links will point to the same object as a path containing one or more of those links in resolved form.&lt;br /&gt;
*File systems may be case-insensitive, or may even provide multiple distinct names for individual directory entries (Win32 short vs. long names).&lt;br /&gt;
&lt;br /&gt;
On &#039;&#039;&#039;Win32,&#039;&#039;&#039; CANONICAL PATH returns the short (8.3) representation of a path, using uppercase spelling, and backslashes as directory separators. Apart from efficiency considerations, this aesthetically unpleasing representation has been chosen to discourage its use for anything except comparing file system objects.&lt;br /&gt;
&lt;br /&gt;
On &#039;&#039;&#039;other systems,&#039;&#039;&#039; CANONICAL PATH will return the result of the POSIX &amp;lt;tt&amp;gt;realpath()&amp;lt;/tt&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
In both cases, a CANONICAL PATH will end with a native directory separator if, and only if, the object pointed to is a directory.&lt;br /&gt;
&lt;br /&gt;
=====REAL PATH &amp;lt;path&amp;gt;=====&lt;br /&gt;
On this command provides a work-alike for the POSIX &amp;lt;tt&amp;gt;realpath()&amp;lt;/tt&amp;gt; function.&lt;br /&gt;
If &amp;lt;path&amp;gt; is empty, or if &amp;lt;path&amp;gt; points to a non-existing object, the result will be empty. Otherwise, an absolute path will be returned, with symbolic links resolved, using forward slashes as directory separators, and with spelling normalized as described for NORMALIZED PATH. A forward slash will be appended if the path points to a directory.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTE:&#039;&#039;&#039; In principle, the path returned by REAL PATH should be just as unambiguous as the result of CANONICAL PATH. However, there are a few caveats:&lt;br /&gt;
* On case-insensitive file systems, two independently obtained results of REAL PATH might differ in case spelling even if referring to the same file system object. This should not be the case for CANONICAL PATH.&lt;br /&gt;
* Determining the result of CANONICAL PATH is a fast operation. In contrast, REAL PATH may be expensive to determine, as for each directory on the path a listing needs to be obtained, and a canonical path needs to be formed, and compared, for half of the listed directory entries on average.&lt;br /&gt;
* On Win32, quite some amount of complexity arises from backward compatibility layers, forbidden file names, multiple filesystem roots, etc. Internally calling &amp;lt;tt&amp;gt;GetShortPathName()&amp;lt;/tt&amp;gt;, CANONICAL PATH does not need to handle that complexity, and may be considered more reliable for identifying file system objects than REAL PATH.&lt;br /&gt;
&lt;br /&gt;
=====READ FILE &amp;lt;filename&amp;gt;=====&lt;br /&gt;
Returns the text content of the file indicated by &amp;lt;filename&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=====WRITE FILE &amp;lt;filename&amp;gt; &amp;lt;line&amp;gt;,  OVERWRITE FILE &amp;lt;filename&amp;gt; &amp;lt;line&amp;gt;, APPEND TO FILE &amp;lt;filename&amp;gt; &amp;lt;line&amp;gt;=====&lt;br /&gt;
These commands write the specified &amp;lt;line&amp;gt; of text to the file indicated by &amp;lt;filename&amp;gt;.   They behave like the &amp;lt;tt&amp;gt;echo&amp;lt;/tt&amp;gt; command of a typical shell, in the sense that a line-ending will be automatically added to the &amp;lt;line&amp;gt; if it does not already end with one. To suppress the automatic line-ending, you can say WRITE FILE &amp;lt;filename&amp;gt; &amp;lt;content&amp;gt; WITHOUT LINE ENDING (or equivalently, for short, you can say WITHOUT LF or WITHOUT CRLF). Any previous content in &amp;lt;file&amp;gt; will be lost if you use WRITE or OVERWRITE (they are the same, just synonyms for each other) whereas APPEND preserves previous content and adds the new &amp;lt;line&amp;gt; to the end.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on executables====&lt;br /&gt;
=====START EXECUTABLE &amp;lt;command line&amp;gt;=====&lt;br /&gt;
Behaves identically to CREATE PROCESS, except that it does not report a process id for the new process. For details, see CREATE PROCESS.&lt;br /&gt;
&lt;br /&gt;
=====CATEGORIZE EXECUTABLE &amp;lt;name or path&amp;gt;=====&lt;br /&gt;
BCI2000 keeps a list of executables/modules that have been built, and sorts them into categories. The CATEGORIZE EXECUTABLE command provides a way to retrieve that information. Given the name or path of an executable, it outputs one of the following categories:&lt;br /&gt;
* SignalSource,&lt;br /&gt;
* SignalProcessing,&lt;br /&gt;
* Application,&lt;br /&gt;
* Operator,&lt;br /&gt;
* Tool,&lt;br /&gt;
* Helper,&lt;br /&gt;
* Unknown.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on Lines of input/output====&lt;br /&gt;
=====WRITE LINE &amp;lt;line&amp;gt;=====&lt;br /&gt;
Writes a line of output. Destination depends on the context in which a script is executed. If the context is an Operator Handler, output is written as a log entry. If the context is a telnet session, output is written to the telnet connection. If the context is a [[User Reference:BCI2000Shell|BCI2000Shell]], output is written to the shell&#039;s stdout.&lt;br /&gt;
&lt;br /&gt;
=====READ LINE=====&lt;br /&gt;
Reads a line of input from the current execution context&#039;s input. If the command is executed within an Operator Handler, it will fail. If executed within a telnet session, the other side of the connection is prompted for input. If executed from within a [[User Reference:BCI2000Shell|BCI2000Shell]], input is read from the shell&#039;s stdin.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on Processes in the Operating System====&lt;br /&gt;
=====CREATE PROCESS &amp;lt;command line&amp;gt;=====&lt;br /&gt;
Starts the specified executable with options. This command returns after the started program has finished initialization, i.e. it will detect load time failures such as missing DLLs on Windows. If the process is still running when CREATE PROCESS returns, its result will be an operating system process id (pid). If the process has terminated, CREATE PROCESS will report its exit code marked with an &amp;lt;tt&amp;gt;ExitCode&amp;lt;/tt&amp;gt; tag to allow distinction between a pid and an exit code.&lt;br /&gt;
Please note that CREATE PROCESS requires quoting of arguments differently from other scripting commands. For details, see the SYSTEM command.&lt;br /&gt;
&lt;br /&gt;
=====TERMINATE PROCESS &amp;lt;pid&amp;gt;=====&lt;br /&gt;
Tries to terminate the process with the given operating system pid, waiting for the process to terminate before returning. Will return &amp;lt;tt&amp;gt;false&amp;lt;/tt&amp;gt; to indicate that a suitable process existed but could not be terminated.&lt;br /&gt;
&lt;br /&gt;
=====WAIT FOR PROCESS &amp;lt;pid&amp;gt; [&amp;lt;timeout seconds&amp;gt; = infinite]=====&lt;br /&gt;
Waits for the process with the given operating system pid to terminate, or the timeout to expire. Returns &amp;lt;tt&amp;gt;false&amp;lt;/tt&amp;gt; to indicate that the process is still executing.&lt;br /&gt;
&lt;br /&gt;
=====SHOW PROCESS &amp;lt;pid&amp;gt;=====&lt;br /&gt;
Makes all windows visible which are associated with the process referred to by pid.&lt;br /&gt;
In addition, brings one of the process&#039; top level (desktop level) windows to the front for user interaction.&lt;br /&gt;
&lt;br /&gt;
=====HIDE PROCESS &amp;lt;pid&amp;gt;=====&lt;br /&gt;
Makes all windows invisible which are associated with the process referred to by pid.&lt;br /&gt;
&lt;br /&gt;
====Global commands====&lt;br /&gt;
=====HELP [&amp;lt;type&amp;gt;]=====&lt;br /&gt;
When called with a type argument, lists commands that exist for the specified type (e.g., SYSTEM, or FILE). When called without argument, lists all commands in their main form. HELP ALL will list all commands, including synonyms.&lt;br /&gt;
&lt;br /&gt;
=====SET &amp;lt;name&amp;gt; &amp;lt;value&amp;gt;, GET &amp;lt;name&amp;gt;, &amp;lt;name&amp;gt;=====&lt;br /&gt;
Allows to set or retrieve the value of local and environment variables. The name is matched against local and environment variables. When no variable with the given name is found, SET will create a local variable, while GET will result in an error. GET may be further abbreviated to only consist of a name.&lt;br /&gt;
&lt;br /&gt;
GET further allows evaluation of [[User_Reference:Expression_Syntax|mathematical expressions]]. When the expression is invalid, or contains an unknown variable, an error is triggered.&lt;br /&gt;
&lt;br /&gt;
=====GET SYSTEM STATE=====&lt;br /&gt;
Prints the current system state. This will be one of Unavailable, Idle, Startup, Initialization, Resting, Suspended, ParamsModified, Running, Termination, Busy (for details, see the WAIT FOR command below).&lt;br /&gt;
&lt;br /&gt;
=====GET CURRENT RUN FILE=====&lt;br /&gt;
Prints the full path to the current run file, or an empty string, if called outside Running state.&lt;br /&gt;
&lt;br /&gt;
=====WAIT FOR &amp;lt;system state&amp;gt; [&amp;lt;timeout seconds&amp;gt;]=====&lt;br /&gt;
Waits until the system is in the specified state. This may be one of Idle, Startup, Connected, Resting, Suspended, ParamsModified, Running, Busy, or a combination of these, separated with a pipe character: &amp;quot;Resting|Suspended&amp;quot;. When no timeout is given, this command waits indefinitely. If the wait is successful, i.e. system state matches one of the specified states, WAIT FOR will return a value of &amp;quot;true&amp;quot;. If timeout occurred, WAIT FOR will return &amp;quot;false&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
If the BCI2000 system is shut down while a script is executing a WAIT FOR command, the script will be terminated with a &amp;quot;wait aborted&amp;quot; error message.&lt;br /&gt;
&lt;br /&gt;
In more detail, user visible states are:&lt;br /&gt;
* Idle: System is shut down and does not do other processing than executing scripts.&lt;br /&gt;
* Startup: System has started up and is listening for incoming connections.&lt;br /&gt;
* Connected: All core modules have connected to the system.&lt;br /&gt;
* Resting: The system has been initialized but is not running.&lt;br /&gt;
* Suspended: The system has ended running and is behaving like in Resting.&lt;br /&gt;
* ParamsModified: In Suspended mode, parameter changes have been published and will be applied at Resume.&lt;br /&gt;
* Running: The system is running, processing brain signals, and displaying stimuli.&lt;br /&gt;
* Busy: The system is performing a transition from one state to another.&lt;br /&gt;
&lt;br /&gt;
As a synonym for &amp;quot;Connected,&amp;quot; &amp;quot;Initialization&amp;quot; is valid as well since it is compatible with the nomenclature in StateMachine.h. Still, it should be avoided because it is easily confused with, but very distinct from, the actions performed in the Initialize() phase.&lt;br /&gt;
&lt;br /&gt;
=====SLEEP &amp;amp;lt;time in seconds&amp;amp;gt;=====&lt;br /&gt;
Waits (sleeps) for the given amount of time. Timing resolution is 50ms. Tends to sleep a little longer than specified, with the error growing with duration.&lt;br /&gt;
&lt;br /&gt;
=====GET SYSTEM VERSION=====&lt;br /&gt;
Prints BCI2000 version information.&lt;br /&gt;
=====SETCONFIG, SET CONFIG=====&lt;br /&gt;
Applies current parameters to the system. Corresponds to the &#039;&#039;SetConfig&#039;&#039; button in the GUI version of the Operator module.&lt;br /&gt;
&lt;br /&gt;
=====START=====&lt;br /&gt;
Starts or resumes system operation, corresponding to the &#039;&#039;Start/Resume&#039;&#039; button in the GUI version of the Operator module.&lt;br /&gt;
=====STOP=====&lt;br /&gt;
Stops system operation. Corresponds to the &#039;&#039;Stop&#039;&#039; button in the GUI version of the Operator.&lt;br /&gt;
=====STARTUP SYSTEM=====&lt;br /&gt;
When in idle state, starts up the system to wait for incoming connections from core modules. Additionally, the following arguments may be given: 1) an IP address on which to listen (default is to listen on all addresses), and 2) a list of generic core module names with ports. The default configuration corresponds to these arguments:&lt;br /&gt;
 STARTUP SYSTEM * SignalSource:4000 SignalProcessing:4001 Application:4002&lt;br /&gt;
&lt;br /&gt;
A system log file may optionally be specified on this line, by inserting the &amp;lt;code&amp;gt;--SystemLogFile&amp;lt;/code&amp;gt; flag between the IP address and the module specifiers. For example:&lt;br /&gt;
&lt;br /&gt;
 STARTUP SYSTEM * --SystemLogFile=SOME_FILE.TXT SignalSource:4000 SignalProcessing:4001 Application:4002&lt;br /&gt;
&lt;br /&gt;
The system log file will record all operator log window messages for the current launch, until the system shuts down.  It may be helpful to use the variables &amp;lt;code&amp;gt;$YYYYMMDD&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;$HHMMSS&amp;lt;/code&amp;gt; to specify the filename.  Note that file name and path cannot contain spaces.&lt;br /&gt;
&lt;br /&gt;
=====SHUTDOWN SYSTEM=====&lt;br /&gt;
Shuts down core modules, and enters idle system state.&lt;br /&gt;
=====RESET SYSTEM=====&lt;br /&gt;
Shuts down the system, and clears all parameter, state, and event information.&lt;br /&gt;
&lt;br /&gt;
=====QUIT, EXIT [&amp;lt;result&amp;gt;]=====&lt;br /&gt;
Quits the operator module after terminating all BCI2000 modules. The optional &#039;&#039;result&#039;&#039; argument determines the result of the executed script.&lt;br /&gt;
&lt;br /&gt;
=====CLOSE CONNECTION, TERMINATE CONNECTION=====&lt;br /&gt;
In the context of a telnet or websocket connection, this command will terminate the connection.&lt;br /&gt;
In other contexts, such as event handlers, it will be ignored.&lt;br /&gt;
&lt;br /&gt;
=====SYSTEM &amp;lt;command line&amp;gt;=====&lt;br /&gt;
Executes a shell command, redirecting any console output into the command&#039;s script result. E.g., to obtain a directory listing, under Windows, you would enter&lt;br /&gt;
 SYSTEM DIR&lt;br /&gt;
&#039;&#039;&#039;NOTE:&#039;&#039;&#039; Arguments to the SYSTEM command are executed by the operating system&#039;s shell, and thus may require quoting different from the other scripting commands. E.g., writing&lt;br /&gt;
 SET mydir ${PARENT DIRECTORY $BCI2000LAUNCHDIR}; ECHO ${LIST FILES $mydir}&lt;br /&gt;
will list files in the BCI2000 main directory, independently of whether the path to that directory contains space characters or not. However, to obtain a directory listing through the SYSTEM command, you would need to write&lt;br /&gt;
 ECHO ${SYSTEM DIR &amp;quot;$mydir&amp;quot;}&lt;br /&gt;
to make sure the content of the variable &#039;&#039;mydir&#039;&#039; is interpreted as a single argument, independently of whether it contains space characters.&lt;br /&gt;
&lt;br /&gt;
=====LOG &amp;lt;message&amp;gt;=====&lt;br /&gt;
Append the specified message to the system log.&lt;br /&gt;
=====WARN &amp;lt;message&amp;gt;=====&lt;br /&gt;
Append the specified message to the system log, formatted as a warning.&lt;br /&gt;
=====ERROR &amp;lt;message&amp;gt;=====&lt;br /&gt;
Append the specified message to the system log, formatted as an error message.&lt;br /&gt;
&lt;br /&gt;
=====CAPTURE MESSAGES &amp;lt;message types&amp;gt;=====&lt;br /&gt;
Captures system log messages into a background buffer. When no message type is given, all messages are captured. When &amp;quot;None&amp;quot; is given as a message type, message capturing is disabled. Otherwise, the message type must be one of &amp;quot;Errors&amp;quot;, &amp;quot;Warnings&amp;quot;, &amp;quot;Debug&amp;quot;, &amp;quot;Log&amp;quot;. Multiple message types may be specified in a single command. When &amp;quot;None&amp;quot; appears within a single command, all preceding message types are ignored. Multiple CAPTURE MESSAGES commands are cumulative, except when &amp;quot;None&amp;quot; is specified as a message type.&lt;br /&gt;
&lt;br /&gt;
=====FLUSH MESSAGES=====&lt;br /&gt;
Clears the background message buffer, and returns its previous content. Use CAPTURE MESSAGES to capture messages into the background message buffer.&lt;br /&gt;
&lt;br /&gt;
====Operator-module defined Commands====&lt;br /&gt;
=====HIDE WINDOW [&amp;lt;name&amp;gt;], SHOW WINDOW [&amp;lt;name&amp;gt;]=====&lt;br /&gt;
Hides or shows the specified window. When called without a window name, the Operator module&#039;s main window is hidden or shown. The window name may be one of Main, Configuration, Log, Watches, and Visualizations. When &amp;quot;Watches&amp;quot; is given, the window state refers to visibility of the Watches area in the Visualizations window.&lt;br /&gt;
&lt;br /&gt;
=====MOVE WINDOW &amp;lt;name&amp;gt; &amp;lt;x&amp;gt; &amp;lt;y&amp;gt;, RESIZE WINDOW &amp;lt;name&amp;gt; &amp;lt;width&amp;gt; &amp;lt;height&amp;gt;=====&lt;br /&gt;
Moves resp. resizes the specified window with one of the above names.&lt;br /&gt;
Window decorations (title bar, frame) are taken into consideration such that the total size of the window matches the given width and height.&lt;br /&gt;
&lt;br /&gt;
=====ARRANGE WINDOW &amp;lt;name&amp;gt; &amp;lt;rows&amp;gt; &amp;lt;cols&amp;gt; &amp;lt;row&amp;gt; &amp;lt;col&amp;gt; [&amp;lt;rowspan&amp;gt; &amp;lt;colspan&amp;gt;]=====&lt;br /&gt;
Arranges the named window in a virtual &#039;&#039;rows&#039;&#039; x &#039;&#039;cols&#039;&#039; grid at grid position &#039;&#039;row, col&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Optional &#039;&#039;rowspan&#039;&#039; and &#039;&#039;colspan&#039;&#039; arguments may be given to specify the extension of the window across more than one row or column. &lt;br /&gt;
Using a &#039;&#039;rowspan&#039;&#039; of 0 will result in the window assuming its minimum height.&lt;br /&gt;
Likewise, a &#039;&#039;colspan&#039;&#039; of 0 will result in the window&#039;s minimum width.&lt;br /&gt;
&lt;br /&gt;
If multiple screens are present, windows are arranged on the screen that contains the Operator window.&lt;br /&gt;
&lt;br /&gt;
=====SET TITLE &amp;lt;title&amp;gt;=====&lt;br /&gt;
Sets the title of the main Operator window.&lt;br /&gt;
=====SET BUTTON &amp;lt;idx&amp;gt; &amp;lt;label&amp;gt; &amp;lt;commands&amp;gt;=====&lt;br /&gt;
Configures the function button with 1-based index &#039;&#039;idx&#039;&#039; such that it is labelled &#039;&#039;label&#039;&#039; and executes &#039;&#039;commands&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=====VISUALIZE WATCH [decimate &amp;lt;n&amp;gt;] [range &amp;lt;min&amp;gt; &amp;lt;max&amp;gt;] &amp;lt;expression1&amp;gt; ...=====&lt;br /&gt;
Adds a watch for the given expressions to the operator module&#039;s Watches window, and makes the Watches window visible if it is hidden. If the &#039;&#039;decimate&#039;&#039; clause is present, the command will create a watch which will be evaluated only for every &#039;&#039;n&#039;&#039;th sample in the state vector. If the &#039;&#039;range&#039;&#039; clause is present, display minimum and maximum will not be adjusted automatically but will be fixed to the values given.&lt;br /&gt;
&lt;br /&gt;
By default, &#039;&#039;decimate&#039;&#039; has a value of &amp;quot;auto&amp;quot;; this means that there is no decimation of evaluation (i.e., all samples are evaluated) but changes that occur within 1ms will be shortened into their maximum and minimum, and thus reported as only two values.&lt;br /&gt;
This has proven to be efficient for avoiding sluggishness from flooding the application with events.&lt;br /&gt;
&lt;br /&gt;
=====MOVE VISUALIZATON &amp;lt;visID&amp;gt; &amp;lt;x&amp;gt; &amp;lt;y&amp;gt;, RESIZE VISUALIZATION &amp;lt;visID&amp;gt; &amp;lt;width&amp;gt; &amp;lt;height&amp;gt;=====&lt;br /&gt;
Moves resp. resizes the specified visualization window.&lt;br /&gt;
Window decorations (title bar, frame) are taken into consideration such that the total size of the window matches the given width and height. If unknown, visualization IDs may be obtained from the Operator&#039;s &#039;&#039;Window-&amp;gt;Visualizations&#039;&#039; menu.&lt;br /&gt;
&lt;br /&gt;
=====ARRANGE VISUALIZATION &amp;lt;visID&amp;gt; &amp;lt;rows&amp;gt; &amp;lt;cols&amp;gt; &amp;lt;row&amp;gt; &amp;lt;col&amp;gt; [&amp;lt;rowspan&amp;gt; &amp;lt;colspan&amp;gt;]=====&lt;br /&gt;
Arranges the named visualization window in a virtual &#039;&#039;rows&#039;&#039; x &#039;&#039;cols&#039;&#039; grid at grid position &#039;&#039;row, col&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Optional &#039;&#039;rowspan&#039;&#039; and &#039;&#039;colspan&#039;&#039; arguments may be given to specify the extension of the window across more than one row or column. &lt;br /&gt;
Using a &#039;&#039;rowspan&#039;&#039; of 0 will result in the window assuming its minimum height.&lt;br /&gt;
Likewise, a &#039;&#039;colspan&#039;&#039; of 0 will result in the window&#039;s minimum width.&lt;br /&gt;
&lt;br /&gt;
If multiple screens are present, windows are arranged on the screen that contains the Operator window.&lt;br /&gt;
&lt;br /&gt;
=====RECORD VISUALIZATION &amp;lt;visID&amp;gt; [on|off]=====&lt;br /&gt;
Registers the named visualization for recording. The visualization name must be the short name as displayed in the &#039;&#039;View-&amp;gt;Visualizations&#039;&#039; menu. Switching a visualization recording to &amp;quot;on&amp;quot; is only possible in idle state (because an event for recording frame numbers must be registered in the system). When a visualization is registered for recording, its frames are stored immediately as they arrive at the operator module. Currently, only bitmap visualizations may be recorded.&lt;br /&gt;
&lt;br /&gt;
Example: Recording the application window&lt;br /&gt;
 RECORD VISUALIZATION ApplicationWindow on&lt;br /&gt;
 ...&lt;br /&gt;
 # after parameters have been loaded&lt;br /&gt;
 Set parameter VisualizeApplicationWindow 1&lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
=====PUT NOTE &amp;lt;note&amp;gt;=====&lt;br /&gt;
Adds the given text to the [[User_Reference:Operator_Notes#Taking_Notes_during_recording_in_Operator|notes window]].&lt;br /&gt;
If the system is in &#039;&#039;Running&#039;&#039; state, the text will also be added to the current notes file as described under&lt;br /&gt;
[[User_Reference:Operator_Notes#Note_storage_and_file_format|&amp;quot;notes file&amp;quot;]].&lt;br /&gt;
&lt;br /&gt;
=====START TELNET [&amp;lt;address&amp;gt;], STOP TELNET [&amp;lt;address&amp;gt;]=====&lt;br /&gt;
Starts or stops a telnet server listening at the given address. The address is in &amp;lt;IP&amp;gt;:&amp;lt;port&amp;gt; format. The default address is 127.0.0.1:3999.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;IMPORTANT:&#039;&#039;&#039; You should not use this command on global internet addresses, as there is no authentication performed. Only use addresses in the local network, such as 10.x.x.x, 172.x.x.x, or 192.168.x.x.&lt;br /&gt;
&lt;br /&gt;
=====START WEBSOCKET [&amp;lt;address&amp;gt;], STOP WEBSOCKET [&amp;lt;address&amp;gt;]=====&lt;br /&gt;
Starts or stops a websocket server listening at the given address. The address is in &amp;lt;IP&amp;gt;:&amp;lt;port&amp;gt; format. The default address is 127.0.0.1:3998.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;IMPORTANT:&#039;&#039;&#039; You should not use this command on global internet addresses, as there is no authentication performed. Only use addresses in the local network, such as 10.x.x.x, 172.x.x.x, or 192.168.x.x.&lt;br /&gt;
&lt;br /&gt;
====Operator-module Commands for Soliciting Input from the User====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=====CHOOSE [INPUT] FILE[S] [OF TYPE &amp;lt;.ext1&amp;gt; [&amp;lt;.ext2&amp;gt; ...]] [STARTING AT &amp;lt;dir&amp;gt;] [WITH PROMPT &amp;lt;message&amp;gt;]=====&lt;br /&gt;
Via the Qt graphical user interface, prompt the user to select an existing file (or multiple files, if the keyword FILES is used). Print the resulting file path(s).&lt;br /&gt;
(See the [[User_Reference:Custom_GUI_Commands|Custom GUI Commands]] page for details and examples.)&lt;br /&gt;
&lt;br /&gt;
=====CHOOSE [OUTPUT] FILE [OF TYPE &amp;lt;.ext1&amp;gt; [&amp;lt;.ext2&amp;gt; ...]] [STARTING AT &amp;lt;dir&amp;gt;] [WITH PROMPT &amp;lt;message&amp;gt;]=====&lt;br /&gt;
Via the Qt graphical user interface, prompt the user to specify a file into which data can be saved (with an overwrite confirmation dialog if it already exists). Print the resulting file path.&lt;br /&gt;
(See the [[User_Reference:Custom_GUI_Commands|Custom GUI Commands]] page for details and examples.)&lt;br /&gt;
&lt;br /&gt;
=====CHOOSE DIRECTORY [STARTING AT &amp;lt;dir&amp;gt;] [WITH PROMPT &amp;lt;message&amp;gt;]=====&lt;br /&gt;
Via the Qt graphical user interface, prompt the user to specify a directory. Print the resulting directory path.&lt;br /&gt;
(See the [[User_Reference:Custom_GUI_Commands|Custom GUI Commands]] page for details and examples.)&lt;br /&gt;
&lt;br /&gt;
=====CUSTOM DIALOG [MESSAGE &amp;lt;msg&amp;gt;] [VAR &amp;lt;varname&amp;gt; &amp;lt;label&amp;gt; {[&amp;lt;options&amp;gt;]} ] [BUTTONS {&amp;lt;buttons&amp;gt;}] ... =====&lt;br /&gt;
Via the Qt graphical user interface, prompt the user with a modal dialog that can be flexibly customized.&lt;br /&gt;
(See the [[User_Reference:Custom_GUI_Commands|Custom GUI Commands]] page for details and examples.)&lt;br /&gt;
&lt;br /&gt;
===Predefined Variables===&lt;br /&gt;
The following variables exist when an Operator script is executed.&lt;br /&gt;
Some of these variables are marked with &#039;&#039;local&#039;&#039;. This means that they are not environment variables, i.e. they are invisible to child processes that are launched using the SYSTEM or START EXECUTABLE commands, and their values may be different between script invocations. In script code, they are accessed like ordinary variables.&lt;br /&gt;
====BCI2000LAUNCHDIR====&lt;br /&gt;
The full absolute path to the directory where the Operator module resides. This is also prepended to the PATH environment variable, such that executables from the current BCI2000 installation will have precedence over any other executable with the same name.&lt;br /&gt;
&lt;br /&gt;
On macOS, this is the path where the Operator module&#039;s application bundle resides. In the default configuration, this is the BCI2000 prog directory, both on macOS and on other platforms.&lt;br /&gt;
&lt;br /&gt;
====BCI2000BINARY====&lt;br /&gt;
The full absolute path to the Operator module.&lt;br /&gt;
====LogLevel (local)====&lt;br /&gt;
Determines the amount of log information written to the Operator log. This variable only affects log messages originating from the current script. May be 2 (display all log messages), 1 (display fewer log messages), or 0 (suppress all log messages). Set to 1 by default. Changes to this local variable are not propagated to any sub-scripts called. &#039;&#039;NOTE:&#039;&#039; Only log messages are controlled by this variable. Error messages originating from a script with &#039;LogLevel 0&#039; will still be displayed in the Operator log. When &#039;AbortOnError&#039; is set to 0, you may set &#039;LogLevel&#039; to -1 in order to display error messages. When &#039;AbortOnError&#039; is 1 (the default), error messages will always be displayed to indicate the reason for failure.&lt;br /&gt;
&lt;br /&gt;
====AbortOnError (local)====&lt;br /&gt;
Determines if a script is aborted when an error happens, or whether the error is silently ignored. Set to 1 by default (script is aborted on error). Changes to this local variable are not propagated to any sub-scripts called.&lt;br /&gt;
&lt;br /&gt;
====Result (local)====&lt;br /&gt;
The result of the last executed scripting command. When a script is executed by calling EXECUTE SCRIPT, the script&#039;s last executed command determines the result of the EXECUTE SCRIPT command itself.&lt;br /&gt;
&lt;br /&gt;
====0, 1, ... 9 (local)====&lt;br /&gt;
When a script file is being executed, these variables contain the arguments of the EXECUTE SCRIPT command.&lt;br /&gt;
&#039;&#039;$0&#039;&#039; resolves to the full absolute path to the current script file. Within scripts, all of the &#039;&#039;0-9&#039;&#039; variables are defined, and those that do not have a matching argument are empty.&lt;br /&gt;
&lt;br /&gt;
====YYYYMMDD (local)====&lt;br /&gt;
Local time at execution of the current script, in YYYYMMDD format. In interactive sessions, reflects the time when the session was initiated.&lt;br /&gt;
&lt;br /&gt;
====HHMMSS (local)====&lt;br /&gt;
Local time at execution of the current script, in HHMMSS format. In interactive sessions, reflects the time when the session was initiated.&lt;br /&gt;
&lt;br /&gt;
===Abbreviated commands and synonyms===&lt;br /&gt;
To minimize the need of consulting documentation, as well as for backward compatibility, a number of &#039;&#039;&#039;synonymous commands&#039;&#039;&#039; are provided. E.g., states may be added by INSERT STATE as well as ADD STATE, and the existence of a file may be queried by IS FILE as well as EXISTS FILE. For an overview over all allowed forms of commands, use the HELP ALL command.&lt;br /&gt;
&lt;br /&gt;
To simplify operation in interactive sessions, &#039;&#039;&#039;abbreviated commands&#039;&#039;&#039; exist. Currently, these are:&lt;br /&gt;
:&#039;&#039;&#039;cd&#039;&#039;&#039; for CHANGE DIRECTORY,&lt;br /&gt;
:&#039;&#039;&#039;pwd&#039;&#039;&#039; and &#039;&#039;&#039;cd&#039;&#039;&#039; without argument for CURRENT DIRECTORY,&lt;br /&gt;
:&#039;&#039;&#039;ls&#039;&#039;&#039; and &#039;&#039;&#039;dir&#039;&#039;&#039; for LIST DIRECTORY,&lt;br /&gt;
:&#039;&#039;&#039;mkdir&#039;&#039;&#039; for MAKE DIRECTORY,&lt;br /&gt;
:&#039;&#039;&#039;echo&#039;&#039;&#039; for WRITE LINE,&lt;br /&gt;
:&#039;&#039;&#039;realpath&#039;&#039;&#039; for REAL PATH,&lt;br /&gt;
:&#039;&#039;&#039;dirname&#039;&#039;&#039; for EXTRACT DIRECTORY,&lt;br /&gt;
:&#039;&#039;&#039;basename&#039;&#039;&#039; for EXTRACT FILE BASE.&lt;br /&gt;
&lt;br /&gt;
===Handlers===&lt;br /&gt;
In the Operator GUI, script execution is bound to a number of Operator Events (not to be confused with Event states, above) that occur during various [[Technical Reference:States of Operation|stages of BCI2000 system operation]]:&lt;br /&gt;
====OnConnect====&lt;br /&gt;
This handler runs at startup, as soon as all modules are connected to the operator module.&lt;br /&gt;
====OnSetConfig====&lt;br /&gt;
This handler runs each time a set of parameters is applied to the system. This happens when the user clicks the &#039;&#039;SetConfig&#039;&#039; button. Execution of the &#039;&#039;SETCONFIG&#039;&#039; command also runs this handler.&lt;br /&gt;
&lt;br /&gt;
====OnStart, OnResume====&lt;br /&gt;
These handlers are triggered by the &#039;&#039;Start&#039;&#039;/&#039;&#039;Resume&#039;&#039; button. One of these handlers is also triggered when the &#039;&#039;Running&#039;&#039; state variable is set to 1 from a script. Whether &#039;&#039;OnStart&#039;&#039; or &#039;&#039;OnResume&#039;&#039; is triggered depends on whether the system has been running before with the current set of parameters.&lt;br /&gt;
&lt;br /&gt;
====OnStartRun====&lt;br /&gt;
Similarly to OnStart and OnResume, this handler is triggered by the &#039;&#039;Start&#039;&#039;/&#039;&#039;Resume&#039;&#039; button. Unlike other event handlers, OnStartRun has an argument, which is the current run file.&lt;br /&gt;
&lt;br /&gt;
OnStart or OnResume are triggered immediately after &#039;&#039;Start&#039;&#039;/&#039;&#039;Resume&#039;&#039; has been pressed, whereas OnStartRun is deferred until modules have confirmed to be in Running state. This makes sure that the OnStartRun event handler receives a valid run file name.&lt;br /&gt;
&lt;br /&gt;
====OnNextFilePart====&lt;br /&gt;
This handler is triggered during a run, whenever the &#039;&#039;FilePart&#039;&#039; event state is incremented. It allows scripts to know when a new&lt;br /&gt;
partial file has begun. For further information, see the [[User_Reference:BCI2000FileWriter#FileSplittingCondition|FileSplittingCondition]] parameter.&lt;br /&gt;
&lt;br /&gt;
====OnSuspend/OnStopRun====&lt;br /&gt;
Triggered when the system goes from running into suspended mode. This happens whenever the &#039;&#039;Running&#039;&#039; state variable changes from 1 to 0. This may happen when the user clicks &#039;&#039;Suspend&#039;&#039;, when the application module switches the system into suspended mode, or when a script sets the &#039;&#039;Running&#039;&#039; state variable to 0.&lt;br /&gt;
&lt;br /&gt;
====OnShutdown====&lt;br /&gt;
Triggered when the operator module shuts down connections, and switches into idle state.&lt;br /&gt;
&lt;br /&gt;
====OnExit====&lt;br /&gt;
Triggered when the operator module exits. Execution of the QUIT command also triggers this handler. This handler is not available to the SET SCRIPT and CLEAR SCRIPT commands. Also, when both an OnShutdown and an OnExit script are defined, the OnExit script may be executed before the OnShutdown script.&lt;br /&gt;
&lt;br /&gt;
====Associating Scripts with Operator Events====&lt;br /&gt;
In the operator module&#039;s preferences dialog, script commands may be entered for each of the handlers listed above.&lt;br /&gt;
Scripts may be specified as paths to script files, or as immediate one-line scripts.&lt;br /&gt;
Entries that start with a minus sign (-) are treated as one-line scripts, which may contain multiple commands separated with semicolons.&lt;br /&gt;
&lt;br /&gt;
Scripts may also be specified from the command line used to start up the operator module. There, handler names are followed with the content of the respective preference entry, enclosed in double quotes (&amp;quot;...&amp;quot;).&lt;br /&gt;
&lt;br /&gt;
Finally, scripts may be specified using the SET SCRIPT command of the scripting language itself.&lt;br /&gt;
&lt;br /&gt;
===Examples===&lt;br /&gt;
====Making use of the Operator module&#039;s Function Buttons====&lt;br /&gt;
To add a state variable called &amp;quot;Artifact&amp;quot;, and to set it using the operator&#039;s function buttons, do this:&lt;br /&gt;
*Enter the following line under &amp;quot;After All Modules Connected&amp;quot; in the operator&#039;s preferences dialog (note the minus sign):&lt;br /&gt;
 -ADD STATE Artifact 1 0&lt;br /&gt;
*Under &amp;quot;Function Buttons&amp;quot;, enter &amp;quot;Set Artifact&amp;quot; as the name of button 1, and as its command, enter (note there is no minus sign):&lt;br /&gt;
 SET STATE Artifact 1&lt;br /&gt;
*Enter &amp;quot;Clear Artifact&amp;quot; as the name of button 2, and as its command, enter&lt;br /&gt;
 SET STATE Artifact 0&lt;br /&gt;
&lt;br /&gt;
====A fully automated BCI2000 session====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Echo Please enter a subject ID:&lt;br /&gt;
Set SubjectID ${Read line}&lt;br /&gt;
&lt;br /&gt;
Startup system&lt;br /&gt;
Start executable SignalGenerator&lt;br /&gt;
Start executable SpectralSignalProcessing&lt;br /&gt;
Start executable CursorTask&lt;br /&gt;
Wait for Connected&lt;br /&gt;
Load parameterfile &amp;quot;../parms/examples/CursorTask_SignalGenerator.prm&amp;quot;&lt;br /&gt;
For i in 1 2 3&lt;br /&gt;
  Load parameterfile &amp;quot;../parms/MyExperiment/Session$i.prm&amp;quot;&lt;br /&gt;
  Set parameter SubjectName $SubjectID&lt;br /&gt;
  Set parameter SubjectSession $i&lt;br /&gt;
  Set config&lt;br /&gt;
  Wait for Resting&lt;br /&gt;
  Start&lt;br /&gt;
  Wait for Suspended 1000&lt;br /&gt;
End&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Automating BCI2000 by Operator command line arguments====&lt;br /&gt;
The following example shows how to specify script commands from the command line.&lt;br /&gt;
It fully automates BCI2000 operation by loading a parameter file, applying parameters, starting the system once the parameters are applied, and quitting the system once the run is over. For better readability, the example is broken across lines, using the ^ DOS line continuation character.&lt;br /&gt;
&lt;br /&gt;
 operator.exe --OnConnect &amp;quot;-LOAD PARAMETERFILE ../parms/examples/CursorTask_SignalGenerator.prm; SETCONFIG&amp;quot; ^&lt;br /&gt;
              --OnSetConfig &amp;quot;-SET STATE Running 1&amp;quot;  ^&lt;br /&gt;
              --OnSuspend &amp;quot;-QUIT&amp;quot;&lt;br /&gt;
&lt;br /&gt;
====Automatically arranging windows in a 4x2 grid====&lt;br /&gt;
The following example arranges Source and Roundtrip visualizations on the left side of the screen.&lt;br /&gt;
On the right side, a large area is dedicated to the Operator&#039;s log window, and the Operator&#039;s main window sits at the bottom right.&lt;br /&gt;
&lt;br /&gt;
 Arrange Visualization SRCD 4 2 1 1 2 1&lt;br /&gt;
 Arrange Visualization RNDT 4 2 3 1&lt;br /&gt;
 Arrange Window Main 4 2 4 2&lt;br /&gt;
 Arrange Window Log  4 2 1 2 3 1&lt;br /&gt;
&lt;br /&gt;
[[File:Operator_Arrangement.PNG|center|640px|border]]&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[User Reference:Module Command Line Options]], [[User Reference:Operator Module]], [[User Reference:BCI2000Shell]], [[Technical Reference:States of Operation]]&lt;br /&gt;
&lt;br /&gt;
[[Category:User Interface]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Operator_Module_Scripting&amp;diff=12562</id>
		<title>User Reference:Operator Module Scripting</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Operator_Module_Scripting&amp;diff=12562"/>
		<updated>2026-08-09T16:03:33Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* START TELNET , STOP TELNET  */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Operator scripts automate actions that the otherwise would be performed by the user, e.g. starting or suspending system operation.&lt;br /&gt;
Scripts may be contained in script files, or given immediately in the operator module&#039;s preferences dialog.&lt;br /&gt;
There is also an option to specify scripts from the [[User Reference:Module Command Line Options#Operator Module|command line]] when starting the operator module. When using the [[User_Reference:BCI2000Shell|BCI2000Shell]], or the [[Technical_Reference:Operator_Library|Operator Library]] from your own application, you may execute scripts at any time.&lt;br /&gt;
&lt;br /&gt;
In addition, the operator scripting language may be used to control an operator module over a [[User_Reference:Module_Command_Line_Options#--Telnet|Telnet]] or [[User_Reference:Module_Command_Line_Options#--WebSocket|WebSocket]] connection.&lt;br /&gt;
===Syntax===&lt;br /&gt;
&#039;&#039;&#039;Command separation.&#039;&#039;&#039; Scripts consist of sequences of the commands listed below. A command must be terminated with either a newline, or a semicolon (;).&lt;br /&gt;
This allows to put multiple commands into one line, separated by semicolon characters.&lt;br /&gt;
Commands are case-insensitive, variables and values may be case-sensitive, depending on context.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Comments.&#039;&#039;&#039; Lines starting with a &#039;#&#039; character are ignored. Such lines may be used to hold comments. In addition, when any of the first two lines of a script contains &amp;quot;#!&amp;quot; (the Unix shell invocation sequence), it will be ignored. In conjunction with [[User Reference:BCI2000Shell|BCI2000Shell]], this may be used to write Operator scripts that may be treated as executables.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Escaping.&#039;&#039;&#039; In order to resolve ambiguity about command arguments that contain white space, they must be included in double quotes, or the white space must be encoded in URL-fashion, e.g. &#039;&#039;%20&#039;&#039; instead of a space character. Similarly, when an argument contains a semicolon (;), it must be included in double quotes, or the semicolon must be encoded in URL-fashion, i.e. as &#039;&#039;%3B&#039;&#039;. Also, &amp;quot;$&amp;quot; characters indicate command substitution, so they should be encoded as &#039;&#039;%24&#039;&#039; if substitution is not desired.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Variable Substitution.&#039;&#039;&#039; When a command contains a dollar sign, alphanumeric characters following the dollar sign will be interpreted as the name of a variable: $NAME. The name will be matched against Expression variable names first, followed with Local variable names, and finally Environment variable names. When a match is found, $NAME will be replaced with the content of the matching variable. When no match is found, $NAME will be resolved to an empty string, without triggering an error.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Command Substitution.&#039;&#039;&#039; When part of a command is enclosed with ${...}, this subexpression will be substituted with the result of its execution as a command. E.g.,&lt;br /&gt;
 LOG &amp;quot;Current system state is: ${GET SYSTEM STATE}&amp;quot;&lt;br /&gt;
 LOG &amp;quot;The path environment variable is: ${PATH}&amp;quot;&lt;br /&gt;
Note that the last example uses the short form of the GET command, which will return the value of a PATH parameter or a PATH state if such exists. To make sure that only environment variables are matched, use the long form of the GET command:&lt;br /&gt;
 LOG MESSAGE &amp;quot;The path environment variable is: ${GET VARIABLE PATH}&amp;quot;&lt;br /&gt;
&amp;lt;!--&lt;br /&gt;
Substitutions may be nested, i.e. the following will work as expected:&lt;br /&gt;
 SET MyVar &amp;quot;LIST STATES&amp;quot;; LOG &amp;quot;States are: ${$MyVar}&amp;quot;&lt;br /&gt;
 SET MyVar &amp;quot;LIST STATES&amp;quot;; LOG &amp;quot;States are: ${${GET VARIABLE MyVar}}&amp;quot;&lt;br /&gt;
--&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Mathematical Expressions.&#039;&#039;&#039; A command may consist of a single [[User Reference:Expression Syntax|mathematical expression]]. This expression is then evaluated, and its result is returned as the command&#039;s result. As a special case, this allows the use of expression variables in ${...} substitutions. Consider for example&lt;br /&gt;
 x:=0; WHILE x&amp;lt;10; LOG ${x:=x+1}; END&lt;br /&gt;
There, the first command creates and initializes the expression variable x. In the WHILE condition, an expression is allowed as well as any command. In the LOG command, an expression appears in ${...}, which executes the expression in braces, and substitutes the result of the expression as an argument into the LOG command, which adds an entry to the Operator log. This results in a sequence of 10 log entries, containing the numbers from 1 to 10.&lt;br /&gt;
&lt;br /&gt;
===Commands===&lt;br /&gt;
====Control commands====&lt;br /&gt;
These commands allow conditional execution of parts of a script. When a condition is expected, any other scripting command may be given. Its result will be considered to represent a boolean value of &amp;quot;true&amp;quot; if it is empty, a nonzero number, or the string &amp;quot;true&amp;quot;. It will be taken to represent a boolean value of &amp;quot;false&amp;quot; if it contains a numeric value of zero, or any string that does not evaluate to a nonzero number. Note that identification of an empty value with &amp;quot;true&amp;quot; differs from string handling in the EVALUATE CONDITION command. This is because most scripting commands return nothing on success, but an error message on failure.&lt;br /&gt;
&lt;br /&gt;
The output of the SYSTEM and START EXECUTABLE commands is handled specially. There, the result code of the created child process is translated into a boolean value in the ordinary manner, treating a result code of zero as &amp;quot;true&amp;quot;, and any other result code as &amp;quot;false&amp;quot;. This allows to use external commands in the same way as in a native shell.&lt;br /&gt;
&lt;br /&gt;
=====IF &amp;lt;condition&amp;gt;; &amp;lt;if commands&amp;gt;; [ ELSEIF &amp;lt;condition&amp;gt;; &amp;lt;elseif commands&amp;gt;;] ... [ ELSE; &amp;lt;else commands&amp;gt;;] END=====&lt;br /&gt;
Executes &#039;&#039;if commands&#039;&#039; if &#039;&#039;condition&#039;&#039; evaluates to &amp;quot;true&amp;quot;. Otherwise, the &#039;&#039;elseif commands&#039;&#039; of the first matching &#039;&#039;elseif condition&#039;&#039; are executed. When none of the &#039;&#039;elseif conditions&#039;&#039; evaluates to &amp;quot;true&amp;quot;,  &#039;&#039;else commands&#039;&#039; are executed. ELSEIF and ELSE blocks may be omitted.&lt;br /&gt;
&lt;br /&gt;
=====WHILE &amp;lt;condition&amp;gt;; &amp;lt;loop commands&amp;gt;; END=====&lt;br /&gt;
Executes &#039;&#039;loop commands&#039;&#039; while &#039;&#039;condition&#039;&#039; evaluates to &#039;&#039;true&#039;&#039;.&lt;br /&gt;
=====DO; &amp;lt;loop commands&amp;gt;; UNTIL &amp;lt;condition&amp;gt;=====&lt;br /&gt;
Executes &#039;&#039;loop commands&#039;&#039; until &#039;&#039;condition&#039;&#039; evaluates to &#039;&#039;true&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=====FOR &amp;lt;name&amp;gt; IN &amp;lt;item1&amp;gt; &amp;lt;item2&amp;gt; ... ; &amp;lt;loop commands&amp;gt;; END=====&lt;br /&gt;
Creates a local variable with the specified name. Then, sequentially assigns each &#039;&#039;item&#039;&#039; to that variable, and executes &#039;&#039;loop commands&#039;&#039;. If an &#039;&#039;item&#039;&#039; contains newline characters, it is split up into multiple items, corresponding to the lines contained in the &#039;&#039;item&#039;&#039;. E.g.,&lt;br /&gt;
 FOR i IN top ${LIST FILES} bottom; LOG ${i}; END&lt;br /&gt;
will first write a log entry &amp;quot;top&amp;quot;. Then, it will create a log entry for each file in the current directory, and finally, it will create a log entry &amp;quot;bottom&amp;quot;.&lt;br /&gt;
=====RETURN [&amp;lt;value&amp;gt;]=====&lt;br /&gt;
Finishes execution of the current script, and optionally returns a value to the caller.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on Conditions====&lt;br /&gt;
=====EVALUATE CONDITION &amp;lt;left&amp;gt; [&amp;lt;op&amp;gt; [&amp;lt;right&amp;gt;]]=====&lt;br /&gt;
Evaluates a comparison between the &#039;&#039;left&#039;&#039; and &#039;&#039;right&#039;&#039; operands. As a comparison operator, the following may be specified: ==, !=, ~=, &amp;lt;, &amp;gt;, &amp;lt;=, &amp;gt;=. There, the != operator behaves identically to the ~= operator. When a test for equality is performed, the two operands are treated as strings, and compared in a case-insensitive manner. When any of the inequality tests is performed, the two operands are converted into floating-point numbers before comparison.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;right&#039;&#039; operand may be omitted, in which case it is treated as if an empty string were specified. Also, the &#039;&#039;op&#039;&#039; operator may be omitted, in which case the following rules apply regarding the remaining operand: If it is an empty string, or equal to the string &amp;quot;false&amp;quot; in case-insensitive comparison, the result is &amp;quot;false&amp;quot;. If entirely consists of the text representation of a floating-point number, the result is &amp;quot;false&amp;quot; if the number is 0, and &amp;quot;true&amp;quot; if the number is not 0.&lt;br /&gt;
&lt;br /&gt;
Inspired by the unix sh shells&#039; &amp;lt;tt&amp;gt;test&amp;lt;/tt&amp;gt; command, there exists a short form of the EVALUATE CONDITION command, where the arguments of EVALUATE CONDITION may appear within square brackets. This allows constructs such as&lt;br /&gt;
 IF [ ${MyVar} == MyValue ]; LOG Is equal; ELSE; LOG Is different; END&lt;br /&gt;
Note that the arguments to EVALUATE CONDITION must always be separated by white space, no matter whether its long or short form is used.&lt;br /&gt;
&lt;br /&gt;
Conditions may be combined logically using the operators &amp;amp;&amp;amp; and ||.   These go &#039;&#039;outside&#039;&#039; the square brackets.  Note also that whitespace around the comparison operators is critical, otherwise the conditional will be treated as a single string (which will always evaluate to &amp;quot;true&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
 IF [ ${foo} == foo ] || [ ${bar} == bar ]; LOG got a match; END&lt;br /&gt;
&lt;br /&gt;
====Commands operating on Local Variables====&lt;br /&gt;
Besides environment variables, there exist local variables in scripts. Local variables are inherited by sub-scripts executed with the EXECUTE SCRIPT command, but changes to the variable&#039;s values will not be propagated to the calling script.&lt;br /&gt;
=====SET VARIABLE &amp;lt;name&amp;gt; &amp;lt;value&amp;gt;=====&lt;br /&gt;
Sets the named variable to the specified value.&lt;br /&gt;
=====CLEAR VARIABLE &amp;lt;name&amp;gt;=====&lt;br /&gt;
Removes the named variable from memory.&lt;br /&gt;
=====GET VARIABLE &amp;lt;name&amp;gt;=====&lt;br /&gt;
Returns the variable&#039;s current value. When the variable does not exist, an empty value is returned rather than an error message generated.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on Environment Variables====&lt;br /&gt;
These scripting commands allow to read and modify environment variables. Changes to environment variables will be visible to child processes started with SYSTEM or START EXECUTABLE. Variable values are stored as strings. Variable names may not contain the equals sign.&lt;br /&gt;
=====SET ENVIRONMENT &amp;lt;name&amp;gt; &amp;lt;value&amp;gt;=====&lt;br /&gt;
Sets the named variable to the specified value.&lt;br /&gt;
=====CLEAR ENVIRONMENT &amp;lt;name&amp;gt;=====&lt;br /&gt;
Removes the named variable from memory.&lt;br /&gt;
=====GET ENVIRONMENT &amp;lt;name&amp;gt;=====&lt;br /&gt;
Returns the variable&#039;s current value. When the variable does not exist, an empty value is returned rather than an error message.&lt;br /&gt;
=====WRITE ENVIRONMENT &amp;lt;fileName&amp;gt; &amp;lt;variableName1&amp;gt; &amp;lt;variableName2&amp;gt; ...=====&lt;br /&gt;
Writes a series of SET ENVIRONMENT statements to the specified file, overwriting any previous file contents.  This saves the values of the named variables in such a way that you can later load them back by calling EXECUTE SCRIPT &amp;lt;filename&amp;gt;. The variable names may refer to either local or environment variables at the time of writing, but when you execute the script they will all be loaded as environment variables.&lt;br /&gt;
=====APPEND ENVIRONMENT &amp;lt;fileName&amp;gt; &amp;lt;variableName1&amp;gt; &amp;lt;variableName2&amp;gt; ...=====&lt;br /&gt;
This is the same as WRITE ENVIRONMENT, except that the SET ENVIRONMENT statements are appended to the named file without overwriting its previous contents.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on Scripts====&lt;br /&gt;
=====SET SCRIPT &amp;lt;handler names&amp;gt; &amp;lt;scripting commands&amp;gt;=====&lt;br /&gt;
Associates a sequence of scripting commands with the named handler. Handlers are specified by names as given [[#Handlers|below]]. Multiple handlers may be specified by concatenating their names with a pipe character, e.g. &amp;quot;OnStart|OnResume&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
Scripting commands must be included in double quotes, unless they consist of a single word.&lt;br /&gt;
When specifying a sequence of scripting commands, they must be separated with a semicolon character: &amp;quot;SetConfig; Start&amp;quot;. In order to use double quotes or semicolons within the commands themselves, encode these as you would in a URL, i.e. replace a double quote character with &#039;&#039;%22&#039;&#039;, and a semicolon with &#039;&#039;%3B&#039;&#039;: &amp;quot;Load Parameters %22my file%22&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
To use a script file rather than a literal script, use the EXECUTE SCRIPT command:&lt;br /&gt;
 SET SCRIPT OnConnect &amp;quot;EXECUTE SCRIPT myscript.txt&amp;quot;&lt;br /&gt;
&lt;br /&gt;
=====GET SCRIPT &amp;lt;handler name&amp;gt;=====&lt;br /&gt;
Returns the script associated with the specified handler.&lt;br /&gt;
&lt;br /&gt;
=====CLEAR SCRIPT &amp;lt;handler names&amp;gt;=====&lt;br /&gt;
Clears scripts for the given handlers. Equivalent to calling SET SCRIPT with an empty script.&lt;br /&gt;
&lt;br /&gt;
=====EXECUTE SCRIPT &amp;lt;file or handler name&amp;gt; [&amp;lt;Arg1&amp;gt; &amp;lt;Arg2&amp;gt; ... &amp;lt;Arg9&amp;gt;]=====&lt;br /&gt;
Executes a script contained in a file, and optionally sets the script&#039;s local variables &#039;&#039;1&#039;&#039; to &#039;&#039;9&#039;&#039; to the specified values. To execute script commands already associated with a handler, provide a handler name rather than a file. When the script is executed successfully, the result of the last executed script command becomes the result of the EXECUTE SCRIPT command itself. Use &amp;quot;RETURN &amp;lt;value&amp;gt;&amp;quot; anywhere in a script in order to finish execution, and return a certain value.&lt;br /&gt;
&lt;br /&gt;
When you run a script through EXECUTE SCRIPT, it will inherit copies of variables from its calling script/command line. Modifications of variables will be local to the script, and will be lost when script execution is complete.&lt;br /&gt;
&lt;br /&gt;
The &#039;&#039;AbortOnError&#039;&#039; variable determines whether a script is aborted when any of its commands result in an error message. &#039;&#039;AbortOnError&#039;&#039; is not inherited but defaults to 1 in any script.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on Parameters====&lt;br /&gt;
=====LOAD PARAMETERFILE &amp;lt;file&amp;gt;, LOAD PARAMETERS &amp;lt;file&amp;gt;=====&lt;br /&gt;
Loads a parameter file specified by its path and name. Relative paths are interpreted relative to the operator module&#039;s working directory at startup. Usually, this matches the executable&#039;s location in the &amp;lt;tt&amp;gt;prog&amp;lt;/tt&amp;gt; directory.&lt;br /&gt;
As the parameter file name must not contain white space, please use HTML-type encoding for white space characters, such as &amp;lt;tt&amp;gt;Documents%20and%20Settings&amp;lt;/tt&amp;gt; when referring to a user&#039;s &amp;quot;Documents and Settings&amp;quot; folder.&lt;br /&gt;
&lt;br /&gt;
NOTE: Only those parameters will be loaded that do exist at the time when this command is called. In idle state, no parameters exist other than created by ADD PARAMETER. After modules are connected and have published their parameters to the Operator module, a full set of parameters exists.&lt;br /&gt;
&lt;br /&gt;
=====ADD PARAMETER &amp;lt;parameter definition&amp;gt;=====&lt;br /&gt;
Adds a parameter to the system. The parameter is specified as a [[Technical_Reference:Parameter_Definition#Parameter_Lines|parameter line]]. This command may not be used after system initialization has completed, i.e. its use is restricted to the &amp;quot;Idle&amp;quot; and &amp;quot;Publishing&amp;quot; [[Technical_Reference:States_of_Operation#Publishing_Phase|phases of system operation]]. In terms of handlers, its use is restricted to the &#039;&#039;OnConnect&#039;&#039; handler.&lt;br /&gt;
=====EXISTS PARAMETER &amp;lt;name&amp;gt;=====&lt;br /&gt;
Returns &amp;quot;true&amp;quot; when the specified parameter exists in the system, and &amp;quot;false&amp;quot; otherwise.&lt;br /&gt;
&lt;br /&gt;
=====ISEMPTY PARAMETER &amp;lt;name&amp;gt;=====&lt;br /&gt;
Returns &amp;quot;true&amp;quot; when the specified parameter exists and has no values, &amp;quot;false&amp;quot; if it exists and has values.&lt;br /&gt;
&lt;br /&gt;
=====SET PARAMETER &amp;lt;name&amp;gt;[( idx1, idx2 )] &amp;lt;value&amp;gt;=====&lt;br /&gt;
Sets the named parameter to the specified value. Values that contain special characters, or whitespace must use the [[Technical_Reference:Parameter_Definition#Special_Characters|parameter value encoding]]. Use parentheses to specify indices or labels. Omitted indices default to 1.&lt;br /&gt;
&lt;br /&gt;
=====SET PARAMETER &amp;lt;parameter line&amp;gt;=====&lt;br /&gt;
Replace a parameter&#039;s value and definition with the information given in the [[Technical_Reference:Parameter_Definition#Parameter_Lines|parameter line]]. The parameter must exist in the system when this command is executed.&lt;br /&gt;
&lt;br /&gt;
=====GET PARAMETER &amp;lt;name&amp;gt;[( idx1, idx2 )]=====&lt;br /&gt;
Prints the value of the named parameter. Use parentheses to specify indices or labels.&lt;br /&gt;
&lt;br /&gt;
=====SET PARAMETERROWS &amp;lt;parameter name&amp;gt; &amp;lt;rows&amp;gt;=====&lt;br /&gt;
Sets the number of rows in the named parameter.&lt;br /&gt;
&lt;br /&gt;
=====GET PARAMETERROWS &amp;lt;parameter name&amp;gt;=====&lt;br /&gt;
Prints the number of rows in the named parameter.&lt;br /&gt;
&lt;br /&gt;
=====SET PARAMETERCOLS &amp;lt;parameter name&amp;gt;=====&lt;br /&gt;
Sets the number of columns in the named parameter.&lt;br /&gt;
&lt;br /&gt;
=====GET PARAMETERCOLS &amp;lt;parameter name&amp;gt;=====&lt;br /&gt;
Prints the number of columns in the named parameter.&lt;br /&gt;
&lt;br /&gt;
=====LIST PARAMETER &amp;lt;wildcard expression&amp;gt;, LIST PARAMETERS=====&lt;br /&gt;
Prints all parameters with names matching the wildcard expression, in form of parameter lines.&lt;br /&gt;
=====CLEAR PARAMETERS=====&lt;br /&gt;
Clears the list of parameters in the system. May only be executed in &#039;&#039;Idle&#039;&#039; and &#039;&#039;Publishing&#039;&#039; system states.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on States====&lt;br /&gt;
In BCI2000, there are three types of States that differ in their alignment to brain signal data (see [[Technical_Reference:State_Definition#Kinds_of_States|Kinds of States]]). This section applies to both Stream States, and normal States.&lt;br /&gt;
For Event States, see the next section.&lt;br /&gt;
&lt;br /&gt;
=====ADD STATE &amp;lt;name&amp;gt; &amp;lt;bit width&amp;gt; &amp;lt;initial value&amp;gt;=====&lt;br /&gt;
Adds a state variable to the system. State variables are defined by name, bit width, and initial value (see [[Technical Reference:State Definition]]). This command may not be used after system initialization has completed, i.e. its use is restricted to the &amp;quot;Idle&amp;quot; and &amp;quot;Publishing&amp;quot; [[Technical_Reference:States_of_Operation#Publishing_Phase|phases of system operation]]. In terms of handlers, its use is restricted to the &#039;&#039;OnConnect&#039;&#039; handler.&lt;br /&gt;
=====EXISTS STATE &amp;lt;name&amp;gt;=====&lt;br /&gt;
Returns &amp;quot;true&amp;quot; when the specified state exists in the system, and &amp;quot;false&amp;quot; otherwise.&lt;br /&gt;
&lt;br /&gt;
=====SET STATE &amp;lt;name&amp;gt; &amp;lt;value&amp;gt;, SET STATES &amp;lt;name1&amp;gt; &amp;lt;value1&amp;gt; &amp;lt;name2&amp;gt; &amp;lt;value2&amp;gt; ...=====&lt;br /&gt;
Sets the named state variable to the specified integer value by sending a state message to the source module.&lt;br /&gt;
Setting the &#039;&#039;Running&#039;&#039; state to 1 will start system operation, setting it to 0 will suspend the system.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;tt&amp;gt;SET STATES&amp;lt;/tt&amp;gt; will atomically set the values of multiple states.&lt;br /&gt;
&lt;br /&gt;
=====GET STATE[(&amp;lt;sample&amp;gt;)] &amp;lt;name&amp;gt;=====&lt;br /&gt;
Gets the value of the named state. Note that state values are not updated from the application module when the &#039;&#039;OperatorBackLink&#039;&#039; parameter is 0. In that case, GET STATE will return the state&#039;s initial value.&lt;br /&gt;
&lt;br /&gt;
An optional one-based sample index may be given to indicate that the state value should be retrieved at the given sample position. Sample positions range from 1 to the number of samples per block present in the state vector. The number of samples per block may be retrieved using GET STATEVECTOR SAMPLES.&lt;br /&gt;
&lt;br /&gt;
By default, the state&#039;s value is retrieved for sample index 1.&lt;br /&gt;
&lt;br /&gt;
=====LIST STATE &amp;lt;wildcard expression&amp;gt;, LIST STATES=====&lt;br /&gt;
Lists all states, or states with names matching the given wildcard expression, in form of state lines.&lt;br /&gt;
=====CLEAR STATES=====&lt;br /&gt;
Clears the list of states in the system. May only be executed in &#039;&#039;Idle&#039;&#039; and &#039;&#039;Publishing&#039;&#039; system states.&lt;br /&gt;
&lt;br /&gt;
=====FREEZE STATES, THAW STATES=====&lt;br /&gt;
Freezes/thaws the values of all states in the state vector, without interfering with system operation.  &amp;quot;Freezing&amp;quot; creates, and &amp;quot;thawing&amp;quot; discards, a frozen snapshot of the state vector. For the duration of its existence, GET STATE calls will be diverted to the snapshot.  This is useful for ensuring that multiple GET STATE commands actually retrieve mutually-consistent values from different state variables (i.e. values from the same sample-block).&lt;br /&gt;
&lt;br /&gt;
=====GET STATEVECTOR SAMPLES=====&lt;br /&gt;
Returns the number of samples in the state vector per block of data. In the system&#039;s &amp;quot;Running&amp;quot; state, this matches the number of samples per block in the source data. Prior to the &amp;quot;Running&amp;quot; state, this command returns 1 because the state vector has not yet been updated from the application module.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on Events====&lt;br /&gt;
Events are a special type of state, which are recorded asynchronously, at single-sample resolution. Events may only be added while the system is in &amp;quot;idle&amp;quot; state. This kind of events is not related to [[#Handlers|Operator Events]] as defined below.&lt;br /&gt;
=====ADD EVENT &amp;lt;name&amp;gt; &amp;lt;bit width&amp;gt; &amp;lt;initial value&amp;gt;=====&lt;br /&gt;
Adds an event to the system. Like state variables, events are defined by name, bit width, and initial value (see [[Technical Reference:State Definition]]). This command may not be used after the system has started up, so it is typically executed in a batch file before &#039;&#039;STARTUP&#039;&#039; has been called.&lt;br /&gt;
&lt;br /&gt;
=====EXISTS EVENT &amp;lt;name&amp;gt;=====&lt;br /&gt;
Returns &amp;quot;true&amp;quot; when the specified event exists in the system, and &amp;quot;false&amp;quot; otherwise.&lt;br /&gt;
&lt;br /&gt;
=====SET EVENT &amp;lt;name&amp;gt; &amp;lt;value&amp;gt;=====&lt;br /&gt;
Asynchronously sets an event to the given value. Recording events requires the EventLink logger component to be present in the source module.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTE:&#039;&#039;&#039; In versions prior to BCI2000 3.06, this command behaved as described for PULSE EVENT below, rather than as advertised. If you used SET EVENT in your scripts, it is recommended to replace it with PULSE EVENT in order to retain original behavior.&lt;br /&gt;
&lt;br /&gt;
=====PULSE EVENT &amp;lt;name&amp;gt; &amp;lt;value&amp;gt;=====&lt;br /&gt;
Asynchronously sets an event to the given value for a single sample duration. Recording events requires the EventLink logger component to be present in the source module.&lt;br /&gt;
&lt;br /&gt;
=====SET EVENTS [&amp;lt;name&amp;gt; &amp;lt;value&amp;gt;] [&amp;lt;name2&amp;gt; &amp;lt;value2&amp;gt;] ... =====&lt;br /&gt;
Asynchronously sets given events to their specified values. Using this command, rather than multiple SET EVENT commands in a row, ensures that all event changes are recorded at exactly the same point in time. Recording events requires the EventLink logger component to be present in the source module.&lt;br /&gt;
&lt;br /&gt;
=====PULSE EVENTS [&amp;lt;name&amp;gt; &amp;lt;value&amp;gt;] [&amp;lt;name2&amp;gt; &amp;lt;value2&amp;gt;] ... =====&lt;br /&gt;
Asynchronously sets given events to their specified values for a single sample duration. Using this command, rather than multiple PULSE EVENT commands in a row, ensures that all event changes are recorded at exactly the same point in time. Recording events requires the EventLink logger component to be present in the source module.&lt;br /&gt;
&lt;br /&gt;
=====GET EVENT[(&amp;lt;sample&amp;gt;)] &amp;lt;name&amp;gt;=====&lt;br /&gt;
Gets the value of the named event. Note that evemt values are not updated from the application module when the &#039;&#039;OperatorBackLink&#039;&#039; parameter is 0.&lt;br /&gt;
&lt;br /&gt;
An optional one-based sample index may be given to indicate that the event&#039;s value should be retrieved at the given sample position. Sample positions range from 1 to the number of samples per block present in the state vector. The number of samples per block may be retrieved using GET STATEVECTOR SAMPLES.&lt;br /&gt;
&lt;br /&gt;
By default, the event&#039;s value is retrieved for sample index 1.&lt;br /&gt;
&lt;br /&gt;
=====LIST EVENT &amp;lt;wildcard expression&amp;gt;, LIST EVENTS=====&lt;br /&gt;
Lists all events, or events with names matching the given wildcard expression, in form of state lines.&lt;br /&gt;
=====CLEAR EVENTS=====&lt;br /&gt;
Clears the list of events in the system. May only be executed in &#039;&#039;Idle&#039;&#039; state.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on VisProperties====&lt;br /&gt;
=====SET VISPROPERTY &amp;lt;visID&amp;gt;.&amp;lt;name&amp;gt; &amp;lt;value&amp;gt;=====&lt;br /&gt;
Sets the named [[Technical Reference:Visualization Properties|visualization property]] for the specified visualization ID to the given value. If the visualization ID contains a dot character, it must be encoded in [[Technical_Reference:Parameter_Definition#Special_Characters|parameter value encoding]]. E.g., setting the window width for the visualization ID &amp;quot;2.D1&amp;quot; would be written &amp;lt;code&amp;gt;SET VISPROPERTY 2%2ED1.Width 200&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=====RESET VISPROPERTY &amp;lt;visID&amp;gt;.&amp;lt;name&amp;gt;=====&lt;br /&gt;
Removes the named visualization property with the specified visualization ID from the property store, effectively resetting its value to its default.&lt;br /&gt;
&lt;br /&gt;
=====GET VISPROPERTY &amp;lt;visID&amp;gt;.&amp;lt;name&amp;gt;=====&lt;br /&gt;
Prints the value of the named [[Technical Reference:Visualization Properties|visualization property]] for the specified visualization ID.&lt;br /&gt;
&lt;br /&gt;
=====SET VISPROPERTIES &amp;lt;property set ID&amp;gt;=====&lt;br /&gt;
Applies a set of visualization property values as given in the [[#VisPropertySets|VisPropertySets]] parameter. In that matrix-valued parameter, row labels specify visualization properties such as &amp;quot;SRCD.Left&amp;quot;, and columns represent sets of property values. Column labels are IDs of the corresponding property sets.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on the Control Signal====&lt;br /&gt;
=====GET SIGNAL( &amp;lt;channel index&amp;gt;, &amp;lt;element index&amp;gt; )=====&lt;br /&gt;
Prints the value of the control signal at the given indices. Indices are 1-based.&lt;br /&gt;
=====GET SIGNAL CHANNELS=====&lt;br /&gt;
Prints the number of channels in the control signal.&lt;br /&gt;
=====GET SIGNAL ELEMENTS=====&lt;br /&gt;
Prints the number of elements (samples) in the control signal.&lt;br /&gt;
=====FREEZE SIGNAL, THAW SIGNAL=====&lt;br /&gt;
Freezes/thaws the contents of the control signal, without interfering with system operation. &amp;quot;Freezing&amp;quot; creates, and &amp;quot;thawing&amp;quot; discards, a frozen snapshot of the control signal. For the duration of its existence, GET SIGNAL calls will be diverted to the snapshot. This is useful for ensuring that multiple GET SIGNAL commands actually retrieve mutually-consistent values from different elements in the control signal (i.e. values from the same sample-block).&lt;br /&gt;
&lt;br /&gt;
====Commands operating on Expressions====&lt;br /&gt;
=====EVALUATE EXPRESSION &amp;lt;expression&amp;gt;=====&lt;br /&gt;
This command treats the remainder of the command as a literal mathematical expression (for a description, see [[User Reference:Expression Syntax]]). An expression may contain assignments to variables; such variables may then be used in later expressions. Note that expression variables are different from local and environment variables that may be accessed by GET/SET VARIABLE/ENVIRONMENT. Expression variables hold numerical values, while local and environment variables hold string values. Also, environment variables are accessible to child processes started with SYSTEM or START EXECUTABLE, while expression variables are accessible only to scripts. When a script is executed using the EXECUTE SCRIPT command, it will inherit a copy of all expression variables present. Changes to these variables from the executed script will not be visible in the parent script.&lt;br /&gt;
&lt;br /&gt;
=====CLEAR EXPRESSION VARIABLE &amp;lt;name&amp;gt;=====&lt;br /&gt;
Clears the named expression variable from storage.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on Watches====&lt;br /&gt;
A &amp;quot;Watch&amp;quot; is an object that consists of a set of expressions, and an action. Whenever the value of any of the expressions changes, the watch is &amp;quot;triggered&amp;quot;, and the action is executed. Watches allow client applications to respond to BCI2000 state changes in a reliable manner. Rather than polling information from BCI2000, a client may create a watch to be notified about changes of interest. This avoids the problem of missing short-lived changes, which is inherent in the polling approach.&lt;br /&gt;
&lt;br /&gt;
For watches created through operator scripting, the action consists of dumping values of all the expressions to a UDP port.&lt;br /&gt;
&lt;br /&gt;
The intended use of a watch from a client application is to create a separate thread that reads from the watch&#039;s UDP port in a blocking mode, and calls an appropriate handler function whenever it receives data.&lt;br /&gt;
The data sent will consist of a single UDP packet with a single line in ASCII format, terminated with a CRLF sequence. The line consists of tab-separated data fields, which contain the current values of the expressions specified when creating the watch. In addition, the first field contains a time stamp in milliseconds. This time stamp represents the point in time where the expression value changed, with an accuracy of a single sample.&lt;br /&gt;
&lt;br /&gt;
=====ADD WATCH [decimate &amp;lt;n&amp;gt;] &amp;lt;expression1&amp;gt; &amp;lt;expression2&amp;gt; ... [AT &amp;lt;ip:port&amp;gt;]=====&lt;br /&gt;
Adds a watch for the listed expressions. Each expression&#039;s value will be reported in a separate field. When an address is specified in &amp;lt;tt&amp;gt;ip:port&amp;lt;/tt&amp;gt; format, the watch tries to open that port for output, and creation fails if that port is taken. When no address is specified, a free port is chosen automatically. In both cases, successful creation of the watch is indicated by returning the output address in ASCII format. The address is also used to uniquely identify a watch in the context of a connection.&lt;br /&gt;
&lt;br /&gt;
When a &#039;&#039;decimate &amp;lt;n&amp;gt;&#039;&#039; clause is present, the watch will be created with decimation, i.e. it will only be evaluated for every &#039;&#039;n&#039;&#039;th&lt;br /&gt;
sample of the state vector.&lt;br /&gt;
&lt;br /&gt;
If a watch is created through a remote connection, it will use the remote host&#039;s external IP address for automatically chosen addresses. Otherwise, the output port will be associated with the machine&#039;s &amp;lt;tt&amp;gt;localhost&amp;lt;/tt&amp;gt; address.&lt;br /&gt;
&lt;br /&gt;
Watches may be created even if the system is currently running. In this case, the watch is triggered immediately at creation, and sends its current expression values to its output port.&lt;br /&gt;
&lt;br /&gt;
=====ADD WATCH SYSTEM STATE [AT &amp;lt;address&amp;gt;]=====&lt;br /&gt;
Similar to the first variant of ADD WATCH, but will watch the system&#039;s state as reported by GET SYSTEM STATE.&lt;br /&gt;
&lt;br /&gt;
=====CLEAR WATCH &amp;lt;address&amp;gt;, CLEAR WATCHES [&amp;lt;wildcard-expression&amp;gt;]=====&lt;br /&gt;
Removes the watches specified by address, or those with their addresses matching a wildcard expression. If CLEAR WATCHES is called without argument, all watches will be deleted.&lt;br /&gt;
&lt;br /&gt;
=====TRIGGER WATCH &amp;lt;address&amp;gt;, TRIGGER WATCHES  [&amp;lt;wildcard-expression&amp;gt;]=====&lt;br /&gt;
Forces dumping of the watches&#039; current expression values to their output ports. Mostly useful for testing purposes.&lt;br /&gt;
&lt;br /&gt;
=====LIST WATCHES  [&amp;lt;wildcard-expression&amp;gt;]=====&lt;br /&gt;
Displays a list of existing watches, and their addresses.&lt;br /&gt;
&lt;br /&gt;
=====COUNT WATCHES  [&amp;lt;wildcard-expression&amp;gt;]=====&lt;br /&gt;
Returns the number of existing watches, or the number of watches whose addresses match the optional wildcard expression.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on Files, Directories, and Paths====&lt;br /&gt;
=====EXTRACT DIRECTORY &amp;lt;path&amp;gt;, EXTRACT FILE &amp;lt;path&amp;gt;, EXTRACT FILE BASE &amp;lt;path&amp;gt;=====&lt;br /&gt;
Extracts the directory or file portion of a given path. When the path specifies a non-existing directory, the directory name must be followed with a separator (&amp;quot;/&amp;quot;) in order to be recognized as a directory. The EXTRACT DIRECTORY command always returns its result with a trailing separator. The EXTRACT FILE BASE command returns the file portion without extension.&lt;br /&gt;
&lt;br /&gt;
=====IS DIRECTORY &amp;lt;path&amp;gt;, IS FILE &amp;lt;path&amp;gt;, IS PATH &amp;lt;path&amp;gt;=====&lt;br /&gt;
Determines whether the specified path points to an existing directory, file, or any of the two. The result is returned as one of the strings &amp;quot;true&amp;quot; or &amp;quot;false&amp;quot;.&lt;br /&gt;
=====PARENT DIRECTORY &amp;lt;path&amp;gt;=====&lt;br /&gt;
Returns the parent directory of the specified path, independently of whether the path points to a directory, or to a file.&lt;br /&gt;
=====CURRENT DIRECTORY=====&lt;br /&gt;
Returns the current working directory. Note that working directories are per-script, i.e. multiple scripts running in parallel may have different working directories.&lt;br /&gt;
&lt;br /&gt;
=====CHANGE DIRECTORY &amp;lt;path&amp;gt;=====&lt;br /&gt;
Changes the script&#039;s working directory. Note that working directories are per-script, i.e. multiple scripts running in parallel may have different working directories. Also, this command will only affect the default directory of scripting commands, not the application-global working directory as seen by the OS.&lt;br /&gt;
&lt;br /&gt;
=====MAKE DIRECTORY &amp;lt;path&amp;gt;=====&lt;br /&gt;
Creates a new directory with the given path. The directory&#039;s parent must exist for the command to succeed.&lt;br /&gt;
=====LIST DIRECTORY [&amp;lt;path&amp;gt; or &amp;lt;wildcard expression&amp;gt;]=====&lt;br /&gt;
Returns a listing of the specified directory, or the current working directory if no path is specified. The listing is in long form. You may use wildcard expressions in order to restrict the output.&lt;br /&gt;
=====LIST FILE &amp;lt;wildcard expression&amp;gt;=====&lt;br /&gt;
Returns a list of file names matching &#039;&#039;wildcard expression&#039;&#039; in the current directory.&lt;br /&gt;
=====LIST FILES [&amp;lt;directory&amp;gt; [&amp;lt;wildcard expression&amp;gt;]]=====&lt;br /&gt;
Returns a list of file names from the specified directory, matching &#039;&#039;wildcard expression&#039;&#039;. When &#039;&#039;wildcard expression&#039;&#039; is missing, all files are listed. When &#039;&#039;directory&#039;&#039; is missing, files in the current directory are listed.&lt;br /&gt;
=====LIST DIRECTORIES [&amp;lt;directory&amp;gt; [&amp;lt;wildcard expression&amp;gt;]]=====&lt;br /&gt;
Returns a list of directory names from the specified directory, matching &#039;&#039;wildcard expression&#039;&#039;. When &#039;&#039;wildcard expression&#039;&#039; is missing, all directories are listed. When &#039;&#039;directory&#039;&#039; is missing, directories in the current directory are listed.&lt;br /&gt;
&lt;br /&gt;
=====RENAME FILE &amp;lt;current path&amp;gt; &amp;lt;new path&amp;gt;, RENAME DIRECTORY &amp;lt;current path&amp;gt; &amp;lt;new name&amp;gt;=====&lt;br /&gt;
Renames a file resp. a directory. For files, a different path may be given in the second argument, resulting in that the file is moved to the location specified by the new path. For directories, the path up to the directory&#039;s name must stay the same.&lt;br /&gt;
=====REMOVE FILE &amp;lt;path&amp;gt;, REMOVE DIRECTORY &amp;lt;path&amp;gt;=====&lt;br /&gt;
Removes the specified file or directory. This command cannot be undone. The directory must be empty for the command to succeed.&lt;br /&gt;
=====FORCEREMOVE DIRECTORY &amp;lt;path&amp;gt;=====&lt;br /&gt;
Removes the specified directory and its contents. Symbolic links are treated as ordinary files, i.e. they are not followed. This command cannot be undone.&lt;br /&gt;
=====NORMALIZED PATH &amp;lt;path&amp;gt;=====&lt;br /&gt;
Returns &amp;lt;path&amp;gt;, with the following transformations applied:&lt;br /&gt;
*Removes relative elements (&amp;lt;tt&amp;gt;..&amp;lt;/tt&amp;gt; &amp;lt;tt&amp;gt;.&amp;lt;/tt&amp;gt;) as far as possible. For absolute paths, the result will not contain any relative elements; for relative paths, double-dots may appear at the beginning of the result if necessary.&lt;br /&gt;
If a relative path simplifies to the empty string, &amp;lt;tt&amp;gt;./&amp;lt;/tt&amp;gt; is returned. Thus, the result of NORMALIZED PATH is never empty, unless its input was empty.&lt;br /&gt;
*Replaces backward slashes with forward slashes to achieve uniformity across platforms.&lt;br /&gt;
*On case-insensitive file systems, replaces the spelling of names with the one stored in the file system.&lt;br /&gt;
*On Win32, replaces short (8.3) names with long ones.&lt;br /&gt;
&lt;br /&gt;
=====CANONICAL PATH &amp;lt;path&amp;gt;=====&lt;br /&gt;
If &amp;lt;path&amp;gt; points to an existing file or directory, CANONICAL PATH returns a valid absolute file path, suitable as an unambiguous representation for the object pointed to. Especially, two non-empty canonical path strings will compare equal if and only if they refer to the same file system object.&lt;br /&gt;
&lt;br /&gt;
If &amp;lt;path&amp;gt; does not point to an existing file system object, construction of a canonical path is not possible due to lack of information about the named object, and CANONICAL PATH will return an empty string.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTES:&#039;&#039;&#039; The need for an unambiguous, or canonical, representation arises due to ambiguities in the string representation of paths, and in file systems themselves.&lt;br /&gt;
*Paths may contain relative elements: &amp;lt;tt&amp;gt;/mydir/../myfile&amp;lt;/tt&amp;gt; points to the same object as &amp;lt;tt&amp;gt;/myfile&amp;lt;/tt&amp;gt;.&lt;br /&gt;
*A path that involves symbolic links will point to the same object as a path containing one or more of those links in resolved form.&lt;br /&gt;
*File systems may be case-insensitive, or may even provide multiple distinct names for individual directory entries (Win32 short vs. long names).&lt;br /&gt;
&lt;br /&gt;
On &#039;&#039;&#039;Win32,&#039;&#039;&#039; CANONICAL PATH returns the short (8.3) representation of a path, using uppercase spelling, and backslashes as directory separators. Apart from efficiency considerations, this aesthetically unpleasing representation has been chosen to discourage its use for anything except comparing file system objects.&lt;br /&gt;
&lt;br /&gt;
On &#039;&#039;&#039;other systems,&#039;&#039;&#039; CANONICAL PATH will return the result of the POSIX &amp;lt;tt&amp;gt;realpath()&amp;lt;/tt&amp;gt; function.&lt;br /&gt;
&lt;br /&gt;
In both cases, a CANONICAL PATH will end with a native directory separator if, and only if, the object pointed to is a directory.&lt;br /&gt;
&lt;br /&gt;
=====REAL PATH &amp;lt;path&amp;gt;=====&lt;br /&gt;
On this command provides a work-alike for the POSIX &amp;lt;tt&amp;gt;realpath()&amp;lt;/tt&amp;gt; function.&lt;br /&gt;
If &amp;lt;path&amp;gt; is empty, or if &amp;lt;path&amp;gt; points to a non-existing object, the result will be empty. Otherwise, an absolute path will be returned, with symbolic links resolved, using forward slashes as directory separators, and with spelling normalized as described for NORMALIZED PATH. A forward slash will be appended if the path points to a directory.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;NOTE:&#039;&#039;&#039; In principle, the path returned by REAL PATH should be just as unambiguous as the result of CANONICAL PATH. However, there are a few caveats:&lt;br /&gt;
* On case-insensitive file systems, two independently obtained results of REAL PATH might differ in case spelling even if referring to the same file system object. This should not be the case for CANONICAL PATH.&lt;br /&gt;
* Determining the result of CANONICAL PATH is a fast operation. In contrast, REAL PATH may be expensive to determine, as for each directory on the path a listing needs to be obtained, and a canonical path needs to be formed, and compared, for half of the listed directory entries on average.&lt;br /&gt;
* On Win32, quite some amount of complexity arises from backward compatibility layers, forbidden file names, multiple filesystem roots, etc. Internally calling &amp;lt;tt&amp;gt;GetShortPathName()&amp;lt;/tt&amp;gt;, CANONICAL PATH does not need to handle that complexity, and may be considered more reliable for identifying file system objects than REAL PATH.&lt;br /&gt;
&lt;br /&gt;
=====READ FILE &amp;lt;filename&amp;gt;=====&lt;br /&gt;
Returns the text content of the file indicated by &amp;lt;filename&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=====WRITE FILE &amp;lt;filename&amp;gt; &amp;lt;line&amp;gt;,  OVERWRITE FILE &amp;lt;filename&amp;gt; &amp;lt;line&amp;gt;, APPEND TO FILE &amp;lt;filename&amp;gt; &amp;lt;line&amp;gt;=====&lt;br /&gt;
These commands write the specified &amp;lt;line&amp;gt; of text to the file indicated by &amp;lt;filename&amp;gt;.   They behave like the &amp;lt;tt&amp;gt;echo&amp;lt;/tt&amp;gt; command of a typical shell, in the sense that a line-ending will be automatically added to the &amp;lt;line&amp;gt; if it does not already end with one. To suppress the automatic line-ending, you can say WRITE FILE &amp;lt;filename&amp;gt; &amp;lt;content&amp;gt; WITHOUT LINE ENDING (or equivalently, for short, you can say WITHOUT LF or WITHOUT CRLF). Any previous content in &amp;lt;file&amp;gt; will be lost if you use WRITE or OVERWRITE (they are the same, just synonyms for each other) whereas APPEND preserves previous content and adds the new &amp;lt;line&amp;gt; to the end.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on executables====&lt;br /&gt;
=====START EXECUTABLE &amp;lt;command line&amp;gt;=====&lt;br /&gt;
Behaves identically to CREATE PROCESS, except that it does not report a process id for the new process. For details, see CREATE PROCESS.&lt;br /&gt;
&lt;br /&gt;
=====CATEGORIZE EXECUTABLE &amp;lt;name or path&amp;gt;=====&lt;br /&gt;
BCI2000 keeps a list of executables/modules that have been built, and sorts them into categories. The CATEGORIZE EXECUTABLE command provides a way to retrieve that information. Given the name or path of an executable, it outputs one of the following categories:&lt;br /&gt;
* SignalSource,&lt;br /&gt;
* SignalProcessing,&lt;br /&gt;
* Application,&lt;br /&gt;
* Operator,&lt;br /&gt;
* Tool,&lt;br /&gt;
* Helper,&lt;br /&gt;
* Unknown.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on Lines of input/output====&lt;br /&gt;
=====WRITE LINE &amp;lt;line&amp;gt;=====&lt;br /&gt;
Writes a line of output. Destination depends on the context in which a script is executed. If the context is an Operator Handler, output is written as a log entry. If the context is a telnet session, output is written to the telnet connection. If the context is a [[User Reference:BCI2000Shell|BCI2000Shell]], output is written to the shell&#039;s stdout.&lt;br /&gt;
&lt;br /&gt;
=====READ LINE=====&lt;br /&gt;
Reads a line of input from the current execution context&#039;s input. If the command is executed within an Operator Handler, it will fail. If executed within a telnet session, the other side of the connection is prompted for input. If executed from within a [[User Reference:BCI2000Shell|BCI2000Shell]], input is read from the shell&#039;s stdin.&lt;br /&gt;
&lt;br /&gt;
====Commands operating on Processes in the Operating System====&lt;br /&gt;
=====CREATE PROCESS &amp;lt;command line&amp;gt;=====&lt;br /&gt;
Starts the specified executable with options. This command returns after the started program has finished initialization, i.e. it will detect load time failures such as missing DLLs on Windows. If the process is still running when CREATE PROCESS returns, its result will be an operating system process id (pid). If the process has terminated, CREATE PROCESS will report its exit code marked with an &amp;lt;tt&amp;gt;ExitCode&amp;lt;/tt&amp;gt; tag to allow distinction between a pid and an exit code.&lt;br /&gt;
Please note that CREATE PROCESS requires quoting of arguments differently from other scripting commands. For details, see the SYSTEM command.&lt;br /&gt;
&lt;br /&gt;
=====TERMINATE PROCESS &amp;lt;pid&amp;gt;=====&lt;br /&gt;
Tries to terminate the process with the given operating system pid, waiting for the process to terminate before returning. Will return &amp;lt;tt&amp;gt;false&amp;lt;/tt&amp;gt; to indicate that a suitable process existed but could not be terminated.&lt;br /&gt;
&lt;br /&gt;
=====WAIT FOR PROCESS &amp;lt;pid&amp;gt; [&amp;lt;timeout seconds&amp;gt; = infinite]=====&lt;br /&gt;
Waits for the process with the given operating system pid to terminate, or the timeout to expire. Returns &amp;lt;tt&amp;gt;false&amp;lt;/tt&amp;gt; to indicate that the process is still executing.&lt;br /&gt;
&lt;br /&gt;
=====SHOW PROCESS &amp;lt;pid&amp;gt;=====&lt;br /&gt;
Makes all windows visible which are associated with the process referred to by pid.&lt;br /&gt;
In addition, brings one of the process&#039; top level (desktop level) windows to the front for user interaction.&lt;br /&gt;
&lt;br /&gt;
=====HIDE PROCESS &amp;lt;pid&amp;gt;=====&lt;br /&gt;
Makes all windows invisible which are associated with the process referred to by pid.&lt;br /&gt;
&lt;br /&gt;
====Global commands====&lt;br /&gt;
=====HELP [&amp;lt;type&amp;gt;]=====&lt;br /&gt;
When called with a type argument, lists commands that exist for the specified type (e.g., SYSTEM, or FILE). When called without argument, lists all commands in their main form. HELP ALL will list all commands, including synonyms.&lt;br /&gt;
&lt;br /&gt;
=====SET &amp;lt;name&amp;gt; &amp;lt;value&amp;gt;, GET &amp;lt;name&amp;gt;, &amp;lt;name&amp;gt;=====&lt;br /&gt;
Allows to set or retrieve the value of local and environment variables. The name is matched against local and environment variables. When no variable with the given name is found, SET will create a local variable, while GET will result in an error. GET may be further abbreviated to only consist of a name.&lt;br /&gt;
&lt;br /&gt;
GET further allows evaluation of [[User_Reference:Expression_Syntax|mathematical expressions]]. When the expression is invalid, or contains an unknown variable, an error is triggered.&lt;br /&gt;
&lt;br /&gt;
=====GET SYSTEM STATE=====&lt;br /&gt;
Prints the current system state. This will be one of Unavailable, Idle, Startup, Initialization, Resting, Suspended, ParamsModified, Running, Termination, Busy (for details, see the WAIT FOR command below).&lt;br /&gt;
&lt;br /&gt;
=====GET CURRENT RUN FILE=====&lt;br /&gt;
Prints the full path to the current run file, or an empty string, if called outside Running state.&lt;br /&gt;
&lt;br /&gt;
=====WAIT FOR &amp;lt;system state&amp;gt; [&amp;lt;timeout seconds&amp;gt;]=====&lt;br /&gt;
Waits until the system is in the specified state. This may be one of Idle, Startup, Connected, Resting, Suspended, ParamsModified, Running, Busy, or a combination of these, separated with a pipe character: &amp;quot;Resting|Suspended&amp;quot;. When no timeout is given, this command waits indefinitely. If the wait is successful, i.e. system state matches one of the specified states, WAIT FOR will return a value of &amp;quot;true&amp;quot;. If timeout occurred, WAIT FOR will return &amp;quot;false&amp;quot;.&lt;br /&gt;
&lt;br /&gt;
If the BCI2000 system is shut down while a script is executing a WAIT FOR command, the script will be terminated with a &amp;quot;wait aborted&amp;quot; error message.&lt;br /&gt;
&lt;br /&gt;
In more detail, user visible states are:&lt;br /&gt;
* Idle: System is shut down and does not do other processing than executing scripts.&lt;br /&gt;
* Startup: System has started up and is listening for incoming connections.&lt;br /&gt;
* Connected: All core modules have connected to the system.&lt;br /&gt;
* Resting: The system has been initialized but is not running.&lt;br /&gt;
* Suspended: The system has ended running and is behaving like in Resting.&lt;br /&gt;
* ParamsModified: In Suspended mode, parameter changes have been published and will be applied at Resume.&lt;br /&gt;
* Running: The system is running, processing brain signals, and displaying stimuli.&lt;br /&gt;
* Busy: The system is performing a transition from one state to another.&lt;br /&gt;
&lt;br /&gt;
As a synonym for &amp;quot;Connected,&amp;quot; &amp;quot;Initialization&amp;quot; is valid as well since it is compatible with the nomenclature in StateMachine.h. Still, it should be avoided because it is easily confused with, but very distinct from, the actions performed in the Initialize() phase.&lt;br /&gt;
&lt;br /&gt;
=====SLEEP &amp;amp;lt;time in seconds&amp;amp;gt;=====&lt;br /&gt;
Waits (sleeps) for the given amount of time. Timing resolution is 50ms. Tends to sleep a little longer than specified, with the error growing with duration.&lt;br /&gt;
&lt;br /&gt;
=====GET SYSTEM VERSION=====&lt;br /&gt;
Prints BCI2000 version information.&lt;br /&gt;
=====SETCONFIG, SET CONFIG=====&lt;br /&gt;
Applies current parameters to the system. Corresponds to the &#039;&#039;SetConfig&#039;&#039; button in the GUI version of the Operator module.&lt;br /&gt;
&lt;br /&gt;
=====START=====&lt;br /&gt;
Starts or resumes system operation, corresponding to the &#039;&#039;Start/Resume&#039;&#039; button in the GUI version of the Operator module.&lt;br /&gt;
=====STOP=====&lt;br /&gt;
Stops system operation. Corresponds to the &#039;&#039;Stop&#039;&#039; button in the GUI version of the Operator.&lt;br /&gt;
=====STARTUP SYSTEM=====&lt;br /&gt;
When in idle state, starts up the system to wait for incoming connections from core modules. Additionally, the following arguments may be given: 1) an IP address on which to listen (default is to listen on all addresses), and 2) a list of generic core module names with ports. The default configuration corresponds to these arguments:&lt;br /&gt;
 STARTUP SYSTEM * SignalSource:4000 SignalProcessing:4001 Application:4002&lt;br /&gt;
&lt;br /&gt;
A system log file may optionally be specified on this line, by inserting the &amp;lt;code&amp;gt;--SystemLogFile&amp;lt;/code&amp;gt; flag between the IP address and the module specifiers. For example:&lt;br /&gt;
&lt;br /&gt;
 STARTUP SYSTEM * --SystemLogFile=SOME_FILE.TXT SignalSource:4000 SignalProcessing:4001 Application:4002&lt;br /&gt;
&lt;br /&gt;
The system log file will record all operator log window messages for the current launch, until the system shuts down.  It may be helpful to use the variables &amp;lt;code&amp;gt;$YYYYMMDD&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;$HHMMSS&amp;lt;/code&amp;gt; to specify the filename.  Note that file name and path cannot contain spaces.&lt;br /&gt;
&lt;br /&gt;
=====SHUTDOWN SYSTEM=====&lt;br /&gt;
Shuts down core modules, and enters idle system state.&lt;br /&gt;
=====RESET SYSTEM=====&lt;br /&gt;
Shuts down the system, and clears all parameter, state, and event information.&lt;br /&gt;
&lt;br /&gt;
=====QUIT, EXIT [&amp;lt;result&amp;gt;]=====&lt;br /&gt;
Quits the operator module after terminating all BCI2000 modules. The optional &#039;&#039;result&#039;&#039; argument determines the result of the executed script.&lt;br /&gt;
&lt;br /&gt;
=====CLOSE CONNECTION, TERMINATE CONNECTION=====&lt;br /&gt;
In the context of a telnet or websocket connection, this command will terminate the connection.&lt;br /&gt;
In other contexts, such as event handlers, it will be ignored.&lt;br /&gt;
&lt;br /&gt;
=====SYSTEM &amp;lt;command line&amp;gt;=====&lt;br /&gt;
Executes a shell command, redirecting any console output into the command&#039;s script result. E.g., to obtain a directory listing, under Windows, you would enter&lt;br /&gt;
 SYSTEM DIR&lt;br /&gt;
&#039;&#039;&#039;NOTE:&#039;&#039;&#039; Arguments to the SYSTEM command are executed by the operating system&#039;s shell, and thus may require quoting different from the other scripting commands. E.g., writing&lt;br /&gt;
 SET mydir ${PARENT DIRECTORY $BCI2000LAUNCHDIR}; ECHO ${LIST FILES $mydir}&lt;br /&gt;
will list files in the BCI2000 main directory, independently of whether the path to that directory contains space characters or not. However, to obtain a directory listing through the SYSTEM command, you would need to write&lt;br /&gt;
 ECHO ${SYSTEM DIR &amp;quot;$mydir&amp;quot;}&lt;br /&gt;
to make sure the content of the variable &#039;&#039;mydir&#039;&#039; is interpreted as a single argument, independently of whether it contains space characters.&lt;br /&gt;
&lt;br /&gt;
=====LOG &amp;lt;message&amp;gt;=====&lt;br /&gt;
Append the specified message to the system log.&lt;br /&gt;
=====WARN &amp;lt;message&amp;gt;=====&lt;br /&gt;
Append the specified message to the system log, formatted as a warning.&lt;br /&gt;
=====ERROR &amp;lt;message&amp;gt;=====&lt;br /&gt;
Append the specified message to the system log, formatted as an error message.&lt;br /&gt;
&lt;br /&gt;
=====CAPTURE MESSAGES &amp;lt;message types&amp;gt;=====&lt;br /&gt;
Captures system log messages into a background buffer. When no message type is given, all messages are captured. When &amp;quot;None&amp;quot; is given as a message type, message capturing is disabled. Otherwise, the message type must be one of &amp;quot;Errors&amp;quot;, &amp;quot;Warnings&amp;quot;, &amp;quot;Debug&amp;quot;, &amp;quot;Log&amp;quot;. Multiple message types may be specified in a single command. When &amp;quot;None&amp;quot; appears within a single command, all preceding message types are ignored. Multiple CAPTURE MESSAGES commands are cumulative, except when &amp;quot;None&amp;quot; is specified as a message type.&lt;br /&gt;
&lt;br /&gt;
=====FLUSH MESSAGES=====&lt;br /&gt;
Clears the background message buffer, and returns its previous content. Use CAPTURE MESSAGES to capture messages into the background message buffer.&lt;br /&gt;
&lt;br /&gt;
====Operator-module defined Commands====&lt;br /&gt;
=====HIDE WINDOW [&amp;lt;name&amp;gt;], SHOW WINDOW [&amp;lt;name&amp;gt;]=====&lt;br /&gt;
Hides or shows the specified window. When called without a window name, the Operator module&#039;s main window is hidden or shown. The window name may be one of Main, Configuration, Log, Watches, and Visualizations. When &amp;quot;Watches&amp;quot; is given, the window state refers to visibility of the Watches area in the Visualizations window.&lt;br /&gt;
&lt;br /&gt;
=====MOVE WINDOW &amp;lt;name&amp;gt; &amp;lt;x&amp;gt; &amp;lt;y&amp;gt;, RESIZE WINDOW &amp;lt;name&amp;gt; &amp;lt;width&amp;gt; &amp;lt;height&amp;gt;=====&lt;br /&gt;
Moves resp. resizes the specified window with one of the above names.&lt;br /&gt;
Window decorations (title bar, frame) are taken into consideration such that the total size of the window matches the given width and height.&lt;br /&gt;
&lt;br /&gt;
=====ARRANGE WINDOW &amp;lt;name&amp;gt; &amp;lt;rows&amp;gt; &amp;lt;cols&amp;gt; &amp;lt;row&amp;gt; &amp;lt;col&amp;gt; [&amp;lt;rowspan&amp;gt; &amp;lt;colspan&amp;gt;]=====&lt;br /&gt;
Arranges the named window in a virtual &#039;&#039;rows&#039;&#039; x &#039;&#039;cols&#039;&#039; grid at grid position &#039;&#039;row, col&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Optional &#039;&#039;rowspan&#039;&#039; and &#039;&#039;colspan&#039;&#039; arguments may be given to specify the extension of the window across more than one row or column. &lt;br /&gt;
Using a &#039;&#039;rowspan&#039;&#039; of 0 will result in the window assuming its minimum height.&lt;br /&gt;
Likewise, a &#039;&#039;colspan&#039;&#039; of 0 will result in the window&#039;s minimum width.&lt;br /&gt;
&lt;br /&gt;
If multiple screens are present, windows are arranged on the screen that contains the Operator window.&lt;br /&gt;
&lt;br /&gt;
=====SET TITLE &amp;lt;title&amp;gt;=====&lt;br /&gt;
Sets the title of the main Operator window.&lt;br /&gt;
=====SET BUTTON &amp;lt;idx&amp;gt; &amp;lt;label&amp;gt; &amp;lt;commands&amp;gt;=====&lt;br /&gt;
Configures the function button with 1-based index &#039;&#039;idx&#039;&#039; such that it is labelled &#039;&#039;label&#039;&#039; and executes &#039;&#039;commands&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
=====VISUALIZE WATCH [decimate &amp;lt;n&amp;gt;] [range &amp;lt;min&amp;gt; &amp;lt;max&amp;gt;] &amp;lt;expression1&amp;gt; ...=====&lt;br /&gt;
Adds a watch for the given expressions to the operator module&#039;s Watches window, and makes the Watches window visible if it is hidden. If the &#039;&#039;decimate&#039;&#039; clause is present, the command will create a watch which will be evaluated only for every &#039;&#039;n&#039;&#039;th sample in the state vector. If the &#039;&#039;range&#039;&#039; clause is present, display minimum and maximum will not be adjusted automatically but will be fixed to the values given.&lt;br /&gt;
&lt;br /&gt;
By default, &#039;&#039;decimate&#039;&#039; has a value of &amp;quot;auto&amp;quot;; this means that there is no decimation of evaluation (i.e., all samples are evaluated) but changes that occur within 1ms will be shortened into their maximum and minimum, and thus reported as only two values.&lt;br /&gt;
This has proven to be efficient for avoiding sluggishness from flooding the application with events.&lt;br /&gt;
&lt;br /&gt;
=====MOVE VISUALIZATON &amp;lt;visID&amp;gt; &amp;lt;x&amp;gt; &amp;lt;y&amp;gt;, RESIZE VISUALIZATION &amp;lt;visID&amp;gt; &amp;lt;width&amp;gt; &amp;lt;height&amp;gt;=====&lt;br /&gt;
Moves resp. resizes the specified visualization window.&lt;br /&gt;
Window decorations (title bar, frame) are taken into consideration such that the total size of the window matches the given width and height. If unknown, visualization IDs may be obtained from the Operator&#039;s &#039;&#039;Window-&amp;gt;Visualizations&#039;&#039; menu.&lt;br /&gt;
&lt;br /&gt;
=====ARRANGE VISUALIZATION &amp;lt;visID&amp;gt; &amp;lt;rows&amp;gt; &amp;lt;cols&amp;gt; &amp;lt;row&amp;gt; &amp;lt;col&amp;gt; [&amp;lt;rowspan&amp;gt; &amp;lt;colspan&amp;gt;]=====&lt;br /&gt;
Arranges the named visualization window in a virtual &#039;&#039;rows&#039;&#039; x &#039;&#039;cols&#039;&#039; grid at grid position &#039;&#039;row, col&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
Optional &#039;&#039;rowspan&#039;&#039; and &#039;&#039;colspan&#039;&#039; arguments may be given to specify the extension of the window across more than one row or column. &lt;br /&gt;
Using a &#039;&#039;rowspan&#039;&#039; of 0 will result in the window assuming its minimum height.&lt;br /&gt;
Likewise, a &#039;&#039;colspan&#039;&#039; of 0 will result in the window&#039;s minimum width.&lt;br /&gt;
&lt;br /&gt;
If multiple screens are present, windows are arranged on the screen that contains the Operator window.&lt;br /&gt;
&lt;br /&gt;
=====RECORD VISUALIZATION &amp;lt;visID&amp;gt; [on|off]=====&lt;br /&gt;
Registers the named visualization for recording. The visualization name must be the short name as displayed in the &#039;&#039;View-&amp;gt;Visualizations&#039;&#039; menu. Switching a visualization recording to &amp;quot;on&amp;quot; is only possible in idle state (because an event for recording frame numbers must be registered in the system). When a visualization is registered for recording, its frames are stored immediately as they arrive at the operator module. Currently, only bitmap visualizations may be recorded.&lt;br /&gt;
&lt;br /&gt;
Example: Recording the application window&lt;br /&gt;
 RECORD VISUALIZATION ApplicationWindow on&lt;br /&gt;
 ...&lt;br /&gt;
 # after parameters have been loaded&lt;br /&gt;
 Set parameter VisualizeApplicationWindow 1&lt;br /&gt;
 ...&lt;br /&gt;
&lt;br /&gt;
=====PUT NOTE &amp;lt;note&amp;gt;=====&lt;br /&gt;
Adds the given text to the [[User_Reference:Operator_Notes#Taking_Notes_during_recording_in_Operator|notes window]].&lt;br /&gt;
If the system is in &#039;&#039;Running&#039;&#039; state, the text will also be added to the current notes file as described under&lt;br /&gt;
[[User_Reference:Operator_Notes#Note_storage_and_file_format|&amp;quot;notes file&amp;quot;]].&lt;br /&gt;
&lt;br /&gt;
=====START TELNET [&amp;lt;address&amp;gt;], STOP TELNET [&amp;lt;address&amp;gt;]=====&lt;br /&gt;
Starts or stops a telnet server listening at the given address. The address is in &amp;lt;IP&amp;gt;:&amp;lt;port&amp;gt; format. The default address is 127.0.0.1:3999.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;IMPORTANT:&#039;&#039;&#039; You should not use this command on global internet addresses, as there is no authentication performed. Only use addresses in the local network, such as 10.x.x.x, 172.x.x.x, or 192.168.x.x.&lt;br /&gt;
&lt;br /&gt;
=====START WEBSOCKET &amp;lt;address&amp;gt;, STOP WEBSOCKET &amp;lt;address&amp;gt;=====&lt;br /&gt;
Starts or stops a websocket server listening at the given address. The address is in &amp;lt;IP&amp;gt;:&amp;lt;port&amp;gt; format.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;IMPORTANT:&#039;&#039;&#039; You should not use this command on global internet addresses, as there is no authentication performed. Only use addresses in the local network, such as 10.x.x.x, 172.x.x.x, or 192.168.x.x.&lt;br /&gt;
&lt;br /&gt;
====Operator-module Commands for Soliciting Input from the User====&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=====CHOOSE [INPUT] FILE[S] [OF TYPE &amp;lt;.ext1&amp;gt; [&amp;lt;.ext2&amp;gt; ...]] [STARTING AT &amp;lt;dir&amp;gt;] [WITH PROMPT &amp;lt;message&amp;gt;]=====&lt;br /&gt;
Via the Qt graphical user interface, prompt the user to select an existing file (or multiple files, if the keyword FILES is used). Print the resulting file path(s).&lt;br /&gt;
(See the [[User_Reference:Custom_GUI_Commands|Custom GUI Commands]] page for details and examples.)&lt;br /&gt;
&lt;br /&gt;
=====CHOOSE [OUTPUT] FILE [OF TYPE &amp;lt;.ext1&amp;gt; [&amp;lt;.ext2&amp;gt; ...]] [STARTING AT &amp;lt;dir&amp;gt;] [WITH PROMPT &amp;lt;message&amp;gt;]=====&lt;br /&gt;
Via the Qt graphical user interface, prompt the user to specify a file into which data can be saved (with an overwrite confirmation dialog if it already exists). Print the resulting file path.&lt;br /&gt;
(See the [[User_Reference:Custom_GUI_Commands|Custom GUI Commands]] page for details and examples.)&lt;br /&gt;
&lt;br /&gt;
=====CHOOSE DIRECTORY [STARTING AT &amp;lt;dir&amp;gt;] [WITH PROMPT &amp;lt;message&amp;gt;]=====&lt;br /&gt;
Via the Qt graphical user interface, prompt the user to specify a directory. Print the resulting directory path.&lt;br /&gt;
(See the [[User_Reference:Custom_GUI_Commands|Custom GUI Commands]] page for details and examples.)&lt;br /&gt;
&lt;br /&gt;
=====CUSTOM DIALOG [MESSAGE &amp;lt;msg&amp;gt;] [VAR &amp;lt;varname&amp;gt; &amp;lt;label&amp;gt; {[&amp;lt;options&amp;gt;]} ] [BUTTONS {&amp;lt;buttons&amp;gt;}] ... =====&lt;br /&gt;
Via the Qt graphical user interface, prompt the user with a modal dialog that can be flexibly customized.&lt;br /&gt;
(See the [[User_Reference:Custom_GUI_Commands|Custom GUI Commands]] page for details and examples.)&lt;br /&gt;
&lt;br /&gt;
===Predefined Variables===&lt;br /&gt;
The following variables exist when an Operator script is executed.&lt;br /&gt;
Some of these variables are marked with &#039;&#039;local&#039;&#039;. This means that they are not environment variables, i.e. they are invisible to child processes that are launched using the SYSTEM or START EXECUTABLE commands, and their values may be different between script invocations. In script code, they are accessed like ordinary variables.&lt;br /&gt;
====BCI2000LAUNCHDIR====&lt;br /&gt;
The full absolute path to the directory where the Operator module resides. This is also prepended to the PATH environment variable, such that executables from the current BCI2000 installation will have precedence over any other executable with the same name.&lt;br /&gt;
&lt;br /&gt;
On macOS, this is the path where the Operator module&#039;s application bundle resides. In the default configuration, this is the BCI2000 prog directory, both on macOS and on other platforms.&lt;br /&gt;
&lt;br /&gt;
====BCI2000BINARY====&lt;br /&gt;
The full absolute path to the Operator module.&lt;br /&gt;
====LogLevel (local)====&lt;br /&gt;
Determines the amount of log information written to the Operator log. This variable only affects log messages originating from the current script. May be 2 (display all log messages), 1 (display fewer log messages), or 0 (suppress all log messages). Set to 1 by default. Changes to this local variable are not propagated to any sub-scripts called. &#039;&#039;NOTE:&#039;&#039; Only log messages are controlled by this variable. Error messages originating from a script with &#039;LogLevel 0&#039; will still be displayed in the Operator log. When &#039;AbortOnError&#039; is set to 0, you may set &#039;LogLevel&#039; to -1 in order to display error messages. When &#039;AbortOnError&#039; is 1 (the default), error messages will always be displayed to indicate the reason for failure.&lt;br /&gt;
&lt;br /&gt;
====AbortOnError (local)====&lt;br /&gt;
Determines if a script is aborted when an error happens, or whether the error is silently ignored. Set to 1 by default (script is aborted on error). Changes to this local variable are not propagated to any sub-scripts called.&lt;br /&gt;
&lt;br /&gt;
====Result (local)====&lt;br /&gt;
The result of the last executed scripting command. When a script is executed by calling EXECUTE SCRIPT, the script&#039;s last executed command determines the result of the EXECUTE SCRIPT command itself.&lt;br /&gt;
&lt;br /&gt;
====0, 1, ... 9 (local)====&lt;br /&gt;
When a script file is being executed, these variables contain the arguments of the EXECUTE SCRIPT command.&lt;br /&gt;
&#039;&#039;$0&#039;&#039; resolves to the full absolute path to the current script file. Within scripts, all of the &#039;&#039;0-9&#039;&#039; variables are defined, and those that do not have a matching argument are empty.&lt;br /&gt;
&lt;br /&gt;
====YYYYMMDD (local)====&lt;br /&gt;
Local time at execution of the current script, in YYYYMMDD format. In interactive sessions, reflects the time when the session was initiated.&lt;br /&gt;
&lt;br /&gt;
====HHMMSS (local)====&lt;br /&gt;
Local time at execution of the current script, in HHMMSS format. In interactive sessions, reflects the time when the session was initiated.&lt;br /&gt;
&lt;br /&gt;
===Abbreviated commands and synonyms===&lt;br /&gt;
To minimize the need of consulting documentation, as well as for backward compatibility, a number of &#039;&#039;&#039;synonymous commands&#039;&#039;&#039; are provided. E.g., states may be added by INSERT STATE as well as ADD STATE, and the existence of a file may be queried by IS FILE as well as EXISTS FILE. For an overview over all allowed forms of commands, use the HELP ALL command.&lt;br /&gt;
&lt;br /&gt;
To simplify operation in interactive sessions, &#039;&#039;&#039;abbreviated commands&#039;&#039;&#039; exist. Currently, these are:&lt;br /&gt;
:&#039;&#039;&#039;cd&#039;&#039;&#039; for CHANGE DIRECTORY,&lt;br /&gt;
:&#039;&#039;&#039;pwd&#039;&#039;&#039; and &#039;&#039;&#039;cd&#039;&#039;&#039; without argument for CURRENT DIRECTORY,&lt;br /&gt;
:&#039;&#039;&#039;ls&#039;&#039;&#039; and &#039;&#039;&#039;dir&#039;&#039;&#039; for LIST DIRECTORY,&lt;br /&gt;
:&#039;&#039;&#039;mkdir&#039;&#039;&#039; for MAKE DIRECTORY,&lt;br /&gt;
:&#039;&#039;&#039;echo&#039;&#039;&#039; for WRITE LINE,&lt;br /&gt;
:&#039;&#039;&#039;realpath&#039;&#039;&#039; for REAL PATH,&lt;br /&gt;
:&#039;&#039;&#039;dirname&#039;&#039;&#039; for EXTRACT DIRECTORY,&lt;br /&gt;
:&#039;&#039;&#039;basename&#039;&#039;&#039; for EXTRACT FILE BASE.&lt;br /&gt;
&lt;br /&gt;
===Handlers===&lt;br /&gt;
In the Operator GUI, script execution is bound to a number of Operator Events (not to be confused with Event states, above) that occur during various [[Technical Reference:States of Operation|stages of BCI2000 system operation]]:&lt;br /&gt;
====OnConnect====&lt;br /&gt;
This handler runs at startup, as soon as all modules are connected to the operator module.&lt;br /&gt;
====OnSetConfig====&lt;br /&gt;
This handler runs each time a set of parameters is applied to the system. This happens when the user clicks the &#039;&#039;SetConfig&#039;&#039; button. Execution of the &#039;&#039;SETCONFIG&#039;&#039; command also runs this handler.&lt;br /&gt;
&lt;br /&gt;
====OnStart, OnResume====&lt;br /&gt;
These handlers are triggered by the &#039;&#039;Start&#039;&#039;/&#039;&#039;Resume&#039;&#039; button. One of these handlers is also triggered when the &#039;&#039;Running&#039;&#039; state variable is set to 1 from a script. Whether &#039;&#039;OnStart&#039;&#039; or &#039;&#039;OnResume&#039;&#039; is triggered depends on whether the system has been running before with the current set of parameters.&lt;br /&gt;
&lt;br /&gt;
====OnStartRun====&lt;br /&gt;
Similarly to OnStart and OnResume, this handler is triggered by the &#039;&#039;Start&#039;&#039;/&#039;&#039;Resume&#039;&#039; button. Unlike other event handlers, OnStartRun has an argument, which is the current run file.&lt;br /&gt;
&lt;br /&gt;
OnStart or OnResume are triggered immediately after &#039;&#039;Start&#039;&#039;/&#039;&#039;Resume&#039;&#039; has been pressed, whereas OnStartRun is deferred until modules have confirmed to be in Running state. This makes sure that the OnStartRun event handler receives a valid run file name.&lt;br /&gt;
&lt;br /&gt;
====OnNextFilePart====&lt;br /&gt;
This handler is triggered during a run, whenever the &#039;&#039;FilePart&#039;&#039; event state is incremented. It allows scripts to know when a new&lt;br /&gt;
partial file has begun. For further information, see the [[User_Reference:BCI2000FileWriter#FileSplittingCondition|FileSplittingCondition]] parameter.&lt;br /&gt;
&lt;br /&gt;
====OnSuspend/OnStopRun====&lt;br /&gt;
Triggered when the system goes from running into suspended mode. This happens whenever the &#039;&#039;Running&#039;&#039; state variable changes from 1 to 0. This may happen when the user clicks &#039;&#039;Suspend&#039;&#039;, when the application module switches the system into suspended mode, or when a script sets the &#039;&#039;Running&#039;&#039; state variable to 0.&lt;br /&gt;
&lt;br /&gt;
====OnShutdown====&lt;br /&gt;
Triggered when the operator module shuts down connections, and switches into idle state.&lt;br /&gt;
&lt;br /&gt;
====OnExit====&lt;br /&gt;
Triggered when the operator module exits. Execution of the QUIT command also triggers this handler. This handler is not available to the SET SCRIPT and CLEAR SCRIPT commands. Also, when both an OnShutdown and an OnExit script are defined, the OnExit script may be executed before the OnShutdown script.&lt;br /&gt;
&lt;br /&gt;
====Associating Scripts with Operator Events====&lt;br /&gt;
In the operator module&#039;s preferences dialog, script commands may be entered for each of the handlers listed above.&lt;br /&gt;
Scripts may be specified as paths to script files, or as immediate one-line scripts.&lt;br /&gt;
Entries that start with a minus sign (-) are treated as one-line scripts, which may contain multiple commands separated with semicolons.&lt;br /&gt;
&lt;br /&gt;
Scripts may also be specified from the command line used to start up the operator module. There, handler names are followed with the content of the respective preference entry, enclosed in double quotes (&amp;quot;...&amp;quot;).&lt;br /&gt;
&lt;br /&gt;
Finally, scripts may be specified using the SET SCRIPT command of the scripting language itself.&lt;br /&gt;
&lt;br /&gt;
===Examples===&lt;br /&gt;
====Making use of the Operator module&#039;s Function Buttons====&lt;br /&gt;
To add a state variable called &amp;quot;Artifact&amp;quot;, and to set it using the operator&#039;s function buttons, do this:&lt;br /&gt;
*Enter the following line under &amp;quot;After All Modules Connected&amp;quot; in the operator&#039;s preferences dialog (note the minus sign):&lt;br /&gt;
 -ADD STATE Artifact 1 0&lt;br /&gt;
*Under &amp;quot;Function Buttons&amp;quot;, enter &amp;quot;Set Artifact&amp;quot; as the name of button 1, and as its command, enter (note there is no minus sign):&lt;br /&gt;
 SET STATE Artifact 1&lt;br /&gt;
*Enter &amp;quot;Clear Artifact&amp;quot; as the name of button 2, and as its command, enter&lt;br /&gt;
 SET STATE Artifact 0&lt;br /&gt;
&lt;br /&gt;
====A fully automated BCI2000 session====&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Echo Please enter a subject ID:&lt;br /&gt;
Set SubjectID ${Read line}&lt;br /&gt;
&lt;br /&gt;
Startup system&lt;br /&gt;
Start executable SignalGenerator&lt;br /&gt;
Start executable SpectralSignalProcessing&lt;br /&gt;
Start executable CursorTask&lt;br /&gt;
Wait for Connected&lt;br /&gt;
Load parameterfile &amp;quot;../parms/examples/CursorTask_SignalGenerator.prm&amp;quot;&lt;br /&gt;
For i in 1 2 3&lt;br /&gt;
  Load parameterfile &amp;quot;../parms/MyExperiment/Session$i.prm&amp;quot;&lt;br /&gt;
  Set parameter SubjectName $SubjectID&lt;br /&gt;
  Set parameter SubjectSession $i&lt;br /&gt;
  Set config&lt;br /&gt;
  Wait for Resting&lt;br /&gt;
  Start&lt;br /&gt;
  Wait for Suspended 1000&lt;br /&gt;
End&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====Automating BCI2000 by Operator command line arguments====&lt;br /&gt;
The following example shows how to specify script commands from the command line.&lt;br /&gt;
It fully automates BCI2000 operation by loading a parameter file, applying parameters, starting the system once the parameters are applied, and quitting the system once the run is over. For better readability, the example is broken across lines, using the ^ DOS line continuation character.&lt;br /&gt;
&lt;br /&gt;
 operator.exe --OnConnect &amp;quot;-LOAD PARAMETERFILE ../parms/examples/CursorTask_SignalGenerator.prm; SETCONFIG&amp;quot; ^&lt;br /&gt;
              --OnSetConfig &amp;quot;-SET STATE Running 1&amp;quot;  ^&lt;br /&gt;
              --OnSuspend &amp;quot;-QUIT&amp;quot;&lt;br /&gt;
&lt;br /&gt;
====Automatically arranging windows in a 4x2 grid====&lt;br /&gt;
The following example arranges Source and Roundtrip visualizations on the left side of the screen.&lt;br /&gt;
On the right side, a large area is dedicated to the Operator&#039;s log window, and the Operator&#039;s main window sits at the bottom right.&lt;br /&gt;
&lt;br /&gt;
 Arrange Visualization SRCD 4 2 1 1 2 1&lt;br /&gt;
 Arrange Visualization RNDT 4 2 3 1&lt;br /&gt;
 Arrange Window Main 4 2 4 2&lt;br /&gt;
 Arrange Window Log  4 2 1 2 3 1&lt;br /&gt;
&lt;br /&gt;
[[File:Operator_Arrangement.PNG|center|640px|border]]&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[User Reference:Module Command Line Options]], [[User Reference:Operator Module]], [[User Reference:BCI2000Shell]], [[Technical Reference:States of Operation]]&lt;br /&gt;
&lt;br /&gt;
[[Category:User Interface]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=File:Event_vs_sample_time_gUSBamp.png&amp;diff=12561</id>
		<title>File:Event vs sample time gUSBamp.png</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=File:Event_vs_sample_time_gUSBamp.png&amp;diff=12561"/>
		<updated>2026-07-30T12:41:43Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: Mellinger uploaded a new version of File:Event vs sample time gUSBamp.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12560</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12560"/>
		<updated>2026-07-30T12:35:48Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Data recorded with g.USBamp source module */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so-called [[Programming_Reference:Events|Events]], that occur during a recording. These events are logged into [[Technical Reference:State Definition|BCI2000 states]], which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, a software component which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference systems.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we may say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first, even if they may not have arrived in temporal order (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events happen while a data block is being digitized by the amplifier&#039;s hardware, and may then be associated with the samples of that block once it arrives at the computer.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are taken from the event queue in temporal order, and applied to the state variables associated with the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay -- acquisition delay is the time delay between digitizing a block’s last sample, and seeing that block in the computer’s memory. &#039;&#039;Regularity&#039;&#039; of data blocks may be assessed using the BCI2000 [[User Reference:Timing|timing window]]; there, data block duration should form a straight line. Acquisition &#039;&#039;delay&#039;&#039;, in contrast, is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input, producing an input signal whenever a new block has entered the computer&#039;s memory.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well these two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz --local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing &#039;&#039;RefLogger&#039;&#039; events at a frequency higher than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event logging software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, using the first time stamp as an offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate regular arrival of sample blocks.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Similarly to the SignalGenerator example above, the Event mechanism is working close to perfectly with the g.UBSamp amplifier, with a jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time for data recorded with the g.USBamp amplifer, we have a nearly perfect distribution of differences, with an event timing jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
Note that time stamps are rounded to full milliseconds, so an event timing jitter in the order of half a millisecond is to be expected.&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
The following script has been used to produce the above graphs.&lt;br /&gt;
Modify the filename variable to hold the name of the BCI2000 data file to analyze.&lt;br /&gt;
&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 &lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 &lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 &lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 &lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 &lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 &lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 &lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 &lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 &lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 &lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Programming_Reference:Events]], [[Technical Reference:State Definition]], [[Programming Tutorial:Implementing an Input Logger]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Data Acquisition]] [[Category:Framework API]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=Contributions:SecondaryBCI2000Controller&amp;diff=12559</id>
		<title>Contributions:SecondaryBCI2000Controller</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=Contributions:SecondaryBCI2000Controller&amp;diff=12559"/>
		<updated>2026-07-30T12:32:12Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Source Code Revisions */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
An extension that controls one or more secondary BCI2000 instances from the GUI of a primary one.&lt;br /&gt;
&lt;br /&gt;
==Location==&lt;br /&gt;
http://{{SERVERNAME}}/svn/trunk/src/contrib/Extensions/SecondaryBCI2000Controller&lt;br /&gt;
&lt;br /&gt;
==Versioning==&lt;br /&gt;
===Author===&lt;br /&gt;
*Jürgen Mellinger (mellinger@neurotechcenter.org) &lt;br /&gt;
===Version History===&lt;br /&gt;
&lt;br /&gt;
===Source Code Revisions===&lt;br /&gt;
*Initial development: 6819&lt;br /&gt;
*Tested under: 9500&lt;br /&gt;
*Known to compile under: 9500&lt;br /&gt;
&lt;br /&gt;
===Known Issues===&lt;br /&gt;
*Log windows of primary and secondary BCI2000 instances will initially have the same location, hiding each other and possibly leading to confusion. The same applies to operator main windows if secondary instances are not hidden by calling &amp;quot;Hide Window&amp;quot; from the secondary batch file(s).&lt;br /&gt;
&lt;br /&gt;
==Functional Description==&lt;br /&gt;
The extension will start up one or more secondary instances of BCI2000, and synchronize data file storage and operation state between the instances. This is useful to record from two or more different data sources at the same time, when online processing of secondary data sources is not required. In later analysis, data from the multiple sources may be aligned in time using BCI2000 time stamps found in the data files.&lt;br /&gt;
&lt;br /&gt;
==Integration into BCI2000==&lt;br /&gt;
Compile the extension into your source module by enabling contributed extensions in your CMake configuration. In your root build folder, run &amp;lt;tt&amp;gt;Configure.sh.cmd&amp;lt;/tt&amp;gt; in order to bring up the CMake GUI; there, enable EXTENSIONS_SECONDARY_BCI2000_CONTROLLER.&lt;br /&gt;
&lt;br /&gt;
Once the extension is built into the source module, enable it by starting the source module with the &amp;lt;code&amp;gt;--SecondaryBCI2000=1&amp;lt;/code&amp;gt; command line argument.&lt;br /&gt;
&lt;br /&gt;
==Usage==&lt;br /&gt;
When the SecondaryBCI2000Controller extension has been enabled in the system at compile time, you will find a &amp;lt;tt&amp;gt;BCI2000TwiceStartup.bat&amp;lt;/tt&amp;gt; batch file in your BCI2000 batch folder. Double-click it to start BCI2000.&lt;br /&gt;
&lt;br /&gt;
Startup configuration of secondary BCI2000 instances is done in the files &amp;lt;tt&amp;gt;BCI2000SecondaryStartup.bat&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;BCI2000Secondary2Startup.bat&amp;lt;/tt&amp;gt;, etc.&lt;br /&gt;
You may load identical or different configuration parameter files in the startup batch files.&lt;br /&gt;
&lt;br /&gt;
Data file location is synchronized between BCI2000 instances. Secondary instances use identical file names to the first one, with an appended &amp;lt;tt&amp;gt;_1&amp;lt;/tt&amp;gt;, &amp;lt;tt&amp;gt;_2&amp;lt;/tt&amp;gt;, ... before the file name extension.&lt;br /&gt;
&lt;br /&gt;
==Parameters==&lt;br /&gt;
The SecondaryBCI2000Controller extension is configured in the &#039;&#039;Source&#039;&#039; tab within the &amp;quot;Secondary BCI2000&amp;quot; section.  The configurable parameters are:&lt;br /&gt;
&lt;br /&gt;
===SecondaryBCI2000===&lt;br /&gt;
Enables/Disables the SecondaryBCI2000Controller.&lt;br /&gt;
&lt;br /&gt;
===SecondaryBCI2000Instances===&lt;br /&gt;
A matrix with one row per secondary instance.&lt;br /&gt;
&lt;br /&gt;
The first column contains the telnet port that is used to control the secondary instance of BCI2000.&lt;br /&gt;
&lt;br /&gt;
The second column contains the the script that is used to start up and configure the secondary instance of BCI2000. An example script is available as &amp;lt;tt&amp;gt;BCI2000SecondaryStartup.bat&amp;lt;/tt&amp;gt; in your BCI2000 batch directory, and is called by the &amp;lt;tt&amp;gt;BCI2000TwiceStartup.bat&amp;lt;/tt&amp;gt; batch file.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
To tell the second instance that it should refer to the first one for file names, there is a command line parameter:&lt;br /&gt;
&lt;br /&gt;
===IsSecondaryBCI2000===&lt;br /&gt;
This parameter must be set to 1 in the secondary instance&#039;s batch file: &amp;lt;tt&amp;gt;--IsSecondaryBCI2000=1&amp;lt;/tt&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Contributions:Extensions]], [[User Reference:Operator Module Scripting]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Contributions]][[Category:Extension]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12558</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12558"/>
		<updated>2026-07-29T15:40:40Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Synopsis */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so-called [[Programming_Reference:Events|Events]], that occur during a recording. These events are logged into [[Technical Reference:State Definition|BCI2000 states]], which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, a software component which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference systems.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we may say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first, even if they may not have arrived in temporal order (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events happen while a data block is being digitized by the amplifier&#039;s hardware, and may then be associated with the samples of that block once it arrives at the computer.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are taken from the event queue in temporal order, and applied to the state variables associated with the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay -- acquisition delay is the time delay between digitizing a block’s last sample, and seeing that block in the computer’s memory. &#039;&#039;Regularity&#039;&#039; of data blocks may be assessed using the BCI2000 [[User Reference:Timing|timing window]]; there, data block duration should form a straight line. Acquisition &#039;&#039;delay&#039;&#039;, in contrast, is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input, producing an input signal whenever a new block has entered the computer&#039;s memory.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well these two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz --local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing &#039;&#039;RefLogger&#039;&#039; events at a frequency higher than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event logging software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, using the first time stamp as an offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate regular arrival of sample blocks.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Similarly to the SignalSource example above, the Event mechanism is working close to perfectly, with a jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time for data recorded with the g.USBamp amplifer, we have a nearly perfect distribution of differences, with an event timing jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
Note that time stamps are rounded to full milliseconds, so an event timing jitter in the order of half a millisecond is to be expected.&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
The following script has been used to produce the above graphs.&lt;br /&gt;
Modify the filename variable to hold the name of the BCI2000 data file to analyze.&lt;br /&gt;
&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 &lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 &lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 &lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 &lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 &lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 &lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 &lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 &lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 &lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 &lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Programming_Reference:Events]], [[Technical Reference:State Definition]], [[Programming Tutorial:Implementing an Input Logger]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Data Acquisition]] [[Category:Framework API]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12557</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12557"/>
		<updated>2026-07-29T15:35:20Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Matlab Analysis Script */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called [[Programming_Reference:Events|Events]], that occur during a recording. These events are logged into [[Technical Reference:State Definition|BCI2000 states]], which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, a software component which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference systems.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we may say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first, even if they may not have arrived in temporal order (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events happen while a data block is being digitized by the amplifier&#039;s hardware, and may then be associated with the samples of that block once it arrives at the computer.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are taken from the event queue in temporal order, and applied to the state variables associated with the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay -- acquisition delay is the time delay between digitizing a block’s last sample, and seeing that block in the computer’s memory. &#039;&#039;Regularity&#039;&#039; of data blocks may be assessed using the BCI2000 [[User Reference:Timing|timing window]]; there, data block duration should form a straight line. Acquisition &#039;&#039;delay&#039;&#039;, in contrast, is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input, producing an input signal whenever a new block has entered the computer&#039;s memory.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well these two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz --local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing &#039;&#039;RefLogger&#039;&#039; events at a frequency higher than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event logging software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, using the first time stamp as an offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate regular arrival of sample blocks.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Similarly to the SignalSource example above, the Event mechanism is working close to perfectly, with a jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time for data recorded with the g.USBamp amplifer, we have a nearly perfect distribution of differences, with an event timing jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
Note that time stamps are rounded to full milliseconds, so an event timing jitter in the order of half a millisecond is to be expected.&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
The following script has been used to produce the above graphs.&lt;br /&gt;
Modify the filename variable to hold the name of the BCI2000 data file to analyze.&lt;br /&gt;
&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 &lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 &lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 &lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 &lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 &lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 &lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 &lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 &lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 &lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 &lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 &lt;br /&gt;
 &lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 &lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Programming_Reference:Events]], [[Technical Reference:State Definition]], [[Programming Tutorial:Implementing an Input Logger]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Data Acquisition]] [[Category:Framework API]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12556</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12556"/>
		<updated>2026-07-29T15:33:37Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Data recorded with g.USBamp source module */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called [[Programming_Reference:Events|Events]], that occur during a recording. These events are logged into [[Technical Reference:State Definition|BCI2000 states]], which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, a software component which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference systems.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we may say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first, even if they may not have arrived in temporal order (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events happen while a data block is being digitized by the amplifier&#039;s hardware, and may then be associated with the samples of that block once it arrives at the computer.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are taken from the event queue in temporal order, and applied to the state variables associated with the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay -- acquisition delay is the time delay between digitizing a block’s last sample, and seeing that block in the computer’s memory. &#039;&#039;Regularity&#039;&#039; of data blocks may be assessed using the BCI2000 [[User Reference:Timing|timing window]]; there, data block duration should form a straight line. Acquisition &#039;&#039;delay&#039;&#039;, in contrast, is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input, producing an input signal whenever a new block has entered the computer&#039;s memory.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well these two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz --local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing &#039;&#039;RefLogger&#039;&#039; events at a frequency higher than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event logging software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, using the first time stamp as an offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate regular arrival of sample blocks.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Similarly to the SignalSource example above, the Event mechanism is working close to perfectly, with a jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time for data recorded with the g.USBamp amplifer, we have a nearly perfect distribution of differences, with an event timing jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
Note that time stamps are rounded to full milliseconds, so an event timing jitter in the order of half a millisecond is to be expected.&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
The following script has been used to produce the above graphs.&lt;br /&gt;
Modify the filename variable to hold the name of the BCI2000 data file to analyze.&lt;br /&gt;
&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Programming_Reference:Events]], [[Technical Reference:State Definition]], [[Programming Tutorial:Implementing an Input Logger]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Data Acquisition]] [[Category:Framework API]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12555</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12555"/>
		<updated>2026-07-29T15:30:33Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Data recorded with g.USBamp source module */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called [[Programming_Reference:Events|Events]], that occur during a recording. These events are logged into [[Technical Reference:State Definition|BCI2000 states]], which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, a software component which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference systems.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we may say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first, even if they may not have arrived in temporal order (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events happen while a data block is being digitized by the amplifier&#039;s hardware, and may then be associated with the samples of that block once it arrives at the computer.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are taken from the event queue in temporal order, and applied to the state variables associated with the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay -- acquisition delay is the time delay between digitizing a block’s last sample, and seeing that block in the computer’s memory. &#039;&#039;Regularity&#039;&#039; of data blocks may be assessed using the BCI2000 [[User Reference:Timing|timing window]]; there, data block duration should form a straight line. Acquisition &#039;&#039;delay&#039;&#039;, in contrast, is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input, producing an input signal whenever a new block has entered the computer&#039;s memory.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well these two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz --local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing &#039;&#039;RefLogger&#039;&#039; events at a frequency higher than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event logging software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, using the first time stamp as an offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate regular arrival of sample blocks.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Similarly to the SignalSource example above, the Event mechanism is working close to perfectly, with a jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time for data recorded with the g.USBamp amplifer, we have a nearly perfect distribution of differences, with an event timing jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
The following script has been used to produce the above graphs.&lt;br /&gt;
Modify the filename variable to hold the name of the BCI2000 data file to analyze.&lt;br /&gt;
&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Programming_Reference:Events]], [[Technical Reference:State Definition]], [[Programming Tutorial:Implementing an Input Logger]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Data Acquisition]] [[Category:Framework API]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12554</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12554"/>
		<updated>2026-07-29T15:28:56Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Data recorded with SignalGenerator source module */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called [[Programming_Reference:Events|Events]], that occur during a recording. These events are logged into [[Technical Reference:State Definition|BCI2000 states]], which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, a software component which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference systems.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we may say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first, even if they may not have arrived in temporal order (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events happen while a data block is being digitized by the amplifier&#039;s hardware, and may then be associated with the samples of that block once it arrives at the computer.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are taken from the event queue in temporal order, and applied to the state variables associated with the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay -- acquisition delay is the time delay between digitizing a block’s last sample, and seeing that block in the computer’s memory. &#039;&#039;Regularity&#039;&#039; of data blocks may be assessed using the BCI2000 [[User Reference:Timing|timing window]]; there, data block duration should form a straight line. Acquisition &#039;&#039;delay&#039;&#039;, in contrast, is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input, producing an input signal whenever a new block has entered the computer&#039;s memory.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well these two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz --local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing &#039;&#039;RefLogger&#039;&#039; events at a frequency higher than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event logging software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, using the first time stamp as an offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate regular arrival of sample blocks.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Similarly to the SignalSource example above, the Event mechanism is working close to perfectly, with a jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time, we have a nearly perfect distribution of differences with the g.USBamp amplifier, with an event timing jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
The following script has been used to produce the above graphs.&lt;br /&gt;
Modify the filename variable to hold the name of the BCI2000 data file to analyze.&lt;br /&gt;
&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Programming_Reference:Events]], [[Technical Reference:State Definition]], [[Programming Tutorial:Implementing an Input Logger]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Data Acquisition]] [[Category:Framework API]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12553</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12553"/>
		<updated>2026-07-29T15:27:09Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Analyzing RefLogger Data */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called [[Programming_Reference:Events|Events]], that occur during a recording. These events are logged into [[Technical Reference:State Definition|BCI2000 states]], which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, a software component which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference systems.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we may say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first, even if they may not have arrived in temporal order (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events happen while a data block is being digitized by the amplifier&#039;s hardware, and may then be associated with the samples of that block once it arrives at the computer.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are taken from the event queue in temporal order, and applied to the state variables associated with the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay -- acquisition delay is the time delay between digitizing a block’s last sample, and seeing that block in the computer’s memory. &#039;&#039;Regularity&#039;&#039; of data blocks may be assessed using the BCI2000 [[User Reference:Timing|timing window]]; there, data block duration should form a straight line. Acquisition &#039;&#039;delay&#039;&#039;, in contrast, is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input, producing an input signal whenever a new block has entered the computer&#039;s memory.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well these two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz --local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing &#039;&#039;RefLogger&#039;&#039; events at a frequency higher than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event logging software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, using the first time stamp as an offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate one.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Similarly to the SignalSource example above, the Event mechanism is working close to perfectly, with a jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time, we have a nearly perfect distribution of differences with the g.USBamp amplifier, with an event timing jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
The following script has been used to produce the above graphs.&lt;br /&gt;
Modify the filename variable to hold the name of the BCI2000 data file to analyze.&lt;br /&gt;
&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Programming_Reference:Events]], [[Technical Reference:State Definition]], [[Programming Tutorial:Implementing an Input Logger]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Data Acquisition]] [[Category:Framework API]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12552</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12552"/>
		<updated>2026-07-29T15:26:23Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Using the RefLogger */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called [[Programming_Reference:Events|Events]], that occur during a recording. These events are logged into [[Technical Reference:State Definition|BCI2000 states]], which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, a software component which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference systems.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we may say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first, even if they may not have arrived in temporal order (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events happen while a data block is being digitized by the amplifier&#039;s hardware, and may then be associated with the samples of that block once it arrives at the computer.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are taken from the event queue in temporal order, and applied to the state variables associated with the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay -- acquisition delay is the time delay between digitizing a block’s last sample, and seeing that block in the computer’s memory. &#039;&#039;Regularity&#039;&#039; of data blocks may be assessed using the BCI2000 [[User Reference:Timing|timing window]]; there, data block duration should form a straight line. Acquisition &#039;&#039;delay&#039;&#039;, in contrast, is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input, producing an input signal whenever a new block has entered the computer&#039;s memory.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well these two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz --local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing &#039;&#039;RefLogger&#039;&#039; events at a frequency higher than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, using the first time stamp as an offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate one.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Similarly to the SignalSource example above, the Event mechanism is working close to perfectly, with a jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time, we have a nearly perfect distribution of differences with the g.USBamp amplifier, with an event timing jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
The following script has been used to produce the above graphs.&lt;br /&gt;
Modify the filename variable to hold the name of the BCI2000 data file to analyze.&lt;br /&gt;
&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Programming_Reference:Events]], [[Technical Reference:State Definition]], [[Programming Tutorial:Implementing an Input Logger]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Data Acquisition]] [[Category:Framework API]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12551</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12551"/>
		<updated>2026-07-29T15:25:00Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Sources of Imprecision */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called [[Programming_Reference:Events|Events]], that occur during a recording. These events are logged into [[Technical Reference:State Definition|BCI2000 states]], which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, a software component which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference systems.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we may say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first, even if they may not have arrived in temporal order (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events happen while a data block is being digitized by the amplifier&#039;s hardware, and may then be associated with the samples of that block once it arrives at the computer.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are taken from the event queue in temporal order, and applied to the state variables associated with the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay -- acquisition delay is the time delay between digitizing a block’s last sample, and seeing that block in the computer’s memory. &#039;&#039;Regularity&#039;&#039; of data blocks may be assessed using the BCI2000 [[User Reference:Timing|timing window]]; there, data block duration should form a straight line. Acquisition &#039;&#039;delay&#039;&#039;, in contrast, is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input, producing an input signal whenever a new block has entered the computer&#039;s memory.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well these two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz --local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, using the first time stamp as an offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate one.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Similarly to the SignalSource example above, the Event mechanism is working close to perfectly, with a jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time, we have a nearly perfect distribution of differences with the g.USBamp amplifier, with an event timing jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
The following script has been used to produce the above graphs.&lt;br /&gt;
Modify the filename variable to hold the name of the BCI2000 data file to analyze.&lt;br /&gt;
&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Programming_Reference:Events]], [[Technical Reference:State Definition]], [[Programming Tutorial:Implementing an Input Logger]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Data Acquisition]] [[Category:Framework API]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12550</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12550"/>
		<updated>2026-07-29T15:20:22Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Associating Time-stamped Events with Samples */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called [[Programming_Reference:Events|Events]], that occur during a recording. These events are logged into [[Technical Reference:State Definition|BCI2000 states]], which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, a software component which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference systems.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we may say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first, even if they may not have arrived in temporal order (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events happen while a data block is being digitized by the amplifier&#039;s hardware, and may then be associated with the samples of that block once it arrives at the computer.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are taken from the event queue in temporal order, and applied to the state variables associated with the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed using the BCI2000 [[User Reference:Timing|timing window]]; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well these two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz --local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, using the first time stamp as an offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate one.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Similarly to the SignalSource example above, the Event mechanism is working close to perfectly, with a jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time, we have a nearly perfect distribution of differences with the g.USBamp amplifier, with an event timing jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
The following script has been used to produce the above graphs.&lt;br /&gt;
Modify the filename variable to hold the name of the BCI2000 data file to analyze.&lt;br /&gt;
&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Programming_Reference:Events]], [[Technical Reference:State Definition]], [[Programming Tutorial:Implementing an Input Logger]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Data Acquisition]] [[Category:Framework API]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12549</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12549"/>
		<updated>2026-07-29T15:20:02Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Associating Time-stamped Events with Samples */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called [[Programming_Reference:Events|Events]], that occur during a recording. These events are logged into [[Technical Reference:State Definition|BCI2000 states]], which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, a software component which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference systems.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we may say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first, even if they may not have arrived in temporal order (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events happen while a data block is being digitized by the amplifier&#039;s hardware, and may then be associated with the samples of that block once it arrives at the computer.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are taken from the event queue in temporal order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed using the BCI2000 [[User Reference:Timing|timing window]]; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well these two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz --local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, using the first time stamp as an offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate one.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Similarly to the SignalSource example above, the Event mechanism is working close to perfectly, with a jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time, we have a nearly perfect distribution of differences with the g.USBamp amplifier, with an event timing jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
The following script has been used to produce the above graphs.&lt;br /&gt;
Modify the filename variable to hold the name of the BCI2000 data file to analyze.&lt;br /&gt;
&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Programming_Reference:Events]], [[Technical Reference:State Definition]], [[Programming Tutorial:Implementing an Input Logger]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Data Acquisition]] [[Category:Framework API]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Contents&amp;diff=12548</id>
		<title>User Reference:Contents</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Contents&amp;diff=12548"/>
		<updated>2026-07-28T22:33:33Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;User reference information helps you configure and perform experiments with BCI2000, using the executables that come with its [[BCI2000 Binaries|binary distribution]].&lt;br /&gt;
For information about how to modify existing filters, or develop your own from scratch, see the [[Programming Reference:Contents|Programming Reference]] section.&lt;br /&gt;
&lt;br /&gt;
In the user reference section, information is available about the following topics:&lt;br /&gt;
&lt;br /&gt;
==Using the Online System==&lt;br /&gt;
*[[User Reference:Operator Module]]:&lt;br /&gt;
**Parameter editing, &lt;br /&gt;
**saving and loading parameter files, &lt;br /&gt;
**starting and stopping BCI operation.&lt;br /&gt;
*[[User Reference:Operator Notes]]: Manually taking notes during recordings.&lt;br /&gt;
*[[User Reference:Operator.ini]]: Operator module settings file.&lt;br /&gt;
*[[User Reference:Operator Module Scripting]]: Automated execution of operator tasks.&lt;br /&gt;
*[[User Reference:Module Command Line Options]]: Modifying module behavior with command line options.&lt;br /&gt;
*[[User Reference:Data File Formats]]: File formats supported by BCI2000.&lt;br /&gt;
*[[User FAQ]]: Frequently asked questions about using BCI2000.&lt;br /&gt;
&lt;br /&gt;
==Configuring the Online System==&lt;br /&gt;
*[[User Reference:Filters|BCI2000 Filters]]: Any BCI2000 configuration consists of different filters in the Data Acquisition, Signal Processing, and Application module. These filters determine what device BCI2000 acquires data from, what data format it uses for data storage, which feature extraction and translation algorithms it uses to translate brain signals into device commands, and which kind of user feedback is provided. This page gives an overview of all filters that are currently available in the BCI2000 distribution.&lt;br /&gt;
*[[User Reference:Parameters]] provides an index of configuration parameters present in BCI2000 components.&lt;br /&gt;
*[[User Reference:States]] provides an index of state variables present in BCI2000 components.&lt;br /&gt;
&lt;br /&gt;
*[[User Reference:Parameter Changes for Version 2]] helps in migrating experiments from BCI2000 version 1 to version 2.&lt;br /&gt;
&lt;br /&gt;
==Verifying the Online System==&lt;br /&gt;
*[[User Reference:Validating the Event Logging Mechanism]]: A description and validation instructions for BCI2000 events.&lt;br /&gt;
*[[User Reference:BCI2000Certification]]: Measure a BCI2000 system&#039;s timing performance.&lt;br /&gt;
&lt;br /&gt;
==Using BCI2000 Tools==&lt;br /&gt;
*[[User Reference:BCI2000Launcher]]: Conveniently start up BCI2000 modules from a graphical interface.&lt;br /&gt;
*[[User Reference:BCI2000Watchdog]]: A tool to start up and monitor BCI2000 for long-term recordings.&lt;br /&gt;
*[[User Reference:BCI2000Viewer]]: View signals recorded with BCI2000.&lt;br /&gt;
*[[User Reference:BCI2000FileInfo]]: View and extract parameter information from BCI2000 data files.&lt;br /&gt;
*[[User Reference:BCI2000Export]]: Export BCI2000 data files into various formats.&lt;br /&gt;
*[[User Reference:BCI2000Analysis]]: Analyze data files offline, create and test filter chains, train classifiers for online recognition.&lt;br /&gt;
*[[User Reference:BCI2000 Offline Analysis]]: MATLAB-based tool to analyze data files offline in order to determine the best features for online recognition.&lt;br /&gt;
*[[User Reference:Eloc Helper]]: Matlab-based tool to create and modify montage files.&lt;br /&gt;
*[[User Reference:EEGLabImport]]: Read BCI2000 data into EEGLab.&lt;br /&gt;
*[[User Reference:Matlab Tools]]: Perform offline data analysis, and manage information in BCI2000 files, using Matlab functions.&lt;br /&gt;
*[[User Reference:Matlab MEX Files]]: Access BCI2000 data from within Matlab.&lt;br /&gt;
*[[User Reference:Command Line Processing]]: Compile BCI2000 filters into executables for offline processing.&lt;br /&gt;
*[[User Reference:StimulationConfigurationIntegrativeTool (SCIT)]]: Easily create and visualize BCI2000 parameter files for the Neuro Omega, CereStim, g.Estim, and Cortec Brain Interchange. &lt;br /&gt;
&lt;br /&gt;
[[Category:Contents]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Contents&amp;diff=12547</id>
		<title>User Reference:Contents</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Contents&amp;diff=12547"/>
		<updated>2026-07-28T22:31:53Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Configuring the Online System */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;User reference information helps you configure and perform experiments with BCI2000, using the executables that come with its [[BCI2000 Binaries|binary distribution]].&lt;br /&gt;
For information about how to modify existing filters, or develop your own from scratch, see the [[Programming Reference:Contents|Programming Reference]] section.&lt;br /&gt;
&lt;br /&gt;
In the user reference section, information is available about the following topics:&lt;br /&gt;
&lt;br /&gt;
==Using the Online System==&lt;br /&gt;
*[[User Reference:Operator Module]]:&lt;br /&gt;
**Parameter editing, &lt;br /&gt;
**saving and loading parameter files, &lt;br /&gt;
**starting and stopping BCI operation.&lt;br /&gt;
*[[User Reference:Operator Notes]]: Manually taking notes during recordings.&lt;br /&gt;
*[[User Reference:Operator.ini]]: Operator module settings file.&lt;br /&gt;
*[[User Reference:Operator Module Scripting]]: Automated execution of operator tasks.&lt;br /&gt;
*[[User Reference:Module Command Line Options]]: Modifying module behavior with command line options.&lt;br /&gt;
*[[User Reference:Data File Formats]]: File formats supported by BCI2000.&lt;br /&gt;
*[[User FAQ]]: Frequently asked questions about using BCI2000.&lt;br /&gt;
&lt;br /&gt;
==Configuring the Online System==&lt;br /&gt;
*[[User Reference:Filters|BCI2000 Filters]]: Any BCI2000 configuration consists of different filters in the Data Acquisition, Signal Processing, and Application module. These filters determine what device BCI2000 acquires data from, what data format it uses for data storage, which feature extraction and translation algorithms it uses to translate brain signals into device commands, and which kind of user feedback is provided. This page gives an overview of all filters that are currently available in the BCI2000 distribution.&lt;br /&gt;
*[[User Reference:Parameters]] provides an index of configuration parameters present in BCI2000 components.&lt;br /&gt;
*[[User Reference:States]] provides an index of state variables present in BCI2000 components.&lt;br /&gt;
&lt;br /&gt;
*[[User Reference:Parameter Changes for Version 2]] helps in migrating experiments from BCI2000 version 1 to version 2.&lt;br /&gt;
&lt;br /&gt;
==Verifying the Online System==&lt;br /&gt;
&lt;br /&gt;
==Using BCI2000 Tools==&lt;br /&gt;
*[[User Reference:BCI2000Certification]]: Measure a BCI2000 system&#039;s timing performance.&lt;br /&gt;
*[[User Reference:BCI2000Launcher]]: Conveniently start up BCI2000 modules from a graphical interface.&lt;br /&gt;
*[[User Reference:BCI2000Watchdog]]: A tool to start up and monitor BCI2000 for long-term recordings.&lt;br /&gt;
*[[User Reference:BCI2000Viewer]]: View signals recorded with BCI2000.&lt;br /&gt;
*[[User Reference:BCI2000FileInfo]]: View and extract parameter information from BCI2000 data files.&lt;br /&gt;
*[[User Reference:BCI2000Export]]: Export BCI2000 data files into various formats.&lt;br /&gt;
*[[User Reference:BCI2000Analysis]]: Analyze data files offline, create and test filter chains, train classifiers for online recognition.&lt;br /&gt;
*[[User Reference:BCI2000 Offline Analysis]]: MATLAB-based tool to analyze data files offline in order to determine the best features for online recognition.&lt;br /&gt;
*[[User Reference:Eloc Helper]]: Matlab-based tool to create and modify montage files.&lt;br /&gt;
*[[User Reference:EEGLabImport]]: Read BCI2000 data into EEGLab.&lt;br /&gt;
*[[User Reference:Matlab Tools]]: Perform offline data analysis, and manage information in BCI2000 files, using Matlab functions.&lt;br /&gt;
*[[User Reference:Matlab MEX Files]]: Access BCI2000 data from within Matlab.&lt;br /&gt;
*[[User Reference:Command Line Processing]]: Compile BCI2000 filters into executables for offline processing.&lt;br /&gt;
*[[User Reference:StimulationConfigurationIntegrativeTool (SCIT)]]: Easily create and visualize BCI2000 parameter files for the Neuro Omega, CereStim, g.Estim, and Cortec Brain Interchange. &lt;br /&gt;
&lt;br /&gt;
[[Category:Contents]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12546</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12546"/>
		<updated>2026-07-28T22:28:09Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Matlab Analysis Script */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called [[Programming_Reference:Events|Events]], that occur during a recording. These events are logged into [[Technical Reference:State Definition|BCI2000 states]], which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, a software component which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference systems.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we may say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first, even if they may not have arrived in temporal order (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events happen while a data block is being digitized by the amplifier&#039;s hardware, and may then be associated with the samples of that block once it arrives at the computer.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed using the BCI2000 [[User Reference:Timing|timing window]]; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well these two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz --local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, using the first time stamp as an offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate one.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Similarly to the SignalSource example above, the Event mechanism is working close to perfectly, with a jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time, we have a nearly perfect distribution of differences with the g.USBamp amplifier, with an event timing jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
The following script has been used to produce the above graphs.&lt;br /&gt;
Modify the filename variable to hold the name of the BCI2000 data file to analyze.&lt;br /&gt;
&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Programming_Reference:Events]], [[Technical Reference:State Definition]], [[Programming Tutorial:Implementing an Input Logger]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Data Acquisition]] [[Category:Framework API]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12545</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12545"/>
		<updated>2026-07-28T22:26:41Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Data recorded with g.USBamp source module */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called [[Programming_Reference:Events|Events]], that occur during a recording. These events are logged into [[Technical Reference:State Definition|BCI2000 states]], which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, a software component which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference systems.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we may say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first, even if they may not have arrived in temporal order (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events happen while a data block is being digitized by the amplifier&#039;s hardware, and may then be associated with the samples of that block once it arrives at the computer.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed using the BCI2000 [[User Reference:Timing|timing window]]; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well these two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz --local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, using the first time stamp as an offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate one.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Similarly to the SignalSource example above, the Event mechanism is working close to perfectly, with a jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time, we have a nearly perfect distribution of differences with the g.USBamp amplifier, with an event timing jitter of +/-0.4 ms (standard deviation).&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Programming_Reference:Events]], [[Technical Reference:State Definition]], [[Programming Tutorial:Implementing an Input Logger]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Data Acquisition]] [[Category:Framework API]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12544</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12544"/>
		<updated>2026-07-28T22:25:47Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Data recorded with g.USBamp source module */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called [[Programming_Reference:Events|Events]], that occur during a recording. These events are logged into [[Technical Reference:State Definition|BCI2000 states]], which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, a software component which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference systems.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we may say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first, even if they may not have arrived in temporal order (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events happen while a data block is being digitized by the amplifier&#039;s hardware, and may then be associated with the samples of that block once it arrives at the computer.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed using the BCI2000 [[User Reference:Timing|timing window]]; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well these two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz --local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, using the first time stamp as an offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate one.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Similarly to the SignalSource example above, the Event mechanism is working close to perfectly, with a jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time, we have a nearly perfect distribution of differences with the g.USBamp amplifier, with an event timing jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Programming_Reference:Events]], [[Technical Reference:State Definition]], [[Programming Tutorial:Implementing an Input Logger]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Data Acquisition]] [[Category:Framework API]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12543</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12543"/>
		<updated>2026-07-28T22:24:22Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Analyzing RefLogger Data */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called [[Programming_Reference:Events|Events]], that occur during a recording. These events are logged into [[Technical Reference:State Definition|BCI2000 states]], which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, a software component which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference systems.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we may say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first, even if they may not have arrived in temporal order (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events happen while a data block is being digitized by the amplifier&#039;s hardware, and may then be associated with the samples of that block once it arrives at the computer.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed using the BCI2000 [[User Reference:Timing|timing window]]; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well these two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz --local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, using the first time stamp as an offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate one.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Also here, the Event mechanism is working close to perfectly, with a jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time, we have a nearly perfect distribution of differences with the g.USBamp amplifier, with an event timing jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Programming_Reference:Events]], [[Technical Reference:State Definition]], [[Programming Tutorial:Implementing an Input Logger]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Data Acquisition]] [[Category:Framework API]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12542</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12542"/>
		<updated>2026-07-28T22:22:46Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Using the RefLogger */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called [[Programming_Reference:Events|Events]], that occur during a recording. These events are logged into [[Technical Reference:State Definition|BCI2000 states]], which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, a software component which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference systems.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we may say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first, even if they may not have arrived in temporal order (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events happen while a data block is being digitized by the amplifier&#039;s hardware, and may then be associated with the samples of that block once it arrives at the computer.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed using the BCI2000 [[User Reference:Timing|timing window]]; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well these two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz --local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, taking into consideration the first time stamp as a offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate one.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Also here, the Event mechanism is working close to perfectly, with a jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time, we have a nearly perfect distribution of differences with the g.USBamp amplifier, with an event timing jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Programming_Reference:Events]], [[Technical Reference:State Definition]], [[Programming Tutorial:Implementing an Input Logger]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Data Acquisition]] [[Category:Framework API]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12541</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12541"/>
		<updated>2026-07-28T22:22:14Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* The RefLogger Component */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called [[Programming_Reference:Events|Events]], that occur during a recording. These events are logged into [[Technical Reference:State Definition|BCI2000 states]], which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, a software component which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference systems.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we may say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first, even if they may not have arrived in temporal order (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events happen while a data block is being digitized by the amplifier&#039;s hardware, and may then be associated with the samples of that block once it arrives at the computer.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed using the BCI2000 [[User Reference:Timing|timing window]]; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well these two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz —local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, taking into consideration the first time stamp as a offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate one.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Also here, the Event mechanism is working close to perfectly, with a jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time, we have a nearly perfect distribution of differences with the g.USBamp amplifier, with an event timing jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Programming_Reference:Events]], [[Technical Reference:State Definition]], [[Programming Tutorial:Implementing an Input Logger]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Data Acquisition]] [[Category:Framework API]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12540</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12540"/>
		<updated>2026-07-28T22:20:51Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Sources of Imprecision */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called [[Programming_Reference:Events|Events]], that occur during a recording. These events are logged into [[Technical Reference:State Definition|BCI2000 states]], which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, a software component which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference systems.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we may say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first, even if they may not have arrived in temporal order (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events happen while a data block is being digitized by the amplifier&#039;s hardware, and may then be associated with the samples of that block once it arrives at the computer.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed using the BCI2000 [[User Reference:Timing|timing window]]; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well the two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz —local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, taking into consideration the first time stamp as a offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate one.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Also here, the Event mechanism is working close to perfectly, with a jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time, we have a nearly perfect distribution of differences with the g.USBamp amplifier, with an event timing jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Programming_Reference:Events]], [[Technical Reference:State Definition]], [[Programming Tutorial:Implementing an Input Logger]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Data Acquisition]] [[Category:Framework API]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12539</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12539"/>
		<updated>2026-07-28T22:19:23Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Associating Time-stamped Events with Samples */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called [[Programming_Reference:Events|Events]], that occur during a recording. These events are logged into [[Technical Reference:State Definition|BCI2000 states]], which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, a software component which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference systems.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we may say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first, even if they may not have arrived in temporal order (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events happen while a data block is being digitized by the amplifier&#039;s hardware, and may then be associated with the samples of that block once it arrives at the computer.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed by using the BCI2000 timing window; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well the two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz —local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, taking into consideration the first time stamp as a offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate one.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Also here, the Event mechanism is working close to perfectly, with a jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time, we have a nearly perfect distribution of differences with the g.USBamp amplifier, with an event timing jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Programming_Reference:Events]], [[Technical Reference:State Definition]], [[Programming Tutorial:Implementing an Input Logger]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Data Acquisition]] [[Category:Framework API]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12538</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12538"/>
		<updated>2026-07-28T22:17:46Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Time-stamping Events */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called [[Programming_Reference:Events|Events]], that occur during a recording. These events are logged into [[Technical Reference:State Definition|BCI2000 states]], which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, a software component which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference systems.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we may say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first, even if they may not have arrived in temporal order (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events that happen while a data block is being digitized should be associated with the samples of that block.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed by using the BCI2000 timing window; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well the two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz —local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, taking into consideration the first time stamp as a offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate one.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Also here, the Event mechanism is working close to perfectly, with a jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time, we have a nearly perfect distribution of differences with the g.USBamp amplifier, with an event timing jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Programming_Reference:Events]], [[Technical Reference:State Definition]], [[Programming Tutorial:Implementing an Input Logger]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Data Acquisition]] [[Category:Framework API]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12537</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12537"/>
		<updated>2026-07-28T22:16:10Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Time-stamping Brain Signal Data */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called [[Programming_Reference:Events|Events]], that occur during a recording. These events are logged into [[Technical Reference:State Definition|BCI2000 states]], which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, a software component which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference systems.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we may say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events that happen while a data block is being digitized should be associated with the samples of that block.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed by using the BCI2000 timing window; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well the two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz —local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, taking into consideration the first time stamp as a offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate one.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Also here, the Event mechanism is working close to perfectly, with a jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time, we have a nearly perfect distribution of differences with the g.USBamp amplifier, with an event timing jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Programming_Reference:Events]], [[Technical Reference:State Definition]], [[Programming Tutorial:Implementing an Input Logger]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Data Acquisition]] [[Category:Framework API]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12536</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12536"/>
		<updated>2026-07-28T22:15:20Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Synopsis */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called [[Programming_Reference:Events|Events]], that occur during a recording. These events are logged into [[Technical Reference:State Definition|BCI2000 states]], which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, a software component which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference systems.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we can say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events that happen while a data block is being digitized should be associated with the samples of that block.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed by using the BCI2000 timing window; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well the two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz —local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, taking into consideration the first time stamp as a offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate one.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Also here, the Event mechanism is working close to perfectly, with a jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time, we have a nearly perfect distribution of differences with the g.USBamp amplifier, with an event timing jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Programming_Reference:Events]], [[Technical Reference:State Definition]], [[Programming Tutorial:Implementing an Input Logger]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Data Acquisition]] [[Category:Framework API]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12535</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12535"/>
		<updated>2026-07-28T22:15:02Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Synopsis */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called [[Programming_Reference:Events|Events]], that occur during a recording. These events are logged into [[Technical Reference:State Definition|BCI2000 states]], which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, a software component which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference system.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we can say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events that happen while a data block is being digitized should be associated with the samples of that block.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed by using the BCI2000 timing window; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well the two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz —local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, taking into consideration the first time stamp as a offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate one.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Also here, the Event mechanism is working close to perfectly, with a jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time, we have a nearly perfect distribution of differences with the g.USBamp amplifier, with an event timing jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Programming_Reference:Events]], [[Technical Reference:State Definition]], [[Programming Tutorial:Implementing an Input Logger]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Data Acquisition]] [[Category:Framework API]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12534</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12534"/>
		<updated>2026-07-28T22:11:02Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called [[Programming_Reference:Events|Events]], that occur during a recording. These events are logged into [[Technical Reference:State Definition|BCI2000 states]], which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference system.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we can say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events that happen while a data block is being digitized should be associated with the samples of that block.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed by using the BCI2000 timing window; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well the two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz —local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, taking into consideration the first time stamp as a offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate one.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Also here, the Event mechanism is working close to perfectly, with a jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time, we have a nearly perfect distribution of differences with the g.USBamp amplifier, with an event timing jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Programming_Reference:Events]], [[Technical Reference:State Definition]], [[Programming Tutorial:Implementing an Input Logger]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Data Acquisition]] [[Category:Framework API]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12533</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12533"/>
		<updated>2026-07-28T22:09:44Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called [[Programming_Reference:Events|Events]], that occur during a recording. These events are logged into BCI2000 states, which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference system.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we can say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events that happen while a data block is being digitized should be associated with the samples of that block.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed by using the BCI2000 timing window; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well the two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz —local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, taking into consideration the first time stamp as a offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate one.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Also here, the Event mechanism is working close to perfectly, with a jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time, we have a nearly perfect distribution of differences with the g.USBamp amplifier, with an event timing jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[Programming_Reference:Events]], [[Technical Reference:State Definition]], [[Programming Tutorial:Implementing an Input Logger]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Data Acquisition]] [[Category:Framework API]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12532</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12532"/>
		<updated>2026-07-28T22:03:29Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Data recorded with g.USBamp source module */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called Events, that occur during a recording. These events are logged into BCI2000 states, which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference system.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we can say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events that happen while a data block is being digitized should be associated with the samples of that block.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed by using the BCI2000 timing window; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well the two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz —local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, taking into consideration the first time stamp as a offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate one.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Also here, the Event mechanism is working close to perfectly, with a jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time, we have a nearly perfect distribution of differences with the g.USBamp amplifier, with an event timing jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12531</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12531"/>
		<updated>2026-07-28T22:03:18Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Data recorded with g.USBamp source module */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called Events, that occur during a recording. These events are logged into BCI2000 states, which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference system.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we can say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events that happen while a data block is being digitized should be associated with the samples of that block.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed by using the BCI2000 timing window; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well the two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz —local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, taking into consideration the first time stamp as a offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate one.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Also here, the Event mechanism is working close to perfectly, with a jitter of +/-0.3 ms.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time, we have a nearly perfect distribution of differences with the g.USBamp amplifier, with an event timing jitter of +/-0.4 ms.&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12530</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12530"/>
		<updated>2026-07-28T22:02:45Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Data recorded with g.USBamp source module */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called Events, that occur during a recording. These events are logged into BCI2000 states, which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference system.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we can say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events that happen while a data block is being digitized should be associated with the samples of that block.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed by using the BCI2000 timing window; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well the two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz —local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, taking into consideration the first time stamp as a offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate one.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
Also here, the Event mechanism is working close to perfectly, with a jitter of +/-0.3 ms.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
When comparing event time stamps to sample time, we have a nearly perfect distribution of differences with the g.USBamp amplifier, with an event timing jitter of +/-0.3 ms.&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12529</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12529"/>
		<updated>2026-07-28T21:59:20Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Data recorded with SignalGenerator source module */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called Events, that occur during a recording. These events are logged into BCI2000 states, which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference system.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we can say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events that happen while a data block is being digitized should be associated with the samples of that block.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed by using the BCI2000 timing window; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well the two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz —local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, taking into consideration the first time stamp as a offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate one.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12528</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12528"/>
		<updated>2026-07-28T21:59:07Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Data recorded with SignalGenerator source module */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called Events, that occur during a recording. These events are logged into BCI2000 states, which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference system.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we can say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events that happen while a data block is being digitized should be associated with the samples of that block.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed by using the BCI2000 timing window; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well the two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz —local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, taking into consideration the first time stamp as a offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
As one may see from this graph, event time stamp disagreement from sample position is within +/-0.5 ms, so the Event time mechanism is working close to perfectly.&lt;br /&gt;
&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
This graph shows that there is quite some disagreement between sample time, and event time.&lt;br /&gt;
This is due to the fact that SignalGenerator is not a true data source, and uses the imprecise Windows &amp;lt;tt&amp;gt;Sleep()&amp;lt;/tt&amp;gt; function to simulate one.&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12527</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12527"/>
		<updated>2026-07-28T21:54:04Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Example Results */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called Events, that occur during a recording. These events are logged into BCI2000 states, which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference system.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we can say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events that happen while a data block is being digitized should be associated with the samples of that block.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed by using the BCI2000 timing window; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well the two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz —local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, taking into consideration the first time stamp as a offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;br /&gt;
&lt;br /&gt;
===Matlab Analysis Script===&lt;br /&gt;
 filename = &#039;RefLogger_gUSBampS001R01.dat&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 [signal, states, parameters] = load_bcidat(filename);&lt;br /&gt;
 SampleBlockSize = parameters.SampleBlockSize.NumericValue;&lt;br /&gt;
 SamplingRateHz = parse_sampling_rate(parameters.SamplingRate.Value{1});&lt;br /&gt;
 SampleBlockDurationMs = SampleBlockSize / SamplingRateHz * 1e3;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare SourceTime state by first reducing to a single value per block,&lt;br /&gt;
 % then interpolating across block&lt;br /&gt;
 SourceTime = double(states.SourceTime);&lt;br /&gt;
 SourceTime2 = SourceTime(1:SampleBlockSize:end);&lt;br /&gt;
 SourceTime2 = extend_timestamp(SourceTime2);&lt;br /&gt;
 SourceTime3 = interp1([1:length(SourceTime2)], SourceTime2, [1:1/SampleBlockSize:length(SourceTime2)])&#039;;&lt;br /&gt;
 % SourceTime reflects the time of acquisition, so sample time is earlier&lt;br /&gt;
 % by a sample block duration&lt;br /&gt;
 SourceTime3 = SourceTime3 - SampleBlockDurationMs;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare a vector of equally spaced sample times (SourceTime may be&lt;br /&gt;
 % jittered)&lt;br /&gt;
 sample_time = linspace(SourceTime3(1), SourceTime3(end), length(SourceTime3))&#039;;&lt;br /&gt;
 %&lt;br /&gt;
 % prepare RefTime state&lt;br /&gt;
 RefTime = double(states.RefTime);&lt;br /&gt;
 RefTime = RefTime(1:length(SourceTime3));&lt;br /&gt;
 % replace &amp;quot;unused&amp;quot; placeholder value with nan&lt;br /&gt;
 RefTime(RefTime &amp;gt; 65535) = nan;&lt;br /&gt;
 valid_idx = ~isnan(RefTime);&lt;br /&gt;
 RefTime2 = RefTime(valid_idx);&lt;br /&gt;
 RefTime2 = extend_timestamp(RefTime2);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated SourceTime&lt;br /&gt;
 % values&lt;br /&gt;
 delta1 = RefTime2 - SourceTime3(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta1, 40);&lt;br /&gt;
 title(&#039;RefTime vs SourceTime disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta1), sqrt(var(delta1))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between RefTime values, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta2 = RefTime2 - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta2, 40);&lt;br /&gt;
 title(&#039;RefTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta2), sqrt(var(delta2))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 % determine difference between interpolated SourceTime, and interpolated sample time&lt;br /&gt;
 % values&lt;br /&gt;
 delta3 = SourceTime3(valid_idx) - sample_time(valid_idx);&lt;br /&gt;
 %&lt;br /&gt;
 figure;&lt;br /&gt;
 hist(delta3, 40);&lt;br /&gt;
 title(&#039;SourceTime vs sample time disagreement&#039;);&lt;br /&gt;
 subtitle(sprintf(&#039;mean: %f ms, sdev: %f ms&#039;, mean(delta3), sqrt(var(delta3))));&lt;br /&gt;
 xlabel(&#039;offset in ms&#039;);&lt;br /&gt;
 %&lt;br /&gt;
 %&lt;br /&gt;
 function result = extend_timestamp(input)&lt;br /&gt;
 % extend timestamp from the 0..65535 to full range&lt;br /&gt;
    result = input;&lt;br /&gt;
    add = 0;&lt;br /&gt;
    for i = 2:length(input)&lt;br /&gt;
        if input(i) &amp;lt; input(i-1)&lt;br /&gt;
            add = add + 65536;&lt;br /&gt;
        end&lt;br /&gt;
        result(i) = input(i) + add;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;br /&gt;
 %&lt;br /&gt;
 function result = parse_sampling_rate(input)&lt;br /&gt;
    [token, remain] = strtok(input, &#039;Hk&#039;);&lt;br /&gt;
    result = str2double(token);&lt;br /&gt;
    if strcmp(remain, &#039;kHz&#039;)&lt;br /&gt;
        result = result * 1e3;&lt;br /&gt;
    end&lt;br /&gt;
 end&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=File:Event_vs_sample_time_gUSBamp.png&amp;diff=12526</id>
		<title>File:Event vs sample time gUSBamp.png</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=File:Event_vs_sample_time_gUSBamp.png&amp;diff=12526"/>
		<updated>2026-07-28T21:48:28Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=File:Event_vs_SourceTime_gUSBamp.png&amp;diff=12525</id>
		<title>File:Event vs SourceTime gUSBamp.png</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=File:Event_vs_SourceTime_gUSBamp.png&amp;diff=12525"/>
		<updated>2026-07-28T21:48:13Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=File:Event_vs_sample_time_SignalGenerator.png&amp;diff=12524</id>
		<title>File:Event vs sample time SignalGenerator.png</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=File:Event_vs_sample_time_SignalGenerator.png&amp;diff=12524"/>
		<updated>2026-07-28T21:46:27Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=File:Event_vs_SourceTime_SignalGenerator.png&amp;diff=12523</id>
		<title>File:Event vs SourceTime SignalGenerator.png</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=File:Event_vs_SourceTime_SignalGenerator.png&amp;diff=12523"/>
		<updated>2026-07-28T21:44:40Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12522</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12522"/>
		<updated>2026-07-28T21:42:32Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Analyzing RefLogger Data */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called Events, that occur during a recording. These events are logged into BCI2000 states, which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference system.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we can say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events that happen while a data block is being digitized should be associated with the samples of that block.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed by using the BCI2000 timing window; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well the two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz —local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, taking into consideration the first time stamp as a offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;br /&gt;
&lt;br /&gt;
===Example Results===&lt;br /&gt;
====Data recorded with SignalGenerator source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_SignalGenerator.png]]&lt;br /&gt;
[[file:Event_vs_sample_time_SignalGenerator.png]]&lt;br /&gt;
&lt;br /&gt;
====Data recorded with g.USBamp source module====&lt;br /&gt;
[[file:Event_vs_SourceTime_gUSBamp.png]]&lt;br /&gt;
[[file:Event_vs_sample_time_gUSBamp.png]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12521</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12521"/>
		<updated>2026-07-28T21:34:10Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Analyzing RefLogger Data */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called Events, that occur during a recording. These events are logged into BCI2000 states, which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference system.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we can say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events that happen while a data block is being digitized should be associated with the samples of that block.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed by using the BCI2000 timing window; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well the two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz —local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two distinct purposes:&lt;br /&gt;
&lt;br /&gt;
# Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
# Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, taking into consideration the first time stamp as a offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Any differences are due to the sources of imprecision discussed above.&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12520</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12520"/>
		<updated>2026-07-28T17:00:53Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* The RefLogger Component */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called Events, that occur during a recording. These events are logged into BCI2000 states, which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference system.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we can say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events that happen while a data block is being digitized should be associated with the samples of that block.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed by using the BCI2000 timing window; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is a simple BCI2000 component that logs a special event, called &#039;&#039;&#039;RefTime&#039;&#039;&#039;, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like &amp;quot;1&amp;quot; or &amp;quot;2&amp;quot; but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well the two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The &#039;&#039;RefLogger&#039;&#039; is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz —local&lt;br /&gt;
You may specify any value for &#039;&#039;RefLoggerFrequency&#039;&#039; but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the &#039;&#039;SamplingRate&#039;&#039; parameter is set to a value lower than &#039;&#039;RefLoggerFrequency&#039;&#039;, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
&#039;&#039;RefLogger&#039;&#039; data analysis follows two purposes:&lt;br /&gt;
1. Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
2. Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare &#039;&#039;&#039;RefTime&#039;&#039;&#039; timestamps against.&lt;br /&gt;
For question (1), the time base is taken from each data block’s &#039;&#039;SourceTime&#039;&#039; timestamp, and extrapolated into the past before determining the difference to the &#039;&#039;&#039;RefTime&#039;&#039;&#039; entries.&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, taking into consideration the first time stamp as a offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Differences are due to the sources of imprecision discussed above.&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12519</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12519"/>
		<updated>2026-07-28T16:58:49Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: /* Synopsis */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called Events, that occur during a recording. These events are logged into BCI2000 states, which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the &#039;&#039;RefLogger&#039;&#039;, which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze &#039;&#039;RefLogger&#039;&#039; data, together with results from a reference system.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we can say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events that happen while a data block is being digitized should be associated with the samples of that block.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed by using the BCI2000 timing window; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The ‘’RefLogger’’ is a simple BCI2000 component that logs a special event, called ‘’’RefTime’’’, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like “1” or “2” but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well the two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The ‘’RefLogger’’ is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz —local&lt;br /&gt;
You may specify any value for ‘’RefLoggerFrequency’’ but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the ‘’SamplingRate’’ parameter is set to a value lower than ‘’RefLoggerFrequency’’, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
‘’RefLogger’’ data analysis follows two purposes:&lt;br /&gt;
1. Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
2. Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare ‘’’RefTime’’’ timestamps against.&lt;br /&gt;
For question (1), the time base is taken from each data block’s ‘’SourceTime’’ timestamp, and extrapolated into the past before determining the difference to the ‘’’RefTime’’’ entries.&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, taking into consideration the first time stamp as a offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Differences are due to the sources of imprecision discussed above.&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12518</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12518"/>
		<updated>2026-07-28T16:58:18Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called Events, that occur during a recording. These events are logged into BCI2000 states, which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the RefLogger, which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze RefLogger data, together with results from a reference system.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we can say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events that happen while a data block is being digitized should be associated with the samples of that block.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed by using the BCI2000 timing window; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;br /&gt;
&lt;br /&gt;
Imprecise block duration will result in differences between measured block durations, and ideal block durations. If this happens, time stamps may appear older than the oldest sample in the current block. If this is the case, the affected events will be associated with the block’s first sample, as the previous block is no longer available for storing events. This will result in events that are shifted slightly into the future, so they may appear to precede the actual time of the physical event that produced them.&lt;br /&gt;
&lt;br /&gt;
==The RefLogger Component==&lt;br /&gt;
The ‘’RefLogger’’ is a simple BCI2000 component that logs a special event, called ‘’’RefTime’’’, in regular intervals.&lt;br /&gt;
What is special about that event is that it does not hold simple values like “1” or “2” but time stamps derived from the computer’s clock, just as the time stamps used to align events with brain signal samples. In data analysis, this allows to compare the sample position of an event to its time stamp value, and to assess how well the two match.&lt;br /&gt;
&lt;br /&gt;
===Using the RefLogger===&lt;br /&gt;
The ‘’RefLogger’’ is available in all BCI2000 modules, but not active by default.&lt;br /&gt;
To enable it, add a command line option to the source module’s &amp;lt;tt&amp;gt;START EXECUTABLE&amp;lt;/tt&amp;gt; command as in the following example:&lt;br /&gt;
 Start Executable SignalGenerator --RefLoggerFrequency=100Hz —local&lt;br /&gt;
You may specify any value for ‘’RefLoggerFrequency’’ but notice that BCI2000 time stamps are limited to millisecond resolution, so producing RefLogger events at a higher frequency than 1000Hz is not useful. Also, if the ‘’SamplingRate’’ parameter is set to a value lower than ‘’RefLoggerFrequency’’, no useful results may be expected.&lt;br /&gt;
&lt;br /&gt;
===Analyzing RefLogger Data===&lt;br /&gt;
‘’RefLogger’’ data analysis follows two purposes:&lt;br /&gt;
1. Assessing whether the Event software mechanism works as designed,&lt;br /&gt;
2. Assessing how precise and useful Event information is for a certain experiment.&lt;br /&gt;
In terms of data, the two questions are quite similar, but differ in the time base used to compare ‘’’RefTime’’’ timestamps against.&lt;br /&gt;
For question (1), the time base is taken from each data block’s ‘’SourceTime’’ timestamp, and extrapolated into the past before determining the difference to the ‘’’RefTime’’’ entries.&lt;br /&gt;
For question (2), a sample-based time base is constructed by taking the recording’s duration as measured in terms of time stamps, and evenly distributing that across all samples in the recording, taking into consideration the first time stamp as a offset. Then, the time stamp differences give an impression of how the event mechanism performs with regard to physical time.&lt;br /&gt;
&lt;br /&gt;
Ideally, the results of (1) and (2) would agree. Differences are due to the sources of imprecision discussed above.&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12517</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12517"/>
		<updated>2026-07-28T16:22:46Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called Events, that occur during a recording. These events are logged into BCI2000 states, which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the RefLogger, which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze RefLogger data, together with results from a reference system.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
===Time-stamping Brain Signal Data===&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we can say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;br /&gt;
&lt;br /&gt;
===Time-stamping Events===&lt;br /&gt;
In the BCI2000 software, a function exists that takes the name of an event, and a value. Whenever this function is called, it takes a time stamp from the computer’s clock and stores the event’s name, value, and time stamp into a queue of events. This queue is organized according to time stamps such that oldest entries will be retrieved first (priority queue).&lt;br /&gt;
&lt;br /&gt;
===Associating Time-stamped Events with Samples===&lt;br /&gt;
For the following, it is crucial to understand that events that happen while a data block is being digitized should be associated with the samples of that block.&lt;br /&gt;
As soon as that block enters the computer’s memory, events are then taken from the event queue in order, and applied to the state variables inside the block.&lt;br /&gt;
&lt;br /&gt;
===Sources of Imprecision===&lt;br /&gt;
The event logging mechanism depends on precision of the BCI2000 time stamp. As this is derived from a high-precision clock available in all modern computers, this is in general not an issue.&lt;br /&gt;
&lt;br /&gt;
More of interest is another source of imprecision: Acquisition irregularities. Ideally, BCI2000 block duration is constant, and data blocks arrive regularly with little to no acquisition delay (time from sampling a block’s last sample, and seeing that block in the computer’s memory). Regularity of data blocks may be assessed by using the BCI2000 timing window; there, data block duration should form a straight line. Acquisition delay is more difficult to measure, and requires an amplifier with a digital (or analog) output that may be connected to an amplifier input.&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12516</id>
		<title>User Reference:Validating the Event Logging Mechanism</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=User_Reference:Validating_the_Event_Logging_Mechanism&amp;diff=12516"/>
		<updated>2026-07-28T15:57:28Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: Created page with &amp;quot;==Synopsis== BCI2000 is able to record asynchronous data, so called Events, that occur during a recording. These events are logged into BCI2000 states, which provide a universal way to store information associated with brain signal sample data.  This page provides an overview over the general concept and implementation of events.  Also, it describes the RefLogger, which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze RefLog...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Synopsis==&lt;br /&gt;
BCI2000 is able to record asynchronous data, so called Events, that occur during a recording. These events are logged into BCI2000 states, which provide a universal way to store information associated with brain signal sample data.&lt;br /&gt;
&lt;br /&gt;
This page provides an overview over the general concept and implementation of events.&lt;br /&gt;
&lt;br /&gt;
Also, it describes the RefLogger, which allows to quantify how reliable event recording is, and shows a simple Matlab script to analyze RefLogger data, together with results from a reference system.&lt;br /&gt;
&lt;br /&gt;
==The Event Logging Mechanism==&lt;br /&gt;
As described elsewhere, BCI2000 processes brain signal data in blocks of fixed size and duration.&lt;br /&gt;
Whenever a block of data has been acquired from the main brain signal source, a time stamp is taken from the computer&#039;s clock.&lt;br /&gt;
Note that this time stamp is taken after the data block has been sampled and transmitted from the hardware.&lt;br /&gt;
Assuming a negligible time delay between sampling of the block&#039;s last sample, and transmission into the computer, we can say that this time stamp represents the time of sampling for the first sample of the following block.&lt;br /&gt;
Knowing this time stamp, and the sampling rate of the brain signal source, we can thus associate each of the block&#039;s samples with a sample time.&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
	<entry>
		<id>https://www.bci2000.org/mediawiki/index.php?title=Contributions:NeusenW4&amp;diff=12515</id>
		<title>Contributions:NeusenW4</title>
		<link rel="alternate" type="text/html" href="https://www.bci2000.org/mediawiki/index.php?title=Contributions:NeusenW4&amp;diff=12515"/>
		<updated>2026-07-27T07:38:03Z</updated>

		<summary type="html">&lt;p&gt;Mellinger: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;==Location==&lt;br /&gt;
http://{{SERVERNAME}}/svn/trunk/src/contrib/SignalSource/NeusenW4&lt;br /&gt;
&lt;br /&gt;
==Versioning==&lt;br /&gt;
===Authors===&lt;br /&gt;
Jintao Li, Frontier Lab for Applied Neurotechnology (https://ant.chenfrontierlab.com), Tianqiao and Chrissy Chen Institute&lt;br /&gt;
&lt;br /&gt;
Jürgen Mellinger, Neurotechcenter&lt;br /&gt;
&lt;br /&gt;
===Source Code Revisions===&lt;br /&gt;
9405: first working version of NeusenW4 source module&lt;br /&gt;
&lt;br /&gt;
==Functional Description==&lt;br /&gt;
This Source module allows BCI2000 to receive electrophysiological data from one single NeusenW device.&lt;br /&gt;
It requires version 4 of the Neuracle API.&lt;br /&gt;
&lt;br /&gt;
==Usage of Device==&lt;br /&gt;
&amp;lt;gallery mode=&amp;quot;packed&amp;quot; widths=800px heights=350px&amp;gt;&lt;br /&gt;
File:Neusenw_picture_1.jpg|Figure 1: NeusenW&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&amp;lt;gallery mode=&amp;quot;packed&amp;quot; widths=800px heights=350px&amp;gt;&lt;br /&gt;
File:Neusenw_picture_2.jpg|Figure 2: Smart Sync Center&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
&amp;lt;gallery mode=&amp;quot;packed&amp;quot; widths=800px heights=350px&amp;gt;&lt;br /&gt;
File:Neusenw_picture_3.jpg|Figure 3: Dongle&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
These are all devices you need to use the Source module: NeusenW amplifier, Smart Sync Center, and the dongle provided by Neuracle. &lt;br /&gt;
Start by connecting the Smart Sync Center to the power source. Then, turn on the amplifier and plug the dongle into the computer. No other software is needed. Make sure the computer and amplifier are connected to the Smart Sync Center through Wi-Fi.&lt;br /&gt;
&amp;lt;gallery mode=&amp;quot;packed&amp;quot; widths=800px heights=350px&amp;gt;&lt;br /&gt;
File:Neusenw_picture_4.jpg|Figure 4: Trigger Box&lt;br /&gt;
&amp;lt;/gallery&amp;gt;&lt;br /&gt;
The trigger box can record a variety of types of events (such as sound, light, program output), with sufficiently high timing accuracy and neurophysiological data synchronously, but it is not supported by the current version of the Source module.&lt;br /&gt;
&lt;br /&gt;
==Setting Up BCI2000==&lt;br /&gt;
Once you compiled BCI2000 correctly and generated the solution, create a batch file to run the NeusenW Source Module. Just copy one of the existing batch files in BCI2000/batch. Then replace the first line beginning with &lt;br /&gt;
 Start executable &lt;br /&gt;
with&lt;br /&gt;
 Start executable NeusenW --local&lt;br /&gt;
.&lt;br /&gt;
&lt;br /&gt;
==Usage Steps==&lt;br /&gt;
Make sure that the device and computer are connecting to Smart Sync Center and the dongle is plugged in before you start BCI2000.&lt;br /&gt;
# Double click the batch file you just created.&lt;br /&gt;
# Click the &amp;quot;Config&amp;quot; button.&lt;br /&gt;
# Set suitable parameters. Detail about these parameters can be found below.&lt;br /&gt;
# Click &amp;quot;Set Config&amp;quot;.&lt;br /&gt;
# Click &amp;quot;Start&amp;quot; to start the experiment.&lt;br /&gt;
&lt;br /&gt;
==Parameters==&lt;br /&gt;
===SourceCh===&lt;br /&gt;
Defines the number of signal channels to be acquired from the NeusenW device given the following constraints. Only certain numbers of channels are supported for specific sampling rates defined by &#039;&#039;SamplingRate&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
When &#039;&#039;SamplingRate&#039;&#039; is not bigger than 1000 Hz (250 Hz/500 Hz/1000 Hz), there is no limit to this parameter. When &#039;&#039;SamplingRate&#039;&#039; is 2000 Hz, &#039;&#039;SourceCh &#039;&#039; cannot be bigger than 32. When &#039;&#039;SamplingRate&#039;&#039; is 4000 Hz, the maximum of &#039;&#039;SourceCh&#039;&#039; is 16. When &#039;&#039;SamplingRate&#039;&#039; is 8000 Hz, the maximum of &#039;&#039;SourceCh&#039;&#039; is 8. When &#039;&#039;SamplingRate&#039;&#039; is 16000 Hz, the maximum of &#039;&#039;SourceCh&#039;&#039; is 4.&lt;br /&gt;
&lt;br /&gt;
If this parameter is set to &amp;quot;auto&amp;quot;, it will automatically be set to the number of available amplifier channels, as is suitable for &#039;&#039;SamplingRate&#039;&#039; settings up to, and including, 1000 Hz.&lt;br /&gt;
&lt;br /&gt;
===SampleBlockSize===&lt;br /&gt;
If you set &#039;&#039;SampleBlockSize&#039;&#039; to &amp;quot;auto&amp;quot;, a data block duration of 100ms will be chosen.&lt;br /&gt;
&lt;br /&gt;
===SamplingRate===&lt;br /&gt;
The NeusenW device supports 7 different sampling rates: 250 Hz, 500 Hz, 1000 Hz, 2000 Hz, 4000 Hz, 8000 Hz, and 16000 Hz. This parameter may limit the value of SourceCh as described above. When set to &amp;quot;auto&amp;quot;, a sampling rate of 1000 Hz will be chosen.&lt;br /&gt;
&lt;br /&gt;
===SourceChGain===&lt;br /&gt;
Use &amp;quot;auto&amp;quot; to have the correct values filled in by the source module.&lt;br /&gt;
&lt;br /&gt;
===SourceChOffset===&lt;br /&gt;
Use &amp;quot;auto&amp;quot;, or a list of length &#039;&#039;SourceCh&#039;&#039; with all zero entries.&lt;br /&gt;
&lt;br /&gt;
===ChannelNames===&lt;br /&gt;
When set to &amp;quot;auto&amp;quot;, channel names will be filled in automatically.&lt;br /&gt;
If &#039;&#039;SourceCh&#039;&#039; is set to 64, EEG 10-20 electrode names from the Neuracle cap will be used.&lt;br /&gt;
Otherwise, generic channel names indicating the sensor type and number will be created.&lt;br /&gt;
&lt;br /&gt;
===SerialNumber===&lt;br /&gt;
The serial number of the desired NeusenW amplifier to connect to, as printed on the device.&lt;br /&gt;
If set to &amp;quot;auto&amp;quot;, and only a single NeusenW amplifier is available, that will be chosen.&lt;br /&gt;
If set to &amp;quot;auto&amp;quot; with more than one NeusenW amplifiers available, an error message will be issued.&lt;br /&gt;
&lt;br /&gt;
==See also==&lt;br /&gt;
[[User Reference:DataIOFilter]], [[Programming Reference:GenericADC Class]]&lt;br /&gt;
[[Category:Filters]][[Category:Data Acquisition]]&lt;/div&gt;</summary>
		<author><name>Mellinger</name></author>
	</entry>
</feed>