174 lines
4.4 KiB
TypeScript
174 lines
4.4 KiB
TypeScript
export const APP_NAME = 'Music Visualizer';
|
|||
|
|
|
||
|
|
export type NoteKind = 'attack' | 'sustain';
|
||
|
|
|
||
|
|
export type NoteEvent = {
|
||
|
|
/** Unique per performance occurrence. */
|
||
|
|
eventId: string;
|
||
|
|
/** Verovio SVG g.note id, shared by repeated performances of the same notehead. */
|
||
|
|
scoreId: string;
|
||
|
|
time: number;
|
||
|
|
duration: number;
|
||
|
|
pitch: number;
|
||
|
|
velocity: number;
|
||
|
|
staff: number;
|
||
|
|
/** SVG user coordinates (outer viewBox). */
|
||
|
|
x: number;
|
||
|
|
y: number;
|
||
|
|
kind: NoteKind;
|
||
|
|
grace?: boolean;
|
||
|
|
};
|
||
|
|
|
||
|
|
export type ScrollKey = {
|
||
|
|
/** Music seconds. */
|
||
|
|
time: number;
|
||
|
|
/** Left edge of the visible score in SVG user units. */
|
||
|
|
x: number;
|
||
|
|
};
|
||
|
|
|
||
|
|
export type ScoreMeta = {
|
||
|
|
title: string;
|
||
|
|
subtitle: string;
|
||
|
|
composer: string;
|
||
|
|
svgWidth: number;
|
||
|
|
svgHeight: number;
|
||
|
|
/** Last sounding note end, in music seconds. */
|
||
|
|
musicDuration: number;
|
||
|
|
/** Audio duration, in seconds. */
|
||
|
|
audioDuration: number;
|
||
|
|
measureCount: number;
|
||
|
|
scoreNoteCount: number;
|
||
|
|
midiNoteCount: number;
|
||
|
|
mappedNoteCount: number;
|
||
|
|
warnings: string[];
|
||
|
|
unsupported: string[];
|
||
|
|
};
|
||
|
|
|
||
|
|
export type VisualSettings = {
|
||
|
|
width: number;
|
||
|
|
height: number;
|
||
|
|
fps: number;
|
||
|
|
introSeconds: number;
|
||
|
|
outroSeconds: number;
|
||
|
|
/** Score strip height as a fraction of the video height. 0.3 - 0.75. */
|
||
|
|
scoreHeightRatio: number;
|
||
|
|
/** Active note target position as a fraction of viewport width. 0.25 - 0.6. */
|
||
|
|
scrollLead: number;
|
||
|
|
paperColor: string;
|
||
|
|
/** Paper opacity; 0 makes the score float on the background with no paper at all. */
|
||
|
|
paperAlpha: number;
|
||
|
|
/** Ink colour for the engraved notation. */
|
||
|
|
scoreColor: string;
|
||
|
|
background: string;
|
||
|
|
staffColors: string[];
|
||
|
|
/** Ball radius multiplier around the default. */
|
||
|
|
ballScale: number;
|
||
|
|
/**
|
||
|
|
* Amplifies the orb's jump along the sheet normal. Resting height above the
|
||
|
|
* ink stays put; 0 slides every hop flat onto the paper.
|
||
|
|
*/
|
||
|
|
ballLift: number;
|
||
|
|
/** Glow intensity multiplier. */
|
||
|
|
glow: number;
|
||
|
|
showTitle: boolean;
|
||
|
|
showProgress: boolean;
|
||
|
|
titleFont: string;
|
||
|
|
audioOffsetMs: number;
|
||
|
|
/** Camera pitch in degrees. Positive values tip the top of the score away. */
|
||
|
|
tiltX: number;
|
||
|
|
/** Camera yaw in degrees. Positive values turn the right side away. */
|
||
|
|
tiltY: number;
|
||
|
|
/** Camera roll in degrees. Negative values lift the right side of the staff. */
|
||
|
|
tiltZ: number;
|
||
|
|
/** CSS perspective distance in pixels. Smaller values give stronger convergence. */
|
||
|
|
perspective: number;
|
||
|
|
/** Extra zoom applied to the tilted score plane so it still fills the frame. */
|
||
|
|
tiltZoom: number;
|
||
|
|
};
|
||
|
|
|
||
|
|
export type RenderData = {
|
||
|
|
version: 1;
|
||
|
|
meta: ScoreMeta;
|
||
|
|
events: NoteEvent[];
|
||
|
|
activations: NoteEvent[];
|
||
|
|
scroll: ScrollKey[];
|
||
|
|
svgUrl: string;
|
||
|
|
audioUrl: string;
|
||
|
|
settings: VisualSettings;
|
||
|
|
};
|
||
|
|
|
||
|
|
export type JobStatus = 'queued' | 'running' | 'ready' | 'rendering' | 'done' | 'error';
|
||
|
|
|
||
|
|
export type JobStage =
|
||
|
|
| 'queued'
|
||
|
|
| 'reading'
|
||
|
|
| 'synthesizing'
|
||
|
|
| 'engraving'
|
||
|
|
| 'mapping'
|
||
|
|
| 'building'
|
||
|
|
| 'rendering'
|
||
|
|
| 'done';
|
||
|
|
|
||
|
|
export type JobPublic = {
|
||
|
|
id: string;
|
||
|
|
status: JobStatus;
|
||
|
|
stage: JobStage;
|
||
|
|
progress: number;
|
||
|
|
message: string;
|
||
|
|
error?: string;
|
||
|
|
warnings?: string[];
|
||
|
|
unsupported?: string[];
|
||
|
|
data?: RenderData;
|
||
|
|
videoUrl?: string;
|
||
|
|
createdAt: string;
|
||
|
|
inputName?: string;
|
||
|
|
inputFormat?: string;
|
||
|
|
};
|
||
|
|
|
||
|
|
export type CreateJobResponse = {
|
||
|
|
id: string;
|
||
|
|
};
|
||
|
|
|
||
|
|
export type HealthResponse = {
|
||
|
|
ok: boolean;
|
||
|
|
musescore: { available: boolean; path?: string; version?: string };
|
||
|
|
chrome: { available: boolean; note: string };
|
||
|
|
ffmpeg: { available: boolean; note: string };
|
||
|
|
version: string;
|
||
|
|
};
|
||
|
|
|
||
|
|
export const DEFAULT_SETTINGS: VisualSettings = {
|
||
|
|
width: 1920,
|
||
|
|
height: 1080,
|
||
|
|
fps: 30,
|
||
|
|
introSeconds: 0.65,
|
||
|
|
outroSeconds: 1.8,
|
||
|
|
scoreHeightRatio: 0.5,
|
||
|
|
scrollLead: 0.38,
|
||
|
|
paperColor: '#c9c2b6',
|
||
|
|
paperAlpha: 1,
|
||
|
|
scoreColor: '#14110d',
|
||
|
|
background: '#111015',
|
||
|
|
staffColors: ['#bda0ff', '#ffbc6d', '#8fe3c8', '#ff8fa3'],
|
||
|
|
ballScale: 1,
|
||
|
|
ballLift: 1,
|
||
|
|
glow: 1,
|
||
|
|
showTitle: true,
|
||
|
|
showProgress: true,
|
||
|
|
titleFont: "Georgia, 'Times New Roman', 'Noto Serif SC', 'Songti SC', serif",
|
||
|
|
audioOffsetMs: 0,
|
||
|
|
tiltX: 12,
|
||
|
|
tiltY: 0,
|
||
|
|
tiltZ: -5,
|
||
|
|
perspective: 1800,
|
||
|
|
tiltZoom: 1.12,
|
||
|
|
};
|
||
|
|
|
||
|
|
export const RESOLUTION_PRESETS = [
|
||
|
|
{id: 'landscape', label: '横屏 · 16:9', width: 1920, height: 1080},
|
||
|
|
{id: 'vertical', label: '竖屏 · 9:16', width: 1080, height: 1920},
|
||
|
|
{id: 'square', label: '方形 · 1:1', width: 1080, height: 1080},
|
||
|
|
] as const;
|
||
|
|
|
||
|
|
export const MAX_UPLOAD_BYTES = 30 * 1024 * 1024;
|
||
|
|
export const MAX_RENDER_SECONDS = 20 * 60;
|