Skip to content

Note

note

High-level note operations.

The Note namespace is the main entry point for working with musical notes. It wraps the parser in _pitch_note with a richer API for transposition, enharmonic spelling, sorting, and conversions to and from MIDI numbers and frequencies.

Notes are parsed from strings in scientific notation: "C4", "Bb3", "F##5", "Cbb-1". Pitch classes (no octave) are also accepted: "C", "Bb", "F#". Invalid input returns the NO_NOTE sentinel rather than raising — check .empty to detect failures.

Example

from tonal_py import Note Note.midi("C4") 60 Note.transpose("C4", "M3") 'E4' Note.simplify("C##") 'D'

Source parity: @tonaljs/note

name

name(note: Any) -> str

Get the canonical name of a note.

Parameters:

Name Type Description Default
note Any

A note name string, Note object, or Pitch object.

required

Returns:

Type Description
str

The note's canonical name (e.g. "Bb4"), or "" if invalid.

Example

from tonal_py import Note Note.name("Bb4") 'Bb4' Note.name("garbage") ''

Source code in src/tonal_py/note.py
def name(note: Any) -> str:
    """Get the canonical name of a note.

    Args:
        note: A note name string, `Note` object, or `Pitch` object.

    Returns:
        The note's canonical name (e.g. `"Bb4"`), or `""` if invalid.

    Example:
        >>> from tonal_py import Note
        >>> Note.name("Bb4")
        'Bb4'
        >>> Note.name("garbage")
        ''
    """
    return get(note).name

pitch_class

pitch_class(note: Any) -> str

Get the pitch class of a note (note name without octave).

Parameters:

Name Type Description Default
note Any

A note name string, Note object, or Pitch object.

required

Returns:

Type Description
str

The pitch class string (e.g. "Bb"), or "" if invalid.

Example

from tonal_py import Note Note.pitch_class("Bb4") 'Bb' Note.pitch_class("C") 'C'

Source code in src/tonal_py/note.py
def pitch_class(note: Any) -> str:
    """Get the pitch class of a note (note name without octave).

    Args:
        note: A note name string, `Note` object, or `Pitch` object.

    Returns:
        The pitch class string (e.g. `"Bb"`), or `""` if invalid.

    Example:
        >>> from tonal_py import Note
        >>> Note.pitch_class("Bb4")
        'Bb'
        >>> Note.pitch_class("C")
        'C'
    """
    return get(note).pc

accidentals

accidentals(note: Any) -> str

Get the accidentals string of a note.

Parameters:

Name Type Description Default
note Any

A note name string, Note object, or Pitch object.

required

Returns:

Type Description
str

The accidentals (e.g. "b", "##", ""), or "" if invalid.

Example

from tonal_py import Note Note.accidentals("Bb4") 'b' Note.accidentals("F##5") '##' Note.accidentals("C4") ''

Source code in src/tonal_py/note.py
def accidentals(note: Any) -> str:
    """Get the accidentals string of a note.

    Args:
        note: A note name string, `Note` object, or `Pitch` object.

    Returns:
        The accidentals (e.g. `"b"`, `"##"`, `""`), or `""` if invalid.

    Example:
        >>> from tonal_py import Note
        >>> Note.accidentals("Bb4")
        'b'
        >>> Note.accidentals("F##5")
        '##'
        >>> Note.accidentals("C4")
        ''
    """
    return get(note).acc

octave

octave(note: Any) -> int | None

Get the octave number of a note.

Parameters:

Name Type Description Default
note Any

A note name string, Note object, or Pitch object.

required

Returns:

Type Description
int | None

The octave (e.g. 4 for "C4"), or None for pitch classes

int | None

(notes without an octave) and invalid input.

Example

from tonal_py import Note Note.octave("C4") 4 Note.octave("C") is None True

Source code in src/tonal_py/note.py
def octave(note: Any) -> int | None:
    """Get the octave number of a note.

    Args:
        note: A note name string, `Note` object, or `Pitch` object.

    Returns:
        The octave (e.g. `4` for `"C4"`), or `None` for pitch classes
        (notes without an octave) and invalid input.

    Example:
        >>> from tonal_py import Note
        >>> Note.octave("C4")
        4
        >>> Note.octave("C") is None
        True
    """
    return get(note).oct

