Merge pull request #10 from Boof2015/VSTs

Add cross platform VSTs
This commit is contained in:
Boof2015
2026-06-03 10:21:21 -04:00
committed by GitHub
parent 180d0dbef5
commit 63ef5ffa0f
63 changed files with 5617 additions and 66 deletions
+122
View File
@@ -0,0 +1,122 @@
#pragma once
#include "ScopeEngine.h"
#include "spectrogram.h" // reused, unmodified, from native/src
#include <vector>
#include <algorithm>
/**
* Spectrogram engine. The reused Visualizer::SpectrogramAnalyzer runs in C++ and
* produces finished display+heat columns. Unlike the other scopes, its DSP needs
* the canvas-derived rowCount (and fft/freq/db/scale/orientation), which only the
* webview knows — the UI pushes the full native config via "prismSpectrogramConfig",
* routed here through configureNative(). process() mixes to mono and runs the DSP;
* buildFrame() emits the columns produced since the last frame (base64) tagged with
* rowCount, so the bridge can match them to the config the UI currently expects.
* configure() (scope settings) is a no-op — every DSP parameter arrives in the
* native config. The host sample rate (from setSampleRate) overrides whatever the
* UI believed, so frequency mapping is always correct.
*/
class SpectrogramEngine : public ScopeEngine
{
public:
const char* scopeId() const override { return "spectrogram"; }
juce::Identifier frameEventId() const override { return frameId; }
PreferredSize preferredSize() const override { return { 700, 340, 360, 200 }; }
void setSampleRate(double sampleRate) override
{
if (sampleRate > 0.0 && (float) sampleRate != config.sampleRate)
{
config.sampleRate = (float) sampleRate;
if (hasConfig)
spectro.configure(config);
}
}
void configure(const juce::var&) override {}
void configureNative(const juce::var& opts) override
{
if (! opts.isObject())
return;
config.fftSize = (size_t) std::max(0, (int) opts.getProperty("fftSize", 4096));
config.rowCount = (size_t) std::max(0, (int) opts.getProperty("rowCount", 0));
config.minFrequency = (float) opts.getProperty("minFrequency", 20.0);
config.maxFrequency = (float) opts.getProperty("maxFrequency", 20000.0);
config.minDecibels = (float) opts.getProperty("minDecibels", -90.0);
config.maxDecibels = (float) opts.getProperty("maxDecibels", -12.0);
config.scrollSpeed = (float) opts.getProperty("scrollSpeed", 2.0);
config.contrast = (float) opts.getProperty("contrast", 1.0);
config.tiltDbPerOctave = (float) opts.getProperty("tiltDbPerOctave", 4.0);
config.clarityMode = opts.getProperty("clarityMode", "sharper").toString().toStdString();
config.scaleMode = opts.getProperty("scaleMode", "log").toString().toStdString();
config.orientation = opts.getProperty("orientation", "horizontal").toString().toStdString();
// Host rate (from setSampleRate) is authoritative; the UI may still hold a
// stale default before it has seen a frame. Only fall back to the UI value
// if we have not yet learned the host rate.
if (config.sampleRate <= 0.0f)
config.sampleRate = (float) opts.getProperty("sampleRate", 48000.0);
hasConfig = config.rowCount > 0 && config.fftSize > 0;
if (hasConfig)
spectro.configure(config);
// The config changed: drop any half-built column batch so the next frame
// starts clean at the new rowCount.
pendingDisplay.clear();
pendingHeat.clear();
pendingColumns = 0;
}
void process(const float* left, const float* right, int numSamples) override
{
if (! hasConfig || numSamples <= 0)
return;
if ((int) mono.size() < numSamples)
mono.resize((size_t) numSamples);
for (int i = 0; i < numSamples; ++i)
mono[(size_t) i] = 0.5f * (left[i] + right[i]);
auto result = spectro.process(mono.data(), (size_t) numSamples);
if (result.columnCount > 0 && result.rowCount == config.rowCount)
{
pendingDisplay.insert(pendingDisplay.end(), result.display.begin(), result.display.end());
pendingHeat.insert(pendingHeat.end(), result.heat.begin(), result.heat.end());
pendingColumns += result.columnCount;
}
}
juce::var buildFrame(double sampleRate) override
{
auto* obj = new juce::DynamicObject();
obj->setProperty("sampleRate", sampleRate);
obj->setProperty("rowCount", (int) config.rowCount);
obj->setProperty("columnCount", (int) pendingColumns);
if (pendingColumns > 0)
{
obj->setProperty("display", juce::Base64::toBase64(pendingDisplay.data(), pendingDisplay.size() * sizeof(float)));
obj->setProperty("heat", juce::Base64::toBase64(pendingHeat.data(), pendingHeat.size() * sizeof(float)));
}
else
{
obj->setProperty("display", juce::String());
obj->setProperty("heat", juce::String());
}
pendingDisplay.clear();
pendingHeat.clear();
pendingColumns = 0;
return juce::var(obj);
}
private:
const juce::Identifier frameId { "spectrogramFrame" };
Visualizer::SpectrogramAnalyzer spectro;
Visualizer::SpectrogramConfig config;
bool hasConfig = false;
std::vector<float> mono;
std::vector<float> pendingDisplay, pendingHeat;
size_t pendingColumns = 0;
};