Module: OutroRails::Theory::Transposer
- Defined in:
- lib/outro_rails/theory/transposer.rb
Overview
Transposes freeform chord-chart text between chord symbols and Nashville numbers, or between keys. Tokens that don't parse as a known chord (or number) pass through untouched, so lyrics and other notation survive:
Transposer.transpose_chords("C Am7 F G7", from: "C", to: "Eb")
# => "Eb Cm7 Ab Bb7"
Transposer.chords_to_nashville("C Am7 F G7", key: "C")
# => "1 6m7 4 57"
Transposer.nashville_to_chords("1 6m7 4 5", key: "G")
# => "G Em7 C D"
Constant Summary collapse
- NOTE_PATTERN =
A note name with its accidental, shared with NoteName so the two can't drift.
NoteName::NAME_PATTERN
- CHORD_PATTERN =
A whole chord symbol: root, everything up to a slash as the quality/extension suffix, then an optional slash bass.
%r{ \A (?<root>#{NOTE_PATTERN}) (?<suffix>[^/]*) (?:/(?<bass>#{NOTE_PATTERN}))? \z }x- WRAPPER_PATTERN =
Chart punctuation that can wrap a chord: "|C|", "(Am)", "F,".
%r{ \A (?<open>[(\[|]*) (?<core>.*?) (?<close>[)\]|,.;:!?]*) \z }mx- KNOWN_SUFFIXES =
Every quality/extension suffix the vocabulary can symbolize.
ChordVocabulary::SYMBOLS.values .flat_map(&:values) .uniq .freeze
- TAIL_PATTERN =
/(?:#{tails})\z/
Class Method Summary collapse
-
.annotate_chords(text, from: nil, to: nil) ⇒ Object
Wraps every recognized chord token in
textwith whatever the block returns, given the symbol as it should be displayed - lyrics and punctuation pass through untouched. -
.chords_to_nashville(text, key:) ⇒ Object
Chord symbols => Nashville numbers relative to
key; the quality suffix carries over verbatim (Am7 in C => 6m7, Bb in C => b7). -
.nashville_to_chords(text, key:) ⇒ Object
Nashville numbers (or roman numerals) => chord symbols in
key, via Key#nashville. -
.scan_chords(text) ⇒ Object
Every distinct chord symbol recognized in freeform chart text, in the order each first appears; lyrics and anything else pass unrecognized ("Amazing Grace, how sweet the C sound" only yields "C").
-
.split_bass(symbol) ⇒ Object
Splits a chord symbol into its base symbol and slash bass:.
-
.transpose_chords(text, from:, to:) ⇒ Object
Chord symbols respelled into a new key; each root (and slash bass) keeps its relationship to the key, so G major's F# lands on Bb major's A.
Class Method Details
.annotate_chords(text, from: nil, to: nil) ⇒ Object
Wraps every recognized chord token in text with whatever the
block returns, given the symbol as it should be displayed -
lyrics and punctuation pass through untouched. Pass from/to to
transpose the wrapped symbol at the same time; omitted (or equal)
leaves chords spelled as written. Used to render a chart body with
its chords highlighted, optionally previewed in another key,
without mutating anything in storage:
Transposer.annotate_chords(body, from: "C", to: "D")
{ |symbol| "<mark>#{symbol}</mark>" }
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 |
# File 'lib/outro_rails/theory/transposer.rb', line 174 def annotate_chords(text, from: nil, to: nil) from_key = from && Key.parse(from) to_key = to && Key.parse(to) transform(text) do |core| chord = parse_chord(core) next nil unless chord root = chord[:root] bass = chord[:bass] if from_key && to_key root = from_key.transpose_note(root, to: to_key) bass = bass && from_key.transpose_note(bass, to: to_key) end yield("#{root}#{chord[:suffix]}#{"/#{bass}" if bass}") end end |
.chords_to_nashville(text, key:) ⇒ Object
Chord symbols => Nashville numbers relative to key; the quality
suffix carries over verbatim (Am7 in C => 6m7, Bb in C => b7).
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/outro_rails/theory/transposer.rb', line 80 def chords_to_nashville(text, key:) key = Key.parse(key) transform(text) do |core| chord = parse_chord(core) next nil unless chord number = nashville_root(key, chord[:root]) next nil unless number if chord[:bass] bass = nashville_root(key, chord[:bass]) next nil unless bass end "#{number}#{chord[:suffix]}#{"/#{bass}" if bass}" end end |
.nashville_to_chords(text, key:) ⇒ Object
Nashville numbers (or roman numerals) => chord symbols in key,
via Key#nashville. Slash basses are degrees too: "1/3" in C => C/E.
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 |
# File 'lib/outro_rails/theory/transposer.rb', line 101 def nashville_to_chords(text, key:) key = Key.parse(key) transform(text) do |core| number, bass = core.split("/", 2) begin symbol = key.nashville(number).fetch(:symbol) if bass parsed = NashvilleNumber.parse(bass) note = key.note_for_degree( parsed.degree, parsed.accidental_offset ) symbol = "#{symbol}/#{note}" end symbol rescue ArgumentError, KeyError nil end end end |
.scan_chords(text) ⇒ Object
Every distinct chord symbol recognized in freeform chart text, in the order each first appears; lyrics and anything else pass unrecognized ("Amazing Grace, how sweet the C sound" only yields "C"). Used to find which chords a chart references, e.g. to look up their diagrams:
Transposer.scan_chords("C Am7 F G7") # => ["C", "Am7", "F"]
133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/outro_rails/theory/transposer.rb', line 133 def scan_chords(text) symbols = [] transform(text) do |core| chord = parse_chord(core) next nil unless chord symbol = "#{chord[:root]}#{chord[:suffix]}" \ "#{"/#{chord[:bass]}" if chord[:bass]}" symbols << symbol unless symbols.include?(symbol) nil # never replace - this only scans, it doesn't rewrite. end symbols end |
.split_bass(symbol) ⇒ Object
Splits a chord symbol into its base symbol and slash bass:
Transposer.split_bass("G/B") # => ["G", "B"]
Transposer.split_bass("Am7") # => ["Am7", nil]
Unrecognized input comes back unchanged with a nil bass, so callers can feed it anything scan_chords produced.
157 158 159 160 161 162 |
# File 'lib/outro_rails/theory/transposer.rb', line 157 def split_bass(symbol) chord = parse_chord(symbol.to_s) return [ symbol.to_s, nil ] unless chord [ "#{chord[:root]}#{chord[:suffix]}", chord[:bass] ] end |
.transpose_chords(text, from:, to:) ⇒ Object
Chord symbols respelled into a new key; each root (and slash bass) keeps its relationship to the key, so G major's F# lands on Bb major's A.
63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
# File 'lib/outro_rails/theory/transposer.rb', line 63 def transpose_chords(text, from:, to:) from = Key.parse(from) to = Key.parse(to) transform(text) do |core| chord = parse_chord(core) next nil unless chord root = from.transpose_note(chord[:root], to: to) bass = chord[:bass] && from.transpose_note(chord[:bass], to: to) "#{root}#{chord[:suffix]}#{"/#{bass}" if bass}" end end |