export type VQChronologicalScenario<Event> = Readonly<{
timeline: readonly Event[];
gapFrames: number;
durationForEvent: (event: Event, fps: number) => number;
assetWorkflow?: VQScenarioAssetWorkflow;
}>;
export type VQScenarioVoicevoxWorkflow = Readonly<{
scriptPath: string;
outputDir: string;
manifestPath: string;
}>;
export type VQScenarioRhubarbWorkflow = Readonly<{
sourceManifestPath: string;
manifestPath: string;
outputDir: string;
rawOutputDir: string;
}>;
export type VQScenarioAssetWorkflow = Readonly<{
voicevox: VQScenarioVoicevoxWorkflow;
rhubarb: VQScenarioRhubarbWorkflow;
}>;
export type VQScheduledScenarioSegment<Event> = Readonly<{
event: Event;
index: number;
from: number;
durationInFrames: number;
}>;
export const defineVQChronologicalScenario = <Event>(
scenario: VQChronologicalScenario<Event>
) => scenario;
export const defineVQScenarioAssetWorkflow = (
workflow: VQScenarioAssetWorkflow
) => workflow;
export const scheduleVQChronologicalScenario = <Event>(
scenario: VQChronologicalScenario<Event>,
fps: number
): VQScheduledScenarioSegment<Event>[] => {
let cursor = 0;
return scenario.timeline.map((event, index) => {
const durationInFrames = scenario.durationForEvent(event, fps);
const segment = {
event,
index,
from: cursor,
durationInFrames,
};
cursor += durationInFrames;
if (index < scenario.timeline.length - 1) {
cursor += scenario.gapFrames;
}
return segment;
});
};
export const activeVQChronologicalScenarioSegmentForFrame = <Event>(
segments: readonly VQScheduledScenarioSegment<Event>[],
frame: number
) => {
let activeSegment = segments[0];
for (const segment of segments) {
if (frame >= segment.from) {
activeSegment = segment;
} else {
break;
}
}
return activeSegment;
};
export const totalVQChronologicalScenarioDurationInFrames = <Event>(
scenario: VQChronologicalScenario<Event>,
fps: number
) =>
scheduleVQChronologicalScenario(scenario, fps).reduce(
(total, segment, index) =>
total +
segment.durationInFrames +
(index < scenario.timeline.length - 1 ? scenario.gapFrames : 0),
0
);