Polyphony Machine

I will do a workshop with schoolkids in may, making music with Sonic Pi. The teacher suggested the material should have a connection to classical music so I decided to build a “Polyphony Machine” - a code that generates a fabric of different yet related voices familiar from classical compositions. This is not an elaborate emulation of a specific period style but a demonstration of different techniques used to derive diverse voices from limited melodic and rhythmic material.
Also, this is not the starting point, but rather the perspective of the workshop, I guess I will introduce all the included techniques isolated for clarity.

One inspiration for this approach was the fugue machine app: Fugue Machine | Alexandernaut

I will probably try to combine this with with @amiika´s great stuff on self-similar melodies:

Any ideas and comments are very welcome!

#Polyphony Machine with comments


Bpm = 80 #tempo of the music in beats per minute; change it!


#First we define the basic structures, which are used by the "live_loop"s to generate
#the music

Tonleiter = scale(:e4, :harmonic_minor, num_octaves: 2) #Scale - e harmonic minor;
#Try major (":major" or ":ionian"), or, for really different results:
#":major_pentatonic" or ":messiaen1"

Akkord = (ring 0, 2, 4) #Defines the basic chord structure, which may result
#in a major or minor chord, depending on the position in the scale

Rhythmus_arp = (ring 0.25) #Defines the rhythm of the arpeggiated chords,
#in this case continuous sixteenth notes

Melodie = (ring 0, 4, 2, 3, 0, 1) #Defines the six notes of the melody, "0" being the first
#in the scale, and so on
#Try changing them but make sure it´s six of them

Rhythmus_mel = (ring 0.5, 0.5, 0.25, 0.25, 0.25, 0.25) #Defines the rhythm of the melody.
#In traditional notation these could be two eight notes and four sixteenth notes



#The transposition moves the chord and the melody to different positions within the scale
live_loop :Transposition do
  4.times do
    Transposition = (ring 0, 5, 1, 4).tick #Try changing the transpositions!
    #If you use a different number than four, for example eight, make sure that
    #"8.times do" is in the line above, otherwise not all transpositions will be
    #activated
    
    sleep 3 #Defines the time to wait until the next transposition
  end
  tick_reset
end


live_loop :Spieler_Melodie do #In this "live_loop" the melody that was defined above
  #is actually played
  
  use_synth :piano #Defines the sound in use for this "live_loop"
  #if you want to change it, look in the help section for "synths"
  
  use_bpm Bpm #References to the tempo defined above
  
  6.times do #Six repeats to play each of the six melody notes
    
    play Tonleiter[1 * Melodie.tick + Transposition], amp: 1.3, decay: 0.8, sustain: 0.5, release: 0.3
    #"play" plays the "Melodie"-pattern; "amp" = amplification,
    #"decay", "sustain" & "release" define the sound envelope - try other values!
    #turning "amp:" to zero makes this "live_loop" silent
    
    
    sleep Rhythmus_mel.look * 4 #Here the rhythm is actually exercised;
    #By multiplying with four the eights and sixteenth notes become whole and half notes
    
  end
  tick_reset
end

live_loop :Spieler_Sequenz do #Plays the notes of the "Melodie" in a faster fashion
  use_synth :piano
  use_bpm Bpm
  
  12.times do # The number of repeats is doubled because the melody is played faster and
    #longer due to the use of ".reflect "  ".mirror"
    play Tonleiter[1 * Melodie.reflect.tick + Transposition] + 12, amp: 0.4, decay: 0.2, sustain: 0.2, release: 0.2
    #"+ 12" transposes the Sequence one octave higher
    
    sleep Rhythmus_mel.mirror.look * 1
    #By inserting ".reflect" the backwarts variant is added to the melody,
    #".mirror" does this to the rhythm in a slightly different way.
    #Delete these or interchange them for differing results!
    
  end
  tick_reset
end

live_loop :Spieler_Arpeggio do #This "live_loop" plays the chord changes
  use_synth :piano
  use_bpm Bpm
  8.times do #Eight repeats result in a specific arpeggio pattern, try other repeats!
    play Tonleiter[Akkord.tick + Transposition], amp: 0.5, decay: 0.3, sustain: 0, release: 0.3
    sleep Rhythmus_arp.look * 1
  end
  tick_reset
end

