GithubHelp home page GithubHelp logo

zuzu-typ / pyopenal Goto Github PK

View Code? Open in Web Editor NEW

This project forked from forrestv/python-openal

34.0 4.0 8.0 812 KB

OpenAL bindings and audio playback for Python

License: The Unlicense

Python 100.00%
openal openal-soft ogg ogg-vorbis ogg-flac opus-codec 3d-audio wav-audio

pyopenal's Introduction

PyOpenAL

PyOpenAL provides OpenAL bindings for python as well as an interface to them.

It also provides a simple way to play WAVE and - if PyOgg is installed - even OGG Vorbis, OGG Opus and FLAC files.

You can install it using the PyPI:

pip install PyOpenAL

PyOpenAL requires a shared OpenAL library (e.g. OpenAL32.dll), one of which the Windows distributions already come with (OpenAL Soft by kcat). You can use the official OpenAL library (deprecated) or any other compatible library, like the aforementioned OpenAL Soft library, which is still actively developed.

PyOpenAL provides OpenAL bindings, as you would find them in C++, meaning you can follow any OpenAL C++ tutorial with Python. OpenAL's methods expect C types as arguments, which means you will have to convert Python's types to C types using ctypes if you want to use them directly. Don't worry though, PyOpenAL can be used without the need to do that.

I removed the support for ALUT, because it is basically impossible to build nowadays. If you want ALUT support, please use the original python-openal from forrestv

Examples

Playing a wave file
# import PyOpenAL (will require an OpenAL shared library)
from openal import * 

# import the time module, for sleeping during playback
import time

# open our wave file
source = oalOpen("test.wav")

# and start playback
source.play()

# check if the file is still playing
while source.get_state() == AL_PLAYING:
	# wait until the file is done playing
	time.sleep(1)
	
# release resources (don't forget this)
oalQuit()
Playing an OGG Opus file (with PyOgg)
# This requires PyOgg to be installed ( pip install pyogg )
from openal import * 

import time

source = oalOpen("test.opus")

source.play()

while source.get_state() == AL_PLAYING:
	time.sleep(1)
	
# remember, don't forget to quit
	oalQuit()
Streaming a file
from openal import *

# The following two lines are optional. You can use them, if you can't update the stream frequently enough
# This one specifies how much data is supposed to be stored in each buffer (only applies for OGG Vorbis and Opus files)
pyoggSetStreamBufferSize(4096*4)
# This one sets the maximum amount of buffers to be created at a time
oalSetStreamBufferCount(4)

sourceStream = oalStream("test.ogg")

sourceStream.play()

while sourceStream.get_state() == AL_PLAYING:
	# do stuff
	[...]
	
	# update the stream (load new data)
	# if you don't do this repeatedly, the stream will suffocate
	sourceStream.update()
	
oalQuit()

Using OpenAL functions

# Here we only import OpenAL's functions 
# (in case you don't need or want PyOpenAL's functions and classes)
from openal.al import *
from openal.alc import *

[...]

# It's as simple as that:
alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED)

# Or a little more complicated, it really depends:
alSourceUnqueueBuffers(source_id, 1, ctypes.pointer(ctypes.c_uint(buffer_ids[id])))

[...]

