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.
801 802 803 804 |
# File 'lib/musa-dsl/music/scales.rb', line 801 def initialize(tuning) @tuning = tuning @scales = {} end |
Instance Attribute Details
#tuning ⇒ ScaleSystemTuning (readonly)
The tuning context.
808 809 810 |
# File 'lib/musa-dsl/music/scales.rb', line 808 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
1057 1058 1059 |
# File 'lib/musa-dsl/music/scales.rb', line 1057 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.
947 948 949 |
# File 'lib/musa-dsl/music/scales.rb', line 947 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.
1166 1167 1168 1169 1170 1171 1172 1173 |
# File 'lib/musa-dsl/music/scales.rb', line 1166 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.
1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 |
# File 'lib/musa-dsl/music/scales.rb', line 1178 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.
1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 |
# File 'lib/musa-dsl/music/scales.rb', line 1207 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.
1071 1072 1073 |
# File 'lib/musa-dsl/music/scales.rb', line 1071 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.
1088 1089 1090 1091 |
# File 'lib/musa-dsl/music/scales.rb', line 1088 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.
976 977 978 979 |
# File 'lib/musa-dsl/music/scales.rb', line 976 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.
961 962 963 |
# File 'lib/musa-dsl/music/scales.rb', line 961 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.
990 991 992 993 |
# File 'lib/musa-dsl/music/scales.rb', line 990 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).
1152 1153 1154 1155 1156 1157 1158 1159 |
# File 'lib/musa-dsl/music/scales.rb', line 1152 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.
914 915 916 |
# File 'lib/musa-dsl/music/scales.rb', line 914 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)
1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 |
# File 'lib/musa-dsl/music/scales.rb', line 1028 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
1115 1116 1117 1118 1119 |
# File 'lib/musa-dsl/music/scales.rb', line 1115 def self. .merge() .merge() end |
.metadata_value(key) ⇒ Object?
Returns a specific metadata value.
1128 1129 1130 |
# File 'lib/musa-dsl/music/scales.rb', line 1128 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.
932 933 934 |
# File 'lib/musa-dsl/music/scales.rb', line 932 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.
1101 1102 1103 |
# File 'lib/musa-dsl/music/scales.rb', line 1101 def self. @custom_metadata = nil end |
Instance Method Details
#==(other) ⇒ Boolean
Checks scale kind equality.
893 894 895 |
# File 'lib/musa-dsl/music/scales.rb', line 893 def ==(other) self.class == other.class && @tuning == other.tuning end |
#absolut ⇒ Scale
Returns scale with absolute root (MIDI 0).
849 850 851 |
# File 'lib/musa-dsl/music/scales.rb', line 849 def absolut self[0] end |
#default_root ⇒ Scale
Returns scale with default root (middle C, MIDI 60).
837 838 839 |
# File 'lib/musa-dsl/music/scales.rb', line 837 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.
878 879 880 881 882 883 884 885 886 887 |
# File 'lib/musa-dsl/music/scales.rb', line 878 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.
822 823 824 825 |
# File 'lib/musa-dsl/music/scales.rb', line 822 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.
900 901 902 |
# File 'lib/musa-dsl/music/scales.rb', line 900 def inspect "<#{self.class.name}: tuning = #{@tuning}>" end |