Files
astra-mobile/modules/astra-scope/cpp/vectorscope.cpp
T
2026-06-17 16:12:51 -04:00

111 lines
3.4 KiB
C++

#include "vectorscope.h"
#include <algorithm>
#include <cmath>
namespace Visualizer {
Vectorscope::Vectorscope()
: sampleRate_(48000.0f)
, bufferSize_(1024)
, writePos_(0)
, validSamples_(0) {
leftBuffer_.resize(VECTORSCOPE_BUFFER_SIZE, 0.0f);
rightBuffer_.resize(VECTORSCOPE_BUFFER_SIZE, 0.0f);
points_.reserve(1024);
// Cascaded lowpass at 8kHz, Butterworth (Q=0.707)
// Two stages per channel = 4th order = 24 dB/oct rolloff
// Removes HF noise that causes erratic Lissajous motion
leftLowpass1_.setLowpass(8000.0f, sampleRate_, 0.707f);
leftLowpass2_.setLowpass(8000.0f, sampleRate_, 0.707f);
rightLowpass1_.setLowpass(8000.0f, sampleRate_, 0.707f);
rightLowpass2_.setLowpass(8000.0f, sampleRate_, 0.707f);
}
void Vectorscope::setSampleRate(float sampleRate) {
sampleRate_ = sampleRate;
// Redesign all filters with new sample rate
leftLowpass1_.setLowpass(8000.0f, sampleRate_, 0.707f);
leftLowpass2_.setLowpass(8000.0f, sampleRate_, 0.707f);
rightLowpass1_.setLowpass(8000.0f, sampleRate_, 0.707f);
rightLowpass2_.setLowpass(8000.0f, sampleRate_, 0.707f);
}
void Vectorscope::setBufferSize(size_t size) {
bufferSize_ = size;
points_.reserve(size);
}
void Vectorscope::pushSamples(
const float* leftChannel,
const float* rightChannel,
size_t length
) {
for (size_t i = 0; i < length; i++) {
// Apply cascaded lowpass filtering
float filteredL = leftLowpass1_.process(leftChannel[i]);
filteredL = leftLowpass2_.process(filteredL);
float filteredR = rightLowpass1_.process(rightChannel[i]);
filteredR = rightLowpass2_.process(filteredR);
leftBuffer_[writePos_] = filteredL;
rightBuffer_[writePos_] = filteredR;
writePos_ = (writePos_ + 1) % VECTORSCOPE_BUFFER_SIZE;
if (validSamples_ < VECTORSCOPE_BUFFER_SIZE) {
validSamples_++;
}
}
}
size_t Vectorscope::getPoints(float* xOut, float* yOut, size_t maxPoints) const {
size_t count = std::min(maxPoints, validSamples_);
// Read the most recent `count` samples from the circular buffer
for (size_t i = 0; i < count; i++) {
size_t idx = (writePos_ + VECTORSCOPE_BUFFER_SIZE - count + i) % VECTORSCOPE_BUFFER_SIZE;
xOut[i] = rightBuffer_[idx]; // X = Right (standard Lissajous)
yOut[i] = leftBuffer_[idx]; // Y = Left
}
return count;
}
// Legacy process method (routes through new pipeline)
const std::vector<VectorscopePoint>& Vectorscope::process(
const float* leftChannel,
const float* rightChannel,
size_t length
) {
// Push through the filtering pipeline
pushSamples(leftChannel, rightChannel, length);
// Build legacy output from buffer
points_.clear();
size_t count = std::min(length, validSamples_);
for (size_t i = 0; i < count; i++) {
size_t idx = (writePos_ + VECTORSCOPE_BUFFER_SIZE - count + i) % VECTORSCOPE_BUFFER_SIZE;
VectorscopePoint p;
p.x = rightBuffer_[idx];
p.y = leftBuffer_[idx];
points_.push_back(p);
}
return points_;
}
void Vectorscope::reset() {
writePos_ = 0;
validSamples_ = 0;
std::fill(leftBuffer_.begin(), leftBuffer_.end(), 0.0f);
std::fill(rightBuffer_.begin(), rightBuffer_.end(), 0.0f);
leftLowpass1_.reset();
leftLowpass2_.reset();
rightLowpass1_.reset();
rightLowpass2_.reset();
points_.clear();
}
} // namespace Visualizer