Class: Music::Transcription::Pitch

Inherits:
Object
  • Object
show all
Includes:
Comparable, Parseable
Defined in:
lib/music-transcription/model/pitch.rb,
lib/music-transcription/parsing/convenience_methods.rb

Overview

Author:

  • James Tunnell

Constant Summary collapse

SEMITONES_PER_OCTAVE =

The default number of semitones per octave is 12, corresponding to the twelve-tone equal temperment tuning system.

12
CENTS_PER_SEMITONE =
100
CENTS_PER_OCTAVE =
SEMITONES_PER_OCTAVE * CENTS_PER_SEMITONE
BASE_FREQ =

The base ferquency is C0

16.351597831287414
PARSER =
Parsing::PitchParser.new
CONVERSION_METHOD =
:to_pitch

Constants included from Parseable

Music::Transcription::Parseable::DEFAULT_SPLIT_PATTERN

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Parseable

included

Constructor Details

#initialize(octave: 0, semitone: 0, cent: 0) ⇒ Pitch

Returns a new instance of Pitch.

Raises:



34
35
36
37
38
39
40
41
42
43
44
# File 'lib/music-transcription/model/pitch.rb', line 34

def initialize octave:0, semitone:0, cent: 0
  raise NonIntegerError, "octave #{octave} is not an integer" unless octave.is_a?(Integer)
  raise NonIntegerError, "semitone #{semitone} is not an integer" unless semitone.is_a?(Integer)
  raise NonIntegerError, "cent #{cent} is not an integer" unless cent.is_a?(Integer)
  
  @octave = octave
  @semitone = semitone
  @cent = cent
  @total_cents = (@octave*SEMITONES_PER_OCTAVE + @semitone)*CENTS_PER_SEMITONE + @cent
  balance!
end

Instance Attribute Details

#centObject (readonly)

Returns the value of attribute cent.



23
24
25
# File 'lib/music-transcription/model/pitch.rb', line 23

def cent
  @cent
end

#octaveFixnum (readonly)

Returns The pitch octave.

Returns:

  • (Fixnum)

    The pitch octave.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/music-transcription/model/pitch.rb', line 21

