Skip to content

xnano_audio

xnano_audio

xnano-audio

xnano-audio is the audio backend for xnano, as well as an easy way to synthesize, play, and capture sound in Python without any audio files, system tooling, or additional compilation steps. Every sound is computed on the fly; nothing is bundled or loaded from disk.

It is deliberately decoupled from xnano-core: this package owns the platform audio libraries, so a missing audio device (or a build without device support) can never take down the terminal core. Use detect_audio_capabilities to find out what is available before opening a device.

Unlike xnano-core, everything is exported directly from this module rather than split across submodules. The public API is made up of independent parts:

- **Synthesis**: Built-in instruments and effects, rendered to plain samples. This needs
no audio device, so it works everywhere (including CI).

    - `render_note`, `note_to_hertz`, `instrument_names`, `is_percussive`: Render a note
    from a built-in instrument, convert note names to frequencies, and inspect what is
    available.

    - `TerminalSound`: An immutable, rendered mono sound (raw samples plus their sample rate).

    - `TerminalAudioEffect`: An immutable, reusable effect description, applied in order when
    rendering or playing a sound.

- **Melodies**: `TerminalNote`, `TerminalMelody`, `TerminalTempoMap`,
`TerminalTimeSignature` and `TerminalScheduledNote` describe notes on a beat timeline.
A melody reports which notes are due between two readings of any clock and renders to
a `TerminalSound`.

- **Playback**: `TerminalAudioPlayer` plays sounds and instruments on the default output
device, with a musical clock for sample-accurate timing.

- **Capture**: `TerminalMicrophone` reads the level of the default input device.

Device failures are raised as TerminalAudioError; a missing device, or a build without device support, is raised as its subclass TerminalAudioUnavailableError.

Classes:

Functions:

Attributes:

  • InstrumentName (TypeAlias) –

    One of the built-in instruments.

  • NoteName (TypeAlias) –

    A note by name: a letter, an optional sharp or flat, and an octave ('C4' is middle C).

  • Pitch (TypeAlias) –

    A pitch: a note name, or a MIDI number from 0 to 127 (60 is middle C).

  • is_optimized_build (bool) –

    Whether the extension was built with optimizations. Timing a debug build says nothing.

InstrumentName module-attribute

InstrumentName: TypeAlias = Literal['piano', 'electric_piano', 'organ', 'bell', 'marimba', 'guitar', 'electric_guitar', 'pluck', 'bass', 'synth', 'pad', 'strings', 'sine', 'square', 'saw', 'triangle', 'kick', 'snare', 'hihat', 'open_hat', 'clap', 'tom', 'cowbell']

NoteName module-attribute

NoteName: TypeAlias = Literal['C0', 'C#0', 'Db0', 'D0', 'D#0', 'Eb0', 'E0', 'F0', 'F#0', 'Gb0', 'G0', 'G#0', 'Ab0', 'A0', 'A#0', 'Bb0', 'B0', 'C1', 'C#1', 'Db1', 'D1', 'D#1', 'Eb1', 'E1', 'F1', 'F#1', 'Gb1', 'G1', 'G#1', 'Ab1', 'A1', 'A#1', 'Bb1', 'B1', 'C2', 'C#2', 'Db2', 'D2', 'D#2', 'Eb2', 'E2', 'F2', 'F#2', 'Gb2', 'G2', 'G#2', 'Ab2', 'A2', 'A#2', 'Bb2', 'B2', 'C3', 'C#3', 'Db3', 'D3', 'D#3', 'Eb3', 'E3', 'F3', 'F#3', 'Gb3', 'G3', 'G#3', 'Ab3', 'A3', 'A#3', 'Bb3', 'B3', 'C4', 'C#4', 'Db4', 'D4', 'D#4', 'Eb4', 'E4', 'F4', 'F#4', 'Gb4', 'G4', 'G#4', 'Ab4', 'A4', 'A#4', 'Bb4', 'B4', 'C5', 'C#5', 'Db5', 'D5', 'D#5', 'Eb5', 'E5', 'F5', 'F#5', 'Gb5', 'G5', 'G#5', 'Ab5', 'A5', 'A#5', 'Bb5', 'B5', 'C6', 'C#6', 'Db6', 'D6', 'D#6', 'Eb6', 'E6', 'F6', 'F#6', 'Gb6', 'G6', 'G#6', 'Ab6', 'A6', 'A#6', 'Bb6', 'B6', 'C7', 'C#7', 'Db7', 'D7', 'D#7', 'Eb7', 'E7', 'F7', 'F#7', 'Gb7', 'G7', 'G#7', 'Ab7', 'A7', 'A#7', 'Bb7', 'B7', 'C8', 'C#8', 'Db8', 'D8', 'D#8', 'Eb8', 'E8', 'F8', 'F#8', 'Gb8', 'G8', 'G#8', 'Ab8', 'A8', 'A#8', 'Bb8', 'B8']

is_optimized_build module-attribute

is_optimized_build: bool

Whether the extension was built with optimizations. Timing a debug build says nothing.

TerminalAudioCapabilities

What audio support this build and machine offer.

Returned by detect_audio_capabilities.

Attributes:

has_device_support property

has_device_support: bool

Whether this build was compiled with audio device support.

Builds made without the system audio libraries (for example, Linux without the ALSA development headers) can still synthesize sound but cannot play or capture it.

is_output_available property

is_output_available: bool

Whether a default output device exists.

is_input_available property

is_input_available: bool

Whether a default input device (microphone) exists.

output_unavailable_reason property

output_unavailable_reason: str | None

Why output is unavailable, or None when it is available.

input_unavailable_reason property

input_unavailable_reason: str | None

Why input is unavailable, or None when it is available.

TerminalAudioEffect

An immutable audio effect description.

Build one with a static constructor and pass it, in order, to render_note, TerminalSound.with_effects or TerminalAudioPlayer.play_note. Effects hold no state, so one instance can be reused for any number of sounds.

Every constructor raises ValueError when an argument is out of range.

Attributes:

  • name (str) –

    The effect's kind, such as "reverb".

Methods:

  • bit_crush –

    Lo-fi: quantize to bits and hold every downsample-th sample.

  • distortion –

    Soft-clip (tanh) distortion.

  • low_pass –

    Remove frequencies above cutoff.

  • high_pass –

    Remove frequencies below cutoff.

  • delay –

    Echoes.

  • reverb –

    Room reverb (Freeverb-style).

  • tremolo –

    Amplitude wobble.

  • __or__ –

    Chain this effect with another effect or sequence of effects.

name property

name: str

The effect's kind, such as "reverb".

bit_crush staticmethod

bit_crush(*, bits: int = 8, downsample: int = 4) -> TerminalAudioEffect

Lo-fi: quantize to bits and hold every downsample-th sample.

Parameters:

  • bits (int, default: 8 ) –

    Resolution, 1 to 16. Lower is crunchier.

  • downsample (int, default: 4 ) –

    Hold each value for this many samples, 1 to 256.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
@staticmethod
def bit_crush(*, bits: int = 8, downsample: int = 4) -> TerminalAudioEffect:
    """
    Lo-fi: quantize to ``bits`` and hold every ``downsample``-th sample.

    Args:
        bits: Resolution, 1 to 16. Lower is crunchier.
        downsample: Hold each value for this many samples, 1 to 256.
    """

distortion staticmethod

distortion(*, drive: float = 4.0) -> TerminalAudioEffect

Soft-clip (tanh) distortion.

Parameters:

  • drive (float, default: 4.0 ) –

    Input gain before clipping, 0.1 to 100. Around 1 is nearly clean; higher is harsher. The output is normalized.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
@staticmethod
def distortion(*, drive: float = 4.0) -> TerminalAudioEffect:
    """
    Soft-clip (tanh) distortion.

    Args:
        drive: Input gain before clipping, 0.1 to 100. Around 1 is nearly
            clean; higher is harsher. The output is normalized.
    """

low_pass staticmethod

low_pass(*, cutoff: float = 1200.0, resonance: float = 0.707) -> TerminalAudioEffect

Remove frequencies above cutoff.

Parameters:

  • cutoff (float, default: 1200.0 ) –

    Corner frequency in hertz, 20 to 20000.

  • resonance (float, default: 0.707 ) –

    Filter Q, 0.1 to 20. The default is flat.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
@staticmethod
def low_pass(
    *, cutoff: float = 1200.0, resonance: float = 0.707
) -> TerminalAudioEffect:
    """
    Remove frequencies above ``cutoff``.

    Args:
        cutoff: Corner frequency in hertz, 20 to 20000.
        resonance: Filter Q, 0.1 to 20. The default is flat.
    """

high_pass staticmethod

high_pass(*, cutoff: float = 400.0, resonance: float = 0.707) -> TerminalAudioEffect

Remove frequencies below cutoff.

Parameters:

  • cutoff (float, default: 400.0 ) –

    Corner frequency in hertz, 20 to 20000.

  • resonance (float, default: 0.707 ) –

    Filter Q, 0.1 to 20. The default is flat.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
@staticmethod
def high_pass(
    *, cutoff: float = 400.0, resonance: float = 0.707
) -> TerminalAudioEffect:
    """
    Remove frequencies below ``cutoff``.

    Args:
        cutoff: Corner frequency in hertz, 20 to 20000.
        resonance: Filter Q, 0.1 to 20. The default is flat.
    """

delay staticmethod

delay(*, time_milliseconds: float = 250.0, feedback: float = 0.4, mix: float = 0.35) -> TerminalAudioEffect

Echoes.

The result is longer than the input so the echoes ring out.

