Module: OutroRails::Theory::ChordVocabulary
- Defined in:
- lib/outro_rails/theory/chord_vocabulary.rb
Overview
The chord vocabulary including which qualities exist, which extensions/alterations/added tones apply to them, the intervals they contribute, and how the combinations are symbolized.
Constant Summary collapse
- QUALITIES =
Qualities mapped to their intervals.
{ major: [ 0, 4, 7 ], minor: [ 0, 3, 7 ], diminished: [ 0, 3, 6 ], augmented: [ 0, 4, 8 ], suspended_2: [ 0, 2, 7 ], suspended_4: [ 0, 5, 7 ], power: [ 0, 7 ] }.freeze
- EXTENSIONS =
Which extensions are valid on which qualities, and the intervals they add.
"Extension" here means anything appended to a bare triad symbol as one lookup axis - so it deliberately includes '6' (strictly an added tone) and 'dim7' (strictly part of the quality) alongside true extensions like '7'/'9'/'11'/'13'.
{ '6': { add: [ 9 ], qualities: %i[major minor] }, '7': { add: [ 10 ], qualities: %i[major minor diminished suspended_2 suspended_4] }, 'maj7': { add: [ 11 ], qualities: %i[major minor augmented] }, 'dim7': { add: [ 9 ], qualities: %i[diminished] }, '9': { add: [ 10, 14 ], qualities: %i[major minor suspended_4] }, '11': { add: [ 10, 14, 17 ], qualities: %i[major minor] }, '13': { add: [ 10, 14, 21 ], qualities: %i[major minor suspended_4] } }.freeze
- SYMBOLS =
How each quality + extension pair is symbolized. :base is the bare chord.
{ major: { base: "", '6': "6", '7': "7", 'maj7': "maj7", '9': "9", '11': "11", '13': "13" }, minor: { base: "m", '6': "m6", '7': "m7", 'maj7': "m(maj7)", '9': "m9", '11': "m11", '13': "m13" }, diminished: { base: "dim", '7': "m7b5", 'dim7': "dim7" }, augmented: { base: "aug", "maj7": "maj7#5" }, suspended_2: { base: "sus2", '7': "7sus2" }, suspended_4: { base: "sus4", '7': "7sus4", '9': "9sus4", '13': "13sus4" }, power: { base: "5" } }.freeze
- ALTERATIONS =
natural: the diatonic interval the alteration replaces (if present) altered: the interval it becomes/adds.
{ 'b5': { natural: 7, altered: 6 }, '#5': { natural: 7, altered: 8 }, 'b9': { natural: 14, altered: 13 }, '#9': { natural: 14, altered: 15 }, '#11': { natural: 17, altered: 18 }, 'b13': { natural: 21, altered: 20 } }.freeze
- ADDED_TONE_INTERVALS =
Mapping of added-tone names to the intervals (in semitones) they contribute above the root.
{ add2: 2, add4: 5, add6: 9, add9: 14, add11: 17, add13: 21 }.freeze
Class Method Summary collapse
-
.apply_alterations(base, alterations) ⇒ Object
Apply interval alterations by replacing natural intervals with their altered equivalents, then return the intervals in ascending order.
-
.intervals(quality:, extension: nil) ⇒ Object
The intervals for a quality with an extension merged in (drops duplicated pitch classes, e.g. the 11th on a sus4 chord).
-
.merge(base, additions) ⇒ Object
Merge added intervals into a base set; drops any interval whose pitch class is already present.
-
.pitch_classes(intervals) ⇒ Object
Reduce intervals to pitch classes (modulo 12), discarding octave information.
-
.slug(symbol) ⇒ Object
URL/file slug for a chord or voicing symbol: 'F#m7b5' => 'fsharpm7flat5', 'A/C#' => 'aslashcsharp'.
-
.symbol(root_name:, quality:, extension: nil, alterations: [], added_tones: []) ⇒ Object
"Cmaj7", "F#m7b5", "Bbsus4" - the canonical symbol for a root/quality/extension/etc.
Class Method Details
.apply_alterations(base, alterations) ⇒ Object
Apply interval alterations by replacing natural intervals with their altered equivalents, then return the intervals in ascending order.
163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/outro_rails/theory/chord_vocabulary.rb', line 163 def apply_alterations(base, alterations) intervals = base.dup alterations.each do |alteration| spec = ALTERATIONS.fetch(alteration.to_sym) intervals.delete(spec[:natural]) intervals << spec[:altered] end intervals.sort end |
.intervals(quality:, extension: nil) ⇒ Object
The intervals for a quality with an extension merged in (drops duplicated pitch classes, e.g. the 11th on a sus4 chord).
154 155 156 157 158 159 |
# File 'lib/outro_rails/theory/chord_vocabulary.rb', line 154 def intervals(quality:, extension: nil) base = QUALITIES.fetch(quality.to_sym) return base unless extension merge(base, EXTENSIONS.fetch(extension.to_sym).fetch(:add)) end |
.merge(base, additions) ⇒ Object
Merge added intervals into a base set; drops any interval whose pitch class is already present.
180 181 182 183 184 185 186 187 188 189 |
# File 'lib/outro_rails/theory/chord_vocabulary.rb', line 180 def merge(base, additions) pcs = pitch_classes(base) ( base + additions.reject do |i| pcs.include?(i % Interval::SEMITONES_PER_OCTAVE) end ).sort end |
.pitch_classes(intervals) ⇒ Object
Reduce intervals to pitch classes (modulo 12), discarding octave information.
193 194 195 |
# File 'lib/outro_rails/theory/chord_vocabulary.rb', line 193 def pitch_classes(intervals) intervals.map { |i| i % Interval::SEMITONES_PER_OCTAVE } end |
.slug(symbol) ⇒ Object
URL/file slug for a chord or voicing symbol: 'F#m7b5' => 'fsharpm7flat5', 'A/C#' => 'aslashcsharp'
141 142 143 144 145 146 147 148 149 150 |
# File 'lib/outro_rails/theory/chord_vocabulary.rb', line 141 def slug(symbol) symbol .gsub("b", "flat") .gsub("#", "sharp") .gsub("(", "-") .gsub(")", "-") .gsub("/", "slash") .delete_suffix("-") .downcase end |
.symbol(root_name:, quality:, extension: nil, alterations: [], added_tones: []) ⇒ Object
"Cmaj7", "F#m7b5", "Bbsus4" - the canonical symbol for a root/quality/extension/etc. combination.
129 130 131 132 133 134 135 136 137 |
# File 'lib/outro_rails/theory/chord_vocabulary.rb', line 129 def symbol(root_name:, quality:, extension: nil, alterations: [], added_tones: []) "#{root_name}" \ "#{SYMBOLS.fetch(quality.to_sym).fetch(extension&.to_sym || :base)}" \ "#{alterations.join}#{added_tones.join}" end |