mirror of
https://github.com/Boof2015/prism.git
synced 2026-08-18 03:33:40 +02:00
significantly improve latency
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import { useEffect, useMemo, useRef, type CSSProperties, type JSX, type ReactNode } from 'react'
|
||||
import { useEffect, useMemo, useRef, useState, type CSSProperties, type JSX, type ReactNode } from 'react'
|
||||
import { useAudioStore } from '../stores/audioStore'
|
||||
import { useSettingsStore, type ScopeSettings } from '../stores/settingsStore'
|
||||
import { useThemeStore, PRESETS, PRESET_IDS } from '../stores/themeStore'
|
||||
import type { ScopeKind } from '../../types/scope'
|
||||
import { buildAnalyzerGridTemplateColumns } from '../analyzerLayout'
|
||||
import { audioRouter, type AudioRouterDiagnostics } from '../audio/AudioRouter'
|
||||
import type { CaptureBackendKind, CaptureBackendPolicy } from '../../types/capture'
|
||||
|
||||
const SCOPE_LABELS: Record<ScopeKind, string> = {
|
||||
spectrum: 'Spectrum',
|
||||
@@ -15,6 +17,23 @@ const SCOPE_LABELS: Record<ScopeKind, string> = {
|
||||
waveform: 'Waveform',
|
||||
}
|
||||
|
||||
function captureBackendLabel(kind: CaptureBackendKind | null): string {
|
||||
switch (kind) {
|
||||
case 'electron-system':
|
||||
return 'Electron System'
|
||||
case 'electron-device':
|
||||
return 'Electron Device'
|
||||
case 'native-macos':
|
||||
return 'Native macOS'
|
||||
case 'native-windows':
|
||||
return 'Native Windows'
|
||||
case 'native-linux':
|
||||
return 'Native Linux'
|
||||
default:
|
||||
return 'None'
|
||||
}
|
||||
}
|
||||
|
||||
function vectorscopeModeLabel(mode: ScopeSettings['vectorscope']['mode']): string {
|
||||
switch (mode) {
|
||||
case 'lissajous':
|
||||
@@ -480,17 +499,25 @@ export default function SettingsPanel({ onClose, onHeightChange }: SettingsPanel
|
||||
devices,
|
||||
selectedDeviceId,
|
||||
captureMode,
|
||||
capturePolicy,
|
||||
activeBackendKind,
|
||||
activeBackendReason,
|
||||
isCapturing,
|
||||
captureStatus,
|
||||
captureError,
|
||||
refreshDevices,
|
||||
refreshBackendSupport,
|
||||
selectDevice,
|
||||
setCaptureMode,
|
||||
setCapturePolicy,
|
||||
startCapture,
|
||||
} = useAudioStore()
|
||||
const { scopeSettings, updateScopeSettings, hiddenScopes, scopeOrder, widthWeights } = useSettingsStore()
|
||||
const { presetId, accent, setPreset, setCustomAccent, customAccent } = useThemeStore()
|
||||
const panelRef = useRef<HTMLDivElement | null>(null)
|
||||
const [routerDiagnostics, setRouterDiagnostics] = useState<AudioRouterDiagnostics>(
|
||||
() => audioRouter.getDiagnosticsSnapshot(),
|
||||
)
|
||||
|
||||
const visibleScopes = scopeOrder.filter((kind) => !hiddenScopes.has(kind))
|
||||
const scopeTrackStyle = useMemo(() => {
|
||||
@@ -500,8 +527,18 @@ export default function SettingsPanel({ onClose, onHeightChange }: SettingsPanel
|
||||
}, [visibleScopes, widthWeights])
|
||||
|
||||
useEffect(() => {
|
||||
void refreshDevices()
|
||||
}, [refreshDevices])
|
||||
void Promise.all([refreshDevices(), refreshBackendSupport()])
|
||||
}, [refreshBackendSupport, refreshDevices])
|
||||
|
||||
useEffect(() => {
|
||||
const intervalId = window.setInterval(() => {
|
||||
setRouterDiagnostics(audioRouter.getDiagnosticsSnapshot())
|
||||
}, 250)
|
||||
|
||||
return () => {
|
||||
window.clearInterval(intervalId)
|
||||
}
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
const panel = panelRef.current
|
||||
@@ -538,6 +575,10 @@ export default function SettingsPanel({ onClose, onHeightChange }: SettingsPanel
|
||||
await startCapture()
|
||||
}
|
||||
|
||||
const handlePolicyChange = async (value: string): Promise<void> => {
|
||||
await setCapturePolicy(value as CaptureBackendPolicy)
|
||||
}
|
||||
|
||||
const indicatorLabel = isCapturing
|
||||
? 'Capturing'
|
||||
: captureStatus === 'connecting'
|
||||
@@ -546,6 +587,10 @@ export default function SettingsPanel({ onClose, onHeightChange }: SettingsPanel
|
||||
? 'Capture Failed'
|
||||
: 'Idle'
|
||||
|
||||
const latencyLabel = routerDiagnostics.overallP95CaptureToScopeMs === null
|
||||
? 'Waiting for samples'
|
||||
: `${routerDiagnostics.overallP95CaptureToScopeMs.toFixed(1)} ms p95`
|
||||
|
||||
return (
|
||||
<div className="settings-panel" ref={panelRef}>
|
||||
<div className="settings-panel__utility-row">
|
||||
@@ -572,11 +617,38 @@ export default function SettingsPanel({ onClose, onHeightChange }: SettingsPanel
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label className="settings-control settings-control--stack">
|
||||
<span className="settings-control__label">Backend Policy</span>
|
||||
<select
|
||||
className="settings-control__select"
|
||||
value={capturePolicy}
|
||||
onChange={(event) => {
|
||||
void handlePolicyChange(event.target.value)
|
||||
}}
|
||||
>
|
||||
<option value="auto">Auto</option>
|
||||
<option value="native">Native</option>
|
||||
<option value="electron">Electron</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<div className={`settings-status-pill is-${captureStatus}`.trim()}>
|
||||
<span className="settings-status-pill__dot" />
|
||||
<span>{indicatorLabel}</span>
|
||||
</div>
|
||||
|
||||
<div className="settings-info-text">
|
||||
Active backend: {captureBackendLabel(activeBackendKind)}
|
||||
</div>
|
||||
|
||||
<div className="settings-info-text">
|
||||
Latency probe: {latencyLabel} · overwrites {routerDiagnostics.totalOverwriteCount} · stale drops {routerDiagnostics.staleSessionDrops}
|
||||
</div>
|
||||
|
||||
{activeBackendReason ? (
|
||||
<div className="settings-info-text">{activeBackendReason}</div>
|
||||
) : null}
|
||||
|
||||
{captureError ? (
|
||||
<div className="settings-error-text">{captureError}</div>
|
||||
) : null}
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useThemeStore } from '../stores/themeStore'
|
||||
import type { ScopeKind } from '../../types/scope'
|
||||
import ScopeModule from './ScopeModule'
|
||||
import { buildAnalyzerGridTemplateColumns } from '../analyzerLayout'
|
||||
import { audioRouter } from '../audio/AudioRouter'
|
||||
|
||||
export default function Strip(): JSX.Element {
|
||||
const scopeOrder = useSettingsStore((s) => s.scopeOrder)
|
||||
@@ -15,7 +16,11 @@ export default function Strip(): JSX.Element {
|
||||
const gridRef = useRef<HTMLDivElement>(null)
|
||||
const scopeRefs = useRef<Partial<Record<ScopeKind, HTMLDivElement | null>>>({})
|
||||
const [handleOffsets, setHandleOffsets] = useState<number[]>([])
|
||||
const visibleScopes = scopeOrder.filter((k) => !hiddenScopes.has(k))
|
||||
const visibleScopes = useMemo(
|
||||
() => scopeOrder.filter((k) => !hiddenScopes.has(k)),
|
||||
[hiddenScopes, scopeOrder],
|
||||
)
|
||||
const visibleScopeKey = useMemo(() => visibleScopes.join('|'), [visibleScopes])
|
||||
const gridTemplateColumns = useMemo(() => {
|
||||
return buildAnalyzerGridTemplateColumns(visibleScopes, widthWeights)
|
||||
}, [visibleScopes, widthWeights])
|
||||
@@ -40,6 +45,23 @@ export default function Strip(): JSX.Element {
|
||||
setHandleOffsets(nextOffsets)
|
||||
}, [visibleScopes])
|
||||
|
||||
useEffect(() => {
|
||||
const visibleScopeSet = new Set(visibleScopes)
|
||||
audioRouter.setVisualizerConsumerDemand('docked-strip', {
|
||||
spectrum: visibleScopeSet.has('spectrum'),
|
||||
oscilloscope: visibleScopeSet.has('oscilloscope'),
|
||||
vectorscope: visibleScopeSet.has('vectorscope'),
|
||||
spectrogram: visibleScopeSet.has('spectrogram'),
|
||||
vumeter: visibleScopeSet.has('vumeter'),
|
||||
lufsmeter: visibleScopeSet.has('lufsmeter'),
|
||||
waveform: visibleScopeSet.has('waveform'),
|
||||
})
|
||||
|
||||
return () => {
|
||||
audioRouter.clearVisualizerConsumerDemand('docked-strip')
|
||||
}
|
||||
}, [visibleScopeKey, visibleScopes])
|
||||
|
||||
useEffect(() => {
|
||||
const strip = stripRef.current
|
||||
if (!strip) return
|
||||
|
||||
Reference in New Issue
Block a user