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
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
- Funciones
- Notación musicalEstilo de partituraAperturas diseñadasAtributos de títuloEstilos de títuloCabeceras y foliosMárgenes simétricosCabeceras según el tipo de páginaColumnas en un recuadro o en el textoEstilos de párrafoPaleta de color semánticaExportación a PDF
- Tipografía
- Libre Caslon Text, Bodoni Moda, Archivo Narrow (SIL OFL 1.1)
- Recursos
bwv-anh-114.musicxmlbwv-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
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
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
// ═══ 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 ajustaspacingystaffSizehasta 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. Cambiaspacinghasta que el último sistema tenga más de un compás. - Un
.mxlcomprimido es un archivo zip: descomprímelo, o exporta el formato sin comprimir, antes de descargarlo.
Créditos
- Receta
- Ignacio Ferro
- Texto
- La introducción y las notas de interpretación, escritas para esta edición · Ignacio Ferro · CC BY 4.0
- Imágenes
- 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
- Fuentes
- Libre Caslon Text (SIL OFL 1.1) · Bodoni Moda (SIL OFL 1.1) · Archivo Narrow (SIL OFL 1.1)
- Código
- MIT, como Postext
Editar este texto ↗ (se abre en una nueva pestaña)Carpeta de la receta en GitHub ↗ (se abre en una nueva pestaña)


