Chord progression tool

I don’t fully understand this, but I don’t think it works like that.

As far as I understand these are scales that match up to the major chords when played with a different tonic.

From the table “Modes of the C major scale”, the V scale is listed as “G7”. I used Sonic Pi to compare the G7 chord, the V chord from C major and C mixolydian, as well as the G mixolydian chord.

 "G 7              [67, 71, 74, 78]"
 "V, c Major       [67, 71, 74, 77]"
 "V, c mixolydian  [67, 70, 74, 77]"
 "G Mixolydian     [67, 69, 71, 72, 74, 76, 77, 79]"

If your musical idea is to stay within a key then for example within key C you would use these chords:
C major7, D minor7, E minor7, F major7, G dom7, A minor7, B dim

And to these chords, within key C, correspond these scales:
C ionian, D dorian, E phrygian, F lydian, G mixolydian, A aeolian, B locrian

Wheneven you use these chords and scales, you will use notes from the c major scale. If you decide to use another scale, let’s say D moxolydian, then you have effectively changed the key to G. This is used in music to do modulations from one key to another: the melody slightly shifts from one scale to another and the chords follow, until you end up with a different key.

Example for a modulation from key F major to key C major:
F major7 - G minor7 - C dom7 - F major7 - D minor7 - C major7 - G dom7 - C major7
The first 3 chords are fixing the key F, chords 4 & 5 are both in key F and C, and chords 6 to 8 are finally fixing key C.

My propsal therefore can be summarized as an attempt to do less key changes but have more notes within a given key.

EDIT: to better hear the differences between the scales, at least 4 notes of each chord should be played. You could do even 5 and it still will sound good.

And here is the example in code. Please have a look at the scales. The first 4 scales belong to key F major, while the last 4 scales belong to key C major. If you want the last 4 scales to belong to key F, you should use:
D aeolian (instead of D dorian)
C mixolydian (instead of C ionian)
G dorian (instead of G mixolydian)
It is these choices that modulate the key from F to C.

use_bpm 70

chords = [chord(:F2, :major7),
          chord(:G2, :minor7),
          chord(:C3, :dom7),
          chord(:F2, :major7),
          chord(:D3, :minor7),
          chord(:C3, :major7),
          chord(:G2, :dom7, inverse: 1),
          chord(:C3, :major7),
          ]


with_synth :piano do
  live_loop :piano do
    play chords.tick, hard: 0.6, sustain: 2, release: 0.5, amp: 1.5
    sleep 2
  end
end


scales = [scale(:F2, :ionian),
          scale(:G2, :dorian),
          scale(:C3, :mixolydian),
          scale(:F2, :ionian),
          scale(:D3, :dorian),
          scale(:C3, :ionian),
          scale(:G2, :mixolydian),
          scale(:C3, :ionian),
          ]

with_synth :pulse do
  live_loop :melody do
    sca = scales.tick
    num_notes = 6
    sca[0, num_notes].each do |n|
      play n, release: 2.0/num_notes, amp: 0.5
      sleep 2.0/num_notes
    end
  end
end

CORRECTION: the scales do not work inside the chord_degree as I expected. Therefore, a separate scale objects is created and used for the arpeggios.

# chord progressions doodles by Robn Newman Jan 2021
# developed from initial code by theibbster
# further developped scales by nechoj

use_debug false
use_bpm 60

scales = {
  :i => :ionian,
  :ii => :dorian,
  :iii => :phrygian,
  :iv => :lydian,
  :v => :mixolydian,
  :vi => :aeolian,
  :vii => :locrian
}

p1 = [:i, :v, :vi, :iv]
p2 = [:i, :iv, :v, :iv]
p3 = [:ii, :v, :i]
p4 = [:i, :vii, :i, :i, :iv, :iv, :i, :i, :v, :iv, :i, :i]
p5 = [:i, :vi, :iv, :v]
p6 = [:i, :v, :vi, :iii, :iv, :i, :iv, :v]
p7 = [:i, :iv, :vi, :vii, :v]
pr = (ring p1, p2, p3, p4, p5, p6, p7).shuffle

nn = 6
st = 0.1

