Chord extensions with degree function

I have been working on creating some shortcuts for myself that include rhythm patterns, chord progressions, and arpeggiator patterns. In doing so I’ve run into a limitation in the “degree” function.

My idea was to store my arpeggiator patterns as rings of scale degrees, then use play degree() to play them, thereby being able to easily translate them to different scales and different chords within the scale. However, I’d like to be able to extend arpeggiator patterns to 9ths, 11ths, and 13ths and “degree” doesn’t currently do that. It’ll accept up to 8, but anything above that and it puts out a rest.

I feel like there could plenty of reasons why one might want to be able to plug in degrees beyond 8 into this function. In the meantime, however, here’s my workaround:

define :playdegree do |deg, tonic, scale|
  if deg > 7
    deg = deg - 7
    tonic = tonic + 12
  play degree(deg, tonic, scale)
  sleep 1
1 Like

Hi @lwalchuk,

just as a side note (because it does not solve your problem but rather attempts to provide a different view on it):

I think the concept of degrees (or functional harmonics) is limited to 7 degrees simply because the 9th (and respectively the 11th and the 13th) is functionally equivalent to the II. degree. In its functionality this theory does not need to extend beyond the octave. With respect to that the degree function is in accordance with the musical theory - as far as I see.

Apart from that: I’d be interested in what you are working on. Are you planning to ‘release’ your shortcuts or code using it?

I don’t think that limiting the system to 7 degrees is a good thing. In some occasions, one might need to extend it to include degrees such as IIb, VIb, etc… It should be at least chromatic to be complete. However, I agree that such a system should not consider going further than one octave (just because pitch /= degree).

Hi @Martin!

I understand that the 9 is functionally the ii, etc. My concern comes from a practical standpoint, rather than theoretical. In lots of music, jazz in particular, the 9, 11, and 13 are regularly notated in chords, etc. When I’m creating my own “notation” for arpeggios, I want to be able to outline a pattern that is, say, [1, 3, 5, 7, 9], or something like that. It makes sense in my head and it’s the way I’d like to be able to notate things.

The other concern is that the “degree” function is limited to an octave, so even if I were to use 2 in place of 9, or 4 in place of 11, I have to change the octave as well to achieve the note I want. It adds an extra step.

To respond to @Bubo’s point – making the “degree” function chromatic would defeat its purpose. The reason I want to use it is so that I can make sure to stay within the given scale by calling out degrees. However, it does raise a good point - could there be a way to force a chromatic change, like adding a flat or a sharp to a degree, if the situation called for it?

Here are some of the functions I’m building. They’re essentially a list of patterns that I can call up by index, as well as specifying the number of steps in the pattern.

define :arp do |idx, deg = 1, num_notes = 0| #arpeggiator patterns
  num_patterns = 3
  assert idx >= 0 && idx < num_patterns, "First argument must be between 0 and #{num_patterns - 1}"
  ptrn = [1, 3, 5, 7] if idx == 0
  ptrn = [1, 5, 3, 7, 5] if idx == 1
  ptrn = [1, 3, 5, 6] if idx == 2
 # off = (deg - 2).to_i
  if num_notes == 0
    steps = ptrn.length
    steps = num_notes
  arpeg = ptrn.ring.take(steps)
#  arpplus = arpeg + off
#  arp = {|n| n.to_i}
  return arpeg

define :rtm do |idx, num_steps = 0| #rhythm patterns
  num_patterns = 13
  assert idx >= 0 && idx < num_patterns, "First argument must be between 0 and #{num_patterns - 1}"
  ptrn = (bools 1, 0, 0, 0) if idx == 0
  ptrn = (bools 1, 0, 1, 0) if idx == 1
  ptrn = (bools 1, 1, 1, 1) if idx == 2
  ptrn = (bools 1, 1, 1, 0) if idx == 3
  ptrn = (bools 1, 1, 0, 1) if idx == 4
  ptrn = (bools 1, 0, 1, 1) if idx == 5
  ptrn = (bools 1, 1, 0, 0) if idx == 6
  ptrn = (bools 1, 0, 0, 1) if idx == 7
  ptrn = (bools 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0) if idx == 8 #Son Clave 2/3
  ptrn = (bools 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 0, 0) if idx == 9 #Son Clave 3/2
  ptrn = (bools 0, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1) if idx == 10 #Rhumba Clave 2/3
  ptrn = (bools 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 1, 0, 0, 0) if idx == 11 #Rhumba Clave 3/2
  ptrn = (bools 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0) if idx == 12 #Chitlins Con Carne
  if num_steps == 0
    steps = ptrn.length
    steps = num_steps
  patrn = ptrn.take(steps)
  return patrn

