Created
December 2, 2008 04:37
-
-
Save joshuaclayton/30997 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class Array | |
def slide(direction) | |
case direction.to_sym | |
when :left | |
self << self.shift | |
when :right | |
self.insert 0, self.pop | |
else | |
raise "Unknown direction" | |
end | |
end | |
end | |
module Music | |
class Note | |
NOTES = ["A", "Bb", "B", "C", "C#", "D", "Eb", "E", "F", "F#", "G", "Ab"] | |
INTERVALS = { | |
"R" => 0, | |
"b2" => 1, | |
"2" => 2, | |
"b3" => 3, | |
"3" => 4, | |
"4" => 5, | |
"#4" => 6, | |
"b5" => 6, | |
"5" => 7, | |
"#5" => 8, | |
"b6" => 8, | |
"6" => 9, | |
"bb7" => 9, | |
"b7" => 10, | |
"7" => 11, | |
"8" => 12, | |
"O" => 12, | |
"9b" => 13, | |
"9" => 14, | |
"#9" => 15, | |
"11" => 17, | |
"#11" => 18, | |
"13" => 21 | |
} | |
attr_accessor :note, :octave | |
def initialize(note, options = {}) | |
@note = note | |
@octave = options[:octave] || false | |
end | |
def transpose(interval) | |
notes = Music::Note::NOTES.clone | |
Music::Note::NOTES.index(@note).times { notes.slide(:left) } | |
notes = (notes << notes.clone).flatten | |
idx = Music::Note::INTERVALS.keys.include?(interval) ? Music::Note::INTERVALS[interval] : interval | |
Music::Note.new(notes[idx], :octave => (idx >= 12)) | |
end | |
def octave? | |
@octave | |
end | |
def to_s | |
"#{@note}#{"(o)" if self.octave?}" | |
end | |
def ==(obj) | |
self.note == obj.note && self.octave == obj.octave | |
end | |
def <=>(other) | |
return 0 if self == other | |
return -1 if Music::Note::NOTES.index(self.note) < Music::Note::NOTES.index(other.note) | |
1 | |
end | |
end | |
class Chord | |
CHORDS = { | |
"major" => ["R", "3", "5"], | |
"5" => ["R", "5"], | |
"6" => ["R", "3", "5", "6"], | |
"7" => ["R", "3", "5", "b7"], | |
"7sus" => ["R", "4", "5", "b7"], | |
"7+5" => ["R", "3", "#5", "b7"], | |
"9" => ["R", "3", "5", "b7", "9"], | |
"11" => ["R", "3", "5", "b7", "9", "11"], | |
"13" => ["R", "3", "5", "b7", "9", "13"], | |
"dim" => ["R", "b3", "b5"], | |
"dim7" => ["R", "b3", "b5", "bb7"], | |
"m" => ["R", "b3", "5"], | |
"m6" => ["R", "b3", "5", "6"], | |
"m7" => ["R", "b3", "5", "b7"], | |
"m9" => ["R", "b3", "5", "b7", "9"], | |
"maj7" => ["R", "3", "5", "7"], | |
"maj9" => ["R", "3", "5", "7", "9"], | |
"sus2" => ["R", "2", "5"], | |
"sus4" => ["R", "4", "5"], | |
"aug" => ["R", "3", "#5"] | |
} | |
attr_reader :notes, :root, :chord | |
def initialize(note, chord) | |
raise "Chord not found" unless Music::Chord::CHORDS.keys.include?(chord) | |
@notes = [] | |
@root = note.is_a?(Music::Note) ? note : Music::Note.new(note) | |
@chord = chord | |
chart = Music::Chord::CHORDS[@chord] | |
chart.each do |interval| | |
@notes << root.transpose(interval) | |
end | |
end | |
def print | |
"#{self.root}#{self.chord} - #{self.notes.join(", ")}" | |
end | |
end | |
class Scale | |
SCALES = { | |
"Ionian" => ["R", "2", "3", "4", "5", "6", "7"], | |
"Dorian" => ["R", "2", "b3", "4", "5", "6", "b7"], | |
"Phrygian" => ["R", "b2", "b3", "4", "5", "b6", "b7"], | |
"Lydian" => ["R", "2", "3", "#4", "5", "6", "7"], | |
"Mixolydian" => ["R", "2", "3", "4", "5", "6", "b7"], | |
"Aeolian" => ["R", "2", "b3", "4", "5", "b6", "b7"], | |
"Locrian" => ["R", "b2", "b3", "4", "b5", "b6", "b7"], | |
"Pentatonic" => ["R", "2", "3", "5", "6"], | |
"Pentatonic Minor" => ["R", "b3", "4", "5", "b7"], | |
"Pentatonic Blues" => ["R", "b3", "4", "b5", "5", "b7"] | |
} | |
attr_reader :notes, :root, :scale | |
def initialize(note, scale) | |
raise "scale not found" unless Music::Scale::SCALES.keys.include?(scale) | |
@notes = [] | |
@root = note.is_a?(Music::Note) ? note : Music::Note.new(note) | |
@scale = scale | |
Music::Scale::SCALES[scale].each do |interval| | |
@notes << root.transpose(interval) | |
end | |
end | |
def print | |
"#{self.root} #{self.scale} - #{self.notes.join(", ")}" | |
end | |
end | |
class Instrument | |
TUNINGS = { | |
:standard => ["E", "A", "D", "G", "B", "E"], | |
:drop_d => ["D", "A", "D", "G", "B", "E"] | |
} | |
attr_reader :strings | |
def initialize(tuning) | |
raise "tuning not found" unless Music::Instrument::TUNINGS.keys.include?(tuning) | |
@strings = [] | |
Music::Instrument::TUNINGS[tuning].each do |note| | |
@strings << Music::InstrumentString.new(note) | |
end | |
end | |
def play(chord) | |
notes_required = chord.notes | |
played_notes = [] | |
self.strings.map do |s| | |
string_opts = {} | |
string_opts[:required] = if played_notes.include?(chord.root) | |
notes_required | |
else | |
[chord.root] | |
end | |
fret = s.note_for_chord(chord, string_opts) | |
played_notes << s.played_note | |
fret | |
end | |
end | |
def chart(chord) | |
chart_str = "" | |
play(chord).each {|fret_no| chart_str << (fret_no.nil? ? "X" : fret_no.to_s) } | |
chart_str | |
end | |
end | |
class InstrumentString | |
attr_reader :root | |
attr_accessor :fret | |
def initialize(note) | |
@fret = nil | |
@root = note.is_a?(Music::Note) ? note : Music::Note.new(note) | |
end | |
def played_note | |
return nil unless self.fret | |
@root.transpose(self.fret) | |
end | |
def note_for_chord(chord, options = {}) | |
min_fret = options.delete(:min_fret) || 0 | |
max_fret = options.delete(:max_fret) || (min_fret + 7) | |
required_notes = options.delete(:required) || [] | |
notes = Music::Note::NOTES.clone | |
(Music::Note::NOTES.index(@root.note)).times { notes.slide(:left) } | |
notes = (notes << notes.clone << notes.clone).flatten | |
available_notes = notes[min_fret..max_fret] | |
possible_notes = (chord.notes.map(&:note) & available_notes).sort | |
possible_notes = possible_notes.reverse if possible_notes.first < notes[min_fret] | |
played_note = if required_notes.map(&:note).any? | |
possible_notes & required_notes.map(&:note) | |
else | |
possible_notes | |
end.first | |
# played_note = (chord.notes.map(&:note) & available_notes & required_notes.map(&:note)).first | |
self.fret = available_notes.index(played_note) if played_note | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment