feat: add adaptive soundtrack mixer
This commit is contained in:
@@ -2,9 +2,30 @@ import type { ChapterIntent } from '@/audio/types';
|
||||
|
||||
export const SOUNDTRACK_SAMPLE_RATE = 48_000;
|
||||
export const MUSIC_BPM = 86;
|
||||
export const MUSIC_BEAT_SECONDS = 60 / MUSIC_BPM;
|
||||
export const MUSIC_BEAT_SAMPLES = Math.round(
|
||||
SOUNDTRACK_SAMPLE_RATE * MUSIC_BEAT_SECONDS,
|
||||
);
|
||||
export const MUSIC_BAR_SECONDS = 4 * 60 / MUSIC_BPM;
|
||||
export const MUSIC_BAR_SAMPLES = 133_953;
|
||||
export const MAIN_PHRASE_BARS = 8;
|
||||
|
||||
export const MAIN_DURATION_SAMPLES = 8_573_023;
|
||||
export const MAIN_DURATION_SECONDS = MAIN_DURATION_SAMPLES / SOUNDTRACK_SAMPLE_RATE;
|
||||
export const AFTERGLOW_TRANSITION_SAMPLES = MUSIC_BAR_SAMPLES;
|
||||
export const AFTERGLOW_TRANSITION_SECONDS = AFTERGLOW_TRANSITION_SAMPLES /
|
||||
SOUNDTRACK_SAMPLE_RATE;
|
||||
export const AFTERGLOW_LOOP_START_SAMPLE = AFTERGLOW_TRANSITION_SAMPLES;
|
||||
export const AFTERGLOW_LOOP_SAMPLES = 1_071_628;
|
||||
export const AFTERGLOW_LOOP_END_SAMPLE = 1_205_581;
|
||||
export const AFTERGLOW_LOOP_START_SECONDS = AFTERGLOW_LOOP_START_SAMPLE /
|
||||
SOUNDTRACK_SAMPLE_RATE;
|
||||
export const AFTERGLOW_LOOP_END_SECONDS = AFTERGLOW_LOOP_END_SAMPLE /
|
||||
SOUNDTRACK_SAMPLE_RATE;
|
||||
export const DRIVE_LIFT_LOOP_SAMPLES = 535_814;
|
||||
export const DRIVE_LIFT_LOOP_SECONDS = DRIVE_LIFT_LOOP_SAMPLES /
|
||||
SOUNDTRACK_SAMPLE_RATE;
|
||||
|
||||
export const INTRO_IN_END_SAMPLE = 267_907;
|
||||
export const INTRO_FIRST_PASS_END_SAMPLE = 1_339_535;
|
||||
export const INTRO_LOOP_START_SAMPLE = INTRO_FIRST_PASS_END_SAMPLE;
|
||||
@@ -51,3 +72,51 @@ export function nextSafeBarTime(
|
||||
const nextBar = Math.ceil(elapsedBars - oneSampleInBars);
|
||||
return anchorTime + nextBar * MUSIC_BAR_SECONDS;
|
||||
}
|
||||
|
||||
export function nextBeatTime(earliest: number, anchorTime: number): number {
|
||||
const elapsedBeats = (Math.max(anchorTime, earliest) - anchorTime) /
|
||||
MUSIC_BEAT_SECONDS;
|
||||
const oneSampleInBeats = 1 / SOUNDTRACK_SAMPLE_RATE / MUSIC_BEAT_SECONDS;
|
||||
return anchorTime + Math.ceil(elapsedBeats - oneSampleInBeats) *
|
||||
MUSIC_BEAT_SECONDS;
|
||||
}
|
||||
|
||||
export function phaseAlignedLoopOffset(
|
||||
when: number,
|
||||
anchorTime: number,
|
||||
loopDuration: number,
|
||||
): number {
|
||||
if (!Number.isFinite(loopDuration) || loopDuration <= 0) return 0;
|
||||
const elapsed = when - anchorTime;
|
||||
return ((elapsed % loopDuration) + loopDuration) % loopDuration;
|
||||
}
|
||||
|
||||
export interface MainScheduleInput {
|
||||
readonly startTime: number;
|
||||
readonly offsetSeconds: number;
|
||||
readonly bufferDuration: number;
|
||||
}
|
||||
|
||||
export interface MainSchedule {
|
||||
readonly sourceStartTime: number;
|
||||
readonly sourceOffsetSeconds: number;
|
||||
readonly musicalAnchorTime: number;
|
||||
readonly sourceEndTime: number;
|
||||
readonly afterglowStartTime: number;
|
||||
}
|
||||
|
||||
export function resolveMainSchedule(input: MainScheduleInput): MainSchedule {
|
||||
const sourceOffsetSeconds = Math.min(
|
||||
Math.max(0, input.offsetSeconds),
|
||||
Math.max(0, input.bufferDuration - 0.05),
|
||||
);
|
||||
const sourceEndTime = input.startTime + input.bufferDuration -
|
||||
sourceOffsetSeconds;
|
||||
return {
|
||||
sourceStartTime: input.startTime,
|
||||
sourceOffsetSeconds,
|
||||
musicalAnchorTime: input.startTime - sourceOffsetSeconds,
|
||||
sourceEndTime,
|
||||
afterglowStartTime: sourceEndTime - AFTERGLOW_TRANSITION_SECONDS,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user