lilypond: Translating tempi with Scheme

Posted on

Lilypond is an excellent musical score typesetting system. If you like LaTeX for text (and not for text-only) documents, you will certainly like Lilypond — as soon as you’ll need to type a couple of notes or, let’s say, opera (if you don’t know what’s LaTeX — you will know and enjoy, my congratulations!-).

One of the best, if not simply the best, things about Lilypond — possibility to integrate score notation and Scheme code: to define macros, for programming… virtually everything. For example, you may put notes with accidentals in different colours, change size of noteheads depending of pitch, or perform more useful tasks — define custom rules for accidental placement, make «templates» for text marks, manage beaming etc-etc-etc (sorry, i can not ever explain how wide you possibilities really).

In this paper i’ll show how i manage tempo marks (Allegro, Moderato, Leggiero e calmo) translations, Italian to Ukrainian.

Task

Let’s say we are preparing score book, something about 200 pages, and we don’t know so far — whether we like tempo marks to appear in Italian or Ukrainian. Besides, we are still discussing Ukrainian terminology in this field, so everything may change in any moment. So, we need to have a «tool» for easy translating tempo marks to Ukrainian, or leave it back in Italian.

Strategy

Starting with Lilypond 2.11.65, afaicr, Lilypond’s command for tempo accepts markup too:

% these are possible \tempo arguments:
\tempo "Allegro"
\tempo 4 = 120
\tempo "Moderato" 4 = 60
\tempo \markup { \italic Faster } 4 = 132

We will create a function which returns markup, and will use it like this:

% our score:
{
  \tempo \markup { \ukr #"Allegro" }
  c4 c c c
}

Solution

Let’s define a «dictionary» as a pair list:

#(define tempi
  '(
    ("Adagio" . "Повільно")
    ("Allegro" . "Швидко")
    ; etc-etc....  
  ))

Our function will be very simple, we can already write it (this is the way Lilypond allows to define functions which return markup):

#(define-markup-command (ukr layout props word) (string?)
  (interpret-markup layout props
    (markup (getLocalized tempi word))))

So, we need to write getLocalized, which will take a «dictionary» and «word to translate».

This function will be very simple too:

#(define (getLocalized items word)
  (if (equal? '() items)
      word
      (if (equal? (car (car items)) word)
          (cdr (car items))
          (getLocalized (cdr items) word))))))

This means: if it reached the end of the «dictionary», return untranslated «word». Otherwise compare «word» with the first element in a pair and return the second element, if the first one matches. Otherwise call self recursively, with shortened «dictionary».

Additional possibilities

Ok, everything is great, but how we can distinguish whether «this tempo mark» was not translated because of «poor» dictionary, or translation was mot made at all? By the way, how to switch translation off? So, we need to modify out code a bit. Let’s add flags for «do we need to translate», «do we need to mark untranslated words» and «do we need to mark translated words»:

%
% ukr.lyi
%

% do we need to translate:
doLocalizeTempo = ##t
% do we need to mark (in some way — see below) untranslated words:
doMarkUntransTempo = ##t
% do we need to mark translated words:
doMarkTransTempo = ##t

#(define tempi
  '(
    ("Adagio" . "Повільно")
    ("Allegro" . "Швидко")
    ; і так далі....  
  ))

% mark translated word —
% put a raised dot before word:
#(define (markTransTempo word)
  (if (and (equal? #t doMarkTransTempo) (equal? #t doLocalizeTempo))
      (markup #:raise 0.5 "." word)
      word))

% mark untranslated word —
% underline it:
#(define (markUntransTempo word)
  (if (and (equal? #t doMarkUntransTempo) (equal? #t doLocalizeTempo))
      (markup #:underline word)
      word))

% dictionary search:
#(define (getLocalized items word)
  (if (equal? '() items)
      (markUntransTempo word)
      (if (equal? (car (car items)) word)
          (markTransTempo (cdr (car items)))
          (getLocalized (cdr items) word))))

#(define-markup-command (ukr layout props word) (string?)
  (interpret-markup layout props
    (markup (if (equal? #t doLocalizeTempo)
                (getLocalized tempi word)
                word))))

So, we can re-use this code — in every score of our book — in this way:

%
\include "ukr.lyi"

% and here are note fragment, simple or complex:
{
  \tempo \markup { \ukr #"Moderato" }
  c4 c c c
}

With an «one click» we manage translations of tempo marks, in whole score book — that’s Lilypond. I like ti very much :-)

Some notes

  1. Obviously, if we need to «switch off» translation for a single score, we can use in this score our flags:

    % do we need to translate?
    doLocalizeTempo = ##f
    

    The same is true for translations marking.

  2. In Lilypond user mailing list i was advised to use assoc-get for fetching a pair from a dictionary, but i can not figure out how to perform marking in a simple manner.
  3. Well… This may not be very clever at all — i’m not a programmer, please don’t forget :-) — but this «works for me».

One Reply to “lilypond: Translating tempi with Scheme”

Leave a Reply

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

This site uses Akismet to reduce spam. Learn how your comment data is processed.