mirror of
https://github.com/Boof2015/prism.git
synced 2026-08-12 05:10:51 +02:00
improve speed scale for waveform + version bump + profile migration
This commit is contained in:
Generated
+2
-2
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "prism",
|
||||
"version": "0.1.0-beta",
|
||||
"version": "0.2.0-beta",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "prism",
|
||||
"version": "0.1.0-beta",
|
||||
"version": "0.2.0-beta",
|
||||
"hasInstallScript": true,
|
||||
"license": "GPL-3.0-only",
|
||||
"dependencies": {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "prism",
|
||||
"version": "0.1.0-beta",
|
||||
"version": "0.2.0-beta",
|
||||
"description": "Open-source audio metering and visualization tool",
|
||||
"main": "./out/main/index.js",
|
||||
"scripts": {
|
||||
|
||||
@@ -351,7 +351,7 @@ export class FileBackedProfileLibrary {
|
||||
throw new Error(`Unsupported profile format in ${basename(filePath)}.`)
|
||||
}
|
||||
|
||||
if (candidate.version !== 1 && candidate.version !== PROFILE_FILE_VERSION) {
|
||||
if (candidate.version !== 1 && candidate.version !== 2 && candidate.version !== PROFILE_FILE_VERSION) {
|
||||
throw new Error(`Unsupported profile version in ${basename(filePath)}.`)
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,11 @@ import {
|
||||
findVUReferencePreset,
|
||||
sanitizeVUReferenceDbfs,
|
||||
} from '../../types/vumeter'
|
||||
import {
|
||||
MAX_WAVEFORM_SCROLL_SPEED,
|
||||
MIN_WAVEFORM_SCROLL_SPEED,
|
||||
WAVEFORM_SCROLL_SPEED_STEP,
|
||||
} from '../../types/waveform'
|
||||
import ThemedSelect from './ThemedSelect'
|
||||
|
||||
function vectorscopeModeLabel(mode: ScopeSettings['vectorscope']['mode']): string {
|
||||
@@ -669,9 +674,9 @@ export default function ScopeSettingsSection({
|
||||
label="Speed"
|
||||
value={current.scrollSpeed}
|
||||
valueLabel={`x${current.scrollSpeed.toFixed(0)}`}
|
||||
min={1}
|
||||
max={8}
|
||||
step={1}
|
||||
min={MIN_WAVEFORM_SCROLL_SPEED}
|
||||
max={MAX_WAVEFORM_SCROLL_SPEED}
|
||||
step={WAVEFORM_SCROLL_SPEED_STEP}
|
||||
fullWidth={false}
|
||||
onChange={(value) => onUpdate('waveform', { scrollSpeed: value })}
|
||||
/>
|
||||
|
||||
@@ -62,7 +62,7 @@ const MULTIBAND_DOMINANCE_SENSITIVITY = 5
|
||||
const MULTIBAND_FOCUSED_BLEND = 0.68
|
||||
const MULTIBAND_FILL_ALPHA = 0.72
|
||||
const MULTIBAND_EDGE_ALPHA = 1.0
|
||||
const BASE_PIXELS_PER_SECOND = 64
|
||||
const BASE_PIXELS_PER_SECOND = 128
|
||||
const DISPLAY_MARGIN = 0.95
|
||||
|
||||
const defaultWaveformDataSource: WaveformDataSource = {
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
type ProfileLocalMetadata,
|
||||
type PrismProfileFile,
|
||||
type PrismProfileFileScopePopoutMap,
|
||||
type PrismProfileFileV2,
|
||||
type PrismProfileFileV3,
|
||||
type PrismProfileLocalStateV1,
|
||||
} from '../types/profile'
|
||||
import { AUDIO_SCOPE_KINDS, SCOPE_KINDS, normalizeScopeKind, type ScopeKind } from '../types/scope'
|
||||
@@ -45,6 +45,19 @@ export function cloneScopeSettings(settings: ScopeSettings): ScopeSettings {
|
||||
return JSON.parse(JSON.stringify(settings)) as ScopeSettings
|
||||
}
|
||||
|
||||
function isLegacyProfileFileVersion(version: unknown): boolean {
|
||||
return version === 1 || version === 2
|
||||
}
|
||||
|
||||
function normalizeWaveformScrollSpeed(value: unknown, legacyProfileFileScale: boolean): number {
|
||||
if (legacyProfileFileScale && value !== undefined && value !== null) {
|
||||
const numeric = Number(value)
|
||||
return clampWaveformScrollSpeed(Number.isFinite(numeric) ? numeric / 2 : value)
|
||||
}
|
||||
|
||||
return clampWaveformScrollSpeed(value ?? DEFAULT_SCOPE_SETTINGS.waveform.scrollSpeed)
|
||||
}
|
||||
|
||||
export function createDefaultScopePopouts(): ScopePopoutStateMap {
|
||||
return SCOPE_KINDS.reduce((acc, kind) => {
|
||||
acc[kind] = { poppedOut: false }
|
||||
@@ -131,7 +144,10 @@ export function normalizeWidthWeights(raw: unknown): Record<ScopeKind, number> {
|
||||
}, {} as Record<ScopeKind, number>)
|
||||
}
|
||||
|
||||
export function mergeScopeSettings(raw: unknown): ScopeSettings {
|
||||
export function mergeScopeSettings(
|
||||
raw: unknown,
|
||||
options: { legacyProfileFileScale?: boolean } = {},
|
||||
): ScopeSettings {
|
||||
const parsed = typeof raw === 'object' && raw !== null
|
||||
? raw as Partial<ScopeSettings>
|
||||
: {}
|
||||
@@ -195,7 +211,7 @@ export function mergeScopeSettings(raw: unknown): ScopeSettings {
|
||||
mode: rawWaveform.mode === 'stereo' || rawWaveform.mode === 'mono'
|
||||
? rawWaveform.mode
|
||||
: DEFAULT_SCOPE_SETTINGS.waveform.mode,
|
||||
scrollSpeed: clampWaveformScrollSpeed(rawWaveform.scrollSpeed ?? DEFAULT_SCOPE_SETTINGS.waveform.scrollSpeed),
|
||||
scrollSpeed: normalizeWaveformScrollSpeed(rawWaveform.scrollSpeed, Boolean(options.legacyProfileFileScale)),
|
||||
multiband: typeof rawWaveform.multiband === 'boolean'
|
||||
? rawWaveform.multiband
|
||||
: DEFAULT_SCOPE_SETTINGS.waveform.multiband,
|
||||
@@ -272,7 +288,7 @@ export function normalizeProfileFile(
|
||||
raw: unknown,
|
||||
fallbackId: string,
|
||||
fallbackName = DEFAULT_PROFILE_NAME,
|
||||
) : PrismProfileFileV2 {
|
||||
) : PrismProfileFileV3 {
|
||||
const parsed = typeof raw === 'object' && raw !== null
|
||||
? raw as Partial<PrismProfileFile>
|
||||
: {}
|
||||
@@ -291,12 +307,14 @@ export function normalizeProfileFile(
|
||||
scopeOrder: normalizeScopeOrder(parsed.scopeOrder),
|
||||
hiddenScopes: normalizeHiddenScopes(parsed.hiddenScopes),
|
||||
widthWeights: normalizeWidthWeights(parsed.widthWeights),
|
||||
scopeSettings: mergeScopeSettings(parsed.scopeSettings),
|
||||
scopeSettings: mergeScopeSettings(parsed.scopeSettings, {
|
||||
legacyProfileFileScale: isLegacyProfileFileVersion(parsed.version),
|
||||
}),
|
||||
scopePopouts: normalizeProfileFileScopePopouts(parsed.scopePopouts),
|
||||
}
|
||||
}
|
||||
|
||||
export function profileToFileData(id: string, profile: Profile): PrismProfileFileV2 {
|
||||
export function profileToFileData(id: string, profile: Profile): PrismProfileFileV3 {
|
||||
const normalized = normalizeProfile(profile, profile.name)
|
||||
|
||||
return {
|
||||
@@ -402,7 +420,9 @@ export function profileFileToProfile(
|
||||
scopeOrder: normalizeScopeOrder(file.scopeOrder),
|
||||
hiddenScopes: normalizeHiddenScopes(file.hiddenScopes),
|
||||
widthWeights: normalizeWidthWeights(file.widthWeights),
|
||||
scopeSettings: mergeScopeSettings(file.scopeSettings),
|
||||
scopeSettings: mergeScopeSettings(file.scopeSettings, {
|
||||
legacyProfileFileScale: isLegacyProfileFileVersion(file.version),
|
||||
}),
|
||||
scopePopouts: SCOPE_KINDS.reduce((acc, kind) => {
|
||||
acc[kind] = {
|
||||
poppedOut: Boolean(file.scopePopouts[kind]?.poppedOut),
|
||||
|
||||
+15
-2
@@ -3,7 +3,7 @@ import type { ScopeKind } from './scope'
|
||||
import type { ScopeSettings } from './settings'
|
||||
|
||||
export const PROFILE_FILE_FORMAT = 'prism-profile'
|
||||
export const PROFILE_FILE_VERSION = 2
|
||||
export const PROFILE_FILE_VERSION = 3
|
||||
export const PROFILE_LOCAL_STATE_FORMAT = 'prism-profile-local'
|
||||
export const PROFILE_LOCAL_STATE_VERSION = 1
|
||||
export const LEGACY_PROFILE_MIGRATION_VERSION = 1
|
||||
@@ -39,6 +39,19 @@ export interface PrismProfileFileV1 {
|
||||
}
|
||||
|
||||
export interface PrismProfileFileV2 {
|
||||
format: typeof PROFILE_FILE_FORMAT
|
||||
version: 2
|
||||
id: string
|
||||
name: string
|
||||
themeId?: string | null
|
||||
scopeOrder: ScopeKind[]
|
||||
hiddenScopes: ScopeKind[]
|
||||
widthWeights: Record<ScopeKind, number>
|
||||
scopeSettings: ScopeSettings
|
||||
scopePopouts: PrismProfileFileScopePopoutMap
|
||||
}
|
||||
|
||||
export interface PrismProfileFileV3 {
|
||||
format: typeof PROFILE_FILE_FORMAT
|
||||
version: typeof PROFILE_FILE_VERSION
|
||||
id: string
|
||||
@@ -51,7 +64,7 @@ export interface PrismProfileFileV2 {
|
||||
scopePopouts: PrismProfileFileScopePopoutMap
|
||||
}
|
||||
|
||||
export type PrismProfileFile = PrismProfileFileV1 | PrismProfileFileV2
|
||||
export type PrismProfileFile = PrismProfileFileV1 | PrismProfileFileV2 | PrismProfileFileV3
|
||||
|
||||
export interface ProfileLocalMetadata {
|
||||
windowBounds?: WindowBounds
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
} from './spectrogram'
|
||||
import { DEFAULT_VU_REFERENCE_DBFS, type VUMeterMode, type VUMeterNeedleChannels, type VUMeterOrientation } from './vumeter'
|
||||
import { DEFAULT_LUFS_METER_READOUT, type LUFSMeterMode, type LUFSMeterReadout } from './lufsmeter'
|
||||
import { DEFAULT_WAVEFORM_MODE, type WaveformMode } from './waveform'
|
||||
import { DEFAULT_WAVEFORM_MODE, DEFAULT_WAVEFORM_SCROLL_SPEED, type WaveformMode } from './waveform'
|
||||
import { DEFAULT_SPECTRUM_PEAK_INFO_MODE, type SpectrumPeakInfoMode } from './spectrum'
|
||||
|
||||
export interface ScopeSettings {
|
||||
@@ -80,7 +80,7 @@ export const DEFAULT_SCOPE_SETTINGS: ScopeSettings = {
|
||||
spectrogram: { fftSize: 4096, tiltDbPerOctave: DEFAULT_SPECTROGRAM_TILT_DB_PER_OCTAVE, scrollSpeed: 2, contrast: DEFAULT_SPECTROGRAM_CONTRAST, clarityMode: 'sharper', scaleMode: 'log', orientation: DEFAULT_SPECTROGRAM_ORIENTATION, colorScheme: 'heat' },
|
||||
vumeter: { mode: 'bar', orientation: 'horizontal', needleChannels: 'stereo', referenceDb: DEFAULT_VU_REFERENCE_DBFS },
|
||||
lufsmeter: { mode: 'bar', readout: DEFAULT_LUFS_METER_READOUT },
|
||||
waveform: { mode: DEFAULT_WAVEFORM_MODE, scrollSpeed: 1, multiband: false },
|
||||
waveform: { mode: DEFAULT_WAVEFORM_MODE, scrollSpeed: DEFAULT_WAVEFORM_SCROLL_SPEED, multiband: false },
|
||||
nowPlaying: {
|
||||
showCoverArt: true,
|
||||
showTitle: true,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
export type WaveformMode = 'mono' | 'stereo'
|
||||
|
||||
export const MIN_WAVEFORM_SCROLL_SPEED = 0.5
|
||||
export const MIN_WAVEFORM_SCROLL_SPEED = 1
|
||||
export const MAX_WAVEFORM_SCROLL_SPEED = 8
|
||||
export const WAVEFORM_SCROLL_SPEED_STEP = 0.5
|
||||
export const WAVEFORM_SCROLL_SPEED_STEP = 1
|
||||
export const DEFAULT_WAVEFORM_SCROLL_SPEED = 1
|
||||
export const DEFAULT_WAVEFORM_MODE: WaveformMode = 'mono'
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ import {
|
||||
createDefaultProfile,
|
||||
extractLocalProfileMetadata,
|
||||
mergeScopeSettings,
|
||||
normalizeProfileFile,
|
||||
normalizeScopeOrder,
|
||||
profileFileToProfile,
|
||||
profileToFileData,
|
||||
@@ -81,6 +82,38 @@ test('profile file serialization excludes geometry and round-trips with local me
|
||||
assert.equal(restored.scopeSettings.nowPlaying.showControls, true)
|
||||
})
|
||||
|
||||
test('profile file waveform speed migration preserves legacy scroll feel', () => {
|
||||
const base = profileToFileData('profile_speed', createDefaultProfile('Speed'))
|
||||
const withWaveformSpeed = (version: number, scrollSpeed: unknown) => normalizeProfileFile({
|
||||
...base,
|
||||
version,
|
||||
scopeSettings: {
|
||||
...base.scopeSettings,
|
||||
waveform: {
|
||||
...base.scopeSettings.waveform,
|
||||
scrollSpeed,
|
||||
},
|
||||
},
|
||||
}, 'profile_speed')
|
||||
|
||||
assert.equal(withWaveformSpeed(1, 8).scopeSettings.waveform.scrollSpeed, 4)
|
||||
assert.equal(withWaveformSpeed(2, 8).scopeSettings.waveform.scrollSpeed, 4)
|
||||
assert.equal(withWaveformSpeed(PROFILE_FILE_VERSION, 4).scopeSettings.waveform.scrollSpeed, 4)
|
||||
assert.equal(withWaveformSpeed(1, 'fast').scopeSettings.waveform.scrollSpeed, 1)
|
||||
|
||||
const legacyMissingSpeed = normalizeProfileFile({
|
||||
...base,
|
||||
version: 1,
|
||||
scopeSettings: {
|
||||
...base.scopeSettings,
|
||||
waveform: {
|
||||
mode: 'mono',
|
||||
},
|
||||
},
|
||||
}, 'profile_speed')
|
||||
assert.equal(legacyMissingSpeed.scopeSettings.waveform.scrollSpeed, 1)
|
||||
})
|
||||
|
||||
test('mergeScopeSettings defaults missing or invalid spectrogram orientation to horizontal', () => {
|
||||
const vertical = mergeScopeSettings({
|
||||
spectrogram: {
|
||||
@@ -253,7 +286,7 @@ test('partial files normalize, unsupported versions fail, and import does not ch
|
||||
assert.equal(partialSnapshot.profiles.profile_partial.scopeSettings.spectrogram.colorScheme, 'heat')
|
||||
assert.equal(partialSnapshot.profiles.profile_partial.scopeSettings.lufsmeter.readout, 'shortTerm')
|
||||
assert.equal(partialSnapshot.profiles.profile_partial.scopeSettings.waveform.mode, 'stereo')
|
||||
assert.equal(partialSnapshot.profiles.profile_partial.scopeSettings.waveform.scrollSpeed, 2)
|
||||
assert.equal(partialSnapshot.profiles.profile_partial.scopeSettings.waveform.scrollSpeed, 1)
|
||||
assert.equal(partialSnapshot.profiles.profile_partial.scopeSettings.waveform.multiband, true)
|
||||
assert.equal(Object.hasOwn(partialSnapshot.profiles.profile_partial.scopeSettings.waveform, 'gainDb'), false)
|
||||
assert.equal(partialSnapshot.profiles.profile_partial.scopePopouts.spectrogram.poppedOut, true)
|
||||
@@ -285,7 +318,7 @@ test('legacy profile files with themeId import successfully and ignore embedded
|
||||
const legacyPath = join(harness.rootDir, 'legacy-theme.prsm')
|
||||
await writeFile(legacyPath, `${JSON.stringify({
|
||||
format: PROFILE_FILE_FORMAT,
|
||||
version: PROFILE_FILE_VERSION,
|
||||
version: 2,
|
||||
id: 'profile_legacy_theme',
|
||||
name: 'Legacy Theme',
|
||||
themeId: 'theme_midnight',
|
||||
|
||||
@@ -4501,6 +4501,34 @@ test('Waveform reconfigures and resets native multiband state on option changes'
|
||||
}
|
||||
})
|
||||
|
||||
test('Waveform maps x4 to the old max scroll rate and x8 to faster headroom', () => {
|
||||
const dom = installFakeCanvasDom()
|
||||
const nativeAnalyzer = createFakeWaveformNativeAnalyzer()
|
||||
const dataSource = {
|
||||
getPendingWaveformSamples: () => [],
|
||||
getPendingWaveformStereoSamples: () => [],
|
||||
getSampleRate: () => 4096,
|
||||
isPlaying: () => true,
|
||||
subscribeToSessionChanges: () => () => {},
|
||||
}
|
||||
const waveform = new Waveform(createFakeCanvas(), {
|
||||
dataSource,
|
||||
nativeAnalyzer,
|
||||
scrollSpeed: 4,
|
||||
})
|
||||
|
||||
try {
|
||||
assert.equal(nativeAnalyzer.configs.at(-1)?.samplesPerColumn, 8)
|
||||
|
||||
nativeAnalyzer.configs.length = 0
|
||||
waveform.setOptions({ scrollSpeed: 8 })
|
||||
assert.equal(nativeAnalyzer.configs.at(-1)?.samplesPerColumn, 4)
|
||||
} finally {
|
||||
waveform.dispose()
|
||||
dom.restore()
|
||||
}
|
||||
})
|
||||
|
||||
test('Vectorscope multiband uses native interleaved band points when available', () => {
|
||||
const recorder = createFakeCanvasRecorder()
|
||||
const dom = installFakeCanvasDom(() => createFakeCanvas(recorder))
|
||||
|
||||
Reference in New Issue
Block a user