Revisit a few design desisions

This commit is contained in:
Nguyễn Gia Phong 2020-04-05 12:36:41 +07:00
parent 80a9d88e90
commit bdfe30306d
4 changed files with 85 additions and 83 deletions

View File

@ -22,7 +22,7 @@ from datetime import datetime, timedelta
from itertools import count, takewhile
from sys import stderr
from time import sleep
from typing import Iterable, Sequence
from typing import Iterable, MutableSequence
from palace import Device, Context, Buffer, Source, MessageHandler
@ -32,7 +32,7 @@ PERIOD: float = 0.025
class EventHandler(MessageHandler):
"""Message handler of buffer loading events."""
def buffer_loading(self, name: str, channel_config: str, sample_type: str,
sample_rate: int, data: Sequence[int]) -> None:
sample_rate: int, data: MutableSequence[int]) -> None:
"""Print buffers information on buffer loading events."""
print(f'Playing {name} ({sample_type},',
f'{channel_config}, {sample_rate} Hz)')

View File

@ -25,7 +25,7 @@ from sys import stderr
from time import sleep
from typing import Iterable
from palace import TRUE, HRTF, HRTF_ID, Device, Context, Source, Decoder
from palace import TRUE, HRTF, HRTF_ID, decode, Device, Context, Source
CHUNK_LEN: int = 12000
QUEUE_SIZE: int = 4
@ -66,7 +66,7 @@ def play(files: Iterable[str], device: str, hrtf_name: str,
for filename in files:
try:
decoder = Decoder(filename)
decoder = decode(filename)
except RuntimeError:
stderr.write(f'Failed to open file: {filename}\n')
continue

View File

@ -24,8 +24,8 @@ from sys import stderr
from time import sleep
from typing import Iterable
from palace import (Device, Context, Source, Decoder,
AuxiliaryEffectSlot, Effect, reverb_preset_names)
from palace import (reverb_preset_names, decode,
Device, Context, Source, AuxiliaryEffectSlot, Effect)
CHUNK_LEN: int = 12000
QUEUE_SIZE: int = 4
@ -57,7 +57,7 @@ def play(files: Iterable[str], device: str, reverb: str) -> None:
for filename in files:
try:
decoder = Decoder(filename)
decoder = decode(filename)
except RuntimeError:
stderr.write(f'Failed to open file: {filename}\n')
continue

View File

@ -70,10 +70,10 @@ __all__ = [
'CHANNEL_CONFIG', 'MONO', 'STEREO', 'QUAD', 'X51', 'X61', 'X71',
'SAMPLE_TYPE', 'BYTE', 'UNSIGNED_BYTE', 'SHORT', 'UNSIGNED_SHORT',
'INT', 'UNSIGNED_INT', 'FLOAT', 'HRTF', 'HRTF_ID',
'sample_types', 'channel_configs', 'device_names',
'reverb_preset_names', 'decoder_factories',
'sample_size', 'sample_length', 'query_extension', 'thread_local',
'current_context', 'use_context', 'current_fileio', 'use_fileio',
'sample_types', 'channel_configs', 'device_names', 'reverb_preset_names',
'decoder_factories', 'current_fileio', 'use_fileio', 'query_extension',
'thread_local', 'current_context', 'use_context',
'cache', 'decode', 'sample_size', 'sample_length',
'Device', 'Context', 'Listener', 'Buffer', 'Source', 'SourceGroup',
'AuxiliaryEffectSlot', 'Effect', 'Decoder', 'BaseDecoder', 'FileIO',
'MessageHandler']
@ -270,6 +270,71 @@ def use_context(context: Optional[Context],
alure.Context.make_current(alure_context)
def cache(*names: str, context: Optional[Context] = None) -> None:
"""Cache given audio resources asynchronously.
Duplicate names and buffers already cached are ignored.
Cached buffers must be free using `remove_buffer`
before destroying the context.
The resources will be scheduled for caching asynchronously,
and should be retrieved later when needed by initializing
`Buffer` corresponding objects. Resources that cannot be
loaded, for example due to an unsupported format, will be
ignored and a later `Buffer` initialization will raise
an exception.
If `context` is not given, `current_context()` will be used.
Raises
------
RuntimeError
If there is neither any context specified nor current.
"""
cdef vector[string] std_names = names
cdef vector[alure.StringView] alure_names
for name in std_names: alure_names.push_back(<alure.StringView> name)
if context is None: context = current_context()
(<Context> context).impl.precache_buffers_async(alure_names)
def decode(name: str, context: Optional[Context] = None) -> Decoder:
"""Return the decoder created from the given resource name.
This first tries user-registered decoder factories in
lexicographical order, then fallback to the internal ones.
Raises
------
RuntimeError
If there is neither any context specified nor current.
See Also
--------
decoder_factories : Simple object for storing decoder factories
"""
def find_resource(name, subst):
if not name: raise RuntimeError('Failed to open file')
try:
if fileio_factory is None:
return open(name, 'rb')
else:
return fileio_factory(name)
except FileNotFoundError:
return find_resource(subst(name), subst)
if context is None: context = current_context()
resource = find_resource(
name, context.message_handler.resource_not_found)
for decoder_factory in decoder_factories:
resource.seek(0)
try:
return decoder_factory(resource)
except RuntimeError:
continue
return Decoder(name, context)
def current_fileio() -> Optional[Callable[[str], 'FileIO']]:
"""Return the file I/O factory currently in used by audio decoders.
@ -719,27 +784,6 @@ cdef class Context:
"""
return self.impl.get_default_resampler_index()
def precache_buffers_async(self, names: List[str]) -> None:
"""Cache given audio resources asynchronously.
Duplicate names and buffers already cached are ignored.
Cached buffers must be free using `remove_buffer`
before destroying the context.
The resources will be scheduled for caching asynchronously,
and should be retrieved later when needed by initializing
`Buffer` corresponding objects. Resources that cannot be
loaded, for example due to an unsupported format, will be
ignored and a later `Buffer` initialization will raise
an exception.
This method require the context to be current.
"""
cdef vector[string] std_names = names
cdef vector[alure.StringView] alure_names
for name in std_names: alure_names.push_back(<alure.StringView> name)
self.impl.precache_buffers_async(alure_names)
@setter
def doppler_factor(self, value: float) -> None:
"""Factor to apply to all source's doppler calculations."""
@ -921,7 +965,7 @@ cdef class Buffer:
self.context, self.name = context, name
self.impl = self.context.impl.find_buffer(self.name)
if not self:
decoder: Decoder = Decoder.smart(self.name, self.context)
decoder: Decoder = decode(self.name, self.context)
self.impl = self.context.impl.create_buffer_from(
self.name, decoder.pimpl)
@ -1669,20 +1713,17 @@ cdef class Source:
self.impl.set_gain_auto(directhf, send, sendhf)
@setter
def direct_filter(self, value: Tuple[float, float, float]) -> None:
def direct_filter(self, value: Vector3) -> None:
"""The filter properties on the direct path signal."""
self.impl.set_direct_filter(make_filter_params(value))
@setter
def send_filter(self,
value: Tuple[int, Tuple[float, float, float]]) -> None:
def send_filter(self, value: Tuple[int, Vector3]) -> None:
"""The filter properties on the given send path signal.
Any filter properties on the send path remain as they were.
"""
self.impl.set_send_filter(
value[0],
make_filter_params(value[1]))
self.impl.set_send_filter(value[0], make_filter_params(value[1]))
@setter
def auxiliary_send(self, value: Tuple[AuxiliaryEffectSlot, int]) -> None:
@ -1695,9 +1736,7 @@ cdef class Source:
@setter
def auxiliary_send_filter(
self,
value: Tuple[AuxiliaryEffectSlot, int,
Tuple[float, float, float]]) -> None:
self, value: Tuple[AuxiliaryEffectSlot, int, Vector3]) -> None:
"""Connect the effect slot to the given send path, using the filter."""
self.impl.set_auxiliary_send_filter(
(<AuxiliaryEffectSlot> value[0]).impl,
@ -2144,7 +2183,7 @@ cdef class Decoder:
from filenames using contexts, it is the superclass of the ABC
(abstract base class) `BaseDecoder`. Because of this, `Decoder`
may only initialize an internal one. To use registered factories,
please call the `smart` static method instead.
please call the module-level `decode` function instead.
"""
cdef shared_ptr[alure.Decoder] pimpl
@ -2152,43 +2191,6 @@ cdef class Decoder:
if context is None: context = current_context()
self.pimpl = (<Context> context).impl.create_decoder(name)
@staticmethod
def smart(name: str, context: Optional[Context] = None) -> Decoder:
"""Return the decoder created from the given resource name.
This first tries user-registered decoder factories in
lexicographical order, then fallback to the internal ones.
Raises
------
RuntimeError
If there is neither any context specified nor current.
See Also
--------
decoder_factories : Simple object for storing decoder factories
"""
def find_resource(name, subst):
if not name: raise RuntimeError('Failed to open file')
try:
if fileio_factory is None:
return open(name, 'rb')
else:
return fileio_factory(name)
except FileNotFoundError:
return find_resource(subst(name), subst)
if context is None: context = current_context()
resource = find_resource(
name, context.message_handler.resource_not_found)
for decoder_factory in decoder_factories:
resource.seek(0)
try:
return decoder_factory(resource)
except RuntimeError:
continue
return Decoder(name, context)
@getter
def frequency(self) -> int:
"""Sample frequency, in hertz, of the audio being decoded."""
@ -2584,11 +2586,11 @@ cdef class MessageHandler:
Sample type of the given audio data.
sample_rate : int
Sample rate of the given audio data.
data : Sequence[int]
data : MutableSequence[int]
The audio data that is about to be fed to the OpenAL buffer.
It is a mutable array of signed 8-bit integers
and follows the Python buffer protocol.
It is a mutable memory array of signed 8-bit integers,
following Python buffer protocol.
"""
def resource_not_found(self, name: str) -> str: