Aller au contenu principal
Recette numéro 167

Recettes · Chapitre 7 · Figures et images

Une partition de piano depuis MusicXML sur plusieurs pages

Deux menuets pour clavier gravés depuis des fichiers MusicXML sur une double portée, coupés entre les systèmes pour que chaque pièce tienne sur une double page.

Sur cette page
Sortie
Canvas · PDF
Postext
Testé avec Postext 1.54.0
Nécessite ≥ 1.54.0 · postext-pdf ≥ 1.54.0
Licence
Mis à jour le 22 oct. 2026
Code MIT · Texte CC BY 4.0

p. 2–3 sur 5

  • Exemple en anglais : pas encore d’édition française
  • Format 230 × 305 mm
  • 1 colonne
  • Libre Caslon Text 10/14,5
  • Archivo Narrow
  • Bodoni Moda
  • 5 pages
  • Niveau
  • Postext 1.54.0
  • Mis en page en 511 ms
  • 165 lignes de code

En bref

Une petite édition de deux menuets pour piano très connus. Elle montre comment prendre un fichier exporté d’un logiciel de notation et le composer en partition qui continue d’une page à l’autre, prête à être jouée.

Ce que vous allez composer

Une édition pour interprètes des deux menuets du Petit livre d’Anna Magdalena Bach de 1725 : celui en sol majeur, que tout élève pianiste apprend, et son pendant en sol mineur, attribués aujourd’hui à Christian Petzold. La page mesure 230 × 305 mm, le format d’un album de partitions. Une belle page présente les pièces sur deux colonnes de Libre Caslon sous un titre de 52 pt en Bodoni. Puis chaque menuet commence sur une fausse page et occupe la double page sur une double portée de 7 mm, gravée en Bravura depuis un fichier MusicXML : quatre mesures par système, les mesures 1 à 16 à gauche et 17 à 32 à droite, si bien que le pianiste ne tourne aucune page au milieu d’une pièce.

Cette recette répond à

  • Comment composer une partition de piano issue d'un fichier MusicXML sur plusieurs pages ?

La réponse courte

script.js · lignes 36–61dans le code 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);
}

Ingrédients

Typographie
Libre Caslon Text, Bodoni Moda, Archivo Narrow (SIL OFL 1.1)
Ressources
  • 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), domaine public)
  • 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), domaine public)

Préparation

#1 · Apporter la pièce en MusicXML

Les deux fichiers d’assets/ sont du MusicXML 4.0 ordinaire, le format qu’écrivent Dorico, Sibelius, Finale et MuseScore à l’export d’une partition (choisissez le .musicxml non compressé, pas le .mxl). Ils contiennent une partie sur deux portées, avec les clés, l’armure, les ligatures, la direction des hampes dans les mesures à deux voix, les ornements et les barres de reprise. Le nom de la partie porte print-object="no" : une étiquette « Piano » imprimée décalerait chaque système vers la droite. Le titre de <work-title> n’est pas imprimé, il sert seulement de description de la partition, si bien que le Markdown donne à chaque pièce son propre titre.

#2 · La composer dans un bloc de code, pas comme ressource

La réponse courte télécharge chaque fichier et l’écrit dans un bloc ```musicxml, où le Markdown garde une ligne de repère. Un bloc est gravé à la largeur de la colonne et coupé en systèmes, et chaque système se place comme une ligne de paragraphe : la pièce continue sur la page suivante entre deux systèmes. Une ressource score reste d’un seul tenant et ne pourrait pas passer d’une page à l’autre. minSystemsAtBreak: 2 empêche qu’un système reste seul au pied d’une page ou passe seul en tête de la suivante.

#3 · Dimensionner la portée pour l’interprète

La portée mesure 7 mm de haut, la taille d’un album de piano imprimé qu’on lit sur le pupitre. spacing: 1.7 écarte les notes jusqu’à ce qu’un système de 186 mm de justification contienne quatre mesures, et chaque moitié d’un menuet prend ainsi quatre systèmes. systemGap: 14 mm laisse la place aux lignes supplémentaires entre les deux mains ; avec cet écart, quatre systèmes tiennent sous le titre de la première page et quatre sur la page en regard : la double barre tombe au passage d’une page à l’autre de la double page.

#4 · Ouvrir chaque pièce sur une fausse page

script.js · lignes 65–103dans le code 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' } fait commencer chaque pièce sur une page de gauche, et la page de titre de l’éditeur prend un style de titre qui ouvre sur une belle page, sans titres courants. Le numéro de catalogue et le compositeur viennent des attributs du titre et se placent aux coins inférieurs de l’ouverture, là où les éditions gravées les impriment.

#5 · Nommer la pièce en tête de la page de droite

script.js · lignes 107–120dans le code 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 première page d’une pièce porte déjà son titre ; les titres courants sont donc réservés aux pages suivantes (pages: 'body'). La belle page imprime le titre de la pièce et le folio au bord extérieur, dans le caractère des étiquettes à 8 pt.

La recette complète

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

Le script.js composé s’exécute tel quel : collez-le dans le script de module de n’importe quelle page, ou ouvrez la recette sur CodePen. Dossier de la recette sur GitHub ↗ (s’ouvre dans un nouvel onglet)

Variantes

#Faire une édition d’étude sur une portée plus petite

Une portée de 6 mm avec l’espacement propre au graveur porte sept ou huit mesures par système, et chaque menuet tient alors sur sa première page.

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

#Numéroter les exemples courts

Quand la musique se compose d’exemples courts cités dans le texte, une ressource score numérotée convient mieux : des chorals à quatre voix en exemples numérotés.

Erreurs fréquentes

  • Les sauts de système écrits dans le fichier (<print new-system="yes">) ne sont pas conservés : les systèmes remplissent la largeur de la colonne, réglez donc spacing et staffSize jusqu’à obtenir le nombre de mesures voulu.
  • Le graveur ne répartit pas les mesures entre les systèmes, et le dernier système est composé à sa largeur naturelle : à 6 mm et avec spacing: 1, la mesure 32 reste seule sur un système. Changez spacing jusqu’à ce que le dernier système porte plus d’une mesure.
  • Un .mxl compressé est une archive zip : décompressez-le, ou exportez le format non compressé, avant de le télécharger.

Crédits

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