Skip to content

Instantly share code, notes, and snippets.

@joshuaclayton
Created December 2, 2008 04:37
Show Gist options
  • Save joshuaclayton/30997 to your computer and use it in GitHub Desktop.
Save joshuaclayton/30997 to your computer and use it in GitHub Desktop.
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