Reference (for PyOpenAL's own classes and functions)

<method> oalInit(device_specifier = None, context_attr_list = None) -> None
	# initializes PyOpenAL
	# this is called automatically, unless you set OAL_DONT_AUTO_INIT
		<c_char_p> device_specifier # you can set a custom device with this
		<c_int_p> context_attr_list # you can pass additional arguments to context creation
	
<method> oalGetInit() -> <bool> initialized
	# finds out wether or not PyOpenAL is initialized
		
<method> oalQuit() -> None
	# exits out of OpenAL and destroys all existing Sources and Buffers
	
<method> oalOpen(path, ext_hint = None) -> Source
	# loads a WAVE / Ogg Vorbis / Ogg Opus / FLAC file to a Source object
		<str> path # path to the file (relative or absolute)
		<str> ext_hint # if the filetype is not wav, wave, ogg, vorbis or opus, you should supply a hint to the extension
		
<method> oalStream(path, ext_hint = None) -> SourceStream
	# loads a WAVE /  Ogg Vorbis / Ogg Opus / FLAC file to a SourceStream object, that streams the data
	# you should use this instead of Source for Soundtracks or other long tracks (as it uses less memory)
	# you will have to update it frequently to avoid suffocation of the stream
	# you can neither rewind nor loop a SourceStream (currently)
		<str> path # path to the file (relative or absolute)
		<str> ext_hint # if the filetype is not ogg, vorbis or opus, you should supply a hint to the extension

<class> openal.Listener()
	# class for hadling the listener (you)
	# an instance of this class is created when you import PyOpenAL.
	# it can be retrieved using oalGetListener()
	# you do NOT have to create an instance yourself
	
	<method> Listener.move(v)
		# move the listener by v
			<tuple or list> v # a translation vector represented as (x, y, z)
	
	<method> Listener.move_to(v)
		# set the listener's position to v
			<tuple or list> v # a position vector represented as (x, y, z)
			
	<method> Listener.set_position(v)
		# see Listener.move_to
		
	<method> Listener.set_orientation(v)
		# sets the listener's orientation to v
			<tuple or list> v # orientation represented as (frontX, frontY, frontZ, upX, upY, upZ)
			
	<method> Listener.set_velocity(v)
		# sets the listener's velocity to v
			<tuple or list> v # velocity represented as (vX, vY, vZ)
			
	<method> Listener.set_gain(value)
		# sets the listener's gain (volume) to value
			<float> value
			
<method> openal.oalGetListener() -> Listener
	# returns PyOpenAL's Listener
	
<class> openal.Source(buffer_ = None)
	# class for managing a source
		<Buffer> buffer_ # where this source finds it's data
		
	<method> Source.play() -> None
		# starts playing
		
	<method> Source.stop() -> None
		# stops playback
		
	<method> Source.pause() -> None
		# pauses playback (use play to resume)
		
	<method> Source.rewind() -> None
		# goes back to the start of the track
		
	<float> Source.pitch
		# the source's current pitch
		
	<float> Source.gain
		# the source's current gain
		
	<float> Source.max_distance
		# the source's current max_distance
		
	<float> Source.rolloff_factor
		# the source's current rolloff_factor
		
	<float> Source.reference_distance
		# the source's current reference_distance
		
	<float> Source.min_gain
		# the source's current min_gain
		
	<float> Source.max_gain
		# the source's current max_gain
		
	<float> Source.cone_outer_angle
		# the source's current cone_outer_angle
		
	<float> Source.cone_inner_angle
		# the source's current cone_inner_angle
		
	<float> Source.cone_outer_gain
		# the source's current cone_outer_gain
		
	<vec3> Source.position
		# the source's current position (use .x, .y, and .z to access it's items or .toTuple())
		
	<vec3> Source.velocity
		# the source's current velocity (use .x, .y, and .z to access it's items or .toTuple())
		
	<vec3> Source.direction
		# the source's current velocity (use .x, .y, and .z to access it's items or .toTuple())
		
	<bool> Source.relative
		# wether or not the source is relative to the listener
		
	<int> Source.source_type
		# the source's current source_type (either AL_UNDETERMINED, AL_STATIC or AL_STREAMING)
		
	<method> Source.set_pitch(value) -> None
		# set the pitch for this source
			<float> value # pitch
			
	<method> Source.set_gain(value) -> None
		# set the gain for this source
			<float> value # gain
			
	<method> Source.set_max_distance(value) -> None
		# set the max_distance for this source
			<float> value # max_distance
			
	<method> Source.set_rolloff_factor(value) -> None
		# set the rolloff_factor for this source
			<float> value # rolloff_factor
			
	<method> Source.set_reference_distance(value) -> None
		# set the reference_distance for this source
			<float> value # reference_distance
			
	<method> Source.set_min_gain(value) -> None
		# set the min_gain for this source
			<float> value # min_gain
			
	<method> Source.set_max_gain(value) -> None
		# set the max_gain for this source
			<float> value # max_gain
			
	<method> Source.set_cone_outer_gain(value) -> None
		# set the cone_outer_gain for this source
			<float> value # cone_outer_gain
			
	<method> Source.set_cone_inner_angle(value) -> None
		# set the cone_inner_angle for this source
			<float> value # cone_inner_angle
			
	<method> Source.set_cone_outer_angle(value) -> None
		# set the cone_outer_angle for this source
			<float> value # cone_outer_angle
			
	<method> Source.set_position(value) -> None
		# set the position for this source
			<tuple or list> value # position
			
	<method> Source.set_velocity(value) -> None
		# set the velocity for this source
			<tuple or list> value # velocity
			
	<method> Source.set_looping(value) -> None
		# set the looping for this source
			<bool> value # looping
			
	<method> Source.set_direction(value) -> None
		# set the direction for this source
			<tuple or list> value # direction
			
	<method> Source.set_source_relative(value) -> None
		# set the source_relative for this source
			<bool> value # source_relative
			
	<method> Source.set_source_type(value) -> None
		# set the source_type for this source
			<int> value # source_type
			
	<method> Source.set_buffer(buffer_) -> None
		# set the buffer for this source
			<Buffer> value # buffer
			
	<method> Source.get_state() -> <int> state of the Source (e.g. AL_PLAYING, AL_STOPPED,etc.)
		# get the current state of the source
		
<class> openal.SourceStream(stream)
	# class for managing a source in streaming mode
		<PYOGG-Stream> stream # where the buffer gets it's data from
		
	<method> update() -> <bool> playing
		# load more data to play (if necessary). 
		# if you don't call this frequently, the source will stop playing after it reaches the last buffer
		# in that case you should consider increasing the buffer size or amount using oalSetStreamBufferCount or pyoggSetStreamBufferSize
	
<class> openal.Buffer(*args)
	# class for managing OpenAL buffers
		<File or tuple or list> args # what to fill the buffer with (either y PyOgg file or a tuple / list with [format, data, length, frequency])
	
	<method> Buffer.fill(*args) -> None
		# fill the buffer
			<File or tuple or list> args # what to fill the buffer with (either y PyOgg file or a tuple / list with [format, data, length, frequency])
		
	<method> Buffer.destroy() -> None
		# destroy this buffer
		
<class> openal.StreamBuffer(stream, count)
	# class for managing OpenAL buffers for audio streaming
		<PYOGG-Stream> stream # from where to get the data
		<int> count # how many buffers to create (usually OAL_STREAM_BUFFER_COUNT, which is 2 initially)
	
	<method> Buffer.fill_buffer(id_) -> None
		# fill the buffer
			<int> id_ # load some data into this buffer
		
	<method> Buffer.destroy() -> None
		# destroy this streambuffer
		
<method> oalGetEnum(enum) -> <str> ENUM
	# returns a literal representation of enum 
		<int> enum # AL_ or ALC_ enumerator
		
<method> oalGetALEnum(enum) -> <str> ENUM
	# returns a literal representation of an AL_ enum 
		<int> enum # AL_ enumerator
		
<method> oalGetALCEnum(enum) -> <str> ENUM
	# returns a literal representation of an ALC_ enum 
		<int> enum # ALC_ enumerator
		
<method> oalGetContext() -> <int> context
	# returns the context used by PyOpenAL
	
<method> oalGetDevice() -> <int> device
	# returns the device used by PyOpenAL
	
<method> oalSetAutoInit(val) -> None
	# changes wether or not PyOpenAL initializes automatically
		<bool> val # wether or not to auto-init
		
	# The other methods and variables are the same as in Source 
	# (note that you can't loop, because the file can be read only once (by now))

pyopenal's People

Contributors

biglizards avatar forrestv avatar gudvinr avatar nilsnoreyson avatar rspencer01 avatar zuzu-typ avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

pyopenal's Issues

Playing short wave streams results in errors

The code

import openal
from time import *
s = openal.oalStream('short.wav')
s.play()
for _ in xrange(100):
    s.update()
    sleep(0.1)

results in error

  File "/home/robert/src/PyOpenAL/openal/__init__.py", line 841, in oalStream
    return SourceStream(stream)
  File "/home/robert/src/PyOpenAL/openal/__init__.py", line 698, in __init__
    alSourceQueueBuffers(self.id, OAL_STREAM_BUFFER_COUNT, self.buffer.buffer_ids)
  File "/home/robert/src/PyOpenAL/openal/_al.py", line 120, in al_check_error
    raise ALError(al_enums[err][0])
openal._al.ALError: AL_INVALID_OPERATION

But replacing 'short.wav' with long.wav does not. (obviously short.wav is a short (under 1s) sound file, and long.wav is longer).

Playing a vorbis file crashes python

This looks related to TeamPyOgg/PyOgg#10 but might not be.
It's not crashing when loading, but when playing - using the default windows openal32.dll.
It works just fine playing opus or wave files.

Here is the code and file used :

import time
from openal import *

source = oalOpen('test.ogg')
source.play()

while source.get_state() == AL_PLAYING:
	time.sleep(1)

oalQuit()

https://files.catbox.moe/wr9w16.ogg (github doesn't allow ogg uploads)

Don't destroy the buffer when a source is destroyed

Sources seem to be a limited resources. In my application, I get an out of memory as soon as the number of sources reaches a critical amount.
Consequently, I destroyed my sources as soon as they were done playing (e.g. AL_STOPPED). This lead to an exception when calling oalQuit, for two reasons:

  1. Source appends to the _items list, but doesn't remove itself on destruction
  2. Destroying a source also destroys the underlying buffer. That's not the intention of sources, however. Buffers can be kept and re-used. And there might be more than one source for every buffer (at different positions, for example).

This should both be fixed.

No documentation

Hi,

this repo I couldn't find any documents about PyALL in this repo.

Where can I find documents describing PyALL in detail?

PyOpenAL works through IDLE 3.7.9, but doesn't work from Visual Studio 2019 or Double-Clicking python file.

*.wav file plays when program is run through through IDLE, but if I double-click the python file or use Visual Studio, the console/IDLE prints:

AL lib: (EE) SetChannelMap: Failed to match front-center channel (2) in channel map
AL lib: (EE) alc_cleanup: 1 device not closed

I am using the following modules in my project:
tkinter
turtle
Pillow 8.0.1
time
PyOpenAL 0.7.11a1
random
sys
PyOpenGL 3.1.5

I am using Python version 3.7.9, 64-bit.

If it helps, my system specs are:
Core i5 10th gen, 64-bit
8GB RAM
Intel UHD graphics + Nvidia GeForce GTX 1650 Ti (Notebook)
512GB SSD

v0.7.10a1 breaks library loading on linux

I'm using void linux, but this probably happens on other distros.

https://github.com/Zuzu-Typ/PyOpenAL/blob/master/openal/library_loader.py#L54

Basically, os.path.realname prepends the current directory to libopenal.so, which stops it from loading since that file doesn't exist.

>>> ctypes.CDLL(ctypes.util.find_library('openal'))
<CDLL 'libopenal.so', handle 55bd434836a0 at 0x7f317de76ba8>

>> ctypes.CDLL(os.path.realpath(ctypes.util.find_library('openal')))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/usr/lib/python3.6/ctypes/__init__.py", line 348, in __init__
    self._handle = _dlopen(self._name, mode)
OSError: /whatever/is/the/current/directory/libopenal.so: cannot open shared object file: No such file or directory

Library loading overcomplicated

Library loading isn't great right now. It works, but it does unnecessary things and changes things that should not be changed.

In library_loader.py:

load_other

PATH change will not affect library resolution at all on Linux, macOS, or FreeBSD. The semicolon also is not how they do PATHs. The current code only serves to destroy the last entry of $PATH.

arch_style: Is this permutation really necessary?

On Windows, the standard name is always just "OpenAL32", regardless of architecture, because history. With how al_lib loads things, the correct value is empty in the common case. In the uncommon case a user is really shipping and naming the dll, the arch bits might make sense, but all these aliases? How hard is it to have the user fix a name?

On "Other", the basic name is just "openal". When 32 and 64 coexist, they usually reside in different folders, but can also huddle together in the same fat binary, or have a name difference. In any case, the loader should not touch it, because Python's magic knows better. It will consult ldconfig on os.name == "posix", which is the real authority and has arcane magic better than everyone here.

_windows_styles

The lib{} bit is surprisingly justified, because MinGW calls it libopenal-1.dll. It still would not work though.

But _dynamic? Why would anyone name a dll that? If it's static, it would not be a dll...

_other_styles

The lib{} bit is not justified because Python knows to add it on mac and to, again, consult the Old Ones on other systems.

In al_lib.py

soft_oal being in front is a bit opinionated for an "OpenAL" library. alsoft is very cool, but doing this just doesn't do what it says on the bottle. The constants for alsoft's fun extensions also aren't there.


Recommendation:

  • remove all the styles except lib{} for win.
  • remove the path handling completely. it's not even used by al_lib!
  • define a way to bypass the search logic and call ctypes.CDLL directly at import for actually usable customization (you don't even need find_library: it's either there or not there, and LoadLibrary / dlopen knows more about the paths). Maybe an environmental variable, since passing stuff at import is pretty difficult.

PyOpenAL is broken on pyinstaller (or in general)

In library_loader.py, you have an if statement that checks if the library to be loaded is a file (line 73). This check is dangerous, because the CDLL constructor is actually able to load files for which the if fails. This happened to me when I tried to package my program with the popular tool pyinstaller.

To fix this, I think we can just remove the if, or did you have special requirements for the inclusion?

Python 3.8: List does not iterate over all members when mutated during loop

for buffer in _buffers:

The above line leaves dangling references in python 3.8 which causes OpenAL to crash.

To reproduce:

for iteration in range(2):
    if not openal.oalGetInit():
            openal.oalInit()
    source1 = openal.oalOpen(path_to_a_sound)
    source2 = openal.oalOpen(path_to_a_sound) 
    source1.play()
    source2.play()
    openal.oalQuit()
    print(len(openal._buffers))  # This will have length 1.

The above code will crash on iteration 2 with INVALID_OPERATION.

Stereo audio not affected

Im trying to run this simple code, positioning audio to the left but it still sounds the same. What am i doing wrong?I expected to hear the audio in my right ear but it sounds normal.

from openal import * 
import time

# open our wave file
source = oalOpen(f)
source.set_position((0,0,0))

x=oalGetListener()
x.set_position=((-5,0,0))

# and start playback
source.play()

check if the file is still playing
while source.get_state() == AL_PLAYING:
    # wait until the file is done playing
    time.sleep(1)

oalQuit()

Audio Device Selection

Hello.

I'm working on audio 3D renderer with Python and PyOpenAL.

I have problems :

  • I can't list all audio device from my windos PC
  • So I can't select specific audio device for rendering audio
  • Will 3D audio spatialisation work with not 7.1 card (example : 10 channels pro audio board) ?

the 7.1 integrated soundboard work when selected by default on windows but I have to select another specific audio device and use PRO Audio USB Board

Many thanks for your answer and help :)

