mirror of
https://github.com/McSinyx/palace
synced 2023-12-14 09:02:59 +01:00
Prove the concept by the first few examples
This commit is contained in:
parent
f7ef057b14
commit
a548e270e8
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -6,6 +6,9 @@ __pycache__/
|
|||
# C extensions
|
||||
*.so
|
||||
|
||||
# C++ files compiled by Cython
|
||||
*.cpp
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
|
|
11
README.md
11
README.md
|
@ -1,2 +1,13 @@
|
|||
# archaicy
|
||||
Pythonic wrapper for Audio Library Utilities REtooled in Cython
|
||||
|
||||
## Prerequisites
|
||||
Archaicy requires Python 3.5+ and [alure](https://github.com/kcat/alure).
|
||||
Currently only GNU/Linux is supported. If you want to help package for
|
||||
other operating systems, head to issue #1.
|
||||
|
||||
## Installation
|
||||
```sh
|
||||
python setup.py build
|
||||
python setup.py install
|
||||
```
|
||||
|
|
488
alure.pxd
Normal file
488
alure.pxd
Normal file
|
@ -0,0 +1,488 @@
|
|||
# Cython declarations of alure
|
||||
# Copyright (C) 2019 Nguyễn Gia Phong
|
||||
#
|
||||
# This file is part of archaicy.
|
||||
#
|
||||
# archaicy is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
#
|
||||
# archaicy is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with archaicy. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from libc.stdint cimport uint64_t
|
||||
from libcpp cimport bool, nullptr_t
|
||||
from libcpp.memory cimport shared_ptr
|
||||
from libcpp.pair cimport pair
|
||||
from libcpp.string cimport string
|
||||
from libcpp.vector cimport vector
|
||||
|
||||
|
||||
cdef extern from '<future>' namespace 'std' nogil:
|
||||
cdef cppclass shared_future[R]:
|
||||
pass
|
||||
|
||||
|
||||
cdef extern from '<alc.h>' nogil:
|
||||
cdef int ALC_TRUE
|
||||
|
||||
|
||||
cdef extern from '<AL/alure2-alext.h>' nogil:
|
||||
cdef int ALC_HRTF_SOFT
|
||||
cdef int ALC_HRTF_ID_SOFT
|
||||
|
||||
|
||||
cdef extern from '<AL/alure2.h>' namespace 'alure::PlaybackName' nogil:
|
||||
cdef enum PlaybackName 'alure::PlaybackName':
|
||||
Basic
|
||||
Full
|
||||
|
||||
|
||||
cdef extern from '<AL/alure2.h>' namespace 'alure' nogil:
|
||||
# Type aliases:
|
||||
# char*: string
|
||||
# ALfloat: float
|
||||
# ALsizei: int
|
||||
# ALuint: unsigned
|
||||
# Vector: vector
|
||||
# ArrayView: vector
|
||||
# String: string
|
||||
# StringView: string
|
||||
# SharedPtr: shared_ptr
|
||||
# SharedFuture: shared_future
|
||||
|
||||
# Structs:
|
||||
cdef cppclass AttributePair:
|
||||
int mAttribute
|
||||
int mValue
|
||||
|
||||
cdef cppclass FilterParams:
|
||||
pass
|
||||
cdef cppclass SourceSend:
|
||||
pass
|
||||
|
||||
|
||||
# Enum classes:
|
||||
cdef cppclass SampleType:
|
||||
pass
|
||||
# The following relies on C++ implicit conversion from char* to string.
|
||||
cdef const string get_sample_type_name 'GetSampleTypeName'(SampleType) except +
|
||||
|
||||
cdef cppclass ChannelConfig:
|
||||
pass
|
||||
cdef const string get_channel_config_name 'GetChannelConfigName'(ChannelConfig) except +
|
||||
cdef unsigned frames_to_bytes 'FramesToBytes'(unsigned, ChannelConfig, SampleType) except +
|
||||
cdef unsigned bytes_to_frames 'BytesToFrames'(unsigned, ChannelConfig, SampleType)
|
||||
|
||||
cdef cppclass DeviceEnumeration:
|
||||
pass
|
||||
|
||||
cdef cppclass DefaultDeviceType:
|
||||
pass
|
||||
|
||||
cdef cppclass DistanceModel:
|
||||
pass
|
||||
|
||||
cdef cppclass Spatialize:
|
||||
pass
|
||||
|
||||
|
||||
# Helper classes
|
||||
cdef cppclass Vector3:
|
||||
pass
|
||||
cdef cppclass Version:
|
||||
pass
|
||||
|
||||
|
||||
# Opaque class implementations:
|
||||
cdef cppclass DeviceManagerImpl:
|
||||
pass
|
||||
cdef cppclass DeviceImpl:
|
||||
pass
|
||||
cdef cppclass ContextImpl:
|
||||
pass
|
||||
cdef cppclass ListenerImpl:
|
||||
pass
|
||||
cdef cppclass BufferImpl:
|
||||
pass
|
||||
cdef cppclass SourceImpl:
|
||||
pass
|
||||
cdef cppclass SourceGroupImpl:
|
||||
pass
|
||||
cdef cppclass AuxiliaryEffectSlotImpl:
|
||||
pass
|
||||
cdef cppclass EffectImpl:
|
||||
pass
|
||||
|
||||
|
||||
# Available class interfaces:
|
||||
cdef cppclass DeviceManager:
|
||||
@staticmethod
|
||||
DeviceManager get_instance 'getInstance'() except +
|
||||
|
||||
DeviceManager()
|
||||
DeviceManager(const DeviceManager&)
|
||||
DeviceManager(DeviceManager&&)
|
||||
|
||||
DeviceManager& operator=(const DeviceManager&)
|
||||
DeviceManager& operator=(DeviceManager&&)
|
||||
DeviceManager& operator=(nullptr_t)
|
||||
|
||||
bool operator bool()
|
||||
|
||||
bool query_extension 'queryExtension'(const string&) except +
|
||||
|
||||
vector[string] enumerate(DeviceEnumeration) except +
|
||||
string defaultDeviceName(DefaultDeviceType) except +
|
||||
|
||||
Device open_playback 'openPlayback'() except +
|
||||
Device open_playback 'openPlayback'(const string&) except +
|
||||
|
||||
|
||||
cdef cppclass Device:
|
||||
ctypedef DeviceImpl* handle_type
|
||||
|
||||
Device() # nil
|
||||
Device(DeviceImpl*)
|
||||
Device(const Device&)
|
||||
Device(Device&&)
|
||||
|
||||
Device& operator=(const Device&)
|
||||
Device& operator=(Device&&)
|
||||
|
||||
bool operator==(const Device&)
|
||||
bool operator!=(const Device&)
|
||||
bool operator<=(const Device&)
|
||||
bool operator>=(const Device&)
|
||||
bool operator<(const Device&)
|
||||
bool operator>(const Device&)
|
||||
|
||||
bool operator bool()
|
||||
|
||||
handle_type get_handle 'getHandle'()
|
||||
|
||||
string get_name 'getName'() except +
|
||||
string get_name 'getName'(PlaybackName) except +
|
||||
|
||||
bool query_extension 'queryExtension'(const string&) except +
|
||||
|
||||
Version get_alc_version 'getALCVersion'() except +
|
||||
Version get_efx_version 'getEFXVersion'() except +
|
||||
|
||||
unsigned get_frequency 'getFrequency'() except +
|
||||
unsigned get_max_auxiliary_sends 'getMaxAuxiliarySends'() except +
|
||||
|
||||
vector[string] enumerate_hrtf_names 'enumerateHRTFNames'() except +
|
||||
bool is_hrtf_enabled 'isHRTFEnabled'() except +
|
||||
string get_current_hrtf 'getCurrentHRTF'() except +
|
||||
|
||||
void reset(vector[AttributePair]) except +
|
||||
|
||||
Context create_context 'createContext'() except +
|
||||
Context create_context 'createContext'(vector[AttributePair]) except +
|
||||
|
||||
void pause_dsp 'pauseDSP'() except +
|
||||
void resume_dsp 'resumeDSP'() except +
|
||||
|
||||
# get_clock_time
|
||||
|
||||
void close() except +
|
||||
|
||||
|
||||
cdef cppclass Context:
|
||||
ctypedef ContextImpl* handle_type
|
||||
|
||||
Context() # nil
|
||||
Context(ContextImpl*)
|
||||
Context(const Context&)
|
||||
Context(Context&&)
|
||||
|
||||
Context& operator=(const Context&)
|
||||
Context& operator=(Context&&)
|
||||
|
||||
bool operator==(const Context&)
|
||||
bool operator!=(const Context&)
|
||||
bool operator<=(const Context&)
|
||||
bool operator>=(const Context&)
|
||||
bool operator<(const Context&)
|
||||
bool operator>(const Context&)
|
||||
|
||||
bool operator bool()
|
||||
|
||||
handle_type get_handle 'getHandle'()
|
||||
|
||||
@staticmethod
|
||||
void make_current 'MakeCurrent'(Context) except +
|
||||
@staticmethod
|
||||
Context get_current 'GetCurrent'() except +
|
||||
|
||||
@staticmethod
|
||||
void make_thread_current 'MakeThreadCurrent'(Context) except +
|
||||
@staticmethod
|
||||
Context get_thread_current 'GetThreadCurrent'() except +
|
||||
|
||||
void destroy() except +
|
||||
|
||||
Device get_device 'getDevice'() except +
|
||||
|
||||
void start_batch 'startBatch'() except +
|
||||
void end_batch 'endBatch'() except +
|
||||
|
||||
Listener get_listener 'getListener'() except +
|
||||
|
||||
shared_ptr[MessageHandler] set_message_handler 'setMessageHandler'(shared_ptr[MessageHandler]) except +
|
||||
shared_ptr[MessageHandler] get_message_handler 'getMessageHandler'() except +
|
||||
|
||||
# set_async_wake_interval
|
||||
# get_async_wake_interval
|
||||
|
||||
shared_ptr[Decoder] create_decoder 'createDecoder'(string) except +
|
||||
|
||||
bool is_supported 'isSupported'(ChannelConfig, SampleType) except +
|
||||
|
||||
vector[string] get_available_resamplers 'getAvailableResamplers'() except +
|
||||
int get_default_resampler_index 'getDefaultResamplerIndex'() except +
|
||||
|
||||
Buffer get_buffer 'getBuffer'(string) except +
|
||||
shared_future[Buffer] get_buffer_async 'getBufferAsync'(string) except +
|
||||
|
||||
void precache_buffers_async 'precacheBuffersAsync'(vector[string]) except +
|
||||
|
||||
Buffer create_buffer_from 'createBufferFrom'(string, shared_ptr[Decoder]) except +
|
||||
shared_future[Buffer] create_buffer_async_from 'createBufferAsyncFrom'(string, shared_ptr[Decoder]) except +
|
||||
|
||||
Buffer find_buffer 'findBuffer'(string) except +
|
||||
shared_future[Buffer] find_buffer_async 'findBufferAsync'(string) except +
|
||||
|
||||
void remove_buffer 'removeBuffer'(string) except +
|
||||
void remove_buffer 'removeBuffer'(Buffer) except +
|
||||
|
||||
Source create_source 'createSource'() except +
|
||||
AuxiliaryEffectSlot create_auxiliary_effect_slot 'createAuxiliaryEffectSlot'() except +
|
||||
Effect create_effect 'createEffect'() except +
|
||||
SourceGroup create_source_group 'createSourceGroup'() except +
|
||||
|
||||
void set_doppler_factor 'setDopplerFactor'(float) except +
|
||||
void set_speed_of_sound 'setSpeedOfSound'(float) except +
|
||||
void set_distance_model 'setDistanceModel'(DistanceModel) except +
|
||||
|
||||
void update() except +
|
||||
|
||||
|
||||
cdef cppclass Listener:
|
||||
pass
|
||||
|
||||
|
||||
cdef cppclass Buffer:
|
||||
ctypedef BufferImpl* handle_type
|
||||
|
||||
Buffer()
|
||||
Buffer(BufferImpl*)
|
||||
Buffer(const Buffer&)
|
||||
Buffer(Buffer&&)
|
||||
|
||||
Buffer& operator=(const Buffer&)
|
||||
Buffer& operator=(Buffer&&)
|
||||
|
||||
bool operator==(const Buffer&)
|
||||
bool operator!=(const Buffer&)
|
||||
bool operator<=(const Buffer&)
|
||||
bool operator>=(const Buffer&)
|
||||
bool operator<(const Buffer&)
|
||||
bool operator>(const Buffer&)
|
||||
|
||||
bool operator bool()
|
||||
|
||||
handle_type get_handle 'getHandle'()
|
||||
|
||||
unsigned get_length 'getLength'() except +
|
||||
unsigned get_frequency 'getFrequency'() except +
|
||||
ChannelConfig get_channel_config 'getChannelConfig'() except +
|
||||
SampleType get_sample_type 'getSampleType'() except +
|
||||
unsigned get_size 'getSize'() except +
|
||||
string get_name 'getName'() except +
|
||||
size_t get_source_count 'getSourceCount'() except +
|
||||
vector[Source] get_sources 'getSources'() except +
|
||||
pair[unsigned, unsigned] get_loop_points 'getLoopPoints'() except +
|
||||
void set_loop_points 'setLoopPoints'(unsigned, unsigned) except +
|
||||
|
||||
|
||||
cdef cppclass Source:
|
||||
ctypedef SourceImpl* handle_type
|
||||
|
||||
Source()
|
||||
Source(SourceImpl*)
|
||||
Source(const Source&)
|
||||
Source(Source&&)
|
||||
|
||||
Source& operator=(const Source&)
|
||||
Source& operator=(Source&&)
|
||||
|
||||
bool operator==(const Source&)
|
||||
bool operator!=(const Source&)
|
||||
bool operator<=(const Source&)
|
||||
bool operator>=(const Source&)
|
||||
bool operator<(const Source&)
|
||||
bool operator>(const Source&)
|
||||
|
||||
bool operator bool()
|
||||
|
||||
handle_type get_handle 'getHandle'()
|
||||
|
||||
void play(Buffer) except +
|
||||
void play(shared_ptr[Decoder], int, int) except +
|
||||
void play(shared_future[Buffer]) except +
|
||||
|
||||
void stop() except +
|
||||
# fade_out_to_stop
|
||||
void pause() except +
|
||||
void resume() except +
|
||||
|
||||
bool is_pending 'isPending'() except +
|
||||
bool is_playing 'isPlaying'() except +
|
||||
bool is_paused 'isPaused'() except +
|
||||
bool is_playing_or_pending 'isPlayingOrPending'() except +
|
||||
|
||||
void set_group 'setGroup'(SourceGroup) except +
|
||||
SourceGroup get_group 'getGroup'() except +
|
||||
|
||||
void set_priority 'setPriority'(unsigned) except +
|
||||
unsigned get_priority 'getPriority'() except +
|
||||
|
||||
void set_offset 'setOffset'(uint64_t) except +
|
||||
# get_sample_offset_latency
|
||||
uint64_t get_sample_offset 'getSampleOffset'() except +
|
||||
# get_sec_offset_latency
|
||||
# get_sec_offset
|
||||
|
||||
void set_looping 'setLooping'(bool) except +
|
||||
bool get_looping 'getLooping'() except +
|
||||
|
||||
void set_pitch 'setPitch'(float) except +
|
||||
float get_pitch 'getPitch'() except +
|
||||
|
||||
void set_gain 'setGain'(float) except +
|
||||
float get_gain 'getGain'() except +
|
||||
void set_gain_range 'setGainRange'(float, float) except +
|
||||
pair[float, float] get_gain_range 'getGainRange'() except +
|
||||
float get_min_gain 'getMinGain'() except +
|
||||
float get_max_gain 'getMaxGain'() except +
|
||||
|
||||
void set_distance_range 'setDistanceRange'(float, float) except +
|
||||
pair[float, float] get_distance_range 'getDistanceRange'() except +
|
||||
float get_reference_distance 'getReferenceDistance'() except +
|
||||
float get_max_distance 'getMaxDistance'() except +
|
||||
|
||||
void set_3d_parameters 'set3DParameters'(const Vector3&, const Vector3&, const Vector3&) except +
|
||||
void set_3d_parameters 'set3DParameters'(const Vector3&, const Vector3&, const pair[Vector3, Vector3]&) except +
|
||||
|
||||
void set_position 'setPosition'(const Vector3&) except +
|
||||
void set_position 'setPosition'(const float*) except +
|
||||
Vector3 get_position 'getPosition'() except +
|
||||
|
||||
void set_velocity 'setVelocity'(const Vector3&) except +
|
||||
void set_velocity 'setVelocity'(const float*) except +
|
||||
Vector3 get_velocity 'getVelocity'() except +
|
||||
|
||||
void set_direction 'setDirection'(const Vector3&) except +
|
||||
void set_direction 'setDirection'(const float*) except +
|
||||
Vector3 get_direction 'getDirection'() except +
|
||||
|
||||
void set_orientation 'setOrientation'(const pair[Vector3, Vector3]&) except +
|
||||
void set_orientation 'setOrientation'(const float*, const float*) except +
|
||||
void set_orientation 'setOrientation'(const float*) except +
|
||||
pair[Vector3, Vector3] get_orientation 'getOrientation'() except +
|
||||
|
||||
void set_cone_angles 'setConeAngles'(float, float) except +
|
||||
pair[float, float] get_cone_angles 'getConeAngles'() except +
|
||||
float get_inner_cone_angle 'getInnerConeAngle'() except +
|
||||
float get_outer_cone_angle 'getOuterConeAngle'() except +
|
||||
|
||||
void set_outer_cone_gains 'setOuterConeGains'(float) except +
|
||||
void set_outer_cone_gains 'setOuterConeGains'(float, float) except +
|
||||
pair[float, float] get_outer_cone_gains 'getOuterConeGains'() except +
|
||||
float get_outer_cone_gain 'getOuterConeGain'() except +
|
||||
float get_outer_cone_gainhf 'getOuterConeGainHF'() except +
|
||||
|
||||
void set_rolloff_factors 'setRolloffFactors'(float) except +
|
||||
void set_rolloff_factors 'setRolloffFactors'(float, float) except +
|
||||
pair[float, float] get_rolloff_factors 'getRolloffFactors'() except +
|
||||
float get_rolloff_factor 'getRolloffFactor'() except +
|
||||
float get_room_rolloff_factor 'getRoomRolloffFactor'() except +
|
||||
|
||||
void set_doppler_factor 'setDopplerFactor'(float) except +
|
||||
float set_doppler_factor 'getDopplerFactor'() except +
|
||||
|
||||
void set_relative 'setRelative'(bool) except +
|
||||
bool get_relative 'getRelative'() except +
|
||||
|
||||
void set_radius 'setRadius'(float) except +
|
||||
float get_radius 'getRadius'() except +
|
||||
|
||||
void set_stereo_angles 'setStereoAngles'(float, float) except +
|
||||
pair[float, float] get_stereo_angles 'getStereoAngles'() except +
|
||||
|
||||
void set_3d_spatialize 'set3DSpatialize'(Spatialize) except +
|
||||
Spatialize get_3d_spatialize 'get3DSpatialize'() except +
|
||||
|
||||
void set_resampler_index 'setResamplerIndex'(int) except +
|
||||
int get_resampler_index 'getResamplerIndex'() except +
|
||||
|
||||
void set_air_absorption_factor 'setAirAbsorptionFactor'(float) except +
|
||||
float get_air_absorption_factor 'getAirAbsorptionFactor'() except +
|
||||
|
||||
void set_gain_auto 'setGainAuto'(bool, bool, bool) except +
|
||||
# get_gain_auto
|
||||
bool get_direct_gain_hf_auto 'getDirectGainHFAuto'() except +
|
||||
bool get_send_gain_auto 'getSendGainAuto'() except +
|
||||
bool get_send_gain_hf_auto 'getSendGainHFAuto'() except +
|
||||
|
||||
void set_direct_filter 'setDirectFilter'(const FilterParams&) except +
|
||||
void set_send_filter 'setSendFilter'(unsigned, const FilterParams&) except +
|
||||
void set_auxiliary_send 'setAuxiliarySend'(AuxiliaryEffectSlot, int) except +
|
||||
void set_auxiliary_send_filter 'setAuxiliarySendFilter'(AuxiliaryEffectSlot, int, const FilterParams&) except +
|
||||
|
||||
void destroy() except +
|
||||
|
||||
|
||||
cdef cppclass SourceGroup:
|
||||
pass
|
||||
|
||||
|
||||
cdef cppclass AuxiliaryEffectSlot:
|
||||
pass
|
||||
|
||||
|
||||
cdef cppclass Effect:
|
||||
pass
|
||||
|
||||
|
||||
cdef cppclass Decoder:
|
||||
int get_frequency 'getFrequency'()
|
||||
ChannelConfig get_channel_config 'getChannelConfig'()
|
||||
SampleType get_sample_type 'getSampleType'()
|
||||
|
||||
uint64_t get_length 'getLength'()
|
||||
bool seek(uint64_t)
|
||||
|
||||
pair[uint64_t, uint64_t] get_loop_points 'getLoopPoints'()
|
||||
|
||||
int read(void*, int)
|
||||
|
||||
|
||||
cdef cppclass DecoderFactory:
|
||||
pass
|
||||
|
||||
|
||||
cdef cppclass FileIOFactory:
|
||||
pass
|
||||
|
||||
|
||||
cdef cppclass MessageHandler:
|
||||
pass
|
336
archaicy.pyx
Normal file
336
archaicy.pyx
Normal file
|
@ -0,0 +1,336 @@
|
|||
# cython: binding=True
|
||||
# Python object wrappers for alure
|
||||
# Copyright (C) 2019 Nguyễn Gia Phong
|
||||
#
|
||||
# This file is part of archaicy.
|
||||
#
|
||||
# archaicy is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
#
|
||||
# archaicy is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with archaicy. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
__doc__ = 'Wrapper for Audio Library Utilities REtooled in Cython'
|
||||
__all__ = ['ALC_TRUE', 'ALC_HRTF_SOFT', 'ALC_HRTF_ID_SOFT',
|
||||
'DeviceManager', 'Device', 'Context', 'Buffer', 'Source', 'Decoder']
|
||||
|
||||
from typing import Dict, List, Tuple
|
||||
|
||||
from libcpp cimport nullptr
|
||||
from libcpp.memory cimport shared_ptr
|
||||
from libcpp.pair cimport pair
|
||||
from libcpp.vector cimport vector
|
||||
|
||||
cimport alure
|
||||
|
||||
# Cast to Python objects
|
||||
ALC_TRUE = alure.ALC_TRUE
|
||||
ALC_HRTF_SOFT = alure.ALC_HRTF_SOFT
|
||||
ALC_HRTF_ID_SOFT = alure.ALC_HRTF_ID_SOFT
|
||||
|
||||
|
||||
cdef vector[alure.AttributePair] mkattrs(vector[pair[int, int]] attrs):
|
||||
"""Convert attribute pairs from Python object to alure format."""
|
||||
cdef vector[alure.AttributePair] attributes
|
||||
cdef alure.AttributePair pair
|
||||
for attribute, value in attrs:
|
||||
pair.mAttribute = attribute
|
||||
pair.mValue = value
|
||||
attributes.push_back(pair) # insert a copy
|
||||
pair.mAttribute = pair.mValue = 0
|
||||
attributes.push_back(pair) # insert a copy
|
||||
return attributes
|
||||
|
||||
|
||||
cdef class DeviceManager:
|
||||
"""Manager of Device objects and other related functionality.
|
||||
This class is a singleton, only one instance will exist in a process
|
||||
at a time.
|
||||
"""
|
||||
cdef alure.DeviceManager impl
|
||||
|
||||
def __init__(self):
|
||||
"""Multiple calls will give the same instance as long as
|
||||
there is still a pre-existing reference to the instance,
|
||||
or else a new instance will be created.
|
||||
"""
|
||||
self.impl = alure.DeviceManager.get_instance()
|
||||
|
||||
def open_playback(self, name: str = None) -> Device:
|
||||
"""Return the playback device given by name.
|
||||
|
||||
Raise RuntimeError on failure.
|
||||
"""
|
||||
device = Device()
|
||||
if name is None:
|
||||
device.impl = self.impl.open_playback()
|
||||
else:
|
||||
device.impl = self.impl.open_playback(name.encode())
|
||||
return device
|
||||
|
||||
|
||||
cdef class Device:
|
||||
"""Playback device."""
|
||||
cdef alure.Device impl
|
||||
|
||||
@property
|
||||
def basic_name(self) -> str:
|
||||
"""Basic name of the device."""
|
||||
return self.impl.get_name(alure.PlaybackName.Basic).decode()
|
||||
|
||||
@property
|
||||
def full_name(self) -> str:
|
||||
"""Full name of the device."""
|
||||
return self.impl.get_name(alure.PlaybackName.Full).decode()
|
||||
|
||||
@property
|
||||
def hrtf_names(self) -> List[str]:
|
||||
"""List of available HRTF names, sorted as OpenAL gives them,
|
||||
such that the index of a given name is the ID to use with
|
||||
ALC_HRTF_ID_SOFT.
|
||||
|
||||
If the ALC_SOFT_HRTF extension is unavailable,
|
||||
this will be an empty list.
|
||||
"""
|
||||
return [name.decode() for name in self.impl.enumerate_hrtf_names()]
|
||||
|
||||
@property
|
||||
def hrtf_enabled(self) -> bool:
|
||||
"""Whether HRTF is enabled on the device.
|
||||
|
||||
If the ALC_SOFT_HRTF extension is unavailable,
|
||||
this will return False although there could still be
|
||||
HRTF applied at a lower hardware level.
|
||||
"""
|
||||
return self.impl.is_hrtf_enabled()
|
||||
|
||||
@property
|
||||
def current_hrtf(self) -> str:
|
||||
"""Name of the HRTF currently being used by this device.
|
||||
|
||||
If HRTF is not currently enabled, this will be None.
|
||||
"""
|
||||
name = self.impl.get_current_hrtf().decode()
|
||||
return name or None
|
||||
|
||||
def create_context(self, attrs: Dict[int, int] = {}) -> Context:
|
||||
"""Return a newly created Context on this device,
|
||||
using the specified attributes.
|
||||
|
||||
Raise RuntimeError on failure.
|
||||
"""
|
||||
context = Context()
|
||||
if attrs:
|
||||
context.impl = self.impl.create_context(mkattrs(attrs.items()))
|
||||
else:
|
||||
context.impl = self.impl.create_context()
|
||||
return context
|
||||
|
||||
def close(self) -> None:
|
||||
"""Close and free the device. All previously-created contexts
|
||||
must first be destroyed.
|
||||
"""
|
||||
self.impl.close()
|
||||
|
||||
|
||||
cdef class Context:
|
||||
"""With statement is supported, for example
|
||||
|
||||
with context:
|
||||
...
|
||||
|
||||
is equivalent to
|
||||
|
||||
Context.make_current(context)
|
||||
...
|
||||
Context.make_current()
|
||||
context.destroy()
|
||||
"""
|
||||
cdef alure.Context impl
|
||||
|
||||
def __enter__(self):
|
||||
Context.make_current(self)
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
Context.make_current()
|
||||
self.destroy()
|
||||
|
||||
@staticmethod
|
||||
def make_current(context: Context = None) -> None:
|
||||
"""Make the specified context current for OpenAL operations."""
|
||||
if context is None:
|
||||
alure.Context.make_current(<alure.Context> nullptr)
|
||||
else:
|
||||
alure.Context.make_current(context.impl)
|
||||
|
||||
def destroy(self) -> None:
|
||||
"""Destroy the context. The context must not be current
|
||||
when this is called.
|
||||
"""
|
||||
self.impl.destroy()
|
||||
|
||||
def create_decoder(self, name: str) -> Decoder:
|
||||
"""Return a Decoder instance for the given audio file
|
||||
or resource name.
|
||||
"""
|
||||
decoder = Decoder()
|
||||
decoder.pimpl = self.impl.create_decoder(name.encode())
|
||||
return decoder
|
||||
|
||||
def get_buffer(self, name: str) -> Buffer:
|
||||
"""Create and cache a Buffer for the given audio file
|
||||
or resource name. Multiple calls with the same name will
|
||||
return the same Buffer object. Cached buffers must be
|
||||
freed using remove_buffer before destroying the context.
|
||||
|
||||
If the buffer can't be loaded RuntimeError will be raised.
|
||||
"""
|
||||
buffer = Buffer()
|
||||
buffer.impl = self.impl.get_buffer(name.encode())
|
||||
return buffer
|
||||
|
||||
def remove_buffer(self, buffer: Buffer) -> None:
|
||||
"""Delete the given cached buffer, invalidating all other
|
||||
Buffer objects with the same name.
|
||||
"""
|
||||
self.impl.remove_buffer(buffer.impl)
|
||||
|
||||
def create_source(self) -> Source:
|
||||
"""Return a newly created Source for playing audio.
|
||||
There is no practical limit to the number of sources you may create.
|
||||
You must call Source.destroy when the source is no longer needed.
|
||||
"""
|
||||
source = Source()
|
||||
source.impl = self.impl.create_source()
|
||||
return source
|
||||
|
||||
def update(self) -> None:
|
||||
"""Update the context and all sources belonging to this context."""
|
||||
self.impl.update()
|
||||
|
||||
|
||||
cdef class Buffer:
|
||||
cdef alure.Buffer impl
|
||||
|
||||
@property
|
||||
def length(self) -> int:
|
||||
"""The length of the buffer in sample frames."""
|
||||
return self.impl.get_length()
|
||||
|
||||
@property
|
||||
def frequency(self) -> int:
|
||||
"""The buffer's frequency in hertz."""
|
||||
return self.impl.get_frequency()
|
||||
|
||||
@property
|
||||
def channel_config_name(self) -> str:
|
||||
"""The buffer's sample configuration name."""
|
||||
return alure.get_channel_config_name(
|
||||
self.impl.get_channel_config()).decode()
|
||||
|
||||
@property
|
||||
def sample_type_name(self) -> str:
|
||||
"""The buffer's sample type name."""
|
||||
return alure.get_sample_type_name(
|
||||
self.impl.get_sample_type()).decode()
|
||||
|
||||
|
||||
cdef class Source:
|
||||
cdef alure.Source impl
|
||||
|
||||
def play_from_buffer(self, buffer: Buffer) -> None:
|
||||
"""Play the source using a buffer. The same buffer
|
||||
may be played from multiple sources simultaneously.
|
||||
"""
|
||||
self.impl.play(buffer.impl);
|
||||
|
||||
def play_from_decoder(self, decoder: Decoder,
|
||||
chunk_len: int, queue_size: int) -> None:
|
||||
"""Plays the source by asynchronously streaming audio from
|
||||
a decoder. The given decoder must NOT have its read or seek
|
||||
methods called from elsewhere while in use.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
decoder : Decoder
|
||||
The decoder object to play audio from.
|
||||
chunk_len : int
|
||||
The number of sample frames to read for each chunk update.
|
||||
Smaller values will require more frequent updates and
|
||||
larger values will handle more data with each chunk.
|
||||
queue_size : int
|
||||
The number of chunks to keep queued during playback.
|
||||
Smaller values use less memory while larger values
|
||||
improve protection against underruns.
|
||||
"""
|
||||
self.impl.play(decoder.pimpl, chunk_len, queue_size)
|
||||
|
||||
@property
|
||||
def playing(self) -> bool:
|
||||
"""Whether the source is currently playing."""
|
||||
return self.impl.is_playing()
|
||||
|
||||
@property
|
||||
def sample_offset(self) -> int:
|
||||
"""The source offset in sample frames. For streaming sources
|
||||
this will be the offset based on the decoder's read position.
|
||||
"""
|
||||
return self.impl.get_sample_offset()
|
||||
|
||||
@property
|
||||
def stereo_angles(self) -> Tuple[float, float]:
|
||||
"""The left and right channel angles, in radians, when playing
|
||||
a stereo buffer or stream. The angles go counter-clockwise,
|
||||
with 0 being in front and positive values going left.
|
||||
|
||||
Has no effect without the AL_EXT_STEREO_ANGLES extension.
|
||||
"""
|
||||
return self.impl.get_stereo_angles()
|
||||
|
||||
@stereo_angles.setter
|
||||
def stereo_angles(self, angles: Tuple[float, float]):
|
||||
left, right = angles
|
||||
self.impl.set_stereo_angles(left, right)
|
||||
|
||||
|
||||
def destroy(self) -> None:
|
||||
"""Destroy the source, stop playback and release resources."""
|
||||
self.impl.destroy()
|
||||
|
||||
|
||||
cdef class Decoder:
|
||||
"""Audio decoder interface."""
|
||||
cdef shared_ptr[alure.Decoder] pimpl
|
||||
|
||||
@property
|
||||
def frequency(self) -> int:
|
||||
"""The sample frequency, in hertz, of the audio being decoded."""
|
||||
return self.pimpl.get()[0].get_frequency()
|
||||
|
||||
@property
|
||||
def channel_config_name(self) -> str:
|
||||
"""Name of the channel configuration of the audio being decoded."""
|
||||
return alure.get_channel_config_name(
|
||||
self.pimpl.get()[0].get_channel_config()).decode()
|
||||
|
||||
@property
|
||||
def sample_type_name(self) -> str:
|
||||
"""Name of the sample type of the audio being decoded."""
|
||||
return alure.get_sample_type_name(
|
||||
self.pimpl.get()[0].get_sample_type()).decode()
|
||||
|
||||
@property
|
||||
def length(self) -> int:
|
||||
"""The total length of the audio, in sample frames,
|
||||
falling-back to 0. Note that if the length is 0,
|
||||
the decoder may not be used to load a Buffer.
|
||||
"""
|
||||
return self.pimpl.get()[0].get_length()
|
103
examples/archaicy-hrtf.py
Executable file
103
examples/archaicy-hrtf.py
Executable file
|
@ -0,0 +1,103 @@
|
|||
#!/usr/bin/env python3
|
||||
# HRTF rendering example using ALC_SOFT_HRTF extension
|
||||
# Copyright (C) 2019 Nguyễn Gia Phong
|
||||
#
|
||||
# This file is part of archaicy.
|
||||
#
|
||||
# archaicy is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
#
|
||||
# archaicy is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with archaicy. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from argparse import ArgumentParser
|
||||
from datetime import datetime, timedelta
|
||||
from itertools import count, takewhile
|
||||
from sys import stderr
|
||||
from time import sleep
|
||||
from typing import Iterable
|
||||
|
||||
from archaicy import ALC_TRUE, ALC_HRTF_SOFT, ALC_HRTF_ID_SOFT, DeviceManager
|
||||
|
||||
PERIOD = 0.025
|
||||
|
||||
|
||||
def pretty_time(seconds: float) -> str:
|
||||
"""Return human-readably formatted time."""
|
||||
time = datetime.min + timedelta(seconds=seconds)
|
||||
if seconds < 3600: return time.strftime('%M:%S')
|
||||
return time.strftime('%H:%M:%S')
|
||||
|
||||
|
||||
def hrtf(files: Iterable[str], device: str, hrtf_name: str,
|
||||
omega: float, angle: float):
|
||||
devmrg = DeviceManager()
|
||||
try:
|
||||
dev = devmrg.open_playback(device)
|
||||
except RuntimeError:
|
||||
stderr.write(f'Failed to open "{device}" - trying default\n')
|
||||
dev = devmrg.open_playback()
|
||||
print('Opened', dev.full_name)
|
||||
|
||||
hrtf_names = dev.hrtf_names
|
||||
if hrtf_names:
|
||||
print('Available HRTFs:')
|
||||
for name in hrtf_names: print(f' {name}')
|
||||
else:
|
||||
print('No HRTF found!')
|
||||
attrs = {ALC_HRTF_SOFT: ALC_TRUE}
|
||||
if hrtf_name is not None:
|
||||
try:
|
||||
attrs[ALC_HRTF_ID_SOFT] = hrtf_names.index(hrtf_name)
|
||||
except ValueError:
|
||||
stderr.write(f'HRTF "{hrtf_name}" not found\n')
|
||||
|
||||
with dev.create_context(attrs) as ctx:
|
||||
if dev.hrtf_enabled:
|
||||
print(f'Using HRTF "{dev.current_hrtf}"')
|
||||
else:
|
||||
print('HRTF not enabled!')
|
||||
|
||||
for filename in files:
|
||||
try:
|
||||
decoder = ctx.create_decoder(filename)
|
||||
except RuntimeError:
|
||||
stderr.write(f'Failed to open file: {filename}\n')
|
||||
continue
|
||||
source = ctx.create_source()
|
||||
|
||||
source.play_from_decoder(decoder, 12000, 4)
|
||||
print(f'Playing {filename} ({decoder.sample_type_name},',
|
||||
f'{decoder.channel_config_name}, {decoder.frequency} Hz)')
|
||||
|
||||
invfreq = 1 / decoder.frequency
|
||||
for i in takewhile(lambda i: source.playing, count(step=PERIOD)):
|
||||
source.stereo_angles = i*omega, i*omega+angle
|
||||
print(f' {pretty_time(source.sample_offset*invfreq)} /'
|
||||
f' {pretty_time(decoder.length*invfreq)}',
|
||||
end='\r', flush=True)
|
||||
sleep(PERIOD)
|
||||
ctx.update()
|
||||
print()
|
||||
source.destroy()
|
||||
dev.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument('files', nargs='+', help='audio files')
|
||||
parser.add_argument('-d', '--device', help='device name')
|
||||
parser.add_argument('-n', '--hrtf', dest='hrtf_name', help='HRTF name')
|
||||
parser.add_argument('-o', '--omega', type=float, default=1.0,
|
||||
help='angular velocity')
|
||||
parser.add_argument('-a', '--angle', type=float, default=1.0,
|
||||
help='relative angle between left and right sources')
|
||||
args = parser.parse_args()
|
||||
hrtf(args.files, args.device, args.hrtf_name, args.omega, args.angle)
|
82
examples/archaicy-play.py
Executable file
82
examples/archaicy-play.py
Executable file
|
@ -0,0 +1,82 @@
|
|||
#!/usr/bin/env python3
|
||||
# A simple example showing how to load and play a sound.
|
||||
# Copyright (C) 2019 Nguyễn Gia Phong
|
||||
#
|
||||
# This file is part of archaicy.
|
||||
#
|
||||
# archaicy is free software: you can redistribute it and/or modify it
|
||||
# under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License,
|
||||
# or (at your option) any later version.
|
||||
#
|
||||
# archaicy is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with archaicy. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from argparse import ArgumentParser
|
||||
from datetime import datetime, timedelta
|
||||
from itertools import count, takewhile
|
||||
from sys import stderr
|
||||
from time import sleep
|
||||
from typing import Iterable
|
||||
|
||||
from archaicy import DeviceManager, Context
|
||||
|
||||
PERIOD = 0.025
|
||||
|
||||
|
||||
def pretty_time(seconds: float) -> str:
|
||||
"""Return human-readably formatted time."""
|
||||
time = datetime.min + timedelta(seconds=seconds)
|
||||
if seconds < 3600: return time.strftime('%M:%S')
|
||||
return time.strftime('%H:%M:%S')
|
||||
|
||||
|
||||
def play(files: Iterable[str], device: str):
|
||||
"""Load and play files on the given device."""
|
||||
devmrg = DeviceManager()
|
||||
try:
|
||||
dev = devmrg.open_playback(device)
|
||||
except RuntimeError:
|
||||
stderr.write(f'Failed to open "{device}" - trying default\n')
|
||||
dev = devmrg.open_playback()
|
||||
print('Opened', dev.full_name)
|
||||
|
||||
ctx = dev.create_context()
|
||||
Context.make_current(ctx)
|
||||
for filename in files:
|
||||
try:
|
||||
buffer = ctx.get_buffer(filename)
|
||||
except RuntimeError:
|
||||
stderr.write(f'Failed to open file: {filename}\n')
|
||||
continue
|
||||
source = ctx.create_source()
|
||||
|
||||
source.play_from_buffer(buffer)
|
||||
print(f'Playing {filename} ({buffer.sample_type_name},',
|
||||
f'{buffer.channel_config_name}, {buffer.frequency} Hz)')
|
||||
|
||||
invfreq = 1 / buffer.frequency
|
||||
for i in takewhile(lambda i: source.playing, count()):
|
||||
print(f' {pretty_time(source.sample_offset*invfreq)} /'
|
||||
f' {pretty_time(buffer.length*invfreq)}',
|
||||
end='\r', flush=True)
|
||||
sleep(PERIOD)
|
||||
print()
|
||||
source.destroy()
|
||||
ctx.remove_buffer(buffer)
|
||||
Context.make_current()
|
||||
ctx.destroy()
|
||||
dev.close()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument('files', nargs='+', help='audio files')
|
||||
parser.add_argument('-d', '--device', help='device name')
|
||||
args = parser.parse_args()
|
||||
play(args.files, args.device)
|
3
pyproject.toml
Normal file
3
pyproject.toml
Normal file
|
@ -0,0 +1,3 @@
|
|||
[build-system]
|
||||
# Minimum requirements for the build system to execute.
|
||||
requires= ['setuptools', 'cython']
|
40
setup.py
Executable file
40
setup.py
Executable file
|
@ -0,0 +1,40 @@
|
|||
#!/usr/bin/env python3
|
||||
from Cython.Build import cythonize
|
||||
from setuptools import setup, Extension
|
||||
|
||||
with open('README.md') as f:
|
||||
long_description = f.read()
|
||||
|
||||
setup(
|
||||
name='archaicy',
|
||||
version='0.0.1',
|
||||
description='Wrapper for Audio Library Utilities REtooled in Cython',
|
||||
long_description=long_description,
|
||||
long_description_content_type='text/markdown',
|
||||
url='https://github.com/McSinyx/archaicy',
|
||||
author='Nguyễn Gia Phong',
|
||||
author_email='vn.mcsinyx@gmail.com',
|
||||
license='LGPLv3+',
|
||||
classifiers=[
|
||||
'Development Status :: 3 - Alpha',
|
||||
'Intended Audience :: Developers',
|
||||
'License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)',
|
||||
'Operating System :: POSIX :: Linux',
|
||||
'Programming Language :: C++',
|
||||
'Programming Language :: Cython',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Programming Language :: Python :: 3.6',
|
||||
'Programming Language :: Python :: 3.7',
|
||||
'Programming Language :: Python :: 3.8',
|
||||
'Programming Language :: Python :: 3.9',
|
||||
'Programming Language :: Python :: 3 :: Only',
|
||||
'Topic :: Multimedia :: Sound/Audio',
|
||||
'Topic :: Software Development :: Libraries',
|
||||
'Typing :: Typed'],
|
||||
keywords='openal alure hrtf',
|
||||
ext_modules=cythonize(
|
||||
Extension('archaicy', ['archaicy.pyx'],
|
||||
include_dirs=['/usr/include/AL/'],
|
||||
libraries=['alure2'], language='c++'),
|
||||
compiler_directives={'language_level': 3}),
|
||||
zip_safe=False)
|
Loading…
Reference in a new issue