Programming Reference:BufferedADC Class
BufferedADC is a base class for signal source components (ADCs). Unlike the more GenericADC class, it provides buffering for data packets read from the ADC. This helps avoiding data loss during intervals in which BCI2000 processing time exceeds the physical duration of a data block.
In addition to the main thread, BufferedADC maintains a separate thread in which data acquisition is running, and internally uses synchronizers to avoid race conditions between these two threads. In most cases, descendants of BufferedADC need not be aware of this multithreading because BufferedADC interface functions are not executed concurrently. An exception to this is when BufferedADC::OnProcess() is implemented, and accesses resources common to both threads.
To interface with an ADC device, a descendant of BufferedADC provides implementations of the following functions:
As is common for BCI2000 filters, the constructor defines configuration parameters.
OnPreflight( SignalProperties& output ) const
This is a forward of the standard Preflight() function as documented for BCI2000 filters, except that it takes a single output signal properties argument, and none for input. The output argument should be modified to match the ADC's number of channels by calling output.SetChannels(), and the sample block size by calling output.SetElements(). Also, Preflight() should check whether the device will be able to acquire data using the current parameter configuration in conjunction with its current physical state (e.g., physically connected, switched on, etc.). Ideally, this is done by configuring the device, and acquiring a few samples.
OnInitialize( const SignalProperties& output )
This is a forward of the standard Initialize() function, except that it only takes a single output properties argument. From here, do all initialization except actually starting data acquisition.
This function is called when the main thread enters the GenericADC::Process() function, before the main thread waits for the acquisition thread to deliver data. In most cases, you will not implement this function. A notable exception is when your code needs to read from state variables, e.g. to set the amplifier's digital output.
This function may run concurrently with DoAcquire(), so you may need to serialize access to common resources with a synchronizer to avoid race conditions. Note that improper synchronization may force both threads into synchrony, such that the benefit of the multi-threaded buffering approach will be lost. Modern amplifier APIs allow for concurrent writing to an amplifier's output while acquiring data, so you will likely not need an additional synchronizer.
This function is called when the acquisition thread is started, and should put the ADC device into acquisition mode.
This function is called before the acquisition thread is terminated, and should put the ADC device into idling mode.
DoAcquire( GenericSignal& output )
This function is called repeatedly while the acquisition thread is running. It should block until data becomes available, and then copy data from the ADC device API's buffer into the GenericSignal object provided that is proviced as an argument.
Allows to choose the size of the buffer, in sample blocks when no unit is given, or in seconds when followed with an "s". Not that the minimum buffer size corresponds to the duration of two sample blocks.
This is the system-wide acquisition time stamp associated with each data block. After DoAcquire() has finished, BufferedADC obtains a system time stamp with millisecond accuracy, and stores it in the sample buffer. As soon as a buffered data block is released into the main processing thread, the SourceTime state is assigned the block's associated time stamp from the buffer. This ensures that a data block's time stamp always matches the time of its physical acquisition, rather than its release into the processing chain.