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

View File

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

View File

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