live_loop :Spieler_Bass do #Playing the bass, transposed an octave lower by "- 12"
  #durch "- 12"
  use_synth :piano
  use_bpm Bpm
  6.times do
    play Tonleiter[1 * Melodie.tick + Transposition] - 12, amp: 0.8, decay: 0.2, sustain: 0.2, release: 0.2
    sleep Rhythmus_mel.look * 2
    #By multiplying with two eight and sixteenth notes become half and quarter notes
  end
  tick_reset
end

What was the reasoning for the material having a connection to classical music, if you don’t mind me asking?

I don´t know, but I guess a certain unfamiliarity with electronic music and the sense that including aspects of classical music would make the teaching more valuable?
I don´t agree with that position, but I´m okay working with it as a starting point. Also, it´s fun to work on the code - I like creating a wide range of options from limited source material.

1 Like

Just found this, vaguely related, and more sophisticated:

Oops, I found a mistake. I forgot to add the correct bpm to the “:Transposition” live_loop.

Here is a corrected version:

#Polyphony Machine with comments


Bpm = 80 #tempo of the music in beats per minute; change it!


#First we define the basic structures, which are used by the "live_loop"s to generate
#the music

Tonleiter = scale(:e4, :harmonic_minor, num_octaves: 2) #Scale - e harmonic minor;
#Try major (":major" or ":ionian"), or, for really different results:
#":major_pentatonic" or ":messiaen1"

Akkord = (ring 0, 2, 4) #Defines the basic chord structure, which may result
#in a major or minor chord, depending on the position in the scale

Rhythmus_arp = (ring 0.25) #Defines the rhythm of the arpeggiated chords,
#in this case continuous sixteenth notes

Melodie = (ring 0, 4, 2, 3, 0, 1) #Defines the six notes of the melody, "0" being the first
#in the scale, and so on
#Try changing them but make sure it´s six of them

Rhythmus_mel = (ring 0.5, 0.5, 0.25, 0.25, 0.25, 0.25) #Defines the rhythm of the melody.
#In traditional notation these could be two eight notes and four sixteenth notes



#The transposition moves the chord and the melody to different positions within the scale
live_loop :Transposition do
  use_bpm Bpm
  4.times do
    Transposition = (ring 0, 5, 1, 4).tick #Try changing the transpositions!
    #If you use a different number than four, for example eight, make sure that
    #"8.times do" is in the line above, otherwise not all transpositions will be
    #activated
    
    sleep 4 #Defines the time to wait until the next transposition
  end
  tick_reset
end


live_loop :Spieler_Melodie do #In this "live_loop" the melody that was defined above
  #is actually played
  
  use_synth :piano #Defines the sound in use for this "live_loop"
  #if you want to change it, look in the help section for "synths"
  
  use_bpm Bpm #References to the tempo defined above
  
  6.times do #Six repeats to play each of the six melody notes
    
    play Tonleiter[1 * Melodie.tick + Transposition], amp: 1.3, decay: 0.8, sustain: 0.5, release: 0.3
    #"play" plays the "Melodie"-pattern; "amp" = amplification,
    #"decay", "sustain" & "release" define the sound envelope - try other values!
    #turning "amp:" to zero makes this "live_loop" silent
    
    
    sleep Rhythmus_mel.look * 4 #Here the rhythm is actually exercised;
    #By multiplying with four the eights and sixteenth notes become whole and half notes
    
  end
  tick_reset
end

live_loop :Spieler_Sequenz do #Plays the notes of the "Melodie" in a faster fashion
  use_synth :piano
  use_bpm Bpm
  
  12.times do # The number of repeats is doubled because the melody is played faster and
    #longer due to the use of ".reflect "  ".mirror"
    play Tonleiter[1 * Melodie.reflect.tick + Transposition] + 12, amp: 0.4, decay: 0.2, sustain: 0.2, release: 0.2
    #"+ 12" transposes the Sequence one octave higher
    
    sleep Rhythmus_mel.mirror.look * 1
    #By inserting ".reflect" the backwarts variant is added to the melody,
    #".mirror" does this to the rhythm in a slightly different way.
    #Delete these or interchange them for differing results!
    
  end
  tick_reset
end

live_loop :Spieler_Arpeggio do #This "live_loop" plays the chord changes
  use_synth :piano
  use_bpm Bpm
  8.times do #Eight repeats result in a specific arpeggio pattern, try other repeats!
    play Tonleiter[Akkord.tick + Transposition], amp: 0.5, decay: 0.3, sustain: 0, release: 0.3
    sleep Rhythmus_arp.look * 1
  end
  tick_reset
