Entering pitches as a list of semitones

The function presented here maps a list of positive or negative numbers to a musical expression, resulting in a melody where each note differs from the previous one's pitch by the given number of semitones. This may be particularly useful in compositional processes where music input (and particularly pitches) is generated arithmetically or from any number sequence.

\version "2.24.0"

\paper { tagline = ##f }

%% Contributed by vv on -user-fr:
%% https://lists.gnu.org/archive/html/lilypond-user-fr/2020-04/msg00112.html

rhythms = {
  %% Strictly speaking, these are not only rhythms,
  %% as the first pitch will actually be used.
  %% Also note that explicitly-pitched notes are
  %% still required in chords and when ties end.
  a2~\(
  \repeat unfold 16 c8 4 8\) r
  r8 8\( 8 8 \tuplet 5/4 {8 8 8 8 8}
  8. 16 \tuplet 3/2 {8 8 8} 4\) r
  %% Chords (sort of) work.
  r2 4.\( 8 <c c c c~>1\) c2. r8
  %% Polyphony (sort of) works.
  %% These may look like spurious polyphonic constructs,
  %% but the goal is actually here to make the
  %% semitones pattern as readable as possible.
  << 8->\( \\ 2*1/4 >>
  << {8 8 8 8} \\ {} >>
  << 2.\) \\ { r4 <c c>(-. q-.)} >>
}

semitones =
#'(
  3 5 2 2
  -4 7 7 2
  -4 7 7 2
  -5 3 -2 -1 -1
  0 1 -1 -1 1 3 -2 -4
  0 -1 3 -2 -1 0
  -2 -5
  ;; Entering the chord from the bottom up.
  -17 5 8 7
  ;; The following note is in two voices.
  -10 0
   2 4 4 -1 -2
  ;; These are the last two-note chords,
  ;; again in a secondary voice.
  -9 4 -4 4)

%% We could keep all of the following input mixed in with
%% rhythms input above.  However, it seems more interesting
%% to separate not only semitones from rhythms, but also
%% structural information.
structure = {
  \key g \minor
  \clef bass
  <>\f
  s1-\markup \italic "pesante"
  \clef treble
  s1*2 s8 s1*23/8\p
  s1\arpeggio \bar "||" \break
  \tempo Moderato \time 6/4
}

%%%%%%%%%%

%% The semitones pattern will be looped if
%% shorter than the given rhythms.
#(define (loop-list-ref ls x)
  (let ((z (length ls)))
   (list-ref ls (remainder x z))))

#(define semi->pitch
  (make-semitone->pitch
   (music-pitches
    #{ c cis d es e f fis g gis a bes b #})))

%% See LSR #266.  This is not merely cosmetic here,
%% as the transposition mechanism may lead
%% to quite extreme results.
#(define (naturalize-pitch p)
  (let* ((o (ly:pitch-octave p))
         (a (* 4 (ly:pitch-alteration p)))
         (n (ly:pitch-notename p))
         (result '()))
   (cond
    ((and (> a 1) (or (eq? n 6) (eq? n 2)))
     (set! a (- a 2))
     (set! n (+ n 1)))
    ((and (< a -1) (or (eq? n 0) (eq? n 3)))
     (set! a (+ a 2))
     (set! n (- n 1))))
   (cond
    ((> a 2) (set! a (- a 4)) (set! n (+ n 1)))
    ((< a -2) (set! a (+ a 4)) (set! n (- n 1))))
   (if (< n 0) (begin (set! o (- o 1)) (set! n (+ n 7))))
   (if (> n 6) (begin (set! o (+ o 1)) (set! n (- n 7))))
   (ly:make-pitch o n (/ a 4))))

applyDeltas =
#(define-music-function (music numbers)
  (ly:music? list?)
"
Apply a list of semitones (entered as positive or
negative integers) to a given musical pattern.  Only
the first pitch of this pattern will be used as a
starting point; all subsequent pitches will be discarded.
"
  (let ((tied? #f)
        (prev-pitch '())
        (delta-pitch '())
        (i -1))
   (music-filter
    (lambda (m)
     ;; We could go into detail into music event types
     ;; and their possible elements, but it's simpler
     ;; to just test for anything that has a pitch.
     (let ((p (ly:music-property m 'pitch)))
      (if (not (null? p))
       (begin
        (if tied?
         ;; If the previous note was tied, just copy its pitch
         ;; and don't move forward in the semitones list.
         (ly:music-set-property! m 'pitch prev-pitch)
         (begin
          (ly:music-set-property! m 'pitch
           (if (null? prev-pitch) p
               ;; calculate the new pitch by adding the number
               ;; of semitones (i.e. delta) to the previous
               ;; note's pitch.
               (naturalize-pitch
                (ly:pitch-transpose
                 (semi->pitch (loop-list-ref numbers i))
                 prev-pitch))))
          (set! i (1+ i))))
        (set! tied? #f)
        ;; testing for tied event.  Since we're only working
        ;; with notes here, ties attached to a whole chord
        ;; chord won't work.  (But one can tie its notes
        ;; individually.)
        (map
         (lambda (j)
                 (if (music-is-of-type? j 'tie-event)
           (set! tied? #t)))
         (ly:music-property m 'articulations))
        (set! prev-pitch (ly:music-property m 'pitch))))
      m))
    ;; We want all notes to be affected, so we need
    ;; to unfold and expand any input shortcut.
    (unfold-repeats-fully
     (expand-repeat-notes!
      (expand-repeat-chords! (list 'rhythmic-event)
       music))))))

%%%%%%%%%%

%% The score given here as an example happens to look much
%% nicer when computed in A minor, then transposed.  A similar
%% process should probably be advisable for most tonal scores.

\new Voice <<
  \structure \transpose a c \applyDeltas \rhythms \semitones
>>

\layout {
  ragged-last = ##t
}