Music Theory (#229)

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

The three rules of Ruby Q.:

  1. Please do not post any solutions or spoiler discussion for this
    quiz until 48 hours have elapsed from the time this message was
    sent.

  2. Support Ruby Q. by submitting ideas and responses
    as often as you can.

  3. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem
helps everyone on Ruby T. follow the discussion. Please reply to
the original quiz message, if you can.


RSS Feed: http://rubyquiz.strd6.com/quizzes.rss

Suggestions?: http://rubyquiz.strd6.com/suggestions

-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

Music Theory (#229)

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 Q… 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!

So, this is my first post to the list, my solution to 229. I’m still
getting the
hang of Ruby, so any tips you might have are more than welcome. My
script
probably doesn’t have the most extensive chord dictionary, but I
designed it
so that it’d be easy to add new definitions.

Anyway, it was fun. Ruby is great. Actually, one of the toughest parts
about
this problem was deciding what degrees of the chord are implied by a
given
symbol. I’m a jazz musician, and we just play whatever the hell we want,
so I
wasn’t sure on the specifics of a few of them.

Again, suggestions are welcome.

– Evan

Evan H. wrote:

Actually, one of the toughest parts
about
this problem was deciding what degrees of the chord are implied by a
given
symbol. I’m a jazz musician, and we just play whatever the hell we want,
so I
wasn’t sure on the specifics of a few of them.

I learned classical at school, so had to unlearn a load of stuff when
trying to play jazz.

OUT:
Cmajor => C E G

IN:
Cmajor => E B or B E
(not C: that’s the bass player’s job)
(not G: perfect 5th just reinforces the root)

Evan H. wrote:

Incidentally, I just tested my code on my other machine and got a
“warning: parenthesize argument(s) for future version”… I hope
there’s no seachange in syntax on the way? I’m assuming this is just
for ambiguities?

The message suggests that the parsing might change. I suspect it’s
unlikely, but it’s safer to add the parentheses as it suggests.

There are all sorts of ambiguities arising from poetry mode. For
example,

puts (1-2).abs
and
puts(1-2).abs

are parsed differently.

Yeah, 3 & 7 decide the nature of the chord. Ditch everything but that,
then add extensions (at least for the instruments that carry the
harmony). You took a better musical route; I learned jazz first so my
theory is good but my knowledge of the traditional ruleset is a bit
lacking.

Incidentally, I just tested my code on my other machine and got a
“warning: parenthesize argument(s) for future version”… I hope
there’s no seachange in syntax on the way? I’m assuming this is just
for ambiguities?

On Mon, Mar 1, 2010 at 4:02 PM, Brian C. [email protected]
wrote:

trying to play jazz.

OUT:
Cmajor => C E G

IN:
Cmajor => E B or B E
(not C: that’s the bass player’s job)
(not G: perfect 5th just reinforces the root)

So you’re saying that to a Jazz player Cmajor has a note from Cmajor7 ?


Rick DeNatale

Blog: http://talklikeaduck.denhaven2.com/
Twitter: http://twitter.com/RickDeNatale
WWR: http://www.workingwithrails.com/person/9021-rick-denatale
LinkedIn: http://www.linkedin.com/in/rickdenatale

Brian C. wrote:

Apologies if this is going way off-topic :slight_smile:

Cheers,

Brian.

I love this topic - because of the music theory!

I’ve barely started learning Ruby, so I’m not playing along with the
quiz.

I don’t know music theory - AT ALL - but I’d love to learn. Can you
guys recommend a couple great books (e.g., textbooks)?

Thanks,

Jim M.

Rick Denatale wrote:

So you’re saying that to a Jazz player Cmajor has a note from Cmajor7 ?

Yes. I had to unlearn a lot of stuff :slight_smile:

As Evan said, it’s the 3rd and 7th which define the nature of the chord,
so there are four basic shells (minor or major 3rd, together with minor
or major 7th). You can put them either way up, which allows for smooth
progressions [1]

And apart from a couple of rules [2], you can add any other notes of the
scale to make a fuller chord. The fact that harmony comes from scales
and not triads was a big revelation to me. Another was the existence of
lots of other scales like the Lydian.

Apologies if this is going way off-topic :slight_smile:

Cheers,

Brian.

[1] e.g. Dm -> G7 -> C could be (F+C), (F+B), (E+B)

[2] Don’t play a perfect 4th with a major 3rd - it jars. And keep either
the 3rd or 7th towards the bottom of the voicing.

If you’re interested in jazz specifically (though if you learn that
picking up the other styles becomes much easier), look for books by
Mark Levine – I have the Jazz Theory Book and the Jazz Piano Book,
both are top-notch, and the former is considered by many to be the
“Bible” of jazz, so to speak.

Jim M. wrote:

I don’t know music theory - AT ALL - but I’d love to learn. Can you
guys recommend a couple great books (e.g., textbooks)?

The jazz I learned mostly through classes, although I have a couple of
chord progression books. The classical theory was many years ago at
school - I think the main tome was called “The Rudiments of Music”

On Tue, Mar 2, 2010 at 4:38 PM, David S. [email protected]
wrote:

I’m not sure about the etiquette of attaching a non-compressed file.
Right now I am working under Windows XP.

Frankly, I’d rather see inline code rather than an attachment.
Especially
if attaching doesn’t save any space.

Hal

OK

Here is my solution.
I’m rather new to Ruby.
So this may look more C-like than Ruby-ish.
I am not a musician.
It should be easy to add additional chords.

I could not come up with a good way to decide wich of two equivalent
notes to display.

I’m not sure about the etiquette of attaching a non-compressed file.
Right now I am working under Windows XP.

Not sure how to deliver a .tar.gz file under Windows.

I probably could just do a .z file though.

I was fun AND consumed way too much of my free time.

Wow, this is some great discussion! Both piano and guitar chords are
fine, some of the benefits on these broader quizzes are seeing
alternative solutions and understanding the problem from different
angles. Likewise the discussions about the differences between
classical and jazz are also welcome. After all, the most important
part of any programming project is understanding the domain.

This writeup is going to be a fun one. Keep the solutions and the
discussion coming!

Daniel X Moore wrote:

Steve wants to be a legendary guitarist.
Is it OK if I use piano chords instead of guitar chords (I think the
answer is yes, but I wanted to make sure)? I used to be a pianist and I
think it would be easier to do piano chords (there isn’t much different,
but my dad - a hobby guitarist - and I don’t understand each other when
talking about chords).
This is the first Ruby Q. I’ve worked on (actually, it’s one of my
first Ruby programs all together, because Ruby Q. is what introduced
me to Ruby!). I’ve got a solution in the workings (easy to add new
chords, too), but unfortunately homework takes precedence so I
won’t/can’t finish or post it yet :confused:
From what it says on the 1st Ruby Q. website (and the 2nd), it seems
like everything related is allowed.
James Edward G. II wrote: (on http://rubyquiz.com/)
Again, you can submit anything you like. There’s no right or wrong answer to Ruby Q… The goals are to think, learn, and have a good time. You have to decide what does that for you.
However, it also says that if the quizmaster includes a criteria in the
quiz, then thats what it’s graded by. Is “guitar chords” a criteria?

Oops, misspelled chords throughout, I think I caught them all but if
you’d mentally s/cord/chord/g that’d be great :stuck_out_tongue:

David S. wrote:

OK

Here is my solution.

$ ruby Chords_DNS.rb
C7
Chord “C7”, C, E, G, A#

That should be a Bb, not an A#.

C7b5
Chord “C7b5”, C, E, F#, A#

That’s my favourite chord, the Lydian Dominant, but I would have called
it C7#4 as the scale also contains a perfect 5th. It’s odd as it
includes both sharps and flats (F# and Bb).

The Simpsons theme tune is an example of a melody using this scale.

Regards,

Brian.

Daniel X Moore wrote:

Wow, this is some great discussion! Both piano and guitar chords are
fine, some of the benefits on these broader quizzes are seeing
alternative solutions and understanding the problem from different
angles. Likewise the discussions about the differences between
classical and jazz are also welcome. After all, the most important
part of any programming project is understanding the domain.

This writeup is going to be a fun one. Keep the solutions and the
discussion coming!

Thanks for the confirmation. I agree, can’t wait until the writeup is
posted.
On reviewing David S.'s solution, I found that our solutions are
quite similar (actually, mine’s pretty much the same except shorter and
with more regex - after completion, it’ll probably be a lot closer).
Would both of our solution merit entries on the Ruby Q. website?
Also, could someone with some time give a brief run-through of what
happens when Evan H.'s code is run? I don’t think I understand it.
What I think happens:
Make a new Chord object
Chord#initialize:
Set @name to the note and @numval to to the notes position in
Map.sharps
Parse note skipping the first character and sharp/flat it if it’s b or

(I’d use a different method though, becouse susb or something would be

picked up)
Flat things by subtracting one from @numval (I think it should have
an error check, @numval=11 if (@numval-=1)<0 to make it easier to port
to other languages) and changing the value of @name (easier done in my
opinion by using Map.flats[@numval])
Sharp things by adding one from @numval (I think it should have an
error check, @numval=0 if (@numval+=1)==12 to make it easier to port to
other languages) and changing the value of @name (easier done in my
opinion by using Map.sharps[@numval])
Chord#to_s:
Returns @name
But that would only return the input in a fancy way! I don’t see how it
returns chords.

David S. wrote:

I’m not sure about the etiquette of attaching a non-compressed file.
Personally, I prefer the attachment of non-compressed files to the
attachment of compressed files or inline code, because it keeps the
thread short, makes the code easier to read (all-the-way-left-justified
and full width), and the code is easier to download (for me, it takes
just 10 keystrokes, no need to search for where it begins/ends and
click+drag). That’s just me, though, and Hal F. doesn’t agree:
Frankly, I’d rather see inline code rather than an attachment. Especially if attaching doesn’t save any space.

Thanks in advance, Ben.

Here’s my version. I think it handles the “spelling” of 7-note scales
correctly, but the 8-note scales don’t always give a satisfactory
answer, e.g.

C#dim7 => C# E Fx A#

(most people would use G rather than F double sharp)

Regards,

Brian.

class Note
NOTES = “ABCDEFG”
SEMITONES = [0, 2, 3, 5, 7, 8, 10] # semitones above A

attr_reader :note # 0-6 representing A-G
attr_reader :semi # 0-11 representing A to G#/Ab

ACCIDENTAL = {“bb”=>-2, “b”=>-1, “”=>0, “#”=>1, “x”=>2}

Parse a note like “C#…”

Return a Note object plus the remainder of the string

def self.parse(str)
raise “Invalid note” unless str =~ /\A([A-G])([b#]?)(.*)\z/
note = NOTES.index($1)
semi = SEMITONES[note] + ACCIDENTAL[$2]
return [new(note, semi), $3]
end

Create a note.

new(0,0) => A

new(0,1) => A#

new(1,1) => Bb

new(1,2) => B

new(1,3) => B#

new(2,2) => Cb

new(2,3) => C

def initialize(note, semi=SEMITONES[note])
@note, @semi = note % 7, semi % 12
end

def to_s
acc = (@semi - SEMITONES[@note] + 6) % 12 - 6
str = if acc < 0
“b” * -acc
elsif acc == 2
“x”
else
“#” * acc
end
NOTES[@note,1] + str
end

return a new note which is N degrees along and M semitones along.

e.g.

fsharp(1,1) => G (one note up, one semitone up)

fsharp(1,2) => G# (one note up, two semitones up)

fsharp(2,2) => Ab (two notes up, two semitones up)

def offset(degree_offset, semi_offset)
self.class.new(@note + degree_offset, @semi + semi_offset)
end

return an array of notes, given an array of [degree,semitone]

offsets

representing a scale, and an array of indexes into that array

def scale(pairs, degrees = [1,3,5,7])
res = []
degrees.each_with_index do |d,i|
pair = pairs[(d-1) % pairs.size]
res << offset(pair[0], pair[1])
end
res
end

Convert a scale into its nth mode

def self.mode(pairs, mode)
a = pairs.dup
(mode-1).times { a.push(a.shift) }
d0, s0 = a.first
a.map { |d,s| [d-d0, s-s0] }
end

Ionian = [[0,0], [1,2], [2,4], [3,5], [4,7], [5,9], [6,11]]
Dorian = mode(Ionian, 2)
Phrygian = mode(Ionian, 3)
Lydian = mode(Ionian, 4)
Mixolydian = mode(Ionian, 5)
Aeolian = mode(Ionian, 6)
Locrian = mode(Ionian, 7)

MelodicMinor = [[0,0], [1,2], [2,3], [3,5], [4,7], [5,9], [6,11]]
PhrygianNatural6 = mode(MelodicMinor, 2)
LydianAugmented = mode(MelodicMinor, 3)
LydianDominant = mode(MelodicMinor, 4)
MixolydianFlat6 = mode(MelodicMinor, 5)
AeolianFlat5 = mode(MelodicMinor, 6)
Altered = mode(MelodicMinor, 7)

Diminished = [[0,0], [1,2], [2,3], [3,5], [3,6], [4,8], [5,9], [6,11]]
EightNoteDominant = mode(Diminished, 2)

Chords = {
“” => [Ionian],
“m” => [MelodicMinor],
“m7” => [Dorian],
“7” => [Mixolydian],
“7+4” => [LydianDominant, [1,3,4,5,7]],
“7alt” => [Altered, [1,3,5,7,9,11,13]],
“dim7” => [Diminished, [1,3,5,7]],
“7b9” => [EightNoteDominant, [1,3,5,7,2,4,6,8]],
# Expand this at your leisure
}

def self.chord(str)
root, chordsym = parse(str)
chordarg = Chords[chordsym] || (raise “Unknown chord:
#{chordsym.inspect}”)
root.scale(*chordarg)
end
end

if FILE == $0
while str = $stdin.gets
str.chomp!
puts Note.chord(str).join(" ")
end
end

Thanks for the pointers. Maybe you could clarify some things below:

On Wed, Mar 3, 2010 at 8:50 AM, Ben R. [email protected] wrote:

picked up)
I’m not sure when “susb” would cause an issue – do you mean an
instance like Gsusb? I don’t know that that’s a chord, but the G would
be read and the susb would raise an exception as an invalid chord
symbol.

Flat things by subtracting one from @numval (I think it should have
an error check, @numval=11 if (@numval-=1)<0 to make it easier to port
to other languages) and changing the value of @name (easier done in my
opinion by using Map.flats[@numval])

Yes, the Map should probably loop. RIght now things like Cb are just
thrown out as invalid.

Sharp things by adding one from @numval (I think it should have an
error check, @numval=0 if (@numval+=1)==12 to make it easier to port to
other languages) and changing the value of @name (easier done in my
opinion by using Map.sharps[@numval])

Same as above. I wrote the flat! and sharp! methods before I started
using the twelve-tone arrays, otherwise I might have done it that way.

Chord#to_s:
Returns @name
But that would only return the input in a fancy way! I don’t see how it
returns chords.

It actually returns the individual notes in the chord, as generated by
Chord#names.

As an aside, can anyone tell me if there is a slick Ruby way to do
what is done in cases like my Key#names, Chord#names, Chord#to_s, etc.
functions, where you’re just mapping things from one array to another,
or from one array to a string, etc?

On Wed, Mar 3, 2010 at 8:13 PM, Evan H. [email protected] wrote:

As an aside, can anyone tell me if there is a slick Ruby way to do
what is done in cases like my Key#names, Chord#names, Chord#to_s, etc.
functions, where you’re just mapping things from one array to another,
or from one array to a string, etc?

def names
notes = []
@notes.each { |n| notes.push n.name }
notes
end

def names
@notes.map {|n| n.name}
end

def to_s
out = “”
names.each { |n| out += n + " " }
out.strip!
end

def to_s
names.join(" ")
end

Jesus.

Oops, the each_with_index was a left-over artefact. It should say:

def scale(pairs, degrees = [1,3,5,7])
degrees.collect do |d|
pair = pairs[(d-1) % pairs.size]
offset(pair[0], pair[1])
end
end