Module music_df.harmony.chords
Functions
def change_rn_mode(rn: str, mode: Literal['M', 'm']) ‑> str-
Expand source code
def change_rn_mode(rn: str, mode: Mode) -> str: n_flats = len(rn) - len(rn.lstrip("b")) if mode == "M": return rn[:n_flats] + rn[n_flats:].upper() return rn[:n_flats] + rn[n_flats:].lower() def get_handle_nested_secondary_rns_cache(sixth_minor: music21.roman.Minor67Default = Minor67Default.CAUTIONARY,
seventh_minor: music21.roman.Minor67Default = Minor67Default.CAUTIONARY,
rn_to_spelled_pitch_cache: CacheDict[tuple[str, str], str] | None = None) ‑> CacheDict[tuple[str, str], str]-
Expand source code
def get_handle_nested_secondary_rns_cache( sixth_minor: Minor67Default = Minor67Default.CAUTIONARY, seventh_minor: Minor67Default = Minor67Default.CAUTIONARY, rn_to_spelled_pitch_cache: CacheDict[tuple[str, str], str] | None = None, ) -> CacheDict[tuple[str, str], str]: """ >>> cache = get_handle_nested_secondary_rns_cache() >>> cache[("vi/III", "M")] '#i' >>> cache[("vi/III", "B")] '#i' """ return CacheDict( lambda token_and_mode_or_key: handle_nested_secondary_rns( *token_and_mode_or_key, sixth_minor=sixth_minor, seventh_minor=seventh_minor, rn_to_spelled_pitch_cache=rn_to_spelled_pitch_cache, ) )>>> cache = get_handle_nested_secondary_rns_cache() >>> cache[("vi/III", "M")] '#i' >>> cache[("vi/III", "B")] '#i' def get_key_pc_cache(minor_scale_type: Literal['harmonic', 'natural', 'melodic'] = 'harmonic',
hex_str: bool = False) ‑> CacheDict[str, list[int] | str]-
Expand source code
def get_key_pc_cache( minor_scale_type: MinorScaleType = "harmonic", hex_str: bool = False ) -> CacheDict[str, list[int] | str]: """ >>> key_pc_cache = get_key_pc_cache() >>> key_pc_cache["c"] [0, 2, 3, 5, 7, 8, 11] >>> key_pc_cache["c"] [0, 2, 3, 5, 7, 8, 11] >>> hex_key_pc_cache = get_key_pc_cache(hex_str=True) >>> hex_key_pc_cache["c"] '023578b' We can double-check that the caching is working by modifying the cached value (which in normal circumstances we should avoid ever doing): >>> cached_value = key_pc_cache["c"] >>> cached_value[-1] = "foobar" >>> key_pc_cache["c"] [0, 2, 3, 5, 7, 8, 'foobar'] """ key_pc_cache: CacheDict[str, list[int] | str] = CacheDict( lambda key: get_key_pitch_classes(key, minor_scale_type, hex_str) ) return key_pc_cache>>> key_pc_cache = get_key_pc_cache() >>> key_pc_cache["c"] [0, 2, 3, 5, 7, 8, 11] >>> key_pc_cache["c"] [0, 2, 3, 5, 7, 8, 11] >>> hex_key_pc_cache = get_key_pc_cache(hex_str=True) >>> hex_key_pc_cache["c"] '023578b'We can double-check that the caching is working by modifying the cached value (which in normal circumstances we should avoid ever doing):
>>> cached_value = key_pc_cache["c"] >>> cached_value[-1] = "foobar" >>> key_pc_cache["c"] [0, 2, 3, 5, 7, 8, 'foobar'] def get_key_pitch_classes(key: str,
minor_scale_type: Literal['harmonic', 'natural', 'melodic'] = 'harmonic',
hex_str: bool = False) ‑> list[int] | str-
Expand source code
def get_key_pitch_classes( key: str, minor_scale_type: MinorScaleType = "harmonic", hex_str: bool = False ) -> list[int] | str: """ Get the pitch classes of a key. >>> get_key_pitch_classes("c") [0, 2, 3, 5, 7, 8, 11] >>> get_key_pitch_classes("C") [0, 2, 4, 5, 7, 9, 11] >>> get_key_pitch_classes("a", minor_scale_type="natural") [9, 11, 0, 2, 4, 5, 7] >>> get_key_pitch_classes("a-", minor_scale_type="melodic") [8, 10, 11, 1, 3, 5, 7] """ k = Key(key) if k.mode == "major": out = [(p + k.tonic.pitchClass) % 12 for p in SCALES["major"]] else: out = [(p + k.tonic.pitchClass) % 12 for p in SCALES[minor_scale_type]] if hex_str: return pc_ints_to_hex_str(out) return outGet the pitch classes of a key.
>>> get_key_pitch_classes("c") [0, 2, 3, 5, 7, 8, 11]>>> get_key_pitch_classes("C") [0, 2, 4, 5, 7, 9, 11]>>> get_key_pitch_classes("a", minor_scale_type="natural") [9, 11, 0, 2, 4, 5, 7] >>> get_key_pitch_classes("a-", minor_scale_type="melodic") [8, 10, 11, 1, 3, 5, 7] def get_mode_from_rn_str(rn: str) ‑> Literal['M', 'm']-
Expand source code
def get_mode_from_rn_str(rn: str) -> Mode: assert rn[-1] in {"i", "I", "v", "V"} return "M" if rn[-1].isupper() else "m" def get_rn_pc_cache(sixth_minor: music21.roman.Minor67Default = Minor67Default.CAUTIONARY,
seventh_minor: music21.roman.Minor67Default = Minor67Default.CAUTIONARY,
case_matters: bool = True,
hex_str: bool = False,
rn_format: Literal['rnbert', 'music21'] = 'music21') ‑> CacheDict[tuple[str, str], list[int] | str]-
Expand source code
def get_rn_pc_cache( sixth_minor: Minor67Default = Minor67Default.CAUTIONARY, seventh_minor: Minor67Default = Minor67Default.CAUTIONARY, case_matters: bool = True, hex_str: bool = False, rn_format: Literal["rnbert", "music21"] = "music21", ) -> CacheDict[tuple[str, str], list[int] | str]: """ >>> rn_pc_cache = get_rn_pc_cache() >>> rn_pc_cache["I", "M"] [0, 4, 7] >>> rn_pc_cache["I", "M"] [0, 4, 7] >>> hex_rn_pc_cache = get_rn_pc_cache(hex_str=True) >>> hex_rn_pc_cache["I", "Eb"] '37a' >>> hex_rn_pc_cache["V/V", "C"] '269' >>> rnbert_cache = get_rn_pc_cache(hex_str=True, rn_format="rnbert") >>> rnbert_cache["VM/V", "C"] '269' >>> rnbert_cache["IVm/V", "C"] '037' >>> rnbert_cache["IM/bII", "Bb"] 'b36' We can double-check that the caching is working by modifying the cached value (which in normal circumstances we should avoid ever doing): >>> cached_value = rn_pc_cache["I", "M"] >>> cached_value[-1] = "foobar" >>> rn_pc_cache["I", "M"] [0, 4, 'foobar'] """ if rn_format == "rnbert": assert case_matters, ( "case_matters must be True with rn_format=rnbert. Even though the format only uses upper case, we translate to case-sensitive music21 symbols for processing" ) rn_translation_cache = get_rn_translation_cache(src="rnbert", dst="music21") else: rn_translation_cache = None def factory(rn_and_mode_or_key: tuple[str, str]) -> list[int] | str: return get_rn_pitch_classes( *rn_and_mode_or_key, sixth_minor=sixth_minor, seventh_minor=seventh_minor, case_matters=case_matters, hex_str=hex_str, rn_format=rn_format, rn_translation_cache=rn_translation_cache, ) return CacheDict(factory)>>> rn_pc_cache = get_rn_pc_cache() >>> rn_pc_cache["I", "M"] [0, 4, 7] >>> rn_pc_cache["I", "M"] [0, 4, 7] >>> hex_rn_pc_cache = get_rn_pc_cache(hex_str=True) >>> hex_rn_pc_cache["I", "Eb"] '37a'>>> hex_rn_pc_cache["V/V", "C"] '269' >>> rnbert_cache = get_rn_pc_cache(hex_str=True, rn_format="rnbert") >>> rnbert_cache["VM/V", "C"] '269' >>> rnbert_cache["IVm/V", "C"] '037' >>> rnbert_cache["IM/bII", "Bb"] 'b36'We can double-check that the caching is working by modifying the cached value (which in normal circumstances we should avoid ever doing):
>>> cached_value = rn_pc_cache["I", "M"] >>> cached_value[-1] = "foobar" >>> rn_pc_cache["I", "M"] [0, 4, 'foobar'] def get_rn_pitch_classes(rn: str,
mode_or_key: Literal['M', 'm'] | str,
sixth_minor: music21.roman.Minor67Default = Minor67Default.CAUTIONARY,
seventh_minor: music21.roman.Minor67Default = Minor67Default.CAUTIONARY,
case_matters: bool = True,
rn_format: Literal['rnbert', 'music21'] = 'music21',
rn_translation_cache: CacheDict[str, str] | None = None,
hex_str: bool = False) ‑> list[int] | str-
Expand source code
def get_rn_pitch_classes( rn: str, mode_or_key: Mode | str, sixth_minor: Minor67Default = Minor67Default.CAUTIONARY, seventh_minor: Minor67Default = Minor67Default.CAUTIONARY, case_matters: bool = True, rn_format: Literal["rnbert", "music21"] = "music21", rn_translation_cache: CacheDict[str, str] | None = None, hex_str: bool = False, ) -> list[int] | str: """ Get the pitch classes of a Roman numeral chord. >>> get_rn_pitch_classes("I", "M") [0, 4, 7] >>> get_rn_pitch_classes("i", "m") [0, 3, 7] >>> get_rn_pitch_classes("i", "c") [0, 3, 7] >>> get_rn_pitch_classes("Im", "c", rn_format="rnbert") [0, 3, 7] >>> get_rn_pitch_classes("I6", "C") [4, 7, 0] >>> get_rn_pitch_classes("I", "Eb", hex_str=True) '37a' """ key = MODE_TO_DEFAULT_KEY.get(mode_or_key, mode_or_key) # type:ignore if rn_translation_cache is not None: rn = rn_translation_cache[rn] elif rn_format == "rnbert": assert case_matters, ( "case_matters must be True with rn_format=rnbert. Even though the format only uses upper case, we translate to case-sensitive music21 symbols for processing" ) rn = translate_rns(rn, src="rnbert", dst="music21") roman = RomanNumeral( rn, key, sixthMinor=sixth_minor, seventhMinor=seventh_minor, caseMatters=case_matters, ) pitch_classes = roman.pitchClasses if key is not None: pitch_classes = transpose_pcs(pitch_classes, KEY_CACHE[key].tonic.pitchClass) if hex_str: return pc_ints_to_hex_str(pitch_classes) return pitch_classesGet the pitch classes of a Roman numeral chord.
>>> get_rn_pitch_classes("I", "M") [0, 4, 7]>>> get_rn_pitch_classes("i", "m") [0, 3, 7]>>> get_rn_pitch_classes("i", "c") [0, 3, 7]>>> get_rn_pitch_classes("Im", "c", rn_format="rnbert") [0, 3, 7]>>> get_rn_pitch_classes("I6", "C") [4, 7, 0]>>> get_rn_pitch_classes("I", "Eb", hex_str=True) '37a' def get_rn_to_spelled_pitch_cache(sixth_minor: music21.roman.Minor67Default = Minor67Default.CAUTIONARY,
seventh_minor: music21.roman.Minor67Default = Minor67Default.CAUTIONARY) ‑> CacheDict[tuple[str, str], str]-
Expand source code
def get_rn_to_spelled_pitch_cache( sixth_minor: Minor67Default = Minor67Default.CAUTIONARY, seventh_minor: Minor67Default = Minor67Default.CAUTIONARY, ) -> CacheDict[tuple[str, str], str]: """ >>> cache = get_rn_to_spelled_pitch_cache() >>> cache[("I", "C")] 'C' """ rn_to_spelled_pitch_cache: CacheDict[tuple[str, str], str] = CacheDict( lambda rn_and_key: rn_to_spelled_pitch( *rn_and_key, sixth_minor=sixth_minor, seventh_minor=seventh_minor ) ) return rn_to_spelled_pitch_cache>>> cache = get_rn_to_spelled_pitch_cache() >>> cache[("I", "C")] 'C' def get_rn_translation_cache(src: Literal['rnbert', 'music21'] = 'rnbert',
dst: Literal['rnbert', 'music21'] = 'music21') ‑> CacheDict[str, str]-
Expand source code
def get_rn_translation_cache( src: Literal["rnbert", "music21"] = "rnbert", dst: Literal["rnbert", "music21"] = "music21", ) -> CacheDict[str, str]: """ >>> cache = get_rn_translation_cache() >>> cache["Im"] 'i' >>> cache["VM"] 'V' >>> cache["IVm6"] 'iv6' """ rn_translation_cache: CacheDict[str, str] = CacheDict( lambda rn: translate_rns(rn, src, dst) ) return rn_translation_cache>>> cache = get_rn_translation_cache() >>> cache["Im"] 'i' >>> cache["VM"] 'V' >>> cache["IVm6"] 'iv6' def get_spelled_pitch_to_rn_cache(sixth_minor: music21.roman.Minor67Default = Minor67Default.CAUTIONARY,
seventh_minor: music21.roman.Minor67Default = Minor67Default.CAUTIONARY) ‑> CacheDict[tuple[str, str, str], str]-
Expand source code
def get_spelled_pitch_to_rn_cache( sixth_minor: Minor67Default = Minor67Default.CAUTIONARY, seventh_minor: Minor67Default = Minor67Default.CAUTIONARY, ) -> CacheDict[tuple[str, str, str], str]: """ >>> cache = get_spelled_pitch_to_rn_cache() >>> cache[("C", "C", "M")] 'I' """ spelled_pitch_to_rn_cache: CacheDict[tuple[str, str, str], str] = CacheDict( lambda spelled_pitch_and_key_and_rn_mode: spelled_pitch_to_rn( *spelled_pitch_and_key_and_rn_mode, sixth_minor=sixth_minor, seventh_minor=seventh_minor, ) ) return spelled_pitch_to_rn_cache>>> cache = get_spelled_pitch_to_rn_cache() >>> cache[("C", "C", "M")] 'I' def get_tonicization_cache(case_matters: bool = False, simplify_enharmonics: bool = True) ‑> CacheDict[tuple[str, str], str]-
Expand source code
def get_tonicization_cache( case_matters: bool = False, simplify_enharmonics: bool = True, ) -> CacheDict[tuple[str, str], str]: """ >>> cache = get_tonicization_cache() >>> cache[("V", "C")] 'G' """ tonicization_cache: CacheDict[tuple[str, str], str] = CacheDict( lambda secondary_rn_and_original_key: tonicization_to_key( *secondary_rn_and_original_key, case_matters, simplify_enharmonics ) ) return tonicization_cache>>> cache = get_tonicization_cache() >>> cache[("V", "C")] 'G' def handle_nested_secondary_rns(token: str,
mode_or_key: Literal['M', 'm'] | str,
sixth_minor: music21.roman.Minor67Default = Minor67Default.CAUTIONARY,
seventh_minor: music21.roman.Minor67Default = Minor67Default.CAUTIONARY,
rn_to_spelled_pitch_cache: CacheDict[tuple[str, str], str] | None = None) ‑> str-
Expand source code
def handle_nested_secondary_rns( token: str, mode_or_key: Mode | str, sixth_minor: Minor67Default = Minor67Default.CAUTIONARY, seventh_minor: Minor67Default = Minor67Default.CAUTIONARY, rn_to_spelled_pitch_cache: CacheDict[tuple[str, str], str] | None = None, ) -> str: """ >>> handle_nested_secondary_rns("vi/III", "M") '#i' >>> handle_nested_secondary_rns("vi/III", "Ab") '#i' >>> handle_nested_secondary_rns("V/V", "M") 'II' >>> handle_nested_secondary_rns("/V/V", "M") # leading slash is optional 'II' >>> handle_nested_secondary_rns("V/V/V", "M") 'VI' >>> handle_nested_secondary_rns("V/V/V/V", "M") 'III' >>> handle_nested_secondary_rns("v/V", "M") 'ii' >>> handle_nested_secondary_rns("vi/III", "m") 'i' >>> handle_nested_secondary_rns("V", "M") 'V' >>> handle_nested_secondary_rns("", "M") 'I' """ token = token.lstrip("/") if not token: return "I" if rn_to_spelled_pitch_cache is None: get_pitch_from_rn = lambda rn, key: rn_to_spelled_pitch( rn, key, sixth_minor, seventh_minor ) else: get_pitch_from_rn = lambda rn, key: rn_to_spelled_pitch_cache[(rn, key)] dummy_key = {"M": "C", "m": "c"}.get(mode_or_key, mode_or_key) # dummy_key = "C" if mode == "M" else "c" while token.count("/"): bits = token.rsplit("/", maxsplit=2) if len(bits) == 3: prefix, secondary2, secondary1 = bits else: prefix = None secondary2, secondary1 = bits secondary_pitch1 = get_pitch_from_rn( secondary1, dummy_key, ) secondary_pitch2 = get_pitch_from_rn( secondary2, match_pitch_case_to_rn_mode(secondary1, secondary_pitch1), ) combined_secondary_without_mode = spelled_pitch_to_rn( secondary_pitch2, dummy_key, get_mode_from_rn_str(secondary2), sixth_minor, seventh_minor, ) combined_secondary = change_rn_mode( combined_secondary_without_mode, get_mode_from_rn_str(secondary2) ) if prefix: token = f"{prefix}/{combined_secondary}" else: token = combined_secondary return token>>> handle_nested_secondary_rns("vi/III", "M") '#i' >>> handle_nested_secondary_rns("vi/III", "Ab") '#i' >>> handle_nested_secondary_rns("V/V", "M") 'II' >>> handle_nested_secondary_rns("/V/V", "M") # leading slash is optional 'II' >>> handle_nested_secondary_rns("V/V/V", "M") 'VI' >>> handle_nested_secondary_rns("V/V/V/V", "M") 'III' >>> handle_nested_secondary_rns("v/V", "M") 'ii' >>> handle_nested_secondary_rns("vi/III", "m") 'i' >>> handle_nested_secondary_rns("V", "M") 'V' >>> handle_nested_secondary_rns("", "M") 'I' def hex_str_to_pc_ints(hex_str: str, return_set: bool = False) ‑> list[int] | set[int]-
Expand source code
def hex_str_to_pc_ints(hex_str: str, return_set: bool = False) -> list[int] | set[int]: """ >>> hex_str_to_pc_ints("047b") [0, 4, 7, 11] >>> hex_str_to_pc_ints("0123456789ab") [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] """ if return_set: return {int(ch, 16) for ch in hex_str} return [int(ch, 16) for ch in hex_str]>>> hex_str_to_pc_ints("047b") [0, 4, 7, 11] >>> hex_str_to_pc_ints("0123456789ab") [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] def match_pitch_case_to_rn_mode(rn: str, pitch: str) ‑> str-
Expand source code
def match_pitch_case_to_rn_mode(rn: str, pitch: str) -> str: """ >>> match_pitch_case_to_rn_mode("iii", "E") 'e' >>> match_pitch_case_to_rn_mode("III", "E") 'E' >>> match_pitch_case_to_rn_mode("biii", "E-") 'e-' >>> match_pitch_case_to_rn_mode("bIII", "E-") 'E-' >>> match_pitch_case_to_rn_mode("#VI", "A#") 'A#' >>> match_pitch_case_to_rn_mode("#vi", "A#") 'a#' """ assert rn[-1] in {"i", "I", "v", "V"} return pitch.capitalize() if rn[-1].isupper() else pitch.lower()>>> match_pitch_case_to_rn_mode("iii", "E") 'e' >>> match_pitch_case_to_rn_mode("III", "E") 'E' >>> match_pitch_case_to_rn_mode("biii", "E-") 'e-' >>> match_pitch_case_to_rn_mode("bIII", "E-") 'E-' >>> match_pitch_case_to_rn_mode("#VI", "A#") 'A#' >>> match_pitch_case_to_rn_mode("#vi", "A#") 'a#' def pc_ints_to_hex_str(pcs: list[int]) ‑> str-
Expand source code
def pc_ints_to_hex_str(pcs: list[int]) -> str: """ >>> pc_ints_to_hex_str([0, 4, 7, 11]) '047b' >>> pc_ints_to_hex_str([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) '0123456789ab' """ return "".join(f"{pc:x}" for pc in pcs)>>> pc_ints_to_hex_str([0, 4, 7, 11]) '047b' >>> pc_ints_to_hex_str([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]) '0123456789ab' def rn_to_spelled_pitch(rn: str,
key: str,
sixth_minor: music21.roman.Minor67Default = Minor67Default.CAUTIONARY,
seventh_minor: music21.roman.Minor67Default = Minor67Default.CAUTIONARY) ‑> str-
Expand source code
def rn_to_spelled_pitch( rn: str, key: str, sixth_minor: Minor67Default = Minor67Default.CAUTIONARY, seventh_minor: Minor67Default = Minor67Default.CAUTIONARY, ) -> str: """ >>> rn_to_spelled_pitch("I", "C") 'C' >>> rn_to_spelled_pitch("#I", "C") 'C#' >>> rn_to_spelled_pitch("bIII", "C") 'E-' Note that the mode of both the key and the rn_str can affect the result: >>> rn_to_spelled_pitch("VI", "C") 'A' >>> rn_to_spelled_pitch("VI", "c") 'A-' >>> rn_to_spelled_pitch("vi", "c") 'A' Having the mode of the rn_str affect the results can be avoided by setting "sixth_minor" and "seventh_minor" appropriately: >>> rn_to_spelled_pitch("VI", "c", sixth_minor=Minor67Default.FLAT) 'A-' >>> rn_to_spelled_pitch("vi", "c", sixth_minor=Minor67Default.FLAT) 'A-' """ out = ( RomanNumeral(rn, key, sixthMinor=sixth_minor, seventhMinor=seventh_minor) .root() .name ) return out>>> rn_to_spelled_pitch("I", "C") 'C' >>> rn_to_spelled_pitch("#I", "C") 'C#' >>> rn_to_spelled_pitch("bIII", "C") 'E-'Note that the mode of both the key and the rn_str can affect the result:
>>> rn_to_spelled_pitch("VI", "C") 'A' >>> rn_to_spelled_pitch("VI", "c") 'A-' >>> rn_to_spelled_pitch("vi", "c") 'A'Having the mode of the rn_str affect the results can be avoided by setting "sixth_minor" and "seventh_minor" appropriately:
>>> rn_to_spelled_pitch("VI", "c", sixth_minor=Minor67Default.FLAT) 'A-' >>> rn_to_spelled_pitch("vi", "c", sixth_minor=Minor67Default.FLAT) 'A-' def spelled_pitch_to_rn(spelled_pitch: str,
key: str,
rn_mode: str,
sixth_minor: music21.roman.Minor67Default = Minor67Default.CAUTIONARY,
seventh_minor: music21.roman.Minor67Default = Minor67Default.CAUTIONARY)-
Expand source code
def spelled_pitch_to_rn( spelled_pitch: str, key: str, rn_mode: str, sixth_minor: Minor67Default = Minor67Default.CAUTIONARY, seventh_minor: Minor67Default = Minor67Default.CAUTIONARY, ): """ Convert a spelled pitch to a Roman numeral in the given key. Args: spelled_pitch: The spelled pitch. key: The key. rn_mode: The mode of the Roman numeral. >>> spelled_pitch_to_rn("C", "C", "M") 'I' >>> spelled_pitch_to_rn("C#", "C", "M") '#I' >>> spelled_pitch_to_rn("Eb", "C", "M") 'bIII' >>> spelled_pitch_to_rn("Ab", "C", "M") 'bVI' >>> spelled_pitch_to_rn("A--", "C", "M") 'bbVI' >>> spelled_pitch_to_rn("Ab", "c", "M") 'VI' >>> spelled_pitch_to_rn("A", "C", "M") 'VI' >>> spelled_pitch_to_rn("A", "c", "M") '#VI' >>> spelled_pitch_to_rn("C", "C", "m") 'i' >>> spelled_pitch_to_rn("C#", "C", "m") '#i' >>> spelled_pitch_to_rn("Eb", "C", "m") 'biii' >>> spelled_pitch_to_rn("Ab", "C", "m") 'bvi' >>> spelled_pitch_to_rn("A--", "c", "m") 'bvi' >>> spelled_pitch_to_rn("Ab", "c", "m") 'vi' >>> spelled_pitch_to_rn("A", "C", "m") 'vi' >>> spelled_pitch_to_rn("A", "c", "m") '#vi' """ # Temporary hack: for some reason this gives VI and VII instead of #VI and #VII *only* # when mode is major but key is minor. # Therefore, rather than doing: # rn = RomanNumeral("i" if mode == "m" else "I", keyOrScale=pitch.upper()) # We always do the rn in minor mode then adjust the output as necessary below rn = RomanNumeral( "i", spelled_pitch.upper(), sixthMinor=sixth_minor, seventhMinor=seventh_minor, ) # (Malcolm 2023-10-05) romanNumeralFromChord always uses the default values # of Minor67Default, meaning it'll sometimes give the wrong answer # for DCML. This is probably few enough cases that it's ok for now. out = romanNumeralFromChord(Chord(rn.pitches), key).romanNumeral if rn_mode == "M": n_flats = len(out) - len(out.lstrip("b")) return out[:n_flats] + out[n_flats:].upper() return outConvert a spelled pitch to a Roman numeral in the given key.
Args
spelled_pitch- The spelled pitch.
key- The key.
rn_mode- The mode of the Roman numeral.
>>> spelled_pitch_to_rn("C", "C", "M") 'I' >>> spelled_pitch_to_rn("C#", "C", "M") '#I' >>> spelled_pitch_to_rn("Eb", "C", "M") 'bIII' >>> spelled_pitch_to_rn("Ab", "C", "M") 'bVI' >>> spelled_pitch_to_rn("A--", "C", "M") 'bbVI' >>> spelled_pitch_to_rn("Ab", "c", "M") 'VI' >>> spelled_pitch_to_rn("A", "C", "M") 'VI' >>> spelled_pitch_to_rn("A", "c", "M") '#VI' >>> spelled_pitch_to_rn("C", "C", "m") 'i' >>> spelled_pitch_to_rn("C#", "C", "m") '#i' >>> spelled_pitch_to_rn("Eb", "C", "m") 'biii' >>> spelled_pitch_to_rn("Ab", "C", "m") 'bvi' >>> spelled_pitch_to_rn("A--", "c", "m") 'bvi' >>> spelled_pitch_to_rn("Ab", "c", "m") 'vi' >>> spelled_pitch_to_rn("A", "C", "m") 'vi' >>> spelled_pitch_to_rn("A", "c", "m") '#vi' def tonicization_to_key(secondary_rn: str,
original_key: str,
case_matters: bool = False,
simplify_enharmonics: bool = True) ‑> str-
Expand source code
def tonicization_to_key( secondary_rn: str, original_key: str, case_matters: bool = False, simplify_enharmonics: bool = True, ) -> str: """ Given the secondary RN and the original key, return the key of the tonicization. If the secondary RNs don't use case to indicate mode, then mode is under-specified and we follow the following heuristic: - if there is a consonant triad in the diatonic scale corresponding to the secondary RN (or nearly so in the case of VII in major and II in minor), then we use the mode of this triad. Similarly for #VI and #VII in minor which are treated as "honorary diatonics". - otherwise, we use the major mode. This is questionable in certain cases (e.g., #I in minor, which would for example in C minor be C# major) but these cases should be extremely rare in any real data. - if simplify_enharmonics is True, return the "enharmonically simplest" equivalent key (e.g., "Eb" rather than "D#"). See MAJOR_KEYS and MINOR_KEYS for the mapping. Args: secondary_rn: The secondary Roman numeral. original_key: The original key. case_matters: Whether case of the secondary RN indicates mode. >>> tonicization_to_key("ii", "C") 'd' >>> tonicization_to_key("V", "C") 'G' >>> tonicization_to_key("I", "C") 'C' >>> tonicization_to_key("VII", "C") 'b' >>> tonicization_to_key("VII", "C", case_matters=True) 'B' >>> tonicization_to_key("bVII", "C") 'Bb' >>> tonicization_to_key("bII", "Bb") 'B' >>> tonicization_to_key("III", "C") 'e' >>> tonicization_to_key("III", "c") 'Eb' >>> tonicization_to_key("#III", "C") 'F' >>> tonicization_to_key("#III", "C", simplify_enharmonics=False) 'E#' """ if secondary_rn == "I": return original_key mode = MODES[original_key[0].isupper()] try: fifths_offset, secondary_mode = TONICIZATIONS[mode][secondary_rn] except KeyError: warnings.warn(f"Unrecognized secondary RN: {secondary_rn}") return original_key if case_matters: secondary_mode = MODES[secondary_rn.lstrip("#b")[0].isupper()] if simplify_enharmonics: original_key_pc = UNSPELLER(original_key) assert isinstance(original_key_pc, int) new_key_pc = (original_key_pc + fifths_offset * 7) % 12 if secondary_mode == "M": new_key = MAJOR_KEYS[new_key_pc] else: new_key = MINOR_KEYS[new_key_pc] else: new_key = SPELLING_TRANSPOSER(original_key, fifths_offset) assert isinstance(new_key, str) if secondary_mode == "m": new_key = new_key[0].lower() + new_key[1:] return new_keyGiven the secondary RN and the original key, return the key of the tonicization.
If the secondary RNs don't use case to indicate mode, then mode is under-specified and we follow the following heuristic: - if there is a consonant triad in the diatonic scale corresponding to the secondary RN (or nearly so in the case of VII in major and II in minor), then we use the mode of this triad. Similarly for #VI and #VII in minor which are treated as "honorary diatonics". - otherwise, we use the major mode. This is questionable in certain cases (e.g., #I in minor, which would for example in C minor be C# major) but these cases should be extremely rare in any real data. - if simplify_enharmonics is True, return the "enharmonically simplest" equivalent key (e.g., "Eb" rather than "D#"). See MAJOR_KEYS and MINOR_KEYS for the mapping.
Args
secondary_rn- The secondary Roman numeral.
original_key- The original key.
case_matters- Whether case of the secondary RN indicates mode.
>>> tonicization_to_key("ii", "C") 'd'>>> tonicization_to_key("V", "C") 'G' >>> tonicization_to_key("I", "C") 'C' >>> tonicization_to_key("VII", "C") 'b' >>> tonicization_to_key("VII", "C", case_matters=True) 'B' >>> tonicization_to_key("bVII", "C") 'Bb' >>> tonicization_to_key("bII", "Bb") 'B'>>> tonicization_to_key("III", "C") 'e' >>> tonicization_to_key("III", "c") 'Eb' >>> tonicization_to_key("#III", "C") 'F' >>> tonicization_to_key("#III", "C", simplify_enharmonics=False) 'E#' def translate_rns(rn: str,
src: Literal['rnbert', 'music21'] = 'rnbert',
dst: Literal['rnbert', 'music21'] = 'music21') ‑> str-
Expand source code
def translate_rns( rn: str, src: Literal["rnbert", "music21"] = "rnbert", dst: Literal["rnbert", "music21"] = "music21", ) -> str: """ >>> translate_rns("Im") 'i' >>> translate_rns("VM") 'V' >>> translate_rns("IVm6") 'iv6' >>> translate_rns("VIIo642") 'viio642' >>> translate_rns("III+") 'III+' >>> translate_rns("xaug665") 'Ger65' >>> translate_rns("xaug643") 'Fr43' # TODO: (Malcolm 2026-01-22) improve augmented sixth handling All other "inversions" should return It6 for now but this should be improved on! >>> translate_rns("xaug63") 'It6' >>> translate_rns("xaug642") 'It6' >>> translate_rns("vM") Traceback (most recent call last): ... TypeError: Mal-formed rnbert Roman numeral begins with lower-case: vM """ if src != "rnbert": raise NotImplementedError else: if dst != "music21": raise NotImplementedError match rn: case "xaug665": return "Ger65" case "xaug643": return "Fr43" if rn.startswith("xaug6"): return "It6" m = re.match(r"[IV]+", rn) if m is None: if src == "rnbert" and re.match(r"^[iv]", rn): raise TypeError( f"Mal-formed rnbert Roman numeral begins with lower-case: {rn}" ) else: return rn degree = m.group(0) match rn[len(degree)]: case "M": return degree.upper() + rn[len(degree) + 1 :] case "m": return degree.lower() + rn[len(degree) + 1 :] case "o": return degree.lower() + rn[len(degree) :] case _: return rn>>> translate_rns("Im") 'i' >>> translate_rns("VM") 'V' >>> translate_rns("IVm6") 'iv6' >>> translate_rns("VIIo642") 'viio642' >>> translate_rns("III+") 'III+'>>> translate_rns("xaug665") 'Ger65' >>> translate_rns("xaug643") 'Fr43'TODO: (Malcolm 2026-01-22) improve augmented sixth handling
All other "inversions" should return It6 for now but this should be improved on!
>>> translate_rns("xaug63") 'It6' >>> translate_rns("xaug642") 'It6'>>> translate_rns("vM") Traceback (most recent call last): ... TypeError: Mal-formed rnbert Roman numeral begins with lower-case: vM def transpose_pcs(pcs: list[int], interval: int) ‑> list[int]-
Expand source code
def transpose_pcs(pcs: list[int], interval: int) -> list[int]: """ Transpose a list of pitch classes by an interval. """ return [(pc + interval) % 12 for pc in pcs]Transpose a list of pitch classes by an interval.
Classes
class CacheDict (default_factory: Callable[[~K], ~V], *args, **kwargs)-
Expand source code
class CacheDict(dict[K, V]): def __init__(self, default_factory: Callable[[K], V], *args, **kwargs): self.default_factory = default_factory super().__init__(*args, **kwargs) def __getitem__(self, key: K) -> V: if key not in self: self[key] = self.default_factory(key) return super().__getitem__(key)dict() -> new empty dictionary dict(mapping) -> new dictionary initialized from a mapping object's (key, value) pairs dict(iterable) -> new dictionary initialized as if via: d = {} for k, v in iterable: d[k] = v dict(**kwargs) -> new dictionary initialized with the name=value pairs in the keyword argument list. For example: dict(one=1, two=2)
Ancestors
- builtins.dict