Restore MovingMedian to the simpler single filter and provide ..Stack separately. Seems little point in coalescing memory there, now we have separate ring buffers anyway

This commit is contained in:
Chris Cannam
2022-06-10 13:09:48 +01:00
parent 6723ca3636
commit 0bfa94a76a

View File

@@ -38,74 +38,96 @@ namespace RubberBand
{ {
template <typename T> template <typename T>
class MovingMedianStack class MovingMedian : public SampleFilter<T>
{ {
public: public:
MovingMedianStack(int nfilters, int filterLength, float percentile = 50.f) : MovingMedian(int filterLength, float percentile = 50.f) :
m_buffers(nfilters, filterLength), m_buffer(filterLength),
m_sortspace(nfilters * filterLength, {}), m_sortspace(filterLength, {})
m_length(filterLength)
{ {
setPercentile(percentile); setPercentile(percentile);
} }
~MovingMedianStack() { ~MovingMedian() {
} }
int getNFilters() const { MovingMedian(const MovingMedian &) =default;
return m_buffers.size(); MovingMedian &operator=(const MovingMedian &) =default;
}
int getSize() const { int getSize() const {
return m_length; return m_buffer.getSize();
} }
void setPercentile(float p) { void setPercentile(float p) {
m_index = int((m_length * p) / 100.f); int length = getSize();
if (m_index >= m_length) m_index = m_length-1; m_index = int((length * p) / 100.f);
if (m_index >= length) m_index = length-1;
if (m_index < 0) m_index = 0; if (m_index < 0) m_index = 0;
} }
void push(int filter, T value) { void push(T value) {
if (value != value) { if (value != value) {
std::cerr << "WARNING: MovingMedian: NaN encountered" << std::endl; std::cerr << "WARNING: MovingMedian: NaN encountered" << std::endl;
value = T(); value = T();
} }
auto &buf = m_buffers[filter]; if (m_buffer.getWriteSpace() == 0) {
if (buf.getWriteSpace() == 0) { T toDrop = m_buffer.readOne();
T toDrop = buf.readOne(); dropAndPut(toDrop, value);
dropAndPut(filter, toDrop, value); m_buffer.writeOne(value);
buf.writeOne(value);
} else { } else {
put(filter, value); put(value);
buf.writeOne(value); m_buffer.writeOne(value);
} }
} }
T get(int filter) const { T get() const {
const T *sorted = sortedFor(filter); return m_sortspace[m_index];
return sorted[m_index];
} }
void reset() { void reset() {
for (auto &buf : m_buffers) buf.reset(); m_buffer.reset();
v_zero(m_sortspace.data(), m_sortspace.size()); v_zero(m_sortspace.data(), m_sortspace.size());
} }
// Convenience function that applies a given filter to an array
// in-place. Array has length n. Modifies both the filter and the
// array.
//
static void filter(MovingMedian<T> &mm, T *v, int n) {
int fn = mm.getSize();
int lag = fn / 2;
mm.reset();
int i = 0;
for (; i < lag; ++i) {
if (i < n) mm.push(v[i]);
}
for (; i < n; ++i) {
mm.push(v[i]);
v[i-lag] = mm.get();
}
for (; i < lag; ++i) {
// just for the unusual case where lag > n
mm.push(T());
(void)mm.get();
}
for (; i < n + lag; ++i) {
mm.push(T());
v[i-lag] = mm.get();
}
}
// As above but with a vector argument
//
static void filter(MovingMedian<T> &mm, std::vector<T> &v) {
filter(mm, v.data(), v.size());
}
private: private:
FixedVector<SingleThreadRingBuffer<T>> m_buffers; SingleThreadRingBuffer<T> m_buffer;
FixedVector<T> m_sortspace; std::vector<T> m_sortspace;
int m_length;
int m_index; int m_index;
const T *sortedFor(int filter) const { void dropAndPut(const T &toDrop, const T &toPut) {
return m_sortspace.data() + filter * m_length;
}
T *sortedFor(int filter) {
return m_sortspace.data() + filter * m_length;
}
void dropAndPut(int filter, const T &toDrop, const T &toPut) {
// precondition: sorted contains m_length values, one of which is toDrop // precondition: sorted contains m_length values, one of which is toDrop
// postcondition: sorted contains m_length values, one of which is toPut // postcondition: sorted contains m_length values, one of which is toPut
// (and one instance of toDrop has been removed) // (and one instance of toDrop has been removed)
@@ -114,8 +136,8 @@ private:
// longer than maybe 16 items). Two binary searches plus a // longer than maybe 16 items). Two binary searches plus a
// memmove should be faster for longer ones. // memmove should be faster for longer ones.
const int n = m_length; const int n = getSize();
T *sorted = sortedFor(filter); T *sorted = m_sortspace.data();
int dropIx; int dropIx;
if (toDrop <= *sorted) { if (toDrop <= *sorted) {
// this is quite a common short-circuit in situations // this is quite a common short-circuit in situations
@@ -175,12 +197,12 @@ private:
#endif #endif
} }
void put(int filter, const T &toPut) { void put(const T &toPut) {
// precondition: sorted contains fewer than m_length values, // precondition: sorted contains fewer than m_length values,
// packed at the start // packed at the start
// postcondition: sorted contains up to m_length values, // postcondition: sorted contains up to m_length values,
// packed at the start, one of which is toPut // packed at the start, one of which is toPut
const int n = m_buffers[filter].getReadSpace(); // items in sorted const int n = m_buffer.getReadSpace(); // items in sorted
#ifdef DEBUG_MM #ifdef DEBUG_MM
if (n >= m_length) { if (n >= m_length) {
@@ -188,7 +210,7 @@ private:
} }
#endif #endif
T *sorted = sortedFor(filter); T *sorted = m_sortspace.data();
int putIx = std::lower_bound(sorted, sorted + n, toPut) - sorted; int putIx = std::lower_bound(sorted, sorted + n, toPut) - sorted;
#ifdef DEBUG_MM #ifdef DEBUG_MM
@@ -220,81 +242,46 @@ private:
} }
#endif #endif
} }
MovingMedianStack(const MovingMedianStack &) =delete;
MovingMedianStack &operator=(const MovingMedianStack &) =delete;
}; };
template <typename T> template <typename T>
class MovingMedian : public SampleFilter<T> class MovingMedianStack
{ {
public: public:
MovingMedian(int size, float percentile = 50.f) : MovingMedianStack(int nfilters, int size, float percentile = 50.f) :
m_mm(1, size, percentile) m_stack(nfilters, { size, percentile })
{ {
} }
~MovingMedian() { ~MovingMedianStack() {
} }
int getSize() const { int getSize() const {
return m_mm.getSize(); return m_stack[0].getSize();
} }
void setPercentile(float p) { void setPercentile(float p) {
m_mm.setPercentile(p); for (auto &f: m_stack) {
f.setPercentile(p);
}
} }
void push(T value) { void push(int filter, T value) {
m_mm.push(0, value); m_stack[filter].push(value);
} }
T get() const { T get(int filter) const {
return m_mm.get(0); return m_stack[filter].get();
} }
void reset() { void reset() {
m_mm.reset(); for (auto &f: m_stack) {
} f.reset();
// Convenience function that applies a given filter to an array
// in-place. Array has length n. Modifies both the filter and the
// array.
//
static void filter(MovingMedian<T> &mm, T *v, int n) {
int fn = mm.getSize();
int lag = fn / 2;
mm.reset();
int i = 0;
for (; i < lag; ++i) {
if (i < n) mm.push(v[i]);
} }
for (; i < n; ++i) {
mm.push(v[i]);
v[i-lag] = mm.get();
}
for (; i < lag; ++i) {
// just for the unusual case where lag > n
mm.push(T());
(void)mm.get();
}
for (; i < n + lag; ++i) {
mm.push(T());
v[i-lag] = mm.get();
}
}
// As above but with a vector argument
//
static void filter(MovingMedian<T> &mm, std::vector<T> &v) {
filter(mm, v.data(), v.size());
} }
private: private:
MovingMedianStack<T> m_mm; std::vector<MovingMedian<T>> m_stack;
MovingMedian(const MovingMedian &) =delete;
MovingMedian &operator=(const MovingMedian &) =delete;
}; };
} }