Source code for pytheory.ragas

"""Hindustani ragas — the living melodic forms of North Indian classical music.

A *thaat* is a parent scale; a *raga* is what musicians actually play. A
raga is more than its notes: it has an ascending line (*aroha*) and a
descending line (*avaroha*) that often differ, a characteristic catch-
phrase (*pakad*) that identifies it in a few notes, a most-important note
(*vadi*) and its second (*samvadi*), a time of day or season it belongs
to, and an emotional flavour (*rasa*).

This module encodes a representative set of the best-known ragas. Swaras
are written in the usual ASCII sargam:

    S   Sa  (tonic)
    r/R komal / shuddha Re        g/G komal / shuddha Ga
    m/M shuddha / tivra  Ma       P   Pa
    d/D komal / shuddha Dha       n/N komal / shuddha Ni

A trailing ``'`` raises a swara an octave, a trailing ``.`` lowers it.
Lowercase is komal (flattened), uppercase is shuddha (natural), and ``M``
is tivra (sharpened) Ma.

Ragas vary by gharana and region; these are common, widely-taught forms.

Example::

    >>> from pytheory import Raga
    >>> yaman = Raga.get("yaman")
    >>> yaman.aroha_swaras()
    ['N.', 'R', 'G', 'M', 'D', 'N', "S'"]
    >>> [t.name for t in yaman.scale_tones("C")]
    ['C', 'D', 'E', 'F#', 'G', 'A', 'B']
    >>> yaman.time, yaman.rasa
    ('evening', 'serene, romantic')
"""
from __future__ import annotations

import math
from fractions import Fraction
from typing import Optional

# Semitone offset of each swara from Sa.
_SWARA = {
    "S": 0, "r": 1, "R": 2, "g": 3, "G": 4, "m": 5,
    "M": 6, "P": 7, "d": 8, "D": 9, "n": 10, "N": 11,
}
# Canonical ascending order for listing a raga's distinct swaras.
_SWARA_ORDER = ["S", "r", "R", "g", "G", "m", "M", "P", "d", "D", "n", "N"]

# Just-intonation ratio of each swara from Sa — the 5-limit intervals of
# the shruti system, the intonation a raga is actually meant to be sung
# in (and what the bundled "shruti" tone system encodes). These are what
# make a komal Ga or a tivra Ma sound "right" rather than the tempered
# 12-TET approximation.
_SWARA_JI = {
    "S": Fraction(1, 1), "r": Fraction(16, 15), "R": Fraction(9, 8),
    "g": Fraction(6, 5), "G": Fraction(5, 4), "m": Fraction(4, 3),
    "M": Fraction(45, 32), "P": Fraction(3, 2), "d": Fraction(8, 5),
    "D": Fraction(5, 3), "n": Fraction(16, 9), "N": Fraction(15, 8),
}


def _parse(token: str) -> Optional[int]:
    """Parse one sargam token to a semitone offset from Sa (or None)."""
    clean = token.replace(",", "").replace("|", "").strip()
    if not clean:
        return None
    letter = clean[0]
    if letter not in _SWARA:
        return None
    offset = _SWARA[letter]
    for ch in clean[1:]:
        if ch == "'":
            offset += 12
        elif ch == ".":
            offset -= 12
    return offset


def _swaras(line: str) -> list[str]:
    """Split a sargam line into clean swara tokens."""
    out = []
    for tok in line.split():
        clean = tok.replace(",", "").replace("|", "").strip()
        if clean and clean[0] in _SWARA:
            out.append(clean)
    return out


class _JustNote:
    """A bare frequency that renders through the normal synth/envelope path.

    ``_render`` asks a note for ``.pitch()`` and iterates ``.tones``; this
    shim answers with an exact just-intoned frequency, so a raga can be
    voiced microtonally without snapping to 12-TET.
    """
    def __init__(self, hz: float):
        self._hz = hz

    @property
    def tones(self):
        return [self]

    def pitch(self, **_):
        return self._hz


