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:
-
TerminalAudioCapabilities–What audio support this build and machine offer.
-
TerminalAudioEffect–An immutable audio effect description.
-
TerminalAudioError–Raised when an audio device or stream fails.
-
TerminalAudioPlayer–Plays sounds on the default output device, with one musical clock.
-
TerminalAudioStream–Lazy decoded file source; no whole-file TerminalSound allocation is required.
-
TerminalAudioUnavailableError–Raised when an audio device is missing, or this build has no device support.
-
TerminalMelody–Notes on a beat timeline with a tempo map and a time signature.
-
TerminalMicrophone–Microphone capture that tracks the input level.
-
TerminalNote–One note on a beat timeline.
-
TerminalPCMFormat–Explicit PCM sample representation; audio bytes are not cell-grid bytes.
-
TerminalPlayback–One independently controlled voice or stream; setters are owner-thread commands.
-
TerminalScheduledNote–A note placed on an external clock.
-
TerminalSound–A rendered mono sound: raw samples plus their sample rate.
-
TerminalStreamingAudioPlayer–Device owner for buffered streams and sample-count timing.
-
TerminalTempoMap–Maps beats to seconds. The tempo is constant between changes.
-
TerminalTimeSignature–How many beats make a bar.
-
TerminalAudioSource–A producer-worker audio source.
-
SupportsAudioEffect–Anything that can produce audio effects, such as an
'audio.*'customization.
Functions:
-
detect_audio_capabilities–Report what audio support is available, without opening any device.
-
instrument_names–Names of every built-in instrument, in catalog order.
-
is_percussive–Whether an instrument rings for its own length and ignores duration.
-
note_to_hertz–Convert a note to hertz.
-
render_note–Render a note with a built-in instrument. Needs no audio device.
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']
One of the built-in instruments.
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']
A note by name: a letter, an optional sharp or flat, and an octave ('C4' is middle C).
Pitch
module-attribute
¶
A pitch: a note name, or a MIDI number from 0 to 127 (60 is middle C).
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(bool) –Whether this build was compiled with audio device support.
-
is_output_available(bool) –Whether a default output device exists.
-
is_input_available(bool) –Whether a default input device (microphone) exists.
-
output_unavailable_reason(str | None) –Why output is unavailable, or
Nonewhen it is available. -
input_unavailable_reason(str | None) –Why input is unavailable, or
Nonewhen it is available.
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_input_available
property
¶
is_input_available: bool
Whether a default input device (microphone) exists.
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:
Methods:
-
bit_crush–Lo-fi: quantize to
bitsand hold everydownsample-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.
bit_crush
staticmethod
¶
bit_crush(*, bits: int = 8, downsample: int = 4) -> TerminalAudioEffect
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
low_pass
staticmethod
¶
low_pass(*, cutoff: float = 1200.0, resonance: float = 0.707) -> TerminalAudioEffect
high_pass
staticmethod
¶
high_pass(*, cutoff: float = 400.0, resonance: float = 0.707) -> TerminalAudioEffect
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
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
tremolo
staticmethod
¶
tremolo(*, rate_hertz: float = 5.0, depth: float = 0.5) -> TerminalAudioEffect
__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
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:
-
is_clock_running(bool) –Whether the clock is currently running.
-
tempo(float) –The clock tempo in beats per minute.
-
beat(float) –The clock position in beats, including the fraction of the current beat.
-
clock_position_milliseconds(float) –The clock position in milliseconds at the current tempo.
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:
-
TerminalAudioUnavailableError–If no output device is available, or this build has no device support.
-
ValueError–If
beats_per_minuteis out of range.
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
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.
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:
-
TerminalAudioError–If the mixer cannot accept the sound.
-
ValueError–If an argument is out of range (including
pan).
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
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:
-
sound(TerminalSound) –From
render_noteorTerminalSound.with_effects. -
volume(float, default:1.0) –Playback gain, as in
play_note. -
pan(float, default:0.0) –Stereo position from -1.0 to 1.0.
-
at_beat(int | None, default:None) –As in
play_note.
Raises:
-
TerminalAudioError–If the mixer cannot accept the sound.
-
ValueError–If
panis outside -1.0 to 1.0.
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
start_clock
¶
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
pause_clock
¶
stop_clock
¶
set_tempo
¶
set_tempo(beats_per_minute: float) -> None
Change the clock tempo.
Raises:
-
ValueError–If
beats_per_minuteis not greater than 0 and at most 600000.
TerminalAudioStream
¶
Lazy decoded file source; no whole-file TerminalSound allocation is required.
Attributes:
-
format(TerminalPCMFormat) –The layout of the bytes
readreturns. -
is_closed(bool) –Whether the stream has been closed.
Methods:
-
open–Open an audio file (WAV, FLAC, Ogg Vorbis, MP3 or MP4/AAC); decoding starts on the first read.
-
read–Decode up to
framesframes; empty bytes mean the end of the file. -
seek–Move to
frame, counted in frames of this stream'sformat. -
close–Release the file; repeated calls are safe.
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:
-
TerminalAudioError–The file cannot be opened or decoded.
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
read
¶
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:
-
notes(list[TerminalNote]) –The notes in start order.
-
tempo_map(TerminalTempoMap) –The tempo map.
-
time_signature(TerminalTimeSignature) –The time signature.
-
length_beats(float) –The end of the last note, or the explicit length when that is longer, in beats.
-
duration_seconds(float) –The melody's length in seconds.
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_beatsis negative.
Methods:
-
with_notes–Return the melody with
notesadded. -
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
counttimes 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
length_beats
property
¶
length_beats: float
The end of the last note, or the explicit length when that is longer, in beats.
with_notes
¶
with_notes(notes: list[TerminalNote]) -> TerminalMelody
with_tempo_map
¶
with_tempo_map(tempo_map: TerminalTempoMap) -> TerminalMelody
with_time_signature
¶
with_time_signature(time_signature: TerminalTimeSignature) -> TerminalMelody
with_length_beats
¶
with_length_beats(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.
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
appended_rest
¶
appended_rest(duration_beats: float = 1.0) -> TerminalMelody
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.
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.
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
repeated
¶
repeated(count: int) -> TerminalMelody
Return the melody played count times in a row.
Raises:
-
ValueError–If
countis zero or the result would hold too many notes.
followed_by
¶
followed_by(other: TerminalMelody) -> TerminalMelody
layered_with
¶
layered_with(other: TerminalMelody) -> TerminalMelody
Return the notes of both melodies played together, keeping this tempo map.
__add__
¶
__add__(other: int) -> TerminalMelody
__add__(other: TerminalMelody | TerminalNote) -> TerminalMelody
__add__(other: int | TerminalMelody | TerminalNote) -> 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
__sub__
¶
__sub__(other: int) -> TerminalMelody
__mul__
¶
__mul__(other: int) -> TerminalMelody
Play the melody several times in a row.
Examples:
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
__or__
¶
__or__(other: TerminalMelody | TerminalNote) -> TerminalMelody
__rshift__
¶
__rshift__(other: float) -> TerminalMelody
__lshift__
¶
__lshift__(other: float) -> TerminalMelody
notes_between_beats
¶
notes_between_beats(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.
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_secondsis earlier thanfrom_elapsed_seconds.
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
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') –One of
instrument_names. -
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
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:
-
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.
Methods:
-
is_available–Whether an input device exists, without opening a stream.
-
reset_peak_level–Forget the peak level, so
peak_leveltracks from now on. -
level_history–The most recent
countlevels, 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
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.
reset_peak_level
¶
level_history
¶
The most recent count levels, oldest first, one per audio buffer.
At most 4096 levels are kept.
Raises:
-
ValueError–If
countis greater than 4096.
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
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:
-
pitch(int) –The MIDI pitch, 0 to 127.
-
name(NoteName) –The sharp-spelled note name, such as
"C#4". -
frequency_hertz(float) –The pitch in hertz.
-
velocity(float) –The strength, 0.0 to 1.0.
-
start_beats(float) –Where the note starts, in beats.
-
duration_beats(float) –How long the key is held, in beats.
-
end_beats(float) –Where the note ends, in beats.
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
NoteNamesuch 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
pitchis not anintorstr.
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"))is60. -
__lt__–Notes are ordered by pitch, then by where they start.
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
with_pitch
¶
with_pitch(pitch: Pitch) -> TerminalNote
with_start_beats
¶
with_start_beats(start_beats: float) -> TerminalNote
with_duration_beats
¶
with_duration_beats(duration_beats: float) -> TerminalNote
with_velocity
¶
with_velocity(velocity: float) -> TerminalNote
transposed
¶
transposed(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
__add__(other: TerminalNote | TerminalMelody) -> TerminalMelody
__add__(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
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
__sub__
¶
__sub__(other: int) -> TerminalNote
__sub__(other: TerminalNote) -> int
__sub__(other: int | TerminalNote) -> TerminalNote | int
__or__
¶
__or__(other: TerminalNote | TerminalMelody) -> TerminalMelody
__lt__
¶
__lt__(other: TerminalNote) -> bool
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:
-
sample_rate(int) –Frames per second.
-
channels(int) –Samples per frame: 1 (mono) or 2 (stereo).
-
sample_type(Literal['f32le', 's16le']) –How each sample is stored:
'f32le'or's16le'. -
bytes_per_frame(int) –Bytes in one interleaved frame.
Create a TerminalPCMFormat.
Parameters:
-
sample_rate(int, default:44100) –int. Defaults to44100. -
channels(int, default:2) –int. Defaults to2. -
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
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:
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(TerminalNote) –The note, in melody beats.
-
start_seconds(float) –When the note starts, in seconds on the external clock.
-
duration_seconds(float) –How long the key is held, in seconds.
-
end_seconds(float) –When the key is released, in seconds on the external clock.
-
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:
-
sample_rate(int) –Samples per second.
-
duration_milliseconds(float) –Length in milliseconds, including any effect tail.
-
peak(float) –Largest absolute sample value, from 0.0 to 1.0.
-
root_mean_square(float) –Root-mean-square level, from 0.0 to 1.0.
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:
pointspeak levels from 0.0 to 1.0. -
with_effects–A new sound with
effectsapplied 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.
duration_milliseconds
property
¶
duration_milliseconds: float
Length in milliseconds, including any effect tail.
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
samplesis 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
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
datais 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
samples
¶
envelope
¶
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
pointsis not between 1 and 100000.
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
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:
-
ValueError–If more than 32 effects are given.
-
TypeError–If an item is not a
TerminalAudioEffect.
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
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_secondsis where on the timeline the sound begins (it may be negative or beforestart_seconds) andgainis 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_secondsorsample_rateis 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
layersis 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
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
__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
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:
-
format(TerminalPCMFormat) –The layout of audio this player mixes.
-
is_closed(bool) –Whether the player has been closed.
-
sample_position(int) –Output frames consumed; render-ahead frames do not advance this counter.
Create a TerminalStreamingAudioPlayer.
Parameters:
-
format(TerminalPCMFormat | None, default:None) –TerminalPCMFormat | None. Defaults toNone. -
lookahead_milliseconds(int, default:300) –int. Defaults to300. -
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)andclose(). -
play_sound–Play a rendered
TerminalSoundas an independent voice. -
crossfade–Fade
outgoingout andincomingin overseconds. -
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
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
play_sound
¶
play_sound(sound: TerminalSound, *, volume: float = 1.0, pan: float = 0.0) -> TerminalPlayback
Play a rendered TerminalSound as an independent voice.
crossfade
¶
crossfade(outgoing: TerminalPlayback, incoming: TerminalPlayback, *, seconds: float = 3.0) -> None
Fade outgoing out and incoming in over seconds.
advance
¶
close
¶
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:
-
initial_beats_per_minute(float) –The tempo before any change.
-
changes(list[tuple[float, float]]) –The tempo changes as
(beat, beats_per_minute)pairs in beat order.
Create a map with one constant tempo.
Parameters:
-
beats_per_minute(float, default:120.0) –Quarter-note beats per minute, 1 to 1000.
Raises:
-
ValueError–If the tempo is out of range.
Methods:
-
with_change–Return the map with a change to
beats_per_minuteatbeat. -
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
changes
property
¶
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:
-
ValueError–If an argument is out of range.
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
scaled
¶
scaled(factor: float) -> TerminalTempoMap
Return the map with every tempo multiplied by factor; 2.0 plays twice as fast.
Raises:
-
ValueError–If
factoris not positive or a tempo leaves its range.
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
beats_per_minute_at
¶
beats_to_seconds
¶
seconds_to_beats
¶
TerminalTimeSignature
¶
How many beats make a bar.
Melodies are measured in quarter-note beats, so TerminalTimeSignature(6, 8) is a
bar of three beats.
Attributes:
-
numerator(int) –Beat units per bar.
-
denominator(int) –The note value of one beat unit.
-
bar_length_beats(float) –The length of one bar in quarter-note beats.
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:
-
ValueError–If an argument is out of range.
Methods:
-
bar_and_beat–Return the zero-based bar and the beat inside it that
beatsfalls on.
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
bar_and_beat
¶
Return the zero-based bar and the beat inside it that beats falls on.
Raises:
-
ValueError–If
beatsis negative or not finite.
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
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
framescomplete interleaved PCM frames; empty bytes means the end. -
close–Release whatever the source holds.
Attributes:
-
format(TerminalPCMFormat) –The layout of the bytes
readreturns.
read
¶
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.
to_audio_effect
¶
to_audio_effect() -> TerminalAudioEffect | Sequence[TerminalAudioEffect]
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
instrument_names
¶
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
note_to_hertz
¶
Convert a note to hertz.
Parameters:
-
note(NoteName | float | str) –A name such as
"C4","c#3","Db5"(the octave defaults to 4;C4is middle C, MIDI 60), a MIDI note number as anint, or a frequency in hertz as afloat. Strings may also be a plain number (MIDI) or end inhz("440hz").
Raises:
-
ValueError–If the note is not recognized or is outside 8 Hz to 20 kHz.
-
TypeError–If
noteis not a string or number.
Source code in packages/xnano-audio/src/xnano_audio/_xnano_audio/__init__.pyi
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
noteis not a string or number.
Examples: