Skip to content

Instantly share code, notes, and snippets.

@kevbuchanan
Created September 19, 2013 22:18
Show Gist options
  • Select an option

  • Save kevbuchanan/6630643 to your computer and use it in GitHub Desktop.

Select an option

Save kevbuchanan/6630643 to your computer and use it in GitHub Desktop.
class Chord
NOTE_POSITIONS = {
'C' => 0,
'B#' => 0,
'Db' => 1,
'C#' => 1,
'D' => 2,
'Eb' => 3,
'D#' => 3,
'Fb' => 4,
'E' => 4,
'F' => 5,
'E#' => 5,
'Gb' => 6,
'F#' => 6,
'G' => 7,
'Ab' => 8,
'G#' => 8,
'A' => 9,
'Bb' => 10,
'A#' => 10,
'Cb' => 11,
'B' => 11
}
TRIADS = {
major: [:major_third, :minor_third],
minor: [:minor_third, :major_third],
diminished: [:minor_third, :minor_third],
augmented: [:major_third, :major_third]
}
attr_reader :notation, :root, :quality, :extended
def initialize(notation)
raise "Chord must be formatted like Cmaj, C#min, Dbdim, or Faug with an optional 7" unless notation.match(/^([A-G][#b]?)(min|maj|dim|aug)(7)?$/)
@notation = notation
@root = $1
@quality = get_quality($2)
@extended = $3
end
def get_quality(abbreviation)
case abbreviation
when 'maj'
:major
when 'min'
:minor
when 'dim'
:diminished
when 'aug'
:augmented
end
end
def notes
base = TRIADS[@quality].each_with_object([@root]) do |third, notes|
base = notes.last
notes << self.send(third, base)
end
base << extended if @extended
base
end
def major_third(root)
keys = get_interval(root, 4)
root[1] == 'b' ? keys.first : keys.last
end
def minor_third(root)
keys = get_interval(root, 3)
root[1] == '#' ? keys.last : keys.first
end
def extended
if @quality == :minor
keys = get_interval(@root, 10)
@root[1] == '#' ? keys.last : keys.first
else
keys = get_interval(@root, 11)
@root[1] == 'b' ? keys.first : keys.last
end
end
def get_interval(root, interval)
root_position = position_of(root)
new_position = (root_position + interval) % 12
keys_of(new_position)
end
def position_of(note)
NOTE_POSITIONS[note]
end
def keys_of(position)
NOTE_POSITIONS.map{ |key, value| value == position ? key : nil }.compact
end
end
require 'rspec'
require_relative 'music'
describe Chord do
let(:major_chord) { Chord.new('Cmaj') }
let(:major_chord_notes) { ['C', 'E', 'G'] }
let(:minor_chord) { Chord.new('Cmin') }
let(:minor_chord_notes) { ['C', 'Eb', 'G'] }
let(:diminished_chord) { Chord.new('Cdim') }
let(:diminished_chord_notes) { ['C', 'Eb', 'Gb'] }
let(:augmented_chord) { Chord.new('Caug') }
let(:augmented_chord_notes) { ['C', 'E', 'G#'] }
it "intializes with properly formatted notation" do
expect{ major_chord }.to_not raise_error
end
it "raise an error with improperly formatted notation" do
expect{ Chord.new('Cmajor7th') }.to raise_error
end
it "has a root" do
expect(major_chord.root).to eq('C')
end
it "has a quality" do
expect(major_chord.quality).to eq(:major)
end
describe "#notes" do
it "returns an array of notes" do
expect(major_chord.notes).to be_a(Array)
end
context "major chord" do
it "returns the correct notes" do
expect(major_chord.notes).to eq(major_chord_notes)
end
end
context "minor chord" do
it "returns the correct notes" do
expect(minor_chord.notes).to eq(minor_chord_notes)
end
end
context "diminished chord" do
it "returns the correct notes" do
expect(diminished_chord.notes).to eq(diminished_chord_notes)
end
end
context "augmented chord" do
it "returns the correct notes" do
expect(augmented_chord.notes).to eq(augmented_chord_notes)
end
end
it "returns the 7th" do
expect( Chord.new('Cmaj7').notes.last ).to eq('B')
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment