Files
music-visualizer/scripts/make-demo.mjs
T

292 lines
7.5 KiB
JavaScript
Raw Normal View History

import fs from 'node:fs';
import path from 'node:path';
const divisions = 4;
const typeFor = (d) =>
({16: 'whole', 8: 'half', 4: 'quarter', 2: 'eighth', 1: '16th'})[d] ?? 'quarter';
let xml = `<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE score-partwise PUBLIC "-//Recordare//DTD MusicXML 4.0 Partwise//EN" "http://www.musicxml.org/dtds/partwise.dtd">
<score-partwise version="4.0">
<work><work-title>Morning Window</work-title></work>
<identification>
<creator type="composer">Demo · Original</creator>
<encoding><software>music-visualizer demo generator</software></encoding>
</identification>
<part-list><score-part id="P1"><part-name>Piano</part-name></score-part></part-list>
<part id="P1">
`;
function note(step, octave, duration, opts = {}) {
const {chord = false, staff = 1, voice = '1', tie, grace = false} = opts;
const parts = [];
if (grace) parts.push('<grace/>');
if (chord) parts.push('<chord/>');
parts.push(`<pitch><step>${step}</step><octave>${octave}</octave></pitch>`);
if (!grace) parts.push(`<duration>${duration}</duration>`);
if (tie === 'start') parts.push('<tie type="start"/>');
if (tie === 'stop') parts.push('<tie type="stop"/>');
parts.push(`<voice>${voice}</voice>`);
if (!grace) parts.push(`<type>${typeFor(duration)}</type>`);
else parts.push('<type>eighth</type>');
if (tie) {
parts.push(`<notations><tied type="${tie}"/></notations>`);
}
parts.push(`<staff>${staff}</staff>`);
return ` <note>${parts.join('')}</note>\n`;
}
function rest(duration, opts = {}) {
const {staff = 1, voice = '1'} = opts;
return ` <note><rest/><duration>${duration}</duration><voice>${voice}</voice><type>${typeFor(duration)}</type><staff>${staff}</staff></note>\n`;
}
function leftArpeggio(notes) {
// 8 eighth notes = 16 divisions
let out = '';
for (const [step, octave] of notes) {
out += note(step, octave, 2, {staff: 2, voice: '5'});
}
return out;
}
const measures = [
// 1
{
right:
note('C', 5, 4) +
note('E', 5, 2) +
note('G', 5, 2) +
note('A', 5, 4) +
note('G', 5, 4),
left: leftArpeggio([
['C', 3], ['G', 3], ['E', 4], ['G', 3],
['C', 3], ['G', 3], ['E', 4], ['G', 3],
]),
},
// 2
{
right:
note('F', 5, 4) +
note('E', 5, 2) +
note('D', 5, 2) +
note('C', 5, 8) +
note('E', 5, 8, {chord: true}) +
note('G', 5, 8, {chord: true}),
left: leftArpeggio([
['F', 3], ['C', 4], ['A', 3], ['C', 4],
['F', 3], ['C', 4], ['A', 3], ['C', 4],
]),
},
// 3
{
right:
rest(4) +
note('G', 5, 2) +
note('A', 5, 2) +
note('G', 5, 4) +
note('E', 5, 4),
left: leftArpeggio([
['G', 3], ['D', 4], ['B', 3], ['D', 4],
['G', 3], ['D', 4], ['B', 3], ['D', 4],
]),
},
// 4
{
right:
note('D', 5, 4) +
note('E', 5, 4) +
note('C', 5, 8),
left: leftArpeggio([
['C', 3], ['G', 3], ['E', 4], ['G', 3],
['C', 3], ['G', 3], ['E', 4], ['G', 3],
]),
},
// 5
{
right:
note('E', 5, 4) +
note('G', 5, 2) +
note('A', 5, 2) +
note('C', 6, 4) +
note('B', 5, 4),
left: leftArpeggio([
['A', 3], ['E', 4], ['C', 4], ['E', 4],
['A', 3], ['E', 4], ['C', 4], ['E', 4],
]),
},
// 6
{
right:
note('A', 5, 4) +
note('G', 5, 2) +
note('E', 5, 2) +
note('D', 5, 4) +
note('E', 5, 4),
left: leftArpeggio([
['F', 3], ['C', 4], ['A', 3], ['C', 4],
['F', 3], ['C', 4], ['A', 3], ['C', 4],
]),
},
// 7
{
right:
note('G', 5, 4) +
note('E', 5, 2) +
note('D', 5, 2) +
note('C', 5, 8),
left: leftArpeggio([
['G', 3], ['D', 4], ['B', 3], ['D', 4],
['G', 3], ['D', 4], ['B', 3], ['D', 4],
]),
},
// 8
{
right:
note('D', 5, 4) +
note('E', 5, 2) +
note('F', 5, 2) +
note('G', 5, 8, {tie: 'start'}),
left: leftArpeggio([
['C', 3], ['G', 3], ['E', 4], ['G', 3],
['C', 3], ['G', 3], ['E', 4], ['G', 3],
]),
},
// 9
{
right:
note('G', 5, 8, {tie: 'stop'}) +
note('A', 5, 4) +
note('G', 5, 4),
left: leftArpeggio([
['A', 3], ['E', 4], ['C', 4], ['E', 4],
['A', 3], ['E', 4], ['C', 4], ['E', 4],
]),
},
// 10
{
right:
note('A', 5, 2, {grace: true}) +
note('G', 5, 4) +
note('E', 5, 4) +
note('D', 5, 8),
left: leftArpeggio([
['F', 3], ['C', 4], ['A', 3], ['C', 4],
['F', 3], ['C', 4], ['A', 3], ['C', 4],
]),
},
// 11
{
right:
note('E', 5, 4) +
note('G', 5, 2) +
note('A', 5, 2) +
note('C', 6, 8),
left: leftArpeggio([
['G', 3], ['D', 4], ['B', 3], ['D', 4],
['G', 3], ['D', 4], ['B', 3], ['D', 4],
]),
},
// 12
{
right:
note('B', 5, 4) +
note('A', 5, 4) +
note('G', 5, 8),
left: leftArpeggio([
['C', 3], ['G', 3], ['E', 4], ['G', 3],
['C', 3], ['G', 3], ['E', 4], ['G', 3],
]),
},
// 13
{
right:
note('C', 6, 4) +
note('B', 5, 2) +
note('A', 5, 2) +
note('G', 5, 4) +
note('E', 5, 4),
left: leftArpeggio([
['A', 3], ['E', 4], ['C', 4], ['E', 4],
['A', 3], ['E', 4], ['C', 4], ['E', 4],
]),
},
// 14
{
right:
note('F', 5, 4) +
note('E', 5, 2) +
note('D', 5, 2) +
note('C', 5, 8),
left: leftArpeggio([
['F', 3], ['C', 4], ['A', 3], ['C', 4],
['F', 3], ['C', 4], ['A', 3], ['C', 4],
]),
},
// 15
{
right:
note('D', 5, 4) +
note('E', 5, 4) +
note('G', 5, 8),
left: leftArpeggio([
['G', 3], ['D', 4], ['B', 3], ['D', 4],
['G', 3], ['D', 4], ['B', 3], ['D', 4],
]),
},
// 16
{
right:
note('C', 5, 16) +
note('E', 5, 16, {chord: true}) +
note('G', 5, 16, {chord: true}),
left:
note('C', 3, 16, {staff: 2, voice: '5'}) +
note('E', 3, 16, {staff: 2, voice: '5', chord: true}) +
note('G', 3, 16, {staff: 2, voice: '5', chord: true}),
},
];
measures.forEach((measure, index) => {
const number = index + 1;
xml += ` <measure number="${number}">\n`;
if (number === 1) {
xml += ` <attributes>
<divisions>${divisions}</divisions>
<key><fifths>0</fifths></key>
<time><beats>4</beats><beat-type>4</beat-type></time>
<staves>2</staves>
<clef number="1"><sign>G</sign><line>2</line></clef>
<clef number="2"><sign>F</sign><line>4</line></clef>
</attributes>
<direction placement="above">
<direction-type><metronome><beat-unit>quarter</beat-unit><per-minute>96</per-minute></metronome></direction-type>
<sound tempo="96"/>
</direction>
<barline location="left"><repeat direction="forward"/></barline>
`;
}
if (number === 9) {
xml += ` <direction placement="above">
<direction-type><metronome><beat-unit>quarter</beat-unit><per-minute>104</per-minute></metronome></direction-type>
<sound tempo="104"/>
</direction>
`;
}
xml += measure.right;
xml += ` <backup><duration>16</duration></backup>\n`;
xml += measure.left;
if (number === 4) {
xml += ` <barline location="right"><repeat direction="backward" times="2"/></barline>\n`;
}
xml += ` </measure>\n`;
});
xml += ` </part>
</score-partwise>
`;
const out = path.resolve('public/demo/demo.musicxml');
fs.mkdirSync(path.dirname(out), {recursive: true});
fs.writeFileSync(out, xml, 'utf8');
console.log(`Wrote ${out} (${xml.length} bytes, ${measures.length} measures)`);