Problem with pyinstaller

OS : W10
Python : 3.9.6

Hello,

I'm trying to package my software with pyinstaller, including PyOpenAL, installed trough pip.
Works great, but when running the exe generated, I get an error in the script 'al_lib.py' at line 12.

It seems that find OpenAl fails.

Entire traceback :

Traceback (most recent call last):
  File "main.py", line 1, in <module>
  File "<frozen importlib._bootstrap>", line 1007, in _find_and_load
  File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
  File "PyInstaller\loader\pyimod03_importers.py", line 495, in exec_module
  File "imports\_imports.py", line 1, in <module>
  File "<frozen importlib._bootstrap>", line 1007, in _find_and_load
  File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
  File "PyInstaller\loader\pyimod03_importers.py", line 495, in exec_module
  File "openal\__init__.py", line 1, in <module>
  File "<frozen importlib._bootstrap>", line 1007, in _find_and_load
  File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
  File "PyInstaller\loader\pyimod03_importers.py", line 495, in exec_module
  File "openal\al.py", line 6, in <module>
  File "<frozen importlib._bootstrap>", line 1007, in _find_and_load
  File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
  File "PyInstaller\loader\pyimod03_importers.py", line 495, in exec_module
  File "openal\al_lib.py", line 12, in <module>
openal.library_loader.ExternalLibraryError: OpenAL library couldn't be found

