Neo-Riemannian Theory for composition, analysis and math education in Sonic Pi

Introduction

Hello there! I’m very excited to join the community to contribute some ideas and various future lines to consider.

This is the first idea: Although, Neo-Riemannian Theory is mainly used to analyze music, I think that an improvisational approach would be quite interesting in the Sonic Pi environment. For this reason, I was configuring three common functions from Neo-Riemannian Theory (P = parallel, R= relative, L = leading tone) to apply during live improvisations.

On the other hand, I have been working with these functions while teaching introductory classes to SP with Mathematical Music Theory; in this sense, it is also a good concept to intersect computer science, music and mathematics.

I share with you the functions and I propose a simple example with a loop that takes base notes and chords and transforms them according to the function P, R or L; also showing the name of the chord in the log.

Code and example

define :p_neo_riemannian do |note, triad| 
  if triad == :major
    x = (chord note, :major)[0]
    y = (chord note, :major)[1]
    y2 = (y - 1)
    z = (chord note, :major)[2]
    puts [note_info(x), "minor"]
    play  [x, y2, z].ring  
  elsif triad == :minor
    x = (chord note, :minor)[0]
    y = (chord note, :minor)[1]
    y3 = (y + 1)
    z = (chord note, :minor)[2]
    puts [note_info(x), "major"]
    play [x, y3, z].ring  
  else
    puts "Wrong chord, enter a major or minor chord" 
  end
end

define :r_neo_riemannian do |note, triad|  
  if triad == :major
    x = (chord note, :major)[0]
    y = (chord note, :major)[1]
    z = (chord note, :major)[2]
    z2 = (z + 2)
    puts [note_info(z2), "minor"]
    play [x, y, z2].ring  
  elsif triad == :minor
    x = (chord note, :minor)[0]
    x2 = (x - 2)
    y = (chord note, :minor)[1]
    z = (chord note, :minor)[2]
    puts [note_info(y), "major"]
    play [x2, y, z].ring  
  else
    puts "Wrong chord, enter a major or minor chord"
  end
end

define :l_neo_riemannian do |note, triad|
  if triad == :major
    x = (chord note, :major)[0]
    x2 = (x - 1)
    y = (chord note, :major)[1]
    z = (chord note, :major)[2]
    puts [note_info(y), "minor"]
    play [x2, y, z].ring
  elsif triad == :minor
    x = (chord note, :minor)[0]
    y = (chord note, :minor)[1]
    z = (chord note, :minor)[2]
    z2 = (z + 1)
    puts [note_info(z2), "major"]
    play [x, y, z2].ring  
  else
    puts "Wrong chord, enter a major or minor chord"   
  end
end
# Example
4.times do
  n = 4
  density n / (n/2) do
    n.times do
      r_neo_riemannian [:a, :f, :d, :e ].tick,
        [:minor, :major, :minor, :minor].look
      sleep 1
    end
    n.times do
      l_neo_riemannian [:c, :b, :d, :g ].tick,
        [:major, :minor, :major, :minor].look
      sleep 1
    end
    n.times do
      p_neo_riemannian [:e, :g, :a, :b ].tick,
        [:major, :major, :minor, :minor].look
      sleep 1
    end
  end
end

Future works

  1. It would be great to create functions that allow you to easily compose, in this way, you could create music with PLP, LRP and other functions from the nuclear functions P, L and R.

  2. Another interesting functionality would be to create an analysis in “improvisational” time of the functions that occur in any chord progression. Thus, you could observe which functions contain a harmonic progression.

  3. Some more generative music: take pseudorandomly generated Neo-Riemannian functions to apply them to a set of chords.

4 Likes

Fascinating stuff. I’ve been reading an article on this in wikipedia. Quite a lot to get your head around!

1 Like

This is great to see.

I use the Tonnetz on an iPad, using Lemur, to improvise with these transformations. Though, sadly, Lemur is now deprecated. One iPad is now ‘frozen’, so that I can carry on using Lemur.

I also have two video live streams using my Modular Synths and exploring NRTs (R, L, P) using the SIG (Stochastic Inspiration Generator)

Looking forward to playing with your code.

BTW: I see NRT’s are more than analytical. Certainly soundtracks for LOTR, Rings of Power, Dune etc., all make extensive use of NRT’s. Pat Metheny’s Roots of Coincidence is pretty much all NRT’s (won a grammy as well). Some of John Barry’s Bond cues from the 60s also follow NRT’s.

1 Like

Thanks for commenting and sharing your videos. I was observing on Lemur and Ipad the use of the Tonnetz. I also watched your videos with the modular synth. The code is intended for harmonic progressions, but also with a few changes to the loop it can be played note by note by chord.

