[QUIZ] Music Theory (#229)

D

Daniel Moore

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

The three rules of Ruby Quiz:

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 Quiz 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 Talk 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 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!
 
B

Brian Candler

Evan said:
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)
 
E

Evan Hanson

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?
 
B

Brian Candler

Evan said:
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.
 
R

Rick DeNatale

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

OUT:
Cmajor =3D> C E G

IN:
Cmajor =3D> E B =A0or =A0B E
=A0(not C: that's the bass player's job)
=A0(not G: perfect 5th just reinforces the root)

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



--=20
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
 
B

Brian Candler

Rick said:
So you're saying that to a Jazz player Cmajor has a note from Cmajor7 ?

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

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 :)

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.
 
J

Jim Maher

Brian said:
Apologies if this is going way off-topic :)

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 Maher
 
B

Brian Candler

Jim said:
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"
 
E

Evan Hanson

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.
 
D

David Springer

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.

Attachments:
http://www.ruby-forum.com/attachment/4531/Chords_DNS.rb
 
H

Hal Fulton

[Note: parts of this message were removed to make it a legal post.]

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
 
B

Ben Rho

Daniel said:
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 Quiz I've worked on (actually, it's one of my
first Ruby programs all together, because Ruby Quiz 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 :/
From what it says on the 1st Ruby Quiz website (and the 2nd), it seems
like everything related is allowed.
James Edward Gray II wrote: (on http://rubyquiz.com/)
Again, you can submit anything you like. There's no right or wrong answer to Ruby Quiz. 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 :p
 
D

Daniel Moore

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!
 
B

Brian Candler

David said:
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.
 
B

Ben Rho

Daniel said:
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 Springer'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 Quiz website?
Also, could someone with some time give a brief run-through of what
happens when Evan Hanson'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 said:
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 Fulton 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.
 
B

Brian Candler

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
 
B

Brian Candler

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
 
E

Evan Hanson

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

Also, could someone with some time give a brief run-through of what
happens when Evan Hanson's code is run? I don't think I understand it.
What I think happens:
Make a new Chord object
Chord#initialize:
=A0Set @name to the note and @numval to to the notes position in
Map.sharps
=A0Parse 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)

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.
=A0 =A0Flat things by subtracting one from @numval (I think it should hav= e
an error check, @numval=3D11 if (@numval-=3D1)<0 to make it easier to por= t
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.
=A0 =A0Sharp things by adding one from @numval (I think it should have an
error check, @numval=3D0 if (@numval+=3D1)=3D=3D12 to make it easier to p= ort 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:
=A0Returns @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?
 
J

Jesús Gabriel y Galán

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.
 
E

Evan Hanson

2010/3/3 Jes=FAs Gabriel y Gal=E1n said:
=A0def names
=A0 =A0notes =3D []
=A0 [email protected] { |n| notes.push n.name }
=A0 =A0notes
=A0end

def names
[email protected] {|n| n.name}
end
=A0def to_s
=A0 =A0out =3D ""
=A0 =A0names.each { |n| out +=3D n + " " }
=A0 =A0out.strip!
=A0end

def to_s
=A0names.join(" ")
end

Jesus.

Ha, that's probably Ruby 101. Cool, thanks.

@Brian Candler -- I see you took the modal approach. Pretty cool. I like th=
is:

(mode-1).times { a.push(a.shift) }
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,767
Messages
2,569,572
Members
45,046
Latest member
Gavizuho

Latest Threads

Top