229 Music Theory

Description

Do-Re-Me Rubyists,

I have a musician friend, let’s call him Steve. Steve wants to be a legendary guitarist. He practices every day, learning new chords and techniques. But he has a problem.

Steve bought all the books, but it’s too much trouble to flip through them when he’s practicing. He tried all sorts of ways to solve this problem: adhesive notes that adhered to everything, big music stands that kept getting knocked over, websites with loud and annoying video advertisements… everything. He even tried enlisting the help of his trusty cat, Pajamas, to turn the pages, yet nothing worked.

He’s trying to learn Amaj7 so he can be cool like his hero, Herman Li, but Pajamas clawed out that part of his book. Steve is programmer, so he knows that when solving a problem it should be solved once and forever. Steve wants to write a program where someone can type in Amaj7 and see the notes that comprise the chord. Not only Amaj7 but Dsus2 as well. In fact, any chord at all.

Steve, because he is a proper programmer, is lazy. He came to me and asked if I could send this out on the “weekly” Ruby Quiz. Anything to help a friend!

Your task is to create a program that will accept strings like: Amaj7, Dsus2, Aminor, C, C9, G#dim, Ebadd9, etc. The output will be the notes that make up the chord, for example

Cmajor => C E G
Ebdim7 => Eb Gb A C

Have fun! And thanks for helping Steve out!

Summary

Introduction

This summary was written by Jean Lazarou.

This quiz has some tricky aspects; translating a chord symbol to the notes that it comprise may be ambiguous. The rule to follow when interpreting a chord symbol may be different from one person to another and still be correct.

As an example take the C major chord, one may expect three notes (C, E and G). On a guitar you can produce 6 notes, on a piano you can produce 10 notes. Would you only play three notes? Another example: Cb on the piano results in hitting the B key. Should a chord like Ab-Cb be rendered as Ab-B? Have a look at the thread of discussion…

Therefore, I am not considering all interpretation differences. I am not an expert and I would probably be wrong.

Terminology

In the hope to make the summary clear, let’s first present the terminology we use.

The ‘thing’ the solution program expects as input is a chord symbol.

The chord symbol has a specific syntax. It starts with a note name, a letter from A to G, that we name the chord root. We can assign a pitch to the note, a flat or a sharp. We write a flatted note by adding the lower case letter ‘b’ and a sharped by adding the ‘#’ symbol.

After the root note follows the quality and the extension, they are a limited set of strings defining what notes to include in the produced chord. The rule to apply to get the notes is the same whatever the root note is. We are going to call it the modifier (Evan’s term) or chord modifier.

Scales are sequences of notes, the basic sequence is for instance the C major: C D E F G A B. We use degree to refer to the distance between notes, for instance in the C scale, G is at a higher degree compared to C.

Solutions analysis

We see two trends in the solutions: three solutions defined classes, like Note, and two went for a straight implementation of the solution. The solutions with classes are intended to be musical APIs. They check if the script is used as a main script to run in interactive mode. Typically code expecting to run both as a utility and as a main script contain code like:

if __FILE__ == $0
  # used as main script
end

All the solutions parse the chord input and validate it in some way. They all use regular expressions but in different ways.

Ben Rho

Ben’s code, that parses the chord symbol, uses an array notation where the index is a regular expression that returns a matching string or nil:

value = "hello"
p value[/hel/] # => "hel"
p value[/aa/]  # => nil

(not common to me)

He uses nested if statements with different regular expressions to validate the chord symbol and builds the result at the same time. Using the root note, he creates a note list as an array of possible notes sorted by degree (either with sharps, either with flats). To define the array he uses a standard array, then rotates the sequence so that the root note becomes the first. The code looks like:

# define an array of note sequence
note_list = (%w(ab a bb b c db d eb e f gb g))

# map the note list to produce a rotated list
rotated_list = note_list.map { ... }

He uses a hash object that defines the ‘chord library’. The chord library maps the modifier to an array of indexes. The indexes give the sequence of elements to select from the note list to build the chord. Each index being an offset from the previous one.

Basically as the note list expresses all the semitones, the first index gives the number of semitones between the root note and the first not to appear in the chord. The next one gives the number of semitones between the second note and the third one, and so on.

Because adding indexes may result in overflowing, he use a modulo 12 to restart from 0.

David Springer

David’s solution is pretty similar, except that his indexes are not cumulative, they all give the offset from the root note.

He has a more complete list of modifiers.

Two differences are worth to notice. David does not use the regular expression literals, supported by Ruby. He explicitly uses the Regexp class. To combine alternative patterns he uses the union method:

