Allow falling back on current context

This commit is contained in:
Nguyễn Gia Phong 2020-03-26 22:27:14 +07:00
parent 2a3bda152f
commit 49072f101e
4 changed files with 110 additions and 52 deletions

View File

@ -57,7 +57,7 @@ def play(files: Iterable[str], device: str, hrtf_name: str,
except ValueError:
stderr.write(f'HRTF {hrtf_name!r} not found\n')
with Context(dev, attrs) as ctx, Source(ctx) as src:
with Context(dev, attrs) as ctx, Source() as src:
if dev.hrtf_enabled:
print(f'Using HRTF {dev.current_hrtf!r}')
else:
@ -66,11 +66,11 @@ def play(files: Iterable[str], device: str, hrtf_name: str,
for filename in files:
try:
decoder = Decoder(ctx, filename)
decoder = Decoder(filename)
except RuntimeError:
stderr.write(f'Failed to open file: {filename}\n')
continue
decoder.play(src, CHUNK_LEN, QUEUE_SIZE)
decoder.play(CHUNK_LEN, QUEUE_SIZE, src)
print(f'Playing {filename} ({decoder.sample_type},',
f'{decoder.channel_config}, {decoder.frequency} Hz)')

View File

@ -52,7 +52,7 @@ def play(files: Iterable[str], device: str) -> None:
ctx.message_handler = LoadingBufferHandler()
for filename in files:
try:
buffer = Buffer(ctx, filename)
buffer = Buffer(filename)
except RuntimeError:
stderr.write(f'Failed to open file: {filename}\n')
continue

View File

@ -43,11 +43,11 @@ def pretty_time(seconds: float) -> str:
def play(files: Iterable[str], device: str) -> None:
"""Load and play files on the given device."""
with Device(device) as dev, Context(dev) as ctx:
with Device(device) as dev, Context(dev):
print('Opened', dev.name)
for filename in files:
try:
buffer = Buffer(ctx, filename)
buffer = Buffer(filename)
except RuntimeError:
stderr.write(f'Failed to open file: {filename}\n')
continue

View File

@ -71,7 +71,7 @@ __all__ = [
'sample_types', 'channel_configs', 'device_names', 'decoder_factories',
'sample_size', 'sample_length', 'query_extension', 'thread_local',
'current_context', 'use_context', 'current_fileio', 'use_fileio',
'Device', 'Context', 'Buffer', 'Source', 'SourceGroup',
'Device', 'Context', 'Listener', 'Buffer', 'Source', 'SourceGroup',
'AuxiliaryEffectSlot', 'Effect', 'Decoder', 'BaseDecoder', 'FileIO',
'MessageHandler']
@ -723,18 +723,28 @@ cdef class Context:
cdef class Listener:
"""Listener instance of the context, i.e each context
will only have one listener.
"""Listener instance of the given context.
It is recommended that application access the listener via
`Context.listener`, which avoid the overhead caused by the
creation of the wrapper object.
Parameters
----------
context : Context
The `context` on which the listener instance is to be created.
context : Optional[Context], optional
The context on which the listener instance is to be created.
By default `current_context()` is used.
Raises
------
RuntimeError
If there is neither any context specified nor current.
"""
cdef alure.Listener impl
def __init__(self, context: Context) -> None:
self.impl = context.impl.get_listener()
def __init__(self, context: Optional[Context] = None) -> None:
if context is None: context = current_context()
self.impl = (<Context> context).impl.get_listener()
def __bool__(self) -> bool:
return <boolean> self.impl
@ -797,11 +807,12 @@ cdef class Buffer:
Parameters
----------
context : Context
The context from which the buffer is to be created and cached.
name : str
Audio file or resource name. Multiple calls with the same name
will return the same buffer.
context : Optional[Context], optional
The context from which the buffer is to be created and cached.
By default `current_context()` is used.
Attributes
----------
@ -811,20 +822,20 @@ cdef class Buffer:
Raises
------
RuntimeError
If the buffer can neither be loaded
nor be found when `existed` is set.
If there is neither any context specified nor current.
"""
cdef alure.Buffer impl
cdef Context context
cdef readonly str name
def __init__(self, context: Context, name: str,
existed: bool = False) -> None:
self.impl = context.impl.find_buffer(name)
if not self:
decoder: Decoder = Decoder.smart(context, name)
self.impl = context.impl.create_buffer_from(name, decoder.pimpl)
def __init__(self, name: str, context: Optional[Context] = None) -> None:
if context is None: context = current_context()
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)
self.impl = self.context.impl.create_buffer_from(
self.name, decoder.pimpl)
def __enter__(self) -> Buffer:
return self
@ -869,27 +880,32 @@ cdef class Buffer:
return f'{self.__class__.__name__}({self.name!r})'
@staticmethod
def from_decoder(decoder: Decoder, context: Context, name: str) -> Buffer:
def from_decoder(decoder: Decoder, name: str,
context: Optional[Context] = None) -> Buffer:
"""Return a buffer created by reading the given decoder.
Parameters
----------
decoder : Decoder
The decoder from which the buffer is to be cached.
context : Context
The context from which the buffer is to be created.
name : str
The name to give to the buffer. It may alias an audio file,
but it must not currently exist in the buffer cache.
context : Optional[Context], optional
The context from which the buffer is to be created.
By default `current_context()` is used.
Raises
------
RuntimeError
If the buffer cannot be created.
If there is neither any context specified nor current;
or if `name` is already used for another buffer.
"""
if context is None: context = current_context()
buffer: Buffer = Buffer.__new__(Buffer)
buffer.impl = context.impl.create_buffer_from(name, decoder.pimpl)
buffer.context, buffer.name = context, name
buffer.impl = buffer.context.impl.create_buffer_from(
buffer.name, decoder.pimpl)
return buffer
@getter
@ -1009,13 +1025,20 @@ cdef class Source:
Parameters
----------
context : Context
context : Optional[Context], optional
The context from which the source is to be created.
By default `current_context()` is used.
Raises
------
RuntimeError
If there is neither any context specified nor current.
"""
cdef alure.Source impl
def __init__(self, context: Context) -> None:
self.impl = context.impl.create_source()
def __init__(self, context: Optional[Context] = None) -> None:
if context is None: context = current_context()
self.impl = (<Context> context).impl.create_source()
def __enter__(self) -> Source:
return self
@ -1586,13 +1609,20 @@ cdef class SourceGroup:
Parameters
----------
context : Context
context : Optional[Context], optional
The context from which the source group is to be created.
By default `current_context()` is used.
Raises
------
RuntimeError
If there is neither any context specified nor current.
"""
cdef alure.SourceGroup impl
def __init__(self, context: Context) -> None:
self.impl = context.impl.create_source_group()
def __init__(self, context: Optional[Context] = None) -> None:
if context is None: context = current_context()
self.impl = (<Context> context).impl.create_source_group()
def __enter__(self) -> SourceGroup:
return self
@ -1730,18 +1760,20 @@ cdef class AuxiliaryEffectSlot:
Parameters
----------
context : Context
context : Optional[Context], optional
The context to create the auxiliary effect slot.
By default `current_context()` is used.
Raises
------
RuntimeError
If the effect slot can't be created.
If there is neither any context specified nor current.
"""
cdef alure.AuxiliaryEffectSlot impl
def __init__(self, context: Context) -> None:
self.impl = context.impl.create_auxiliary_effect_slot()
def __init__(self, context: Optional[Context] = None) -> None:
if context is None: context = current_context()
self.impl = (<Context> context).impl.create_auxiliary_effect_slot()
def __enter__(self) -> AuxiliaryEffectSlot:
return self
@ -1843,13 +1875,20 @@ cdef class Effect:
Parameters
----------
context : Context
context : Optional[Context], optional
The context from which the effect is to be created.
By default `current_context()` is used.
Raises
------
RuntimeError
If there is neither any context specified nor current.
"""
cdef alure.Effect impl
def __init__(self, context: Context) -> None:
self.impl = context.impl.create_effect()
def __init__(self, context: Optional[Context] = None) -> None:
if context is None: context = current_context()
self.impl = (<Context> context).impl.create_effect()
def __enter__(self) -> Effect:
return self
@ -1952,15 +1991,16 @@ cdef class Decoder:
Parameters
----------
context : Context
The context from which the decoder is to be created.
name : str
Audio file or resource name.
context : Optional[Context], optional
The context from which the decoder is to be created.
By default `current_context()` is used.
Raises
------
RuntimeError
If decoder creation fails.
If there is neither any context specified nor current.
See Also
--------
@ -1976,15 +2016,25 @@ cdef class Decoder:
"""
cdef shared_ptr[alure.Decoder] pimpl
def __init__(self, context: Context, name: str) -> None:
self.pimpl = context.impl.create_decoder(name)
def __init__(self, name: str, context: Optional[Context] = None) -> None:
if context is None: context = current_context()
self.pimpl = (<Context> context).impl.create_decoder(name)
@staticmethod
def smart(context: Context, name: str) -> Decoder:
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')
@ -1996,6 +2046,7 @@ cdef class Decoder:
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:
@ -2004,7 +2055,7 @@ cdef class Decoder:
return decoder_factory(resource)
except RuntimeError:
continue
return Decoder(context, name)
return Decoder(name, context)
@getter
def frequency(self) -> int:
@ -2089,7 +2140,8 @@ cdef class Decoder:
PyMem_RawFree(ptr)
return samples
def play(self, source: Source, chunk_len: int, queue_size: int) -> None:
def play(self, chunk_len: int, queue_size: int,
source: Optional[Source] = None) -> Source:
"""Stream audio asynchronously from the decoder.
The decoder must NOT have its `read` or `seek` called
@ -2097,8 +2149,6 @@ cdef class Decoder:
Parameters
----------
source : Source
The source object to play audio.
chunk_len : int
The number of sample frames to read for each chunk update.
Smaller values will require more frequent updates and
@ -2107,8 +2157,16 @@ cdef class Decoder:
The number of chunks to keep queued during playback.
Smaller values use less memory while larger values
improve protection against underruns.
source : Optional[Source], optional
The source object to play audio. If `None` is given,
a new one will be created from the current context.
Returns
-------
The source used for playing.
"""
source.impl.play(self.pimpl, chunk_len, queue_size)
if source is None: source = Source()
(<Source> source).impl.play(self.pimpl, chunk_len, queue_size)
# Decoder interface