end

live_loop :Spieler_Bass do #Playing the bass, transposed an octave lower by "- 12"
  #durch "- 12"
  use_synth :piano
  use_bpm Bpm
  6.times do
    play Tonleiter[1 * Melodie.tick + Transposition] - 12, amp: 1, decay: 0.2, sustain: 0.2, release: 0.2
    sleep Rhythmus_mel.look * 2
    #By multiplying with two eight and sixteenth notes become half and quarter notes
  end
  tick_reset
end

Also, I continued playing around with these ideas and combined it with the markov chain processes @nabisco brought up here in the forum.

Sounds much more like actual music but also this is far beyond the scope of the workshop…

Have a look/listen:

 #Markov Polyphony Machine
#by Gisbert Schürig w thanks to Nabisco

use_random_seed 1 #change for different results!


Bpm = 30

#initial state
set :current_state, 0
set :current_state_harmonik, 0


Tonleiter = scale(:e4, :harmonic_minor, num_octaves: 2)
#try other scales, ":mayor" gives traditional results but I love ":messiaen1" or ":pelog"

Melodie = (ring 0, 4, 2, 5, 1) #melody pattern for the four voices
Rhythmus_mel = (ring 0.5, 0.75, 0.25, 0.25, 0.25) #rhythm pattern

#State machine rules for the four voices; each state corresponds to one note and one duration
#from the "Melodie" und "Rhythmus_mel" rings
RULES = {
  0 => [1, 2, 4], #prime intervall ("0" in "Melodie" ring) may lead to second, third or fifth intervall
  1 => [2],
  2 => [3],
  3 => [1, 4], #sixth may lead to third or fifth
  4 => [0, 1, 2] #second may lead to prime, fifth or second
}


Akkordfolge = (ring 0, 3, 6, 2, 5, 1, 4) #chord pattern

#State machine rules for the chord pattern/transpositions
#used to emulate "traditional" harmonic progressions
RULES_harmonik = {
  0 => [0, 1, 3, 4, 5, 6], #tonic may lead to any other chord
  1 => [0, 2, 6], #subdominant may lead to tonic or dominant functions
  2 => [3],
  3 => [4],
  4 => [5],
  5 => [6],
  6 => [0, 4] #dominant leads to tonic or the tonics parallel minor chord
}

live_loop :Akkorddauer do #determining the tempo of the harmonic pattern/transpositions
  use_bpm Bpm
  Dauer = (ring 1, 2, 4, 8).choose
  sleep 8
end


live_loop :Transposition do #applying the state machine rules to the "Transposition" variable
  use_bpm Bpm
  s1 = get[:current_state_harmonik]
  Transposition = Akkordfolge[s1]
  sleep Dauer
  set :current_state_harmonik, RULES_harmonik[s1].choose
end


live_loop :Komplementärrhythmus do #varying the rhythm by multiplying it
  T1 = (ring 0.5, 1, 2, 4).shuffle
  T2 = T1.drop(0).shuffle #dropping the element chosen by T1
  T3 = T2.drop(0).shuffle #dropping the element chosen by T2, resulting in a complementary rhythm
  sleep (ring 0.5, 1, 2, 4, 8).choose
end

with_fx :reverb do
  
  live_loop :Spieler_Melodie do
    use_synth :sine
    use_bpm Bpm
    s = get[:current_state]
    play Tonleiter[1 * Melodie[s] + Transposition], amp: rrand(0.4, 0.9), decay: 0.3, sustain: 0, release: 0.3, pan: 0.1
    sleep Rhythmus_mel[s] * T1[0] #applying the rhythm state machine rules and multiplication for
    #complementary rhythm
    set :current_state, RULES[s].choose
  end
  
  live_loop :Melodische_Transposition do
    Mel_Transp = (ring 0, 2, 4, 7).choose
    Mel_Transp1 = (ring 0, 2, 4, 7).choose
    sleep rrand_i(1, 8)
  end
  
  
  live_loop :Spieler_Sequenz do
    use_synth :sine
    use_bpm Bpm
    s = get[:current_state]
    play Tonleiter[1 * Melodie.reflect[s] + Transposition + Mel_Transp], amp: rrand(0.15, 0.6), decay: 0.2, sustain: 0.2, release: 0.2, pan: 0.35
    sleep Rhythmus_mel.mirror[s] * T2[0]
    set :current_state, RULES[s].choose
  end
  
  live_loop :Spieler_Sequenz1 do
    use_synth :sine
    use_bpm Bpm
    s = get[:current_state]
    play Tonleiter[1 * Melodie[s] + Transposition + Mel_Transp1] - 12, amp: rrand(0.15, 0.6), decay: 0.2, sustain: 0.2, release: 0.2, pan: -0.35
    sleep Rhythmus_mel[s] * T3[0]
    set :current_state, RULES[s].choose
  end
  
  
  live_loop :Spieler_Bass do
    use_synth :sine
    use_bpm Bpm
    s = get[:current_state]
    play Tonleiter[1 * Melodie[s] + Transposition] - 24, amp: rrand(0.15, 0.4), decay: 0.2, sustain: 0.2, release: 0.2
    sleep Rhythmus_mel[s] * 2
    set :current_state, RULES[s].choose
  end
  
