Starting with version 0.4.0 the GUI features a plugin architecture, allowing it to be expanded with external modules without the need to recompile the entire program every time a new feature is added. Currently, the GUI supports the following four types of plugins:
- Processor plugins. These plugins are used to create processors that can be placed in the signal chain. This kind is the most extensive, as processors can be either sources, sinks, filters or utilities. A processor plugin can also be a visualizer, like the LfpViewer, and can feature custom editors and viewport components.
- DataThread plugins. These plugins are used by the SourceNode core processor to asynchronously access source data, usually from acquisition hardware, allowing you to implement controller nodes for multiple different hardware devices.
- RecordEngine plugins. Record Engines are the classes used by the record subsystem to store data to disk. They allow you to define new file formats for storing recorded data.
- FileSource plugins. The inverse of the Record Engines, these plugins are used by the File Reader to load different file formats for data replay.
The main elements of the plugin architecture are the library files, the Plugin Manager and the shared services that the GUI offers.
Library files are the compiled binaries that contain the different plugins. A single library file can contain multiple plugins of different kinds, exporting a set of methods that allow the Plugin Manager in the GUI to inspect its contents and know how to instantiate the different plugins.
All library files are placed into a "plugins" folder located next to the binary executable and are loaded at startup. If the GUI console is active, it will show all the files it's trying to load, the result of the operation, and the number of plugins each library contains if load its succesful.
There are two main limitations in the use of external library files:
- The library must have been built against a version of the code with the same plugin API. Libraries contain a plugin API version field that is checked against the one of the running GUI, and they will not be loaded unless the API versions match.
- The library must have been built for the same OS and with the same compiler as the GUI. Since the libraries make use of many C++ classes inside the GUI code and viceversa, both must be binary compatible, which means they must have been compiled by the same compiler. Different versions of the same compiler might or might not be compatible between them, so this should be tested on a case-by-case basis.
The PluginManager is the class responsible for loading and managing plugins. It maintains a list of all loaded libraries and the plugins they contain, and can be accessed via AcessClass to instantiate a specific plugin. It also provides search capabilities to be able to load plugins from signal chains generated in configurations with different available plugins.
In addition to the base classes from which the plugins are derived, the GUI offers a series of methods that allow interaction outside the scope of the plugin. Some examples include: showing messages through the status bar, refreshing the signal chain, or starting/stopping acquisition or recording. All of these functions can be accessed through the CoreServices namespace, available to all plugins. All JUCE classes are also available for the plugins to use, and are exported through the GUI itself.
To ease plugin creation, all headers that might be needed by any plugin are located inside header files available at Source/Plugins/Headers. A plugin must not use headers outside the ones in that directory. The available headers are:
- ProcessorHeaders.h contains all headers needed to create a Processor, which are derived from GenericProcessor.
- EditorHeaders.h contains all headers needed to create an Editor for a Processor plugin.
- VisualizerEditorHeaders.h If the processor is a Visualizer, this header should be used for the editor instead of the generic EditorHeaders.
- VisualizerWindowHeaders.h contains the headers needed by Visualizer windows and graphic canvas.
- SpikeLib.h contains helper methods to allow working with spikes, including functions to pack them into standard data structures as well a specific visualization methods.
- SerialLib.h contains some helper classes and methods to use serial port communications.
- DataThread.h contains all headers needed to create a DataThread class and plugin.
- RecordingLib.h contains all headers needed to create a Record Engine class and plugin.
- FileSourceHeaders.h contains all headers needed to create a FileSource class and plugin.
- PluginInfo.h contains headers related to the plugin information structures and methods. Should only be needed by OpenEphysLib.cpp
There are a number of rules that all plugins must follow:
- A plugin should be as self-contained as possible.
- A plugin must not depend on other plugins to work.
- Plugins must not make use of any GUI Source headers outside of those provided in Source/Plugins/Headers.
- Plugins must not acquire by any means, nor use, direct pointers to other processors or any other class or code in the GUI outside the scope of the plugin itself.
- As such, any external functionality must be called via the CoreServices namespace. Extra information can be passed down the chain by creating specific event channels, through the ChannelExtraData class.
- Communication between processors or parts of the GUI outside the available methods is not allowed.
Outside those rules, there are no further limitations on what the plugin can do. Plugins are free to make use of external libraries and any other means needed to achieve their functionality. Although it is highly recommended that a plugin can be compiled on all three platforms, nothing prevents a library from being specific to a single OS, as long as the build files take that into account.