Parameters:

  • time_milliseconds (float, default: 250.0 ) –

    Time between echoes, 1 to 2000.

  • feedback (float, default: 0.4 ) –

    How much of each echo is fed back, 0.0 to 0.95.

  • mix (float, default: 0.35 ) –

    Level of the echoes against the dry sound, 0.0 to 1.0.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
@staticmethod
def delay(
    *, time_milliseconds: float = 250.0, feedback: float = 0.4, mix: float = 0.35
) -> TerminalAudioEffect:
    """
    Echoes.

    The result is longer than the input so the echoes ring out.

    Args:
        time_milliseconds: Time between echoes, 1 to 2000.
        feedback: How much of each echo is fed back, 0.0 to 0.95.
        mix: Level of the echoes against the dry sound, 0.0 to 1.0.
    """

reverb staticmethod

reverb(*, room: float = 0.5, damping: float = 0.5, mix: float = 0.25) -> TerminalAudioEffect

Room reverb (Freeverb-style).

The result is longer than the input so the tail rings out.

Parameters:

  • room (float, default: 0.5 ) –

    Room size, 0.0 to 1.0. Larger rooms ring longer.

  • damping (float, default: 0.5 ) –

    How quickly high frequencies fade, 0.0 to 1.0.

  • mix (float, default: 0.25 ) –

    Wet level against the dry sound, 0.0 to 1.0.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
@staticmethod
def reverb(
    *, room: float = 0.5, damping: float = 0.5, mix: float = 0.25
) -> TerminalAudioEffect:
    """
    Room reverb (Freeverb-style).

    The result is longer than the input so the tail rings out.

    Args:
        room: Room size, 0.0 to 1.0. Larger rooms ring longer.
        damping: How quickly high frequencies fade, 0.0 to 1.0.
        mix: Wet level against the dry sound, 0.0 to 1.0.
    """

tremolo staticmethod

tremolo(*, rate_hertz: float = 5.0, depth: float = 0.5) -> TerminalAudioEffect

Amplitude wobble.

Parameters:

  • rate_hertz (float, default: 5.0 ) –

    Wobbles per second, 0.1 to 40.

  • depth (float, default: 0.5 ) –

    0.0 (none) to 1.0 (silent at each trough).

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
@staticmethod
def tremolo(*, rate_hertz: float = 5.0, depth: float = 0.5) -> TerminalAudioEffect:
    """
    Amplitude wobble.

    Args:
        rate_hertz: Wobbles per second, 0.1 to 40.
        depth: 0.0 (none) to 1.0 (silent at each trough).
    """

__or__

__or__(other: AudioEffectSource) -> list[TerminalAudioEffect]

Chain this effect with another effect or sequence of effects.

Examples:

chain = TerminalAudioEffect.low_pass(cutoff=800.0) | TerminalAudioEffect.reverb(room=0.5)
assert [effect.name for effect in chain] == ["low_pass", "reverb"]
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def __or__(self, other: AudioEffectSource) -> list[TerminalAudioEffect]:
    """
    Chain this effect with another effect or sequence of effects.

    Examples:
        ```python
        chain = TerminalAudioEffect.low_pass(cutoff=800.0) | TerminalAudioEffect.reverb(room=0.5)
        assert [effect.name for effect in chain] == ["low_pass", "reverb"]
        ```
    """

TerminalAudioError

Bases: RuntimeError

Raised when an audio device or stream fails.

Treat this (and a failed import of the package) as "audio is not available": SSH sessions, containers and CI usually have no audio device.

TerminalAudioPlayer

TerminalAudioPlayer(*, beats_per_minute: float = 120.0)

Plays sounds on the default output device, with one musical clock.

Not thread-safe (unsendable): use it from the thread that created it.

Sounds can start immediately or on a clock beat. The clock runs at a tempo in beats per minute (120 by default), and a sound scheduled with at_beat starts sample-accurately when the clock reaches that beat, so sequences do not drift with Python's timing.

Attributes:

Examples:

player = TerminalAudioPlayer()
player.play_note("bell", "C6")  # a "ding", right now
player.start_clock()
for index, note in enumerate(["C4", "E4", "G4"]):
    player.play_note("piano", note, at_beat=int(player.beat) + 1 + index)

Open the default output device.

Parameters:

  • beats_per_minute (float, default: 120.0 ) –

    Initial clock tempo, greater than 0 and at most 600000.

Raises:

Methods:

  • is_available –

    Whether an output device exists, without opening it.

  • play_note –

    Render a built-in instrument note and play it.

  • play_sound –

    Play an already rendered sound, optionally on a clock beat.

  • start_clock –

    Start the clock.

  • pause_clock –

    Pause the clock, keeping its position.

  • stop_clock –

    Stop the clock and reset it to beat zero.

  • set_tempo –

    Change the clock tempo.

  • close –

    Close owned audio output; repeated calls are safe.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def __init__(self, *, beats_per_minute: float = 120.0) -> None:
    """
    Open the default output device.

    Args:
        beats_per_minute: Initial clock tempo, greater than 0 and at most
            600000.

    Raises:
        TerminalAudioUnavailableError: If no output device is available, or
            this build has no device support.
        ValueError: If ``beats_per_minute`` is out of range.
    """

is_clock_running property

is_clock_running: bool

Whether the clock is currently running.

tempo property

tempo: float

The clock tempo in beats per minute.

beat property

beat: float

The clock position in beats, including the fraction of the current beat.

Use it to keep animations in time with music.

clock_position_milliseconds property

clock_position_milliseconds: float

The clock position in milliseconds at the current tempo.

is_available staticmethod

is_available() -> bool

Whether an output device exists, without opening it.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
@staticmethod
def is_available() -> bool:
    """Whether an output device exists, without opening it."""

play_note

play_note(instrument: InstrumentName, note: NoteName | float | str | None = None, *, duration_milliseconds: int = 500, velocity: float = 0.8, effects: list[TerminalAudioEffect] | None = None, volume: float = 1.0, pan: float = 0.0, at_beat: int | None = None) -> None

Render a built-in instrument note and play it.

See render_note for instrument, note, duration_milliseconds, velocity and effects. Rendering takes a few milliseconds and releases the GIL.

Parameters:

  • volume (float, default: 1.0 ) –

    Playback gain, 0.0 to 1.0 (values outside are clamped; NaN and infinity raise ValueError).

  • pan (float, default: 0.0 ) –

    Stereo position from -1.0 (left) to 1.0 (right). The centre keeps full level; panning only quiets the far side.

  • at_beat (int | None, default: None ) –

    If set, start sample-accurately when the clock reaches this beat; otherwise start immediately.

Raises:

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def play_note(
    self,
    instrument: InstrumentName,
    note: NoteName | float | str | None = None,
    *,
    duration_milliseconds: int = 500,
    velocity: float = 0.8,
    effects: list[TerminalAudioEffect] | None = None,
    volume: float = 1.0,
    pan: float = 0.0,
    at_beat: int | None = None,
) -> None:
    """
    Render a built-in instrument note and play it.

    See [`render_note`][xnano_audio.render_note] for ``instrument``,
    ``note``, ``duration_milliseconds``, ``velocity`` and ``effects``.
    Rendering takes a few milliseconds and releases the GIL.

    Args:
        volume: Playback gain, 0.0 to 1.0 (values outside are clamped;
            NaN and infinity raise ``ValueError``).
        pan: Stereo position from -1.0 (left) to 1.0 (right). The centre
            keeps full level; panning only quiets the far side.
        at_beat: If set, start sample-accurately when the clock reaches
            this beat; otherwise start immediately.

    Raises:
        TerminalAudioError: If the mixer cannot accept the sound.
        ValueError: If an argument is out of range (including ``pan``).
    """

play_sound

play_sound(sound: TerminalSound, *, volume: float = 1.0, pan: float = 0.0, at_beat: int | None = None) -> None

Play an already rendered sound, optionally on a clock beat.

Render once and play many times to avoid re-rendering.

Parameters:

Raises:

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def play_sound(
    self,
    sound: TerminalSound,
    *,
    volume: float = 1.0,
    pan: float = 0.0,
    at_beat: int | None = None,
) -> None:
    """
    Play an already rendered sound, optionally on a clock beat.

    Render once and play many times to avoid re-rendering.

    Args:
        sound: From [`render_note`][xnano_audio.render_note] or
            [`TerminalSound.with_effects`][xnano_audio.TerminalSound.with_effects].
        volume: Playback gain, as in
            [`play_note`][xnano_audio.TerminalAudioPlayer.play_note].
        pan: Stereo position from -1.0 to 1.0.
        at_beat: As in
            [`play_note`][xnano_audio.TerminalAudioPlayer.play_note].

    Raises:
        TerminalAudioError: If the mixer cannot accept the sound.
        ValueError: If ``pan`` is outside -1.0 to 1.0.
    """

start_clock

start_clock() -> None

Start the clock.

Clock commands (start, pause, stop, tempo) are applied by the audio thread on its next buffer, so beat can lag a call by a few milliseconds.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def start_clock(self) -> None:
    """
    Start the clock.

    Clock commands (start, pause, stop, tempo) are applied by the audio
    thread on its next buffer, so [`beat`][xnano_audio.TerminalAudioPlayer.beat]
    can lag a call by a few milliseconds.
    """

pause_clock

pause_clock() -> None

Pause the clock, keeping its position.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def pause_clock(self) -> None:
    """Pause the clock, keeping its position."""

stop_clock

stop_clock() -> None

Stop the clock and reset it to beat zero.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def stop_clock(self) -> None:
    """Stop the clock and reset it to beat zero."""

set_tempo

set_tempo(beats_per_minute: float) -> None

Change the clock tempo.