end#fx reverb

This has a nice meandering feel to it, would probably be a bit more sharp if I would combine the markov chain process with a new beginning in regular time intervalls at the start of a bar.
@robin.newman any ideas how to realize that?
Cheers!

This sounds very nice. I’ll try and have a dig around, but for various reasons I don’t think I can manage this soon.

1 Like

Thanks for the kind words @robin.newman!

Here is another more straightforward version:

 #Polyphony Machine one ring melody/harmony

use_random_seed 2 #try changing this!

Bpm = 80

Tonleiter = scale(:e4, :harmonic_minor, num_octaves: 2).drop_last

Akkord = (ring 0, 2, 4, 7, 9, 11)

Melodie = (ring 0, 4, 1, 2, 3, 2, 5, 0) #try changing these; melody and harmony will change

x = Melodie.count

Rhythmus_mel = (ring 0.5, 0.5, 0.25, 0.25, 0.5, 0.25, 0.25, 0.5)


live_loop :Transposition do
  use_bpm Bpm
  x.times do
    Transposition = Melodie.tick
    
    sleep 16*Rhythmus_mel.look
  end
  tick_reset
end

live_loop :Komplementärrhythmus do
  use_bpm Bpm
  set :T1, (ring 1, 2, 4).shuffle
  set :T2, get[:T1].drop(0).shuffle
  set :T3, get[:T2].drop(0).shuffle
  sleep (ring 0.5, 1, 2, 4, 8).choose
end

live_loop :Spieler_Melodie do
  use_synth :piano
  use_bpm Bpm
  x.times do
    play Tonleiter[1 * Melodie.tick + Transposition], amp: 01, decay: 0.5, sustain: 0, release: 0.4
    sleep Rhythmus_mel.look * get[:T1][0]
  end
  tick_reset
end

live_loop :Spieler_Melodie1 do
  use_synth :piano
  use_bpm Bpm
  x.times do
    play Tonleiter[1 * Melodie.tick + Transposition] - 24, amp: 0.7, decay: 0.3, sustain: 0, release: 0.3
    sleep Rhythmus_mel.reverse.look * get[:T2][0]
  end
  tick_reset
end

live_loop :Spieler_Melodie2 do
  use_synth :piano
  use_bpm Bpm
  2*x.times do
    play Tonleiter[1 * Akkord[Melodie.mirror.tick] + Transposition] - 24, amp: 0.4, decay: 0.3, sustain: 0, release: 0.3
    sleep Rhythmus_mel.reflect.look * get[:T3][0]
  end
  tick_reset
end

live_loop :Spieler_Melodie3 do
  use_synth :piano
  use_bpm Bpm
  x.times do
    play Tonleiter[Akkord[Melodie.tick] + Transposition] - 12, amp: 0.4, decay: 0.3, sustain: 0, release: 0.3
    sleep 0.25
  end
  tick_reset
end

I noticed a mistake in the Markov Polyphony Machine, the “Komplementärrhythmus” live_loop did not work correct, I fixed that by using set and get.
By the way, @Robin Newman, this does the same thing I looked for in this thread:

I never answered your suggestion, sorry, how rude of me!

Here is the fixed Markov Polyphony Machine:

#Markov Polyphony Machine 1
#by Gisbert Schürig w thanks to Nabisco
#Komplementärrhythmus fixed

use_random_seed 1 #change for different results!


Bpm = 30

#initial state
set :current_state, 0
set :current_state_harmonik, 0


