lib/head_music/pitch.rb in head_music-0.20.0 vs lib/head_music/pitch.rb in head_music-0.22.0
- old
+ new
@@ -5,14 +5,18 @@
include Comparable
attr_reader :spelling
attr_reader :octave
- delegate :letter_name, :letter_name_cycle, to: :spelling
+ delegate :letter_name, to: :spelling
+ delegate :series_ascending, :series_descending, to: :letter_name, prefix: true
delegate :sign, :sharp?, :flat?, to: :spelling
delegate :pitch_class, to: :spelling
+ delegate :number, to: :pitch_class, prefix: true
+ delegate :pitch_class_number, to: :natural, prefix: true
delegate :semitones, to: :sign, prefix: true, allow_nil: true
+ delegate :steps_to, to: :letter_name, prefix: true
delegate :smallest_interval_to, to: :pitch_class
delegate :enharmonic_equivalent?, :enharmonic?, to: :enharmonic_equivalence
delegate :octave_equivalent?, to: :octave_equivalence
@@ -29,20 +33,23 @@
get('A4')
end
def self.from_pitch_class(pitch_class)
return nil unless pitch_class.is_a?(HeadMusic::PitchClass)
+
fetch_or_create(pitch_class.sharp_spelling)
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
def self.from_number(number)
return nil unless number == number.to_i
+
spelling = HeadMusic::Spelling.from_number(number)
octave = (number.to_i / 12) - 1
fetch_or_create(spelling, octave)
end
@@ -62,10 +69,11 @@
end
def self.fetch_or_create(spelling, octave = nil)
octave ||= HeadMusic::Octave::DEFAULT
return unless spelling && (-1..9).cover?(octave)
+
@pitches ||= {}
hash_key = [spelling, octave].join
@pitches[hash_key] ||= new(spelling, octave)
end
@@ -92,19 +100,28 @@
def to_i
midi_note_number
end
def natural
- HeadMusic::Pitch.get(to_s.gsub(/[#b]/, ''))
+ HeadMusic::Pitch.get(to_s.gsub(HeadMusic::Sign.matcher, ''))
end
def +(other)
- HeadMusic::Pitch.get(to_i + other.to_i)
+ if other.is_a?(HeadMusic::FunctionalInterval)
+ # return a pitch
+ other.above(self)
+ else
+ # assume value represents an interval in semitones and return another pitch
+ HeadMusic::Pitch.get(to_i + other.to_i)
+ end
end
def -(other)
- if other.is_a?(HeadMusic::Pitch)
+ if other.is_a?(HeadMusic::FunctionalInterval)
+ # return a pitch
+ other.below(self)
+ elsif other.is_a?(HeadMusic::Pitch)
# return an interval
HeadMusic::Interval.get(to_i - other.to_i)
else
# assume value represents an interval in semitones and return another pitch
HeadMusic::Pitch.get(to_i - other.to_i)
@@ -130,20 +147,37 @@
def frequency
tuning.frequency_for(self)
end
+ def steps_to(other)
+ other = HeadMusic::Pitch.get(other)
+ letter_name_steps_to(other) + 7 * octave_changes_to(other)
+ end
+
private_class_method :new
private
+ def octave_changes_to(other)
+ other.octave - octave - octave_adjustment_to(other)
+ end
+
+ def octave_adjustment_to(other)
+ (pitch_class_above?(other) ? 1 : 0)
+ end
+
+ def pitch_class_above?(other)
+ natural_pitch_class_number > other.natural_pitch_class_number
+ end
+
def enharmonic_equivalence
- @enharmonic_equivalence ||= EnharmonicEquivalence.get(self)
+ @enharmonic_equivalence ||= HeadMusic::Pitch::EnharmonicEquivalence.get(self)
end
def octave_equivalence
- @octave_equivalence ||= OctaveEquivalence.get(self)
+ @octave_equivalence ||= HeadMusic::Pitch::OctaveEquivalence.get(self)
end
def tuning
@tuning ||= HeadMusic::Tuning.new
end
@@ -166,58 +200,8 @@
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
-
- # An enharmonic equivalent pitch is the same frequency spelled differently, such as D# and Eb.
- class EnharmonicEquivalence
- def self.get(pitch)
- pitch = HeadMusic::Pitch.get(pitch)
- @enharmonic_equivalences ||= {}
- @enharmonic_equivalences[pitch.to_s] ||= new(pitch)
- end
-
- attr_reader :pitch
-
- delegate :pitch_class, to: :pitch
-
- def initialize(pitch)
- @pitch = HeadMusic::Pitch.get(pitch)
- end
-
- def enharmonic_equivalent?(other)
- other = HeadMusic::Pitch.get(other)
- pitch.midi_note_number == other.midi_note_number && pitch.spelling != other.spelling
- end
-
- alias enharmonic? enharmonic_equivalent?
- alias equivalent? enharmonic_equivalent?
-
- private_class_method :new
- end
-
- # Octave equivalence is the functional equivalence of pitches with the same spelling separated by one or more octaves.
- class OctaveEquivalence
- def self.get(pitch)
- @octave_equivalences ||= {}
- @octave_equivalences[pitch.to_s] ||= new(pitch)
- end
-
- attr_reader :pitch
-
- def initialize(pitch)
- @pitch = pitch
- end
-
- def octave_equivalent?(other)
- other = HeadMusic::Pitch.get(other)
- pitch.spelling == other.spelling && pitch.octave != other.octave
- end
-
- alias equivalent? octave_equivalent?
-
- private_class_method :new
+ @target_letter_name[num_steps] ||= letter_name.steps_up(num_steps)
end
end