On the other hand, there is currently an online tool that associates both the Tonnetz transformation and its dual (generalized to different simplicial complexes) together with the circle of fifths and chromatic in real time on the web. The Tonnetz – One Key, Many Representations
(note that it supports midi controller input)

Of course, NRT is not only for analysis, the study that Lehman does on this transformational perspective in film music, especially, is enriching.

References:

  1. Lehman, F. 2014. Film music and NRT. Oxford Handbook.
  2. Lehman, F. 2018. Hollywood Harmony: Musical Wonder and the Sound of Cinema. Oxford University Press.

# @robin.newman : Thanks for the comment and interest in the post.

@EdgarDelgadoVega well it’s not just about chords. If you do a harmonic reduction of single lines then you have harmony. The harmonic implication of this figure is a shift between Cm to Em and Cm to Abm as thirds based movement between roots which is a notable feature of NRT based music.

p = (ring :C4, :D4, :Eb4, :G4, :Ab4)

t = (ring 0, 4, 0, -4)

live_loop :nrt do
  current_t = t.tick(:me)
  
  20.times do
    play p.tick + current_t
    sleep 0.25
  end
end

NRT is really an attempt to rationalise non-diatonic movements that occurred in the music of Wagner and later practitioners etc. Riemann’s development of functional notation I, ii, iii, etc., could not adequately explain these types of shift, hence NRT’s.

I know Lehman well.

My point is that Lemur is a good way to improvise NRT’s in a very straightforward manner. Lemur ‘removes’ the instrumental barrier of keyboard competency, as well as ableism, thereby making it more accessible. If I’m playing a eurorack module, then a one finger chord press is a saviour given I’m using foot controllers as well to modulate parameters.

BTW: it was good to listen to your classical guitar performances and your Sonic Pi tracks.

1 Like

Thanks for the reply, @Hussein
I don’t know about Eurorack hardware, but I guess it’s easier to run the NRT stuff simultaneously with Lemur. In this sense, the SP code also removes the barrier of pianistic competence. Following this line, below I attach other (composite) two functions based on Obluda’s thesis entitled:
Topics in Hollywood Scores: Using Topic Theory to Expand on Recent Neo-Riemannian Analyzes of Film Music (2021).

These are the F and N functions of NRT (one example):

define :f_transformation do |p, triad|
  if triad == :major
    puts (chord p + 7, :minor)
    return (chord p + 7, :minor) 
  elsif triad == :minor
    puts (chord p - 7, :major)
    return (chord p - 7, :major)
  else
    puts "Wrong chord, introduce a major o minor chord"
  end 
end

define :n_transformation do |p, triad|
  if triad ==  :major
    puts (chord p + 5, :minor)
    return (chord p + 5, :minor)  
  elsif triad == :minor
    puts (chord p - 5, :major)
    return (chord p - 5, :major)
  else
    puts "Wrong chord, introduce a major o minor chord"
  end
end

##| Example

live_loop :obluda do
  dur = 0.5
  density 2 do
    6.times do
      play f_transformation(:c4, :minor).tick, release: dur
      sleep dur
    end
    6.times do
      play n_transformation(:ab4, :major).tick, release: dur
      sleep dur
    end
  end
end

Reference:

  1. Obluda, D.C. (2021). Topics in Hollywood Scores: Using Topic Theory to Expand on Recent Neo-Riemannian Analyzes of Film Music (Doctoral dissertation, University of Colorado at Boulder).

Thank you for posting this! I love this stuff (it’s what drew me to study music theory in grad school, back in the day), and I’ve been thinking along similar lines. I was imagining a slightly different syntax and implementation, but I haven’t gotten around to coding any of it. :flushed::smile:

How does this sound?

  1. In terms of syntax, Neo-Riemannian transformations could be chainable methods on chord objects. For example:
ch = chord(:c4, major)
# ch.p = c minor, root position
# ch.l = e minor, 2nd inversion
# ch.r = a minor, 1st inversion
# ch.l.r.p = g minor, 1st inversion

This has the advantage of capturing the left-to-right direction of composition that most people seem to use. For example, if “LR” means L then R, then ch.l.r is easier to read than r(l(ch)).

  1. The methods could make use of chord’s invert: parameter to put them in the expected position for smooth voice leading, as shown above. For in classical-ish styles (at least), this was the big advantage of neo-Riemannian theory. On the other hand, I’m open to the idea that it’s best for these functions to return root-position chords.

  2. This might be overkill, but what about implementing them by means of Julian Hook’s “Uniform Triadic Transformations”? It’s basically the same as what you’ve done above: a scalar value for root transposition & a chord quality. And it provides a framework that could be easily extended to other transformations, chord types, and even other tuning systems!
    Hook, Julian. “Uniform Triadic Transformations.” Journal of Music Theory 46, no. 1/2 (2002): 57–126. https://www.jstor.org/stable/4147678 (behind a paywall, sorry)