Tonleiter = scale(:e4, :harmonic_minor, num_octaves: 2)
#try other scales, ":mayor" gives traditional results but I love ":messiaen1" or ":pelog"

Melodie = (ring 0, 4, 2, 5, 1) #melody pattern for the four voices
Rhythmus_mel = (ring 0.5, 0.75, 0.25, 0.25, 0.25) #rhythm pattern

#State machine rules for the four voices; each state corresponds to one note and one duration
#from the "Melodie" und "Rhythmus_mel" rings
RULES = {
  0 => [1, 2, 4], #prime intervall ("0" in "Melodie" ring) may lead to second, third or fifth intervall
  1 => [2],
  2 => [3],
  3 => [1, 4], #sixth may lead to third or fifth
  4 => [0, 1, 2] #second may lead to prime, fifth or second
}


Akkordfolge = (ring 0, 3, 6, 2, 5, 1, 4) #chord pattern

#State machine rules for the chord pattern/transpositions
#used to emulate "traditional" harmonic progressions
RULES_harmonik = {
  0 => [0, 1, 3, 4, 5, 6], #tonic may lead to any other chord
  1 => [0, 2, 6], #subdominant may lead to tonic or dominant functions
  2 => [3],
  3 => [4],
  4 => [5, 1], #tonic parallel minor may lead to dominant or subdominant
  5 => [6],
  6 => [0, 4] #dominant leads to tonic or the tonics parallel minor chord
}

live_loop :Akkorddauer do #determining the tempo of the harmonic pattern/transpositions
  use_bpm Bpm
  Dauer = (ring 1, 2, 4, 8).choose
  sleep 8
end


live_loop :Transposition do #applying the state machine rules to the "Transposition" variable
  use_bpm Bpm
  s1 = get[:current_state_harmonik]
  Transposition = Akkordfolge[s1]
  sleep Dauer
  set :current_state_harmonik, RULES_harmonik[s1].choose
end


live_loop :Komplementärrhythmus do #varying the rhythm by multiplying it
  use_bpm Bpm
  set :T1, (ring 0.5, 1, 2, 4).shuffle
  set :T2, T1.drop(0).shuffle #dropping the element chosen by T1
  set :T3, T2.drop(0).shuffle #dropping the element chosen by T2, resulting in a complementary rhythm
  sleep (ring 0.5, 1, 2, 4, 8).choose
end

with_fx :reverb do
  
  live_loop :Spieler_Melodie do
    use_synth :sine
    use_bpm Bpm
    s = get[:current_state]
    play Tonleiter[1 * Melodie[s] + Transposition], amp: rrand(0.4, 0.9), decay: 0.3, sustain: 0, release: 0.3, pan: 0.1
    sleep Rhythmus_mel[s] * get[:T1][0] #applying the rhythm state machine rules and multiplication for
    #complementary rhythm
    set :current_state, RULES[s].choose
  end
  
  live_loop :Melodische_Transposition do
    Mel_Transp = (ring 0, 2, 4, 7).choose
    Mel_Transp1 = (ring 0, 2, 4, 7).choose
    sleep rrand_i(1, 8)
  end
  
  
  live_loop :Spieler_Sequenz do
    use_synth :sine
    use_bpm Bpm
    s = get[:current_state]
    play Tonleiter[1 * Melodie.reflect[s] + Transposition + Mel_Transp], amp: rrand(0.15, 0.6), decay: 0.2, sustain: 0.2, release: 0.2, pan: 0.35
    sleep Rhythmus_mel.mirror[s] * get[:T2][0]
    set :current_state, RULES[s].choose
  end
  
  live_loop :Spieler_Sequenz1 do
    use_synth :sine
    use_bpm Bpm
    s = get[:current_state]
    play Tonleiter[1 * Melodie[s] + Transposition + Mel_Transp1] - 12, amp: rrand(0.15, 0.6), decay: 0.2, sustain: 0.2, release: 0.2, pan: -0.35
    sleep Rhythmus_mel[s] * get[:T3][0]
    set :current_state, RULES[s].choose
  end
  
  
  live_loop :Spieler_Bass do
    use_synth :sine
    use_bpm Bpm
    s = get[:current_state]
    play Tonleiter[1 * Melodie[s] + Transposition] - 24, amp: rrand(0.15, 0.4), decay: 0.2, sustain: 0.2, release: 0.2
    sleep Rhythmus_mel[s] * 2
    set :current_state, RULES[s].choose
  end
  
end#fx reverb