Skip to content

Pcset

pcset

Pitch class sets — the bit-vector representation that powers chord/scale lookups.

A pitch class set (Pcset) is the set of distinct pitch classes (chromas 0-11) in a collection of notes or intervals. Tonal represents it as a 12-character binary string where position i is 1 iff chroma i is present:

Notes Chroma Set num
C 100000000000 2048
C, E, G 100010010000 2192
C major 101011010101 2773
C minor 101101011010 2906

This compact encoding makes set operations (subset, superset, equality) into fast bitwise operations, and it's the foundation that ChordType and ScaleType index their dictionaries by.

Example

from tonal_py import Pcset Pcset.chroma(["C", "E", "G"]) '100010010000' Pcset.num(["C", "E", "G"]) 2192 in_cmaj_triad = Pcset.is_subset_of(["C", "E", "G"]) in_cmaj_triad(["C", "E"]) True

Source parity: @tonaljs/pcset

is_chroma

is_chroma(s: Any) -> bool

True if s is a 12-character binary chroma string.

Parameters:

Name Type Description Default
s Any

Any value.

required

Returns:

Type Description
bool

True iff s is a string of exactly 12 characters, each '0' or '1'.

Example

from tonal_py import Pcset Pcset.is_chroma("100010010000") True Pcset.is_chroma("not a chroma") False Pcset.is_chroma("10010") # too short False

Source code in src/tonal_py/pcset.py
def is_chroma(s: Any) -> bool:
    """True if `s` is a 12-character binary chroma string.

    Args:
        s: Any value.

    Returns:
        True iff `s` is a string of exactly 12 characters, each `'0'` or `'1'`.

    Example:
        >>> from tonal_py import Pcset
        >>> Pcset.is_chroma("100010010000")
        True
        >>> Pcset.is_chroma("not a chroma")
        False
        >>> Pcset.is_chroma("10010")             # too short
        False
    """
    return isinstance(s, str) and bool(_CHROMA_REGEX.match(s))

get

get(src: Any) -> Pcset

Get a Pcset from a chroma string, set number, list, or existing Pcset.

Parameters:

Name Type Description Default
src Any

One of: - A 12-bit chroma string (e.g. "100010010000"). - An int 0-4095 (the chroma as a number). - A list of note names or interval names. - An existing Pcset (returns it unchanged via the cache).

required

Returns:

Type Description
Pcset

The corresponding Pcset. Invalid input returns EMPTY_PCSET.

Example

from tonal_py import Pcset Pcset.get(["C", "E", "G"]).set_num 2192 Pcset.get(2192).chroma '100010010000' Pcset.get("100010010000").set_num 2192 Pcset.get("garbage").empty True

Source code in src/tonal_py/pcset.py
def get(src: Any) -> Pcset:
    """Get a `Pcset` from a chroma string, set number, list, or existing Pcset.

    Args:
        src: One of:
            - A 12-bit chroma string (e.g. `"100010010000"`).
            - An int 0-4095 (the chroma as a number).
            - A list of note names or interval names.
            - An existing `Pcset` (returns it unchanged via the cache).

    Returns:
        The corresponding `Pcset`. Invalid input returns `EMPTY_PCSET`.

    Example:
        >>> from tonal_py import Pcset
        >>> Pcset.get(["C", "E", "G"]).set_num
        2192
        >>> Pcset.get(2192).chroma
        '100010010000'
        >>> Pcset.get("100010010000").set_num
        2192
        >>> Pcset.get("garbage").empty
        True
    """
    if is_chroma(src):
        chroma = src
    elif _is_pcset_num(src):
        chroma = _set_num_to_chroma(src)
    elif isinstance(src, list):
        chroma = _list_to_chroma(src)
    elif _is_pcset(src):
        chroma = src.chroma
    else:
        chroma = EMPTY_PCSET.chroma
    cached = _CACHE.get(chroma)
    if cached is not None:
        return cached
    value = _chroma_to_pcset(chroma)
    _CACHE[chroma] = value
    return value

chroma

chroma(s: Any) -> str

Get the 12-bit chroma string of a pitch class set.

Example

from tonal_py import Pcset Pcset.chroma(["C", "D", "E"]) '101010000000'

Source code in src/tonal_py/pcset.py
def chroma(s: Any) -> str:
    """Get the 12-bit chroma string of a pitch class set.

    Example:
        >>> from tonal_py import Pcset
        >>> Pcset.chroma(["C", "D", "E"])
        '101010000000'
    """
    return get(s).chroma

intervals

intervals(s: Any) -> tuple[str, ...]

Get the intervals (from C) of a pitch class set.

Example

from tonal_py import Pcset Pcset.intervals(["C", "E", "G"]) ('1P', '3M', '5P')

Source code in src/tonal_py/pcset.py
def intervals(s: Any) -> tuple[str, ...]:
    """Get the intervals (from C) of a pitch class set.

    Example:
        >>> from tonal_py import Pcset
        >>> Pcset.intervals(["C", "E", "G"])
        ('1P', '3M', '5P')
    """
    return get(s).intervals

num

