lib/music-transcription/pitch.rb in music-transcription-0.5.6 vs lib/music-transcription/pitch.rb in music-transcription-0.5.7
- old
+ new
@@ -1,230 +1,158 @@
module Music
module Transcription
-# Abstraction of a musical pitch. Contains values for octave, semitone,
-# and cent. These values are useful because they allow simple mapping to
-# both the abstract (musical scales) and concrete (audio data).
+# Abstraction of a musical pitch. Contains values for octave and semitone.
#
-# Fundamentally, pitch can be considered a ratio to some base number.
-# For music, this is a base frequency. The pitch frequency can be
-# determined by multiplying the base frequency by the pitch ratio. For
-# the standard musical scale, the base frequency of C0 is 16.35 Hz.
-#
# Octaves represent the largest means of differing two pitches. Each
# octave added will double the ratio. At zero octaves, the ratio is
-# 1.0. At one octave, the ratio will be 2.0. Each semitone and cent
-# is an increment of less-than-power-of-two.
+# 1.0. At one octave, the ratio will be 2.0. Each semitone is an increment
+# of less-than-power-of-two.
#
-# Semitones are the primary steps between octaves. By default, the
-# number of semitones per octave is 12, corresponding to the twelve-tone equal
-# temperment tuning system. The number of semitones per octave can be
-# modified at runtime by overriding the Pitch::SEMITONES_PER_OCTAVE
-# constant.
-#
-# Cents are the smallest means of differing two pitches. By default, the
-# number of cents per semitone is 100 (hence the name cent, as in per-
-# cent). This number can be modified at runtime by overriding the
-# Pitch::CENTS_PER_SEMITONE constant.
-#
+# Semitones are the primary steps between octaves. The number of
+# semitones per octave is 12.
+
# @author James Tunnell
#
# @!attribute [r] octave
# @return [Fixnum] The pitch octave.
# @!attribute [r] semitone
# @return [Fixnum] The pitch semitone.
-# @!attribute [r] cent
-# @return [Fixnum] The pitch cent.
-# @!attribute [r] cents_per_octave
-# @return [Fixnum] The number of cents per octave. Default is 1200
-# (12 x 100). If a different scale is required,
-# modify CENTS_PER_SEMITONE (default 12) and/or
-# SEMITONES_PER_OCTAVE (default 100).
-# @!attribute [r] base_freq
-# @return [Numeric] Multiplied with pitch ratio to determine the final frequency
-# of the pitch. Defaults to DEFAULT_BASE_FREQ, but can be set
-# during initialization to something else using the :base_freq key.
#
class Pitch
include Comparable
- attr_reader :cents_per_octave, :base_freq, :octave, :semitone, :cent
+ attr_reader :octave, :semitone
#The default number of semitones per octave is 12, corresponding to
# the twelve-tone equal temperment tuning system.
SEMITONES_PER_OCTAVE = 12
- #The default number of cents per semitone is 100 (hence the name cent,
- # as in percent).
- CENTS_PER_SEMITONE = 100
-
- # The default base ferquency is C0
- DEFAULT_BASE_FREQ = 16.351597831287414
+ # The base ferquency is C0
+ BASE_FREQ = 16.351597831287414
# A new instance of Pitch.
# @raise [NonPositiveFrequencyError] if base_freq is not > 0.
- def initialize octave:0, semitone:0, cent:0, base_freq:DEFAULT_BASE_FREQ
- @cents_per_octave = CENTS_PER_SEMITONE * SEMITONES_PER_OCTAVE
+ def initialize octave:0, semitone:0
@octave = octave
@semitone = semitone
- @cent = cent
- raise ValueNonPositiveError if base_freq <= 0
- @base_freq = base_freq
normalize!
end
- ## Set @base_freq, which is used with the pitch ratio to produce the
- ## pitch frequency.
- #def base_freq= base_freq
- # raise NonPositiveFrequencyError if base_freq <= 0
- # @base_freq = base_freq
- #end
- #
- ## Set @octave.
- #def octave= octave
- # @octave = octave
- #end
- #
- ## Set semitone.
- #def semitone= semitone
- # @semitone = semitone
- #end
- #
- ## Set @cent.
- #def cent= cent
- # @cent = cent
- #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
+ return self.ratio() * BASE_FREQ
end
# Set the pitch according to the given frequency. Uses the current base_freq
# to determine what the pitch ratio should be, and sets it accordingly.
def freq= freq
- self.ratio = freq / @base_freq
+ self.ratio = freq / BASE_FREQ
end
- # Calculate the total cent count. Converts octave and semitone count
- # to cent count before adding to existing cent count.
- # @return [Fixnum] total cent count
- def total_cent
- return (@octave * @cents_per_octave) +
- (@semitone * CENTS_PER_SEMITONE) + @cent
+ # Calculate the total semitone count. Converts octave to semitone count
+ # before adding to existing semitone count.
+ # @return [Fixnum] total semitone count
+ def total_semitone
+ return (@octave * SEMITONES_PER_OCTAVE) + @semitone
end
- # Set the Pitch ratio according to a total number of cents.
- # @param [Fixnum] cent The total number of cents to use.
- # @raise [ArgumentError] if cent is not a Fixnum
- def total_cent= cent
- raise ArgumentError, "cent is not a Fixnum" if !cent.is_a?(Fixnum)
- @octave, @semitone, @cent = 0, 0, cent
+ # Set the Pitch ratio according to a total number of semitones.
+ # @param [Fixnum] semitone The total number of semitones to use.
+ # @raise [ArgumentError] if semitone is not an Integer
+ def total_semitone= semitone
+ raise ArgumentError, "semitone is not a Integer" if !semitone.is_a?(Integer)
+ @octave, @semitone = 0, semitone
normalize!
end
- # Calculate the pitch ratio. Raises 2 to the power of the total cent
- # count divided by cents-per-octave.
+ # Calculate the pitch ratio. Raises 2 to the power of the total semitone
+ # count divided by semitones-per-octave.
# @return [Float] ratio
def ratio
- 2.0**(self.total_cent.to_f / @cents_per_octave)
+ 2.0**(self.total_semitone.to_f / SEMITONES_PER_OCTAVE)
end
# Represent the Pitch ratio according to a ratio.
# @param [Numeric] ratio The ratio to represent.
# @raise [RangeError] if ratio is less than or equal to 0.0
def ratio= ratio
raise RangeError, "ratio #{ratio} is less than or equal to 0.0" if ratio <= 0.0
x = Math.log2 ratio
- self.total_cent = (x * @cents_per_octave).round
+ self.total_semitone = (x * SEMITONES_PER_OCTAVE).round
end
# Round to the nearest semitone.
def round
self.clone.round!
end
- # Round to the nearest semitone.
- def round!
- if @cent >= (CENTS_PER_SEMITONE / 2)
- @semitone += 1
- end
- @cent = 0
- normalize!
- return self
- end
-
# Calculates the number of semitones which would represent the pitch's
- # octave and semitone count. Excludes cents.
+ # octave and semitone count
def total_semitone
return (@octave * SEMITONES_PER_OCTAVE) + @semitone
end
# Override default hash method.
def hash
- return self.total_cent
+ return self.total_semitone
end
- # Compare pitch equality using total cent
+ # Compare pitch equality using total semitone
def ==(other)
- self.total_cent == other.total_cent
+ self.total_semitone == other.total_semitone
end
- # Compare pitches. A higher ratio or total cent is considered larger.
+ 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)
- self.total_cent <=> other.total_cent
+ self.total_semitone <=> other.total_semitone
end
- # Add pitches by adding the total cent count of each.
+ # Add pitches by adding the total semitone count of each.
# @param [Pitch] other The pitch object to add.
def + (other)
self.class.new(
octave: (@octave + other.octave),
- semitone: (@semitone + other.semitone),
- cent: (@cent + other.cent)
+ semitone: (@semitone + other.semitone)
)
end
- # Add pitches by subtracting the total cent count.
+ # Add pitches by subtracting the total semitone count.
# @param [Pitch] other The pitch object to subtract.
def - (other)
self.class.new(
octave: (@octave - other.octave),
semitone: (@semitone - other.semitone),
- cent: (@cent - other.cent)
)
end
# Produce an identical Pitch object.
def clone
Marshal.load(Marshal.dump(self)) # is this cheating?
end
- # Balance out the octave, semitone, and cent count.
+ # Balance out the octave and semitone count.
def normalize!
- centTotal = (@octave * @cents_per_octave) + (@semitone * CENTS_PER_SEMITONE) + @cent
+ semitoneTotal = (@octave * SEMITONES_PER_OCTAVE) + @semitone
- @octave = centTotal / @cents_per_octave
- centTotal -= @octave * @cents_per_octave
+ @octave = semitoneTotal / SEMITONES_PER_OCTAVE
+ semitoneTotal -= @octave * SEMITONES_PER_OCTAVE
- @semitone = centTotal / CENTS_PER_SEMITONE
- centTotal -= @semitone * CENTS_PER_SEMITONE
-
- @cent = centTotal
+ @semitone = semitoneTotal
return self
end
# Produce a string representation of a pitch (e.g. "C2")
def to_s
- if @cents_per_octave != 1200
- raise "Don't know how to produce a string representation since cents_per_octave is not 1200."
- end
-
semitone_str = case @semitone
when 0 then "C"
when 1 then "Db"
when 2 then "D"
when 3 then "Eb"
@@ -239,13 +167,21 @@
end
return semitone_str + @octave.to_s
end
- def self.make_from_freq(freq, base_freq = DEFAULT_BASE_FREQ)
+ def self.make_from_freq(freq)
pitch = Pitch.new()
- pitch.ratio = freq / base_freq
+ pitch.ratio = freq / BASE_FREQ
return pitch
+ end
+
+ def self.make_from_semitone semitones
+ if semitones.is_a?(Integer)
+ return Pitch.new(semitone: semitones)
+ else
+ raise ArgumentError, "Cannot make Pitch from #{semitones}"
+ end
end
end
end
end