Saltar al contingut principal
Recepta número 167

Receptari · Capítol 7 · Figures i imatges

Una partitura de piano des de MusicXML en diverses pàgines

Dos minuets per a teclat gravats des de fitxers MusicXML en doble pentagrama, partits entre sistemes perquè cada peça ocupi una doble pàgina.

En aquesta pàgina
Sortida
Canvas · PDF
Nivell
Intermedi
Postext
Provada amb Postext 1.54.0
Requereix ≥ 1.54.0 · postext-pdf ≥ 1.54.0
Llicència
Actualitzada el 22 d’oct. del 2026
Codi MIT · Text CC BY 4.0
  • Mostra en castellà: encara no hi ha edició en català
  • Format 230 × 305 mm
  • 1 columna
  • Libre Caslon Text 10/14,5
  • Archivo Narrow
  • Bodoni Moda
  • 5 pàgines
  • Nivell
  • Postext 1.54.0
  • Compost en 422 ms
  • 165 línies de codi

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

script.js · línies 36–61al codi complet
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

Tipografia
Libre Caslon Text, Bodoni Moda, Archivo Narrow (SIL OFL 1.1)
Recursos
  • bwv-anh-114.musicxml
  • bwv-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

script.js · línies 65–103al codi complet
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

script.js · línies 107–120al codi complet
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

Sandbox
// ═══ 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 ajusta spacing i staffSize fins 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. Canvia spacing fins que l'últim sistema tingui més d'un compàs.
  • Un .mxl comprimit és un fitxer zip: descomprimeix-lo, o exporta el format sense comprimir, abans de descarregar-lo.

Crèdits

Text
  • The introduction and performance notes, written for this edition · Ignacio Ferro · CC BY 4.0
Imatges
Fonts
Libre Caslon Text (SIL OFL 1.1) · Bodoni Moda (SIL OFL 1.1) · Archivo Narrow (SIL OFL 1.1)
SandboxPDF