Any thoughts on how to fix this ?

DLL load Error

Hi ,
How can I load pyopenal32.dll or soft_oal.dll.
I'm getting an error.

I create a folder named lib inside the pyopenal folder.
or lib \ 32bit
or lib \ 64bit
I copy openal32.dll to these folders, I get an error.

I am copying the dll to a path I added to sys.path. not happening again.

thanks.

  File "userConfig\addons\speedExplorer\globalPlugins\SpeedExplorer\openal\__init__.py", line 1, in <module>
    from .al import *
  File "userConfig\addons\speedExplorer\globalPlugins\SpeedExplorer\openal\al.py", line 6, in <module>
    from .al_lib import lib
  File "userConfig\addons\speedExplorer\globalPlugins\SpeedExplorer\openal\al_lib.py", line 12, in <module>
    raise ExternalLibraryError("OpenAL library couldn't be found")
openal.library_loader.ExternalLibraryError: OpenAL library couldn't be found

No setup.py file

This is a package on PyPI, but the setup.py is not present in the repo.

Could I have an example using audio effects?

Hello, first of all thanks for this module, which also reproduces .ogg files in an easy way. I haven't found in the examples something that helps to understand how to reproduce audio effects, such as echo, reverb, or adjust the playback speed of the file (slower or faster). Could you give me an example of this?
Thank you.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.