Raises:

  • ValueError –

    If beats_per_minute is not greater than 0 and at most 600000.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def set_tempo(self, beats_per_minute: float) -> None:
    """
    Change the clock tempo.

    Raises:
        ValueError: If ``beats_per_minute`` is not greater than 0 and at
            most 600000.
    """

close

close() -> None

Close owned audio output; repeated calls are safe.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def close(self) -> None:
    """Close owned audio output; repeated calls are safe."""

TerminalAudioStream

Lazy decoded file source; no whole-file TerminalSound allocation is required.

Attributes:

Methods:

  • open –

    Open an audio file (WAV, FLAC, Ogg Vorbis, MP3 or MP4/AAC); decoding starts on the first read.

  • read –

    Decode up to frames frames; empty bytes mean the end of the file.

  • seek –

    Move to frame, counted in frames of this stream's format.

  • close –

    Release the file; repeated calls are safe.

format property

The layout of the bytes read returns.

is_closed property

is_closed: bool

Whether the stream has been closed.

open classmethod

open(path: str | PathLike[str], *, format: TerminalPCMFormat | None = None) -> TerminalAudioStream

Open an audio file (WAV, FLAC, Ogg Vorbis, MP3 or MP4/AAC); decoding starts on the first read.

Parameters:

  • path (str | PathLike[str]) –

    The audio file.

  • format (TerminalPCMFormat | None, default: None ) –

    The layout to decode into. Defaults to the file's own rate and channels as f32le.

Raises:

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
@classmethod
def open(
    cls, path: str | PathLike[str], *, format: TerminalPCMFormat | None = None
) -> TerminalAudioStream:
    """
    Open an audio file (WAV, FLAC, Ogg Vorbis, MP3 or MP4/AAC); decoding starts on the first read.

    Args:
        path: The audio file.
        format: The layout to decode into. Defaults to the file's own rate and channels as ``f32le``.

    Raises:
        TerminalAudioError: The file cannot be opened or decoded.
    """

read

read(frames: int) -> bytes

Decode up to frames frames; empty bytes mean the end of the file.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def read(self, frames: int) -> bytes:
    """Decode up to ``frames`` frames; empty bytes mean the end of the file."""

seek

seek(frame: int) -> None

Move to frame, counted in frames of this stream's format.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def seek(self, frame: int) -> None:
    """Move to ``frame``, counted in frames of this stream's ``format``."""

close

close() -> None

Release the file; repeated calls are safe.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def close(self) -> None:
    """Release the file; repeated calls are safe."""

TerminalAudioUnavailableError

Bases: TerminalAudioError

Raised when an audio device is missing, or this build has no device support.

The message says which of the two it is and how to fix it.

TerminalMelody

TerminalMelody(notes: list[TerminalNote] | None = None, *, tempo_map: TerminalTempoMap | None = None, time_signature: TerminalTimeSignature | None = None, length_beats: float = 0.0)

Notes on a beat timeline with a tempo map and a time signature.

Immutable: every method that changes the melody returns a new one. Notes can start together to form chords. Ask which notes are due between two readings of any clock with scheduled_between, or render the melody to a TerminalSound with render. len(melody) is the number of notes and iterating yields the notes in start order.

Attributes:

Examples:

melody = (
    TerminalMelody(tempo_map=TerminalTempoMap(100))
    .appended_note("C4")
    .appended_chord(["E4", "G4"], duration_beats=2)
    .appended_rest(1)
)
sound = melody.render("piano")
due = melody.scheduled_between(0.0, 1.0)

Create a melody.

Parameters:

  • notes (list[TerminalNote] | None, default: None ) –

    The notes, in any order.

  • tempo_map (TerminalTempoMap | None, default: None ) –

    Defaults to a constant 120 beats per minute.

  • time_signature (TerminalTimeSignature | None, default: None ) –

    Defaults to 4/4.

  • length_beats (float, default: 0.0 ) –

    A minimum length that adds trailing silence after the last note.

Raises:

  • ValueError –

    If there are more than 100000 notes or length_beats is negative.

Methods:

  • with_notes –

    Return the melody with notes added.

  • with_tempo_map –

    Return the melody with another tempo map.

  • with_time_signature –

    Return the melody with another time signature.

  • with_length_beats –

    Return the melody lasting at least length_beats, which adds trailing silence.

  • appended_note –

    Return the melody with one note appended at its end.

  • appended_chord –

    Return the melody with notes that sound together appended at its end.

  • appended_rest –

    Return the melody with silence appended at its end.

  • transposed –

    Return the melody with every pitch moved by semitones.

  • shifted –

    Return the melody moved later by beats, or earlier when negative.

  • stretched –

    Return the melody with every start, duration and tempo change multiplied by factor.

  • repeated –

    Return the melody played count times in a row.

  • followed_by –

    Return the melody followed by other, keeping this melody's tempo map.

  • layered_with –

    Return the notes of both melodies played together, keeping this tempo map.

  • __add__ –

    Play something after the melody, or raise it by semitones.

  • __sub__ –

    Lower every note by semitones.

  • __mul__ –

    Play the melody several times in a row.

  • __or__ –

    Play two melodies together.

  • __rshift__ –

    Start the melody later by beats.

  • __lshift__ –

    Start the melody earlier by beats.

  • notes_between_beats –

    Return the notes that start in [from_beats, to_beats).

  • notes_between_seconds –

    Return the notes that start in [from_seconds, to_seconds) of melody time.

  • scheduled_between –

    Return the notes due on an external clock between two readings.

  • render –

    Render the melody, or the notes starting inside a window, to a sound.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def __init__(
    self,
    notes: list[TerminalNote] | None = None,
    *,
    tempo_map: TerminalTempoMap | None = None,
    time_signature: TerminalTimeSignature | None = None,
    length_beats: float = 0.0,
) -> None:
    """
    Create a melody.

    Args:
        notes: The notes, in any order.
        tempo_map: Defaults to a constant 120 beats per minute.
        time_signature: Defaults to 4/4.
        length_beats: A minimum length that adds trailing silence after the last note.

    Raises:
        ValueError: If there are more than 100000 notes or ``length_beats`` is negative.
    """

notes property

The notes in start order.

tempo_map property

tempo_map: TerminalTempoMap

The tempo map.

time_signature property

time_signature: TerminalTimeSignature

The time signature.

length_beats property

length_beats: float

The end of the last note, or the explicit length when that is longer, in beats.

duration_seconds property

duration_seconds: float

The melody's length in seconds.

with_notes

with_notes(notes: list[TerminalNote]) -> TerminalMelody

Return the melody with notes added.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def with_notes(self, notes: list[TerminalNote]) -> TerminalMelody:
    """Return the melody with ``notes`` added."""

with_tempo_map

with_tempo_map(tempo_map: TerminalTempoMap) -> TerminalMelody

Return the melody with another tempo map.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def with_tempo_map(self, tempo_map: TerminalTempoMap) -> TerminalMelody:
    """Return the melody with another tempo map."""

with_time_signature

with_time_signature(time_signature: TerminalTimeSignature) -> TerminalMelody

Return the melody with another time signature.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def with_time_signature(
    self, time_signature: TerminalTimeSignature
) -> TerminalMelody:
    """Return the melody with another time signature."""

with_length_beats

with_length_beats(length_beats: float) -> TerminalMelody

Return the melody lasting at least length_beats, which adds trailing silence.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def with_length_beats(self, length_beats: float) -> TerminalMelody:
    """Return the melody lasting at least ``length_beats``, which adds trailing silence."""

appended_note

appended_note(pitch: Pitch, *, duration_beats: float = 1.0, velocity: float = 0.8) -> TerminalMelody

Return the melody with one note appended at its end.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def appended_note(
    self, pitch: Pitch, *, duration_beats: float = 1.0, velocity: float = 0.8
) -> TerminalMelody:
    """Return the melody with one note appended at its end."""

appended_chord

appended_chord(pitches: Sequence[Pitch], *, duration_beats: float = 1.0, velocity: float = 0.8) -> TerminalMelody

Return the melody with notes that sound together appended at its end.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def appended_chord(
    self,
    pitches: Sequence[Pitch],
    *,
    duration_beats: float = 1.0,
    velocity: float = 0.8,
) -> TerminalMelody:
    """Return the melody with notes that sound together appended at its end."""

appended_rest

appended_rest(duration_beats: float = 1.0) -> TerminalMelody

Return the melody with silence appended at its end.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def appended_rest(self, duration_beats: float = 1.0) -> TerminalMelody:
    """Return the melody with silence appended at its end."""

transposed

transposed(semitones: int) -> TerminalMelody

Return the melody with every pitch moved by semitones.

Raises:

  • ValueError –

    If a pitch would leave the MIDI range 0 to 127.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def transposed(self, semitones: int) -> TerminalMelody:
    """
    Return the melody with every pitch moved by ``semitones``.

    Raises:
        ValueError: If a pitch would leave the MIDI range 0 to 127.
    """

shifted

shifted(beats: float) -> TerminalMelody

Return the melody moved later by beats, or earlier when negative.

Raises:

  • ValueError –

    If a note would start before beat zero.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def shifted(self, beats: float) -> TerminalMelody:
    """
    Return the melody moved later by ``beats``, or earlier when negative.

    Raises:
        ValueError: If a note would start before beat zero.
    """

stretched

stretched(factor: float) -> TerminalMelody

Return the melody with every start, duration and tempo change multiplied by factor.

The tempo values are unchanged, so 2.0 makes the melody twice as long in time.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def stretched(self, factor: float) -> TerminalMelody:
    """
    Return the melody with every start, duration and tempo change multiplied by ``factor``.

    The tempo values are unchanged, so 2.0 makes the melody twice as long in time.
    """

repeated