define :prg do |idx, num_chords = 0| #chord progressions
  num_patterns = 14
  assert idx >= 0 && idx < num_patterns, "First argument must be between 0 and #{num_patterns - 1}"
  ptrn = [1, 4, 5] if idx == 0
  ptrn = [1, 5, 6, 4] if idx == 1
  ptrn = [1, 6, 4, 5] if idx == 2
  ptrn = [6, 4, 1, 5] if idx == 3
  ptrn = [1, 4, 6, 5] if idx == 4
  ptrn = [1, 4, 5, 4] if idx == 5
  ptrn = [1, 4, 1, 5] if idx == 6
  ptrn = [1, 5, 6, 3, 4, 1, 4, 5] if idx == 7 #pachelbel
  ptrn = [4, 5, 6, 1] if idx == 8
  ptrn = [4, 5, 6] if idx == 9
  ptrn = [1, 5, 4] if idx == 10
  ptrn = [6, 1, 4, 5] if idx == 11
  ptrn = [6, 2, 4, 5] if idx == 12
  ptrn = [4, 3, 2, 3] if idx == 13
  if num_chords == 0
    steps = ptrn.length
    steps = num_chords
  prog = ptrn.ring.take(steps)
  return prog
1 Like

Yes, sure. That makes totally sense! Inspired by your idea I did some coding. I am not sure whether this is the direction you want to go in but you might want to have a look:

# @params:
# base = tonic
# mode = scale
# degree = notated as integer, 1..7
# num = number of notes to play
# returns ring with arpeggiated scale
define :arpeggio_from_degree do |base, mode, degree, num|
  notes = (scale base, mode, num_octaves: 3)
  i = degree - 1
  arp = []
  num.times do
    i += 2
  return arp.ring

# Testing
notes = 6 # adjust to how many arpeggio notes you want
notes.times do
  play (arpeggio_from_degree :c4, :major, 7, notes).tick
  sleep 0.25

This is just a sketch and has some obvious limitations.

Nevertheless, it can already produce some nice sounds; I have just noticed that there is a scale called shang :wink:

live_loop :arpeggios do
  use_synth :fm
  use_synth_defaults release: 0.25, cutoff: rrand(70, 130)
  degrees = (range 1, 8)
  # Just learned about the pleasures of random_seed in Sam's last patreon video session 
  use_random_seed 20 # change to change mood
  d = degrees.shuffle.tick
  with_fx :reverb, room: 0.75, mix: 1 do
    7.times do
      play (arpeggio_from_degree [:c3, :c4].choose, :shang, d.to_i, 7).tick(:n), amp: rrand(0.25,1)
      sleep 0.125

And thanks for posting your other code. This looks interesting and I am looking foward to listen to some music where you are using this!

The Sonic Pi scale definition actually includes degree symbols up to :xii, but then only looks them up in a single-octave scale, so maybe this is an oversight in the code? Maybe @samaaron would consider a PR to extend it in the next release.

EDIT: I just realised that the symbols up to :xii are needed for a single octave of the chromatic scale, so maybe it wasn’t an oversight after all. But the point remains that Sam might be open to extending this in a future release.

1 Like

Happy to consider extensions to the behaviour. What’s your suggestion? :slight_smile:

I think it would make sense to support degrees larger than an octave (such as a thirteenth).

It might also be useful to support augmented and diminished intervals (maybe by prefixing the symbol with an a or a d?) to add or subtract a semitone from the note (obviously that wouldn’t work with numeric degrees, but we could allow strings like "d5" as well as :dv for diminished 5th).

I’d be happy to put together a pull request if that sounds reasonable.

How does this look @samaaron?

Have you tried the chord_degree function? If you use this with .tick, .choose and other ring chains, you can arpeggiate through different chords. This function does include an argument which allows you to add extensions.

