Skip to main content

PyALSoft

CI status PyPI version Supported Python versions

OpenAL Soft 1.25.2

PyALSoft provides function-oriented managed playback and capture APIs, plus typed bindings for OpenAL Soft, including core OpenAL, ALC, EFX, and supported extensions. The managed API lives at the package root; the complete low-level interface remains available through pyalsoft.bindings.

PyALSoft is an independent project and is not affiliated with or endorsed by the OpenAL Soft project.

Installation

PyALSoft requires Python 3.12 or later.

python3 -m pip install pyalsoft

Quick start

play starts a WAV file immediately and returns an optional control handle:

import time

from pyalsoft import play

sound = play("sound.wav")
while sound.playing:
    time.sleep(0.1)

Playback is asynchronous. If no control is needed, the return value can be ignored without stopping the sound:

play("notification.wav")

The returned PlayingSound has transport, timeline, gain, pitch, looping, and spatial controls. The convenience API supports uncompressed mono or stereo WAV files containing 8-bit unsigned or 16-bit signed PCM. It opens its default audio session lazily, reuses clips loaded from the same resolved path, and releases it automatically at process exit. Applications can call shutdown() to close it earlier.

Controlling one sound

Every VoiceConfig field can also be passed directly to play. Direct keywords override the corresponding field when both forms are used:

sound = play(
    "engine.wav",
    gain=0.7,
    pitch=1.1,
    looping=True,
    relative=True,
    position=(-2.0, 0.0, -4.0),
    reference_distance=1.0,
    max_distance=20.0,
    rolloff_factor=1.0,
)

sound.position = (2.0, 0.0, -4.0)
sound.pitch = 1.25
sound.seek(3.0)

# Group changes made in the same application update. Only changed OpenAL
# properties are sent to the backend.
sound.update(
    position=(3.0, 0.0, -2.0),
    velocity=(1.0, 0.0, 0.0),
    gain=0.6,
)

offset_seconds is the playhead position on the original source-audio timeline. It is not elapsed wall-clock time. For example, at pitch=2.0 the offset advances two source seconds per wall-clock second, while duration_seconds remains unchanged. remaining_seconds and progress use the same source timeline. For sample-accurate work, use offset_frames, remaining_frames, frame_count, and seek_frames(). rewind() follows OpenAL behavior by moving to the beginning and entering the INITIAL state; restart() moves to the beginning and immediately plays.

Format and length information is available without opening an audio device:

from pyalsoft import get_sound_info

info = get_sound_info("engine.wav")
print(info.duration_seconds, info.frame_count)
print(info.channels, info.sample_rate, info.bit_depth)

The same immutable SoundInfo is available as clip.info and sound.info. PlayingSound also exposes path, channels, sample_rate, and sample_type. When a sound ends, end_reason distinguishes natural completion, an explicit stop(), runtime shutdown, and a disconnected device when the backend supports connection reporting. Stopped sounds retain their configuration, so controls can be changed before rewind() or restart() creates another native source.

The spatial controls describe a sound relative to the playback listener:

Control Meaning
position The sound's (x, y, z) location. By default, +X is right, +Y is up, and -Z is forward.
velocity Motion, in coordinate units per second, used for Doppler shift. It does not automatically update position.
direction The vector the sound's directional cone points along. (0, 0, 0) makes it omnidirectional.
relative When true, position, velocity, and direction use listener-local coordinates; otherwise they use world coordinates.
reference_distance The reference point where distance attenuation has unity gain. Clamped models keep unity distance gain at closer distances.
max_distance The outer distance used by clamped distance models; attenuation no longer changes beyond it.
rolloff_factor Scales distance attenuation. 0 disables distance rolloff; larger values attenuate more rapidly.
min_gain, max_gain Lower and upper clamps applied after distance and cone attenuation.
cone_inner_angle Full angle, in degrees, inside which direction causes no cone attenuation.
cone_outer_angle Full angle beyond which the outer-cone gain is used; OpenAL interpolates between the two angles.
cone_outer_gain Gain multiplier used when the listener is outside the outer cone.

Gain is a linear amplitude multiplier: 1.0 is unchanged, 0.5 is about -6 dB, and 0.0 is silent. Pitch changes playback rate and audible pitch together; OpenAL does not perform independent time stretching.

For conventional positional audio, use a mono sound. OpenAL normally plays stereo sources without applying 3D position or direction.

The convenience runtime's listener and global distance/Doppler behavior can be configured without opening an explicit Playback:

from pyalsoft import (
    Acoustics,
    DistanceModel,
    Listener,
    set_acoustics,
    set_listener,
    update_listener,
)

set_listener(Listener(position=(0.0, 1.7, 0.0)))
set_acoustics(
    Acoustics(
        distance_model=DistanceModel.INVERSE_CLAMPED,
        doppler_factor=1.0,
        speed_of_sound=343.3,
    )
)
update_listener(position=(2.0, 1.7, 0.0))

get_listener(), get_acoustics(), update_listener(), and update_acoustics() operate on the convenience runtime by default. Pass an explicit Playback as the first argument to use that session instead.

The complete example is available as examples/play_file.py and can be run from a source checkout with:

