Remembering What Just Happened
The geophone sitting on my basement floor records continuously. But here’s the problem: by the time something interesting happens — a truck rumbles past, the furnace kicks on, an actual seismic event — the interesting part has already started. If you only begin recording when you detect something, you’ve missed the onset.
Seismologists solved this decades ago with a structure that seems paradoxical at first: a buffer that forgets. You write samples in a circle, newest overwriting oldest, maintaining a rolling window of the recent past. When a trigger fires, you already have the pre-event data sitting there waiting.
class RingBuffer<T> {
private buf: T[];
private pos = 0;
constructor(private size: number) {
this.buf = new Array(size);
}
push(val: T): void {
this.buf[this.pos] = val;
this.pos = (this.pos + 1) % this.size;
}
snapshot(): T[] {
return [...this.buf.slice(this.pos), ...this.buf.slice(0, this.pos)];
}
}
The modulo arithmetic does all the work. Position wraps from size - 1 back to 0, and snapshot() unwinds the circular layout into chronological order. No shifting, no copying on every insert — just one assignment and one increment.
OCaml makes the immutable version interesting because you’d think “overwrite in a circle” requires mutation. But you can model it as a zipper:
type 'a ring = { before: 'a list; after: 'a list; cap: int }
let drop_oldest n xs =
let rec loop n xs =
if n <= 0 then xs else
match xs with [] -> [] | _ :: rest -> loop (n - 1) rest
in
loop n xs
let push x { before; after; cap } =
let window = List.rev before @ after @ [x] in
let overflow = max 0 (List.length window - cap) in
{ before = List.rev (drop_oldest overflow window); after = []; cap }
let to_list { before; after; _ } = List.rev before @ after
This trades the O(1) insert for immutability — you get persistence, the ability to keep old snapshots around without defensive copying. Real seismograph software uses the mutable version for the hot path, but the functional model clarifies what a ring buffer actually is: a fixed-size window sliding over a stream.
My geophone setup uses a 30-second ring at 100 samples per second — 3,000 floats, about 24 KB. When the amplitude crosses a threshold, I dump the buffer plus the next 60 seconds to disk. The furnace blower’s 3.2 Hz signature? I only noticed it because the pre-trigger data showed the oscillation ramping up before it stabilized.