1
1
Fork 0
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:
Nguyễn Gia Phong 2019-12-22 16:26:51 +07:00
parent f7ef057b14
commit a548e270e8
8 changed files with 1066 additions and 0 deletions

3
.gitignore vendored
View file

@ -6,6 +6,9 @@ __pycache__/
# C extensions
*.so
# C++ files compiled by Cython
*.cpp
# Distribution / packaging
.Python
build/

View file

@ -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
View 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
View 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
View 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
View 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
View file

@ -0,0 +1,3 @@
[build-system]
# Minimum requirements for the build system to execute.
requires= ['setuptools', 'cython']

40
setup.py Executable file
View 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)