[docs] class Raga: """A Hindustani raga: its melodic rules, mood, and time. Attributes: name: the raga's name. thaat: parent scale (one of the ten thaats). aroha: ascending line (sargam string). avaroha: descending line (sargam string). pakad: characteristic identifying phrase (sargam string). vadi: the most important (sonant) swara. samvadi: the second-most important (consonant) swara. time: the time of day (or season) the raga belongs to. rasa: its emotional flavour. aka: alternative names. """
[docs] def __init__(self, name, *, thaat, aroha, avaroha, vadi, samvadi, time, rasa, pakad=None, aka=None, tradition="hindustani"): self.name = name # The parent scale: a thaat for Hindustani ragas, a melakarta for # Carnatic ones. self.thaat = thaat self.tradition = tradition self.aroha = aroha self.avaroha = avaroha self.pakad = pakad self.vadi = vadi self.samvadi = samvadi self.time = time self.rasa = rasa self.aka = aka or []
# ── sargam access ────────────────────────────────────────────────
[docs] def aroha_swaras(self) -> list[str]: """The ascending line as a list of sargam tokens.""" return _swaras(self.aroha)
[docs] def avaroha_swaras(self) -> list[str]: """The descending line as a list of sargam tokens.""" return _swaras(self.avaroha)
[docs] def pakad_swaras(self) -> list[str]: """The identifying phrase as sargam tokens (empty if none).""" return _swaras(self.pakad) if self.pakad else []
@property def swara_set(self) -> list[str]: """The distinct swaras the raga uses, in ascending order.""" used = set() for line in (self.aroha, self.avaroha): for tok in _swaras(line): base = tok[0] # Keep the accidental letter (r vs R), drop octave marks. used.add(base) return [s for s in _SWARA_ORDER if s in used] @property def jati(self) -> str: """Classification by ascending/descending note counts.""" names = {5: "audav", 6: "shadav", 7: "sampurna"} a = len({t[0] for t in self.aroha_swaras()}) d = len({t[0] for t in self.avaroha_swaras()}) return f"{names.get(a, a)}-{names.get(d, d)}" # ── pitched output ─────────────────────────────────────────────── def _tones(self, line: str, sa): from .tones import Tone if not isinstance(sa, Tone): sa = Tone.from_string(sa if any(c.isdigit() for c in str(sa)) else f"{sa}4", system="western") tones = [] for tok in line.split(): off = _parse(tok) if off is not None: tones.append(sa.add(off)) return tones
[docs] def aroha_tones(self, sa="C4") -> list: """The ascending line as :class:`Tone` objects, with Sa = *sa*.""" return self._tones(self.aroha, sa)
[docs] def avaroha_tones(self, sa="C4") -> list: """The descending line as :class:`Tone` objects, with Sa = *sa*.""" return self._tones(self.avaroha, sa)
[docs] def pakad_tones(self, sa="C4") -> list: """The pakad as :class:`Tone` objects (empty if none).""" return self._tones(self.pakad, sa) if self.pakad else []
[docs] def scale_tones(self, sa="C") -> list: """The raga's distinct tones in one octave, ascending from Sa.""" from .tones import Tone base = Tone.from_string(f"{sa}4" if not any(c.isdigit() for c in str(sa)) else str(sa), system="western") return [base.add(_SWARA[s]) for s in self.swara_set]
[docs] def note_names(self, sa="C") -> list[str]: """The raga's distinct note names in the key of *sa*.""" return [t.name for t in self.scale_tones(sa)]
[docs] def melody(self, sa="C4") -> list: """Aroha then avaroha as one :class:`Tone` line — a quick run.""" return self.aroha_tones(sa) + self.avaroha_tones(sa)[1:]
# ── just intonation (shruti) ─────────────────────────────────────
[docs] def just_ratios(self) -> dict: """Each distinct swara's just-intonation ratio from Sa. These are the shruti intervals — 5-limit ratios like 5/4 for Ga and 45/32 for tivra Ma — that the raga is meant to be intoned in, rather than the tempered 12-TET grid. Example:: >>> Raga.get("yaman").just_ratios()["G"] Fraction(5, 4) """ return {s: _SWARA_JI[s] for s in self.swara_set}
def _just_hz(self, line: str, sa) -> list[float]: from .tones import Tone if not isinstance(sa, Tone): sa = Tone.from_string(sa if any(c.isdigit() for c in str(sa)) else f"{sa}4", system="western") sa_hz = sa.frequency out = [] for tok in line.split(): clean = tok.replace(",", "").replace("|", "").strip() if not clean or clean[0] not in _SWARA_JI: continue ratio = _SWARA_JI[clean[0]] for ch in clean[1:]: if ch == "'": ratio *= 2 elif ch == ".": ratio /= 2 out.append(sa_hz * float(ratio)) return out
[docs] def just_frequencies(self, sa="C4") -> list[float]: """The aroha→avaroha run as just-intoned frequencies (Hz). Sa is movable: it's pinned to *sa* and the shruti ratios are built on top, so you get authentic intonation in any key. """ return (self._just_hz(self.aroha, sa) + self._just_hz(self.avaroha, sa)[1:])
[docs] def shruti_table(self, sa="C") -> list[dict]: """Per-swara intonation detail: ratio, Hz, and cents off 12-TET. Shows how far each shruti sits from the tempered piano — e.g. the just major 3rd (Ga, 5/4) is ~14 cents flatter than the equal-tempered E. """ from .tones import Tone sa_tone = Tone.from_string( f"{sa}4" if not any(c.isdigit() for c in str(sa)) else str(sa), system="western") sa_hz = sa_tone.frequency rows = [] for s in self.swara_set: ratio = _SWARA_JI[s] ji_cents = 1200 * math.log2(float(ratio)) cents_off = ji_cents - _SWARA[s] * 100 rows.append({ "swara": s, "ratio": f"{ratio.numerator}/{ratio.denominator}", "note": sa_tone.add(_SWARA[s]).name, "hz": round(sa_hz * float(ratio), 2), "cents_off": round(cents_off, 1), }) return rows
[docs] def render(self, sa="C4", *, synth="sitar", t=420, envelope="pluck", just=True, reverb=0.35): """Render the aroha→avaroha run to a mono sample buffer. Notes are laid end to end and a hall reverb is mixed over the whole phrase, so the tail of one swara bleeds into the next — much closer to how a raga actually breathes than dry, separate notes. Returns a NumPy array (no audio device needed). Args: sa: the tonic for Sa. synth: waveform name (default sitar). t: duration per swara in ms. envelope: ADSR preset name. just: just intonation (True, default) vs tempered 12-TET. reverb: wet mix 0..1 (0 disables); default 0.35. """ import numpy from .play import _render, _apply_reverb, Synth, Envelope, SAMPLE_RATE s = Synth[synth.upper()] env = Envelope[envelope.upper()] if just: notes = [_JustNote(hz) for hz in self.just_frequencies(sa)] else: notes = self.melody(sa) buffers = [_render(n, synth=s, t=t, envelope=env) for n in notes] buf = numpy.concatenate(buffers).astype(numpy.float32) if reverb and reverb > 0: # Pad so the reverb tail isn't clipped off the end. tail = numpy.zeros(int(SAMPLE_RATE * 1.5), dtype=numpy.float32) buf = _apply_reverb(numpy.concatenate([buf, tail]), mix=reverb) return buf
[docs] def play(self, sa="C4", *, synth="sitar", t=420, envelope="pluck", just=True, reverb=0.35): """Play the aroha then avaroha through the speakers. Defaults to the sitar voice in **just intonation** (the shruti tuning a raga is meant to be heard in), with a touch of reverb so the phrase rings like a hall. Pass ``just=False`` for tempered 12-TET, ``reverb=0`` for a dry run, or any synth name you like. """ from .play import _play_for, SAMPLE_RATE buf = self.render(sa, synth=synth, t=t, envelope=envelope, just=just, reverb=reverb) _play_for(buf, ms=int(len(buf) / SAMPLE_RATE * 1000))
# ── lookup ───────────────────────────────────────────────────────
[docs] @classmethod def get(cls, name: str) -> "Raga": """Look up a raga by name (case-insensitive, ignores spaces). An exact name match always wins over an alias, so a real raga is never shadowed by another's alternative spelling. """ def norm(s): return s.lower().replace(" ", "").replace("-", "") key = norm(name) for raga in _RAGAS.values(): # names first if key == norm(raga.name): return raga for raga in _RAGAS.values(): # then aliases if any(key == norm(a) for a in raga.aka): return raga raise KeyError(f"Unknown raga: {name!r}. " f"Try Raga.names() for the list.")
[docs] @classmethod def all(cls) -> list["Raga"]: """Every raga, sorted by name.""" return [_RAGAS[k] for k in sorted(_RAGAS)]
[docs] @classmethod def names(cls) -> list[str]: """The names of every known raga, sorted.""" return sorted(r.name for r in _RAGAS.values())
[docs] @classmethod def by_thaat(cls, thaat: str) -> list["Raga"]: """Every raga belonging to the given thaat.""" t = thaat.lower() return [r for r in cls.all() if r.thaat.lower() == t]
[docs] @classmethod def by_time(cls, period: str) -> list["Raga"]: """Ragas whose time of day matches (substring, case-insensitive).""" p = period.lower() return [r for r in cls.all() if p in r.time.lower()]
[docs] @classmethod def by_tradition(cls, tradition: str) -> list["Raga"]: """Ragas of a tradition — ``"hindustani"`` or ``"carnatic"``.""" t = tradition.lower() return [r for r in cls.all() if r.tradition.lower() == t]
[docs] def __repr__(self) -> str: return f"<Raga {self.name} (thaat {self.thaat}, {self.time})>"
def __str__(self) -> str: return self.name
# ── the ragas ──────────────────────────────────────────────────────── # Common, widely-taught forms. Lowercase swara = komal, uppercase = # shuddha, M = tivra Ma; trailing ' = upper octave, . = lower octave. _RAGA_LIST = [ Raga("Yaman", thaat="kalyan", aroha="N. R G M D N S'", avaroha="S' N D P M G R S", pakad="N. R G, R, P M G R S", vadi="G", samvadi="N", time="evening", rasa="serene, romantic", aka=["Kalyan"]), Raga("Bilawal", thaat="bilawal", aroha="S R G m P D N S'", avaroha="S' N D P m G R S", pakad="G R, G P, m G R S", vadi="D", samvadi="G", time="morning", rasa="cheerful, devotional"), Raga("Khamaj", thaat="khamaj", aroha="S G m P D N S'", avaroha="S' n D P m G R S", pakad="G m P D, m G, P D n D, m G", vadi="G", samvadi="N", time="night", rasa="romantic, light"), Raga("Kafi", thaat="kafi", aroha="S R g m P D n S'", avaroha="S' n D P m g R S", pakad="S R g, g m, P, D n D P", vadi="P", samvadi="S", time="night", rasa="playful, spring"), Raga("Asavari", thaat="asavari", aroha="S R m P d S'", avaroha="S' n d P m g R S", pakad="R m P, d, m P g R S", vadi="d", samvadi="g", time="late morning", rasa="renunciation, pathos"), Raga("Bhairavi", thaat="bhairavi", aroha="S r g m P d n S'", avaroha="S' n d P m g r S", pakad="S, g m P, g r S", vadi="m", samvadi="S", time="morning", rasa="devotional, compassion"), Raga("Bhairav", thaat="bhairav", aroha="S r G m P d N S'", avaroha="S' N d P m G r S", pakad="S r S, d. N. S, G m P, d P", vadi="d", samvadi="r", time="dawn", rasa="serious, peaceful, devotional"), Raga("Todi", thaat="todi", aroha="S r g M P d N S'", avaroha="S' N d P M g r S", pakad="d. N. S r g, r g r S", vadi="d", samvadi="g", time="late morning", rasa="intense, yearning", aka=["Miyan ki Todi"]), Raga("Marwa", thaat="marwa", aroha="S r G M D N S'", avaroha="S' N D M G r S", pakad="D. N. r S, D. r G M G r S", vadi="r", samvadi="D", time="sunset", rasa="anticipation, restlessness"), Raga("Purvi", thaat="purvi", aroha="S r G M P d N S'", avaroha="S' N d P M G r S", pakad="N. r G, M G, M d, P", vadi="G", samvadi="N", time="sunset", rasa="serious, mystical", aka=["Poorvi"]), Raga("Bhimpalasi", thaat="kafi", aroha="n. S g m P n S'", avaroha="S' n D P m g R S", pakad="n. S m, g m, P, m g R S", vadi="m", samvadi="S", time="afternoon", rasa="longing, devotion", aka=["Bhimpalas", "Bheempalasi"]), Raga("Bhupali", thaat="kalyan", aroha="S R G P D S'", avaroha="S' D P G R S", pakad="G R S D., S R G, P G", vadi="G", samvadi="D", time="evening", rasa="peaceful, devotional", aka=["Bhoop", "Bhup"]), Raga("Malkauns", thaat="bhairavi", aroha="S g m d n S'", avaroha="S' n d m g S", pakad="m g, m d n d, m g S", vadi="m", samvadi="S", time="late night", rasa="meditative, profound", aka=["Malkosh", "Malkounts"]), Raga("Durga", thaat="bilawal", aroha="S R m P D S'", avaroha="S' D P m R S", pakad="S R m, P D, m R, P D S'", vadi="m", samvadi="S", time="night", rasa="devotional, valour"), Raga("Desh", thaat="khamaj", aroha="S R m P N S'", avaroha="S' n D P m G R S", pakad="R m P, n D P, m G R", vadi="R", samvadi="P", time="night", rasa="patriotic, monsoon", aka=["Des"]), Raga("Bageshri", thaat="kafi", aroha="S g m D n S'", avaroha="S' n D m g R S", pakad="S g m D, n D, m g R S", vadi="m", samvadi="S", time="late night", rasa="longing, union", aka=["Bageshree", "Bagesri"]), Raga("Darbari Kanada", thaat="asavari", aroha="S R g m P d n S'", avaroha="S' d n P m P g m R S", pakad="R g g S, d. n. S R, P", vadi="R", samvadi="P", time="midnight", rasa="majestic, serious", aka=["Darbari", "Darbari Kanada"]), Raga("Hamsadhwani", thaat="bilawal", aroha="S R G P N S'", avaroha="S' N P G R S", pakad="S R G, P N S', N P G R", vadi="G", samvadi="P", time="evening", rasa="joyful, auspicious", aka=["Hansadhwani"]), Raga("Jaunpuri", thaat="asavari", aroha="S R m P d n S'", avaroha="S' n d P m g R S", pakad="m P d n d, P, m P g R S", vadi="d", samvadi="g", time="late morning", rasa="pathos, devotion"), Raga("Hindol", thaat="kalyan", aroha="S G M D N S'", avaroha="S' N D M G S", pakad="S G, M D, N D, M G S", vadi="D", samvadi="G", time="morning", rasa="joyful, spring"), Raga("Ahir Bhairav", thaat="bhairav", aroha="S r G m P D n S'", avaroha="S' n D P m G r S", pakad="S r, G m, D n D, P", vadi="m", samvadi="S", time="early morning", rasa="serene, devotional", aka=["Ahir Bhairon"]), Raga("Jogiya", thaat="bhairav", aroha="S r m P d S'", avaroha="S' n d P m r S", pakad="m P d, P, m r S", vadi="m", samvadi="S", time="early morning", rasa="pathos, renunciation"), Raga("Sohni", thaat="marwa", aroha="S G M D N S'", avaroha="S' N D M G r S", pakad="M D N S', N D M G, M G r S", vadi="D", samvadi="G", time="late night", rasa="longing, pathos", aka=["Sohini"]), Raga("Puriya", thaat="marwa", aroha="N. r G M D N S'", avaroha="S' N D M G r S", pakad="N. r G, M G, M D N D M G", vadi="G", samvadi="N", time="evening", rasa="serious, contemplative"), Raga("Puriya Dhanashri", thaat="purvi", aroha="S r G M P d N S'", avaroha="S' N d P M G r S", pakad="N. r G, M d, P, M G r G r S", vadi="P", samvadi="r", time="sunset", rasa="pathos, devotion", aka=["Puriya Dhanashree", "Poorya Dhanashri"]), Raga("Shree", thaat="purvi", aroha="S r M P N S'", avaroha="S' N d P M G r S", pakad="r r S, N. r S, P P, M G r S", vadi="r", samvadi="P", time="sunset", rasa="serious, devotional", aka=["Shri", "Sri"]), Raga("Multani", thaat="todi", aroha="S g M P N S'", avaroha="S' N d P M g r S", pakad="N. S g, M g, P g, r S", vadi="P", samvadi="S", time="afternoon", rasa="serious, yearning"), Raga("Brindabani Sarang", thaat="kafi", aroha="S R m P N S'", avaroha="S' n P m R S", pakad="N. S R, m R, P m R, n P", vadi="R", samvadi="P", time="afternoon", rasa="bright, restful", aka=["Vrindavani Sarang", "Brindavani Sarang", "Sarang"]), Raga("Megh", thaat="kafi", aroha="S R m P n S'", avaroha="S' n P m R S", pakad="S R, m R, P, m R n. S", vadi="S", samvadi="P", time="monsoon", rasa="monsoon, valour", aka=["Megh Malhar"]), Raga("Shivaranjani", thaat="kafi", aroha="S R g P D S'", avaroha="S' D P g R S", pakad="S R g, P, g R S, D. S", vadi="P", samvadi="R", time="night", rasa="pathos, longing", aka=["Shivranjani", "Sivaranjani"]), Raga("Tilang", thaat="khamaj", aroha="S G m P N S'", avaroha="S' n P m G S", pakad="S G, m G, P, N S', n P", vadi="G", samvadi="N", time="night", rasa="light, romantic", aka=["Tilung"]), Raga("Jhinjhoti", thaat="khamaj", aroha="S R m P D S'", avaroha="S' n D P m G R S", pakad="S R m, P D, m G R, n. S", vadi="G", samvadi="D", time="night", rasa="playful, folk", aka=["Jhinjhuti"]), Raga("Rageshri", thaat="khamaj", aroha="S G m D n S'", avaroha="S' n D m G R S", pakad="S G m D, n D, m G R S", vadi="m", samvadi="S", time="night", rasa="serene, devotional", aka=["Rageshree", "Rageshwari"]), Raga("Kalavati", thaat="khamaj", aroha="S G P D n S'", avaroha="S' n D P G S", pakad="S G P, D n D, P G S", vadi="P", samvadi="G", time="night", rasa="sweet, romantic"), Raga("Deshkar", thaat="bilawal", aroha="S R G P D S'", avaroha="S' D P G R S", pakad="D. S R, G P, D P, G R S", vadi="D", samvadi="G", time="morning", rasa="bright, expansive"), Raga("Shankara", thaat="bilawal", aroha="S G P D N S'", avaroha="S' N D P G R S", pakad="G P, N S', N D P, G R S", vadi="G", samvadi="N", time="late night", rasa="majestic, heroic", aka=["Shankra"]), # ── Carnatic ragas ──────────────────────────────────────────────── # Parent here is the melakarta (the 72-scale system), not a thaat. # Carnatic practice doesn't bind ragas to a time of day the way # Hindustani does, so `time` is "any". Raga("Shankarabharanam", thaat="Dheerashankarabharanam (29)", tradition="carnatic", aroha="S R G m P D N S'", avaroha="S' N D P m G R S", vadi="G", samvadi="N", time="any", rasa="majestic, devotional", aka=["Sankarabharanam"]), Raga("Kalyani", thaat="Mechakalyani (65)", tradition="carnatic", aroha="S R G M P D N S'", avaroha="S' N D P M G R S", vadi="G", samvadi="D", time="any", rasa="auspicious, grand", aka=["Mechakalyani", "Kalyan"]), Raga("Kharaharapriya", thaat="Kharaharapriya (22)", tradition="carnatic", aroha="S R g m P D n S'", avaroha="S' n D P m g R S", vadi="P", samvadi="S", time="any", rasa="pathos, longing"), Raga("Hanumatodi", thaat="Hanumatodi (8)", tradition="carnatic", aroha="S r g m P d n S'", avaroha="S' n d P m g r S", vadi="d", samvadi="g", time="any", rasa="serious, devotional", aka=["Todi (Carnatic)"]), Raga("Mayamalavagowla", thaat="Mayamalavagowla (15)", tradition="carnatic", aroha="S r G m P d N S'", avaroha="S' N d P m G r S", vadi="d", samvadi="r", time="any", rasa="serious, devotional (the first scale taught)", aka=["Malahari parent"]), Raga("Mohanam", thaat="Harikambhoji (28)", tradition="carnatic", aroha="S R G P D S'", avaroha="S' D P G R S", vadi="G", samvadi="D", time="any", rasa="cheerful, devotional"), Raga("Hindolam", thaat="Natabhairavi (20)", tradition="carnatic", aroha="S g m d n S'", avaroha="S' n d m g S", vadi="m", samvadi="S", time="any", rasa="meditative, profound"), Raga("Abhogi", thaat="Kharaharapriya (22)", tradition="carnatic", aroha="S R g m D S'", avaroha="S' D m g R S", vadi="m", samvadi="S", time="any", rasa="pleasing, tender"), Raga("Charukesi", thaat="Charukesi (26)", tradition="carnatic", aroha="S R G m P d n S'", avaroha="S' n d P m G R S", vadi="P", samvadi="S", time="any", rasa="bittersweet"), Raga("Keeravani", thaat="Keeravani (21)", tradition="carnatic", aroha="S R g m P d N S'", avaroha="S' N d P m g R S", vadi="P", samvadi="S", time="any", rasa="romantic, plaintive", aka=["Kirwani"]), Raga("Natabhairavi", thaat="Natabhairavi (20)", tradition="carnatic", aroha="S R g m P d n S'", avaroha="S' n d P m g R S", vadi="m", samvadi="S", time="any", rasa="solemn", aka=["Asavari equivalent"]), Raga("Kambhoji", thaat="Harikambhoji (28)", tradition="carnatic", aroha="S R G m P D S'", avaroha="S' n D P m G R S", vadi="G", samvadi="P", time="any", rasa="majestic"), Raga("Shanmukhapriya", thaat="Shanmukhapriya (56)", tradition="carnatic", aroha="S R g M P d n S'", avaroha="S' n d P M g R S", vadi="d", samvadi="g", time="any", rasa="intense, yearning"), Raga("Simhendramadhyamam", thaat="Simhendramadhyamam (57)", tradition="carnatic", aroha="S R g M P d N S'", avaroha="S' N d P M g R S", vadi="d", samvadi="g", time="any", rasa="grand, serious"), Raga("Madhyamavati", thaat="Kharaharapriya (22)", tradition="carnatic", aroha="S R m P n S'", avaroha="S' n P m R S", vadi="R", samvadi="P", time="any", rasa="auspicious (sung to close a concert)"), Raga("Bilahari", thaat="Dheerashankarabharanam (29)", tradition="carnatic", aroha="S R G P D S'", avaroha="S' N D P m G R S", vadi="R", samvadi="P", time="any", rasa="joyful, festive"), Raga("Saveri", thaat="Mayamalavagowla (15)", tradition="carnatic", aroha="S r m P d S'", avaroha="S' N d P m G r S", vadi="d", samvadi="r", time="any", rasa="devotional, plaintive"), Raga("Shuddha Saveri", thaat="Dheerashankarabharanam (29)", tradition="carnatic", aroha="S R m P D S'", avaroha="S' D P m R S", vadi="m", samvadi="S", time="any", rasa="bright, austere"), ] _RAGAS = {r.name.lower(): r for r in _RAGA_LIST}