Skip to content

EFX alFilterf Fails Silently on Windows with OpenAL Soft #24

@SeanTerry01

Description

@SeanTerry01

Hello PyOpenAL team,
First, thank you for your work on this library. I am developing an application that uses EFX for sound occlusion and have run into a persistent issue that I cannot solve. I am hoping you can provide some guidance.
System Environment
• OS: Windows 11
• Python: 3.13.3
• PyOpenAL: Freshly installed from pip on May 25, 2025.
• OpenAL Backend: OpenAL Soft 1.24.3.0 (official binaries installed correctly in System32/SysWOW64)
The Problem
I am trying to use the EFX extension to apply a simple low-pass filter to a source. When I call efx.alFilterf to set the AL_LOWPASS_GAIN or AL_LOWPASS_GAINHF parameters, the function call succeeds and alGetError() returns AL_NO_ERROR. However, there is no audible change in the sound. The sound is not muffled or quieted.
Key Symptom
After performing a completely clean installation of both OpenAL Soft and PyOpenAL (from pip), my application logs the following warning on startup:
WARNING - PyOpenAL 'efx' submodule not found. EFX calls will attempt 'al.' prefix if EFX is device-supported.
This suggests to me that the EFX extension is not being built or included correctly during the pip installation, even though a fully capable OpenAL Soft backend is present. The functions appear to exist on the base al object, but they do not seem to be connected to the driver correctly, as they fail silently.
Minimal, Reproducible Test Case
I have created the following minimal script that completely reproduces the problem in isolation. When this script is run, the second sound that is played should be very quiet and heavily muffled, but on my system, it sounds identical to the first.
Python
Copy code
import time
import ctypes
import os
import sys

This script requires PyOpenAL and soundfile (pip install soundfile)

try:
from openal import al, alc
# Attempt to import efx, but handle the case where it might not exist
try:
from openal import efx
print("--- EFX submodule found. ---")
except ImportError:
print("--- EFX submodule not found, using 'al' as fallback. ---")
efx = al
import soundfile
except ImportError:
print("ERROR: PyOpenAL or soundfile is not installed.")
print("Please run: pip install PyOpenAL soundfile")
sys.exit()

Helper function to check for OpenAL errors

def check_al_error(tag=""):
error = al.alGetError()
if error != al.AL_NO_ERROR:
# Assuming alGetString is available on the base 'al' object
print(f"!!! OpenAL Error ({tag}):", al.alGetString(error).decode())
return True
return False

Main Test

def run_efx_test():
print("--- Starting EFX Low-Pass Filter Test ---")

device = alc.alcOpenDevice(None)
if not device:
    print("!!! Could not open audio device.")
    return

context = alc.alcCreateContext(device, None)
if not context or not alc.alcMakeContextCurrent(context):
    print("!!! Could not create or set audio context.")
    alc.alcCloseDevice(device)
    return

print("--- OpenAL Initialized ---")

if not alc.alcIsExtensionPresent(device, b"ALC_EXT_EFX"):
    print("!!! EFX Extension not supported on this device. Test cannot continue.")
    alc.alcMakeContextCurrent(None)
    alc.alcDestroyContext(context)
    alc.alcCloseDevice(device)
    return
    
print("--- EFX Extension Supported ---")

sound_file = 'test_sound.wav'
if not os.path.exists(sound_file):
    print(f"!!! ERROR: Cannot find '{sound_file}'.")
    return
    
data, samplerate, channels, format_type = None, None, None, None
try:
    with soundfile.SoundFile(sound_file, 'r') as sf:
        data = sf.read(dtype='int16')
        samplerate = sf.samplerate
        channels = sf.channels
        format_type = {
            (1, 'PCM_16'): al.AL_FORMAT_MONO16,
            (2, 'PCM_16'): al.AL_FORMAT_STEREO16
        }.get((channels, sf.subtype), None)

    if format_type is None: return
    buffer_data = (ctypes.c_short * len(data))(*data)
    buffer_size = ctypes.sizeof(buffer_data)
    print(f"--- Loaded '{sound_file}' ---")
except Exception as e:
    print(f"!!! Failed to load sound file: {e}")
    return

source = al.alGenSources(1)
buffer = al.alGenBuffers(1)
al.alBufferData(buffer, format_type, buffer_data, buffer_size, samplerate)
al.alSourcei(source, al.AL_BUFFER, buffer)
if check_al_error("Creating source/buffer"): return

effect_slot = efx.alGenAuxiliaryEffectSlots(1)
null_effect = efx.alGenEffects(1)
efx.alEffecti(null_effect, efx.AL_EFFECT_TYPE, efx.AL_EFFECT_NULL)
lowpass_filter = efx.alGenFilters(1)
efx.alFilteri(lowpass_filter, efx.AL_FILTER_TYPE, efx.AL_FILTER_LOWPASS)
efx.alAuxiliaryEffectSloti(effect_slot, efx.AL_EFFECTSLOT_EFFECT, null_effect)
if check_al_error("Creating EFX objects"): return
print("--- EFX Objects Created Successfully ---")

# --- TEST 1: Play sound normally ---
print("\n>>> Playing sound normally...")
al.alSourcePlay(source)
while al.alGetSourcei(source, al.AL_SOURCE_STATE) == al.AL_PLAYING:
    time.sleep(0.1)
print("...Finished.")
time.sleep(1)

# --- TEST 2: Play sound muffled ---
print("\n>>> Applying muffle and playing again...")

muffle_gain = 0.1  # Should be very quiet
muffle_gain_hf = 0.1 # Should be very muffled
efx.alFilterf(lowpass_filter, efx.AL_LOWPASS_GAIN, muffle_gain)
efx.alFilterf(lowpass_filter, efx.AL_LOWPASS_GAINHF, muffle_gain_hf)
print(f"--- Set filter GAIN to {muffle_gain} and GAINHF to {muffle_gain_hf} ---")
if check_al_error("Setting filter parameters"): return

al.alSource3i(source, efx.AL_AUXILIARY_SEND_FILTER, effect_slot, 0, lowpass_filter)
if check_al_error("Connecting source to filter"): return
print("--- Connected source to filter. Playing... ---")

al.alSourcePlay(source)
while al.alGetSourcei(source, al.AL_SOURCE_STATE) == al.AL_PLAYING:
    time.sleep(0.1)
print("...Finished.")

# Cleanup
print("\n--- Cleaning up ---")
al.alSourcei(source, al.AL_BUFFER, 0)
al.alDeleteSources(1, ctypes.byref(ctypes.c_uint(source)))
al.alDeleteBuffers(1, ctypes.byref(ctypes.c_uint(buffer)))
efx.alDeleteFilters(1, ctypes.byref(ctypes.c_uint(lowpass_filter)))
efx.alDeleteEffects(1, ctypes.byref(ctypes.c_uint(null_effect)))
efx.alDeleteAuxiliaryEffectSlots(1, ctypes.byref(ctypes.c_uint(effect_slot)))
alc.alcMakeContextCurrent(None)
alc.alcDestroyContext(context)
alc.alcCloseDevice(device)
print("--- Test Complete ---")

if name == 'main':
run_efx_test()
Thank you for any guidance you can offer on how to achieve a fully functional EFX installation on Windows.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions