Les fonctions musicales 1. Premier contact avec Scheme dans Lilypond

Traduction d’un article de Urs Liska

Cet article est le premier d’une série consacrée à l’utilisation de Scheme dans nos fichiers Lilypond. Petite précision : je ne suis pas un grand prêtre du Scheme : il s’agit juste de vous transmettre ce que je sais de Scheme, dans l’ordre où je l’ai appris.

La programmation Scheme au sein de fichiers .ly est l’une des grandes forces de Lilypond. Il ne s’agit pas simplement de script de surface mais d’intéraction profonde avec le programme : Lilypond fonctionnant sur une combinaison de C++ et de Scheme, il est possible alors, en injectant du code Scheme dans les fichiers à compiler, de modifier et surtout d’étendre le comportement intrinsèque de Lilypond.

Revers de la médaille : non seulement Scheme n’est pas à proprement parler un langage facile d’accès, mais ses interactions avec les mécanismes de Lilypond ne sont pas évidentes à comprendre. Ajoutons que si la documentation de Lilypond est complète et claire pour les parties Initiation et Notation, celle consacrée à son fonctionnement interne est assez concise, et parfois difficile d’accès. La partie Extension des fonctionnalités, consacrée à l’extension de Lilypond via Scheme, en fait hélas partie. Il m’a donc paru intéressant de rédiger une introduction à Scheme plus accessible. Je ne traiterai pas du langage Scheme à proprement parler – cela dépasse mes compétences : je me limiterai à l’utilisation concrète de Scheme dans Lilypond, en essayant d’être plus explicite et pratique que la documentation officielle. Bien entendu, toute remarque sera la bienvenue, et ceux ou celles qui souhaitent traiter un sujet particulier et apporter leur pierre à l’édifice sont les bienvenu(e)s !

Préface : Comment inclure du Scheme dans le code Lilypond

Comme le dit la documentation, Lilypond interprète tout ce qui suit un signe # comme du Scheme, plus précisément : évalue l’expression qui suit le #. En Scheme, tout est expression qui doit être évaluée : il ne s’agit pas, comme dans la plupart des langages, d’exécuter du code, de lancer des commandes, et c’est pourquoi Scheme et les autres dialectes LISP sont assez difficiles d’accès pour des programmeurs habitués à autres langages. À ce titre, Lilypond fonctionne comme Scheme – il y a même un chapitre du manuel d’initiation intitulé : “La partition est une (unique) expression musicale composée”.

Autre chose à noter : les variables Lilypond et les variables Scheme dans les fichiers .ly sont interchangeables.

Regardons cela dans un exemple concret :

\version "2.18.0"

% création d'une variable en utilisant la syntaxe habituelle de Lilypond
tempoA = 60

% création d'une variable en utilisant Scheme
#(define tempoB 72)

{
  % définition littérale du tempo
  \tempo 8 = 54
  R1
  
  % définition du tempo grâce aux variables en utilisant la syntaxe Lilypond
  \tempo 8 = \tempoA
  R1
  
  \tempo 8 = \tempoB
  R1
  
   % définition du tempo grâce aux variables en utilisant la syntaxe Scheme
  \tempo 8 = #tempoA
  R1
  
  \tempo 8 = #tempoB
  R1 
}

Nous définissons tout d’abord tempoA, une variable Lilypond classique ; puis sa sœur jumelle en Scheme (tempoB). Nous indiquons à Lilypond, grâce au signe #, qu’il s’agit d’une variable en Scheme, définie entre les deux parenthèses qui suivent (ah, les parenthèses en Scheme ! Les débutants adorent ça, nous y reviendrons !). L’expression Scheme définit une variable tempoB et lui attribue la valeur 72. Nous avons donc deux variables, tempoA et tempoB, dont les valeurs sont différentes, mais qui jouent le même rôle.

first-music-function

Comme vous le voyez, nous pouvons aussi coder littéralement la valeur du tempo, sans utiliser de variable. Lilypond utilise indifféremment ces trois types de codages.

Notre première fonction Scheme

Nous allons maintenant écrire notre première fonction Scheme, en suivant la documentation “Extension des fonctionnalités”. Il s’agira d’une fonction peu utile, à vrai dire, mais qui fonctionne et qui est facile à comprendre.

Commençons par l’écrire avec la syntaxe habituelle de Lilypond :

\version “2.18.0″

maFonction = { c2 }

\relative c{
c4 \maFonction c
}

Nous avons défini une variable nommée maFonction, qui peut être utilisée à l’intérieur du code musical (ici, nous insérons un do blanche). Notez un point important : la valeur blanche n’est pas retenue par le programme pour la suite des notes : le dernier do est, comme le premier, une noire.

Maintenant, écrivons la même fonction, mais en Scheme :

maFonctionScheme =
#(define-music-function (parser location)()
   #{
     c2
   #})

\relative c' {
  c4 \maFonctionScheme c
}

first-music-function

