mirror of
https://github.com/Boof2015/prism.git
synced 2026-08-28 16:50:57 +02:00
improve vst refresh rate on windows
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
#include "VectorscopeEngine.h"
|
||||
#include "SpectrogramEngine.h"
|
||||
#include "WaveformEngine.h"
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
|
||||
#if ! PRISM_USE_DEV_SERVER
|
||||
@@ -19,7 +20,24 @@
|
||||
namespace
|
||||
{
|
||||
const juce::Identifier kRestoreSettingsEvent { "prismRestoreSettings" };
|
||||
constexpr int kDrainCapacity = 16384;
|
||||
constexpr int kDrainCapacity = 1 << 16;
|
||||
#if JUCE_WINDOWS
|
||||
constexpr int kFallbackWindowsFrameRateHz = 60;
|
||||
constexpr int kMaxWindowsFrameRateHz = 240;
|
||||
|
||||
int resolveWindowsFrameRateHz(const juce::Component& component)
|
||||
{
|
||||
const auto& displays = juce::Desktop::getInstance().getDisplays();
|
||||
if (const auto* display = displays.getDisplayForRect(component.getScreenBounds()))
|
||||
{
|
||||
const auto frequency = display->verticalFrequencyHz;
|
||||
if (frequency.has_value() && std::isfinite(*frequency) && *frequency > 0.0)
|
||||
return juce::jlimit(30, kMaxWindowsFrameRateHz, (int) std::lround(*frequency));
|
||||
}
|
||||
|
||||
return kFallbackWindowsFrameRateHz;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::unique_ptr<ScopeEngine> makeEngine()
|
||||
{
|
||||
@@ -128,9 +146,22 @@ PrismSpectrumEditor::PrismSpectrumEditor(PrismSpectrumProcessor& p)
|
||||
setResizeLimits(pref.minWidth, pref.minHeight, 4096, 4096);
|
||||
setSize(pref.defaultWidth, pref.defaultHeight);
|
||||
|
||||
// Drive frames at the display's refresh rate (adapts to 60/120/144 Hz).
|
||||
if (webView != nullptr)
|
||||
{
|
||||
#if JUCE_WINDOWS
|
||||
startTimerHz(resolveWindowsFrameRateHz(*this));
|
||||
#else
|
||||
// Drive frames at the display's refresh rate (adapts to 60/120/144 Hz).
|
||||
vblank = juce::VBlankAttachment(this, [this] { renderFrame(); });
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
PrismSpectrumEditor::~PrismSpectrumEditor()
|
||||
{
|
||||
#if JUCE_WINDOWS
|
||||
stopTimer();
|
||||
#endif
|
||||
}
|
||||
|
||||
void PrismSpectrumEditor::resized()
|
||||
@@ -176,6 +207,13 @@ void PrismSpectrumEditor::showWebViewFallback()
|
||||
addAndMakeVisible(webViewFallback);
|
||||
}
|
||||
|
||||
#if JUCE_WINDOWS
|
||||
void PrismSpectrumEditor::timerCallback()
|
||||
{
|
||||
renderFrame();
|
||||
}
|
||||
#endif
|
||||
|
||||
void PrismSpectrumEditor::onSettingsPanel(juce::var payload)
|
||||
{
|
||||
const int height = juce::jmax(0, (int) payload.getProperty("height", 0));
|
||||
|
||||
@@ -9,20 +9,26 @@
|
||||
/**
|
||||
* Hosts the React webview UI and bridges the reused Prism spectrum DSP to it.
|
||||
*
|
||||
* Driven by a VBlankAttachment (message thread, synced to the display's refresh
|
||||
* rate). Each vblank it drains stereo audio buffered by the processor, runs
|
||||
* Visualizer::Spectrum, and emits mid + side magnitudes to the webview as a
|
||||
* "spectrumFrame" event. Receives "prismConfig" (settings + fftSize/smoothing)
|
||||
* and "prismReady" events from the UI.
|
||||
* Drains stereo audio buffered by the processor, feeds the selected scope engine,
|
||||
* and emits frame payloads to the webview. macOS uses display vblank; Windows uses
|
||||
* a steady message-thread timer because some DAW/WebView2 embeddings deliver
|
||||
* vblank callbacks too slowly for data-driven scopes.
|
||||
*/
|
||||
class PrismSpectrumEditor : public juce::AudioProcessorEditor
|
||||
#if JUCE_WINDOWS
|
||||
, private juce::Timer
|
||||
#endif
|
||||
{
|
||||
public:
|
||||
explicit PrismSpectrumEditor(PrismSpectrumProcessor&);
|
||||
~PrismSpectrumEditor() override = default;
|
||||
~PrismSpectrumEditor() override;
|
||||
|
||||
void resized() override;
|
||||
|
||||
#if JUCE_WINDOWS
|
||||
void timerCallback() override;
|
||||
#endif
|
||||
|
||||
// Called by the webview event listeners (message thread).
|
||||
void onPrismConfig(juce::var payload);
|
||||
void onPrismReady();
|
||||
@@ -64,9 +70,11 @@ private:
|
||||
std::unique_ptr<juce::WebBrowserComponent> webView;
|
||||
juce::Label webViewFallback;
|
||||
|
||||
// Declared last so it is destroyed first — no vblank callback can fire into
|
||||
// a partially-destroyed editor.
|
||||
// Declared last so it is destroyed first; no vblank callback can fire into a
|
||||
// partially-destroyed editor. Windows uses juce::Timer instead.
|
||||
#if ! JUCE_WINDOWS
|
||||
juce::VBlankAttachment vblank;
|
||||
#endif
|
||||
|
||||
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(PrismSpectrumEditor)
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useState, type JSX, type ReactNode } from 'react'
|
||||
import { useEffect, useRef, useState, type CSSProperties, type JSX, type ReactNode } from 'react'
|
||||
import type { ScopeKind } from '../types/scope'
|
||||
import type { ScopeSettings } from '../types/settings'
|
||||
import type { PrismResolvedTheme } from '../types/theme'
|
||||
@@ -26,20 +26,38 @@ interface ScopeAppProps<K extends ScopeKind> {
|
||||
export default function ScopeApp<K extends ScopeKind>({ kind, renderScope }: ScopeAppProps<K>): JSX.Element {
|
||||
const { settings, resolvedTheme, handleUpdate } = useScopeHostSync(kind)
|
||||
const [settingsOpen, setSettingsOpen] = useState(false)
|
||||
const [lockedViewportHeight, setLockedViewportHeight] = useState<number | null>(null)
|
||||
const viewportRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
emitToHost('prismSettingsPanel', { height: settingsOpen ? PANEL_HEIGHT : 0 })
|
||||
}, [settingsOpen])
|
||||
|
||||
const toggleSettings = (): void => {
|
||||
if (settingsOpen) {
|
||||
setSettingsOpen(false)
|
||||
setLockedViewportHeight(null)
|
||||
return
|
||||
}
|
||||
|
||||
const rect = viewportRef.current?.getBoundingClientRect()
|
||||
setLockedViewportHeight(rect && rect.height > 0 ? Math.ceil(rect.height) : null)
|
||||
setSettingsOpen(true)
|
||||
}
|
||||
|
||||
const appStyle = lockedViewportHeight === null
|
||||
? undefined
|
||||
: ({ '--spectrum-viewport-height': `${lockedViewportHeight}px` } as CSSProperties)
|
||||
|
||||
return (
|
||||
<div className="spectrum-app">
|
||||
<div className="spectrum-app__viewport">
|
||||
<div className={`spectrum-app ${settingsOpen ? 'has-settings' : ''}`.trim()} style={appStyle}>
|
||||
<div ref={viewportRef} className="spectrum-app__viewport">
|
||||
{renderScope(settings, resolvedTheme)}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={`spectrum-app__gear ${settingsOpen ? 'is-active' : ''}`.trim()}
|
||||
onClick={() => setSettingsOpen((open) => !open)}
|
||||
onClick={toggleSettings}
|
||||
aria-label="Settings"
|
||||
title="Settings"
|
||||
>
|
||||
|
||||
@@ -26,6 +26,11 @@ body {
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.spectrum-app.has-settings {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* The scope fills everything above the (optional) bottom settings panel. */
|
||||
@@ -35,6 +40,11 @@ body {
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.spectrum-app.has-settings .spectrum-app__viewport {
|
||||
flex: 0 0 var(--spectrum-viewport-height, 100%);
|
||||
height: var(--spectrum-viewport-height, 100%);
|
||||
}
|
||||
|
||||
.spectrum-scope {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
@@ -84,10 +94,9 @@ body {
|
||||
border-color: var(--control-border-active, rgba(56, 189, 248, 0.4));
|
||||
}
|
||||
|
||||
/* Settings panel along the bottom (like the app). The editor window grows by exactly
|
||||
this height when it opens, so the scope viewport above keeps its size. The fixed
|
||||
basis MUST match PANEL_HEIGHT in ScopeApp.tsx (the grow delta), or the scope would
|
||||
shift. Content taller than this scrolls. */
|
||||
/* Settings panel along the bottom. The editor asks the host to grow by this height,
|
||||
but Windows DAWs may delay or constrain that resize, so the viewport locks to its
|
||||
previous height and this panel overflows instead of shrinking the scope. */
|
||||
.spectrum-app__panel {
|
||||
flex: 0 0 280px;
|
||||
width: 100%;
|
||||
|
||||
Reference in New Issue
Block a user