انتقل إلى المحتوى الرئيسي
الوصفة رقم 167

دليل الوصفات · الفصل 7 · الأشكال والصور

مدوّنة بيانو من ملف MusicXML على عدة صفحات

مينويتان للآلة ذات المفاتيح تُحفران من ملفات MusicXML على مدرج مزدوج، وتنقسمان بين الأسطر الموسيقية فتشغل كل قطعة صفحتين متقابلتين.

في هذه الصفحة
النوع
أي نوع
المُخرَج
Canvas · PDF
المستوى
متوسط
Postext
اختُبرت مع Postext 1.54.0
تتطلب ≥ 1.54.0 · postext-pdf ≥ 1.54.0
الترخيص
حُدّثت في 22 أكتوبر 2026
الشيفرة MIT · النص CC BY 4.0
  • نموذج باللغة الإنجليزية: لا توجد طبعة عربية بعد
  • مقاس القص 230 × 305 مم
  • عمود واحد
  • Libre Caslon Text 10/14.5
  • Archivo Narrow
  • Bodoni Moda
  • 5 صفحات
  • المستوى
  • Postext 1.54.0
  • أُخرجت في 511 ms
  • أسطر الشيفرة: 165

باختصار

طبعة صغيرة لقطعتي مينويت معروفتين للبيانو. تبيّن كيف تأخذ ملفًا صدّرته برامج التدوين الموسيقي وتنضده مدوّنةً للبيانو تمتد من صفحة إلى أخرى ويُعزف منها مباشرة.

ما الذي ستنضده

طبعة للعازفين من قطعتي المينويت في دفتر آنا ماغدالينا باخ لعام 1725: قطعة صول الكبير التي يتعلمها كل دارس للبيانو، وقرينتها في صول الصغير، وتُنسبان اليوم إلى كريستيان بيتسولد. مقاس الصفحة 230 × 305 مم، وهو مقاس ألبومات النوتة. تقدّم صفحة فردية القطعتين في عمودين من Libre Caslon تحت عنوان بخط Bodoni مقاسه 52 نقطة. ثم تبدأ كل مينويت على صفحة زوجية وتمتد على الصفحتين المتقابلتين في مدرج مزدوج ارتفاعه 7 مم، محفورًا بخط Bravura من ملف MusicXML: أربع موازير في كل سطر موسيقي، والموازير 1–16 على اليسار و17–32 على اليمين، فلا يقلب العازف صفحة في أثناء القطعة.

تجيب هذه الوصفة عن

  • كيف أُخرج مدوّنة بيانو من ملف 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/ هما MusicXML 4.0 عادي، الصيغة التي تكتبها Dorico وSibelius وFinale وMuseScore عند تصدير المدوّنة (اختر .musicxml غير المضغوط لا .mxl). فيهما جزء واحد على مدرجين، بالمفاتيح والدليل والعوارض واتجاه السيقان في الموازير ذات الصوتين والزخارف وعلامات الإعادة. يحمل اسم الجزء print-object="no": فلو طُبعت كلمة «Piano» لدفعت كل سطر موسيقي إلى اليمين. والعنوان في <work-title> لا يُطبع، وإنما يُقرأ وصفًا للمدوّنة، ولذلك يعطي Markdown كل قطعة عنوانًا خاصًا بها.

#2 · ضعها في كتلة شيفرة لا في مورد

يجلب الجواب المختصر كل ملف ويكتبه في كتلة ```musicxml يترك فيها Markdown سطرًا محجوزًا. تُحفر الكتلة بعرض العمود وتُقطّع أسطرًا موسيقية، ويوضع كل سطر كما يوضع سطر الفقرة، فتتابع القطعة على الصفحة التالية بين سطرين. أما مورد score فيبقى كتلة واحدة ولا يعبر الصفحة. ويمنع minSystemsAtBreak: 2 أن يبقى سطر موسيقي وحيدًا في أسفل الصفحة أو ينتقل وحده إلى رأس التالية.

#3 · قدّر حجم المدرج للعازف

ارتفاع المدرج 7 مم، وهو حجمه في ألبوم بيانو مطبوع يُقرأ على حامل النوتة. يباعد spacing: 1.7 بين النغمات حتى يحمل السطر الموسيقي بعرض 186 مم أربع موازير، فيشغل كل نصف من المينويت أربعة أسطر. ويترك systemGap: 14 mm مكانًا للخطوط الإضافية بين اليدين، وبهذه المسافة تتسع الصفحة الأولى لأربعة أسطر تحت العنوان والصفحة المقابلة لأربعة: فيقع الخط المزدوج عند الانتقال بين الصفحتين المتقابلتين.

#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 المجمّع كما هو: الصقه في سكربت الوحدة (module) لأي صفحة، أو افتح الوصفة على CodePen. مجلد الوصفة على GitHub ↗ (يفتح في تبويب جديد)

تنويعات

#اصنع طبعة دراسية بمدرج أصغر

مدرج بارتفاع 6 مم مع تباعد محرك الحفر نفسه يتسع لسبع موازير أو ثمانٍ في السطر، فتتسع كل مينويت لصفحتها الأولى.

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

#رقّم الأمثلة القصيرة

إذا كانت الموسيقى أمثلة قصيرة يذكرها النص، فالأنسب مورد score مرقّم: كورالات رباعية الأصوات أمثلةً مرقّمة.

أخطاء شائعة

  • لا تُحفظ فواصل الأسطر المكتوبة في الملف (<print new-system="yes">): تملأ الأسطر عرض العمود، فاضبط spacing وstaffSize حتى تحمل الموازير التي تريدها.
  • لا يوزّع محرك الحفر الموازير بالتساوي على الأسطر، ويُنضد السطر الأخير بعرضه الطبيعي: بمدرج 6 مم وspacing: 1 تبقى المازورة 32 وحدها في سطر. غيّر 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)
الشيفرة
MIT، مثل Postext
SandboxPDF