Class: OutroRails::Theory::Pitch
- Inherits:
-
Object
- Object
- OutroRails::Theory::Pitch
- Includes:
- Comparable
- Defined in:
- lib/outro_rails/theory/pitch.rb
Overview
A NoteName pinned to an octave - a concrete key you could play. MIDI convention C4 == 60.
The written octave follows the letter, not the sounding pitch: Cb4 sounds as B3 (midi 59) and B#3 sounds as C4 (midi 60).
Constant Summary collapse
- MIDDLE_C_MIDI =
The anchor of the octave numbering: middle C is midi 60 in octave 4.
60- MIDDLE_C_OCTAVE =
4- PARSE_PATTERN =
A letter with optional accidental followed by an octave, which may be negative: "C4", "F#3", "Bb-1".
/\A([A-Ga-g](?:#{NoteName::ACCIDENTAL_PATTERN})?)(-?\d+)\z/
Instance Attribute Summary collapse
-
#note_name ⇒ Object
readonly
The spelled note and its written octave.
-
#octave ⇒ Object
readonly
The spelled note and its written octave.
Class Method Summary collapse
-
.from_midi(midi, prefer: :sharps) ⇒ Object
60 => C4.
-
.parse(value) ⇒ Object
Accepts a Pitch or a string like 'C4', 'F#3', 'Bb-1'.
-
.sounding(note_name, midi) ⇒ Object
The pitch that writes as note_name and sounds at midi - the inverse of #midi for a chosen spelling.
Instance Method Summary collapse
-
#<=>(other) ⇒ Object
Sounding-pitch comparison (Cb4 == B3).
-
#==(other) ⇒ Object
(also: #eql?)
Equality is by spelling, not sound: Cb4 != B3.
-
#hash ⇒ Object
Hashes on spelling too, to stay consistent with #eql?.
-
#initialize(note_name, octave) ⇒ Pitch
constructor
Takes a NoteName (or anything it parses) and a written octave.
-
#inspect ⇒ Object
Shows both the spelling and what it sounds as.
-
#midi ⇒ Object
'C4' => 60, 'F#3' => 54, 'Fb4' => 64, 'Cb4' => 59, 'B#3' => 60.
-
#pitch_class ⇒ Object
Sounding pitch class, 0-11, so Cb4 and B3 both give 11.
-
#to_s ⇒ Object
As written, e.g.
-
#transpose(semitones, prefer: :sharps) ⇒ Object
A new Pitch this many semitones away, respelled from midi - the original spelling is not preserved.
Constructor Details
#initialize(note_name, octave) ⇒ Pitch
Takes a NoteName (or anything it parses) and a written octave. Instances are immutable.
60 61 62 63 64 |
# File 'lib/outro_rails/theory/pitch.rb', line 60 def initialize(note_name, octave) @note_name = NoteName.parse(note_name) @octave = Integer(octave) freeze end |
Instance Attribute Details
#note_name ⇒ Object (readonly)
The spelled note and its written octave
23 24 25 |
# File 'lib/outro_rails/theory/pitch.rb', line 23 def note_name @note_name end |
#octave ⇒ Object (readonly)
The spelled note and its written octave
23 24 25 |
# File 'lib/outro_rails/theory/pitch.rb', line 23 def octave @octave end |
Class Method Details
.from_midi(midi, prefer: :sharps) ⇒ Object
60 => C4. Prefer :sharps (default) or :flats for black keys.
40 41 42 43 |
# File 'lib/outro_rails/theory/pitch.rb', line 40 def self.from_midi(midi, prefer: :sharps) note = NoteName.for_pitch_class(midi, prefer: prefer) new(note, (midi / Interval::SEMITONES_PER_OCTAVE) - 1) end |
.parse(value) ⇒ Object
Accepts a Pitch or a string like 'C4', 'F#3', 'Bb-1'.
26 27 28 29 30 31 32 33 34 35 36 37 |
# File 'lib/outro_rails/theory/pitch.rb', line 26 def self.parse(value) return value if value.is_a?(self) match = value.to_s.strip.match(PARSE_PATTERN) unless match raise ArgumentError, "Invalid pitch: #{value.inspect} " \ '(expected e.g. "C3", "F#4")' end new(NoteName.parse(match[1]), Integer(match[2])) end |
.sounding(note_name, midi) ⇒ Object
The pitch that writes as note_name and sounds at midi - the inverse of #midi for a chosen spelling. The written octave follows the letter, so Cb sounding at 59 is Cb4 (not B3), and B# sounding at 60 is B#3.
49 50 51 52 53 54 55 56 |
# File 'lib/outro_rails/theory/pitch.rb', line 49 def self.sounding(note_name, midi) note_name = NoteName.parse(note_name) octave = ((midi - note_name.accidental_offset) / Interval::SEMITONES_PER_OCTAVE) - 1 new(note_name, octave) end |
Instance Method Details
#<=>(other) ⇒ Object
Sounding-pitch comparison (Cb4 == B3).
107 108 109 |
# File 'lib/outro_rails/theory/pitch.rb', line 107 def <=>(other) midi <=> other.midi end |
#==(other) ⇒ Object Also known as: eql?
Equality is by spelling, not sound: Cb4 != B3.
94 95 96 97 98 |
# File 'lib/outro_rails/theory/pitch.rb', line 94 def ==(other) other.is_a?(self.class) && note_name == other.note_name && octave == other.octave end |
#hash ⇒ Object
Hashes on spelling too, to stay consistent with #eql?
102 103 104 |
# File 'lib/outro_rails/theory/pitch.rb', line 102 def hash [ note_name, octave ].hash end |
#inspect ⇒ Object
Shows both the spelling and what it sounds as
89 90 91 |
# File 'lib/outro_rails/theory/pitch.rb', line 89 def inspect "#<#{self.class.name} #{self} midi=#{midi}>" end |
#midi ⇒ Object
'C4' => 60, 'F#3' => 54, 'Fb4' => 64, 'Cb4' => 59, 'B#3' => 60
67 68 69 70 |
# File 'lib/outro_rails/theory/pitch.rb', line 67 def midi (octave + 1) * Interval::SEMITONES_PER_OCTAVE + note_name.letter_natural_midi_offset end |
#pitch_class ⇒ Object
Sounding pitch class, 0-11, so Cb4 and B3 both give 11.
73 74 75 |
# File 'lib/outro_rails/theory/pitch.rb', line 73 def pitch_class midi % Interval::SEMITONES_PER_OCTAVE end |
#to_s ⇒ Object
As written, e.g. "F#3"
84 85 86 |
# File 'lib/outro_rails/theory/pitch.rb', line 84 def to_s "#{note_name}#{octave}" end |
#transpose(semitones, prefer: :sharps) ⇒ Object
A new Pitch this many semitones away, respelled from midi - the original spelling is not preserved.
79 80 81 |
# File 'lib/outro_rails/theory/pitch.rb', line 79 def transpose(semitones, prefer: :sharps) self.class.from_midi(midi + semitones, prefer: prefer) end |