Class: Music::Note

Inherits:
Object
  • Object
show all
Includes:
Comparable
Defined in:
lib/music/note.rb

Constant Summary collapse

CHROMATIC_SCALE =
%w[C C/D D D/E E F F/G G G/A A A/B B]
DIATONIC_SCALE =
%w[C D E F G A B]
ACCIDENTALS =
%w[# ♭]
REGEXP =
/
  (?<letter>     [CDEFGAB] )
  (?<accidental> [#♭b]?    )
  (?<octave>     (-?\d+)?  )
/x

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name) ⇒ Note

Returns a new instance of Note.

Examples:

Note.new("C")
Note.new("C#")
Note.new("C♭") #=> or Note.new("Cb")

Note.new("C1")
Note.new("B-1")

Parameters:

  • name (String)

    Consists of a letter (C, D, E, F, G, A, B), accidental (# or ♭) and octave (any integer).



51
52
53
54
55
56
57
58
59
# File 'lib/music/note.rb', line 51

def initialize(name)
  unless match = name.match(/^#{REGEXP}$/)
    raise ArgumentError, "invalid note name: #{name} (example: C#1)"
  end

  @letter     = match[:letter]
  @accidental = match[:accidental].sub("b", "♭") unless match[:accidental].empty?
  @octave     = match[:octave].to_i              unless match[:octave].empty?
end

Instance Attribute Details

#accidentalString (readonly)

# or ♭.

Returns:

  • (String)


31
32
33
# File 'lib/music/note.rb', line 31

def accidental
  @accidental
end

#letterString (readonly)

C, D, E, F, G, A or B.

Returns:

  • (String)


25
26
27
# File 'lib/music/note.rb', line 25

def letter
  @letter
end

#octaveInteger (readonly)

An integer.

Returns:

  • (Integer)


37
38
39
# File 'lib/music/note.rb', line 37

def octave
  @octave
end

Instance Method Details

#-(other) ⇒ Music::Interval

Examples:

Note.new("E") - Note.new("C") #=> #<Music::Interval @number=3, @quality=:major>

Parameters:

Returns:



130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/music/note.rb', line 130

def -(other)
  number = (self.diatonic_idx - other.diatonic_idx).abs + 1
  number += (self.octave - other.octave).abs * DIATONIC_SCALE.size unless octave.nil?

  distance = (self.pitch - other.pitch).abs
  quality = Interval::QUALITIES.find do |quality|
    begin; Interval.new(number, quality).size == distance; rescue ArgumentError; end
  end

  interval = Interval.new(number, quality)
  self >= other ? interval : -interval
end

#<=>(other) ⇒ Object

Compares notes by their pitch.

Examples:

Note.new("C#") == Note.new("D♭") #=> true
Note.new("C")  <  Note.new("D")  #=> true
Note.new("C2") >  Note.new("C1") #=> true

Parameters:



75
76
77
# File 'lib/music/note.rb', line 75

def <=>(other)
  self.pitch <=> other.pitch
end

#nameObject Also known as: to_s



61
62
63
# File 'lib/music/note.rb', line 61

def name
  [letter, accidental, octave].join
end

#transpose_by(interval) ⇒ Music::Note

Examples:

major_third = Interval.new(3, :major)
Note.new("C").transpose_by(major_third)  == Note.new("E") #=> true
Note.new("E").transpose_by(-major_third) == Note.new("C") #=> true

Parameters:

Returns:



87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/music/note.rb', line 87

def transpose_by(interval)
  transposed_pitch = pitch + interval.size
  transposed_pitch %= CHROMATIC_SCALE.size if octave.nil?

  transposed_diatonic_idx = (diatonic_idx + interval.diff) % DIATONIC_SCALE.size
  transposed_letter = DIATONIC_SCALE.fetch(transposed_diatonic_idx)

  transposed_octave = octave + (diatonic_idx + interval.diff) / DIATONIC_SCALE.size if octave

  transposed_accidental = ACCIDENTALS.find do |accidental|
    note = Note.new [transposed_letter, accidental, transposed_octave].join
    note.pitch == transposed_pitch
  end

  Note.new [transposed_letter, transposed_accidental, transposed_octave].join
end

#transpose_down(interval) ⇒ Music::Note

Parameters:

Returns:

See Also:



120
121
122
# File 'lib/music/note.rb', line 120

def transpose_down(interval)
  transpose_by(-interval)
end

#transpose_up(interval) ⇒ Music::Note

Parameters:

Returns:

See Also:



110
111
112
# File 'lib/music/note.rb', line 110

def transpose_up(interval)
  transpose_by(interval)
end