[QUIZ] Music Theory (#229)

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

evanhanson.tar.gz (3.74 KB)

···

On Fri, Feb 26, 2010 at 11:36 AM, Daniel Moore <yahivin@gmail.com> wrote:

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

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!

--
-Daniel
http://rubyquiz.strd6.com

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 :confused:
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 :stuck_out_tongue:

···

--
Posted via http://www.ruby-forum.com/\.

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>

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 via http://www.ruby-forum.com/\.

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

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

···

--
-Daniel
http://rubyquiz.strd6.com

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

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

···

On Mon, Mar 1, 2010 at 3:02 PM, Brian Candler <b.candler@pobox.com> wrote:

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 via http://www.ruby-forum.com/\.

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

···

On Mon, Mar 1, 2010 at 4:02 PM, Brian Candler <b.candler@pobox.com> wrote:

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)

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

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 via http://www.ruby-forum.com/\.

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 via http://www.ruby-forum.com/\.

Rick Denatale wrote:

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

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

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

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

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

Cheers,

Brian.

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

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

···

--
Posted via http://www.ruby-forum.com/\.

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 via http://www.ruby-forum.com/.

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

I'm not sure when "susb" would cause an issue -- do you mean an
instance like Gsusb? I don't know that that's a chord, but the G would
be read and the susb would raise an exception as an invalid chord
symbol.

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

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

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

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

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

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

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

···

On Wed, Mar 3, 2010 at 8:50 AM, Ben Rho <dearbenj@yahoo.com> wrote:

Brian Candler wrote:

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

Cheers,

Brian.

I love this topic - because of the music theory!

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

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

Thanks,

Jim Maher

···

--
Posted via http://www.ruby-forum.com/\.

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 via http://www.ruby-forum.com/.

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 Wed, Mar 3, 2010 at 8:13 PM, Evan Hanson <vnhnsn@gmail.com> wrote:

Evan Hanson wrote all single >'d lines

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

···

On Wed, Mar 3, 2010 at 8:50 AM, Ben Rho <dearbenj@yahoo.com> wrote:

--------------------------

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! :slight_smile:

#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 via http://www.ruby-forum.com/\.

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 via http://www.ruby-forum.com/\.

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 Tue, Mar 2, 2010 at 10:10 AM, Jim Maher <jdmaher@jdmaher.com> wrote:

Brian Candler wrote:

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

Cheers,

Brian.

I love this topic - because of the music theory!

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

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

Thanks,

Jim Maher
--
Posted via http://www.ruby-forum.com/\.

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

···

2010/3/3 Jesús Gabriel y Galán <jgabrielygalan@gmail.com>:

On Wed, Mar 3, 2010 at 8:13 PM, Evan Hanson

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.