Saltar al contenido principal
Receta número 167

Recetario · Capítulo 7 · Figuras e imágenes

Una partitura de piano desde MusicXML en varias páginas

Dos minuetos para teclado grabados desde archivos MusicXML en doble pentagrama, partidos entre sistemas para que cada pieza ocupe una doble página.

En esta página
Salida
Canvas · PDF
Postext
Probada con Postext 1.54.0
Requiere ≥ 1.54.0 · postext-pdf ≥ 1.54.0
Licencia
Actualizada el 22 oct 2026
Código MIT · Texto CC BY 4.0

pp. 2–3 de 5

  • Formato 230 × 305 mm
  • 1 columna
  • Libre Caslon Text 10/14,5
  • Archivo Narrow
  • Bodoni Moda
  • 5 páginas
  • Nivel
  • Postext 1.54.0
  • Compuesto en 422 ms
  • 165 líneas de código

En pocas palabras

Una pequeña edición de dos minuetos de piano muy conocidos. Enseña a tomar un archivo exportado de un programa de notación y componerlo como una partitura que sigue de una página a otra, lista para tocar.

Lo que vas a componer

Una edición para intérpretes de los dos minuetos del Cuaderno de Anna Magdalena Bach de 1725: el de sol mayor, que aprende todo estudiante de piano, y su pareja en sol menor, hoy atribuidos a Christian Petzold. La página mide 230 × 305 mm, el tamaño de un álbum de partituras. Una página impar presenta las piezas a dos columnas de Libre Caslon bajo un título de 52 pt en Bodoni. Después cada minueto empieza en una página par y ocupa la doble página en un doble pentagrama de 7 mm, grabado en Bravura desde un archivo MusicXML: cuatro compases por sistema, los compases 1 a 16 a la izquierda y del 17 al 32 a la derecha, así que el pianista no pasa página dentro de una pieza.

Esta receta responde a

  • ¿Cómo compongo una partitura de piano de un archivo MusicXML a lo largo de varias páginas?

La respuesta corta

script.js · líneas 36–61en el código completo
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);
}

Ingredientes

Tipografía
Libre Caslon Text, Bodoni Moda, Archivo Narrow (SIL OFL 1.1)
Recursos
  • bwv-anh-114.musicxml
  • bwv-anh-115.musicxml
  • Minueto en sol mayor, BWV Anh. 114: las notas de la edición de la Bach-Gesellschaft tal como se introdujeron para el Mutopia Project (Mutopia-2017/01/19-75), convertidas a MusicXML 4.0 y revisadas compás a compás para esta edición (Christian Petzold; Allen Garvin (Mutopia Project), dominio público)
  • Minueto en sol menor, BWV Anh. 115: las notas de la edición de la Bach-Gesellschaft tal como se introdujeron para el Mutopia Project (Mutopia-2015/08/21-76), convertidas a MusicXML 4.0 y revisadas compás a compás para esta edición (Christian Petzold; Allen Garvin (Mutopia Project), dominio público)

Elaboración

#1 · Trae la pieza en MusicXML

Los dos archivos de assets/ son MusicXML 4.0 sin más, el formato que escriben Dorico, Sibelius, Finale y MuseScore al exportar una partitura (elige el .musicxml sin comprimir, no el .mxl). Contienen una parte en dos pentagramas, con las claves, la armadura, las barras, la dirección de las plicas en los compases a dos voces, los adornos y las barras de repetición. El nombre de la parte lleva print-object="no": una etiqueta «Piano» impresa empujaría todos los sistemas a la derecha. El título de <work-title> no se imprime, solo sirve de descripción de la partitura, así que el Markdown da a cada pieza su propio título.

#2 · Componla en un bloque de código, no como recurso

La respuesta corta descarga cada archivo y lo escribe en un bloque ```musicxml, donde el Markdown deja una línea de marcador. Un bloque se graba al ancho de la columna y se corta en sistemas, y cada sistema se coloca como una línea de párrafo, de modo que la pieza sigue en la página siguiente entre dos sistemas. Un recurso score se mantiene entero y no podría cruzar una página. minSystemsAtBreak: 2 evita que un sistema quede solo al pie de una página o pase solo a la cabeza de la siguiente.

#3 · Ajusta el pentagrama a quien toca

El pentagrama mide 7 mm de alto, lo que mide en un álbum de piano impreso que se lee desde el atril. spacing: 1.7 separa las notas hasta que un sistema de 186 mm de medida contiene cuatro compases, así que cada mitad de un minueto ocupa cuatro sistemas. systemGap: 14 mm deja sitio a las líneas adicionales entre las dos manos, y con él caben cuatro sistemas bajo el título de la primera página y cuatro en la de enfrente: la doble barra cae en el cambio de página del pliego.

#4 · Abre cada pieza en página par

script.js · líneas 65–103en el código completo
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' } empieza cada pieza en una página de la izquierda, y la portada del editor usa un estilo de título que abre en página impar y sin cabeceras. El número de catálogo y el compositor salen de los atributos del título y van en las esquinas inferiores de la apertura, donde los imprimen las ediciones grabadas.

#5 · Pon el nombre de la pieza en la cabecera de la derecha

script.js · líneas 107–120en el código completo
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 de una pieza ya lleva su título, así que las cabeceras quedan para las páginas siguientes (pages: 'body'). La página impar imprime el título de la pieza y el folio en el borde exterior, con la letra de rótulos a 8 pt.

La receta 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

El script.js compuesto funciona tal cual: pégalo como script de módulo en cualquier página o abre la receta en CodePen. Carpeta de la receta en GitHub ↗ (se abre en una nueva pestaña)

Variantes

#Haz una edición de estudio con un pentagrama menor

Un pentagrama de 6 mm con el espaciado propio del grabador lleva siete u ocho compases por sistema, así que cada minueto cabe en su primera página.

-  staffSize: mm(7),
+  staffSize: mm(6),
-  spacing: 1.7,
+  spacing: 1,

#Numera los ejemplos breves

Cuando la música son ejemplos breves citados en el texto, encaja mejor un recurso score numerado: corales a cuatro voces como ejemplos numerados.

Errores frecuentes

  • Los saltos de sistema escritos en el archivo (<print new-system="yes">) no se conservan: los sistemas llenan el ancho de la columna, así que ajusta spacing y staffSize hasta que lleven los compases que quieres.
  • El grabador no reparte los compases entre sistemas, y el último sistema se compone a su ancho natural: a 6 mm y con spacing: 1, el compás 32 queda solo en un sistema. Cambia spacing hasta que el último sistema tenga más de un compás.
  • Un .mxl comprimido es un archivo zip: descomprímelo, o exporta el formato sin comprimir, antes de descargarlo.

Créditos

Texto
  • La introducción y las notas de interpretación, escritas para esta edición · Ignacio Ferro · CC BY 4.0
Imágenes
Fuentes
Libre Caslon Text (SIL OFL 1.1) · Bodoni Moda (SIL OFL 1.1) · Archivo Narrow (SIL OFL 1.1)
SandboxPDF