Scale math - evaluating collections of notes

Happy New Year sonic alchemists!

I’ve searched thread titles and see nothing related specifically to this specifically, so creating a thread to discuss and share ideas on evaluating melodies, and scales.

I’ll be hoping to compare scales, for equivalence and uniqueness.
I imagine it’ll be something like take if {criteria satisfied} but may be more than one way, as is often the case.

If all scale names were returned how many are unique?
When changing modes what scales are equivalent?
What notes in a collection (a ring or a sequence) are not in given scale (s), and so on.

I haven’t dabbled with the “ziffers” thing yet, maybe some of these goals are achieved with that…

So those are some of my goals. Contributions and critique most welcome!

Happy New Year to you too.

Let me know if you start to dabble with Ziffers and have some ideas how to make it better. There is the wiki and article to get you started :slight_smile:

Ziffers monkeypatches scale method in Sonic Pi, which means you have access to all of the 1490 scales in the 12 tone equal temperament using integers (See Ian Rings study of scales). In addition the patched scale method also has a Scala scale parser that support extended format as defined in Sevish scale workshop. Also see the tutorial for the workbench.

So for example with Ziffers installed you can do:

print scale(:major)
print scale(2741) # Also major

Last year I was mostly doing Topos but come back to Sonic Pi time to time. With Topos I have been playing around with the ideas from the Ian Rings site (See Proximity chapter in study of scales) to calculate distance between scales. So you could do a similar method for Sonic Pi like:

def distance(int1, int2)
  # Apply XOR operation to sum changed bits between two scales
  bits1 = int1.to_s(2)
  bits2 = int2.to_s(2)
  (0...bits1.length).map do |i|
    if bits2[i] != bits1[i]
      if bits1[i + 1] != bits2[i + 1] && bits1[i] != bits1[i + 1]
        bits2[i + 1] = bits1[i + 1]

This could be used to get the distances between scales:

print distance(2741,2737) # 1
print distance(2741,2999) # 2
print distance(2741,2653) # 3

In addition to the distances you could also get all the the scales that are one step away with the same idea using some binary magic:

 def near_scales(scale_int)
  (1..11).flat_map do |i|
    near = scale_int
    if (scale_int & (1 << i)) != 0
      off = near ^ (1 << i)
      result = [off, off | (1 << (i - 1))]
      result << (off | (1 << (i + 1))) unless i == 11
      [near |= (1 << i)]

print near_scales(2741) # [2743, 2737, ...]

This is the list of the same scales as listed in Ian Rings site for all the scales at the bottom of the scale pages, for example for 2741 for the major scale.

1 Like

Thanks for this!! I haven’t tried to grasp the bit magic (yet) and this is certainly not the best way to do this, but … I did a thing, and it worked! Your example syntax to access Scales by key name via the SCALE hash (const?) came in handy!

This is quite inelegant, I just used the flatten method out of curiosity… originally I tried any? but this always reported false, so I went for has_value? Also the index isn’t being used.

Reviewing the ruby docs I wonder if any? looks for element with same key and value