with_fx :reverb, room: 0.8, mix: 0.6 do # add reverb
  with_fx :level, amp: 1 do |vol| # vary vol as piece progresses
    set :vol,vol # stopre ref to fx_level for control
    
    live_loop :foo, auto_cue: false do
      
      base = [:C3, :F4, :C4, :G3].choose
      pl = pr.tick
      pl.each do |deg|
        sc = scales[deg]
        
        n = (chord_degree deg, base, :major, nn)
        
        with_synth :dsaw do
          play n[0] - 12, amp: 0.6, sustain: nn*st, release: 0.1, pan: [-0.7, 0.7].tick(:pan)
        end
        
        sca = scale n[0] + 12, scales[deg], num_octaves: 2
        with_synth :pulse do
          sca[rrand_i(0, 3), nn].each do |m|
            play m, sustain: 0.1, release: 0.05, amp: 0.5, pan: rrand(-1, 1)
            sleep st
          end
        end
      end
    end
  end
end

live_loop :vet do # control level output on 10 sec up/down ramp
  vlev=[0.4,1].tick
  control get(:vol),amp: vlev,amp_slide: 10
  sleep 10
end
1 Like

Stumbled to this jazz theory video which shows how different 7th chords are related to different modes. This way you could randomize different scales for all chords in the progression. Would be dope to do some kind of mapping from different chord types to related scales in Sonic Pi. Only problem is that there are 33 scales and some are not even in Sonic Pi. Scales also listed in here as PDF.

Hi @amiika, that’s exactly what I am currently after. A first experiment on that matter is contained in this post Improvisations over chord progressions. The basis for automatic harmony selection is contained in a recent post here: Harmonic random walk in major.

Next step is to extend this work to a wider range of jazz harmonies and scales. Missing scales in SPI will not pose a too big problem, as scales can easily be constructed from midi notes and the ring mechanism. I cannot foresee when the next version of the random walk including imporvisations using jazz scales will be ready to show, but the complexity of the matter is quite high. A couple of weeks, I guess …

Cool. I made some experiments expanding Sonic Pi’s scales and chords using monkey patching.

Here’s the idea. Create a new file extending the scales and chords via monkey patching:

#scale_patch.rb
class SonicPi::Scale
  def self.patch(scales)
    scales.each { |name, intervals|
      self::SCALE[name] = intervals unless self::SCALE.key? name
    }
  end
end

class SonicPi::Chord
  def self.patch(chords)
    chords.each { |name, intervals|
      unless self::CHORD.key? name.to_sym
        self::CHORD[name.to_sym] = intervals
        self::CHORD_LOOKUP[name.to_sym] = intervals
        self::CHORD_NAMES.append(name)
      end
    }
  end
end

#Missing scales based on: https://www.newjazz.dk/Compendiums/scales_of_harmonies.pdf
scales = lambda {
  ionian1s = [1,2,1,2,2,2,2]
  ionian5s = [2,2,1,3,1,2,1]
  ionian6b = [2,2,1,2,1,3,1]
  {
    # Family 2
    :ionian1s=>ionian1s,
    :dorian7s=>ionian1s.rotate(1),
    :phrygian6s=>ionian1s.rotate(2),
    :lydian5s=>ionian1s.rotate(3),
    :mixolydian4s=>ionian1s.rotate(4),
    :aeolian3s=>ionian1s.rotate(5),
    :locrian2s=>ionian1s.rotate(6),
    # Family 3
    :ionian5s=>ionian5s,
    :dorian4s=>ionian5s.rotate(1),
    :phrygian3s=>ionian5s.rotate(2),
    :lydian2s=>ionian5s.rotate(3),
    :mixolydian1s=>ionian5s.rotate(4),
    :aeolian7s=>ionian5s.rotate(5),
    :locrian6s=>ionian5s.rotate(6),
    # Family 4
    :ionian6b=>ionian6b,
    :dorian5b=>ionian6b.rotate(1),
    :phrygian4b=>ionian6b.rotate(2),
    :lydian3b=>ionian6b.rotate(3),
    :mixolydian2b=>ionian6b.rotate(4),
    :aeolian1b=>ionian6b.rotate(5),
    :locrian7b=>ionian6b.rotate(6),
  }
}.call

