Module: OutroRails::Instruments::Guitar::ChordShapes

Defined in:
lib/outro_rails/instruments/guitar/chord_shapes.rb

Overview

The library of guitar chord shapes (CAGED and friends) in standard tuning, keyed by [quality, extension].

Each shape is written relative to its open position: frets are the frets you'd play for the shape's namesake chord (the :c shape is spelled as open C). A shape becomes any other root by shifting every fret up by the same offset. Shapes carry:

root_string  – the string whose note names the chord
frets        – { string => fret }, string 6 (low) .. 1 (high)
movable_omit – strings dropped when the shape is barred up the
             neck (e.g. the awkward pinky notes of the G and A
             shapes); open-position voicings keep every string.

Chords without an entry here (9/11/13, altered dominants, added tones, ...) fall back to stub generation - add shapes below to bring them into the generator.

Defined Under Namespace

Classes: Shape

Constant Summary collapse

CAGED_LETTERS =

The five CAGED families, in their conventional order.

%w[C A G E D].freeze
OPEN_BASS_MAX_FRET =

Open-chord bass extensions (slash chords for Travis picking etc.) may only reach this far up the neck.

3
SHAPES =

The shape table itself: { [quality, extension] => [Shape, ...] }. A missing key means the generator falls back to stubs.

{
  # Full CAGED, ordered C A G E D (order is cosmetic - voicings are
  # sorted by neck position at generation time).
  [ :major, nil ] => [
    shape(:c, 5, { 5 => 3, 4 => 2, 3 => 0, 2 => 1, 1 => 0 }),
    shape(:a, 5, { 5 => 0, 4 => 2, 3 => 2, 2 => 2, 1 => 0 }),
    shape(:g, 6, { 6 => 3, 5 => 2, 4 => 0, 3 => 0, 2 => 0, 1 => 3 }),
    shape(:e, 6, { 6 => 0, 5 => 2, 4 => 2, 3 => 1, 2 => 0, 1 => 0 }),
    shape(:d, 4, { 4 => 0, 3 => 2, 2 => 3, 1 => 2 })
  ],

  # The practical minor shapes. The Cm and Gm forms exist in theory
  # but are unplayable as grips; add them here if you ever want them.
  [ :minor, nil ] => [
    shape(:a_minor, 5, { 5 => 0, 4 => 2, 3 => 2, 2 => 1, 1 => 0 }),
    shape(:e_minor, 6, { 6 => 0, 5 => 2, 4 => 2, 3 => 0, 2 => 0, 1 => 0 }),
    shape(:d_minor, 4, { 4 => 0, 3 => 2, 2 => 3, 1 => 1 })
  ],

  # Power chords: root on string 6 and string 5 only.
  [ :power, nil ] => [
    shape(:e_power, 6, { 6 => 0, 5 => 2, 4 => 2 }),
    shape(:a_power, 5, { 5 => 0, 4 => 2, 3 => 2 })
  ],

  # Dominant sevenths get the full CAGED treatment. The G7 shape
  # keeps string 1 - it carries the seventh.
  [ :major, :'7' ] => [
    shape(:c7, 5, { 5 => 3, 4 => 2, 3 => 3, 2 => 1, 1 => 0 }),
    shape(:a7, 5, { 5 => 0, 4 => 2, 3 => 0, 2 => 2, 1 => 0 }),
    shape(:g7, 6, { 6 => 3, 5 => 2, 4 => 0, 3 => 0, 2 => 0, 1 => 1 }),
    shape(:e7, 6, { 6 => 0, 5 => 2, 4 => 0, 3 => 1, 2 => 0, 1 => 0 }),
    shape(:d7, 4, { 4 => 0, 3 => 2, 2 => 1, 1 => 2 })
  ],

  [ :major, :maj7 ] => [
    shape(:cmaj7, 5, { 5 => 3, 4 => 2, 3 => 0, 2 => 0, 1 => 0 }),
    shape(:amaj7, 5, { 5 => 0, 4 => 2, 3 => 1, 2 => 2, 1 => 0 }),
    shape(:gmaj7, 6, { 6 => 3, 5 => 2, 4 => 0, 3 => 0, 2 => 0, 1 => 2 }),
    shape(:emaj7, 6, { 6 => 0, 5 => 2, 4 => 1, 3 => 1, 2 => 0, 1 => 0 }),
    shape(:dmaj7, 4, { 4 => 0, 3 => 2, 2 => 2, 1 => 2 })
  ],

  [ :major, :'6' ] => [
    shape(:c6, 5, { 5 => 3, 4 => 2, 3 => 2, 2 => 1, 1 => 0 }),
    shape(:a6, 5, { 5 => 0, 4 => 2, 3 => 2, 2 => 2, 1 => 2 }),
    shape(:g6, 6, { 6 => 3, 5 => 2, 4 => 0, 3 => 0, 2 => 0, 1 => 0 }),
    shape(:e6, 6, { 6 => 0, 5 => 2, 4 => 2, 3 => 1, 2 => 2, 1 => 0 }),
    shape(:d6, 4, { 4 => 0, 3 => 2, 2 => 0, 1 => 2 })
  ],

  [ :minor, :'7' ] => [
    shape(:am7, 5, { 5 => 0, 4 => 2, 3 => 0, 2 => 1, 1 => 0 }),
    shape(:em7, 6, { 6 => 0, 5 => 2, 4 => 0, 3 => 0, 2 => 0, 1 => 0 }),
    shape(:dm7, 4, { 4 => 0, 3 => 2, 2 => 1, 1 => 1 })
  ],

  [ :minor, :maj7 ] => [
    shape(:am_maj7, 5, { 5 => 0, 4 => 2, 3 => 1, 2 => 1, 1 => 0 }),
    shape(:em_maj7, 6, { 6 => 0, 5 => 2, 4 => 1, 3 => 0, 2 => 0, 1 => 0 }),
    shape(:dm_maj7, 4, { 4 => 0, 3 => 2, 2 => 2, 1 => 1 })
  ],

  [ :minor, :'6' ] => [
    shape(:am6, 5, { 5 => 0, 4 => 2, 3 => 2, 2 => 1, 1 => 2 }),
    shape(:em6, 6, { 6 => 0, 5 => 2, 4 => 2, 3 => 0, 2 => 2, 1 => 0 }),
    shape(:dm6, 4, { 4 => 0, 3 => 2, 2 => 0, 1 => 1 })
  ],

  [ :suspended_2, nil ] => [
    shape(:a_sus2, 5, { 5 => 0, 4 => 2, 3 => 2, 2 => 0, 1 => 0 }),
    shape(:d_sus2, 4, { 4 => 0, 3 => 2, 2 => 3, 1 => 0 })
  ],

  [ :suspended_2, :'7' ] => [
    shape(:a7sus2, 5, { 5 => 0, 4 => 2, 3 => 0, 2 => 0, 1 => 0 }),
    shape(:d7sus2, 4, { 4 => 0, 3 => 2, 2 => 1, 1 => 0 })
  ],

  [ :suspended_4, nil ] => [
    shape(:a_sus4, 5, { 5 => 0, 4 => 2, 3 => 2, 2 => 3, 1 => 0 }),
    shape(:e_sus4, 6, { 6 => 0, 5 => 2, 4 => 2, 3 => 2, 2 => 0, 1 => 0 }),
    shape(:d_sus4, 4, { 4 => 0, 3 => 2, 2 => 3, 1 => 3 })
  ],

  [ :suspended_4, :'7' ] => [
    shape(:a7sus4, 5, { 5 => 0, 4 => 2, 3 => 0, 2 => 3, 1 => 0 }),
    shape(:e7sus4, 6, { 6 => 0, 5 => 2, 4 => 0, 3 => 2, 2 => 0, 1 => 0 }),
    shape(:d7sus4, 4, { 4 => 0, 3 => 2, 2 => 1, 1 => 3 })
  ],

  [ :diminished, nil ] => [
    shape(:a_dim, 5, { 5 => 0, 4 => 1, 3 => 2, 2 => 1 }),
    shape(:d_dim, 4, { 4 => 0, 3 => 1, 2 => 3, 1 => 1 })
  ],

  # quality :diminished + extension '7' is symbolized m7b5.
  [ :diminished, :'7' ] => [
    shape(:am7b5, 5, { 5 => 0, 4 => 1, 3 => 0, 2 => 1 }),
    shape(:dm7b5, 4, { 4 => 0, 3 => 1, 2 => 1, 1 => 1 })
  ],

  [ :diminished, :dim7 ] => [
    shape(:adim7, 5, { 5 => 1, 4 => 2, 3 => 0, 2 => 2 }),
    shape(:ddim7, 4, { 4 => 0, 3 => 1, 2 => 0, 1 => 1 })
  ],

  [ :augmented, nil ] => [
    shape(:a_aug, 5, { 5 => 0, 4 => 3, 3 => 2, 2 => 2, 1 => 1 }),
    shape(:e_aug, 6, { 6 => 0, 5 => 3, 4 => 2, 3 => 1, 2 => 1, 1 => 0 })
  ],

  [ :augmented, :maj7 ] => [
    shape(:amaj7sharp5, 5, { 5 => 0, 4 => 3, 3 => 1, 2 => 2 }),
    shape(:emaj7sharp5, 6, { 6 => 0, 5 => 3, 4 => 1, 3 => 1 })
  ]
}.freeze

