Jump to content
Sign in to follow this  
JulioHerrlein

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

Share this post


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)

 

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

Julio, the CHORD-CLOSEST-PATH function is doing exacly that:

(chord-closest-path '(eb4g4b4) '(b3eb5g3 cs6e7gs3 b4f5g6 f7e5c2 d4f7e4 gs7e2a8))
image.png

Share this post


Link to post
Share on other sites

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

Share this post


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

Share this post


Link to post
Share on other sites

what about (b-a, g-f, e-e, c-c) sum 4

Share this post


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 

Share this post


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  !!

Share this post


Link to post
Share on other sites

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))
Screenshot 2019-04-21 at 18.20.44.png

c-c = 0
e-e = 0

g-f = 2
b-a = 2
total 4

etc...

 

Share this post


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

Share this post


Link to post
Share on other sites

Hi Janusz,

 

These look correct to me! I haven’t done the math, but I don’t see any voice leading issues. 

 

Thanks!

Avner 

Share this post


Link to post
Share on other sites

Dear Janusz,

 

In my version of Opusmodus, I get this:

image1.png

 

But maybe it's important to have all compressed within one octave, like this:

(g3b3eb4 gs3cs4e4 b3g3f4 c4f3e4 d4e4f3 e4gs3a3)

image2.png

What do you think?

Best,

Julio

 

Share this post


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)

 

Share this post


Link to post
Share on other sites

Should I change the name to CLOSEST-PATH, VOICE-LEADING or leave as it is 🙂

Share this post


Link to post
Share on other sites

Maybe closest-path and keep chord-closest-path as the non strict version?

 

either way - thank you so much!!! 😁

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

Dear Janusz,

 

When I evaluate this expression:

