5

In version 2.19.x, how can I make LilyPond decide whether – and by how much – to transpose up or down, depending on a target octave/range for one of the transposed notes (I'll call that note the "reference note")?

All the other notes are to be transposed in the same direction and by the same amount as the reference note, so a single LilyPond \transpose command will handle all of the transposed music.

Use case: I want to make a set of chord voicings, each in multiple transpositions, with the lowest note of each transposed voicing always within a specified octave, say between c and b (the octave below middle C in LilyPond notation). This would require some sort of conditional (in concept, something like: "try transposing the chord down; if the lowest note of the downward transposition is too low, transpose up").

Added to clarify (?) the goal: Typically chord voicings are fairly tight, with separations between the notes of no more than a 6th and usually less, but the solution shouldn't be based on the distance (or any other relationship) between the notes being transposed. I just want to pick a note in the chord/music as the reference note, choose a transposition key/note name, and define a range into which the transposed note must fall after transposition. LilyPond/Scheme would take over to figure out the details of the \transpose command, up or down by how many octaves, and would apply that command to the entirety of the original subject 'chunk' of music. The result would be that the entire chunk – including the reference note and all the other notes therein – would be transposed by the same amount and in the same direction. The reference note is the only one that must fall in the specified range. In the typical case, you want a chord voicing that is not too low to be muddy, but as low as you can get it so it stays more or less out of the way of the melody. There. Please feel free to improve on/simplify this description of what I'm looking for.

I can't find any relevant snippets in the LilyPond snippet library. There is one discussion of conditional transposing I found via a Google search but the Scheme code is opaque to me--with the sparse comments I can't even understand what the original poster was trying to accomplish, much less the proposed solution. And it gave me an error when I tried to run it using the sample LilyPond music passage ("input") (see below). To be clear, since I can't follow the Scheme in that link, I'm not sure how – or if – the code is relevant to my problem, other than that both involve some sort of conditional transposition.

Parsing...C:...tmpdocument.ly:7:3: In procedure ly:music-set-property! in expression ((setter ly:music-property) (quote from-to) music ...):
C:...tmpdocument.ly:7:3: Wrong type argument in position 1 (expecting Prob): from-to

I can imagine a fairly general approach that takes any specified note in a series as the reference point for the up/down decision, but for this purpose the reference note will always be the first one in the chord, for example the c in the C major < c e g > triad.

enter image description here

Edit/addition: Since I first posted, I've figured out working logic for transposing to a range. A condition is involved, but not the one I originally imagined. Here's Python code implementing that logic, based on numeric values for pitches:

# find same note at a different octave, ie in specified range
starting_pitch = 67  # these values are just examples
bottom_of_range = 48 # 'c'  in Lilypond, C below middle C
top_of_range = 59 # # Lilypond 'b'

octave_delta_from_bottom = -((starting_pitch - bottom_of_range) // 12) 
    #round down
octave_delta_from_top = (-(starting_pitch - top_of_range)) // 12
    #round up by negating the numerator

if octave_delta_from_bottom == octave_delta_from_top: 
    transposed_note = starting_pitch + (octave_delta_from_bottom *12)
    print (transposed_note)
else: #
    print ("target note not in specified range.")
3
  • Could you elaborate on what exactly should be the input and what the output by giving some examples, perhaps? I can’t really understand how the function in the link you cite should be applied in your situation. Commented Aug 29, 2019 at 6:16
  • @Jasper Habicht: I edited my post to emphasize and (I hope) clarify that the link I gave is of unknown applicability to my situation--it was really included just to demonstrate that I had made efforts to find solutions before posting.
    – Joan Eliot
    Commented Aug 29, 2019 at 23:24
  • @Jasper Habicht: To flesh out the example case included in the paragraph with the little graphic, let's say the target range is C below middle C to B below middle C. If input is <c e g> and I want to transpose the chord to the key of A, output should be either (1) <a cis' e'> (not <a, cis e>) or (2) \transpose c a <c e g> (not \transpose c a, <c e g>.
    – Joan Eliot
    Commented Aug 29, 2019 at 23:44

1 Answer 1

3

I am still not fully sure whether I really understood what you want. Perhaps this code can help you:

The function takes three parameters before a musical expression (which would be your chord): the first is the bottom of the range, the second is the top of the range, the third is the desired transposition from c.

The function tries to extract the first pitch of the first note (or of the first chord if the first item is a chord) and checks wether this pitch is below the bottom of the range. If yes, it transposes the musical function up one octave. If the the pitch is not below the bottom of the range, it further tests if it is above the range. If yes, it transposes the musical function down one octave.

I am not sure if this really results always in the desired output, though … 

\version "2.18.2"

conditionalTransposition = 
#(define-music-function (parser location lowpitch highpitch transposepitch music) 
  (ly:pitch? ly:pitch? ly:pitch? ly:music?) 
  (let ((firstitem (first (ly:music-property music 'elements))))
    (if (eq? (ly:music-property firstitem 'name) 'EventChord) 
      (set! firstitem (first (ly:music-property firstitem 'elements)))
      (set! firstitem firstitem))
    (if (ly:pitch<? (ly:music-property firstitem 'pitch) lowpitch)
      (set! music #{ \transpose c, #transposepitch { \relative c { #music } } #} )
      (if (ly:pitch<? highpitch (ly:music-property firstitem 'pitch))
      (set! music #{ \transpose c' #transposepitch { \relative c { #music } } #} )
      (set! music #{ \transpose c #transposepitch { \relative c { #music } } #} )))
  music))

\new Staff {
  \conditionalTransposition c b a { <c, e g> } 
  \conditionalTransposition c b a { <c e g> } 
  \conditionalTransposition c b a { <c' e g> } 
}

Edit: The above function works only in relative pitch mode and for certain octaves. But I found a better approach that can do without the if clause (and is therefore much shorter). It merely uses the pitches of the first note of the chord and the pitch of the bottom range to transpose the musical expression. I tested it with different octaves and I hope it works as expected.

I leave the older version of my code to document my gradual approach to the solution … maybe it helps to understand how the new code works.

\version "2.18.2" % works up to current version 2.19.53

conditionalTransposition =
#(define-music-function (parser location lowpitch highpitch transposepitch music)
  (ly:pitch? ly:pitch? ly:pitch? ly:music?)
  (let ((firstitem (first (ly:music-property music 'elements))))
    (if (eq? (ly:music-property firstitem 'name) 'EventChord)
      (set! firstitem (first (ly:music-property firstitem 'elements)))
      (set! firstitem firstitem))
      (set! music #{ \transpose 
        #(ly:make-pitch (ly:pitch-octave (ly:music-property firstitem 'pitch)) 0) 
        #(ly:make-pitch (if (ly:pitch<? 
            highpitch
            (ly:pitch-transpose (ly:music-property firstitem 'pitch) transposepitch)) 
          (- (ly:pitch-octave lowpitch) 1) 
          (ly:pitch-octave lowpitch)) (ly:pitch-notename transposepitch) (ly:pitch-alteration transposepitch)) 
        { $music } #} )
  music))

khord = { <g' b' d''> } % target range = c' to b'
\new Staff {
  { \khord }
  \transpose c a { \khord } % incorrect, lowest note above of range
  \transpose c a, { \khord } % correct result, lowest note in range
  \conditionalTransposition c' b' a \khord
}

At the moment, the function ignores the top range limit given in highpitch. But, do you really need it? What would happen if you only state a range from c to a and enter a b? Which octave should this note have? And what octave should the function choose for a c' if you state a range from c to d'? Maybe you still want to include it in the function, so I leave it as it is.


Edit 2: Because the two approaches above do not fully work, I came up with a third idea. The macro \condTranspose works in part like the normal \transpose macro but takes a number as third argument that tells LilyPond which octave to use for transposition. Octaves in the musical expression are ignored.

\version "2.18.2"

condTranspose = 
#(define-music-function (parser location frompitch topitch tooctave music) 
  (ly:pitch? ly:pitch? number? ly:music?) 
  (let ((firstitem (first (ly:music-property music 'elements))))
    (if (eq? (ly:music-property firstitem 'name) 'EventChord) 
      (set! firstitem (first (ly:music-property firstitem 'elements)))
      (set! firstitem firstitem))
      (set! music #{ \transpose 
        #(ly:make-pitch 0 (ly:pitch-notename frompitch)) 
        #(ly:make-pitch (- tooctave (ly:pitch-octave (ly:music-property firstitem 'pitch))) (ly:pitch-notename topitch)) 
        { $music } #} )
  music))

\new Staff {
  \condTranspose c b #0 { <c' e' g'> } 
  \condTranspose c b #0 { <c e g> } 
  \condTranspose c b #0 { <c, e, g,> } 
}
11
  • Wow, Jasper, thanks for this help. I continue to be baffled by Scheme but you've made it clear to me that a relatively simple Scheme function would solve my problem -- even if I haven't been able to make that problem clear to you. I will edit my original post with further clarifications. Meanwhile though, I'll post a second comment with a test of your function that should help.
    – Joan Eliot
    Commented Sep 1, 2019 at 20:17
  • %---(version # and function def above)--- khord = {<g' b' d''>} % target range = c' to b' \new Staff { {\khord} \transpose c a {\khord} % incorrect, lowest note above of range \transpose c a, {\khord} % correct result, lowest note in range \conditionalTransposition c' b' a \khord % per-note transpositions?
    – Joan Eliot
    Commented Sep 1, 2019 at 20:29
  • Sorry, I guess, I know where the problem is … I need some time to revise my answer. =) Commented Sep 1, 2019 at 21:13
  • @JoanEliot : Please see my edit. I think this is about what you wanted? Commented Sep 2, 2019 at 17:23
  • Very good progress, and I'm beginning to make a tiny bit of headway grasping what the Scheme code is doing. Remaining issues: (1) transposing a, bes, or b works, but as soon as you get to c (as in \conditionalTransposition c' b' c \khord ) the octave is off by one (the result is an octave too low); and (2) changing the range so that it crosses a C ( e.g. a gis') knocks it down another octave; and (3) flats/sharps in the "transpose-to" key are ignored (\conditionalTransposition c b des gives same results as \conditionalTransposition c b d) Thank you again!
    – Joan Eliot
    Commented Sep 3, 2019 at 15:18

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.