Nous avons donc défini la variable Scheme maFonctionScheme, et lui avons attribué une expression, située après le signe # (et entre parenthèses). L’expression est composée de quatre parties :

  • Le mot-clé : define-music-function
    En fait, il ne s’agit pas d’un mot-clé Scheme mais d’une fonction définie par Lilypond. Comme son nom l’indique, il s’agit dune fonction musicale qui retourne de la musique. On peut donc l’utiliser comme expression musicale au sein du code musical Lilypond, comme le montre l’avant-dernière ligne du code ci-dessus. Les trois éléments de code qui suivent sont les paramètres attendus par cette fonction.

  • Les paramètres
    Les deux paramètres parser et location sont obligatoires, même si vous n’aurez en réalité pas besoin d’eux par la suite (notez-les, mais oubliez-les !). Si vous souhaitez que votre fonction utilise des paramètres “utiles” (nous verrons cela dans un article ultérieur), vous ajouterez des paramètres à la suite de ces deux premiers – par exemple : (parser, location, hauteur) pour le paramètre unique hauteur.

  • Le type de paramètre
    Dans le cas de la liste (parser, location, hauteur), il faudra préciser le type de la variable hauteur – sans doute (number?) pour nombre. Mais dans l’exemple qui nous occupe pour l’instant, n’ayant pas de paramètre autre que parser et location, nous laissons la liste des types vide : ()

  • L’expression musicale proprement dite
    Comme nous l’avons dit, la fonction retourne une expression musicale. De façon générale, les fonctions Scheme retournent le résultat de la dernière expression, et c’est donc en fin de fonction que nous allons écrire notre expression musicale. Mais au lieu de l’écrire en Scheme (nous verrons cela plus tard), nous utilisons la construction #{ ... #} qui nous permet, fort heureusement, d’utiliser le code Lilypond au sein de Scheme. Concrètement, tout ce qui est situé entre #{ et }# est traité comme une unique expression Scheme, bien qu’étant écrit en Lilypond. Dans notre exemple, l’expression retournée contient c2. La fonction retourne donc un do blanche, et notre exemple donne, comme précédemment : {c4 c2 c4}.

Nous sommes d’accord : une fonction qui retourne des valeurs codables directement en Lilypond ne sert pas à grand chose. Mais j’ai pensé que cet exemple vous permettrait de bien comprendre l’interconnexion des codes Lilypond et Scheme.

Je vous promet que, dans le prochain article, nous ferons quelque chose d’utile !


Commentaires après la version anglaise

vvillenave March 28, 2014 at 09:58

Voici comment j’aime initier mes élèves au concept de variables Scheme. Le code est très proche du tien, mais il permet de montrer l’utilité de Scheme :

#(define maListe
   (list
    #{ c2 #}
    #{ d2 #}
    #{ e2 #}))

maFonctionScheme =
#(define-music-function (parser location) ()
   (list-ref maListe (random (length maListe))))

\relative c' {
  c4 \maFonctionScheme c
}

Réponse ↓
Urs Liska March 28, 2014 at 10:16

Merci pour ces précisions. Peux-tu expliquer ce que fait la fonction avec ta liste ?

Réponse ↓
Phil Holmes March 28, 2014 at 15:01

Je suppose que la fonction est sensée retourner une note de la liste. Concrètement, elle retourne toujours la même note – et si tu l’appelle plusieurs fois, elle retourne toujours la même suite de notes, parce que la fonction “aléatoire” n’est pas réinitialisée à chaque appel, pour donner une nouvelle suite de notes.
Voici une solution, hélas pas aussi simple, pour contourner cette limitation.

#(define maListe
   (list
    #{ b2 #}
    #{ c'2 #}
    #{ d'2 #}
    #{ e'2 #}))

maFonctionScheme =
#(define-music-function (parser location) ()
   (list-ref maListe (random (length maListe))))

monHasard =
#(define-void-function (parser location) ()
   (let ((time (gettimeofday)))
     (set! *random-state*
           (seed->random-state (+ (car time)
                                 (cdr time))))))


{
  \monHasard c'4 \maFonctionScheme c' \maFonctionScheme c' \maFonctionScheme c' \maFonctionScheme c' \maFonctionScheme c'
}

Réponse ↓
vvillenave March 30, 2014 at 20:04

En fait, on n’est pas sensé la faire tourner plusieurs fois ;o)
A vrai dire, ça marche bien quand les élèves la font tourner sur leur propre ordinateur : ils obtiennent des résultats différents. Et ils ont une idée de ce à quoi Scheme pourrait leur servir.


Peter Bjuhr March 28, 2014 at 12:43

Super, je pense que beaucoup de lilypondeurs vont apprécier cette série d’articles !


Helge March 29, 2014 at 07:13
Belle introduction !

Quand tu écriras tes articles, peux-tu mentionner comment passer des paramètres aux fonctions et leur typage, ainsi que la différence entre la musique et le texte ? Quand utiliser quoi, en fait.

Réponse ↓
Urs Liska March 29, 2014 at 23:07

Merci pour les encouragements ! Je ne sais pas encore exactement ce que je vais expliquer. Le prochain article est presque terminé, et j’ai une idée assez claire du troisième. Et je vois arriver le moment où je vais devoir apprendre moi-même avant de pouvoir écrire un article dessus ;o)


Erich April 6, 2014 at 07:06
Remarque pour le chapitre “Notre première fonction Scheme”

maFonction = { c2 }

\relative c' {
  c4 \maFonction c
}

Ce n’est pas exactement une substitution, parce que le code \relative c' { c4 {c2} c} donnerait un autre résultat.


Erich April 6, 2014 at 07:44

“Notre première fonction Scheme”
Autre remarque :

maFonctionScheme =
#(define-music-function (parser location)()
   #{
     c2
   #})

\relative c' {
  c4 \maFonctionScheme c
}

Il s’agit bien d’une fonction sans paramètre, et le type ne doit donc pas être déclaré. Il serait plus simple d’expliquer cet exemple après avoir fait une fonction paramétrée.

One thought on “Les fonctions musicales 1. Premier contact avec Scheme dans Lilypond

  1. Pingback: Les fonctions musicales 2. Commençons à faire quelque chose d’utile ! | Scores of Beauty

Leave a Reply

Your email address will not be published. Required fields are marked *