class Music::Note
Constants
- CHORD_ALIASES
- CHORD_INTERVALS
- NOTES
- NOTE_STRINGS
Attributes
Public Class Methods
# File lib/music/note.rb, line 288 def calculate_frequency(*args) case args.size when 1 letter, accidental, octave = parse_note_string(args[0]) when 2 letter, accidental, octave = parse_note_string(args[0], args[1]) when 3 letter, accidental, octave = args else raise ArgumentError, "Invalid octave of arguments" end distance = note_distance('A4', "#{letter}#{accidental}#{octave}") frequency_adjustment(440.0, distance) end
# File lib/music/note.rb, line 305 def calculate_note(frequency, give_flat = false) # Would like to use #log(frequency / 440.0, 2), but would like to support Ruby 1.8 frequency_log_base_2 = Math.log(frequency / 440.0) / Math.log(2) distance = (NOTES.size * frequency_log_base_2).round index = 9 + distance # 9 is index for A octave = 4 + (index / NOTES.size) # 4 is because we're using A4 index = (index % NOTES.size) parts = "#{NOTES[index]}".split('/') note = if give_flat parts.last else parts.first end "#{note}#{octave}" note_parts = note.split('') note_parts + (note_parts.size == 1 ? [nil] : []) + [octave.to_i] end
# File lib/music/note.rb, line 281 def frequency_adjustment(start_frequency, distance) result = (start_frequency * (2.0 ** (distance.to_f / NOTES.size))) # Would like to use #round(2), but want to support Ruby 1.8 (result * 100.0).round / 100.0 end
# File lib/music/note.rb, line 328 def nearest_note_frequency(frequency) Note.calculate_frequency(Note.calculate_note(frequency).join) end
Creates a new note
@param [String, Numeric] descriptor Either a string describing the note (e.g. 'C#4') or a number giving the note's frequency (e.g. 440) @param [Numeric, nil] assumed_octave If no octive is given in the descriptor, use this @returns [Note] Note
specified
# File lib/music/note.rb, line 29 def initialize(descriptor, assumed_octave = nil) self.frequency = if descriptor.is_a? Numeric Note.nearest_note_frequency(descriptor) else Note.calculate_frequency(descriptor, assumed_octave) end end
# File lib/music/note.rb, line 257 def note_distance(note_string1, note_string2) letter1, accidental1, octave1 = parse_note_string(note_string1) letter2, accidental2, octave2 = parse_note_string(note_string2) get_index = Proc.new do |letter, accidental| NOTES.index do |note| regex = case accidental when '#' then /^#{letter}#/ when 'b' then /#{letter}b$/ else /^#{letter}$/ end note.match(regex) end end index1 = get_index.call(letter1, accidental1) index2 = get_index.call(letter2, accidental2) (index2 - index1) + ((octave2.to_i - octave1.to_i) * NOTES.size) end
# File lib/music/note.rb, line 246 def parse_note_string(note_string, assumed_octave = nil) match = note_string.match(/^([A-Ga-g])([#b]?)([0-8]?)$/) raise ArgumentError, "Did not recognize note string: #{note_string}" if !match raise ArgumentError, "No octave found or specified" if match[3].empty? && assumed_octave.nil? raise ArgumentError if match[3].to_i > 8 || (assumed_octave && !(0..8).include?(assumed_octave)) octave = match[3].empty? ? assumed_octave : match[3] [match[1].upcase, match[2] == '' ? nil : match[2], octave.to_i] end
Public Instance Methods
# File lib/music/note.rb, line 10 def <=>(other_note) self.frequency <=> other_note.frequency end
Returns the accidental portion of the note e.g. '#' or 'b'
@param [boolean] give_flat Should the result give a flat? (defaults to giving a sharp) @return [String] The resulting accidental
# File lib/music/note.rb, line 60 def accidental(give_flat = false) Note.calculate_note(self.frequency, give_flat)[1] end
Return another note adjusted by a given interval
@param [Fixnum] interval Number of semitones to adjust by @return [Note] Resulting note after adjustment
# File lib/music/note.rb, line 98 def adjust_by_semitones(interval) Note.new(Note.frequency_adjustment(self.frequency, interval)) end
# File lib/music/note.rb, line 224 def chord(description) description = :major if description.to_s.empty? description = description.to_s description.downcase! unless ['M', 'M7'].include?(description) description.gsub!(/[\s\-]+/, '_') description = description.to_sym intervals = CHORD_INTERVALS[description] || CHORD_INTERVALS[CHORD_ALIASES[description]] if intervals Chord.new([self] + intervals.collect {|interval| self.send(interval) }) end end
Return the distance (in semitones) to a note
@return [Fixnum] Number of semitones
# File lib/music/note.rb, line 90 def distance_to(note) Note.note_distance(self.note_string, note.note_string) end
# File lib/music/note.rb, line 16 def eql?(other_note) self.frequency == other_note.frequency end
# File lib/music/note.rb, line 13 def hash self.frequency.hash end
Returns the letter portion of the note e.g. 'C'
@param [boolean] give_flat Should the result be based on giving a flat? (defaults to giving a sharp) @return [String] The resulting note letter
# File lib/music/note.rb, line 51 def letter(give_flat = false) Note.calculate_note(self.frequency, give_flat)[0] end
Uses note as key to give major scale
@returns [Array<Note>] Notes in major scale
# File lib/music/note.rb, line 127 def major_scale [self, self.major_second, self.major_third, self.perfect_fourth, self.perfect_fifth, self.major_sixth, self.major_seventh, ] end
Uses note as key to give minor scale
@returns [Array<Note>] Notes in minor scale
# File lib/music/note.rb, line 141 def minor_scale [self, self.major_second, self.minor_third, self.perfect_fourth, self.perfect_fifth, self.minor_sixth, self.minor_seventh, ] end
Returns string representing note with letter, accidental, and octave number e.g. 'C#5'
@param [boolean] give_flat Should the result give a flat? (defults to giving a sharp) @return [String] The resulting note string
# File lib/music/note.rb, line 42 def note_string(give_flat = false) Note.calculate_note(self.frequency, give_flat).join end
Returns the octive number of the note e.g. 4
@return [Fixnum] The resulting octive number
# File lib/music/note.rb, line 68 def octave Note.calculate_note(self.frequency)[2] end
Return the previous note (adjusted by one semitone down)
@return [Note] The previous note
# File lib/music/note.rb, line 75 def prev Note.new(Note.frequency_adjustment(self.frequency, -1)) end
Return the next note (adjusted by one semitone up)
@return [Note] The next note
# File lib/music/note.rb, line 82 def succ Note.new(Note.frequency_adjustment(self.frequency, 1)) end
# File lib/music/note.rb, line 20 def to_s self.note_string end