Setting Up the EEG Dev Environment

I needed a clean C++ development environment to start building the real-time EEG visualizer. The goal: acquire brain signals from the OpenBCI Cyton board, process them, and render scrolling waveforms on screen — all with minimal dependencies and no package manager bloat.

Here’s what I landed on and how it all fits together.

The stack

After evaluating several options, I settled on four libraries that cover acquisition, rendering, audio, and signal processing — all MIT/BSD/public-domain licensed with zero runtime dependencies beyond macOS system frameworks.

LibraryPurposeSizeNotes
BrainFlowEEG acquisition + DSP~5MB dylibsAlso handles filtering, FFT, and board abstraction
SokolGPU rendering (Metal)6 headersImmediate-mode, header-only, perfect for waveforms
miniaudioAudio I/O (CoreAudio)1 headerFor future sonification work
KissFFTFFT (real-to-complex)~20KBLightweight alternative to BrainFlow’s built-in FFT

Currently, the environment runs on macOS ARM64 with Apple Clang 16 and CMake 4.2. I’m using Ninja for faster builds. I will soon test the same environment on Linux.

BrainFlow — built from source

Pre-built binaries exist on the BrainFlow GitHub releases, but building from source gives you the CMake config files that find_package() needs. It takes about two minutes.

git clone https://github.com/brainflow-dev/brainflow.git
cd brainflow && mkdir build && cd build
cmake -DCMAKE_INSTALL_PREFIX=~/dev/brainflow/installed \
      -DCMAKE_BUILD_TYPE=Release -G Ninja ..
ninja && ninja install

The install produces libBoardController.dylib, libDataHandler.dylib, and the C++ headers (board_shim.h, data_filter.h, etc.) — everything needed to stream from the Cyton or run a synthetic board for testing.

Sokol — header-only rendering

Sokol is a set of single-file headers for cross-platform graphics. On macOS it uses Metal as the backend. No build step — just copy the headers and create one implementation file compiled as Objective-C:

// sokol_impl.c — compiled once with -x objective-c
#define SOKOL_IMPL
#define SOKOL_METAL
#include "sokol_app.h"
#include "sokol_gfx.h"
#include "sokol_glue.h"
#include "sokol_log.h"
#include "sokol_time.h"
#include "sokol_gl.h"

This gets linked against Metal, MetalKit, Cocoa, and QuartzCore frameworks.

miniaudio and KissFFT

Both follow the same pattern: single-header libraries with a small implementation file.

miniaudio auto-detects CoreAudio on macOS, so no extra linking is needed. KissFFT provides a clean real-to-complex FFT in about 500 lines of C — useful for custom spectral analysis beyond what BrainFlow’s DataFilter offers.

The build system

Everything ties together in a single CMakeLists.txt. Each library becomes a small static lib, and the main app links against all of them plus BrainFlow’s dynamic libraries. A single ninja command builds everything from scratch.

The project tree looks like this:

eeg-viz/
├── CMakeLists.txt
├── lib/
│   ├── sokol/          ← 6 headers + sokol_impl.c
│   ├── miniaudio/      ← miniaudio.h + miniaudio_impl.c
│   └── kissfft/        ← kiss_fft.h/.c + kiss_fftr.h/.c
├── src/
│   └── main.cpp
└── build/

First smoke test

For the initial test, I wrote a minimal app that streams from BrainFlow’s synthetic board (no hardware needed), buffers the last 1000 samples per channel into circular buffers, and draws 8 colored scrolling waveforms using Sokol’s immediate-mode GL layer.

It works. Eight channels of synthetic EEG data scrolling across a dark window at the board’s native sample rate. Switching to the real Cyton hardware later is a two-line change: set the board ID to CYTON_BOARD and point it at the serial port.

What’s next

The immediate next steps are adding Dear ImGui for UI controls (there’s a sokol_imgui.h bridge that makes this trivial), ImPlot for proper scrolling plots and heatmaps, and eventually ONNX Runtime for deploying trained models. But for now, the foundation is solid — clean C++17, no package managers, single build command.