chords = {
  # https://en.wikipedia.org/wiki/Minor_major_seventh_chord
  'mM7'=> [0, 3, 7, 11],
  # https://en.wikipedia.org/wiki/Augmented_major_seventh_chord
  'maj7+5'=> [0, 4, 8, 11],
  '6+5'=> [0, 4, 8, 9],
  # Missing altered chords: https://en.wikipedia.org/wiki/Altered_chord
  '7-5-3'=>[0, 3, 6, 10],
  '7+5+9'=>[0, 4, 8, 10, 14],
  '7-5-9'=>[0, 4, 6, 10, 13],
  '7-5+9'=>[0, 4, 6, 10, 14]

}

SonicPi::Scale.patch(scales)
SonicPi::Chord.patch(chords)

EDIT: Fixed a bug, thanks to @Nechoj and added some missing altered 7th chords.

Then load it to the Sonic Pi and create mapping between chord types and those new scales:

load "~/patch.rb" # Load extra scales and chords from separate file

use_random_seed 31
use_bpm 90

harmonic_scales = {
  :M7=>[:ionian, :lydian, :lydian2s, :ionian6b, :augmented],
  :minor7=>[:dorian, :phrygian, :aeolian, :phrygian6s, :dorian4s, :phrygian4b],
  '7'=>[:mixolydian, :mixolydian4s, :aeolian3s, :phrygian3s, :mixolydian2b, :diminished],
  'm7-5'=>[:locrian, :locrian2s, :ionian1s, :locrian6s, :dorian5b],
  'mM7'=>[:dorian7s, :aeolian7s, :lydian3b],
  :dim7=>[:mixolydian1s, :locrian7b, :aeolian1b, :diminished],
  'maj7+5'=>[:lydian5s, :ionian5s, :aeolian1b],
  '7-5'=>[:whole_tone],
  '7+5'=>[:whole_tone],
  '6+5'=>[:augmented2],
  # Other altered chords
  '7-5-3'=>[:ionian1],
  'm7+5'=>[:ionian1],
  '9'=>[:ionian1]
}

prog = [{tonic: :D, type: 'm7-5', invert: -1}, {tonic: :G, type: '7', invert: -1},{tonic: :C, type: 'mM7', invert: 1}]

live_loop :chords do
  with_fx :flanger, feedback: 0.5 do
    prog.each do |c|
      synth :hollow, note: chord(c[:tonic], c[:type], invert: c[:invert] ? c[:invert] : 0), sustain: 3, amp: 2
      sleep 4
    end
  end
end

live_loop :impro do
  with_synth :tri do
    with_synth_defaults sustain: 0.20, env_curve: 2, cutoff: 40, decay: 0.05, decay_level: 0.5, sustain_level: 0.5 do
      prog.each do |c|
        harmonic_scale = harmonic_scales[c[:type]].choose
        notes = scale(c[:tonic], harmonic_scale).shuffle
        4.times do
          s = [0.25,0.5,1.0].choose
          (1/s).times do
            if s>0.25 and rand>0.85
              2.times do
                play notes.tick
                sleep s/2
              end
            else
              play notes.tick
              sleep s
            end
          end
        end
      end
    end
  end
end

live_loop :bass do
  with_fx :reverb, damp: 0.9, room: 0.8  do
    with_synth :fm do
      use_synth_defaults depth: -1, divisor: 1, release: [0.25,0.5].choose, amp: 0.5
      use_octave -2
      prog.each_with_index do |c,i|
        harmonic_scale = harmonic_scales[c[:type]].choose
        notes = scale(c[:tonic], harmonic_scale).shuffle
        play chord(c[:tonic], c[:type], invert: c[:invert] ? c[:invert] : 0)[0]
        sleep 0.5
        play chord(c[:tonic], c[:type], invert: c[:invert] ? c[:invert] : 0)[2]
        sleep 0.5
        3.times do
          if rand>0.7
            2.times do
              play notes.tick
              sleep 0.25
            end
          else
            play notes.tick
            sleep 0.5
          end
        end
        peek = prog.ring[i+1]
        play chord(peek[:tonic], peek[:type], invert: peek[:invert] ? peek[:invert] : 0)[0] - [-1,1,].choose
        sleep 0.5
      end
    end
  end
