feat: integrate authored nightdrive soundtrack

This commit is contained in:
2026-09-04 14:39:28 +03:00
parent eb7f276b91
commit 745fa8169e
39 changed files with 1013 additions and 714 deletions

View File

@@ -0,0 +1,53 @@
import type { AudioJourneyState } from '@/audio/types';
export const SOUNDTRACK_SAMPLE_RATE = 48_000;
export const MUSIC_BPM = 86;
export const MUSIC_BAR_SECONDS = 4 * 60 / MUSIC_BPM;
export const MAIN_PHRASE_BARS = 8;
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;
export const INTRO_LOOP_END_SAMPLE = 2_411_163;
export const INTRO_OUT_START_SAMPLE = INTRO_LOOP_END_SAMPLE;
export const INTRO_OUT_END_SAMPLE = 2_679_070;
export const INTRO_RUNTIME_END_SAMPLE = 2_691_070;
export const INTRO_IN_END_SECONDS = INTRO_IN_END_SAMPLE / SOUNDTRACK_SAMPLE_RATE;
export const INTRO_FIRST_PASS_END_SECONDS = INTRO_FIRST_PASS_END_SAMPLE / SOUNDTRACK_SAMPLE_RATE;
export const INTRO_LOOP_START_SECONDS = INTRO_LOOP_START_SAMPLE / SOUNDTRACK_SAMPLE_RATE;
export const INTRO_LOOP_END_SECONDS = INTRO_LOOP_END_SAMPLE / SOUNDTRACK_SAMPLE_RATE;
export const INTRO_OUT_START_SECONDS = INTRO_OUT_START_SAMPLE / SOUNDTRACK_SAMPLE_RATE;
export const INTRO_OUT_END_SECONDS = INTRO_OUT_END_SAMPLE / SOUNDTRACK_SAMPLE_RATE;
export const INTRO_RUNTIME_END_SECONDS = INTRO_RUNTIME_END_SAMPLE / SOUNDTRACK_SAMPLE_RATE;
export type InitialPlayback =
| { readonly kind: 'intro' }
| { readonly kind: 'main'; readonly offsetSeconds: number };
export function resolveInitialPlayback(state: AudioJourneyState): InitialPlayback {
return state.atStart
? { kind: 'intro' }
: { kind: 'main', offsetSeconds: mainPhraseOffsetSeconds(state.entryPhrase) };
}
export function mainPhraseOffsetSeconds(entryPhrase: number): number {
const finitePhrase = Number.isFinite(entryPhrase) ? Math.trunc(entryPhrase) : 0;
const phrase = Math.min(7, Math.max(0, finitePhrase));
const targetSamples = Math.round(
phrase * MAIN_PHRASE_BARS * MUSIC_BAR_SECONDS * SOUNDTRACK_SAMPLE_RATE,
);
return targetSamples / SOUNDTRACK_SAMPLE_RATE;
}
export function nextSafeBarTime(
now: number,
anchorTime: number,
minimumLeadSeconds: number,
): number {
const earliest = Math.max(anchorTime, now + Math.max(0, minimumLeadSeconds));
const elapsedBars = (earliest - anchorTime) / MUSIC_BAR_SECONDS;
const oneSampleInBars = 1 / SOUNDTRACK_SAMPLE_RATE / MUSIC_BAR_SECONDS;
const nextBar = Math.ceil(elapsedBars - oneSampleInBars);
return anchorTime + nextBar * MUSIC_BAR_SECONDS;
}