num(s: Any) -> int

Get the set number (decimal value of the chroma).

Example

from tonal_py import Pcset Pcset.num(["C", "D", "E"]) 2688

Source code in src/tonal_py/pcset.py
def num(s: Any) -> int:
    """Get the set number (decimal value of the chroma).

    Example:
        >>> from tonal_py import Pcset
        >>> Pcset.num(["C", "D", "E"])
        2688
    """
    return get(s).set_num

notes

notes(s: Any) -> list[str]

Get the pitch class names (transposed from C).

Example

from tonal_py import Pcset Pcset.notes(["C", "E", "G"]) ['C', 'E', 'G']

Source code in src/tonal_py/pcset.py
def notes(s: Any) -> list[str]:
    """Get the pitch class names (transposed from C).

    Example:
        >>> from tonal_py import Pcset
        >>> Pcset.notes(["C", "E", "G"])
        ['C', 'E', 'G']
    """
    return [_pitch_distance.transpose("C", ivl) for ivl in get(s).intervals]

chromas

chromas() -> list[str]

All 2048 chromas with C as root (set numbers 2048-4095).

Example

from tonal_py import Pcset len(Pcset.chromas()) 2048 Pcset.chromas()[0] # smallest 12-bit chroma starting with 1 '100000000000'

Source code in src/tonal_py/pcset.py
def chromas() -> list[str]:
    """All 2048 chromas with C as root (set numbers 2048-4095).

    Example:
        >>> from tonal_py import Pcset
        >>> len(Pcset.chromas())
        2048
        >>> Pcset.chromas()[0]      # smallest 12-bit chroma starting with 1
        '100000000000'
    """
    return [_set_num_to_chroma(n) for n in collection.range(2048, 4095)]

modes

modes(s: Any, normalize: bool = True) -> list[str]

Rotate the chroma to enumerate "modes" (rotations starting with 1).

With normalize=True (default), drops rotations that begin with 0 — keeping only those that could be rooted at a real pitch class. Useful for finding all the modes of a scale.

Parameters:

Name Type Description Default
s Any

A Pcset (any input accepted by get).

required
normalize bool

If True, drop rotations starting with 0.

True

Returns:

Type Description
list[str]

List of chroma strings.

Example

from tonal_py import Pcset

C major has 7 rotations starting with 1

len(Pcset.modes(["C", "D", "E", "F", "G", "A", "B"])) 7

Source code in src/tonal_py/pcset.py
def modes(s: Any, normalize: bool = True) -> list[str]:
    """Rotate the chroma to enumerate "modes" (rotations starting with 1).

    With `normalize=True` (default), drops rotations that begin with `0`
    — keeping only those that could be rooted at a real pitch class.
    Useful for finding all the modes of a scale.

    Args:
        s: A Pcset (any input accepted by [`get`][tonal_py.pcset.get]).
        normalize: If True, drop rotations starting with `0`.

    Returns:
        List of chroma strings.

    Example:
        >>> from tonal_py import Pcset
        >>> # C major has 7 rotations starting with 1
        >>> len(Pcset.modes(["C", "D", "E", "F", "G", "A", "B"]))
        7
    """
    pcs = get(s)
    binary = list(pcs.chroma)
    out: list[str] = []
    for i in range(len(binary)):
        r = collection.rotate(i, binary)
        if normalize and r[0] == "0":
            continue
        out.append("".join(r))
    return collection.compact(out)

is_equal

is_equal(s1: Any, s2: Any) -> bool

Test if two pitch class sets contain the same chromas.

Octaves and note duplicates don't matter — only the set of chromas.

Example

from tonal_py import Pcset Pcset.is_equal(["C", "E", "G"], ["G2", "E5", "C4", "C6"]) True

Source code in src/tonal_py/pcset.py
def is_equal(s1: Any, s2: Any) -> bool:
    """Test if two pitch class sets contain the same chromas.

    Octaves and note duplicates don't matter — only the set of chromas.

    Example:
        >>> from tonal_py import Pcset
        >>> Pcset.is_equal(["C", "E", "G"], ["G2", "E5", "C4", "C6"])
        True
    """
    return get(s1).set_num == get(s2).set_num

is_subset_of

is_subset_of(s: Any) -> Callable[[Any], bool]

Curry a "is subset of" test against a fixed superset.

is_subset_of(SUPER)(TEST) returns True if every pitch class in TEST is also in SUPER AND TEST has strictly fewer notes (proper subset).

Parameters:

Name Type Description Default
s Any

The superset to test against.

required

Returns:

Type Description
Callable[[Any], bool]

A predicate function (test_set) -> bool.

Example

from tonal_py import Pcset in_cmaj = Pcset.is_subset_of(["C", "E", "G"]) in_cmaj(["C", "E"]) # CE is a subset of CEG True in_cmaj(["C", "E", "G", "B"]) # CEGB has notes outside CEG False in_cmaj(["C", "E", "G"]) # not strict — equal sets fail False

