This commit is contained in:
Boof2015
2026-06-19 13:19:37 -04:00
parent 978d001db9
commit bc687dfe87
145 changed files with 4685 additions and 197 deletions
+34
View File
@@ -73,4 +73,38 @@ Java_expo_modules_astrascope_ScopeBridge_nativeFillOscilloscope(
return static_cast<jint>(n);
}
// --- POST-EQ source (M4) ----------------------------------------------------
// The post-EQ tap pushes here; the EQ screen pulls the post-EQ spectrum.
JNIEXPORT void JNICALL
Java_expo_modules_astrascope_ScopeBridge_nativePushFramesPostEq(
JNIEnv* env, jobject /*thiz*/, jfloatArray frames, jint frameCount,
jint channelCount) {
if (frames == nullptr || frameCount <= 0 || channelCount <= 0) {
return;
}
auto* data = static_cast<float*>(
env->GetPrimitiveArrayCritical(frames, nullptr));
if (data == nullptr) {
return;
}
driver().pushInterleavedPostEq(data, static_cast<size_t>(frameCount),
static_cast<int>(channelCount));
env->ReleasePrimitiveArrayCritical(frames, data, JNI_ABORT);
}
JNIEXPORT jint JNICALL
Java_expo_modules_astrascope_ScopeBridge_nativeFillSpectrumPostEq(
JNIEnv* env, jobject /*thiz*/, jobject buffer, jint capacityFloats) {
if (buffer == nullptr || capacityFloats <= 0) {
return 0;
}
auto* dst = static_cast<float*>(env->GetDirectBufferAddress(buffer));
if (dst == nullptr) {
return 0;
}
const size_t n = driver().fillSpectrumPostEq(dst, static_cast<size_t>(capacityFloats));
return static_cast<jint>(n);
}
} // extern "C"
+70 -1
View File
@@ -184,10 +184,70 @@ class ScopeDriver {
return count;
}
// ---- POST-EQ source (M4) -------------------------------------------------
// A second, spectrum-only SPSC source fed by the post-EQ tap. Mirrors the
// pre-EQ spectrum path exactly; used only by the EQ screen's response-curve
// overlay, so there is no post-EQ oscilloscope.
// Audio thread. Downmix interleaved float frames to mono into the post-EQ ring.
void pushInterleavedPostEq(const float* data, size_t frames, int channels) {
if (data == nullptr || frames == 0 || channels <= 0) {
return;
}
size_t w = postEqWritePos_.load(std::memory_order_relaxed);
const float inv = 1.0f / static_cast<float>(channels);
for (size_t f = 0; f < frames; ++f) {
float sum = 0.0f;
const float* frame = data + f * channels;
for (int c = 0; c < channels; ++c) {
sum += frame[c];
}
postEqRing_[w & kMask] = sum * inv;
++w;
}
postEqWritePos_.store(w, std::memory_order_release);
}
// Render thread. Latest post-EQ spectrum window -> `out` (dB magnitudes).
size_t fillSpectrumPostEq(float* out, size_t cap) {
if (out == nullptr || cap == 0) {
return 0;
}
const int sr = pendingSampleRate_.load(std::memory_order_acquire);
if (sr != postEqAppliedSampleRate_) {
postEqSpectrum_.setSampleRate(static_cast<float>(sr));
postEqAppliedSampleRate_ = sr;
}
const size_t fftSize = postEqSpectrum_.getFFTSize();
const size_t w = postEqWritePos_.load(std::memory_order_acquire);
const size_t sampleRate = sr > 0 ? static_cast<size_t>(sr) : static_cast<size_t>(48000);
const size_t delaySamples = scopeOutputDelaySamples(sampleRate);
const size_t readHead = w > delaySamples ? w - delaySamples : 0;
const std::vector<float>* mags;
if (readHead >= fftSize) {
postEqScratch_.resize(fftSize);
const size_t start = readHead - fftSize;
for (size_t i = 0; i < fftSize; ++i) {
postEqScratch_[i] = postEqRing_[(start + i) & kMask];
}
mags = &postEqSpectrum_.process(postEqScratch_.data(), fftSize);
} else {
mags = &postEqSpectrum_.process(nullptr, 0);
}
const size_t n = std::min(cap, mags->size());
std::memcpy(out, mags->data(), n * sizeof(float));
return n;
}
size_t binCount() const { return spectrum_.getFFTSize() / 2; }
void reset() {
spectrum_.reset();
postEqSpectrum_.reset();
osc_.reset();
oscReadPos_ = writePos_.load(std::memory_order_acquire);
oscSamplesSeen_ = 0;
@@ -196,9 +256,11 @@ class ScopeDriver {
}
private:
ScopeDriver() : spectrum_(kFftSize) {
ScopeDriver() : spectrum_(kFftSize), postEqSpectrum_(kFftSize) {
spectrum_.setSmoothing(0.92f);
postEqSpectrum_.setSmoothing(0.92f);
ring_.assign(kSize, 0.0f);
postEqRing_.assign(kSize, 0.0f);
}
static constexpr size_t kFftSize = 2048; // -> 1024 dB bins
@@ -277,6 +339,13 @@ class ScopeDriver {
Visualizer::Spectrum spectrum_;
int appliedSampleRate_{0};
// Post-EQ source (M4) — second SPSC ring + spectrum-only analyzer.
std::vector<float> postEqRing_;
std::atomic<size_t> postEqWritePos_{0};
std::vector<float> postEqScratch_;
Visualizer::Spectrum postEqSpectrum_;
int postEqAppliedSampleRate_{0};
Visualizer::Oscilloscope osc_;
size_t oscReadPos_{0};
size_t oscSamplesSeen_{0};