Skip to content

RomanNumeral

roman_numeral

Roman numeral analysis.

The RomanNumeral namespace parses roman-numeral strings into structured analysis objects. Used by Progression for chord progression conversions and by Key internally for diatonic chord generation.

The parser handles:

  • Case to encode mode: uppercase = major (I, V, bVII), lowercase = minor (i, iv, vi)
  • Accidentals: #, b, x (double-sharp), bb (double-flat)
  • Optional chord-type suffix: Vmaj7, iim7, bIII7b5
Example

from tonal_py import RomanNumeral RomanNumeral.get("V").interval '5P' RomanNumeral.get("bVII").interval '7m' RomanNumeral.get("iv").major False RomanNumeral.get("Vmaj7").chord_type 'maj7'

Source parity: @tonaljs/roman-numeral

tokenize

tokenize(s: str) -> tuple[str, str, str, str]

Tokenize a roman-numeral string into its components.

Parameters:

Name Type Description Default
s str

A roman-numeral string.

required

Returns:

Type Description
str

(full_match, accidentals, roman, chord_type). All four are

str

empty when input doesn't match.

Example

from tonal_py import RomanNumeral RomanNumeral.tokenize("VIIb5") ('VIIb5', '', 'VII', 'b5') RomanNumeral.tokenize("bVII") ('bVII', 'b', 'VII', '') RomanNumeral.tokenize("garbage") ('', '', '', '')

Source code in src/tonal_py/roman_numeral.py
def tokenize(s: str) -> tuple[str, str, str, str]:
    """Tokenize a roman-numeral string into its components.

    Args:
        s: A roman-numeral string.

    Returns:
        `(full_match, accidentals, roman, chord_type)`. All four are
        empty when input doesn't match.

    Example:
        >>> from tonal_py import RomanNumeral
        >>> RomanNumeral.tokenize("VIIb5")
        ('VIIb5', '', 'VII', 'b5')
        >>> RomanNumeral.tokenize("bVII")
        ('bVII', 'b', 'VII', '')
        >>> RomanNumeral.tokenize("garbage")
        ('', '', '', '')
    """
    m = _REGEX.match(s)
    if not m:
        return ("", "", "", "")
    return (m.group(0), m.group(1), m.group(2), m.group(3))

get

get(src: Any) -> RomanNumeral

Parse a roman numeral from a string, int, Pitch, or Interval.

Parameters:

Name Type Description Default
src Any

One of: - A string like "V7", "bVII", "iv", "VIIb5". - An int 0-6 selecting from the major roman names ["I", "II", "III", "IV", "V", "VI", "VII"] (note: 0-based, a tonal.js quirk). - A Pitch or Interval object — uses its step+alt fields.

required

Returns:

Type Description
RomanNumeral

A RomanNumeral dataclass. Returns NO_ROMAN_NUMERAL for

RomanNumeral

unrecognized input.

Example

from tonal_py import RomanNumeral RomanNumeral.get("V").interval '5P' RomanNumeral.get("V").major True RomanNumeral.get("v").major False RomanNumeral.get("Vmaj7").chord_type 'maj7' RomanNumeral.get(2).roman # 0-based: 2 -> "III" 'III'

Source code in src/tonal_py/roman_numeral.py
def get(src: Any) -> RomanNumeral:
    """Parse a roman numeral from a string, int, `Pitch`, or `Interval`.

    Args:
        src: One of:
            - A string like `"V7"`, `"bVII"`, `"iv"`, `"VIIb5"`.
            - An int 0-6 selecting from the major roman names
              `["I", "II", "III", "IV", "V", "VI", "VII"]` (note: 0-based,
              a tonal.js quirk).
            - A `Pitch` or `Interval` object — uses its step+alt fields.

    Returns:
        A `RomanNumeral` dataclass. Returns `NO_ROMAN_NUMERAL` for
        unrecognized input.

    Example:
        >>> from tonal_py import RomanNumeral
        >>> RomanNumeral.get("V").interval
        '5P'
        >>> RomanNumeral.get("V").major
        True
        >>> RomanNumeral.get("v").major
        False
        >>> RomanNumeral.get("Vmaj7").chord_type
        'maj7'
        >>> RomanNumeral.get(2).roman      # 0-based: 2 -> "III"
        'III'
    """
    if isinstance(src, str):
        cached = _CACHE.get(src)
        if cached is not None:
            return cached  # type: ignore[return-value]
        value = _parse(src)
        _CACHE[src] = value
        return value
    if isinstance(src, bool):
        # bool is a subclass of int in Python; rule it out before isinstance(int)
        return NO_ROMAN_NUMERAL
    if isinstance(src, int):
        # 1-based: NAMES[src] gives the (src-1)th. JS uses NAMES[src] directly,
        # which means src=0 returns NAMES[0]="I" — preserve that quirk.
        return get(NAMES[src] if 0 <= src < len(NAMES) else "")
    if _pitch.is_pitch(src):
        return _from_pitch(src)
    if _pitch.is_named_pitch(src):
        return get(src.name)
    return NO_ROMAN_NUMERAL

names

names(major: bool = True) -> list[str]

Return the seven roman numerals.

Parameters:

Name Type Description Default
major bool

If True (default), returns uppercase (major); if False, returns lowercase (minor).

True

Returns:

Type Description
list[str]

["I", ..., "VII"] or ["i", ..., "vii"].

Example

from tonal_py import RomanNumeral RomanNumeral.names() ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII'] RomanNumeral.names(major=False) ['i', 'ii', 'iii', 'iv', 'v', 'vi', 'vii']

Source code in src/tonal_py/roman_numeral.py
def names(major: bool = True) -> list[str]:
    """Return the seven roman numerals.

    Args:
        major: If True (default), returns uppercase (major); if False,
            returns lowercase (minor).

    Returns:
        `["I", ..., "VII"]` or `["i", ..., "vii"]`.

    Example:
        >>> from tonal_py import RomanNumeral
        >>> RomanNumeral.names()
        ['I', 'II', 'III', 'IV', 'V', 'VI', 'VII']
        >>> RomanNumeral.names(major=False)
        ['i', 'ii', 'iii', 'iv', 'v', 'vi', 'vii']
    """
    return (NAMES if major else NAMES_MINOR).copy()