Pular para o conteúdo principal
Receita número 167

Receitas · Capítulo 7 · Figuras e imagens

Uma partitura de piano de MusicXML em várias páginas

Dois minuetos para teclado gravados a partir de arquivos MusicXML em pauta dupla, divididos entre sistemas para que cada peça ocupe uma página dupla.

Nesta página
Saída
Canvas · PDF
Postext
Testada com o Postext 1.54.0
Requer ≥ 1.54.0 · postext-pdf ≥ 1.54.0
Licença
Atualizada em 22 de out. de 2026
Código MIT · Texto CC BY 4.0
  • Amostra em inglês: ainda sem edição em português
  • Refile 230 × 305 mm
  • 1 coluna
  • Libre Caslon Text 10/14,5
  • Archivo Narrow
  • Bodoni Moda
  • 5 páginas
  • Nível
  • Postext 1.54.0
  • Diagramado em 511 ms
  • 165 linhas de código

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

script.js · linhas 36–61no 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

Tipografia
Libre Caslon Text, Bodoni Moda, Archivo Narrow (SIL OFL 1.1)
Materiais
  • 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), 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

script.js · linhas 65–103no 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' } 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

script.js · linhas 107–120no 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: [] };

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

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 = '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 ajuste spacing e staffSize até 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. Mude spacing até que o último sistema tenha mais de um compasso.
  • Um .mxl comprimido é um arquivo zip: descompacte-o, ou exporte o formato sem compressão, antes de baixá-lo.

Créditos

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