lib/head_music/pitch.rb in head_music-0.17.0 vs lib/head_music/pitch.rb in head_music-0.18.0
- old
+ new
@@ -1,5 +1,8 @@
+# frozen_string_literal: true
+
+# A pitch is a named frequency represented by a spelling and an octive.
class HeadMusic::Pitch
include Comparable
attr_reader :spelling
attr_reader :octave
@@ -13,10 +16,14 @@
def self.get(value)
from_name(value) || from_number(value)
end
+ def self.middle_c
+ get('C4')
+ end
+
def self.from_name(name)
return nil unless name == name.to_s
fetch_or_create(HeadMusic::Spelling.get(name), HeadMusic::Octave.get(name).to_i)
end
@@ -27,25 +34,28 @@
fetch_or_create(spelling, octave)
end
def self.from_number_and_letter(number, letter_name)
letter_name = HeadMusic::LetterName.get(letter_name)
- natural_letter_pitch = get(HeadMusic::LetterName.get(letter_name).pitch_class)
- natural_letter_pitch += 12 while (number - natural_letter_pitch.to_i).to_i >= 11
- natural_letter_pitch = get(natural_letter_pitch)
+ natural_letter_pitch = natural_letter_pitch(number, letter_name)
sign_interval = natural_letter_pitch.smallest_interval_to(HeadMusic::PitchClass.get(number))
sign = HeadMusic::Sign.by(:semitones, sign_interval) if sign_interval != 0
spelling = HeadMusic::Spelling.fetch_or_create(letter_name, sign)
fetch_or_create(spelling, natural_letter_pitch.octave)
end
+ def self.natural_letter_pitch(number, letter_name)
+ natural_letter_pitch = get(HeadMusic::LetterName.get(letter_name).pitch_class)
+ natural_letter_pitch += 12 while (number - natural_letter_pitch.to_i).to_i >= 11
+ get(natural_letter_pitch)
+ end
+
def self.fetch_or_create(spelling, octave)
+ return unless spelling && (-1..9).cover?(octave)
@pitches ||= {}
- if spelling && (-1..9).include?(octave)
- hash_key = [spelling, octave].join
- @pitches[hash_key] ||= new(spelling, octave)
- end
+ hash_key = [spelling, octave].join
+ @pitches[hash_key] ||= new(spelling, octave)
end
def initialize(spelling, octave)
@spelling = HeadMusic::Spelling.get(spelling.to_s)
@octave = octave.to_i
@@ -57,65 +67,82 @@
def midi_note_number
(octave + 1) * 12 + letter_name.pitch_class.to_i + sign_semitones.to_i
end
- alias_method :midi, :midi_note_number
- alias_method :number, :midi_note_number
+ alias midi midi_note_number
+ alias number midi_note_number
def to_s
name
end
def to_i
midi_note_number
end
def natural
- HeadMusic::Pitch.get(self.to_s.gsub(/[#b]/, ''))
+ HeadMusic::Pitch.get(to_s.gsub(/[#b]/, ''))
end
def enharmonic?(other)
- self.midi_note_number == other.midi_note_number
+ midi_note_number == other.midi_note_number
end
- def +(value)
- HeadMusic::Pitch.get(self.to_i + value.to_i)
+ def +(other)
+ HeadMusic::Pitch.get(to_i + other.to_i)
end
- def -(value)
- if value.is_a?(HeadMusic::Pitch)
+ def -(other)
+ if other.is_a?(HeadMusic::Pitch)
# return an interval
- HeadMusic::Interval.get(self.to_i - value.to_i)
+ HeadMusic::Interval.get(to_i - other.to_i)
else
# assume value represents an interval in semitones and return another pitch
- HeadMusic::Pitch.get(self.to_i - value.to_i)
+ HeadMusic::Pitch.get(to_i - other.to_i)
end
end
- def ==(value)
- other = HeadMusic::Pitch.get(value)
+ def ==(other)
+ other = HeadMusic::Pitch.get(other)
to_s == other.to_s
end
def <=>(other)
- self.midi_note_number <=> other.midi_note_number
+ midi_note_number <=> other.midi_note_number
end
def scale(scale_type_name = nil)
HeadMusic::Scale.get(self, scale_type_name)
end
def natural_steps(num_steps)
- target_letter_name = self.letter_name.steps(num_steps)
- direction = num_steps >= 0 ? 1 : -1
- octaves_delta = (num_steps.abs / 7) * direction
- if num_steps < 0 && target_letter_name.position > letter_name.position
+ HeadMusic::Pitch.get([target_letter_name(num_steps), octave + octaves_delta(num_steps)].join)
+ end
+
+ private_class_method :new
+
+ private
+
+ def octaves_delta(num_steps)
+ octaves_delta = (num_steps.abs / 7) * (num_steps >= 0 ? 1 : -1)
+ if wrapped_down?(num_steps)
octaves_delta -= 1
- elsif num_steps > 0 && target_letter_name.position < letter_name.position
+ elsif wrapped_up?(num_steps)
octaves_delta += 1
end
- HeadMusic::Pitch.get([target_letter_name, octave + octaves_delta].join)
+ octaves_delta
end
- private_class_method :new
+ def wrapped_down?(num_steps)
+ num_steps.negative? && target_letter_name(num_steps).position > letter_name.position
+ end
+
+ def wrapped_up?(num_steps)
+ num_steps.positive? && target_letter_name(num_steps).position < letter_name.position
+ end
+
+ def target_letter_name(num_steps)
+ @target_letter_name ||= {}
+ @target_letter_name[num_steps] ||= letter_name.steps(num_steps)
+ end
end