class Pitch
  include Comparable
  attr_reader :octave, :semitone, :cent, :total_cents

  #The default number of semitones per octave is 12, corresponding to
  # the twelve-tone equal temperment tuning system.
  SEMITONES_PER_OCTAVE = 12
  CENTS_PER_SEMITONE = 100
  CENTS_PER_OCTAVE = SEMITONES_PER_OCTAVE * CENTS_PER_SEMITONE

  # The base ferquency is C0
  BASE_FREQ = 16.351597831287414

  def initialize octave:0, semitone:0, cent: 0
    raise NonIntegerError, "octave #{octave} is not an integer" unless octave.is_a?(Integer)
    raise NonIntegerError, "semitone #{semitone} is not an integer" unless semitone.is_a?(Integer)
    raise NonIntegerError, "cent #{cent} is not an integer" unless cent.is_a?(Integer)
    
    @octave = octave
    @semitone = semitone
    @cent = cent
    @total_cents = (@octave*SEMITONES_PER_OCTAVE + @semitone)*CENTS_PER_SEMITONE + @cent
    balance!
  end

  # Return the pitch's frequency, which is determined by multiplying the base
  # frequency and the pitch ratio. Base frequency defaults to DEFAULT_BASE_FREQ,
  # but can be set during initialization to something else by specifying the
  # :base_freq key.
  def freq
    return self.ratio() * BASE_FREQ
  end

  # Calculate the pitch ratio. Raises 2 to the power of the total cent
  # count divided by cents-per-octave.
  # @return [Float] ratio
  def ratio
    2.0**(@total_cents.to_f / CENTS_PER_OCTAVE)
  end

  # Override default hash method.
  def hash
    return @total_cents
  end

  # Compare pitch equality using total semitone
  def ==(other)
    return (self.class == other.class &&
      @total_cents == other.total_cents)
  end

  def eql?(other)
    self == other
  end

  # Compare pitches. A higher ratio or total semitone is considered larger.
  # @param [Pitch] other The pitch object to compare.
  def <=> (other)
    @total_cents <=> other.total_cents
  end

  # rounds to the nearest semitone
  def round
    if @cent == 0
      self.clone
    else
      Pitch.new(semitone: (@total_cents / CENTS_PER_SEMITONE.to_f).round)
    end
  end

  # diff in (rounded) semitones
  def diff other
    Rational(@total_cents - other.total_cents, CENTS_PER_SEMITONE)
  end

  def transpose semitones
    Pitch.new(cent: (@total_cents + semitones * CENTS_PER_SEMITONE).round)
  end
  
  def total_semitones
    Rational(@total_cents, CENTS_PER_SEMITONE)
  end
  
  def self.from_semitones semitones
    Pitch.new(cent: (semitones * CENTS_PER_SEMITONE).round)
  end
  
  def clone
    Pitch.new(cent: @total_cents)
  end

  def to_s(sharpit = false)
    letter = case semitone
    when 0 then "C"
    when 1 then sharpit  ? "C#" : "Db"
    when 2 then "D"
    when 3 then sharpit  ? "D#" : "Eb"
    when 4 then "E"
    when 5 then "F"
    when 6 then sharpit  ? "F#" : "Gb"
    when 7 then "G"
    when 8 then sharpit  ? "G#" : "Ab"
    when 9 then "A"
    when 10 then sharpit  ? "A#" : "Bb"
    when 11 then "B"
    end
    
    if @cent == 0
      return letter + octave.to_s
    elsif @cent > 0
      return letter + octave.to_s + "+" + @cent.to_s
    else
      return letter + octave.to_s + @cent.to_s
    end
  end

  def self.from_ratio ratio
    raise NonPositiveError, "ratio #{ratio} is not > 0" unless ratio > 0
    x = Math.log2 ratio
    new(cent: (x * CENTS_PER_OCTAVE).round)
  end

  def self.from_freq freq
    from_ratio(freq / BASE_FREQ)
  end

  private

  # Balance out the octave and semitone count.
  def balance!
    centsTotal = @total_cents
    
    @octave = centsTotal / CENTS_PER_OCTAVE
    centsTotal -= @octave * CENTS_PER_OCTAVE
    
    @semitone = centsTotal / CENTS_PER_SEMITONE
    centsTotal -= @semitone * CENTS_PER_SEMITONE
    
    @cent = centsTotal
    return self
  end
end

#semitoneFixnum (readonly)

Returns The pitch semitone.

Returns:

  • (Fixnum)

    The pitch semitone.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/music-transcription/model/pitch.rb', line 21

