LilyPond — Extension des fonctionnalités

Ce document fournit les informations de base pour étendre les fonctionnalités de LilyPond version 2.25.14.

Pour connaître la place qu’occupe ce manuel dans la documentation, consultez la page Manuels.

Si vous ne disposez pas de certains manuels, la documentation complète se trouve sur https://lilypond.org/.


1 Tutoriel Scheme

LilyPond recourt abondamment au langage de programmation Scheme, tant au niveau de la syntaxe de saisie que des mécanismes internes chargés de combiner les différents modules du logiciel. Les lignes qui suivent constituent un bref aperçu de la manière de saisir des données en Scheme. Si vous désirez en apprendre plus sur Scheme, n’hésitez pas à vous rendre sur https://www.Schemers.org.

Le Scheme utilisé par LilyPond repose sur l’implémentation GNU Guile ; celle-ci se base sur le standard Scheme « R5RS ». Si votre but est d’apprendre Scheme au travers de LilyPond, sachez que l’utilisation d’une autre implémentation ou d’un autre standard pourrait être source de désagrément. Vous trouverez plus d’information sur Guile à la page https://www.gnu.org/software/guile/ ; le standard Scheme « R5RS » est quant à lui disponible à la page https://www.Schemers.org/Documents/Standards/R5RS/.


1.1 Introduction à Scheme

Nous commencerons par nous intéresser à Scheme et à son fonctionnement, grâce à l’interpréteur Guile. Une fois plus à l’aise avec Scheme, nous verrons comment ce langage peut s’intégrer à un fichier LilyPond.


1.1.1 Le bac à sable de Scheme

L’installation de LilyPond comprend l’implémentation Guile de Scheme. Tous les paquetages de LilyPond disposent d’un « bac à sable » Scheme, accessible par la commande :

lilypond Scheme-sandbox

Une fois le bac à sable actif, vous obtiendrez l’invite :

guile>

Vous pouvez dès à présent saisir des expressions Scheme pour vous exercer. Si vous souhaitez pouvoir utiliser la bibliothèque GNU readline, qui offre une ligne de commande plus élaborée, consultez les informations contenues dans le fichier ly/Scheme-sandbox.ly. La bibliothèque readline, dans la mesure où elle est habituellement activée dans vos sessions Guile, devrait être effective y compris dans le bac à sable.


1.1.2 Scheme et les variables

Une variable Scheme peut contenir n’importe quelle valeur valide en Scheme, y compris une procédure Scheme.

Une variable Scheme se crée avec la fonction define :

guile> (define a 2)
guile>

L’évaluation d’une variable Scheme se réalise en saisissant le nom de cette variable à l’invite de Guile :

guile> a
2
guile>

Une variable Scheme s’affiche à l’écran à l’aide de la fonction display :

guile> (display a)
2guile>

Vous aurez remarqué que la valeur 2 et l’invite guile apparaissent sur une même ligne. On peut améliorer la présentation à l’aide de la procédure newline ou bien en affichant un caractère « retour chariot ».

guile> (display a)(newline)
2
guile> (display a)(display "\n")
2
guile>

Après avoir créé une variable, vous pouvez en modifier la valeur grâce à un set! :

guile> (set! a 12345)
guile> a
12345
guile>

Vous quitterez proprement le bac à sable à l’aide de l’instruction quit :

guile> (quit)

1.1.3 Types de données Scheme simples

L’un des concepts de base de tout langage est la saisie de données, qu’il s’agisse de nombres, de chaînes de caractères, de listes, etc. Voici les différents types de données Scheme simples utilisées couramment dans LilyPond.

Booléens

Les valeurs booléennes sont vrai ou faux. En Scheme, ce sera #t pour vrai, et #f pour faux.

Nombres

Les nombres se saisissent le plus communément : 1 est le nombre (entier) un, alors que -1.5 est un nombre à virgule flottante (un nombre non entier).

Chaînes

Les chaînes de caractères sont bornées par des guillemets informatiques :

"ceci est une chaîne"

Une chaîne peut s’étendre sur plusieurs lignes :

"ceci
est
une chaîne"

auquel cas les retours à la ligne seront inclus dans la chaîne.

Un caractère de retour à la ligne peut s’ajouter dans la chaîne, sous la forme d’un \n.

"ceci\nest une\nchaîne multiligne"

Guillemets et obliques inverses dans une chaîne doivent être précédés d’une oblique inverse. La chaîne \a dit "b" se saisit donc

"\\a dit \"b\""

Il existe bien d’autres types de données Scheme, dont nous ne parlerons pas ici. Vous en trouverez une liste exhaustive dans le guide de référence de Guile, à la page https://www.gnu.org/software/guile/docs/docs-1.8/guile-ref/Simple-Data-Types.html.


1.1.4 Types de données Scheme composites

Scheme prend aussi en charge des types de données composites. LilyPond utilise beaucoup les paires, listes, listes associatives et tables de hachage.


Paires

Le type de donnée composite fondamental est la paire (pair). Comme son nom l’indique, il s’agit de lier deux valeurs, à l’aide de l’opérateur cons.

guile> (cons 4 5)
(4 . 5)
guile>

Vous aurez noté que la paire s’affiche sous la forme de deux éléments bornés par des parenthèses et séparés par une espace, un point (.) et une autre espace. Le point n’est en aucune manière un séparateur décimal ; il s’agit de l’indicateur d’une paire.

Vous pouvez aussi saisir littéralement les valeurs d’une paire, en la faisant précéder d’une apostrophe.

guile> '(4 . 5)
(4 . 5)
guile>

Les deux éléments d’une paire peuvent être constitués de n’importe quelle valeur Scheme valide :