end

live_loop :swing do
  with_fx :reverb, amp: 2 do
    sample :drum_cymbal_closed, amp: 0.2, beat_stretch: [0.1,0.2,0.3].choose
    sample :drum_cymbal_soft, amp: 0.1
    sleep 1
    sample :drum_cymbal_soft, amp: 0.1
    sleep 0.8
    sample :drum_cymbal_soft, amp: 0.1
    sleep 0.2
  end
end

EDIT EDIT: Added variations to walking bass

Sounds quite nice, even for random serialist approach. Creating arpeggios and filling in the gaps should create even nicer outcome.

3 Likes

I will ask some feedbacks to my little cousin 8 years old and I will tell you his point of view.

It seems that some sonic pi users are great children for ever :blush:

By the way a little joke :
What is the difference between a jazz band and a rock band ?
A jazz band it’s 2000 chords by song and 3 people into the public. You guess for the rock band ?

Cheers and keep sharing friens your amazing scripts !

Cool! Thanks for sharing. Good idea to do the monkey patches, it always helps if nothing else helps :sunglasses: :monkey_face:
Nice voices in the piece, too!

Well, somehow I have heard this joke before … Fact is, however, that every serious composer in our days should have a solid background in jazz harmonies and scales. One of my favorite YouTube’er explaining the harmonic structure of contemporary pieces is Adam Neely. Watch his video on Lady Gaga’s presentation of the US anthem at Joe Biden’s inauguration. I guess, there have been more than 3 people listening to it and this piece is loaded with jazz hamonies and was played by the military corps band. (Btw, Lady Gaga has a solid jazz background as well, hear e.g. this piece with Tony Bennett who - I am said to mention - died on 1st of February this year)

There was a bug in this piece which made it more random than serial. Made few tweaks. Subtle differences.

To be honest I don’t care if a song is listened by lot of people or not. It was a joke.
Not sure every punk or rock band had some jazz background. Jésus and Mary chain : 2 chords, (power chord otherwise it will have sound too rich ) but a fingerprint, a sound that you know it’s them.

Some jazz tunes seem to me just a technical show with 2000 chords per song. And happy few people can really appreciate but they are right to play or listen what they like.

Every kind of music is worth to exist to represent the variety of human beings.

I get back to Schubert right now :relaxed:

This is impressive. Great work!

Hi @amiika, just discovered that your patch file appends the scales on every run of the piece. Therefore I added some if statements to avoid populating the scales and chords again and again

(EDIT: Code deleted as it was merged with original post above)

Good catch. Btw, would you happen to know how to construct a 6#5 chord? I interpreted it to be raised 5th on a 6th chord … something like [0, 4, 8, 9] … but thats probably wrong.

No that’s ok. Because in the inverted augmented scale you don’t have the maj7 (=11) note and you take the 6 instead in order to get the chord with 4 notes. But then this collides with the #5. I tried to play some versions on the guitar and it sounds best if you take the 6 into the bass, like C6#5 = a-e-#g-c. In other words, use an inverted version. As numbers this looks like [-3, 4, 8, 12]. But you can keep the [0, 4, 8, 9] version if you want maximum dissonance.
EDIT: the [-3, 4, 8, 12]. is basically the same as a minormajor7 chord. So C6#5 inverted = Am major7
EDITEDIT: should read -3 instead of -2

Yes. Thanks. It sounded too dissonant for my taste so I just thought it had to be wrong :sweat_smile:

Hi @amiika a comma is missing in line 60, '6+5'=> [0, 4, 8, 9],

1 Like

Thanks! Added some of that to the craziness to the walking bass also.

1 Like

Just stumbled upon this thread. This is such a great idea musically, and at the same time an elegant implementation programmatically (of course depending on the level of continuous compatibility of Sonic Pi with Ruby :grinning:). Thank you for sharing!

1 Like