Circle of fifths generator

Disclaimer: I’m not a music theory expert at all and just learn it on the fly for fun.


Hi,

I created a circle of fifths generator.

Why?

Well firstly to my knowledge no one else has done it in sonic pi and there is no easy way to get it for any note and scale. Secondly when jamming with sonic pi I tend to pick a scale then use chord degree to come up with a progression. From those chords I either arpeggio off them for a easy melody or sometimes grab the notes for a bass line so things kind of line up ok. Easy to keep everything in sync. One thing I often do for bass is just take whatever scale my chords are in and more or less make a ring to grab some notes for a bass line that follows my drums over the chords / melody. However it can be tricky to just on the cuff pick some notes from a scale that sound ok together.

I found out about something called the circle of fifths. It’s used for making cords or choosing notes that sound good together.

Check out this cool online tool

https://apps.musedlab.org/aqwertyon/theory/C-4-major-pentatonic

My intent with my code here is to be able to make a ring that I can more or less pick notes from that sound ok together but is in scale with whatever my chords are.

I’m sure there are better ways to do this (do let me know).

And I don’t know enough of music theory to just know what notes will sound goodish together, yet. :sunglasses:


TL;DR: Code below.

I wrote this example just to demonstrate the functions but I think you can figure out how to incorporate it.

P.S. Any ideas on how to generate a chromatic scale? Also general comments suggestions would be cool. Thanks.

https://github.com/boardkeystown/sonicPicache/blob/master/c5thsv.rb
##| @author boardkeystown
##| Circle of fifths from note and scale ring generator v1.0
##| ----------------------------------------------------------------------------------

##| Note on use:
##| Since midi is 0 to 127 ish... These functions work best when using notes at the
##| start of the midi scale e.g. :c0, :db0, :d0 .... then move it down a octave
##| so that it is at octave -1. For example :c0-12
##| For a glance at the midi notes see
##| https://sonic-pi.mehackit.org/exercises/en/01-introduction/02-play-a-melody.html


##| Functions


##| #this will print out the sonic pi notes, midi, and index of a scale ring
##| #where x is the scale
define :printNotes do |x|
  k = 0
  for i in x
    puts  note_info(i).to_s +  " midi: "+ i.to_s + " index #{k}"; k=k+1
  end
  puts "Len: " + x.length.to_s
end

##| returns a ring of fifths based on a
##| starting note and a scale.
##| IDK if this works 100% right with every scale
define :makeFifths do |startNote, use_scale_|
  count_ = 0
  out = []
  ##| build a scale and store the first note of it
  tempScale = (scale startNote, use_scale_)
  nextNote = tempScale[0]
  out.push(nextNote)
  ##| We need 11 more notes for a circle of 5ths
  ##| to build it we just need to grab to dominate note and
  ##| and keep building up the scale (see)
  ##| https://www.musical-u.com/learn/how-to-use-circle-fifths/
  ##| https://www.musictheory.net/lessons/23
  while count_ < 11
    dom = tempScale.length/2
    dom = tempScale.length - dom
    nextNote = tempScale[dom]
    out.push(nextNote)
    tempScale = (scale nextNote, use_scale_)
    count_ = count_ + 1
  end
  out = out.ring
  return out
end

## | This is a mess but all it does to 'normalize' the circle of 5ths is
## | set the notes to the same octave
define :normalize do |notes_in, target_oct|
  #table of midi notes
  note_table = [[0,    1,    2,  3,    4,   5,   6,   7,   8,   9,  10,  11],
                [12,  13,   14, 15,   16,  17,  18,  19,  20,  21,  22,  23],
                [24,  25,   26, 27,   28,  29,  30,  31,  32,  33,  34,  35],
                [36,  37,   38, 39,   40,  41,  42,  43,  44,  45,  46,  47],
                [48,  49,   50, 51,   52,  53,  54,  55,  56,  57,  58,  59],
                [60,  61,   62, 63,   64,  65,  66,  67,  68,  69,  70,  71],
                [72,  73,   74, 75,   76,  77,  78,  79,  80,  81,  82,  83],
                [84,  85,   86, 87,   88,  89,  90,  91,  92,  93,  94,  95],
                [96,  97,   98, 99,  100, 101, 102, 103, 104, 105, 106, 107],
                [108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119],
                [120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131]]
  notes_out = []
  notes_in.each do |note_|
    done = false
    #search the table for the a note at a index
    for i in note_table
      for j in i
        if j == note_ then
          #adjust the note to match the octave
          index = note_table.index(i)
          while target_oct < index
            index = index - 1
            note_ = note_ - 12
          end
          while target_oct > index
            index = index + 1
            note_ = note_ + 12
          end
          notes_out.push(note_)
          done = true
          break
        end
      end
      if done == true then break end
    end
  end
  notes_out = notes_out.ring
  return notes_out
