简单来说
一本围着餐桌唱歌用的小歌本。每首歌用一种叫ABC的记谱法写成纯文本,Postext画出谱表、音符、吉他和弦和音符下的歌词,其余几段歌词分两栏排在后面。
成品一览
《Songs for the Long Table》是一本A5开本的口袋歌本:茜红色的书名页,一篇写给唱歌的人的说明(附目录),三首歌,以及每首歌出处的注释。每首歌开头是一个茜红色的大号编号、用Fraunces排的歌名和一行小型大写字母排的署名。下面是旋律,用Leland排在5.5 mm的谱表上,吉他和弦标在小节上方,第一段歌词用本书的正文字体Spectral排在音符下。其余几段歌词分两栏排,唱的人从最后一小节直接往下读。《Amazing Grace》的诗行很长,放进一栏会转行,所以它的几段歌词一段接一段居中排列。西班牙语版是一本生日歌本,收三首较短的歌,每页两首。
这道食谱解答
- 怎样排一本歌集:旋律、音符下的歌词和和弦记号?
简短回答
// A song is an ```abc fence in the Markdown: the melody, its chords written "G" before the note
// they fall on, and the first verse on a w: line under it. The look is set here, once.
const scoreStyle = {
staffSize: mm(5.5), // a songbook rastral; the words under it come out at about 8 pt
musicFont: 'Leland', // lighter noteheads than Bravura, close to Spectral's colour
textFont: TEXT, // words and chord names in the text face, not the engraver's Times
spacing: 1, // the engraver's own: five or six bars of 3/4 to a system on this measure
barNumbers: 'none', // a table of singers counts verses, not bars
systemGap: mm(4.5), // room for the chord names between one system's words and the next
marginTop: pt(4),
marginBottom: pt(LEAD),
color: col('ink'),
minSystemsAtBreak: 2, // never a lone system at a page foot or head
};
// Two habits of Verovio's ABC reader shape the w: lines. Every sung note takes the next
// syllable in order: the `_` and `*` holds are read as nothing, so the second note of a
// slurred pair (B/G/ under "-ing") gets a no-break space, which prints nothing. A tied note
// takes none. `~` joins two syllables on one note: "de~a-trás".
用料
做法
#1 · 写下歌曲,乐谱样式只设一次
乐谱样式就是上面的简短回答。在Markdown中,每首歌是一个```abc代码块:头部几行给出拍号、音符时值和调号;双引号里的和弦名("D7"A)印在紧跟其后的那个音符上方;音乐下面的w:行在每个音符下放一个音节,音节之间用连字符分开。有主歌和副歌的曲子写成两行音乐,每行各有自己的w:行。谱表大小、音乐字体、歌词字体、小节编号和谱行间距都来自scoreStyle,所以三首歌外观一致,代码块里只有音乐。
#2 · 每首歌以编号和署名开头
// # Amazing Grace {credit="Words John Newton, 1779 · Tune New Britain, American, 1835"}
// The songs run on, three lines apart: a short song shares its page with the next one.
const at = (to, edge, x = 0, y = 0) => ({ anchor: { to, edge }, offset: { x: mm(x), y: mm(y) } });
const text = (id, content, font, size, color, placement, extra = {}) => ({ kind: 'text', id,
content, fontFamily: font, fontSize: pt(size), color: col(color), align: 'left', placement,
...extra });
const songHead = { enabled: true, minHeight: mm(21), slot: { elements: [
text('number', '{number}', DISPLAY, 26, 'madder', at('container', 'top-left', 0, 1),
{ fontWeight: 600, lineHeight: 1 }),
text('title', '{titleText}', DISPLAY, 21, 'ink', { ...at('container', 'top-left', 12, 2),
size: { width: mm(100) } }, { fontWeight: 600, lineHeight: 1.05 }),
text('credit', '{attr.credit}', LABEL, 8.4, 'madder', { ...at('#title', 'below', 0, 2.2),
size: { width: mm(100) } }, { fontWeight: 600, letterSpacing: pt(0.6) }),
] } };
const songs = { level: 1, numberingTemplate: '{1}', advancedDesign: songHead,
marginTop: pt(LEAD * 3), marginBottom: pt(0), breakBefore: { enabled: false } };
歌曲是一个带credit属性的一级标题,它的设计据此排出编号、歌名和署名行。minHeight让每首歌的标题区都高21 mm,所以每一页的谱表都从同样的深度开始。歌曲接排,每个歌名上方空三行;乐谱绝不在第二行谱之前断开(minSystemsAtBreak: 2):英文版的歌一首占一页,西班牙语版的两首短歌合占一页。
#3 · 其余几段歌词分两栏排
// :::columns{count=2} holds one :::verse block per stanza; each line of the stanza is a
// line of Markdown, the turned lines indented two spaces (one em). A hymn in long lines
// (Amazing Grace) drops the columns: its stanzas stand one under another, centred.
const verse = { id: 'verse', fontSize: pt(9.4), lineHeight: pt(13), hyphenation: false,
marginBottom: pt(13 / 2) };
const chorus = { id: 'chorus', fontSize: pt(9.4), lineHeight: pt(13), italic: true,
hyphenation: false, marginBottom: pt(LEAD / 2) };
const colophon = { id: 'colophon', fontFamily: LABEL, fontSize: pt(7.8), lineHeight: pt(11),
color: col('muted'), textAlign: 'left', firstLineIndent: pt(0), marginTop: pt(LEAD * 2) };
每段歌词是一个:::verse块,所以诗行在诗人断行的地方断开,第二行和第四行缩进1 em。一个:::columns{count=2 gap=4mm}组把它们以9.4 pt并排。先数一数字符:Burns的诗行放得进55 mm宽的栏,Newton的长格律(long metre)放不进,所以《Amazing Grace》保留一个居中的:::verse块,用空行分隔各段。
#4 · 书眉和页码保持低调
const head = (id, content, parity, edge, x, style = {}) => ({
kind: 'text', id, content, parity, pages: 'all', // a song's first page carries them too
fontFamily: LABEL, fontSize: pt(8.4), fontWeight: 600, letterSpacing: pt(0.9),
color: col('muted'), ...style,
placement: { anchor: { to: 'page', edge }, offset: { x: mm(x), y: mm(12.5) } },
});
const folio = { fontFamily: TEXT, fontWeight: 600, letterSpacing: pt(0), color: col('madder') };
const header = { elements: [
head('verso-folio', '{pageNumber}', 'even', 'top-left', OUTER, folio),
head('verso-title', '{title}', 'even', 'top-left', OUTER + 8),
head('recto-song', '{chapterTitle}', 'odd', 'top-right', -(OUTER + 8)),
head('recto-folio', '{pageNumber}', 'odd', 'top-right', -OUTER, folio),
] };
歌曲排到的每一页都有书眉,左页印书名,右页印歌名,页码用与歌曲编号相同的茜红色。书名页和写给唱歌的人的说明在各自的标题样式中设一个空的header,去掉书眉。
完整食谱
// ═══ Postext Cookbook · Nº 164 · Songbook: melody, words under the notes, chords ═══════
// https://postext.dev/en/cookbook/songbook-melody-words-chords
// Code: MIT · Words and tunes: public domain (see the notes in each edition) · Prose: CC BY 4.0
// Fonts: Spectral, Spectral SC, Fraunces (SIL OFL 1.1); Leland (music) · 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 = 'songbook-melody-words-chords';
// ─── 1 · Design ─────────────────────────────────────────────────────────────
const palette = { // every colour in the config links to one of these
ink: '#211e1b', // text and notes: a warm near-black
madder: '#8a3324', // the one accent: song numbers, the title page, credit lines
tint: '#f1e7d8', // the subtitle on the madder ground (8.2:1)
rule: '#cbbca6', // hairlines
muted: '#675e55', // running heads, notes and the colophon (5.9:1 on the paper)
paper: '#fbf8f1', // a cream stock
};
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' } })),
// The engine's defaults link to 'main-color': point it at the accent, so nothing prints blue.
{ id: 'main-color', name: 'madder (defaults)', value: { hex: palette.madder, model: 'hex' } },
];
const [TEXT, LABEL, DISPLAY] = ['Spectral', 'Spectral SC', 'Fraunces'];
const LEAD = 14; // pt: the body's leading
const OUTER = 15; // mm: the outer margin, where the running heads and folios align
// #region answer: the score style: a 5 mm staff in Leland, the words in the book's face
// A song is an ```abc fence in the Markdown: the melody, its chords written "G" before the note
// they fall on, and the first verse on a w: line under it. The look is set here, once.
const scoreStyle = {
staffSize: mm(5.5), // a songbook rastral; the words under it come out at about 8 pt
musicFont: 'Leland', // lighter noteheads than Bravura, close to Spectral's colour
textFont: TEXT, // words and chord names in the text face, not the engraver's Times
spacing: 1, // the engraver's own: five or six bars of 3/4 to a system on this measure
barNumbers: 'none', // a table of singers counts verses, not bars
systemGap: mm(4.5), // room for the chord names between one system's words and the next
marginTop: pt(4),
marginBottom: pt(LEAD),
color: col('ink'),
minSystemsAtBreak: 2, // never a lone system at a page foot or head
};
// Two habits of Verovio's ABC reader shape the w: lines. Every sung note takes the next
// syllable in order: the `_` and `*` holds are read as nothing, so the second note of a
组合好的script.js可以直接运行:把它粘贴到任何页面的模块脚本中,或在CodePen上打开这道食谱。 GitHub上的食谱文件夹 ↗ (在新标签页中打开)
变化
常见问题
易错点
标题样式会继承其级别的分页设置
headingStyles中的条目没有写出的字段都取自它所属的标题级别,breakBefore也不例外。在:::pagebreak之后以H1设置样式的目录页或版权页会继承parity 'odd',结果落在一张空白页之后。给这类样式设置breakBefore: { enabled: false }。 标题样式 →
- Verovio的ABC读取器忽略
w:行中的_和*这两个占位记号:每个音符都取下一个音节。在圆滑线的第二个音符下,单独写一个不换行空格(U+00A0)作为一个音节,它什么也不印。用延音线连起的音符不取音节。 ~把两个音节合到一个音符下(de~a-trás),但刻谱引擎用普通空格把它们印出来,不印连接弧。- 和弦名尽量不用♭或♯:大多数正文字体没有这些符号,页面会改用系统字体。选和弦都是本位音的调(G、C、D7、Em),或者在正文中写成“降B”。
致谢
- 文本
- Amazing Grace: words (1779, five of the six stanzas, in the Olney Hymns spelling) · John Newton · 公有领域
- Amazing Grace: the tune New Britain (Southern Harmony, 1835), melody as in E. O. Excell's setting of 1900, chord symbols added · Traditional American; William Walker; Edwin O. Excell · 公有领域
- Oh My Darling, Clementine: words and tune (Oliver Ditson, Boston, 1884) · Percy Montrose · 公有领域
- Auld Lang Syne: words (1788) and the Scots tune of George Thomson's Select Songs of Scotland (1799) · Robert Burns; traditional · 公有领域
- Cumpleaños feliz: the tune of Good Morning to All (Song Stories for the Kindergarten, Chicago, 1893); Spanish words traditional · Mildred J. Hill; traditional · 公有领域
- Martinillo: the French tune of Frère Jacques (18th century) with the traditional Spanish words · Traditional · 公有领域
- La cucaracha: tune, chorus and verse as in the Spanish Wikipedia transcription; two traditional Mexican coplas · Traditional · 公有领域
- The preface, the notes on the songs and the chord symbols · Ignacio Ferro · CC BY 4.0
- 字体
- Spectral (SIL OFL 1.1) · Spectral SC (SIL OFL 1.1) · Fraunces (SIL OFL 1.1)