repeated(count: int) -> TerminalMelody

Return the melody played count times in a row.

Raises:

  • ValueError –

    If count is zero or the result would hold too many notes.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def repeated(self, count: int) -> TerminalMelody:
    """
    Return the melody played ``count`` times in a row.

    Raises:
        ValueError: If ``count`` is zero or the result would hold too many notes.
    """

followed_by

followed_by(other: TerminalMelody) -> TerminalMelody

Return the melody followed by other, keeping this melody's tempo map.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def followed_by(self, other: TerminalMelody) -> TerminalMelody:
    """Return the melody followed by ``other``, keeping this melody's tempo map."""

layered_with

layered_with(other: TerminalMelody) -> TerminalMelody

Return the notes of both melodies played together, keeping this tempo map.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def layered_with(self, other: TerminalMelody) -> TerminalMelody:
    """Return the notes of both melodies played together, keeping this tempo map."""

__add__

__add__(other: int) -> TerminalMelody

Play something after the melody, or raise it by semitones.

Examples:

phrase = TerminalNote("C4") + TerminalNote("E4")
assert len((phrase + TerminalNote("G4")).notes) == 3
assert [note.name for note in phrase + 2] == ["D4", "F#4"]
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def __add__(self, other: int | TerminalMelody | TerminalNote) -> TerminalMelody:
    """
    Play something after the melody, or raise it by semitones.

    Examples:
        ```python
        phrase = TerminalNote("C4") + TerminalNote("E4")
        assert len((phrase + TerminalNote("G4")).notes) == 3
        assert [note.name for note in phrase + 2] == ["D4", "F#4"]
        ```
    """

__sub__

__sub__(other: int) -> TerminalMelody

Lower every note by semitones.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def __sub__(self, other: int) -> TerminalMelody:
    """Lower every note by semitones."""

__mul__

__mul__(other: int) -> TerminalMelody

Play the melody several times in a row.

Examples:

phrase = TerminalNote("C4") + TerminalNote("E4")
assert len((phrase * 3).notes) == 6
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def __mul__(self, other: int) -> TerminalMelody:
    """
    Play the melody several times in a row.

    Examples:
        ```python
        phrase = TerminalNote("C4") + TerminalNote("E4")
        assert len((phrase * 3).notes) == 6
        ```
    """

__or__

Play two melodies together.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def __or__(self, other: TerminalMelody | TerminalNote) -> TerminalMelody:
    """Play two melodies together."""

__rshift__

__rshift__(other: float) -> TerminalMelody

Start the melody later by beats.

Examples:

phrase = TerminalNote("C4") + TerminalNote("E4")
assert [note.start_beats for note in phrase >> 2] == [2.0, 3.0]
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def __rshift__(self, other: float) -> TerminalMelody:
    """
    Start the melody later by beats.

    Examples:
        ```python
        phrase = TerminalNote("C4") + TerminalNote("E4")
        assert [note.start_beats for note in phrase >> 2] == [2.0, 3.0]
        ```
    """

__lshift__

__lshift__(other: float) -> TerminalMelody

Start the melody earlier by beats.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def __lshift__(self, other: float) -> TerminalMelody:
    """Start the melody earlier by beats."""

notes_between_beats

notes_between_beats(from_beats: float, to_beats: float) -> list[TerminalNote]

Return the notes that start in [from_beats, to_beats).

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def notes_between_beats(
    self, from_beats: float, to_beats: float
) -> list[TerminalNote]:
    """Return the notes that start in ``[from_beats, to_beats)``."""

notes_between_seconds

notes_between_seconds(from_seconds: float, to_seconds: float) -> list[TerminalNote]

Return the notes that start in [from_seconds, to_seconds) of melody time.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def notes_between_seconds(
    self, from_seconds: float, to_seconds: float
) -> list[TerminalNote]:
    """Return the notes that start in ``[from_seconds, to_seconds)`` of melody time."""

scheduled_between

scheduled_between(from_elapsed_seconds: float, to_elapsed_seconds: float, *, start_seconds: float = 0.0, should_loop: bool = False) -> list[TerminalScheduledNote]

Return the notes due on an external clock between two readings.

The melody begins when the clock reads start_seconds. Notes that start in [from_elapsed_seconds, to_elapsed_seconds) are returned with their times on that same clock, so consecutive windows never repeat or skip a note. Call it every frame with the previous and current reading of a manual or monotonic clock.

Parameters:

  • from_elapsed_seconds (float) –

    The previous clock reading (inclusive).

  • to_elapsed_seconds (float) –

    The current clock reading (exclusive).

  • start_seconds (float, default: 0.0 ) –

    The clock reading at which the melody begins.

  • should_loop (bool, default: False ) –

    Restart the melody every time it reaches its length.

Raises:

  • ValueError –

    If a reading is not finite or to_elapsed_seconds is earlier than from_elapsed_seconds.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def scheduled_between(
    self,
    from_elapsed_seconds: float,
    to_elapsed_seconds: float,
    *,
    start_seconds: float = 0.0,
    should_loop: bool = False,
) -> list[TerminalScheduledNote]:
    """
    Return the notes due on an external clock between two readings.

    The melody begins when the clock reads ``start_seconds``. Notes that start in
    ``[from_elapsed_seconds, to_elapsed_seconds)`` are returned with their times on
    that same clock, so consecutive windows never repeat or skip a note. Call it every
    frame with the previous and current reading of a manual or monotonic clock.

    Args:
        from_elapsed_seconds: The previous clock reading (inclusive).
        to_elapsed_seconds: The current clock reading (exclusive).
        start_seconds: The clock reading at which the melody begins.
        should_loop: Restart the melody every time it reaches its length.

    Raises:
        ValueError: If a reading is not finite or ``to_elapsed_seconds`` is earlier than
            ``from_elapsed_seconds``.
    """

render

render(instrument: InstrumentName = 'piano', *, sample_rate: int = 44100, effects: list[TerminalAudioEffect] | None = None, from_seconds: float = 0.0, to_seconds: float | None = None) -> TerminalSound

Render the melody, or the notes starting inside a window, to a sound.

Notes are placed sample-accurately from the tempo map and mixed; the result is limited to stay below clipping. The first sample is the instant from_seconds. Notes ring past the window with their natural tail.

Parameters:

  • instrument (InstrumentName, default: 'piano' ) –
  • sample_rate (int, default: 44100 ) –

    Output rate in hertz, 8000 to 192000.

  • effects (list[TerminalAudioEffect] | None, default: None ) –

    Effects applied in order to the mixed result (at most 32).

  • from_seconds (float, default: 0.0 ) –

    Melody time of the first sample.

  • to_seconds (float | None, default: None ) –

    Melody time at which the window ends; defaults to the melody's end.

Raises:

  • ValueError –

    If the instrument is unknown, an argument is out of range or the window is longer than 600 seconds.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def render(
    self,
    instrument: InstrumentName = 'piano',
    *,
    sample_rate: int = 44100,
    effects: list[TerminalAudioEffect] | None = None,
    from_seconds: float = 0.0,
    to_seconds: float | None = None,
) -> TerminalSound:
    """
    Render the melody, or the notes starting inside a window, to a sound.

    Notes are placed sample-accurately from the tempo map and mixed; the result is
    limited to stay below clipping. The first sample is the instant ``from_seconds``.
    Notes ring past the window with their natural tail.

    Args:
        instrument: One of [`instrument_names`][xnano_audio.instrument_names].
        sample_rate: Output rate in hertz, 8000 to 192000.
        effects: Effects applied in order to the mixed result (at most 32).
        from_seconds: Melody time of the first sample.
        to_seconds: Melody time at which the window ends; defaults to the melody's end.

    Raises:
        ValueError: If the instrument is unknown, an argument is out of range or the
            window is longer than 600 seconds.
    """

TerminalMicrophone

TerminalMicrophone()

Microphone capture that tracks the input level.

Capture runs on the audio thread; Python only reads levels, so the GIL is never involved. Not thread-safe (unsendable). Usable as a context manager, which closes the device on exit.

On macOS the terminal application needs microphone permission; without it the stream opens but delivers silence (a level of exactly 0.0).

Attributes:

  • level (float) –

    Root-mean-square level of the most recent audio buffer, from 0.0 to 1.0.

  • peak_level (float) –

    The highest level since opening or the last reset_peak_level.

  • last_error (str | None) –

    The most recent stream error reported by the device, if any.

  • sample_rate (int) –

    The device's sample rate in hertz.

  • channels (int) –

    The device's channel count.

  • is_open (bool) –

    Whether the stream is still capturing.

Examples:

with TerminalMicrophone() as microphone:
    levels = microphone.level_history(60)  # ready for a sparkline

Open the default input device and start capturing.

Raises:

Methods:

  • is_available –

    Whether an input device exists, without opening a stream.

  • reset_peak_level –

    Forget the peak level, so peak_level tracks from now on.

  • level_history –

    The most recent count levels, oldest first, one per audio buffer.

  • close –

    Stop capturing and release the device. Safe to call repeatedly.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def __init__(self) -> None:
    """
    Open the default input device and start capturing.

    Raises:
        TerminalAudioUnavailableError: If there is no input device, the sample
            format is unsupported, or this build has no device support.
        TerminalAudioError: If the stream cannot be opened.
    """

level property

level: float

Root-mean-square level of the most recent audio buffer, from 0.0 to 1.0.

peak_level property

peak_level: float

The highest level since opening or the last reset_peak_level.

last_error property

last_error: str | None

The most recent stream error reported by the device, if any.

sample_rate property

sample_rate: int

The device's sample rate in hertz.

channels property

channels: int

The device's channel count.

is_open property

is_open: bool

Whether the stream is still capturing.

