Skip to content

Range

range

Note range generators (chromatic between waypoints).

The Range namespace builds chromatic ranges between a sequence of waypoint notes. Each adjacent pair gets connected by an inclusive ascending or descending range, so ["C4", "E4", "C4"] walks up from C4 to E4, then back down to C4.

Returns either MIDI numbers (numeric) or note names (chromatic).

Shadows the built-in range

The module is named range to match tonal.js. Don't import it as range into namespaces that need the builtin — use from tonal_py import Range (or import the functions individually).

Example

from tonal_py import Range Range.numeric(["C4", "E4"]) [60, 61, 62, 63, 64] Range.chromatic(["C4", "E4"]) ['C4', 'Db4', 'D4', 'Eb4', 'E4']

Source parity: @tonaljs/range

numeric

numeric(notes: list[Any]) -> list[int]

Build a chromatic MIDI range connecting a list of waypoint notes.

Each pair of consecutive waypoints is connected by an inclusive ascending or descending integer range. Notes can be strings or MIDI numbers.

Parameters:

Name Type Description Default
notes list[Any]

Waypoint notes (names or MIDI numbers).

required

Returns:

Type Description
list[int]

MIDI numbers walking through every waypoint in order. Empty if

list[int]

any input fails to parse.

Example

from tonal_py import Range Range.numeric(["C4", "E4"]) [60, 61, 62, 63, 64] Range.numeric(["C4", "E4", "C4"]) [60, 61, 62, 63, 64, 63, 62, 61, 60] Range.numeric([60, 64]) [60, 61, 62, 63, 64]

Source code in src/tonal_py/range.py
def numeric(notes: list[Any]) -> list[int]:
    """Build a chromatic MIDI range connecting a list of waypoint notes.

    Each pair of consecutive waypoints is connected by an inclusive
    ascending or descending integer range. Notes can be strings or MIDI
    numbers.

    Args:
        notes: Waypoint notes (names or MIDI numbers).

    Returns:
        MIDI numbers walking through every waypoint in order. Empty if
        any input fails to parse.

    Example:
        >>> from tonal_py import Range
        >>> Range.numeric(["C4", "E4"])
        [60, 61, 62, 63, 64]
        >>> Range.numeric(["C4", "E4", "C4"])
        [60, 61, 62, 63, 64, 63, 62, 61, 60]
        >>> Range.numeric([60, 64])
        [60, 61, 62, 63, 64]
    """
    midis = collection.compact(
        [n if isinstance(n, (int, float)) and not isinstance(n, bool) else to_midi(n) for n in notes]
    )
    if not notes or len(midis) != len(notes):
        return []
    result = [midis[0]]
    for note in midis[1:]:
        last = result[-1]
        # Skip the first element of each segment to avoid duplicating waypoints
        result.extend(collection.range(last, note)[1:])
    return result

chromatic

chromatic(notes: list[Any], options: ToNoteNameOptions | None = None) -> list[str]

Like numeric but returns note names.

Parameters:

Name Type Description Default
notes list[Any]

Waypoint notes (names or MIDI numbers).

required
options ToNoteNameOptions | None

Optional dict to control note name formatting (e.g. {"sharps": True}).

None

Returns:

Type Description
list[str]

Note names walking through every waypoint.

Example

from tonal_py import Range Range.chromatic(["C4", "E4"]) ['C4', 'Db4', 'D4', 'Eb4', 'E4'] Range.chromatic(["C4", "E4"], {"sharps": True}) ['C4', 'C#4', 'D4', 'D#4', 'E4']

Source code in src/tonal_py/range.py
def chromatic(notes: list[Any], options: ToNoteNameOptions | None = None) -> list[str]:
    """Like [`numeric`][tonal_py.range.numeric] but returns note names.

    Args:
        notes: Waypoint notes (names or MIDI numbers).
        options: Optional dict to control note name formatting (e.g.
            `{"sharps": True}`).

    Returns:
        Note names walking through every waypoint.

    Example:
        >>> from tonal_py import Range
        >>> Range.chromatic(["C4", "E4"])
        ['C4', 'Db4', 'D4', 'Eb4', 'E4']
        >>> Range.chromatic(["C4", "E4"], {"sharps": True})
        ['C4', 'C#4', 'D4', 'D#4', 'E4']
    """
    return [midi_to_note_name(m, options) for m in numeric(notes)]