Files
prism/src/plugin-ui/BridgeSpectrumAnalyzer.ts
T
Boof2015 63ef5ffa0f Merge pull request #10 from Boof2015/VSTs
Add cross platform VSTs
2026-06-03 10:21:21 -04:00

119 lines
3.5 KiB
TypeScript

import type { SpectrumNativeAnalyzer } from '../renderer/audio/native'
const FFT_SILENCE_DB = -100
/**
* A drop-in `SpectrumNativeAnalyzer` for the plugin webview.
*
* In the Electron app, `SpectrumAnalyzer` pushes raw samples into the N-API DSP
* addon and reads magnitudes back. There is no N-API addon inside a webview, so
* here the DSP runs in the C++ plugin instead: it computes magnitudes off the
* realtime thread and pushes them over the JUCE bridge. This shim simply caches
* the latest pushed magnitudes and serves them through the same interface
* `SpectrumAnalyzer` already consumes — so the visualizer needs no changes.
*
* `pushSamples` / `pushStereoSamples` are intentional no-ops: audio never flows
* through the webview.
*/
export class BridgeSpectrumAnalyzer implements SpectrumNativeAnalyzer {
private fftSize = 2048
private sampleRate = 48000
private magnitudes: Float32Array
private sideMagnitudes: Float32Array
constructor(fftSize = 2048) {
this.fftSize = fftSize
this.magnitudes = new Float32Array(fftSize / 2).fill(FFT_SILENCE_DB)
this.sideMagnitudes = new Float32Array(fftSize / 2).fill(FFT_SILENCE_DB)
}
/** Called by the bridge whenever the host emits a new frame. */
setMagnitudes(magnitudes: Float32Array, side?: Float32Array): void {
if (magnitudes.length !== this.magnitudes.length) {
this.magnitudes = new Float32Array(magnitudes.length)
}
this.magnitudes.set(magnitudes)
if (side && side.length > 0) {
if (side.length !== this.sideMagnitudes.length) {
this.sideMagnitudes = new Float32Array(side.length)
}
this.sideMagnitudes.set(side)
}
}
isAvailable(): boolean {
return true
}
setFFTSize(size: number): void {
if (size > 0 && size !== this.fftSize) {
this.fftSize = size
this.magnitudes = new Float32Array(size / 2).fill(FFT_SILENCE_DB)
this.sideMagnitudes = new Float32Array(size / 2).fill(FFT_SILENCE_DB)
}
}
getFFTSize(): number {
return this.fftSize
}
setSampleRate(sampleRate: number): void {
this.sampleRate = sampleRate
}
// Smoothing is applied in the C++ DSP; nothing to do on this side.
setSmoothing(_smoothing: number): void {}
pushSamples(_audioData: Float32Array): void {}
pushStereoSamples(_leftChannel: Float32Array, _rightChannel: Float32Array): void {}
fillMagnitudes(output: Float32Array): number {
const count = Math.min(output.length, this.magnitudes.length)
if (count > 0) {
output.set(this.magnitudes.subarray(0, count), 0)
}
return count
}
// The C++ side sends already-smoothed magnitudes; serve them for the raw
// request too (the heatmap path re-smooths from this in the visualizer).
fillRawMagnitudes(output: Float32Array): number {
return this.fillMagnitudes(output)
}
fillSideMagnitudes(output: Float32Array): number {
const count = Math.min(output.length, this.sideMagnitudes.length)
if (count > 0) {
output.set(this.sideMagnitudes.subarray(0, count), 0)
}
return count
}
getMagnitudes(): Float32Array {
return this.magnitudes
}
getRawMagnitudes(): Float32Array {
return this.magnitudes
}
getSideMagnitudes(): Float32Array {
return this.sideMagnitudes
}
process(_audioData: Float32Array): Float32Array {
return this.magnitudes
}
binToFrequency(bin: number): number {
return (bin * this.sampleRate) / this.fftSize
}
reset(): void {
this.magnitudes.fill(FFT_SILENCE_DB)
this.sideMagnitudes.fill(FFT_SILENCE_DB)
}
}