midi

midi(note: Any) -> int | None

Get the MIDI number (0-127) of a note.

Parameters:

Name Type Description Default
note Any

A note name string, Note object, or Pitch object.

required

Returns:

Type Description
int | None

The MIDI number, or None for pitch classes (no octave),

int | None

out-of-range octaves, or invalid input.

Example

from tonal_py import Note Note.midi("C4") 60 Note.midi("A4") 69 Note.midi("C") is None True

Source code in src/tonal_py/note.py
def midi(note: Any) -> int | None:
    """Get the MIDI number (0-127) of a note.

    Args:
        note: A note name string, `Note` object, or `Pitch` object.

    Returns:
        The MIDI number, or `None` for pitch classes (no octave),
        out-of-range octaves, or invalid input.

    Example:
        >>> from tonal_py import Note
        >>> Note.midi("C4")
        60
        >>> Note.midi("A4")
        69
        >>> Note.midi("C") is None
        True
    """
    return get(note).midi

freq

freq(note: Any) -> float | None

Get the frequency in Hz of a note (A4 = 440 Hz).

Parameters:

Name Type Description Default
note Any

A note name string, Note object, or Pitch object.

required

Returns:

Type Description
float | None

The frequency in hertz, or None for pitch classes and

float | None

invalid input.

Example

from tonal_py import Note Note.freq("A4") 440.0 round(Note.freq("C4"), 2) 261.63

Source code in src/tonal_py/note.py
def freq(note: Any) -> float | None:
    """Get the frequency in Hz of a note (A4 = 440 Hz).

    Args:
        note: A note name string, `Note` object, or `Pitch` object.

    Returns:
        The frequency in hertz, or `None` for pitch classes and
        invalid input.

    Example:
        >>> from tonal_py import Note
        >>> Note.freq("A4")
        440.0
        >>> round(Note.freq("C4"), 2)
        261.63
    """
    return get(note).freq

chroma

chroma(note: Any) -> float

