diff options
Diffstat (limited to 'apps/web/src/timeline/audio-display.ts')
| -rw-r--r-- | apps/web/src/timeline/audio-display.ts | 64 |
1 files changed, 64 insertions, 0 deletions
diff --git a/apps/web/src/timeline/audio-display.ts b/apps/web/src/timeline/audio-display.ts new file mode 100644 index 00000000..9958c472 --- /dev/null +++ b/apps/web/src/timeline/audio-display.ts @@ -0,0 +1,64 @@ +import { clampDb } from "./audio-state"; +import { VOLUME_DB_MAX, VOLUME_DB_MIN } from "./audio-constants"; + +const SLIDER_CURVE_EXPONENT = 2; +const MIN_DISPLAY_DB = -40; +const WAVEFORM_BAR_EXPONENT = 1.5; +const MIN_LINEAR_GAIN = 10 ** (VOLUME_DB_MIN / 20); +const MAX_LINEAR_GAIN = 10 ** (VOLUME_DB_MAX / 20); +const LINEAR_GAIN_RANGE = MAX_LINEAR_GAIN - MIN_LINEAR_GAIN; + +function getNormalizedGainFromDb({ db }: { db: number }): number { + const clampedDb = clampDb(db); + const linearGain = 10 ** (clampedDb / 20); + return (linearGain - MIN_LINEAR_GAIN) / LINEAR_GAIN_RANGE; +} + +/** + * Maps the clip's volume setting to the line position. The curve is defined in + * linear gain space so dragging near 0 dB is precise while mute compresses into + * the bottom of the clip. + */ +export function getLinePosFromDb({ db }: { db: number }): number { + const normalizedGain = Math.max( + 0, + Math.min(1, getNormalizedGainFromDb({ db })), + ); + const progress = normalizedGain ** (1 / SLIDER_CURVE_EXPONENT); + return (1 - progress) * 100; +} + +/** + * Inverse of getLinePosFromDb. Converts a drag position back into the clip's + * volume setting without depending on the underlying audio content. + */ +export function getDbFromLinePos({ percent }: { percent: number }): number { + const clampedPercent = Math.max(0, Math.min(100, percent)); + const progress = 1 - clampedPercent / 100; + const normalizedGain = progress ** SLIDER_CURVE_EXPONENT; + const linearGain = MIN_LINEAR_GAIN + normalizedGain * LINEAR_GAIN_RANGE; + return clampDb(20 * Math.log10(linearGain)); +} + +/** + * Maps an output amplitude (raw sample amplitude × gain) to a visible waveform + * height fraction using a dB scale. Agnostic to whether the input is peak or + * RMS — the caller decides which measure feeds this function; the mapping + * curve is the same either way. + * + * The log scale keeps quiet content visible while reserving the top of the + * element for amplitudes that approach 0 dBFS. + */ +export function getBarFractionFromOutputAmplitude({ + outputAmplitude, +}: { + outputAmplitude: number; +}): number { + if (outputAmplitude <= 0) return 0; + const db = 20 * Math.log10(outputAmplitude); + if (db <= MIN_DISPLAY_DB) return 0; + return Math.min( + 1, + ((db - MIN_DISPLAY_DB) / -MIN_DISPLAY_DB) ** WAVEFORM_BAR_EXPONENT, + ); +} |
