lib/head_music/pitch.rb in head_music-0.19.1 vs lib/head_music/pitch.rb in head_music-0.19.2
- old
+ new
@@ -12,18 +12,30 @@
delegate :pitch_class, to: :spelling
delegate :semitones, to: :sign, prefix: true, allow_nil: true
delegate :smallest_interval_to, to: :pitch_class
+ delegate :enharmonic_equivalent?, :enharmonic?, to: :enharmonic_equivalence
+ delegate :octave_equivalent?, to: :octave_equivalence
+
def self.get(value)
- from_name(value) || from_number(value)
+ from_pitch_class(value) || from_name(value) || from_number(value)
end
def self.middle_c
get('C4')
end
+ def self.concert_a
+ 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
@@ -47,11 +59,12 @@
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)
+ 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
@@ -82,14 +95,10 @@
def natural
HeadMusic::Pitch.get(to_s.gsub(/[#b]/, ''))
end
- def enharmonic?(other)
- midi_note_number == other.midi_note_number
- end
-
def +(other)
HeadMusic::Pitch.get(to_i + other.to_i)
end
def -(other)
@@ -117,14 +126,30 @@
def natural_steps(num_steps)
HeadMusic::Pitch.get([target_letter_name(num_steps), octave + octaves_delta(num_steps)].join)
end
+ def frequency
+ tuning.frequency_for(self)
+ end
+
private_class_method :new
private
+ def enharmonic_equivalence
+ @enharmonic_equivalence ||= EnharmonicEquivalence.get(self)
+ end
+
+ def octave_equivalence
+ @octave_equivalence ||= OctaveEquivalence.get(self)
+ end
+
+ def tuning
+ @tuning ||= HeadMusic::Tuning.new
+ end
+
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 wrapped_up?(num_steps)
@@ -142,7 +167,57 @@
end
def target_letter_name(num_steps)
@target_letter_name ||= {}
@target_letter_name[num_steps] ||= letter_name.steps(num_steps)
+ end
+
+ # Enharmonic equivalence occurs when two pitches are spelled differently but refer to the same frequency, such as D♯ and E♭.
+ 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
end
end