Lento’s interface consists of a class **Part** and a main processing function proc. A configuration file **.lento** can be used to set some system variables.


Part

Signature: (events, metadata={})

A Part object can be thought of as a single instrument.

  • events: a dictionary which accepts following keys:

    • "notes": a collection (monophon Part) or a collection of collections (polyphon Part) of either MIDI key numbers or Lilypond note names (as strings, including Lilypond rests "r" and spacer rests "s", more on notes and rests) or a sorted collection (list or tuple) of them as chords. Please note that MIDI key numbers below or beyond the MIDI range (0, 127) can also be notated (if no midi output needed). Microtonal notes smaller than quarter tones are supported according to the specifications of **Ekmelily**. Please note that ekmelily is loaded into the ly file by default. It is possible to prevent it from being loaded by setting the load_ekmelily key to no in the .lento configuration file.

    • "beats": a collection (monophon Part) or a collection of collections (polyphon Part) of integers or floats.

    • "durations": a dictionary with keys being beats getting the new durations and values being the durations.

  • metadata: a dictionary which accepts zero (default) or more of the following keys. If Part is polyphon, a specific metadata can be assigned to a single voice by wrapping that metadata’s dictionary inside an outer dictionary with keys of type integer representing the voice number (starting from 0) and values being the metadata dictionary. If Part is polyphon and no voice assignment takes place, the metadata dictionary will be applied to all voices likewise.

    • "who" : a string. This should be a unique id and will be used in the ly file as the variable name holding the processed Part instance. If "who" is not specified, lento will generate a random id.

    • “what”: a dictionary which accepts the following two keys:

      • "name": value should be a string used as the instrument name.

      • "abbr": value should be a string used as the abbreviation for the instrument name.

    • “staff”: a dictionary which accepts following key/value pairs:

      • "n": an integer specifying the number of systems. Defaults to 1.

      • “types”: a string specifying the type of the staff which can be any of the following names: "basic", "drum", "rhythmic", "tablature", "mensural", "vaticana", "gregorian", or a dictionary (in case the Part instance is polyphonic) with keys being integers specifying the number of each voice (starting from 0) and values being one of the above staff names.

      • “bind”: a string specifying the type of bracket used for grouping the stave together. It can be one of: "basic", "group", "choir", "grand", "piano"

    • “timesig”: a dictionary which accepts following key/value pairs:

      • key: can be an integer specifying the beat (started from 0) before which the new time signature should appear, or a two element tuple specifying the time interval to which the time signature should be applied. If key is a tuple, time signature will be set according to the time interval specified by the tuple, i.e. time signature changes will occure at proper points if the time interval divided by the numerator of the time signature yields a rest. Overlapping time signature intervals will be processed, setting the current time signature back to the previous one if the current one lies inside the range of the previous time signature. If key is an integer, the time signature will be valid up to the next integer key. This will overwrite all future time intervals (please note that this also doesn’t allow any future time interval specifications for setting time signatures. Consider using tuples as _key_s instead).

      • value: a two element tuple or list representing the numerator and the denominator of the time signature. Defaults to (4, 4). (**timesig example**)