Class Method Summary collapse

Class Method Details

.chords_for_letter(letter) ⇒ Object

Every chord type with a shape in a CAGED family, in vocabulary order: [[quality, extension, shape], ...]. Not every type has every letter (minor has no C or G grip, sus2 only A and D).



205
206
207
208
209
210
211
212
# File 'lib/outro_rails/instruments/guitar/chord_shapes.rb', line 205

def chords_for_letter(letter)
  target = letter.to_s.upcase

  SHAPES.filter_map do |(quality, extension), shapes|
    shape = shapes.find { |candidate| candidate.caged_letter == target }
    [ quality, extension, shape ] if shape
  end
end

.for(quality:, extension: nil) ⇒ Object

The shapes for a chord type, or nil when none are defined.



193
194
195
# File 'lib/outro_rails/instruments/guitar/chord_shapes.rb', line 193

def for(quality:, extension: nil)
  SHAPES[[ quality&.to_sym, extension&.to_sym ]]
end

.namesake_pitch_class(shape, guitar: Guitar.new) ⇒ Object

Pitch class of the note naming the shape's open-position chord (the E shape's namesake root is the open low E). Capo charts build on this too.



237
238
239
# File 'lib/outro_rails/instruments/guitar/chord_shapes.rb', line 237

def namesake_pitch_class(shape, guitar: Guitar.new)
  guitar.pitch_class_at(shape.root_string, shape.root_fret)
