class Music::Note

Constants

CHORD_ALIASES
CHORD_INTERVALS
NOTES
NOTE_STRINGS

Attributes

frequency[RW]

Public Class Methods

calculate_frequency(*args) click to toggle source
# 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
calculate_note(frequency, give_flat = false) click to toggle source
# 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
frequency_adjustment(start_frequency, distance) click to toggle source
# 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
nearest_note_frequency(frequency) click to toggle source
# File lib/music/note.rb, line 328
def nearest_note_frequency(frequency)
  Note.calculate_frequency(Note.calculate_note(frequency).join)
end
new(descriptor, assumed_octave = nil) click to toggle source

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
note_distance(note_string1, note_string2) click to toggle source
# 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
parse_note_string(note_string, assumed_octave = nil) click to toggle source
# 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

<=>(other_note) click to toggle source
# File lib/music/note.rb, line 10
def <=>(other_note)
  self.frequency <=> other_note.frequency
end
accidental(give_flat = false) click to toggle source

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
adjust_by_semitones(interval) click to toggle source

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
chord(description) click to toggle source
# 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
distance_to(note) click to toggle source

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
eql?(other_note) click to toggle source
# File lib/music/note.rb, line 16
def eql?(other_note)
  self.frequency == other_note.frequency
end
hash() click to toggle source
# File lib/music/note.rb, line 13
def hash
  self.frequency.hash
end
letter(give_flat = false) click to toggle source

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
major_scale() click to toggle source

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
minor_scale() click to toggle source

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
next()
Alias for: succ
note_string(give_flat = false) click to toggle source

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
octave() click to toggle source

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
prev() click to toggle source

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
succ() click to toggle source

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
Also aliased as: next
to_s() click to toggle source
# File lib/music/note.rb, line 20
def to_s
  self.note_string
end