*  `"clef"`: a dictionary which accepts following key/value pairs:
    *  __key__: can be an integer or a float specifying the beat where the clef change should take place, or a two element tuple specifying the time interval to which the clef change should be applied. Overlapping time intervals will be processed, setting the current clef back to the previous one if the current one lies inside the range of the previous clef. If the key is not a tuple (i.e. is an integer or a float), the clef will be valid up to the next _integer_ or _float_ key. This will overwrite all future time intervals (please note that this also doesn't allow any future time interval specifications for setting clefs. Consider using tuples as _key_s instead).
    *  __value__: can be one of the following strings: `"C", "F", "G", "G2", "GG", "alto", "altovarC", "baritone", "baritonevarC", "baritonevarF", "bass", "blackmensural-c1", "blackmensural-c2", "blackmensural-c3", "blackmensural-c4", "blackmensural-c5", "french", "hufnagel-do-fa", "hufnagel-do1", "hufnagel-do2", "hufnagel-do3", "hufnagel-fa1", "hufnagel-fa2", "kievan-do", "medicaea-do1", "medicaea-do2", "medicaea-do3", "medicaea-fa1", "medicaea-fa2", "mensural-c1", "mensural-c2", "mensural-c3", "mensural-c4", "mensural-c5", "mensural-f", "mensural-g", "mezzosoprano", "moderntab", "neomensural-c1", "neomensural-c2", "neomensural-c3", "neomensural-c4", "neomensural-c5", "percussion", "petrucci-c1", "petrucci-c2", "petrucci-c3", "petrucci-c4", "petrucci-c5", "petrucci-f", "petrucci-f2", "petrucci-f3", "petrucci-f4", "petrucci-f5", "petrucci-g", "petrucci-g1", "petrucci-g2", "soprano", "subbass", "tab", "tenor", "tenorG", "tenorvarC", "treble", "varC", "varbaritone", "varpercussion", "vaticana-do1", "vaticana-do2", "vaticana-do3", "vaticana-fa1", "vaticana-fa2", "violin"`. Transposing clefs can be written in the form `"clef+N"`, `"clef+[N]"` or `"clef+(N)"` for a _clef_ transpoing _N_ diatonic steps upwards, or `"clef-N"`, `"clef-[N]"` or `"clef-(N)"` for a _clef_ transpoing _N_ diatonic steps downwards. Defaults to `"treble"`. ([clef example](#clef_example))


*  `"notehead"`: a dictionary which accepts following key/value pairs:
    *  __key__: can be an integer or a float specifying the beat where the notehead should appear, or a two element tuple specifying the time interval to which the notehead should be applied. Notes should exist on specified beats, or else the notehead will be applied to the nearest existing note. Overlapping time intervals will be processed, setting the current notehead back to the previous one if the current one lies inside the range of the previous notehead. If the key is not a tuple (i.e. an integer or a float), the notehead will be valid up to the next _integer_ or _float_ key.  This will overwrite all future noteheads specified in a time interval (please note that this also doesn't allow any future time interval specifications for setting noteheads. Consider using tuples as _key_s instead).
    *  __value__: can be one of the following strings: `"default", "altdefault", "baroque",`
   `"neomensural", "mensural", "petrucci", "harmonic", "harmonic-black", "harmonic-mixed",`
   `"diamond", "cross", "xcircle", "triangle", "slash",`
   `"do", "re", "mi", "fa", "#f", "la", "ti"`. Defaults to `"default"`. ([notehead example](#notehead_example))




*  `"barline"`: a dictionary which accepts following key/value pairs:
    *  __key__: can be an integer or a float specifying the beat where the barline should appear.
    *  __value__: a string which can be one of the Lilypond barlines or a new barline definition with the following syntax: `"bartype => end begin span"` where each of `end, begin` or `span` can be one of barline elements supported by Lilypond or an underscore `_` as a placeholder for an invisible barline element. Once a new barline has been defined, it can be used subsequently just as built-in barlines. (for more information about Lilypond barlines and new barline definitions please consult the [barline documentation page](http://lilypond.org/doc/v2.18/Documentation/notation/bars)). ([__barline examples__](#barline_example))


*  `"dynamic"`: a dictionary which accepts following key/value pairs:
    *  __key__: can be an integer or a float specifying the beat where the _absolute_ dynamic should appear, or a two element tuple specifying the time interval to which the _relative_ dynamic (crescendo or decrescendo) should be applied. Overlapping or crossing dynamics will be processed accordingly. If no notes exist on specified beats, rests are created to accommodate the dynamics.
    *  __value__: a string which can be any of the [Lilypond dynamics](http://lilypond.org/doc/v2.19/Documentation/notation/expressive-marks-attached-to-notes#dynamics) or a new _absolute_ dynamic definition with the following syntax: `"dynamic_name => args"`, where `dynamic_name` is the name of the variable used as the new dynamic and `args` are any white-space-seperated combination of ascii characters and the new dynamic (an arbitrary combination of the following characters: `f, m, p, r, s, z`) prefixed by an identifier `D`. Once a new dynamic has been defined, it can be used subsequently just as built-in dynamics. ([__dynamic examples__](#dynamic_example))


*  `"legato"`: a dictionary which accepts following key/value pairs:
    *  __key__: one of the strings: `"solid"`, `"halfsolid"`, `"dashed"`, `"halfdashed"` or `"dotted"`.
    *  __value__: a list or a tuple of a two element tuple of integers or floats specifying the start and the end beats of the legato (exclusive of the ending beat). If no notes exist on the specified starting beat, a rest is created to accommodate the legato. Please note that Lilypond allows a maximum of two overlapping legatos. From the [documentation page](http://lilypond.org/doc/v2.18/Documentation/notation/expressive-marks-as-curves): _Simultaneous or overlapping slurs are not permitted, but a phrasing slur can overlap a slur. This permits two slurs to be printed at once._ [Also](http://lilypond.org/doc/v2.18/Documentation/notation/expressive-marks-as-curves#phrasing-slurs): _Simultaneous or overlapping phrasing slurs are not permitted_. The legato metadata _values_ will be processed according to these aspects. For some examples see the [__Legato Examples__](#legato_example) page.

* `"tie"`: a dictionary which accepts following key/value pairs:
    *  __key__: one of the strings: `"up"`, `"down"`, `"neutral"`, `"dotted"`, `"dashed"`, `"halfdashed"`, `"halfsolid"` or `"solid"`.
    *  __value__: a list or a tuple of a two element tuple of integers or floats, specifying the start and the end beats of the tie (including the ending beat). For some examples see [__Tie Examples__](#tie_examples).


*  `"articulation"`: a dictionary which accepts following key/value pairs:
    *  __keys__: can be an integer or a float specifying the beat where the articulation should take place, or a two element tuple specifying the time interval to which the articulation should be applied.
    *  __values__: can be one or more of the following strings:`"accent", "espressivo", "marcato", "portato",`
`"staccatissimo", "staccato", "tenuto", "prall", "prallup", "pralldown", "upprall",`
`"downprall", "prallprall", "lineprall", "prallmordent", "mordent", "upmordent",`
`"downmordent", "trill", "turn", "reverseturn", "shortfermata", "fermata", "longfermata",`
`"verylongfermata", "upbow", "downbow", "flageolet", "open", "halfopen", "lheel", "rheel",`
`"ltoe", "rtoe", "snappizzicato", "stopped", "segno", "coda", "varcoda", "accentus",`
`"circulus", "ictus", "semicirculus", "signumcongruentiae", ">", "^", "_", "!", ".", "-", "+", "<>"`.
Except with [_fermatas_](http://lilypond.org/doc/v2.18/Documentation/notation/list-of-articulations#fermata-scripts) and [_repeat signs_](http://lilypond.org/doc/v2.18/Documentation/notation/list-of-articulations#repeat-sign-scripts) a note should exist on specified beats or no articulations will be shown. Please note that as soon as rests have been created to show _fermatas_ and _repeat signs_ if specified beats are missing, these new beats may also accept other articulations. ([articulation example](#articulation_example))

proc

Signature: (score, metadata={}, file_name="lento", path="/tmp", dot_lento="~/.lento", outputs=["pdf"], view=True)

The main processing function with following parameters:

  • score: can be a Part instance or a list/tuple of Part instances.

  • metadata: Global metadata will be applied to all Part instances in the score parameter. Each Part’s metadata takes precedence over this global metadata.

  • file_name: will be used as the name for all output files.

  • path: where to save the output files.

  • dot_lento: the path to the .lento file.

  • outputs: a collection of output format strings. Can be any of: "pdf" (default), "svg", "jpg", "midi", "mid".

  • view: whether to show the PDF file using the pdf viewer specified in the .lento file after compilation or not. Defaults to True.


dotlento

The content of this configuration file will be used for setting some system variables. The path to this file defaults to /home/username/.lento, but can be reassigned in the dot_lento parameter of the lento function.
The syntax is key = value. Lines starting with a pound sign (#) are comments.
As of this writing (23. Aug. 2018) following keys can be set:

  • ly_version: Lilypond version. Defaults to 2.21.0.

  • ly_language: Lilypond language. Defaults to deutsch.

  • pdf_viewer: path to the PDF-viewer binary. Defaults to /usr/bin/zathura.

  • ly_bin: path to the Lilypond binary. Defaults to /usr/local/bin/lilypond.

  • ly_paper_size: paper format used by Lilypond. Defaults to quarto. More formats

  • ly_staff_size: staff size used by Lilypond. Defaults to 14.

  • load_ekmelily: whether the ekmelily system should be loaded for microtonal notation (yes) or not (no). Defaults to yes.

It is also possible to assign keys to values obtained by a shell command. The syntax for this is key = $cmd, where cmd is the command yielding the desired value. For instance, to assign the keyword pdf_viewer to the address of the evince PDF viewer, one can write:

pdf_viewer = $which evince

Metadata examples

``from lento import *`` applies to all of the code snippets and will be omitted in the following.

timesig:

The following metadata:


lento(Part(events={"notes": range(60, 84),
                   "beats": range(24)},
           metadata={"timesig": {
               (1, 12): (3, 8),
               (4, 7): [2, 32],
               17: (5, 4),
               (19, 20): (1, 8)
           }}))

will be processed in the following way:

  1. beat 0 (4/4, default time signature) to beat 1 results in a 1/4

  2. beat 1 (3/8) to beat 4 results in a 3/8

  3. beat 4 (2/32) to beat 7 results in one 2/32 and a remainder of 1/32

  4. beat 7 resets to 3/8 till beat 12, resulting in one 3/8 and a remainder of 2/8

  5. beat 12 resets to 4/4 (default time signature) till beat 17, resulting in one 4/4 and a remainder of 1/4

  6. from beat 17 on 5/4

and produce:

timesig_out

clef:


lento(Part(events={"notes": range(60, 84),
                   "beats": range(24)},
           metadata={"clef": {
               (0.5, 8+1/3): "alto",
               (4, 6): "tenor",
               8+2/3: "treble+9",
               13.5: "treble-8",
               16: "soprano"
           }}))
clef_out

notehead:

The following metadata:


lento(Part(events={"notes": range(60, 84),
                   "beats": range(24)},
           metadata={"notehead": {
               (.5, 8+1/3): "mensural",
               (4, 6): "harmonic",
               8+2/3: "cross",
               13.5: "triangle",
               16: "xcircle"
           }}))

will be processed in the following way:

  1. nearest existing note to the beat 0.5 is on beat 0 (0.5 will be rounded downwards), from beat 0 to beat 4 are "mensural"

  2. from beat 4 to 6 are "harmonic"

  3. nearest existing note to the beat 8.333333333333334 is on beat 8, from beat 6 to beat 8 are set back to "mensural"

  4. nearest existing note to the beat 8.666666666666666 is on beat 9, beat 8 to beat 9 is set back to "default"

  5. nearest existing note to the beat 13.5 is on beat 13 (again 0.5 will be rounded downwards), from beat 9 to beat 13 are "cross"

  6. from beat 13 to beat 16 are "triangle"

  7. from beat 16 onwards are "xcircle"

and produce:

notehead_out

barline:


def merge_dicts(d1, *ds):
    """merge two or more dictionaries"""
    for d in ds:
        d1.update(d)
    return d1

lento(Part(events={"notes": range(60, 84),
                   "beats": range(24)},
           metadata={"barline": merge_dicts(
               # make the default barlines invisible
               {beat: "" for beat in range(3, 24, 4)},
               # set new barlines after each third beat
               {beat: "!" for beat in range(2, 24, 3)},
               # and a closing barline at the end
               {23: "|."}
           )}))
barline_out

lento(Part({"notes":[range(48, 84)] * 3,
            "beats": [range(36)] * 3},
           {"staff": {"n": 3, "bind": "grand"},
            "barline": {
                # define new barlines and use them
                0: {n * 4 - 1: "= => = _ _" for n in range(1, 10)},
                1: {n * 4 - 1: "] => ] _ _" for n in range(1, 10)},
                2: {n * 4 - 1: "[ => [ _ _" for n in range(1, 10)}
            }}))
barline_define_out

dynamic:


lento(Part({"notes": range(60, 72),
            "beats": [n * .5 for n in range(12)]},
           {"dynamic": {(0, 5.5): "<"}}))
dynamic_out_1

def merge_dicts(d1, d2):
    """merge two dictionaries"""
    d1.update(d2)
    return d1

lento(Part(events={"notes": [range(60, 72), range(72, 60, -1)] * 5,
                   "beats": [range(12)] * 10},
           metadata={"staff": {"n": 10, "bind": "grand"},
                     "dynamic": merge_dicts(
                         {v: {3: "p", 7: "sf", (0, 11): ">"}
                          for v in range(0, 10, 2)},
                         {v: {2: "sf", (0, 11): "<"}
                          for v in range(1, 10, 2)})
           }))
dynamic_out_2

lento(Part({"notes": range(60, 72),
            "beats": range(12)},
           {"dynamic": {(0, 11): "<",
                        (4, 8): ">",
                        6: "ppp"}}))
dynamic_out_3
def merge_dicts(d1, *ds):
    """merge two or more dictionaries"""
    for d in ds:
        d1.update(d)
    return d1

lento(Part(events={"notes": range(60, 72),
                   "beats": range(12)},
           metadata={"dynamic": merge_dicts(
               # define some new absolute dynamics
               {0: "starf => * Drfz *"},
               {1: "roundp => ( Dp )"},
               # now they can be accessed just as any other dynamics
               {beat: "starf" for beat in range(2, 12, 2)},
               {beat: "roundp" for beat in range(3, 12, 2)}
           )}))
</code></pre>
dynamic_out_4
lento(Part(events={"notes": range(60, 84),
                   "beats": range(24)},
           metadata={"dynamic": {
               (0, 6): "<",
               (2, 5.1): ">",
               5.5: "sf",
               4: "mp",
               (6.5, 23): "dim",
               (10+2/3, 15.5): "cr",
               13+3/7: "p",
               23: "rfz"
           }}))
</code></pre>
dynamic_out

Legato Examples

legato:

lento(Part(events={"notes": range(60, 84),
                   "beats": range(24)},
           metadata={"legato": {
               "solid": ((1, 15.5),
                         (3+1/3, 5),
                         (5, 7)),
               "halfdashed": [(7, 12.5),
                              (12.5, 17)],
               "dotted": ((8, 9),
                          (16, 23))
           }}))
</code></pre>
![legato_out](legato_out.jpg "legato out")


- - -


<h4 id="articulation_example">articulation:</h4>


<pre><code>
lento(Part(events={"notes": range(60, 72),
                   "beats": range(12)},
           metadata={"articulation": {
            3: (">", "."),
        (1, 7): "staccatissimo",
        (4, 6): ["prall", "^", "trill", "turn"],
        10.5: ("fermata", "trill"),
        11: "<>"
       }}))
</code></pre>
articulation_out