class HeadMusic::Scale

A scale contains ordered pitches starting at a tonal center.

Constants

SCALE_REGEX

Attributes

root_pitch[R]
scale_type[R]

Public Class Methods

get(root_pitch, scale_type = nil) click to toggle source
# File lib/head_music/scale.rb, line 7
def self.get(root_pitch, scale_type = nil)
  root_pitch, scale_type = root_pitch.split(/\s+/) if root_pitch.is_a?(String) && scale_type =~ SCALE_REGEX
  root_pitch = HeadMusic::Pitch.get(root_pitch)
  scale_type = HeadMusic::ScaleType.get(scale_type || :major)
  @scales ||= {}
  hash_key = HeadMusic::Utilities::HashKey.for(
    [root_pitch, scale_type].join(' ').gsub(/#|♯/, 'sharp').gsub(/(\w)[b♭]/, '\\1flat')
  )
  @scales[hash_key] ||= new(root_pitch, scale_type)
end
new(root_pitch, scale_type) click to toggle source
# File lib/head_music/scale.rb, line 22
def initialize(root_pitch, scale_type)
  @root_pitch = HeadMusic::Pitch.get(root_pitch)
  @scale_type = HeadMusic::ScaleType.get(scale_type)
end

Public Instance Methods

degree(degree_number) click to toggle source
# File lib/head_music/scale.rb, line 45
def degree(degree_number)
  pitches[degree_number - 1]
end
pitch_names(direction: :ascending, octaves: 1) click to toggle source
# File lib/head_music/scale.rb, line 37
def pitch_names(direction: :ascending, octaves: 1)
  pitches(direction: direction, octaves: octaves).map(&:name)
end
pitches(direction: :ascending, octaves: 1) click to toggle source
# File lib/head_music/scale.rb, line 27
def pitches(direction: :ascending, octaves: 1)
  @pitches ||= {}
  @pitches[direction] ||= {}
  @pitches[direction][octaves] ||= determine_scale_pitches(direction, octaves)
end
root_pitch_number() click to toggle source
# File lib/head_music/scale.rb, line 41
def root_pitch_number
  @root_pitch_number ||= root_pitch.number
end
spellings(direction: :ascending, octaves: 1) click to toggle source
# File lib/head_music/scale.rb, line 33
def spellings(direction: :ascending, octaves: 1)
  pitches(direction: direction, octaves: octaves).map(&:spelling).map(&:to_s)
end

Private Instance Methods

child_scale_letter_for_step(semitones_from_root) click to toggle source
# File lib/head_music/scale.rb, line 109
def child_scale_letter_for_step(semitones_from_root)
  return unless scale_type.parent

  parent_scale_pitch_for(semitones_from_root).letter_name
end
determine_scale_pitches(direction, octaves) click to toggle source
# File lib/head_music/scale.rb, line 51
def determine_scale_pitches(direction, octaves)
  semitones_from_root = 0
  pitches = [root_pitch]
  %i[ascending descending].each do |single_direction|
    next unless [single_direction, :both].include?(direction)

    (1..octaves).each do
      pitches += octave_scale_pitches(single_direction, semitones_from_root)
      semitones_from_root += 12 * direction_sign(single_direction)
    end
  end
  pitches
end
diatonic_letter_for_step(direction, step) click to toggle source
# File lib/head_music/scale.rb, line 103
def diatonic_letter_for_step(direction, step)
  return unless scale_type.diatonic?

  direction == :ascending ? letter_name_series_ascending[step % 7] : letter_name_series_descending[step % 7]
end
direction_intervals(direction) click to toggle source
# File lib/head_music/scale.rb, line 76
def direction_intervals(direction)
  scale_type.send("#{direction}_intervals")
end
direction_sign(direction) click to toggle source
# File lib/head_music/scale.rb, line 72
def direction_sign(direction)
  direction == :descending ? -1 : 1
end
flat_letter_for_step(semitones_from_root) click to toggle source
# File lib/head_music/scale.rb, line 115
def flat_letter_for_step(semitones_from_root)
  return unless root_pitch.flat?

  HeadMusic::PitchClass::FLAT_SPELLINGS[pitch_class_number(semitones_from_root)]
end
letter_for_step(step, semitones_from_root, direction) click to toggle source
# File lib/head_music/scale.rb, line 96
def letter_for_step(step, semitones_from_root, direction)
  diatonic_letter_for_step(direction, step) ||
    child_scale_letter_for_step(semitones_from_root) ||
    flat_letter_for_step(semitones_from_root) ||
    sharp_letter_for_step(semitones_from_root)
end
octave_scale_pitches(direction, semitones_from_root) click to toggle source
# File lib/head_music/scale.rb, line 65
def octave_scale_pitches(direction, semitones_from_root)
  direction_intervals(direction).map.with_index(1) do |semitones, i|
    semitones_from_root += semitones * direction_sign(direction)
    pitch_for_step(i, semitones_from_root, direction)
  end
end
parent_scale_pitch_for(semitones_from_root) click to toggle source
# File lib/head_music/scale.rb, line 84
def parent_scale_pitch_for(semitones_from_root)
  parent_scale_pitches.detect do |parent_scale_pitch|
    parent_scale_pitch.pitch_class == (root_pitch + semitones_from_root).to_i % 12
  end
end
parent_scale_pitches() click to toggle source
# File lib/head_music/scale.rb, line 80
def parent_scale_pitches
  HeadMusic::Scale.get(root_pitch, scale_type.parent_name).pitches if scale_type.parent
end
pitch_class_number(semitones_from_root) click to toggle source
# File lib/head_music/scale.rb, line 125
def pitch_class_number(semitones_from_root)
  (root_pitch.pitch_class.to_i + semitones_from_root) % 12
end
pitch_for_step(step, semitones_from_root, direction) click to toggle source
# File lib/head_music/scale.rb, line 90
def pitch_for_step(step, semitones_from_root, direction)
  pitch_number = root_pitch_number + semitones_from_root
  letter_name = letter_for_step(step, semitones_from_root, direction)
  HeadMusic::Pitch.from_number_and_letter(pitch_number, letter_name)
end
sharp_letter_for_step(semitones_from_root) click to toggle source
# File lib/head_music/scale.rb, line 121
def sharp_letter_for_step(semitones_from_root)
  HeadMusic::PitchClass::SHARP_SPELLINGS[pitch_class_number(semitones_from_root)]
end