Lesson 27 of 30 advanced 5 min read

Before this:Decimation & resampling

Real-time processing & buffering

Key takeaways Live DSP must consume samples as fast as they arrive — forever. It does this in blocks (efficient, but each block adds latency), passed between producer and consumer through a ring buffer that absorbs timing jitter. The governing rule: average throughput must meet or beat the sample rate, or the buffer overruns and samples are lost. This is the engineering that turns the algorithms of this module into a scanner that keeps up.

Every prior lesson assumed the samples were simply there. This one is about the relentless clock behind them: an SDR delivers millions of samples per second, every second, and the pipeline must never fall behind. It builds on decimation — the first tool for coping with the firehose.

The firehose problem

A capture at 2.4 MS/s of complex I/Q is 2.4 million samples arriving every second, and they never stop. Unlike an offline file you can process at leisure, a live stream imposes a hard deadline: on average you must finish each second’s work within that second. The single most important defence is to decimate to a narrow channel rate as early as possible, shrinking the stream before the expensive stages ever see it.

Block processing

You rarely process one sample at a time. Instead samples are gathered into blocks (a few hundred to a few thousand) and handled together. Blocks amortize function-call and loop overhead, keep data in cache, and suit batch algorithms like the FFT. The cost is latency: the system must wait for a block to fill before it can start, so results always trail real time by at least one block.

bigger blocks  -> higher throughput, higher latency
smaller blocks -> lower latency, more per-block overhead

Choosing block size is choosing a point on that curve — small enough to feel responsive, large enough to stay efficient.

Ring buffers: decoupling producer and consumer

The SDR (producer) and the DSP (consumer) don’t run in perfect lockstep — the OS schedules them unevenly, and processing time varies block to block. A ring buffer (a circular buffer) sits between them to absorb that jitter. It’s a fixed array with a write pointer where new samples land and a read pointer where they’re consumed; both wrap around the end back to the start.

write (producer) read buffered
A ring buffer: the producer writes ahead, the consumer reads behind, and the arc between them is the cushion that absorbs timing jitter.

The gap between the pointers is slack that soaks up short bursts. In GopherTrunk this decoupling is often expressed with Go channels between concurrent stages — the same producer/consumer pattern with the buffer built in.

Overruns: the real-time failure

The buffer has a limit. If the consumer is persistently too slow, the write pointer catches the read pointer — an overrun — and incoming samples are dropped. Dropped samples are gaps in the signal, and gaps break symbol recovery and framing downstream. A buffer can hide a momentary slowness, but it cannot fix a consumer whose average speed is below the sample rate — that only ends one way. The fixes are all about average throughput: decimate early, keep the hot loops tight, and mind the number format, which the next lesson takes up.

Quick check: a ring buffer prevents dropped samples only if…

Recap

  • Live DSP faces a hard deadline: keep up with a never-ending sample stream.
  • Block processing is efficient but adds latency — a throughput-vs-delay tradeoff.
  • A ring buffer decouples producer and consumer, absorbing timing jitter.
  • Buffers hide momentary slowness only; sustained under-speed causes overruns and lost samples.

Next up: the walkthrough tying every stage together — DSP in GopherTrunk.

Frequently asked questions

Why is DSP done in blocks instead of one sample at a time?

Processing a whole block of samples at once amortizes per-call overhead, keeps data in cache, and lets algorithms like the FFT work on a batch, so it is far more efficient than handling single samples. The tradeoff is latency: the system must wait to collect a full block before it can start, so bigger blocks mean higher throughput but longer delay before results appear.

What is a ring buffer and why is it used in real-time DSP?

A ring buffer is a fixed-size array treated as if its ends were joined, with a write pointer where new samples arrive and a read pointer where the processor consumes them. It lets a fast producer and a slower consumer run at their own paces without copying or reallocating memory, absorbing short bursts. If the producer laps the consumer the buffer overruns and samples are lost — the classic real-time failure.