(closest-path '(b3eb5g3 cs6e7gs3 b4f5g6 f7e5c2 d4f7e4 gs7e2a8))

I get this:

image.png

 

I was expecting this:

 

image.png

Maybe a good idea would be something like a octave compress option.

 

Best,

Julio

Share this post


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é

Share this post


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.

Share this post


Link to post
Share on other sites

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 !

Share this post


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.

Sign in to follow this  

  • Similar Topics

    • By opmo
      I am working on a new omn symbol VOICES and wonder which way is more natural to write.
      I would like to hear your comments and ideas on this.
       
      Here are the two examples.
       
      With symbol :vx

      1) Each voice after each event, next to each other (1 bar 4/4):
      symbol option :vx (:v1 q cs5 ff :v2 q gs4 ff :v3 q f4 ff :v4 q f3 ff :v1 q eb5 ff :v2 q gs4 ff :v3 q c4 ff :v4 q gs3 ff :v1 q cs5 ff :v2 q gs4 ff :v3 q f4 ff :v4 q cs3 ff :v1 q f5 ff :v2 q cs5 ff :v3 q gs4 ff :v4 q cs3 ff) symbol option :x (:1 q cs5 ff :2 q gs4 ff :3 q f4 ff :4 q f3 ff :1 q eb5 ff :2 q gs4 ff :3 q c4 ff :4 q gs3 ff :1 q cs5 ff :2 q gs4 ff :3 q f4 ff :4 q cs3 ff :1 q f5 ff :2 q cs5 ff :3 q gs4 ff :4 q cs3 ff)  
      2) Each voice in a separate bar (1 bar 4/4):
      symbol option :vx (:v1 (q bb4 ff g4 gs4 e g4 f4)  :v2 (q cs5 ff b4 gs4 fs4)  :v3 (q f4 ff bb3 gs3 e bb3 gs3)  :v3 (q d3 ff e eb3 d3 q c3 bb2)) symbol option :x (:1 (q e4 ff h f4 q c5) :2 (e fs4 ff gs4 bb4 gs4 q fs4 f4)  :3 (s g3 ff gs3 q bb3 s gs3 g3 q gs3 f4)  :4 (q c3 ff h f2 q f3))  
      The symbol option :1 :2 :3 :4
      (:1 (q e4 ff h f4 q c5)  :2 (e fs4 ff gs4 bb4 gs4 q fs4 f4)  :3 (s g3 ff gs3 q bb3 s gs3 g3 q gs3 f4)  :4 (q c3 ff h f2 q f3))  
      will print in the Listener:
      (:|1| (q e4 ff h f4 q c5)  :|2| (e fs4 ff gs4 bb4 gs4 q fs4 f4)  :|3| (s g3 ff gs3 q bb3 s gs3 g3 q gs3 f4)  :|4| (q c3 ff h f2 q f3))  
      I personally think the second option is better to control.
       
      Best wishes,
      JP
    • By Jorvd
      Hi everyone, this one should be pretty simple but I can't seem to figure it out yet.
      I am trying to notate this (see attachment) first bar in OM.
      So far I have this:
      (h e3c4g4e5 f3f4a4d5 z^q g4 f4 h g3d4b4 c3c4e4c5 ) Which doesn't work yet, although it's close.
      Additionally, I would love to use the same figured bass notation (with the brackets and everything).
      I know how to use the text attribute for Instruments but it seems to map everything based on white-space between the text, maybe I'm missing something?

      Thanks!
      - Jor
       

    • By Stephane Boussuge
      Hi, 
      here are the two functions i use daily in my workflow.
       
      The first gen-pitch-line can be used as this but is also required for the second function svoice1.
       
      svoice1 is a generic omn generator i find useful for my work.
       
      ;;; ------------------------------------------------------------------------------ ;;; GEN-PITCH-LINE ;;; Fonction de génération de hauteurs basées sur une conversion de vecteur de bruit ;;; avec un grand choix de type de bruit, taux de compression du vecteur, filtrage des répétitions et ambitus. (defun gen-pitch-line (nb-pitch &key (compress 1) (ambitus '(c4 c6)) seed filter-repeat (type :white)) (setf seed (rnd-seed seed)) (let (pitches) (do-verbose ("gen-pitch-line :seed ~s" seed) (labels ((white-or-pink (nb-pitch seed type) (if (eq type ':pink) (gen-pink-noise nb-pitch :seed seed) (gen-white-noise nb-pitch :seed seed :type (if (eq type ':white) :normal type)))) (process (nb-pitch &key (compress 1) (ambitus '(c4 c6)) seed filter-repeat type) (setf pitches (vector-to-pitch ambitus (vector-smooth compress (white-or-pink nb-pitch seed type)))) (when filter-repeat (setf pitches (gen-trim nb-pitch (filter-repeat filter-repeat pitches)))) pitches) ) (process nb-pitch :compress compress :ambitus ambitus :filter-repeat filter-repeat :seed (seed) :type type))))) #| USAGE (gen-pitch-line 24 :compress 0.42 :type :white :filter-repeat 1) (gen-pitch-line 24 :compress 0.42 :type :pink :filter-repeat 1) (gen-pitch-line 24 :compress 0.42 :type :extreme :filter-repeat 1) (gen-eval 8 '(make-omn :pitch (gen-pitch-line 24 :compress 0.42 :type :white :filter-repeat 1) :length (euclidean-rhythm 16 1 16 's :type 2) ) :seed 33) |# ;;; ------------------------------------------------------------------------------ ;;; SVOICE1 ;;; VERSION 0.1 (defun svoice1 (nb-pitch &key (level 16) (low 1) (high 16) (ratio 1/16) (e-type 2)(e-rotate nil)(e-variant nil) (compress 1)(filter-repeat nil)(pline-type :white) (p-divide nil)(articulation nil) (length nil)(pitch nil)(velocity nil) (seed nil)(articulation-map nil) (add-interval-if-length nil) (i-length '1/16) (i-list '(-4 -5 -3 -7)) ) (setf seed (rnd-seed seed)) (do-verbose ("svoice1 :seed ~s" seed) (let* (( pitch (if pitch pitch (if p-divide (gen-divide p-divide (gen-pitch-line nb-pitch :compress compress :seed (seed) :filter-repeat filter-repeat :type pline-type)) (gen-pitch-line nb-pitch :compress compress :seed (seed) :filter-repeat filter-repeat :type pline-type)))) (len (if length length (euclidean-rhythm level low high ratio :type e-type :rotate e-rotate :variant e-variant :seed (seed)))) (art (if articulation articulation (if articulation-map (length-map articulation-map len :otherwise '(default)) ))) (velo (if velocity velocity '(mf))) ) (if add-interval-if-length (add-interval-if-length (make-omn :pitch pitch :length len :velocity velo :articulation art ) :length-val i-length :interval-list i-list ) (make-omn :pitch pitch :length len :velocity velo :articulation art ))))) #| USAGE (svoice1 32) (svoice1 32 :seed 1234) (svoice1 32 :pitch '(c4 d4 e4) :articulation '(marc)) (svoice1 32 :length '((h h)(q q e e e e))) (svoice1 32 :pitch '(c4 g4) :length '((h h)(q q e e e e)) :velocity '((f)(pp))) (svoice1 32 :i-list '(-7 -4 -5 -3) :low 1 :high 4) (svoice1 128 :p-divide 8 :level (gen-repeat 12 '(16)) :compress 0.16) (svoice1 128 :p-divide 8 :level (gen-repeat 12 '(16)) :compress 0.16 :articulation-map '((1/8 stacc)(1/16 leg))) (svoice1 128 :p-divide 8 :level (gen-repeat 12 '(16)) :low 8 :high 8 :compress 0.16 :articulation-map '((1/8 stacc)(1/16 leg))) (svoice1 128 :p-divide 8 :level (gen-repeat 12 '(16)) :low 8 :high 8 :compress 0.16 :articulation-map '((1/8 stacc)(1/16 leg)) :add-interval-if-length t) (svoice1 128 :p-divide 8 :level (gen-repeat 12 '(16)) :low 2 :high 16 :compress 0.16 :articulation-map '((1/8 stacc)(1/16 leg))) (tonality-map '(((0 2 4 6 8 10) :root d4)) (svoice1 128 :p-divide 8 :level (gen-repeat 12 '(16)) :low 2 :high 16 :compress 0.16 :articulation-map '((1/8 stacc)(1/16 leg))) ) |# S.
×
×
  • Create New...