Class: Fet::Note

Inherits:
Object
  • Object
show all
Includes:
NoteValidations
Defined in:
lib/fet/note.rb

Overview

Class responsible for parsing and validating musical notes from a string, e.g. “Eb”

Constant Summary collapse

ACCIDENTAL_TO_SEMITONES_MAP =
{
  "b" => -1,
  "#" => 1,
  "x" => 2,
}.deep_freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(note) ⇒ Note

Returns a new instance of Note.



21
22
23
24
25
26
27
28
29
30
# File 'lib/fet/note.rb', line 21

def initialize(note)
  self.full_note = note
  validate_full_note!

  self.natural_note = note[0]
  validate_natural_note!

  self.accidental = note[1..]
  validate_accidental!
end

Instance Attribute Details

#accidentalObject

TODO: this class could also potentially handle the octave number - note that e.g. Bb,B,Bx… are in octave 3 while Cb,C,C# are in octave 4



17
18
19
# File 'lib/fet/note.rb', line 17

def accidental
  @accidental
end

#full_noteObject

TODO: this class could also potentially handle the octave number - note that e.g. Bb,B,Bx… are in octave 3 while Cb,C,C# are in octave 4



17
18
19
# File 'lib/fet/note.rb', line 17

def full_note
  @full_note
end

#natural_noteObject

TODO: this class could also potentially handle the octave number - note that e.g. Bb,B,Bx… are in octave 3 while Cb,C,C# are in octave 4



17
18
19
# File 'lib/fet/note.rb', line 17

def natural_note
  @natural_note
end

Class Method Details

.accidental_from_semitone_offset(semitone_offset) ⇒ Object



32
33
34
35
36
37
38
39
# File 'lib/fet/note.rb', line 32

def self.accidental_from_semitone_offset(semitone_offset)
  return "" if semitone_offset.zero?
  return "b" * -semitone_offset if semitone_offset.negative?

  number_of_hashes = (semitone_offset % 2).zero? ? 0 : 1
  number_of_xs = semitone_offset / 2
  return "#" * number_of_hashes + "x" * number_of_xs
end

Instance Method Details

#accidental_to_semitone_offsetObject



100
101
102
# File 'lib/fet/note.rb', line 100

def accidental_to_semitone_offset
  return accidental.chars.map { |char| ACCIDENTAL_TO_SEMITONES_MAP[char] }.sum
end

#degree(root_name) ⇒ Object

NOTE: Note.new(“E”).change_natural_note(“D”) -> Note.new(“Dx”) def change_natural_note(new_natural_note)

semitone_offset = accidental_to_semitone_offset
return Note.new("#{natural_note}#{self.class.accidental_from_semitone_offset(semitone_offset)}") if new_natural_note == natural_note

end



89
90
91
92
93
94
95
96
97
98
# File 'lib/fet/note.rb', line 89

def degree(root_name)
  notes_array = Fet::MusicTheory.notes_of_mode(root_name, "major")
  index = notes_array.index { |note| Note.new(note).natural_note == natural_note }

  degree = index + 1
  degree_note = Note.new(notes_array[index])

  accidental_difference = accidental_to_semitone_offset - degree_note.accidental_to_semitone_offset
  return "#{self.class.accidental_from_semitone_offset(accidental_difference)}#{degree}"
end

#flattened?Boolean

Returns:

  • (Boolean)


108
109
110
# File 'lib/fet/note.rb', line 108

def flattened?
  return accidental.chars.include?("b")
end

#flattened_noteObject

NOTE: performs the following conversions: Fxx -> F#x -> Fx -> F# -> F -> Fb -> Fbb



58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/fet/note.rb', line 58

def flattened_note
  note_as_string = case
                   when accidental.start_with?("x")
                     "#{natural_note}##{accidental[1..]}"
                   when accidental.start_with?("#")
                     "#{natural_note}#{accidental[1..]}"
                   else
                     "#{natural_note}#{accidental}b"
                   end

  return Note.new(note_as_string)
end

#natural?Boolean

Returns:

  • (Boolean)


104
105
106
# File 'lib/fet/note.rb', line 104

def natural?
  return accidental.chars.empty?
end

#normalized_noteObject

NOTE: normalizing the note means:

  • converting the natural note + accidentals such that the remaining accidental is either “b”, “”, or “#”, or

  • if a note name is provided, then convert the accidental such that the natural note matches the pitch of the original



74
75
76
77
78
79
80
81
# File 'lib/fet/note.rb', line 74

def normalized_note
  remaining_semitones = accidental_to_semitone_offset
  next_note = remaining_semitones.positive? ? next_natural_note : previous_natural_note
  next_note_offset = remaining_semitones.positive? ? semitone_offset_to_next_natural_note : semitone_offset_to_previous_natural_note
  return Note.new(full_note) if next_note_offset.abs > remaining_semitones.abs

  return Note.new("#{next_note}#{self.class.accidental_from_semitone_offset(remaining_semitones - next_note_offset)}").normalized_note
end

#sharpened?Boolean

Returns:

  • (Boolean)


112
113
114
# File 'lib/fet/note.rb', line 112

def sharpened?
  return accidental.chars.include?("#") || accidental.chars.include?("x")
end

#sharpened_noteObject

NOTE: performs the following conversions: Fbb -> Fb -> F -> F# ->Fx -> F#x -> Fxx



43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/fet/note.rb', line 43

def sharpened_note
  note_as_string = case
                   when accidental.start_with?("b")
                     "#{natural_note}#{accidental[1..]}"
                   when accidental.start_with?("#")
                     "#{natural_note}x#{accidental[1..]}"
                   else
                     "#{natural_note}##{accidental}"
                   end

  return Note.new(note_as_string)
end