class Pitch
  include Comparable
  attr_reader :octave, :semitone, :cent, :total_cents

  #The default number of semitones per octave is 12, corresponding to
  # the twelve-tone equal temperment tuning system.
  SEMITONES_PER_OCTAVE = 12
  CENTS_PER_SEMITONE = 100
  CENTS_PER_OCTAVE = SEMITONES_PER_OCTAVE * CENTS_PER_SEMITONE

  # The base ferquency is C0
  BASE_FREQ = 16.351597831287414

  def initialize octave:0, semitone:0, cent: 0
    raise NonIntegerError, "octave #{octave} is not an integer" unless octave.is_a?(Integer)
    raise NonIntegerError, "semitone #{semitone} is not an integer" unless semitone.is_a?(Integer)
    raise NonIntegerError, "cent #{cent} is not an integer" unless cent.is_a?(Integer)
    
    @octave = octave
    @semitone = semitone
    @cent = cent
    @total_cents = (@octave*SEMITONES_PER_OCTAVE + @semitone)*CENTS_PER_SEMITONE + @cent
    balance!
  end

  # Return the pitch's frequency, which is determined by multiplying the base
  # frequency and the pitch ratio. Base frequency defaults to DEFAULT_BASE_FREQ,
  # but can be set during initialization to something else by specifying the
  # :base_freq key.
  def freq
    return self.ratio() * BASE_FREQ
  end

  # Calculate the pitch ratio. Raises 2 to the power of the total cent
  # count divided by cents-per-octave.
  # @return [Float] ratio
  def ratio
    2.0**(@total_cents.to_f / CENTS_PER_OCTAVE)
  end

  # Override default hash method.
  def hash
    return @total_cents
  end

  # Compare pitch equality using total semitone
  def ==(other)
    return (self.class == other.class &&
      @total_cents == other.total_cents)
  end

  def eql?(other)
    self == other
  end

  # Compare pitches. A higher ratio or total semitone is considered larger.
  # @param [Pitch] other The pitch object to compare.
  def <=> (other)
    @total_cents <=> other.total_cents
  end

  # rounds to the nearest semitone
  def round
    if @cent == 0
      self.clone
    else
      Pitch.new(semitone: (@total_cents / CENTS_PER_SEMITONE.to_f).round)
    end
  end

  # diff in (rounded) semitones
  def diff other
    Rational(@total_cents - other.total_cents, CENTS_PER_SEMITONE)
  end

  def transpose semitones
    Pitch.new(cent: (@total_cents + semitones * CENTS_PER_SEMITONE).round)
  end
  
  def total_semitones
    Rational(@total_cents, CENTS_PER_SEMITONE)
  end
  
  def self.from_semitones semitones
    Pitch.new(cent: (semitones * CENTS_PER_SEMITONE).round)
  end
  
  def clone
    Pitch.new(cent: @total_cents)
  end

  def to_s(sharpit = false)
    letter = case semitone
    when 0 then "C"
    when 1 then sharpit  ? "C#" : "Db"
    when 2 then "D"
    when 3 then sharpit  ? "D#" : "Eb"
    when 4 then "E"
    when 5 then "F"
    when 6 then sharpit  ? "F#" : "Gb"
    when 7 then "G"
    when 8 then sharpit  ? "G#" : "Ab"
    when 9 then "A"
    when 10 then sharpit  ? "A#" : "Bb"
    when 11 then "B"
    end
    
    if @cent == 0
      return letter + octave.to_s
    elsif @cent > 0
      return letter + octave.to_s + "+" + @cent.to_s
    else
      return letter + octave.to_s + @cent.to_s
    end
  end

  def self.from_ratio ratio
    raise NonPositiveError, "ratio #{ratio} is not > 0" unless ratio > 0
    x = Math.log2 ratio
    new(cent: (x * CENTS_PER_OCTAVE).round)
  end

  def self.from_freq freq
    from_ratio(freq / BASE_FREQ)
  end

  private

  # Balance out the octave and semitone count.
  def balance!
    centsTotal = @total_cents
    
    @octave = centsTotal / CENTS_PER_OCTAVE
    centsTotal -= @octave * CENTS_PER_OCTAVE
    
    @semitone = centsTotal / CENTS_PER_SEMITONE
    centsTotal -= @semitone * CENTS_PER_SEMITONE
    
    @cent = centsTotal
    return self
  end
end

#total_centsObject (readonly)

Returns the value of attribute total_cents.



23
24
25
# File 'lib/music-transcription/model/pitch.rb', line 23

def total_cents
  @total_cents
end

Class Method Details

.from_freq(freq) ⇒ Object



143
144
145
# File 'lib/music-transcription/model/pitch.rb', line 143

def self.from_freq freq
  from_ratio(freq / BASE_FREQ)
end

.from_ratio(ratio) ⇒ Object

Raises:



137
138
139
140
141
# File 'lib/music-transcription/model/pitch.rb', line 137

def self.from_ratio ratio
  raise NonPositiveError, "ratio #{ratio} is not > 0" unless ratio > 0
  x = Math.log2 ratio
  new(cent: (x * CENTS_PER_OCTAVE).round)
