Files
music-visualizer/src/shared/types.ts
T
blumia a237a7c604 Add one-line score visualizer with 3D orb and MP4 export
- Verovio single-system engraving, MuseScore audio/MIDI from the same source
- React + Remotion player with live camera, colour and orb controls
- Express pipeline, render queue and local H.264/AAC export
- MIT license plus third-party notices for the reference skill
2026-11-05 11:38:25 +08:00

175 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;