Audio Effect

Choose a mode through AudioSettings.mode (AudioMode provides editor completion), or discover presets with effects.audio.presets(). The curated set is objects, tiles, glow, bars, mirror, waveform, and spectrum. The minimal preset remains monochrome bars.

from bruhanimate import AudioSettings, effects

settings = AudioSettings(mode="objects", smoothing=0.7,
                         sensitivity=1.0, noise_floor=-65)
assert settings.mode == "objects"
assert "objects" in effects.audio.presets()
# To open capture in your terminal:
# effects.audio.play(settings=settings, on_resize="resize")

Objects fills the viewport with rectangular blocks, including after resize. Bass (20–250 Hz) grows each block from 3×3 cells to fill its allotted region completely: neighbors touch at maximum size, including along the viewport edges. Smaller viewports clip the minimum size. Mids (250–4,000 Hz) move blocks within their remaining space; movement settles as they fill their regions. Treble (above 4,000 Hz) adds highlights on the blocks, and overall volume controls brightness. This is an artistic sound-reactive scene, not a waveform or a calibrated measurement.

tiles fills the viewport with separated blocks. Columns span low to high frequencies from left to right; each block’s brightness follows its frequency range with a gentle pulse. glow blends three moving pools of light: broad amber bass, violet mids, and smaller cyan treble highlights. Band levels control brightness and spread; mids increase movement. Both use colored backgrounds, with ASCII shading when color=False, and fade toward silence. Resize rebuilds the geometry while preserving motion phase. The demo uses arrow keys only; there is no automatic mode switch. The Audio Demo renders these modes inside a computer monitor; C switches between the original artwork and a larger rectangular frame. Standalone effects still use their entire supplied buffer.

Readable response

Frequency levels sum energy across each band, so diffuse sounds such as cymbals remain visible alongside a stronger bass tone. Window compensation keeps a band’s amplitude approximately equal to a sine’s peak amplitude; overall volume uses RMS. Bar spacing eases into a logarithmic scale, giving mids and treble more screen space instead of repeating a handful of bass bins across the left side. Very wide viewports can still repeat bins at the capture chunk’s resolution.

Levels use a fixed dB scale; neither chunks nor individual bands are normalized to their own peak. Quiet sounds remain visibly quieter. noise_floor is the bottom of that scale, in dB relative to full-scale samples (default -65, allowed -120 through -1). Raise it, for example to -40, to suppress background noise; lower it to include quieter detail. sensitivity scales input amplitude before level conversion; zero suppresses response. These controls do not change playback volume or establish sound-pressure levels. The lower default floor retains more quiet detail; use noise_floor=-55 to restore the earlier display threshold.

smoothing is release retention per simulation tick: 0 responds immediately, 0.7 falls gradually, and 1 holds prior levels. Attack retention is capped at 0.25 so transients still rise promptly. Motion, peak decay, and history use simulation ticks; 30 updates/sec is the demo’s reference rate. Drawing never advances time or reads a new audio chunk. Analysis uses one FFT per tick and caches its window and frequency bins. Windows uses the device’s actual sample rate and the channel with the greatest chunk energy, so right-only or opposite-phase stereo is visible.

Bars have a green/yellow/red height gradient and falling peak caps. Mirror shares the frequency layout; tiny viewports remain drawable. bar_gain multiplies Bars/Mirror display height and peak caps, with a default of 2 so moderate audio uses more of the viewport. Values clip at full height; Mirror reaches both edges symmetrically. Set bar_gain=1 for the original height, increase it for taller bars, or use 0 to hide them. This is a fixed display multiplier, not automatic normalization; it does not change analysis or the other modes. compact=True removes gaps between columns, as used by the Bars/Mirror demo.

from bruhanimate import AudioSettings, effects

settings = AudioSettings(mode="mirror", bar_gain=2.5, compact=True)
assert settings.bar_gain == 2.5
# effects.audio.play(settings=settings, on_resize="resize")

Spectrum paints every row with low pitches at the bottom and scrolls newest samples in from the right. Waveform is a volume-history envelope, not a sample-by-sample oscilloscope. Objects retains a subtle idle drift; Tiles and Glow fade to the background when their smoothed audio levels reach silence.

Capture starts only in start(). Player/Scene owns cleanup; changing mode with set_mode validates the typed name and clears stale pixels without restarting capture. Resize retains capture, phase and smoothed levels; the object field reflows on the next update. Synthetic audio tests cover rendering and cleanup; physical-device behavior depends on the optional backend. See Native platform checks for explicit native checks.

