[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.3 Building complicated functions

This section explains how to gather the information necessary to create complicated music functions.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.3.1 Displaying music expressions

When writing a music function it is often instructive to inspect how a music expression is stored internally. This can be done with the music function \displayMusic.

 
{
  \displayMusic { c'4\f }
}

will display

 
(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))))

By default, LilyPond will print these messages to the console along with all the other messages. To split up these messages and save the results of \display{STUFF}, you can specify an optional output port to use:

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

This will overwrite a previous output file whenever it is called; if you need to write more than one expression, you would use a variable for your port and reuse it:

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

Guile’s manual describes ports in detail. Closing the port is actually only necessary if you need to read the file before LilyPond finishes; in the first example, we did not bother to do so.

A bit of reformatting makes the above information easier to read:

 
(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))))

A { … } music sequence has the name SequentialMusic, and its inner expressions are stored as a list in its 'elements property. A note is represented as a NoteEvent object (storing the duration and pitch properties) with attached information (in this case, an AbsoluteDynamicEvent with a "f" text property) stored in its articulations property.

\displayMusic returns the music it displays, so it will get interpreted as well as displayed. To avoid interpretation, write \void before \displayMusic.


[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.3.2 Music properties

Let’s look at an example:

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

The NoteEvent object is the representation of someNote. Straightforward. How about putting c’ in a chord?

 
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))))

Now the NoteEvent object is the first object of the 'elements property of someNote.

The display-scheme-music function is the function used by \displayMusic to display the Scheme representation of a music expression.

 
#(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))

Then the note pitch is accessed through the 'pitch property of the NoteEvent object.

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

The note pitch can be changed by setting this 'pitch property.

 
#(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

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.3.3 Doubling a note with slurs (example)

Suppose we want to create a function that translates input like a into { a( a) }. We begin by examining the internal representation of the desired result.

 
\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))))

The bad news is that the SlurEvent expressions must be added ‘inside’ the note (in its articulations property).

Now we examine the input.

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

So in our function, we need to clone this expression (so that we have two notes to build the sequence), add a SlurEvent to the 'articulations property of each one, and finally make a SequentialMusic with the two NoteEvent elements. For adding to a property, it is useful to know that an unset property is read out as '(), the empty list, so no special checks are required before we put another element at the front of the articulations property.

 
doubleSlur = #(define-music-function (note) (ly:music?)
         "Return: { note ( note ) }.
         `note' is supposed to be a single note."
         (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))))

[ < ] [ > ]   [ << ] [ Up ] [ >> ]         [Top] [Contents] [Index] [ ? ]

1.3.4 Adding articulation to notes (example)

The easy way to add articulation to notes is to juxtapose two music expressions. However, suppose that we want to write a music function that does this.

A $variable inside the #{…#} notation is like a regular \variable in classical LilyPond notation. We could write

 
{ \music -. -> }

but for the sake of this example, we will learn how to do this in Scheme. We begin by examining our input and desired output.

 
%  input
\displayMusic c4
===>
(make-music
  'NoteEvent
  'duration
  (ly:make-duration 2 0 1/1)
  'pitch
  (ly:make-pitch -1 0 0))))
=====
%  desired output
\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))

We see that a note (c4) is represented as a NoteEvent expression. To add an accent articulation, an ArticulationEvent expression must be added to the articulations property of the NoteEvent expression.

To build this function, we begin with

 
(define (add-accent note-event)
  "Add an accent ArticulationEvent to the articulations of `note-event',
  which is supposed to be a NoteEvent expression."
  (set! (ly:music-property note-event 'articulations)
        (cons (make-music 'ArticulationEvent
                'articulation-type "accent")
              (ly:music-property note-event 'articulations)))
  note-event)

The first line is the way to define a function in Scheme: the function name is add-accent, and has one variable called note-event. In Scheme, the type of variable is often clear from its name. (this is good practice in other programming languages, too!)

 
"Add an accent…"

is a description of what the function does. This is not strictly necessary, but just like clear variable names, it is good practice.

You may wonder why we modify the note event directly instead of working on a copy (ly:music-deep-copy can be used for that). The reason is a silent contract: music functions are allowed to modify their arguments: they are either generated from scratch (like user input) or are already copied (referencing a music variable with ‘\name’ or music from immediate Scheme expressions ‘$(…)’ provides a copy). Since it would be inefficient to create unnecessary copies, the return value from a music function is not copied. So to heed that contract, you must not use any arguments more than once, and returning it counts as one use.

In an earlier example, we constructed music by repeating a given music argument. In that case, at least one repetition had to be a copy of its own. If it weren’t, strange things may happen. For example, if you use \relative or \transpose on the resulting music containing the same elements multiple times, those will be subjected to relativation or transposition multiple times. If you assign them to a music variable, the curse is broken since referencing ‘\name’ will again create a copy which does not retain the identity of the repeated elements.

Now while the above function is not a music function, it will normally be used within music functions. So it makes sense to heed the same contract we use for music functions: the input may be modified for producing the output, and the caller is responsible for creating copies if it still needs the unchanged argument itself. If you take a look at LilyPond’s own functions like music-map, you’ll find that they stick with the same principles.

Where were we? We now have a note-event we may modify, not because of using ly:music-deep-copy but because of a long-winded explanation. We add the accent to its 'articulations list property.

 
(set! place new-value)

Here, what we want to set (the ‘place’) is the 'articulations property of note-event expression.

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

ly:music-property is the function used to access music properties (the 'articulations, 'duration, 'pitch, etc, that we see in the \displayMusic output above). The new value is the former 'articulations property, with an extra item: the ArticulationEvent expression, which we copy from the \displayMusic output,

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

cons is used to add an element to the front of a list without modifying the original list. This is what we want: the same list as before, plus the new ArticulationEvent expression. The order inside the 'articulations property is not important here.

Finally, once we have added the accent articulation to its articulations property, we can return note-event, hence the last line of the function.

Now we transform the add-accent function into a music function (a matter of some syntactic sugar and a declaration of the type of its argument).

 
addAccent = #(define-music-function (note-event) (ly:music?)
  "Add an accent ArticulationEvent to the articulations of `note-event',
  which is supposed to be a NoteEvent expression."
  (set! (ly:music-property note-event 'articulations)
        (cons (make-music 'ArticulationEvent
                'articulation-type "accent")
              (ly:music-property note-event 'articulations)))
  note-event)

We then verify that this music function works correctly:

 
\displayMusic \addAccent c4

[ < ] [ > ]   [ << ] [ Up ] [ >> ]

This document was generated by gub on July 13, 2020 using texi2html 1.82.

其他语言:deutsch, español, français。
关于 自动语言选择