end

.from_semitones(semitones) ⇒ Object



104
105
106
# File 'lib/music-transcription/model/pitch.rb', line 104

def self.from_semitones semitones
  Pitch.new(cent: (semitones * CENTS_PER_SEMITONE).round)
end

Instance Method Details

#<=>(other) ⇒ Object

Compare pitches. A higher ratio or total semitone is considered larger.

Parameters:

  • other (Pitch)

    The pitch object to compare.



78
79
80
# File 'lib/music-transcription/model/pitch.rb', line 78

def <=> (other)
  @total_cents <=> other.total_cents
end

#==(other) ⇒ Object

Compare pitch equality using total semitone



67
68
69
70
# File 'lib/music-transcription/model/pitch.rb', line 67

def ==(other)
  return (self.class == other.class &&
    @total_cents == other.total_cents)
end

#cloneObject



108
109
110
# File 'lib/music-transcription/model/pitch.rb', line 108

def clone
  Pitch.new(cent: @total_cents)
end

#diff(other) ⇒ Object

diff in (rounded) semitones



92
93
94
# File 'lib/music-transcription/model/pitch.rb', line 92

def diff other
  Rational(@total_cents - other.total_cents, CENTS_PER_SEMITONE)
end

#eql?(other) ⇒ Boolean

Returns:

  • (Boolean)


72
73
74
# File 'lib/music-transcription/model/pitch.rb', line 72

def eql?(other)
  self == other
end

#freqObject

Return the pitch’s frequency, which is determined by multiplying the base frequency and the pitch ratio. Base frequency defaults to DEFAULT_BASE_FREQ, but can be set during initialization to something else by specifying the :base_freq key.



50
51
52
# File 'lib/music-transcription/model/pitch.rb', line 50

def freq
  return self.ratio() * BASE_FREQ
end

#hashObject

Override default hash method.



62
63
64
# File 'lib/music-transcription/model/pitch.rb', line 62

def hash
  return @total_cents
end

#ratioFloat

Calculate the pitch ratio. Raises 2 to the power of the total cent count divided by cents-per-octave.

Returns:

  • (Float)

    ratio



57
58
59
# File 'lib/music-transcription/model/pitch.rb', line 57

def ratio
  2.0**(@total_cents.to_f / CENTS_PER_OCTAVE)
end

#roundObject

rounds to the nearest semitone



83
84
85
86
87
88
89
# File 'lib/music-transcription/model/pitch.rb', line 83

def round
  if @cent == 0
    self.clone
  else
    Pitch.new(semitone: (@total_cents / CENTS_PER_SEMITONE.to_f).round)
  end
end

#to_s(sharpit = false) ⇒ Object



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/music-transcription/model/pitch.rb', line 112

def to_s(sharpit = false)
  letter = case semitone
  when 0 then "C"
  when 1 then sharpit  ? "C#" : "Db"
  when 2 then "D"
  when 3 then sharpit  ? "D#" : "Eb"
  when 4 then "E"
  when 5 then "F"
  when 6 then sharpit  ? "F#" : "Gb"
  when 7 then "G"
  when 8 then sharpit  ? "G#" : "Ab"
  when 9 then "A"
  when 10 then sharpit  ? "A#" : "Bb"
  when 11 then "B"
  end
  
  if @cent == 0
    return letter + octave.to_s
  elsif @cent > 0
    return letter + octave.to_s + "+" + @cent.to_s
  else
    return letter + octave.to_s + @cent.to_s
  end
end

#total_semitonesObject



100
101
102
# File 'lib/music-transcription/model/pitch.rb', line 100

def total_semitones
  Rational(@total_cents, CENTS_PER_SEMITONE)
end

#transpose(semitones) ⇒ Object



96
97
98
# File 'lib/music-transcription/model/pitch.rb', line 96

def transpose semitones
  Pitch.new(cent: (@total_cents + semitones * CENTS_PER_SEMITONE).round)
end