Implement MessageHandler

Since this introduce a new source files, all sources are moved
to a new directory src.
This commit is contained in:
Nguyễn Gia Phong 2020-02-13 16:05:01 +07:00
parent 9b6b3164bf
commit 19ac906048
9 changed files with 298 additions and 36 deletions

View File

@ -1 +1 @@
include alure.pxd palace.pyx CMakeLists.txt examples/*.py tests/*.py
include src/*.pxd src/*.pyx src/*.h tests/*.py examples/*.py CMakeLists.txt

View File

@ -1,6 +1,6 @@
[metadata]
name = palace
version = 0.0.8
version = 0.0.9
url = https://github.com/McSinyx/palace
author = Nguyễn Gia Phong
author_email = vn.mcsinyx@gmail.com

View File

@ -50,7 +50,7 @@ class BuildAlure2Ext(build_ext):
setup(cmdclass={'build_ext': BuildAlure2Ext},
ext_modules=cythonize(
Extension(name='palace', sources=['palace.pyx'],
Extension(name='palace', sources=[join('src', 'palace.pyx')],
language='c++', define_macros=[('CYTHON_TRACE', 1)]),
compiler_directives=dict(language_level='3str', c_string_type='str',
c_string_encoding='utf8', linetrace=True,

View File

@ -25,31 +25,9 @@ from libcpp.string cimport string
from libcpp.utility cimport pair
from libcpp.vector cimport vector
# C++ standard library
cdef extern from '<chrono>' namespace 'std::chrono' nogil:
cdef cppclass duration[Rep, Period=*]:
ctypedef Rep rep
duration() except +
duration(const rep&) except + # ugly hack, see cython/cython#3198
rep count() except +
ctypedef duration[int64_t, nano] nanoseconds
ctypedef duration[int64_t, milli] milliseconds
from std cimport duration, nanoseconds, milliseconds, shared_future
cdef extern from '<future>' namespace 'std' nogil:
cdef cppclass shared_future[R]:
pass
cdef extern from '<ratio>' namespace 'std' nogil:
cdef cppclass nano:
pass
cdef cppclass milli:
pass
# OpenAL and Alure auxiliary declarations
cdef extern from 'alc.h' nogil:
cdef int ALC_FALSE

80
src/bases.h Normal file
View File

@ -0,0 +1,80 @@
// Base classes for Cython compatibility
// Copyright (C) 2020 Nguyễn Gia Phong
//
// This file is part of palace.
//
// palace 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.
//
// palace 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 palace. If not, see <https://www.gnu.org/licenses/>.
#ifndef PALACE_BASES_H
#define PALACE_BASES_H
#include <algorithm>
#include <string>
#include <vector>
#include "alure2.h"
namespace palace {
// Due to the lack of support for noexcept keyword in Cython, this is
// created to work around the looser throw specifier error in C++.
class BaseMessageHandler : public alure::MessageHandler {
public:
virtual void device_disconnected (alure::Device device) = 0;
inline void
deviceDisconnected (alure::Device device) noexcept override
{
device_disconnected (device);
}
virtual void source_stopped (alure::Source source) = 0;
inline void
sourceStopped (alure::Source source) noexcept override
{
source_stopped (source);
}
virtual void source_force_stopped (alure::Source source) = 0;
inline void
sourceForceStopped (alure::Source source) noexcept override
{
source_force_stopped (source);
}
virtual void buffer_loading (std::string name, std::string channel_config,
std::string sample_type, unsigned sample_rate,
std::vector<signed char> data) = 0;
inline void
bufferLoading (alure::StringView name, alure::ChannelConfig channels,
alure::SampleType type, ALuint samplerate,
alure::ArrayView<ALbyte> data) noexcept override
{
std::vector<signed char> std_data (data.size());
// FIXME: This defeats the entire point of alure::ArrayView.
std::copy (data.begin(), data.end(), std_data.begin());
buffer_loading (name.data(), alure::GetChannelConfigName (channels),
alure::GetSampleTypeName (type), samplerate, std_data);
}
virtual std::string resource_not_found (std::string name) = 0;
inline alure::String
resourceNotFound (alure::StringView name) noexcept override
{
return resource_not_found (name.data());
}
};
} // namespace palace
#endif // PALACE_BASES_H

30
src/bases.pxd Normal file
View File

@ -0,0 +1,30 @@
# Cython declarations of worked-around alure base classes
# Copyright (C) 2020 Nguyễn Gia Phong
#
# This file is part of palace.
#
# palace 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.
#
# palace 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 palace. If not, see <https://www.gnu.org/licenses/>.
from libcpp.string cimport string
from alure cimport Device, Source, MessageHandler
# GIL is needed for operations with Python objects.
cdef extern from 'bases.h' namespace 'palace':
cdef cppclass BaseMessageHandler(MessageHandler):
void device_disconnected(Device)
void source_stopped(Source)
void source_force_stopped(Source)
string resource_not_found(string)

View File

@ -31,7 +31,8 @@ device_name_default : Dict[str, str]
__all__ = ['ALC_FALSE', 'ALC_TRUE', 'ALC_HRTF_SOFT', 'ALC_HRTF_ID_SOFT',
'device_name_default', 'device_names',
'query_extension', 'use_context',
'Device', 'Context', 'Buffer', 'Source', 'SourceGroup', 'Decoder']
'Device', 'Context', 'Buffer', 'Source', 'SourceGroup',
'AuxiliaryEffectSlot', 'Decoder', 'MessageHandler']
from types import TracebackType
@ -40,9 +41,12 @@ from warnings import warn
from libcpp cimport bool as boolean, nullptr
from libcpp.memory cimport shared_ptr
from libcpp.string cimport string
from libcpp.utility cimport pair
from libcpp.vector cimport vector
from std cimport milliseconds
from bases cimport BaseMessageHandler
cimport alure
# Type aliases
@ -128,8 +132,9 @@ cdef class Device:
Parameters
----------
name : str, optional
The name of the playback device.
name : Optional[str], optional
The name of the playback device. If it is `None`,
the object is left uninitialized.
fail_safe : bool, optional
On failure, fallback to the default device if this is `True`,
otherwise `RuntimeError` is raised. Default to `False`.
@ -152,7 +157,9 @@ cdef class Device:
"""
cdef alure.Device impl
def __init__(self, name: str = '', fail_safe: bool = False) -> None:
def __init__(self, name: Optional[str] = '',
fail_safe: bool = False) -> None:
if name is None: return
try:
self.impl = devmgr.open_playback(name)
except RuntimeError as exc:
@ -229,7 +236,7 @@ cdef class Device:
def efx_version(self) -> Tuple[int, int]:
"""EFX version supported by this device.
If the ALC_EXT_EFX extension is unsupported,
If the `ALC_EXT_EFX` extension is unsupported,
this will be `(0, 0)`.
"""
cdef alure.Version version = self.impl.get_efx_version()
@ -244,7 +251,7 @@ cdef class Device:
def max_auxiliary_sends(self) -> int:
"""Maximum number of auxiliary source sends.
If ALC_EXT_EFX is unsupported, this will be 0.
If `ALC_EXT_EFX` is unsupported, this will be 0.
"""
return self.impl.get_max_auxiliary_sends()
@ -252,7 +259,7 @@ cdef class Device:
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.
`ALC_HRTF_ID_SOFT`.
If the `ALC_SOFT_HRTF` extension is unavailable,
this will be an empty list.
@ -351,6 +358,10 @@ cdef class Context:
----------
device : Device
The device this context was created from.
listener : Listener
The listener instance of this context.
message_handler : MessageHandler
Handler of some certain events.
Raises
------
@ -360,11 +371,15 @@ cdef class Context:
cdef alure.Context impl
cdef readonly Device device
cdef readonly Listener listener
cdef public MessageHandler message_handler
def __init__(self, device: Device, attrs: Dict[int, int] = {}) -> None:
self.impl = device.impl.create_context(mkattrs(attrs.items()))
self.device = device
self.listener = Listener(self)
self.message_handler = MessageHandler()
self.impl.set_message_handler(
shared_ptr[alure.MessageHandler](new CppMessageHandler(self)))
def __enter__(self) -> Context:
use_context(self)
@ -493,6 +508,11 @@ cdef class Buffer:
Audio file or resource name. Multiple calls with the same name
will return the same buffer.
Attributes
----------
name : str
Audio file or resource name.
Raises
------
RuntimeError
@ -667,7 +687,7 @@ cdef class Source:
which should be called regularly (30 to 50 times per second)
for the fading to be smooth.
"""
self.impl.fade_out_to_stop(gain, alure.milliseconds(ms))
self.impl.fade_out_to_stop(gain, milliseconds(ms))
def pause(self) -> None:
"""Pause the source if it is playing."""
@ -1482,3 +1502,114 @@ cdef class Decoder:
if source is None: source = Source(self.context)
(<Source> source).impl.play(self.pimpl, chunk_len, queue_size)
return source
cdef class MessageHandler:
"""Message handler interface.
Applications may derive from this and set an instance on a context
to receive messages. The base methods are no-ops, so subclasses
only need to implement methods for relevant messages.
"""
def device_disconnected(self, device: Device) -> None:
"""Handle disconnected device messages.
This is called when the given device has been disconnected and
is no longer usable for output. As per the `ALC_EXT_disconnect`
specification, disconnected devices remain valid, however all
playing sources are automatically stopped, any sources that are
attempted to play will immediately stop, and new contexts may
not be created on the device.
Notes
-----
Connection status is checked during `Context.update` calls, so
method must be called regularly to be notified when a device is
disconnected. This method may not be called if the device lacks
support for the `ALC_EXT_disconnect` extension.
"""
def source_stopped(self, source: Source) -> None:
"""Handle end-of-buffer/stream messages.
This is called when the given source reaches the end of buffer
or stream, which is detected upon a call to `Context.update`.
"""
def source_force_stopped(self, source: Source) -> None:
"""Handle forcefully stopped sources.
This is called when the given source was forced to stop,
because of one of the following reasons:
* There were no more mixing sources and a higher-priority source
preempted it.
* `source` is part of a `SourceGroup` (or sub-group thereof)
that had its `SourceGroup.stop_all` method called.
* `source` was playing a buffer that's getting removed.
"""
def buffer_loading(self, name: str, channel_config: str, sample_type: str,
sample_rate: int, data: List[int]) -> None:
"""Handle messages from Buffer initialization.
This is called when a new buffer is about to be created
and loaded. which may be called asynchronously for buffers
being loaded asynchronously.
Parameters
----------
name : str
Resource name passed to `Buffer`.
channel_config : str
Channel configuration of the given audio data.
sample_type : str
Sample type of the given audio data.
sample_rate : int
Sample rate of the given audio data.
data : List[int]
The audio data that is about to be fed to the OpenAL buffer.
"""
def resource_not_found(self, name: str) -> str:
"""Return the fallback resource for the one of the given name.
This is called when `name` is not found, allowing substitution
of a different resource until the returned string either points
to a valid resource or is empty (default).
For buffers being cached, the original name will still be used
for the cache entry so one does not have to keep track of
substituted resource names.
"""
return ''
cdef cppclass CppMessageHandler(BaseMessageHandler):
Context context
CppMessageHandler(Context ctx):
this.context = ctx # Will this be garbage collected?
void device_disconnected(alure.Device alure_device):
cdef Device device = Device(None)
device.impl = alure_device
context.message_handler.device_disconnected(device)
void source_stopped(alure.Source alure_source):
cdef Source source = Source(None)
source.impl = alure_source
context.message_handler.source_stopped(source)
void source_force_stopped(alure.Source alure_source):
cdef Source source = Source(None)
source.impl = alure_source
context.message_handler.source_force_stopped(source)
void buffer_loading(string name, string channel_config, string sample_type,
unsigned sample_rate, vector[signed char] data):
context.message_handler.buffer_loading(name, channel_config,
sample_type, sample_rate, data)
string resource_not_found(string name):
return context.message_handler.resource_not_found(name)

43
src/std.pxd Normal file
View File

@ -0,0 +1,43 @@
# Cython declarations of some missing C++ standard libraries
# Copyright (C) 2019, 2020 Nguyễn Gia Phong
# Copyright (C) 2020 Ngô Ngọc Đức Huy
#
# This file is part of palace.
#
# palace 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.
#
# palace 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 palace. If not, see <https://www.gnu.org/licenses/>.
from libc.stdint cimport int64_t
cdef extern from '<chrono>' namespace 'std::chrono' nogil:
cdef cppclass duration[Rep, Period=*]:
ctypedef Rep rep
duration() except +
duration(const rep&) except + # ugly hack, see cython/cython#3198
rep count() except +
ctypedef duration[int64_t, nano] nanoseconds
ctypedef duration[int64_t, milli] milliseconds
cdef extern from '<future>' namespace 'std' nogil:
cdef cppclass shared_future[R]:
pass
cdef extern from '<ratio>' namespace 'std' nogil:
cdef cppclass nano:
pass
cdef cppclass milli:
pass

View File

@ -17,8 +17,8 @@ filename = *.pxd, *.pyx, *.py
hang-closing = True
ignore = E225, E226, E227, E701
per-file-ignores =
alure.pxd:E501,E999
palace.pyx:E999
*.pxd:E501,E999
*.pyx:E999
; See https://github.com/PyCQA/pycodestyle/issues/906
;max-doc-length = 72