fix spectrum sideline being JS not native

This commit is contained in:
Boof2015
2026-05-19 19:18:26 -04:00
parent d9cf263856
commit 056d46e95d
7 changed files with 619 additions and 285 deletions
+40
View File
@@ -174,6 +174,19 @@ Napi::Value SpectrumPushSamples(const Napi::CallbackInfo& info) {
return env.Undefined();
}
Napi::Value SpectrumPushStereoSamples(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (info.Length() < 2 || !info[0].IsTypedArray() || !info[1].IsTypedArray()) {
Napi::TypeError::New(env, "Expected left and right Float32Array").ThrowAsJavaScriptException();
return env.Null();
}
Napi::Float32Array leftData = info[0].As<Napi::Float32Array>();
Napi::Float32Array rightData = info[1].As<Napi::Float32Array>();
const size_t length = std::min(leftData.ElementLength(), rightData.ElementLength());
spectrum.pushStereoSamples(leftData.Data(), rightData.Data(), length);
return env.Undefined();
}
Napi::Value SpectrumGetMagnitudes(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
const auto& magnitudes = spectrum.getMagnitudes();
@@ -190,6 +203,14 @@ Napi::Value SpectrumGetRawMagnitudes(const Napi::CallbackInfo& info) {
return result;
}
Napi::Value SpectrumGetSideMagnitudes(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
const auto& magnitudes = spectrum.getSideMagnitudes();
Napi::Float32Array result = Napi::Float32Array::New(env, magnitudes.size());
memcpy(result.Data(), magnitudes.data(), magnitudes.size() * sizeof(float));
return result;
}
Napi::Value SpectrumFillRawMagnitudes(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (info.Length() < 1 || !info[0].IsTypedArray()) {
@@ -222,6 +243,22 @@ Napi::Value SpectrumFillMagnitudes(const Napi::CallbackInfo& info) {
return Napi::Number::New(env, static_cast<double>(count));
}
Napi::Value SpectrumFillSideMagnitudes(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (info.Length() < 1 || !info[0].IsTypedArray()) {
Napi::TypeError::New(env, "Expected output Float32Array").ThrowAsJavaScriptException();
return env.Null();
}
Napi::Float32Array output = info[0].As<Napi::Float32Array>();
const auto& magnitudes = spectrum.getSideMagnitudes();
const size_t count = std::min(output.ElementLength(), magnitudes.size());
if (count > 0) {
memcpy(output.Data(), magnitudes.data(), count * sizeof(float));
}
return Napi::Number::New(env, static_cast<double>(count));
}
Napi::Value SpectrumProcess(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (info.Length() < 1 || !info[0].IsTypedArray()) {
@@ -644,10 +681,13 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
specExports.Set("setSampleRate", Napi::Function::New(env, SpectrumSetSampleRate));
specExports.Set("setSmoothing", Napi::Function::New(env, SpectrumSetSmoothing));
specExports.Set("pushSamples", Napi::Function::New(env, SpectrumPushSamples));
specExports.Set("pushStereoSamples", Napi::Function::New(env, SpectrumPushStereoSamples));
specExports.Set("fillRawMagnitudes", Napi::Function::New(env, SpectrumFillRawMagnitudes));
specExports.Set("fillMagnitudes", Napi::Function::New(env, SpectrumFillMagnitudes));
specExports.Set("fillSideMagnitudes", Napi::Function::New(env, SpectrumFillSideMagnitudes));
specExports.Set("getRawMagnitudes", Napi::Function::New(env, SpectrumGetRawMagnitudes));
specExports.Set("getMagnitudes", Napi::Function::New(env, SpectrumGetMagnitudes));
specExports.Set("getSideMagnitudes", Napi::Function::New(env, SpectrumGetSideMagnitudes));
specExports.Set("process", Napi::Function::New(env, SpectrumProcess));
specExports.Set("binToFrequency", Napi::Function::New(env, SpectrumBinToFrequency));
specExports.Set("reset", Napi::Function::New(env, SpectrumReset));
+97 -23
View File
@@ -13,11 +13,14 @@ Spectrum::Spectrum(size_t fftSize)
, bufferedSamples_(0) {
fft_ = std::make_unique<DSP::FFT>(fftSize);
historyBuffer_.resize(fftSize, 0.0f);
sideHistoryBuffer_.resize(fftSize, 0.0f);
windowedInput_.resize(fftSize);
magnitudes_.resize(fftSize / 2);
rawMagnitudes_.resize(fftSize / 2, -100.0f);
// Initialize to silence (-100.0f dB)
smoothedMagnitudes_.resize(fftSize / 2, -100.0f);
sideRawMagnitudes_.resize(fftSize / 2, -100.0f);
sideSmoothedMagnitudes_.resize(fftSize / 2, -100.0f);
}
void Spectrum::setFFTSize(size_t size) {
@@ -25,11 +28,14 @@ void Spectrum::setFFTSize(size_t size) {
fftSize_ = size;
fft_ = std::make_unique<DSP::FFT>(size);
historyBuffer_.assign(size, 0.0f);
sideHistoryBuffer_.assign(size, 0.0f);
windowedInput_.resize(size);
magnitudes_.resize(size / 2);
rawMagnitudes_.resize(size / 2, -100.0f);
rawMagnitudes_.assign(size / 2, -100.0f);
// Initialize to silence (-100.0f dB)
smoothedMagnitudes_.resize(size / 2, -100.0f);
smoothedMagnitudes_.assign(size / 2, -100.0f);
sideRawMagnitudes_.assign(size / 2, -100.0f);
sideSmoothedMagnitudes_.assign(size / 2, -100.0f);
bufferedSamples_ = 0;
}
}
@@ -57,31 +63,48 @@ void Spectrum::applyWindow(const float* input, float* output, size_t length) {
}
}
void Spectrum::pushHistory(const float* input, size_t length) {
void Spectrum::pushHistory(std::vector<float>& history, const float* input, size_t length) {
if (length == 0 || fftSize_ == 0) {
return;
}
// Keep only the most recent fftSize_ samples.
if (length >= fftSize_) {
std::memcpy(historyBuffer_.data(), input + (length - fftSize_), fftSize_ * sizeof(float));
bufferedSamples_ = fftSize_;
std::memcpy(history.data(), input + (length - fftSize_), fftSize_ * sizeof(float));
return;
}
const size_t keep = fftSize_ - length;
std::move(historyBuffer_.begin() + length, historyBuffer_.end(), historyBuffer_.begin());
std::memcpy(historyBuffer_.data() + keep, input, length * sizeof(float));
bufferedSamples_ = std::min(fftSize_, bufferedSamples_ + length);
std::move(history.begin() + length, history.end(), history.begin());
std::memcpy(history.data() + keep, input, length * sizeof(float));
}
void Spectrum::updateMagnitudes() {
if (historyBuffer_.empty() || magnitudes_.empty()) {
void Spectrum::pushZeroHistory(std::vector<float>& history, size_t length) {
if (length == 0 || fftSize_ == 0) {
return;
}
if (length >= fftSize_) {
std::fill(history.begin(), history.end(), 0.0f);
return;
}
const size_t keep = fftSize_ - length;
std::move(history.begin() + length, history.end(), history.begin());
std::fill(history.begin() + keep, history.end(), 0.0f);
}
void Spectrum::updateMagnitudesForHistory(
const std::vector<float>& history,
std::vector<float>& rawMagnitudes,
std::vector<float>& smoothedMagnitudes
) {
if (history.empty() || magnitudes_.empty()) {
return;
}
// Always analyze a full FFT frame from the rolling buffer.
applyWindow(historyBuffer_.data(), windowedInput_.data(), fftSize_);
applyWindow(history.data(), windowedInput_.data(), fftSize_);
// Perform FFT
fft_->forward(windowedInput_.data(), magnitudes_.data());
@@ -99,37 +122,85 @@ void Spectrum::updateMagnitudes() {
// Clamp to a stable display range.
db = std::clamp(db, -120.0f, 12.0f);
rawMagnitudes_[i] = db;
rawMagnitudes[i] = db;
if (bufferedSamples_ < fftSize_) {
smoothedMagnitudes_[i] = db;
smoothedMagnitudes[i] = db;
continue;
}
// Apply temporal smoothing only (no bin-to-bin averaging).
smoothedMagnitudes_[i] = smoothing_ * smoothedMagnitudes_[i] + (1.0f - smoothing_) * db;
smoothedMagnitudes[i] = smoothing_ * smoothedMagnitudes[i] + (1.0f - smoothing_) * db;
// Safety check
if (!std::isfinite(smoothedMagnitudes_[i])) {
smoothedMagnitudes_[i] = -100.0f;
if (!std::isfinite(smoothedMagnitudes[i])) {
smoothedMagnitudes[i] = -100.0f;
}
}
}
void Spectrum::updateMagnitudes() {
updateMagnitudesForHistory(historyBuffer_, rawMagnitudes_, smoothedMagnitudes_);
updateMagnitudesForHistory(sideHistoryBuffer_, sideRawMagnitudes_, sideSmoothedMagnitudes_);
}
void Spectrum::updateSilentSideMagnitudes() {
const float silentDb = -120.0f;
for (size_t i = 0; i < sideSmoothedMagnitudes_.size(); i++) {
sideRawMagnitudes_[i] = silentDb;
if (bufferedSamples_ < fftSize_) {
sideSmoothedMagnitudes_[i] = silentDb;
continue;
}
sideSmoothedMagnitudes_[i] = smoothing_ * sideSmoothedMagnitudes_[i] + (1.0f - smoothing_) * silentDb;
if (!std::isfinite(sideSmoothedMagnitudes_[i])) {
sideSmoothedMagnitudes_[i] = -100.0f;
}
}
}
void Spectrum::pushSamples(const float* input, size_t length) {
if (input != nullptr && length > 0) {
pushHistory(input, length);
pushHistory(historyBuffer_, input, length);
pushZeroHistory(sideHistoryBuffer_, length);
bufferedSamples_ = length >= fftSize_ ? fftSize_ : std::min(fftSize_, bufferedSamples_ + length);
updateMagnitudesForHistory(historyBuffer_, rawMagnitudes_, smoothedMagnitudes_);
updateSilentSideMagnitudes();
return;
}
updateMagnitudes();
}
void Spectrum::pushStereoSamples(const float* left, const float* right, size_t length) {
if (left != nullptr && right != nullptr && length > 0 && fftSize_ > 0) {
if (length >= fftSize_) {
const size_t start = length - fftSize_;
for (size_t i = 0; i < fftSize_; i++) {
const float leftValue = left[start + i];
const float rightValue = right[start + i];
historyBuffer_[i] = (leftValue + rightValue) * 0.5f;
sideHistoryBuffer_[i] = (leftValue - rightValue) * 0.5f;
}
bufferedSamples_ = fftSize_;
} else {
const size_t keep = fftSize_ - length;
std::move(historyBuffer_.begin() + length, historyBuffer_.end(), historyBuffer_.begin());
std::move(sideHistoryBuffer_.begin() + length, sideHistoryBuffer_.end(), sideHistoryBuffer_.begin());
for (size_t i = 0; i < length; i++) {
const float leftValue = left[i];
const float rightValue = right[i];
historyBuffer_[keep + i] = (leftValue + rightValue) * 0.5f;
sideHistoryBuffer_[keep + i] = (leftValue - rightValue) * 0.5f;
}
bufferedSamples_ = std::min(fftSize_, bufferedSamples_ + length);
}
}
updateMagnitudes();
}
const std::vector<float>& Spectrum::process(const float* audioData, size_t length) {
if (audioData != nullptr && length > 0) {
pushHistory(audioData, length);
}
updateMagnitudes();
pushSamples(audioData, length);
return smoothedMagnitudes_;
}
@@ -139,8 +210,11 @@ float Spectrum::binToFrequency(int bin) const {
void Spectrum::reset() {
std::fill(historyBuffer_.begin(), historyBuffer_.end(), 0.0f);
std::fill(sideHistoryBuffer_.begin(), sideHistoryBuffer_.end(), 0.0f);
std::fill(rawMagnitudes_.begin(), rawMagnitudes_.end(), -100.0f);
std::fill(smoothedMagnitudes_.begin(), smoothedMagnitudes_.end(), -100.0f);
std::fill(sideRawMagnitudes_.begin(), sideRawMagnitudes_.end(), -100.0f);
std::fill(sideSmoothedMagnitudes_.begin(), sideSmoothedMagnitudes_.end(), -100.0f);
bufferedSamples_ = 0;
}
+13 -1
View File
@@ -18,12 +18,14 @@ public:
// Feed new samples into the rolling history and update the latest magnitudes.
void pushSamples(const float* input, size_t length);
void pushStereoSamples(const float* left, const float* right, size_t length);
// Read the latest raw clamped dB magnitudes without mutating analyzer state.
const std::vector<float>& getRawMagnitudes() const { return rawMagnitudes_; }
// Read the latest smoothed magnitudes without mutating analyzer state.
const std::vector<float>& getMagnitudes() const { return smoothedMagnitudes_; }
const std::vector<float>& getSideMagnitudes() const { return sideSmoothedMagnitudes_; }
// Process audio and get spectrum data
// Returns magnitude data (size = fftSize / 2)
@@ -42,14 +44,24 @@ private:
std::unique_ptr<DSP::FFT> fft_;
std::vector<float> historyBuffer_;
std::vector<float> sideHistoryBuffer_;
std::vector<float> windowedInput_;
std::vector<float> magnitudes_;
std::vector<float> rawMagnitudes_;
std::vector<float> smoothedMagnitudes_;
std::vector<float> sideRawMagnitudes_;
std::vector<float> sideSmoothedMagnitudes_;
size_t bufferedSamples_;
void applyWindow(const float* input, float* output, size_t length);
void pushHistory(const float* input, size_t length);
void pushHistory(std::vector<float>& history, const float* input, size_t length);
void pushZeroHistory(std::vector<float>& history, size_t length);
void updateMagnitudesForHistory(
const std::vector<float>& history,
std::vector<float>& rawMagnitudes,
std::vector<float>& smoothedMagnitudes
);
void updateSilentSideMagnitudes();
void updateMagnitudes();
};