En bref
Un chapitre sur les cadences d’un traité d’harmonie de conservatoire, en deux colonnes. Ses cinq exemples musicaux sont écrits comme du texte, gravés en partitions, numérotés comme des figures et placés près du paragraphe qui les cite.
Ce que vous allez composer
Le chapitre 4 de Four-Part Harmony, une méthode pour les premières années de conservatoire, sur une page de 210 × 280 mm en deux colonnes de Crimson Pro. La leçon porte sur les cadences, et ses cinq exemples sont des chorals à quatre voix sur deux portées, avec les degrés en chiffres romains sous la basse : les cinq cadences de sol majeur, trois vers de Nun danket alle Gott et le psaume 134 de Genève (l’Old Hundredth) en entier. Chacun est numéroté de Example 4.1 à 4.5 (Ejemplo dans l’édition espagnole), légendé dans le caractère des étiquettes avec un libellé bordeaux, et placé en tête ou en pied de la colonne qui suit le paragraphe qui le cite ; le psaume entier court sur les deux colonnes en tête de la page 63. Les harmonisations ont été écrites pour cette méthode et vérifiées : quintes et octaves parallèles, écarts entre les voix, résolution de la sensible.
Cette recette répond à
- Comment numéroter les exemples musicaux et les faire flotter comme des figures dans un livre à deux colonnes ?
- Comment ajouter une figure avec une légende numérotée et la citer dans le texte (« voir fig. 3.2 ») ?
- Comment choisir où va une figure : en haut de page, sur les deux colonnes, exactement ici ou dans la marge ?
La réponse courte
// In the Markdown, :ref{id="ex-old-hundredth" style="full"} prints 'Example 4.4' ('Ejemplo
// 4.4' in Spanish) and ::resource{id="…"} anchors the float after the paragraph that cites it.
const example = (id, source, placement, styleId) => {
const [caption, note, altText] = CAPTIONS[id];
return { id, typeId: 'example', kind: 'score', caption, note, altText, placement,
score: { format: 'humdrum', source, styleId }, createdAt: 0, updatedAt: 0 };
};
// The **kern sources are in #region art-scores; captions in content.captions.<lang>.md.
const resources = () => [
example('ex-cadences', CADENCES, { position: 'bottom' }, 'column'),
example('ex-nun-danket', NUN_DANKET, { position: 'top' }, 'column'),
example('ex-nun-danket-3', NUN_DANKET_3, { position: 'auto' }, 'column'),
// Across both columns in the document's own score style: the whole tune in two systems.
example('ex-old-hundredth', OLD_HUNDREDTH, { position: 'top', span: 'page' }),
example('ex-deceptive', DECEPTIVE, { position: 'auto', span: 'page' }),
];
Ingrédients
- Fonctionnalités
- Exemples musicaux numérotésPlacement des figuresDes citations qui placent les figuresNotation musicaleStyle de partitionLégendes numérotéesFigure et Tableau dans votre langueTypes de ressource personnalisésStyle des légendesLignes de source et de créditUne ou deux colonnesOuvertures composéesAttributs de titreTitres courants et foliosMarges en miroirTitres courants selon le rôle de la pageEncadrésStyles de paragraphePalette de couleurs sémantique
- Typographie
- Crimson Pro, Playfair Display, Fira Sans Condensed (SIL OFL 1.1)
- Ressources
- Aucune : chaque image est dessinée en code
Préparation
#1 · Écrire chaque choral en Humdrum **kern
// Each example lists its spines as Humdrum wants them, lowest first: the bass staff split into
// tenor and bass, the **harm spine (printed under that staff), the treble staff split into
// soprano and alto. Writing them with spaces keeps the pen free of tab characters.
const kern = (meter, body) => [
'**kern **harm **kern', '*clefF4 * *clefG2', '*k[f#] * *k[f#]', `*M${meter} * *M${meter}`,
'*^ * *^', ...body.trim().split('\n'), '== == == == ==', '*- *- *- *- *-',
].map((row) => row.trim().split(/ +/)
// Spanish harmony books write every degree in capitals: vi is VI, iib is IIb.
.map((cell) => (LANG === 'es' && /^(?:i{1,3}|iv|vi{0,2})[bc7]?$/.test(cell)
? cell.replace(/^[iv]+/, (degree) => degree.toUpperCase()) : cell))
.join('\t')).join('\n');
Humdrum écrit les voix en colonnes séparées par des tabulations, de la plus grave à la plus aiguë. Chaque portée est une colonne (spine) divisée en deux par *^ : ténor et basse sur la portée de fa, soprano et alto sur celle de sol, si bien que le graveur met deux voix par portée avec leurs hampes opposées (la voix de gauche vers le haut, celle de droite vers le bas). Une colonne **harm après la portée de basse imprime les degrés sous celle-ci ; Ib est le premier renversement, Ic la quarte et sixte. Le script écrit les colonnes avec des espaces et les change en tabulations : aucun éditeur ne peut casser le fichier, et l’édition espagnole reçoit ses degrés en capitales (VI, IIb), comme les écrivent les traités d’harmonie espagnols.
#2 · Faire de chaque exemple une ressource de type exemple
La réponse courte construit les cinq ressources. typeId: 'example' prend le type Exemple intégré, que le moteur ajoute aux types de ressources du livre quand une ressource l’utilise, nommé dans la langue du document (locale: 'es' donne Ejemplo) et numéroté par chapitre : le premier exemple du chapitre 4 est le 4.1. Une ressource partition reste entière et flotte comme une figure : position: 'top' ou 'bottom' prend la tête ou le pied de la prochaine colonne qui a de la place après la citation, et span: 'page' étend l’Old Hundredth sur les deux colonnes. Légendes, crédits et textes alternatifs viennent d’un second fichier Markdown par édition.
#3 · Donner aux exemples de colonne une portée plus petite
const scoreStyle = {
staffSize: mm(5.4), musicFont: 'Bravura', barNumbers: 'system', color: col('ink'),
textFont: TEXT, // the Roman numerals under the bass, in the text face
marginTop: pt(4), marginBottom: pt(2),
};
const scoreStyles = [ // rastral 4.6 mm: four staves of a chorale fit a column in two systems
{ id: 'column', name: t({ en: 'Column examples', es: 'Ejemplos de columna' }),
staffSize: mm(4.6), barNumbers: 'none', spacing: 0.8 },
];
Les deux chorals pleine largeur suivent le scoreStyle du document, avec des portées de 5,4 mm et des numéros de mesure au début de chaque système. Les exemples de colonne choisissent le style nommé column : portées de 4,6 mm, pas de numéros de mesure et un espacement de 0,8, qui garde les cinq mesures de l’exemple 4.2 sur un seul système au lieu de laisser la dernière seule sur un second.
#4 · Démarrer le graveur avant la composition
await initMusicEngine(); // until it runs, every score is laid out as a grey box
const continuation = { pageNumbering: { startAt: 61 }, // odd: the opener is a recto
headings: { h1: 3, h2: 0, h3: 0, h4: 0, h5: 0, h6: 0 } }; // the next # is chapter 4
const doc = await buildDocumentWithFonts({ markdown, resources: resources(), continuation },
config, kitFonts(FONTS));
showPages(doc, { title: t({ en: 'Numbered chorale examples in a harmony method',
es: 'Corales numerados en un método de armonía' }) });
initMusicEngine() charge Verovio, le graveur, depuis le paquet ; tant qu’il ne tourne pas, chaque partition est mise en page comme une boîte grise. La continuation fait de ce chapitre le quatrième d’un livre plus long : les exemples sont numérotés de 4.1 à 4.5 et les folios commencent à 61, sur une belle page.
#5 · Ouvrir le chapitre sur les deux colonnes
const label = { fontFamily: LABEL, fontWeight: 600, textTransform: 'uppercase' };
const opener = {
enabled: true, minHeight: mm(46),
slot: { elements: [
{ kind: 'text', id: 'kicker', content: '{attr.kicker}', ...label, fontSize: pt(9),
letterSpacing: pt(1.8), color: col('accent'), align: 'left',
placement: { anchor: { to: 'container', edge: 'top-left' },
offset: { x: mm(0), y: mm(6) } } },
{ kind: 'text', id: 'title', content: '{titleText}', fontFamily: DISPLAY, fontSize: pt(36),
lineHeight: 1.05, fontWeight: 400, color: col('ink'), align: 'left',
placement: { anchor: { to: '#kicker', edge: 'below' }, offset: { x: mm(0), y: mm(3) },
size: { width: mm(150), height: 'auto' } } },
{ kind: 'text', id: 'deck', content: '{attr.deck}', fontFamily: TEXT, fontSize: pt(13),
lineHeight: 1.25, italic: true, color: col('muted'), align: 'left',
placement: { anchor: { to: '#title', edge: 'below' }, offset: { x: mm(0), y: mm(3) },
size: { width: mm(120), height: 'auto' } } },
{ kind: 'rule', id: 'rule', direction: 'horizontal', thickness: pt(1.5), color: col('accent'),
placement: { anchor: { to: '#deck', edge: 'below' }, offset: { x: mm(0), y: mm(5) },
size: { width: mm(18) } } },
] },
};
L’ouverture prend les deux colonnes (span: 'page' sur le titre de niveau un) et tire son surtitre et son chapeau des attributs du titre. minHeight descend le texte de 46 mm, si bien que les deux colonnes commencent sur la même ligne sous le filet.
La recette complète
// ═══ Postext Cookbook · Nº 166 · Numbered chorale examples in a harmony method ══
// https://postext.dev/en/cookbook/conservatory-method-chorales
// Code: MIT · Text and four-part settings: original (CC BY 4.0) · Melodies: public domain
// Fonts: Crimson Pro, Playfair Display, Fira Sans Condensed (OFL 1.1) · Needs postext ≥ 1.54.0
import {
buildDocumentWithFonts, renderPageToCanvas, defaultResourceTypes, initMusicEngine,
} from 'https://esm.sh/postext';
const LANG = 'en'; // @lang: the language of the sample document ('en' | 'es')
const RECIPE = 'conservatory-method-chorales';
// ─── 1 · Design ─────────────────────────────────────────────────────────────
// A claret accent on warm paper tones, every colour linked by id.
const palette = { ink: '#1f1a17', accent: '#7a2531', tint: '#f5eee6', rule: '#cdbfb2',
muted: '#6a5f57', 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' } },
];
const [TEXT, DISPLAY, LABEL] = ['Crimson Pro', 'Playfair Display', 'Fira Sans Condensed'];
const OUTER = 16; // outer margin in mm: running heads and folios align to it
const LEAD = 14.2; // body leading in pt
// #region answer: score resources typed 'example', floated and numbered like figures
// In the Markdown, :ref{id="ex-old-hundredth" style="full"} prints 'Example 4.4' ('Ejemplo
// 4.4' in Spanish) and ::resource{id="…"} anchors the float after the paragraph that cites it.
const example = (id, source, placement, styleId) => {
const [caption, note, altText] = CAPTIONS[id];
return { id, typeId: 'example', kind: 'score', caption, note, altText, placement,
score: { format: 'humdrum', source, styleId }, createdAt: 0, updatedAt: 0 };
};
// The **kern sources are in #region art-scores; captions in content.captions.<lang>.md.
const resources = () => [
example('ex-cadences', CADENCES, { position: 'bottom' }, 'column'),
example('ex-nun-danket', NUN_DANKET, { position: 'top' }, 'column'),
example('ex-nun-danket-3', NUN_DANKET_3, { position: 'auto' }, 'column'),
// Across both columns in the document's own score style: the whole tune in two systems.
example('ex-old-hundredth', OLD_HUNDREDTH, { position: 'top', span: 'page' }),
example('ex-deceptive', DECEPTIVE, { position: 'auto', span: 'page' }),
];
// #endregion
// #region scores: one score style for the page-wide chorale, a smaller one for the columns
const scoreStyle = {
staffSize: mm(5.4), musicFont: 'Bravura', barNumbers: 'system', color: col('ink'),
textFont: TEXT, // the Roman numerals under the bass, in the text face
marginTop: pt(4), marginBottom: pt(2),
Le script.js composé s’exécute tel quel : collez-le dans le script de module de n’importe quelle page, ou ouvrez la recette sur CodePen. Dossier de la recette sur GitHub ↗ (s’ouvre dans un nouvel onglet)
Variantes
#Placer un exemple non numéroté dans le texte
Un bloc de code garde dans le texte une partition que le texte ne cite pas, comme une formule centrée : ni numéro ni légende.
- ::resource{id="ex-cadences"}
+ ```humdrum {style="column" alt="Les cinq cadences en sol majeur"}
+ **kern **harm **kern
+ …
+ ```#Les appeler exemples musicaux
Définissez un type à vous pour un autre nom et faites pointer typeId dessus.
- resourceTypes: defaultResourceTypes(LANG),
+ resourceTypes: [...defaultResourceTypes(LANG), { id: 'musex', name: 'Exemple musical',
+ shortLabel: 'Ex. mus.', captionPrefix: 'Exemple musical', numberingTemplate: '{h1}.{n}',
+ resetOn: 'h1', counterFormat: 'decimal' }],Erreurs fréquentes
Erreur fréquente
::resource{id="…"} n’accepte que les guillemets doubles
Une intégration en bloc n’est reconnue que sous la forme ::resource{id="…"} avec des guillemets doubles ; toute autre forme reste dans le texte comme une ligne visible. Des figures exactement ici →
- Une ressource partition ne se coupe jamais entre deux colonnes ou deux pages. Limitez les exemples flottants à quelques mesures et mettez une pièce plus longue dans un bloc de code, qui se coupe entre les systèmes.
- Le graveur remplit chaque système jusqu’à la largeur de son emplacement, et le dernier garde sa largeur naturelle. Quand un exemple laisse une mesure seule sur un second système, baissez le
spacingou la taille de portée de son style.
Crédits
- Recette
- Ignacio Ferro
- Texte
- Chapter text and the four-part settings of every example · Ignacio Ferro · CC BY 4.0
- Melody of Nun danket alle Gott (Praxis pietatis melica, 1647), read from the soprano of Bach's setting BWV 386 · Johann Crüger · domaine public
- Melody of the Old Hundredth (Psalm 134 in the Genevan Psalter, 1551) · Louis Bourgeois (attributed) · domaine public
- Polices
- Crimson Pro (SIL OFL 1.1) · Playfair Display (SIL OFL 1.1) · Fira Sans Condensed (SIL OFL 1.1)
Modifier ce texte ↗ (s’ouvre dans un nouvel onglet)Dossier de la recette sur GitHub ↗ (s’ouvre dans un nouvel onglet)


