Class: OutroRails::Theory::NashvilleNumber
- Inherits:
-
Object
- Object
- OutroRails::Theory::NashvilleNumber
- Defined in:
- lib/outro_rails/theory/nashville_number.rb
Overview
Parses Nashville numbers ("1", "4", "2m", "b7", and with extensions riding along: "2m7", "57" is 5 with a dominant 7) and roman numerals ("IV", "ii", "V7", "bVII", "vii°"). The result is key-agnostic; Theory::Key#nashville resolves it to a chord.
Defined Under Namespace
Classes: ParseResult
Constant Summary collapse
- ROMAN_NUMERALS =
Uppercase numerals in scale-degree order, indexed by degree - 1.
%w[I II III IV V VI VII].freeze
- ROMAN_DEGREES =
Lowercase numeral => degree, derived so the two tables can't drift.
ROMAN_NUMERALS .each_with_index .to_h { |numeral, index| [ numeral.downcase, index + 1 ] } .freeze
- QUALITY_SUFFIXES =
Every accepted spelling of a quality suffix. "" maps to nil so the caller can fall back to the quality implied by numeral case.
{ "" => nil, "m" => :minor, "min" => :minor, "-" => :minor, "maj" => :major, "dim" => :diminished, "\u00B0" => :diminished, "o" => :diminished, "aug" => :augmented, "+" => :augmented, "sus2" => :suspended_2, "sus4" => :suspended_4, "sus" => :suspended_4, "5" => :power }.freeze
- EXTENSION_SUFFIXES =
Extensions recognized after the degree/quality, matching ChordVocabulary::EXTENSIONS keys.
%w[maj7 dim7 6 7 9 11 13].freeze
- ARABIC_PATTERN =
Splits a symbol into optional accidental, degree, and the suffix carrying quality and extension - arabic ("b7m") and roman ("bviim").
/\A(?<accidental>[b#]?)(?<degree>[1-7])(?<rest>.*)\z/- ROMAN_PATTERN =
/\A(?<accidental>[b#]?)(?<numeral>[ivIV]+)(?<rest>.*)\z/- QUALITY_DISPLAY =
Display suffix per quality for arabic Nashville numbers; qualities not listed fall back to their name.
{ major: "", minor: "m", diminished: "\u00B0", augmented: "+" }.freeze
- ACCIDENTAL_DISPLAY =
Semitone offset => the accidental that writes it.
{ -1 => "b", 0 => "", 1 => "#" }.freeze
- ACCIDENTAL_OFFSETS =
Inverse of ACCIDENTAL_DISPLAY, for parsing.
ACCIDENTAL_DISPLAY.invert.freeze
Class Method Summary collapse
-
.format(degree:, quality: :major, accidental_offset: 0) ⇒ Object
Formats a degree + quality as an arabic Nashville number, parseable back by .parse: (2, :minor) => "2m", (7, :diminished) => "7°".
-
.parse(input) ⇒ Object
Parses either notation into a ParseResult, trying arabic first.
-
.roman(degree:, quality: :major, accidental_offset: 0) ⇒ Object
The same chord as a roman numeral, case carrying the quality: (2, :minor) => "ii", (5, :major) => "V", (7, :diminished) => "vii°".
Class Method Details
.format(degree:, quality: :major, accidental_offset: 0) ⇒ Object
Formats a degree + quality as an arabic Nashville number, parseable back by .parse: (2, :minor) => "2m", (7, :diminished) => "7°".
96 97 98 99 |
# File 'lib/outro_rails/theory/nashville_number.rb', line 96 def self.format(degree:, quality: :major, accidental_offset: 0) "#{ACCIDENTAL_DISPLAY.fetch(accidental_offset)}#{degree}" \ "#{QUALITY_DISPLAY.fetch(quality) { quality.to_s }}" end |
.parse(input) ⇒ Object
Parses either notation into a ParseResult, trying arabic first. Raises ArgumentError on anything unrecognized.
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/outro_rails/theory/nashville_number.rb', line 73 def self.parse(input) text = input.to_s.strip raise ArgumentError, "blank Nashville number" if text.empty? if (match = text.match(ARABIC_PATTERN)) from_match(match, degree: Integer(match[:degree]), explicit_quality: nil) elsif (match = text.match(ROMAN_PATTERN)) numeral = match[:numeral] degree = ROMAN_DEGREES.fetch(numeral.downcase) do raise ArgumentError, "unknown roman numeral #{numeral.inspect}" end # Case carries quality: IV is major, iv is minor. explicit_quality = numeral == numeral.downcase ? :minor : :major from_match(match, degree: degree, explicit_quality: explicit_quality) else raise ArgumentError, "cannot parse Nashville number #{input.inspect}" end end |
.roman(degree:, quality: :major, accidental_offset: 0) ⇒ Object
The same chord as a roman numeral, case carrying the quality: (2, :minor) => "ii", (5, :major) => "V", (7, :diminished) => "vii°".
103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/outro_rails/theory/nashville_number.rb', line 103 def self.roman(degree:, quality: :major, accidental_offset: 0) numeral = ROMAN_NUMERALS.fetch(degree - 1) accidental = ACCIDENTAL_DISPLAY.fetch(accidental_offset) case quality when :minor then "#{accidental}#{numeral.downcase}" when :diminished then "#{accidental}#{numeral.downcase}°" when :augmented then "#{accidental}#{numeral}+" else "#{accidental}#{numeral}" end end |