end

## | combines the later functions for easier use
define :makeC5ths do |note_,use_scale_,oct_|
  out_ = makeFifths note_, use_scale_
  out_ = normalize out_,oct_
  return out_
end

##| Example Use

##| Pick a scale and a note
use_scale = :major_pentatonic
##| use_scale = :blues_major
##| use_scale = :mixolydian
##| use_scale = :major
myNote = :g0-12

x = (scale myNote, use_scale)

##| Just to show what a scale looks like to compare to c5ths
puts note_info(x[0]).to_s + " scale!"; puts
printNotes x

##| Use this website to see if the c5ths is correct
##| https://apps.musedlab.org/aqwertyon/theory/G-0-major-pentatonic

puts
puts "C5ths b4 making 'normal'"; puts
out = makeFifths myNote, use_scale
printNotes out

puts
puts "C5ths after making 'normal'";puts
out = normalize out,2
printNotes out

##| Just a example of how you could use set
##| To run these heavy functions once to get a
##| Circle of 5ths (I hope you get the idea)

##| out = makeC5ths myNote, use_scale, 2
##| set :c5ths, out
##| printNotes get(:c5ths)



#play all 12 fifths
live_loop :foo do
  ##| stop
  ##| use_octave 1
  use_synth :piano
  play out.tick, amp: 0.3
  sleep 1
end

#easy to make a quick grove
#Don't even care what notes these are it's just cool
#they all sound ok together
live_loop :bar do
  stop
  use_octave 1
  use_synth :piano
  w = (knit 0.25,4, 0.5,1, 0.25,1)
  n = (ring 0,3,2,2,1,5,11)
  tick
  play out[n.look], amp: 0.3
  sleep w.look
end
5 Likes

Robin beat you to it my friend. :slight_smile:

But I bet by mixing idea’s from both posts, you can make
it better.

Eli…

Cool! But would not say beat me to it. Since my thing generates circle of fifths and is not hard coded. With a little quick teaks I’ve mashed to two things together lazily. This is now some crazy noise generator hahaha

try different scales and change that octave value 1 through 10. Lower ones sound like some alien noises. It’s super fun. Again this was a quick mash of the two not much thought went into it.

define :makeFifths do |startNote, use_scale_|
  count_ = 0
  out = []
  tempScale = (scale startNote, use_scale_)
  nextNote = tempScale[0]
  out.push(nextNote)
  while count_ < 11
    dom = tempScale.length/2
    dom = tempScale.length - dom
    nextNote = tempScale[dom]
    out.push(nextNote)
    tempScale = (scale nextNote, use_scale_)
    count_ = count_ + 1
  end
  out = out.ring
  return out
end

## | This is a mess but all it does to 'normalize' the circle of 5ths is
## | set the notes to the same octave
define :normalize do |notes_in, target_oct|
  note_table = [[0,    1,    2,  3,    4,   5,   6,   7,   8,   9,  10,  11],
                [12,  13,   14, 15,   16,  17,  18,  19,  20,  21,  22,  23],
                [24,  25,   26, 27,   28,  29,  30,  31,  32,  33,  34,  35],
                [36,  37,   38, 39,   40,  41,  42,  43,  44,  45,  46,  47],
                [48,  49,   50, 51,   52,  53,  54,  55,  56,  57,  58,  59],
                [60,  61,   62, 63,   64,  65,  66,  67,  68,  69,  70,  71],
                [72,  73,   74, 75,   76,  77,  78,  79,  80,  81,  82,  83],
                [84,  85,   86, 87,   88,  89,  90,  91,  92,  93,  94,  95],
                [96,  97,   98, 99,  100, 101, 102, 103, 104, 105, 106, 107],
                [108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119],
                [120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131]]
  notes_out = []
  notes_in.each do |note_|
    done = false
    for i in note_table
      for j in i
        if j == note_ then
          index = note_table.index(i)
          while target_oct < index
            index = index - 1
            note_ = note_ - 12
          end
          while target_oct > index
            index = index + 1
            note_ = note_ + 12
          end
          notes_out.push(note_)
          done = true
          break
        end
      end
      if done == true then break end
    end
  end
  notes_out = notes_out.ring
  return notes_out
end


define :makeC5ths do |note_,use_scale_,oct_|
  out_ = makeFifths note_, use_scale_
  out_ = normalize out_,oct_
  return out_
end