Source code in src/tonal_py/pcset.py
def is_subset_of(s: Any) -> Callable[[Any], bool]:
    """Curry a "is subset of" test against a fixed superset.

    `is_subset_of(SUPER)(TEST)` returns True if every pitch class in
    `TEST` is also in `SUPER` AND `TEST` has strictly fewer notes
    (proper subset).

    Args:
        s: The superset to test against.

    Returns:
        A predicate function `(test_set) -> bool`.

    Example:
        >>> from tonal_py import Pcset
        >>> in_cmaj = Pcset.is_subset_of(["C", "E", "G"])
        >>> in_cmaj(["C", "E"])               # CE is a subset of CEG
        True
        >>> in_cmaj(["C", "E", "G", "B"])     # CEGB has notes outside CEG
        False
        >>> in_cmaj(["C", "E", "G"])          # not strict — equal sets fail
        False
    """
    s_num = get(s).set_num

    def predicate(notes: Any) -> bool:
        o_num = get(notes).set_num
        return bool(s_num != 0 and s_num != o_num and (o_num & s_num) == o_num)

    return predicate

is_superset_of

is_superset_of(s: Any) -> Callable[[Any], bool]

Curry a "is superset of" test against a fixed subset.

is_superset_of(SUB)(TEST) returns True if TEST contains every pitch class in SUB plus at least one more.

Parameters:

Name Type Description Default
s Any

The subset to test against.

required

Returns:

Type Description
Callable[[Any], bool]

A predicate function (test_set) -> bool.

Example

from tonal_py import Pcset extends_cmaj = Pcset.is_superset_of(["C", "E", "G"]) extends_cmaj(["C", "E", "G", "B"]) # Cmaj7 extends Cmaj True extends_cmaj(["C", "E"]) # missing the G False

Source code in src/tonal_py/pcset.py
def is_superset_of(s: Any) -> Callable[[Any], bool]:
    """Curry a "is superset of" test against a fixed subset.

    `is_superset_of(SUB)(TEST)` returns True if `TEST` contains every
    pitch class in `SUB` plus at least one more.

    Args:
        s: The subset to test against.

    Returns:
        A predicate function `(test_set) -> bool`.

    Example:
        >>> from tonal_py import Pcset
        >>> extends_cmaj = Pcset.is_superset_of(["C", "E", "G"])
        >>> extends_cmaj(["C", "E", "G", "B"])      # Cmaj7 extends Cmaj
        True
        >>> extends_cmaj(["C", "E"])                 # missing the G
        False
    """
    s_num = get(s).set_num

    def predicate(notes: Any) -> bool:
        o_num = get(notes).set_num
        return bool(s_num != 0 and s_num != o_num and (o_num | s_num) == o_num)

    return predicate

is_note_included_in

is_note_included_in(s: Any) -> Callable[[Any], bool]

Curry a membership test for "is this note in the pitch class set?".

Parameters:

Name Type Description Default
s Any

The pitch class set.

required

Returns:

Type Description
Callable[[Any], bool]

A predicate function (note_name) -> bool.

Example

from tonal_py import Pcset in_cmaj = Pcset.is_note_included_in(["C", "E", "G"]) in_cmaj("C4") True in_cmaj("C#4") False

Source code in src/tonal_py/pcset.py
def is_note_included_in(s: Any) -> Callable[[Any], bool]:
    """Curry a membership test for "is this note in the pitch class set?".

    Args:
        s: The pitch class set.

    Returns:
        A predicate function `(note_name) -> bool`.

    Example:
        >>> from tonal_py import Pcset
        >>> in_cmaj = Pcset.is_note_included_in(["C", "E", "G"])
        >>> in_cmaj("C4")
        True
        >>> in_cmaj("C#4")
        False
    """
    pcs = get(s)

    def predicate(note_name: Any) -> bool:
        n = _pitch_note.note(note_name)
        return bool(not n.empty and pcs.chroma[int(n.chroma)] == "1")

    return predicate

filter

filter(s: Any) -> Callable[[list[str]], list[str]]

Curry a list-filter that keeps only notes belonging to the pitch class set.

Parameters:

Name Type Description Default
s Any

The pitch class set.

required

Returns:

Type Description
Callable[[list[str]], list[str]]

A function (notes) -> notes that filters in-set notes.

Example

from tonal_py import Pcset keep = Pcset.filter(["C", "E", "G"]) keep(["C2", "C#2", "E2", "F2", "G2"]) ['C2', 'E2', 'G2']

Source code in src/tonal_py/pcset.py
def filter(s: Any) -> Callable[[list[str]], list[str]]:  # noqa: A001
    """Curry a list-filter that keeps only notes belonging to the pitch class set.

    Args:
        s: The pitch class set.

    Returns:
        A function `(notes) -> notes` that filters in-set notes.

    Example:
        >>> from tonal_py import Pcset
        >>> keep = Pcset.filter(["C", "E", "G"])
        >>> keep(["C2", "C#2", "E2", "F2", "G2"])
        ['C2', 'E2', 'G2']
    """
    is_included = is_note_included_in(s)

    def fn(note_list: list[str]) -> list[str]:
        return [n for n in note_list if is_included(n)]

    return fn