En poques paraules
Una petita edició de dos minuets de piano molt coneguts. Ensenya a agafar un fitxer exportat d'un programa de notació i compondre'l com una partitura que continua d'una pàgina a l'altra, a punt per tocar.
Què compondràs
Una edició per a intèrprets dels dos minuets del Quadern d'Anna Magdalena Bach de 1725: el de sol major, que aprèn tot estudiant de piano, i la seva parella en sol menor, avui atribuïts a Christian Petzold. La pàgina fa 230 × 305 mm, la mida d'un àlbum de partitures. Una pàgina senar presenta les peces a dues columnes de Libre Caslon sota un títol de 52 pt en Bodoni. Després cada minuet comença en una pàgina parella i ocupa la doble pàgina en un doble pentagrama de 7 mm, gravat en Bravura des d'un fitxer MusicXML: quatre compassos per sistema, els compassos de l'1 al 16 a l'esquerra i del 17 al 32 a la dreta, de manera que el pianista no passa pàgina dins d'una peça.
Aquesta recepta respon a
- Com componc una partitura de piano d'un fitxer MusicXML al llarg de diverses pàgines?
La resposta curta
const scoreStyle = {
staffSize: mm(7), // the rastral of a performing edition: read at arm's length
musicFont: 'Bravura', // the engraved plate look
barNumbers: 'system', // a number at the head of each system, as editions print them
spacing: 1.7, // about four bars to a system, eight systems to a piece
systemGap: mm(14), // room for the ledger lines above and below the grand staff
marginTop: pt(LEAD),
marginBottom: pt(0),
minSystemsAtBreak: 2, // never one system alone at a foot or the head of a page
color: col('ink'),
};
// A fence breaks between systems, so a piece runs on over pages; a score resource is kept
// whole. The file is what notation software exports: the Markdown keeps a placeholder
// line in each fence and the pen puts the fetched file there.
const SCORES = {
'bwv-anh-114': asset('bwv-anh-114.musicxml'),
'bwv-anh-115': asset('bwv-anh-115.musicxml'),
};
async function withScores(text) {
const files = await Promise.all(Object.entries(SCORES).map(async ([id, url]) => {
const res = await fetch(url);
if (!res.ok) throw new Error(`Score not found (${res.status}): ${url}`);
return [id, (await res.text()).trim()];
}));
return files.reduce((md, [id, xml]) => md.replace(`{{${id}}}`, () => xml), text);
}
Ingredients
- Funcions
- Notació musicalEstil de partituraObertures dissenyadesAtributs de títolEstils de títolCapçaleres i folisMarges simètricsCapçaleres segons el tipus de pàginaColumnes en un requadre o al textEstils de paràgrafPaleta de color semànticaExportació a PDF
- Tipografia
- Libre Caslon Text, Bodoni Moda, Archivo Narrow (SIL OFL 1.1)
- Recursos
bwv-anh-114.musicxmlbwv-anh-115.musicxml- Menuet in G major, BWV Anh. 114: the notes of the Bach-Gesellschaft edition as entered for the Mutopia Project (Mutopia-2017/01/19-75), converted to MusicXML 4.0 and proofread bar by bar for this edition (Christian Petzold; Allen Garvin (Mutopia Project), domini públic)
- Menuet in G minor, BWV Anh. 115: the notes of the Bach-Gesellschaft edition as entered for the Mutopia Project (Mutopia-2015/08/21-76), converted to MusicXML 4.0 and proofread bar by bar for this edition (Christian Petzold; Allen Garvin (Mutopia Project), domini públic)
Elaboració
#1 · Porta la peça en MusicXML
Els dos fitxers d'assets/ són MusicXML 4.0 sense més, el format que escriuen Dorico, Sibelius, Finale i MuseScore quan exporten una partitura (tria el .musicxml sense comprimir, no el .mxl). Contenen una part en dos pentagrames, amb les claus, l'armadura, les barres, la direcció de les pliques als compassos a dues veus, els ornaments i les barres de repetició. El nom de la part porta print-object="no": una etiqueta «Piano» impresa empenyeria tots els sistemes cap a la dreta. El títol de <work-title> no s'imprimeix, només serveix de descripció de la partitura, així que el Markdown dona a cada peça el seu propi títol.
#2 · Compon-la en un bloc de codi, no com a recurs
La resposta curta descarrega cada fitxer i l'escriu en un bloc ```musicxml, on el Markdown deixa una línia de marcador. Un bloc es grava a l'amplada de la columna i es talla en sistemes, i cada sistema es col·loca com una línia de paràgraf, de manera que la peça continua a la pàgina següent entre dos sistemes. Un recurs score es manté sencer i no podria creuar una pàgina. minSystemsAtBreak: 2 evita que un sistema quedi sol al peu d'una pàgina o passi sol al capdamunt de la següent.
#3 · Ajusta el pentagrama a qui toca
El pentagrama fa 7 mm d'alt, el que fa en un àlbum de piano imprès que es llegeix des del faristol. spacing: 1.7 separa les notes fins que un sistema de 186 mm de mesura conté quatre compassos, així que cada meitat d'un minuet ocupa quatre sistemes. systemGap: 14 mm deixa lloc a les línies addicionals entre les dues mans, i amb aquest valor hi caben quatre sistemes sota el títol de la primera pàgina i quatre a la del davant: la doble barra cau al canvi de pàgina del plec.
#4 · Obre cada peça en pàgina parella
const label = (id, content, edge, x, y, extra = {}) => ({
kind: 'text', id, content, fontFamily: LABEL, fontSize: pt(9), fontWeight: 500,
color: col('ink'), align: edge.endsWith('right') ? 'right' : 'left',
placement: { anchor: { to: 'container', edge }, offset: { x: mm(x), y: mm(y) } }, ...extra,
});
const kicker = { fontSize: pt(8.5), fontWeight: 700, letterSpacing: pt(1.6),
textTransform: 'uppercase', color: col('accent'), align: 'center' };
const centred = (to, y) => ({ anchor: { to, edge: to === 'container' ? 'top' : 'below' },
offset: { x: mm(0), y: mm(y) }, size: { width: 'fill' } });
const pieceOpener = {
enabled: true,
minHeight: mm(34),
slot: { elements: [
label('kicker', '{attr.kicker}', 'top', 0, 2,
{ ...kicker, placement: centred('container', 2) }),
{ kind: 'text', id: 'title', content: '{titleText}', fontFamily: DISPLAY, fontSize: pt(26),
fontWeight: 500, color: col('ink'), align: 'center',
placement: centred('#kicker', 2.5) },
label('catalogue', '{attr.catalogue}', 'bottom-left', 0, -3),
label('composer', '{attr.composer}', 'bottom-right', 0, -3, { italic: true }),
] },
};
const frontOpener = {
enabled: true,
minHeight: mm(78),
slot: { elements: [
label('kicker', '{attr.kicker}', 'top', 0, 14,
{ ...kicker, placement: centred('container', 14) }),
{ kind: 'text', id: 'title', content: '{titleText}', fontFamily: DISPLAY, fontSize: pt(52),
fontWeight: 500, color: col('ink'), align: 'center',
placement: centred('#kicker', 5) },
{ kind: 'text', id: 'composer', content: '{attr.composer}', fontFamily: SERIF,
fontSize: pt(13), italic: true, color: col('ink'), align: 'center',
placement: centred('#title', 4) },
{ kind: 'rule', id: 'rule', direction: 'horizontal', thickness: pt(0.75), color: col('accent'),
placement: { anchor: { to: '#composer', edge: 'below', at: 'center' }, offset: { y: mm(8) },
size: { width: mm(24) } } },
] },
};
breakBefore: { parity: 'even' } comença cada peça en una pàgina de l'esquerra, i la portada de l'editor fa servir un estil de títol que obre en pàgina senar i sense capçaleres. El número de catàleg i el compositor surten dels atributs del títol i van a les cantonades inferiors de l'obertura, on els imprimeixen les edicions gravades.
#5 · Posa el nom de la peça a la capçalera de la dreta
const head = (id, content, parity, edge, x, extra = {}) => ({
kind: 'text', id, content, parity, pages: 'body',
fontFamily: LABEL, fontSize: pt(8), fontWeight: 600, letterSpacing: pt(1.2),
textTransform: 'uppercase', color: col('muted'),
placement: { anchor: { to: 'page', edge }, offset: { x: mm(x), y: mm(12) } }, ...extra,
});
const folio = { fontWeight: 700, color: col('ink'), letterSpacing: pt(0) };
const header = { elements: [
head('verso-folio', '{pageNumber}', 'even', 'top-left', OUTER, folio),
head('verso-title', '{chapterTitle}', 'even', 'top-left', OUTER + 9),
head('recto-title', '{chapterTitle}', 'odd', 'top-right', -(OUTER + 9)),
head('recto-folio', '{pageNumber}', 'odd', 'top-right', -OUTER, folio),
] };
const footer = { elements: [] };
La primera pàgina d'una peça ja porta el seu títol, així que les capçaleres queden per a les pàgines següents (pages: 'body'). La pàgina senar imprimeix el títol de la peça i el foli a la vora exterior, amb la lletra de rètols a 8 pt.
La recepta completa
// ═══ Postext Cookbook · Nº 167 · A piano score from MusicXML over several pages ═══════
// https://postext.dev/en/cookbook/piano-score-from-musicxml
// Code: MIT · Text: original (CC BY 4.0) · Music: Mutopia Project (public domain)
// Fonts: Libre Caslon Text, Bodoni Moda, Archivo Narrow (SIL OFL 1.1) · Needs postext ≥ 1.54.0
import {
buildDocumentWithFonts, renderPageToCanvas, initMusicEngine,
} from 'https://esm.sh/postext';
import { renderToPdf, decompressWoff2 } from 'https://esm.sh/postext-pdf';
const LANG = 'es'; // @lang: the language of the sample document ('en' | 'es')
const RECIPE = 'piano-score-from-musicxml';
// ─── 1 · Design ─────────────────────────────────────────────────────────────
// #region palette: a plate-black ink, one deep green accent, every colour linked by id
const palette = {
ink: '#1b1a18', // the notes and the text: a warm near-black
accent: '#2f5a46', // titles' kickers, rules: the green of an old music-shop wrapper
tint: '#eef2ee',
rule: '#b9c4bc', // hairlines
muted: '#625f59', // running heads, the colophon
paper: '#ffffff',
};
const col = (id) => ({ hex: palette[id], model: 'hex', paletteId: id });
const colorPalette = [
...Object.entries(palette).map(([id, hex]) => ({ id, name: id, value: { hex, model: 'hex' } })),
{ id: 'main-color', name: 'accent (defaults)', value: { hex: palette.accent, model: 'hex' } },
];
// #endregion
const SERIF = 'Libre Caslon Text';
const DISPLAY = 'Bodoni Moda';
const LABEL = 'Archivo Narrow';
const OUTER = 20; // outer margin in mm: the running heads align to it
const LEAD = 14.5; // body leading in pt
// #region answer: the score style of a performing edition, and a MusicXML file in a fence
const scoreStyle = {
staffSize: mm(7), // the rastral of a performing edition: read at arm's length
musicFont: 'Bravura', // the engraved plate look
barNumbers: 'system', // a number at the head of each system, as editions print them
spacing: 1.7, // about four bars to a system, eight systems to a piece
systemGap: mm(14), // room for the ledger lines above and below the grand staff
marginTop: pt(LEAD),
marginBottom: pt(0),
minSystemsAtBreak: 2, // never one system alone at a foot or the head of a page
color: col('ink'),
};
// A fence breaks between systems, so a piece runs on over pages; a score resource is kept
// whole. The file is what notation software exports: the Markdown keeps a placeholder
L'script.js compost funciona tal com és: enganxa'l com a script de mòdul en qualsevol pàgina o obre la recepta a CodePen. Carpeta de la recepta a GitHub ↗ (s'obre en una pestanya nova)
Variants
#Fes una edició d'estudi amb un pentagrama més petit
Un pentagrama de 6 mm amb l'espaiat propi del gravador porta set o vuit compassos per sistema, així que cada minuet cap a la seva primera pàgina.
- staffSize: mm(7),
+ staffSize: mm(6),
- spacing: 1.7,
+ spacing: 1,#Numera els exemples breus
Quan la música són exemples breus citats al text, hi encaixa millor un recurs score numerat: corals a quatre veus com a exemples numerats.
Errors freqüents
- Els salts de sistema escrits al fitxer (
<print new-system="yes">) no es conserven: els sistemes omplen l'amplada de la columna, així que ajustaspacingistaffSizefins que portin els compassos que vols. - El gravador no reparteix els compassos entre sistemes, i l'últim sistema es compon a la seva amplada natural: a 6 mm i amb
spacing: 1, el compàs 32 queda sol en un sistema. Canviaspacingfins que l'últim sistema tingui més d'un compàs. - Un
.mxlcomprimit és un fitxer zip: descomprimeix-lo, o exporta el format sense comprimir, abans de descarregar-lo.
Crèdits
- Recepta
- Ignacio Ferro
- Text
- The introduction and performance notes, written for this edition · Ignacio Ferro · CC BY 4.0
- Imatges
- Menuet in G major, BWV Anh. 114: the notes of the Bach-Gesellschaft edition as entered for the Mutopia Project (Mutopia-2017/01/19-75), converted to MusicXML 4.0 and proofread bar by bar for this edition · Christian Petzold; Allen Garvin (Mutopia Project) · domini públic
- Menuet in G minor, BWV Anh. 115: the notes of the Bach-Gesellschaft edition as entered for the Mutopia Project (Mutopia-2015/08/21-76), converted to MusicXML 4.0 and proofread bar by bar for this edition · Christian Petzold; Allen Garvin (Mutopia Project) · domini públic
- Fonts
- Libre Caslon Text (SIL OFL 1.1) · Bodoni Moda (SIL OFL 1.1) · Archivo Narrow (SIL OFL 1.1)
- Codi
- MIT, com Postext
Editar aquest text ↗ (s'obre en una pestanya nova)Carpeta de la recepta a GitHub ↗ (s'obre en una pestanya nova)


