mirror of
https://github.com/Boof2015/prism.git
synced 2026-08-16 16:21:11 +02:00
scope rotation
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import { useEffect, useRef, type JSX } from 'react'
|
||||
import { useEffect, useLayoutEffect, useRef, type JSX } from 'react'
|
||||
import { Oscilloscope } from '../renderer/visualizers/Oscilloscope'
|
||||
import type { ScopeSettings } from '../types/settings'
|
||||
import type { ResolvedOscilloscopeTheme } from '../types/theme'
|
||||
import type { BridgeOscilloscopeAnalyzer } from './BridgeOscilloscopeAnalyzer'
|
||||
import type { PluginWebViewDataSource } from './PluginWebViewDataSource'
|
||||
import { oscilloscopeSettingsToOptions } from './oscilloscopeOptions'
|
||||
import { getScopeCanvasTransformStyle } from '../renderer/scopeCanvasTransform'
|
||||
import { applyPluginScopeCanvasLayout } from './scopeCanvasLayout'
|
||||
|
||||
interface OscilloscopeScopeProps {
|
||||
dataSource: PluginWebViewDataSource
|
||||
@@ -22,6 +24,9 @@ export default function OscilloscopeScope({
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
const vizRef = useRef<Oscilloscope | null>(null)
|
||||
const rotationRef = useRef(settings.rotation)
|
||||
const applySizeRef = useRef<(() => void) | null>(null)
|
||||
rotationRef.current = settings.rotation
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current
|
||||
@@ -36,16 +41,12 @@ export default function OscilloscopeScope({
|
||||
vizRef.current = viz
|
||||
|
||||
const applySize = (): void => {
|
||||
const rect = container.getBoundingClientRect()
|
||||
const dpr = window.devicePixelRatio || 1
|
||||
const pixelWidth = Math.max(1, Math.floor(rect.width * dpr))
|
||||
const pixelHeight = Math.max(1, Math.floor(rect.height * dpr))
|
||||
if (canvas.width !== pixelWidth || canvas.height !== pixelHeight) {
|
||||
canvas.width = pixelWidth
|
||||
canvas.height = pixelHeight
|
||||
const { changed } = applyPluginScopeCanvasLayout(container, canvas, rotationRef.current)
|
||||
if (changed) {
|
||||
viz.resize()
|
||||
}
|
||||
}
|
||||
applySizeRef.current = applySize
|
||||
|
||||
applySize()
|
||||
viz.start()
|
||||
@@ -54,6 +55,7 @@ export default function OscilloscopeScope({
|
||||
|
||||
return () => {
|
||||
observer.disconnect()
|
||||
applySizeRef.current = null
|
||||
viz.dispose()
|
||||
vizRef.current = null
|
||||
}
|
||||
@@ -65,9 +67,17 @@ export default function OscilloscopeScope({
|
||||
vizRef.current?.setOptions(oscilloscopeSettingsToOptions(settings, theme))
|
||||
}, [settings, theme])
|
||||
|
||||
useLayoutEffect(() => {
|
||||
applySizeRef.current?.()
|
||||
}, [settings.rotation])
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="spectrum-scope">
|
||||
<canvas ref={canvasRef} className="spectrum-scope__canvas" />
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="spectrum-scope__canvas"
|
||||
style={getScopeCanvasTransformStyle(settings.rotation, settings.mirrorHorizontal)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, type JSX } from 'react'
|
||||
import { useEffect, useLayoutEffect, useRef, type JSX } from 'react'
|
||||
import { Spectrogram } from '../renderer/visualizers/Spectrogram'
|
||||
import type { ScopeSettings } from '../types/settings'
|
||||
import type { ResolvedSpectrogramTheme } from '../types/theme'
|
||||
@@ -6,6 +6,8 @@ import type { BridgeSpectrogramAnalyzer } from './BridgeSpectrogramAnalyzer'
|
||||
import type { PluginWebViewDataSource } from './PluginWebViewDataSource'
|
||||
import { spectrogramSettingsToOptions } from './spectrogramOptions'
|
||||
import { resolveScrollingCanvasSize } from './scrollingCanvas'
|
||||
import { getScopeCanvasTransformStyle } from '../renderer/scopeCanvasTransform'
|
||||
import { applyPluginScopeCanvasLayout } from './scopeCanvasLayout'
|
||||
|
||||
interface SpectrogramScopeProps {
|
||||
dataSource: PluginWebViewDataSource
|
||||
@@ -23,6 +25,9 @@ export default function SpectrogramScope({
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
const vizRef = useRef<Spectrogram | null>(null)
|
||||
const rotationRef = useRef(settings.rotation)
|
||||
const applySizeRef = useRef<(() => void) | null>(null)
|
||||
rotationRef.current = settings.rotation
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current
|
||||
@@ -37,15 +42,17 @@ export default function SpectrogramScope({
|
||||
vizRef.current = viz
|
||||
|
||||
const applySize = (): void => {
|
||||
const rect = container.getBoundingClientRect()
|
||||
const dpr = window.devicePixelRatio || 1
|
||||
const { width, height } = resolveScrollingCanvasSize(rect.width, rect.height, dpr)
|
||||
if (canvas.width !== width || canvas.height !== height) {
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
const { changed } = applyPluginScopeCanvasLayout(
|
||||
container,
|
||||
canvas,
|
||||
rotationRef.current,
|
||||
resolveScrollingCanvasSize,
|
||||
)
|
||||
if (changed) {
|
||||
viz.resize()
|
||||
}
|
||||
}
|
||||
applySizeRef.current = applySize
|
||||
|
||||
applySize()
|
||||
viz.start()
|
||||
@@ -54,6 +61,7 @@ export default function SpectrogramScope({
|
||||
|
||||
return () => {
|
||||
observer.disconnect()
|
||||
applySizeRef.current = null
|
||||
viz.dispose()
|
||||
vizRef.current = null
|
||||
}
|
||||
@@ -65,9 +73,17 @@ export default function SpectrogramScope({
|
||||
vizRef.current?.setOptions(spectrogramSettingsToOptions(settings, theme))
|
||||
}, [settings, theme])
|
||||
|
||||
useLayoutEffect(() => {
|
||||
applySizeRef.current?.()
|
||||
}, [settings.rotation])
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="spectrum-scope">
|
||||
<canvas ref={canvasRef} className="spectrum-scope__canvas" />
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="spectrum-scope__canvas"
|
||||
style={getScopeCanvasTransformStyle(settings.rotation, settings.mirrorHorizontal)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -6,10 +6,11 @@ import type { SpectrumPeakInfo } from '../types/spectrum'
|
||||
import type { BridgeSpectrumAnalyzer } from './BridgeSpectrumAnalyzer'
|
||||
import type { PluginWebViewDataSource } from './PluginWebViewDataSource'
|
||||
import { spectrumSettingsToOptions } from './spectrumOptions'
|
||||
import { getScopeCanvasTransformStyle } from '../renderer/scopeCanvasTransform'
|
||||
import { applyPluginScopeCanvasLayout } from './scopeCanvasLayout'
|
||||
import {
|
||||
formatSpectrumPeakDb,
|
||||
formatSpectrumPeakFrequency,
|
||||
measureCanvasResizeState,
|
||||
resolveFollowingPeakOverlayStyle,
|
||||
type CanvasResizeState,
|
||||
type SizeMeasurement,
|
||||
@@ -32,11 +33,14 @@ export default function SpectrumScope({
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
const analyzerRef = useRef<SpectrumAnalyzer | null>(null)
|
||||
const resizeStateRef = useRef<CanvasResizeState | null>(null)
|
||||
const rotationRef = useRef(settings.rotation)
|
||||
const applySizeRef = useRef<(() => void) | null>(null)
|
||||
const peakOverlayRef = useRef<HTMLDivElement | null>(null)
|
||||
const [peak, setPeak] = useState<SpectrumPeakInfo | null>(null)
|
||||
const [overlaySize, setOverlaySize] = useState<SizeMeasurement | null>(null)
|
||||
|
||||
const peakMode = settings.peakInfoMode
|
||||
rotationRef.current = settings.rotation
|
||||
|
||||
// Create the analyzer once per data source / shim.
|
||||
useEffect(() => {
|
||||
@@ -54,14 +58,13 @@ export default function SpectrumScope({
|
||||
analyzerRef.current = analyzer
|
||||
|
||||
const applySize = (): void => {
|
||||
const state = measureCanvasResizeState(container)
|
||||
resizeStateRef.current = state
|
||||
if (canvas.width !== state.pixelWidth || canvas.height !== state.pixelHeight) {
|
||||
canvas.width = state.pixelWidth
|
||||
canvas.height = state.pixelHeight
|
||||
const { changed, layout } = applyPluginScopeCanvasLayout(container, canvas, rotationRef.current)
|
||||
resizeStateRef.current = layout
|
||||
if (changed) {
|
||||
analyzer.resize()
|
||||
}
|
||||
}
|
||||
applySizeRef.current = applySize
|
||||
|
||||
applySize()
|
||||
analyzer.start()
|
||||
@@ -70,6 +73,7 @@ export default function SpectrumScope({
|
||||
|
||||
return () => {
|
||||
observer.disconnect()
|
||||
applySizeRef.current = null
|
||||
analyzer.dispose()
|
||||
analyzerRef.current = null
|
||||
}
|
||||
@@ -87,6 +91,10 @@ export default function SpectrumScope({
|
||||
})
|
||||
}, [settings, theme])
|
||||
|
||||
useLayoutEffect(() => {
|
||||
applySizeRef.current?.()
|
||||
}, [settings.rotation])
|
||||
|
||||
// Measure the overlay so "following" placement can avoid the screen edges.
|
||||
useLayoutEffect(() => {
|
||||
const overlay = peakOverlayRef.current
|
||||
@@ -107,12 +115,22 @@ export default function SpectrumScope({
|
||||
const showPeak = peakMode !== 'off' && peak !== null
|
||||
const overlayStyle: CSSProperties | undefined =
|
||||
peakMode === 'following' && peak
|
||||
? resolveFollowingPeakOverlayStyle(peak, resizeStateRef.current, overlaySize)
|
||||
? resolveFollowingPeakOverlayStyle(
|
||||
peak,
|
||||
resizeStateRef.current,
|
||||
overlaySize,
|
||||
settings.rotation,
|
||||
settings.mirrorHorizontal,
|
||||
)
|
||||
: undefined
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="spectrum-scope">
|
||||
<canvas ref={canvasRef} className="spectrum-scope__canvas" />
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="spectrum-scope__canvas"
|
||||
style={getScopeCanvasTransformStyle(settings.rotation, settings.mirrorHorizontal)}
|
||||
/>
|
||||
{showPeak && peak && (
|
||||
<div
|
||||
ref={peakMode === 'following' ? peakOverlayRef : null}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef, type JSX } from 'react'
|
||||
import { useEffect, useLayoutEffect, useRef, type JSX } from 'react'
|
||||
import { Waveform } from '../renderer/visualizers/Waveform'
|
||||
import type { ScopeSettings } from '../types/settings'
|
||||
import type { ResolvedWaveformTheme } from '../types/theme'
|
||||
@@ -6,6 +6,8 @@ import type { BridgeWaveformAnalyzer } from './BridgeWaveformAnalyzer'
|
||||
import type { PluginWebViewDataSource } from './PluginWebViewDataSource'
|
||||
import { waveformSettingsToOptions } from './waveformOptions'
|
||||
import { resolveScrollingCanvasSize } from './scrollingCanvas'
|
||||
import { getScopeCanvasTransformStyle } from '../renderer/scopeCanvasTransform'
|
||||
import { applyPluginScopeCanvasLayout } from './scopeCanvasLayout'
|
||||
|
||||
interface WaveformScopeProps {
|
||||
dataSource: PluginWebViewDataSource
|
||||
@@ -23,6 +25,9 @@ export default function WaveformScope({
|
||||
const containerRef = useRef<HTMLDivElement>(null)
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
const vizRef = useRef<Waveform | null>(null)
|
||||
const rotationRef = useRef(settings.rotation)
|
||||
const applySizeRef = useRef<(() => void) | null>(null)
|
||||
rotationRef.current = settings.rotation
|
||||
|
||||
useEffect(() => {
|
||||
const container = containerRef.current
|
||||
@@ -37,15 +42,17 @@ export default function WaveformScope({
|
||||
vizRef.current = viz
|
||||
|
||||
const applySize = (): void => {
|
||||
const rect = container.getBoundingClientRect()
|
||||
const dpr = window.devicePixelRatio || 1
|
||||
const { width, height } = resolveScrollingCanvasSize(rect.width, rect.height, dpr)
|
||||
if (canvas.width !== width || canvas.height !== height) {
|
||||
canvas.width = width
|
||||
canvas.height = height
|
||||
const { changed } = applyPluginScopeCanvasLayout(
|
||||
container,
|
||||
canvas,
|
||||
rotationRef.current,
|
||||
resolveScrollingCanvasSize,
|
||||
)
|
||||
if (changed) {
|
||||
viz.resize()
|
||||
}
|
||||
}
|
||||
applySizeRef.current = applySize
|
||||
|
||||
applySize()
|
||||
viz.start()
|
||||
@@ -54,6 +61,7 @@ export default function WaveformScope({
|
||||
|
||||
return () => {
|
||||
observer.disconnect()
|
||||
applySizeRef.current = null
|
||||
viz.dispose()
|
||||
vizRef.current = null
|
||||
}
|
||||
@@ -65,9 +73,17 @@ export default function WaveformScope({
|
||||
vizRef.current?.setOptions(waveformSettingsToOptions(settings, theme))
|
||||
}, [settings, theme])
|
||||
|
||||
useLayoutEffect(() => {
|
||||
applySizeRef.current?.()
|
||||
}, [settings.rotation])
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="spectrum-scope">
|
||||
<canvas ref={canvasRef} className="spectrum-scope__canvas" />
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="spectrum-scope__canvas"
|
||||
style={getScopeCanvasTransformStyle(settings.rotation, settings.mirrorHorizontal)}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
import type { CSSProperties } from 'react'
|
||||
import type { SpectrumPeakInfo } from '../types/spectrum'
|
||||
import type { ScopeDisplayRotation } from '../types/scopeTransform'
|
||||
import {
|
||||
measureScopeCanvasLayout,
|
||||
transformNormalizedScopePoint,
|
||||
type ScopeCanvasLayout,
|
||||
} from '../renderer/scopeCanvasTransform'
|
||||
|
||||
/**
|
||||
* Peak-overlay positioning + formatting, mirroring ScopeModule.tsx so the plugin's
|
||||
@@ -7,13 +13,7 @@ import type { SpectrumPeakInfo } from '../types/spectrum'
|
||||
* copy (pure functions) so the plugin doesn't import the heavy ScopeModule.
|
||||
*/
|
||||
|
||||
export interface CanvasResizeState {
|
||||
cssWidth: number
|
||||
cssHeight: number
|
||||
pixelWidth: number
|
||||
pixelHeight: number
|
||||
dpr: number
|
||||
}
|
||||
export type CanvasResizeState = ScopeCanvasLayout
|
||||
|
||||
export interface SizeMeasurement {
|
||||
width: number
|
||||
@@ -45,25 +45,19 @@ export function formatSpectrumPeakFrequency(value: number): string {
|
||||
return `${value.toFixed(2)}Hz`
|
||||
}
|
||||
|
||||
export function measureCanvasResizeState(container: HTMLElement): CanvasResizeState {
|
||||
const rect = container.getBoundingClientRect()
|
||||
const cssWidth = Math.max(1, Math.floor(rect.width))
|
||||
const cssHeight = Math.max(1, Math.floor(rect.height))
|
||||
const dpr = window.devicePixelRatio || 1
|
||||
|
||||
return {
|
||||
cssWidth,
|
||||
cssHeight,
|
||||
pixelWidth: Math.max(1, Math.floor(cssWidth * dpr)),
|
||||
pixelHeight: Math.max(1, Math.floor(cssHeight * dpr)),
|
||||
dpr,
|
||||
}
|
||||
export function measureCanvasResizeState(
|
||||
container: HTMLElement,
|
||||
rotation: ScopeDisplayRotation = 0,
|
||||
): CanvasResizeState {
|
||||
return measureScopeCanvasLayout(container, rotation)
|
||||
}
|
||||
|
||||
export function resolveFollowingPeakOverlayStyle(
|
||||
peakInfo: SpectrumPeakInfo,
|
||||
resizeState: CanvasResizeState | null,
|
||||
overlaySize: SizeMeasurement | null,
|
||||
rotation: ScopeDisplayRotation = 0,
|
||||
mirrorHorizontal = false,
|
||||
): CSSProperties {
|
||||
if (!resizeState) {
|
||||
return {
|
||||
@@ -72,12 +66,17 @@ export function resolveFollowingPeakOverlayStyle(
|
||||
}
|
||||
}
|
||||
|
||||
const width = resizeState.cssWidth
|
||||
const height = resizeState.cssHeight
|
||||
const width = resizeState.viewportCssWidth
|
||||
const height = resizeState.viewportCssHeight
|
||||
const overlayWidth = overlaySize?.width ?? SPECTRUM_PEAK_OVERLAY_FALLBACK_WIDTH_PX
|
||||
const overlayHeight = overlaySize?.height ?? SPECTRUM_PEAK_OVERLAY_FALLBACK_HEIGHT_PX
|
||||
const peakX = peakInfo.normalizedX * width
|
||||
const peakY = peakInfo.normalizedY * height
|
||||
const transformedPeak = transformNormalizedScopePoint(
|
||||
{ x: peakInfo.normalizedX, y: peakInfo.normalizedY },
|
||||
rotation,
|
||||
mirrorHorizontal,
|
||||
)
|
||||
const peakX = transformedPeak.x * width
|
||||
const peakY = transformedPeak.y * height
|
||||
const maxLeft = Math.max(
|
||||
SPECTRUM_PEAK_OVERLAY_MARGIN_PX,
|
||||
width - overlayWidth - SPECTRUM_PEAK_OVERLAY_MARGIN_PX,
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import type { ScopeDisplayRotation } from '../types/scopeTransform'
|
||||
import {
|
||||
measureScopeCanvasLayout,
|
||||
type ScopeCanvasLayout,
|
||||
} from '../renderer/scopeCanvasTransform'
|
||||
|
||||
type PixelSizeResolver = (
|
||||
cssWidth: number,
|
||||
cssHeight: number,
|
||||
dpr: number,
|
||||
) => { width: number; height: number }
|
||||
|
||||
export function applyPluginScopeCanvasLayout(
|
||||
container: HTMLElement,
|
||||
canvas: HTMLCanvasElement,
|
||||
rotation: ScopeDisplayRotation,
|
||||
pixelSizeResolver?: PixelSizeResolver,
|
||||
): { changed: boolean; layout: ScopeCanvasLayout } {
|
||||
const layout = measureScopeCanvasLayout(container, rotation)
|
||||
const resolvedPixels = pixelSizeResolver
|
||||
? pixelSizeResolver(layout.cssWidth, layout.cssHeight, layout.dpr)
|
||||
: { width: layout.pixelWidth, height: layout.pixelHeight }
|
||||
|
||||
canvas.style.width = `${layout.cssWidth}px`
|
||||
canvas.style.height = `${layout.cssHeight}px`
|
||||
|
||||
const changed = canvas.width !== resolvedPixels.width || canvas.height !== resolvedPixels.height
|
||||
if (changed) {
|
||||
canvas.width = resolvedPixels.width
|
||||
canvas.height = resolvedPixels.height
|
||||
}
|
||||
|
||||
return { changed, layout }
|
||||
}
|
||||
@@ -20,7 +20,7 @@ export function spectrogramSettingsToOptions(
|
||||
contrast: settings.contrast,
|
||||
clarityMode: settings.clarityMode,
|
||||
scaleMode: settings.scaleMode,
|
||||
orientation: settings.orientation,
|
||||
orientation: 'horizontal',
|
||||
colorScheme: settings.colorScheme,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,21 +3,13 @@ import { DEFAULT_SCOPE_SETTINGS, type ScopeSettings } from '../types/settings'
|
||||
import type { ScopeKind } from '../types/scope'
|
||||
import type { PrismResolvedTheme } from '../types/theme'
|
||||
import { createBundledThemes, createDefaultTheme, parseThemeFileContent, resolveTheme } from '../shared/themeState'
|
||||
import { mergeScopeSettings as normalizeAllScopeSettings } from '../shared/profileState'
|
||||
import { emitToHost, onHostEvent } from './juceBridge'
|
||||
|
||||
const DEFAULT_THEME = resolveTheme(createDefaultTheme())
|
||||
|
||||
function mergeScopeSettings<K extends ScopeKind>(kind: K, raw: unknown): ScopeSettings[K] {
|
||||
const defaults = DEFAULT_SCOPE_SETTINGS[kind] as Record<string, unknown>
|
||||
if (typeof raw !== 'object' || raw === null) return { ...defaults } as ScopeSettings[K]
|
||||
const parsed = raw as Record<string, unknown>
|
||||
const next: Record<string, unknown> = { ...defaults }
|
||||
for (const key of Object.keys(defaults)) {
|
||||
if (key in parsed && typeof parsed[key] === typeof defaults[key]) {
|
||||
next[key] = parsed[key]
|
||||
}
|
||||
}
|
||||
return next as ScopeSettings[K]
|
||||
return normalizeAllScopeSettings({ [kind]: raw })[kind]
|
||||
}
|
||||
|
||||
function resolveAppTheme(themeId: string, themeFile: string): PrismResolvedTheme {
|
||||
|
||||
Reference in New Issue
Block a user