uv run python examples/play_file.py

Effects and filters

EFX configuration uses immutable values just like the other managed playback controls. Reverb is routed through an auxiliary EffectSend; filter is the sound's one direct filter:

from pyalsoft import EffectSend, LowPassFilter, Reverb, play

room = Reverb(
    gain=0.2,
    decay_time=0.6,
    high_frequency_decay_ratio=0.8,
)
sound = play(
    "voice.wav",
    filter=LowPassFilter(high_frequency_gain=0.1),
    effect_sends=(EffectSend(effect=room),),
)

LowPassFilter and HighPassFilter expose EFX gain controls rather than a cutoff frequency. A filter may also be placed on an EffectSend to shape only the wet signal. Send tuple order determines the native auxiliary-send index, and the playback device determines how many simultaneous sends it supports.

Live sounds accept replacement values through update. Pass filter=None or an empty effect_sends tuple to restore the dry, unfiltered signal; the same values can be assigned through the corresponding properties:

from pyalsoft import HighPassFilter

sound.update(filter=HighPassFilter(low_frequency_gain=0.1))
sound.update(filter=None)
sound.effect_sends = ()

The same fields are available on VoiceConfig for explicit voices and streams. PyALSoft owns their native filters, effects, and auxiliary slots and releases them with the voice. Configuring EFX raises AudioBackendError when the selected device does not expose EFX or cannot provide the requested number of sends.

See examples/play_with_reverb.py and examples/filter_sound.py for complete examples.

Recording

The managed capture API collects audio in memory while your application does other work. Native capture buffers are drained on a background thread, so the common API returns one PCM value instead of exposing chunks:

from pyalsoft import start_recording, stop_recording

recording = start_recording()
input("Speak now, then press Enter to stop... ")
captured = stop_recording(recording)

start_recording uses the default input device and records 48 kHz, mono, 16-bit PCM unless told otherwise. Use list_capture_devices() to select a specific input. For a known duration, record(3.0) is the blocking equivalent. Captured and generated PCM values can be passed directly to play:

sound = play(captured)

examples/record_and_play.py records until Enter is pressed and then plays the complete recording:

uv run python examples/record_and_play.py

Explicit playback sessions

Applications that generate audio, stream it, select a device, or require fully explicit resource lifetimes can use the underlying managed API directly:

from pyalsoft import PCM, open_playback, play, release, upload

pcm = PCM(
    # Half a second of mono silence; replace with your application's PCM bytes.
    samples=b"\0\0" * 22_050,
    channels=1,
    sample_rate=44_100,
)

with open_playback() as playback:
    clip = upload(playback, pcm)
    voice = play(playback, clip)
    # Query or control the voice here.
    release(playback, voice)
    release(playback, clip)

Device selection and HRTF

Playback devices can be enumerated and passed to open_playback. Context preferences such as HRTF are requested with PlaybackConfig; query PlaybackInfo to see what the audio backend actually enabled:

from pyalsoft import (
    PlaybackConfig,
    get_playback_info,
    list_playback_devices,
    open_playback,
)

devices = list_playback_devices()
selected = next((device for device in devices if device.is_default), None)

with open_playback(selected, config=PlaybackConfig(hrtf=True)) as playback:
    info = get_playback_info(playback)
    print(info.device_name, info.hrtf_status.value, info.hrtf_name)

See examples/play_sine.py, examples/move_sine.py, and examples/stream_sine.py for complete explicit API examples. Device selection and HRTF are demonstrated in examples/select_device_hrtf.py.

API layers

Automatically generated ctypes bindings for OpenAL live at pyalsoft.bindings. The same namespace also provides owned playback, capture, loopback, and context handles for deterministic native resource lifetimes. See the owned backend handle guide and the generated bindings reference.

pyalsoft holds the hand authored Python API, intended to make working with the library more Pythonic and manageable.

Contributing

Direct development occurs on development, the base branch of the repository. Pull requests from there to master represent official releases, signified by a version increase in pyproject.toml. Version increases should only be done from development. If implementing your own feature, it is requested that you fork this repository and make your own feature branch, and then merge into development.

Create the locked development environment with uv:

uv sync --python 3.12

Before submitting a change, run the same core checks as CI:

uv run pytest
uv run ruff check .
uv run ruff format --check .
uv run mypy
uv run python tools/generate_bindings.py --check
uv run python tools/sync_openal_soft.py --check

Bindings and docs/reference.md are generated from the vendored OpenAL registry plus reviewed corrections in tools/semantic_overrides.toml. After changing the generator, registry, or overrides, regenerate them with:

uv run python tools/generate_bindings.py

See the repository tool guide for the purpose and structure of each development and release command.

License

PyALSoft's original Python code is available under the MIT License. Bundled OpenAL Soft and other third-party components remain under their respective licenses. The distribution includes the complete license texts and a third-party notice.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyalsoft-0.9.0.tar.gz (3.0 MB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

pyalsoft-0.9.0-py3-none-win_amd64.whl (1.7 MB view details)

Uploaded Python 3Windows x86-64

pyalsoft-0.9.0-py3-none-manylinux_2_28_x86_64.whl (1.8 MB view details)

