Class: Musa::Scales::ScaleKind Abstract
Overview
Subclass and implement abstract methods
Abstract base class for scale types (major, minor, chromatic, etc.).
ScaleKind defines a type of scale (major, minor, chromatic, etc.) independent of root pitch or tuning. It specifies:
- Scale degrees and their pitch offsets
- Function names for each degree (tonic, dominant, etc.)
- Number of grades per octave
- Whether the scale is chromatic (contains all pitches)
Subclass Requirements
Subclasses must implement:
- ScaleKind.id: Unique symbol identifier (:major, :minor, :chromatic, etc.)
- ScaleKind.pitches: Array defining scale structure
- ScaleKind.chromatic?: Whether this is the chromatic scale (default: false)
- ScaleKind.grades: Number of grades per octave (if different from pitches.length)
Pitch Structure
The ScaleKind.pitches array defines the scale structure:
[{ functions: [:I, :tonic, :_1], pitch: 0 },
{ functions: [:II, :supertonic, :_2], pitch: 2 },
...]
- functions: Array of symbols that can access this degree
- pitch: Semitone offset from root
Dynamic Method Creation
Each scale instance gets methods for all registered scale kinds:
note.major # Get major scale rooted on this note
note.minor # Get minor scale rooted on this note
Usage
ScaleKind instances are accessed via tuning:
tuning = Scales[:et12][440.0]
major_kind = tuning[:major] # ScaleKind instance
c_major = major_kind[60] # Scale instance
Or directly via convenience methods:
c_major = tuning.major[60]
Direct Known Subclasses
AlteredScaleKind, BebopDominantScaleKind, BebopMajorScaleKind, BebopMinorScaleKind, BluesMajorScaleKind, BluesScaleKind, ChromaticScaleKind, DiminishedHWScaleKind, DiminishedWHScaleKind, DorianB2ScaleKind, DorianScaleKind, DoubleHarmonicScaleKind, HarmonicMajorScaleKind, HungarianMinorScaleKind, LocrianScaleKind, LocrianSharp2ScaleKind, LydianAugmentedScaleKind, LydianDominantScaleKind, LydianScaleKind, MajorScaleKind, MelodicMinorScaleKind, MinorHarmonicScaleKind, MinorNaturalScaleKind, MixolydianB6ScaleKind, MixolydianScaleKind, NeapolitanMajorScaleKind, NeapolitanMinorScaleKind, PentatonicMajorScaleKind, PentatonicMinorScaleKind, PhrygianDominantScaleKind, PhrygianScaleKind, WholeToneScaleKind
Instance Attribute Summary collapse
-
#tuning ⇒ ScaleSystemTuning
readonly
The tuning context.
Class Method Summary collapse
-
.base_metadata ⇒ Hash
Returns base metadata defined by the musa-dsl library.
-
.chromatic? ⇒ Boolean
Indicates whether this is the chromatic scale.
-
.compute_intervals ⇒ Array<Integer>?
private
Computes intervals between consecutive scale degrees.
-
.compute_symmetry ⇒ Symbol?
private
Computes symmetry type of the scale.
-
.create_grade_functions_index ⇒ self
private
Creates internal index mapping function names to grade indices.
-
.custom_metadata ⇒ Hash
Returns custom metadata added by users at runtime.
-
.extend_metadata(**metadata) ⇒ Hash
Adds custom metadata to this scale kind.
-
.grade_of_function(symbol) ⇒ Integer?
private
Returns grade index for a function symbol.
-
.grades ⇒ Integer
Returns the number of grades per octave.
-
.grades_functions ⇒ Array<Symbol>
private
Returns all function symbols for accessing scale degrees.
-
.has_metadata?(key, value = nil) ⇒ Boolean
Checks whether metadata contains a key or key-value match.
-
.id ⇒ Symbol
abstract
Returns the unique identifier for this scale kind.
-
.intrinsic_metadata ⇒ Hash
Returns intrinsic metadata derived from scale structure.
-
.metadata ⇒ Hash
Returns combined metadata from all three layers.
-
.metadata_value(key) ⇒ Object?
Returns a specific metadata value.
-
.pitches ⇒ Array<Hash>
abstract
Returns the pitch structure definition.
-
.reset_custom_metadata ⇒ nil
Clears all custom metadata from this scale kind.
Instance Method Summary collapse
-
#==(other) ⇒ Boolean
Checks scale kind equality.
-
#absolut ⇒ Scale
Returns scale with absolute root (MIDI 0).
-
#default_root ⇒ Scale
Returns scale with default root (middle C, MIDI 60).
-
#find_chord_in_scales(chord, roots: nil) ⇒ Array<Musa::Chords::Chord>
Finds all scales of this kind that contain the given chord.
-
#get(root_pitch) ⇒ Scale
(also: #[])
Creates or retrieves a scale rooted on specific pitch.
-
#initialize(tuning) ⇒ ScaleKind
constructor
private
Creates a scale kind instance.
-
#inspect ⇒ String
(also: #to_s)
Returns string representation.
Constructor Details
#initialize(tuning) ⇒ ScaleKind
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Creates a scale kind instance.
760 761 762 763 |
# File 'lib/musa-dsl/music/scales.rb', line 760 def initialize(tuning) @tuning = tuning @scales = {} end |
Instance Attribute Details
#tuning ⇒ ScaleSystemTuning (readonly)
The tuning context.
767 768 769 |
# File 'lib/musa-dsl/music/scales.rb', line 767 def tuning @tuning end |
Class Method Details
.base_metadata ⇒ Hash
Returns base metadata defined by the musa-dsl library.
This metadata is defined in each ScaleKind subclass using the
@base_metadata class instance variable. It typically includes:
- :family: Scale family (:diatonic, :greek_modes, :pentatonic, etc.)
- :brightness: Relative brightness (-3 to +3, major = 0)
- :character: Array of descriptive tags
- :parent: Parent scale and degree for modes
993 994 995 |
# File 'lib/musa-dsl/music/scales.rb', line 993 def self. @base_metadata || {} end |
.chromatic? ⇒ Boolean
Indicates whether this is the chromatic scale.
Only one scale kind per system should return true. The chromatic scale contains all notes in the scale system and is used as a fallback for non-diatonic notes.
896 897 898 |
# File 'lib/musa-dsl/music/scales.rb', line 896 def self.chromatic? false end |
.compute_intervals ⇒ Array<Integer>?
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Computes intervals between consecutive scale degrees.
1102 1103 1104 1105 1106 1107 1108 1109 |
# File 'lib/musa-dsl/music/scales.rb', line 1102 def self.compute_intervals return nil unless respond_to?(:pitches) && pitches.size > 1 pitch_values = pitches.map { |p| p[:pitch] } # Only compute within first octave first_octave = pitch_values.take_while { |p| p < 12 } first_octave.push(12) if first_octave.last != 12 first_octave.each_cons(2).map { |a, b| b - a } end |
.compute_symmetry ⇒ Symbol?
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Computes symmetry type of the scale.
1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 |
# File 'lib/musa-dsl/music/scales.rb', line 1114 def self.compute_symmetry return nil unless respond_to?(:pitches) intervals = compute_intervals return nil unless intervals && intervals.any? # Check if intervals are all equal (e.g., whole tone: [2,2,2,2,2,2]) return :equal if intervals.uniq.size == 1 # Check for palindrome pattern return :palindrome if intervals == intervals.reverse # Check for repeating pattern (1..intervals.size / 2).each do |len| pattern = intervals.take(len) if intervals.each_slice(len).all? { |slice| slice == pattern || slice.size < len } return :repeating end end nil end |
.create_grade_functions_index ⇒ self
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Creates internal index mapping function names to grade indices.
1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 |
# File 'lib/musa-dsl/music/scales.rb', line 1143 def self.create_grade_functions_index @grade_names_index = {} pitches.each_index do |i| pitches[i][:functions].each do |function| @grade_names_index[function] = i end end self end |
.custom_metadata ⇒ Hash
Returns custom metadata added by users at runtime.
This metadata is added via extend_metadata and can be cleared with reset_custom_metadata. Takes precedence over base_metadata.
1007 1008 1009 |
# File 'lib/musa-dsl/music/scales.rb', line 1007 def self. @custom_metadata || {} end |
.extend_metadata(**metadata) ⇒ Hash
Adds custom metadata to this scale kind.
Custom metadata takes precedence over base_metadata when queried via metadata. Multiple calls merge metadata together.
1024 1025 1026 1027 |
# File 'lib/musa-dsl/music/scales.rb', line 1024 def self.(**) @custom_metadata ||= {} @custom_metadata = @custom_metadata.merge().freeze end |
.grade_of_function(symbol) ⇒ Integer?
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns grade index for a function symbol.
925 926 927 928 |
# File 'lib/musa-dsl/music/scales.rb', line 925 def self.grade_of_function(symbol) create_grade_functions_index unless @grade_names_index @grade_names_index[symbol] end |
.grades ⇒ Integer
Returns the number of grades per octave.
For scales defining extended harmony (8th, 9th, etc.), this returns the number of diatonic degrees within one octave. Defaults to the number of pitch definitions.
910 911 912 |
# File 'lib/musa-dsl/music/scales.rb', line 910 def self.grades pitches.length end |
.grades_functions ⇒ Array<Symbol>
This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.
Returns all function symbols for accessing scale degrees.
939 940 941 942 |
# File 'lib/musa-dsl/music/scales.rb', line 939 def self.grades_functions create_grade_functions_index unless @grade_names_index @grade_names_index.keys end |
.has_metadata?(key, value = nil) ⇒ Boolean
Checks whether metadata contains a key or key-value match.
When called with just a key, checks for key existence. When called with key and value, checks for exact match or array inclusion (if metadata value is an array).
1088 1089 1090 1091 1092 1093 1094 1095 |
# File 'lib/musa-dsl/music/scales.rb', line 1088 def self.(key, value = nil) if value.nil? .key?(key) else [key] == value || ([key].is_a?(Array) && [key].include?(value)) end end |
.id ⇒ Symbol
Subclass must implement
Returns the unique identifier for this scale kind.
861 862 863 |
# File 'lib/musa-dsl/music/scales.rb', line 861 def self.id raise 'Method not implemented. Should be implemented in subclass.' end |
.intrinsic_metadata ⇒ Hash
Returns intrinsic metadata derived from scale structure.
This metadata is automatically calculated from the scale's pitch structure and cannot be modified. It includes:
- :id: Scale kind identifier
- :grades: Number of diatonic degrees
- :pitches: Array of pitch offsets from root
- :intervals: Intervals between consecutive degrees
- :has_leading_tone: Whether scale has pitch 11 (semitone below octave)
- :has_tritone: Whether scale contains tritone (pitch 6)
- :symmetric: Type of symmetry if any (:equal, :palindrome, :repeating)
964 965 966 967 968 969 970 971 972 973 974 975 976 |
# File 'lib/musa-dsl/music/scales.rb', line 964 def self. result = {} result[:id] = id if respond_to?(:id) result[:grades] = grades if respond_to?(:grades) if respond_to?(:pitches) result[:pitches] = pitches.map { |p| p[:pitch] } result[:intervals] = compute_intervals result[:has_leading_tone] = pitches.any? { |p| p[:pitch] == 11 } result[:has_tritone] = pitches.any? { |p| p[:pitch] == 6 } result[:symmetric] = compute_symmetry end result.compact end |
.metadata ⇒ Hash
Returns combined metadata from all three layers.
Layers are merged with later layers taking precedence: intrinsic_metadata < base_metadata < custom_metadata
1051 1052 1053 1054 1055 |
# File 'lib/musa-dsl/music/scales.rb', line 1051 def self. .merge() .merge() end |
.metadata_value(key) ⇒ Object?
Returns a specific metadata value.
1064 1065 1066 |
# File 'lib/musa-dsl/music/scales.rb', line 1064 def self.(key) [key] end |
.pitches ⇒ Array<Hash>
Subclass must implement
Returns the pitch structure definition.
Defines the scale degrees and their pitch offsets from the root. Each entry specifies function names and semitone offset.
881 882 883 |
# File 'lib/musa-dsl/music/scales.rb', line 881 def self.pitches raise 'Method not implemented. Should be implemented in subclass.' end |
.reset_custom_metadata ⇒ nil
Clears all custom metadata from this scale kind.
1037 1038 1039 |
# File 'lib/musa-dsl/music/scales.rb', line 1037 def self. @custom_metadata = nil end |
Instance Method Details
#==(other) ⇒ Boolean
Checks scale kind equality.
840 841 842 |
# File 'lib/musa-dsl/music/scales.rb', line 840 def ==(other) self.class == other.class && @tuning == other.tuning end |
#absolut ⇒ Scale
Returns scale with absolute root (MIDI 0).
803 804 805 |
# File 'lib/musa-dsl/music/scales.rb', line 803 def absolut self[0] end |
#default_root ⇒ Scale
Returns scale with default root (middle C, MIDI 60).
793 794 795 |
# File 'lib/musa-dsl/music/scales.rb', line 793 def default_root self[60] end |
#find_chord_in_scales(chord, roots: nil) ⇒ Array<Musa::Chords::Chord>
Finds all scales of this kind that contain the given chord.
Searches through scales rooted on different pitches to find which ones contain all the notes of the given chord. Returns chords with their containing scale as context.
825 826 827 828 829 830 831 832 833 834 |
# File 'lib/musa-dsl/music/scales.rb', line 825 def find_chord_in_scales(chord, roots: nil) roots ||= 0...tuning.notes_in_octave base_pitch = chord.root.pitch % tuning.notes_in_octave roots.filter_map do |root_offset| root_pitch = base_pitch + root_offset scale = self[root_pitch] chord.as_chord_in_scale(scale) end end |
#get(root_pitch) ⇒ Scale Also known as: []
Creates or retrieves a scale rooted on specific pitch.
Scales are cached—repeated calls with same pitch return same instance.
780 781 782 783 |
# File 'lib/musa-dsl/music/scales.rb', line 780 def get(root_pitch) @scales[root_pitch] = Scale.new(self, root_pitch: root_pitch) unless @scales.key?(root_pitch) @scales[root_pitch] end |
#inspect ⇒ String Also known as: to_s
Returns string representation.
847 848 849 |
# File 'lib/musa-dsl/music/scales.rb', line 847 def inspect "<#{self.class.name}: tuning = #{@tuning}>" end |