Ring Buffer (Circular Buffer)
Pure Nim implementation of a lock-free ring buffer optimized for audio streaming.
Features:
- Fixed capacity at compile time (no dynamic allocation)
- Lock-free single-producer/single-consumer (SPSC)
- Audio streaming optimized
- Configurable overwrite behavior
- Generic type support via templates
- ⚠️ BREAKING CHANGE v0.9.1: Capacity N must be a power of 2 (2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, etc.)
Performance:
- Uses bitwise AND instead of modulo (1 cycle vs 12-30 cycles on ARM Cortex-M7)
- Compile-time validation ensures power-of-2 sizes
Use Cases:
- Audio sample buffering (delay lines, loopers)
- Inter-thread communication
- Streaming data processing
- Real-time data capture
Memory:
- Stack allocated only
- Size known at compile time
- Zero runtime overhead
Migration from v0.9.0:
- If you used non-power-of-2 sizes (e.g., 100, 500), round up to next power of 2
- Example: RingBuffer[100, float32] → RingBuffer[128, float32]
- Example: RingBuffer[500, float32] → RingBuffer[512, float32]
Usage:
import nimphea_ringbuffer # Create ring buffer for 1024 samples (MUST be power of 2!) var buffer: RingBuffer[1024, float32] buffer.init() # Write samples discard buffer.write(0.5) discard buffer.write(0.7) # Read samples var sample: float32 if buffer.read(sample): echo sample # 0.5 # Block operations var samples: array[64, float32] let written = buffer.writeBlock(samples) let readCount = buffer.readBlock(samples)
Types
OverwriteMode = enum OVERWRITE_OLDEST, ## Overwrite oldest data (circular) REJECT_NEW ## Reject new writes when full
- Behavior when buffer is full Source Edit
RingBuffer[N; T] = object
-
Lock-free circular buffer for audio streaming
⚠️ CRITICAL: N must be a power of 2 (2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, etc.) This is enforced at compile time for performance (bitwise AND vs modulo).
Generic Parameters:
- N - Capacity (MUST be power of 2, checked at compile time)
- T - Element type (typically float32 for audio)
Fields:
- data - Fixed array storage
- writeIdx - Write position
- readIdx - Read position
- mode - Overwrite behavior
Procs
proc `$`[N: static int; T](this: RingBuffer[N, T]): string
-
Convert ring buffer to string representation (for debugging)
Example:
echo buffer # "RingBuffer[1024, float32](available=128/1024)"
Source Edit proc available[N: static int; T](this: RingBuffer[N, T]): int {.inline.}
-
Get number of samples available to read
Returns: Number of readable samples
Example:
echo buffer.available() # e.g., 128
Source Edit proc capacity[N: static int; T](this: RingBuffer[N, T]): int {.inline.}
-
Get maximum capacity
Returns: Maximum capacity (compile-time constant N)
Source Edit proc clear[N: static int; T](this: var RingBuffer[N, T])
-
Clear all data from the buffer
Example:
buffer.clear()
Source Edit proc init[N: static int; T](this: var RingBuffer[N, T]; mode: OverwriteMode = OVERWRITE_OLDEST)
-
Initialize the ring buffer
Parameters:
- mode - Overwrite behavior (default: OVERWRITE_OLDEST)
proc isEmpty[N: static int; T](this: RingBuffer[N, T]): bool {.inline.}
-
Check if buffer is empty
Returns: true if no data available
Source Edit proc isFull[N: static int; T](this: RingBuffer[N, T]): bool {.inline.}
-
Check if buffer is full
Returns: true if no space remaining
Source Edit proc peek[N: static int; T](this: RingBuffer[N, T]; value: var T; offset: int = 0): bool {.inline.}
-
Peek at a value without removing it
Parameters:
- value - Output parameter to receive the value
- offset - Offset from current read position (default: 0)
Returns: true if successful, false if offset out of range
Example:
var sample: float32 if buffer.peek(sample, 5): echo "5 samples ahead: ", sample
Source Edit proc read[N: static int; T](this: var RingBuffer[N, T]; value: var T): bool {. inline.}
-
Read a single value from the buffer
Parameters:
- value - Output parameter to receive the value
Returns: true if successful, false if empty
Example:
var sample: float32 if buffer.read(sample): echo sample
Source Edit proc readBlock[N: static int; T](this: var RingBuffer[N, T]; data: var openArray[T]): int
-
Read multiple values from the buffer
Parameters:
- data - Array to fill with read values
Returns: Number of values actually read
Example:
var samples: array[64, float32] let count = buffer.readBlock(samples) echo "Read ", count, " samples"
Source Edit proc remaining[N: static int; T](this: RingBuffer[N, T]): int {.inline.}
-
Get number of free slots for writing
Returns: Number of writable slots
Example:
echo buffer.remaining() # e.g., 384
Source Edit proc write[N: static int; T](this: var RingBuffer[N, T]; value: T): bool {. inline.}
-
Write a single value to the buffer
Parameters:
- value - Value to write
Returns: true if successful, false if full (REJECT_NEW mode)
Example:
if not buffer.write(0.5): echo "Buffer full"
Source Edit proc writeBlock[N: static int; T](this: var RingBuffer[N, T]; data: openArray[T]): int
-
Write multiple values to the buffer
Parameters:
- data - Array of values to write
Returns: Number of values actually written
Example:
var samples: array[64, float32] # ... fill samples ... let written = buffer.writeBlock(samples) echo "Wrote ", written, " samples"
Source Edit