PacketCodec

Wire codec for BrainAccess stream packets and the channel-config blob.

PORTED from the legacy Utils.processData / convertConfigToByteArray / convertInt24ToInt32. The bit-packing + delta-decode math is preserved byte-for-byte; the only behavioural change on the decode side is performance: the legacy code grew FloatArrays with += (realloc+copy per value, O(n^2) per packet at up to 1 kHz x 32 ch). Here every output buffer is pre-sized and index-written. The protocol golden-vector tests gate this against a verbatim reference transcription of the legacy decoder. The config encoder deliberately departs from the legacy one — see encodeConfig.

The decoder is dependency-free and side-effect-free apart from advancing DecodeState. It must NEVER be "improved" without re-passing the golden vectors (see PROTOCOL.md).

Functions

Link copied to clipboard

Convert a 24-bit integer to 32-bit, preserving sign (legacy convertInt24ToInt32).

Link copied to clipboard
fun decode(packet: ByteArray, config: DeviceConfig, state: DecodeState, contactOut: (BooleanArray) -> Unit? = null, hardware: DeviceHardware = DeviceHardware.legacyDefault(config.model), out: (EegFrame) -> Unit): Boolean

Decode one stream packet into typed EegFrames, advancing state. hardware decides the footer layout (accelerometer / gyroscope blocks present or not); it defaults to the V2 board of the config's model, which is every device without a readable Model Number. Returns true on success, false if the packet was malformed (matching the legacy broad-catch behaviour). See decodeRaw for the underlying legacy-positional output.

Link copied to clipboard
fun decodeRaw(packet: ByteArray, config: DeviceConfig, state: DecodeState, contactOut: (BooleanArray) -> Unit? = null, hardware: DeviceHardware = DeviceHardware.legacyDefault(config.model), out: (values: FloatArray, isDummy: Boolean) -> Unit): Boolean

Decode one stream packet, emitting the legacy positional layout per sample: [ch_0 … ch_{A-1}, (accX, accY, accZ if accel), (gyroX, gyroY, gyroZ if gyro), sampleIndex, dummyFlag, battery], where dummy (gap-fill) samples use channelCount leading zeros and dummyFlag = 1. IMU values are raw int16 counts. This is the language-neutral shape captured by the golden vectors.

Link copied to clipboard

Encode a DeviceConfig into the channel-config characteristic blob (PROTOCOL.md §4), the layout the firmware parses and the vendor C SDK writes: