Jump to content

Parsimonious Voice Leading (again): attempts to provide an algorithm


Recommended Posts

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)
image.png.8119f04f427a5e28d603c4233d984bae.png

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)))))))))
image.png.c384ecee7241c0568051027a02991388.png

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)))
image.png.858c83b30f9103f794875010d9737692.png

Hope it help some VL efforts.

 

Best,

Julio Herrlein

Link to post
Share on other sites

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)

 

Link to post
Share on other sites
  • 1 year later...

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))
image.png

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. 

image.png

 

image.png

If you have any thoughts I'd be happy to hear them. 

 

All the best,

Avner

Link to post
Share on other sites
  • 3 weeks later...

Hi Janusz, 

 

If I try this: 

 

(chord-closest-path '(b4g4e4c4) '(b4g4e4c4 c5a4e4f4 b4a4f4d4 b4g4e4d4))

It produces either 

image.png.3530e620778d5ac3479538522bd8497b.png

or 

image.png.e7ea4848ef590ee5ab29a8da3d0d1772.png

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

Link to post
Share on other sites

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

Link to post
Share on other sites

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 

Link to post
Share on other sites
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

image.png.3530e620778d5ac3479538522bd8497b.png

 

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

 

image.png.e7ea4848ef590ee5ab29a8da3d0d1772.png

 

 

I think voice-leading is one of the hardest things  !!

Link to post
Share on other sites

Please check:

'(f3d4e5 e3a3gs5)
Screenshot 2019-04-22 at 13.02.13.png
(chord-closest-path '(f3d4e5) '(f3d4e5 e3a3gs5))
=> (f3e5d4 e5gs3a3)
Screenshot 2019-04-22 at 13.02.24.png
'(b3eb5g3 cs6e7gs3 b4f5g6 f7e5c2 d4f7e4 gs7e2a8)
Screenshot 2019-04-22 at 13.02.35.png
(chord-closest-path '(b3eb5g3) '(b3eb5g3 cs6e7gs3 b4f5g6 f7e5c2 d4f7e4 gs7e2a8))
=> (g3b3eb5 gs3cs4e5 b3g3f5 c4f3e5 d4e5f3 e5gs3a3)
Screenshot 2019-04-22 at 13.02.42.png
'(b4g4e4c4 c5a4e4f4 b4a4f4d4 b4g4e4d4)
Screenshot 2019-04-22 at 13.02.53.png
(chord-closest-path '(b4g4e4c4) '(b4g4e4c4 c5a4e4f4 b4a4f4d4 b4g4e4d4))
=> (c4b4e4g4 e4f4c4a4 b3f4d4a4 d4b3e4g4)
Screenshot 2019-04-22 at 13.03.02.png

yours,

Janusz

Link to post
Share on other sites

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)

 

Link to post
Share on other sites
(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é

Link to post
Share on other sites

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.

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

Terms of Use Privacy Policy