Posted December 30, 20177 yr Parsimonious Voice Leading (again): attempts to provide an algorithm In a previous post I deleted, I was trying to find a good way to ensure the minimal parsimonious Voice-Leading (VL) between a sequence of chords. In this post I will try to explain my second attempt. Let's take a look at a sequence of chords, spreaded out almost in a random way, with no VL (setf chordstovl2 '(b3eb5g3 cs6e7gs3 b4f5g6 f7e5c2 d4f7e4 gs7e2a8) Let's specify and evaluate a variable for the number of voices used: (setf voices 3) Let's try to think the best way to connect this pitches with minimal movement. I will apply the following expression to ensure a better and less ambiguous result when applying the CHORD-CLOSEST-PATH function. Evaluate voices before (setf voices 3) Then (setf chordized (mclist (chordize-list (gen-divide voices (setf vlfinal (integer-to-pitch (modus (interval-to-pitch (replace-map '((-11 1) (-10 2) (-9 3) (-8 4) (-7 5) (7 -5) (8 -4) (9 -3)(10 -2)(11 -1)) (integer-to-interval (modus (pitch-to-midi (setf chordmelo (pitch-melodize chordstovl2)))))) :start (car chordmelo))))))))) Please note the use of the replace-map function ensuring that no movement will be greater than a tritone away. This means that a movement like "C to G" (7 semitones) will be convertet in a G to C (5 semitones). This ensure a modulo 12 (octave constraint) reduction of all the material and also a constraint in terms of the size of the movements that will not exceed 6 semitones. Finally, I will apply the CHORD-CLOSEST-PATH function (chord-closest-path (car chordized) (chordize-list (gen-divide voices vlfinal))) Hope it help some VL efforts. Best, Julio Herrlein
December 30, 20177 yr Author Janusz suggested a different way for the expression: (setf voices 3) (setf chordstovl2 '(b3eb5g3 cs6e7gs3 b4f5g6 f7e5c2 d4f7e4 gs7e2a8)) (setf chordmelo (pitch-melodize chordstovl2)) (setf intervals (integer-to-interval (modus (pitch-to-midi chordmelo)))) (setf map (replace-map '((-11 1) (-10 2) (-9 3) (-8 4) (-7 5) (7 -5) (8 -4) (9 -3) (10 -2) (11 -1)) intervals)) (setf vlfinal (integer-to-pitch (modus (interval-to-pitch map :start (car chordmelo))))) (setf chordized (mclist (chordize-list (gen-divide voices vlfinal)))) (chord-closest-path (car chordized) chordized)
March 29, 20196 yr Dear Julio, Thanks for sharing these codes. So I'm trying to understand if the following approach could work - it seems to work for your example (but not for every progression). (setf chordstovl2 '(b3eb5g3 cs6e7gs3 b4f5g6 f7e5c2 d4f7e4 gs7e2a8)) (defun voicelead (chords) (chord-closest-path (list (car chords)) chords)) (voicelead (ambitus '(c4 c5) chordstovl2)) but (voicelead (harmonic-progression '(0 3 6 2) '(c major) :size 4 :step 2 :relative nil :variant 'r)) can generate one of two options. The second one is obviously better since it uses a common tone. Even with ambitus-chord I'm not able to make it work consistently. If you have any thoughts I'd be happy to hear them. All the best, Avner
April 19, 20196 yr Julio, the CHORD-CLOSEST-PATH function is doing exacly that: (chord-closest-path '(eb4g4b4) '(b3eb5g3 cs6e7gs3 b4f5g6 f7e5c2 d4f7e4 gs7e2a8))
April 19, 20196 yr Hi Janusz, If I try this: (chord-closest-path '(b4g4e4c4) '(b4g4e4c4 c5a4e4f4 b4a4f4d4 b4g4e4d4)) It produces either or My question is why does it produce two variants and how can one make sure it always chooses the shortest path? To me it seems like the second option is the correct one since the total half steps between all the voices is 6 (b-c, g-f, c-a) and in the first the total half steps between the voices is 8. Thanks! Avner p.s. - if anyone's curious - Dmitri Tymoczko wrote a software that does minimal voice leading for music 21 in Python. It's available here - http://dmitri.mycpanel.princeton.edu/voiceleading_utilities.py
April 21, 20196 yr Author Dear Friends, Avner, I noticed exactly the same behaviour. I feel a contradiction in having two results for something called "the closest path", because "the closest path", in my interpretation, means only one solution: the one in which the sum of the semitones of all voice-leading moves are the less. It's logically impossible to have "the closest path" with two solutions. You cannot have the cake and eat it too... If the function alternate between two solutions, maybe could be the case that two solutions would sum the same amount of semitonal moves, but the example above do not seems to be that case. After I realized it, I tried to find a way to assure a more accurate expression to represent that one "closest" solution. Janusz, what do you think ? All the best !! I love Opusmodus !! Thank you ! Best, Julio
April 21, 20196 yr Yes - obviously you're right - that one is the best. So is there a way to implement that? I actually appreciate that chord-closest-path gives different options on different runs (it's quite creative 🙂) but I would also like to have the option of a truly parsimonious voice leading function. Julio's solution seems to work, but I can't quite manage to make it into a function....and I don't know enough Python (and maybe not enough lisp) to easily convert Tymoczko's code to lisp. By the way - I love Opusmodus as well! Thanks 🙂 Avner
April 21, 20196 yr Author On 4/19/2019 at 3:27 PM, Avner Dorman said: Hi Janusz, In the example below (first two chords) b-c (+1) g-a (+2) e-f (+1) c-e(+3) total: 7 semitones move In the example below (first two chords) b-c (+1) g-f (-2) e-e (0) c-a (-3) total: 6 semitones move - this is smoother, more parsimounious (considering only the first two chords). I think voice-leading is one of the hardest things !!
April 21, 20196 yr It is not hard if we look for strict result e.i. smallest total without thinking about parallel movement etc... Here is the result of our example (strict): (chord-closest-path '(b4g4e4c4) '(b4g4e4c4 c5a4e4f4 b4a4f4d4 b4g4e4d4)) c-c = 0 e-e = 0 g-f = 2 b-a = 2 total 4 etc...
April 22, 20196 yr Please check: '(f3d4e5 e3a3gs5) (chord-closest-path '(f3d4e5) '(f3d4e5 e3a3gs5)) => (f3e5d4 e5gs3a3) '(b3eb5g3 cs6e7gs3 b4f5g6 f7e5c2 d4f7e4 gs7e2a8) (chord-closest-path '(b3eb5g3) '(b3eb5g3 cs6e7gs3 b4f5g6 f7e5c2 d4f7e4 gs7e2a8)) => (g3b3eb5 gs3cs4e5 b3g3f5 c4f3e5 d4e5f3 e5gs3a3) '(b4g4e4c4 c5a4e4f4 b4a4f4d4 b4g4e4d4) (chord-closest-path '(b4g4e4c4) '(b4g4e4c4 c5a4e4f4 b4a4f4d4 b4g4e4d4)) => (c4b4e4g4 e4f4c4a4 b3f4d4a4 d4b3e4g4) yours, Janusz
April 22, 20196 yr Hi Janusz, These look correct to me! I haven’t done the math, but I don’t see any voice leading issues. Thanks! Avner
April 22, 20196 yr Author Dear Janusz, In my version of Opusmodus, I get this: But maybe it's important to have all compressed within one octave, like this: (g3b3eb4 gs3cs4e4 b3g3f4 c4f3e4 d4e4f3 e4gs3a3) What do you think? Best, Julio
April 22, 20196 yr What you see above is a new revised function. Examples: Now the start is optional. (chord-closest-path '(f3d4e5 e3a3gs5)) => (f3e5d4 e5gs3a3) (chord-closest-path '(b3eb5g3 cs6e7gs3 b4f5g6 f7e5c2 d4f7e4 gs7e2a8)) => (g3b3eb5 gs3cs4e5 b3g3f5 c4f3e5 d4e5f3 e5gs3a3) (chord-closest-path '(b3eb5g3 cs6e7gs3 b4f5g6 f7e5c2 d4f7e4 gs7e2a8) :start '(c3eb4f5)) => (g5eb4b2 gs5cs3e4 b2g5f4 c3f4e5 d3e5f4 e5gs4a2) (chord-closest-path '(b4g4e4c4 c5a4e4f4 b4a4f4d4 b4g4e4d4)) => (c4b4e4g4 e4f4c4a4 b3f4d4a4 d4b3e4g4) (chord-closest-path '(b4g4e4c4 c5a4e4f4 b4a4f4d4 b4g4e4d4) :start '(c3eb4f5d6)) => (c3e6g4b5 e6f4c3a5 b2f6d4a5 d4b2e6g5)
April 22, 20196 yr Maybe closest-path and keep chord-closest-path as the non strict version? either way - thank you so much!!! 😁
April 22, 20196 yr Author Yes, it also occurred to me to change the name. If this is really an sucessful algorythm for the closest-path, I would suggest to change the old function to another name. Thank you, Janusz ! Best, Julio
April 28, 20196 yr Author Dear Janusz, When I evaluate this expression: (closest-path '(b3eb5g3 cs6e7gs3 b4f5g6 f7e5c2 d4f7e4 gs7e2a8)) I get this: I was expecting this: Maybe a good idea would be something like a octave compress option. Best, Julio
April 28, 20196 yr (ambitus-octaves 'c3 2 (closest-path '(b3eb5g3 cs6e7gs3 b4f5g6 f7e5c2 d4f7e4 gs7e2a8))) => (g3b3eb4 gs3cs4e4 b3g3f4 c4f3e4 d4e4f3 e4gs3a3) or you could put this two FUNCTIONS into ONE => (closest-path*... just take all the values/arguments from the basic version and add the octave-thing? greetings andré
April 28, 20196 yr What you expect it is not a closes path. As you can see the first chord in the sequence is b3eb5g3. There are two option you can use to get the result you are looking for: 1. start chord (closest-path '(b3eb5g3 cs6e7gs3 b4f5g6 f7e5c2 d4f7e4 gs7e2a8) :start 'b4eb4g4) 2. ambitus-chord values (closest-path '(b3eb5g3 cs6e7gs3 b4f5g6 f7e5c2 d4f7e4 gs7e2a8) :ambitus-chord 11) @AM example is another good solution.
April 28, 20196 yr Author Dear André, It's all solved with :start option ! You can decide a "model" voicing for the voice-leading ! Very cool ! Best, Julio Thank you, Janusz !
Create an account or sign in to comment