Uploaded Python 3manylinux: glibc 2.28+ x86-64

pyalsoft-0.9.0-py3-none-manylinux_2_28_aarch64.whl (1.7 MB view details)

Uploaded Python 3manylinux: glibc 2.28+ ARM64

pyalsoft-0.9.0-py3-none-macosx_11_0_arm64.whl (868.4 kB view details)

Uploaded Python 3macOS 11.0+ ARM64

pyalsoft-0.9.0-py3-none-macosx_10_13_x86_64.whl (952.8 kB view details)

Uploaded Python 3macOS 10.13+ x86-64

File details

Details for the file pyalsoft-0.9.0.tar.gz.

File metadata

  • Download URL: pyalsoft-0.9.0.tar.gz
  • Upload date:
  • Size: 3.0 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for pyalsoft-0.9.0.tar.gz
Algorithm Hash digest
SHA256 4d2b6339cc976b828dcc1cf44bc822441a40febd4c9f1d88f2ae7e64a360434b
MD5 7fc433af3456e1447d9006a57a41e952
BLAKE2b-256 e3b4a079c74067c431ffb41d122b92183aa0a299a64c23f810d4ba4b9f0dd0a2

See more details on using hashes here.

File details

Details for the file pyalsoft-0.9.0-py3-none-win_amd64.whl.

File metadata

  • Download URL: pyalsoft-0.9.0-py3-none-win_amd64.whl
  • Upload date:
  • Size: 1.7 MB
  • Tags: Python 3, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for pyalsoft-0.9.0-py3-none-win_amd64.whl
Algorithm Hash digest
SHA256 b2cae62663e97b2ed4047044415d9e3256a81ea5c8ae61aea3a05c2fd2595f26
MD5 78f133c2963904cf3d71e17e82705d66
BLAKE2b-256 07e6d540cec2b5bcb950d10aa4c731774c0f542c4d1d6f45d8b9a7af3bf7e2ec

See more details on using hashes here.

File details

Details for the file pyalsoft-0.9.0-py3-none-manylinux_2_28_x86_64.whl.

File metadata

  • Download URL: pyalsoft-0.9.0-py3-none-manylinux_2_28_x86_64.whl
  • Upload date:
  • Size: 1.8 MB
  • Tags: Python 3, manylinux: glibc 2.28+ x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for pyalsoft-0.9.0-py3-none-manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 c0ea2bed62e15bd1dd1adccd67fc658bbeb722620bb656744b291236614e78a7
MD5 98e2a98332aee13fadc91140571571fc
BLAKE2b-256 9b35563a11dc8395cd4add50c7f98dd0a7ac1493aac415011991664e3567dded

See more details on using hashes here.

File details

Details for the file pyalsoft-0.9.0-py3-none-manylinux_2_28_aarch64.whl.

File metadata

  • Download URL: pyalsoft-0.9.0-py3-none-manylinux_2_28_aarch64.whl
  • Upload date:
  • Size: 1.7 MB
  • Tags: Python 3, manylinux: glibc 2.28+ ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for pyalsoft-0.9.0-py3-none-manylinux_2_28_aarch64.whl
Algorithm Hash digest
SHA256 3ad3000e38ccd8c94925906211020923622ec7c4be7241734071c38a3976efc4
MD5 d8610f4a1c41424d43f142139a0438af
BLAKE2b-256 5d4e5bdb000b46f325cdc68a9b4a0f7509a204d21585182fb66fe0472a89842c

See more details on using hashes here.

File details

Details for the file pyalsoft-0.9.0-py3-none-macosx_11_0_arm64.whl.

File metadata

  • Download URL: pyalsoft-0.9.0-py3-none-macosx_11_0_arm64.whl
  • Upload date:
  • Size: 868.4 kB
  • Tags: Python 3, macOS 11.0+ ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for pyalsoft-0.9.0-py3-none-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 4ac26777bd8c95d94ce403c7399e33b867df7e22d4bdb4ce6eb744a1268956ce
MD5 ba8b45397cb97ac94d24a127185d54eb
BLAKE2b-256 f7f27e98721a8e6ce440cb7cd4ef3aa496c6821e2ef861115b1d1b3b79fc6d20

See more details on using hashes here.

File details

Details for the file pyalsoft-0.9.0-py3-none-macosx_10_13_x86_64.whl.

File metadata

  • Download URL: pyalsoft-0.9.0-py3-none-macosx_10_13_x86_64.whl
  • Upload date:
  • Size: 952.8 kB
  • Tags: Python 3, macOS 10.13+ x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: uv/0.11.29 {"installer":{"name":"uv","version":"0.11.29","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

File hashes

Hashes for pyalsoft-0.9.0-py3-none-macosx_10_13_x86_64.whl
Algorithm Hash digest
SHA256 fdb795c042640bd0d1b69374396be852ffbe746c026395e4b8a1189c195501cd
MD5 50394753c4669f0bda3db562eeb8e68f
BLAKE2b-256 402b6e5ea41ebfd8e054053fab1d9c6d8e90d96a999426e5b39ef773acdb52b1

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page