باختصار
طبعة صغيرة لقطعتي مينويت معروفتين للبيانو. تبيّن كيف تأخذ ملفًا صدّرته برامج التدوين الموسيقي وتنضده مدوّنةً للبيانو تمتد من صفحة إلى أخرى ويُعزف منها مباشرة.
ما الذي ستنضده
طبعة للعازفين من قطعتي المينويت في دفتر آنا ماغدالينا باخ لعام 1725: قطعة صول الكبير التي يتعلمها كل دارس للبيانو، وقرينتها في صول الصغير، وتُنسبان اليوم إلى كريستيان بيتسولد. مقاس الصفحة 230 × 305 مم، وهو مقاس ألبومات النوتة. تقدّم صفحة فردية القطعتين في عمودين من Libre Caslon تحت عنوان بخط Bodoni مقاسه 52 نقطة. ثم تبدأ كل مينويت على صفحة زوجية وتمتد على الصفحتين المتقابلتين في مدرج مزدوج ارتفاعه 7 مم، محفورًا بخط Bravura من ملف MusicXML: أربع موازير في كل سطر موسيقي، والموازير 1–16 على اليسار و17–32 على اليمين، فلا يقلب العازف صفحة في أثناء القطعة.
تجيب هذه الوصفة عن
- كيف أُخرج مدوّنة بيانو من ملف MusicXML على عدة صفحات؟
الجواب المختصر
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);
}
المكونات
- الميزات
- التدوين الموسيقينمط المدوّنة الموسيقيةصفحات افتتاح مصمَّمةسمات العنوانأنماط العناوينالترويسات وأرقام الصفحاتهوامش متناظرةالترويسات بحسب دور الصفحةأعمدة داخل الإطار أو النصأنماط الفقراتلوحة ألوان دلاليةالتصدير إلى PDF
- الخطوط
- Libre Caslon Text, Bodoni Moda, Archivo Narrow (SIL OFL 1.1)
- الأصول
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), ملك عام)
- 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 · افتتح كل قطعة على صفحة زوجية
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 · اكتب اسم القطعة في ترويسة الصفحة اليمنى
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 نقاط.
الوصفة كاملة
// ═══ 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
- الصور
- 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) · ملك عام
- الخطوط
- Libre Caslon Text (SIL OFL 1.1) · Bodoni Moda (SIL OFL 1.1) · Archivo Narrow (SIL OFL 1.1)
- الشيفرة
- MIT، مثل Postext
حرّر هذا الشرح ↗ (يفتح في تبويب جديد)مجلد الوصفة على GitHub ↗ (يفتح في تبويب جديد)


