# MEI谱例与脚注中的校勘记

> MEI 5代码块以5毫米谱表刻印在正文中，异文用app、lem和rdg编码，校勘记按小节和音符排在脚注里。

- HTML版本: https://postext.dev/zh/cookbook/musicology-mei-apparatus
- 食谱 No. 165 · 框与注释 · 难度 2 (中级) · 输出: Canvas, PDF
- 体裁: 论文与学术出版物
- 需要 postext ≥ 1.54.0, postext-pdf ≥ 1.54.0 · 已用 1.54.0, postext-pdf 1.54.0 测试，日期 2026-10-22
- 页面: [1](https://postext.dev/cookbook/musicology-mei-apparatus/en/p01.webp?v=cd2d6301), [2](https://postext.dev/cookbook/musicology-mei-apparatus/en/p02.webp?v=cd2d6301), [3](https://postext.dev/cookbook/musicology-mei-apparatus/en/p03.webp?v=cd2d6301), [4](https://postext.dev/cookbook/musicology-mei-apparatus/en/p04.webp?v=cd2d6301), [5](https://postext.dev/cookbook/musicology-mei-apparatus/en/p05.webp?v=cd2d6301)
- PDF: https://postext.dev/cookbook/musicology-mei-apparatus/en/musicology-mei-apparatus.pdf?v=cd2d6301
- 在沙盒中打开: https://postext.dev/zh/sandbox#recipe=musicology-mei-apparatus&lang=en (.postext: https://postext.dev/cookbook/musicology-mei-apparatus/en/musicology-mei-apparatus.postext)
- 最后更新: 2026-10-22
- 其他语言: [en](https://postext.dev/en/cookbook/musicology-mei-apparatus.md), [es](https://postext.dev/es/cookbook/musicology-mei-apparatus.md), [ca](https://postext.dev/ca/cookbook/musicology-mei-apparatus.md), [de](https://postext.dev/de/cookbook/musicology-mei-apparatus.md), [fr](https://postext.dev/fr/cookbook/musicology-mei-apparatus.md), [it](https://postext.dev/it/cookbook/musicology-mei-apparatus.md), [pt](https://postext.dev/pt/cookbook/musicology-mei-apparatus.md), [ja](https://postext.dev/ja/cookbook/musicology-mei-apparatus.md), [ar](https://postext.dev/ar/cookbook/musicology-mei-apparatus.md)

## 简单来说

一部中世纪音乐研究著作中的一章。一首十三世纪英国轮唱曲的几个短小段落以乐谱形式印在正文里，每页底部的注释逐小节记下旧版本之间的出入。

## 成品一览

一部研究英国古物学家如何阅读中世纪音乐的学术著作中的第3章：五页，开本156 × 234毫米，讨论雷丁轮唱曲 *Sumer is icumen in*，以及1776年至1889年间为它编写总谱的各位编者。轮唱曲的三个段落用Leipzig字体、5毫米谱表刻印在正文中，小节号沿用W. S. Rockstro总谱的编号；每段讨论都在页脚引出自己的校勘记：“b. 40, n. 2 A ] G Hw, Bu”。注释简短紧凑，正是校勘记的样子；乐谱占满正文的版心宽度。每条异文都出自Rockstro为Grove《音乐与音乐家辞典》所写的条目，本章在第二段就交代了这一点。

**这份食谱回答的问题:**

- 怎样用脚注为谱例加上记录各版本异文的校勘记？
- 脚注怎么做？

## 简短回答

````js
// script.js, 行 36–60
// In the Markdown each example is a ```mei fence. A variant is encoded where it stands,
// <app><lem>…</lem><rdg source="#Hw #Bu">…</rdg></app>: the staff shows the lemma, and
// the file keeps the reading for anyone who opens the encoding. The apparatus entry is a
// footnote cited from the paragraph that discusses the bar: `[^b40]: **b. 40, n. 2** A ] G`.
const scoreStyle = {
  staffSize: mm(5), // running-text examples: 4.5–5.5 mm
  musicFont: 'Leipzig', // lighter than Bravura, close to a 19th-century plate
  textFont: 'Crimson Pro', // the underlay in the book's own face
  barNumbers: 'system', // the apparatus is keyed by bar: number every system
  spacing: 0.95,
  systemGap: mm(4),
  marginTop: pt(4),
  marginBottom: pt(LEAD / 2),
  color: col('ink'),
  minSystemsAtBreak: 2, // never leave one system of an example alone at a page foot
};
const footnotes = {
  placement: 'column', // the foot of the page in one column
  numbering: 'chapter',
  fontSize: pt(8.5),
  lineHeight: pt(10.75),
  hangingIndent: em(1.2), // turnover lines align past the number
  spaceBetween: pt(2),
  separator: { width: 0.2, lineWidth: pt(0.5), color: col('rubric') },
};
````

## 用料

**讲解**

- [乐谱记谱](https://postext.dev/zh/docs/music.md#正文中的乐谱): 以文本写在```abc、```musicxml、```mei或```humdrum代码块中的乐谱，由Verovio按栏宽刻印，并切成一行行谱表系统，可跨栏、跨页接排。
- [脚注](https://postext.dev/zh/docs/document-format-notes-references.md#脚注): 用[^id]引用的注释，排在引用它的那一栏底部，或排在章末，按章或全书连续编号。

**还用到**

- [乐谱样式](https://postext.dev/zh/docs/configuration-resources.md#乐谱样式)
- [设计过的章首页](https://postext.dev/zh/docs/configuration-headings.md#通栏与高级设计)
- [标题属性](https://postext.dev/zh/docs/document-format.md#标题属性)
- [书眉与页码](https://postext.dev/zh/docs/configuration-headers-footers.md#页眉与页脚)
- [对称页边距](https://postext.dev/zh/docs/configuration-page-layout.md#对称页边距)
- [按页面角色显示书眉](https://postext.dev/zh/docs/configuration-headers-footers.md#文本元素)
- [段落样式](https://postext.dev/zh/docs/configuration-styles.md#段落样式)
- [语义调色板](https://postext.dev/zh/docs/configuration-fonts-colors-viewers.md#调色板)
- [纸张颜色](https://postext.dev/zh/docs/configuration-page-layout.md#页面)
- [段末孤行、段首孤行与孤字](https://postext.dev/zh/docs/configuration-page-flow.md#段首孤行段末孤行孤字与不拆分规则)
- [导出PDF](https://postext.dev/zh/docs/configuration-pdf-output.md#生成pdf)
- [代码清单](https://postext.dev/zh/docs/configuration-styles.md#代码清单)
- [各栏齐底](https://postext.dev/zh/docs/configuration-page-flow.md#各栏齐底)
- [嵌入PDF的字体](https://postext.dev/zh/docs/configuration-pdf-output.md#为什么需要字体提供函数)

**配置一览**

- [`bodyText`](https://postext.dev/zh/docs/configuration-text.md#正文), [`colorPalette`](https://postext.dev/zh/docs/configuration-fonts-colors-viewers.md#调色板), [`footer`](https://postext.dev/zh/docs/configuration-headers-footers.md#页眉与页脚), `footnotes`, [`header`](https://postext.dev/zh/docs/configuration-headers-footers.md#页眉与页脚), [`headings`](https://postext.dev/zh/docs/configuration-headings.md#标题), [`layout`](https://postext.dev/zh/docs/configuration-page-layout.md#版式), [`locale`](https://postext.dev/zh/docs/configuration-language.md#断词), [`page`](https://postext.dev/zh/docs/configuration-page-layout.md#页面), [`paragraphStyles`](https://postext.dev/zh/docs/configuration-styles.md#段落样式), [`scoreStyle`](https://postext.dev/zh/docs/configuration-resources.md#乐谱样式)

**API**

- [`buildDocumentWithFonts`](https://postext.dev/zh/docs/configuration-programmatic-usage.md#排版前加载字体), [`decompressWoff2`](https://postext.dev/zh/docs/configuration-pdf-output.md#浏览器字体提供函数fontsource--woff2), [`initMusicEngine`](https://postext.dev/zh/docs/music.md#正文中的乐谱), [`renderPageToCanvas`](https://postext.dev/zh/docs/configuration-programmatic-usage.md#把页面渲染为位图), [`renderToPdf`](https://postext.dev/zh/docs/configuration-pdf-output.md#生成pdf)

**字体**

- Crimson Pro (OFL-1.1), Cormorant Garamond (OFL-1.1), Source Sans 3 (OFL-1.1)

## 做法

### 1 · 把每个谱例写成MEI，异文一并写入

设置见上方的[简短回答](https://postext.dev/zh/cookbook/musicology-mei-apparatus.md#简短回答)；音乐写在Markdown里，每个谱例一个 ```` ```mei ```` 代码块。两个来源不一致的地方，小节里放一个 `<app>`：采用的读法放在 `<lem>`，另一种放在 `<rdg source="#Hw #Bu">`。刻印的是 `<lem>`，所以[谱例3.3](https://postext.dev/cookbook/musicology-mei-apparatus/en/p05.webp?v=cd2d6301)第40小节印的是A，而Hawkins和Burney的G留在文件里，供打开编码的人查看。给每个代码块一个页面语言的 `alt`：屏幕阅读器和带标签的PDF读的就是它。

### 2 · 让校勘记对准读者看到的小节

`barNumbers: 'system'` 在每行谱的第一个小节标号；每个代码块的 `<measure n="37">` 从所依据版本中该段的起点开始计数，所以谱例3.3从37开始，注释里写“b. 40”，读者就能找到那个小节。校勘条目是普通的脚注，从讨论该小节的段落中引出：先写采用的读法，接右方括号，再写异文和斜体的版本代号。乐谱内部无法插入注码，所以注码放在说出这一读法的句子上。

### 3 · 按正文的尺寸安排乐谱

5毫米的Leipzig谱表放在116毫米的版心里，每行容纳五到六个3/2拍的小节，谱例3.3的十一个小节正好排成两行。`textFont` 让歌词用正文字体Crimson Pro排印；`minSystemsAtBreak: 2` 避免一页只留下某个谱例的一行谱而其余转到下一页。每个谱例的标题是三级标题，因此不会脱离乐谱单独留在页底。

### 4 · 允许带注释的页面提前结束

```js
// script.js, 行 64–78
const opener = {
  enabled: true,
  minHeight: mm(50), // every chapter's text starts on the same grid line
  slot: { elements: [
    { kind: 'text', id: 'kicker', content: '{attr.kicker}', ...label, fontSize: pt(8.5),
      letterSpacing: pt(1.8), color: col('rubric'), align: 'left',
      placement: at('container', 'top-left', 0, 8) },
    { kind: 'text', id: 'title', content: '{titleText}', fontFamily: 'Cormorant Garamond',
      italic: true, fontWeight: 500, fontSize: pt(29), lineHeight: 1.05, color: col('ink'),
      align: 'left', overflow: 'wrap', // design text ends in an ellipsis by default
      placement: { ...at('#kicker', 'below', 0, 3), size: { width: mm(100), height: 'auto' } } },
    { kind: 'rule', id: 'rule', direction: 'horizontal', thickness: pt(1), color: col('rubric'),
      placement: { ...at('#title', 'below', 0, 5), size: { width: mm(14) } } },
  ] },
};
```

章首页把正文下沉50毫米，上方是Cormorant斜体标题。后面若某页的下一行会带来一条较长的注释，这一页就提前几行结束；`balancing: { maxLinesPerHeading: 1 }` 防止分栏平衡把这段空白挪到下一节标题的上方，那样会像论述中断了一样。

### 5 · 左页印书名，右页印章名

```js
// script.js, 行 82–99
const head = (id, content, parity, edge, x, extra = {}) => ({
  kind: 'text', id, content, parity, pages: 'body', // never on the opener
  ...label, fontSize: pt(7.5), letterSpacing: pt(1.3), color: col('muted'),
  placement: at('page', edge, x, 13), ...extra,
});
const folio = { color: col('rubric'), fontWeight: 700 };
const header = { elements: [
  head('verso-folio', '{pageNumber}', 'even', 'top-left', MARGIN.outer,
    { ...folio, align: 'left' }),
  head('verso-title', '{title}', 'even', 'top-left', MARGIN.outer + 8, { align: 'left' }),
  head('recto-title', '{chapterTitle}', 'odd', 'top-right', -(MARGIN.outer + 8),
    { align: 'right', overflow: 'ellipsis' }),
  head('recto-folio', '{pageNumber}', 'odd', 'top-right', -MARGIN.outer,
    { ...folio, align: 'right' }),
] };
// The opener carries a drop folio instead, centred under the text block.
const footer = { elements: [{ ...head('drop-folio', '{pageNumber}', 'all', 'bottom', 0,
  { ...folio, align: 'center', placement: at('page', 'bottom', 0, -11) }), pages: 'opener' }] };
```

页码用朱红色印在外侧，左页是书名，右页是章名；`pages: 'body'` 让它们不出现在章首页，章首页在页底居中印页码。

## 完整食谱

一个文件，由食谱文件夹合成，示例文本和排版食谱的公共工具包都已内联；它自己排出页面。要运行它，把它放进空白页面的`<script type="module">`，或粘贴到新建CodePen的JS面板里（设为模块）。它从esm.sh导入postext，不需要安装或构建。

- 食谱文件夹: https://github.com/drnachio/postext/tree/main/cookbook/musicology-mei-apparatus

### script.js

````js
// ═══ Postext Cookbook · Nº 165 · MEI music examples with a critical apparatus ═════
// https://postext.dev/en/cookbook/musicology-mei-apparatus
// Code: MIT · Text: original (CC BY 4.0) · Music: Sumer is icumen in, after Rockstro (PD)
// Fonts: Crimson Pro, Cormorant Garamond, Source Sans 3 (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 = 'musicology-mei-apparatus';

// ─── 1 · Design ─────────────────────────────────────────────────────────────
// #region palette: a rubric red for kickers, labels and the notes' rule; ink on cream
const palette = {
  ink: '#211d1a', // text and notation: a warm near-black
  rubric: '#8c2f1f', // the one accent, the red of the manuscript's rubrics
  muted: '#6c645b', // running heads, colophon
  rule: '#cbbfae', // hairlines
  paper: '#fcfaf6',
};
// col(id): a colour linked to its palette entry, with the entry's hex beside the id.
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, never the default blue.
  { id: 'main-color', name: 'accent (defaults)', value: { hex: palette.rubric, model: 'hex' } },
];
// #endregion
const MARGIN = { top: 22, bottom: 22, inner: 19, outer: 21 }; // 156 × 234 mm, mirrored
const LEAD = 14.5; // body leading in pt: the baseline grid
const at = (to, edge, x = 0, y = 0) => ({ anchor: { to, edge }, offset: { x: mm(x), y: mm(y) } });
const label = { fontFamily: 'Source Sans 3', fontWeight: 600, textTransform: 'uppercase' };

// #region answer: MEI examples engraved in the text, their apparatus in footnotes
// In the Markdown each example is a ```mei fence. A variant is encoded where it stands,
// <app><lem>…</lem><rdg source="#Hw #Bu">…</rdg></app>: the staff shows the lemma, and
// the file keeps the reading for anyone who opens the encoding. The apparatus entry is a
// footnote cited from the paragraph that discusses the bar: `[^b40]: **b. 40, n. 2** A ] G`.
const scoreStyle = {
  staffSize: mm(5), // running-text examples: 4.5–5.5 mm
  musicFont: 'Leipzig', // lighter than Bravura, close to a 19th-century plate
  textFont: 'Crimson Pro', // the underlay in the book's own face
  barNumbers: 'system', // the apparatus is keyed by bar: number every system
  spacing: 0.95,
  systemGap: mm(4),
  marginTop: pt(4),
  marginBottom: pt(LEAD / 2),
  color: col('ink'),
  minSystemsAtBreak: 2, // never leave one system of an example alone at a page foot
};
const footnotes = {
  placement: 'column', // the foot of the page in one column
  numbering: 'chapter',
  fontSize: pt(8.5),
  lineHeight: pt(10.75),
  hangingIndent: em(1.2), // turnover lines align past the number
  spaceBetween: pt(2),
  separator: { width: 0.2, lineWidth: pt(0.5), color: col('rubric') },
};
// #endregion

// #region opener: kicker, a Cormorant italic title and a short red rule, sunk 50 mm
const opener = {
  enabled: true,
  minHeight: mm(50), // every chapter's text starts on the same grid line
  slot: { elements: [
    { kind: 'text', id: 'kicker', content: '{attr.kicker}', ...label, fontSize: pt(8.5),
      letterSpacing: pt(1.8), color: col('rubric'), align: 'left',
      placement: at('container', 'top-left', 0, 8) },
    { kind: 'text', id: 'title', content: '{titleText}', fontFamily: 'Cormorant Garamond',
      italic: true, fontWeight: 500, fontSize: pt(29), lineHeight: 1.05, color: col('ink'),
      align: 'left', overflow: 'wrap', // design text ends in an ellipsis by default
      placement: { ...at('#kicker', 'below', 0, 3), size: { width: mm(100), height: 'auto' } } },
    { kind: 'rule', id: 'rule', direction: 'horizontal', thickness: pt(1), color: col('rubric'),
      placement: { ...at('#title', 'below', 0, 5), size: { width: mm(14) } } },
  ] },
};
// #endregion

// #region running-heads: book title on the verso, chapter on the recto, folios outside
const head = (id, content, parity, edge, x, extra = {}) => ({
  kind: 'text', id, content, parity, pages: 'body', // never on the opener
  ...label, fontSize: pt(7.5), letterSpacing: pt(1.3), color: col('muted'),
  placement: at('page', edge, x, 13), ...extra,
});
const folio = { color: col('rubric'), fontWeight: 700 };
const header = { elements: [
  head('verso-folio', '{pageNumber}', 'even', 'top-left', MARGIN.outer,
    { ...folio, align: 'left' }),
  head('verso-title', '{title}', 'even', 'top-left', MARGIN.outer + 8, { align: 'left' }),
  head('recto-title', '{chapterTitle}', 'odd', 'top-right', -(MARGIN.outer + 8),
    { align: 'right', overflow: 'ellipsis' }),
  head('recto-folio', '{pageNumber}', 'odd', 'top-right', -MARGIN.outer,
    { ...folio, align: 'right' }),
] };
// The opener carries a drop folio instead, centred under the text block.
const footer = { elements: [{ ...head('drop-folio', '{pageNumber}', 'all', 'bottom', 0,
  { ...folio, align: 'center', placement: at('page', 'bottom', 0, -11) }), pages: 'opener' }] };
// #endregion

const config = {
  locale: t({ en: 'en-gb', es: 'es' }), // the language of the hyphenation patterns
  colorPalette,
  page: { sizePreset: 'custom', width: mm(156), height: mm(234), dpi: 150,
    backgroundColor: col('paper'),
    margins: { top: mm(MARGIN.top), bottom: mm(MARGIN.bottom), left: mm(MARGIN.inner),
      right: mm(MARGIN.outer), mirror: true } }, // left = inner
  layout: { layoutType: 'single' },
  bodyText: { fontFamily: 'Crimson Pro', fontSize: pt(11), lineHeight: pt(LEAD),
    color: col('ink'), boldColor: col('ink'), italicColor: col('ink'), referenceColor: col('ink'),
    textAlign: 'justify', firstLineIndent: mm(4.5), indentAfterHeading: false,
    hyphenation: { enabled: true }, optimalLineBreaking: true,
    avoidWidows: true, avoidOrphans: true, avoidRunts: true },
  headings: { fontFamily: 'Crimson Pro', color: col('ink'),
    // A page whose next line cannot take its footnote ends short: at most one extra grid
    // line goes above a heading, the rest stays at the foot, as in a book with notes.
    balancing: { maxLinesPerHeading: 1 },
    levels: [
    { level: 1, advancedDesign: opener, marginBottom: pt(0),
      breakBefore: { enabled: true, parity: 'odd' } },
    { level: 2, fontFamily: 'Cormorant Garamond', fontSize: pt(14), lineHeight: pt(LEAD),
      fontWeight: 600, italic: true, color: col('ink'), marginTop: pt(LEAD), marginBottom: pt(3) },
    // The example labels: a heading keeps itself with the score under it.
    { level: 3, ...label, fontSize: pt(7.5), lineHeight: pt(LEAD), letterSpacing: pt(1.1),
      color: col('rubric'), marginTop: pt(LEAD / 2), marginBottom: pt(0) },
  ] },
  paragraphStyles: [
    { id: 'colophon', ...label, textTransform: 'none', fontWeight: 400, fontSize: pt(7.5),
      lineHeight: pt(10), color: col('muted'), textAlign: 'left', firstLineIndent: pt(0),
      marginTop: pt(LEAD) },
  ],
  scoreStyle,
  footnotes,
  header,
  footer,
};

// ─── 2 · Content ────────────────────────────────────────────────────────────
const markdown = `---
title: "The Antiquaries’ Middle Ages"
---

# The Reading Rota and Its Editors {kicker="Chapter 3"}

No piece of medieval music was printed, scored and argued over by English antiquaries as often as the six-voice rota *Sumer is icumen in*. It survives on a single leaf, fol. 11v of the miscellany that Humfrey Wanley described for the Earl of Oxford around 1709 and that is now British Library, Harley MS 978. Wanley called it the oldest piece of its kind he had seen. Between Sir John Hawkins’s *General History* of 1776 and the article that William Smyth Rockstro wrote for George Grove’s *Dictionary of Music and Musicians*, the rota went through at least five scores, each claiming to correct the last.

This chapter follows that sequence of editions and tests it at three places where the editors disagreed. The music examples are new transcriptions, and their critical apparatus stands in the notes: each entry gives the bar and the note, the reading adopted, a closing bracket, and the readings of the other witnesses.

## The witnesses and this edition

Four witnesses are cited. **H** is the manuscript. **Hw** is the score in the first volume of Hawkins’s *General History of the Science and Practice of Music* (1776). **Bu** is the score in the second volume of Charles Burney’s *General History of Music* (1782). **Ro** is Rockstro’s solution of the canon, printed with the first part of his article in the third volume of Grove’s *Dictionary*.[^ro]

The examples follow Ro in pitch, clefs and note values, since Rockstro is the only editor of the period who explains every substitution he made. The manuscript, he writes, uses only a C clef, the *B rotundum*, square black notes with tails, one square note without a tail, and black lozenges. He renders a tailed note as a dotted semibreve when it is perfect and as a semibreve when it is imperfect, the untailed square note as a semibreve, and the lozenges as minims, under a signature of perfect time with the lesser prolation, “closely corresponding with the Signature 3-2 in modern Music”.[^values] The readings of H, Hw and Bu below are those that Rockstro reports. This chapter has not collated the two eighteenth-century prints afresh, and where Rockstro is silent, the apparatus is silent too.

## The first phrase and a corrected note

### Example 3.1 · Cantus I, bars 1–7, with the English and Latin texts

\`\`\`mei {alt="Sumer is icumen in, the first voice, bars 1 to 7, with the English words and the Latin text Perspice Christicola"}
<mei xmlns="http://www.music-encoding.org/ns/mei" meiversion="5.1">
<meiHead><fileDesc><titleStmt><title>Sumer is icumen in, Cantus I, bars 1–7</title></titleStmt><pubStmt/><sourceDesc><source xml:id="H"><bibl>BL Harley 978, f. 11v</bibl></source><source xml:id="Ro"><bibl>Rockstro, Grove iii</bibl></source></sourceDesc></fileDesc></meiHead>
<music><body><mdiv><score>
<scoreDef meter.count="3" meter.unit="2" keysig="1f"><staffGrp><staffDef n="1" lines="5" clef.shape="G" clef.line="2"/></staffGrp></scoreDef>
<section>
<measure n="1"><staff n="1"><layer n="1"><note pname="f" oct="5" dur="1"><verse n="1"><syl wordpos="i" con="d">Sum</syl></verse><verse n="2"><syl wordpos="i" con="d">Per</syl></verse></note><app><lem><note pname="e" oct="5" dur="2"><verse n="1"><syl wordpos="t">er</syl></verse><verse n="2"><syl wordpos="m" con="d">spi</syl></verse></note></lem><rdg source="#H"><note pname="f" oct="5" dur="2"><verse n="1"><syl wordpos="t">er</syl></verse><verse n="2"><syl wordpos="m" con="d">spi</syl></verse></note></rdg></app></layer></staff></measure>
<measure n="2"><staff n="1"><layer n="1"><note pname="d" oct="5" dur="1"><verse n="1"><syl>is</syl></verse><verse n="2"><syl wordpos="t">ce</syl></verse></note><note pname="e" oct="5" dur="2"><verse n="1"><syl wordpos="i" con="d">i</syl></verse><verse n="2"><syl wordpos="i" con="d">Chri</syl></verse></note></layer></staff></measure>
<measure n="3"><staff n="1"><layer n="1"><note pname="f" oct="5" dur="1"><verse n="1"><syl wordpos="m" con="d">cum</syl></verse><verse n="2"><syl wordpos="m" con="d">sti</syl></verse></note><note pname="f" oct="5" dur="2"><verse n="1"><syl wordpos="t">en</syl></verse><verse n="2"><syl wordpos="m" con="d">co</syl></verse></note></layer></staff></measure>
<measure n="4"><staff n="1"><layer n="1"><note pname="e" oct="5" dur="2" dots="1"><verse n="1"><syl>in,</syl></verse><verse n="2"><syl wordpos="t">la,</syl></verse></note><note pname="d" oct="5" dur="4"/><note pname="c" oct="5" dur="2"/></layer></staff></measure>
<measure n="5"><staff n="1"><layer n="1"><note pname="a" oct="4" dur="1"><verse n="1"><syl wordpos="i" con="d">Lhud</syl></verse><verse n="2"><syl>que</syl></verse></note><note pname="a" oct="4" dur="2"><verse n="1"><syl wordpos="t">e</syl></verse><verse n="2"><syl wordpos="i" con="d">dig</syl></verse></note></layer></staff></measure>
<measure n="6"><staff n="1"><layer n="1"><note pname="b" oct="4" dur="1"><verse n="1"><syl>sing</syl></verse><verse n="2"><syl wordpos="m" con="d">na</syl></verse></note><note pname="g" oct="4" dur="2"><verse n="1"><syl wordpos="i" con="d">cuc</syl></verse><verse n="2"><syl wordpos="m" con="d">ci</syl></verse></note></layer></staff></measure>
<measure n="7"><staff n="1"><layer n="1"><note pname="a" oct="4" dur="1" dots="1"><verse n="1"><syl wordpos="t">cu.</syl></verse><verse n="2"><syl wordpos="t">o.</syl></verse></note></layer></staff></measure>
</section></score></mdiv></body></music></mei>
\`\`\`

The opening is the best-documented phrase of the rota, because Rockstro returned to the leaf itself in the second part of his article and listed the erasures he could see. The scribe corrected his own work in the same ink, and the first correction falls on the second note.[^b1] Rockstro took the erasures as improvements made while the piece was being written down rather than as repairs of a faulty copy, and used them to support his view that the canon grew out of a popular tune that a trained musician adjusted until its phrases fitted against one another.

Two further decisions are visible in bars 2 and 4. The Latin text is printed in full where the manuscript abbreviates the name of Christ,[^b2] and the single tailed lozenge under the word *in* becomes a dotted minim and a crotchet.[^b4] The flat in the signature stands for the *B rotundum* of the manuscript, which Rockstro reads as a B flat throughout.[^bflat]

## The pes

### Example 3.2 · Pes, bars 1–8, as scored by Rockstro

\`\`\`mei {alt="Sumer is icumen in, the two lower voices of the pes, bars 1 to 8"}
<mei xmlns="http://www.music-encoding.org/ns/mei" meiversion="5.1">
<meiHead><fileDesc><titleStmt><title>Sumer is icumen in, the pes, bars 1–8</title></titleStmt><pubStmt/><sourceDesc><source xml:id="Ro"><bibl>Rockstro, Grove iii</bibl></source></sourceDesc></fileDesc></meiHead>
<music><body><mdiv><score>
<scoreDef meter.count="3" meter.unit="2" keysig="1f"><staffGrp symbol="bracket" bar.thru="false"><staffDef n="1" lines="5" clef.shape="F" clef.line="4"/><staffDef n="2" lines="5" clef.shape="F" clef.line="4"/></staffGrp></scoreDef>
<section>
<measure n="1"><staff n="1"><layer n="1"><note pname="f" oct="3" dur="1" dots="1"><verse n="1"><syl>Sing</syl></verse></note></layer></staff><staff n="2"><layer n="1"><note pname="c" oct="4" dur="1" dots="1"><verse n="1"><syl>Sing</syl></verse></note></layer></staff></measure>
<measure n="2"><staff n="1"><layer n="1"><note pname="g" oct="3" dur="1" dots="1"><verse n="1"><syl wordpos="i" con="d">cuc</syl></verse></note></layer></staff><staff n="2"><layer n="1"><note pname="b" oct="3" dur="1" dots="1"><verse n="1"><syl wordpos="i" con="d">cuc</syl></verse></note></layer></staff></measure>
<measure n="3"><staff n="1"><layer n="1"><note pname="f" oct="3" dur="1" dots="1"><verse n="1"><syl wordpos="t">cu</syl></verse></note></layer></staff><staff n="2"><layer n="1"><note pname="c" oct="4" dur="1" dots="1"><verse n="1"><syl wordpos="t">cu.</syl></verse></note></layer></staff></measure>
<measure n="4"><staff n="1"><layer n="1"><note pname="g" oct="3" dur="1"><verse n="1"><syl>nu.</syl></verse></note><note pname="a" oct="3" dur="2"/></layer></staff><staff n="2"><layer n="1"><mRest/></layer></staff></measure>
<measure n="5"><staff n="1"><layer n="1"><note pname="c" oct="4" dur="1" dots="1"><verse n="1"><syl>Sing</syl></verse></note></layer></staff><staff n="2"><layer n="1"><note pname="f" oct="3" dur="1" dots="1"><verse n="1"><syl>Sing</syl></verse></note></layer></staff></measure>
<measure n="6"><staff n="1"><layer n="1"><note pname="b" oct="3" dur="1" dots="1"><verse n="1"><syl wordpos="i" con="d">cuc</syl></verse></note></layer></staff><staff n="2"><layer n="1"><note pname="g" oct="3" dur="1" dots="1"><verse n="1"><syl wordpos="i" con="d">cuc</syl></verse></note></layer></staff></measure>
<measure n="7"><staff n="1"><layer n="1"><note pname="c" oct="4" dur="1" dots="1"><verse n="1"><syl wordpos="t">cu.</syl></verse></note></layer></staff><staff n="2"><layer n="1"><note pname="f" oct="3" dur="1" dots="1"><verse n="1"><syl wordpos="t">cu</syl></verse></note></layer></staff></measure>
<measure n="8"><staff n="1"><layer n="1"><mRest/></layer></staff><staff n="2"><layer n="1"><note pname="g" oct="3" dur="1"><verse n="1"><syl>nu.</syl></verse></note><note pname="a" oct="3" dur="2"/></layer></staff></measure>
</section></score></mdiv></body></music></mei>
\`\`\`

Below the four voices of the canon the manuscript writes two short parts to be sung as a ground, each to the words *Sing cuccu nu*. The Latin rubric opens *Hanc rotam cantare possunt quatuor socii*: four companions can sing the round, not counting those who hold the pes. Each new voice begins “at the first note after the cross”, which Ro marks over bar 5. The first pes is repeated *faciens pausacionem in fine*, the second *pausans in medio et non in fine*.[^rubric] Ro follows the rubric exactly: the first pes rests in bar 8, the second in bar 4, so that one of the two lower voices is always sounding.

The pes is also where the eighteenth-century scores went most visibly wrong. According to Rockstro, Hawkins misread both of its ligatures and printed the passage incorrectly at every repetition; Burney corrected the mistake.[^lig] Neither ligature can be shown in a modern score, and Example 3.2 resolves both into separate notes, as Ro does.

Rockstro’s own analysis of the part-writing begins from this score. The fourth, sixth, seventh, eighth and twenty-fourth bars, he observes, are in strict two-part counterpoint “of irreproachable purity”, while the passage from bar 9 to bar 10 gives consecutive fifths between the first and third voices of the canon, and bars 37 to 39 three consecutive fifths between the first cantus and the second voice of the pes.[^fifths] For Rockstro the mixture of styles was the strongest argument for a melody older than its harmony.

## The question of date

The dates proposed for the leaf say as much about the editors as about the manuscript. Hawkins placed it in the middle of the fifteenth century, arguing that its notation was the *cantus figuratus* he credited to John Dunstable, who, Hawkins wrote, died in 1455; Rockstro called the reasoning an anachronism “absolutely worthless”.[^hawkins] Burney thought the piece not much later than the thirteenth or fourteenth century. Joseph Ritson referred the manuscript to the middle of the thirteenth, and Thomas Busby, reprinting Burney’s score note for note in 1819, returned it to the fifteenth.

The decisive arguments came from palaeography. In 1855 William Chappell published a coloured facsimile in *Popular Music of the Olden Time* and later argued that the leaf was written at Reading Abbey by a monk named John of Fornsete around 1226. In April 1862 Sir Frederick Madden noted on a flyleaf of the volume that the whole was of the thirteenth century and that its earlier part had been written at Reading about 1240, a date the Palaeographical Society adopted for its facsimile of 1878. On the Continent, Johann Nikolaus Forkel (1788), August Wilhelm Ambros (1862 and 1868) and Edmond de Coussemaker (1865) all wrote on the rota without seeing the manuscript, a point Rockstro makes against each of them in turn.[^continent] A more recent study puts the copying between 1261 and 1264.[^wulstan]

## “Cuccu” in bar 40

### Example 3.3 · Cantus I, bars 37–47

\`\`\`mei {alt="Sumer is icumen in, the first voice, bars 37 to 47, from Cuccu, cuccu to ne swik thu nauer nu"}
<mei xmlns="http://www.music-encoding.org/ns/mei" meiversion="5.1">
<meiHead><fileDesc><titleStmt><title>Sumer is icumen in, Cantus I, bars 37–47</title></titleStmt><pubStmt/><sourceDesc><source xml:id="Hw"><bibl>Hawkins 1776</bibl></source><source xml:id="Bu"><bibl>Burney 1782</bibl></source><source xml:id="Ro"><bibl>Rockstro, Grove iii</bibl></source></sourceDesc></fileDesc></meiHead>
<music><body><mdiv><score>
<scoreDef meter.count="3" meter.unit="2" keysig="1f"><staffGrp><staffDef n="1" lines="5" clef.shape="G" clef.line="2"/></staffGrp></scoreDef>
<section>
<measure n="37"><staff n="1"><layer n="1"><note pname="c" oct="5" dur="1" dots="1"><verse n="1"><syl wordpos="i" con="d">Cuc</syl></verse></note></layer></staff></measure>
<measure n="38"><staff n="1"><layer n="1"><note pname="d" oct="5" dur="1" dots="1"><verse n="1"><syl wordpos="t">cu,</syl></verse></note></layer></staff></measure>
<measure n="39"><staff n="1"><layer n="1"><note pname="c" oct="5" dur="1" dots="1"><verse n="1"><syl wordpos="i" con="d">cuc</syl></verse></note></layer></staff></measure>
<measure n="40"><staff n="1"><layer n="1"><note pname="b" oct="4" dur="1"><verse n="1"><syl wordpos="t">cu.</syl></verse></note><app><lem><note pname="a" oct="4" dur="2"/></lem><rdg source="#Hw #Bu"><note pname="g" oct="4" dur="2"/></rdg></app></layer></staff></measure>
<measure n="41"><staff n="1"><layer n="1"><note pname="f" oct="4" dur="1"><verse n="1"><syl>Wel</syl></verse></note><note pname="a" oct="4" dur="2"><verse n="1"><syl wordpos="i" con="d">sing</syl></verse></note></layer></staff></measure>
<measure n="42"><staff n="1"><layer n="1"><note pname="b" oct="4" dur="1"><verse n="1"><syl wordpos="t">es</syl></verse></note><note pname="g" oct="4" dur="2"><verse n="1"><syl>thu</syl></verse></note></layer></staff></measure>
<measure n="43"><staff n="1"><layer n="1"><note pname="a" oct="4" dur="1" dots="1"><verse n="1"><syl wordpos="i" con="d">cuc</syl></verse></note></layer></staff></measure>
<measure n="44"><staff n="1"><layer n="1"><note pname="b" oct="4" dur="1"><verse n="1"><syl wordpos="t">cu,</syl></verse></note><note pname="c" oct="5" dur="2"><verse n="1"><syl>ne</syl></verse></note></layer></staff></measure>
<measure n="45"><staff n="1"><layer n="1"><note pname="a" oct="4" dur="1"><verse n="1"><syl>swik</syl></verse></note><note pname="c" oct="5" dur="2"><verse n="1"><syl>thu</syl></verse></note></layer></staff></measure>
<measure n="46"><staff n="1"><layer n="1"><note pname="g" oct="4" dur="1"><verse n="1"><syl wordpos="i" con="d">nau</syl></verse></note><note pname="e" oct="4" dur="2"><verse n="1"><syl wordpos="t">er</syl></verse></note></layer></staff></measure>
<measure n="47"><staff n="1"><layer n="1"><note pname="f" oct="4" dur="1" dots="1"><verse n="1"><syl>nu.</syl></verse></note></layer></staff></measure>
</section></score></mdiv></body></music></mei>
\`\`\`

The last passage of the tune shows how an error could travel. Hawkins and Burney, Rockstro reports, both print a G where the manuscript has an A in bar 40, on the second syllable of *cuccu*, and they do so each time the phrase returns in a new voice.[^b40] Against Hawkins’s faulty pes the G passes; against Burney’s corrected pes it makes what Rockstro calls “a horrible discord”. The variant is encoded in the source of Example 3.3 as a reading of Hw and Bu, while the engraved staff shows the lemma.

Both historians also fitted the words to the notes differently from the manuscript from bar 41 onwards, at *Wel singes thu cuccu, ne swik thu nauer nu*.[^b41] Rockstro gives no details of their underlay, and the example therefore records only the fact. The one modern copy Rockstro knew went further and left out the pes, reducing the rota to four parts.

:::paragraphs{style="colophon"}
Set in Crimson Pro, Cormorant Garamond and Source Sans 3 (SIL OFL); music in Leipzig, engraved by Verovio · Text: original, CC BY 4.0 · Music: *Sumer is icumen in* (13th century), after W. S. Rockstro’s score in Grove’s *Dictionary of Music and Musicians*, iii, 766–767 (public domain)
:::

[^ro]: W. S. Rockstro, “Sumer is icumen in”, in G. Grove (ed.), *A Dictionary of Music and Musicians*, iii, 765–768, continued in iv, 1–3 (London: Macmillan, 1900 printing). The score, iii, 766–767, is cited from the Wikisource transcription of that printing.

[^values]: Ro, iii, 765. The tailed lozenge on the first note of *in* is the one exception (see the note on bar 4).

[^b1]: **b. 1, n. 2** E ] F *H* before correction (“The second note on Stave 1 was originally an F”, Ro, iv, 1). The reading before correction is encoded as a variant in Example 3.1.

[^b2]: **b. 2, n. 2 – b. 4, n. 1** *Chri-sti-co-la* ] abbreviated with the Greek letters *chi* and *rho* *H*, Ro. Expanded as Rockstro glosses it, *Christicola* (iii, 765).

[^b4]: **b. 4, nn. 1–2** dotted minim, crotchet ] one lozenge with a tail *H*, the only tailed lozenge in the manuscript, which Rockstro could “scarcely conceive to be accidental” (iii, 765).

[^bflat]: **bb. 1–7, signature** B flat ] *B rotundum* *H*. The flat is printed in the signature, as in Ro.

[^rubric]: The rubric is quoted from the transcription and translation in the Wikipedia article “Sumer is icumen in”, which also gives the folio. Rockstro scores the canon “in exact accordance with the Latin directions” (iii, 765) but does not print them.

[^lig]: **Pes, bb. 1–8** both ligatures misread at every statement *Hw*; corrected *Bu* (Ro, iii, 765). Rockstro does not say how Hawkins read them, so no variant is encoded.

[^fifths]: Ro, iv, 2. The bar numbers refer to Rockstro’s score and to the examples of this chapter.

[^hawkins]: Ro, iii, 765. Rockstro dates Hawkins’s first volume 1770; the *General History* appeared in 1776.

[^continent]: Ro, iii, 768, and iv, 1, citing Forkel, *Allgemeine Geschichte der Musik*, ii, 490–500; Ambros, *Geschichte der Musik*, ii, 473–475; Coussemaker, *L’Art harmonique*, 144.

[^wulstan]: D. Wulstan, “‘Sumer is icumen in’: A Perpetual Puzzle-Canon?”, *Plainsong and Medieval Music* 9 (2000), 1–17, at 8. Wulstan proposes W. de Wycombe as the scribe.

[^b40]: **b. 40, n. 2** A ] G *Hw*, *Bu* (and Busby 1819, after *Bu*), at every entry of the phrase (Ro, iii, 765). Encoded as a variant in Example 3.3.

[^b41]: **bb. 41–47, underlay** as *H* ] text placed otherwise under the notes *Hw*, *Bu* (Ro, iii, 765).
`; // content.<lang>.md, inlined by the Cookbook

// ─── 3 · Fonts ──────────────────────────────────────────────────────────────
// Every face the design uses, loaded before the first build with the ones the config names.
const FONTS = {
  'Crimson Pro': ['400', '400i', '600', '600i', '700'],
  'Cormorant Garamond': ['500i', '600i'],
  'Source Sans 3': ['400', '600', '700'],
};

// ─── 4 · Build & show ───────────────────────────────────────────────────────
await initMusicEngine(); // Verovio, loaded only by books that hold a score
const doc = await buildDocumentWithFonts({ markdown }, config, kitFonts(FONTS));
showPages(doc, { title: t({ en: 'Music examples with a critical apparatus',
  es: 'Ejemplos musicales con aparato crítico' }) });
offerPdf(() => renderToPdf(doc, { fontProvider: fontsourceProvider }), `${RECIPE}.pdf`);

// ─── Kit ── helpers shared by every Cookbook recipe · postext.dev/cookbook ─────

// ─── Kit · core v1 ── the same in every recipe · postext.dev/cookbook
function mm(value) { return { value, unit: 'mm' }; }
function pt(value) { return { value, unit: 'pt' }; }
function em(value) { return { value, unit: 'em' }; }
/** The sample language's string: t({ en: 'Figure', es: 'Figura' }). */
function t(strings) { return strings[LANG] ?? Object.values(strings)[0]; }
/** A file in this recipe's assets folder, served from the Postext repo by jsDelivr. */
function asset(file) { return `https://cdn.jsdelivr.net/gh/drnachio/postext@main/cookbook/${RECIPE}/assets/${file}`; }

// ─── Kit · fonts v3 ── the same in every recipe · postext.dev/cookbook
// Postext loads the faces a config asks for before layout (prepareFonts,
// buildDocumentWithFonts) and asks the kit for the files: Fontsource's, the
// files the PDF embeds too.

/** The options of buildDocumentWithFonts / prepareFonts / withLoadedFonts:
 *  the Fontsource resolver, and the faces of FONTS loaded with the ones the
 *  config names. FONTS = { 'Family Name': ['400', '400i', '700'] }. */
function kitFonts(FONTS = {}) {
  const faces = Object.entries(FONTS).flatMap(([family, specs]) => specs.map((spec) =>
    ({ family, weight: parseInt(spec, 10), style: spec.endsWith('i') ? 'italic' : 'normal' })));
  return { resolve: fontsourceResolver, faces };
}

/** A face's Fontsource files, in a fixed order: latin, then latin-ext and greek
 *  when `text` needs them and the family ships them (kitSubsetsFor), each with
 *  its unicode-range. Null for a weight or slant Fontsource does not ship. */
async function fontsourceResolver(family, weight, style, { text = '' } = {}) {
  const id = fontsourceId(family);
  const meta = await fontsourceMeta(family);
  if (meta && !(meta.weights.includes(weight) && meta.styles.includes(style))) return null;
  kitStatus('Loading fonts…');
  const at = await fontsourceAt(id);
  return ['latin', ...kitSubsetsFor(text, meta)].map((subset) => ({
    source: `${at}files/${id}-${subset}-${weight}-${style}.woff2`,
    unicodeRange: kitRanges()[subset],
  }));
}

/** The unicode-range of each Fontsource file the kit loads (a function: the
 *  kit is inlined after the pen's code, which runs before a const would be set). */
function kitRanges() {
  return {
    latin: 'U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,'
      + 'U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD',
    'latin-ext': 'U+0100-02BA,U+02BD-02C5,U+02C7-02CC,U+02CE-02D7,U+02DD-02FF,U+0304,U+0308,U+0329,'
      + 'U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF',
    greek: 'U+0370-03FF',
  };
}

/** For the cjk, arabic and comics blocks, which add their own files: the
 *  faces of `faces` = { 'Family Name': ['400', '400i', '700'] }. `text` is the sample:
 *  č ł † α χ also load latin-ext and greek files (kitSubsetsFor). With
 *  `optional`, a face Fontsource does not ship is skipped instead of failing.
 *  The files are added once all have loaded, in the order asked (latin, then
 *  latin-ext, then greek), whatever order they arrive in: the last one added
 *  wins a character two files hold. Resolves to the number of faces added. */
async function loadFonts(faces, text = '', { optional = false } = {}) {
  kitStatus('Loading fonts…');
  const ranges = kitRanges();
  const jobs = [];
  for (const [family, specs] of Object.entries(faces)) {
    const id = fontsourceId(family);
    const todo = [...new Set(specs)].map((spec) => [parseInt(spec, 10), spec.endsWith('i') ? 'italic' : 'normal'])
      .filter(([weight, style]) => !hasFace(family, weight, style)); // before any await
    const at = fontsourceAt(id);
    const meta = optional || /[^\0-ÿ]/u.test(text) ? await fontsourceMeta(family) : null;
    const subsets = ['latin', ...kitSubsetsFor(text, meta)];
    for (const [weight, style] of todo) {
      if (optional && !(meta?.weights.includes(weight) && meta.styles.includes(style))) continue;
      for (const subset of subsets) {
        const url = `${await at}files/${id}-${subset}-${weight}-${style}.woff2`;
        const face = new FontFace(family, `url(${url}) format('woff2')`,
          { weight: String(weight), style, unicodeRange: ranges[subset] });
        jobs.push(face.load().catch(() => {
          if (subset === 'latin' && !optional) throw new Error(`Fontsource has no ${family} ${weight} ${style}`);
        }));
      }
    }
  }
  const ready = await Promise.all(jobs).catch((error) => { kitFail(error); throw error; });
  return ready.filter((face) => face && document.fonts.add(face)).length;
}

/** A loaded FontFace covers this family, weight and style (fonts.check() would
 *  also say yes for families nobody declared). */
function hasFace(family, weight, style) {
  for (const face of document.fonts) {
    if (face.status !== 'loaded' || face.style !== style) continue;
    if (face.family.replace(/^["']|["']$/g, '') !== family) continue;
    const [low, high = low] = face.weight.split(' ').map(Number);
    if (weight >= low && weight <= high) return true;
  }
  return false;
}

/** The files beyond latin `text` needs that `meta`'s family ships. */
function kitSubsetsFor(text, meta) {
  return [[/[Ā-˿ᴀ-ᶿḀ-ỿ†ℓⱠ-Ɀ꜠-ꟿ]/u, 'latin-ext'], [/[Ͱ-Ͽ]/u, 'greek']]
    .filter(([re, x]) => re.test(text) && meta?.subsets?.includes(x)).map(([, x]) => x);
}

/** Fontsource's id for a family: 'Source Serif 4' → 'source-serif-4'. */
function fontsourceId(family) { return family.toLowerCase().replace(/\s+/g, '-'); }

/** A package's folder at the one version @5 names now (#766). */
function fontsourceAt(id, u = `https://cdn.jsdelivr.net/npm/@fontsource/${id}@`) {
  return (fontsourceAt.c ??= {})[id] ??= fetch(u + '5/package.json').then((r) => r.json())
    .then((p) => p.version || 5, () => 5).then((v) => `${u}${v}/`);
}

/** The family's Fontsource metadata (weights, styles, subsets), or null. */
function fontsourceMeta(family) {
  fontsourceMeta.cache ??= new Map();
  const id = fontsourceId(family);
  if (!fontsourceMeta.cache.has(id)) {
    fontsourceMeta.cache.set(id, fetch(`https://api.fontsource.org/v1/fonts/${id}`)
      .then((res) => (res.ok ? res.json() : null), () => null));
  }
  return fontsourceMeta.cache.get(id);
}

// ─── Kit · viewer v2 ── the same in every recipe · postext.dev/cookbook
/** The pages as spreads on a dark desk, page 1 alone, then verso | recto,
 *  each painted when it scrolls near. Other options go to renderPageToCanvas. */
function showPages(docs, { title, width = 460, ...render } = {}) {
  const root = viewer(title);
  const pages = [docs].flat().flatMap((doc) =>
    doc.pages.map((page) => ({ doc, page, n: (doc.pageIndexOffset ?? 0) + page.index })));
  const spreads = [];
  let verso = null;
  for (const p of pages) {
    if (p.n % 2 === 1) { if (verso) spreads.push([verso, null]); verso = p; }
    else { spreads.push([verso, p]); verso = null; }
  }
  if (verso) spreads.push([verso, null]);
  const density = Math.min(window.devicePixelRatio || 1, 2);
  showPages.painter?.disconnect();
  const painter = new IntersectionObserver((entries) => {
    for (const { isIntersecting, target } of entries) {
      if (!isIntersecting) continue;
      painter.unobserve(target);
      const { doc, page } = target.postext;
      renderPageToCanvas(page, doc, target, { ...render, scale: (width * density) / page.width });
    }
  }, { rootMargin: '800px' });
  showPages.painter = painter;
  root.replaceChildren(...spreads.map((pair) => {
    const spread = document.createElement('div');
    spread.className = 'pt-spread';
    for (const p of pair) {
      const figure = document.createElement('figure');
      if (p) {
        const label = p.page.pageLabel || String(p.n + 1);
        const canvas = document.createElement('canvas');
        canvas.postext = p;
        canvas.style.aspectRatio = `${p.page.width} / ${p.page.height}`;
        canvas.setAttribute('role', 'img');
        canvas.setAttribute('aria-label', `Page ${label}`);
        const folio = document.createElement('figcaption');
        folio.textContent = label;
        figure.append(canvas, folio);
        painter.observe(canvas);
      } else figure.className = 'pt-blank';
      spread.append(figure);
    }
    return spread;
  }));
  kitStatus(`${pages.length} ${pages.length === 1 ? 'page' : 'pages'}`);
  document.documentElement.dataset.postext = 'ready';
  return pages.length;
}

/** The desk, the bar and the error reporting, created once. */
function viewer(title) {
  if (!document.getElementById('pt-kit')) {
    document.head.insertAdjacentHTML('beforeend', `<style id="pt-kit">
      :root { color-scheme: dark; }
      body { margin: 0; background: #0e1014; color: #b9bcc4; font: 13px/1.45 system-ui, sans-serif; }
      #pt-bar { position: sticky; top: 0; z-index: 1; display: flex; flex-wrap: wrap; align-items: center;
        gap: 6px 16px; padding: 10px 16px; background: rgb(14 16 20 / .92); backdrop-filter: blur(6px);
        border-bottom: 1px solid #23262d; }
      #pt-bar strong { color: #f4f1ea; font-weight: 600; }
      #pt-actions { display: flex; gap: 12px; margin-left: auto; }
      #pt-actions a, #pt-actions button { color: #d8a21a; font: inherit; background: none; border: 0; padding: 0; cursor: pointer; }
      #pages { display: grid; justify-items: center; gap: 48px; padding: 32px 16px 72px; }
      .pt-spread { display: flex; }
      .pt-spread figure { margin: 0; width: min(460px, 44vw); }
      .pt-spread canvas { display: block; width: 100%; background: #fff;
        box-shadow: 0 1px 2px rgb(0 0 0 / .5), 0 22px 44px -16px rgb(0 0 0 / .8); }
      .pt-spread figure:first-child canvas { box-shadow: inset -14px 0 14px -14px rgb(0 0 0 / .18), 0 1px 2px rgb(0 0 0 / .5), 0 22px 44px -16px rgb(0 0 0 / .8); }
      .pt-spread figcaption { margin-top: 10px; text-align: center; font: 600 10px/1 system-ui, sans-serif;
        letter-spacing: .18em; text-transform: uppercase; color: #6c7079; }
      .pt-blank { visibility: hidden; }
      @media (max-width: 760px) {
        .pt-spread { flex-direction: column; gap: 32px; }
        .pt-spread figure { width: min(460px, 92vw); }
        .pt-blank { display: none; }
      }
    </style>`);
    document.body.insertAdjacentHTML('afterbegin',
      '<header id="pt-bar"><strong id="pt-title"></strong><span id="pt-status" role="status"></span><span id="pt-actions"></span></header>');
    document.getElementById('pt-title').textContent = document.title || 'Postext';
    addEventListener('error', (event) => kitFail(event.error ?? event.message));
    addEventListener('unhandledrejection', (event) => kitFail(event.reason));
  }
  if (title) document.getElementById('pt-title').textContent = title;
  return document.getElementById('pages')
    ?? document.body.appendChild(Object.assign(document.createElement('main'), { id: 'pages' }));
}

function kitStatus(text) {
  viewer();
  document.getElementById('pt-status').textContent = text;
}

function kitFail(error) {
  document.documentElement.dataset.postext = 'error';
  kitStatus(`Error: ${error?.message ?? error}`);
}

// ─── Kit · pdf v3 ── the same in every recipe that exports a PDF
/** The Fontsource files the screen used, as TrueType, with their range and
 *  place: the nearest weight the family ships, upright with no italic;
 *  latin, then what its letters need (kitSubsetsFor). */
async function fontsourceProvider(family, weight, style, request) {
  const id = fontsourceId(family);
  const meta = await fontsourceMeta(family);
  const weights = meta?.weights?.length ? meta.weights : [400, 700];
  const w = weights.reduce((a, b) => (Math.abs(b - weight) < Math.abs(a - weight) ? b : a));
  const s = style === 'italic' && meta && !meta.styles.includes('italic') ? 'normal' : style;
  const text = String.fromCodePoint(...(request?.codePoints ?? []));
  const files = await Promise.all(['latin', ...kitSubsetsFor(text, meta)].map(async (subset, order) => {
    const res = await fetch(`${await fontsourceAt(id)}files/${id}-${subset}-${w}-${s}.woff2`);
    if (!res.ok) throw new Error(`Fontsource has no ${family} ${w} ${s} ${subset}`);
    return Object.assign(await decompressWoff2(new Uint8Array(await res.arrayBuffer())),
      { unicodeRange: kitRanges()[subset], order });
  }));
  return files.length === 1 ? files[0] : files;
}

/** A "Build the PDF" button; then "Open the PDF" (a new tab: CodePen's frame
 *  shows no PDFs) and a download link. */
function offerPdf(makePdf, filename) {
  viewer();
  const button = Object.assign(document.createElement('button'), { type: 'button', textContent: 'Build the PDF' });
  button.dataset.postextPdf = filename;
  button.addEventListener('click', async () => {
    button.disabled = true;
    button.textContent = 'Building the PDF…';
    try {
      const bytes = await makePdf();
      const url = URL.createObjectURL(new Blob([bytes], { type: 'application/pdf' }));
      const size = `${Math.max(1, Math.round(bytes.length / 1024))} KB`;
      button.replaceWith(
        Object.assign(document.createElement('a'), { href: url, target: '_blank', rel: 'noopener', textContent: 'Open the PDF ↗' }),
        Object.assign(document.createElement('a'), { href: url, download: filename, textContent: `Download ${filename} · ${size}` }));
    } catch (error) {
      button.disabled = false;
      button.textContent = 'Build the PDF';
      kitFail(error);
    }
  });
  document.getElementById('pt-actions').append(button);
}

// ─── /Kit ───────────────────────────────────────────────────────────────────────
````

## 变化

### 每个小节都标号

每行谱上有很多校勘条目时，每个小节都有号码更便于查找。

```diff
-  barNumbers: 'system', // the apparatus is keyed by bar: number every system
+  barNumbers: 'every',
```

### 改用Bravura刻印谱例

Bravura的符头更粗，适合铜版刻印风格的出版社。

```diff
-  musicFont: 'Leipzig', // lighter than Bravura, close to a 19th-century plate
+  musicFont: 'Bravura',
```

## 常见问题

- MEI 5的调号写成 `scoreDef` 或 `staffDef` 上的 `keysig="1f"`。MEI 4的属性 `key.sig` 会被悄悄忽略，降号随之消失。
- 每个 `staffDef` 都要写 `lines="5"`：没有它，音符和谱号会刻在没有谱线的空白上。
- 刻印的是 `<app>` 中的 `<lem>`，不是 `<rdg>`。需要读者在页面上看到的异文，应写进校勘注或另立一个谱例。

## 致谢

- 食谱: Ignacio Ferro ([@drnachio](https://github.com/drnachio))
- 文字: The chapter text and the apparatus notes, in English and Spanish: Ignacio Ferro, CC-BY-4.0
- 文字: Sumer is icumen in (13th century), the music examples transcribed from W. S. Rockstro's score and the readings his article reports, in A Dictionary of Music and Musicians, ed. George Grove, vol. 3, pp. 765–768, and vol. 4, pp. 1–3 (Macmillan, 1900 printing), as transcribed on Wikisource: Anonymous; William Smyth Rockstro ([来源](https://en.wikisource.org/wiki/A_Dictionary_of_Music_and_Musicians/Sumer_is_icumen_in)), 公有领域
- 文字: The Latin rubric of Harley MS 978, f. 11v, and the folio, as quoted in the Wikipedia article (fact check): Wikipedia contributors ([来源](https://en.wikipedia.org/wiki/Sumer_is_icumen_in)), CC-BY-SA-4.0
- 字体: Crimson Pro (OFL-1.1), Cormorant Garamond (OFL-1.1), Source Sans 3 (OFL-1.1)
- 代码: MIT · 示例内容: CC-BY-4.0

## 相关食谱

- [No. 071 · 脚注排在引用它的那一栏底部](https://postext.dev/zh/cookbook/novel-footnotes-column-foot.md): Markdown的[^id]标记变成编号注释，排在引用它的那一栏底部；另有开关可切换为单栏、章末注和连续编号。 · 难度 2 (中级) · 小说、戏剧与文学散文
- [No. 044 · 校勘本：行号与按行号编排的注释](https://postext.dev/zh/cookbook/critical-edition-line-numbers.md): 按1645年文本排弥尔顿的《Lycidas》：lineNumbers给诗行计数，把每第五行的行号印在侧栏里，每条注释以其行号开头，行号排成行内标签。 · 难度 3 (高级) · 诗歌
- [No. 163 · 用ABC写谱例的和声教程](https://postext.dev/zh/cookbook/harmony-textbook-abc-examples.md): 和声教程中讲终止式的一章：谱例是写在Markdown中的ABC代码块，按栏宽刻印成谱，其中最长的一例像段落一样从一页接排到下一页。 · 难度 2 (中级) · 教科书