is_available staticmethod

is_available() -> bool

Whether an input device exists, without opening a stream.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
@staticmethod
def is_available() -> bool:
    """Whether an input device exists, without opening a stream."""

reset_peak_level

reset_peak_level() -> None

Forget the peak level, so peak_level tracks from now on.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def reset_peak_level(self) -> None:
    """Forget the peak level, so ``peak_level`` tracks from now on."""

level_history

level_history(count: int) -> list[float]

The most recent count levels, oldest first, one per audio buffer.

At most 4096 levels are kept.

Raises:

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def level_history(self, count: int) -> list[float]:
    """
    The most recent ``count`` levels, oldest first, one per audio buffer.

    At most 4096 levels are kept.

    Raises:
        ValueError: If ``count`` is greater than 4096.
    """

close

close() -> None

Stop capturing and release the device. Safe to call repeatedly.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def close(self) -> None:
    """Stop capturing and release the device. Safe to call repeatedly."""

TerminalNote

TerminalNote(pitch: Pitch, *, start_beats: float = 0.0, duration_beats: float = 1.0, velocity: float = 0.8)

One note on a beat timeline.

Immutable. Pitches are MIDI numbers (60 is middle C); a name such as "C#4" is accepted wherever a pitch is. Times are measured in quarter-note beats from the start of the melody. Every with_* method returns a new note.

Attributes:

Examples:

note = TerminalNote("C4", start_beats=1.0, duration_beats=0.5)
assert note.pitch == 60 and note.end_beats == 1.5

Create a note.

Parameters:

  • pitch (Pitch) –

    A NoteName such as "C4" or "Db5", or a MIDI number from 0 to 127. Lower case and a missing octave ("c#" is "C#4") are accepted too.

  • start_beats (float, default: 0.0 ) –

    Where the note starts, zero or more.

  • duration_beats (float, default: 1.0 ) –

    How long the key is held, greater than zero.

  • velocity (float, default: 0.8 ) –

    Strength from 0.0 to 1.0.

Raises:

  • ValueError –

    If an argument is out of range or the pitch name is not recognized.

  • TypeError –

    If pitch is not an int or str.

Methods:

  • with_pitch –

    Return the note with another pitch.

  • with_start_beats –

    Return the note starting at another beat.

  • with_duration_beats –

    Return the note held for another number of beats.

  • with_velocity –

    Return the note with another strength.

  • transposed –

    Return the note moved by semitones.

  • __add__ –

    Move the note up by semitones, or play something after it.

  • __sub__ –

    Move the note down by semitones, or measure the distance to another note.

  • __or__ –

    Play notes together, as a chord.

  • __int__ –

    The MIDI pitch, so int(TerminalNote("C4")) is 60.

  • __lt__ –

    Notes are ordered by pitch, then by where they start.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def __init__(
    self,
    pitch: Pitch,
    *,
    start_beats: float = 0.0,
    duration_beats: float = 1.0,
    velocity: float = 0.8,
) -> None:
    """
    Create a note.

    Args:
        pitch: A [`NoteName`][xnano_audio.NoteName] such as ``"C4"`` or ``"Db5"``, or a
            MIDI number from 0 to 127. Lower case and a missing octave (``"c#"`` is
            ``"C#4"``) are accepted too.
        start_beats: Where the note starts, zero or more.
        duration_beats: How long the key is held, greater than zero.
        velocity: Strength from 0.0 to 1.0.

    Raises:
        ValueError: If an argument is out of range or the pitch name is not recognized.
        TypeError: If ``pitch`` is not an ``int`` or ``str``.
    """

pitch property

pitch: int

The MIDI pitch, 0 to 127.

name property

name: NoteName

The sharp-spelled note name, such as "C#4".

frequency_hertz property

frequency_hertz: float

The pitch in hertz.

velocity property

velocity: float

The strength, 0.0 to 1.0.

start_beats property

start_beats: float

Where the note starts, in beats.

duration_beats property

duration_beats: float

How long the key is held, in beats.

end_beats property

end_beats: float

Where the note ends, in beats.

with_pitch

with_pitch(pitch: Pitch) -> TerminalNote

Return the note with another pitch.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def with_pitch(self, pitch: Pitch) -> TerminalNote:
    """Return the note with another pitch."""

with_start_beats

with_start_beats(start_beats: float) -> TerminalNote

Return the note starting at another beat.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def with_start_beats(self, start_beats: float) -> TerminalNote:
    """Return the note starting at another beat."""

with_duration_beats

with_duration_beats(duration_beats: float) -> TerminalNote

Return the note held for another number of beats.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def with_duration_beats(self, duration_beats: float) -> TerminalNote:
    """Return the note held for another number of beats."""

with_velocity

with_velocity(velocity: float) -> TerminalNote

Return the note with another strength.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def with_velocity(self, velocity: float) -> TerminalNote:
    """Return the note with another strength."""

transposed

transposed(semitones: int) -> TerminalNote

Return the note moved by semitones.

Raises:

  • ValueError –

    If the result leaves the MIDI range 0 to 127.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def transposed(self, semitones: int) -> TerminalNote:
    """
    Return the note moved by ``semitones``.

    Raises:
        ValueError: If the result leaves the MIDI range 0 to 127.
    """

__add__

__add__(other: int) -> TerminalNote

Move the note up by semitones, or play something after it.

note + 1 is the note one semitone higher, note + 12 an octave higher. Adding a note or a melody makes a TerminalMelody that plays them one after the other.

Raises:

  • ValueError –

    If the result leaves the MIDI range 0 to 127.

Examples:

assert TerminalNote("C4") + 1 == TerminalNote("C#4")
assert 12 + TerminalNote("C4") == TerminalNote("C5")
melody = TerminalNote("C4") + TerminalNote("E4")
assert [note.name for note in melody] == ["C4", "E4"]
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def __add__(
    self, other: int | TerminalNote | TerminalMelody
) -> TerminalNote | TerminalMelody:
    """
    Move the note up by semitones, or play something after it.

    ``note + 1`` is the note one semitone higher, ``note + 12`` an octave higher.
    Adding a note or a melody makes a [`TerminalMelody`][xnano_audio.TerminalMelody]
    that plays them one after the other.

    Raises:
        ValueError: If the result leaves the MIDI range 0 to 127.

    Examples:
        ```python
        assert TerminalNote("C4") + 1 == TerminalNote("C#4")
        assert 12 + TerminalNote("C4") == TerminalNote("C5")
        melody = TerminalNote("C4") + TerminalNote("E4")
        assert [note.name for note in melody] == ["C4", "E4"]
        ```
    """

__sub__

__sub__(other: int) -> TerminalNote
__sub__(other: TerminalNote) -> int
__sub__(other: int | TerminalNote) -> TerminalNote | int

Move the note down by semitones, or measure the distance to another note.

Examples:

assert TerminalNote("C4") - 1 == TerminalNote("B3")
assert TerminalNote("E4") - TerminalNote("C4") == 4
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def __sub__(self, other: int | TerminalNote) -> TerminalNote | int:
    """
    Move the note down by semitones, or measure the distance to another note.

    Examples:
        ```python
        assert TerminalNote("C4") - 1 == TerminalNote("B3")
        assert TerminalNote("E4") - TerminalNote("C4") == 4
        ```
    """

__or__

Play notes together, as a chord.

Examples:

chord = TerminalNote("C4") | TerminalNote("E4") | TerminalNote("G4")
assert {note.start_beats for note in chord} == {0.0}
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def __or__(self, other: TerminalNote | TerminalMelody) -> TerminalMelody:
    """
    Play notes together, as a chord.

    Examples:
        ```python
        chord = TerminalNote("C4") | TerminalNote("E4") | TerminalNote("G4")
        assert {note.start_beats for note in chord} == {0.0}
        ```
    """

__int__

__int__() -> int

The MIDI pitch, so int(TerminalNote("C4")) is 60.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def __int__(self) -> int:
    """The MIDI pitch, so ``int(TerminalNote("C4"))`` is ``60``."""

__lt__

__lt__(other: TerminalNote) -> bool

Notes are ordered by pitch, then by where they start.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def __lt__(self, other: TerminalNote) -> bool:
    """Notes are ordered by pitch, then by where they start."""

TerminalPCMFormat

TerminalPCMFormat(*, sample_rate: int = 44100, channels: int = 2, sample_type: Literal['f32le', 's16le'] = 'f32le')

Explicit PCM sample representation; audio bytes are not cell-grid bytes.

Attributes:

Create a TerminalPCMFormat.

Parameters:

  • sample_rate (int, default: 44100 ) –

    int. Defaults to 44100.

  • channels (int, default: 2 ) –

    int. Defaults to 2.

  • sample_type (Literal['f32le', 's16le'], default: 'f32le' ) –

    Literal['f32le', 's16le']. Defaults to 'f32le'.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def __init__(
    self,
    *,
    sample_rate: int = 44100,
    channels: int = 2,
    sample_type: Literal['f32le', 's16le'] = 'f32le',
) -> None:
    """
    Create a TerminalPCMFormat.

    Args:
        sample_rate: ``int``. Defaults to ``44100``.
        channels: ``int``. Defaults to ``2``.
        sample_type: ``Literal['f32le', 's16le']``. Defaults to ``'f32le'``.
    """

sample_rate property

sample_rate: int

Frames per second.

channels property

channels: int

Samples per frame: 1 (mono) or 2 (stereo).

sample_type property

sample_type: Literal['f32le', 's16le']

How each sample is stored: 'f32le' or 's16le'.

bytes_per_frame property

bytes_per_frame: int

Bytes in one interleaved frame.

TerminalPlayback

One independently controlled voice or stream; setters are owner-thread commands.

