Class: Clef::Core::Pitch
- Inherits:
-
Object
- Object
- Clef::Core::Pitch
- Includes:
- Comparable
- Defined in:
- lib/clef/core/pitch.rb
Constant Summary collapse
- NOTE_VALUES =
{ c: 0, d: 2, e: 4, f: 5, g: 7, a: 9, b: 11 }.freeze
- VALID_NOTE_NAMES =
NOTE_VALUES.keys.freeze
- ALTERATION_SUFFIX =
{ -2 => "eses", -1 => "es", 0 => "", 1 => "is", 2 => "isis" }.freeze
- SUFFIX_ALTERATION =
ALTERATION_SUFFIX.invert.freeze
- MIDI_CLASS_TO_PITCH =
{ 0 => [:c, 0], 1 => [:c, 1], 2 => [:d, 0], 3 => [:d, 1], 4 => [:e, 0], 5 => [:f, 0], 6 => [:f, 1], 7 => [:g, 0], 8 => [:g, 1], 9 => [:a, 0], 10 => [:a, 1], 11 => [:b, 0] }.freeze
- MIDI_CLASS_TO_FLAT_PITCH =
{ 0 => [:c, 0], 1 => [:d, -1], 2 => [:d, 0], 3 => [:e, -1], 4 => [:e, 0], 5 => [:f, 0], 6 => [:g, -1], 7 => [:g, 0], 8 => [:a, -1], 9 => [:a, 0], 10 => [:b, -1], 11 => [:b, 0] }.freeze
- SCIENTIFIC_PITCH_REGEX =
/\A([A-Ga-g])([#b]{0,2})(-?\d+)\z/- LILYPOND_PITCH_REGEX =
/\A([a-g])(eses|isis|es|is)?([',]*)\z/- TRANSPOSE_PREFERENCES =
%i[sharp flat].freeze
- MIDI_RANGE =
(0..127)
Instance Attribute Summary collapse
-
#alteration ⇒ Object
readonly
Returns the value of attribute alteration.
-
#note_name ⇒ Object
readonly
Returns the value of attribute note_name.
-
#octave ⇒ Object
readonly
Returns the value of attribute octave.
Class Method Summary collapse
Instance Method Summary collapse
- #<=>(other) ⇒ Integer?
- #enharmonic?(other) ⇒ Boolean
-
#initialize(note_name, octave, alteration: 0) ⇒ Pitch
constructor
A new instance of Pitch.
- #semitones ⇒ Integer
- #to_frequency(tuning: 440.0) ⇒ Float
- #to_lilypond ⇒ String
- #to_midi ⇒ Integer
- #transpose(semitones_or_interval, prefer: nil, key_signature: nil) ⇒ Pitch
Constructor Details
#initialize(note_name, octave, alteration: 0) ⇒ Pitch
Returns a new instance of Pitch.
64 65 66 67 68 69 70 71 72 73 |
# File 'lib/clef/core/pitch.rb', line 64 def initialize(note_name, octave, alteration: 0) validate_note_name!(note_name) validate_octave!(octave) validate_alteration!(alteration) @note_name = note_name @octave = octave @alteration = alteration freeze end |
Instance Attribute Details
#alteration ⇒ Object (readonly)
Returns the value of attribute alteration.
59 60 61 |
# File 'lib/clef/core/pitch.rb', line 59 def alteration @alteration end |
#note_name ⇒ Object (readonly)
Returns the value of attribute note_name.
59 60 61 |
# File 'lib/clef/core/pitch.rb', line 59 def note_name @note_name end |
#octave ⇒ Object (readonly)
Returns the value of attribute octave.
59 60 61 |
# File 'lib/clef/core/pitch.rb', line 59 def octave @octave end |
Class Method Details
.parse(str) ⇒ Pitch
131 132 133 134 135 136 137 138 139 140 141 |
# File 'lib/clef/core/pitch.rb', line 131 def self.parse(str) raise ArgumentError, "pitch string must be a String" unless str.is_a?(String) match = LILYPOND_PITCH_REGEX.match(str) raise ArgumentError, "invalid lilypond pitch: #{str}" unless match note_name = match[1].to_sym suffix = match[2] || "" octave = 3 + match[3].count("'") - match[3].count(",") new(note_name, octave, alteration: SUFFIX_ALTERATION.fetch(suffix)) end |
.parse_any(value) ⇒ Pitch
158 159 160 161 162 163 164 165 |
# File 'lib/clef/core/pitch.rb', line 158 def self.parse_any(value) return value if value.is_a?(self) return parse(value.to_s.downcase) if value.is_a?(Symbol) parse_scientific(value) rescue ArgumentError parse(value.to_s.downcase) end |
.parse_scientific(str) ⇒ Pitch
145 146 147 148 149 150 151 152 153 154 |
# File 'lib/clef/core/pitch.rb', line 145 def self.parse_scientific(str) raise ArgumentError, "pitch string must be a String" unless str.is_a?(String) match = SCIENTIFIC_PITCH_REGEX.match(str) raise ArgumentError, "invalid scientific pitch: #{str}" unless match note_name = match[1].downcase.to_sym alteration = {"" => 0, "#" => 1, "##" => 2, "b" => -1, "bb" => -2}.fetch(match[2]) new(note_name, match[3].to_i, alteration: alteration) end |
Instance Method Details
#<=>(other) ⇒ Integer?
118 119 120 121 122 |
# File 'lib/clef/core/pitch.rb', line 118 def <=>(other) return nil unless other.is_a?(self.class) semitones <=> other.semitones end |
#enharmonic?(other) ⇒ Boolean
112 113 114 |
# File 'lib/clef/core/pitch.rb', line 112 def enharmonic?(other) other.is_a?(self.class) && semitones == other.semitones end |
#semitones ⇒ Integer
84 85 86 |
# File 'lib/clef/core/pitch.rb', line 84 def semitones octave * 12 + NOTE_VALUES.fetch(note_name) + alteration end |
#to_frequency(tuning: 440.0) ⇒ Float
90 91 92 |
# File 'lib/clef/core/pitch.rb', line 90 def to_frequency(tuning: 440.0) tuning * (2.0**((to_midi - 69) / 12.0)) end |
#to_lilypond ⇒ String
125 126 127 |
# File 'lib/clef/core/pitch.rb', line 125 def to_lilypond [note_name, ALTERATION_SUFFIX.fetch(alteration), octave_marks].join end |
#to_midi ⇒ Integer
76 77 78 79 80 81 |
# File 'lib/clef/core/pitch.rb', line 76 def to_midi midi = semitones + 12 raise RangeError, "MIDI pitch out of range: #{midi}" unless MIDI_RANGE.cover?(midi) midi end |
#transpose(semitones_or_interval, prefer: nil, key_signature: nil) ⇒ Pitch
98 99 100 101 102 103 104 105 106 107 108 |
# File 'lib/clef/core/pitch.rb', line 98 def transpose(semitones_or_interval, prefer: nil, key_signature: nil) spelling = transpose_preference(prefer, key_signature) target_midi = to_midi + normalize_semitones(semitones_or_interval) raise RangeError, "MIDI pitch out of range: #{target_midi}" unless MIDI_RANGE.cover?(target_midi) octave = (target_midi / 12) - 1 pitch_map = (spelling == :flat) ? MIDI_CLASS_TO_FLAT_PITCH : MIDI_CLASS_TO_PITCH note_name, alteration = pitch_map.fetch(target_midi % 12) self.class.new(note_name, octave, alteration: alteration) end |