Kurz gesagt
Eine kleine Notenausgabe mit zwei bekannten Klaviermenuetten. Sie zeigt, wie eine aus einem Notensatzprogramm exportierte Datei als Klaviernoten gesetzt wird, die über die Seiten weiterlaufen und spielbar sind.
Was Sie setzen
Eine Spielausgabe der beiden Menuette aus dem Notenbüchlein für Anna Magdalena Bach von 1725: das G-Dur-Menuett, das jeder Klavierschüler lernt, und sein Gegenstück in g-Moll, heute Christian Petzold zugeschrieben. Die Seite misst 230 × 305 mm, das Format eines Notenalbums. Eine rechte Seite stellt die Stücke in zwei Spalten Libre Caslon unter einem 52-pt-Titel in Bodoni vor. Danach beginnt jedes Menuett auf einer linken Seite und läuft über die Doppelseite, in einem Klaviersystem mit 7 mm hohen Notenzeilen, aus einer MusicXML-Datei in Bravura gestochen: vier Takte je System, Takt 1–16 links und 17–32 rechts, sodass innerhalb eines Stücks nicht umgeblättert wird.
Dieses Rezept beantwortet
- Wie setze ich einen Klaviersatz aus einer MusicXML-Datei über mehrere Seiten?
Die kurze Antwort
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);
}
Zutaten
- Funktionen
- NotensatzNotenstilGestaltete AuftakteÜberschriftenattributeÜberschriftenformateKolumnentitel und SeitenzahlenGespiegelte RänderKopfzeilen nach SeitenrolleSpalten in einem Kasten oder im TextAbsatzformateSemantische FarbpalettePDF-Export
- Nutzt außerdem
- Code-ListingsIm PDF eingebettete SchriftenKolumnentitel je AbschnittKapitel ohne Nummer
- Schrift
- Libre Caslon Text, Bodoni Moda, Archivo Narrow (SIL OFL 1.1)
- Dateien
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), gemeinfrei)
- 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), gemeinfrei)
Zubereitung
#1 · Das Stück als MusicXML holen
Die beiden Dateien in assets/ sind gewöhnliches MusicXML 4.0, das Format, das Dorico, Sibelius, Finale und MuseScore beim Export schreiben (wählen Sie das unkomprimierte .musicxml, nicht .mxl). Sie enthalten eine Stimme auf zwei Notenzeilen, mit Schlüsseln, Vorzeichen, Balken, der Halsrichtung in den zweistimmigen Takten, Verzierungen und Wiederholungszeichen. Der Stimmenname trägt print-object="no": ein gedrucktes Etikett „Piano“ würde jedes System nach rechts schieben. Der Titel in <work-title> wird nicht gedruckt, er dient nur als Beschreibung der Noten; darum gibt das Markdown jedem Stück eine eigene Überschrift.
#2 · In einen Codeblock setzen, nicht als Ressource
Die kurze Antwort lädt jede Datei und schreibt sie in einen ```musicxml-Block, in dem das Markdown eine Platzhalterzeile hält. Ein Block wird in der Breite der Spalte gestochen und in Systeme geteilt, und jedes System wird wie eine Absatzzeile platziert: Das Stück läuft zwischen zwei Systemen auf der nächsten Seite weiter. Eine score-Ressource bleibt ganz und könnte keine Seite überqueren. minSystemsAtBreak: 2 verhindert, dass ein System allein am Fuß einer Seite stehen bleibt oder allein an den Kopf der nächsten wandert.
#3 · Die Notenzeile für den Spieler bemessen
Die Notenzeile ist 7 mm hoch, so groß wie in einem gedruckten Klavieralbum, das vom Notenpult gelesen wird. spacing: 1.7 zieht die Noten auseinander, bis ein System auf 186 mm Satzbreite vier Takte enthält; jede Hälfte eines Menuetts braucht damit vier Systeme. systemGap: 14 mm lässt Platz für die Hilfslinien zwischen den Händen, und mit diesem Abstand passen vier Systeme unter den Titel der ersten Seite und vier auf die gegenüberliegende: Der Doppelstrich fällt auf den Seitenwechsel der Doppelseite.
#4 · Jedes Stück auf einer linken Seite eröffnen
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' } lässt jedes Stück auf einer linken Seite beginnen, und die Titelseite des Herausgebers nutzt einen Überschriftenstil, der auf einer rechten Seite ohne Kolumnentitel öffnet. Werkverzeichnisnummer und Komponist kommen aus den Attributen der Überschrift und stehen in den unteren Ecken des Auftakts, wo gestochene Ausgaben sie drucken.
#5 · Das Stück im Kolumnentitel der rechten Seite nennen
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: [] };
Die erste Seite eines Stücks trägt schon seinen Titel, also bleiben die Kolumnentitel den folgenden Seiten vorbehalten (pages: 'body'). Die rechte Seite druckt den Titel des Stücks und die Seitenzahl am Außenrand, in der Auszeichnungsschrift zu 8 pt.
Das ganze Rezept
// ═══ 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
Das zusammengesetzte script.js läuft unverändert: Fügen Sie es in das Modulskript einer beliebigen Seite ein, oder öffnen Sie das Rezept auf CodePen. Ordner des Rezepts auf GitHub ↗ (öffnet sich in einem neuen Tab)
Varianten
#Eine Studienausgabe mit kleinerer Notenzeile setzen
Eine 6-mm-Notenzeile mit dem eigenen Abstand des Notenstechers fasst sieben oder acht Takte je System, sodass jedes Menuett auf seine erste Seite passt.
- staffSize: mm(7),
+ staffSize: mm(6),
- spacing: 1.7,
+ spacing: 1,#Kurze Beispiele nummerieren
Besteht die Musik aus kurzen Beispielen, die der Text zitiert, passt eine nummerierte score-Ressource besser: vierstimmige Choräle als nummerierte Notenbeispiele.
Häufige Fehler
- Systemumbrüche, die in der Datei stehen (
<print new-system="yes">), bleiben nicht erhalten: Die Systeme füllen die Spaltenbreite, also stellen SiespacingundstaffSizeein, bis sie die gewünschten Takte enthalten. - Der Notenstecher verteilt die Takte nicht gleichmäßig auf die Systeme, und das letzte System wird in seiner natürlichen Breite gesetzt: Bei 6 mm und
spacing: 1steht Takt 32 allein in einem System. Ändern Siespacing, bis das letzte System mehr als einen Takt enthält. - Eine komprimierte
.mxl-Datei ist ein Zip-Archiv: Entpacken Sie sie oder exportieren Sie das unkomprimierte Format, bevor Sie sie laden.
Nachweise
- Rezept
- Ignacio Ferro
- Text
- The introduction and performance notes, written for this edition · Ignacio Ferro · CC BY 4.0
- Bilder
- 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) · gemeinfrei
- 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) · gemeinfrei
- Schriften
- Libre Caslon Text (SIL OFL 1.1) · Bodoni Moda (SIL OFL 1.1) · Archivo Narrow (SIL OFL 1.1)
- Code
- MIT, wie Postext
Diesen Text bearbeiten ↗ (öffnet sich in einem neuen Tab)Ordner des Rezepts auf GitHub ↗ (öffnet sich in einem neuen Tab)


