-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- 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!
on 2010-02-26 18:39
on 2010-03-01 18:39
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
on 2010-03-01 22:02
Evan Hanson 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)
on 2010-03-02 01:38
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 2010-03-02 10:04
Evan Hanson 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.
on 2010-03-02 14:02
On Mon, Mar 1, 2010 at 4:02 PM, Brian Candler <b.candler@pobox.com> 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
on 2010-03-02 14:48
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 :-)
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.
on 2010-03-02 17:09
Brian Candler wrote: > > 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
on 2010-03-02 21:51
Jim Maher 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 2010-03-02 22:11
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.
on 2010-03-02 23:38
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.
on 2010-03-02 23:55
On Tue, Mar 2, 2010 at 4:38 PM, David Springer <dnspringer@gmail.com> 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
on 2010-03-03 02:51
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 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
on 2010-03-03 05:54
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!
on 2010-03-03 10:42
David Springer 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.
on 2010-03-03 15:46
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 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 Springer 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 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.
on 2010-03-03 17:34
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
on 2010-03-03 17:39
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
on 2010-03-03 20:16
Thanks for the pointers. Maybe you could clarify some things below: On Wed, Mar 3, 2010 at 8:50 AM, Ben Rho <dearbenj@yahoo.com> 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 2010-03-03 20:59
On Wed, Mar 3, 2010 at 8:13 PM, Evan Hanson <vnhnsn@gmail.com> 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.
on 2010-03-03 21:49
2010/3/3 Jesús Gabriel y Galán <jgabrielygalan@gmail.com>: > > Jesus. > Ha, that's probably Ruby 101. Cool, thanks. @Brian Candler -- I see you took the modal approach. Pretty cool. I like this: (mode-1).times { a.push(a.shift) }
on 2010-03-04 01:58
Evan Hanson wrote all single >'d lines > On Wed, Mar 3, 2010 at 8:50 AM, Ben Rho <dearbenj@yahoo.com> 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. Yes, I do mean like Gsusb. Susb wasn't a valid example, but if it was actually the name of a type of chord, it would be counted as Gbsusb. Your code (with my comments): note[1..-1].scan(/./).each do |n| #for every character in the input except the first store it in n and do the following ( btw an easier way is simply note[1..-1].each('') ): if n == 'b' then flat! #if n is b then flat the base note elsif n == '#' then sharp! #else, if n is # then sharp the base note else raise ArgumentError, 'Invalid note name!' end #otherwise rase an error end #endfor For Gbsusb (assuming susb is a valid chord type) get the second character, which is b, and if it is b (which it is), flat the base note. Then do the same for s, then u, then s, then b (and flat it again), etc. On second thought, the sus should hit the ArgumentError, hm.. Can anyone explain that? >> 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. #...# @name = note[0,1] #if note="Hello World" this sets @name to 'H' #...# #apply sharps and flats to @name #...# def to_s @name end #...# To me it seems that the base note is stored in @name, which is what is returned in to_s - so it would just return the base note. > 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? See Jesús' answer. One thing to add - array * str is the same as array.join(str): [1,2,3]* #=> ERROR [1,2,3]*'' #=> '123' [1,2,3]*' ' #=> '1 2 3' [1,2,3].join #=> '123' [1,2,3].join(' ') #=> '1 2 3' Wait, I just realized some of my arguments are invalid! For some reason I've been looking at the Note class all along, instead of the Chord class! *facepalm* Sorry about that.. -------------------------- I started re-doing this Quiz from scratch about an hour ago without looking at anyone else's code, unfortunately, the results look about the same as David Springer's code (mine follows). > # Sharps the note in place... Comments are fun! I agree! They're also useful for anyone trying to read or debug your code! :) #I'll be making this library bigger, given enough time. I'd copy David Springer's (he has given me his permission), but this is case insensitive and doesn't have optional notes). chord_library={ '7' => [3,4,3], 'major' => [4,3], 'maj' => [4,3], 'major7' => [4,3,4], 'maj7' => [4,3,4], 'minor' => [3,4], 'min' => [3,4], 'minor7' => [3,4,3], 'min7' => [3,4,3], 'm7' => [3,4,3], 'sus' => [7], 'sus2' => [2,5], 'sus4' => [5,2] } rotated_notes = [] while !((chord=gets.to_s.chomp.downcase).empty?) base_note = chord[/^[abdeg]b/] if (base_note == nil) base_note = chord[/^[acdfg]#/] if (base_note == nil) base_note = chord[/^[a-g]/] if (base_note == nil) puts 'ERROR: Invalid base note' next end end end note_list = ((base_note[1,1]=='b') ? (%w(ab a bb b c db d eb e f gb g)) : (%w(a a# b c c# d d# e f f# g g#))) i=-1 rotated_notes = note_list.map do i += 1 note_list[(i + note_list.index(base_note))%12] end variation = ($'.empty?) ? ('major') : ($') if (chord_library[variation] == nil) puts 'ERROR: Chord not found' next end out = 'The notes in the chord ' + chord.capitalize + ' are: ' + base_note.capitalize note_tally = 0 chord_library[variation].each do |note| if (note<0) next end out << ' ' + rotated_notes[((note+note_tally)%12)].capitalize note_tally += note end puts out end
on 2010-03-04 01:59
> #I'll be making this library bigger, given enough time.
Sorry for the double post, but by 'library' I meant 'chord dictionary'..
on 2010-03-08 15:08
Here's an updated version. I now explicitly give out the scales as well as chords, and have attempted to optimise the "spelling" of 8-note scales (which don't sit naturally on the 7 letters A-G). I made some simplifications too. Sample interaction: C7 Scale: C D E F G A Bb Chord: C E G Bb C7alt Scale: C Db Eb Fb Gb Ab Bb Chord: C Eb Gb Bb C7b9 Scale: C Db Eb E F# G A Bb Chord: C E G Bb Db Eb F# A Cdim7 Scale: C D Eb F Gb Ab A B Chord: C Eb Gb A C#dim7 Scale: C# D# E F# G A A# B# Chord: C# E G A#
on 2010-03-08 15:17
And just to clarify what I mean by 'spelling', I mean the sequence of letters and accidentals which make a scale. On a 7-note scale each scale corresponds to the next letter. Example: G# major Correct spelling: G# A# B# C# D# E# Fx (Fx = F double-sharp) It would be wrong to write something like: G# A# C C# D# F G because of the gaps and duplications of letters. This is the reason why a C7 chord is C E G Bb not C E G A#. It's because the associated scale is C D E F G A Bb This scale is called the 'Mixolydian mode' (named by the ancient Greeks, I believe). It also happens to be the notes of F major with a different starting point, so fortunately you can re-use your major scale finger patterns :-) Regards, Brian.
on 2010-03-08 18:45
On 03/08/2010 15:08, Brian Candler wrote: > Here's an updated version. I now explicitly give out the scales as well > as chords, and have attempted to optimise the "spelling" of 8-note > scales (which don't sit naturally on the 7 letters A-G). I made some > simplifications too. C Scale: C D E F G A B Chord: C E G B Why does the Chord contain a B?
on 2010-03-08 20:13
Alexander Jesner wrote: > C > Scale: C D E F G A B > Chord: C E G B > > Why does the Chord contain a B? Explained earlier in the thread. If you've just joined: http://www.ruby-forum.com/topic/204996 But if you want traditional triads, it's easy enough to reconfigure the program so that it returns, say, C => C E G CMaj7 => C E G B
on 2010-03-08 20:55
Here is a partial solution, it does not support all type of chord
symbols (see
comments in the main script).
The main script is chord_translator.rb, it contains the 'chord' API and
also
translates chords when run from the command line.
Usage: ruby chord_translator.rb {chord-symbols}
Example:
>ruby chord_translator.rb C Cdim7 Am F#+7
C => C E G
Cdim7 => C Eb Gb A
Am => A C E
F#+7 => F# A# D E
The chord_translator_test.rb file contain tests validating the currently
supported chord symbols and also presents the APIs implemented by
chord_translator.rb.
The other scripts are files for the GUI version of the tool, they use
the
current trunk version of Swiby (a layer on top of Java/Swing) and need
jRuby.
(the first two scripts can run with any Ruby interpreter)
The startup command is: jruby -I<swiby-trunk>/core/lib
chord_translator_ui.rb
(chord_translator_ui.rb contains the GUI definition and the GUI logic -
pretty
basic -, score_painter.rb paints the musical notation - staff - and
styles.rb
defines the CSS-like styles of the GUI). It uses a font to draw the
musical symbols, downloaded from
http://simplythebest.net/fonts/fonts/musical_symbols.html
The attached ZIP-file contains all the scripts and an image file
presenting a run of the GUI version.
Regards to all...
on 2010-04-03 19:30
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:
- What are the benefits of using them?
- Should they be easy to understand?
- Should they be intuitive to use?
- Should they be helpful?
- Should they allow doing different things or only a specific one?
- Should the code extend existing classes?
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.
Music Theory (#229) -
Solutions<http://rubyquiz.strd6.com/quizzes/229.tar.gz>
Please log in before posting. Registration is free and takes only a minute.
Existing account
(Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
Log in with Google account | Log in with Yahoo account
No account? Register here.