##| use_scale = :major_pentatonic
use_scale = :diminished2
##| use_scale = :blues_major
out = makeC5ths :g0-12,use_scale, 2 #change this 1-10 for some crazy sounds
sl_ = 1  #change for sleep
set(:s, out)



##| define  :arp do |n,m=0,r=false| #n is base note,m is minor offset,r is reverse arpeggio (when true)
##|   c1=[0,4-m,7,12,7,4-m]*2 #part 1 note offsets
##|   c2=[7,12,16-m,19,16-m,19,24,19,24,28-m,24,19]*2 #part 2 note offsets
##|   if r==true #can reverse play direction using r
##|     c1=c1.reverse
##|     c2=c2.reverse
##|   end
##|   12.times do |i|
##|     play note(n)+c1[i],release: 1.0/3 ,cutoff: rrand(70,120)#3 notes per beat
##|     play note(n)+c2[i],release: 1.0/3,cutoff: rrand(70,120)
##|     sleep 1.0/3
##|   end
##| end

with_fx :reverb,room: 0.8,mix: 0.6 do
  live_loop :pl do
    
    use_bpm 120
    use_transpose (ring 0,-5,7,12,7,-5)[look(:tr) / 4]   #change every 4th pass of the loop
    use_synth :tb303
    tick(:b) #ticks next base note for arpeggio
    bmaj=get(:s).look(:b)
    in_thread do
      play note(bmaj)-12 ,sustain:  3.5,release: 0.5,cutoff: rrand(60,100),amp: 0.7
    end
    dir=rand_i
    ##| arp(bmaj,0,dir) #play major arpeggios
    #tick(:b) if dice(4)==1 #sometimes advance another note in sequence (optional)
    bmin=get(:s).look(:b)
    sleep sl_
    in_thread do
      play note(bmin)-12 ,sustain: 3.5,release: 0.5,cutoff: rrand(60,100),amp: 0.7
    end
    ##| arp(bmin,dice(2),dir) #play minor arpeggio, with random factor for "3rd"
    ##| tick(:b) if dice(2)==1 #sometimes advance anoterh note in sequence (optional)
    if rt(vt) >=192 #start coda
      puts "start coda.."
      use_transpose 0
      7.times do |j|
        base=get(:s)
        min=[0,1,1,1,0,0,0]
        in_thread do
          play note(base[j])-12 ,sustain:  3.5,release: 0.5,cutoff: rrand(60,100),amp: 0.7
        end
        play get(:s).look
      end
      play get(:s),sustain: 2,release: 10,cutoff: 80,amp: 1
      stop
    end
  end
end
2 Likes

Good point, lets call it draw then. :slight_smile:

I’m looking forward to playing with this when I
get home from work.

Eli…

1 Like

Both of you made a good job and share with the spi community, no competition just two ways to deal with a topic :slight_smile:
Cheers

1 Like

It was never a competition :joy::joy::joy::joy:like no sarcasm.

Just sharing something I made :slightly_smiling_face: and looking for feedback I guess.

I think I’m my next goal is to make it include either flats or sharps. Or I guess you would call it the minors? And instead of dropping it to the same octave have a option to have the notes push out a little but stay relatively closer. I think the way I’m doing it gets the notes in the dominates correctly but clearly the tone of the midi notes isn’t prefect when it’s all on the same octave. I’ve been playing with the use_transpose feature to make a ring that jumps a repeating note up a octave to make it sound warmer.

for example:

live_loop :foo2 do
  t = (ring 0,0,0,12)
  n = (ring 0,2,3,0)
  use_synth :piano
  tick
  use_transpose t.look
  play out[n.look], amp: 0.3
  sleep 1
end

But I bet there is a way to make a smarter ring. What if it somehow kept track of the last note played and automatically jumped a repeating note a octave making it easier to work with while live coding?

Anyone know if you can make a class in sonic pi or is that too heavy?

SIDE NOTE:

I’m also curious how music generation for things like band in a box works. I’ve been reading up on music theory and I wonder what algorithms exist for generating licks. Anyone know of a good music theory resource?

Can only stand going to borderline adverts for music theory so much. Like if I want to look up something about ruby I can find what I want very easily. But some music things not so much. Maybe it’s because I don’t know what to the correct terms are? :upside_down_face:

1 Like

If you’re curious about BiaB then Impro-Visor might assist.

1 Like

sick, thanks
post must be at least 20 characters

If you like podcasts, I’ve enjoyed the Music Student 101 podcast. I get lost in some of the more advanced topics but I’ve listened to the basic ones a few times in the background and some of the concepts are starting to make a more comprehensive picture in my head.

1 Like