Jump to content

Neo-Riemannian approach (Tonnetz, etc.)

Recommended Posts

I'm not an expert in Neo-Riemannian Theory at all but as a consequence of my Common Lisp study and writing of some functions to do Diatonic Neigbour Tones and Leading Tones, I bumped into this and it's kind of what I'm trying to accomplish musically.


Does Opusmodus have functions for this (implicitly or explicitly) or does anyone know of a Common Lisp library that is already specialized in Neo-Riemannian transformation? Basically the ability to do P R and L motions using elegant functions and calculating the shortest modulation (shortest Tonnetz steps) from any triad to any other triad.


One of the many explanations on youtube: 






Link to comment
Share on other sites

Hi Wim,


i have made some test not really on neo-riemannian style but a bit connected to this idea.

I have tried to "navigate" form chord to chord with horizontal trajectory made with the Opusmodus function gen-tendency.


Here's my attempt:

;;; Chords Trajectory
;;; ========================================
(setf base-chords '(c4e4g4c5 f4a4c5f5 d4fs4a4d5 g4b4d5g5))
;; Extract horizontal pitch lines from base-chords
(setf base-chords.v1 (pitch-demix 1 base-chords))
(setf base-chords.v2 (pitch-demix 2 base-chords))
(setf base-chords.v3 (pitch-demix 3 base-chords))
(setf base-chords.v4 (pitch-demix 4 base-chords))

;; transpose them two octave up for greater than zero values
(setf base-chords.v1.t (pitch-transpose 24 base-chords.v1))
(setf base-chords.v2.t (pitch-transpose 24 base-chords.v2))
(setf base-chords.v3.t (pitch-transpose 24 base-chords.v3))
(setf base-chords.v4.t (pitch-transpose 24 base-chords.v4))

;; tendency trajectory with variance 0
(setf td-steps 4)
(setf td-n (* (length base-chords) td-steps))
(setf v1.td (mapcar 'round (gen-tendency td-n (pitch-to-integer base-chords.v1.t) :variance 0)))
(setf v2.td (mapcar 'round (gen-tendency td-n (pitch-to-integer base-chords.v2.t) :variance 0)))
(setf v3.td (mapcar 'round (gen-tendency td-n (pitch-to-integer base-chords.v3.t) :variance 0)))
(setf v4.td (mapcar 'round (gen-tendency td-n (pitch-to-integer base-chords.v4.t) :variance 0)))

;; convert back to pitch
(setf v1.td.p (integer-to-pitch v1.td))
(setf v2.td.p (integer-to-pitch v2.td))
(setf v3.td.p (integer-to-pitch v3.td))
(setf v4.td.p (integer-to-pitch v4.td))

;; back to chords
(setf re-chords (pitch-transpose -24 (pitch-mix (apply-eval '(v1.td.p v2.td.p v3.td.p v4.td.p)))))

;; closest path
(setf re-chords.closest (chord-closest-path (list (car re-chords)) re-chords))

;; relative path
(setf re-chords.relative (chord-relative-path (list (car re-chords)) re-chords))

Thank you for the videos links, very interesting.



Link to comment
Share on other sites

Hi Stephane,


Thanks, will have a look at this.


I'm thinking of creating a Tonnetz object in Lisp that can be P R and L moved and potentially also polarized Tonnetz movements  (feed it with a list of movements and it will return a list of triad harmonies with proper voice leading) as well as a modulation method to modulate to every other root of a triad. I'm trying to figure out the most elegant API for this right now ... implementation comes after that ;-)


Just a thought in the back of my mind right now. If anyone has ideas ... please let me/all of us know. Anyhow looks like a great Common Lisp kata.


Big hug,


Wim Dijkgraaf

Link to comment
Share on other sites

Dear Wim,


neo Riemannian functions and Tonnetz functionality has been implemented as part of Music21, or extensions of it. E.g., see



Perhaps you want to port some of that Python code to Common Lisp, that might be easier than developing the algorithms from scratch?


BTW, some of the code discussed there is by Dmitri Tymoczko, the author of A Geometry of Music (which, I agree, is a book really worth reading for the likes of us :).





PS: I assume you are not only programming in Common Lisp, so you may already know Python. (If not, that is rather easy to learn if you know Lisp already...)

Link to comment
Share on other sites

Have published the initial Tonnetz implementation (really bad code, has to be refactored and has some bugs):


This is my first attempt ... still completely novice in Common Lisp.




Big hug

Thanks Torsten! Will check it out. I'm fluent in C#, Typescript and Javascript but will check it out anyway.

Link to comment
Share on other sites

The update produces an error:

(move-s '(c4 eb4 g4))

> Error: The value nil is not of the expected type fixnum.
> While executing: move-r, in process Listener-3(11).
> Type cmd-. to abort, cmd-\ for a list of available restarts.
> Type :? for other options.


Link to comment
Share on other sites

Very nice start!


Unfortunately, the model results in some enharmonic problems. For example, (apply-tonnetz '(c4 e4 g4) '(n)) should result in f-minor, but instead results in (c4 f4 gs4). I assume there is no real way around that with Opusmodus and 12-EDO so far.


I solved such problems once simply by using a different tuning system, e.g., 31-EDO, because that tuning has distinct pitch classes for tones like ab vs. gs, and distinct pitch class intervals for, say, a minor third vs an augmented second.


Any plans to go beyond, e.g., having transformations for chords beyond major and minor, taking scales into account etc?


Minor issue: some of your tests meanwhile fail, likely because of some later refactoring.


(equal (triad-is-major '(c4 e4 g4)) 't)


Also, you may want to introduce some function that allows for recursive transformations to avoid things like (move (move (move triad 'l) 'p) 'r)).




Link to comment
Share on other sites

Hi Torsten,


Yes, will be continued. I needed a "topic" to work on to improve my Lisp skills. This is something not too big to work on. My first aim is to implement the diagram you find on this wiki: https://en.wikipedia.org/wiki/Tonnetz . Still have to do some refactoring. Maybe trying a more OO approach too.


Next step is to fully support this circle: https://commons.wikimedia.org/wiki/File:Circle_of_fifths,_clock_and_tonnetz.svg


Besides this, I'm studying Dimitri's book (as you and Stephane mentioned). Too early to have plans to implement that but it looks interesting enough to support his approach(es).


And of course the idea is to experiment with the Tonnetz and using it in a more free way by super-imposing triads, transposing voices, adding ornamentations etc.


Any additional ideas are very welcome of course.

Link to comment
Share on other sites

Would it help to have your transformation functions be related to explicit scales and its scale degrees (instead of measuring intervals in semitones)? That is what I did in my own computational harmony model. That allowed me to use arbitrary scales and the chords that belong to those scales. 


The tricky bit is that I am using constraint programming, where each variable can be controlled by multiple restrictions (constraints). So, I can restrict both intervals measured in semitones and in scale degrees independently. Would be tricky to translate that into plain functions.


Attached is a related (unpublished) paper that outlines the approach in principle, though many details are missing in that paper. Tricky to share the code on that, because there is lots. Some core functionality (the data structure) is defined here (Oz programming language).



Constraint programming is also possible within Common Lisp (e.g., with Screamer, https://nikodemus.github.io/screamer/, and other constraint solvers exist as well), and for mere harmony modelling (in contrast to modelling polyphonic music composition in general) this might be rather fast.






Link to comment
Share on other sites

Hi Torsten,


My main goal to learn Common Lisp, Opusmodus and have something like the Tonnetz to do musical experiments with. For that I'm happy with correct absolute pitches and less concerned with enharmonic correctness.


Constraint programming is completely new to me. As soon as I've a bit of time I'll definitely have a look at it as well as to your paper. I've checked the link and browsed a bit through your projects. Wow, really impressive stuff! Great to get to know you a little bit through this forum.





Link to comment
Share on other sites

A little extra feature to play with:


(apply-tonnetz '(c4 e4 g4) '(l r n s p))

=> ((c4 e4 g4) (b3 e4 g4) (b3 d4 g4) (c4 eb4 g4) (b3 eb4 fs4) (b3 d4 fs4))


Ability to not include a move in the output (by putting that move between parentheses):


(apply-tonnetz '(c4 e4 g4) '(l r n (s) p))

=> ((c4 e4 g4) (b3 e4 g4) (b3 d4 g4) (c4 eb4 g4) (b3 d4 fs4))


Link to comment
Share on other sites

  • 1 year later...
  • 1 year later...

Following our successful presentation in the Museo di Storia Naturale in Venice on the 7th December, Achim Bornhoeft and I spent some time talking and playing with the Neo-Riemann theory with an outcome of a diagram and a function. This will be part of the next release.







Transformations on a C major triad



One step transformation (basic transformations):

P (parallel)

R (relative)

L (leading)


Two step transformations:

Parallel: PR and PL

Relative: RL and RP

Leading: LR and LP


Three step transformations:

Parallel: PLR and PRL

Relative: RLP and RPL

Leading: LPR and LPR



PLR and RLP are equal.

PRL and LRP are equal.

RPL and LPR are equal.



(tonnetz 'c4e4g4 '(p p r r l l))
=> (c4e4g4 c4eb4g4 c4e4g4 c4e4a4 c4e4g4 b3e4g4 c4e4g4)

(tonnetz '(c4 e4 g4) '(p p r r l l))
=> ((c4 e4 g4) (c4 eb4 g4) (c4 e4 g4) (c4 e4 a4)
    (c4 e4 g4) (b3 e4 g4) (c4 e4 g4))

(tonnetz 'e4g4b4 '(l r l r p l r l r p))
=> (e4g4b4 e4g4c5 e4a4c5 f4a4c5 f4a4d5 fs4a4d5 fs4a4cs5
    e4a4cs5 e4gs4cs5 e4gs4b4 e4g4b4)

(tonnetz 'ab3c4eb4 '(p l p l p l p l))
=> (ab3c4eb4 gs3b3eb4 gs3b3e4 g3b3e4 g3c4e4 g3c4eb4 gs3c4eb4
    gs3b3eb4 gs3b3e4)

(tonnetz 'ab3c4eb4 '(pl pl pl pl))
=> (ab3c4eb4 gs3b3e4 g3c4e4 gs3c4eb4 gs3b3e4)

(tonnetz 'c4e4g4 '(p r p r p r p r))
=> (c4e4g4 c4eb4g4 bb3eb4g4 bb3eb4fs4 bb3cs4fs4 a3cs4fs4
    a3cs4e4 a3c4e4 g3c4e4)

(setf moves (rnd-order '(p l r lr lp rp rl pr pl plr prl rpl) :seed 26))
=> (rp rl rpl pr prl lr r p plr pl lp l)

(tonnetz 'c4e4g4 moves)
=> (c4e4g4 cs4e4a4 d4fs4a4 eb4fs4bb4 eb4g4c5 f4a4c5 e4g4c5
    e4a4c5 e4a4cs5 f4a4d5 fs4a4cs5 f4a4d5 f4bb4d5)

(setf transitions '(p l r lr lp rp rl pr pl plr prl rpl prlpr lrplprpp))
(setf rnd-transition (rnd-sample 15 transitions :seed 750989))
=> (prlpr r r p pl pl lrplprpp lr prlpr rpl lp rl prl rl rpl)

(tonnetz 'ab3c4eb4 rnd-transition))
=> (ab3c4eb4 g3c4eb4 g3bb3eb4 g3c4eb4 g3c4e4 gs3c4eb4 gs3b3e4
    f3bb3d4 f3a3c4 e3a3c4 eb3gs3c4 e3g3c4 f3a3c4 eb3g3c4
    d3g3bb3 cs3fs3bb3)
(tonnetz '(c maj) '(plp rpr lpl rpr lpl lpl rprp lpl))
=> (c4e4g4 b3eb4gs4 a3d4fs4 bb3cs4f4 gs3b3e4 g3c4eb4 gs3b3e4 bb3d4f4 a3cs4fs4)


Link to comment
Share on other sites

Dear Janusz,


This is a really cool feature I was looking for !

There is a lot of interesting transformations and graphs related to this subject, like this, compiled by my friend Ciro Visconti, who studied with Straus:























And so on, all related to the basic tonnetz transformations.


A good idea is thinking how to model also four note structures.


All the best !



Link to comment
Share on other sites

Achim Bornhoeft and I spent some time talking and playing with the Neo-Riemann theory with an outcome of a diagram and a function


Thanks for sharing. Do I understand correctly that these transformations always assume triads? 


Of course, one can always extend the triads afterwards...




Link to comment
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.

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