end

.positions(shape, root, guitar: Guitar.new) ⇒ Object

[string, fret] positions playing shape rooted on root: the whole grip shifts up by the semitone distance from the shape's namesake root. Open position (offset 0) keeps every string; barred positions drop the shape's movable_omit strings.



218
219
220
221
222
223
# File 'lib/outro_rails/instruments/guitar/chord_shapes.rb', line 218

def positions(shape, root, guitar: Guitar.new)
  offset = root_offset(shape, root, guitar: guitar)
  frets = offset.zero? ? shape.frets : shape.movable_frets

  frets.map { |string, fret| [ string, fret + offset ] }
end

.root_offset(shape, root, guitar: Guitar.new) ⇒ Object

Semitones the shape shifts up the neck to sound root (0..11).



226
227
228
229
230
231
232
# File 'lib/outro_rails/instruments/guitar/chord_shapes.rb', line 226

def root_offset(shape, root, guitar: Guitar.new)
  root_pitch_class = Theory::NoteName.parse(root).pitch_class
  namesake_pitch_class = namesake_pitch_class(shape, guitar: guitar)
  offset = root_pitch_class - namesake_pitch_class

  offset % Theory::Interval::SEMITONES_PER_OCTAVE
end

.supports?(quality:, extension: nil) ⇒ Boolean

Whether the library has shapes for a chord type.

Returns:

  • (Boolean)


198
199
200
# File 'lib/outro_rails/instruments/guitar/chord_shapes.rb', line 198

def supports?(quality:, extension: nil)
  SHAPES.key?([ quality&.to_sym, extension&.to_sym ])
end