本文へスキップ
レシピ番号167

レシピ集 · 第7章 · 図と画像

MusicXMLから数ページにわたるピアノ譜を組む

2曲の鍵盤用メヌエットをMusicXMLファイルから大譜表に刻譜し、段と段のあいだで改ページして、1曲が見開きに収まるようにします。

このページの内容
出力
Canvas · PDF
レベル
中級
Postext
Postext 1.54.0で検証済み
1.54.0以上が必要 · postext-pdf ≥ 1.54.0
ライセンス
2026年10月22日更新
コードMIT · 本文CC BY 4.0

2〜3ページ(全5ページ)

  • 英語の見本:日本語版はまだありません
  • 仕上がり230 × 305 mm
  • 1段
  • Libre Caslon Text 10/14.5
  • Archivo Narrow
  • Bodoni Moda
  • 5ページ
  • レベル
  • Postext 1.54.0
  • 組版時間511 ms
  • コード165行

かんたんな説明

よく知られたピアノのメヌエット2曲を収めた小さな楽譜です。記譜ソフトから書き出したファイルを、ページをまたいで続き、そのまま弾けるピアノ譜に組む方法を示します。

できあがり

1725年の『アンナ・マグダレーナ・バッハの音楽帳』にある2曲のメヌエットの演奏用の版です。ピアノを習う人ならだれもが弾くト長調の曲と、対になるト短調の曲で、いまはクリスティアン・ペツォールトの作とされています。判型は230 × 305 mmで、楽譜集の大きさです。最初の奇数ページで、52ポイントのBodoniの題の下に、Libre Caslonの2段組みで曲を紹介します。続いて各メヌエットは偶数ページから始まり、見開きいっぱいに7 mmの大譜表で、MusicXMLファイルからBravuraで刻譜されます。1段に4小節、第1〜16小節が左ページ、第17〜32小節が右ページなので、1曲の途中で譜めくりはありません。

このレシピが答える質問

  • MusicXMLファイルのピアノ譜を数ページにわたって組むには?

手短な答え

script.js · 36〜61行コード全体で見る
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);
}

材料

種類
Libre Caslon Text, Bodoni Moda, Archivo Narrow (SIL OFL 1.1)
素材
  • 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), パブリックドメイン)
  • 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), パブリックドメイン)

作り方

#1 · 曲をMusicXMLで持ち込む

assets/の2つのファイルはふつうのMusicXML 4.0で、Dorico、Sibelius、Finale、MuseScoreが楽譜を書き出すときの形式です(.mxlではなく、圧縮しない.musicxmlを選びます)。2つの譜表に1つのパートがあり、音部記号、調号、連桁、2声の小節の符幹の向き、装飾音、反復記号を含みます。パート名にはprint-object="no"を付けています。「Piano」というラベルが印刷されると、すべての段が右へずれるためです。<work-title>の題は印刷されず、楽譜の説明として読まれるだけなので、Markdownで曲ごとに見出しを付けます。

#2 · リソースではなくコードブロックに入れる

手短な答えは各ファイルを取得し、Markdownに仮の1行を置いた```musicxmlブロックに書き込みます。ブロックは段の幅で刻譜されて段ごとに分かれ、各段が段落の1行のように配置されるので、曲は段と段のあいだで次のページへ続きます。scoreリソースはひとまとまりのまま置かれ、ページをまたげません。minSystemsAtBreak: 2により、ページの下端に1段だけが残ったり、次のページの頭に1段だけが送られたりしません。

#3 · 弾く人に合わせて譜表の大きさを決める

譜表の高さは7 mmで、譜面台に置いて読む印刷されたピアノ曲集の大きさです。spacing: 1.7で音符の間隔を広げ、186 mmの行長の1段に4小節が入るようにしたので、メヌエットの前半と後半がそれぞれ4段になります。systemGap: 14 mmは両手のあいだの加線の場所を取り、この間隔なら1ページ目の題の下に4段、向かいのページに4段が入ります。複縦線が見開きの折り目にちょうど来ます。

#4 · どの曲も偶数ページから始める

script.js · 65〜103行コード全体で見る
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' }で各曲を左ページから始めます。編者による扉は別の見出しスタイルを使い、柱を付けずに奇数ページから始まります。作品番号と作曲者は見出しの属性から取り、刻譜された版と同じく、曲の始まりの下の両隅に置きます。

#5 · 右ページの柱に曲名を入れる

script.js · 107〜120行コード全体で見る
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: [] };

曲の最初のページにはすでに題があるので、柱はその後のページだけに付けます(pages: 'body')。奇数ページは小口側に曲名とノンブルを、8ポイントのラベル書体で印刷します。

レシピの全体

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

組み立てたscript.jsはそのまま動きます。任意のページのモジュールスクリプトに貼り付けるか、レシピをCodePenで開いてください。 GitHub上のレシピのフォルダー ↗ (新しいタブで開きます)

アレンジ

#小さめの譜表で学習用の版にする

6 mmの譜表と刻譜エンジン本来の間隔なら1段に7〜8小節が入り、各メヌエットが最初のページに収まります。

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

#短い譜例には番号を付ける

本文で引用する短い譜例なら、番号付きのscoreリソースが向いています:4声のコラールを番号付きの譜例にする。

よくあるつまずき

  • ファイルに書かれた段の区切り(<print new-system="yes">)は保たれません。段は段の幅いっぱいに組まれるので、spacingとstaffSizeを調整して、望む小節数が入るようにします。
  • 刻譜エンジンは段ごとの小節数をならさず、最後の段は本来の幅で組みます。6 mm、spacing: 1では第32小節だけで1段になります。最後の段に2小節以上が入るまでspacingを変えます。
  • 圧縮された.mxlはzipアーカイブです。取得する前に展開するか、圧縮しない形式で書き出します。

クレジット

レシピ
Ignacio Ferro
本文
  • The introduction and performance notes, written for this edition · Ignacio Ferro · CC BY 4.0
画像
フォント
Libre Caslon Text (SIL OFL 1.1) · Bodoni Moda (SIL OFL 1.1) · Archivo Narrow (SIL OFL 1.1)
SandboxPDF