c = (chord_degree, 1, :a3, :major, 5) # The last argument determines how many notes in the chord. 
                                      # 5 includes the 9th, 6 includes 9th & 11th, 7 includes 9th, 1tth & 13th
                                      # I supposed you could use chains to drop a value or play diff patterns

live_loop :cord do
  c.length.times do
    play c.tick # arppegiates a major chord 1, 3, 5, 7, 9
    sleep 1

Admittedly, I haven’t dug too deep into the code examples posted in this thread so I could be way off. But based on the code in your first post, you could do something similar in terms of changing the tonic, scale and degree with the chord_degree function as well as having the option for extensions.

1 Like

I originally approached this using the chord_degree function. The problem is, I like to make arpeggiator patterns that don’t necessarily stick within the chord, like 1, 3, 5, 6, 5, 3 or something like that. That’s what led me to using degree instead.

My PR was just merged :smiley: so the next Sonic Pi release will have an expanded degree function that supports intervals over one octave, as well as augmented/diminished intervals.


@emlyn - thank-you again for your fab work :slight_smile:

@samaaron thank YOU for making it possible! Sonic Pi is an awesome piece of software and it feels great to be able to do a little bit to make it even better :smiley:


Just trying this out. Very nice and useful for doing chord progressions.
I also like your improved piano synth also in 3.2dev. Playing with a honky tonk piano generated using its micro tuning capability . Thanks a lot.


Side question: are there Sonic Pi libraries?

AFAIK, the tutorial covers the language capabilities, but not all functionality should come from the language itself.

For instance, I would also like to use intervals (“play note+p5” to play a 5th above or something like that) and I imagine I am not the first… It seems something perfect for a library. Arpegios and other patterns are another. Different backgrounds mean everyone wants to do stuff slightly differently: libraries allow this customisation.

I’ve stumbled across a few projects that are written as libraries mainly to serve the author’s specific needs. Curiously, (and without judgement) producing general-purpose code abstractions hasn’t been a primary concern for this community. I’d guess that the average Sonic Pi user is more knowledgeable about music than software development, resulting in a lot of one-off or copy+paste development.

One that comes to mind, which is still actively developed is ziffers. For the example you mentioned, you could likely use or borrow code from there related to calculating intervals.

Sorry for resurrecting another old thread. I feel really late to the party!

Another alternative is to do these as part chords. CMaj13 is a combination of Cmaj7 + Dm; or C + Bm7b5; or C (just the root) + Em + Dm. If you’re doing Jazz then the upper extension triads get you closer to these sorts of superimpositions. It means a simple adaptation for CMaj7 to Cmaj#11 (C and Bm) to C + B7 (or E harmonic minor played over CMaj7). I believe you can combine all of these using Knit and might mean a bit more mobility and flexibility by being able to ‘swap’ lower and upper parts. Just an idea.

Anyway, it’s an old topic, so not sure how useful this info is now, but might be of interest to some.


Oh, definitely still useful @Hussein, thanks! :grinning_face_with_smiling_eyes:


Just did a bit more looking at the the chord types and the :add2 is a really good one as well. Do you think there’s any chance of having the minor variant 1, 2, b3, 5?. Correction: just realised there’s the madd2 available already. Both are classic tools for improvisational parts built on chord tones.

For example C7 could improvise on chord tones I and b7 by using C D, E, G (I) and Bb, C, D, F (bVII). here’s a rather silly example:. Uncomment the additional chain methods to get a bit sillier.

use_bpm 275
use_octave 1
live_loop :complexLine do
  cLine = chord(:C3, :add2)#.reverse.mirror
  bbLine = chord(:Bb2, :add2)#.reverse.mirror
  dAdd2 = chord(:D3, :add2)#.reverse.mirror
  dbAdd2 = chord(:Db3, :add2)#.mirror
  lines = (ring cLine, bbLine, dAdd2, dbAdd2)#, bAdd2)
  cnt = 0
  lines.size.times do
    liney = lines[cnt]
    liney.size.times do
      play liney.tick, release: 0.4
      sleep 0.5
    cnt +=1 

live_loop :chordLoop do
  with_octave 0 do
    rhy = spread(5,8)
    play chord(:C3, '7'), release:0.4, on: rhy.tick
    sleep 1