Get the pitch class number 0-11 of a note (C=0, C#=1, ..., B=11).

Parameters:

Name Type Description Default
note Any

A note name string, Note object, or Pitch object.

required

Returns:

Type Description
float

The chroma 0-11. math.nan if invalid.

Example

from tonal_py import Note Note.chroma("C4") 0 Note.chroma("D5") 2 Note.chroma("Bb3") 10

Source code in src/tonal_py/note.py
def chroma(note: Any) -> float:
    """Get the pitch class number 0-11 of a note (C=0, C#=1, ..., B=11).

    Args:
        note: A note name string, `Note` object, or `Pitch` object.

    Returns:
        The chroma 0-11. `math.nan` if invalid.

    Example:
        >>> from tonal_py import Note
        >>> Note.chroma("C4")
        0
        >>> Note.chroma("D5")
        2
        >>> Note.chroma("Bb3")
        10
    """
    return get(note).chroma

names

names(array: list | None = None) -> list[str]

Return the natural note names, or filter a list to its valid notes.

Parameters:

Name Type Description Default
array list | None

When None, return the seven natural note names. When a list, return the canonical names of valid notes only, preserving order. Non-list arguments return [].

None

Returns:

Type Description
list[str]

Either the natural names ['C', 'D', ..., 'B'], or the filtered

list[str]

canonical names of valid input.

Example

from tonal_py import Note Note.names() ['C', 'D', 'E', 'F', 'G', 'A', 'B'] Note.names(["C4", "garbage", "Bb3"]) ['C4', 'Bb3']

Source code in src/tonal_py/note.py
def names(array: list | None = None) -> list[str]:
    """Return the natural note names, or filter a list to its valid notes.

    Args:
        array: When `None`, return the seven natural note names. When a
            list, return the canonical names of valid notes only,
            preserving order. Non-list arguments return `[]`.

    Returns:
        Either the natural names `['C', 'D', ..., 'B']`, or the filtered
        canonical names of valid input.

    Example:
        >>> from tonal_py import Note
        >>> Note.names()
        ['C', 'D', 'E', 'F', 'G', 'A', 'B']
        >>> Note.names(["C4", "garbage", "Bb3"])
        ['C4', 'Bb3']
    """
    if array is None:
        return NAMES.copy()
    if not isinstance(array, list):
        return []
    return [n.name for n in (get(x) for x in array) if not n.empty]

from_midi

from_midi(midi_val: float) -> str

Convert a MIDI number to a note name (using flats for accidentals).

Parameters:

Name Type Description Default
midi_val float

MIDI note number, typically 0-127. Decimal values are rounded.

required

Returns:

Type Description
str

The note name in scientific notation (e.g. "C4", "Db5").

str

Returns "" for nan or inf.

Example

from tonal_py import Note Note.from_midi(60) 'C4' Note.from_midi(61) 'Db4' Note.from_midi(61.7) 'D4'

See Also

from_midi_sharps — same but with sharps

Source code in src/tonal_py/note.py
def from_midi(midi_val: float) -> str:
    """Convert a MIDI number to a note name (using flats for accidentals).

    Args:
        midi_val: MIDI note number, typically 0-127. Decimal values are
            rounded.

    Returns:
        The note name in scientific notation (e.g. `"C4"`, `"Db5"`).
        Returns `""` for `nan` or `inf`.

    Example:
        >>> from tonal_py import Note
        >>> Note.from_midi(60)
        'C4'
        >>> Note.from_midi(61)
        'Db4'
        >>> Note.from_midi(61.7)
        'D4'

    See Also:
        [`from_midi_sharps`][tonal_py.note.from_midi_sharps] — same but with sharps
    """
    return midi_to_note_name(midi_val)

from_midi_sharps

from_midi_sharps(midi_val: float) -> str

Convert a MIDI number to a note name (using sharps for accidentals).

Parameters:

Name Type Description Default
midi_val float

MIDI note number, typically 0-127.

required

Returns:

Type Description
str

The note name with sharps (e.g. "C#4").

Example

from tonal_py import Note Note.from_midi_sharps(61) 'C#4'

Source code in src/tonal_py/note.py
def from_midi_sharps(midi_val: float) -> str:
    """Convert a MIDI number to a note name (using sharps for accidentals).

    Args:
        midi_val: MIDI note number, typically 0-127.

    Returns:
        The note name with sharps (e.g. `"C#4"`).

    Example:
        >>> from tonal_py import Note
        >>> Note.from_midi_sharps(61)
        'C#4'
    """
    return midi_to_note_name(midi_val, {"sharps": True})

from_freq

from_freq(freq_val: float) -> str

Convert a frequency in Hz to a note name (using flats).

Parameters:

Name Type Description Default
freq_val float

Frequency in hertz.

required

Returns:

Type Description
str

The nearest note name (rounded to nearest semitone).

Example

from tonal_py import Note Note.from_freq(440) 'A4' Note.from_freq(261.62) 'C4'

Source code in src/tonal_py/note.py
def from_freq(freq_val: float) -> str:
    """Convert a frequency in Hz to a note name (using flats).

    Args:
        freq_val: Frequency in hertz.

    Returns:
        The nearest note name (rounded to nearest semitone).

    Example:
        >>> from tonal_py import Note
        >>> Note.from_freq(440)
        'A4'
        >>> Note.from_freq(261.62)
        'C4'
    """
    return midi_to_note_name(freq_to_midi(freq_val))

from_freq_sharps

from_freq_sharps(freq_val: float) -> str

Convert a frequency in Hz to a note name (using sharps).

Parameters:

Name Type Description Default
freq_val float

Frequency in hertz.

required

Returns:

Type Description
str

The nearest note name with sharps for accidentals.

Example

from tonal_py import Note Note.from_freq_sharps(277.18) 'C#4'

Source code in src/tonal_py/note.py
def from_freq_sharps(freq_val: float) -> str:
    """Convert a frequency in Hz to a note name (using sharps).

    Args:
        freq_val: Frequency in hertz.

    Returns:
        The nearest note name with sharps for accidentals.

    Example:
        >>> from tonal_py import Note
        >>> Note.from_freq_sharps(277.18)
        'C#4'
    """
    return midi_to_note_name(freq_to_midi(freq_val), {"sharps": True})

transpose_by

transpose_by(interval: Any) -> Callable[[Any], str]

Curry the interval to map across many notes.

Parameters:

Name Type Description Default
interval Any

The interval to apply (string, Interval, or [fifths, octaves]).

required

Returns:

Type Description
Callable[[Any], str]

A function that takes a note and returns the transposed note name.

Example

from tonal_py import Note up_a_fifth = Note.transpose_by("P5") [up_a_fifth(n) for n in ["C", "D", "E"]]['G', 'A', 'B']

Source code in src/tonal_py/note.py
def transpose_by(interval: Any) -> Callable[[Any], str]:
    """Curry the interval to map across many notes.

    Args:
        interval: The interval to apply (string, `Interval`, or
            `[fifths, octaves]`).

    Returns:
        A function that takes a note and returns the transposed note name.

    Example:
        >>> from tonal_py import Note
        >>> up_a_fifth = Note.transpose_by("P5")
        >>> [up_a_fifth(n) for n in ["C", "D", "E"]]
        ['G', 'A', 'B']
    """
    return lambda note: transpose(note, interval)

transpose_from

transpose_from(note: Any) -> Callable[[Any], str]

Curry the starting note to map across many intervals.

Parameters:

Name Type Description Default
note Any

The note to transpose from (string, Note, or Pitch).

required

Returns:

Type Description
Callable[[Any], str]

A function that takes an interval and returns the transposed note.

Example

from tonal_py import Note from_c = Note.transpose_from("C") [from_c(i) for i in ["1P", "3M", "5P"]]['C', 'E', 'G']

Source code in src/tonal_py/note.py
def transpose_from(note: Any) -> Callable[[Any], str]:
    """Curry the starting note to map across many intervals.

    Args:
        note: The note to transpose from (string, `Note`, or `Pitch`).

    Returns:
        A function that takes an interval and returns the transposed note.

    Example:
        >>> from tonal_py import Note
        >>> from_c = Note.transpose_from("C")
        >>> [from_c(i) for i in ["1P", "3M", "5P"]]
        ['C', 'E', 'G']
    """
    return lambda interval: transpose(note, interval)

transpose_fifths

transpose_fifths(note_name: Any, fifths: int) -> str

Transpose by N perfect fifths (positive up, negative down).

This skips the interval-string parser entirely — it adds directly to the circle-of-fifths coordinate.

Parameters:

Name Type Description Default
note_name Any

The note to transpose.

required
fifths int

Number of perfect fifths to add (negative subtracts).

required

Returns:

Type Description
str

The transposed note name.

Example

from tonal_py import Note Note.transpose_fifths("C", 2) 'D' [Note.transpose_fifths("C", n) for n in range(5)]['C', 'G', 'D', 'A', 'E']

Source code in src/tonal_py/note.py
def transpose_fifths(note_name: Any, fifths: int) -> str:
    """Transpose by N perfect fifths (positive up, negative down).

    This skips the interval-string parser entirely — it adds directly to
    the circle-of-fifths coordinate.

    Args:
        note_name: The note to transpose.
        fifths: Number of perfect fifths to add (negative subtracts).

    Returns:
        The transposed note name.

    Example:
        >>> from tonal_py import Note
        >>> Note.transpose_fifths("C", 2)
        'D'
        >>> [Note.transpose_fifths("C", n) for n in range(5)]
        ['C', 'G', 'D', 'A', 'E']
    """
    return transpose(note_name, [fifths, 0])

transpose_octaves

transpose_octaves(note_name: Any, octaves: int) -> str

Transpose by N octaves.

Parameters:

Name Type Description Default
note_name Any

The note to transpose.

required
octaves int

Number of octaves to add (negative subtracts).

required

Returns:

Type Description
str

The transposed note name.

Example

from tonal_py import Note Note.transpose_octaves("C4", 2) 'C6' Note.transpose_octaves("A4", -1) 'A3'

Source code in src/tonal_py/note.py
def transpose_octaves(note_name: Any, octaves: int) -> str:
    """Transpose by N octaves.

    Args:
        note_name: The note to transpose.
        octaves: Number of octaves to add (negative subtracts).

    Returns:
        The transposed note name.

    Example:
        >>> from tonal_py import Note
        >>> Note.transpose_octaves("C4", 2)
        'C6'
        >>> Note.transpose_octaves("A4", -1)
        'A3'
    """
    return transpose(note_name, [0, octaves])

ascending

ascending(a: Note, b: Note) -> int

Comparator that orders notes by pitch height (low to high).

Returns negative if a < b, zero if equal, positive if a > b.

Example

from tonal_py import Note Note.sorted_names(["E4", "C4", "G4"], Note.ascending) ['C4', 'E4', 'G4']

Source code in src/tonal_py/note.py
def ascending(a: _NoteType, b: _NoteType) -> int:
    """Comparator that orders notes by pitch height (low to high).

    Returns negative if `a < b`, zero if equal, positive if `a > b`.

    Example:
        >>> from tonal_py import Note
        >>> Note.sorted_names(["E4", "C4", "G4"], Note.ascending)
        ['C4', 'E4', 'G4']
    """
    return a.height - b.height

descending

descending(a: Note, b: Note) -> int

Comparator that orders notes by pitch height (high to low).

Example

from tonal_py import Note Note.sorted_names(["E4", "C4", "G4"], Note.descending) ['G4', 'E4', 'C4']

Source code in src/tonal_py/note.py
def descending(a: _NoteType, b: _NoteType) -> int:
    """Comparator that orders notes by pitch height (high to low).

    Example:
        >>> from tonal_py import Note
        >>> Note.sorted_names(["E4", "C4", "G4"], Note.descending)
        ['G4', 'E4', 'C4']
    """
    return b.height - a.height

sorted_names

sorted_names(notes: list, comparator: Callable[[Note, Note], int] | None = None) -> list[str]

Sort note names by pitch height; filter out invalid input.

Parameters:

Name Type Description Default
notes list

Notes to sort (any inputs accepted by [get][tonal_py.note.get]).

required
comparator Callable[[Note, Note], int] | None

Optional ordering function. Defaults to ascending.

None

Returns:

Type Description
list[str]

The sorted, filtered note names.

Example

from tonal_py import Note Note.sorted_names(["E4", "C4", "garbage", "G4"]) ['C4', 'E4', 'G4']

Source code in src/tonal_py/note.py
def sorted_names(notes: list, comparator: Callable[[_NoteType, _NoteType], int] | None = None) -> list[str]:
    """Sort note names by pitch height; filter out invalid input.

    Args:
        notes: Notes to sort (any inputs accepted by [`get`][tonal_py.note.get]).
        comparator: Optional ordering function. Defaults to
            [`ascending`][tonal_py.note.ascending].

    Returns:
        The sorted, filtered note names.

    Example:
        >>> from tonal_py import Note
        >>> Note.sorted_names(["E4", "C4", "garbage", "G4"])
        ['C4', 'E4', 'G4']
    """
    cmp = comparator or ascending
    parsed = _only_notes(notes)
    from functools import cmp_to_key
    parsed.sort(key=cmp_to_key(cmp))
    return [n.name for n in parsed]

sorted_uniq_names

sorted_uniq_names(notes: list) -> list[str]

Sort note names ascending and remove duplicates.

Parameters:

Name Type Description Default
notes list

Notes (any inputs accepted by [get][tonal_py.note.get]).

required

Returns:

Type Description
list[str]

Sorted, deduplicated, valid-only note names.

Example

from tonal_py import Note Note.sorted_uniq_names(["E4", "C4", "C4", "G4", "garbage"]) ['C4', 'E4', 'G4']

Source code in src/tonal_py/note.py
def sorted_uniq_names(notes: list) -> list[str]:
    """Sort note names ascending and remove duplicates.

    Args:
        notes: Notes (any inputs accepted by [`get`][tonal_py.note.get]).

    Returns:
        Sorted, deduplicated, valid-only note names.

    Example:
        >>> from tonal_py import Note
        >>> Note.sorted_uniq_names(["E4", "C4", "C4", "G4", "garbage"])
        ['C4', 'E4', 'G4']
    """
    sorted_ = sorted_names(notes, ascending)
    out: list[str] = []
    for i, n in enumerate(sorted_):
        if i == 0 or n != sorted_[i - 1]:
            out.append(n)
    return out

simplify

simplify(note_name: Any) -> str

Reduce double accidentals and out-of-range spellings.

Picks a "simpler" enharmonic equivalent — C## becomes D, B#4 becomes C5, etc. Uses the source's accidental direction when picking sharps or flats.

Parameters:

Name Type Description Default
note_name Any

A note name (string, Note, or Pitch).

required

Returns:

Type Description
str

The simplified name, or "" if input is invalid.

Example

from tonal_py import Note Note.simplify("C##") 'D' Note.simplify("C###") 'D#' Note.simplify("B#4") 'C5' Note.simplify("Cbb") 'Bb'

Source code in src/tonal_py/note.py
def simplify(note_name: Any) -> str:
    """Reduce double accidentals and out-of-range spellings.

    Picks a "simpler" enharmonic equivalent — `C##` becomes `D`, `B#4`
    becomes `C5`, etc. Uses the source's accidental direction when picking
    sharps or flats.

    Args:
        note_name: A note name (string, `Note`, or `Pitch`).

    Returns:
        The simplified name, or `""` if input is invalid.

    Example:
        >>> from tonal_py import Note
        >>> Note.simplify("C##")
        'D'
        >>> Note.simplify("C###")
        'D#'
        >>> Note.simplify("B#4")
        'C5'
        >>> Note.simplify("Cbb")
        'Bb'
    """
    n = get(note_name)
    if n.empty:
        return ""
    # JS: `note.midi || note.chroma` — falls back to chroma when midi is None.
    # Note that midi=0 is valid but JS treats 0 as falsy too; we mirror that
    # quirk because chroma for C is also 0, so the result is the same.
    midi_or_chroma = n.midi if n.midi else n.chroma
    return midi_to_note_name(
        midi_or_chroma,
        {"sharps": n.alt > 0, "pitchClass": n.midi is None},
    )

enharmonic

enharmonic(note_name: Any, dest_name: Any = None) -> str

Find the enharmonic equivalent of a note.

By default picks "the other" common spelling (sharps if the source used flats, vice versa). When dest_name is provided, returns the note re-spelled to use that pitch class — but only if dest_name's chroma matches the source's chroma.

Parameters:

Name Type Description Default
note_name Any

The source note (string, Note, or Pitch).

required
dest_name Any

Optional explicit destination pitch class (e.g. "E#" to constrain the spelling).

None

Returns:

Type Description
str

The enharmonic note name, or "" if either input is invalid or

str

the requested destination doesn't share the source's chroma.

Example

from tonal_py import Note Note.enharmonic("Db") 'C#' Note.enharmonic("C") # already simplest 'C' Note.enharmonic("F2", "E#") # explicit destination 'E#2' Note.enharmonic("C##b") # invalid input ''

Source code in src/tonal_py/note.py
def enharmonic(note_name: Any, dest_name: Any = None) -> str:
    """Find the enharmonic equivalent of a note.

    By default picks "the other" common spelling (sharps if the source
    used flats, vice versa). When `dest_name` is provided, returns the
    note re-spelled to use that pitch class — but only if `dest_name`'s
    chroma matches the source's chroma.

    Args:
        note_name: The source note (string, `Note`, or `Pitch`).
        dest_name: Optional explicit destination pitch class (e.g.
            `"E#"` to constrain the spelling).

    Returns:
        The enharmonic note name, or `""` if either input is invalid or
        the requested destination doesn't share the source's chroma.

    Example:
        >>> from tonal_py import Note
        >>> Note.enharmonic("Db")
        'C#'
        >>> Note.enharmonic("C")        # already simplest
        'C'
        >>> Note.enharmonic("F2", "E#") # explicit destination
        'E#2'
        >>> Note.enharmonic("C##b")     # invalid input
        ''
    """
    src = get(note_name)
    if src.empty:
        return ""
    if dest_name is None:
        dest_name = midi_to_note_name(
            src.midi if src.midi else src.chroma,
            {"sharps": src.alt < 0, "pitchClass": True},
        )
    dest = get(dest_name)
    if dest.empty or dest.chroma != src.chroma:
        return ""
    if src.oct is None:
        return dest.pc
    # Octave bookkeeping: if the rewrite crossed a B->C or C->B boundary,
    # adjust by one. JS uses chroma-minus-alt to detect the wrap.
    src_chroma = src.chroma - src.alt
    dest_chroma = dest.chroma - dest.alt
    if src_chroma > 11 or dest_chroma < 0:
        offset = -1
    elif src_chroma < 0 or dest_chroma > 11:
        offset = 1
    else:
        offset = 0
    return dest.pc + str(src.oct + offset)