Forum: Ruby Music Theory (#229)

Posted by Daniel X Moore (yahivin)
on 2010-02-26 18:39
(Received via mailing list)
-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

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!
Posted by Evan Hanson (Guest)
on 2010-03-01 18:39
Attachment: evanhanson.tar.gz (3,74 KB)
(Received via mailing list)
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
Posted by Brian Candler (candlerb)
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)
Posted by Evan Hanson (Guest)
on 2010-03-02 01:38
(Received via mailing list)
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?
Posted by Brian Candler (candlerb)
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.
Posted by Rick Denatale (rdenatale)
on 2010-03-02 14:02
(Received via mailing list)
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
Posted by Brian Candler (candlerb)
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.
Posted by Jim Maher (jdmaher)
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
Posted by Brian Candler (candlerb)
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"
Posted by Evan Hanson (Guest)
on 2010-03-02 22:11
(Received via mailing list)
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.
Posted by David Springer (dnspringer)
on 2010-03-02 23:38
Attachment: Chords_DNS.rb (4,29 KB)
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.
Posted by Hal Fulton (Guest)
on 2010-03-02 23:55
(Received via mailing list)
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
Posted by Ben Rho (bmr)
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
Posted by Daniel X Moore (yahivin)
on 2010-03-03 05:54
(Received via mailing list)
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!
Posted by Brian Candler (candlerb)
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.
Posted by Ben Rho (bmr)
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.
Posted by Brian Candler (candlerb)
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
Posted by Brian Candler (candlerb)
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
Posted by Evan Hanson (Guest)
on 2010-03-03 20:16
(Received via mailing list)
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?
Posted by Jesús Gabriel y Galán (Guest)
on 2010-03-03 20:59
(Received via mailing list)
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.
Posted by Evan Hanson (Guest)
on 2010-03-03 21:49
(Received via mailing list)
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) }
Posted by Ben Rho (bmr)
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
Posted by Ben Rho (bmr)
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'..
Posted by Brian Candler (candlerb)
on 2010-03-08 15:08
Attachment: chords-bc.rb (5,11 KB)
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#
Posted by Brian Candler (candlerb)
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.
Posted by Alexander Jesner (Guest)
on 2010-03-08 18:45
(Received via mailing list)
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?
Posted by Brian Candler (candlerb)
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
Posted by Jean Lazarou (jeanlazarou)
on 2010-03-08 20:55
Attachment: chord.zip (28,5 KB)
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...
Posted by Daniel X Moore (yahivin)
on 2010-04-03 19:30
(Received via mailing list)
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
No account? Register here.