Attributes:

  • is_playing (bool) –

    Whether the voice is producing sound (not finished and not paused).

  • is_paused (bool) –

    Whether the voice is paused.

  • volume (float) –

    Playback gain from 0.0 to 1.0; assigning it ramps over a few milliseconds.

  • pan (float) –

    Stereo position from -1.0 (left) to 1.0 (right).

  • position_frames (int) –

    Frames of this voice played so far.

  • buffered_frames (int) –

    Frames decoded ahead and waiting to play.

  • underruns (int) –

    How many times the buffer ran dry before the source ended.

  • last_error (str | None) –

    The message of the last source failure, or None.

Methods:

  • pause –

    Pause the voice, keeping its position.

  • resume –

    Resume a paused voice.

  • fade_to –

    Ramp the volume to volume over seconds.

  • stop –

    Stop the voice, optionally fading out first.

is_playing property

is_playing: bool

Whether the voice is producing sound (not finished and not paused).

is_paused property

is_paused: bool

Whether the voice is paused.

volume property writable

volume: float

Playback gain from 0.0 to 1.0; assigning it ramps over a few milliseconds.

pan property writable

pan: float

Stereo position from -1.0 (left) to 1.0 (right).

position_frames property

position_frames: int

Frames of this voice played so far.

buffered_frames property

buffered_frames: int

Frames decoded ahead and waiting to play.

underruns property

underruns: int

How many times the buffer ran dry before the source ended.

last_error property

last_error: str | None

The message of the last source failure, or None.

pause

pause() -> None

Pause the voice, keeping its position.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def pause(self) -> None:
    """Pause the voice, keeping its position."""

resume

resume() -> None

Resume a paused voice.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def resume(self) -> None:
    """Resume a paused voice."""

fade_to

fade_to(volume: float, *, seconds: float) -> None

Ramp the volume to volume over seconds.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def fade_to(self, volume: float, *, seconds: float) -> None:
    """Ramp the volume to ``volume`` over ``seconds``."""

stop

stop(*, fade_seconds: float = 0.0) -> None

Stop the voice, optionally fading out first.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def stop(self, *, fade_seconds: float = 0.0) -> None:
    """Stop the voice, optionally fading out first."""

TerminalScheduledNote

A note placed on an external clock.

Returned by TerminalMelody.scheduled_between. Its times are seconds on the clock that was passed in, such as the terminal runtime's clock.

Attributes:

note property

The note, in melody beats.

start_seconds property

start_seconds: float

When the note starts, in seconds on the external clock.

duration_seconds property

duration_seconds: float

How long the key is held, in seconds.

end_seconds property

end_seconds: float

When the key is released, in seconds on the external clock.

iteration property

iteration: int

How many times the melody had looped before this note, starting at zero.

TerminalSound

A rendered mono sound: raw samples plus their sample rate.

Immutable. Create one with render_note, then inspect it, run it through more effects, or play it with TerminalAudioPlayer.play_sound. len(sound) is the number of samples.

Attributes:

Methods:

  • from_samples –

    Build a sound from your own mono floats.

  • from_bytes –

    Build a sound from little-endian 32-bit float bytes.

  • to_bytes –

    The samples as little-endian 32-bit floats.

  • samples –

    The samples as a list of floats.

  • envelope –

    The loudness outline of the sound: points peak levels from 0.0 to 1.0.

  • with_effects –

    A new sound with effects applied in order.

  • mix –

    Mix sounds placed on a timeline into one sound.

  • add –

    Apply one or more audio effects (or effect chains) in order and return the new sound.

  • __or__ –

    Apply an audio effect or effect chain to this sound and return the new sound.

sample_rate property

sample_rate: int

Samples per second.

duration_milliseconds property

duration_milliseconds: float

Length in milliseconds, including any effect tail.

peak property

peak: float

Largest absolute sample value, from 0.0 to 1.0.

root_mean_square property

root_mean_square: float

Root-mean-square level, from 0.0 to 1.0.

from_samples staticmethod

from_samples(samples: list[float], *, sample_rate: int = 44100) -> TerminalSound

Build a sound from your own mono floats.

This is how to add a custom instrument or waveform: generate the samples in Python (from numpy, call .tolist()), then process and play them like any built-in sound.

Parameters:

  • samples (list[float]) –

    Values from -1.0 to 1.0; they are not rescaled.

  • sample_rate (int, default: 44100 ) –

    Samples per second, 8000 to 192000.

Raises:

  • ValueError –

    If samples is empty or contains NaN or infinity, or the rate is out of range.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
@staticmethod
def from_samples(samples: list[float], *, sample_rate: int = 44100) -> TerminalSound:
    """
    Build a sound from your own mono floats.

    This is how to add a custom instrument or waveform: generate the
    samples in Python (from numpy, call ``.tolist()``), then process and
    play them like any built-in sound.

    Args:
        samples: Values from -1.0 to 1.0; they are not rescaled.
        sample_rate: Samples per second, 8000 to 192000.

    Raises:
        ValueError: If ``samples`` is empty or contains NaN or infinity,
            or the rate is out of range.
    """

from_bytes staticmethod

from_bytes(data: bytes, *, sample_rate: int = 44100) -> TerminalSound

Build a sound from little-endian 32-bit float bytes.

The inverse of to_bytes.

Raises:

  • ValueError –

    If data is empty or its length is not a multiple of 4, it contains NaN or infinity, or the rate is out of range.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
@staticmethod
def from_bytes(data: bytes, *, sample_rate: int = 44100) -> TerminalSound:
    """
    Build a sound from little-endian 32-bit float bytes.

    The inverse of [`to_bytes`][xnano_audio.TerminalSound.to_bytes].

    Raises:
        ValueError: If ``data`` is empty or its length is not a multiple
            of 4, it contains NaN or infinity, or the rate is out of range.
    """

to_bytes

to_bytes() -> bytes

The samples as little-endian 32-bit floats.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def to_bytes(self) -> bytes:
    """The samples as little-endian 32-bit floats."""

samples

samples() -> list[float]

The samples as a list of floats.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def samples(self) -> list[float]:
    """The samples as a list of floats."""

envelope

envelope(points: int) -> list[float]

The loudness outline of the sound: points peak levels from 0.0 to 1.0.

Each value is the largest absolute sample in one equal slice of the sound, ready to draw as a waveform or a sparkline.

Raises:

  • ValueError –

    If points is not between 1 and 100000.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def envelope(self, points: int) -> list[float]:
    """
    The loudness outline of the sound: ``points`` peak levels from 0.0 to 1.0.

    Each value is the largest absolute sample in one equal slice of the
    sound, ready to draw as a waveform or a sparkline.

    Raises:
        ValueError: If ``points`` is not between 1 and 100000.
    """

with_effects

with_effects(effects: list[TerminalAudioEffect]) -> TerminalSound

A new sound with effects applied in order.

Echo and reverb tails make the result longer than the original. The peak is kept below 0.98 and the added tail is capped at 30 seconds. The GIL is released while processing.

Raises:

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def with_effects(self, effects: list[TerminalAudioEffect]) -> TerminalSound:
    """
    A new sound with ``effects`` applied in order.

    Echo and reverb tails make the result longer than the original. The
    peak is kept below 0.98 and the added tail is capped at 30 seconds.
    The GIL is released while processing.

    Raises:
        ValueError: If more than 32 effects are given.
        TypeError: If an item is not a
            [`TerminalAudioEffect`][xnano_audio.TerminalAudioEffect].
    """

mix staticmethod

mix(layers: Sequence[tuple[TerminalSound, float, float]], *, duration_seconds: float, start_seconds: float = 0.0, sample_rate: int = 44100) -> TerminalSound

Mix sounds placed on a timeline into one sound.

The result is the part of the timeline from start_seconds for duration_seconds. Each layer is added at its offset, multiplied by its gain, and the sum is limited to -1.0 to 1.0. A layer that starts before the window is cut at the window's start, and one that runs past its end is cut there. The GIL is released while mixing.

Parameters:

  • layers (Sequence[tuple[TerminalSound, float, float]]) –

    (sound, offset_seconds, gain) tuples. offset_seconds is where on the timeline the sound begins (it may be negative or before start_seconds) and gain is a factor of 0.0 or more.

  • duration_seconds (float) –

    The length of the mixed sound, above 0 and at most 3600.

  • start_seconds (float, default: 0.0 ) –

    The time on the timeline where the mixed sound begins.

  • sample_rate (int, default: 44100 ) –

    Samples per second, 8000 to 192000. Every layer must have been rendered at this rate.

Raises:

  • ValueError –

    If duration_seconds or sample_rate is out of range, the window holds more than 172800000 samples or cannot be allocated, a layer does not have exactly three values, an offset is not finite, a gain is not a finite number from 0.0 to 1000.0, or a layer was rendered at a different sample rate.

  • TypeError –

    If layers is not a sequence of (sound, offset_seconds, gain) tuples, or an offset or gain is not a number.

Examples:

