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
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
- Fonctionnalités
- Notation musicaleStyle de partitionOuvertures composéesAttributs de titreStyles de titreTitres courants et foliosMarges en miroirTitres courants selon le rôle de la pageColonnes dans un encadré ou dans le texteStyles de paragraphePalette de couleurs sémantiqueExport PDF
- Utilise aussi
- Listings de codePolices intégrées au PDFTitres courants par sectionChapitres non numérotés
- Typographie
- Libre Caslon Text, Bodoni Moda, Archivo Narrow (SIL OFL 1.1)
- Ressources
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), 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
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
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
// ═══ 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 doncspacingetstaffSizejusqu’à 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. Changezspacingjusqu’à ce que le dernier système porte plus d’une mesure. - Un
.mxlcompressé est une archive zip : décompressez-le, ou exportez le format non compressé, avant de le télécharger.
Crédits
- Recette
- Ignacio Ferro
- Texte
- The introduction and performance notes, written for this edition · Ignacio Ferro · CC BY 4.0
- Images
- 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
- Polices
- Libre Caslon Text (SIL OFL 1.1) · Bodoni Moda (SIL OFL 1.1) · Archivo Narrow (SIL OFL 1.1)
Modifier ce texte ↗ (s’ouvre dans un nouvel onglet)Dossier de la recette sur GitHub ↗ (s’ouvre dans un nouvel onglet)


