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