beat = render_note("kick", "C2")
note = render_note("sine", "A4", duration_milliseconds=500)
window = TerminalSound.mix(
    [(beat, 0.0, 1.0), (note, 0.5, 0.5)],
    duration_seconds=1.0,
)
assert window.duration_milliseconds == 1000.0
assert window.peak <= 1.0
quiet = TerminalSound.mix([(note, 0.0, 0.0)], duration_seconds=0.25)
assert quiet.peak == 0.0
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
@staticmethod
def mix(
    layers: Sequence[tuple[TerminalSound, float, float]],
    *,
    duration_seconds: float,
    start_seconds: float = 0.0,
    sample_rate: int = 44100,
) -> TerminalSound:
    """
    Mix sounds placed on a timeline into one sound.

    The result is the part of the timeline from ``start_seconds`` for
    ``duration_seconds``. Each layer is added at its offset, multiplied by its gain, and
    the sum is limited to -1.0 to 1.0. A layer that starts before the window is cut at
    the window's start, and one that runs past its end is cut there. The GIL is released
    while mixing.

    Args:
        layers: ``(sound, offset_seconds, gain)`` tuples. ``offset_seconds`` is where on the
            timeline the sound begins (it may be negative or before ``start_seconds``) and
            ``gain`` is a factor of 0.0 or more.
        duration_seconds: The length of the mixed sound, above 0 and at most 3600.
        start_seconds: The time on the timeline where the mixed sound begins.
        sample_rate: Samples per second, 8000 to 192000. Every layer must have been
            rendered at this rate.

    Raises:
        ValueError: If ``duration_seconds`` or ``sample_rate`` is out of range, the window
            holds more than 172800000 samples or cannot be allocated, a layer does not have
            exactly three values, an offset is not finite, a gain is not a finite number from
            0.0 to 1000.0, or a layer was rendered at a different sample rate.
        TypeError: If ``layers`` is not a sequence of ``(sound, offset_seconds, gain)``
            tuples, or an offset or gain is not a number.

    Examples:
        ```python
        beat = render_note("kick", "C2")
        note = render_note("sine", "A4", duration_milliseconds=500)
        window = TerminalSound.mix(
            [(beat, 0.0, 1.0), (note, 0.5, 0.5)],
            duration_seconds=1.0,
        )
        assert window.duration_milliseconds == 1000.0
        assert window.peak <= 1.0
        quiet = TerminalSound.mix([(note, 0.0, 0.0)], duration_seconds=0.25)
        assert quiet.peak == 0.0
        ```
    """

add

add(*effects: AudioEffectSource) -> TerminalSound

Apply one or more audio effects (or effect chains) in order and return the new sound.

Examples:

sound = render_note("piano", "C4", duration_milliseconds=100).add(
    TerminalAudioEffect.low_pass(cutoff=900.0)
)
assert len(sound) > 0
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def add(self, *effects: AudioEffectSource) -> TerminalSound:
    """
    Apply one or more audio effects (or effect chains) in order and return the new sound.

    Examples:
        ```python
        sound = render_note("piano", "C4", duration_milliseconds=100).add(
            TerminalAudioEffect.low_pass(cutoff=900.0)
        )
        assert len(sound) > 0
        ```
    """

__or__

__or__(other: AudioEffectSource) -> TerminalSound

Apply an audio effect or effect chain to this sound and return the new sound.

Examples:

sound = (
    render_note("piano", "C4", duration_milliseconds=100)
    | TerminalAudioEffect.low_pass(cutoff=900.0)
    | TerminalAudioEffect.reverb(room=0.4)
)
assert len(sound) > 0
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def __or__(self, other: AudioEffectSource) -> TerminalSound:
    """
    Apply an audio effect or effect chain to this sound and return the new sound.

    Examples:
        ```python
        sound = (
            render_note("piano", "C4", duration_milliseconds=100)
            | TerminalAudioEffect.low_pass(cutoff=900.0)
            | TerminalAudioEffect.reverb(room=0.4)
        )
        assert len(sound) > 0
        ```
    """

TerminalStreamingAudioPlayer

TerminalStreamingAudioPlayer(*, format: TerminalPCMFormat | None = None, lookahead_milliseconds: int = 300, backend: Literal['default', 'null'] = 'default')

Device owner for buffered streams and sample-count timing.

Construct on its owner thread. A producer worker reads/decodes sources ahead; the device callback consumes native buffered samples only. Underruns emit silence and increment counters. Source failure affects its playback handle rather than the terminal runtime. A failed start closes any resources opened by that start operation.

Attributes:

Create a TerminalStreamingAudioPlayer.

Parameters:

  • format (TerminalPCMFormat | None, default: None ) –

    TerminalPCMFormat | None. Defaults to None.

  • lookahead_milliseconds (int, default: 300 ) –

    int. Defaults to 300.

  • backend (Literal['default', 'null'], default: 'default' ) –

    Literal['default', 'null']. Defaults to 'default'.

Methods:

  • play_stream –

    Play a stream or any object with format, read(frames) and close().

  • play_sound –

    Play a rendered TerminalSound as an independent voice.

  • crossfade –

    Fade outgoing out and incoming in over seconds.

  • advance –

    Deterministic null-backend consumption only; rejects live-device advancement.

  • close –

    Idempotent bounded shutdown; stop the device before releasing its callback storage.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def __init__(
    self,
    *,
    format: TerminalPCMFormat | None = None,
    lookahead_milliseconds: int = 300,
    backend: Literal['default', 'null'] = 'default',
) -> None:
    """
    Create a TerminalStreamingAudioPlayer.

    Args:
        format: ``TerminalPCMFormat | None``. Defaults to ``None``.
        lookahead_milliseconds: ``int``. Defaults to ``300``.
        backend: ``Literal['default', 'null']``. Defaults to ``'default'``.
    """

format property

The layout of audio this player mixes.

is_closed property

is_closed: bool

Whether the player has been closed.

sample_position property

sample_position: int

Output frames consumed; render-ahead frames do not advance this counter.

play_stream

play_stream(source: TerminalAudioSource, *, volume: float = 1.0, pan: float = 0.0, should_close_source: bool = True) -> TerminalPlayback

Play a stream or any object with format, read(frames) and close().

Parameters:

  • source (TerminalAudioSource) –

    The audio source.

  • volume (float, default: 1.0 ) –

    Gain from 0.0 to 1.0.

  • pan (float, default: 0.0 ) –

    Stereo position from -1.0 to 1.0.

  • should_close_source (bool, default: True ) –

    Whether the voice closes the source when it ends.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def play_stream(
    self,
    source: TerminalAudioSource,
    *,
    volume: float = 1.0,
    pan: float = 0.0,
    should_close_source: bool = True,
) -> TerminalPlayback:
    """
    Play a stream or any object with ``format``, ``read(frames)`` and ``close()``.

    Args:
        source: The audio source.
        volume: Gain from 0.0 to 1.0.
        pan: Stereo position from -1.0 to 1.0.
        should_close_source: Whether the voice closes the source when it ends.
    """

play_sound

play_sound(sound: TerminalSound, *, volume: float = 1.0, pan: float = 0.0) -> TerminalPlayback

Play a rendered TerminalSound as an independent voice.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def play_sound(
    self, sound: TerminalSound, *, volume: float = 1.0, pan: float = 0.0
) -> TerminalPlayback:
    """Play a rendered [`TerminalSound`][xnano_audio.TerminalSound] as an independent voice."""

crossfade

crossfade(outgoing: TerminalPlayback, incoming: TerminalPlayback, *, seconds: float = 3.0) -> None

Fade outgoing out and incoming in over seconds.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def crossfade(
    self,
    outgoing: TerminalPlayback,
    incoming: TerminalPlayback,
    *,
    seconds: float = 3.0,
) -> None:
    """Fade ``outgoing`` out and ``incoming`` in over ``seconds``."""

advance

advance(frames: int) -> bytes

Deterministic null-backend consumption only; rejects live-device advancement.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def advance(self, frames: int) -> bytes:
    """Deterministic null-backend consumption only; rejects live-device advancement."""

close

close(*, fade_seconds: float = 0.04, timeout_seconds: float = 0.25) -> None

Idempotent bounded shutdown; stop the device before releasing its callback storage.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def close(self, *, fade_seconds: float = 0.04, timeout_seconds: float = 0.25) -> None:
    """Idempotent bounded shutdown; stop the device before releasing its callback storage."""

TerminalTempoMap

TerminalTempoMap(beats_per_minute: float = 120.0)

Maps beats to seconds. The tempo is constant between changes.

Immutable. Use it to find when a beat sounds on a clock, or which beat a clock reading falls on, even across tempo changes.

Attributes:

Create a map with one constant tempo.

Parameters:

  • beats_per_minute (float, default: 120.0 ) –

    Quarter-note beats per minute, 1 to 1000.

Raises:

Methods:

  • with_change –

    Return the map with a change to beats_per_minute at beat.

  • scaled –

    Return the map with every tempo multiplied by factor; 2.0 plays twice as fast.

  • beats_per_minute_at –

    Return the tempo in effect at beat.

  • beats_to_seconds –

    Return the seconds from beat zero to beat.

  • seconds_to_beats –

    Return the beat reached after seconds.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def __init__(self, beats_per_minute: float = 120.0) -> None:
    """
    Create a map with one constant tempo.

    Args:
        beats_per_minute: Quarter-note beats per minute, 1 to 1000.

    Raises:
        ValueError: If the tempo is out of range.
    """

initial_beats_per_minute property

initial_beats_per_minute: float

The tempo before any change.

changes property

changes: list[tuple[float, float]]

The tempo changes as (beat, beats_per_minute) pairs in beat order.

with_change

with_change(beat: float, beats_per_minute: float) -> TerminalTempoMap

Return the map with a change to beats_per_minute at beat.

A change at beat 0 replaces the initial tempo; a change at an existing beat replaces that change.

Raises:

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def with_change(self, beat: float, beats_per_minute: float) -> TerminalTempoMap:
    """
    Return the map with a change to ``beats_per_minute`` at ``beat``.

    A change at beat 0 replaces the initial tempo; a change at an existing beat
    replaces that change.

    Raises:
        ValueError: If an argument is out of range.
    """

scaled

scaled(factor: float) -> TerminalTempoMap

Return the map with every tempo multiplied by factor; 2.0 plays twice as fast.

