Em poucas palavras
Uma pequena edição de dois minuetos de piano muito conhecidos. Mostra como pegar um arquivo exportado de um programa de notação e compô-lo como uma partitura que continua de uma página para outra, pronta para tocar.
O que você vai compor
Uma edição para intérpretes dos dois minuetos do Caderno de Anna Magdalena Bach de 1725: o de sol maior, que todo aluno de piano aprende, e seu par em sol menor, hoje atribuídos a Christian Petzold. A página mede 230 × 305 mm, o tamanho de um álbum de partituras. Uma página ímpar apresenta as peças em duas colunas de Libre Caslon sob um título de 52 pt em Bodoni. Depois cada minueto começa numa página par e ocupa a página dupla numa pauta dupla de 7 mm, gravada em Bravura a partir de um arquivo MusicXML: quatro compassos por sistema, os compassos 1 a 16 à esquerda e 17 a 32 à direita, de modo que o pianista não vira a página no meio de uma peça.
Esta receita responde a
- Como faço uma partitura de piano de um arquivo MusicXML ao longo de várias páginas?
A 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);
}
Ingredientes
- Funcionalidades
- Notação musicalEstilo de partituraAberturas desenhadasAtributos de títuloEstilos de títuloCabeços e fóliosMargens espelhadasCabeços por tipo de páginaColunas num boxe ou no textoEstilos de parágrafoPaleta de cores semânticaExportação para PDF
- Tipografia
- Libre Caslon Text, Bodoni Moda, Archivo Narrow (SIL OFL 1.1)
- Materiais
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), domínio público)
- 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), domínio público)
Preparo
#1 · Trazer a peça em MusicXML
Os dois arquivos em assets/ são MusicXML 4.0 comum, o formato que Dorico, Sibelius, Finale e MuseScore gravam ao exportar uma partitura (escolha o .musicxml sem compressão, não o .mxl). Eles têm uma parte em duas pautas, com as claves, a armadura, as barras de colcheia, a direção das hastes nos compassos a duas vozes, os ornamentos e as barras de repetição. O nome da parte leva print-object="no": um rótulo «Piano» impresso empurraria cada sistema para a direita. O título em <work-title> não é impresso, serve só de descrição da partitura, então o Markdown dá a cada peça um título próprio.
#2 · Compor num bloco de código, não como recurso
A resposta curta baixa cada arquivo e o escreve num bloco ```musicxml, onde o Markdown guarda uma linha de marcador. Um bloco é gravado na largura da coluna e cortado em sistemas, e cada sistema é colocado como uma linha de parágrafo: a peça continua na página seguinte entre dois sistemas. Um recurso score fica inteiro e não poderia atravessar uma página. minSystemsAtBreak: 2 impede que um sistema fique sozinho no pé de uma página ou passe sozinho para o alto da seguinte.
#3 · Dimensionar a pauta para quem toca
A pauta tem 7 mm de altura, o tamanho de um álbum de piano impresso lido na estante. spacing: 1.7 afasta as notas até que um sistema de 186 mm de medida tenha quatro compassos, e assim cada metade de um minueto ocupa quatro sistemas. systemGap: 14 mm deixa espaço para as linhas suplementares entre as mãos, e com ele cabem quatro sistemas sob o título da primeira página e quatro na página em frente: a barra dupla cai na passagem de uma página à outra da página dupla.
#4 · Abrir cada peça numa 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' } faz cada peça começar numa página da esquerda, e a página de rosto do editor usa um estilo de título que abre numa página ímpar, sem cabeços. O número de catálogo e o compositor vêm dos atributos do título e ficam nos cantos inferiores da abertura, onde as edições gravadas os imprimem.
#5 · Pôr o nome da peça no cabeço da direita
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: [] };
A primeira página de uma peça já leva o seu título, então os cabeços ficam para as páginas seguintes (pages: 'body'). A página ímpar imprime o título da peça e o fólio na borda externa, na fonte de rótulos a 8 pt.
A receita 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 = 'en'; // @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
O script.js montado funciona como está: cole-o como script de módulo em qualquer página ou abra a receita no CodePen. Pasta da receita no GitHub ↗ (abre em uma nova aba)
Variações
#Fazer uma edição de estudo com uma pauta menor
Uma pauta de 6 mm com o espaçamento próprio do gravador leva sete ou oito compassos por sistema, e cada minueto cabe na sua primeira página.
- staffSize: mm(7),
+ staffSize: mm(6),
- spacing: 1.7,
+ spacing: 1,#Numerar os exemplos curtos
Quando a música são exemplos curtos citados no texto, um recurso score numerado serve melhor: corais a quatro vozes como exemplos numerados.
Erros comuns
- As quebras de sistema escritas no arquivo (
<print new-system="yes">) não são mantidas: os sistemas preenchem a largura da coluna, então ajustespacingestaffSizeaté que tenham os compassos que você quer. - O gravador não distribui os compassos entre os sistemas, e o último sistema é composto na sua largura natural: com 6 mm e
spacing: 1, o compasso 32 fica sozinho num sistema. Mudespacingaté que o último sistema tenha mais de um compasso. - Um
.mxlcomprimido é um arquivo zip: descompacte-o, ou exporte o formato sem compressão, antes de baixá-lo.
Créditos
- Receita
- Ignacio Ferro
- Texto
- The introduction and performance notes, written for this edition · Ignacio Ferro · CC BY 4.0
- Imagens
- 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) · domínio público
- 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) · domínio público
- Fontes
- Libre Caslon Text (SIL OFL 1.1) · Bodoni Moda (SIL OFL 1.1) · Archivo Narrow (SIL OFL 1.1)
- Código
- MIT, como o Postext
Editar este texto ↗ (abre em uma nova aba)Pasta da receita no GitHub ↗ (abre em uma nova aba)