class Hash - Documentation for Ruby 3.4 (

unique_scales = {}

Scale::SCALE.flatten.each_with_index do |_,i| if _.instance_of? Symbol    
  unique_scales[_] = Scale::SCALE[_] unless unique_scales.has_value? Scale::SCALE[_]  
end; end #if and each

puts "unique scales = #{unique_scales.length} (/#{scale_names.length})"

Reportedly unique scales = 103 (/151)

Then for fun I thought I’d look for non-standard scales

puts "weird scales! "
Scale::SCALE.each do |_|
  puts "#{_[0]} (#{_[1].sum} #{_[1].length})"  if _[1].sum != 12 or  _[1].length != 7  

Almost useful! :+1:

Sonic Pi’s scales are built from a list of ratios and semitones defined in the Scale-class. Many of the symbols are just synonyms of the same scale. There’s a lot more equal temperament scales (1490 total) out there cataloged in the Ian Rings study and by Zeitler before … and then there is infinite number of microtonal scales to explore - even if our ears might not appreciate the nuances of microtonality :slight_smile:

The binary distance method (as explained by Ian Ring) only works for 12 tone equal temperament scales - but one could use something like euclidean distance to compare any scale including microtonal scales. I might write such method one day, if i find any use for comparing microtonal scales.

… and fun thing about scale math is that you can come up with all kinds of revelations how to construct new scales (original or not - at least original in my bubble). My latest was that you can easily construct any n-EDO scale and use interval structures from those 1490 scales to do for example … “9edo 919/Gathian” scale :slight_smile:

Lots to check! Thanks again!

I’ve just found a method I made a while back, whilst looking at the steps between tonic and each note in a scale - the pitch-class set, I believe.

define :get_steps do |s|
  scale_steps = []
  (scale s).to_a.to_a.each_cons(2) { |a, b| scale_steps << b - a }

puts get_steps :major # [0, 2, 4, 5, 7, 9, 11, 12]

#all scales
scale_names.each #{|s| puts "#{s} - #{scale s}"; puts "#{get_steps s}";puts '###'}

Am probably over-complicating, but not (yet) aware of another way to get these…


Just a quick note to point out that scale_steps and each_cons are not doing anything there. You can get the same result with:

define :get_steps do |s|
  (scale s).to_a.to_a

puts get_steps :major # [0, 2, 4, 5, 7, 9, 11, 12]

The requirement for two to_as is a bit weird - it seems that (scale ...) returns a ring, and to_a converts that to a scale (not an array as the method name implies), so you need the second call to actually get an array.

The each_cons returns each pair of values, so would give the difference of each note from the previous note, not from the tonic. If that’s what you wanted you would need to return scale_steps at the end:

define :get_steps do |s|
  scale_steps = []
  (scale s).to_a.to_a.each_cons(2) { |a, b| scale_steps << b - a }

puts get_steps :major # [2, 2, 1, 2, 2, 2, 1]

I’ve been working with the Melakarta system, which may be described using group theory. There are 72 scales, each of which is a diatonic system on itself. These scales may be used with any 12 tone tuning, equal, just, etc. I characterize them as strings of intervals.

Interesting. Melakarta names are also listed by Ian in Scale Name Tradition: Carnatic Melakarta ( For example: 1451 = Hanumatodi = Phrygian.

Are you also familiar with Janya Ragas derived from Melakarta ragas? Any idea how to decipher those ascending and descending scales? For example in Megha = S R₁ M₁ P D₁ N₁ D₁ P Ṡ … What is this notation being used?

I suppose those are just subsets and other variations of the Melakarta and map to chromatic scale as follows:

S => C
R1 => C#
R2 => D
G2 => D#
G3 => E
M1 => F
M2 => F#
P => G
D1 => G#
D2 => A
N2 = A#
N3 => B
S‘ => C

And note to self: The ragas of carnatic music leads into a very deep rabbit hole. :magic_wand: :rabbit2: :timer_clock: :night_with_stars:

1 Like

Thanks for your questions. My intent has been to introduce the Melakartas to Western musicians.

Here’s a zip file with a “periodic table of musical scales” and a booklet describing the 72 Melakartas in Western theory. You will probably recognize that the periods are simply the grabdeha groups.

(I’m not sure if zip files or pdfs can be posted to the Sonic Pi site, so I’m sending via email.)

I’ve also calculated a Consonance Number to evaluate the harmonic quality of each scale. This concept goes all the way back to Helmholtz’s original calculations.

All of this is just the tip of the iceberg; for instance, by following the rules of the Melakarta system, another 48 scales can be formed, giving a total of 120.

(Attachment is missing)

I’ve searched for a list of allowed file types. No joy.

Amika, if you would like to get deeper into the melakartas from a group theoretic viewpoint email me directly:

I use a different notation (intervals), which simplifies some calculations.