1 Like

Thanks for comment @pashultz. Actually, it could be implemented with other MaMuth algebraic groups: PLR group, UTTs group, TI group, JQZ group; or, the group(field) of the affine parabola that works on all these mentioned groups.
As you mention, both the composition P \circ L with left or right notation is possible (in the mathematical sense first L, then P). A simpler syntax by concatenating PLR functions seems to me a better way around it than using ((())).
The point is to achieve the best implementation of these groups for live coding? Regarding the chord position to achieve a smooth voice leading it could be rearmed by extracting the elements by index (chord :x, :triad)[ n ] and regrouping them in the ideal position. The other way would be by chord inversion (as you mentioned). I’ll be watching if you come up with a new code in SP for PLR, or UTTs.

The implementation wasn’t intended to build UTTs, but as you mention, I didn’t realize that the way the code is written analogically looks very similar to the definition provided by Julian Hook with his group of UTTs. It could be thought that the implementation in SP described above proposes PLR as a subgroup of UTTs. Actually, it has made me think that when coding the function it is associated with a specific mathematical definition, even though the result is similar.

1 Like

Hello, a couple of months ago, without knowing anything about Riemann, I experimented on a harmony based on the succession of random triads, starting from one of the notes of the chord. For example, from a triad c,e,g (c major) a triad g,bb,d (g minor) could happen.

The triads could be in the fundamental position or in any of the other two inversions.
As for the melody that was generated while the chord was playing, it was major or minor pentatonic depending on the type of chord that was used at that moment.

I’m going to try to recover the code, it’s somewhere…

1 Like

I did some tonnetz experimentation with ziffers last year using roman numerals / numbered notation and Neo-Riemannian PLR operations. Could make some more generic Sonic Pi version as well at some point. Didn’t continue on working on that as transformations were limited only working for major/minor triads.

1 Like

@Raul We look forward to seeing its implementation.

@amiika thanks for your comment.
I just looked at the Ziffersystem documentation. It abbreviates the SP syntax a lot and is great for such a creative and combinatorial purpose.
I just checked the Tonnetz section. Regarding your assertion about transformations limited to triads, indeed, the conventional Tonnetz [3,4,5] has only major and minor triads. But there are more possibilities: simplicial complexes with the same transformations but different harmonies. I took the liberty of using the Tonnetz found in the Chord Transformations section and adding one of those mentioned possibilities:

Image taken and modified from Transformations, Chord Transformations - Tonnetz by Miika Alonen

As you can see on the Tonnetz K_TI[2,3,7] (TI = Transposition/Inversion), the same Neo-Riemannian operations work on other types of triads. There are 10 other alternative Tonnetz. It will be great to know your opinion about it.

At the moment, over the Tonnetz [2,3,7], I wrote the following Neo-Riemannian progression in Ziffers 2 manually:

zplay "R<0.25> 035 025 257 57T ", key: :c, scale: :chromatic, rhythm: "qe"
##| Progression: 035 - P - 025 - R - 257 - L - 57T
# variables generales e inicio
use_bpm 90
notas = []
raiz = 51
48.times do |i|
  notas[i]=raiz
  raiz +=1
end
use_bpm 96
acordes = [[0,4,7],[4,7,12],[7,12,16],[0,3,7],[3,7,12],[7,12,15]] # acoredes maj y min e inversiones
raiz = 0
maj = [0,2,4,7,9,12,14,16,19] # pentatonica maj
min = [0,3,5,7,10,12,15,17,19] #pentatonica min
escala = 0 # escala por defecto maj
acorde_uso = rrand_i(0,5) # acorde uso
acorde_esc = acordes[acorde_uso] # notas acorde
raiz = acorde_esc[[0,1,2].choose] # eleccion una de las notas como raiz
t = 0
zy = "zynaddsubfx_zynaddsubfx_132_0" # puerto
fl = "fluid_synth_(1856)_synth_input_port_(1856_0)_134_0" # puerto
contador = 0