guile> (cons #t #f)
(#t . #f)
guile> '("blah-blah" . 3.1415926535)
("blah-blah" . 3.1415926535)
guile>

Les premier et second éléments de la paire sont accessibles à l’aide des procédures Scheme car et cdr.

guile> (define mypair (cons 123 "hello there")
… )
guile> (car mypair)
123
guile> (cdr mypair)
"hello there"
guile>

Note : cdr se prononce « couldeur », comme l’indiquent Sussman et Abelson – voir https://mitpress.mit.edu/sites/default/files/sicp/full-text/book/book-Z-H-14.html#footnote_Temp_133.


Listes

Autre structure de donnée commune en Scheme : la liste (list). Une liste « correcte » se définit comme étant vide (représentée par '() et de longueur 0) ou une paire dont le cdr est une liste.

Il existe plusieurs méthodes pour créer une liste, la plus courante étant l’utilisation de la procédure list :

guile> (list 1 2 3 "abc" 17.5)
(1 2 3 "abc" 17.5)

La représentation d’une liste par la succession de ses éléments, séparés par des espaces, bornée par des parenthèses, n’est en fait qu’une vue compacte des paires qui la constituent. Les paires sont ainsi dépourvues du point de séparation et de la parenthèse ouvrante qui le suit et des parenthèses fermantes. Sans ce « compactage », cette liste serait ainsi présentée :

(1 . (2 . (3 . ("abc" . (17.5 . ())))))

Vous pouvez donc saisir une liste comme elle serait présentée, en entourant ses éléments par des parenthèses à la suite d’une apostrophe (afin que ce qui suit ne soit pas interprété comme un appel à une fonction) :

guile> '(17 23 "foo" "bar" "bazzle")
(17 23 "foo" "bar" "bazzle")

Les listes ont une importance considérable en Scheme. Certains vont d’ailleurs jusqu’à considérer Scheme comme un dialecte du lisp, où « lisp » serait une abréviation de « List Processing ». Il est vrai que toute expression Scheme est une liste.


Listes associatives (alists)

Il existe un type particulier de liste : la liste associative – ou alist. Une alist permet de stocker des données dans le but de les réutiliser.

Une liste associative est une liste dont les éléments sont des paires. Le car de chacun des éléments constitue une clé (key) et chaque cdr une valeur (value). La procédure Scheme assoc permet de retrouver une entrée de la liste associative ; son cdr en fournira la valeur :

guile> (define mon-alist '((1  . "A") (2 . "B") (3 . "C")))
guile> mon-alist
((1 . "A") (2 . "B") (3 . "C"))
guile> (assoc 2 mon-alist)
(2 . "B")
guile> (cdr (assoc 2 mon-alist))
"B"
guile>

LilyPond recourt abondamment aux alists pour stocker des propriétés ou autres données.


Tables de hachage

Il s’agit d’une structure de données à laquelle LilyPond fait parfois appel. Une table de hachage (hash table) peut se comparer à une matrice ou un tableau dont l’index peut être n’importe quel type de valeur Scheme et ne se limitant pas à des nombres entiers.

Les tables de hachage sont un moyen plus efficace que les listes associatives lorsqu’il s’agit d’enregistrer de nombreuses données qui ne changeront que peu fréquemment.

La syntaxe permettant de créer une table de hachage peut paraître complexe, mais vous en trouverez de nombreux exemples dans les sources de LilyPond.

guile> (define h (make-hash-table 10))
guile> h
#<hash-table 0/31>
guile> (hashq-set! h 'cle1 "valeur1")
"valeur1"
guile> (hashq-set! h 'key2 "valeur2")
"valeur2"
guile> (hashq-set! h 3 "valeur3")
"valeur3"

La procédure hashq-ref permet de récupérer une valeur dans la table de hachage.

guile> (hashq-ref h 3)
"valeur3"
guile> (hashq-ref h 'cle2)
"valeur2"
guile>

La procédure hashq-get-handle permet de retrouver à la fois une clé et sa valeur. Cette procédure a l’avantage de renvoyer #f lorsque la clé n’existe pas.

guile> (hashq-get-handle h 'cle1)
(cle1 . "valeur1")
guile> (hashq-get-handle h 'zut)
#f
guile>

1.1.5 Scheme et les calculs

Scheme permet aussi d’effectuer des calculs. Il utilise alors un préfixe. Additionner 1 et 2 s’écrira (+ 1 2) et non 1+2 comme on aurait pu s’y attendre.

guile> (+ 1 2)
3

Les calculs peuvent s’imbriquer ; le résultat d’une fonction peut servir pour un autre calcul.

guile> (+ 1 (* 3 4))
13

Ces calculs sont un exemple d’évaluation : une expression telle que (* 3 4) est remplacée par sa valeur, soit 12.

En matière de calcul, Scheme fait la différence entre des nombres entiers ou non. Les calculs sur des nombres entiers seront exacts, alors que s’il s’agit de nombres non entiers, les calculs tiendront compte de la précision mentionnée :

guile> (/ 7 3)
7/3
guile> (/ 7.0 3.0)
2.33333333333333

Lorsque l’interpréteur Scheme rencontre une expression sous forme de liste, le premier élément de cette liste est considéré en tant que procédure qui prendra en argument le restant de la liste. C’est la raison pour laquelle, en Scheme, tous les opérateurs sont en préfixe.

Le fait que le premier élément d’une expression Scheme sous forme de liste ne soit pas un opérateur ou une procédure déclenchera une erreur de la part de l’interpréteur :

guile> (1 2 3)

Backtrace:
In current input:
  52: 0* [1 2 3]

<unnamed port>:52:1: In expression (1 2 3):
<unnamed port>:52:1: Wrong type to apply: 1
ABORT: (misc-error)
guile>

Vous pouvez constater que l’interpréteur a tenté de considérer 1 comme étant un opérateur ou une procédure, ce qu’il n’a pu réaliser. Il a donc renvoyé l’erreur « Wrong type to apply: 1 » (Application d’un type erroné : 1).

C’est pourquoi il est impératif, pour créer une liste, soit d’utiliser l’opérateur consacré (list), soit de faire précéder la liste d’une apostrophe, de telle sorte que l’interpréteur ne tente pas de l’évaluer.

guile> (list 1 2 3)
(1 2 3)
guile> '(1 2 3)
(1 2 3)
guile>

Vous pourrez être confronté à cette erreur lorsque vous intégrerez Scheme à LilyPond.


1.1.6 Scheme et les procédures

Une procédure Scheme est une expression Scheme qui renverra une valeur issue de son exécution. Les procédures Scheme sont capables de manipuler des variables qui ne sont pas définies en leur sein.


Définition de procédures

En Scheme, on définit une procédure à l’aide de l’instruction define :

(define (nom-fonction argument1 argument2 … argumentn)
 expression-Scheme-qui-donnera-une-valeur-en-retour)

Nous pourrions, par exemple, définir une procédure calculant la moyenne de deux nombres :

guile> (define (moyenne x y) (/ (+ x y) 2))
guile> moyenne
#<procedure moyenne (x y)>

Une fois la procédure définie, on l’appelle en la faisant suivre, dans une liste, des arguments qui doivent l’accompagner. Calculons maintenant la moyenne de 3 et 12 :

guile> (moyenne 3 12)
15/2

Prédicats

Une procédure Scheme chargée de retourner une valeur booléenne s’appelle un « prédicat » (predicate). Par convention, plutôt que par nécessité, le nom d’un prédicat se termine par un point d’interrogation :

guile> (define (moins-de-dix? x) (< x 10))
guile> (moins-de-dix? 9)
#t
guile> (moins-de-dix? 15)
#f

Valeurs de retour

Une procédure Scheme doit toujours renvoyer une valeur de retour, en l’occurrence la valeur de la dernière expression exécutée par cette procédure. La valeur de retour sera une valeur Scheme valide, y compris une structure de donnée complexe ou une procédure.

On peut avoir besoin de regrouper plusieurs expressions Scheme dans une même procédure. Deux méthodes permettent de combiner des expressions multiples. La première consiste à utiliser la procédure begin, qui permet l’évaluation de plusieurs expressions et renvoie la valeur de la dernière expression.

guile> (begin (+ 1 2) (- 5 8) (* 2 2))
4

Une deuxième méthode consiste à combiner les expressions dans un bloc let. Ceci aura pour effet de créer une série de liens, puis d’évaluer en séquence les expressions susceptibles d’inclure ces liens. La valeur renvoyée par un bloc let est la valeur de retour de la dernière clause de ce bloc :

guile> (let ((x 2) (y 3) (z 4)) (display (+ x y)) (display (- z 4))
… (+ (* x y) (/ z x)))
508

1.1.7 Scheme et les conditions


if

Scheme dispose d’une procédure if :

(if expression-test expression-affirmative expression-négative)

expression-test est une expression qui renverra une valeur booléenne. Dans le cas où expression-test retourne #t, la procédure if renvoie la valeur de expression-affirmative, et celle de expression-négative dans le cas contraire.

guile> (define a 3)
guile> (define b 5)
guile> (if (> a b) "a est plus grand que b" "a n'est pas plus grand que b")
"a n'est pas plus grand que b"

cond

Une autre manière d’introduire une condition en Scheme est d’utiliser l’instruction cond :

(cond (expression-test-1 expression-résultat-séquence-1)
      (expression-test-2 expression-résultat-séquence-2)
      …
      (expression-test-n expression-résultat-séquence-n))

Comme par exemple ici :

guile> (define a 6)
guile> (define b 8)
guile> (cond ((< a b) "a est plus petit que b")
…          ((= a b) "a égale b")
…          ((> a b) "a est plus grand que b"))
"a est plus petit que b"

1.2 Scheme et LilyPond


1.2.1 Syntaxe Scheme dans LilyPond

L’installation de LilyPond comprenant l’interpréteur Guile, les fichiers source LilyPond peuvent contenir du Scheme. Vous disposez de plusieurs méthodes pour inclure du Scheme dans vos fichiers LilyPond.

La méthode la plus simple consiste à insérer un hash (le caractère #, improprement appelé dièse) avant l’expression Scheme.

Rappelons-nous qu’un fichier source LilyPond est structuré en jetons et expressions, tout comme le langage humain est structuré en mots et phrases. LilyPond dispose d’un analyseur lexical (appelé lexer) qui sait identifier les jetons – nombres, chaînes, éléments Scheme, hauteurs, etc. – ainsi que d’un analyseur syntaxique (appelé parser). Dès lors que le programme sait quelle règle grammaticale particulière doit s’appliquer, il exécute les consignes qui lui sont associées.

Le recours à un hash pour mettre en exergue du Scheme est tout à fait approprié. Dès qu’il rencontre un #, l’analyseur lexical passe le relais au lecteur Scheme qui va alors déchiffrer l’intégralité de l’expression Scheme – ce peut être un identificateur, une expression bornée par des parenthèses ou bien d’autres choses encore. Une fois cette expression lue, elle est enregistrée en tant que valeur d’un élément grammatical SCM_TOKEN. Puisque l’analyseur syntaxique sait comment traiter ce jeton, il charge Guile d’évaluer l’expression Scheme. Dans la mesure où le parser requiert une lecture en avance de la part du lexer pour prendre une décision, cette distinction entre lecture et évaluation – lexer et parser – révèle toute sa pertinence lorsqu’il s’agit d’exécuter conjointement des expressions LilyPond et des expressions Scheme. C’est la raison pour laquelle nous vous recommandons, dans toute la mesure du possible, d’utiliser un signe hash lorsque vous faites appel à Scheme.

Une autre manière de faire appel à l’interpréteur Scheme à partir de LilyPond consiste à introduire une expression Scheme par un caractère dollar au lieu d’un caractère dièse – un $ au lieu d’un #. En pareil cas, LilyPond évalue le code dès sa lecture par l’analyseur lexical, vérifie le type d’expression Scheme qui en résulte et détermine un type de jeton (l’un des xxx_IDENTIFIER de la grammaire) qui lui correspond, puis en fait une copie qui servira à traiter la valeur de ce jeton. Lorsque la valeur de l’expression est void, autrement dit une valeur Guile *unspecified* (pour non spécifiée), aucune information n’est transmise à l’analyseur grammatical.

C’est, en réalité, la manière dont LilyPond opère lorsque vous rappelez une variable ou une fonction par son nom – au travers d’un \nom –, à la seule différence que sa finalité est déterminée par l’analyseur lexical de LilyPond sans consultation du lecteur Scheme ; le nom de la variable rappelée doit donc être en corrélation avec le mode LilyPond actif à ce moment là.

L’immédiateté de l’opérateur $ peut entraîner des effets indésirables dont nous reparlerons à la rubrique Saisie de variables et Scheme ; aussi est-il préférable d’utiliser un # dès que l’analyseur grammatical le supporte. Dans le cadre d’une expression musicale, une expression qui aura été créée à l’aide d’un # sera interprétée comme étant de la musique. Elle ne sera cependant pas recopiée avant utilisation. Si la structure qui l’abrite devait être réutilisée, un appel expicite à ly:music-deep-copy pourrait être requis.

Les opérateurs $@ et #@ agissent comme des « colleurs de liste » : leur fonction consiste à insérer tous les éléments d’une liste dans le contexte environnant.

Examinons à présent du vrai code Scheme. Nous pouvons définir des procédures Scheme au milieu d’un fichier source LilyPond :

#(define (moyenne a b c) (/ (+ a b c) 3))

Pour mémoire, vous noterez que les commentaires LilyPond (% ou %{…%}) ne peuvent s’utiliser dans du code Scheme, même si celui-ci se trouve au sein d’un fichier LilyPond. Ceci tient au fait que l’expression Scheme est lue par l’interpréteur Guile, et en aucune façon par l’analyseur lexical de LilyPond. Voici comment introduire des commentaires dans votre code Scheme :

; ceci n'est qu'une simple ligne de commentaire

#!
  Ceci constitue un bloc de commentaire (non imbricable)
  dans le style Guile.
  En fait, les Schemeurs les utilisent très rarement,
  et vous n'en trouverez jamais dans le code source
  de LilyPond.
!#

Dans la suite de notre propos, nous partons du principe que les données sont incluses dans un fichier musical, aussi toutes les expressions Scheme seront introduites par un #.

Toutes les expressions Scheme de haut niveau incluses dans un fichier LilyPond peuvent se combiner en une expression Scheme unique à l’aide de la clause begin :

#(begin
  (define foo 0)
  (define bar 1))

1.2.2 Variables LilyPond

Les variables LilyPond sont enregistrées en interne sous la forme de variables Scheme. Ainsi,

douze = 12

est équivalant à

#(define douze 12)

Ceci a pour conséquence que toute variable LilyPond peut être utilisée dans une expression Scheme. Par exemple, nous pourrions dire

vingtQuatre = #(* 2 douze)

ce qui aurait pour conséquence que le nombre 24 sera stocké dans la variable LilyPond (et Scheme) vingtQuatre.

Scheme autorise la modification d’expressions complexes au fil de l’eau, ce que réalise LilyPond dans le cadre des fonctions musicales. Toutefois, lorsqu’une expression musicale est stockée dans une variable plutôt que saisie au fur et à mesure, on s’attend, alors qu’elle est passée à une fonction musicale, à ce que sa valeur originale ne soit en rien modifiée. C’est la raison pour laquelle faire référence à une variable à l’aide d’une oblique inverse – autrement dit saisir \vingtQuatre – aura pour effet que LilyPond créera une copie de la valeur musicale de cette variable aux fins de l’utiliser au sein de l’expression musicale au lieu d’utiliser directement la valeur de cette variable.

Par voie de conséquence, une expression musicale introduite par # ne devrait pas contenir de matériau inexistant auparavant ou bien littéralement recopié, mais plutôt une référence explicite.

Voir aussi

Manuel d’extension : Syntaxe Scheme dans LilyPond.


1.2.3 Débogage de code Scheme

Lorsque l’on débogue de larges portions de code Scheme, il est bien appréciable que soit mise en évidence la ligne du programme à la source même de l’erreur. Lilypond permet la localisation dans les sources pour les erreurs Scheme dès lors d’ezst ajoutée à la ligne de commande l’option -dcompile-Scheme-code. L’ajout d’une ligne #(ly:set-option 'compile-Scheme-code) en tête d’un fichier LilyPond aura le même effet.

Par ailleurs, il est possible d’obtenir encore plus d’informations au sujet de l’erreur grâce à l’option -ddebug-eval – ou une ligne #(debug-enable 'backtrace) dans le fichier. Grâce à ce mode, et dès la survenance d’une erreur, s’affiche en mode verbeux une « trace inverse » contenant des informations sur tous les appels de fonction, ce qui permet de remonter le fil jusqu’àa la pierre d’achoppement.

Problèmes connus et avertissements

En raison d’une limitation dans l’implémentation de Guile, l’option -dcompile-Scheme-code ne permet pas de compiler des fichiers LilyPond comportant au-delà de quelques milliers d’expressions Scheme.


1.2.4 Saisie de variables et Scheme

Le format de saisie prend en charge la notion de variable – ou identificateur. Dans l’exemple suivant, une expression musicale se voit attribuer un identificateur qui portera le nom de traLaLa.

traLaLa = { c'4 d'4 }

Une variable a aussi une portée. Dans l’exemple suivant, le bloc \layout contient une variable traLaLa tout à fait indépendante de l’autre \traLaLa.

traLaLa = { c'4 d'4 }
\layout { traLaLa = 1.0 }

Dans les faits, chaque fichier a un domaine de compétence, et les différents blocs \header, \midi et \layout ont leur propre champ de compétence, imbriqué dans ce domaine principal.

Variables et champs de compétence sont implémentés par le système de modules de Guile. Un module anonyme Scheme est attaché à chacun de ces domaines. Une assertion telle que

traLaLa = { c'4 d'4 }

est convertie, en interne, en une définition Scheme :

(define traLaLa valeur Scheme de '')

Cela signifie que variables LilyPond et variables Scheme peuvent tout à fait se mélanger. Dans l’exemple suivant, un fragment musical est stocké dans la variable traLaLa puis dupliqué à l’aide de Scheme. Le résultat est alors importé dans un bloc \score au moyen d’une seconde variable twice.

traLaLa = { c'4 d'4 }

#(define newLa (map ly:music-deep-copy
  (list traLaLa traLaLa)))
#(define twice
  (make-sequential-music newLa))

\twice

[image of music]

Cet exemple est particulièrement intéressant. L’assignation n’interviendra qu’une fois que l’analyseur grammatical aura l’assurance que rien du type de \addlyrics ne suit ; il doit donc vérifier ce qui vient après. Le parser lit le # et l’expression Scheme qui le suit sans l’évaluer, de telle sorte qu’il peut procéder à l’assignation, et ensuite exécuter le code Scheme sans problème.


1.2.5 Import de code Scheme dans LilyPond

L’exemple précédent illustre la manière « d’exporter » une expression musicale à partir des saisies et à destination de l’interpréteur Scheme. L’inverse est aussi réalisable : en la plaçant derrière un $, une valeur Scheme sera interprétée comme si elle avait été saisie en syntaxe LilyPond. Au lieu de définir \twice, nous aurions tout aussi bien pu écrire

…
$(make-sequential-music newLa)

Vous pouvez utiliser $ suivi d’une expression Scheme partout où vous auriez utilisé \nom, dès lors que vous aurez assigné à cette expression Scheme le nom de variable nom. La substitution intervenant au niveau de l’analyseur lexical (le lexer), LilyPond ne saurait faire la différence.

Cette manière de procéder comporte cependant un inconvénient au niveau de la temporisation. Si nous avions défini newLa avec un $ plutôt qu’un #, la définition Scheme suivante aurait échoué du fait que traLaLa n’était pas encore défini. Pour plus d’information quant au problème de synchronisation, voir la rubrique Syntaxe Scheme dans LilyPond.

Une autre façon de procéder serait de recourir aux « colleurs de liste » $@ et #@ dont la fonction est d’insérer les éléments d’une liste dans le contexte environnant. Grâce à ces opérateurs, la dernière partie de notre fonction pourrait s’écrire ainsi :

…
{ #@newLa }

Ici, chaque élément de la liste stockée dans newLa est pris à son tour et inséré dans la liste, tout comme si nous avions écrit

{ #(premier newLa) #(deuxième newLa) }

Dans ces deux dernières formes, le code Scheme est évalué alors même que le code initial est en cours de traitement, que ce soit par le lexer ou par le parser. Si le code Scheme ne doit être exécuté que plus tard, consultez la rubrique Fonctions Scheme fantômes, ou stockez-le dans une procédure comme ici :

#(define (nopc)
  (ly:set-option 'point-and-click #f))

…
#(nopc)
{ c'4 }

1.2.6 Propriétés des objets

Les propriétés des objets sont stockées dans LilyPond sous la forme d’enchaînements de listes associatives, autrement dit des listes de listes associatives. Une propriété se détermine par l’ajout de valeurs en début de liste de cette propriété. Les caractéristiques d’une propriété s’ajustent donc à la lecture des différentes valeurs des listes associatives.

La modification d’une valeur pour une propriété donnée requiert l’assignation d’une valeur de la liste associative, tant pour la clé que pour la valeur associée. Voici comment procéder selon la syntaxe de LilyPond :

\override Stem.thickness = #2.6

Cette instruction ajuste l’apparence des hampes. Une entrée '(thickness . 2.6) de la alist est ajoutée à la liste de la propriété de l’objet Stem. thickness devant s’exprimer en unité d’épaisseur de ligne, les hampes auront donc une épaisseur de 2,6 lignes de portée, et à peu près le double de leur épaisseur normale. Afin de faire la distinction entre les variables que vous définissez au fil de vos fichiers – tel le vingtQuatre que nous avons vu plus haut – et les variables internes des objets, nous parlerons de « propriétés » pour ces dernières, et de « variables » pour les autres. Ainsi, l’objet hampe possède une propriété thickness, alors que vingtQuatre est une variable.


1.2.7 Variables LilyPond composites


Décalages (offsets)

Les décalages (offset) sur deux axes (coordonnées X et Y) sont stockés sous forme de paires. Le car de l’offset correspond à l’abscisse (coordonnée X) et le cdr à l’ordonnée (coordonnée Y).

\override TextScript.extra-offset = #'(1 . 2)

Cette clause affecte la paire (1 . 2) à la propriété extra-offset de l’objet TextScript. Ces nombres sont exprimés en espace de portée. La commande aura donc pour effet de déplacer l’objet d’un espace de portée vers la droite, et de deux espaces vers le haut.

Les procédures permettant de manipuler les offsets sont regroupées dans le fichier scm/lily-library.scm.


Fractions

Les fractions, telles que LilyPond les utilise, sont aussi stockées sous forme de paire. Alors que Scheme est tout à fait capable de représenter des nombres rationnels, vous conviendrez que, musicalement parlant, ‘2/4’ et ‘1/2’ ne se valent pas ; nous devrons donc pouvoir les distinguer. Dans le même ordre d’idée, LilyPond ne connaît pas les « fractions » négatives. Pour ces raisons, 2/4 en LilyPond correspond à (2 . 4) en Scheme, et #2/4 en LilyPond correspond à 1/2 en Scheme.


Étendues (extents)

Les paires permettent aussi de stocker des intervalles qui représentent un ensemble de nombres compris entre un minimum (le car) et un maximum (le cdr). Ces intervalles stockent l’étendue, tant au niveau horizontal (X) que vertical (Y) des objets imprimables. En matière d’étendue sur les X, le car correspond à la coordonnée de l’extrémité gauche, et le cdr à la coordonnée de l’extrémité droite. En matière d’étendue sur les Y, le car correspond à la coordonnée de l’extrémité basse, et le cdr à la coordonnée de l’extrémité haute.

Les procédures permettant de manipuler les offsets sont regroupées dans le fichier scm/lily-library.scm. Nous vous recommandons l’utilisation de ces procédures dans toute la mesure du possible afin d’assurer la cohérence du code.


Propriété en alist

Les propriétés en alist sont des structures de données particulières à LilyPond. Il s’agit de listes associatives dont les clés sont des propriétés et les valeurs des expressions Scheme fournissant la valeur requise pour cette propriété.

Les propriétés LilyPond sont des symboles Scheme, à l’instar de 'thickness.


Chaînes d’alist

Une chaîne d’alist est une liste contenant les listes associatives d’une propriété.

L’intégralité du jeu de propriétés qui doivent s’appliquer à un objet graphique est en fait stocké en tant que chaîne d’alist. Afin d’obtenir la valeur d’une propriété particulière qu’un objet graphique devrait avoir, on examinera chacune des listes associatives de la chaîne, à la recherche d’une entrée contenant la clé de cette propriété. Est renvoyée la première entrée d’alist trouvée, sa valeur étant la valeur de la propriété.

L’obtention des valeurs de propriété des objets graphiques se réalise en principe à l’aide de la procédure Scheme chain-assoc-get.


1.2.8 Représentation interne de la musique

Dans les entrailles du programme, la musique se présente comme une liste Scheme. Cette liste comporte les différents éléments qui affecteront la sortie imprimable. L’analyse grammaticale (l’opération parsing) est le processus chargé de convertir la musique représentée par le code LilyPond en présentation interne Scheme.

L’analyse d’une expression musicale se traduit par un jeu d’objets musicaux en Scheme. Une objet musical est déterminé par le temps qu’il occupe, que l’on appelle durée. Les durées s’expriment par des nombres rationnels représentant la longueur d’un objet musical par rapport à la ronde.

Un objet musical dispose de trois types :

L’information réelle d’une expression musicale est enregistrée sous forme de propriétés. Par exemple, un NoteEvent dispose des propriétés pitch et duration, respectivement chargées de stocker la hauteur et la durée de cette note. Les différentes propriétés sont répertoriées à la rubrique Music properties de la référence des propriétés internes.

Une expression composite est un objet musical dont les propriétés contiennent d’autres objets musicaux. S’il s’agit d’une liste d’objets, elle sera stockée dans la propriété elements d’un objet musical ; s’il n’y a qu’un seul objet « enfant », il sera stocké dans la propriété element. Ainsi, par exemple, les enfants de SequentialMusic iront dans elements, alors que l’argument unique de GraceMusic ira dans element. De même, le corps d’une répétition ira dans la propriété element d’un RepeatedMusic, les alternatives quant à elles dans la propriété elements.


1.3 Construction de fonctions complexes

Nous allons voir dans cette partie les moyens dont vous disposez pour obtenir les informations qui vous permettront de créer vos propres fonctions musicales complexes.


1.3.1 Première approche (exemple)

  1. La problématique

    Partant du code suivant, l’indication « espressivo » (les deux petits soufflets) devrait être plus large, mais pas gagner en hauteur.

    { f'\espressivo }
    

    [image of music]

  2. Les solutions envisageables
    1. créer un nouveau stencil
    2. redimensionner le stencil par défaut sur l’axe des X
  3. Réflexions sur l’Thoughts about previous item

    La solution A est réalisable, mais demandera plus d’effort de codage. Quant à la solution B, bien que moins exigeante, elle a le désavantage de ne pas donner de résultats acceptables dans tous les cas – mais on ne le saura pas avant d’avoir essayé.

    ⇒ opter pour la solution B.

  4. Codage
    • Si ’lon veut manipuler le stencil par défaut, il faut s’informer à son sujet.

      L’indication d’espressivo est un objet graphique Script. En consultant la référence des propriétés internes, on lit

      ‘stencil’ (stencil):
           ‘ly:script-interface::print’
      

      Voyons voir si tout cela marche. Pour être assurés d’affecter le bon grob, nous utiliserons des couleurs. Il existe une fonction prédéfinie à cet effet – voir Scheme functions.

      -- Fonction: stencil-with-color stencil couleur
          Renvoie une version modifiée du stencil donné, teint
          dans la couleur donnée. Voir ‘normalize-color’ pour
          les formats de couleur possibles.
      
      -- Fonction: normalize-color couleur
          Convertit une couleur donnée dans l'un des formats pris en
          charge en une liste de 4 nombres : R, G, B, A. Les formats
          possibles sont : une liste de 4 nombres ; une liste de 3
          nombres (transparence par défaut à 1.0) ; une chaîne CSS
          (nom de couleur, ou “#RRGGBB”, “#RRGGBBAA”, “#RGB” ou
          “#RGBA”).
      

      Utilisant un nom de couleur (‘red’), nous obtenons le code suivant :

      colorDefaultStencil =
      \once \override Script.stencil =
        % `lambda` starts a procedure; its argument is `grob`.
        #(lambda (grob)
           ;; With `let`, local variables are defined.
           (let ((stil (ly:script-interface::print grob)))
             ;; The procedure returns the colored default stencil.
             (stencil-with-color stil red)))
      
      { \colorDefaultStencil f'\espressivo }
      

      [image of music]

      Parfait, cela fonctionne.

    • Ce type d’opération – prendre un stencil ou une propriété par défaut et en retourner une version modifiée – est très courante, et LilyPond dispose d’une fonction dédiée à cet effet : grob-transformer.
      -- Function: grob-transformer property func
          Create an override value good for applying FUNC to either
          pure or unpure values.  FUNC is called with the
          respective grob as first argument and the default value
          (after resolving all callbacks) as the second.
      

      Laissons-nous tenter. Les concepts de pur ou impur seront vus plus avant, dans Conteneurs requalifiants ; ignorons-les pour l’instant.

      colorDefaultStencil =
      \once \override Script.stencil =
        #(grob-transformer 'stencil
           (lambda (grob orig)
             (stencil-with-color orig red)))
      
      { \colorDefaultStencil f'\espressivo }
      

      [image of music]

      Excellent !

    • Pour obtenir un stencil redimensionné, il faut utiliser
      -- Function: ly:stencil-scale stil x y
          Scale stencil STIL using the horizontal and vertical
          scaling factors X and optional Y (defaulting to X).
          Negative values flip or mirror STIL without changing its
          origin; this may result in collisions unless it is
          repositioned.
      

      à partir du même chapitre de la référence des propriétés internes.

      Résultat :

      scaleColorDefaultStencil =
      \once \override Script.stencil =
        #(grob-transformer 'stencil
           (lambda (grob orig)
             (stencil-with-color
              (ly:stencil-scale
               orig
               ;; 1 is the neutral element with ly:stencil-scale,
               ;; i.e., scaling with '1 1' (for x- and y-axis)
               ;; returns a visibly unchanged stencil.
               2 1)
              red)))
      
      { \scaleColorDefaultStencil f'\espressivo }
      

      [image of music]

      Mis à part la couleur, cela ressemble fort à ce que l’on voulait.

    • Certains problèmes subsistent cependant, comme le montre l’exemple suivant :
      { \scaleColorDefaultStencil f'_\espressivo ^\fermata }
      

      [image of music]

      • − Le point d’orgue est redimensionné lui aussi.
      • − Devoir taper la commande avant les notes et \espressivo après ces notes n’est pas pratique.

      Solution aux deux inconvénients : utilisons \tweak !

      {
        f'-\tweak stencil
            #(grob-transformer 'stencil
               (lambda (grob orig)
                 (stencil-with-color
                  (ly:stencil-scale orig 2 1)
                  red)))
            _\espressivo
          ^\fermata
      }
      

      [image of music]

      Cela fait ce qui est demandé, mais autant de saisie n’est pas la panacée ; pourquoi ne pas définir une variable que l’on utiliserait dans le tweak ? Après un peu de copier coller, nous obtenons

      #(define longer-script
         (grob-transformer 'stencil
           (lambda (grob orig)
             (stencil-with-color
              (ly:stencil-scale orig 2 1)
              red))))
      
      {
        f'-\tweak stencil #longer-script _\espressivo
          ^\fermata
      }
      

      [image of music]

      Cela fonctionne correctement, bien que les valeurs soient codées en dur et que taper -\tweak stencil #longer-script sans arrêt soit rébarbatif.

      Pour régler cela, commençons par introduire des variables dans longer-script pour les valeurs de redimensionnement x et y.

      #(define (longer-script x y)
         (grob-transformer 'stencil
           (lambda (grob orig)
             (stencil-with-color
              (ly:stencil-scale orig x y)
              red))))
      

      Ensuite, et pour réduire la frappe, nous définissons une fonction événementielle – voir Fonctions événementielles. Les éléments du premier groupe entre parenthèses – (x-val y-val) – sont les variables de la fonction, et les éléments du second groupe entre parenthèses sont les types respectifs de variable (on cherche deux fois un nombre – voir Types de prédicat prédéfinis pour les possibilités de test).

      scaleEspr =
      #(define-event-function (x-val y-val) (number? number?)
        #{
          \tweak stencil #(longer-script x-val y-val)
          \espressivo
        #})
      
      { f'_\scaleEspr 2 1
           ^\fermata }
      

      [image of music]

      Cela fonctionne à merveille ! Notez l’utilisation de #{ … #} : dans cette construction Scheme, il est tout à fait possible d’utiliser la syntaxe de LilyPond, et les arguments de la fonction, tel que x-val, sont correctement expansés – voir Blocs de code LilyPond.

  5. Le code finalisé

    Pour terminer, faisons un peu de ménage.

    • − Nous souhaitons redimensionner uniquement dans l’horizontalité ; nous pouvons donc forcer le redimensionnement vertical dans la fonction événementielle.
    • − Supprimons la couleur.

    Ceci nous amène au code suivant.

    #(define (longer-script x y)
       (grob-transformer 'stencil
         (lambda (grob orig)
           (ly:stencil-scale orig x y))))
    
    longEspressivo =
    #(define-event-function (x-val) (number?)
      #{
        \tweak stencil #(longer-script x-val 1)
        \espressivo
      #})
    
    { f'^\longEspressivo #2 _\fermata }
    

    [image of music]

    Voilà !

    Le principal bénéfice à l’utilisation de grob-transformer réside dans le fait que nul n’est besoin de connaître le nom exact de la fonction de stencil. Dans l’exemple qui suit, nous pivotons une liaison et une ligature de 90 degrés – que les fonction de stencil s’appellent respectivement ly:slur::print et ly:beam::print n’est d’aucun intérêt en l’occurrence.

    #(define rotate-90
       (grob-transformer 'stencil
         (lambda (grob orig)
           (ly:stencil-rotate orig 90 0 0))))
    
    { f'8( g')
      f'8-\tweak stencil #rotate-90 ( g')
      f'8[ g']
      f'8-\tweak stencil #rotate-90 [ g'] }
    

    [image of music]


1.3.2 Affichage d’expressions musicales

Lorsque l’on veut écrire une fonction musicale, il est intéressant d’examiner comment une expression musicale est représentée en interne. Vous disposez à cet effet de la fonction musicale \displayMusic.

{
  \displayMusic { c'4\f }
}

affichera

(make-music
  'SequentialMusic
  'elements
  (list (make-music
          'NoteEvent
          'articulations
          (list (make-music
                  'AbsoluteDynamicEvent
                  'text
                  "f"))
          'duration
          (ly:make-duration 2 0 1/1)
          'pitch
          (ly:make-pitch 0 0 0))))

Par défaut, LilyPond affichera ces messages sur la console, parmi toutes les autres informations. Vous pouvez, afin de les isoler et de garder le résultat des commandes \display{TRUC}, spécifier un port optionnel à utiliser pour la sortie :

{
  \displayMusic #(open-output-file "display.txt") { c'4\f }
}

Ceci aura pour effet d’écraser tout fichier précédemment généré. Lorsque plusieurs expressions doivent être retranscrites, il suffit de faire appel à une variable pour le port puis de la réutiliser :

{
  port = #(open-output-file "display.txt")
  \displayMusic \port { c'4\f }
  \displayMusic \port { d'4 }
  #(close-output-port port)
}

La documentation de Guile fournit une description détaillée des ports. Clôturer un port n’est requis que si vous désirez consulter le fichier avant que LilyPond n’ait fini, ce dont nous ne nous sommes pas préoccupé dans le premier exemple.

L’information sera encore plus lisible après un peu de mise en forme :

(make-music 'SequentialMusic
  'elements (list
	     (make-music 'NoteEvent
               'articulations (list
			       (make-music 'AbsoluteDynamicEvent
				 'text
				 "f"))
	       'duration (ly:make-duration 2 0 1/1)
	       'pitch    (ly:make-pitch 0 0 0))))

Une séquence musicale { … } se voit attribuer le nom de SequentialMusic, et les expressions qu’elle contient sont enregistrées en tant que liste dans sa propriété 'elements. Une note est représentée par un objet NoteEvent – contenant les propriétés de durée et hauteur – ainsi que l’information qui lui est attachée – en l’occurrence un AbsoluteDynamicEvent ayant une propriété text de valeur "f" – et stockée dans sa propriété articulations.

La fonction \displayMusic renvoie la musique qu’elle affiche ; celle-ci sera donc aussi interprétée. L’insertion d’une commande \void avant le \displayMusic permet de s’affranchir de la phase d’interprétation.


1.3.3 Propriétés musicales

Nous abordons ici les propriétés music, et non pas les propriétés context ou layout.

Partons de cet exemple simple :

someNote = c'
\displayMusic \someNote
===>
(make-music
  'NoteEvent
  'duration
  (ly:make-duration 2 0 1/1)
  'pitch
  (ly:make-pitch 0 0 0))

L’objet NoteEvent est la représentation brute de someNote. Voyons ce qui se passe lorsque nous plaçons ce c’ dans une construction d’accord :

someNote = <c'>
\displayMusic \someNote
===>
(make-music
  'EventChord
  'elements
  (list (make-music
          'NoteEvent
          'duration
          (ly:make-duration 2 0 1/1)
          'pitch
          (ly:make-pitch 0 0 0))))

L’objet NoteEvent est maintenant le premier objet de la propriété 'elements de someNote.

\displayMusic utilise la fonction display-Scheme-music pour afficher la représentation en Scheme d’une expression musicale :

#(display-Scheme-music (first (ly:music-property someNote 'elements)))
===>
(make-music
  'NoteEvent
  'duration
  (ly:make-duration 2 0 1/1)
  'pitch
  (ly:make-pitch 0 0 0))

La hauteur de la note est accessible au travers de la propriété 'pitch de l’objet NoteEvent :

#(display-Scheme-music
   (ly:music-property (first (ly:music-property someNote 'elements))
                      'pitch))
===>
(ly:make-pitch 0 0 0)

La hauteur de la note se modifie en définissant sa propriété 'pitch :

#(set! (ly:music-property (first (ly:music-property someNote 'elements))
                          'pitch)
       (ly:make-pitch 0 1 0)) ;; set the pitch to d'.
\displayLilyMusic \someNote
===>
d'4

1.3.4 Doublement d’une note avec liaison (exemple)

Supposons que nous ayons besoin de créer une fonction transformant une saisie a en { a( a) }. Commençons par examiner comment le résultat est représenté en interne.

\displayMusic{ a'( a') }
===>
(make-music
  'SequentialMusic
  'elements
  (list (make-music
          'NoteEvent
          'articulations
          (list (make-music
                  'SlurEvent
                  'span-direction
                  -1))
          'duration
          (ly:make-duration 2 0 1/1)
          'pitch
          (ly:make-pitch 0 5 0))
        (make-music
          'NoteEvent
          'articulations
          (list (make-music
                  'SlurEvent
                  'span-direction
                  1))
          'duration
          (ly:make-duration 2 0 1/1)
          'pitch
          (ly:make-pitch 0 5 0))))

Mauvaise nouvelle ! Les expressions SlurEvent doivent s’ajouter « à l’intérieur » de la note – dans sa propriété articulations.

Examinons à présent la saisie :

\displayMusic a'
===>
(make-music
  'NoteEvent
  'duration
  (ly:make-duration 2 0 1/1)
  'pitch
  (ly:make-pitch 0 5 0))))

Nous aurons donc besoin, dans notre fonction, de cloner cette expression – de telle sorte que les deux notes constituent la séquence – puis d’ajouter un SlurEvent à la propriété 'articulations de chacune d’elles, et enfin réaliser un SequentialMusic de ces deux éléments NoteEvent. En tenant compte du fait que, dans le cadre d’un ajout, une propriété non définie est lue '() (une liste vide), aucune vérification n’est requise avant d’introduire un nouvel élément en tête de la propriété articulations.

doubleSlur = #(define-music-function (note) (ly:music?)
         "Renvoie : { note ( note ) }.
         `note` est censé être une note unique."
         (let ((note2 (ly:music-deep-copy note)))
           (set! (ly:music-property note 'articulations)
                 (cons (make-music 'SlurEvent 'span-direction -1)
                       (ly:music-property note 'articulations)))
           (set! (ly:music-property note2 'articulations)
                 (cons (make-music 'SlurEvent 'span-direction 1)
                       (ly:music-property note2 'articulations)))
           (make-music 'SequentialMusic 'elements (list note note2))))

1.3.5 Ajout d’articulation à des notes (exemple)

Le moyen d’ajouter une articulation à des notes consiste à juxtaposer deux expressions musicales. L’option de réaliser nous-mêmes une fonction musicale à cette fin.

Un $variable au milieu de la notation #{ … #} se comporte exactement comme un banal \variable en notation LilyPond traditionnelle. Nous pourrions écrire

{ \musique -. -> }

mais, pour les besoins de la démonstration, nous allons voir comment réaliser ceci en Scheme. Commençons par examiner une saisie simple et le résultat auquel nous désirons aboutir :

%  saisie
\displayMusic c4
===>
(make-music
  'NoteEvent
  'duration
  (ly:make-duration 2 0 1/1)
  'pitch
  (ly:make-pitch -1 0 0))))
=====
%  résultat attendu
\displayMusic c4->
===>
(make-music
  'NoteEvent
  'articulations
  (list (make-music
          'ArticulationEvent
          'articulation-type 'accent))
  'duration
  (ly:make-duration 2 0 1/1)
  'pitch
  (ly:make-pitch -1 0 0))

Nous voyons qu’une note (c4) est représentée par une expression NoteEvent. Si nous souhaitons ajouter une articulation accent, nous devrons ajouter une expression ArticulationEvent à la propriété articulations de l’expression NoteEvent.

Construisons notre fonction en commençant par

(define (ajoute-accent note-event)
  "Ajoute un accent (ArticulationEvent) aux articulations de `note-event`,
  qui est censé être une expression NoteEvent."
  (set! (ly:music-property note-event 'articulations)
        (cons (make-music 'ArticulationEvent
                'articulation-type 'accent)
              (ly:music-property note-event 'articulations)))
  note-event)

La première ligne est la manière de définir une fonction en Scheme : la fonction Scheme a pour nom ajoute-accent et elle comporte une variable appelée note-event. En Scheme, le type d’une variable se déduit la plupart de temps de par son nom – c’est d’ailleurs une excellente pratique que l’on retrouve dans de nombreux autres langages.

"Ajoute un accent…"

décrit ce que fait la fonction. Bien que ceci ne soit pas primordial, tout comme des noms de variable évidents, tâchons néanmoins de prendre de bonnes habitudes dès nos premiers pas.

Vous pouvez vous demander pourquoi nous modifions directement l’événement note plutôt que d’en manipuler une copie – on pourrait utiliser ly:music-deep-copy à cette fin. La raison en est qu’il existe un contrat tacite : les fonctions musicales sont autorisées à modifier leurs arguments – ils sont générés en partant de zéro (comme les notes que vous saisissez) ou déjà recopiés (faire référence à une variable musicale avec ‘\nom’ ou à de la musique issue d’expressions Scheme ‘$(…)’ aboutit à une copie). Dans la mesure où surmultiplier les copies serait contre productif, la valeur de retour d’une fonction musicale n’est pas recopiée. Afin de respecter ce contrat, n’utilisez pas un même argument à plusieurs reprises, et n’oubliez pas que le retourner compte pour une utilisation.

Dans un exemple précédent, nous avons construit de la musique en répétant un certain argument musical. Dans ce cas là, l’une des répétitions se devait d’être une copie. Dans le cas contraire, certaines bizarreries auraient pu survenir. Par exemple, la présence d’un \relative ou d’un \transpose, après plusieurs répétitions du même élément, entraînerait des « relativisations » ou transpositions en cascade. Si nous les assignons à une variable musicale, l’enchaînement est rompu puisque la référence à ‘\nom’ créera une nouvelle copie sans toutefois prendre en considération l’identité des éléments répétés.

Cette fonction n’étant pas une fonction musicale à part entière, elle peut s’utiliser dans d’autres fonctions musicales. Il est donc sensé de respecter le même contrat que pour les fonctions musicales : l’entrée peut être modifiée pour arriver à une sortie, et il est de la responsabilité de l’appelant d’effectuer des copies s’il a réellement besoin de l’argument dans son état originel. Vous constaterez, à la lecture des fonctions propres à LilyPond, comme music-map, que ce principe est toujours respecté.

Revenons à nos moutons… Nous disposons maintenant d’un note-event que nous pouvons modifier, non pas grâce à un ly:music-deep-copy, mais plutôt en raison de notre précédente réflexion. Nous ajoutons l’accent à la liste de ses propriétés 'articulations.

(set! emplacement nouvelle-valeur)

L’emplacement est ce que nous voulons ici définir. Il s’agit de la propriété 'articulations de l’expression note-event.

(ly:music-property note-event 'articulations)

La fonction ly:music-property permet d’accéder aux propriétés musicales – les 'articulations, 'duration, 'pitch, etc. que \displayMusic nous a indiquées. La nouvelle valeur sera l’ancienne propriété 'articulations, augmentée d’un élément : l’expression ArticulationEvent, que nous recopions à partir des informations de \displayMusic.

(cons (make-music 'ArticulationEvent
        'articulation-type 'accent)
      (ly:music-property result-event-chord 'articulations))

cons permet d’ajouter un élément en tête de liste sans pour autant modifier la liste originale. C’est exactement ce que nous recherchons : la même liste qu’auparavant, plus la nouvelle expression ArticulationEvent. L’ordre au sein de la propriété 'articulations n’a ici aucune importance.

Enfin, après avoir ajouté l’articulation accent à sa propriété articulations, nous pouvons renvoyer le note-event, ce que réalise la dernière ligne de notre fonction.

Nous pouvons à présent transformer la fonction ajoute-accent en fonction musicale, à l’aide d’un peu d’enrobage syntaxique et mention du type de son argument.

ajouteAccent = #(define-music-function (note-event) (ly:music?)
  "Ajoute un accent (ArticulationEvent) aux articulations de `note-event`,
  qui est censé être une expression NoteEvent."
  (set! (ly:music-property note-event 'articulations)
        (cons (make-music 'ArticulationEvent
                'articulation-type 'accent)
              (ly:music-property note-event 'articulations)))
  note-event)

Par acquis de conscience, vérifions que tout ceci fonctione :

\displayMusic \ajouteAccent c4

2 Interfaces pour programmeurs

Scheme permet de réaliser des affinages très pointus. Si vous ne connaissez rien de Scheme, vous en aurez un aperçu au travers de notre Tutoriel Scheme.


2.1 Blocs de code LilyPond

L’utilisation de Scheme pour créer des expressions musicales peut s’avérer ardue, principalement à cause des imbrications et de la longueur du code Scheme qui en résulte. Dans le cas de tâches simples, on peut toutefois contourner une partie du problème en utilisant des blocs de code LilyPond, ce qui autorise la syntaxe habituelle de LilyPond au sein même de Scheme.

Les blocs de code LilyPond ressemblent à

  #{ du code LilyPond #}

En voici un exemple basique :

ritpp = #(define-event-function () ()
  #{ ^"rit." \pp #}
)

{ c'4 e'4\ritpp g'2 }

[image of music]

Les blocs de code LilyPond peuvent s’utiliser partout où vous pouvez écrire du code Scheme. Le lecteur Scheme est en fait quelque peu adapté pour accepter des blocs de code LilyPond ; il est capable de traiter des expressions Scheme intégrées débutant par ‘$’ ou ‘#’.

Le lecteur Scheme extrait le bloc de code LilyPond et déclenche un appel à l’analyseur grammatical de LilyPond (le parser) qui réalise en temps réel l’interprétation de ce bloc de code LilyPond. Toute expression Scheme imbriquée est exécutée dans l’environnement lexical du bloc de code LilyPond, de telle sorte que vous avez accès aux variables locales et aux paramètres de la fonction au moment même où le bloc de code LilyPond est écrit. Les variables définies dans d’autres modules Scheme, tels ceux contenant les blocs \header ou \layout, ne sont pas accessibles en tant que variables Scheme (préfixées par un ‘#’) mais en tant que variables LilyPond (préfixées par un ‘\’).

Toute la musique générée au sein de ce bloc de code voit son origine établie à cet emplacement.

Un bloc de code LilyPond peut contenir tout ce que vous pourriez mettre à droite de l’assignation. Par ailleurs, un bloc LilyPond vide correspond à une expression fantôme, et un bloc LilyPond de multiples événements musicaux sera transformé en une expression de musique séquentielle.


2.2 Fonctions Scheme

Les fonctions Scheme sont des procédures Scheme chargées de créer des expressions Scheme à partir de code rédigé selon la syntaxe de LilyPond. Elles peuvent être appelées en de nombreux endroits, à l’aide d’un ‘#’, où spécifier une valeur en syntaxe Scheme est autorisé. Bien que Scheme dispose de fonctions en propre, nous nous intéresserons, au fil des paragraphes qui suivent, aux fonctions syntaxiques, autrement dit des fonctions qui reçoivent des arguments libellés dans la syntaxe de LilyPond.


2.2.1 Définition de fonctions Scheme

D’une manière générale, une fonction Scheme se définit ainsi :

fonction =
#(define-scheme-function
     (arg1 arg2…)
     (type1? type2?…)
   corps)

argN

n-ième argument.

typeN?

Un type de prédicat Scheme pour lequel argN devra retourner #t. Il existe aussi une forme spécifique – (prédicat? default) – qui permet de fournir des argument optionnels. En l’absence d’argument réel au moment de l’appel à la fonction, c’est la valeur par défaut qui lui sera substituée. Les valeurs par défaut sont évaluées dès l’apparition de la définition, y compris dans le cas de blocs de code LilyPond ; vous devrez donc, si ces valeurs par défaut ne peuvent être déterminées que plus tard, mentionner une valeur spéciale que vous reconnaîtrez facilement. Lorsque vous mentionnez un prédicat entre parenthèses sans toutefois fournir sa valeur par défaut, celle-ci sera considérée comme étant #f. Les valeurs par défaut d’un prédicat? ne sont vérifiées ni au moment de la définition, ni à l’exécution ; il est de votre ressort de gérer les valeurs que vous spécifiez. Une valeur par défaut constituée d’une expression musicale est recopiée dès la définition de origin à l’emplacement courant du code.

corps

Une séquence de formules Scheme évaluées dans l’ordre, la dernière servant de valeur de retour de la fonction. Il peut contenir des blocs de code LilyPond, enchâssés dans des accolades et hashes#{…#} – comme indiqué à la rubrique Blocs de code LilyPond. Au sein d’un bloc de code LilyPond, un ‘#’ permet de référencer des arguments de la fonction – tel #arg1 – ou d’ouvrir une expression Scheme contenant les arguments de la fonction – par exemple ‘#(cons arg1 arg2). Dans le cas où une expression Scheme introduite par ‘#’ ne vous permet pas de parvenir à vos fins, vous pourriez devoir revenir à une expression Scheme « immédiate » à l’aide d’un ‘$’, comme $music.

Lorsque votre fonction retourne une expression musicale, lui est attribuée la valeur origin.

La recevabilité des arguments est déterminée par un appel effectif au prédicat après que LilyPond les a déjà convertis en expression Scheme. Par voie de conséquence, l’argument peut tout à fait se libeller en syntaxe Scheme – introduite par un ‘#’ ou en tant que résultat d’un appel à une fonction Scheme. Par ailleurs, LilyPond convertira en Scheme un certain nombre de constructions purement LilyPond avant même d’en avoir vérifié le prédicat. C’est notamment le cas de la musique, des postévénements, des chaînes simples (avec ou sans guillemets), des nombres, des markups et listes de markups, ainsi que des blocs score, book, bookpart, ou qui définissent un contexte ou un format de sortie.

Il existe certaines situations pour lesquelles LilyPond lèvera toute ambiguïté grâce aux fonctions de prédicat : un ‘-3’ est-il un postévénement de type doigté ou un nombre négatif ? Un "a" 4 en mode paroles est-il une chaîne suivie d’un nombre ou bien un événement syllabe de durée 4 ? LilyPond répondra à ces questions par des interprétations successives du prédicat de l’argument, dans un ordre défini de sorte à minimiser les interprétations erronées et le besoin de lecture en avance.

Un prédicat qui accepte par exemple aussi bien une expression musicale qu’une hauteur considèrera c'' comme étant une hauteur plutôt qu’une expression musicale. Les durées ou postévénements qui viennent juste après viendront modifier cette interprétation. C’est la raison pour laquelle il vaut mieux éviter des prédicats par trop permissifs tel que Scheme? lorsque l’application fait plutôt appel à des types d’argument plus spécifiques.

Les différents types de prédicat propres à LilyPond sont recensés à l’annexe Types de prédicats prédéfinis.

Voir aussi

Manuel de notation : Types de prédicats prédéfinis.

Fichiers d’initialisation : lily/music-Scheme.cc, scm/c++.scm, scm/lily.scm.


2.2.2 Utilisation de fonctions Scheme

Vous pouvez appeler une fonction Scheme pratiquement partout où une expression Scheme derrière un ‘#’ peut prendre place. Vous appelez une fonction Scheme à partir de LilyPond en faisant précéder son nom d’un ‘\’, et en le faisant suivre de ses arguments. Lorsqu’un prédicat d’argument optionnel ne correspond pas à un argument, LilyPond l’ignore ainsi que tous les arguments optionnels qui suivent, les remplaçant par leur valeur par défaut, et « sauvegarde » en tant que prochain argument obligatoire l’argument qui ne correspondait pas. Dans la mesure où l’argument sauvegardé doit servir, les argument optionnels ne sont en fait pas considérés comme optionnels, sauf à être suivis d’un argument obligatoire.

Une exception cependant à cette règle : le fait de donner un \default en tant qu’argument optionnel aura pour résultat que cet argument et tous les autres arguments optionnels qui suivent seront ignorés et remplacés par leur valeur par défaut. Il en va de même lorsqu’aucun argument obligatoire ne suit, du fait que \default ne requiert pas de sauvegarde. C’est d’ailleurs ainsi que fonctionnent les commandes mark et key, qui retrouvent leur comportement par défaut lorsque vous les faites suivre d’un \default.

En plus de là où une expression Scheme est requise, il y a quelques endroits où des expressions ‘#’ sont acceptées et évaluées uniquement pour leurs effets annexes. Il s’agit, dans la plupart des cas, d’endroits où une affectation serait tout à fait envisageable.

Dans la mesure où il n’est pas bon de renvoyer une valeur qui pourrait être mal interprétée dans certains contextes, nous vous enjoignons à utiliser des fonctions Scheme normales uniquement dans les cas où vous renvoyez toujours une valeur utile, et une fonction fantôme – voir Fonctions Scheme fantômes – dans le cas contraire.

Pour des raisons de commodité, il est possible de faire appel à des fonctions Scheme directement en Scheme, courcircuitant ainsi l’analyseur de LilyPond. Leur nom s’utilise comme n’importe quel nom de fonction. Le contrôle de typologie des arguments et l’omission des arguments optionnels seront traîtés de la même manière que lorsque l’appel est fait à partir de LilyPond, la valeur Scheme *unspecified* ayant le rôle du mot réservé \default pour omettre explicitement les arguments optionnels. Les arguments optionnels en fin de liste d’arguments peuvent être omsi sans plus d’indication lorsque l’appel est fait au sein même de Scheme.


2.2.3 Fonctions Scheme fantômes

Il arrive qu’une procédure soit exécutée pour réaliser une action, non pour renvoyer une valeur. Certains langages de programmation, tels le C et Scheme, utilisent des fonctions dans les deux cas et se débarrassent tout bonnement de la valeur renvoyée ; en règle générale, il suffit que l’expression fasse office de déclaration, et d’ignorer le résultat. C’est futé, mais pas sans risque d’erreur : la plupart des compilateurs C actuels déclenchent un avertissement si l’on se débarrasse de certaines expressions non-void. Pour de nombreuses fonctions réalisant une action, les standards Scheme déclarent que la valeur de retour est indéfinie. L’interpréteur Guile qu’utilise le Scheme de LilyPond dispose d’une valeur unique *unspecified* qu’il retourne alors, en règle générale – notamment lorsqu’on utilise set! directement sur une variable – mais malheureusement pas toujours.

Une fonction LilyPond définie à l’aide de la clause define-void-function vous apporte l’assurance que c’est cette valeur spéciale – la seule valeur qui satisfasse au prédicat void? – qui sera retournée.

noPointAndClick =
#(define-void-function
     ()
     ()
   (ly:set-option 'point-and-click #f))
…
\noPointAndClick   % desactive le "pointer-cliquer"

L’utilisation d’un préfixe \void permet ainsi d’évaluer une expression pour ses effets annexes sans interprétation d’une quelconque valeur de retour :

\void #(hashq-set! une-table une-clé une-valeur)

Vous serez alors assuré que LilyPond ne tentera pas d’affecter un sens à la valeur de retour, à quelque endroit qu’elle ressorte. Ceci est aussi opérationnel dans le cadre de fonctions musicales telles que \displayMusic.


2.3 Fonctions musicales

Les fonctions musicales sont des procédures Scheme capables de créer automatiquement des expressions musicales ; elles permettent de grandement simplifier un fichier source.


2.3.1 Définition de fonctions musicales

Une fonction musicale se définit ainsi :

fonction =
#(define-music-function
     (arg1 arg2…)
     (type1? type2?…)
   corps)

de manière similaire aux fonctions Scheme. La plupart du temps, le corps sera constitué d’un bloc de code LilyPond.

Les différents types des prédicat sont recensés à l’annexe Types de prédicats prédéfinis.

Voir aussi

Manuel de notation : Types de prédicats prédéfinis.

Fichiers d’initialisation : lily/music-Scheme.cc, scm/c++.scm, scm/lily.scm.


2.3.2 Utilisation de fonctions musicales

En ce qui concerne la gestion des listes d’argments, les fonctions musicales ne diffèrent en rien des fonction Scheme – voir Utilisation de fonctions Scheme.

Une « fonction musicale » doit impérativement renvoyer une expression répondant au prédicat ly:music?. Ceci a pour conséquence d’autoriser l’appel à une fonction musicale en tant qu’argument de type ly:music? dans le cadre de l’appel à une autre fonction musicale.

Certaines restrictions s’appliqueront selon le contexte où une fonction musicale est utilisée, de telle sorte que l’analyse syntaxique soit sans ambiguïté.

Des fonctions « polymorphes » telles que \tweak peuvent s’appliquer aux postévénements, constituants d’accord et expressions de haut niveau.


2.3.3 Fonctions de substitution simple

Une fonction de substitution simple renvoie une expression musicale écrite au format LilyPond et contient des arguments au format de l’expression résultante. Vous en trouverez une description détaillée à la rubrique Exemples de fonction de substitution.


2.3.4 Fonctions de substitution intermédiaires

Une fonction de substitution intermédiaire est une fonction dont l’expression musicale résultante mélangera du code Scheme au code LilyPond.

Certaines commandes \override nécessitent un argument supplémentaire constitué d’une paire de nombres, appelée cons cell en Scheme – que l’on pourrait traduire par « construction de cellule ».

Cette paire peut se mentionner directement dans la fonction musicale à l’aide d’une variable pair? :

manualBeam =
#(define-music-function
     (beg-end)
     (pair?)
   #{
     \once \override Beam.positions = #beg-end
   #})

\relative c' {
  \manualBeam #'(3 . 6) c8 d e f
}

Autre manière de procéder, les nombres formant la paire sont transmis comme arguments séparés ; le code Scheme chargé de créer la paire pourra alors être inclus dans l’expression musicale :

manualBeam =
#(define-music-function
     (beg end)
     (number? number?)
   #{
     \once \override Beam.positions = #(cons beg end)
   #})

\relative c' {
  \manualBeam #3 #6 c8 d e f
}

[image of music]

L’entretien des propriétés peut se voir comme un empilement par propriété par objet par contexte. Les fonctions musicales peuvent nécessiter des dérogations pour une ou plusieurs propriétés pour la durée de la fonction, puis de revenir aux valeurs précédentes avant de quitter. Néanmoins, une dérogation normale va retirer de la pile – ou dépiler – et supprimer le sommet de la pile de la propriété avant d’y ajouter quoi que ce soit – ou empiler – ; la valeur précédente de la propriété est de fait perdue. Lorsque la valeur antérieure doit être préservée, l’instruction \override devra être préfixée d’un \temporary, comme ceci :

\temporary \override …

L’utilisation d’un \temporary a pour effet d’effacer la propriété pop-first (commence par dépiler normalement activée) de la dérogation ; la valeur antérieure ne sera alors pas supprimée de la pile de la propriété avant d’y empiler la nouvelle valeur. Lorsqu’un \revert viendra par la suite supprimer la valeur dérogatoire temporaire, réapparaitra la valeur antérieure.

En d’autres termes, un \revert qui suit un \temporary \override pour la même propriété n’apporte rien. Ce principe est aussi valable pour un couple \temporary et \undo sur la même musique contenant des dérogations.

Voici un exemple de fonction musicale utilisant cette fonctionnalité. La présence du \temporary permet de s’assurer qu’en sortant de la fonction, les propriétés cross-staff et style retrouveront les valeurs qu’elles avaient avant que ne soit appelée la fonction crossStaff. En l’absence de \temporary, ces propriétés auraient retrouvé leurs valeurs par défaut à la sortie de la fonction.

crossStaff =
#(define-music-function (notes) (ly:music?)
  (_i "Create cross-staff stems")
  #{
  \temporary \override Stem.cross-staff = #cross-staff-connect
  \temporary \override Flag.style = #'no-flag
  #notes
  \revert Stem.cross-staff
  \revert Flag.style
#})

2.3.5 De l’usage des mathématiques dans les fonctions

Une fonction musicale peut requérir, en plus d’une simple substitution, une part de programmation en Scheme.

AltOn =
#(define-music-function
     (mag)
     (number?)
   #{
     \override Stem.length = #(* 7.0 mag)
     \override NoteHead.font-size =
       #(inexact->exact (* (/ 6.0 (log 2.0)) (log mag)))
   #})

AltOff = {
  \revert Stem.length
  \revert NoteHead.font-size
}

\relative {
  c'2 \AltOn #0.5 c4 c
  \AltOn #1.5 c c \AltOff c2
}

[image of music]

Cette fonction pourrait tout à fait être réécrite de telle sorte qu’elle s’applique à une expression musicale :

withAlt =
#(define-music-function
     (mag music)
     (number? ly:music?)
   #{
     \override Stem.length = #(* 7.0 mag)
     \override NoteHead.font-size =
       #(inexact->exact (* (/ 6.0 (log 2.0)) (log mag)))
     #music
     \revert Stem.length
     \revert NoteHead.font-size
   #})

\relative {
  c'2 \withAlt #0.5 { c4 c }
  \withAlt #1.5 { c c } c2
}

[image of music]


2.3.6 Fonctions dépourvues d’argument

Dans la plupart des cas, une fonction dépourvue d’argument devrait être créée à l’aide d’une variable :

dolce = \markup{ \italic \bold dolce }

Il peut, dans certains cas particuliers, s’avérer utile de créer une fonction sans argument comme ici,

displayBarNum =
#(define-music-function
     ()
     ()
   (if (eq? #t (ly:get-option 'display-bar-numbers))
       #{ \once \override Score.BarNumber.break-visibility = ##f #}
       #{#}))

de manière à pouvoir afficher les numéros de mesure grâce à un appel à cette fonction. En pareil cas, vous devrez invoquer lilypond en respectant la syntaxe

lilypond -d display-bar-numbers MONFICHIER.ly

2.3.7 Fonctions musicales fantômes

Une fonction musicale doit renvoyer une expression musicale. Toutefois, une fonction musicale peut n’être exécutée que dans le but d’en retenir les effets annexes ; vous devrez alors utiliser une procédure define-void-function. Il peut cependant arriver que vous ayez besoin d’une fonction qui, selon le cas, produise ou non (comme dans l’exemple de la rubrique précédente) une expression musicale. L’utilisation d’un #{ #} vous permettra de renvoyer une expression musicale void.


2.4 Fonctions événementielles

L’utilisation d’une fonction musicale pour placer un événement requiert l’insertion d’un indicateur de position, ce qui peut ne pas correspondre à la syntaxe de la construction à remplacer. C’est par exemple le cas lorsque vous voulez écrire une commande de nuance, instruction qui ne comporte habituellement pas d’indicateur de positionnement, comme dans c'\pp. Voici de quoi vous permettre de mentionner n’importe quelle nuance :

dyn=#(define-event-function (arg) (markup?)
         (make-dynamic-script arg))
\relative { c'\dyn pfsss }

[image of music]

Vous pourriez obtenir le même résultat avec une fonction musicale, à ceci près que chaque appel à la fonction devra être précédé d’un indicateur de positionnement, comme c-\dyn pfsss.


2.5 Fonctions pour markups

Les markups sont implémentés au travers de fonctions Scheme spécifiques qui produisent des objets Stencil comprenant un certain nombre d’arguments.


2.5.1 Construction d’un markup en Scheme

Les expressions markup sont représentées en Scheme de manière interne par la macro markup :

(markup expression)

La commande \displayScheme permet d’obtenir la représentation en Scheme d’une expression markup :

\displaysScheme
\markup {
  \column {
    \line { \bold \italic "hello" \raise #0.4 "world" }
    \larger \line { foo bar baz }
  }
}

Compiler ce code renverra en console les lignes suivantes :

(markup
  #:line
  (#:column
   (#:line
    (#:bold (#:italic "hello") #:raise 0.4 "world")
    #:larger
    (#:line ("foo" "bar" "baz")))))

L’impression du markup sera suspendue dès lors qu’apparaîtra un ‘\void \displayScheme markup. Tout comme pour la commande \displayMusic, le résultat de \displayScheme peut être sauvegardé dans un fichier séparé. Voir à ce sujet Affichage d’expressions musicales.

Vous pouvez constater les principales règles de traduction entre les syntaxes respectives de LilyPond et de Scheme en matière de markup. Bien que le passage en syntaxe LilyPond grâce à #{ … #} apporte de la souplesse, nous allons voir comment utiliser la macro markup en Scheme exclusivement.

LilyPondScheme
\markup markup1(markup markup1)
\markup { markup1 markup2… }(markup markup1 markup2… )
\commande-markup#:commande-markup
\variablevariable
\center-column { … }#:center-column ( … )
chaîne"chaîne"
#argument-Schemeargument-Scheme

L’intégralité du langage Scheme est accessible à l’intérieur même de la macro markup. Vous pouvez ainsi appeler des fonctions à partir de markup pour manipuler des chaînes de caractères, ce qui est particulièrement pratique lorsque vous créez votre propre commande de markup – voir Définition d’une nouvelle commande de markup.

Problèmes connus et avertissements

L’argument markup-list des commandes #:line, #:center ou #:column ne saurait être une variable ni le résultat de l’appel à une fonction.

(markup #:line (fonction-qui-retourne-des-markups))

n’est pas valide. Il vaut mieux, en pareil cas, utiliser les fonctions make-line-markup, make-center-markup ou make-column-markup :

(markup (make-line-markup (fonction-qui-retourne-des-markups)))

2.5.2 Fonctionnement interne des markups

Dans un markup tel que

\raise #0.5 "text example"

\raise représente en fait la fonction raise-markup. L’expression markup est enregistrée sous la forme

(list raise-markup 0.5 "text example")

Lorsque ce markup est converti en objets imprimables (stencils), la fonction raise-markup est appelée ainsi :

(apply raise-markup
       \layout objet
       liste des alists de propriété
       0.5
       le markup "text example")

La fonction raise-markup commence par créer le stencil pour la chaîne text example, puis remonte ce stencil d’un demi espace de portée. Il s’agit là d’un exemple relativement simple, et nous en aborderons de plus complexes au fil des paragraphes suivants ; d’autres exemples se trouvent directement dans le fichier scm/define-markup-commands.scm.


2.5.3 Définition d’une nouvelle commande de markup

Nous allons étudier dans ce qui suit la manière de définir une nouvelle commande de markup.


Syntaxe d’une commande markup

Une commande de markup personnalisée se définit à l’aide de la macro Scheme define-markup-command, placée en tête de fichier.

(define-markup-command (nom-commande layout props arg1 arg2…)
    (arg1-type? arg2-type?…)
    [ #:properties ((propriété1 valeur-par-défaut1)
                    …) ]
    [ #:as-string expression ]
  …corps de la commande…)

Quelques commentaires sur les arguments :

nom-commande

le nom que vous attribuez à votre commande de markup.

layout

la définition du « layout » – son formatage.

props

une liste de listes associatives, comprenant toutes les propriétés actives.

argi

le ième argument de la commande.

argi-type?

un type de prédicat pour le ième argument.

Si la commande utilise des propriétés à partir des arguments props, le mot-clé #:properties permet de spécifier ces différentes propriétés ainsi que leur valeur par défaut.

Les arguments se distinguent selon leur type :

Il n’existe aucune restriction quant à l’ordre des arguments fournis à la suite des arguments layout et props. Néanmoins, les fonctions markup qui ont en dernier argument un markup ont ceci de particulier qu’elles peuvent s’appliquer à des listes de markups ; ceci résultera en une liste de markups où tous les éléments de la liste originelle se verront appliquer cette fonction markup avec ses arguments de tête.

La réplication des arguments de tête dans le but d’appliquer une fonction markup à une liste de markups est économique, principalement lorsqu’il s’agit d’arguments Scheme. Vous éviterez ainsi d’éventuelles pertes de performance en utilisant des arguments Scheme en tant qu’arguments principaux d’une fonction markup dont le dernier argument est un markup.

Les commandes de markup ont un cycle de vie relativement complexe. Le corps de la définition d’une commande de markup est chargé de convertir les arguments de la commande en expression stencil qui sera alors renvoyée. Bien souvent, ceci s’accomplit par un appel à la fonction interpret-markup, en lui passant les arguments layout et props. Ces arguments ne seront en principe connus que bien plus tardivement dans le processus typographique. Lors de l’expansion d’une expression LilyPond \markup ou d’une macro Scheme markup, les expressions markup auront déjà vu leurs composants assemblés en expressions markup. L’évaluation et le contrôle du type des arguments à une commande de markup n’interviennent qu’au moment de l’interprétation de \markup ou markup.

Seule l’application de interpret-markup sur une expression markup réalisera effectivement la conversion des expressions markup en stencil, au travers de l’exécution du corps des fonctions markup.


Attribution de propriétés

Les arguments layout et props d’une commande de markup fournissent un contexte à l’interprétation du markup : taille de fonte, longueur de ligne, etc.

L’argument layout permet d’accéder aux propriétés définies dans les blocs \paper, grâce à la fonction ly:output-def-lookup. Par exemple, la longueur de ligne, identique à celle de la partition, est lue au travers de

(ly:output-def-lookup layout 'line-width)

L’argument props rend certaines propriétés accessibles aux commandes de markup. Il en va ainsi lors de l’interprétation d’un markup de titre d’ouvrage : toutes les variables définies dans le bloc \header sont automatiquement ajoutées aux props, de telle sorte que le markup de titrage de l’ouvrage pourra accéder aux différents champs titre, compositeur, etc. Ceci permet aussi de configurer le comportement d’une commande de markup : la taille des fontes, par exemple, est lue à partir de props plutôt que grâce à un argument font-size. La fonction appelant une commande de markup peut altérer la valeur de la propriété taille des fontes et donc en modifier le comportement. L’utilisation du mot-clé #:properties, attaché à define-markup-command, permet de spécifier les propriétés devant être lues parmi les arguments props.

L’exemple proposé à la rubrique suivante illustre comment, au sein d’une commande de markup, accéder aux différentes propriétés et les modifier.


Exemple commenté

Nous allons, dans cet exemple, nous attacher à encadrer du texte avec un double liseré.

Commençons par construire quelque chose d’approximatif à l’aide d’un simple markup. La lecture de Commandes pour markup nous indique la commande \box, qui semble ici appropriée.

\markup \box \box HELLO

[image of music]

Dans un souci d’esthétique, nous aimerions que le texte et les encadrements ne soient pas autant accolés. Selon la documentation de \box, cette commande utilise la propriété box-padding, fixée par défaut à 0,2. Cette même documentation nous indique aussi comment la modifier :

\markup \box \override #'(box-padding . 0.6) \box A

[image of music]

L’espacement des deux liserés est cependant toujours trop réduit ; modifions le à son tour :

\markup \override #'(box-padding . 0.4) \box
     \override #'(box-padding . 0.6) \box A

[image of music]

Vous conviendrez que recopier une telle définition de markup deviendra vite fastidieux. C’est pourquoi nous écrivons la commande de markup double-box qui prendra un seul argument – le texte. Cette commande se chargera de dessiner les encadrements, en tenant compte des espacements.

#(define-markup-command (double-box layout props text) (markup?)
  "Dessine un double encadrement autour du texte."
  (interpret-markup layout props
    #{\markup \override #'(box-padding . 0.4) \box
            \override #'(box-padding . 0.6) \box { #text }#}))

ou bien son équivalent

#(define-markup-command (double-box layout props text) (markup?)
  "Dessine un double encadrement autour du texte."
  (interpret-markup layout props
    (markup #:override '(box-padding . 0.4) #:box
            #:override '(box-padding . 0.6) #:box text)))

text est le nom de l’argument de notre commande, et markup? son type – l’argument sera identifié comme étant un markup. La fonction interpret-markup, utilisée dans la plupart des commandes de markup, construira un stencil à partir de layout, props et un markup. Dans la seconde variante, ce markup sera construit à l’aide de la macro Scheme markup – voir Construction d’un markup en Scheme. La transformation d’une expression \markup en expression Scheme est des plus triviales.

Notre commande personnalisée s’utilise ainsi :

\markup \double-box A

Il serait intéressant de rendre cette commande double-box plus souple : les valeurs de box-padding sont figées et ne peuvent être modifiées à l’envie. Pareillement, il serait bien de distinguer l’espacement entre les encadrements de l’espacement entre le texte et ses encadrements. Nous allons donc introduire une propriété supplémentaire, que nous appellerons inter-box-padding, chargée de gérer l’espacement des encadrements ; box-padding ne servira alors que pour l’espacement intérieur. Voici le code adapté à ces évolutions :

#(define-markup-command (double-box layout props text) (markup?)
  #:properties ((inter-box-padding 0.4)
                (box-padding 0.6))
  "Dessine un double encadrement autour du texte."
  (interpret-markup layout props
    #{\markup \override #`(box-padding . ,inter-box-padding) \box
               \override #`(box-padding . ,box-padding) \box
               { #text } #}))

Ainsi que son équivalent à partir de la macro markup :

#(define-markup-command (double-box layout props text) (markup?)
  #:properties ((inter-box-padding 0.4)
                (box-padding 0.6))
  "Dessine un double encadrement autour du texte."
  (interpret-markup layout props
    (markup #:override `(box-padding . ,inter-box-padding) #:box
            #:override `(box-padding . ,box-padding) #:box text)))

C’est ici le mot-clé #:properties qui permet de lire les propriétés inter-box-padding et box-padding à partir de l’argumenet props ; on leur a d’ailleurs fourni des valeurs par défaut au cas où elles ne seraient pas définies.

Ces valeurs permettront alors d’adapter les propriétés de box-padding utilisées par les deux commandes \box. Vous aurez remarqué, dans l’argument \override, la présence de l’apostrophe inversée (`) et de la virgule ; elles vous permettent d’insérer une valeur variable au sein d’une expression littérale.

Notre commande est maintenant prête à servir dans un markup, et les encadrements sont repositionnables.

#(define-markup-command (double-box layout props text) (markup?)
  #:properties ((inter-box-padding 0.4)
                (box-padding 0.6))
  "Draw a double box around text."
  (interpret-markup layout props
    #{\markup \override #`(box-padding . ,inter-box-padding) \box
              \override #`(box-padding . ,box-padding) \box
              { #text } #}))

\markup \double-box A
\markup \override #'(inter-box-padding . 0.8) \double-box A
\markup \override #'(box-padding . 1.0) \double-box A

[image of music]


Adaptation d’une commande incorporée

Le meilleur moyen de construire ses propres commandes de markup consiste à prendre exemple sur les commandes déjà incorporées. La plupart des commandes de markup fournies avec LilyPond sont répertoriées dans le fichier scm/define-markup-commands.scm.

Nous pourrions, par exemple, envisager d’adapter la commande \draw-line pour dessiner plutôt une ligne double. Voici comment est définie la commande \draw-line, expurgée de sa documentation :

(define-markup-command (draw-line layout props dest)
  (number-pair?)
  #:category graphic
  #:properties ((thickness 1))
  "…documentation…"
  (let ((th (* (ly:output-def-lookup layout 'line-thickness)
               thickness))
        (x (car dest))
        (y (cdr dest)))
    (make-line-stencil th 0 0 x y)))

Avant de définir notre propre commande basée sur l’une de celles fournies par LilyPond, commençons par en recopier la définition, puis attribuons lui un autre nom. Le mot-clé #:category peut être supprimé sans risque ; il ne sert que lors de la génération de la documentation et n’est d’aucune utilité pour une commande personnalisée.

(define-markup-command (draw-double-line layout props dest)
  (number-pair?)
  #:properties ((thickness 1))
  "…documentation…"
  (let ((th (* (ly:output-def-lookup layout 'line-thickness)
               thickness))
        (x (car dest))
        (y (cdr dest)))
    (make-line-stencil th 0 0 x y)))

Nous ajoutons ensuite une propriété pour gérer l’écart entre les deux lignes, que nous appelons line-gap, et lui attribuons une valeur par défaut de 6 dixièmes :

(define-markup-command (draw-double-line layout props dest)
  (number-pair?)
  #:properties ((thickness 1)
                (line-gap 0.6))
  "…documentation…"
  …

Nous ajoutons enfin le code qui dessinera nos deux lignes. Deux appels à make-line-stencil permettrons de dessiner les lignes dont nous regrouperons les stencils à l’aide de ly:stencil-add :

#(define-markup-command (my-draw-line layout props dest)
  (number-pair?)
  #:properties ((thickness 1)
                (line-gap 0.6))
  "..documentation.."
  (let* ((th (* (ly:output-def-lookup layout 'line-thickness)
                thickness))
         (dx (car dest))
         (dy (cdr dest))
         (w (/ line-gap 2.0))
         (x (cond ((= dx 0) w)
                  ((= dy 0) 0)
                  (else (/ w (sqrt (+ 1 (* (/ dx dy) (/ dx dy))))))))
         (y (* (if (< (* dx dy) 0) 1 -1)
               (cond ((= dy 0) w)
                     ((= dx 0) 0)
                     (else (/ w (sqrt (+ 1 (* (/ dy dx) (/ dy dx))))))))))
     (ly:stencil-add (make-line-stencil th x y (+ dx x) (+ dy y))
                     (make-line-stencil th (- x) (- y) (- dx x) (- dy y)))))

\markup \my-draw-line #'(4 . 3)
\markup \override #'(line-gap . 1.2) \my-draw-line #'(4 . 3)

[image of music]


Conversion de markups en chaînes

Les markups sont parfois convertis en chaînes littérales, ce qui est le cas pour générer une métadonnée PDF basée sur le champ d’entête title ou pour convertir des paroles en MIDI. Cette conversion n’est pas parfaite, mais elle est aussi fidèle que possible. La fonction dévolue à cette opération est markup->string.

composerName = \markup \box "Arnold Schönberg"

\markup \composerName

\markup \typewriter #(markup->string composerName)

[image of music]

Dans le cas de commandes de markup personnalisées, le comportement par défaut consiste à convertir dans un premier temps tous les markups ou les arguments de la liste de markups, puis à joindre les résultats par des espaces.

#(define-markup-command (authors-and layout props authorA authorB)
                        (markup? markup?)
   (interpret-markup layout props
    #{
      \markup \fill-line { \box #authorA and \box #authorB }
    #}))

defaultBehavior = \markup \authors-and "Bertolt Brecht" "Kurt Weill"

\markup \defaultBehavior

\markup \typewriter #(markup->string defaultBehavior)

[image of music]

markup->string peut aussi recevoir les argument nommés #:layout layout et #:props props, avec la même signification que lorsqu’ils apparaissent dans la définition d’une commande de markup. Ils sont toutefois optionnels dans la mesure où ils peuvent ne pas toujours être pourvus – cas typique de l’argument layout pour convertir en MIDI.

Afin de définir une conversion différente du comportement par défaut dans une commande pour markup personnalisée, on peut fournir le paramètre #:as-string à define-markup-command. Il attend une expression, évaluée par markup->string, afin de produire la chaîne.

#(define-markup-command (authors-and layout props authorA authorB)
                        (markup? markup?)
   #:as-string (format #f "~a and ~a"
                       (markup->string authorA #:layout layout #:props props)
                       (markup->string authorB #:layout layout #:props props))
   (interpret-markup layout props
    #{
      \markup \fill-line { \box #authorA and \box #authorB }
    #}))

customized = \markup \authors-and "Bertolt Brecht" "Kurt Weill"

\markup \customized

\markup \typewriter #(markup->string customized)

[image of music]

Au sein de l’expression sont disponibles les mêmes variables que dans le corps de la commande de markup, à savoir les argument de la commande, ainsi que layout et props, et les éventuelles propriétés.

#(define-markup-command (authors-and layout props authorA authorB)
                        (markup? markup?)
   #:properties ((author-separator " and "))
   #:as-string (format #f "~a~a~a"
                       (markup->string authorA #:layout layout #:props props)
                       (markup->string author-separator #:layout layout #:props props)
                       (markup->string authorB #:layout layout #:props props))
   (interpret-markup layout props
    #{
      \markup { \box #authorA #author-separator \box #authorB }
    #}))

customized = \markup \override #'(author-separator . ", ")
                     \authors-and "Bertolt Brecht" "Kurt Weill"

\markup \customized

\markup \typewriter #(markup->string customized)

[image of music]

La plupart du temps, il suffira au gestionnaire personnalisé d’un appel récursif à markup->string sur certains arguments, comme illustré ci-dessus. Néanmoins, on peut toujours recourir directement à layout et props comme si l’on était dans le corps principal. Une attention particulière doit être portée au fait que, dès lors que #:layout n’est pas passé à markup->string, l’argument layout égale #f. L’argument props est par défaut une liste vide.


2.5.4 Définition d’une nouvelle commande de liste de markups

Une commande traitant une liste de markups se définit à l’aide de la macro Scheme define-markup-list-command, de manière analogue à la macro define-markup-command abordée à la rubrique Définition d’une nouvelle commande de markup, à ceci près que cette dernière renvoie un seul stencil, non une liste de stencils.

La fonction interpret-markup-list, à l’instar de la fonction interpret-markup, permet de convertir une liste de markups en liste de stencils.

Dans l’exemple suivant, nous définissons \paragraph, une commande de liste de markups, qui renverra une liste de lignes justifiées dont la première sera indentée. La largeur de l’alinéa sera récupérée par l’argument props.

#(define-markup-list-command (paragraph layout props args) (markup-list?)
   #:properties ((par-indent 2))
   (interpret-markup-list layout props
     #{\markuplist \justified-lines { \hspace #par-indent #args } #}))

La version purement Scheme est un peu plus complexe :

#(define-markup-list-command (paragraph layout props args) (markup-list?)
   #:properties ((par-indent 2))
   (interpret-markup-list layout props
     (make-justified-lines-markup-list (cons (make-hspace-markup par-indent)
                                             args))))

En dehors des habituels arguments layout et props, la commande de liste de markups paragraph prend en argument une liste de markups appelée args. Le prédicat des listes de markups est markup-list?.

Pour commencer, la fonction récupère la taille de l’alinéa, propriété ici dénommée par-indent, à partir de la liste de propriétés props. En cas d’absence, la valeur par défaut sera de 2. Ensuite est créée une liste de lignes justifiées grâce à la commande prédéfinie \justified-lines, liée à la fonction make-justified-lines-markup-list. Un espace horizontal est ajouté en tête, grâce à \hspace ou à la fonction make-hspace-markup. Enfin, la liste de markups est interprétée par la fonction interpret-markup-list.

Voici comment utiliser cette nouvelle commande de liste de markups :

\markuplist {
  \paragraph {
    The art of music typography is called \italic {(plate) engraving.}
    The term derives from the traditional process of music printing.
    Just a few decades ago, sheet music was made by cutting and stamping
    the music into a zinc or pewter plate in mirror image.
  }
  \override-lines #'(par-indent . 4) \paragraph {
    The plate would be inked, the depressions caused by the cutting
    and stamping would hold ink.  An image was formed by pressing paper
    to the plate.  The stamping and cutting was completely done by
    hand.
  }
}

2.6 Contextes pour programmeurs


2.6.1 Évaluation d’un contexte

Un contexte peut être modifié, au moment même de son interprétation, par du code Scheme. La syntaxe consacrée au sein d’un bloc LilyPond est

\applyContext fonction

et, dans le cadre d’un code Scheme :

(make-apply-context fonction)

fonction est constitué d’une fonction Scheme comportant un unique argument : le contexte au sein duquel la commande \applyContext est appelée. Cette fonction peut accéder aussi bien aux propriétés de grob (y compris modifiées par \override ou \set) qu’aux propriétés de contexte. Toute action entreprise par la fonction et qui dépendrait de l’état du contexte sera limitée à l’état de ce contexte au moment de l’appel à la fonction. Par ailleurs, les adaptations résultant d’un appel à \applyContext seront effectives jusqu’à ce qu’elles soient à nouveau directement modifiées ou bien annulées, quand bien même les conditions initiales dont elles dépendent auraient changé.

Voici quelques fonctions Scheme utiles avec \applyContext :

ly:context-property

recherche la valeur d’une propriété de contexte,

ly:context-set-property!

détermine une propriété de contexte,

ly:context-grob-definition
ly:assoc-get

recherche la valeur d’une propriété de grob,

ly:context-pushpop-property

réalise un \temporary \override ou un \revert sur une propriété de grob.

L’exemple suivant recherche la valeur en cours de fontSize puis la double :

doubleFontSize =
\applyContext
  #(lambda (context)
     (let ((fontSize (ly:context-property context 'fontSize)))
       (ly:context-set-property! context 'fontSize (+ fontSize 6))))

{
  \set fontSize = -3
  b'4
  \doubleFontSize
  b'
}

[image of music]

L’exemple suivant recherche la couleur des objets NoteHead, Stem et Beam, puis diminue pour chacun d’eux le degré de saturation.

desaturate =
\applyContext
  #(lambda (context)
     (define (desaturate-grob grob)
       (let* ((grob-def (ly:context-grob-definition context grob))
              (color (ly:assoc-get 'color grob-def black))
              (new-color (map (lambda (x) (min 1 (/ (1+ x) 2))) color)))
         (ly:context-pushpop-property context grob 'color new-color)))
     (for-each desaturate-grob '(NoteHead Stem Beam)))

\relative {
  \time 3/4
  g'8[ g] \desaturate g[ g] \desaturate g[ g]
 \override NoteHead.color = #darkred
  \override Stem.color = #darkred
  \override Beam.color = #darkred
  g[ g] \desaturate g[ g] \desaturate g[ g]
}

[image of music]

Ceci pourrait tout à fait s’implémenter sous la forme d’une fonction musicale, afin d’en réduire les effets à un seul bloc de musique. Notez comment ly:context-pushpop-property est utilisé à la fois pour un \temporary \override et pour un \revert :

desaturate =
#(define-music-function
   (music) (ly:music?)
   #{
     \applyContext
     #(lambda (context)
        (define (desaturate-grob grob)
          (let* ((grob-def (ly:context-grob-definition context grob))
                 (color (ly:assoc-get 'color grob-def black))
                 (new-color (map (lambda (x) (min 1 (/ (1+ x) 2))) color)))
            (ly:context-pushpop-property context grob 'color new-color)))
        (for-each desaturate-grob '(NoteHead Stem Beam)))
     #music
     \applyContext
     #(lambda (context)
        (define (revert-color grob)
          (ly:context-pushpop-property context grob 'color))
        (for-each revert-color '(NoteHead Stem Beam)))
   #})

\relative {
  \override NoteHead.color = #darkblue
  \override Stem.color = #darkblue
  \override Beam.color = #darkblue
  g'8 a b c
  \desaturate { d c b a }
  g b d b g2
}

[image of music]


2.6.2 Application d’une fonction à tous les objets de mise en forme

La manière la plus souple d’affiner un objet consiste à utiliser la commande \applyOutput. Celle-ci va insérer un événement (ApplyOutputEvent) dans le contexte spécifié. Elle répond aussi bien à la syntaxe

\applyOutput Contexte procédure

que

\applyOutput Contexte.Grob procédure

procédure est une fonction Scheme à trois arguments.

Lors de l’interprétation de cette commande, la fonction procédure est appelée pout tout objet de rendu (nommé Grob si celui-ci est spécifié) appartenant au contexte Contexte à cet instant précis, avec les arguments suivants :

De plus, ce qui est à l’origine de l’objet de rendu – l’expression musicale ou l’objet qui l’a générée – se retrouve en tant que propriété d’objet cause. Il s’agit, pour une tête de note, d’un événement NoteHead, et d’un objet Stem pour une hampe.

Voici une fonction utilisable avec la commande \applyOutput : elle « blanchit » la tête des notes se trouvant sur la ligne médiane ou bien directement à son contact.

#(define (blanker grob grob-origin context)
   (if (< (abs (ly:grob-property grob 'staff-position)) 2)
       (set! (ly:grob-property grob 'transparent) #t)))

\relative {
  a'4 e8 <<\applyOutput Voice.NoteHead #blanker a c d>> b2
}

[image of music]

La procédure sera interprétée au niveau Score (partition) ou Staff (portée) dès lors que vous utiliserez l’une des syntaxes

\applyOutput Score…
\applyOutput Staff…

2.7 Fonctions de rappel

Certaines propriétés, entre autres thickness ou direction, peuvent voir leur valeur figée à l’aide d’un \override comme ici :

\override Stem.thickness = #2.0

Une procédure Scheme peut aussi se charger de modifier des propriétés :

\override Stem.thickness = #(lambda (grob)
    (if (= UP (ly:grob-property grob 'direction))
        2.0
        7.0))
\relative { c'' b a g b a g b }

[image of music]

Dans ce cas, la procédure est exécutée dès que la valeur de la propriété est nécessaire au processus de mise en forme.

La majeure partie du procédé typographique consiste en la réalisation de tels rappels (callbacks en anglais). Entre autres propriétés utilisant particulièrement des rappels, nous mentionnerons

stencil

Routine d’impression, construisant le dessin du symbole

X-offset

Routine effectuant le positionnement horizontal

X-extent

Routine calculant la largeur d’un objet

La procédure prend un unique argument, en l’occurrence l’objet graphique (le grob).

Cette procédure, grâce à un appel à la fonction de rappel dévolue à cette propriété – mentionnée dans la référence des propriétés internes et dans le fichier define-grobs.scm –, pourra accéder à la valeur usuelle de la propriété :

\relative {
  \override Flag.X-offset = #(lambda (flag)
    (let ((default (ly:flag::calc-x-offset flag)))
      (* default 4.0)))
  c''4. d8 a4. g8
}

La valeur par défaut est aussi accessible à l’aide de la fonction grob-transformer :

\relative {
  \override Flag.X-offset = #(grob-transformer 'X-offset
    (lambda (flag default) (* default 4.0)))
  c''4. d8 a4. g8
}

[image of music]

Au sein d’un callback, le meilleur moyen d’évaluer un markup consiste à utiliser la fonction grob-interpret-markup, comme ici :

my-callback = #(lambda (grob)
                 (grob-interpret-markup grob (markup "foo")))

2.8 Conteneurs requalifiants

Note : Bien que cette rubrique reflète la situation actuelle, l’exemple qui suit est discutable et ne met rien en évidence.

Les conteneurs requalifiants permettent de faciliter le calcul des espacements en cas de modification du Y-axis – plus particulièrement les composantes Y-offset et Y-extent – à l’aide d’une fonction Scheme en lieu et place de valeurs.

L’envergure verticale (Y-extent) de certains objets dépend de la propriété stencil ; jouer sur leur stencil requiert alors une intervention supplémentaire au niveau du Y-extent à l’aide d’un conteneur transitoire. Lorsqu’une fonction affecte un Y-offset ou un Y-extent, cela déclenche la détermination des sauts de ligne de manière anticipée dans la séquence des traitements. Il en résulte que cette opération n’est en fait pas exécutée ; elle renvoie habituellement ‘0’ ou ‘'(0 . 0)’, ce qui peut engendrer des collisions. Une fonction « pure » évitera d’avorter la construction des propriétés ou objets, qui de ce fait verront leurs arguments liés à la verticalité (Y-axis) correctement évalués.

Il existe actuellement une trentaine de fonctions que l’on peut qualifier de « pures ». Le recours à un conteneur transitoire permet de requalifier une fonction de telle sorte qu’elle soit reconnue comme « pure » et soit donc évaluée avant détermination des sauts de ligne – l’espacement horizontal sera de fait ajusté en temps et en heure. La fonction « impure » sera ensuite évaluée après le positionnement des sauts de ligne.

Note : Il n’est pas toujours facile d’avoir l’assurance qu’une fonction soit qualifiée de « pure » ; aussi nous vous recommandons d’éviter d’utiliser les objets Beam ou VerticalAlignment lorsque vous désirez en créer une.

Un conteneur requalifiant se construit selon la syntaxe

(ly:make-unpure-pure-container f0 f1)

f0 est une fonction prenant n arguments (n_>=_1), le premier devant être l’objet en question ; il s’agit de la fonction dont le résultat sera réutilisé. f1 est la fonction qui sera qualifiée de « pure ». Elle prend n_+_2 arguments, le premier devant être lui aussi l’objet en question, et les second et troisième étant respectivement les « point de départ » (start) et « point d’arrivée » (end).

start et end sont dans tous les cas des valeurs fictives qui trouveront leur utilité dans le cas d’objets de type Spanner, tels les soufflets (Hairpin) ou barres de ligature (Beam), en retournant les différentes estimations de hauteur basées sur leurs début et fin d’extension.

Viennent ensuite les autres arguments de la fonction initiale f0 – autrement dit aucun si n_=_1.

Les résultats de la deuxième fonction (f1) permettent une approximation des valeurs qui seront ensuite utilisées par la fonction initiale aux fins d’ajustement lors des phases ultérieures d’espacement.

#(define (square-line-circle-space grob)
(let* ((pitch (ly:event-property (ly:grob-property grob 'cause)
                                 'pitch))
      (notename (ly:pitch-notename pitch)))
 (if (= 0 (modulo notename 2))
     (make-circle-stencil 0.5 0.0 #t)
     (make-filled-box-stencil '(0 . 1.0)
                              '(-0.5 . 0.5)))))

squareLineCircleSpace = {
  \override NoteHead.stencil = #square-line-circle-space
}

smartSquareLineCircleSpace = {
  \squareLineCircleSpace
  \override NoteHead.Y-extent =
   #(ly:make-unpure-pure-container
      ly:grob::stencil-height
      (lambda (grob start end) (ly:grob::stencil-height grob)))
}

\new Voice \with { \remove Stem_engraver }
\relative c'' {
  \squareLineCircleSpace
  cis4 ces disis d
  \smartSquareLineCircleSpace
  cis4 ces disis d
}

[image of music]

La première mesure de l’exemple ci-dessus ne fait pas appel à un conteneur requalifiant ; le moteur d’espacement n’a donc aucune connaissance de la largeur des têtes de note et ne peut empêcher qu’elles chevauchent les altérations. Dans la deuxième mesure, par contre, le recours à un conteneur requalifiant informe le moteur d’espacement de la largeur des têtes de note ; les collisions sont alors évitées du fait de l’espace réservé à chacune des têtes.

Lorsqu’il s’agit de calculs simples, les fonctions, tant pour la partie « pure » que pour la partie « impure », peuvent être identiques au détail près du nombre d’arguments utilisés ou du domaine d’intervention. Ce cas de figure étant relativement répandu, ly:make-unpure-pure-container construira d’elle même cette deuxième lorsqu’il ne sera fait appel qu’à une seule fonction en argument.

Note : Le fait de qualifier une fonction de « pure » alors qu’elle ne l’est pas peut générer des résultats imprévisibles.


2.9 Retouches complexes

Certains réglages sont plus délicats que d’autres.


3 Interfaces LilyPond Scheme

Ce chapitre aborde les différents outils fournis par LilyPond à l’intention des programmeurs en Scheme désireux d’obtenir des informations à partir et autour des fluxs de musique.

TODO – figure out what goes in here and how to organize it


Appendix A GNU Free Documentation License

Version 1.3, 3 November 2008
Copyright © 2000, 2001, 2002, 2007, 2008 Free Software Foundation, Inc.
https://fsf.org/

Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
  1. PREAMBLE

    The purpose of this License is to make a manual, textbook, or other functional and useful document free in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and publisher a way to get credit for their work, while not being considered responsible for modifications made by others.

    This License is a kind of “copyleft”, which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software.

    We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference.

  2. APPLICABILITY AND DEFINITIONS

    This License applies to any manual or other work, in any medium, that contains a notice placed by the copyright holder saying it can be distributed under the terms of this License. Such a notice grants a world-wide, royalty-free license, unlimited in duration, to use that work under the conditions stated herein. The “Document”, below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as “you”. You accept the license if you copy, modify or distribute the work in a way requiring permission under copyright law.

    A “Modified Version” of the Document means any work containing the Document or a portion of it, either copied verbatim, or with modifications and/or translated into another language.

    A “Secondary Section” is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or authors of the Document to the Document’s overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (Thus, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical or political position regarding them.

    The “Invariant Sections” are certain Secondary Sections whose titles are designated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. If a section does not fit the above definition of Secondary then it is not allowed to be designated as Invariant. The Document may contain zero Invariant Sections. If the Document does not identify any Invariant Sections then there are none.

    The “Cover Texts” are certain short passages of text that are listed, as Front-Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A Front-Cover Text may be at most 5 words, and a Back-Cover Text may be at most 25 words.

    A “Transparent” copy of the Document means a machine-readable copy, represented in a format whose specification is available to the general public, that is suitable for revising the document straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format whose markup, or absence of markup, has been arranged to thwart or discourage subsequent modification by readers is not Transparent. An image format is not Transparent if used for any substantial amount of text. A copy that is not “Transparent” is called “Opaque”.

    Examples of suitable formats for Transparent copies include plain ASCII without markup, Texinfo input format, LaTeX input format, SGML or XML using a publicly available DTD, and standard-conforming simple HTML, PostScript or PDF designed for human modification. Examples of transparent image formats include PNG, XCF and JPG. Opaque formats include proprietary formats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally available, and the machine-generated HTML, PostScript or PDF produced by some word processors for output purposes only.

    The “Title Page” means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, “Title Page” means the text near the most prominent appearance of the work’s title, preceding the beginning of the body of the text.

    The “publisher” means any person or entity that distributes copies of the Document to the public.

    A section “Entitled XYZ” means a named subunit of the Document whose title either is precisely XYZ or contains XYZ in parentheses following text that translates XYZ in another language. (Here XYZ stands for a specific section name mentioned below, such as “Acknowledgements”, “Dedications”, “Endorsements”, or “History”.) To “Preserve the Title” of such a section when you modify the Document means that it remains a section “Entitled XYZ” according to this definition.

    The Document may include Warranty Disclaimers next to the notice which states that this License applies to the Document. These Warranty Disclaimers are considered to be included by reference in this License, but only as regards disclaiming warranties: any other implication that these Warranty Disclaimers may have is void and has no effect on the meaning of this License.

  3. VERBATIM COPYING

    You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in section 3.

    You may also lend copies, under the same conditions stated above, and you may publicly display copies.

  4. COPYING IN QUANTITY

    If you publish printed copies (or copies in media that commonly have printed covers) of the Document, numbering more than 100, and the Document’s license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects.

    If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the actual cover, and continue the rest onto adjacent pages.

    If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine-readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a computer-network location from which the general network-using public has access to download using public-standard network protocols a complete Transparent copy of the Document, free of added material. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public.

    It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document.

  5. MODIFICATIONS

    You may copy and distribute a Modified Version of the Document under the conditions of sections 2 and 3 above, provided that you release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the Modified Version:

    1. Use in the Title Page (and on the covers, if any) a title distinct from that of the Document, and from those of previous versions (which should, if there were any, be listed in the History section of the Document). You may use the same title as a previous version if the original publisher of that version gives permission.
    2. List on the Title Page, as authors, one or more persons or entities responsible for authorship of the modifications in the Modified Version, together with at least five of the principal authors of the Document (all of its principal authors, if it has fewer than five), unless they release you from this requirement.
    3. State on the Title page the name of the publisher of the Modified Version, as the publisher.
    4. Preserve all the copyright notices of the Document.
    5. Add an appropriate copyright notice for your modifications adjacent to the other copyright notices.
    6. Include, immediately after the copyright notices, a license notice giving the public permission to use the Modified Version under the terms of this License, in the form shown in the Addendum below.
    7. Preserve in that license notice the full lists of Invariant Sections and required Cover Texts given in the Document’s license notice.
    8. Include an unaltered copy of this License.
    9. Preserve the section Entitled “History”, Preserve its Title, and add to it an item stating at least the title, year, new authors, and publisher of the Modified Version as given on the Title Page. If there is no section Entitled “History” in the Document, create one stating the title, year, authors, and publisher of the Document as given on its Title Page, then add an item describing the Modified Version as stated in the previous sentence.
    10. Preserve the network location, if any, given in the Document for public access to a Transparent copy of the Document, and likewise the network locations given in the Document for previous versions it was based on. These may be placed in the “History” section. You may omit a network location for a work that was published at least four years before the Document itself, or if the original publisher of the version it refers to gives permission.
    11. For any section Entitled “Acknowledgements” or “Dedications”, Preserve the Title of the section, and preserve in the section all the substance and tone of each of the contributor acknowledgements and/or dedications given therein.
    12. Preserve all the Invariant Sections of the Document, unaltered in their text and in their titles. Section numbers or the equivalent are not considered part of the section titles.
    13. Delete any section Entitled “Endorsements”. Such a section may not be included in the Modified Version.
    14. Do not retitle any existing section to be Entitled “Endorsements” or to conflict in title with any Invariant Section.
    15. Preserve any Warranty Disclaimers.

    If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Document, you may at your option designate some or all of these sections as invariant. To do this, add their titles to the list of Invariant Sections in the Modified Version’s license notice. These titles must be distinct from any other section titles.

    You may add a section Entitled “Endorsements”, provided it contains nothing but endorsements of your Modified Version by various parties—for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard.

    You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back-Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one.

    The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version.

  6. COMBINING DOCUMENTS

    You may combine the Document with other documents released under this License, under the terms defined in section 4 above for modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified, and list them all as Invariant Sections of your combined work in its license notice, and that you preserve all their Warranty Disclaimers.

    The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work.

    In the combination, you must combine any sections Entitled “History” in the various original documents, forming one section Entitled “History”; likewise combine any sections Entitled “Acknowledgements”, and any sections Entitled “Dedications”. You must delete all sections Entitled “Endorsements.”

  7. COLLECTIONS OF DOCUMENTS

    You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects.

    You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document.

  8. AGGREGATION WITH INDEPENDENT WORKS

    A compilation of the Document or its derivatives with other separate and independent documents or works, in or on a volume of a storage or distribution medium, is called an “aggregate” if the copyright resulting from the compilation is not used to limit the legal rights of the compilation’s users beyond what the individual works permit. When the Document is included in an aggregate, this License does not apply to the other works in the aggregate which are not themselves derivative works of the Document.

    If the Cover Text requirement of section 3 is applicable to these copies of the Document, then if the Document is less than one half of the entire aggregate, the Document’s Cover Texts may be placed on covers that bracket the Document within the aggregate, or the electronic equivalent of covers if the Document is in electronic form. Otherwise they must appear on printed covers that bracket the whole aggregate.

  9. TRANSLATION

    Translation is considered a kind of modification, so you may distribute translations of the Document under the terms of section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License, and all the license notices in the Document, and any Warranty Disclaimers, provided that you also include the original English version of this License and the original versions of those notices and disclaimers. In case of a disagreement between the translation and the original version of this License or a notice or disclaimer, the original version will prevail.

    If a section in the Document is Entitled “Acknowledgements”, “Dedications”, or “History”, the requirement (section 4) to Preserve its Title (section 1) will typically require changing the actual title.

  10. TERMINATION

    You may not copy, modify, sublicense, or distribute the Document except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, or distribute it is void, and will automatically terminate your rights under this License.

    However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation.

    Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice.

    Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, receipt of a copy of some or all of the same material does not give you any rights to use it.

  11. FUTURE REVISIONS OF THIS LICENSE

    The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. See https://www.gnu.org/licenses/.

    Each version of the License is given a distinguishing version number. If the Document specifies that a particular numbered version of this License “or any later version” applies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not specify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation. If the Document specifies that a proxy can decide which future versions of this License can be used, that proxy’s public statement of acceptance of a version permanently authorizes you to choose that version for the Document.

  12. RELICENSING

    “Massive Multiauthor Collaboration Site” (or “MMC Site”) means any World Wide Web server that publishes copyrightable works and also provides prominent facilities for anybody to edit those works. A public wiki that anybody can edit is an example of such a server. A “Massive Multiauthor Collaboration” (or “MMC”) contained in the site means any set of copyrightable works thus published on the MMC site.

    “CC-BY-SA” means the Creative Commons Attribution-Share Alike 3.0 license published by Creative Commons Corporation, a not-for-profit corporation with a principal place of business in San Francisco, California, as well as future copyleft versions of that license published by that same organization.

    “Incorporate” means to publish or republish a Document, in whole or in part, as part of another Document.

    An MMC is “eligible for relicensing” if it is licensed under this License, and if all works that were first published under this License somewhere other than this MMC, and subsequently incorporated in whole or in part into the MMC, (1) had no cover texts or invariant sections, and (2) were thus incorporated prior to November 1, 2008.

    The operator of an MMC Site may republish an MMC contained in the site under CC-BY-SA on the same site at any time before August 1, 2009, provided the MMC is eligible for relicensing.

ADDENDUM: How to use this License for your documents

To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page:

  Copyright (C)  year  your name.
  Permission is granted to copy, distribute and/or modify this document
  under the terms of the GNU Free Documentation License, Version 1.3
  or any later version published by the Free Software Foundation;
  with no Invariant Sections, no Front-Cover Texts, and no Back-Cover
  Texts.  A copy of the license is included in the section entitled ``GNU
  Free Documentation License''.

If you have Invariant Sections, Front-Cover Texts and Back-Cover Texts, replace the “with…Texts.” line with this:

    with the Invariant Sections being list their titles, with
    the Front-Cover Texts being list, and with the Back-Cover Texts
    being list.

If you have Invariant Sections without Cover Texts, or some other combination of the three, merge those two alternatives to suit the situation.

If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software.


Appendix B Index de LilyPond

Jump to:   A   C   D   E   F   G   L   M   O   P   R   S   T   U   V   É  
Index Entry  Section

A
accéder à Scheme 1 Tutoriel Scheme
appel de code durant l’interprétation 2.6.1 Évaluation d’un contexte
appel de code sur des objets de mise en forme 2.6.2 Application d’une fonction à tous les objets de mise en forme

C
code, blocs LilyPond 2.1 Blocs de code LilyPond

D
displayMusic 1.3.2 Affichage d’expressions musicales
débogage, code Scheme 1.2.3 Débogage de code Scheme
définition d’une commande markup 2.5.1 Construction d’un markup en Scheme
dérogation temporaire (override) 2.3.4 Fonctions de substitution intermédiaires

E
erreur Scheme, localisation du code 1.2.3 Débogage de code Scheme
espacement horizontal, affinage 2.8 Conteneurs requalifiants
expression musicale, affichage 1.3.2 Affichage d’expressions musicales

F
fantôme, fonction 2.2.3 Fonctions Scheme fantômes
fonction musicale, définition 2.3.1 Définition de fonctions musicales

G
Guile 1 Tutoriel Scheme

L
ligne, localisation, message d’erreur Scheme 1.2.3 Débogage de code Scheme
LilyPond, bloc de code 2.1 Blocs de code LilyPond
LISP 1 Tutoriel Scheme
liste de markup, définition de commande 2.5.4 Définition d’une nouvelle commande de liste de markups
localisation du code, message d’erreur Scheme 1.2.3 Débogage de code Scheme

M
macro de markup Syntaxe d’une commande markup
markup macro Syntaxe d’une commande markup
markup, conversion en chaîne Conversion de markups en chaînes
markup, définition d’une commande 2.5.1 Construction d’un markup en Scheme
musicale, fonction 2.3 Fonctions musicales

O
objets de mise en forme, appel de code 2.6.2 Application d’une fonction à tous les objets de mise en forme

P
propriétés ou variables 1.2.6 Propriétés des objets
propriétés, retour à la valeur précédente 2.3.4 Fonctions de substitution intermédiaires
pure containers, Scheme 2.8 Conteneurs requalifiants

R
représentation interne, affichage 1.3.2 Affichage d’expressions musicales

S
Scheme 1 Tutoriel Scheme
Scheme, débogage 1.2.3 Débogage de code Scheme
Scheme, fonctions (syntaxe LilyPond) 2.2 Fonctions Scheme
Scheme, inclusion de code 1 Tutoriel Scheme
Scheme, pure container 2.8 Conteneurs requalifiants
Scheme, unpure container 2.8 Conteneurs requalifiants
stockage interne 1.3.2 Affichage d’expressions musicales

T
temporaire, dérogation (override) 2.3.4 Fonctions de substitution intermédiaires

U
unpure containers, Scheme 2.8 Conteneurs requalifiants

V
variables ou propriétés 1.2.6 Propriétés des objets
void, fonction 2.2.3 Fonctions Scheme fantômes

É
évaluation Scheme 1 Tutoriel Scheme
événementielle, fonction 2.4 Fonctions événementielles

Jump to:   A   C   D   E   F   G   L   M   O   P   R   S   T   U   V   É  

Table of Contents


GNU LilyPond – Extension des fonctionnalités v2.25.14 (branche de développement).