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
This commit is contained in:
commit
a237a7c604
38 files changed
+13958
No files matched your search
@@ -0,0 +1,174 @@
|
||||
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;
|
||||
Reference in new issue
Block a user