This commit is contained in:
Boof2015
2026-06-19 13:19:37 -04:00
parent 978d001db9
commit bc687dfe87
145 changed files with 4685 additions and 197 deletions
+32
View File
@@ -0,0 +1,32 @@
// Per-track oscilloscope display gain.
//
// The oscilloscope tap sits right after the normalization processor, so each track
// arrives at a different level (loudness normalization scales quiet and loud masters
// by different amounts). A single fixed display gain therefore buries quiet tracks
// and clips loud ones. Instead we pick ONE gain per track that maps the track's peak
// to a consistent display height, and hold it for the whole track — so the song's own
// quiet/loud dynamics are preserved (a constant multiplier doesn't change them) while
// every track lines up to the same reference. It only changes when the track changes.
/** Fallback when the track's peak is unknown (not yet analyzed / untagged). */
export const DEFAULT_OSC_GAIN = 1.8;
// Map the track's (post-normalization) peak to this fraction of the half-height,
// leaving a little headroom so the line doesn't kiss the edges.
const TARGET_LEVEL = 0.85;
const MIN_OSC_GAIN = 0.5;
const MAX_OSC_GAIN = 8; // cap so a very quiet master doesn't blow up to noise
/**
* Display gain for the oscilloscope given the track's pre-normalization linear peak
* and the normalization gain currently applied (the tap is post-normalization, so the
* level it sees is `basePeak * normGain`). Returns {@link DEFAULT_OSC_GAIN} when the
* peak is unknown.
*/
export function computeOscilloscopeGain(basePeak: number | null, normGain: number): number {
if (basePeak == null || !(basePeak > 0)) return DEFAULT_OSC_GAIN;
const postNormPeak = basePeak * (normGain > 0 ? normGain : 1);
if (!(postNormPeak > 0)) return DEFAULT_OSC_GAIN;
const gain = TARGET_LEVEL / postNormPeak;
return Math.max(MIN_OSC_GAIN, Math.min(MAX_OSC_GAIN, gain));
}
+10
View File
@@ -1,4 +1,5 @@
import { create } from 'zustand';
import { DEFAULT_OSC_GAIN } from './oscilloscopeGain';
/**
* Whether the visualizers should run. Set by useScopeLifecycle (foreground +
@@ -8,11 +9,20 @@ import { create } from 'zustand';
interface ScopeStore {
active: boolean;
setActive: (active: boolean) => void;
/**
* Per-track oscilloscope display gain. Set once per track by useNormalizationSync
* (from the track's peak + the normalization gain) and read each frame by the
* oscilloscope so the level is consistent across tracks but constant within one.
*/
oscGain: number;
setOscGain: (gain: number) => void;
}
export const useScopeStore = create<ScopeStore>((set) => ({
active: false,
setActive: (active) => set({ active }),
oscGain: DEFAULT_OSC_GAIN,
setOscGain: (oscGain) => set({ oscGain }),
}));
export const useScopeActive = (): boolean => useScopeStore((s) => s.active);