BCI · Stream a neural window2 / 2

Stream-transcribe a sliding window over a neural signal

Example on GitHub(packages/sdk/examples/bci/bci-filesystem-streaming.ts)

The previous lesson decoded a neural file all at once. This one feeds it chunk by chunk to simulate a live stream.

bciTranscribeStream opens a duplex session. Writing bytes into session.write(chunk) feeds the sliding window, and iterating the session reads the decoded text as the window advances. The session is fully streaming on both sides: writes don't have to wait for the previous decode to finish, and the reader doesn't have to wait for a full file.

The first step is loading the BCI model. whisperConfig and bciConfig tell the engine which day of neural data we're transcribing:

const modelId = await loadModel({
  modelSrc: BCI_WINDOWED,
  modelConfig: {
    whisperConfig: { language: "en", n_threads: 4, temperature: 0.0 },
    bciConfig: { day_idx: 1 },
  },
});

The session needs one behavior change before it can stream useful updates. Set emit: "delta" so each read contains only the new tokens instead of replaying the full transcript. The session options would look like this:

const session = await bciTranscribeStream({ modelId, emit: "delta" });

Sequential read-then-write would deadlock, so the consume task runs as an IIFE in parallel with the write loop, with await consume after session.end(). Writing in 64KB chunks looks like:

const consume = (async () => {
  for await (const text of session) {
    process.stdout.write(text);
  }
})();

const data = readFileSync(neuralFilePath);
for (let offset = 0; offset < data.length; offset += CHUNK_SIZE) {
  const chunk = data.subarray(offset, offset + CHUNK_SIZE);
  session.write(chunk);
  await new Promise((resolve) => setTimeout(resolve, 10));
}
session.end();
await consume;

await unloadModel({ modelId });

emit: "delta" is the mode that streams the running transcript (each iteration is the new text since the last read). The alternative mode emits the full transcript on every read.

Note: a 64KB chunk is a reasonable starting size for the example, but a real device driver usually has a buffer of its own. If your source is producing frames continuously, the write loop forwards each frame as it comes in.

Questions

Question 1 of 2

What happens if the consume task reads the sliding window only after the write loop finishes?

Question 2 of 2

How does emit: 'delta' change each iteration's output?

index.ts
Loading editor...

Run your code, check your answer, or ask a question. It all shows up here.