Raises:

  • ValueError –

    If factor is not positive or a tempo leaves its range.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def scaled(self, factor: float) -> TerminalTempoMap:
    """
    Return the map with every tempo multiplied by ``factor``; 2.0 plays twice as fast.

    Raises:
        ValueError: If ``factor`` is not positive or a tempo leaves its range.
    """

beats_per_minute_at

beats_per_minute_at(beat: float) -> float

Return the tempo in effect at beat.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def beats_per_minute_at(self, beat: float) -> float:
    """Return the tempo in effect at ``beat``."""

beats_to_seconds

beats_to_seconds(beat: float) -> float

Return the seconds from beat zero to beat.

Raises:

  • ValueError –

    If beat is negative or not finite.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def beats_to_seconds(self, beat: float) -> float:
    """
    Return the seconds from beat zero to ``beat``.

    Raises:
        ValueError: If ``beat`` is negative or not finite.
    """

seconds_to_beats

seconds_to_beats(seconds: float) -> float

Return the beat reached after seconds.

Raises:

  • ValueError –

    If seconds is negative or not finite.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def seconds_to_beats(self, seconds: float) -> float:
    """
    Return the beat reached after ``seconds``.

    Raises:
        ValueError: If ``seconds`` is negative or not finite.
    """

TerminalTimeSignature

TerminalTimeSignature(numerator: int = 4, denominator: int = 4)

How many beats make a bar.

Melodies are measured in quarter-note beats, so TerminalTimeSignature(6, 8) is a bar of three beats.

Attributes:

Create a time signature.

Parameters:

  • numerator (int, default: 4 ) –

    Beat units per bar, 1 to 64.

  • denominator (int, default: 4 ) –

    The note value of one beat unit: 1, 2, 4, 8, 16 or 32.

Raises:

Methods:

  • bar_and_beat –

    Return the zero-based bar and the beat inside it that beats falls on.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def __init__(self, numerator: int = 4, denominator: int = 4) -> None:
    """
    Create a time signature.

    Args:
        numerator: Beat units per bar, 1 to 64.
        denominator: The note value of one beat unit: 1, 2, 4, 8, 16 or 32.

    Raises:
        ValueError: If an argument is out of range.
    """

numerator property

numerator: int

Beat units per bar.

denominator property

denominator: int

The note value of one beat unit.

bar_length_beats property

bar_length_beats: float

The length of one bar in quarter-note beats.

bar_and_beat

bar_and_beat(beats: float) -> tuple[int, float]

Return the zero-based bar and the beat inside it that beats falls on.

Raises:

  • ValueError –

    If beats is negative or not finite.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def bar_and_beat(self, beats: float) -> tuple[int, float]:
    """
    Return the zero-based bar and the beat inside it that ``beats`` falls on.

    Raises:
        ValueError: If ``beats`` is negative or not finite.
    """

TerminalAudioSource

Bases: Protocol

A producer-worker audio source.

Python callbacks never execute on the device thread: a worker thread calls read ahead of playback.

Methods:

  • read –

    Return up to frames complete interleaved PCM frames; empty bytes means the end.

  • close –

    Release whatever the source holds.

Attributes:

format property

The layout of the bytes read returns.

read

read(frames: int) -> bytes

Return up to frames complete interleaved PCM frames; empty bytes means the end.

Source code in packages/xnano-audio/src/xnano_audio/__init__.py
def read(self, frames: int) -> bytes:
    """Return up to ``frames`` complete interleaved PCM frames; empty bytes means the end."""
    ...

close

close() -> None

Release whatever the source holds.

Source code in packages/xnano-audio/src/xnano_audio/__init__.py
def close(self) -> None:
    """Release whatever the source holds."""
    ...

SupportsAudioEffect

Bases: Protocol

Anything that can produce audio effects, such as an 'audio.*' customization.

Effects, sounds and add accept it wherever they accept a TerminalAudioEffect.

Methods:

to_audio_effect

The audio effect or effects this object stands for.

Source code in packages/xnano-audio/src/xnano_audio/__init__.py
def to_audio_effect(self) -> TerminalAudioEffect | Sequence[TerminalAudioEffect]:
    """The audio effect or effects this object stands for."""
    ...

detect_audio_capabilities

detect_audio_capabilities() -> TerminalAudioCapabilities

Report what audio support is available, without opening any device.

Examples:

capabilities = detect_audio_capabilities()
if capabilities.is_output_available:
    TerminalAudioPlayer().play_note("bell", "C6")
else:
    print(capabilities.output_unavailable_reason)
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def detect_audio_capabilities() -> TerminalAudioCapabilities:
    """
    Report what audio support is available, without opening any device.

    Examples:
        ```python
        capabilities = detect_audio_capabilities()
        if capabilities.is_output_available:
            TerminalAudioPlayer().play_note("bell", "C6")
        else:
            print(capabilities.output_unavailable_reason)
        ```
    """

instrument_names

instrument_names() -> list[InstrumentName]

Names of every built-in instrument, in catalog order.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def instrument_names() -> list[InstrumentName]:
    """Names of every built-in instrument, in catalog order."""

is_percussive

is_percussive(instrument: InstrumentName) -> bool

Whether an instrument rings for its own length and ignores duration.

Raises:

  • ValueError –

    If the instrument name is unknown (the message lists the valid names).

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def is_percussive(instrument: InstrumentName) -> bool:
    """
    Whether an instrument rings for its own length and ignores duration.

    Raises:
        ValueError: If the instrument name is unknown (the message lists the
            valid names).
    """

note_to_hertz

note_to_hertz(note: NoteName | float | str) -> float

Convert a note to hertz.

Parameters:

  • note (NoteName | float | str) –

    A name such as "C4", "c#3", "Db5" (the octave defaults to 4; C4 is middle C, MIDI 60), a MIDI note number as an int, or a frequency in hertz as a float. Strings may also be a plain number (MIDI) or end in hz ("440hz").

Raises:

  • ValueError –

    If the note is not recognized or is outside 8 Hz to 20 kHz.

  • TypeError –

    If note is not a string or number.

Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def note_to_hertz(note: NoteName | float | str) -> float:
    """
    Convert a note to hertz.

    Args:
        note: A name such as ``"C4"``, ``"c#3"``, ``"Db5"`` (the octave
            defaults to 4; ``C4`` is middle C, MIDI 60), a MIDI note number
            as an ``int``, or a frequency in hertz as a ``float``. Strings may
            also be a plain number (MIDI) or end in ``hz`` (``"440hz"``).

    Raises:
        ValueError: If the note is not recognized or is outside 8 Hz to
            20 kHz.
        TypeError: If ``note`` is not a string or number.
    """

render_note

render_note(instrument: InstrumentName, note: NoteName | float | str | None = None, *, duration_milliseconds: int = 500, velocity: float = 0.8, effects: list[TerminalAudioEffect] | None = None, sample_rate: int = 44100) -> TerminalSound

Render a note with a built-in instrument. Needs no audio device.

The output is deterministic: the same arguments always give identical samples. The GIL is released while rendering.

Parameters:

  • instrument (InstrumentName) –

    One of instrument_names. Case, spaces and hyphens are forgiving ("Electric Piano" works).

  • note (NoteName | float | str | None, default: None ) –

    What to play; see note_to_hertz. Defaults to "C4".

  • duration_milliseconds (int, default: 500 ) –

    How long the note is held, 1 to 60000. The release or natural decay is added on top, so the sound is longer. Ignored by percussive instruments.

  • velocity (float, default: 0.8 ) –

    Strength from 0.0 to 1.0 (clamped; NaN raises ValueError). Louder notes are also brighter on instruments that model it (piano, synth, ...).

  • effects (list[TerminalAudioEffect] | None, default: None ) –

    Effects applied in order after the instrument (at most 32).

  • sample_rate (int, default: 44100 ) –

    Output rate in hertz, 8000 to 192000.

Raises:

  • ValueError –

    If the instrument is unknown (the message lists the valid names) or an argument is out of range.

  • TypeError –

    If note is not a string or number.

Examples:

ding = render_note("bell", "C6", effects=[TerminalAudioEffect.reverb()])
assert ding.peak <= 0.98
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
def render_note(
    instrument: InstrumentName,
    note: NoteName | float | str | None = None,
    *,
    duration_milliseconds: int = 500,
    velocity: float = 0.8,
    effects: list[TerminalAudioEffect] | None = None,
    sample_rate: int = 44100,
) -> TerminalSound:
    """
    Render a note with a built-in instrument. Needs no audio device.

    The output is deterministic: the same arguments always give identical
    samples. The GIL is released while rendering.

    Args:
        instrument: One of
            [`instrument_names`][xnano_audio.instrument_names]. Case,
            spaces and hyphens are forgiving (``"Electric Piano"`` works).
        note: What to play; see
            [`note_to_hertz`][xnano_audio.note_to_hertz]. Defaults to
            ``"C4"``.
        duration_milliseconds: How long the note is held, 1 to 60000. The
            release or natural decay is added on top, so the sound is longer.
            Ignored by percussive instruments.
        velocity: Strength from 0.0 to 1.0 (clamped; NaN raises
            ``ValueError``). Louder notes are also brighter on
            instruments that model it (piano, synth, ...).
        effects: Effects applied in order after the instrument (at most 32).
        sample_rate: Output rate in hertz, 8000 to 192000.

    Raises:
        ValueError: If the instrument is unknown (the message lists the valid
            names) or an argument is out of range.
        TypeError: If ``note`` is not a string or number.

    Examples:
        ```python
        ding = render_note("bell", "C6", effects=[TerminalAudioEffect.reverb()])
        assert ding.peak <= 0.98
        ```
    """