|
@@ -8,56 +8,76 @@ namespace rack { |
|
|
|
|
|
|
|
|
/** A simple cyclic buffer. |
|
|
/** A simple cyclic buffer. |
|
|
S must be a power of 2. |
|
|
S must be a power of 2. |
|
|
push() is constant time O(1) |
|
|
|
|
|
|
|
|
Thread-safe for single producers and consumers. |
|
|
*/ |
|
|
*/ |
|
|
template <typename T, int S> |
|
|
|
|
|
|
|
|
template <typename T, size_t S> |
|
|
struct RingBuffer { |
|
|
struct RingBuffer { |
|
|
T data[S]; |
|
|
T data[S]; |
|
|
int start = 0; |
|
|
|
|
|
int end = 0; |
|
|
|
|
|
|
|
|
size_t start = 0; |
|
|
|
|
|
size_t end = 0; |
|
|
|
|
|
|
|
|
int mask(int i) const { |
|
|
|
|
|
return i & (S - 1); |
|
|
|
|
|
|
|
|
size_t mask(size_t i) const { |
|
|
|
|
|
return i & (S - 1); |
|
|
} |
|
|
} |
|
|
void push(T t) { |
|
|
void push(T t) { |
|
|
int i = mask(end++); |
|
|
|
|
|
|
|
|
size_t i = mask(end++); |
|
|
data[i] = t; |
|
|
data[i] = t; |
|
|
} |
|
|
} |
|
|
|
|
|
void pushBuffer(const T *t, int n) { |
|
|
|
|
|
size_t i = mask(end); |
|
|
|
|
|
size_t e1 = i + n; |
|
|
|
|
|
size_t e2 = (e1 < S) ? e1 : S; |
|
|
|
|
|
memcpy(&data[i], t, sizeof(T) * (e2 - i)); |
|
|
|
|
|
if (e1 > S) { |
|
|
|
|
|
memcpy(data, &t[S - i], sizeof(T) * (e1 - S)); |
|
|
|
|
|
} |
|
|
|
|
|
end += n; |
|
|
|
|
|
} |
|
|
T shift() { |
|
|
T shift() { |
|
|
return data[mask(start++)]; |
|
|
return data[mask(start++)]; |
|
|
} |
|
|
} |
|
|
|
|
|
void shiftBuffer(T *t, size_t n) { |
|
|
|
|
|
size_t i = mask(start); |
|
|
|
|
|
size_t s1 = i + n; |
|
|
|
|
|
size_t s2 = (s1 < S) ? s1 : S; |
|
|
|
|
|
memcpy(t, &data[i], sizeof(T) * (s2 - i)); |
|
|
|
|
|
if (s1 > S) { |
|
|
|
|
|
memcpy(&t[S - i], data, sizeof(T) * (s1 - S)); |
|
|
|
|
|
} |
|
|
|
|
|
start += n; |
|
|
|
|
|
} |
|
|
void clear() { |
|
|
void clear() { |
|
|
start = end; |
|
|
start = end; |
|
|
} |
|
|
} |
|
|
bool empty() const { |
|
|
bool empty() const { |
|
|
return start >= end; |
|
|
|
|
|
|
|
|
return start == end; |
|
|
} |
|
|
} |
|
|
bool full() const { |
|
|
bool full() const { |
|
|
return end - start >= S; |
|
|
|
|
|
|
|
|
return end - start == S; |
|
|
} |
|
|
} |
|
|
int size() const { |
|
|
|
|
|
|
|
|
size_t size() const { |
|
|
return end - start; |
|
|
return end - start; |
|
|
} |
|
|
} |
|
|
int capacity() const { |
|
|
|
|
|
|
|
|
size_t capacity() const { |
|
|
return S - size(); |
|
|
return S - size(); |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
/** A cyclic buffer which maintains a valid linear array of size S by keeping a copy of the buffer in adjacent memory. |
|
|
/** A cyclic buffer which maintains a valid linear array of size S by keeping a copy of the buffer in adjacent memory. |
|
|
S must be a power of 2. |
|
|
S must be a power of 2. |
|
|
push() is constant time O(2) relative to RingBuffer |
|
|
|
|
|
|
|
|
Thread-safe for single producers and consumers? |
|
|
*/ |
|
|
*/ |
|
|
template <typename T, int S> |
|
|
|
|
|
|
|
|
template <typename T, size_t S> |
|
|
struct DoubleRingBuffer { |
|
|
struct DoubleRingBuffer { |
|
|
T data[S*2]; |
|
|
T data[S*2]; |
|
|
int start = 0; |
|
|
|
|
|
int end = 0; |
|
|
|
|
|
|
|
|
size_t start = 0; |
|
|
|
|
|
size_t end = 0; |
|
|
|
|
|
|
|
|
int mask(int i) const { |
|
|
|
|
|
|
|
|
size_t mask(size_t i) const { |
|
|
return i & (S - 1); |
|
|
return i & (S - 1); |
|
|
} |
|
|
} |
|
|
void push(T t) { |
|
|
void push(T t) { |
|
|
int i = mask(end++); |
|
|
|
|
|
|
|
|
size_t i = mask(end++); |
|
|
data[i] = t; |
|
|
data[i] = t; |
|
|
data[i + S] = t; |
|
|
data[i + S] = t; |
|
|
} |
|
|
} |
|
@@ -68,15 +88,15 @@ struct DoubleRingBuffer { |
|
|
start = end; |
|
|
start = end; |
|
|
} |
|
|
} |
|
|
bool empty() const { |
|
|
bool empty() const { |
|
|
return start >= end; |
|
|
|
|
|
|
|
|
return start == end; |
|
|
} |
|
|
} |
|
|
bool full() const { |
|
|
bool full() const { |
|
|
return end - start >= S; |
|
|
|
|
|
|
|
|
return end - start == S; |
|
|
} |
|
|
} |
|
|
int size() const { |
|
|
|
|
|
|
|
|
size_t size() const { |
|
|
return end - start; |
|
|
return end - start; |
|
|
} |
|
|
} |
|
|
int capacity() const { |
|
|
|
|
|
|
|
|
size_t capacity() const { |
|
|
return S - size(); |
|
|
return S - size(); |
|
|
} |
|
|
} |
|
|
/** Returns a pointer to S consecutive elements for appending. |
|
|
/** Returns a pointer to S consecutive elements for appending. |
|
@@ -86,16 +106,16 @@ struct DoubleRingBuffer { |
|
|
T *endData() { |
|
|
T *endData() { |
|
|
return &data[mask(end)]; |
|
|
return &data[mask(end)]; |
|
|
} |
|
|
} |
|
|
void endIncr(int n) { |
|
|
|
|
|
int e = mask(end); |
|
|
|
|
|
int e1 = e + n; |
|
|
|
|
|
int e2 = min(e1, S); |
|
|
|
|
|
|
|
|
void endIncr(size_t n) { |
|
|
|
|
|
size_t e = mask(end); |
|
|
|
|
|
size_t e1 = e + n; |
|
|
|
|
|
size_t e2 = (e1 < S) ? e1 : S; |
|
|
// Copy data forward |
|
|
// Copy data forward |
|
|
memcpy(data + S + e, data + e, sizeof(T) * (e2 - e)); |
|
|
|
|
|
|
|
|
memcpy(&data[S + e], &data[e], sizeof(T) * (e2 - e)); |
|
|
|
|
|
|
|
|
if (e1 > S) { |
|
|
if (e1 > S) { |
|
|
// Copy data backward from the doubled block to the main block |
|
|
// Copy data backward from the doubled block to the main block |
|
|
memcpy(data, data + S, sizeof(T) * (e1 - S)); |
|
|
|
|
|
|
|
|
memcpy(data, &data[S], sizeof(T) * (e1 - S)); |
|
|
} |
|
|
} |
|
|
end += n; |
|
|
end += n; |
|
|
} |
|
|
} |
|
@@ -105,7 +125,7 @@ struct DoubleRingBuffer { |
|
|
const T *startData() const { |
|
|
const T *startData() const { |
|
|
return &data[mask(start)]; |
|
|
return &data[mask(start)]; |
|
|
} |
|
|
} |
|
|
void startIncr(int n) { |
|
|
|
|
|
|
|
|
void startIncr(size_t n) { |
|
|
start += n; |
|
|
start += n; |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
@@ -114,17 +134,18 @@ struct DoubleRingBuffer { |
|
|
The linear array of S elements are moved back to the start of the block once it outgrows past the end. |
|
|
The linear array of S elements are moved back to the start of the block once it outgrows past the end. |
|
|
This happens every N - S pushes, so the push() time is O(1 + S / (N - S)). |
|
|
This happens every N - S pushes, so the push() time is O(1 + S / (N - S)). |
|
|
For example, a float buffer of size 64 in a block of size 1024 is nearly as efficient as RingBuffer. |
|
|
For example, a float buffer of size 64 in a block of size 1024 is nearly as efficient as RingBuffer. |
|
|
|
|
|
Not thread-safe. |
|
|
*/ |
|
|
*/ |
|
|
template <typename T, int S, int N> |
|
|
|
|
|
|
|
|
template <typename T, size_t S, size_t N> |
|
|
struct AppleRingBuffer { |
|
|
struct AppleRingBuffer { |
|
|
T data[N]; |
|
|
T data[N]; |
|
|
int start = 0; |
|
|
|
|
|
int end = 0; |
|
|
|
|
|
|
|
|
size_t start = 0; |
|
|
|
|
|
size_t end = 0; |
|
|
|
|
|
|
|
|
void returnBuffer() { |
|
|
void returnBuffer() { |
|
|
// move end block to beginning |
|
|
// move end block to beginning |
|
|
// may overlap, but that's okay |
|
|
|
|
|
int s = size(); |
|
|
|
|
|
|
|
|
// may overlap, but memmove handles that correctly |
|
|
|
|
|
size_t s = size(); |
|
|
memmove(data, &data[start], sizeof(T) * s); |
|
|
memmove(data, &data[start], sizeof(T) * s); |
|
|
start = 0; |
|
|
start = 0; |
|
|
end = s; |
|
|
end = s; |
|
@@ -139,20 +160,20 @@ struct AppleRingBuffer { |
|
|
return data[start++]; |
|
|
return data[start++]; |
|
|
} |
|
|
} |
|
|
bool empty() const { |
|
|
bool empty() const { |
|
|
return start >= end; |
|
|
|
|
|
|
|
|
return start == end; |
|
|
} |
|
|
} |
|
|
bool full() const { |
|
|
bool full() const { |
|
|
return end - start >= S; |
|
|
|
|
|
|
|
|
return end - start == S; |
|
|
} |
|
|
} |
|
|
int size() const { |
|
|
|
|
|
|
|
|
size_t size() const { |
|
|
return end - start; |
|
|
return end - start; |
|
|
} |
|
|
} |
|
|
int capacity() const { |
|
|
|
|
|
|
|
|
size_t capacity() const { |
|
|
return S - size(); |
|
|
return S - size(); |
|
|
} |
|
|
} |
|
|
/** Returns a pointer to S consecutive elements for appending, requesting to append n elements. |
|
|
/** Returns a pointer to S consecutive elements for appending, requesting to append n elements. |
|
|
*/ |
|
|
*/ |
|
|
T *endData(int n) { |
|
|
|
|
|
|
|
|
T *endData(size_t n) { |
|
|
if (end + n > N) { |
|
|
if (end + n > N) { |
|
|
returnBuffer(); |
|
|
returnBuffer(); |
|
|
} |
|
|
} |
|
@@ -161,7 +182,7 @@ struct AppleRingBuffer { |
|
|
/** Actually increments the end position |
|
|
/** Actually increments the end position |
|
|
Must be called after endData(), and `n` must be at most the `n` passed to endData() |
|
|
Must be called after endData(), and `n` must be at most the `n` passed to endData() |
|
|
*/ |
|
|
*/ |
|
|
void endIncr(int n) { |
|
|
|
|
|
|
|
|
void endIncr(size_t n) { |
|
|
end += n; |
|
|
end += n; |
|
|
} |
|
|
} |
|
|
/** Returns a pointer to S consecutive elements for consumption |
|
|
/** Returns a pointer to S consecutive elements for consumption |
|
@@ -170,7 +191,7 @@ struct AppleRingBuffer { |
|
|
const T *startData() const { |
|
|
const T *startData() const { |
|
|
return &data[start]; |
|
|
return &data[start]; |
|
|
} |
|
|
} |
|
|
void startIncr(int n) { |
|
|
|
|
|
|
|
|
void startIncr(size_t n) { |
|
|
// This is valid as long as n < S |
|
|
// This is valid as long as n < S |
|
|
start += n; |
|
|
start += n; |
|
|
} |
|
|
} |
|
|