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.")
Elements in Space
  • 10,785
  • 2
  • 23
  • 67
Joan Eliot
  • 301
  • 1
  • 5
  • 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. – Jasper Habicht Aug 29 '19 at 06: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 Aug 29 '19 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 and I want to transpose the chord to the key of A, output should be either (1) `` (not ``) or (2) `\transpose c a ` (not `\transpose c a, `. – Joan Eliot Aug 29 '19 at 23:44

1 Answers1

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,> } 
}
Jasper Habicht
  • 1,939
  • 10
  • 19
  • 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 Sep 01 '19 at 20:17
  • `%---(version # and function def above)--- khord = {} % 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 Sep 01 '19 at 20:29
  • Sorry, I guess, I know where the problem is … I need some time to revise my answer. =) – Jasper Habicht Sep 01 '19 at 21:13
  • @JoanEliot : Please see my edit. I think this is about what you wanted? – Jasper Habicht Sep 02 '19 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 Sep 03 '19 at 15:18
  • Error in (1) above: should have said "transposing a or b works, but... – Joan Eliot Sep 03 '19 at 17:19
  • In response to your (good) questions in last paragraph: The note of interest (the lowest note in the chord) must after transposition always fall within the specified range, with all other notes transposed by the same amount (that is, above it by the same intervals as were present in the original chord). If the transposed note can't be fit into the range -- if the result of transposition is a b but the range is from c to a, the function should report/raise an error (as appropriate in Scheme). A range larger than 12 semitones should cause an error too. Good catches on these ambiguous conditions. – Joan Eliot Sep 03 '19 at 17:36
  • Sorry, I only have time every now and then … and it is a bit complicated to think through all possible cases to come up with a solution that covers everything … As soon as I have more time, I will try to go through it again and hopefully solve your problem. – Jasper Habicht Sep 05 '19 at 15:59
  • I continue to appreciate your efforts and hope to be able to figure it out on my own in Scheme before long. My mind rebelled at all those Scheme brackets and the !: ' # characters, so I took the long way around and wrote a crude Lilypond parser in Python. I'm sure it would have taken me less time to learn to do it in Scheme. More to the point the code would be 100 x shorter and much less prone to error. So I'd still be grateful for a 'native' Lilypond solution, solution, if you ever do have the time to figure it out. – Joan Eliot Sep 13 '19 at 03:08
  • @JoanEliot : Sorry, I am still struggling with the octave-shifting. Would it be OK for you to have a function like `\condTranspose c e #1` where `c` and `e` work like the normal `\transpose` function and the number tells LilyPond which octave to use? Octaves in the musical expression would be ignored then. This is far easier to implement from my point of view … – Jasper Habicht Oct 15 '19 at 16:47
  • Apologies, not sure how I missed your reply in October. Using a number instead of LilyPond notation to specify the target octave would be fine, but I need arbitrary ranges (like f to e') and also to preserve the intervals in the original expression. Your code suggests a path forward but I'm on to other challenges, dragging my huge, clunky Python preprocessor along with me. – Joan Eliot Jan 13 '20 at 13:34