Removed modes in 1.3

The experimental modes and same-named presets radial, rain, tunnel, ripple, vortex, wave, interference, lightning, aurora, orbit, orbit_gradient, scope, grid, helix, lissajous, sunburst, mandala, comet, weave, fireworks, starfield, and bounce have been removed from the API, CLI discovery, and demo. Existing calls using them now raise validation errors. Use objects, tiles, or glow for a screen-wide reactive scene, or waveform/spectrum for sound history. There are no silent aliases. Audio levels and smoothing also look different from older builds; retune sensitivity and noise_floor if needed.

Copyright 2023 Ethan Christensen

Licensed under the Apache License, Version 2.0 (the “License”); you may not use this file except in compliance with the License. You may obtain a copy of the License at

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

bruhanimate.bruheffect.audio_effect._find_monitor_device()[source]

Return a sounddevice input device index for a PulseAudio/PipeWire monitor, or None.

class bruhanimate.bruheffect.audio_effect.AudioEffect(buffer: Buffer, background: str, settings: AudioSettings | None = None)[source]

Bases: BaseEffect

Visualizes system audio output as terminal animations.

Seven modes: objects (a screen-wide field of reactive shapes), tiles (frequency blocks), glow (moving light pools), bars, mirror, waveform (volume history), and spectrum (frequency history). Summed band energy includes diffuse mids and treble, with a fixed noise floor instead of normalizing each chunk to its peak. These are artistic displays, not calibrated audio meters.

Windows requires PyAudioWPatch for WASAPI loopback:

pip install bruhanimate[audio]

Linux requires sounddevice and a PulseAudio/PipeWire monitor source:

pip install bruhanimate[audio]
__init__(buffer: Buffer, background: str, settings: AudioSettings | None = None)[source]

Initialize the audio effect.

Parameters:
  • buffer (Buffer) – Effect buffer to push updates to.

  • background (str) – Character or string to use for the background.

  • settings (AudioSettings, optional) – Configuration for the audio effect.

start()[source]

Start capture explicitly; constructing/configuring an effect is inert.

set_mode(mode: Literal['objects', 'tiles', 'glow', 'bars', 'mirror', 'waveform', 'spectrum']) → None[source]

Select a supported mode without restarting capture; clear stale pixels.

set_sensitivity(sensitivity: float) → None[source]

Adjust the amplitude sensitivity multiplier.

set_smoothing(smoothing: float) → None[source]

Set release retention (0..1); attack retention is capped at 0.25.

set_color(color: bool) → None[source]

Enable or disable colorized output.

set_bar_char(bar_char: str) → None[source]

Set the character used to draw bars and fills.

set_compact(compact: bool) → None[source]

Remove the gap column between bars when True.

_analyze(audio: ndarray, rate: int) → None[source]

Analyze once per tick with cached windows/bins and absolute levels.

static _height_color(ratio: float) → int[source]

Green → yellow → red based on magnitude (0–1).

static _heat_color(ratio: float) → int[source]

Blue → cyan → green → yellow → red heat palette (0–1).

update(dt: float) → None[source]

Advance one simulation tick; draw only copies the persistent raster.

_render_objects(audio: ndarray) → None[source]

Blocks grow from 3x3 cells to adjoining, fully filled tile regions.

Bass controls both dimensions; mids move blocks within their remaining space and treble highlights their faces. Tiny viewports clip the minimum size. Integer tile boundaries cover odd dimensions without seams. Resize retains phase and needs no random draws or resources.

_render_tiles(audio: ndarray) → None[source]

Luminous frequency tiles: low bands left, high bands right.

_render_glow(audio: ndarray) → None[source]

Broad bass light, moving midrange pools and sharper treble highlights.

_render_bars(audio: ndarray)[source]

Frequency bars with a height gradient and gently falling peak caps.

_render_waveform(audio: ndarray)[source]

Scrolling RMS amplitude envelope: each column = one frame, scrolls left over time.

_render_spectrum(audio: ndarray)[source]

Scroll one complete frequency column; low pitches at the bottom.

stop(timeout: float = 2.0)[source]

Stop capture and wait at most timeout seconds for resource cleanup.

A backend stuck during device initialization is reported, rather than silently leaving a live worker behind. A later close can retry joining.

resize(size: tuple[int, int]) → None[source]

Reflow caches without reading or restarting capture.

Existing pixels remain until the next audio update. Wave history keeps newest samples on the right; automatic bar counts follow the width.