# aura, gost whistle 1, bass analog 3, simple brass,Binary_piano2
define :canales do
  midi_cc 0,25,channel: 1,port: zy
  midi_cc 32,0,channel: 1,port: zy
  midi_pc 50,channel: 1,port: zy
  sleep 1
  midi_cc 0,4,channel: 2,port: zy
  midi_cc 32,0,channel: 2,port: zy
  midi_pc 22,channel: 2,port: zy#
  sleep 1
  midi_cc 0,1,channel: 3,port: zy
  midi_cc 32,0,channel: 3,port: zy
  midi_pc 2,channel: 3,port: zy
  sleep 1
  midi_cc 0,2,channel: 4, port: zy
  midi_cc 32,0,channel: 4,port: zy
  midi_pc 64,channel: 4,port: zy
  sleep 1
  midi_cc 0,24,channel: 5, port: zy
  midi_cc 32,0,channel: 5,port: zy
  midi_pc 39,channel: 5,port: zy
  sleep 1
  midi_cc 10,32,channel: 1, port: zy # pan canal 1
  sleep 1
  midi_cc 10,110,channel: 2, port: zy # pan canal 2
  sleep 1
  midi_cc 10,74,channel: 3, port: zy # pan canal 3
  sleep 1
  midi_cc 10,54,channel: 4 , port: zy  # pan canal
  sleep 1
  midi_cc 10,96,channel: 5, port: zy  # pan canal
  sleep 1
  midi_cc 0,0,channel: 1,port: fl # banco 0
  midi_cc 32,0,channel: 1,port: fl # va a cambiar el banco 25
  midi_pc 25,channel: 1,port: fl # programa 25 banco 0
  sleep 1
  
end

define :inicio do
  # eleccion acordes
  raiz = raiz+acorde_esc[[1].choose] # eleccion raiz
  acorde_uso = rrand_i(0,5) # acorde uso
  acorde_esc = acordes[acorde_uso] #  notas acorde uso
  # limitar octava raiz
  if raiz > 11
    raiz -= 12
  end
  t = 8
  contador += 1
end


define :bajo do
  # notas bajo
  midi_note_on notas[raiz]-24, channel: 3, port: zy,velocity: 60
  midi_note_on notas[raiz]-12, channel: 4, port: zy,velocity: 60
end

define :pads do
  
  3.times do |i|
    # notas acorde
    midi_note_on notas[raiz+acorde_esc[i]],channel: 1, velocity: 80, port: zy
    midi_note_on notas[raiz+acorde_esc[i]],channel: 2, velocity: 80, port: zy
    
  end
end

define :escala do
  # eleccion escala por defecto maj
  escala= maj
  # los 3 primeros acordes son maj
  if acorde_uso > 2
    escala= min
  end
end


define :melodia do
  # notas que contendra melodia
  z = rrand_i(5,9)
  
  
  use_synth :pluck
  11.times do
    if (spread z,11).tick
      no = rrand_i(0,7)
      n = raiz+escala[no]
      m = notas[n]
      midi_note_on m,channel: 1, port: fl,velocity: 90
      play m,pan: -0.6,amp: 2
      if no > 4 and no < 8
        n2 = raiz+escala[[no-2,no-3].choose]
        m2 = notas[n2]
        midi_note_on m2,channel: 1,port: fl,velocity: 90
        play m,pan: -0.6,amp: 2
      end
    end
    sleep 0.5
    midi_note_off m, channel: 1, port: fl
    midi_note_off m2, channel: 1, port: fl
    
    if (spread 11-z,11).look
      sleep 0.5
    end
  end
end

define :fx do
  with_fx :reverb do
    with_fx :ixi_techno,phase: (ring 0.5,0.25,0.125).choose do
      with_fx :slicer,phase: (ring 0.5,0.25,0.125).choose do
        live_loop :buc do
          z = [0,1,2].choose
          if z == 0 # off loop
            stop
          end
          3.times do |i|
            synth :mod_dsaw, note: notas[raiz+acorde_esc[i]],amp: 0.2,attack: 2,sustain: 4,release: 3,cutoff: 80,pan: rrand(-1,1)
          end
          sleep 2
        end
      end
    end
  end
end

define :drum do
  live_loop :dr do
    z = [0,1,2].choose
    if z == 0 # off loop
      stop
    end
    with_fx :echo,pahase: (ring 0.5,0.25).choose do
      sample 25,pan: [-1,1].choose,amp: 2
      sleep 1
    end
  end
end


canales()
sleep 1

live_loop :a do
  if contador == 25
    midi_all_notes_off
    stop
  end
  use_real_time
  inicio()
  bajo()
  drum()
  escala()
  fx()
  pads()
  sleep 0.5
  melodia()
  midi_all_notes_off
  sleep 4
end

The video is of a modification of the Code, it used two zynaddsubfx synthesizers, at different tunings, to create a melody with a chromatic scale of 24 notes per octave

2 Likes