mirror of
https://github.com/Boof2015/prism.git
synced 2026-08-15 07:41:04 +02:00
improve scope speed and efficiency
This commit is contained in:
@@ -64,6 +64,7 @@ function resolveNativeCapturePollDelay(chunkCount: number): number {
|
||||
return chunkCount > 0 ? 0 : 2
|
||||
}
|
||||
|
||||
|
||||
export interface CaptureManagerStatus {
|
||||
captureMode: CaptureMode
|
||||
backendPolicy: CaptureBackendPolicy
|
||||
@@ -860,10 +861,6 @@ class AudioCapture {
|
||||
capturedAt: chunk.capturedAt,
|
||||
sequence: chunk.sequence,
|
||||
})
|
||||
nativeVisualizerTransport.handleChunk(chunk.left, chunk.right, {
|
||||
sessionId: this.sessionId,
|
||||
channelCount: chunk.channelCount,
|
||||
})
|
||||
}
|
||||
|
||||
setInputGain(db: number): void {
|
||||
|
||||
@@ -1,11 +1,6 @@
|
||||
import { oscilloscope, spectrum, vectorscope, isNativeAvailable } from './native'
|
||||
import type { VisualizerConsumerDemand } from './AudioRouter'
|
||||
|
||||
export interface NativeVisualizerTransportChunkMeta {
|
||||
sessionId?: number
|
||||
channelCount?: number
|
||||
}
|
||||
|
||||
export interface NativeVisualizerTransportSessionState {
|
||||
sessionId: number
|
||||
sampleRate: number
|
||||
@@ -78,23 +73,12 @@ function normalizeDemand(demand: VisualizerConsumerDemand): Required<VisualizerC
|
||||
export class NativeVisualizerTransport {
|
||||
private readonly bridge: NativeVisualizerTransportBridge
|
||||
private demand: Required<VisualizerConsumerDemand> = { ...EMPTY_DEMAND }
|
||||
private sessionId = 0
|
||||
private sampleRate = 48000
|
||||
private capturing = false
|
||||
private hasSpectrumData = false
|
||||
private spectrumMonoScratch = new Float32Array(0)
|
||||
|
||||
constructor(bridge: NativeVisualizerTransportBridge = defaultBridge) {
|
||||
this.bridge = bridge
|
||||
}
|
||||
|
||||
private ensureSpectrumMonoScratch(length: number): Float32Array {
|
||||
if (this.spectrumMonoScratch.length !== length) {
|
||||
this.spectrumMonoScratch = new Float32Array(length)
|
||||
}
|
||||
return this.spectrumMonoScratch
|
||||
}
|
||||
|
||||
setDemand(demand: VisualizerConsumerDemand): void {
|
||||
const nextDemand = normalizeDemand(demand)
|
||||
if (
|
||||
@@ -115,7 +99,6 @@ export class NativeVisualizerTransport {
|
||||
}
|
||||
if (this.demand.spectrum && !nextDemand.spectrum) {
|
||||
this.bridge.spectrum.reset()
|
||||
this.hasSpectrumData = false
|
||||
}
|
||||
if (this.demand.vectorscope && !nextDemand.vectorscope) {
|
||||
this.bridge.vectorscope.reset()
|
||||
@@ -125,7 +108,6 @@ export class NativeVisualizerTransport {
|
||||
}
|
||||
if (!this.demand.spectrum && nextDemand.spectrum) {
|
||||
this.bridge.spectrum.setSampleRate(this.sampleRate)
|
||||
this.hasSpectrumData = false
|
||||
}
|
||||
if (!this.demand.vectorscope && nextDemand.vectorscope) {
|
||||
this.bridge.vectorscope.setSampleRate(this.sampleRate)
|
||||
@@ -135,52 +117,8 @@ export class NativeVisualizerTransport {
|
||||
this.demand = nextDemand
|
||||
}
|
||||
|
||||
handleChunk(left: Float32Array, right: Float32Array, meta: NativeVisualizerTransportChunkMeta = {}): void {
|
||||
if (!this.capturing || !this.bridge.isAvailable()) {
|
||||
return
|
||||
}
|
||||
|
||||
if (meta.sessionId !== undefined && meta.sessionId !== this.sessionId) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.demand.oscilloscope && !this.demand.spectrum && !this.demand.vectorscope) {
|
||||
return
|
||||
}
|
||||
|
||||
const effectiveChannelCount = Math.max(1, Math.floor(meta.channelCount ?? 2) || 1)
|
||||
const resolvedRight = effectiveChannelCount > 1 && right.length > 0 ? right : left
|
||||
const length = Math.min(left.length, resolvedRight.length)
|
||||
if (length === 0) {
|
||||
return
|
||||
}
|
||||
|
||||
const leftSamples = left.length === length ? left : left.subarray(0, length)
|
||||
const rightSamples = resolvedRight.length === length ? resolvedRight : resolvedRight.subarray(0, length)
|
||||
|
||||
if (this.demand.oscilloscope) {
|
||||
this.bridge.oscilloscope.pushSamples(leftSamples)
|
||||
}
|
||||
|
||||
if (this.demand.vectorscope) {
|
||||
this.bridge.vectorscope.pushSamples(leftSamples, rightSamples)
|
||||
}
|
||||
|
||||
if (this.demand.spectrum) {
|
||||
const mono = this.ensureSpectrumMonoScratch(length)
|
||||
for (let index = 0; index < length; index += 1) {
|
||||
mono[index] = (leftSamples[index] + rightSamples[index]) * 0.5
|
||||
}
|
||||
this.bridge.spectrum.pushSamples(mono)
|
||||
this.hasSpectrumData = true
|
||||
}
|
||||
}
|
||||
|
||||
reset(sessionState: NativeVisualizerTransportSessionState): void {
|
||||
this.sessionId = sessionState.sessionId
|
||||
this.capturing = sessionState.capturing
|
||||
this.sampleRate = Math.max(1, Math.floor(sessionState.sampleRate) || 1)
|
||||
this.hasSpectrumData = false
|
||||
|
||||
if (!this.bridge.isAvailable()) {
|
||||
return
|
||||
@@ -204,14 +142,6 @@ export class NativeVisualizerTransport {
|
||||
this.bridge.vectorscope.setSampleRate(this.sampleRate)
|
||||
}
|
||||
}
|
||||
|
||||
fillLatestSpectrumMagnitudes(output: Float32Array): number {
|
||||
if (!this.bridge.isAvailable() || !this.demand.spectrum || !this.hasSpectrumData) {
|
||||
return 0
|
||||
}
|
||||
|
||||
return this.bridge.spectrum.fillMagnitudes(output)
|
||||
}
|
||||
}
|
||||
|
||||
export const nativeVisualizerTransport = new NativeVisualizerTransport()
|
||||
|
||||
@@ -58,12 +58,6 @@ export class ScopePopoutDataSource implements AnyScopeDataSource {
|
||||
if (isStereoScope(this.scopeKind)) {
|
||||
if (!isStereoBatch(batch)) return
|
||||
this.stereoQueue.push(...batch)
|
||||
for (const chunk of batch) {
|
||||
this.nativeVisualizerTransport.handleChunk(chunk.left, chunk.right, {
|
||||
sessionId: this.sessionState.sessionId,
|
||||
channelCount: this.sessionState.channelCount,
|
||||
})
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -71,38 +65,16 @@ export class ScopePopoutDataSource implements AnyScopeDataSource {
|
||||
if (isStereoBatch(batch)) {
|
||||
this.monoQueue = []
|
||||
this.stereoQueue.push(...batch)
|
||||
if (this.scopeKind === 'spectrum') {
|
||||
for (const chunk of batch) {
|
||||
this.nativeVisualizerTransport.handleChunk(chunk.left, chunk.right, {
|
||||
sessionId: this.sessionState.sessionId,
|
||||
channelCount: this.sessionState.channelCount,
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
this.stereoQueue = []
|
||||
this.monoQueue.push(...batch)
|
||||
if (this.scopeKind === 'spectrum') {
|
||||
for (const chunk of batch) {
|
||||
this.nativeVisualizerTransport.handleChunk(chunk, chunk, {
|
||||
sessionId: this.sessionState.sessionId,
|
||||
channelCount: 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (isStereoBatch(batch)) return
|
||||
this.monoQueue.push(...batch)
|
||||
for (const chunk of batch) {
|
||||
this.nativeVisualizerTransport.handleChunk(chunk, chunk, {
|
||||
sessionId: this.sessionState.sessionId,
|
||||
channelCount: 1,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
setSessionState(nextState: ScopePopoutSessionState): void {
|
||||
@@ -134,9 +106,6 @@ export class ScopePopoutDataSource implements AnyScopeDataSource {
|
||||
}
|
||||
}
|
||||
|
||||
getNativeVisualizerTransport(): NativeVisualizerTransport {
|
||||
return this.nativeVisualizerTransport
|
||||
}
|
||||
|
||||
getPendingSpectrumSamples(): Float32Array[] {
|
||||
const batch = this.monoQueue
|
||||
|
||||
@@ -86,6 +86,7 @@ export class Oscilloscope {
|
||||
private staticLayerCtx: CanvasRenderingContext2D
|
||||
private staticLayerKey = ''
|
||||
private renderBuffer = new Float32Array(0)
|
||||
private pushScratch = new Float32Array(0)
|
||||
private static readonly WARMUP_SAMPLES = 4096
|
||||
|
||||
constructor(canvas: HTMLCanvasElement, options: OscilloscopeOptions = {}) {
|
||||
@@ -186,6 +187,31 @@ export class Oscilloscope {
|
||||
return this.renderBuffer
|
||||
}
|
||||
|
||||
private concatMonoChunks(chunks: Float32Array[]): Float32Array {
|
||||
if (chunks.length === 1) return chunks[0]
|
||||
|
||||
let totalLength = 0
|
||||
for (const chunk of chunks) {
|
||||
totalLength += chunk.length
|
||||
}
|
||||
|
||||
if (this.pushScratch.length < totalLength) {
|
||||
this.pushScratch = new Float32Array(totalLength)
|
||||
}
|
||||
|
||||
const out = this.pushScratch.length === totalLength
|
||||
? this.pushScratch
|
||||
: this.pushScratch.subarray(0, totalLength)
|
||||
|
||||
let offset = 0
|
||||
for (const chunk of chunks) {
|
||||
out.set(chunk, offset)
|
||||
offset += chunk.length
|
||||
}
|
||||
|
||||
return out
|
||||
}
|
||||
|
||||
private drawFrame = (): void => {
|
||||
const { canvas, ctx, options } = this
|
||||
const width = canvas.width
|
||||
@@ -207,13 +233,11 @@ export class Oscilloscope {
|
||||
return
|
||||
}
|
||||
|
||||
const nativeTransport = this.dataSource.getNativeVisualizerTransport?.() ?? null
|
||||
const pendingSamples = this.dataSource.getPendingOscilloscopeSamples()
|
||||
for (const chunk of pendingSamples) {
|
||||
if (!nativeTransport) {
|
||||
nativeOscilloscope.pushSamples(chunk)
|
||||
}
|
||||
this.samplesReceived += chunk.length
|
||||
if (pendingSamples.length > 0) {
|
||||
const merged = this.concatMonoChunks(pendingSamples)
|
||||
nativeOscilloscope.pushSamples(merged)
|
||||
this.samplesReceived += merged.length
|
||||
}
|
||||
|
||||
if (options.pitchLock && this.samplesReceived < Oscilloscope.WARMUP_SAMPLES) {
|
||||
|
||||
@@ -246,6 +246,7 @@ export class SpectrumAnalyzer {
|
||||
private jsBufferedSamples = 0
|
||||
private jsHasSpectrumData = false
|
||||
private nativeMagnitudeBuffer = new Float32Array(0)
|
||||
private pushScratch = new Float32Array(0)
|
||||
private primaryPointX = new Float32Array(0)
|
||||
private primaryPointY = new Float32Array(0)
|
||||
private primaryPointHeatmap = new Float32Array(0)
|
||||
@@ -497,11 +498,38 @@ export class SpectrumAnalyzer {
|
||||
}
|
||||
|
||||
private pushPendingSpectrumChunks(pendingSpectrum: Float32Array[]): void {
|
||||
if (pendingSpectrum.length === 0) return
|
||||
|
||||
if (pendingSpectrum.length === 1) {
|
||||
if (pendingSpectrum[0].length > 0) {
|
||||
nativeSpectrum.pushSamples(pendingSpectrum[0])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let totalLength = 0
|
||||
for (const chunk of pendingSpectrum) {
|
||||
totalLength += chunk.length
|
||||
}
|
||||
if (totalLength === 0) return
|
||||
|
||||
if (this.pushScratch.length < totalLength) {
|
||||
this.pushScratch = new Float32Array(totalLength)
|
||||
}
|
||||
|
||||
const merged = this.pushScratch.length === totalLength
|
||||
? this.pushScratch
|
||||
: this.pushScratch.subarray(0, totalLength)
|
||||
|
||||
let offset = 0
|
||||
for (const chunk of pendingSpectrum) {
|
||||
if (chunk.length > 0) {
|
||||
nativeSpectrum.pushSamples(chunk)
|
||||
merged.set(chunk, offset)
|
||||
offset += chunk.length
|
||||
}
|
||||
}
|
||||
|
||||
nativeSpectrum.pushSamples(merged)
|
||||
}
|
||||
|
||||
private clearPendingSpectrumQueues(): void {
|
||||
@@ -747,17 +775,12 @@ export class SpectrumAnalyzer {
|
||||
return
|
||||
}
|
||||
|
||||
const nativeTransport = this.dataSource.getNativeVisualizerTransport?.() ?? null
|
||||
const pendingSpectrum = this.dataSource.getPendingSpectrumSamples()
|
||||
if (!nativeTransport) {
|
||||
this.pushPendingSpectrumChunks(pendingSpectrum)
|
||||
}
|
||||
this.pushPendingSpectrumChunks(pendingSpectrum)
|
||||
|
||||
const nativeMagnitudes = this.ensureNativeMagnitudeBuffer()
|
||||
primaryData = nativeMagnitudes
|
||||
primaryDataLength = nativeTransport
|
||||
? nativeTransport.fillLatestSpectrumMagnitudes(nativeMagnitudes)
|
||||
: nativeSpectrum.fillMagnitudes(nativeMagnitudes)
|
||||
primaryDataLength = nativeSpectrum.fillMagnitudes(nativeMagnitudes)
|
||||
}
|
||||
|
||||
if (!primaryData || primaryDataLength === 0) {
|
||||
|
||||
@@ -81,6 +81,8 @@ export class Vectorscope {
|
||||
private multibandPointScratch: MultibandChunk = createMultibandChunk(0)
|
||||
private nativePointX = new Float32Array(0)
|
||||
private nativePointY = new Float32Array(0)
|
||||
private pushScratchL = new Float32Array(0)
|
||||
private pushScratchR = new Float32Array(0)
|
||||
private staticLayerKey = ''
|
||||
|
||||
constructor(canvas: HTMLCanvasElement, options: VectorscopeOptions = {}) {
|
||||
@@ -214,16 +216,14 @@ export class Vectorscope {
|
||||
offscreenCtx.fillRect(0, 0, width, height)
|
||||
offscreenCtx.globalCompositeOperation = 'source-over'
|
||||
|
||||
const nativeTransport = this.dataSource.getNativeVisualizerTransport?.() ?? null
|
||||
const pendingSamples = this.dataSource.getPendingVectorscopeSamples()
|
||||
|
||||
if (options.multiband) {
|
||||
this.drawMultibandPoints(offscreenCtx, pendingSamples, centerX, centerY, scale)
|
||||
} else if (isNativeAvailable()) {
|
||||
if (!nativeTransport) {
|
||||
for (const chunk of pendingSamples) {
|
||||
nativeVectorscope.pushSamples(chunk.left, chunk.right)
|
||||
}
|
||||
if (pendingSamples.length > 0) {
|
||||
const { left, right } = this.concatStereoChunks(pendingSamples)
|
||||
nativeVectorscope.pushSamples(left, right)
|
||||
}
|
||||
|
||||
const count = this.fillNativePoints(options.displayPoints)
|
||||
@@ -423,6 +423,37 @@ export class Vectorscope {
|
||||
return this.multibandPointScratch
|
||||
}
|
||||
|
||||
private concatStereoChunks(chunks: Array<{ left: Float32Array; right: Float32Array }>): { left: Float32Array; right: Float32Array } {
|
||||
if (chunks.length === 1) return chunks[0]
|
||||
|
||||
let totalLength = 0
|
||||
for (const chunk of chunks) {
|
||||
totalLength += Math.min(chunk.left.length, chunk.right.length)
|
||||
}
|
||||
|
||||
if (this.pushScratchL.length < totalLength) {
|
||||
this.pushScratchL = new Float32Array(totalLength)
|
||||
this.pushScratchR = new Float32Array(totalLength)
|
||||
}
|
||||
|
||||
const left = this.pushScratchL.length === totalLength
|
||||
? this.pushScratchL
|
||||
: this.pushScratchL.subarray(0, totalLength)
|
||||
const right = this.pushScratchR.length === totalLength
|
||||
? this.pushScratchR
|
||||
: this.pushScratchR.subarray(0, totalLength)
|
||||
|
||||
let offset = 0
|
||||
for (const chunk of chunks) {
|
||||
const len = Math.min(chunk.left.length, chunk.right.length)
|
||||
left.set(chunk.left.subarray(0, len), offset)
|
||||
right.set(chunk.right.subarray(0, len), offset)
|
||||
offset += len
|
||||
}
|
||||
|
||||
return { left, right }
|
||||
}
|
||||
|
||||
private drawProjectedDot(
|
||||
ctx: CanvasRenderingContext2D,
|
||||
left: number,
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
import { audioRouter } from '../audio/AudioRouter'
|
||||
import { nativeVisualizerTransport } from '../audio/NativeVisualizerTransport'
|
||||
import type { ScopePopoutSessionState } from '../../types/popout'
|
||||
import type { NativeVisualizerTransport } from '../audio/NativeVisualizerTransport'
|
||||
|
||||
export interface VisualizerSessionSource {
|
||||
getSampleRate: () => number
|
||||
isPlaying: () => boolean
|
||||
subscribeToSessionChanges: (listener: (state: ScopePopoutSessionState) => void) => () => void
|
||||
getNativeVisualizerTransport?: () => NativeVisualizerTransport | null
|
||||
}
|
||||
|
||||
export const defaultVisualizerSessionSource: VisualizerSessionSource = {
|
||||
getSampleRate: () => audioRouter.getSampleRate(),
|
||||
isPlaying: () => audioRouter.isCapturing(),
|
||||
subscribeToSessionChanges: (listener) => audioRouter.subscribeToSessionChanges((state) => listener(state)),
|
||||
getNativeVisualizerTransport: () => nativeVisualizerTransport,
|
||||
}
|
||||
|
||||
+90
-36
@@ -94,6 +94,7 @@ const SCOPES_SCHEMA = {
|
||||
} as const satisfies SectionSchema<ThemeScopesTokens>
|
||||
|
||||
const SPECTRUM_SCHEMA = {
|
||||
background: 'background',
|
||||
line: 'line',
|
||||
side_line: 'sideLine',
|
||||
fill: 'fill',
|
||||
@@ -102,23 +103,28 @@ const SPECTRUM_SCHEMA = {
|
||||
heat_high: 'heatHigh',
|
||||
heat_base: 'heatBase',
|
||||
guides: 'guides',
|
||||
labels: 'labels',
|
||||
} as const satisfies SectionSchema<ThemeSpectrumTokens>
|
||||
|
||||
const OSCILLOSCOPE_SCHEMA = {
|
||||
background: 'background',
|
||||
line: 'line',
|
||||
fill: 'fill',
|
||||
guides: 'guides',
|
||||
} as const satisfies SectionSchema<ThemeOscilloscopeTokens>
|
||||
|
||||
const VECTORSCOPE_SCHEMA = {
|
||||
background: 'background',
|
||||
trace: 'trace',
|
||||
band_low: 'bandLow',
|
||||
band_mid: 'bandMid',
|
||||
band_high: 'bandHigh',
|
||||
guides: 'guides',
|
||||
labels: 'labels',
|
||||
} as const satisfies SectionSchema<ThemeVectorscopeTokens>
|
||||
|
||||
const SPECTROGRAM_SCHEMA = {
|
||||
background: 'background',
|
||||
mono: 'mono',
|
||||
heat_low: 'heatLow',
|
||||
heat_mid: 'heatMid',
|
||||
@@ -126,21 +132,26 @@ const SPECTROGRAM_SCHEMA = {
|
||||
} as const satisfies SectionSchema<ThemeSpectrogramTokens>
|
||||
|
||||
const VUMETER_SCHEMA = {
|
||||
background: 'background',
|
||||
level: 'level',
|
||||
track: 'track',
|
||||
peak: 'peak',
|
||||
clip: 'clip',
|
||||
scale: 'scale',
|
||||
labels: 'labels',
|
||||
} as const satisfies SectionSchema<ThemeVUMeterTokens>
|
||||
|
||||
const LUFSMETER_SCHEMA = {
|
||||
background: 'background',
|
||||
level: 'level',
|
||||
track: 'track',
|
||||
target: 'target',
|
||||
scale: 'scale',
|
||||
labels: 'labels',
|
||||
} as const satisfies SectionSchema<ThemeLUFSMeterTokens>
|
||||
|
||||
const WAVEFORM_SCHEMA = {
|
||||
background: 'background',
|
||||
line: 'line',
|
||||
band_low: 'bandLow',
|
||||
band_mid: 'bandMid',
|
||||
@@ -817,40 +828,82 @@ export function serializeThemeFile(theme: PrismTheme): string {
|
||||
}
|
||||
|
||||
export function createTemplateThemeFile(): string {
|
||||
const template = createDefaultTheme()
|
||||
template.id = 'theme_template'
|
||||
template.name = 'Template Theme'
|
||||
template.credit = 'Your Name'
|
||||
template.website = 'https://example.com'
|
||||
const base = createDefaultTheme()
|
||||
const resolved = resolveTheme(base)
|
||||
|
||||
const template: PrismTheme = {
|
||||
id: 'theme_template',
|
||||
name: 'Template Theme',
|
||||
credit: 'Your Name',
|
||||
website: 'https://example.com',
|
||||
description: 'Custom Prism theme',
|
||||
app: {
|
||||
...base.app,
|
||||
toolbarBg: resolved.interface.toolbarBg,
|
||||
settingsBgTop: resolved.interface.settingsBgTop,
|
||||
settingsBgBottom: resolved.interface.settingsBgBottom,
|
||||
bottomBarBg: resolved.interface.bottomBarBg,
|
||||
},
|
||||
controls: { ...base.controls },
|
||||
scopes: { ...base.scopes },
|
||||
spectrum: {
|
||||
...base.spectrum,
|
||||
background: resolved.spectrum.background,
|
||||
guides: resolved.spectrum.guides,
|
||||
labels: resolved.spectrum.labels,
|
||||
heatBase: resolved.spectrum.heatBase,
|
||||
},
|
||||
oscilloscope: {
|
||||
...base.oscilloscope,
|
||||
background: resolved.oscilloscope.background,
|
||||
guides: resolved.oscilloscope.guides,
|
||||
},
|
||||
vectorscope: {
|
||||
...base.vectorscope,
|
||||
background: resolved.vectorscope.background,
|
||||
guides: resolved.vectorscope.guides,
|
||||
labels: resolved.vectorscope.labels,
|
||||
},
|
||||
spectrogram: {
|
||||
...base.spectrogram,
|
||||
background: resolved.spectrogram.background,
|
||||
},
|
||||
vumeter: {
|
||||
...base.vumeter,
|
||||
background: resolved.vumeter.background,
|
||||
scale: resolved.vumeter.scale,
|
||||
labels: resolved.vumeter.labels,
|
||||
},
|
||||
lufsmeter: {
|
||||
...base.lufsmeter,
|
||||
background: resolved.lufsmeter.background,
|
||||
scale: resolved.lufsmeter.scale,
|
||||
labels: resolved.lufsmeter.labels,
|
||||
},
|
||||
waveform: {
|
||||
...base.waveform,
|
||||
background: resolved.waveform.background,
|
||||
guides: resolved.waveform.guides,
|
||||
},
|
||||
astra: { ...base.astra },
|
||||
}
|
||||
|
||||
return `# Prism theme template
|
||||
#
|
||||
# Authoring rules:
|
||||
# - Colors use R, G, B or R, G, B, A (0-255)
|
||||
# - CSS colors like #hex, rgb(), and rgba() also work
|
||||
# Colors use R, G, B or R, G, B, A (0-255)
|
||||
# CSS colors like #hex, rgb(), and rgba() also work
|
||||
#
|
||||
# ── UI ──
|
||||
# [App] Overall window chrome, background, and text
|
||||
# toolbar_bg, settings_bg_top, settings_bg_bottom, bottom_bar_bg
|
||||
# can be set to override the default derived values
|
||||
# [App] Window background, text, and accent color
|
||||
# [Controls] Buttons, inputs, menus, and sliders
|
||||
# Set flat_controls = true to disable glass highlights/gradients
|
||||
#
|
||||
# ── Scopes ──
|
||||
# [Scopes] Shared background and guide color for all scopes
|
||||
# background defaults to [App] background if not set
|
||||
# Set it explicitly here to decouple scope bg from window bg
|
||||
# [Scopes] Shared defaults for all scopes (background, guides, overlays)
|
||||
#
|
||||
# Per-scope sections override colors unique to that module.
|
||||
# Each scope can set its own guides to override the shared [Scopes] guides.
|
||||
#
|
||||
# [Spectrum] line, fill, heat gradient (heat_low/mid/high/base), guides
|
||||
# [Oscilloscope] line, fill, guides
|
||||
# [Vectorscope] trace, band_low/mid/high (RGB overlay colors), guides
|
||||
# [Spectrogram] mono, heat gradient
|
||||
# [VUMeter] level, track, peak, clip, scale
|
||||
# [LUFSMeter] level, track, target, scale
|
||||
# [Waveform] line, band_low/mid/high, guides
|
||||
# Each scope section below has FULL control over its own colors.
|
||||
# Per-scope values override the shared [Scopes] defaults.
|
||||
# Every token shown below can be changed independently.
|
||||
#
|
||||
${serializeThemeFile(template)}`
|
||||
}
|
||||
@@ -1038,13 +1091,14 @@ function resolveSpectrumTheme(
|
||||
const sideLine = section.sideLine ?? withAlpha(line, 0.5)
|
||||
const fill = section.fill ?? withAlpha(line, 0.34)
|
||||
const guides = section.guides ?? scopes.guides
|
||||
const background = section.background ?? scopes.background
|
||||
return {
|
||||
line,
|
||||
sideLine,
|
||||
guides,
|
||||
guidesSecondary: multiplyAlpha(guides, 0.5),
|
||||
labels: section.guides ? guides : scopes.labels,
|
||||
background: scopes.background,
|
||||
labels: section.labels ?? guides,
|
||||
background,
|
||||
fill,
|
||||
fillGradient: [
|
||||
withAlpha(fill, 0),
|
||||
@@ -1056,7 +1110,7 @@ function resolveSpectrumTheme(
|
||||
section.heatMid ?? DEFAULT_HEAT_MID,
|
||||
section.heatHigh ?? DEFAULT_HEAT_HIGH,
|
||||
],
|
||||
heatBase: section.heatBase ?? scopes.background,
|
||||
heatBase: section.heatBase ?? background,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1070,7 +1124,7 @@ function resolveOscilloscopeTheme(
|
||||
line: theme.oscilloscope.line ?? app.accent,
|
||||
guides,
|
||||
guidesSecondary: multiplyAlpha(guides, 0.5),
|
||||
background: scopes.background,
|
||||
background: theme.oscilloscope.background ?? scopes.background,
|
||||
fill: theme.oscilloscope.fill ?? 'rgba(245, 248, 252, 0.18)',
|
||||
}
|
||||
}
|
||||
@@ -1085,8 +1139,8 @@ function resolveVectorscopeTheme(
|
||||
trace: theme.vectorscope.trace ?? app.accent,
|
||||
guides,
|
||||
guidesSecondary: multiplyAlpha(guides, 0.5),
|
||||
labels: theme.vectorscope.guides ? guides : scopes.labels,
|
||||
background: scopes.background,
|
||||
labels: theme.vectorscope.labels ?? guides,
|
||||
background: theme.vectorscope.background ?? scopes.background,
|
||||
bandLow: theme.vectorscope.bandLow ?? DEFAULT_BAND_LOW,
|
||||
bandMid: theme.vectorscope.bandMid ?? DEFAULT_BAND_MID,
|
||||
bandHigh: theme.vectorscope.bandHigh ?? DEFAULT_BAND_HIGH,
|
||||
@@ -1100,7 +1154,7 @@ function resolveSpectrogramTheme(
|
||||
): ResolvedSpectrogramTheme {
|
||||
return {
|
||||
mono: theme.spectrogram.mono ?? app.accent,
|
||||
background: scopes.background,
|
||||
background: theme.spectrogram.background ?? scopes.background,
|
||||
heatColors: [
|
||||
theme.spectrogram.heatLow ?? DEFAULT_HEAT_LOW,
|
||||
theme.spectrogram.heatMid ?? DEFAULT_HEAT_MID,
|
||||
@@ -1121,8 +1175,8 @@ function resolveVUMeterTheme(
|
||||
peak: theme.vumeter.peak ?? 'rgb(255, 127, 0)',
|
||||
clip: theme.vumeter.clip ?? 'rgba(255, 120, 80, 0.9)',
|
||||
scale: theme.vumeter.scale ?? scopes.guides,
|
||||
labels: blendText(app.text, app.textMuted, 0.35),
|
||||
background: scopes.background,
|
||||
labels: theme.vumeter.labels ?? blendText(app.text, app.textMuted, 0.35),
|
||||
background: theme.vumeter.background ?? scopes.background,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1137,8 +1191,8 @@ function resolveLUFSMeterTheme(
|
||||
track: theme.lufsmeter.track ?? withAlpha(level, 0.08),
|
||||
target: theme.lufsmeter.target ?? withAlpha(level, 0.25),
|
||||
scale: theme.lufsmeter.scale ?? scopes.guides,
|
||||
labels: blendText(app.text, app.textMuted, 0.2),
|
||||
background: scopes.background,
|
||||
labels: theme.lufsmeter.labels ?? blendText(app.text, app.textMuted, 0.2),
|
||||
background: theme.lufsmeter.background ?? scopes.background,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1152,7 +1206,7 @@ function resolveWaveformTheme(
|
||||
line: theme.waveform.line ?? app.accent,
|
||||
guides,
|
||||
guidesSecondary: multiplyAlpha(guides, 0.5),
|
||||
background: scopes.background,
|
||||
background: theme.waveform.background ?? scopes.background,
|
||||
bandLow: theme.waveform.bandLow ?? DEFAULT_BAND_LOW,
|
||||
bandMid: theme.waveform.bandMid ?? DEFAULT_BAND_MID,
|
||||
bandHigh: theme.waveform.bandHigh ?? DEFAULT_BAND_HIGH,
|
||||
|
||||
@@ -56,6 +56,7 @@ export interface ThemeScopesTokens {
|
||||
}
|
||||
|
||||
export interface ThemeSpectrumTokens {
|
||||
background?: string
|
||||
line?: string
|
||||
sideLine?: string
|
||||
fill?: string
|
||||
@@ -64,23 +65,28 @@ export interface ThemeSpectrumTokens {
|
||||
heatHigh?: string
|
||||
heatBase?: string
|
||||
guides?: string
|
||||
labels?: string
|
||||
}
|
||||
|
||||
export interface ThemeOscilloscopeTokens {
|
||||
background?: string
|
||||
line?: string
|
||||
fill?: string
|
||||
guides?: string
|
||||
}
|
||||
|
||||
export interface ThemeVectorscopeTokens {
|
||||
background?: string
|
||||
trace?: string
|
||||
bandLow?: string
|
||||
bandMid?: string
|
||||
bandHigh?: string
|
||||
guides?: string
|
||||
labels?: string
|
||||
}
|
||||
|
||||
export interface ThemeSpectrogramTokens {
|
||||
background?: string
|
||||
mono?: string
|
||||
heatLow?: string
|
||||
heatMid?: string
|
||||
@@ -88,21 +94,26 @@ export interface ThemeSpectrogramTokens {
|
||||
}
|
||||
|
||||
export interface ThemeVUMeterTokens {
|
||||
background?: string
|
||||
level?: string
|
||||
track?: string
|
||||
peak?: string
|
||||
clip?: string
|
||||
scale?: string
|
||||
labels?: string
|
||||
}
|
||||
|
||||
export interface ThemeLUFSMeterTokens {
|
||||
background?: string
|
||||
level?: string
|
||||
track?: string
|
||||
target?: string
|
||||
scale?: string
|
||||
labels?: string
|
||||
}
|
||||
|
||||
export interface ThemeWaveformTokens {
|
||||
background?: string
|
||||
line?: string
|
||||
bandLow?: string
|
||||
bandMid?: string
|
||||
|
||||
Reference in New Issue
Block a user