sharps = Regexp.new("^[ACDFG]#")
flats = Regexp.new("^[ABDEG]b")
naturals = Regexp.new("^[A-G]")
get_root = Regexp.union(sharps,flats,naturals)

The second difference is that David does not rotate the arrays containing the scale, to move the root note at the first position. He addresses the items using modulo, after computing the position of the root note. He also uses negative indexes to mark optional notes.

Evan Hanson

Evan defines several classes: Note, Chord and Key. He also extends the Map class with class level methods

He creates a Chord instance with the chord symbol as parameter. The initializer parses the chord symbol and actually creates the result.

Because he accepts a combination of any number of modifiers, he tries to match as much strings as possible, by removing one character from the modifier string at a time, until it finds some match. Evan uses a hash (dictionary) with the supported modifiers. The code searching for all the modifiers looks like:

# duplicate entry so that next lines do not destroy the original modifier
mod = original_modifier.dup

# search for the longer match...
until chords_dictionary.include?(mod) or mod.empty? do
  mod.slice! -1 # remove last character
end

# retrieve the value associated with the modifier, if any
x = chords_dictionary[mod] unless mod.empty?

The values stored in the chord dictionary are arrays of method names. The methods must be sent to a Note object. The methods return a note at some interval (or distance) from the root note. Calling all the methods produces the chord.

At the end he uses the uniq! method of the Chord class to remove any duplicate note.

Brian Candler

Brian introduces one class, named Note. The class level scale method returns the chord and the scale. A Note object has two attributes: the note expressed as an index (A note is 0) and the distance from A (as semitones).

The scale method uses another class level method named parse. The parse method returns a Note instance and the chord modifier. Again, the code validates the modifier with a dictionary. The dictionary provides the scale in an array of numbers, each number gives the number of semitones between the root note and each note. Actually, dictionary entries may contain two other arrays to add more notes in the scale and the notes making up the chord. The code converts the array of semitones to an array of notes (strangely the method making the conversion is also named scale but is not in the same scope, instance method here).

Brian’s dictionary contains a pretty complete list of modes.

Once he gets the scale, he selects the notes for the chord.

An interesting usage in parse is the call to new to create an instance. As the method belongs to the Note class, the new method, with respect to the current self, is the one in the class object.

def parse str
  # skip code
  [new(note, semi), $3]
end

Using new this way, instead of calling Note.new has the advantage of making easier class renaming or copy-pasting code.

Jean Lazarou

The last solution is mine. It contains three classes: Note, Chord, and Interval.

The Interval represents a distance and is expressed as a number of degrees and a number of semitones. A Note has a name, an index (like Brian’s index, 0 is A) and the pitch.

The Chord class parses a chord symbol in the initializer. It does not build the solution, it stores the elements of the chord definition. The parsing uses only one regular expression with the group options to retrieve each element:

  # only part of the real regexp
  if @chord_symbol =~ /^([A-G])([#b]{0,1})(maj|m|mi|min|){0,1}$/
    puts "root note #{$1}"
    puts "pitch is #{$2}"
    puts "quality is #{$3}"
  end

As you see in the example above the expression has 3 groups: [A-G], [#b] and maj|m|mi|min.

The Chord class has a dictionary providing arrays of intervals. When it comes to generate the chord, the code calls the to_a method. to_a creates an array by adding each interval to the root note. The Note class overloads the add and subtract operators:

  def + interval
    # code here
  end
  
  def - interval
    # code here
  end

I also added tests to test the chord parsing, adding intervals to notes and the chord generation. To make the tests easier to read I created constants named A, B, C, etc.

Finally, I added a script to run a GUI version displaying the notes using the score notation, see screenshot. It uses Swiby as the GUI layer on top of Java/Swing, that’s why I wrote the solution as an API.

Comments

Reading the code with classes was not very easy.

I will not start a debate about API design, still I think some questions should help in writing APIs:

Let’s try to look at the solutions with the questions in mind.

Should they be easy to understand?

Brian’s Note class contains a class level method named parse, it parses a string and creates a Note object.

Should they be intuitive to use?

Evan has a class named Key. It contains a method named include? taking a note as parameter and easy to guess what it is supposed to do: it returns true if the given notes belongs to key.

Should they allow doing different things or only a specific one?

My Note class allows to add intervals to calculate another note. The feature is used to build the chord, it could also be used to generate sequences of notes by applying rules like oriental music style or jazz style.

Should they be helpful?

The solution make use of their APIs and do not contains unused feature, obviously they are helpful.

Conclusion

So, Steve, I would suggest that you make use of Brian’s solution because it seems to support more chords. But, I think you can rather easily improve or enhance any of the solutions to meet your needs.


Friday, February 26, 2010