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,71 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import {parseMusicXml, buildPerformanceOrder} from '../src/server/musicxml.js';
|
||||
import {engraveOneLine} from '../src/server/engrave.js';
|
||||
import {exportAudioAndMidi} from '../src/server/musescore.js';
|
||||
import {parseMidiFile} from '../src/server/midi.js';
|
||||
import {buildEvents} from '../src/server/events.js';
|
||||
import {readAudioDuration} from '../src/server/audio.js';
|
||||
import {DEFAULT_SETTINGS} from '../src/shared/types.js';
|
||||
|
||||
const demo = path.resolve('public/demo/demo.musicxml');
|
||||
const outDir = path.resolve('data/check');
|
||||
fs.mkdirSync(outDir, {recursive: true});
|
||||
|
||||
const parsed = parseMusicXml(fs.readFileSync(demo));
|
||||
console.log('title', parsed.title, '| composer', parsed.composer);
|
||||
console.log('parts', parsed.parts.length, 'measures', parsed.measures.length, 'notes', parsed.allNotes.length);
|
||||
console.log('unsupported', parsed.unsupported);
|
||||
console.log('warnings', parsed.warnings.slice(0, 5));
|
||||
|
||||
const performance = buildPerformanceOrder(parsed.measures, []);
|
||||
console.log('performance order length', performance.order.length);
|
||||
console.log('order', performance.order.map((i) => i + 1).join(', '));
|
||||
|
||||
const engraved = await engraveOneLine(parsed.annotatedXml);
|
||||
fs.writeFileSync(path.join(outDir, 'score.svg'), engraved.svg);
|
||||
console.log('svg', engraved.geometry.width, 'x', engraved.geometry.height, 'geometry notes', engraved.geometry.noteCount);
|
||||
console.log('measure times', engraved.measureStartMs.size, 'note times', engraved.noteTimes.size);
|
||||
|
||||
console.time('musescore');
|
||||
const {wav, midi} = await exportAudioAndMidi(demo, outDir);
|
||||
console.timeEnd('musescore');
|
||||
console.log('wav', fs.statSync(wav).size, 'midi', fs.statSync(midi).size);
|
||||
|
||||
const audioDuration = await readAudioDuration(wav);
|
||||
const parsedMidi = parseMidiFile(midi);
|
||||
console.log('audioDuration', audioDuration, 'midi notes', parsedMidi.notes.length, 'midi duration', parsedMidi.duration);
|
||||
|
||||
const built = buildEvents({
|
||||
parsed,
|
||||
engrave: engraved,
|
||||
midi: parsedMidi,
|
||||
performanceOrder: performance.order,
|
||||
audioDuration,
|
||||
settings: DEFAULT_SETTINGS,
|
||||
});
|
||||
console.log('events', built.events.length, 'scroll keys', built.scroll.length);
|
||||
console.log('meta', JSON.stringify({...built.meta, warnings: built.meta.warnings.slice(0, 8)}, null, 2));
|
||||
console.log('first events', built.events.slice(0, 8));
|
||||
console.log('last events', built.events.slice(-5));
|
||||
console.log('scroll head', built.scroll.slice(0, 6));
|
||||
console.log('scroll tail', built.scroll.slice(-4));
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(outDir, 'score.json'),
|
||||
JSON.stringify(
|
||||
{
|
||||
version: 1,
|
||||
meta: built.meta,
|
||||
events: built.events,
|
||||
activations: built.activations,
|
||||
scroll: built.scroll,
|
||||
svgUrl: '/jobs/check/score.svg',
|
||||
audioUrl: '/jobs/check/audio.wav',
|
||||
settings: DEFAULT_SETTINGS,
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
);
|
||||
console.log('wrote', path.join(outDir, 'score.json'));
|
||||
Reference in new issue
Block a user