import { booleanOf, type PropertyBag } from '../model/Properties'; import { FADE_CURVES, type FadeCurve, isFadeCurve, type SoundCut } from '../model/sound'; import { coerceAs } from './kinds'; import type { PropertyDef } from './PropertySchema'; /** How loud this sound is by itself, 0–1; a `Play sound` node scales this rather than replacing it. */ export const VOLUME_KEY = 'volume'; /** Whether this sound is music: it repeats until something stops it. */ export const LOOP_KEY = 'loop'; /** Where in its file a clip starts and ends, in seconds; an end of 0 is the end of the file (#343). */ export const START_KEY = 'start'; export const END_KEY = 'end'; /** Milliseconds the start and the end ramp over when they lie inside the file, so they do not click (#345). */ export const FADE_IN_KEY = 'fadeIn'; export const FADE_OUT_KEY = 'fadeOut'; const DEFAULT_FADE_MS = 5; /** The shape of each ramp. */ export const FADE_IN_CURVE_KEY = 'fadeInCurve'; export const FADE_OUT_CURVE_KEY = 'fadeOutCurve'; export const DEFAULT_FADE_CURVE: FadeCurve = 'linear'; /** Whether the stretch plays backwards (#321). */ export const REVERSE_KEY = 'reverse'; /** The rows of a clip of a sound (#357): the stretch it is, set in the Sound tab, and how it is played. */ export const CLIP_ROWS: readonly PropertyDef[] = [ { key: START_KEY, label: 'Start (s)', kind: 'number', default: 0, min: 0, step: 0.01, decimals: 3 }, { key: END_KEY, label: 'End (s)', kind: 'number', default: 0, min: 0, step: 0.01, decimals: 3 }, // That stretch backwards (#321), its fade in at the end it starts from. { key: REVERSE_KEY, label: 'Reverse', kind: 'boolean', default: false }, // The ramps at a start and an end inside the file, each with its shape (#345). ...( [ [FADE_IN_KEY, 'Fade in (ms)', FADE_IN_CURVE_KEY, 'Fade in curve'], [FADE_OUT_KEY, 'Fade out (ms)', FADE_OUT_CURVE_KEY, 'Fade out curve'], ] as const ).flatMap(([key, label, curveKey, curveLabel]): PropertyDef[] => [ { key, label, kind: 'number', default: DEFAULT_FADE_MS, min: 0, max: 1000, step: 1, decimals: 0 }, { key: curveKey, label: curveLabel, kind: 'enum', default: DEFAULT_FADE_CURVE, options: FADE_CURVES }, ]), ]; export function volumeOf(bag: PropertyBag): number { return coerceAs(bag[VOLUME_KEY], 'number', 1); } export function loopsOf(bag: PropertyBag): boolean { return booleanOf(bag[LOOP_KEY]); } /** The stretch of its file a clip is, its fades in seconds. */ export function cutOf(bag: PropertyBag): SoundCut { const seconds = (key: string, fallback: number) => Math.max(0, coerceAs(bag[key], 'number', fallback)); const curve = (key: string) => { const value = bag[key]; return isFadeCurve(value) ? value : DEFAULT_FADE_CURVE; }; return { start: seconds(START_KEY, 0), end: seconds(END_KEY, 0), fadeIn: seconds(FADE_IN_KEY, DEFAULT_FADE_MS) / 1000, fadeOut: seconds(FADE_OUT_KEY, DEFAULT_FADE_MS) / 1000, curveIn: curve(FADE_IN_CURVE_KEY), curveOut: curve(FADE_OUT_CURVE_KEY), reverse: booleanOf(bag[REVERSE_KEY]), }; }