Compare commits
103 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 917a11b398 | |||
| ce84e69cc1 | |||
| c2cfe0211b | |||
| bfe4899721 | |||
| 40a1219dac | |||
| 54e2712b7a | |||
| f9685e0b30 | |||
| b4a670c50d | |||
| 370a4b6249 | |||
| eca217dff9 | |||
| 65d3c4a283 | |||
| adc0d800e1 | |||
| 02cf16d10d | |||
| 94ced0517e | |||
| 698e6044d3 | |||
| 2c95f4ff6b | |||
| f19d139f64 | |||
| dc51fa75b5 | |||
| 85ff47ad43 | |||
| 88f38284bb | |||
| fe7561beea | |||
| 2314aaeb7e | |||
| bf24ec65ca | |||
| 478d0559e6 | |||
| 891a30eb08 | |||
| 74d9e7d6e1 | |||
| fa10bf6999 | |||
| 7de446c3c7 | |||
| 5cbc88607d | |||
| 0fb8b1d9f3 | |||
| 3cb51bdf90 | |||
| 6e96f8556c | |||
| fddd239be1 | |||
| 8950de75bf | |||
| 8d932eb444 | |||
| 9e59862701 | |||
| 74318d1601 | |||
| bd458cb5ac | |||
| 4d1cb4b3af | |||
| 8daa39deae | |||
| 88e04d65f8 | |||
| 4c00e7371b | |||
| 0e84b51a08 | |||
| 8de9c92791 | |||
| 2112840b4d | |||
| 526bd05eaf | |||
| a83979a71e | |||
| 750140c278 | |||
| d28b117c22 | |||
| ed5ecccc97 | |||
| 369b202e04 | |||
| 3e5f3bd14b | |||
| cb6fc6231c | |||
| 7e2e99d072 | |||
| 5e8a08bc26 | |||
| 7b9d9828e6 | |||
| 08b80110b0 | |||
| 9188071945 | |||
| 00cb3e0a3a | |||
| e06fcc2e67 | |||
| 554a68562f | |||
| bee66b741e | |||
| 4750bb7dc4 | |||
| 039388d1e2 | |||
| 56f8e948c5 | |||
| 5fc5918e01 | |||
| 3c0469c0fc | |||
| f0971c9454 | |||
| 707e7947b7 | |||
| ef914ca069 | |||
| 72a31c802a | |||
| 833cde1455 | |||
| cf41942ed5 | |||
| 83104faa0b | |||
| fc3517b00e | |||
| aa77693b79 | |||
| c18a957644 | |||
| dfb89bde62 | |||
| 5c2a006553 | |||
| 7fa6f1c85c | |||
| df89c12581 | |||
| 40c4386803 | |||
| 387983c9af | |||
| 6fdbd9a2e5 | |||
| 169612637f | |||
| d6fd8748f1 | |||
| 5df4378f52 | |||
| 71e8bf1b1e | |||
| ba03ad3c75 | |||
| 2cf24ef81b | |||
| fa12a62e57 | |||
| 8fa9d27a88 | |||
| 470d6140df | |||
| 8bb39b0ff4 | |||
| 03325b1561 | |||
| 1c9229226c | |||
| a0f5bc1548 | |||
| 511deb2e16 | |||
| 0acbf9b345 | |||
| dc68788ebf | |||
| ef3e9671f0 | |||
| 47795bcc72 | |||
| 335500fc1d |
@@ -0,0 +1,9 @@
|
|||||||
|
*.pyc
|
||||||
|
*.so
|
||||||
|
MANIFEST
|
||||||
|
doc/gh-pages/
|
||||||
|
doc/html/
|
||||||
|
doc/doctrees/
|
||||||
|
gh-pages/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
Version 0.8.2:
|
||||||
|
- fix #3 (we cannot get the revision from git for pip installs)
|
||||||
|
|
||||||
|
Version 0.8.1:
|
||||||
|
- document changes (this file)
|
||||||
|
|
||||||
|
Version 0.8:
|
||||||
|
- 'PCM()' has new 'device' and 'cardindex' keyword arguments.
|
||||||
|
|
||||||
|
The keyword 'device' allows to select virtual devices, 'cardindex' can be
|
||||||
|
used to select hardware cards by index (as with 'mixers()' and 'Mixer()').
|
||||||
|
|
||||||
|
The 'card' keyword argument is still supported, but deprecated.
|
||||||
|
|
||||||
|
The reason for this change is that the 'card' keyword argument guessed
|
||||||
|
a device name from the card name, but this only works sometimes, and breaks
|
||||||
|
opening virtual devices.
|
||||||
|
|
||||||
|
- new function 'pcms()' to list available PCM devices.
|
||||||
|
|
||||||
|
- mixers() and Mixer() take an additional 'device' keyword argument.
|
||||||
|
This allows to list or open virtual devices.
|
||||||
|
|
||||||
|
- The default behaviour of Mixer() without any arguments has changed.
|
||||||
|
Now Mixer() will try to open the 'default' Mixer instead of the Mixer
|
||||||
|
that is associated with card 0.
|
||||||
|
|
||||||
|
- fix a memory leak under Python 3.x
|
||||||
|
|
||||||
|
- some more memory leaks in error conditions fixed.
|
||||||
|
|
||||||
|
Version 0.7:
|
||||||
|
- fixed several memory leaks (patch 3372909), contributed by Erik Kulyk)
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.6:
|
||||||
|
- mostly reverted patch 2594366: alsapcm_setup did not do complete error
|
||||||
|
checking for good reasons; some ALSA functions in alsapcm_setup may fail without
|
||||||
|
rendering the device unusable
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.5:
|
||||||
|
- applied patch 2777035: Fixed setrec method in alsaaudio.c
|
||||||
|
This included a mixertest with more features
|
||||||
|
- fixed/applied patch 2594366: alsapcm_setup does not do any error checking
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.4:
|
||||||
|
- API changes: mixers() and Mixer() now take a card index instead of a
|
||||||
|
card name as optional parameter.
|
||||||
|
- Support for Python 3.0
|
||||||
|
- Documentation converted to reStructuredText; use Sphinx instead of LaTeX.
|
||||||
|
- added cards()
|
||||||
|
- added PCM.close()
|
||||||
|
- added Mixer.close()
|
||||||
|
- added mixer.getenum()
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.3:
|
||||||
|
- wrapped blocking calls with Py_BEGIN_ALLOW_THREADS/Py_END_ALLOW_THREADS
|
||||||
|
- added pause
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.2:
|
||||||
|
- Many bugfixes related to playback in particular
|
||||||
|
- Module documentation in the doc subdirectory
|
||||||
|
|
||||||
|
|
||||||
|
Version 0.1:
|
||||||
|
- Initial version
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
PyAlsaAudio is released under the same conditions as Python itself.
|
||||||
|
The original wording of this license can be found below.
|
||||||
|
|
||||||
|
|
||||||
|
PSF LICENSE AGREEMENT FOR PYTHON 2.4
|
||||||
|
------------------------------------
|
||||||
|
|
||||||
|
1. This LICENSE AGREEMENT is between the Python Software Foundation
|
||||||
|
("PSF"), and the Individual or Organization ("Licensee") accessing and
|
||||||
|
otherwise using Python 2.4 software in source or binary form and its
|
||||||
|
associated documentation.
|
||||||
|
|
||||||
|
2. Subject to the terms and conditions of this License Agreement, PSF
|
||||||
|
hereby grants Licensee a nonexclusive, royalty-free, world-wide
|
||||||
|
license to reproduce, analyze, test, perform and/or display publicly,
|
||||||
|
prepare derivative works, distribute, and otherwise use Python 2.4
|
||||||
|
alone or in any derivative version, provided, however, that PSF's
|
||||||
|
License Agreement and PSF's notice of copyright, i.e., "Copyright (c)
|
||||||
|
2001, 2002, 2003, 2004 Python Software Foundation; All Rights Reserved"
|
||||||
|
are retained in Python 2.4 alone or in any derivative version prepared
|
||||||
|
by Licensee.
|
||||||
|
|
||||||
|
3. In the event Licensee prepares a derivative work that is based on
|
||||||
|
or incorporates Python 2.4 or any part thereof, and wants to make
|
||||||
|
the derivative work available to others as provided herein, then
|
||||||
|
Licensee hereby agrees to include in any such work a brief summary of
|
||||||
|
the changes made to Python 2.4.
|
||||||
|
|
||||||
|
4. PSF is making Python 2.4 available to Licensee on an "AS IS"
|
||||||
|
basis. PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
|
||||||
|
IMPLIED. BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
|
||||||
|
DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
|
||||||
|
FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 2.4 WILL NOT
|
||||||
|
INFRINGE ANY THIRD PARTY RIGHTS.
|
||||||
|
|
||||||
|
5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
|
||||||
|
2.4 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
|
||||||
|
A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 2.4,
|
||||||
|
OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
|
||||||
|
|
||||||
|
6. This License Agreement will automatically terminate upon a material
|
||||||
|
breach of its terms and conditions.
|
||||||
|
|
||||||
|
7. Nothing in this License Agreement shall be deemed to create any
|
||||||
|
relationship of agency, partnership, or joint venture between PSF and
|
||||||
|
Licensee. This License Agreement does not grant permission to use PSF
|
||||||
|
trademarks or trade name in a trademark sense to endorse or promote
|
||||||
|
products or services of Licensee, or any third party.
|
||||||
|
|
||||||
|
8. By copying, installing or otherwise using Python 2.4, Licensee
|
||||||
|
agrees to be bound by the terms and conditions of this License
|
||||||
|
Agreement.
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
include *.py
|
||||||
|
include CHANGES
|
||||||
|
include TODO
|
||||||
|
include LICENSE
|
||||||
|
recursive-include doc *.html *.gif *.png *.css *.py *.rst *.js *.json Makefile
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# Publishing the documentation
|
||||||
|
|
||||||
|
- Install Sphinx; `sudo pip install sphinx`
|
||||||
|
- Clone gh-pages branch: `cd doc; git clone -b gh-pages git@github.com:larsimmisch/pyalsaaudio.git gh-pages`
|
||||||
|
- `cd doc; make publish`
|
||||||
|
|
||||||
|
# Release procedure
|
||||||
|
|
||||||
|
- Update version number in setup.py
|
||||||
|
- Create tag and push it, i.e. `git tag x.y.z; git push origin x.y.z`
|
||||||
|
- `python setup.py sdist upload -r pypi`
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
# PyAlsaAudio
|
||||||
|
|
||||||
|
For documentation, see http://larsimmisch.github.io/pyalsaaudio/
|
||||||
|
|
||||||
|
> Author: Casper Wilstrup (cwi@aves.dk)
|
||||||
|
> Maintainer: Lars Immisch (lars@ibp.de)
|
||||||
|
|
||||||
|
This package contains wrappers for accessing the
|
||||||
|
[ALSA](http://www.alsa-project.org/) API from Python. It
|
||||||
|
is currently fairly complete for PCM devices, and has some support for mixers.
|
||||||
|
|
||||||
|
If you find bugs in the wrappers please open an issue in the issue tracker.
|
||||||
|
Please don't send bug reports regarding ALSA specifically. There are several
|
||||||
|
bugs in the ALSA API, and those should be reported to the ALSA team - not
|
||||||
|
me.
|
||||||
|
|
||||||
|
This software is licensed under the PSF license - the same one used
|
||||||
|
by the majority of the python distribution. Basically you can use it
|
||||||
|
for anything you wish (even commercial purposes). There is no warranty
|
||||||
|
whatsoever.
|
||||||
|
|
||||||
|
|
||||||
|
# Installation
|
||||||
|
|
||||||
|
## PyPI
|
||||||
|
|
||||||
|
To install pyalsaaudio via `pip` (or `easy_install`):
|
||||||
|
|
||||||
|
```
|
||||||
|
$ pip install pyalsaaudio
|
||||||
|
```
|
||||||
|
|
||||||
|
## Manual installation
|
||||||
|
|
||||||
|
*Note:* the wrappers need a kernel with ALSA support, and the
|
||||||
|
ALSA library and headers. The installation of these varies from distribution
|
||||||
|
to distribution.
|
||||||
|
|
||||||
|
On Debian or Ubuntu, make sure to install `libasound2-dev`. On Arch,
|
||||||
|
install `alsa-lib`. When in doubt, search your distribution for a
|
||||||
|
package that contains `libasound.so` and `asoundlib.h`.
|
||||||
|
|
||||||
|
First, get the sources and change to the source directory:
|
||||||
|
```
|
||||||
|
$ git clone https://github.com/larsimmisch/pyalsaaudio.git
|
||||||
|
$ cd pyalsaaudio
|
||||||
|
```
|
||||||
|
|
||||||
|
Then, build:
|
||||||
|
```
|
||||||
|
$ python setup.py build
|
||||||
|
```
|
||||||
|
|
||||||
|
And install:
|
||||||
|
```
|
||||||
|
$ sudo python setup.py install
|
||||||
|
```
|
||||||
|
|
||||||
|
# Using the API
|
||||||
|
The API documentation is included in the doc subdirectory of the source
|
||||||
|
distribution; it is also online on [http://larsimmisch.github.io/pyalsaaudio/](http://larsimmisch.github.io/pyalsaaudio/).
|
||||||
|
|
||||||
|
There are some example programs included with the source:
|
||||||
|
|
||||||
|
* [playwav.py](https://github.com/larsimmisch/pyalsaaudio/blob/master/playwav.py) plays back a wav file
|
||||||
|
* [playbacktest.py](https://github.com/larsimmisch/pyalsaaudio/blob/master/playbacktest.py) plays back raw sound data read from stdin
|
||||||
|
* [recordtest.py](https://github.com/larsimmisch/pyalsaaudio/blob/master/recordtest.py) captures sound from the microphone and writes
|
||||||
|
it raw to stdout.
|
||||||
|
* [mixertest.py](https://github.com/larsimmisch/pyalsaaudio/blob/master/mixertest.py) can be used to manipulate the mixers.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
- Better example code (aplay,arecord,amixer workalike for example)
|
||||||
|
- Implement MIDI/sequencer support.
|
||||||
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
alsaaudio documentation
|
|
||||||
=======================
|
|
||||||
|
|
||||||
.. toctree::
|
|
||||||
:maxdepth: 2
|
|
||||||
|
|
||||||
pyalsaaudio
|
|
||||||
terminology
|
|
||||||
libalsaaudio
|
|
||||||
|
|
||||||
|
|
||||||
Github pages
|
|
||||||
=================
|
|
||||||
|
|
||||||
* `Project page <https://github.com/larsimmisch/pyalsaaudio/>`_
|
|
||||||
* `Download from pypi <https://pypi.python.org/pypi/pyalsaaudio>`_
|
|
||||||
* `Bug tracker <https://github.com/larsimmisch/pyalsaaudio/issues>`_
|
|
||||||
|
|
||||||
|
|
||||||
Indices and tables
|
|
||||||
==================
|
|
||||||
|
|
||||||
* :ref:`genindex`
|
|
||||||
* :ref:`modindex`
|
|
||||||
* :ref:`search`
|
|
||||||
|
|
||||||
@@ -1,864 +0,0 @@
|
|||||||
****************
|
|
||||||
:mod:`alsaaudio`
|
|
||||||
****************
|
|
||||||
|
|
||||||
.. module:: alsaaudio
|
|
||||||
:platform: Linux
|
|
||||||
|
|
||||||
.. moduleauthor:: Casper Wilstrup <cwi@aves.dk>
|
|
||||||
.. moduleauthor:: Lars Immisch <lars@ibp.de>
|
|
||||||
|
|
||||||
The :mod:`alsaaudio` module defines functions and classes for using ALSA.
|
|
||||||
|
|
||||||
.. function:: pcms(pcmtype: int = PCM_PLAYBACK) ->list[str]
|
|
||||||
|
|
||||||
List available PCM devices by name.
|
|
||||||
|
|
||||||
Arguments are:
|
|
||||||
|
|
||||||
* *pcmtype* - can be either :const:`PCM_CAPTURE` or :const:`PCM_PLAYBACK`
|
|
||||||
(default).
|
|
||||||
|
|
||||||
**Note:**
|
|
||||||
|
|
||||||
For :const:`PCM_PLAYBACK`, the list of device names should be equivalent
|
|
||||||
to the list of device names that ``aplay -L`` displays on the commandline::
|
|
||||||
|
|
||||||
$ aplay -L
|
|
||||||
|
|
||||||
For :const:`PCM_CAPTURE`, the list of device names should be equivalent
|
|
||||||
to the list of device names that ``arecord -L`` displays on the
|
|
||||||
commandline::
|
|
||||||
|
|
||||||
$ arecord -L
|
|
||||||
|
|
||||||
*New in 0.8*
|
|
||||||
|
|
||||||
.. function:: cards() -> list[str]
|
|
||||||
|
|
||||||
List the available ALSA cards by name. This function is only moderately
|
|
||||||
useful. If you want to see a list of available PCM devices, use :func:`pcms`
|
|
||||||
instead.
|
|
||||||
|
|
||||||
..
|
|
||||||
Omitted by intention due to being superseded by cards():
|
|
||||||
|
|
||||||
.. function:: card_indexes()
|
|
||||||
.. function:: card_name()
|
|
||||||
|
|
||||||
.. function:: mixers(cardindex: int = -1, device: str = 'default') -> list[str]
|
|
||||||
|
|
||||||
List the available mixers. The arguments are:
|
|
||||||
|
|
||||||
* *cardindex* - the card index. If this argument is given, the device name
|
|
||||||
is constructed as: 'hw:*cardindex*' and
|
|
||||||
the `device` keyword argument is ignored. ``0`` is the first hardware sound
|
|
||||||
card.
|
|
||||||
|
|
||||||
**Note:** This should not be used, as it bypasses most of ALSA's configuration.
|
|
||||||
|
|
||||||
* *device* - the name of the device on which the mixer resides. The default
|
|
||||||
is ``'default'``.
|
|
||||||
|
|
||||||
**Note:** For a list of available controls, you can also use ``amixer`` on
|
|
||||||
the commandline::
|
|
||||||
|
|
||||||
$ amixer
|
|
||||||
|
|
||||||
To elaborate the example, calling :func:`mixers` with the argument
|
|
||||||
``cardindex=0`` should give the same list of Mixer controls as::
|
|
||||||
|
|
||||||
$ amixer -c 0
|
|
||||||
|
|
||||||
And calling :func:`mixers` with the argument ``device='foo'`` should give
|
|
||||||
the same list of Mixer controls as::
|
|
||||||
|
|
||||||
$ amixer -D foo
|
|
||||||
|
|
||||||
*Changed in 0.8*:
|
|
||||||
|
|
||||||
- The keyword argument `device` is new and can be used to
|
|
||||||
select virtual devices. As a result, the default behaviour has subtly
|
|
||||||
changed. Since 0.8, this functions returns the mixers for the default
|
|
||||||
device, not the mixers for the first card.
|
|
||||||
|
|
||||||
.. function:: asoundlib_version() -> str
|
|
||||||
|
|
||||||
Return a Python string containing the ALSA version found.
|
|
||||||
|
|
||||||
|
|
||||||
.. _pcm-objects:
|
|
||||||
|
|
||||||
PCM Objects
|
|
||||||
-----------
|
|
||||||
|
|
||||||
PCM objects in :mod:`alsaaudio` can play or capture (record) PCM
|
|
||||||
sound through speakers or a microphone. The PCM constructor takes the
|
|
||||||
following arguments:
|
|
||||||
|
|
||||||
.. class:: PCM(type: int = PCM_PLAYBACK, mode: int = PCM_NORMAL, rate: int = 44100, channels: int = 2,
|
|
||||||
format: int = PCM_FORMAT_S16_LE, periodsize: int = 32, periods: int = 4,
|
|
||||||
device: str = 'default', cardindex: int = -1) -> PCM
|
|
||||||
|
|
||||||
This class is used to represent a PCM device (either for playback or
|
|
||||||
recording). The constructor's arguments are:
|
|
||||||
|
|
||||||
* *type* - can be either :const:`PCM_CAPTURE` or :const:`PCM_PLAYBACK`
|
|
||||||
(default).
|
|
||||||
* *mode* - can be either :const:`PCM_NONBLOCK`, or :const:`PCM_NORMAL`
|
|
||||||
(default).
|
|
||||||
* *rate* - the sampling rate in Hz. Typical values are ``8000`` (mainly used for telephony), ``16000``, ``44100`` (default), ``48000`` and ``96000``.
|
|
||||||
* *channels* - the number of channels. The default value is 2 (stereo).
|
|
||||||
* *format* - the data format. This controls how the PCM device interprets data for playback, and how data is encoded in captures.
|
|
||||||
The default value is :const:`PCM_FORMAT_S16_LE`.
|
|
||||||
|
|
||||||
========================= ===============
|
|
||||||
Format Description
|
|
||||||
========================= ===============
|
|
||||||
``PCM_FORMAT_S8`` Signed 8 bit samples for each channel
|
|
||||||
``PCM_FORMAT_U8`` Unsigned 8 bit samples for each channel
|
|
||||||
``PCM_FORMAT_S16_LE`` Signed 16 bit samples for each channel Little Endian byte order)
|
|
||||||
``PCM_FORMAT_S16_BE`` Signed 16 bit samples for each channel (Big Endian byte order)
|
|
||||||
``PCM_FORMAT_U16_LE`` Unsigned 16 bit samples for each channel (Little Endian byte order)
|
|
||||||
``PCM_FORMAT_U16_BE`` Unsigned 16 bit samples for each channel (Big Endian byte order)
|
|
||||||
``PCM_FORMAT_S24_LE`` Signed 24 bit samples for each channel (Little Endian byte order in 4 bytes)
|
|
||||||
``PCM_FORMAT_S24_BE`` Signed 24 bit samples for each channel (Big Endian byte order in 4 bytes)
|
|
||||||
``PCM_FORMAT_U24_LE`` Unsigned 24 bit samples for each channel (Little Endian byte order in 4 bytes)
|
|
||||||
``PCM_FORMAT_U24_BE`` Unsigned 24 bit samples for each channel (Big Endian byte order in 4 bytes)
|
|
||||||
``PCM_FORMAT_S32_LE`` Signed 32 bit samples for each channel (Little Endian byte order)
|
|
||||||
``PCM_FORMAT_S32_BE`` Signed 32 bit samples for each channel (Big Endian byte order)
|
|
||||||
``PCM_FORMAT_U32_LE`` Unsigned 32 bit samples for each channel (Little Endian byte order)
|
|
||||||
``PCM_FORMAT_U32_BE`` Unsigned 32 bit samples for each channel (Big Endian byte order)
|
|
||||||
``PCM_FORMAT_FLOAT_LE`` 32 bit samples encoded as float (Little Endian byte order)
|
|
||||||
``PCM_FORMAT_FLOAT_BE`` 32 bit samples encoded as float (Big Endian byte order)
|
|
||||||
``PCM_FORMAT_FLOAT64_LE`` 64 bit samples encoded as float (Little Endian byte order)
|
|
||||||
``PCM_FORMAT_FLOAT64_BE`` 64 bit samples encoded as float (Big Endian byte order)
|
|
||||||
``PCM_FORMAT_MU_LAW`` A logarithmic encoding (used by Sun .au files and telephony)
|
|
||||||
``PCM_FORMAT_A_LAW`` Another logarithmic encoding
|
|
||||||
``PCM_FORMAT_IMA_ADPCM`` A 4:1 compressed format defined by the Interactive Multimedia Association.
|
|
||||||
``PCM_FORMAT_MPEG`` MPEG encoded audio?
|
|
||||||
``PCM_FORMAT_GSM`` 9600 bits/s constant rate encoding for speech
|
|
||||||
``PCM_FORMAT_S24_3LE`` Signed 24 bit samples for each channel (Little Endian byte order in 3 bytes)
|
|
||||||
``PCM_FORMAT_S24_3BE`` Signed 24 bit samples for each channel (Big Endian byte order in 3 bytes)
|
|
||||||
``PCM_FORMAT_U24_3LE`` Unsigned 24 bit samples for each channel (Little Endian byte order in 3 bytes)
|
|
||||||
``PCM_FORMAT_U24_3BE`` Unsigned 24 bit samples for each channel (Big Endian byte order in 3 bytes)
|
|
||||||
========================= ===============
|
|
||||||
|
|
||||||
* *periodsize* - the period size in frames.
|
|
||||||
Make sure you understand :ref:`the meaning of periods <term-period>`.
|
|
||||||
The default value is 32, which is below the actual minimum of most devices,
|
|
||||||
and will therefore likely be larger in practice.
|
|
||||||
* *periods* - the number of periods in the buffer. The default value is 4.
|
|
||||||
* *device* - the name of the PCM device that should be used (for example
|
|
||||||
a value from the output of :func:`pcms`). The default value is
|
|
||||||
``'default'``.
|
|
||||||
* *cardindex* - the card index. If this argument is given, the device name
|
|
||||||
is constructed as 'hw:*cardindex*' and
|
|
||||||
the `device` keyword argument is ignored.
|
|
||||||
``0`` is the first hardware sound card.
|
|
||||||
|
|
||||||
**Note:** This should not be used, as it bypasses most of ALSA's configuration.
|
|
||||||
|
|
||||||
The defaults mentioned above are values passed by :mod:alsaaudio
|
|
||||||
to ALSA, not anything internal to ALSA.
|
|
||||||
|
|
||||||
**Note:** For default and non-default values alike, there is no
|
|
||||||
guarantee that a PCM device supports the requested configuration,
|
|
||||||
and ALSA may pick realizable values which it believes to be closest
|
|
||||||
to the request. Therefore, after creating a PCM object, it is
|
|
||||||
necessary to verify whether its realized configuration is acceptable.
|
|
||||||
The :func:info method can be used to query it.
|
|
||||||
|
|
||||||
*Changed in 0.10:*
|
|
||||||
|
|
||||||
- Added the optional named parameter `periods`.
|
|
||||||
|
|
||||||
*Changed in 0.9:*
|
|
||||||
|
|
||||||
- Added the optional named parameters `rate`, `channels`, `format` and `periodsize`.
|
|
||||||
|
|
||||||
*Changed in 0.8:*
|
|
||||||
|
|
||||||
- The `card` keyword argument is still supported,
|
|
||||||
but deprecated. Please use `device` instead.
|
|
||||||
|
|
||||||
- The keyword argument `cardindex` was added.
|
|
||||||
|
|
||||||
The `card` keyword is deprecated because it guesses the real ALSA
|
|
||||||
name of the card. This was always fragile and broke some legitimate usecases.
|
|
||||||
|
|
||||||
PCM objects have the following methods:
|
|
||||||
|
|
||||||
.. method:: PCM.info() -> dict
|
|
||||||
|
|
||||||
Returns a dictionary containing the configuration of a PCM device.
|
|
||||||
|
|
||||||
A small subset of properties reflects fixed parameters given by the
|
|
||||||
user, stored within alsaaudio. To distinguish them from properties
|
|
||||||
retrieved from ALSA when the call is made, they have their name
|
|
||||||
prefixed with **" (call value) "**.
|
|
||||||
|
|
||||||
Descriptions of properties which can be directly set during PCM object
|
|
||||||
instantiation carry the prefix "PCM():", followed by the respective
|
|
||||||
constructor parameter. Note that due to device limitations, the values
|
|
||||||
may deviate from those originally requested.
|
|
||||||
|
|
||||||
Yet another set of properties cannot be set, and derives directly from
|
|
||||||
the hardware, possibly depending on other properties. Those properties'
|
|
||||||
descriptions are prefixed with "hw:" below.
|
|
||||||
|
|
||||||
=========================== ==================================== ==================================================================
|
|
||||||
Key Description (Reference) Type
|
|
||||||
=========================== ==================================== ==================================================================
|
|
||||||
name PCM():device string
|
|
||||||
card_no *index of card* integer (negative indicates device not associable with a card)
|
|
||||||
device_no *index of PCM device* integer
|
|
||||||
subdevice_no *index of PCM subdevice* integer
|
|
||||||
state *name of PCM state* string
|
|
||||||
access_type *name of PCM access type* string
|
|
||||||
(call value) type PCM():type integer
|
|
||||||
(call value) type_name PCM():type string
|
|
||||||
(call value) mode PCM():mode integer
|
|
||||||
(call value) mode_name PCM():mode string
|
|
||||||
format PCM():format integer
|
|
||||||
format_name PCM():format string
|
|
||||||
format_description PCM():format string
|
|
||||||
subformat_name *name of PCM subformat* string
|
|
||||||
subformat_description *description of subformat* string
|
|
||||||
channels PCM():channels integer
|
|
||||||
rate PCM():rate integer (Hz)
|
|
||||||
period_time *period duration* integer (:math:`\mu s`)
|
|
||||||
period_size PCM():period_size integer (frames)
|
|
||||||
buffer_time *buffer time* integer (:math:`\mu s`) (negative indicates error)
|
|
||||||
buffer_size *buffer size* integer (frames) (negative indicates error)
|
|
||||||
get_periods *approx. periods in buffer* integer (negative indicates error)
|
|
||||||
rate_numden *numerator, denominator* tuple (integer (Hz), integer (Hz))
|
|
||||||
significant_bits *significant bits in sample* [#tss]_ integer (negative indicates error)
|
|
||||||
nominal_bits *nominal bits in sample* [#tss]_ integer (negative indicates error)
|
|
||||||
physical_bits *sample width in bits* [#tss]_ integer (negative indicates error)
|
|
||||||
is_batch *hw: double buffering* boolean (True: hardware supported)
|
|
||||||
is_block_transfer *hw: block transfer* boolean (True: hardware supported)
|
|
||||||
is_double *hw: double buffering* boolean (True: hardware supported)
|
|
||||||
is_half_duplex *hw: half-duplex* boolean (True: hardware supported)
|
|
||||||
is_joint_duplex *hw: joint-duplex* boolean (True: hardware supported)
|
|
||||||
can_overrange *hw: overrange detection* boolean (True: hardware supported)
|
|
||||||
can_mmap_sample_resolution *hw: sample-resol. mmap* boolean (True: hardware supported)
|
|
||||||
can_pause *hw: pause* boolean (True: hardware supported)
|
|
||||||
can_resume *hw: resume* boolean (True: hardware supported)
|
|
||||||
can_sync_start *hw: synchronized start* boolean (True: hardware supported)
|
|
||||||
=========================== ==================================== ==================================================================
|
|
||||||
.. [#tss] More information in the :ref:`terminology section for sample size <term-sample-size>`
|
|
||||||
|
|
||||||
..
|
|
||||||
|
|
||||||
The italicized descriptions give a summary of the "full" description
|
|
||||||
as can be found in the
|
|
||||||
`ALSA documentation <https://www.alsa-project.org/alsa-doc>`_.
|
|
||||||
|
|
||||||
*New in 0.9.1*
|
|
||||||
|
|
||||||
.. method:: PCM.dumpinfo()
|
|
||||||
|
|
||||||
Dumps the PCM object's configured parameters to stdout.
|
|
||||||
|
|
||||||
.. method:: PCM.pcmtype() -> int
|
|
||||||
|
|
||||||
Returns the type of PCM object. Either :const:`PCM_CAPTURE` or
|
|
||||||
:const:`PCM_PLAYBACK`.
|
|
||||||
|
|
||||||
.. method:: PCM.pcmmode() -> int
|
|
||||||
|
|
||||||
Return the mode of the PCM object. One of :const:`PCM_NONBLOCK`,
|
|
||||||
:const:`PCM_ASYNC`, or :const:`PCM_NORMAL`
|
|
||||||
|
|
||||||
.. method:: PCM.cardname() -> string
|
|
||||||
|
|
||||||
Return the name of the sound card used by this PCM object.
|
|
||||||
|
|
||||||
..
|
|
||||||
Omitted by intention due to not really fitting the c'tor-based setup concept:
|
|
||||||
|
|
||||||
.. method:: PCM.getchannels()
|
|
||||||
|
|
||||||
Returns list of the device's supported channel counts.
|
|
||||||
|
|
||||||
.. method:: PCM.getratebounds()
|
|
||||||
|
|
||||||
Returns the card's minimum and maximum supported sample rates as
|
|
||||||
a tuple of integers.
|
|
||||||
|
|
||||||
.. method:: PCM.getrates()
|
|
||||||
|
|
||||||
Returns the sample rates supported by the device.
|
|
||||||
The returned value can be of one of the following, depending on
|
|
||||||
the card's properties:
|
|
||||||
* Card supports only a single rate: returns the rate
|
|
||||||
* Card supports a continuous range of rates: returns a tuple of
|
|
||||||
the range's lower and upper bounds (inclusive)
|
|
||||||
* Card supports a collection of well-known rates: returns a list of
|
|
||||||
the supported rates
|
|
||||||
|
|
||||||
.. method:: PCM.getformats()
|
|
||||||
|
|
||||||
Returns a dictionary of supported format codes (integers) keyed by
|
|
||||||
their standard ALSA names (strings).
|
|
||||||
|
|
||||||
.. method:: PCM.setchannels(nchannels: int) -> int
|
|
||||||
|
|
||||||
.. deprecated:: 0.9 Use the `channels` named argument to :func:`PCM`.
|
|
||||||
|
|
||||||
.. method:: PCM.setrate(rate: int) -> int
|
|
||||||
|
|
||||||
.. deprecated:: 0.9 Use the `rate` named argument to :func:`PCM`.
|
|
||||||
|
|
||||||
.. method:: PCM.setformat(format: int) -> int
|
|
||||||
|
|
||||||
.. deprecated:: 0.9 Use the `format` named argument to :func:`PCM`.
|
|
||||||
|
|
||||||
.. method:: PCM.setperiodsize(period: int) -> int
|
|
||||||
|
|
||||||
.. deprecated:: 0.9 Use the `periodsize` named argument to :func:`PCM`.
|
|
||||||
|
|
||||||
.. method:: PCM.state() -> int
|
|
||||||
|
|
||||||
Returs the current state of the stream, which can be one of
|
|
||||||
:const:`PCM_STATE_OPEN` (this should not actually happen),
|
|
||||||
:const:`PCM_STATE_SETUP` (after :func:`drop` or :func:`drain`),
|
|
||||||
:const:`PCM_STATE_PREPARED` (after construction),
|
|
||||||
:const:`PCM_STATE_RUNNING`,
|
|
||||||
:const:`PCM_STATE_XRUN`,
|
|
||||||
:const:`PCM_STATE_DRAINING`,
|
|
||||||
:const:`PCM_STATE_PAUSED`,
|
|
||||||
:const:`PCM_STATE_SUSPENDED`, and
|
|
||||||
:const:`PCM_STATE_DISCONNECTED`.
|
|
||||||
|
|
||||||
*New in 0.10*
|
|
||||||
|
|
||||||
.. method:: PCM.avail() -> int
|
|
||||||
|
|
||||||
For :const:`PCM_PLAYBACK` PCM objects, returns the number of writable
|
|
||||||
(that is, free) frames in the buffer.
|
|
||||||
|
|
||||||
For :const:`PCM_CAPTURE` PCM objects, returns the number of readable
|
|
||||||
(that is, filled) frames in the buffer.
|
|
||||||
|
|
||||||
An attempt to read/write more frames than indicated will block (in
|
|
||||||
:const:`PCM_NORMAL` mode) or fail and return zero (in
|
|
||||||
:const:`PCM_NONBLOCK` mode).
|
|
||||||
|
|
||||||
*New in 0.11*
|
|
||||||
|
|
||||||
.. method:: PCM.read() -> tuple[int, bytes]
|
|
||||||
|
|
||||||
In :const:`PCM_NORMAL` mode, this function blocks until a full period is
|
|
||||||
available, and then returns a tuple (length,data) where *length* is
|
|
||||||
the number of frames of captured data, and *data* is the captured
|
|
||||||
sound frames as a string. The length of the returned data will be
|
|
||||||
periodsize\*framesize bytes.
|
|
||||||
|
|
||||||
In :const:`PCM_NONBLOCK` mode, the call will not block, but will return
|
|
||||||
``(0,'')`` if no new period has become available since the last
|
|
||||||
call to read.
|
|
||||||
|
|
||||||
In case of a buffer overrun, this function will return the negative
|
|
||||||
size :const:`-EPIPE`, and no data is read.
|
|
||||||
This indicates that data was lost. To resume capturing, just call read
|
|
||||||
again, but note that the stream was already corrupted.
|
|
||||||
To avoid the problem in the future, try using a larger period size
|
|
||||||
and/or more periods, at the cost of higher latency.
|
|
||||||
|
|
||||||
.. method:: PCM.write(data: bytes) -> int
|
|
||||||
|
|
||||||
Writes (plays) the sound in data. The length of data *must* be a
|
|
||||||
multiple of the frame size, and *should* be exactly the size of a
|
|
||||||
period. If less than 'period size' frames are provided, the actual
|
|
||||||
playout will not happen until more data is written.
|
|
||||||
|
|
||||||
If the data was successfully written, the call returns the size of the
|
|
||||||
data *in frames*.
|
|
||||||
|
|
||||||
If the device is not in :const:`PCM_NONBLOCK` mode, this call will block
|
|
||||||
if the kernel buffer is full, and until enough sound has been played
|
|
||||||
to allow the sound data to be buffered.
|
|
||||||
|
|
||||||
In :const:`PCM_NONBLOCK` mode, the call will return immediately, with a
|
|
||||||
return value of zero, if the buffer is full. In this case, the data
|
|
||||||
should be written again at a later time.
|
|
||||||
|
|
||||||
In case of a buffer underrun, this function will return the negative
|
|
||||||
size :const:`-EPIPE`, and no data is written.
|
|
||||||
At this point, the playback was already corrupted. If you want to play
|
|
||||||
the data nonetheless, call write again with the same data.
|
|
||||||
To avoid the problem in the future, try using a larger period size
|
|
||||||
and/or more periods, at the cost of higher latency.
|
|
||||||
|
|
||||||
Note that this call completing means only that the samples were buffered
|
|
||||||
in the kernel, and playout will continue afterwards. Make sure that the
|
|
||||||
stream is drained before discarding the PCM handle.
|
|
||||||
|
|
||||||
.. method:: PCM.pause([enable: int = True]) -> int
|
|
||||||
|
|
||||||
If *enable* is :const:`True`, playback or capture is paused.
|
|
||||||
Otherwise, playback/capture is resumed.
|
|
||||||
|
|
||||||
.. method:: PCM.drop() -> int
|
|
||||||
|
|
||||||
Stop the stream and drop residual buffered frames.
|
|
||||||
|
|
||||||
*New in 0.9*
|
|
||||||
|
|
||||||
.. method:: PCM.drain() -> int
|
|
||||||
|
|
||||||
For :const:`PCM_PLAYBACK` PCM objects, play residual buffered frames
|
|
||||||
and then stop the stream. In :const:`PCM_NORMAL` mode,
|
|
||||||
this function blocks until all pending playback is drained.
|
|
||||||
|
|
||||||
For :const:`PCM_CAPTURE` PCM objects, this function is not very useful.
|
|
||||||
|
|
||||||
*New in 0.10*
|
|
||||||
|
|
||||||
.. method:: PCM.close() -> None
|
|
||||||
|
|
||||||
Closes the PCM device.
|
|
||||||
|
|
||||||
For :const:`PCM_PLAYBACK` PCM objects in :const:`PCM_NORMAL` mode,
|
|
||||||
this function blocks until all pending playback is drained.
|
|
||||||
|
|
||||||
.. method:: PCM.polldescriptors() -> list[tuple[int, int]]
|
|
||||||
|
|
||||||
Returns a list of tuples of *(file descriptor, eventmask)* that can be
|
|
||||||
used to wait for changes on the PCM with *select.poll*.
|
|
||||||
|
|
||||||
The *eventmask* value is compatible with `poll.register`__ in the Python
|
|
||||||
:const:`select` module.
|
|
||||||
|
|
||||||
.. method:: PCM.polldescriptors_revents(descriptors: list[tuple[int, int]]) -> int
|
|
||||||
|
|
||||||
Processes the descriptor list returned by :func:`polldescriptors` after
|
|
||||||
using it with *select.poll*, and returns a single *eventmask* value that
|
|
||||||
is meaningful for deciding whether :func:`read` or :func:`write` should
|
|
||||||
be called.
|
|
||||||
|
|
||||||
*New in 0.11*
|
|
||||||
|
|
||||||
.. method:: PCM.set_tstamp_mode([mode: int = PCM_TSTAMP_ENABLE])
|
|
||||||
|
|
||||||
Set the ALSA timestamp mode on the device. The mode argument can be set to
|
|
||||||
either :const:`PCM_TSTAMP_NONE` or :const:`PCM_TSTAMP_ENABLE`.
|
|
||||||
|
|
||||||
.. method:: PCM.get_tstamp_mode() -> int
|
|
||||||
|
|
||||||
Return the integer value corresponding to the ALSA timestamp mode. The
|
|
||||||
return value can be either :const:`PCM_TSTAMP_NONE` or :const:`PCM_TSTAMP_ENABLE`.
|
|
||||||
|
|
||||||
.. method:: PCM.set_tstamp_type([type: int = PCM_TSTAMP_TYPE_GETTIMEOFDAY]) -> None
|
|
||||||
|
|
||||||
Set the ALSA timestamp mode on the device. The type argument
|
|
||||||
can be set to either :const:`PCM_TSTAMP_TYPE_GETTIMEOFDAY`,
|
|
||||||
:const:`PCM_TSTAMP_TYPE_MONOTONIC` or :const:`PCM_TSTAMP_TYPE_MONOTONIC_RAW`.
|
|
||||||
|
|
||||||
.. method:: PCM.get_tstamp_type() -> int
|
|
||||||
|
|
||||||
Return the integer value corresponding to the ALSA timestamp type. The
|
|
||||||
return value can be either :const:`PCM_TSTAMP_TYPE_GETTIMEOFDAY`,
|
|
||||||
:const:`PCM_TSTAMP_TYPE_MONOTONIC` or :const:`PCM_TSTAMP_TYPE_MONOTONIC_RAW`.
|
|
||||||
|
|
||||||
.. method:: PCM.htimestamp() -> tuple[int, int, int]
|
|
||||||
|
|
||||||
Return a Python tuple *(seconds, nanoseconds, frames_available_in_buffer)*.
|
|
||||||
|
|
||||||
The type of output is controlled by the tstamp_type, as described in the table below.
|
|
||||||
|
|
||||||
================================= ===========================================
|
|
||||||
Timestamp Type Description
|
|
||||||
================================= ===========================================
|
|
||||||
``PCM_TSTAMP_TYPE_GETTIMEOFDAY`` System-wide realtime clock with seconds
|
|
||||||
since epoch.
|
|
||||||
``PCM_TSTAMP_TYPE_MONOTONIC`` Monotonic time from an unspecified starting
|
|
||||||
time. Progress is NTP synchronized.
|
|
||||||
``PCM_TSTAMP_TYPE_MONOTONIC_RAW`` Monotonic time from an unspecified starting
|
|
||||||
time using only the system clock.
|
|
||||||
================================= ===========================================
|
|
||||||
|
|
||||||
The timestamp mode is controlled by the tstamp_mode, as described in the table below.
|
|
||||||
|
|
||||||
================================= ===========================================
|
|
||||||
Timestamp Mode Description
|
|
||||||
================================= ===========================================
|
|
||||||
``PCM_TSTAMP_NONE`` No timestamp.
|
|
||||||
``PCM_TSTAMP_ENABLE`` Update timestamp at every hardware position
|
|
||||||
update.
|
|
||||||
================================= ===========================================
|
|
||||||
|
|
||||||
**A few hints on using PCM devices for playback**
|
|
||||||
|
|
||||||
The most common reason for problems with playback of PCM audio is that writes
|
|
||||||
to PCM devices must *exactly* match the data rate of the device.
|
|
||||||
|
|
||||||
If too little data is written to the device, it will underrun, and
|
|
||||||
ugly clicking sounds will occur. Conversely, if too much data is
|
|
||||||
written to the device, the write function will either block
|
|
||||||
(:const:`PCM_NORMAL` mode) or return zero (:const:`PCM_NONBLOCK` mode).
|
|
||||||
|
|
||||||
If your program does nothing but play sound, the best strategy is to put the
|
|
||||||
device in :const:`PCM_NORMAL` mode, and just write as much data to the device as
|
|
||||||
possible. This strategy can also be achieved by using a separate
|
|
||||||
thread with the sole task of playing out sound.
|
|
||||||
|
|
||||||
In GUI programs, however, it may be a better strategy to setup the device,
|
|
||||||
preload the buffer with a few periods by calling write a couple of times, and
|
|
||||||
then use some timer method to write one period size of data to the device every
|
|
||||||
period. The purpose of the preloading is to avoid underrun clicks if the used
|
|
||||||
timer doesn't expire exactly on time.
|
|
||||||
|
|
||||||
Also note, that most timer APIs that you can find for Python will
|
|
||||||
accummulate time delays: If you set the timer to expire after 1/10'th
|
|
||||||
of a second, the actual timeout will happen slightly later, which will
|
|
||||||
accumulate to quite a lot after a few seconds. Hint: use time.time()
|
|
||||||
to check how much time has really passed, and add extra writes as nessecary.
|
|
||||||
|
|
||||||
|
|
||||||
.. _mixer-objects:
|
|
||||||
|
|
||||||
Mixer Objects
|
|
||||||
-------------
|
|
||||||
|
|
||||||
Mixer objects provides access to the ALSA mixer API.
|
|
||||||
|
|
||||||
.. class:: Mixer(control: str = 'Master', id: int = 0, cardindex: int = -1, device: str = 'default') -> Mixer
|
|
||||||
|
|
||||||
Arguments are:
|
|
||||||
|
|
||||||
* *control* - specifies which control to manipulate using this mixer
|
|
||||||
object. The list of available controls can be found with the
|
|
||||||
:mod:`alsaaudio`.\ :func:`mixers` function. The default value is
|
|
||||||
``'Master'`` - other common controls may be ``'Master Mono'``, ``'PCM'``,
|
|
||||||
``'Line'``, etc.
|
|
||||||
|
|
||||||
* *id* - the id of the mixer control. Default is ``0``.
|
|
||||||
|
|
||||||
* *cardindex* - specifies which card should be used. If this argument
|
|
||||||
is given, the device name is constructed like this: 'hw:*cardindex*' and
|
|
||||||
the `device` keyword argument is ignored. ``0`` is the
|
|
||||||
first sound card.
|
|
||||||
|
|
||||||
* *device* - the name of the device on which the mixer resides. The default
|
|
||||||
value is ``'default'``.
|
|
||||||
|
|
||||||
*Changed in 0.8*:
|
|
||||||
|
|
||||||
- The keyword argument `device` is new and can be used to select virtual
|
|
||||||
devices.
|
|
||||||
|
|
||||||
Mixer objects have the following methods:
|
|
||||||
|
|
||||||
.. method:: Mixer.cardname() -> str
|
|
||||||
|
|
||||||
Return the name of the sound card used by this Mixer object
|
|
||||||
|
|
||||||
.. method:: Mixer.mixer() -> str
|
|
||||||
|
|
||||||
Return the name of the specific mixer controlled by this object, For example
|
|
||||||
``'Master'`` or ``'PCM'``
|
|
||||||
|
|
||||||
.. method:: Mixer.mixerid() -> int
|
|
||||||
|
|
||||||
Return the ID of the ALSA mixer controlled by this object.
|
|
||||||
|
|
||||||
.. method:: Mixer.switchcap() -> int
|
|
||||||
|
|
||||||
Returns a list of the switches which are defined by this specific mixer.
|
|
||||||
Possible values in this list are:
|
|
||||||
|
|
||||||
====================== ================
|
|
||||||
Switch Description
|
|
||||||
====================== ================
|
|
||||||
'Mute' This mixer can mute
|
|
||||||
'Joined Mute' This mixer can mute all channels at the same time
|
|
||||||
'Playback Mute' This mixer can mute the playback output
|
|
||||||
'Joined Playback Mute' Mute playback for all channels at the same time}
|
|
||||||
'Capture Mute' Mute sound capture
|
|
||||||
'Joined Capture Mute' Mute sound capture for all channels at a time}
|
|
||||||
'Capture Exclusive' Not quite sure what this is
|
|
||||||
====================== ================
|
|
||||||
|
|
||||||
To manipulate these switches use the :meth:`setrec` or
|
|
||||||
:meth:`setmute` methods
|
|
||||||
|
|
||||||
.. method:: Mixer.volumecap() -> int
|
|
||||||
|
|
||||||
Returns a list of the volume control capabilities of this
|
|
||||||
mixer. Possible values in the list are:
|
|
||||||
|
|
||||||
======================== ================
|
|
||||||
Capability Description
|
|
||||||
======================== ================
|
|
||||||
'Volume' This mixer can control volume
|
|
||||||
'Joined Volume' This mixer can control volume for all channels at the same time
|
|
||||||
'Playback Volume' This mixer can manipulate the playback output
|
|
||||||
'Joined Playback Volume' Manipulate playback volumne for all channels at the same time
|
|
||||||
'Capture Volume' Manipulate sound capture volume
|
|
||||||
'Joined Capture Volume' Manipulate sound capture volume for all channels at a time
|
|
||||||
======================== ================
|
|
||||||
|
|
||||||
.. method:: Mixer.getenum() -> tuple[str, list[str]]
|
|
||||||
|
|
||||||
For enumerated controls, return the currently selected item and the list of
|
|
||||||
items available.
|
|
||||||
|
|
||||||
Returns a tuple *(string, list of strings)*.
|
|
||||||
|
|
||||||
For example, my soundcard has a Mixer called *Mono Output Select*. Using
|
|
||||||
*amixer*, I get::
|
|
||||||
|
|
||||||
$ amixer get "Mono Output Select"
|
|
||||||
Simple mixer control 'Mono Output Select',0
|
|
||||||
Capabilities: enum
|
|
||||||
Items: 'Mix' 'Mic'
|
|
||||||
Item0: 'Mix'
|
|
||||||
|
|
||||||
Using :mod:`alsaaudio`, one could do::
|
|
||||||
|
|
||||||
>>> import alsaaudio
|
|
||||||
>>> m = alsaaudio.Mixer('Mono Output Select')
|
|
||||||
>>> m.getenum()
|
|
||||||
('Mix', ['Mix', 'Mic'])
|
|
||||||
|
|
||||||
This method will return an empty tuple if the mixer is not an enumerated
|
|
||||||
control.
|
|
||||||
|
|
||||||
.. method:: Mixer.setenum(index: int) -> None
|
|
||||||
|
|
||||||
For enumerated controls, sets the currently selected item.
|
|
||||||
*index* is an index into the list of available enumerated items returned
|
|
||||||
by :func:`getenum`.
|
|
||||||
|
|
||||||
.. method:: Mixer.getrange(pcmtype: int = PCM_PLAYBACK, units: int = VOLUME_UNITS_RAW) -> tuple[int, int]
|
|
||||||
|
|
||||||
Return the volume range of the ALSA mixer controlled by this object.
|
|
||||||
The value is a tuple of integers whose meaning is determined by the
|
|
||||||
*units* argument.
|
|
||||||
|
|
||||||
The optional *pcmtype* argument can be either :const:`PCM_PLAYBACK` or
|
|
||||||
:const:`PCM_CAPTURE`, which is relevant if the mixer can control both
|
|
||||||
playback and capture volume. The default value is :const:`PCM_PLAYBACK`
|
|
||||||
if the mixer has playback channels, otherwise it is :const:`PCM_CAPTURE`.
|
|
||||||
|
|
||||||
The optional *units* argument can be one of :const:`VOLUME_UNITS_PERCENTAGE`,
|
|
||||||
:const:`VOLUME_UNITS_RAW`, or :const:`VOLUME_UNITS_DB`.
|
|
||||||
|
|
||||||
.. method:: Mixer.getvolume(pcmtype: int = PCM_PLAYBACK, units: int = VOLUME_UNITS_PERCENTAGE) -> int
|
|
||||||
|
|
||||||
Returns a list with the current volume settings for each channel. The list
|
|
||||||
elements are integers whose meaning is determined by the *units* argument.
|
|
||||||
|
|
||||||
The optional *pcmtype* argument can be either :const:`PCM_PLAYBACK` or
|
|
||||||
:const:`PCM_CAPTURE`, which is relevant if the mixer can control both
|
|
||||||
playback and capture volume. The default value is :const:`PCM_PLAYBACK`
|
|
||||||
if the mixer has playback channels, otherwise it is :const:`PCM_CAPTURE`.
|
|
||||||
|
|
||||||
The optional *units* argument can be one of :const:`VOLUME_UNITS_PERCENTAGE`,
|
|
||||||
:const:`VOLUME_UNITS_RAW`, or :const:`VOLUME_UNITS_DB`.
|
|
||||||
|
|
||||||
.. method:: Mixer.setvolume(volume: int, pcmtype: int = PCM_PLAYBACK, units: int = VOLUME_UNITS_PERCENTAGE, channel: (int | None) = None) -> None
|
|
||||||
|
|
||||||
Change the current volume settings for this mixer. The *volume* argument
|
|
||||||
is an integer whose meaning is determined by the *units* argument.
|
|
||||||
|
|
||||||
If the optional argument *channel* is present, the volume is set
|
|
||||||
only for this channel. This assumes that the mixer can control the
|
|
||||||
volume for the channels independently.
|
|
||||||
|
|
||||||
The optional *pcmtype* argument can be either :const:`PCM_PLAYBACK` or
|
|
||||||
:const:`PCM_CAPTURE`, which is relevant if the mixer can control both
|
|
||||||
playback and capture volume. The default value is :const:`PCM_PLAYBACK`
|
|
||||||
if the mixer has playback channels, otherwise it is :const:`PCM_CAPTURE`.
|
|
||||||
|
|
||||||
The optional *units* argument can be one of :const:`VOLUME_UNITS_PERCENTAGE`,
|
|
||||||
:const:`VOLUME_UNITS_RAW`, or :const:`VOLUME_UNITS_DB`.
|
|
||||||
|
|
||||||
.. method:: Mixer.getmute() -> list[int]
|
|
||||||
|
|
||||||
Return a list indicating the current mute setting for each channel.
|
|
||||||
0 means not muted, 1 means muted.
|
|
||||||
|
|
||||||
This method will fail if the mixer has no playback switch capabilities.
|
|
||||||
|
|
||||||
.. method:: Mixer.setmute(mute: bool, channel: (int | None) = None) -> None
|
|
||||||
|
|
||||||
Sets the mute flag to a new value. The *mute* argument is either 0 for not
|
|
||||||
muted, or 1 for muted.
|
|
||||||
|
|
||||||
The optional *channel* argument controls which channel is
|
|
||||||
muted. The default is to set the mute flag for all channels.
|
|
||||||
|
|
||||||
This method will fail if the mixer has no playback mute capabilities
|
|
||||||
|
|
||||||
.. method:: Mixer.getrec() -> list[int]
|
|
||||||
|
|
||||||
Return a list indicating the current record mute setting for each channel.
|
|
||||||
0 means not recording, 1 means recording.
|
|
||||||
|
|
||||||
This method will fail if the mixer has no capture switch capabilities.
|
|
||||||
|
|
||||||
.. method:: Mixer.setrec(capture: int, channel: (int | None) = None) -> None
|
|
||||||
|
|
||||||
Sets the capture mute flag to a new value. The *capture* argument
|
|
||||||
is either 0 for no capture, or 1 for capture.
|
|
||||||
|
|
||||||
The optional *channel* argument controls which channel is
|
|
||||||
changed. The default is to set the capture flag for all channels.
|
|
||||||
|
|
||||||
This method will fail if the mixer has no capture switch capabilities.
|
|
||||||
|
|
||||||
.. method:: Mixer.polldescriptors() -> list[tuple[int, int]]
|
|
||||||
|
|
||||||
Returns a list of tuples of *(file descriptor, eventmask)* that can be
|
|
||||||
used to wait for changes on the mixer with *select.poll*.
|
|
||||||
|
|
||||||
The *eventmask* value is compatible with `poll.register`__ in the Python
|
|
||||||
:const:`select` module.
|
|
||||||
|
|
||||||
.. method:: Mixer.handleevents() -> int
|
|
||||||
|
|
||||||
Acknowledge events on the :func:`polldescriptors` file descriptors
|
|
||||||
to prevent subsequent polls from returning the same events again.
|
|
||||||
Returns the number of events that were acknowledged.
|
|
||||||
|
|
||||||
.. method:: Mixer.close() -> None
|
|
||||||
|
|
||||||
Closes the Mixer device.
|
|
||||||
|
|
||||||
**A rant on the ALSA Mixer API**
|
|
||||||
|
|
||||||
The ALSA mixer API is extremely complicated - and hardly documented at all.
|
|
||||||
:mod:`alsaaudio` implements a much simplified way to access this API. In
|
|
||||||
designing the API I've had to make some choices which may limit what can and
|
|
||||||
cannot be controlled through the API. However, if I had chosen to implement the
|
|
||||||
full API, I would have reexposed the horrible complexity/documentation ratio of
|
|
||||||
the underlying API. At least the :mod:`alsaaudio` API is easy to
|
|
||||||
understand and use.
|
|
||||||
|
|
||||||
If my design choises prevents you from doing something that the underlying API
|
|
||||||
would have allowed, please let me know, so I can incorporate these needs into
|
|
||||||
future versions.
|
|
||||||
|
|
||||||
If the current state of affairs annoys you, the best you can do is to write a
|
|
||||||
HOWTO on the API and make this available on the net. Until somebody does this,
|
|
||||||
the availability of ALSA mixer capable devices will stay quite limited.
|
|
||||||
|
|
||||||
Unfortunately, I'm not able to create such a HOWTO myself, since I only
|
|
||||||
understand half of the API, and that which I do understand has come from a
|
|
||||||
painful trial and error process.
|
|
||||||
|
|
||||||
|
|
||||||
.. _pcm-example:
|
|
||||||
|
|
||||||
Examples
|
|
||||||
--------
|
|
||||||
|
|
||||||
The following example are provided:
|
|
||||||
|
|
||||||
* `playwav.py`
|
|
||||||
* `recordtest.py`
|
|
||||||
* `playbacktest.py`
|
|
||||||
* `mixertest.py`
|
|
||||||
|
|
||||||
All examples (except `mixertest.py`) accept the commandline option
|
|
||||||
*-c <cardname>*.
|
|
||||||
|
|
||||||
To determine a valid card name, use the commandline ALSA player::
|
|
||||||
|
|
||||||
$ aplay -L
|
|
||||||
|
|
||||||
or::
|
|
||||||
|
|
||||||
$ python
|
|
||||||
|
|
||||||
>>> import alsaaudio
|
|
||||||
>>> alsaaudio.pcms()
|
|
||||||
|
|
||||||
mixertest.py accepts the commandline options *-d <device>* and
|
|
||||||
*-c <cardindex>*.
|
|
||||||
|
|
||||||
playwav.py
|
|
||||||
~~~~~~~~~~
|
|
||||||
|
|
||||||
**playwav.py** plays a wav file.
|
|
||||||
|
|
||||||
To test PCM playback (on your default soundcard), run::
|
|
||||||
|
|
||||||
$ python playwav.py <wav file>
|
|
||||||
|
|
||||||
recordtest.py and playbacktest.py
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
**recordtest.py** and **playbacktest.py** will record and play a raw
|
|
||||||
sound file in CD quality.
|
|
||||||
|
|
||||||
To test PCM recordings (on your default soundcard), run::
|
|
||||||
|
|
||||||
$ python recordtest.py <filename>
|
|
||||||
|
|
||||||
Speak into the microphone, and interrupt the recording at any time
|
|
||||||
with ``Ctl-C``.
|
|
||||||
|
|
||||||
Play back the recording with::
|
|
||||||
|
|
||||||
$ python playbacktest.py <filename>
|
|
||||||
|
|
||||||
mixertest.py
|
|
||||||
~~~~~~~~~~~~
|
|
||||||
|
|
||||||
Without arguments, **mixertest.py** will list all available *controls* on the
|
|
||||||
default soundcard.
|
|
||||||
|
|
||||||
The output might look like this::
|
|
||||||
|
|
||||||
$ ./mixertest.py
|
|
||||||
Available mixer controls:
|
|
||||||
'Master'
|
|
||||||
'Master Mono'
|
|
||||||
'Headphone'
|
|
||||||
'PCM'
|
|
||||||
'Line'
|
|
||||||
'Line In->Rear Out'
|
|
||||||
'CD'
|
|
||||||
'Mic'
|
|
||||||
'PC Speaker'
|
|
||||||
'Aux'
|
|
||||||
'Mono Output Select'
|
|
||||||
'Capture'
|
|
||||||
'Mix'
|
|
||||||
'Mix Mono'
|
|
||||||
|
|
||||||
With a single argument - the *control*, it will display the settings of
|
|
||||||
that control; for example::
|
|
||||||
|
|
||||||
$ ./mixertest.py Master
|
|
||||||
Mixer name: 'Master'
|
|
||||||
Capabilities: Playback Volume Playback Mute
|
|
||||||
Channel 0 volume: 61%
|
|
||||||
Channel 1 volume: 61%
|
|
||||||
|
|
||||||
With two arguments, the *control* and a *parameter*, it will set the
|
|
||||||
parameter on the mixer::
|
|
||||||
|
|
||||||
$ ./mixertest.py Master mute
|
|
||||||
|
|
||||||
This will mute the Master mixer.
|
|
||||||
|
|
||||||
Or::
|
|
||||||
|
|
||||||
$ ./mixertest.py Master 40
|
|
||||||
|
|
||||||
This sets the volume to 40% on all channels.
|
|
||||||
|
|
||||||
To select a different soundcard, use either the *device* or *cardindex*
|
|
||||||
argument::
|
|
||||||
|
|
||||||
$ ./mixertest.py -c 0 Master
|
|
||||||
Mixer name: 'Master'
|
|
||||||
Capabilities: Playback Volume Playback Mute
|
|
||||||
Channel 0 volume: 61%
|
|
||||||
Channel 1 volume: 61%
|
|
||||||
@@ -1,128 +0,0 @@
|
|||||||
************
|
|
||||||
Introduction
|
|
||||||
************
|
|
||||||
|
|
||||||
:Author: Casper Wilstrup <cwi@aves.dk>
|
|
||||||
:Author: Lars Immisch <lars@ibp.de>
|
|
||||||
|
|
||||||
.. |release| replace:: version
|
|
||||||
|
|
||||||
.. _front:
|
|
||||||
|
|
||||||
This software is licensed under the PSF license - the same one used by the
|
|
||||||
majority of the python distribution. Basically you can use it for anything you
|
|
||||||
wish (even commercial purposes). There is no warranty whatsoever.
|
|
||||||
|
|
||||||
.. topic:: Abstract
|
|
||||||
|
|
||||||
This package contains wrappers for accessing the ALSA API from Python. It is
|
|
||||||
currently fairly complete for PCM devices and Mixer access. MIDI sequencer
|
|
||||||
support is low on our priority list, but volunteers are welcome.
|
|
||||||
|
|
||||||
If you find bugs in the wrappers please use the github issue tracker.
|
|
||||||
Please don't send bug reports regarding ALSA specifically. There are several
|
|
||||||
bugs in this API, and those should be reported to the ALSA team - not me.
|
|
||||||
|
|
||||||
|
|
||||||
************
|
|
||||||
What is ALSA
|
|
||||||
************
|
|
||||||
|
|
||||||
The Advanced Linux Sound Architecture (ALSA) provides audio and MIDI
|
|
||||||
functionality to the Linux operating system.
|
|
||||||
|
|
||||||
Logically ALSA consists of these components:
|
|
||||||
|
|
||||||
* A set of kernel drivers. --- These drivers are responsible for handling the
|
|
||||||
physical sound hardware from within the Linux kernel, and have been the
|
|
||||||
standard sound implementation in Linux since kernel version 2.5
|
|
||||||
|
|
||||||
* A kernel level API for manipulating the ALSA devices.
|
|
||||||
|
|
||||||
* A user-space C library for simplified access to the sound hardware from
|
|
||||||
userspace applications. This library is called *libasound* and is required by
|
|
||||||
all ALSA capable applications.
|
|
||||||
|
|
||||||
More information about ALSA may be found on the project homepage
|
|
||||||
`<http://www.alsa-project.org>`_
|
|
||||||
|
|
||||||
|
|
||||||
ALSA and Python
|
|
||||||
===============
|
|
||||||
|
|
||||||
The older Linux sound API (OSS) -- which is now deprecated -- is well supported
|
|
||||||
by the standard Python library, through the ossaudiodev module. No native ALSA
|
|
||||||
support exists in the standard library.
|
|
||||||
|
|
||||||
There are a few other "ALSA for Python" projects available, including at least
|
|
||||||
two different projects called pyAlsa. Neither of these seem to be under active
|
|
||||||
development at the time - and neither are very feature complete.
|
|
||||||
|
|
||||||
I wrote PyAlsaAudio to fill this gap. My long term goal is to have the module
|
|
||||||
included in the standard Python library, but that looks currently unlikely.
|
|
||||||
|
|
||||||
PyAlsaAudio has full support for sound capture, playback of sound, as well as
|
|
||||||
the ALSA Mixer API.
|
|
||||||
|
|
||||||
MIDI support is not available, and since I don't own any MIDI hardware, it's
|
|
||||||
difficult for me to implement it. Volunteers to work on this would be greatly
|
|
||||||
appreciated.
|
|
||||||
|
|
||||||
|
|
||||||
************
|
|
||||||
Installation
|
|
||||||
************
|
|
||||||
|
|
||||||
Note: the wrappers link with the alsasound library (from the alsa-lib package)
|
|
||||||
and need the ALSA headers for compilation. Verify that you have
|
|
||||||
/usr/lib/libasound.so and /usr/include/alsa (or similar paths) before building.
|
|
||||||
|
|
||||||
*On Debian (and probably Ubuntu), install libasound2-dev.*
|
|
||||||
|
|
||||||
Naturally you also need to use a kernel with proper ALSA support. This is the
|
|
||||||
default in Linux kernel 2.6 and later. If you are using kernel version 2.4 you
|
|
||||||
may need to install the ALSA patches yourself - although most distributions
|
|
||||||
ship with ALSA kernels.
|
|
||||||
|
|
||||||
To install, execute the following: --- ::
|
|
||||||
|
|
||||||
$ python setup.py build
|
|
||||||
|
|
||||||
And then as root: --- ::
|
|
||||||
|
|
||||||
# python setup.py install
|
|
||||||
|
|
||||||
|
|
||||||
*******
|
|
||||||
Testing
|
|
||||||
*******
|
|
||||||
|
|
||||||
Make sure that :code:`aplay` plays a file through the soundcard you want, then
|
|
||||||
try::
|
|
||||||
|
|
||||||
$ python playwav.py <filename.wav>
|
|
||||||
|
|
||||||
If :code:`aplay` needs a device argument, like
|
|
||||||
:code:`aplay -D hw:CARD=sndrpihifiberry,DEV=0`, use::
|
|
||||||
|
|
||||||
$ python playwav.py -d hw:CARD=sndrpihifiberry,DEV=0 <filename.wav>
|
|
||||||
|
|
||||||
To test PCM recordings (on your default soundcard), verify your
|
|
||||||
microphone works, then do::
|
|
||||||
|
|
||||||
$ python recordtest.py -d <device> <filename>
|
|
||||||
|
|
||||||
Speak into the microphone, and interrupt the recording at any time
|
|
||||||
with ``Ctl-C``.
|
|
||||||
|
|
||||||
Play back the recording with::
|
|
||||||
|
|
||||||
$ python playbacktest.py -d <device> <filename>
|
|
||||||
|
|
||||||
There is a minimal test suite in :code:`test.py`, but it is
|
|
||||||
a bit dependent on the ALSA configuration and may fail without indicating
|
|
||||||
a real problem.
|
|
||||||
|
|
||||||
If you find bugs/problems, please file a `bug report
|
|
||||||
<https://github.com/larsimmisch/pyalsaaudio/issues>`_.
|
|
||||||
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
****************************
|
|
||||||
PCM Terminology and Concepts
|
|
||||||
****************************
|
|
||||||
|
|
||||||
In order to use PCM devices it is useful to be familiar with some concepts and
|
|
||||||
terminology.
|
|
||||||
|
|
||||||
Sample
|
|
||||||
PCM audio, whether it is input or output, consists of *samples*.
|
|
||||||
A single sample represents the amplitude of one channel of sound
|
|
||||||
at a certain point in time. A lot of individual samples are
|
|
||||||
necessary to represent actual sound; for CD audio, 44100 samples
|
|
||||||
are taken every second.
|
|
||||||
|
|
||||||
Samples can be of many different sizes, ranging from 8 bit to 64
|
|
||||||
bit precision. The specific format of each sample can also vary -
|
|
||||||
they can be big endian byte integers, little endian byte integers, or
|
|
||||||
floating point numbers.
|
|
||||||
|
|
||||||
Musically, the sample size determines the dynamic range. The
|
|
||||||
dynamic range is the difference between the quietest and the
|
|
||||||
loudest signal that can be reproduced.
|
|
||||||
|
|
||||||
Frame
|
|
||||||
A frame consists of exactly one sample per channel. If there is only one
|
|
||||||
channel (Mono sound) a frame is simply a single sample. If the sound is
|
|
||||||
stereo, each frame consists of two samples, etc.
|
|
||||||
|
|
||||||
Frame size
|
|
||||||
This is the size in bytes of each frame. This can vary a lot: if each sample
|
|
||||||
is 8 bits, and we're handling mono sound, the frame size is one byte.
|
|
||||||
For six channel audio with 64 bit floating point samples, the frame size
|
|
||||||
is 48 bytes.
|
|
||||||
|
|
||||||
Rate
|
|
||||||
PCM sound consists of a flow of sound frames. The sound rate controls how
|
|
||||||
often the current frame is replaced. For example, a rate of 8000 Hz
|
|
||||||
means that a new frame is played or captured 8000 times per second.
|
|
||||||
|
|
||||||
Data rate
|
|
||||||
This is the number of bytes which must be consumed or provided per
|
|
||||||
second at a certain frame size and rate.
|
|
||||||
|
|
||||||
8000 Hz mono sound with 8 bit (1 byte) samples has a data rate of
|
|
||||||
8000 \* 1 \* 1 = 8 kb/s or 64kbit/s. This is typically used for telephony.
|
|
||||||
|
|
||||||
At the other end of the scale, 96000 Hz, 6 channel sound with 64
|
|
||||||
bit (8 bytes) samples has a data rate of 96000 \* 6 \* 8 = 4608
|
|
||||||
kb/s (almost 5 MB sound data per second).
|
|
||||||
|
|
||||||
If the data rate requirement is not met, an overrun (on capture) or
|
|
||||||
underrun (on playback) occurs; the term "xrun" is used to refer to
|
|
||||||
either event.
|
|
||||||
|
|
||||||
.. _term-period:
|
|
||||||
|
|
||||||
Period
|
|
||||||
The CPU processes sample data in chunks of frames, so-called periods
|
|
||||||
(also called fragments by some systems). The operating system kernel's
|
|
||||||
sample buffer must hold at least two periods (at any given time, one
|
|
||||||
is processed by the sound hardware, and one by the CPU).
|
|
||||||
|
|
||||||
The completion of a *period* triggers a CPU interrupt, which causes
|
|
||||||
processing and context switching overhead. Therefore, a smaller period
|
|
||||||
size causes higher CPU resource usage at a given data rate.
|
|
||||||
|
|
||||||
A bigger size of the *buffer* improves the system's resilience to xruns.
|
|
||||||
The buffer being split into a bigger number of smaller periods also does
|
|
||||||
that, as it allows it to be drained / topped up sooner.
|
|
||||||
|
|
||||||
On the other hand, a bigger size of the *buffer* also increases the
|
|
||||||
playback latency, that is, the time it takes for a frame from being
|
|
||||||
sent out by the application to being actually audible.
|
|
||||||
|
|
||||||
Similarly, a bigger *period* size increases the capture latency.
|
|
||||||
|
|
||||||
The trade-off between latency, xrun resilience, and resource usage
|
|
||||||
must be made depending on the application.
|
|
||||||
|
|
||||||
Period size
|
|
||||||
This is the size of each period in frames. *Not bytes, but frames!*
|
|
||||||
In :mod:`alsaaudio` the period size is set directly, and it is
|
|
||||||
therefore important to understand the significance of this
|
|
||||||
number. If the period size is configured to for example 32,
|
|
||||||
each write should contain exactly 32 frames of sound data, and each
|
|
||||||
read will return either 32 frames of data or nothing at all.
|
|
||||||
|
|
||||||
.. _term-sample-size:
|
|
||||||
|
|
||||||
Sample size
|
|
||||||
Each sample takes *physical_bits* of space. *nominal_bits* tells
|
|
||||||
how many least significant bits are used. This is the bit depth
|
|
||||||
in the format name and sometimes called just *sample bits* or
|
|
||||||
*format width*. *significant_bits* tells how many most significant
|
|
||||||
bits of the *nominal_bits* are used by the sample. This can be thought
|
|
||||||
of as the *sample resolution*. This is visualized as follows::
|
|
||||||
|
|
||||||
MSB |00000000 XXXXXXXX XXXXXXXX 00000000| LSB
|
|
||||||
|--significant--|
|
|
||||||
|---------nominal---------|
|
|
||||||
|-------------physical--------------|
|
|
||||||
|
|
||||||
Once you understand these concepts, you will be ready to use the PCM API. Read
|
|
||||||
on.
|
|
||||||
|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 673 B |
@@ -1,701 +0,0 @@
|
|||||||
@import url("basic.css");
|
|
||||||
|
|
||||||
/* -- page layout ----------------------------------------------------------- */
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: Georgia, serif;
|
|
||||||
font-size: 17px;
|
|
||||||
background-color: #fff;
|
|
||||||
color: #000;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
div.document {
|
|
||||||
width: 940px;
|
|
||||||
margin: 30px auto 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.documentwrapper {
|
|
||||||
float: left;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.bodywrapper {
|
|
||||||
margin: 0 0 0 220px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar {
|
|
||||||
width: 220px;
|
|
||||||
font-size: 14px;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
hr {
|
|
||||||
border: 1px solid #B1B4B6;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body {
|
|
||||||
background-color: #fff;
|
|
||||||
color: #3E4349;
|
|
||||||
padding: 0 30px 0 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body > .section {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footer {
|
|
||||||
width: 940px;
|
|
||||||
margin: 20px auto 30px auto;
|
|
||||||
font-size: 14px;
|
|
||||||
color: #888;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footer a {
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.caption {
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
div.relations {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
div.sphinxsidebar a {
|
|
||||||
color: #444;
|
|
||||||
text-decoration: none;
|
|
||||||
border-bottom: 1px dotted #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar a:hover {
|
|
||||||
border-bottom: 1px solid #999;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebarwrapper {
|
|
||||||
padding: 18px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebarwrapper p.logo {
|
|
||||||
padding: 0;
|
|
||||||
margin: -10px 0 0 0px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebarwrapper h1.logo {
|
|
||||||
margin-top: -10px;
|
|
||||||
text-align: center;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebarwrapper h1.logo-name {
|
|
||||||
margin-top: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebarwrapper p.blurb {
|
|
||||||
margin-top: 0;
|
|
||||||
font-style: normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar h3,
|
|
||||||
div.sphinxsidebar h4 {
|
|
||||||
font-family: Georgia, serif;
|
|
||||||
color: #444;
|
|
||||||
font-size: 24px;
|
|
||||||
font-weight: normal;
|
|
||||||
margin: 0 0 5px 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar h4 {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar h3 a {
|
|
||||||
color: #444;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar p.logo a,
|
|
||||||
div.sphinxsidebar h3 a,
|
|
||||||
div.sphinxsidebar p.logo a:hover,
|
|
||||||
div.sphinxsidebar h3 a:hover {
|
|
||||||
border: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar p {
|
|
||||||
color: #555;
|
|
||||||
margin: 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar ul {
|
|
||||||
margin: 10px 0;
|
|
||||||
padding: 0;
|
|
||||||
color: #000;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar ul li.toctree-l1 > a {
|
|
||||||
font-size: 120%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar ul li.toctree-l2 > a {
|
|
||||||
font-size: 110%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar input {
|
|
||||||
border: 1px solid #CCC;
|
|
||||||
font-family: Georgia, serif;
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar hr {
|
|
||||||
border: none;
|
|
||||||
height: 1px;
|
|
||||||
color: #AAA;
|
|
||||||
background: #AAA;
|
|
||||||
|
|
||||||
text-align: left;
|
|
||||||
margin-left: 0;
|
|
||||||
width: 50%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar .badge {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar .badge:hover {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* To address an issue with donation coming after search */
|
|
||||||
div.sphinxsidebar h3.donation {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- body styles ----------------------------------------------------------- */
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #004B6B;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
color: #6D4100;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body h1,
|
|
||||||
div.body h2,
|
|
||||||
div.body h3,
|
|
||||||
div.body h4,
|
|
||||||
div.body h5,
|
|
||||||
div.body h6 {
|
|
||||||
font-family: Georgia, serif;
|
|
||||||
font-weight: normal;
|
|
||||||
margin: 30px 0px 10px 0px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body h1 { margin-top: 0; padding-top: 0; font-size: 240%; }
|
|
||||||
div.body h2 { font-size: 180%; }
|
|
||||||
div.body h3 { font-size: 150%; }
|
|
||||||
div.body h4 { font-size: 130%; }
|
|
||||||
div.body h5 { font-size: 100%; }
|
|
||||||
div.body h6 { font-size: 100%; }
|
|
||||||
|
|
||||||
a.headerlink {
|
|
||||||
color: #DDD;
|
|
||||||
padding: 0 4px;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.headerlink:hover {
|
|
||||||
color: #444;
|
|
||||||
background: #EAEAEA;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body p, div.body dd, div.body li {
|
|
||||||
line-height: 1.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition {
|
|
||||||
margin: 20px 0px;
|
|
||||||
padding: 10px 30px;
|
|
||||||
background-color: #EEE;
|
|
||||||
border: 1px solid #CCC;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition tt.xref, div.admonition code.xref, div.admonition a tt {
|
|
||||||
background-color: #FBFBFB;
|
|
||||||
border-bottom: 1px solid #fafafa;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition p.admonition-title {
|
|
||||||
font-family: Georgia, serif;
|
|
||||||
font-weight: normal;
|
|
||||||
font-size: 24px;
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
padding: 0;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition p.last {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.highlight {
|
|
||||||
background-color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
dt:target, .highlight {
|
|
||||||
background: #FAF3E8;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.warning {
|
|
||||||
background-color: #FCC;
|
|
||||||
border: 1px solid #FAA;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.danger {
|
|
||||||
background-color: #FCC;
|
|
||||||
border: 1px solid #FAA;
|
|
||||||
-moz-box-shadow: 2px 2px 4px #D52C2C;
|
|
||||||
-webkit-box-shadow: 2px 2px 4px #D52C2C;
|
|
||||||
box-shadow: 2px 2px 4px #D52C2C;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.error {
|
|
||||||
background-color: #FCC;
|
|
||||||
border: 1px solid #FAA;
|
|
||||||
-moz-box-shadow: 2px 2px 4px #D52C2C;
|
|
||||||
-webkit-box-shadow: 2px 2px 4px #D52C2C;
|
|
||||||
box-shadow: 2px 2px 4px #D52C2C;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.caution {
|
|
||||||
background-color: #FCC;
|
|
||||||
border: 1px solid #FAA;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.attention {
|
|
||||||
background-color: #FCC;
|
|
||||||
border: 1px solid #FAA;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.important {
|
|
||||||
background-color: #EEE;
|
|
||||||
border: 1px solid #CCC;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.note {
|
|
||||||
background-color: #EEE;
|
|
||||||
border: 1px solid #CCC;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.tip {
|
|
||||||
background-color: #EEE;
|
|
||||||
border: 1px solid #CCC;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.hint {
|
|
||||||
background-color: #EEE;
|
|
||||||
border: 1px solid #CCC;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.seealso {
|
|
||||||
background-color: #EEE;
|
|
||||||
border: 1px solid #CCC;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.topic {
|
|
||||||
background-color: #EEE;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.admonition-title {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.admonition-title:after {
|
|
||||||
content: ":";
|
|
||||||
}
|
|
||||||
|
|
||||||
pre, tt, code {
|
|
||||||
font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.hll {
|
|
||||||
background-color: #FFC;
|
|
||||||
margin: 0 -12px;
|
|
||||||
padding: 0 12px;
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.screenshot {
|
|
||||||
}
|
|
||||||
|
|
||||||
tt.descname, tt.descclassname, code.descname, code.descclassname {
|
|
||||||
font-size: 0.95em;
|
|
||||||
}
|
|
||||||
|
|
||||||
tt.descname, code.descname {
|
|
||||||
padding-right: 0.08em;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.screenshot {
|
|
||||||
-moz-box-shadow: 2px 2px 4px #EEE;
|
|
||||||
-webkit-box-shadow: 2px 2px 4px #EEE;
|
|
||||||
box-shadow: 2px 2px 4px #EEE;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.docutils {
|
|
||||||
border: 1px solid #888;
|
|
||||||
-moz-box-shadow: 2px 2px 4px #EEE;
|
|
||||||
-webkit-box-shadow: 2px 2px 4px #EEE;
|
|
||||||
box-shadow: 2px 2px 4px #EEE;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.docutils td, table.docutils th {
|
|
||||||
border: 1px solid #888;
|
|
||||||
padding: 0.25em 0.7em;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.field-list, table.footnote {
|
|
||||||
border: none;
|
|
||||||
-moz-box-shadow: none;
|
|
||||||
-webkit-box-shadow: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.footnote {
|
|
||||||
margin: 15px 0;
|
|
||||||
width: 100%;
|
|
||||||
border: 1px solid #EEE;
|
|
||||||
background: #FDFDFD;
|
|
||||||
font-size: 0.9em;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.footnote + table.footnote {
|
|
||||||
margin-top: -15px;
|
|
||||||
border-top: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.field-list th {
|
|
||||||
padding: 0 0.8em 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.field-list td {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.field-list p {
|
|
||||||
margin-bottom: 0.8em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Cloned from
|
|
||||||
* https://github.com/sphinx-doc/sphinx/commit/ef60dbfce09286b20b7385333d63a60321784e68
|
|
||||||
*/
|
|
||||||
.field-name {
|
|
||||||
-moz-hyphens: manual;
|
|
||||||
-ms-hyphens: manual;
|
|
||||||
-webkit-hyphens: manual;
|
|
||||||
hyphens: manual;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.footnote td.label {
|
|
||||||
width: .1px;
|
|
||||||
padding: 0.3em 0 0.3em 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.footnote td {
|
|
||||||
padding: 0.3em 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl dd {
|
|
||||||
margin-left: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
blockquote {
|
|
||||||
margin: 0 0 0 30px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul, ol {
|
|
||||||
/* Matches the 30px from the narrow-screen "li > ul" selector below */
|
|
||||||
margin: 10px 0 10px 30px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
background: #EEE;
|
|
||||||
padding: 7px 30px;
|
|
||||||
margin: 15px 0px;
|
|
||||||
line-height: 1.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.viewcode-block:target {
|
|
||||||
background: #ffd;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl pre, blockquote pre, li pre {
|
|
||||||
margin-left: 0;
|
|
||||||
padding-left: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
tt, code {
|
|
||||||
background-color: #ecf0f3;
|
|
||||||
color: #222;
|
|
||||||
/* padding: 1px 2px; */
|
|
||||||
}
|
|
||||||
|
|
||||||
tt.xref, code.xref, a tt {
|
|
||||||
background-color: #FBFBFB;
|
|
||||||
border-bottom: 1px solid #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.reference {
|
|
||||||
text-decoration: none;
|
|
||||||
border-bottom: 1px dotted #004B6B;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Don't put an underline on images */
|
|
||||||
a.image-reference, a.image-reference:hover {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.reference:hover {
|
|
||||||
border-bottom: 1px solid #6D4100;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.footnote-reference {
|
|
||||||
text-decoration: none;
|
|
||||||
font-size: 0.7em;
|
|
||||||
vertical-align: top;
|
|
||||||
border-bottom: 1px dotted #004B6B;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.footnote-reference:hover {
|
|
||||||
border-bottom: 1px solid #6D4100;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover tt, a:hover code {
|
|
||||||
background: #EEE;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@media screen and (max-width: 870px) {
|
|
||||||
|
|
||||||
div.sphinxsidebar {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.document {
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
div.documentwrapper {
|
|
||||||
margin-left: 0;
|
|
||||||
margin-top: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.bodywrapper {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-right: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
margin-left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
li > ul {
|
|
||||||
/* Matches the 30px from the "ul, ol" selector above */
|
|
||||||
margin-left: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.document {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.bodywrapper {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.github {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@media screen and (max-width: 875px) {
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
padding: 20px 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.documentwrapper {
|
|
||||||
float: none;
|
|
||||||
background: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar {
|
|
||||||
display: block;
|
|
||||||
float: none;
|
|
||||||
width: 102.5%;
|
|
||||||
margin: 50px -30px -20px -30px;
|
|
||||||
padding: 10px 20px;
|
|
||||||
background: #333;
|
|
||||||
color: #FFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar h3, div.sphinxsidebar h4, div.sphinxsidebar p,
|
|
||||||
div.sphinxsidebar h3 a {
|
|
||||||
color: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar a {
|
|
||||||
color: #AAA;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar p.logo {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.document {
|
|
||||||
width: 100%;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footer {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.bodywrapper {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body {
|
|
||||||
min-height: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rtd_doc_footer {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.document {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footer {
|
|
||||||
width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.github {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* misc. */
|
|
||||||
|
|
||||||
.revsys-inline {
|
|
||||||
display: none!important;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Make nested-list/multi-paragraph items look better in Releases changelog
|
|
||||||
* pages. Without this, docutils' magical list fuckery causes inconsistent
|
|
||||||
* formatting between different release sub-lists.
|
|
||||||
*/
|
|
||||||
div#changelog > div.section > ul > li > p:only-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hide fugly table cell borders in ..bibliography:: directive output */
|
|
||||||
table.docutils.citation, table.docutils.citation td, table.docutils.citation th {
|
|
||||||
border: none;
|
|
||||||
/* Below needed in some edge cases; if not applied, bottom shadows appear */
|
|
||||||
-moz-box-shadow: none;
|
|
||||||
-webkit-box-shadow: none;
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* relbar */
|
|
||||||
|
|
||||||
.related {
|
|
||||||
line-height: 30px;
|
|
||||||
width: 100%;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.related.top {
|
|
||||||
border-bottom: 1px solid #EEE;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.related.bottom {
|
|
||||||
border-top: 1px solid #EEE;
|
|
||||||
}
|
|
||||||
|
|
||||||
.related ul {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.related li {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav#rellinks {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav#rellinks li+li:before {
|
|
||||||
content: "|";
|
|
||||||
}
|
|
||||||
|
|
||||||
nav#breadcrumbs li+li:before {
|
|
||||||
content: "\00BB";
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Hide certain items when printing */
|
|
||||||
@media print {
|
|
||||||
div.related {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,925 +0,0 @@
|
|||||||
/*
|
|
||||||
* basic.css
|
|
||||||
* ~~~~~~~~~
|
|
||||||
*
|
|
||||||
* Sphinx stylesheet -- basic theme.
|
|
||||||
*
|
|
||||||
* :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS.
|
|
||||||
* :license: BSD, see LICENSE for details.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* -- main layout ----------------------------------------------------------- */
|
|
||||||
|
|
||||||
div.clearer {
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.section::after {
|
|
||||||
display: block;
|
|
||||||
content: '';
|
|
||||||
clear: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- relbar ---------------------------------------------------------------- */
|
|
||||||
|
|
||||||
div.related {
|
|
||||||
width: 100%;
|
|
||||||
font-size: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related h3 {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related ul {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0 0 0 10px;
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related li {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related li.right {
|
|
||||||
float: right;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- sidebar --------------------------------------------------------------- */
|
|
||||||
|
|
||||||
div.sphinxsidebarwrapper {
|
|
||||||
padding: 10px 5px 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar {
|
|
||||||
float: left;
|
|
||||||
width: 230px;
|
|
||||||
margin-left: -100%;
|
|
||||||
font-size: 90%;
|
|
||||||
word-wrap: break-word;
|
|
||||||
overflow-wrap : break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar ul {
|
|
||||||
list-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar ul ul,
|
|
||||||
div.sphinxsidebar ul.want-points {
|
|
||||||
margin-left: 20px;
|
|
||||||
list-style: square;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar ul ul {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar form {
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar input {
|
|
||||||
border: 1px solid #98dbcc;
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar #searchbox form.search {
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar #searchbox input[type="text"] {
|
|
||||||
float: left;
|
|
||||||
width: 80%;
|
|
||||||
padding: 0.25em;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar #searchbox input[type="submit"] {
|
|
||||||
float: left;
|
|
||||||
width: 20%;
|
|
||||||
border-left: none;
|
|
||||||
padding: 0.25em;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
img {
|
|
||||||
border: 0;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- search page ----------------------------------------------------------- */
|
|
||||||
|
|
||||||
ul.search {
|
|
||||||
margin: 10px 0 0 20px;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.search li {
|
|
||||||
padding: 5px 0 5px 20px;
|
|
||||||
background-image: url(file.png);
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-position: 0 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.search li a {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.search li p.context {
|
|
||||||
color: #888;
|
|
||||||
margin: 2px 0 0 30px;
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul.keywordmatches li.goodmatch a {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- index page ------------------------------------------------------------ */
|
|
||||||
|
|
||||||
table.contentstable {
|
|
||||||
width: 90%;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.contentstable p.biglink {
|
|
||||||
line-height: 150%;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.biglink {
|
|
||||||
font-size: 1.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.linkdescr {
|
|
||||||
font-style: italic;
|
|
||||||
padding-top: 5px;
|
|
||||||
font-size: 90%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- general index --------------------------------------------------------- */
|
|
||||||
|
|
||||||
table.indextable {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.indextable td {
|
|
||||||
text-align: left;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.indextable ul {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
list-style-type: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.indextable > tbody > tr > td > ul {
|
|
||||||
padding-left: 0em;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.indextable tr.pcap {
|
|
||||||
height: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.indextable tr.cap {
|
|
||||||
margin-top: 10px;
|
|
||||||
background-color: #f2f2f2;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.toggler {
|
|
||||||
margin-right: 3px;
|
|
||||||
margin-top: 3px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.modindex-jumpbox {
|
|
||||||
border-top: 1px solid #ddd;
|
|
||||||
border-bottom: 1px solid #ddd;
|
|
||||||
margin: 1em 0 1em 0;
|
|
||||||
padding: 0.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.genindex-jumpbox {
|
|
||||||
border-top: 1px solid #ddd;
|
|
||||||
border-bottom: 1px solid #ddd;
|
|
||||||
margin: 1em 0 1em 0;
|
|
||||||
padding: 0.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- domain module index --------------------------------------------------- */
|
|
||||||
|
|
||||||
table.modindextable td {
|
|
||||||
padding: 2px;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- general body styles --------------------------------------------------- */
|
|
||||||
|
|
||||||
div.body {
|
|
||||||
min-width: 360px;
|
|
||||||
max-width: 800px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body p, div.body dd, div.body li, div.body blockquote {
|
|
||||||
-moz-hyphens: auto;
|
|
||||||
-ms-hyphens: auto;
|
|
||||||
-webkit-hyphens: auto;
|
|
||||||
hyphens: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.headerlink {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:visited {
|
|
||||||
color: #551A8B;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1:hover > a.headerlink,
|
|
||||||
h2:hover > a.headerlink,
|
|
||||||
h3:hover > a.headerlink,
|
|
||||||
h4:hover > a.headerlink,
|
|
||||||
h5:hover > a.headerlink,
|
|
||||||
h6:hover > a.headerlink,
|
|
||||||
dt:hover > a.headerlink,
|
|
||||||
caption:hover > a.headerlink,
|
|
||||||
p.caption:hover > a.headerlink,
|
|
||||||
div.code-block-caption:hover > a.headerlink {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body p.caption {
|
|
||||||
text-align: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body td {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.first {
|
|
||||||
margin-top: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.rubric {
|
|
||||||
margin-top: 30px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.align-left, figure.align-left, .figure.align-left, object.align-left {
|
|
||||||
clear: left;
|
|
||||||
float: left;
|
|
||||||
margin-right: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.align-right, figure.align-right, .figure.align-right, object.align-right {
|
|
||||||
clear: right;
|
|
||||||
float: right;
|
|
||||||
margin-left: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.align-center, figure.align-center, .figure.align-center, object.align-center {
|
|
||||||
display: block;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.align-default, figure.align-default, .figure.align-default {
|
|
||||||
display: block;
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.align-left {
|
|
||||||
text-align: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
.align-center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.align-default {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.align-right {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- sidebars -------------------------------------------------------------- */
|
|
||||||
|
|
||||||
div.sidebar,
|
|
||||||
aside.sidebar {
|
|
||||||
margin: 0 0 0.5em 1em;
|
|
||||||
border: 1px solid #ddb;
|
|
||||||
padding: 7px;
|
|
||||||
background-color: #ffe;
|
|
||||||
width: 40%;
|
|
||||||
float: right;
|
|
||||||
clear: right;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.sidebar-title {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
nav.contents,
|
|
||||||
aside.topic,
|
|
||||||
div.admonition, div.topic, blockquote {
|
|
||||||
clear: left;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- topics ---------------------------------------------------------------- */
|
|
||||||
|
|
||||||
nav.contents,
|
|
||||||
aside.topic,
|
|
||||||
div.topic {
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
padding: 7px;
|
|
||||||
margin: 10px 0 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.topic-title {
|
|
||||||
font-size: 1.1em;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- admonitions ----------------------------------------------------------- */
|
|
||||||
|
|
||||||
div.admonition {
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
padding: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition dt {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.admonition-title {
|
|
||||||
margin: 0px 10px 5px 0px;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body p.centered {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- content of sidebars/topics/admonitions -------------------------------- */
|
|
||||||
|
|
||||||
div.sidebar > :last-child,
|
|
||||||
aside.sidebar > :last-child,
|
|
||||||
nav.contents > :last-child,
|
|
||||||
aside.topic > :last-child,
|
|
||||||
div.topic > :last-child,
|
|
||||||
div.admonition > :last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sidebar::after,
|
|
||||||
aside.sidebar::after,
|
|
||||||
nav.contents::after,
|
|
||||||
aside.topic::after,
|
|
||||||
div.topic::after,
|
|
||||||
div.admonition::after,
|
|
||||||
blockquote::after {
|
|
||||||
display: block;
|
|
||||||
content: '';
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- tables ---------------------------------------------------------------- */
|
|
||||||
|
|
||||||
table.docutils {
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
border: 0;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.align-center {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.align-default {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
table caption span.caption-number {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
table caption span.caption-text {
|
|
||||||
}
|
|
||||||
|
|
||||||
table.docutils td, table.docutils th {
|
|
||||||
padding: 1px 8px 1px 5px;
|
|
||||||
border-top: 0;
|
|
||||||
border-left: 0;
|
|
||||||
border-right: 0;
|
|
||||||
border-bottom: 1px solid #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
text-align: left;
|
|
||||||
padding-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.citation {
|
|
||||||
border-left: solid 1px gray;
|
|
||||||
margin-left: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.citation td {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
th > :first-child,
|
|
||||||
td > :first-child {
|
|
||||||
margin-top: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
th > :last-child,
|
|
||||||
td > :last-child {
|
|
||||||
margin-bottom: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- figures --------------------------------------------------------------- */
|
|
||||||
|
|
||||||
div.figure, figure {
|
|
||||||
margin: 0.5em;
|
|
||||||
padding: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.figure p.caption, figcaption {
|
|
||||||
padding: 0.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.figure p.caption span.caption-number,
|
|
||||||
figcaption span.caption-number {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.figure p.caption span.caption-text,
|
|
||||||
figcaption span.caption-text {
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- field list styles ----------------------------------------------------- */
|
|
||||||
|
|
||||||
table.field-list td, table.field-list th {
|
|
||||||
border: 0 !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-list ul {
|
|
||||||
margin: 0;
|
|
||||||
padding-left: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-list p {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.field-name {
|
|
||||||
-moz-hyphens: manual;
|
|
||||||
-ms-hyphens: manual;
|
|
||||||
-webkit-hyphens: manual;
|
|
||||||
hyphens: manual;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- hlist styles ---------------------------------------------------------- */
|
|
||||||
|
|
||||||
table.hlist {
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.hlist td {
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- object description styles --------------------------------------------- */
|
|
||||||
|
|
||||||
.sig {
|
|
||||||
font-family: 'Consolas', 'Menlo', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sig-name, code.descname {
|
|
||||||
background-color: transparent;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sig-name {
|
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
code.descname {
|
|
||||||
font-size: 1.2em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sig-prename, code.descclassname {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.optional {
|
|
||||||
font-size: 1.3em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sig-paren {
|
|
||||||
font-size: larger;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sig-param.n {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* C++ specific styling */
|
|
||||||
|
|
||||||
.sig-inline.c-texpr,
|
|
||||||
.sig-inline.cpp-texpr {
|
|
||||||
font-family: unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sig.c .k, .sig.c .kt,
|
|
||||||
.sig.cpp .k, .sig.cpp .kt {
|
|
||||||
color: #0033B3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sig.c .m,
|
|
||||||
.sig.cpp .m {
|
|
||||||
color: #1750EB;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sig.c .s, .sig.c .sc,
|
|
||||||
.sig.cpp .s, .sig.cpp .sc {
|
|
||||||
color: #067D17;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* -- other body styles ----------------------------------------------------- */
|
|
||||||
|
|
||||||
ol.arabic {
|
|
||||||
list-style: decimal;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol.loweralpha {
|
|
||||||
list-style: lower-alpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol.upperalpha {
|
|
||||||
list-style: upper-alpha;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol.lowerroman {
|
|
||||||
list-style: lower-roman;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol.upperroman {
|
|
||||||
list-style: upper-roman;
|
|
||||||
}
|
|
||||||
|
|
||||||
:not(li) > ol > li:first-child > :first-child,
|
|
||||||
:not(li) > ul > li:first-child > :first-child {
|
|
||||||
margin-top: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
:not(li) > ol > li:last-child > :last-child,
|
|
||||||
:not(li) > ul > li:last-child > :last-child {
|
|
||||||
margin-bottom: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol.simple ol p,
|
|
||||||
ol.simple ul p,
|
|
||||||
ul.simple ol p,
|
|
||||||
ul.simple ul p {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol.simple > li:not(:first-child) > p,
|
|
||||||
ul.simple > li:not(:first-child) > p {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol.simple p,
|
|
||||||
ul.simple p {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
aside.footnote > span,
|
|
||||||
div.citation > span {
|
|
||||||
float: left;
|
|
||||||
}
|
|
||||||
aside.footnote > span:last-of-type,
|
|
||||||
div.citation > span:last-of-type {
|
|
||||||
padding-right: 0.5em;
|
|
||||||
}
|
|
||||||
aside.footnote > p {
|
|
||||||
margin-left: 2em;
|
|
||||||
}
|
|
||||||
div.citation > p {
|
|
||||||
margin-left: 4em;
|
|
||||||
}
|
|
||||||
aside.footnote > p:last-of-type,
|
|
||||||
div.citation > p:last-of-type {
|
|
||||||
margin-bottom: 0em;
|
|
||||||
}
|
|
||||||
aside.footnote > p:last-of-type:after,
|
|
||||||
div.citation > p:last-of-type:after {
|
|
||||||
content: "";
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl.field-list {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: fit-content(30%) auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl.field-list > dt {
|
|
||||||
font-weight: bold;
|
|
||||||
word-break: break-word;
|
|
||||||
padding-left: 0.5em;
|
|
||||||
padding-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl.field-list > dd {
|
|
||||||
padding-left: 0.5em;
|
|
||||||
margin-top: 0em;
|
|
||||||
margin-left: 0em;
|
|
||||||
margin-bottom: 0em;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
dd > :first-child {
|
|
||||||
margin-top: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
dd ul, dd table {
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
dd {
|
|
||||||
margin-top: 3px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
margin-left: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sig dd {
|
|
||||||
margin-top: 0px;
|
|
||||||
margin-bottom: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sig dl {
|
|
||||||
margin-top: 0px;
|
|
||||||
margin-bottom: 0px;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl > dd:last-child,
|
|
||||||
dl > dd:last-child > :last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
dt:target, span.highlighted {
|
|
||||||
background-color: #fbe54e;
|
|
||||||
}
|
|
||||||
|
|
||||||
rect.highlighted {
|
|
||||||
fill: #fbe54e;
|
|
||||||
}
|
|
||||||
|
|
||||||
dl.glossary dt {
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 1.1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.versionmodified {
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.system-message {
|
|
||||||
background-color: #fda;
|
|
||||||
padding: 5px;
|
|
||||||
border: 3px solid red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.footnote:target {
|
|
||||||
background-color: #ffa;
|
|
||||||
}
|
|
||||||
|
|
||||||
.line-block {
|
|
||||||
display: block;
|
|
||||||
margin-top: 1em;
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.line-block .line-block {
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 0;
|
|
||||||
margin-left: 1.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.guilabel, .menuselection {
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
.accelerator {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.classifier {
|
|
||||||
font-style: oblique;
|
|
||||||
}
|
|
||||||
|
|
||||||
.classifier:before {
|
|
||||||
font-style: normal;
|
|
||||||
margin: 0 0.5em;
|
|
||||||
content: ":";
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
abbr, acronym {
|
|
||||||
border-bottom: dotted 1px;
|
|
||||||
cursor: help;
|
|
||||||
}
|
|
||||||
|
|
||||||
.translated {
|
|
||||||
background-color: rgba(207, 255, 207, 0.2)
|
|
||||||
}
|
|
||||||
|
|
||||||
.untranslated {
|
|
||||||
background-color: rgba(255, 207, 207, 0.2)
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- code displays --------------------------------------------------------- */
|
|
||||||
|
|
||||||
pre {
|
|
||||||
overflow: auto;
|
|
||||||
overflow-y: hidden; /* fixes display issues on Chrome browsers */
|
|
||||||
}
|
|
||||||
|
|
||||||
pre, div[class*="highlight-"] {
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.pre {
|
|
||||||
-moz-hyphens: none;
|
|
||||||
-ms-hyphens: none;
|
|
||||||
-webkit-hyphens: none;
|
|
||||||
hyphens: none;
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
div[class*="highlight-"] {
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
td.linenos pre {
|
|
||||||
border: 0;
|
|
||||||
background-color: transparent;
|
|
||||||
color: #aaa;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.highlighttable {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.highlighttable tbody {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.highlighttable tr {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.highlighttable td {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.highlighttable td.linenos {
|
|
||||||
padding-right: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.highlighttable td.code {
|
|
||||||
flex: 1;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.highlight .hll {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.highlight pre,
|
|
||||||
table.highlighttable pre {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.code-block-caption + div {
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.code-block-caption {
|
|
||||||
margin-top: 1em;
|
|
||||||
padding: 2px 5px;
|
|
||||||
font-size: small;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.code-block-caption code {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
table.highlighttable td.linenos,
|
|
||||||
span.linenos,
|
|
||||||
div.highlight span.gp { /* gp: Generic.Prompt */
|
|
||||||
user-select: none;
|
|
||||||
-webkit-user-select: text; /* Safari fallback only */
|
|
||||||
-webkit-user-select: none; /* Chrome/Safari */
|
|
||||||
-moz-user-select: none; /* Firefox */
|
|
||||||
-ms-user-select: none; /* IE10+ */
|
|
||||||
}
|
|
||||||
|
|
||||||
div.code-block-caption span.caption-number {
|
|
||||||
padding: 0.1em 0.3em;
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.code-block-caption span.caption-text {
|
|
||||||
}
|
|
||||||
|
|
||||||
div.literal-block-wrapper {
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
code.xref, a code {
|
|
||||||
background-color: transparent;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
|
|
||||||
background-color: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.viewcode-link {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.viewcode-back {
|
|
||||||
float: right;
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.viewcode-block:target {
|
|
||||||
margin: -1px -10px;
|
|
||||||
padding: 0 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- math display ---------------------------------------------------------- */
|
|
||||||
|
|
||||||
img.math {
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body div.math p {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.eqno {
|
|
||||||
float: right;
|
|
||||||
}
|
|
||||||
|
|
||||||
span.eqno a.headerlink {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.math:hover a.headerlink {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -- printout stylesheet --------------------------------------------------- */
|
|
||||||
|
|
||||||
@media print {
|
|
||||||
div.document,
|
|
||||||
div.documentwrapper,
|
|
||||||
div.bodywrapper {
|
|
||||||
margin: 0 !important;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar,
|
|
||||||
div.related,
|
|
||||||
div.footer,
|
|
||||||
#top-link {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 756 B |
|
Before Width: | Height: | Size: 829 B |
|
Before Width: | Height: | Size: 641 B |
@@ -1 +0,0 @@
|
|||||||
/* This file intentionally left blank. */
|
|
||||||
@@ -1,256 +0,0 @@
|
|||||||
/*
|
|
||||||
* default.css_t
|
|
||||||
* ~~~~~~~~~~~~~
|
|
||||||
*
|
|
||||||
* Sphinx stylesheet -- default theme.
|
|
||||||
*
|
|
||||||
* :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS.
|
|
||||||
* :license: BSD, see LICENSE for details.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
@import url("basic.css");
|
|
||||||
|
|
||||||
/* -- page layout ----------------------------------------------------------- */
|
|
||||||
|
|
||||||
body {
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 100%;
|
|
||||||
background-color: #11303d;
|
|
||||||
color: #000;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.document {
|
|
||||||
background-color: #1c4e63;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.documentwrapper {
|
|
||||||
float: left;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.bodywrapper {
|
|
||||||
margin: 0 0 0 230px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body {
|
|
||||||
background-color: #ffffff;
|
|
||||||
color: #000000;
|
|
||||||
padding: 0 20px 30px 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footer {
|
|
||||||
color: #ffffff;
|
|
||||||
width: 100%;
|
|
||||||
padding: 9px 0 9px 0;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 75%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.footer a {
|
|
||||||
color: #ffffff;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related {
|
|
||||||
background-color: #133f52;
|
|
||||||
line-height: 30px;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.related a {
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar {
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar h3 {
|
|
||||||
font-family: 'Trebuchet MS', sans-serif;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 1.4em;
|
|
||||||
font-weight: normal;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar h3 a {
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar h4 {
|
|
||||||
font-family: 'Trebuchet MS', sans-serif;
|
|
||||||
color: #ffffff;
|
|
||||||
font-size: 1.3em;
|
|
||||||
font-weight: normal;
|
|
||||||
margin: 5px 0 0 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar p {
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar p.topless {
|
|
||||||
margin: 5px 10px 10px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar ul {
|
|
||||||
margin: 10px;
|
|
||||||
padding: 0;
|
|
||||||
color: #ffffff;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar a {
|
|
||||||
color: #98dbcc;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.sphinxsidebar input {
|
|
||||||
border: 1px solid #98dbcc;
|
|
||||||
font-family: sans-serif;
|
|
||||||
font-size: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* -- hyperlink styles ------------------------------------------------------ */
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: #355f7c;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:visited {
|
|
||||||
color: #355f7c;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover {
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* -- body styles ----------------------------------------------------------- */
|
|
||||||
|
|
||||||
div.body h1,
|
|
||||||
div.body h2,
|
|
||||||
div.body h3,
|
|
||||||
div.body h4,
|
|
||||||
div.body h5,
|
|
||||||
div.body h6 {
|
|
||||||
font-family: 'Trebuchet MS', sans-serif;
|
|
||||||
background-color: #f2f2f2;
|
|
||||||
font-weight: normal;
|
|
||||||
color: #20435c;
|
|
||||||
border-bottom: 1px solid #ccc;
|
|
||||||
margin: 20px -20px 10px -20px;
|
|
||||||
padding: 3px 0 3px 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body h1 { margin-top: 0; font-size: 200%; }
|
|
||||||
div.body h2 { font-size: 160%; }
|
|
||||||
div.body h3 { font-size: 140%; }
|
|
||||||
div.body h4 { font-size: 120%; }
|
|
||||||
div.body h5 { font-size: 110%; }
|
|
||||||
div.body h6 { font-size: 100%; }
|
|
||||||
|
|
||||||
a.headerlink {
|
|
||||||
color: #c60f0f;
|
|
||||||
font-size: 0.8em;
|
|
||||||
padding: 0 4px 0 4px;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a.headerlink:hover {
|
|
||||||
background-color: #c60f0f;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.body p, div.body dd, div.body li {
|
|
||||||
text-align: justify;
|
|
||||||
line-height: 130%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition p.admonition-title + p {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition p {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition pre {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.admonition ul, div.admonition ol {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.note {
|
|
||||||
background-color: #eee;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.seealso {
|
|
||||||
background-color: #ffc;
|
|
||||||
border: 1px solid #ff6;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.topic {
|
|
||||||
background-color: #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.warning {
|
|
||||||
background-color: #ffe4e4;
|
|
||||||
border: 1px solid #f66;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.admonition-title {
|
|
||||||
display: inline;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.admonition-title:after {
|
|
||||||
content: ":";
|
|
||||||
}
|
|
||||||
|
|
||||||
pre {
|
|
||||||
padding: 5px;
|
|
||||||
background-color: #eeffcc;
|
|
||||||
color: #333333;
|
|
||||||
line-height: 120%;
|
|
||||||
border: 1px solid #ac9;
|
|
||||||
border-left: none;
|
|
||||||
border-right: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
tt {
|
|
||||||
background-color: #ecf0f3;
|
|
||||||
padding: 0 1px 0 1px;
|
|
||||||
font-size: 0.95em;
|
|
||||||
}
|
|
||||||
|
|
||||||
th {
|
|
||||||
background-color: #ede;
|
|
||||||
}
|
|
||||||
|
|
||||||
.warning tt {
|
|
||||||
background: #efc2c2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.note tt {
|
|
||||||
background: #d6d6d6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.viewcode-back {
|
|
||||||
font-family: sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.viewcode-block:target {
|
|
||||||
background-color: #f4debf;
|
|
||||||
border-top: 1px solid #ac9;
|
|
||||||
border-bottom: 1px solid #ac9;
|
|
||||||
}
|
|
||||||
@@ -1,156 +0,0 @@
|
|||||||
/*
|
|
||||||
* doctools.js
|
|
||||||
* ~~~~~~~~~~~
|
|
||||||
*
|
|
||||||
* Base JavaScript utilities for all Sphinx HTML documentation.
|
|
||||||
*
|
|
||||||
* :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS.
|
|
||||||
* :license: BSD, see LICENSE for details.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
const BLACKLISTED_KEY_CONTROL_ELEMENTS = new Set([
|
|
||||||
"TEXTAREA",
|
|
||||||
"INPUT",
|
|
||||||
"SELECT",
|
|
||||||
"BUTTON",
|
|
||||||
]);
|
|
||||||
|
|
||||||
const _ready = (callback) => {
|
|
||||||
if (document.readyState !== "loading") {
|
|
||||||
callback();
|
|
||||||
} else {
|
|
||||||
document.addEventListener("DOMContentLoaded", callback);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Small JavaScript module for the documentation.
|
|
||||||
*/
|
|
||||||
const Documentation = {
|
|
||||||
init: () => {
|
|
||||||
Documentation.initDomainIndexTable();
|
|
||||||
Documentation.initOnKeyListeners();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* i18n support
|
|
||||||
*/
|
|
||||||
TRANSLATIONS: {},
|
|
||||||
PLURAL_EXPR: (n) => (n === 1 ? 0 : 1),
|
|
||||||
LOCALE: "unknown",
|
|
||||||
|
|
||||||
// gettext and ngettext don't access this so that the functions
|
|
||||||
// can safely bound to a different name (_ = Documentation.gettext)
|
|
||||||
gettext: (string) => {
|
|
||||||
const translated = Documentation.TRANSLATIONS[string];
|
|
||||||
switch (typeof translated) {
|
|
||||||
case "undefined":
|
|
||||||
return string; // no translation
|
|
||||||
case "string":
|
|
||||||
return translated; // translation exists
|
|
||||||
default:
|
|
||||||
return translated[0]; // (singular, plural) translation tuple exists
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
ngettext: (singular, plural, n) => {
|
|
||||||
const translated = Documentation.TRANSLATIONS[singular];
|
|
||||||
if (typeof translated !== "undefined")
|
|
||||||
return translated[Documentation.PLURAL_EXPR(n)];
|
|
||||||
return n === 1 ? singular : plural;
|
|
||||||
},
|
|
||||||
|
|
||||||
addTranslations: (catalog) => {
|
|
||||||
Object.assign(Documentation.TRANSLATIONS, catalog.messages);
|
|
||||||
Documentation.PLURAL_EXPR = new Function(
|
|
||||||
"n",
|
|
||||||
`return (${catalog.plural_expr})`
|
|
||||||
);
|
|
||||||
Documentation.LOCALE = catalog.locale;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* helper function to focus on search bar
|
|
||||||
*/
|
|
||||||
focusSearchBar: () => {
|
|
||||||
document.querySelectorAll("input[name=q]")[0]?.focus();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialise the domain index toggle buttons
|
|
||||||
*/
|
|
||||||
initDomainIndexTable: () => {
|
|
||||||
const toggler = (el) => {
|
|
||||||
const idNumber = el.id.substr(7);
|
|
||||||
const toggledRows = document.querySelectorAll(`tr.cg-${idNumber}`);
|
|
||||||
if (el.src.substr(-9) === "minus.png") {
|
|
||||||
el.src = `${el.src.substr(0, el.src.length - 9)}plus.png`;
|
|
||||||
toggledRows.forEach((el) => (el.style.display = "none"));
|
|
||||||
} else {
|
|
||||||
el.src = `${el.src.substr(0, el.src.length - 8)}minus.png`;
|
|
||||||
toggledRows.forEach((el) => (el.style.display = ""));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const togglerElements = document.querySelectorAll("img.toggler");
|
|
||||||
togglerElements.forEach((el) =>
|
|
||||||
el.addEventListener("click", (event) => toggler(event.currentTarget))
|
|
||||||
);
|
|
||||||
togglerElements.forEach((el) => (el.style.display = ""));
|
|
||||||
if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) togglerElements.forEach(toggler);
|
|
||||||
},
|
|
||||||
|
|
||||||
initOnKeyListeners: () => {
|
|
||||||
// only install a listener if it is really needed
|
|
||||||
if (
|
|
||||||
!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS &&
|
|
||||||
!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
|
|
||||||
document.addEventListener("keydown", (event) => {
|
|
||||||
// bail for input elements
|
|
||||||
if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
|
|
||||||
// bail with special keys
|
|
||||||
if (event.altKey || event.ctrlKey || event.metaKey) return;
|
|
||||||
|
|
||||||
if (!event.shiftKey) {
|
|
||||||
switch (event.key) {
|
|
||||||
case "ArrowLeft":
|
|
||||||
if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;
|
|
||||||
|
|
||||||
const prevLink = document.querySelector('link[rel="prev"]');
|
|
||||||
if (prevLink && prevLink.href) {
|
|
||||||
window.location.href = prevLink.href;
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case "ArrowRight":
|
|
||||||
if (!DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) break;
|
|
||||||
|
|
||||||
const nextLink = document.querySelector('link[rel="next"]');
|
|
||||||
if (nextLink && nextLink.href) {
|
|
||||||
window.location.href = nextLink.href;
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// some keyboard layouts may need Shift to get /
|
|
||||||
switch (event.key) {
|
|
||||||
case "/":
|
|
||||||
if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) break;
|
|
||||||
Documentation.focusSearchBar();
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// quick alias for translations
|
|
||||||
const _ = Documentation.gettext;
|
|
||||||
|
|
||||||
_ready(Documentation.init);
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
const DOCUMENTATION_OPTIONS = {
|
|
||||||
VERSION: '0.11.0',
|
|
||||||
LANGUAGE: 'en',
|
|
||||||
COLLAPSE_INDEX: false,
|
|
||||||
BUILDER: 'html',
|
|
||||||
FILE_SUFFIX: '.html',
|
|
||||||
LINK_SUFFIX: '.html',
|
|
||||||
HAS_SOURCE: true,
|
|
||||||
SOURCELINK_SUFFIX: '.txt',
|
|
||||||
NAVIGATION_WITH_KEYS: false,
|
|
||||||
SHOW_SEARCH_SUMMARY: true,
|
|
||||||
ENABLE_SEARCH_SHORTCUTS: true,
|
|
||||||
};
|
|
||||||
|
Before Width: | Height: | Size: 222 B |
|
Before Width: | Height: | Size: 202 B |
|
Before Width: | Height: | Size: 286 B |
|
Before Width: | Height: | Size: 4.7 KiB |
@@ -1,19 +0,0 @@
|
|||||||
/*
|
|
||||||
* graphviz.css
|
|
||||||
* ~~~~~~~~~~~~
|
|
||||||
*
|
|
||||||
* Sphinx stylesheet -- graphviz extension.
|
|
||||||
*
|
|
||||||
* :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS.
|
|
||||||
* :license: BSD, see LICENSE for details.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
img.graphviz {
|
|
||||||
border: 0;
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
object.graphviz {
|
|
||||||
max-width: 100%;
|
|
||||||
}
|
|
||||||
@@ -1,199 +0,0 @@
|
|||||||
/*
|
|
||||||
* language_data.js
|
|
||||||
* ~~~~~~~~~~~~~~~~
|
|
||||||
*
|
|
||||||
* This script contains the language-specific data used by searchtools.js,
|
|
||||||
* namely the list of stopwords, stemmer, scorer and splitter.
|
|
||||||
*
|
|
||||||
* :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS.
|
|
||||||
* :license: BSD, see LICENSE for details.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
var stopwords = ["a", "and", "are", "as", "at", "be", "but", "by", "for", "if", "in", "into", "is", "it", "near", "no", "not", "of", "on", "or", "such", "that", "the", "their", "then", "there", "these", "they", "this", "to", "was", "will", "with"];
|
|
||||||
|
|
||||||
|
|
||||||
/* Non-minified version is copied as a separate JS file, if available */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Porter Stemmer
|
|
||||||
*/
|
|
||||||
var Stemmer = function() {
|
|
||||||
|
|
||||||
var step2list = {
|
|
||||||
ational: 'ate',
|
|
||||||
tional: 'tion',
|
|
||||||
enci: 'ence',
|
|
||||||
anci: 'ance',
|
|
||||||
izer: 'ize',
|
|
||||||
bli: 'ble',
|
|
||||||
alli: 'al',
|
|
||||||
entli: 'ent',
|
|
||||||
eli: 'e',
|
|
||||||
ousli: 'ous',
|
|
||||||
ization: 'ize',
|
|
||||||
ation: 'ate',
|
|
||||||
ator: 'ate',
|
|
||||||
alism: 'al',
|
|
||||||
iveness: 'ive',
|
|
||||||
fulness: 'ful',
|
|
||||||
ousness: 'ous',
|
|
||||||
aliti: 'al',
|
|
||||||
iviti: 'ive',
|
|
||||||
biliti: 'ble',
|
|
||||||
logi: 'log'
|
|
||||||
};
|
|
||||||
|
|
||||||
var step3list = {
|
|
||||||
icate: 'ic',
|
|
||||||
ative: '',
|
|
||||||
alize: 'al',
|
|
||||||
iciti: 'ic',
|
|
||||||
ical: 'ic',
|
|
||||||
ful: '',
|
|
||||||
ness: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
var c = "[^aeiou]"; // consonant
|
|
||||||
var v = "[aeiouy]"; // vowel
|
|
||||||
var C = c + "[^aeiouy]*"; // consonant sequence
|
|
||||||
var V = v + "[aeiou]*"; // vowel sequence
|
|
||||||
|
|
||||||
var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0
|
|
||||||
var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1
|
|
||||||
var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1
|
|
||||||
var s_v = "^(" + C + ")?" + v; // vowel in stem
|
|
||||||
|
|
||||||
this.stemWord = function (w) {
|
|
||||||
var stem;
|
|
||||||
var suffix;
|
|
||||||
var firstch;
|
|
||||||
var origword = w;
|
|
||||||
|
|
||||||
if (w.length < 3)
|
|
||||||
return w;
|
|
||||||
|
|
||||||
var re;
|
|
||||||
var re2;
|
|
||||||
var re3;
|
|
||||||
var re4;
|
|
||||||
|
|
||||||
firstch = w.substr(0,1);
|
|
||||||
if (firstch == "y")
|
|
||||||
w = firstch.toUpperCase() + w.substr(1);
|
|
||||||
|
|
||||||
// Step 1a
|
|
||||||
re = /^(.+?)(ss|i)es$/;
|
|
||||||
re2 = /^(.+?)([^s])s$/;
|
|
||||||
|
|
||||||
if (re.test(w))
|
|
||||||
w = w.replace(re,"$1$2");
|
|
||||||
else if (re2.test(w))
|
|
||||||
w = w.replace(re2,"$1$2");
|
|
||||||
|
|
||||||
// Step 1b
|
|
||||||
re = /^(.+?)eed$/;
|
|
||||||
re2 = /^(.+?)(ed|ing)$/;
|
|
||||||
if (re.test(w)) {
|
|
||||||
var fp = re.exec(w);
|
|
||||||
re = new RegExp(mgr0);
|
|
||||||
if (re.test(fp[1])) {
|
|
||||||
re = /.$/;
|
|
||||||
w = w.replace(re,"");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (re2.test(w)) {
|
|
||||||
var fp = re2.exec(w);
|
|
||||||
stem = fp[1];
|
|
||||||
re2 = new RegExp(s_v);
|
|
||||||
if (re2.test(stem)) {
|
|
||||||
w = stem;
|
|
||||||
re2 = /(at|bl|iz)$/;
|
|
||||||
re3 = new RegExp("([^aeiouylsz])\\1$");
|
|
||||||
re4 = new RegExp("^" + C + v + "[^aeiouwxy]$");
|
|
||||||
if (re2.test(w))
|
|
||||||
w = w + "e";
|
|
||||||
else if (re3.test(w)) {
|
|
||||||
re = /.$/;
|
|
||||||
w = w.replace(re,"");
|
|
||||||
}
|
|
||||||
else if (re4.test(w))
|
|
||||||
w = w + "e";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 1c
|
|
||||||
re = /^(.+?)y$/;
|
|
||||||
if (re.test(w)) {
|
|
||||||
var fp = re.exec(w);
|
|
||||||
stem = fp[1];
|
|
||||||
re = new RegExp(s_v);
|
|
||||||
if (re.test(stem))
|
|
||||||
w = stem + "i";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 2
|
|
||||||
re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/;
|
|
||||||
if (re.test(w)) {
|
|
||||||
var fp = re.exec(w);
|
|
||||||
stem = fp[1];
|
|
||||||
suffix = fp[2];
|
|
||||||
re = new RegExp(mgr0);
|
|
||||||
if (re.test(stem))
|
|
||||||
w = stem + step2list[suffix];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 3
|
|
||||||
re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/;
|
|
||||||
if (re.test(w)) {
|
|
||||||
var fp = re.exec(w);
|
|
||||||
stem = fp[1];
|
|
||||||
suffix = fp[2];
|
|
||||||
re = new RegExp(mgr0);
|
|
||||||
if (re.test(stem))
|
|
||||||
w = stem + step3list[suffix];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 4
|
|
||||||
re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/;
|
|
||||||
re2 = /^(.+?)(s|t)(ion)$/;
|
|
||||||
if (re.test(w)) {
|
|
||||||
var fp = re.exec(w);
|
|
||||||
stem = fp[1];
|
|
||||||
re = new RegExp(mgr1);
|
|
||||||
if (re.test(stem))
|
|
||||||
w = stem;
|
|
||||||
}
|
|
||||||
else if (re2.test(w)) {
|
|
||||||
var fp = re2.exec(w);
|
|
||||||
stem = fp[1] + fp[2];
|
|
||||||
re2 = new RegExp(mgr1);
|
|
||||||
if (re2.test(stem))
|
|
||||||
w = stem;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Step 5
|
|
||||||
re = /^(.+?)e$/;
|
|
||||||
if (re.test(w)) {
|
|
||||||
var fp = re.exec(w);
|
|
||||||
stem = fp[1];
|
|
||||||
re = new RegExp(mgr1);
|
|
||||||
re2 = new RegExp(meq1);
|
|
||||||
re3 = new RegExp("^" + C + v + "[^aeiouwxy]$");
|
|
||||||
if (re.test(stem) || (re2.test(stem) && !(re3.test(stem))))
|
|
||||||
w = stem;
|
|
||||||
}
|
|
||||||
re = /ll$/;
|
|
||||||
re2 = new RegExp(mgr1);
|
|
||||||
if (re.test(w) && re2.test(w)) {
|
|
||||||
re = /.$/;
|
|
||||||
w = w.replace(re,"");
|
|
||||||
}
|
|
||||||
|
|
||||||
// and turn initial Y back to y
|
|
||||||
if (firstch == "y")
|
|
||||||
w = firstch.toLowerCase() + w.substr(1);
|
|
||||||
return w;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 90 B |
|
Before Width: | Height: | Size: 90 B |
@@ -1,75 +0,0 @@
|
|||||||
pre { line-height: 125%; }
|
|
||||||
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
|
|
||||||
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
|
|
||||||
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
|
|
||||||
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
|
|
||||||
.highlight .hll { background-color: #ffffcc }
|
|
||||||
.highlight { background: #eeffcc; }
|
|
||||||
.highlight .c { color: #408090; font-style: italic } /* Comment */
|
|
||||||
.highlight .err { border: 1px solid #FF0000 } /* Error */
|
|
||||||
.highlight .k { color: #007020; font-weight: bold } /* Keyword */
|
|
||||||
.highlight .o { color: #666666 } /* Operator */
|
|
||||||
.highlight .ch { color: #408090; font-style: italic } /* Comment.Hashbang */
|
|
||||||
.highlight .cm { color: #408090; font-style: italic } /* Comment.Multiline */
|
|
||||||
.highlight .cp { color: #007020 } /* Comment.Preproc */
|
|
||||||
.highlight .cpf { color: #408090; font-style: italic } /* Comment.PreprocFile */
|
|
||||||
.highlight .c1 { color: #408090; font-style: italic } /* Comment.Single */
|
|
||||||
.highlight .cs { color: #408090; background-color: #fff0f0 } /* Comment.Special */
|
|
||||||
.highlight .gd { color: #A00000 } /* Generic.Deleted */
|
|
||||||
.highlight .ge { font-style: italic } /* Generic.Emph */
|
|
||||||
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
|
|
||||||
.highlight .gr { color: #FF0000 } /* Generic.Error */
|
|
||||||
.highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */
|
|
||||||
.highlight .gi { color: #00A000 } /* Generic.Inserted */
|
|
||||||
.highlight .go { color: #333333 } /* Generic.Output */
|
|
||||||
.highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */
|
|
||||||
.highlight .gs { font-weight: bold } /* Generic.Strong */
|
|
||||||
.highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
|
|
||||||
.highlight .gt { color: #0044DD } /* Generic.Traceback */
|
|
||||||
.highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */
|
|
||||||
.highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */
|
|
||||||
.highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */
|
|
||||||
.highlight .kp { color: #007020 } /* Keyword.Pseudo */
|
|
||||||
.highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */
|
|
||||||
.highlight .kt { color: #902000 } /* Keyword.Type */
|
|
||||||
.highlight .m { color: #208050 } /* Literal.Number */
|
|
||||||
.highlight .s { color: #4070a0 } /* Literal.String */
|
|
||||||
.highlight .na { color: #4070a0 } /* Name.Attribute */
|
|
||||||
.highlight .nb { color: #007020 } /* Name.Builtin */
|
|
||||||
.highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */
|
|
||||||
.highlight .no { color: #60add5 } /* Name.Constant */
|
|
||||||
.highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */
|
|
||||||
.highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */
|
|
||||||
.highlight .ne { color: #007020 } /* Name.Exception */
|
|
||||||
.highlight .nf { color: #06287e } /* Name.Function */
|
|
||||||
.highlight .nl { color: #002070; font-weight: bold } /* Name.Label */
|
|
||||||
.highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */
|
|
||||||
.highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */
|
|
||||||
.highlight .nv { color: #bb60d5 } /* Name.Variable */
|
|
||||||
.highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */
|
|
||||||
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
|
|
||||||
.highlight .mb { color: #208050 } /* Literal.Number.Bin */
|
|
||||||
.highlight .mf { color: #208050 } /* Literal.Number.Float */
|
|
||||||
.highlight .mh { color: #208050 } /* Literal.Number.Hex */
|
|
||||||
.highlight .mi { color: #208050 } /* Literal.Number.Integer */
|
|
||||||
.highlight .mo { color: #208050 } /* Literal.Number.Oct */
|
|
||||||
.highlight .sa { color: #4070a0 } /* Literal.String.Affix */
|
|
||||||
.highlight .sb { color: #4070a0 } /* Literal.String.Backtick */
|
|
||||||
.highlight .sc { color: #4070a0 } /* Literal.String.Char */
|
|
||||||
.highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */
|
|
||||||
.highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */
|
|
||||||
.highlight .s2 { color: #4070a0 } /* Literal.String.Double */
|
|
||||||
.highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */
|
|
||||||
.highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */
|
|
||||||
.highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */
|
|
||||||
.highlight .sx { color: #c65d09 } /* Literal.String.Other */
|
|
||||||
.highlight .sr { color: #235388 } /* Literal.String.Regex */
|
|
||||||
.highlight .s1 { color: #4070a0 } /* Literal.String.Single */
|
|
||||||
.highlight .ss { color: #517918 } /* Literal.String.Symbol */
|
|
||||||
.highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */
|
|
||||||
.highlight .fm { color: #06287e } /* Name.Function.Magic */
|
|
||||||
.highlight .vc { color: #bb60d5 } /* Name.Variable.Class */
|
|
||||||
.highlight .vg { color: #bb60d5 } /* Name.Variable.Global */
|
|
||||||
.highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */
|
|
||||||
.highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */
|
|
||||||
.highlight .il { color: #208050 } /* Literal.Number.Integer.Long */
|
|
||||||
@@ -1,619 +0,0 @@
|
|||||||
/*
|
|
||||||
* searchtools.js
|
|
||||||
* ~~~~~~~~~~~~~~~~
|
|
||||||
*
|
|
||||||
* Sphinx JavaScript utilities for the full-text search.
|
|
||||||
*
|
|
||||||
* :copyright: Copyright 2007-2024 by the Sphinx team, see AUTHORS.
|
|
||||||
* :license: BSD, see LICENSE for details.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple result scoring code.
|
|
||||||
*/
|
|
||||||
if (typeof Scorer === "undefined") {
|
|
||||||
var Scorer = {
|
|
||||||
// Implement the following function to further tweak the score for each result
|
|
||||||
// The function takes a result array [docname, title, anchor, descr, score, filename]
|
|
||||||
// and returns the new score.
|
|
||||||
/*
|
|
||||||
score: result => {
|
|
||||||
const [docname, title, anchor, descr, score, filename] = result
|
|
||||||
return score
|
|
||||||
},
|
|
||||||
*/
|
|
||||||
|
|
||||||
// query matches the full name of an object
|
|
||||||
objNameMatch: 11,
|
|
||||||
// or matches in the last dotted part of the object name
|
|
||||||
objPartialMatch: 6,
|
|
||||||
// Additive scores depending on the priority of the object
|
|
||||||
objPrio: {
|
|
||||||
0: 15, // used to be importantResults
|
|
||||||
1: 5, // used to be objectResults
|
|
||||||
2: -5, // used to be unimportantResults
|
|
||||||
},
|
|
||||||
// Used when the priority is not in the mapping.
|
|
||||||
objPrioDefault: 0,
|
|
||||||
|
|
||||||
// query found in title
|
|
||||||
title: 15,
|
|
||||||
partialTitle: 7,
|
|
||||||
// query found in terms
|
|
||||||
term: 5,
|
|
||||||
partialTerm: 2,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const _removeChildren = (element) => {
|
|
||||||
while (element && element.lastChild) element.removeChild(element.lastChild);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#escaping
|
|
||||||
*/
|
|
||||||
const _escapeRegExp = (string) =>
|
|
||||||
string.replace(/[.*+\-?^${}()|[\]\\]/g, "\\$&"); // $& means the whole matched string
|
|
||||||
|
|
||||||
const _displayItem = (item, searchTerms, highlightTerms) => {
|
|
||||||
const docBuilder = DOCUMENTATION_OPTIONS.BUILDER;
|
|
||||||
const docFileSuffix = DOCUMENTATION_OPTIONS.FILE_SUFFIX;
|
|
||||||
const docLinkSuffix = DOCUMENTATION_OPTIONS.LINK_SUFFIX;
|
|
||||||
const showSearchSummary = DOCUMENTATION_OPTIONS.SHOW_SEARCH_SUMMARY;
|
|
||||||
const contentRoot = document.documentElement.dataset.content_root;
|
|
||||||
|
|
||||||
const [docName, title, anchor, descr, score, _filename] = item;
|
|
||||||
|
|
||||||
let listItem = document.createElement("li");
|
|
||||||
let requestUrl;
|
|
||||||
let linkUrl;
|
|
||||||
if (docBuilder === "dirhtml") {
|
|
||||||
// dirhtml builder
|
|
||||||
let dirname = docName + "/";
|
|
||||||
if (dirname.match(/\/index\/$/))
|
|
||||||
dirname = dirname.substring(0, dirname.length - 6);
|
|
||||||
else if (dirname === "index/") dirname = "";
|
|
||||||
requestUrl = contentRoot + dirname;
|
|
||||||
linkUrl = requestUrl;
|
|
||||||
} else {
|
|
||||||
// normal html builders
|
|
||||||
requestUrl = contentRoot + docName + docFileSuffix;
|
|
||||||
linkUrl = docName + docLinkSuffix;
|
|
||||||
}
|
|
||||||
let linkEl = listItem.appendChild(document.createElement("a"));
|
|
||||||
linkEl.href = linkUrl + anchor;
|
|
||||||
linkEl.dataset.score = score;
|
|
||||||
linkEl.innerHTML = title;
|
|
||||||
if (descr) {
|
|
||||||
listItem.appendChild(document.createElement("span")).innerHTML =
|
|
||||||
" (" + descr + ")";
|
|
||||||
// highlight search terms in the description
|
|
||||||
if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js
|
|
||||||
highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted"));
|
|
||||||
}
|
|
||||||
else if (showSearchSummary)
|
|
||||||
fetch(requestUrl)
|
|
||||||
.then((responseData) => responseData.text())
|
|
||||||
.then((data) => {
|
|
||||||
if (data)
|
|
||||||
listItem.appendChild(
|
|
||||||
Search.makeSearchSummary(data, searchTerms, anchor)
|
|
||||||
);
|
|
||||||
// highlight search terms in the summary
|
|
||||||
if (SPHINX_HIGHLIGHT_ENABLED) // set in sphinx_highlight.js
|
|
||||||
highlightTerms.forEach((term) => _highlightText(listItem, term, "highlighted"));
|
|
||||||
});
|
|
||||||
Search.output.appendChild(listItem);
|
|
||||||
};
|
|
||||||
const _finishSearch = (resultCount) => {
|
|
||||||
Search.stopPulse();
|
|
||||||
Search.title.innerText = _("Search Results");
|
|
||||||
if (!resultCount)
|
|
||||||
Search.status.innerText = Documentation.gettext(
|
|
||||||
"Your search did not match any documents. Please make sure that all words are spelled correctly and that you've selected enough categories."
|
|
||||||
);
|
|
||||||
else
|
|
||||||
Search.status.innerText = _(
|
|
||||||
"Search finished, found ${resultCount} page(s) matching the search query."
|
|
||||||
).replace('${resultCount}', resultCount);
|
|
||||||
};
|
|
||||||
const _displayNextItem = (
|
|
||||||
results,
|
|
||||||
resultCount,
|
|
||||||
searchTerms,
|
|
||||||
highlightTerms,
|
|
||||||
) => {
|
|
||||||
// results left, load the summary and display it
|
|
||||||
// this is intended to be dynamic (don't sub resultsCount)
|
|
||||||
if (results.length) {
|
|
||||||
_displayItem(results.pop(), searchTerms, highlightTerms);
|
|
||||||
setTimeout(
|
|
||||||
() => _displayNextItem(results, resultCount, searchTerms, highlightTerms),
|
|
||||||
5
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// search finished, update title and status message
|
|
||||||
else _finishSearch(resultCount);
|
|
||||||
};
|
|
||||||
// Helper function used by query() to order search results.
|
|
||||||
// Each input is an array of [docname, title, anchor, descr, score, filename].
|
|
||||||
// Order the results by score (in opposite order of appearance, since the
|
|
||||||
// `_displayNextItem` function uses pop() to retrieve items) and then alphabetically.
|
|
||||||
const _orderResultsByScoreThenName = (a, b) => {
|
|
||||||
const leftScore = a[4];
|
|
||||||
const rightScore = b[4];
|
|
||||||
if (leftScore === rightScore) {
|
|
||||||
// same score: sort alphabetically
|
|
||||||
const leftTitle = a[1].toLowerCase();
|
|
||||||
const rightTitle = b[1].toLowerCase();
|
|
||||||
if (leftTitle === rightTitle) return 0;
|
|
||||||
return leftTitle > rightTitle ? -1 : 1; // inverted is intentional
|
|
||||||
}
|
|
||||||
return leftScore > rightScore ? 1 : -1;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Default splitQuery function. Can be overridden in ``sphinx.search`` with a
|
|
||||||
* custom function per language.
|
|
||||||
*
|
|
||||||
* The regular expression works by splitting the string on consecutive characters
|
|
||||||
* that are not Unicode letters, numbers, underscores, or emoji characters.
|
|
||||||
* This is the same as ``\W+`` in Python, preserving the surrogate pair area.
|
|
||||||
*/
|
|
||||||
if (typeof splitQuery === "undefined") {
|
|
||||||
var splitQuery = (query) => query
|
|
||||||
.split(/[^\p{Letter}\p{Number}_\p{Emoji_Presentation}]+/gu)
|
|
||||||
.filter(term => term) // remove remaining empty strings
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search Module
|
|
||||||
*/
|
|
||||||
const Search = {
|
|
||||||
_index: null,
|
|
||||||
_queued_query: null,
|
|
||||||
_pulse_status: -1,
|
|
||||||
|
|
||||||
htmlToText: (htmlString, anchor) => {
|
|
||||||
const htmlElement = new DOMParser().parseFromString(htmlString, 'text/html');
|
|
||||||
for (const removalQuery of [".headerlinks", "script", "style"]) {
|
|
||||||
htmlElement.querySelectorAll(removalQuery).forEach((el) => { el.remove() });
|
|
||||||
}
|
|
||||||
if (anchor) {
|
|
||||||
const anchorContent = htmlElement.querySelector(`[role="main"] ${anchor}`);
|
|
||||||
if (anchorContent) return anchorContent.textContent;
|
|
||||||
|
|
||||||
console.warn(
|
|
||||||
`Anchored content block not found. Sphinx search tries to obtain it via DOM query '[role=main] ${anchor}'. Check your theme or template.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if anchor not specified or not found, fall back to main content
|
|
||||||
const docContent = htmlElement.querySelector('[role="main"]');
|
|
||||||
if (docContent) return docContent.textContent;
|
|
||||||
|
|
||||||
console.warn(
|
|
||||||
"Content block not found. Sphinx search tries to obtain it via DOM query '[role=main]'. Check your theme or template."
|
|
||||||
);
|
|
||||||
return "";
|
|
||||||
},
|
|
||||||
|
|
||||||
init: () => {
|
|
||||||
const query = new URLSearchParams(window.location.search).get("q");
|
|
||||||
document
|
|
||||||
.querySelectorAll('input[name="q"]')
|
|
||||||
.forEach((el) => (el.value = query));
|
|
||||||
if (query) Search.performSearch(query);
|
|
||||||
},
|
|
||||||
|
|
||||||
loadIndex: (url) =>
|
|
||||||
(document.body.appendChild(document.createElement("script")).src = url),
|
|
||||||
|
|
||||||
setIndex: (index) => {
|
|
||||||
Search._index = index;
|
|
||||||
if (Search._queued_query !== null) {
|
|
||||||
const query = Search._queued_query;
|
|
||||||
Search._queued_query = null;
|
|
||||||
Search.query(query);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
hasIndex: () => Search._index !== null,
|
|
||||||
|
|
||||||
deferQuery: (query) => (Search._queued_query = query),
|
|
||||||
|
|
||||||
stopPulse: () => (Search._pulse_status = -1),
|
|
||||||
|
|
||||||
startPulse: () => {
|
|
||||||
if (Search._pulse_status >= 0) return;
|
|
||||||
|
|
||||||
const pulse = () => {
|
|
||||||
Search._pulse_status = (Search._pulse_status + 1) % 4;
|
|
||||||
Search.dots.innerText = ".".repeat(Search._pulse_status);
|
|
||||||
if (Search._pulse_status >= 0) window.setTimeout(pulse, 500);
|
|
||||||
};
|
|
||||||
pulse();
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* perform a search for something (or wait until index is loaded)
|
|
||||||
*/
|
|
||||||
performSearch: (query) => {
|
|
||||||
// create the required interface elements
|
|
||||||
const searchText = document.createElement("h2");
|
|
||||||
searchText.textContent = _("Searching");
|
|
||||||
const searchSummary = document.createElement("p");
|
|
||||||
searchSummary.classList.add("search-summary");
|
|
||||||
searchSummary.innerText = "";
|
|
||||||
const searchList = document.createElement("ul");
|
|
||||||
searchList.classList.add("search");
|
|
||||||
|
|
||||||
const out = document.getElementById("search-results");
|
|
||||||
Search.title = out.appendChild(searchText);
|
|
||||||
Search.dots = Search.title.appendChild(document.createElement("span"));
|
|
||||||
Search.status = out.appendChild(searchSummary);
|
|
||||||
Search.output = out.appendChild(searchList);
|
|
||||||
|
|
||||||
const searchProgress = document.getElementById("search-progress");
|
|
||||||
// Some themes don't use the search progress node
|
|
||||||
if (searchProgress) {
|
|
||||||
searchProgress.innerText = _("Preparing search...");
|
|
||||||
}
|
|
||||||
Search.startPulse();
|
|
||||||
|
|
||||||
// index already loaded, the browser was quick!
|
|
||||||
if (Search.hasIndex()) Search.query(query);
|
|
||||||
else Search.deferQuery(query);
|
|
||||||
},
|
|
||||||
|
|
||||||
_parseQuery: (query) => {
|
|
||||||
// stem the search terms and add them to the correct list
|
|
||||||
const stemmer = new Stemmer();
|
|
||||||
const searchTerms = new Set();
|
|
||||||
const excludedTerms = new Set();
|
|
||||||
const highlightTerms = new Set();
|
|
||||||
const objectTerms = new Set(splitQuery(query.toLowerCase().trim()));
|
|
||||||
splitQuery(query.trim()).forEach((queryTerm) => {
|
|
||||||
const queryTermLower = queryTerm.toLowerCase();
|
|
||||||
|
|
||||||
// maybe skip this "word"
|
|
||||||
// stopwords array is from language_data.js
|
|
||||||
if (
|
|
||||||
stopwords.indexOf(queryTermLower) !== -1 ||
|
|
||||||
queryTerm.match(/^\d+$/)
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// stem the word
|
|
||||||
let word = stemmer.stemWord(queryTermLower);
|
|
||||||
// select the correct list
|
|
||||||
if (word[0] === "-") excludedTerms.add(word.substr(1));
|
|
||||||
else {
|
|
||||||
searchTerms.add(word);
|
|
||||||
highlightTerms.add(queryTermLower);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
if (SPHINX_HIGHLIGHT_ENABLED) { // set in sphinx_highlight.js
|
|
||||||
localStorage.setItem("sphinx_highlight_terms", [...highlightTerms].join(" "))
|
|
||||||
}
|
|
||||||
|
|
||||||
// console.debug("SEARCH: searching for:");
|
|
||||||
// console.info("required: ", [...searchTerms]);
|
|
||||||
// console.info("excluded: ", [...excludedTerms]);
|
|
||||||
|
|
||||||
return [query, searchTerms, excludedTerms, highlightTerms, objectTerms];
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* execute search (requires search index to be loaded)
|
|
||||||
*/
|
|
||||||
_performSearch: (query, searchTerms, excludedTerms, highlightTerms, objectTerms) => {
|
|
||||||
const filenames = Search._index.filenames;
|
|
||||||
const docNames = Search._index.docnames;
|
|
||||||
const titles = Search._index.titles;
|
|
||||||
const allTitles = Search._index.alltitles;
|
|
||||||
const indexEntries = Search._index.indexentries;
|
|
||||||
|
|
||||||
// Collect multiple result groups to be sorted separately and then ordered.
|
|
||||||
// Each is an array of [docname, title, anchor, descr, score, filename].
|
|
||||||
const normalResults = [];
|
|
||||||
const nonMainIndexResults = [];
|
|
||||||
|
|
||||||
_removeChildren(document.getElementById("search-progress"));
|
|
||||||
|
|
||||||
const queryLower = query.toLowerCase().trim();
|
|
||||||
for (const [title, foundTitles] of Object.entries(allTitles)) {
|
|
||||||
if (title.toLowerCase().trim().includes(queryLower) && (queryLower.length >= title.length/2)) {
|
|
||||||
for (const [file, id] of foundTitles) {
|
|
||||||
let score = Math.round(100 * queryLower.length / title.length)
|
|
||||||
normalResults.push([
|
|
||||||
docNames[file],
|
|
||||||
titles[file] !== title ? `${titles[file]} > ${title}` : title,
|
|
||||||
id !== null ? "#" + id : "",
|
|
||||||
null,
|
|
||||||
score,
|
|
||||||
filenames[file],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// search for explicit entries in index directives
|
|
||||||
for (const [entry, foundEntries] of Object.entries(indexEntries)) {
|
|
||||||
if (entry.includes(queryLower) && (queryLower.length >= entry.length/2)) {
|
|
||||||
for (const [file, id, isMain] of foundEntries) {
|
|
||||||
const score = Math.round(100 * queryLower.length / entry.length);
|
|
||||||
const result = [
|
|
||||||
docNames[file],
|
|
||||||
titles[file],
|
|
||||||
id ? "#" + id : "",
|
|
||||||
null,
|
|
||||||
score,
|
|
||||||
filenames[file],
|
|
||||||
];
|
|
||||||
if (isMain) {
|
|
||||||
normalResults.push(result);
|
|
||||||
} else {
|
|
||||||
nonMainIndexResults.push(result);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// lookup as object
|
|
||||||
objectTerms.forEach((term) =>
|
|
||||||
normalResults.push(...Search.performObjectSearch(term, objectTerms))
|
|
||||||
);
|
|
||||||
|
|
||||||
// lookup as search terms in fulltext
|
|
||||||
normalResults.push(...Search.performTermsSearch(searchTerms, excludedTerms));
|
|
||||||
|
|
||||||
// let the scorer override scores with a custom scoring function
|
|
||||||
if (Scorer.score) {
|
|
||||||
normalResults.forEach((item) => (item[4] = Scorer.score(item)));
|
|
||||||
nonMainIndexResults.forEach((item) => (item[4] = Scorer.score(item)));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort each group of results by score and then alphabetically by name.
|
|
||||||
normalResults.sort(_orderResultsByScoreThenName);
|
|
||||||
nonMainIndexResults.sort(_orderResultsByScoreThenName);
|
|
||||||
|
|
||||||
// Combine the result groups in (reverse) order.
|
|
||||||
// Non-main index entries are typically arbitrary cross-references,
|
|
||||||
// so display them after other results.
|
|
||||||
let results = [...nonMainIndexResults, ...normalResults];
|
|
||||||
|
|
||||||
// remove duplicate search results
|
|
||||||
// note the reversing of results, so that in the case of duplicates, the highest-scoring entry is kept
|
|
||||||
let seen = new Set();
|
|
||||||
results = results.reverse().reduce((acc, result) => {
|
|
||||||
let resultStr = result.slice(0, 4).concat([result[5]]).map(v => String(v)).join(',');
|
|
||||||
if (!seen.has(resultStr)) {
|
|
||||||
acc.push(result);
|
|
||||||
seen.add(resultStr);
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return results.reverse();
|
|
||||||
},
|
|
||||||
|
|
||||||
query: (query) => {
|
|
||||||
const [searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms] = Search._parseQuery(query);
|
|
||||||
const results = Search._performSearch(searchQuery, searchTerms, excludedTerms, highlightTerms, objectTerms);
|
|
||||||
|
|
||||||
// for debugging
|
|
||||||
//Search.lastresults = results.slice(); // a copy
|
|
||||||
// console.info("search results:", Search.lastresults);
|
|
||||||
|
|
||||||
// print the results
|
|
||||||
_displayNextItem(results, results.length, searchTerms, highlightTerms);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* search for object names
|
|
||||||
*/
|
|
||||||
performObjectSearch: (object, objectTerms) => {
|
|
||||||
const filenames = Search._index.filenames;
|
|
||||||
const docNames = Search._index.docnames;
|
|
||||||
const objects = Search._index.objects;
|
|
||||||
const objNames = Search._index.objnames;
|
|
||||||
const titles = Search._index.titles;
|
|
||||||
|
|
||||||
const results = [];
|
|
||||||
|
|
||||||
const objectSearchCallback = (prefix, match) => {
|
|
||||||
const name = match[4]
|
|
||||||
const fullname = (prefix ? prefix + "." : "") + name;
|
|
||||||
const fullnameLower = fullname.toLowerCase();
|
|
||||||
if (fullnameLower.indexOf(object) < 0) return;
|
|
||||||
|
|
||||||
let score = 0;
|
|
||||||
const parts = fullnameLower.split(".");
|
|
||||||
|
|
||||||
// check for different match types: exact matches of full name or
|
|
||||||
// "last name" (i.e. last dotted part)
|
|
||||||
if (fullnameLower === object || parts.slice(-1)[0] === object)
|
|
||||||
score += Scorer.objNameMatch;
|
|
||||||
else if (parts.slice(-1)[0].indexOf(object) > -1)
|
|
||||||
score += Scorer.objPartialMatch; // matches in last name
|
|
||||||
|
|
||||||
const objName = objNames[match[1]][2];
|
|
||||||
const title = titles[match[0]];
|
|
||||||
|
|
||||||
// If more than one term searched for, we require other words to be
|
|
||||||
// found in the name/title/description
|
|
||||||
const otherTerms = new Set(objectTerms);
|
|
||||||
otherTerms.delete(object);
|
|
||||||
if (otherTerms.size > 0) {
|
|
||||||
const haystack = `${prefix} ${name} ${objName} ${title}`.toLowerCase();
|
|
||||||
if (
|
|
||||||
[...otherTerms].some((otherTerm) => haystack.indexOf(otherTerm) < 0)
|
|
||||||
)
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let anchor = match[3];
|
|
||||||
if (anchor === "") anchor = fullname;
|
|
||||||
else if (anchor === "-") anchor = objNames[match[1]][1] + "-" + fullname;
|
|
||||||
|
|
||||||
const descr = objName + _(", in ") + title;
|
|
||||||
|
|
||||||
// add custom score for some objects according to scorer
|
|
||||||
if (Scorer.objPrio.hasOwnProperty(match[2]))
|
|
||||||
score += Scorer.objPrio[match[2]];
|
|
||||||
else score += Scorer.objPrioDefault;
|
|
||||||
|
|
||||||
results.push([
|
|
||||||
docNames[match[0]],
|
|
||||||
fullname,
|
|
||||||
"#" + anchor,
|
|
||||||
descr,
|
|
||||||
score,
|
|
||||||
filenames[match[0]],
|
|
||||||
]);
|
|
||||||
};
|
|
||||||
Object.keys(objects).forEach((prefix) =>
|
|
||||||
objects[prefix].forEach((array) =>
|
|
||||||
objectSearchCallback(prefix, array)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return results;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* search for full-text terms in the index
|
|
||||||
*/
|
|
||||||
performTermsSearch: (searchTerms, excludedTerms) => {
|
|
||||||
// prepare search
|
|
||||||
const terms = Search._index.terms;
|
|
||||||
const titleTerms = Search._index.titleterms;
|
|
||||||
const filenames = Search._index.filenames;
|
|
||||||
const docNames = Search._index.docnames;
|
|
||||||
const titles = Search._index.titles;
|
|
||||||
|
|
||||||
const scoreMap = new Map();
|
|
||||||
const fileMap = new Map();
|
|
||||||
|
|
||||||
// perform the search on the required terms
|
|
||||||
searchTerms.forEach((word) => {
|
|
||||||
const files = [];
|
|
||||||
const arr = [
|
|
||||||
{ files: terms[word], score: Scorer.term },
|
|
||||||
{ files: titleTerms[word], score: Scorer.title },
|
|
||||||
];
|
|
||||||
// add support for partial matches
|
|
||||||
if (word.length > 2) {
|
|
||||||
const escapedWord = _escapeRegExp(word);
|
|
||||||
if (!terms.hasOwnProperty(word)) {
|
|
||||||
Object.keys(terms).forEach((term) => {
|
|
||||||
if (term.match(escapedWord))
|
|
||||||
arr.push({ files: terms[term], score: Scorer.partialTerm });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (!titleTerms.hasOwnProperty(word)) {
|
|
||||||
Object.keys(titleTerms).forEach((term) => {
|
|
||||||
if (term.match(escapedWord))
|
|
||||||
arr.push({ files: titleTerms[term], score: Scorer.partialTitle });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// no match but word was a required one
|
|
||||||
if (arr.every((record) => record.files === undefined)) return;
|
|
||||||
|
|
||||||
// found search word in contents
|
|
||||||
arr.forEach((record) => {
|
|
||||||
if (record.files === undefined) return;
|
|
||||||
|
|
||||||
let recordFiles = record.files;
|
|
||||||
if (recordFiles.length === undefined) recordFiles = [recordFiles];
|
|
||||||
files.push(...recordFiles);
|
|
||||||
|
|
||||||
// set score for the word in each file
|
|
||||||
recordFiles.forEach((file) => {
|
|
||||||
if (!scoreMap.has(file)) scoreMap.set(file, {});
|
|
||||||
scoreMap.get(file)[word] = record.score;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// create the mapping
|
|
||||||
files.forEach((file) => {
|
|
||||||
if (!fileMap.has(file)) fileMap.set(file, [word]);
|
|
||||||
else if (fileMap.get(file).indexOf(word) === -1) fileMap.get(file).push(word);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// now check if the files don't contain excluded terms
|
|
||||||
const results = [];
|
|
||||||
for (const [file, wordList] of fileMap) {
|
|
||||||
// check if all requirements are matched
|
|
||||||
|
|
||||||
// as search terms with length < 3 are discarded
|
|
||||||
const filteredTermCount = [...searchTerms].filter(
|
|
||||||
(term) => term.length > 2
|
|
||||||
).length;
|
|
||||||
if (
|
|
||||||
wordList.length !== searchTerms.size &&
|
|
||||||
wordList.length !== filteredTermCount
|
|
||||||
)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// ensure that none of the excluded terms is in the search result
|
|
||||||
if (
|
|
||||||
[...excludedTerms].some(
|
|
||||||
(term) =>
|
|
||||||
terms[term] === file ||
|
|
||||||
titleTerms[term] === file ||
|
|
||||||
(terms[term] || []).includes(file) ||
|
|
||||||
(titleTerms[term] || []).includes(file)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
break;
|
|
||||||
|
|
||||||
// select one (max) score for the file.
|
|
||||||
const score = Math.max(...wordList.map((w) => scoreMap.get(file)[w]));
|
|
||||||
// add result to the result list
|
|
||||||
results.push([
|
|
||||||
docNames[file],
|
|
||||||
titles[file],
|
|
||||||
"",
|
|
||||||
null,
|
|
||||||
score,
|
|
||||||
filenames[file],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
return results;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* helper function to return a node containing the
|
|
||||||
* search summary for a given text. keywords is a list
|
|
||||||
* of stemmed words.
|
|
||||||
*/
|
|
||||||
makeSearchSummary: (htmlText, keywords, anchor) => {
|
|
||||||
const text = Search.htmlToText(htmlText, anchor);
|
|
||||||
if (text === "") return null;
|
|
||||||
|
|
||||||
const textLower = text.toLowerCase();
|
|
||||||
const actualStartPosition = [...keywords]
|
|
||||||
.map((k) => textLower.indexOf(k.toLowerCase()))
|
|
||||||
.filter((i) => i > -1)
|
|
||||||
.slice(-1)[0];
|
|
||||||
const startWithContext = Math.max(actualStartPosition - 120, 0);
|
|
||||||
|
|
||||||
const top = startWithContext === 0 ? "" : "...";
|
|
||||||
const tail = startWithContext + 240 < text.length ? "..." : "";
|
|
||||||
|
|
||||||
let summary = document.createElement("p");
|
|
||||||
summary.classList.add("context");
|
|
||||||
summary.textContent = top + text.substr(startWithContext, 240).trim() + tail;
|
|
||||||
|
|
||||||
return summary;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
_ready(Search.init);
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
/*
|
|
||||||
* sidebar.js
|
|
||||||
* ~~~~~~~~~~
|
|
||||||
*
|
|
||||||
* This script makes the Sphinx sidebar collapsible.
|
|
||||||
*
|
|
||||||
* .sphinxsidebar contains .sphinxsidebarwrapper. This script adds
|
|
||||||
* in .sphixsidebar, after .sphinxsidebarwrapper, the #sidebarbutton
|
|
||||||
* used to collapse and expand the sidebar.
|
|
||||||
*
|
|
||||||
* When the sidebar is collapsed the .sphinxsidebarwrapper is hidden
|
|
||||||
* and the width of the sidebar and the margin-left of the document
|
|
||||||
* are decreased. When the sidebar is expanded the opposite happens.
|
|
||||||
* This script saves a per-browser/per-session cookie used to
|
|
||||||
* remember the position of the sidebar among the pages.
|
|
||||||
* Once the browser is closed the cookie is deleted and the position
|
|
||||||
* reset to the default (expanded).
|
|
||||||
*
|
|
||||||
* :copyright: Copyright 2007-2014 by the Sphinx team, see AUTHORS.
|
|
||||||
* :license: BSD, see LICENSE for details.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
$(function() {
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// global elements used by the functions.
|
|
||||||
// the 'sidebarbutton' element is defined as global after its
|
|
||||||
// creation, in the add_sidebar_button function
|
|
||||||
var bodywrapper = $('.bodywrapper');
|
|
||||||
var sidebar = $('.sphinxsidebar');
|
|
||||||
var sidebarwrapper = $('.sphinxsidebarwrapper');
|
|
||||||
|
|
||||||
// for some reason, the document has no sidebar; do not run into errors
|
|
||||||
if (!sidebar.length) return;
|
|
||||||
|
|
||||||
// original margin-left of the bodywrapper and width of the sidebar
|
|
||||||
// with the sidebar expanded
|
|
||||||
var bw_margin_expanded = bodywrapper.css('margin-left');
|
|
||||||
var ssb_width_expanded = sidebar.width();
|
|
||||||
|
|
||||||
// margin-left of the bodywrapper and width of the sidebar
|
|
||||||
// with the sidebar collapsed
|
|
||||||
var bw_margin_collapsed = '.8em';
|
|
||||||
var ssb_width_collapsed = '.8em';
|
|
||||||
|
|
||||||
// colors used by the current theme
|
|
||||||
var dark_color = $('.related').css('background-color');
|
|
||||||
var light_color = $('.document').css('background-color');
|
|
||||||
|
|
||||||
function sidebar_is_collapsed() {
|
|
||||||
return sidebarwrapper.is(':not(:visible)');
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggle_sidebar() {
|
|
||||||
if (sidebar_is_collapsed())
|
|
||||||
expand_sidebar();
|
|
||||||
else
|
|
||||||
collapse_sidebar();
|
|
||||||
}
|
|
||||||
|
|
||||||
function collapse_sidebar() {
|
|
||||||
sidebarwrapper.hide();
|
|
||||||
sidebar.css('width', ssb_width_collapsed);
|
|
||||||
bodywrapper.css('margin-left', bw_margin_collapsed);
|
|
||||||
sidebarbutton.css({
|
|
||||||
'margin-left': '0',
|
|
||||||
'height': bodywrapper.height()
|
|
||||||
});
|
|
||||||
sidebarbutton.find('span').text('»');
|
|
||||||
sidebarbutton.attr('title', _('Expand sidebar'));
|
|
||||||
document.cookie = 'sidebar=collapsed';
|
|
||||||
}
|
|
||||||
|
|
||||||
function expand_sidebar() {
|
|
||||||
bodywrapper.css('margin-left', bw_margin_expanded);
|
|
||||||
sidebar.css('width', ssb_width_expanded);
|
|
||||||
sidebarwrapper.show();
|
|
||||||
sidebarbutton.css({
|
|
||||||
'margin-left': ssb_width_expanded-12,
|
|
||||||
'height': bodywrapper.height()
|
|
||||||
});
|
|
||||||
sidebarbutton.find('span').text('«');
|
|
||||||
sidebarbutton.attr('title', _('Collapse sidebar'));
|
|
||||||
document.cookie = 'sidebar=expanded';
|
|
||||||
}
|
|
||||||
|
|
||||||
function add_sidebar_button() {
|
|
||||||
sidebarwrapper.css({
|
|
||||||
'float': 'left',
|
|
||||||
'margin-right': '0',
|
|
||||||
'width': ssb_width_expanded - 28
|
|
||||||
});
|
|
||||||
// create the button
|
|
||||||
sidebar.append(
|
|
||||||
'<div id="sidebarbutton"><span>«</span></div>'
|
|
||||||
);
|
|
||||||
var sidebarbutton = $('#sidebarbutton');
|
|
||||||
light_color = sidebarbutton.css('background-color');
|
|
||||||
// find the height of the viewport to center the '<<' in the page
|
|
||||||
var viewport_height;
|
|
||||||
if (window.innerHeight)
|
|
||||||
viewport_height = window.innerHeight;
|
|
||||||
else
|
|
||||||
viewport_height = $(window).height();
|
|
||||||
sidebarbutton.find('span').css({
|
|
||||||
'display': 'block',
|
|
||||||
'margin-top': (viewport_height - sidebar.position().top - 20) / 2
|
|
||||||
});
|
|
||||||
|
|
||||||
sidebarbutton.click(toggle_sidebar);
|
|
||||||
sidebarbutton.attr('title', _('Collapse sidebar'));
|
|
||||||
sidebarbutton.css({
|
|
||||||
'color': '#FFFFFF',
|
|
||||||
'border-left': '1px solid ' + dark_color,
|
|
||||||
'font-size': '1.2em',
|
|
||||||
'cursor': 'pointer',
|
|
||||||
'height': bodywrapper.height(),
|
|
||||||
'padding-top': '1px',
|
|
||||||
'margin-left': ssb_width_expanded - 12
|
|
||||||
});
|
|
||||||
|
|
||||||
sidebarbutton.hover(
|
|
||||||
function () {
|
|
||||||
$(this).css('background-color', dark_color);
|
|
||||||
},
|
|
||||||
function () {
|
|
||||||
$(this).css('background-color', light_color);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function set_position_from_cookie() {
|
|
||||||
if (!document.cookie)
|
|
||||||
return;
|
|
||||||
var items = document.cookie.split(';');
|
|
||||||
for(var k=0; k<items.length; k++) {
|
|
||||||
var key_val = items[k].split('=');
|
|
||||||
var key = key_val[0].replace(/ /, ""); // strip leading spaces
|
|
||||||
if (key == 'sidebar') {
|
|
||||||
var value = key_val[1];
|
|
||||||
if ((value == 'collapsed') && (!sidebar_is_collapsed()))
|
|
||||||
collapse_sidebar();
|
|
||||||
else if ((value == 'expanded') && (sidebar_is_collapsed()))
|
|
||||||
expand_sidebar();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
add_sidebar_button();
|
|
||||||
var sidebarbutton = $('#sidebarbutton');
|
|
||||||
set_position_from_cookie();
|
|
||||||
});
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
/* Highlighting utilities for Sphinx HTML documentation. */
|
|
||||||
"use strict";
|
|
||||||
|
|
||||||
const SPHINX_HIGHLIGHT_ENABLED = true
|
|
||||||
|
|
||||||
/**
|
|
||||||
* highlight a given string on a node by wrapping it in
|
|
||||||
* span elements with the given class name.
|
|
||||||
*/
|
|
||||||
const _highlight = (node, addItems, text, className) => {
|
|
||||||
if (node.nodeType === Node.TEXT_NODE) {
|
|
||||||
const val = node.nodeValue;
|
|
||||||
const parent = node.parentNode;
|
|
||||||
const pos = val.toLowerCase().indexOf(text);
|
|
||||||
if (
|
|
||||||
pos >= 0 &&
|
|
||||||
!parent.classList.contains(className) &&
|
|
||||||
!parent.classList.contains("nohighlight")
|
|
||||||
) {
|
|
||||||
let span;
|
|
||||||
|
|
||||||
const closestNode = parent.closest("body, svg, foreignObject");
|
|
||||||
const isInSVG = closestNode && closestNode.matches("svg");
|
|
||||||
if (isInSVG) {
|
|
||||||
span = document.createElementNS("http://www.w3.org/2000/svg", "tspan");
|
|
||||||
} else {
|
|
||||||
span = document.createElement("span");
|
|
||||||
span.classList.add(className);
|
|
||||||
}
|
|
||||||
|
|
||||||
span.appendChild(document.createTextNode(val.substr(pos, text.length)));
|
|
||||||
const rest = document.createTextNode(val.substr(pos + text.length));
|
|
||||||
parent.insertBefore(
|
|
||||||
span,
|
|
||||||
parent.insertBefore(
|
|
||||||
rest,
|
|
||||||
node.nextSibling
|
|
||||||
)
|
|
||||||
);
|
|
||||||
node.nodeValue = val.substr(0, pos);
|
|
||||||
/* There may be more occurrences of search term in this node. So call this
|
|
||||||
* function recursively on the remaining fragment.
|
|
||||||
*/
|
|
||||||
_highlight(rest, addItems, text, className);
|
|
||||||
|
|
||||||
if (isInSVG) {
|
|
||||||
const rect = document.createElementNS(
|
|
||||||
"http://www.w3.org/2000/svg",
|
|
||||||
"rect"
|
|
||||||
);
|
|
||||||
const bbox = parent.getBBox();
|
|
||||||
rect.x.baseVal.value = bbox.x;
|
|
||||||
rect.y.baseVal.value = bbox.y;
|
|
||||||
rect.width.baseVal.value = bbox.width;
|
|
||||||
rect.height.baseVal.value = bbox.height;
|
|
||||||
rect.setAttribute("class", className);
|
|
||||||
addItems.push({ parent: parent, target: rect });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (node.matches && !node.matches("button, select, textarea")) {
|
|
||||||
node.childNodes.forEach((el) => _highlight(el, addItems, text, className));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const _highlightText = (thisNode, text, className) => {
|
|
||||||
let addItems = [];
|
|
||||||
_highlight(thisNode, addItems, text, className);
|
|
||||||
addItems.forEach((obj) =>
|
|
||||||
obj.parent.insertAdjacentElement("beforebegin", obj.target)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Small JavaScript module for the documentation.
|
|
||||||
*/
|
|
||||||
const SphinxHighlight = {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* highlight the search words provided in localstorage in the text
|
|
||||||
*/
|
|
||||||
highlightSearchWords: () => {
|
|
||||||
if (!SPHINX_HIGHLIGHT_ENABLED) return; // bail if no highlight
|
|
||||||
|
|
||||||
// get and clear terms from localstorage
|
|
||||||
const url = new URL(window.location);
|
|
||||||
const highlight =
|
|
||||||
localStorage.getItem("sphinx_highlight_terms")
|
|
||||||
|| url.searchParams.get("highlight")
|
|
||||||
|| "";
|
|
||||||
localStorage.removeItem("sphinx_highlight_terms")
|
|
||||||
url.searchParams.delete("highlight");
|
|
||||||
window.history.replaceState({}, "", url);
|
|
||||||
|
|
||||||
// get individual terms from highlight string
|
|
||||||
const terms = highlight.toLowerCase().split(/\s+/).filter(x => x);
|
|
||||||
if (terms.length === 0) return; // nothing to do
|
|
||||||
|
|
||||||
// There should never be more than one element matching "div.body"
|
|
||||||
const divBody = document.querySelectorAll("div.body");
|
|
||||||
const body = divBody.length ? divBody[0] : document.querySelector("body");
|
|
||||||
window.setTimeout(() => {
|
|
||||||
terms.forEach((term) => _highlightText(body, term, "highlighted"));
|
|
||||||
}, 10);
|
|
||||||
|
|
||||||
const searchBox = document.getElementById("searchbox");
|
|
||||||
if (searchBox === null) return;
|
|
||||||
searchBox.appendChild(
|
|
||||||
document
|
|
||||||
.createRange()
|
|
||||||
.createContextualFragment(
|
|
||||||
'<p class="highlight-link">' +
|
|
||||||
'<a href="javascript:SphinxHighlight.hideSearchWords()">' +
|
|
||||||
_("Hide Search Matches") +
|
|
||||||
"</a></p>"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* helper function to hide the search marks again
|
|
||||||
*/
|
|
||||||
hideSearchWords: () => {
|
|
||||||
document
|
|
||||||
.querySelectorAll("#searchbox .highlight-link")
|
|
||||||
.forEach((el) => el.remove());
|
|
||||||
document
|
|
||||||
.querySelectorAll("span.highlighted")
|
|
||||||
.forEach((el) => el.classList.remove("highlighted"));
|
|
||||||
localStorage.removeItem("sphinx_highlight_terms")
|
|
||||||
},
|
|
||||||
|
|
||||||
initEscapeListener: () => {
|
|
||||||
// only install a listener if it is really needed
|
|
||||||
if (!DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS) return;
|
|
||||||
|
|
||||||
document.addEventListener("keydown", (event) => {
|
|
||||||
// bail for input elements
|
|
||||||
if (BLACKLISTED_KEY_CONTROL_ELEMENTS.has(document.activeElement.tagName)) return;
|
|
||||||
// bail with special keys
|
|
||||||
if (event.shiftKey || event.altKey || event.ctrlKey || event.metaKey) return;
|
|
||||||
if (DOCUMENTATION_OPTIONS.ENABLE_SEARCH_SHORTCUTS && (event.key === "Escape")) {
|
|
||||||
SphinxHighlight.hideSearchWords();
|
|
||||||
event.preventDefault();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
_ready(() => {
|
|
||||||
/* Do not call highlightSearchWords() when we are on the search page.
|
|
||||||
* It will highlight words from the *previous* search query.
|
|
||||||
*/
|
|
||||||
if (typeof Search === "undefined") SphinxHighlight.highlightSearchWords();
|
|
||||||
SphinxHighlight.initEscapeListener();
|
|
||||||
});
|
|
||||||
@@ -1,999 +0,0 @@
|
|||||||
// Underscore.js 1.3.1
|
|
||||||
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
|
|
||||||
// Underscore is freely distributable under the MIT license.
|
|
||||||
// Portions of Underscore are inspired or borrowed from Prototype,
|
|
||||||
// Oliver Steele's Functional, and John Resig's Micro-Templating.
|
|
||||||
// For all details and documentation:
|
|
||||||
// http://documentcloud.github.com/underscore
|
|
||||||
|
|
||||||
(function() {
|
|
||||||
|
|
||||||
// Baseline setup
|
|
||||||
// --------------
|
|
||||||
|
|
||||||
// Establish the root object, `window` in the browser, or `global` on the server.
|
|
||||||
var root = this;
|
|
||||||
|
|
||||||
// Save the previous value of the `_` variable.
|
|
||||||
var previousUnderscore = root._;
|
|
||||||
|
|
||||||
// Establish the object that gets returned to break out of a loop iteration.
|
|
||||||
var breaker = {};
|
|
||||||
|
|
||||||
// Save bytes in the minified (but not gzipped) version:
|
|
||||||
var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;
|
|
||||||
|
|
||||||
// Create quick reference variables for speed access to core prototypes.
|
|
||||||
var slice = ArrayProto.slice,
|
|
||||||
unshift = ArrayProto.unshift,
|
|
||||||
toString = ObjProto.toString,
|
|
||||||
hasOwnProperty = ObjProto.hasOwnProperty;
|
|
||||||
|
|
||||||
// All **ECMAScript 5** native function implementations that we hope to use
|
|
||||||
// are declared here.
|
|
||||||
var
|
|
||||||
nativeForEach = ArrayProto.forEach,
|
|
||||||
nativeMap = ArrayProto.map,
|
|
||||||
nativeReduce = ArrayProto.reduce,
|
|
||||||
nativeReduceRight = ArrayProto.reduceRight,
|
|
||||||
nativeFilter = ArrayProto.filter,
|
|
||||||
nativeEvery = ArrayProto.every,
|
|
||||||
nativeSome = ArrayProto.some,
|
|
||||||
nativeIndexOf = ArrayProto.indexOf,
|
|
||||||
nativeLastIndexOf = ArrayProto.lastIndexOf,
|
|
||||||
nativeIsArray = Array.isArray,
|
|
||||||
nativeKeys = Object.keys,
|
|
||||||
nativeBind = FuncProto.bind;
|
|
||||||
|
|
||||||
// Create a safe reference to the Underscore object for use below.
|
|
||||||
var _ = function(obj) { return new wrapper(obj); };
|
|
||||||
|
|
||||||
// Export the Underscore object for **Node.js**, with
|
|
||||||
// backwards-compatibility for the old `require()` API. If we're in
|
|
||||||
// the browser, add `_` as a global object via a string identifier,
|
|
||||||
// for Closure Compiler "advanced" mode.
|
|
||||||
if (typeof exports !== 'undefined') {
|
|
||||||
if (typeof module !== 'undefined' && module.exports) {
|
|
||||||
exports = module.exports = _;
|
|
||||||
}
|
|
||||||
exports._ = _;
|
|
||||||
} else {
|
|
||||||
root['_'] = _;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Current version.
|
|
||||||
_.VERSION = '1.3.1';
|
|
||||||
|
|
||||||
// Collection Functions
|
|
||||||
// --------------------
|
|
||||||
|
|
||||||
// The cornerstone, an `each` implementation, aka `forEach`.
|
|
||||||
// Handles objects with the built-in `forEach`, arrays, and raw objects.
|
|
||||||
// Delegates to **ECMAScript 5**'s native `forEach` if available.
|
|
||||||
var each = _.each = _.forEach = function(obj, iterator, context) {
|
|
||||||
if (obj == null) return;
|
|
||||||
if (nativeForEach && obj.forEach === nativeForEach) {
|
|
||||||
obj.forEach(iterator, context);
|
|
||||||
} else if (obj.length === +obj.length) {
|
|
||||||
for (var i = 0, l = obj.length; i < l; i++) {
|
|
||||||
if (i in obj && iterator.call(context, obj[i], i, obj) === breaker) return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (var key in obj) {
|
|
||||||
if (_.has(obj, key)) {
|
|
||||||
if (iterator.call(context, obj[key], key, obj) === breaker) return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Return the results of applying the iterator to each element.
|
|
||||||
// Delegates to **ECMAScript 5**'s native `map` if available.
|
|
||||||
_.map = _.collect = function(obj, iterator, context) {
|
|
||||||
var results = [];
|
|
||||||
if (obj == null) return results;
|
|
||||||
if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context);
|
|
||||||
each(obj, function(value, index, list) {
|
|
||||||
results[results.length] = iterator.call(context, value, index, list);
|
|
||||||
});
|
|
||||||
if (obj.length === +obj.length) results.length = obj.length;
|
|
||||||
return results;
|
|
||||||
};
|
|
||||||
|
|
||||||
// **Reduce** builds up a single result from a list of values, aka `inject`,
|
|
||||||
// or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available.
|
|
||||||
_.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) {
|
|
||||||
var initial = arguments.length > 2;
|
|
||||||
if (obj == null) obj = [];
|
|
||||||
if (nativeReduce && obj.reduce === nativeReduce) {
|
|
||||||
if (context) iterator = _.bind(iterator, context);
|
|
||||||
return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator);
|
|
||||||
}
|
|
||||||
each(obj, function(value, index, list) {
|
|
||||||
if (!initial) {
|
|
||||||
memo = value;
|
|
||||||
initial = true;
|
|
||||||
} else {
|
|
||||||
memo = iterator.call(context, memo, value, index, list);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
if (!initial) throw new TypeError('Reduce of empty array with no initial value');
|
|
||||||
return memo;
|
|
||||||
};
|
|
||||||
|
|
||||||
// The right-associative version of reduce, also known as `foldr`.
|
|
||||||
// Delegates to **ECMAScript 5**'s native `reduceRight` if available.
|
|
||||||
_.reduceRight = _.foldr = function(obj, iterator, memo, context) {
|
|
||||||
var initial = arguments.length > 2;
|
|
||||||
if (obj == null) obj = [];
|
|
||||||
if (nativeReduceRight && obj.reduceRight === nativeReduceRight) {
|
|
||||||
if (context) iterator = _.bind(iterator, context);
|
|
||||||
return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator);
|
|
||||||
}
|
|
||||||
var reversed = _.toArray(obj).reverse();
|
|
||||||
if (context && !initial) iterator = _.bind(iterator, context);
|
|
||||||
return initial ? _.reduce(reversed, iterator, memo, context) : _.reduce(reversed, iterator);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Return the first value which passes a truth test. Aliased as `detect`.
|
|
||||||
_.find = _.detect = function(obj, iterator, context) {
|
|
||||||
var result;
|
|
||||||
any(obj, function(value, index, list) {
|
|
||||||
if (iterator.call(context, value, index, list)) {
|
|
||||||
result = value;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Return all the elements that pass a truth test.
|
|
||||||
// Delegates to **ECMAScript 5**'s native `filter` if available.
|
|
||||||
// Aliased as `select`.
|
|
||||||
_.filter = _.select = function(obj, iterator, context) {
|
|
||||||
var results = [];
|
|
||||||
if (obj == null) return results;
|
|
||||||
if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context);
|
|
||||||
each(obj, function(value, index, list) {
|
|
||||||
if (iterator.call(context, value, index, list)) results[results.length] = value;
|
|
||||||
});
|
|
||||||
return results;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Return all the elements for which a truth test fails.
|
|
||||||
_.reject = function(obj, iterator, context) {
|
|
||||||
var results = [];
|
|
||||||
if (obj == null) return results;
|
|
||||||
each(obj, function(value, index, list) {
|
|
||||||
if (!iterator.call(context, value, index, list)) results[results.length] = value;
|
|
||||||
});
|
|
||||||
return results;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Determine whether all of the elements match a truth test.
|
|
||||||
// Delegates to **ECMAScript 5**'s native `every` if available.
|
|
||||||
// Aliased as `all`.
|
|
||||||
_.every = _.all = function(obj, iterator, context) {
|
|
||||||
var result = true;
|
|
||||||
if (obj == null) return result;
|
|
||||||
if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context);
|
|
||||||
each(obj, function(value, index, list) {
|
|
||||||
if (!(result = result && iterator.call(context, value, index, list))) return breaker;
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Determine if at least one element in the object matches a truth test.
|
|
||||||
// Delegates to **ECMAScript 5**'s native `some` if available.
|
|
||||||
// Aliased as `any`.
|
|
||||||
var any = _.some = _.any = function(obj, iterator, context) {
|
|
||||||
iterator || (iterator = _.identity);
|
|
||||||
var result = false;
|
|
||||||
if (obj == null) return result;
|
|
||||||
if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context);
|
|
||||||
each(obj, function(value, index, list) {
|
|
||||||
if (result || (result = iterator.call(context, value, index, list))) return breaker;
|
|
||||||
});
|
|
||||||
return !!result;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Determine if a given value is included in the array or object using `===`.
|
|
||||||
// Aliased as `contains`.
|
|
||||||
_.include = _.contains = function(obj, target) {
|
|
||||||
var found = false;
|
|
||||||
if (obj == null) return found;
|
|
||||||
if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1;
|
|
||||||
found = any(obj, function(value) {
|
|
||||||
return value === target;
|
|
||||||
});
|
|
||||||
return found;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Invoke a method (with arguments) on every item in a collection.
|
|
||||||
_.invoke = function(obj, method) {
|
|
||||||
var args = slice.call(arguments, 2);
|
|
||||||
return _.map(obj, function(value) {
|
|
||||||
return (_.isFunction(method) ? method || value : value[method]).apply(value, args);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Convenience version of a common use case of `map`: fetching a property.
|
|
||||||
_.pluck = function(obj, key) {
|
|
||||||
return _.map(obj, function(value){ return value[key]; });
|
|
||||||
};
|
|
||||||
|
|
||||||
// Return the maximum element or (element-based computation).
|
|
||||||
_.max = function(obj, iterator, context) {
|
|
||||||
if (!iterator && _.isArray(obj)) return Math.max.apply(Math, obj);
|
|
||||||
if (!iterator && _.isEmpty(obj)) return -Infinity;
|
|
||||||
var result = {computed : -Infinity};
|
|
||||||
each(obj, function(value, index, list) {
|
|
||||||
var computed = iterator ? iterator.call(context, value, index, list) : value;
|
|
||||||
computed >= result.computed && (result = {value : value, computed : computed});
|
|
||||||
});
|
|
||||||
return result.value;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Return the minimum element (or element-based computation).
|
|
||||||
_.min = function(obj, iterator, context) {
|
|
||||||
if (!iterator && _.isArray(obj)) return Math.min.apply(Math, obj);
|
|
||||||
if (!iterator && _.isEmpty(obj)) return Infinity;
|
|
||||||
var result = {computed : Infinity};
|
|
||||||
each(obj, function(value, index, list) {
|
|
||||||
var computed = iterator ? iterator.call(context, value, index, list) : value;
|
|
||||||
computed < result.computed && (result = {value : value, computed : computed});
|
|
||||||
});
|
|
||||||
return result.value;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Shuffle an array.
|
|
||||||
_.shuffle = function(obj) {
|
|
||||||
var shuffled = [], rand;
|
|
||||||
each(obj, function(value, index, list) {
|
|
||||||
if (index == 0) {
|
|
||||||
shuffled[0] = value;
|
|
||||||
} else {
|
|
||||||
rand = Math.floor(Math.random() * (index + 1));
|
|
||||||
shuffled[index] = shuffled[rand];
|
|
||||||
shuffled[rand] = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return shuffled;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Sort the object's values by a criterion produced by an iterator.
|
|
||||||
_.sortBy = function(obj, iterator, context) {
|
|
||||||
return _.pluck(_.map(obj, function(value, index, list) {
|
|
||||||
return {
|
|
||||||
value : value,
|
|
||||||
criteria : iterator.call(context, value, index, list)
|
|
||||||
};
|
|
||||||
}).sort(function(left, right) {
|
|
||||||
var a = left.criteria, b = right.criteria;
|
|
||||||
return a < b ? -1 : a > b ? 1 : 0;
|
|
||||||
}), 'value');
|
|
||||||
};
|
|
||||||
|
|
||||||
// Groups the object's values by a criterion. Pass either a string attribute
|
|
||||||
// to group by, or a function that returns the criterion.
|
|
||||||
_.groupBy = function(obj, val) {
|
|
||||||
var result = {};
|
|
||||||
var iterator = _.isFunction(val) ? val : function(obj) { return obj[val]; };
|
|
||||||
each(obj, function(value, index) {
|
|
||||||
var key = iterator(value, index);
|
|
||||||
(result[key] || (result[key] = [])).push(value);
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Use a comparator function to figure out at what index an object should
|
|
||||||
// be inserted so as to maintain order. Uses binary search.
|
|
||||||
_.sortedIndex = function(array, obj, iterator) {
|
|
||||||
iterator || (iterator = _.identity);
|
|
||||||
var low = 0, high = array.length;
|
|
||||||
while (low < high) {
|
|
||||||
var mid = (low + high) >> 1;
|
|
||||||
iterator(array[mid]) < iterator(obj) ? low = mid + 1 : high = mid;
|
|
||||||
}
|
|
||||||
return low;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Safely convert anything iterable into a real, live array.
|
|
||||||
_.toArray = function(iterable) {
|
|
||||||
if (!iterable) return [];
|
|
||||||
if (iterable.toArray) return iterable.toArray();
|
|
||||||
if (_.isArray(iterable)) return slice.call(iterable);
|
|
||||||
if (_.isArguments(iterable)) return slice.call(iterable);
|
|
||||||
return _.values(iterable);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Return the number of elements in an object.
|
|
||||||
_.size = function(obj) {
|
|
||||||
return _.toArray(obj).length;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Array Functions
|
|
||||||
// ---------------
|
|
||||||
|
|
||||||
// Get the first element of an array. Passing **n** will return the first N
|
|
||||||
// values in the array. Aliased as `head`. The **guard** check allows it to work
|
|
||||||
// with `_.map`.
|
|
||||||
_.first = _.head = function(array, n, guard) {
|
|
||||||
return (n != null) && !guard ? slice.call(array, 0, n) : array[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
// Returns everything but the last entry of the array. Especcialy useful on
|
|
||||||
// the arguments object. Passing **n** will return all the values in
|
|
||||||
// the array, excluding the last N. The **guard** check allows it to work with
|
|
||||||
// `_.map`.
|
|
||||||
_.initial = function(array, n, guard) {
|
|
||||||
return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Get the last element of an array. Passing **n** will return the last N
|
|
||||||
// values in the array. The **guard** check allows it to work with `_.map`.
|
|
||||||
_.last = function(array, n, guard) {
|
|
||||||
if ((n != null) && !guard) {
|
|
||||||
return slice.call(array, Math.max(array.length - n, 0));
|
|
||||||
} else {
|
|
||||||
return array[array.length - 1];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Returns everything but the first entry of the array. Aliased as `tail`.
|
|
||||||
// Especially useful on the arguments object. Passing an **index** will return
|
|
||||||
// the rest of the values in the array from that index onward. The **guard**
|
|
||||||
// check allows it to work with `_.map`.
|
|
||||||
_.rest = _.tail = function(array, index, guard) {
|
|
||||||
return slice.call(array, (index == null) || guard ? 1 : index);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Trim out all falsy values from an array.
|
|
||||||
_.compact = function(array) {
|
|
||||||
return _.filter(array, function(value){ return !!value; });
|
|
||||||
};
|
|
||||||
|
|
||||||
// Return a completely flattened version of an array.
|
|
||||||
_.flatten = function(array, shallow) {
|
|
||||||
return _.reduce(array, function(memo, value) {
|
|
||||||
if (_.isArray(value)) return memo.concat(shallow ? value : _.flatten(value));
|
|
||||||
memo[memo.length] = value;
|
|
||||||
return memo;
|
|
||||||
}, []);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Return a version of the array that does not contain the specified value(s).
|
|
||||||
_.without = function(array) {
|
|
||||||
return _.difference(array, slice.call(arguments, 1));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Produce a duplicate-free version of the array. If the array has already
|
|
||||||
// been sorted, you have the option of using a faster algorithm.
|
|
||||||
// Aliased as `unique`.
|
|
||||||
_.uniq = _.unique = function(array, isSorted, iterator) {
|
|
||||||
var initial = iterator ? _.map(array, iterator) : array;
|
|
||||||
var result = [];
|
|
||||||
_.reduce(initial, function(memo, el, i) {
|
|
||||||
if (0 == i || (isSorted === true ? _.last(memo) != el : !_.include(memo, el))) {
|
|
||||||
memo[memo.length] = el;
|
|
||||||
result[result.length] = array[i];
|
|
||||||
}
|
|
||||||
return memo;
|
|
||||||
}, []);
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Produce an array that contains the union: each distinct element from all of
|
|
||||||
// the passed-in arrays.
|
|
||||||
_.union = function() {
|
|
||||||
return _.uniq(_.flatten(arguments, true));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Produce an array that contains every item shared between all the
|
|
||||||
// passed-in arrays. (Aliased as "intersect" for back-compat.)
|
|
||||||
_.intersection = _.intersect = function(array) {
|
|
||||||
var rest = slice.call(arguments, 1);
|
|
||||||
return _.filter(_.uniq(array), function(item) {
|
|
||||||
return _.every(rest, function(other) {
|
|
||||||
return _.indexOf(other, item) >= 0;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Take the difference between one array and a number of other arrays.
|
|
||||||
// Only the elements present in just the first array will remain.
|
|
||||||
_.difference = function(array) {
|
|
||||||
var rest = _.flatten(slice.call(arguments, 1));
|
|
||||||
return _.filter(array, function(value){ return !_.include(rest, value); });
|
|
||||||
};
|
|
||||||
|
|
||||||
// Zip together multiple lists into a single array -- elements that share
|
|
||||||
// an index go together.
|
|
||||||
_.zip = function() {
|
|
||||||
var args = slice.call(arguments);
|
|
||||||
var length = _.max(_.pluck(args, 'length'));
|
|
||||||
var results = new Array(length);
|
|
||||||
for (var i = 0; i < length; i++) results[i] = _.pluck(args, "" + i);
|
|
||||||
return results;
|
|
||||||
};
|
|
||||||
|
|
||||||
// If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**),
|
|
||||||
// we need this function. Return the position of the first occurrence of an
|
|
||||||
// item in an array, or -1 if the item is not included in the array.
|
|
||||||
// Delegates to **ECMAScript 5**'s native `indexOf` if available.
|
|
||||||
// If the array is large and already in sort order, pass `true`
|
|
||||||
// for **isSorted** to use binary search.
|
|
||||||
_.indexOf = function(array, item, isSorted) {
|
|
||||||
if (array == null) return -1;
|
|
||||||
var i, l;
|
|
||||||
if (isSorted) {
|
|
||||||
i = _.sortedIndex(array, item);
|
|
||||||
return array[i] === item ? i : -1;
|
|
||||||
}
|
|
||||||
if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item);
|
|
||||||
for (i = 0, l = array.length; i < l; i++) if (i in array && array[i] === item) return i;
|
|
||||||
return -1;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Delegates to **ECMAScript 5**'s native `lastIndexOf` if available.
|
|
||||||
_.lastIndexOf = function(array, item) {
|
|
||||||
if (array == null) return -1;
|
|
||||||
if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) return array.lastIndexOf(item);
|
|
||||||
var i = array.length;
|
|
||||||
while (i--) if (i in array && array[i] === item) return i;
|
|
||||||
return -1;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Generate an integer Array containing an arithmetic progression. A port of
|
|
||||||
// the native Python `range()` function. See
|
|
||||||
// [the Python documentation](http://docs.python.org/library/functions.html#range).
|
|
||||||
_.range = function(start, stop, step) {
|
|
||||||
if (arguments.length <= 1) {
|
|
||||||
stop = start || 0;
|
|
||||||
start = 0;
|
|
||||||
}
|
|
||||||
step = arguments[2] || 1;
|
|
||||||
|
|
||||||
var len = Math.max(Math.ceil((stop - start) / step), 0);
|
|
||||||
var idx = 0;
|
|
||||||
var range = new Array(len);
|
|
||||||
|
|
||||||
while(idx < len) {
|
|
||||||
range[idx++] = start;
|
|
||||||
start += step;
|
|
||||||
}
|
|
||||||
|
|
||||||
return range;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Function (ahem) Functions
|
|
||||||
// ------------------
|
|
||||||
|
|
||||||
// Reusable constructor function for prototype setting.
|
|
||||||
var ctor = function(){};
|
|
||||||
|
|
||||||
// Create a function bound to a given object (assigning `this`, and arguments,
|
|
||||||
// optionally). Binding with arguments is also known as `curry`.
|
|
||||||
// Delegates to **ECMAScript 5**'s native `Function.bind` if available.
|
|
||||||
// We check for `func.bind` first, to fail fast when `func` is undefined.
|
|
||||||
_.bind = function bind(func, context) {
|
|
||||||
var bound, args;
|
|
||||||
if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
|
|
||||||
if (!_.isFunction(func)) throw new TypeError;
|
|
||||||
args = slice.call(arguments, 2);
|
|
||||||
return bound = function() {
|
|
||||||
if (!(this instanceof bound)) return func.apply(context, args.concat(slice.call(arguments)));
|
|
||||||
ctor.prototype = func.prototype;
|
|
||||||
var self = new ctor;
|
|
||||||
var result = func.apply(self, args.concat(slice.call(arguments)));
|
|
||||||
if (Object(result) === result) return result;
|
|
||||||
return self;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Bind all of an object's methods to that object. Useful for ensuring that
|
|
||||||
// all callbacks defined on an object belong to it.
|
|
||||||
_.bindAll = function(obj) {
|
|
||||||
var funcs = slice.call(arguments, 1);
|
|
||||||
if (funcs.length == 0) funcs = _.functions(obj);
|
|
||||||
each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); });
|
|
||||||
return obj;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Memoize an expensive function by storing its results.
|
|
||||||
_.memoize = function(func, hasher) {
|
|
||||||
var memo = {};
|
|
||||||
hasher || (hasher = _.identity);
|
|
||||||
return function() {
|
|
||||||
var key = hasher.apply(this, arguments);
|
|
||||||
return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments));
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Delays a function for the given number of milliseconds, and then calls
|
|
||||||
// it with the arguments supplied.
|
|
||||||
_.delay = function(func, wait) {
|
|
||||||
var args = slice.call(arguments, 2);
|
|
||||||
return setTimeout(function(){ return func.apply(func, args); }, wait);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Defers a function, scheduling it to run after the current call stack has
|
|
||||||
// cleared.
|
|
||||||
_.defer = function(func) {
|
|
||||||
return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1)));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Returns a function, that, when invoked, will only be triggered at most once
|
|
||||||
// during a given window of time.
|
|
||||||
_.throttle = function(func, wait) {
|
|
||||||
var context, args, timeout, throttling, more;
|
|
||||||
var whenDone = _.debounce(function(){ more = throttling = false; }, wait);
|
|
||||||
return function() {
|
|
||||||
context = this; args = arguments;
|
|
||||||
var later = function() {
|
|
||||||
timeout = null;
|
|
||||||
if (more) func.apply(context, args);
|
|
||||||
whenDone();
|
|
||||||
};
|
|
||||||
if (!timeout) timeout = setTimeout(later, wait);
|
|
||||||
if (throttling) {
|
|
||||||
more = true;
|
|
||||||
} else {
|
|
||||||
func.apply(context, args);
|
|
||||||
}
|
|
||||||
whenDone();
|
|
||||||
throttling = true;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Returns a function, that, as long as it continues to be invoked, will not
|
|
||||||
// be triggered. The function will be called after it stops being called for
|
|
||||||
// N milliseconds.
|
|
||||||
_.debounce = function(func, wait) {
|
|
||||||
var timeout;
|
|
||||||
return function() {
|
|
||||||
var context = this, args = arguments;
|
|
||||||
var later = function() {
|
|
||||||
timeout = null;
|
|
||||||
func.apply(context, args);
|
|
||||||
};
|
|
||||||
clearTimeout(timeout);
|
|
||||||
timeout = setTimeout(later, wait);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Returns a function that will be executed at most one time, no matter how
|
|
||||||
// often you call it. Useful for lazy initialization.
|
|
||||||
_.once = function(func) {
|
|
||||||
var ran = false, memo;
|
|
||||||
return function() {
|
|
||||||
if (ran) return memo;
|
|
||||||
ran = true;
|
|
||||||
return memo = func.apply(this, arguments);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Returns the first function passed as an argument to the second,
|
|
||||||
// allowing you to adjust arguments, run code before and after, and
|
|
||||||
// conditionally execute the original function.
|
|
||||||
_.wrap = function(func, wrapper) {
|
|
||||||
return function() {
|
|
||||||
var args = [func].concat(slice.call(arguments, 0));
|
|
||||||
return wrapper.apply(this, args);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Returns a function that is the composition of a list of functions, each
|
|
||||||
// consuming the return value of the function that follows.
|
|
||||||
_.compose = function() {
|
|
||||||
var funcs = arguments;
|
|
||||||
return function() {
|
|
||||||
var args = arguments;
|
|
||||||
for (var i = funcs.length - 1; i >= 0; i--) {
|
|
||||||
args = [funcs[i].apply(this, args)];
|
|
||||||
}
|
|
||||||
return args[0];
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Returns a function that will only be executed after being called N times.
|
|
||||||
_.after = function(times, func) {
|
|
||||||
if (times <= 0) return func();
|
|
||||||
return function() {
|
|
||||||
if (--times < 1) { return func.apply(this, arguments); }
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Object Functions
|
|
||||||
// ----------------
|
|
||||||
|
|
||||||
// Retrieve the names of an object's properties.
|
|
||||||
// Delegates to **ECMAScript 5**'s native `Object.keys`
|
|
||||||
_.keys = nativeKeys || function(obj) {
|
|
||||||
if (obj !== Object(obj)) throw new TypeError('Invalid object');
|
|
||||||
var keys = [];
|
|
||||||
for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key;
|
|
||||||
return keys;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Retrieve the values of an object's properties.
|
|
||||||
_.values = function(obj) {
|
|
||||||
return _.map(obj, _.identity);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Return a sorted list of the function names available on the object.
|
|
||||||
// Aliased as `methods`
|
|
||||||
_.functions = _.methods = function(obj) {
|
|
||||||
var names = [];
|
|
||||||
for (var key in obj) {
|
|
||||||
if (_.isFunction(obj[key])) names.push(key);
|
|
||||||
}
|
|
||||||
return names.sort();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Extend a given object with all the properties in passed-in object(s).
|
|
||||||
_.extend = function(obj) {
|
|
||||||
each(slice.call(arguments, 1), function(source) {
|
|
||||||
for (var prop in source) {
|
|
||||||
obj[prop] = source[prop];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return obj;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Fill in a given object with default properties.
|
|
||||||
_.defaults = function(obj) {
|
|
||||||
each(slice.call(arguments, 1), function(source) {
|
|
||||||
for (var prop in source) {
|
|
||||||
if (obj[prop] == null) obj[prop] = source[prop];
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return obj;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create a (shallow-cloned) duplicate of an object.
|
|
||||||
_.clone = function(obj) {
|
|
||||||
if (!_.isObject(obj)) return obj;
|
|
||||||
return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Invokes interceptor with the obj, and then returns obj.
|
|
||||||
// The primary purpose of this method is to "tap into" a method chain, in
|
|
||||||
// order to perform operations on intermediate results within the chain.
|
|
||||||
_.tap = function(obj, interceptor) {
|
|
||||||
interceptor(obj);
|
|
||||||
return obj;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Internal recursive comparison function.
|
|
||||||
function eq(a, b, stack) {
|
|
||||||
// Identical objects are equal. `0 === -0`, but they aren't identical.
|
|
||||||
// See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal.
|
|
||||||
if (a === b) return a !== 0 || 1 / a == 1 / b;
|
|
||||||
// A strict comparison is necessary because `null == undefined`.
|
|
||||||
if (a == null || b == null) return a === b;
|
|
||||||
// Unwrap any wrapped objects.
|
|
||||||
if (a._chain) a = a._wrapped;
|
|
||||||
if (b._chain) b = b._wrapped;
|
|
||||||
// Invoke a custom `isEqual` method if one is provided.
|
|
||||||
if (a.isEqual && _.isFunction(a.isEqual)) return a.isEqual(b);
|
|
||||||
if (b.isEqual && _.isFunction(b.isEqual)) return b.isEqual(a);
|
|
||||||
// Compare `[[Class]]` names.
|
|
||||||
var className = toString.call(a);
|
|
||||||
if (className != toString.call(b)) return false;
|
|
||||||
switch (className) {
|
|
||||||
// Strings, numbers, dates, and booleans are compared by value.
|
|
||||||
case '[object String]':
|
|
||||||
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
|
|
||||||
// equivalent to `new String("5")`.
|
|
||||||
return a == String(b);
|
|
||||||
case '[object Number]':
|
|
||||||
// `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for
|
|
||||||
// other numeric values.
|
|
||||||
return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b);
|
|
||||||
case '[object Date]':
|
|
||||||
case '[object Boolean]':
|
|
||||||
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
|
|
||||||
// millisecond representations. Note that invalid dates with millisecond representations
|
|
||||||
// of `NaN` are not equivalent.
|
|
||||||
return +a == +b;
|
|
||||||
// RegExps are compared by their source patterns and flags.
|
|
||||||
case '[object RegExp]':
|
|
||||||
return a.source == b.source &&
|
|
||||||
a.global == b.global &&
|
|
||||||
a.multiline == b.multiline &&
|
|
||||||
a.ignoreCase == b.ignoreCase;
|
|
||||||
}
|
|
||||||
if (typeof a != 'object' || typeof b != 'object') return false;
|
|
||||||
// Assume equality for cyclic structures. The algorithm for detecting cyclic
|
|
||||||
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
|
|
||||||
var length = stack.length;
|
|
||||||
while (length--) {
|
|
||||||
// Linear search. Performance is inversely proportional to the number of
|
|
||||||
// unique nested structures.
|
|
||||||
if (stack[length] == a) return true;
|
|
||||||
}
|
|
||||||
// Add the first object to the stack of traversed objects.
|
|
||||||
stack.push(a);
|
|
||||||
var size = 0, result = true;
|
|
||||||
// Recursively compare objects and arrays.
|
|
||||||
if (className == '[object Array]') {
|
|
||||||
// Compare array lengths to determine if a deep comparison is necessary.
|
|
||||||
size = a.length;
|
|
||||||
result = size == b.length;
|
|
||||||
if (result) {
|
|
||||||
// Deep compare the contents, ignoring non-numeric properties.
|
|
||||||
while (size--) {
|
|
||||||
// Ensure commutative equality for sparse arrays.
|
|
||||||
if (!(result = size in a == size in b && eq(a[size], b[size], stack))) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Objects with different constructors are not equivalent.
|
|
||||||
if ('constructor' in a != 'constructor' in b || a.constructor != b.constructor) return false;
|
|
||||||
// Deep compare objects.
|
|
||||||
for (var key in a) {
|
|
||||||
if (_.has(a, key)) {
|
|
||||||
// Count the expected number of properties.
|
|
||||||
size++;
|
|
||||||
// Deep compare each member.
|
|
||||||
if (!(result = _.has(b, key) && eq(a[key], b[key], stack))) break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Ensure that both objects contain the same number of properties.
|
|
||||||
if (result) {
|
|
||||||
for (key in b) {
|
|
||||||
if (_.has(b, key) && !(size--)) break;
|
|
||||||
}
|
|
||||||
result = !size;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Remove the first object from the stack of traversed objects.
|
|
||||||
stack.pop();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform a deep comparison to check if two objects are equal.
|
|
||||||
_.isEqual = function(a, b) {
|
|
||||||
return eq(a, b, []);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Is a given array, string, or object empty?
|
|
||||||
// An "empty" object has no enumerable own-properties.
|
|
||||||
_.isEmpty = function(obj) {
|
|
||||||
if (_.isArray(obj) || _.isString(obj)) return obj.length === 0;
|
|
||||||
for (var key in obj) if (_.has(obj, key)) return false;
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Is a given value a DOM element?
|
|
||||||
_.isElement = function(obj) {
|
|
||||||
return !!(obj && obj.nodeType == 1);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Is a given value an array?
|
|
||||||
// Delegates to ECMA5's native Array.isArray
|
|
||||||
_.isArray = nativeIsArray || function(obj) {
|
|
||||||
return toString.call(obj) == '[object Array]';
|
|
||||||
};
|
|
||||||
|
|
||||||
// Is a given variable an object?
|
|
||||||
_.isObject = function(obj) {
|
|
||||||
return obj === Object(obj);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Is a given variable an arguments object?
|
|
||||||
_.isArguments = function(obj) {
|
|
||||||
return toString.call(obj) == '[object Arguments]';
|
|
||||||
};
|
|
||||||
if (!_.isArguments(arguments)) {
|
|
||||||
_.isArguments = function(obj) {
|
|
||||||
return !!(obj && _.has(obj, 'callee'));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Is a given value a function?
|
|
||||||
_.isFunction = function(obj) {
|
|
||||||
return toString.call(obj) == '[object Function]';
|
|
||||||
};
|
|
||||||
|
|
||||||
// Is a given value a string?
|
|
||||||
_.isString = function(obj) {
|
|
||||||
return toString.call(obj) == '[object String]';
|
|
||||||
};
|
|
||||||
|
|
||||||
// Is a given value a number?
|
|
||||||
_.isNumber = function(obj) {
|
|
||||||
return toString.call(obj) == '[object Number]';
|
|
||||||
};
|
|
||||||
|
|
||||||
// Is the given value `NaN`?
|
|
||||||
_.isNaN = function(obj) {
|
|
||||||
// `NaN` is the only value for which `===` is not reflexive.
|
|
||||||
return obj !== obj;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Is a given value a boolean?
|
|
||||||
_.isBoolean = function(obj) {
|
|
||||||
return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
|
|
||||||
};
|
|
||||||
|
|
||||||
// Is a given value a date?
|
|
||||||
_.isDate = function(obj) {
|
|
||||||
return toString.call(obj) == '[object Date]';
|
|
||||||
};
|
|
||||||
|
|
||||||
// Is the given value a regular expression?
|
|
||||||
_.isRegExp = function(obj) {
|
|
||||||
return toString.call(obj) == '[object RegExp]';
|
|
||||||
};
|
|
||||||
|
|
||||||
// Is a given value equal to null?
|
|
||||||
_.isNull = function(obj) {
|
|
||||||
return obj === null;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Is a given variable undefined?
|
|
||||||
_.isUndefined = function(obj) {
|
|
||||||
return obj === void 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Has own property?
|
|
||||||
_.has = function(obj, key) {
|
|
||||||
return hasOwnProperty.call(obj, key);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Utility Functions
|
|
||||||
// -----------------
|
|
||||||
|
|
||||||
// Run Underscore.js in *noConflict* mode, returning the `_` variable to its
|
|
||||||
// previous owner. Returns a reference to the Underscore object.
|
|
||||||
_.noConflict = function() {
|
|
||||||
root._ = previousUnderscore;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Keep the identity function around for default iterators.
|
|
||||||
_.identity = function(value) {
|
|
||||||
return value;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Run a function **n** times.
|
|
||||||
_.times = function (n, iterator, context) {
|
|
||||||
for (var i = 0; i < n; i++) iterator.call(context, i);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Escape a string for HTML interpolation.
|
|
||||||
_.escape = function(string) {
|
|
||||||
return (''+string).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''').replace(/\//g,'/');
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add your own custom functions to the Underscore object, ensuring that
|
|
||||||
// they're correctly added to the OOP wrapper as well.
|
|
||||||
_.mixin = function(obj) {
|
|
||||||
each(_.functions(obj), function(name){
|
|
||||||
addToWrapper(name, _[name] = obj[name]);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
// Generate a unique integer id (unique within the entire client session).
|
|
||||||
// Useful for temporary DOM ids.
|
|
||||||
var idCounter = 0;
|
|
||||||
_.uniqueId = function(prefix) {
|
|
||||||
var id = idCounter++;
|
|
||||||
return prefix ? prefix + id : id;
|
|
||||||
};
|
|
||||||
|
|
||||||
// By default, Underscore uses ERB-style template delimiters, change the
|
|
||||||
// following template settings to use alternative delimiters.
|
|
||||||
_.templateSettings = {
|
|
||||||
evaluate : /<%([\s\S]+?)%>/g,
|
|
||||||
interpolate : /<%=([\s\S]+?)%>/g,
|
|
||||||
escape : /<%-([\s\S]+?)%>/g
|
|
||||||
};
|
|
||||||
|
|
||||||
// When customizing `templateSettings`, if you don't want to define an
|
|
||||||
// interpolation, evaluation or escaping regex, we need one that is
|
|
||||||
// guaranteed not to match.
|
|
||||||
var noMatch = /.^/;
|
|
||||||
|
|
||||||
// Within an interpolation, evaluation, or escaping, remove HTML escaping
|
|
||||||
// that had been previously added.
|
|
||||||
var unescape = function(code) {
|
|
||||||
return code.replace(/\\\\/g, '\\').replace(/\\'/g, "'");
|
|
||||||
};
|
|
||||||
|
|
||||||
// JavaScript micro-templating, similar to John Resig's implementation.
|
|
||||||
// Underscore templating handles arbitrary delimiters, preserves whitespace,
|
|
||||||
// and correctly escapes quotes within interpolated code.
|
|
||||||
_.template = function(str, data) {
|
|
||||||
var c = _.templateSettings;
|
|
||||||
var tmpl = 'var __p=[],print=function(){__p.push.apply(__p,arguments);};' +
|
|
||||||
'with(obj||{}){__p.push(\'' +
|
|
||||||
str.replace(/\\/g, '\\\\')
|
|
||||||
.replace(/'/g, "\\'")
|
|
||||||
.replace(c.escape || noMatch, function(match, code) {
|
|
||||||
return "',_.escape(" + unescape(code) + "),'";
|
|
||||||
})
|
|
||||||
.replace(c.interpolate || noMatch, function(match, code) {
|
|
||||||
return "'," + unescape(code) + ",'";
|
|
||||||
})
|
|
||||||
.replace(c.evaluate || noMatch, function(match, code) {
|
|
||||||
return "');" + unescape(code).replace(/[\r\n\t]/g, ' ') + ";__p.push('";
|
|
||||||
})
|
|
||||||
.replace(/\r/g, '\\r')
|
|
||||||
.replace(/\n/g, '\\n')
|
|
||||||
.replace(/\t/g, '\\t')
|
|
||||||
+ "');}return __p.join('');";
|
|
||||||
var func = new Function('obj', '_', tmpl);
|
|
||||||
if (data) return func(data, _);
|
|
||||||
return function(data) {
|
|
||||||
return func.call(this, data, _);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add a "chain" function, which will delegate to the wrapper.
|
|
||||||
_.chain = function(obj) {
|
|
||||||
return _(obj).chain();
|
|
||||||
};
|
|
||||||
|
|
||||||
// The OOP Wrapper
|
|
||||||
// ---------------
|
|
||||||
|
|
||||||
// If Underscore is called as a function, it returns a wrapped object that
|
|
||||||
// can be used OO-style. This wrapper holds altered versions of all the
|
|
||||||
// underscore functions. Wrapped objects may be chained.
|
|
||||||
var wrapper = function(obj) { this._wrapped = obj; };
|
|
||||||
|
|
||||||
// Expose `wrapper.prototype` as `_.prototype`
|
|
||||||
_.prototype = wrapper.prototype;
|
|
||||||
|
|
||||||
// Helper function to continue chaining intermediate results.
|
|
||||||
var result = function(obj, chain) {
|
|
||||||
return chain ? _(obj).chain() : obj;
|
|
||||||
};
|
|
||||||
|
|
||||||
// A method to easily add functions to the OOP wrapper.
|
|
||||||
var addToWrapper = function(name, func) {
|
|
||||||
wrapper.prototype[name] = function() {
|
|
||||||
var args = slice.call(arguments);
|
|
||||||
unshift.call(args, this._wrapped);
|
|
||||||
return result(func.apply(_, args), this._chain);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add all of the Underscore functions to the wrapper object.
|
|
||||||
_.mixin(_);
|
|
||||||
|
|
||||||
// Add all mutator Array functions to the wrapper.
|
|
||||||
each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
|
|
||||||
var method = ArrayProto[name];
|
|
||||||
wrapper.prototype[name] = function() {
|
|
||||||
var wrapped = this._wrapped;
|
|
||||||
method.apply(wrapped, arguments);
|
|
||||||
var length = wrapped.length;
|
|
||||||
if ((name == 'shift' || name == 'splice') && length === 0) delete wrapped[0];
|
|
||||||
return result(wrapped, this._chain);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add all accessor Array functions to the wrapper.
|
|
||||||
each(['concat', 'join', 'slice'], function(name) {
|
|
||||||
var method = ArrayProto[name];
|
|
||||||
wrapper.prototype[name] = function() {
|
|
||||||
return result(method.apply(this._wrapped, arguments), this._chain);
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start chaining a wrapped Underscore object.
|
|
||||||
wrapper.prototype.chain = function() {
|
|
||||||
this._chain = true;
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Extracts the result from a wrapped and chained object.
|
|
||||||
wrapper.prototype.value = function() {
|
|
||||||
return this._wrapped;
|
|
||||||
};
|
|
||||||
|
|
||||||
}).call(this);
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
// Underscore.js 1.3.1
|
|
||||||
// (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
|
|
||||||
// Underscore is freely distributable under the MIT license.
|
|
||||||
// Portions of Underscore are inspired or borrowed from Prototype,
|
|
||||||
// Oliver Steele's Functional, and John Resig's Micro-Templating.
|
|
||||||
// For all details and documentation:
|
|
||||||
// http://documentcloud.github.com/underscore
|
|
||||||
(function(){function q(a,c,d){if(a===c)return a!==0||1/a==1/c;if(a==null||c==null)return a===c;if(a._chain)a=a._wrapped;if(c._chain)c=c._wrapped;if(a.isEqual&&b.isFunction(a.isEqual))return a.isEqual(c);if(c.isEqual&&b.isFunction(c.isEqual))return c.isEqual(a);var e=l.call(a);if(e!=l.call(c))return false;switch(e){case "[object String]":return a==String(c);case "[object Number]":return a!=+a?c!=+c:a==0?1/a==1/c:a==+c;case "[object Date]":case "[object Boolean]":return+a==+c;case "[object RegExp]":return a.source==
|
|
||||||
c.source&&a.global==c.global&&a.multiline==c.multiline&&a.ignoreCase==c.ignoreCase}if(typeof a!="object"||typeof c!="object")return false;for(var f=d.length;f--;)if(d[f]==a)return true;d.push(a);var f=0,g=true;if(e=="[object Array]"){if(f=a.length,g=f==c.length)for(;f--;)if(!(g=f in a==f in c&&q(a[f],c[f],d)))break}else{if("constructor"in a!="constructor"in c||a.constructor!=c.constructor)return false;for(var h in a)if(b.has(a,h)&&(f++,!(g=b.has(c,h)&&q(a[h],c[h],d))))break;if(g){for(h in c)if(b.has(c,
|
|
||||||
h)&&!f--)break;g=!f}}d.pop();return g}var r=this,G=r._,n={},k=Array.prototype,o=Object.prototype,i=k.slice,H=k.unshift,l=o.toString,I=o.hasOwnProperty,w=k.forEach,x=k.map,y=k.reduce,z=k.reduceRight,A=k.filter,B=k.every,C=k.some,p=k.indexOf,D=k.lastIndexOf,o=Array.isArray,J=Object.keys,s=Function.prototype.bind,b=function(a){return new m(a)};if(typeof exports!=="undefined"){if(typeof module!=="undefined"&&module.exports)exports=module.exports=b;exports._=b}else r._=b;b.VERSION="1.3.1";var j=b.each=
|
|
||||||
b.forEach=function(a,c,d){if(a!=null)if(w&&a.forEach===w)a.forEach(c,d);else if(a.length===+a.length)for(var e=0,f=a.length;e<f;e++){if(e in a&&c.call(d,a[e],e,a)===n)break}else for(e in a)if(b.has(a,e)&&c.call(d,a[e],e,a)===n)break};b.map=b.collect=function(a,c,b){var e=[];if(a==null)return e;if(x&&a.map===x)return a.map(c,b);j(a,function(a,g,h){e[e.length]=c.call(b,a,g,h)});if(a.length===+a.length)e.length=a.length;return e};b.reduce=b.foldl=b.inject=function(a,c,d,e){var f=arguments.length>2;a==
|
|
||||||
null&&(a=[]);if(y&&a.reduce===y)return e&&(c=b.bind(c,e)),f?a.reduce(c,d):a.reduce(c);j(a,function(a,b,i){f?d=c.call(e,d,a,b,i):(d=a,f=true)});if(!f)throw new TypeError("Reduce of empty array with no initial value");return d};b.reduceRight=b.foldr=function(a,c,d,e){var f=arguments.length>2;a==null&&(a=[]);if(z&&a.reduceRight===z)return e&&(c=b.bind(c,e)),f?a.reduceRight(c,d):a.reduceRight(c);var g=b.toArray(a).reverse();e&&!f&&(c=b.bind(c,e));return f?b.reduce(g,c,d,e):b.reduce(g,c)};b.find=b.detect=
|
|
||||||
function(a,c,b){var e;E(a,function(a,g,h){if(c.call(b,a,g,h))return e=a,true});return e};b.filter=b.select=function(a,c,b){var e=[];if(a==null)return e;if(A&&a.filter===A)return a.filter(c,b);j(a,function(a,g,h){c.call(b,a,g,h)&&(e[e.length]=a)});return e};b.reject=function(a,c,b){var e=[];if(a==null)return e;j(a,function(a,g,h){c.call(b,a,g,h)||(e[e.length]=a)});return e};b.every=b.all=function(a,c,b){var e=true;if(a==null)return e;if(B&&a.every===B)return a.every(c,b);j(a,function(a,g,h){if(!(e=
|
|
||||||
e&&c.call(b,a,g,h)))return n});return e};var E=b.some=b.any=function(a,c,d){c||(c=b.identity);var e=false;if(a==null)return e;if(C&&a.some===C)return a.some(c,d);j(a,function(a,b,h){if(e||(e=c.call(d,a,b,h)))return n});return!!e};b.include=b.contains=function(a,c){var b=false;if(a==null)return b;return p&&a.indexOf===p?a.indexOf(c)!=-1:b=E(a,function(a){return a===c})};b.invoke=function(a,c){var d=i.call(arguments,2);return b.map(a,function(a){return(b.isFunction(c)?c||a:a[c]).apply(a,d)})};b.pluck=
|
|
||||||
function(a,c){return b.map(a,function(a){return a[c]})};b.max=function(a,c,d){if(!c&&b.isArray(a))return Math.max.apply(Math,a);if(!c&&b.isEmpty(a))return-Infinity;var e={computed:-Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b>=e.computed&&(e={value:a,computed:b})});return e.value};b.min=function(a,c,d){if(!c&&b.isArray(a))return Math.min.apply(Math,a);if(!c&&b.isEmpty(a))return Infinity;var e={computed:Infinity};j(a,function(a,b,h){b=c?c.call(d,a,b,h):a;b<e.computed&&(e={value:a,computed:b})});
|
|
||||||
return e.value};b.shuffle=function(a){var b=[],d;j(a,function(a,f){f==0?b[0]=a:(d=Math.floor(Math.random()*(f+1)),b[f]=b[d],b[d]=a)});return b};b.sortBy=function(a,c,d){return b.pluck(b.map(a,function(a,b,g){return{value:a,criteria:c.call(d,a,b,g)}}).sort(function(a,b){var c=a.criteria,d=b.criteria;return c<d?-1:c>d?1:0}),"value")};b.groupBy=function(a,c){var d={},e=b.isFunction(c)?c:function(a){return a[c]};j(a,function(a,b){var c=e(a,b);(d[c]||(d[c]=[])).push(a)});return d};b.sortedIndex=function(a,
|
|
||||||
c,d){d||(d=b.identity);for(var e=0,f=a.length;e<f;){var g=e+f>>1;d(a[g])<d(c)?e=g+1:f=g}return e};b.toArray=function(a){return!a?[]:a.toArray?a.toArray():b.isArray(a)?i.call(a):b.isArguments(a)?i.call(a):b.values(a)};b.size=function(a){return b.toArray(a).length};b.first=b.head=function(a,b,d){return b!=null&&!d?i.call(a,0,b):a[0]};b.initial=function(a,b,d){return i.call(a,0,a.length-(b==null||d?1:b))};b.last=function(a,b,d){return b!=null&&!d?i.call(a,Math.max(a.length-b,0)):a[a.length-1]};b.rest=
|
|
||||||
b.tail=function(a,b,d){return i.call(a,b==null||d?1:b)};b.compact=function(a){return b.filter(a,function(a){return!!a})};b.flatten=function(a,c){return b.reduce(a,function(a,e){if(b.isArray(e))return a.concat(c?e:b.flatten(e));a[a.length]=e;return a},[])};b.without=function(a){return b.difference(a,i.call(arguments,1))};b.uniq=b.unique=function(a,c,d){var d=d?b.map(a,d):a,e=[];b.reduce(d,function(d,g,h){if(0==h||(c===true?b.last(d)!=g:!b.include(d,g)))d[d.length]=g,e[e.length]=a[h];return d},[]);
|
|
||||||
return e};b.union=function(){return b.uniq(b.flatten(arguments,true))};b.intersection=b.intersect=function(a){var c=i.call(arguments,1);return b.filter(b.uniq(a),function(a){return b.every(c,function(c){return b.indexOf(c,a)>=0})})};b.difference=function(a){var c=b.flatten(i.call(arguments,1));return b.filter(a,function(a){return!b.include(c,a)})};b.zip=function(){for(var a=i.call(arguments),c=b.max(b.pluck(a,"length")),d=Array(c),e=0;e<c;e++)d[e]=b.pluck(a,""+e);return d};b.indexOf=function(a,c,
|
|
||||||
d){if(a==null)return-1;var e;if(d)return d=b.sortedIndex(a,c),a[d]===c?d:-1;if(p&&a.indexOf===p)return a.indexOf(c);for(d=0,e=a.length;d<e;d++)if(d in a&&a[d]===c)return d;return-1};b.lastIndexOf=function(a,b){if(a==null)return-1;if(D&&a.lastIndexOf===D)return a.lastIndexOf(b);for(var d=a.length;d--;)if(d in a&&a[d]===b)return d;return-1};b.range=function(a,b,d){arguments.length<=1&&(b=a||0,a=0);for(var d=arguments[2]||1,e=Math.max(Math.ceil((b-a)/d),0),f=0,g=Array(e);f<e;)g[f++]=a,a+=d;return g};
|
|
||||||
var F=function(){};b.bind=function(a,c){var d,e;if(a.bind===s&&s)return s.apply(a,i.call(arguments,1));if(!b.isFunction(a))throw new TypeError;e=i.call(arguments,2);return d=function(){if(!(this instanceof d))return a.apply(c,e.concat(i.call(arguments)));F.prototype=a.prototype;var b=new F,g=a.apply(b,e.concat(i.call(arguments)));return Object(g)===g?g:b}};b.bindAll=function(a){var c=i.call(arguments,1);c.length==0&&(c=b.functions(a));j(c,function(c){a[c]=b.bind(a[c],a)});return a};b.memoize=function(a,
|
|
||||||
c){var d={};c||(c=b.identity);return function(){var e=c.apply(this,arguments);return b.has(d,e)?d[e]:d[e]=a.apply(this,arguments)}};b.delay=function(a,b){var d=i.call(arguments,2);return setTimeout(function(){return a.apply(a,d)},b)};b.defer=function(a){return b.delay.apply(b,[a,1].concat(i.call(arguments,1)))};b.throttle=function(a,c){var d,e,f,g,h,i=b.debounce(function(){h=g=false},c);return function(){d=this;e=arguments;var b;f||(f=setTimeout(function(){f=null;h&&a.apply(d,e);i()},c));g?h=true:
|
|
||||||
a.apply(d,e);i();g=true}};b.debounce=function(a,b){var d;return function(){var e=this,f=arguments;clearTimeout(d);d=setTimeout(function(){d=null;a.apply(e,f)},b)}};b.once=function(a){var b=false,d;return function(){if(b)return d;b=true;return d=a.apply(this,arguments)}};b.wrap=function(a,b){return function(){var d=[a].concat(i.call(arguments,0));return b.apply(this,d)}};b.compose=function(){var a=arguments;return function(){for(var b=arguments,d=a.length-1;d>=0;d--)b=[a[d].apply(this,b)];return b[0]}};
|
|
||||||
b.after=function(a,b){return a<=0?b():function(){if(--a<1)return b.apply(this,arguments)}};b.keys=J||function(a){if(a!==Object(a))throw new TypeError("Invalid object");var c=[],d;for(d in a)b.has(a,d)&&(c[c.length]=d);return c};b.values=function(a){return b.map(a,b.identity)};b.functions=b.methods=function(a){var c=[],d;for(d in a)b.isFunction(a[d])&&c.push(d);return c.sort()};b.extend=function(a){j(i.call(arguments,1),function(b){for(var d in b)a[d]=b[d]});return a};b.defaults=function(a){j(i.call(arguments,
|
|
||||||
1),function(b){for(var d in b)a[d]==null&&(a[d]=b[d])});return a};b.clone=function(a){return!b.isObject(a)?a:b.isArray(a)?a.slice():b.extend({},a)};b.tap=function(a,b){b(a);return a};b.isEqual=function(a,b){return q(a,b,[])};b.isEmpty=function(a){if(b.isArray(a)||b.isString(a))return a.length===0;for(var c in a)if(b.has(a,c))return false;return true};b.isElement=function(a){return!!(a&&a.nodeType==1)};b.isArray=o||function(a){return l.call(a)=="[object Array]"};b.isObject=function(a){return a===Object(a)};
|
|
||||||
b.isArguments=function(a){return l.call(a)=="[object Arguments]"};if(!b.isArguments(arguments))b.isArguments=function(a){return!(!a||!b.has(a,"callee"))};b.isFunction=function(a){return l.call(a)=="[object Function]"};b.isString=function(a){return l.call(a)=="[object String]"};b.isNumber=function(a){return l.call(a)=="[object Number]"};b.isNaN=function(a){return a!==a};b.isBoolean=function(a){return a===true||a===false||l.call(a)=="[object Boolean]"};b.isDate=function(a){return l.call(a)=="[object Date]"};
|
|
||||||
b.isRegExp=function(a){return l.call(a)=="[object RegExp]"};b.isNull=function(a){return a===null};b.isUndefined=function(a){return a===void 0};b.has=function(a,b){return I.call(a,b)};b.noConflict=function(){r._=G;return this};b.identity=function(a){return a};b.times=function(a,b,d){for(var e=0;e<a;e++)b.call(d,e)};b.escape=function(a){return(""+a).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'").replace(/\//g,"/")};b.mixin=function(a){j(b.functions(a),
|
|
||||||
function(c){K(c,b[c]=a[c])})};var L=0;b.uniqueId=function(a){var b=L++;return a?a+b:b};b.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var t=/.^/,u=function(a){return a.replace(/\\\\/g,"\\").replace(/\\'/g,"'")};b.template=function(a,c){var d=b.templateSettings,d="var __p=[],print=function(){__p.push.apply(__p,arguments);};with(obj||{}){__p.push('"+a.replace(/\\/g,"\\\\").replace(/'/g,"\\'").replace(d.escape||t,function(a,b){return"',_.escape("+
|
|
||||||
u(b)+"),'"}).replace(d.interpolate||t,function(a,b){return"',"+u(b)+",'"}).replace(d.evaluate||t,function(a,b){return"');"+u(b).replace(/[\r\n\t]/g," ")+";__p.push('"}).replace(/\r/g,"\\r").replace(/\n/g,"\\n").replace(/\t/g,"\\t")+"');}return __p.join('');",e=new Function("obj","_",d);return c?e(c,b):function(a){return e.call(this,a,b)}};b.chain=function(a){return b(a).chain()};var m=function(a){this._wrapped=a};b.prototype=m.prototype;var v=function(a,c){return c?b(a).chain():a},K=function(a,c){m.prototype[a]=
|
|
||||||
function(){var a=i.call(arguments);H.call(a,this._wrapped);return v(c.apply(b,a),this._chain)}};b.mixin(b);j("pop,push,reverse,shift,sort,splice,unshift".split(","),function(a){var b=k[a];m.prototype[a]=function(){var d=this._wrapped;b.apply(d,arguments);var e=d.length;(a=="shift"||a=="splice")&&e===0&&delete d[0];return v(d,this._chain)}});j(["concat","join","slice"],function(a){var b=k[a];m.prototype[a]=function(){return v(b.apply(this._wrapped,arguments),this._chain)}});m.prototype.chain=function(){this._chain=
|
|
||||||
true;return this};m.prototype.value=function(){return this._wrapped}}).call(this);
|
|
||||||
|
Before Width: | Height: | Size: 214 B |
|
Before Width: | Height: | Size: 203 B |
@@ -1,808 +0,0 @@
|
|||||||
/*
|
|
||||||
* websupport.js
|
|
||||||
* ~~~~~~~~~~~~~
|
|
||||||
*
|
|
||||||
* sphinx.websupport utilities for all documentation.
|
|
||||||
*
|
|
||||||
* :copyright: Copyright 2007-2016 by the Sphinx team, see AUTHORS.
|
|
||||||
* :license: BSD, see LICENSE for details.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
(function($) {
|
|
||||||
$.fn.autogrow = function() {
|
|
||||||
return this.each(function() {
|
|
||||||
var textarea = this;
|
|
||||||
|
|
||||||
$.fn.autogrow.resize(textarea);
|
|
||||||
|
|
||||||
$(textarea)
|
|
||||||
.focus(function() {
|
|
||||||
textarea.interval = setInterval(function() {
|
|
||||||
$.fn.autogrow.resize(textarea);
|
|
||||||
}, 500);
|
|
||||||
})
|
|
||||||
.blur(function() {
|
|
||||||
clearInterval(textarea.interval);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
$.fn.autogrow.resize = function(textarea) {
|
|
||||||
var lineHeight = parseInt($(textarea).css('line-height'), 10);
|
|
||||||
var lines = textarea.value.split('\n');
|
|
||||||
var columns = textarea.cols;
|
|
||||||
var lineCount = 0;
|
|
||||||
$.each(lines, function() {
|
|
||||||
lineCount += Math.ceil(this.length / columns) || 1;
|
|
||||||
});
|
|
||||||
var height = lineHeight * (lineCount + 1);
|
|
||||||
$(textarea).css('height', height);
|
|
||||||
};
|
|
||||||
})(jQuery);
|
|
||||||
|
|
||||||
(function($) {
|
|
||||||
var comp, by;
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
initEvents();
|
|
||||||
initComparator();
|
|
||||||
}
|
|
||||||
|
|
||||||
function initEvents() {
|
|
||||||
$(document).on("click", 'a.comment-close', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
hide($(this).attr('id').substring(2));
|
|
||||||
});
|
|
||||||
$(document).on("click", 'a.vote', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
handleVote($(this));
|
|
||||||
});
|
|
||||||
$(document).on("click", 'a.reply', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
openReply($(this).attr('id').substring(2));
|
|
||||||
});
|
|
||||||
$(document).on("click", 'a.close-reply', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
closeReply($(this).attr('id').substring(2));
|
|
||||||
});
|
|
||||||
$(document).on("click", 'a.sort-option', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
handleReSort($(this));
|
|
||||||
});
|
|
||||||
$(document).on("click", 'a.show-proposal', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
showProposal($(this).attr('id').substring(2));
|
|
||||||
});
|
|
||||||
$(document).on("click", 'a.hide-proposal', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
hideProposal($(this).attr('id').substring(2));
|
|
||||||
});
|
|
||||||
$(document).on("click", 'a.show-propose-change', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
showProposeChange($(this).attr('id').substring(2));
|
|
||||||
});
|
|
||||||
$(document).on("click", 'a.hide-propose-change', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
hideProposeChange($(this).attr('id').substring(2));
|
|
||||||
});
|
|
||||||
$(document).on("click", 'a.accept-comment', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
acceptComment($(this).attr('id').substring(2));
|
|
||||||
});
|
|
||||||
$(document).on("click", 'a.delete-comment', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
deleteComment($(this).attr('id').substring(2));
|
|
||||||
});
|
|
||||||
$(document).on("click", 'a.comment-markup', function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
toggleCommentMarkupBox($(this).attr('id').substring(2));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set comp, which is a comparator function used for sorting and
|
|
||||||
* inserting comments into the list.
|
|
||||||
*/
|
|
||||||
function setComparator() {
|
|
||||||
// If the first three letters are "asc", sort in ascending order
|
|
||||||
// and remove the prefix.
|
|
||||||
if (by.substring(0,3) == 'asc') {
|
|
||||||
var i = by.substring(3);
|
|
||||||
comp = function(a, b) { return a[i] - b[i]; };
|
|
||||||
} else {
|
|
||||||
// Otherwise sort in descending order.
|
|
||||||
comp = function(a, b) { return b[by] - a[by]; };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset link styles and format the selected sort option.
|
|
||||||
$('a.sel').attr('href', '#').removeClass('sel');
|
|
||||||
$('a.by' + by).removeAttr('href').addClass('sel');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a comp function. If the user has preferences stored in
|
|
||||||
* the sortBy cookie, use those, otherwise use the default.
|
|
||||||
*/
|
|
||||||
function initComparator() {
|
|
||||||
by = 'rating'; // Default to sort by rating.
|
|
||||||
// If the sortBy cookie is set, use that instead.
|
|
||||||
if (document.cookie.length > 0) {
|
|
||||||
var start = document.cookie.indexOf('sortBy=');
|
|
||||||
if (start != -1) {
|
|
||||||
start = start + 7;
|
|
||||||
var end = document.cookie.indexOf(";", start);
|
|
||||||
if (end == -1) {
|
|
||||||
end = document.cookie.length;
|
|
||||||
by = unescape(document.cookie.substring(start, end));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setComparator();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Show a comment div.
|
|
||||||
*/
|
|
||||||
function show(id) {
|
|
||||||
$('#ao' + id).hide();
|
|
||||||
$('#ah' + id).show();
|
|
||||||
var context = $.extend({id: id}, opts);
|
|
||||||
var popup = $(renderTemplate(popupTemplate, context)).hide();
|
|
||||||
popup.find('textarea[name="proposal"]').hide();
|
|
||||||
popup.find('a.by' + by).addClass('sel');
|
|
||||||
var form = popup.find('#cf' + id);
|
|
||||||
form.submit(function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
addComment(form);
|
|
||||||
});
|
|
||||||
$('#s' + id).after(popup);
|
|
||||||
popup.slideDown('fast', function() {
|
|
||||||
getComments(id);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Hide a comment div.
|
|
||||||
*/
|
|
||||||
function hide(id) {
|
|
||||||
$('#ah' + id).hide();
|
|
||||||
$('#ao' + id).show();
|
|
||||||
var div = $('#sc' + id);
|
|
||||||
div.slideUp('fast', function() {
|
|
||||||
div.remove();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Perform an ajax request to get comments for a node
|
|
||||||
* and insert the comments into the comments tree.
|
|
||||||
*/
|
|
||||||
function getComments(id) {
|
|
||||||
$.ajax({
|
|
||||||
type: 'GET',
|
|
||||||
url: opts.getCommentsURL,
|
|
||||||
data: {node: id},
|
|
||||||
success: function(data, textStatus, request) {
|
|
||||||
var ul = $('#cl' + id);
|
|
||||||
var speed = 100;
|
|
||||||
$('#cf' + id)
|
|
||||||
.find('textarea[name="proposal"]')
|
|
||||||
.data('source', data.source);
|
|
||||||
|
|
||||||
if (data.comments.length === 0) {
|
|
||||||
ul.html('<li>No comments yet.</li>');
|
|
||||||
ul.data('empty', true);
|
|
||||||
} else {
|
|
||||||
// If there are comments, sort them and put them in the list.
|
|
||||||
var comments = sortComments(data.comments);
|
|
||||||
speed = data.comments.length * 100;
|
|
||||||
appendComments(comments, ul);
|
|
||||||
ul.data('empty', false);
|
|
||||||
}
|
|
||||||
$('#cn' + id).slideUp(speed + 200);
|
|
||||||
ul.slideDown(speed);
|
|
||||||
},
|
|
||||||
error: function(request, textStatus, error) {
|
|
||||||
showError('Oops, there was a problem retrieving the comments.');
|
|
||||||
},
|
|
||||||
dataType: 'json'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a comment via ajax and insert the comment into the comment tree.
|
|
||||||
*/
|
|
||||||
function addComment(form) {
|
|
||||||
var node_id = form.find('input[name="node"]').val();
|
|
||||||
var parent_id = form.find('input[name="parent"]').val();
|
|
||||||
var text = form.find('textarea[name="comment"]').val();
|
|
||||||
var proposal = form.find('textarea[name="proposal"]').val();
|
|
||||||
|
|
||||||
if (text == '') {
|
|
||||||
showError('Please enter a comment.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Disable the form that is being submitted.
|
|
||||||
form.find('textarea,input').attr('disabled', 'disabled');
|
|
||||||
|
|
||||||
// Send the comment to the server.
|
|
||||||
$.ajax({
|
|
||||||
type: "POST",
|
|
||||||
url: opts.addCommentURL,
|
|
||||||
dataType: 'json',
|
|
||||||
data: {
|
|
||||||
node: node_id,
|
|
||||||
parent: parent_id,
|
|
||||||
text: text,
|
|
||||||
proposal: proposal
|
|
||||||
},
|
|
||||||
success: function(data, textStatus, error) {
|
|
||||||
// Reset the form.
|
|
||||||
if (node_id) {
|
|
||||||
hideProposeChange(node_id);
|
|
||||||
}
|
|
||||||
form.find('textarea')
|
|
||||||
.val('')
|
|
||||||
.add(form.find('input'))
|
|
||||||
.removeAttr('disabled');
|
|
||||||
var ul = $('#cl' + (node_id || parent_id));
|
|
||||||
if (ul.data('empty')) {
|
|
||||||
$(ul).empty();
|
|
||||||
ul.data('empty', false);
|
|
||||||
}
|
|
||||||
insertComment(data.comment);
|
|
||||||
var ao = $('#ao' + node_id);
|
|
||||||
ao.find('img').attr({'src': opts.commentBrightImage});
|
|
||||||
if (node_id) {
|
|
||||||
// if this was a "root" comment, remove the commenting box
|
|
||||||
// (the user can get it back by reopening the comment popup)
|
|
||||||
$('#ca' + node_id).slideUp();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
error: function(request, textStatus, error) {
|
|
||||||
form.find('textarea,input').removeAttr('disabled');
|
|
||||||
showError('Oops, there was a problem adding the comment.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively append comments to the main comment list and children
|
|
||||||
* lists, creating the comment tree.
|
|
||||||
*/
|
|
||||||
function appendComments(comments, ul) {
|
|
||||||
$.each(comments, function() {
|
|
||||||
var div = createCommentDiv(this);
|
|
||||||
ul.append($(document.createElement('li')).html(div));
|
|
||||||
appendComments(this.children, div.find('ul.comment-children'));
|
|
||||||
// To avoid stagnating data, don't store the comments children in data.
|
|
||||||
this.children = null;
|
|
||||||
div.data('comment', this);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* After adding a new comment, it must be inserted in the correct
|
|
||||||
* location in the comment tree.
|
|
||||||
*/
|
|
||||||
function insertComment(comment) {
|
|
||||||
var div = createCommentDiv(comment);
|
|
||||||
|
|
||||||
// To avoid stagnating data, don't store the comments children in data.
|
|
||||||
comment.children = null;
|
|
||||||
div.data('comment', comment);
|
|
||||||
|
|
||||||
var ul = $('#cl' + (comment.node || comment.parent));
|
|
||||||
var siblings = getChildren(ul);
|
|
||||||
|
|
||||||
var li = $(document.createElement('li'));
|
|
||||||
li.hide();
|
|
||||||
|
|
||||||
// Determine where in the parents children list to insert this comment.
|
|
||||||
for(i=0; i < siblings.length; i++) {
|
|
||||||
if (comp(comment, siblings[i]) <= 0) {
|
|
||||||
$('#cd' + siblings[i].id)
|
|
||||||
.parent()
|
|
||||||
.before(li.html(div));
|
|
||||||
li.slideDown('fast');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we get here, this comment rates lower than all the others,
|
|
||||||
// or it is the only comment in the list.
|
|
||||||
ul.append(li.html(div));
|
|
||||||
li.slideDown('fast');
|
|
||||||
}
|
|
||||||
|
|
||||||
function acceptComment(id) {
|
|
||||||
$.ajax({
|
|
||||||
type: 'POST',
|
|
||||||
url: opts.acceptCommentURL,
|
|
||||||
data: {id: id},
|
|
||||||
success: function(data, textStatus, request) {
|
|
||||||
$('#cm' + id).fadeOut('fast');
|
|
||||||
$('#cd' + id).removeClass('moderate');
|
|
||||||
},
|
|
||||||
error: function(request, textStatus, error) {
|
|
||||||
showError('Oops, there was a problem accepting the comment.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteComment(id) {
|
|
||||||
$.ajax({
|
|
||||||
type: 'POST',
|
|
||||||
url: opts.deleteCommentURL,
|
|
||||||
data: {id: id},
|
|
||||||
success: function(data, textStatus, request) {
|
|
||||||
var div = $('#cd' + id);
|
|
||||||
if (data == 'delete') {
|
|
||||||
// Moderator mode: remove the comment and all children immediately
|
|
||||||
div.slideUp('fast', function() {
|
|
||||||
div.remove();
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// User mode: only mark the comment as deleted
|
|
||||||
div
|
|
||||||
.find('span.user-id:first')
|
|
||||||
.text('[deleted]').end()
|
|
||||||
.find('div.comment-text:first')
|
|
||||||
.text('[deleted]').end()
|
|
||||||
.find('#cm' + id + ', #dc' + id + ', #ac' + id + ', #rc' + id +
|
|
||||||
', #sp' + id + ', #hp' + id + ', #cr' + id + ', #rl' + id)
|
|
||||||
.remove();
|
|
||||||
var comment = div.data('comment');
|
|
||||||
comment.username = '[deleted]';
|
|
||||||
comment.text = '[deleted]';
|
|
||||||
div.data('comment', comment);
|
|
||||||
},
|
|
||||||
error: function(request, textStatus, error) {
|
|
||||||
showError('Oops, there was a problem deleting the comment.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function showProposal(id) {
|
|
||||||
$('#sp' + id).hide();
|
|
||||||
$('#hp' + id).show();
|
|
||||||
$('#pr' + id).slideDown('fast');
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideProposal(id) {
|
|
||||||
$('#hp' + id).hide();
|
|
||||||
$('#sp' + id).show();
|
|
||||||
$('#pr' + id).slideUp('fast');
|
|
||||||
}
|
|
||||||
|
|
||||||
function showProposeChange(id) {
|
|
||||||
$('#pc' + id).hide();
|
|
||||||
$('#hc' + id).show();
|
|
||||||
var textarea = $('#pt' + id);
|
|
||||||
textarea.val(textarea.data('source'));
|
|
||||||
$.fn.autogrow.resize(textarea[0]);
|
|
||||||
textarea.slideDown('fast');
|
|
||||||
}
|
|
||||||
|
|
||||||
function hideProposeChange(id) {
|
|
||||||
$('#hc' + id).hide();
|
|
||||||
$('#pc' + id).show();
|
|
||||||
var textarea = $('#pt' + id);
|
|
||||||
textarea.val('').removeAttr('disabled');
|
|
||||||
textarea.slideUp('fast');
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleCommentMarkupBox(id) {
|
|
||||||
$('#mb' + id).toggle();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Handle when the user clicks on a sort by link. */
|
|
||||||
function handleReSort(link) {
|
|
||||||
var classes = link.attr('class').split(/\s+/);
|
|
||||||
for (var i=0; i<classes.length; i++) {
|
|
||||||
if (classes[i] != 'sort-option') {
|
|
||||||
by = classes[i].substring(2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setComparator();
|
|
||||||
// Save/update the sortBy cookie.
|
|
||||||
var expiration = new Date();
|
|
||||||
expiration.setDate(expiration.getDate() + 365);
|
|
||||||
document.cookie= 'sortBy=' + escape(by) +
|
|
||||||
';expires=' + expiration.toUTCString();
|
|
||||||
$('ul.comment-ul').each(function(index, ul) {
|
|
||||||
var comments = getChildren($(ul), true);
|
|
||||||
comments = sortComments(comments);
|
|
||||||
appendComments(comments, $(ul).empty());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Function to process a vote when a user clicks an arrow.
|
|
||||||
*/
|
|
||||||
function handleVote(link) {
|
|
||||||
if (!opts.voting) {
|
|
||||||
showError("You'll need to login to vote.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var id = link.attr('id');
|
|
||||||
if (!id) {
|
|
||||||
// Didn't click on one of the voting arrows.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// If it is an unvote, the new vote value is 0,
|
|
||||||
// Otherwise it's 1 for an upvote, or -1 for a downvote.
|
|
||||||
var value = 0;
|
|
||||||
if (id.charAt(1) != 'u') {
|
|
||||||
value = id.charAt(0) == 'u' ? 1 : -1;
|
|
||||||
}
|
|
||||||
// The data to be sent to the server.
|
|
||||||
var d = {
|
|
||||||
comment_id: id.substring(2),
|
|
||||||
value: value
|
|
||||||
};
|
|
||||||
|
|
||||||
// Swap the vote and unvote links.
|
|
||||||
link.hide();
|
|
||||||
$('#' + id.charAt(0) + (id.charAt(1) == 'u' ? 'v' : 'u') + d.comment_id)
|
|
||||||
.show();
|
|
||||||
|
|
||||||
// The div the comment is displayed in.
|
|
||||||
var div = $('div#cd' + d.comment_id);
|
|
||||||
var data = div.data('comment');
|
|
||||||
|
|
||||||
// If this is not an unvote, and the other vote arrow has
|
|
||||||
// already been pressed, unpress it.
|
|
||||||
if ((d.value !== 0) && (data.vote === d.value * -1)) {
|
|
||||||
$('#' + (d.value == 1 ? 'd' : 'u') + 'u' + d.comment_id).hide();
|
|
||||||
$('#' + (d.value == 1 ? 'd' : 'u') + 'v' + d.comment_id).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the comments rating in the local data.
|
|
||||||
data.rating += (data.vote === 0) ? d.value : (d.value - data.vote);
|
|
||||||
data.vote = d.value;
|
|
||||||
div.data('comment', data);
|
|
||||||
|
|
||||||
// Change the rating text.
|
|
||||||
div.find('.rating:first')
|
|
||||||
.text(data.rating + ' point' + (data.rating == 1 ? '' : 's'));
|
|
||||||
|
|
||||||
// Send the vote information to the server.
|
|
||||||
$.ajax({
|
|
||||||
type: "POST",
|
|
||||||
url: opts.processVoteURL,
|
|
||||||
data: d,
|
|
||||||
error: function(request, textStatus, error) {
|
|
||||||
showError('Oops, there was a problem casting that vote.');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Open a reply form used to reply to an existing comment.
|
|
||||||
*/
|
|
||||||
function openReply(id) {
|
|
||||||
// Swap out the reply link for the hide link
|
|
||||||
$('#rl' + id).hide();
|
|
||||||
$('#cr' + id).show();
|
|
||||||
|
|
||||||
// Add the reply li to the children ul.
|
|
||||||
var div = $(renderTemplate(replyTemplate, {id: id})).hide();
|
|
||||||
$('#cl' + id)
|
|
||||||
.prepend(div)
|
|
||||||
// Setup the submit handler for the reply form.
|
|
||||||
.find('#rf' + id)
|
|
||||||
.submit(function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
addComment($('#rf' + id));
|
|
||||||
closeReply(id);
|
|
||||||
})
|
|
||||||
.find('input[type=button]')
|
|
||||||
.click(function() {
|
|
||||||
closeReply(id);
|
|
||||||
});
|
|
||||||
div.slideDown('fast', function() {
|
|
||||||
$('#rf' + id).find('textarea').focus();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Close the reply form opened with openReply.
|
|
||||||
*/
|
|
||||||
function closeReply(id) {
|
|
||||||
// Remove the reply div from the DOM.
|
|
||||||
$('#rd' + id).slideUp('fast', function() {
|
|
||||||
$(this).remove();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Swap out the hide link for the reply link
|
|
||||||
$('#cr' + id).hide();
|
|
||||||
$('#rl' + id).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Recursively sort a tree of comments using the comp comparator.
|
|
||||||
*/
|
|
||||||
function sortComments(comments) {
|
|
||||||
comments.sort(comp);
|
|
||||||
$.each(comments, function() {
|
|
||||||
this.children = sortComments(this.children);
|
|
||||||
});
|
|
||||||
return comments;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the children comments from a ul. If recursive is true,
|
|
||||||
* recursively include childrens' children.
|
|
||||||
*/
|
|
||||||
function getChildren(ul, recursive) {
|
|
||||||
var children = [];
|
|
||||||
ul.children().children("[id^='cd']")
|
|
||||||
.each(function() {
|
|
||||||
var comment = $(this).data('comment');
|
|
||||||
if (recursive)
|
|
||||||
comment.children = getChildren($(this).find('#cl' + comment.id), true);
|
|
||||||
children.push(comment);
|
|
||||||
});
|
|
||||||
return children;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Create a div to display a comment in. */
|
|
||||||
function createCommentDiv(comment) {
|
|
||||||
if (!comment.displayed && !opts.moderator) {
|
|
||||||
return $('<div class="moderate">Thank you! Your comment will show up '
|
|
||||||
+ 'once it is has been approved by a moderator.</div>');
|
|
||||||
}
|
|
||||||
// Prettify the comment rating.
|
|
||||||
comment.pretty_rating = comment.rating + ' point' +
|
|
||||||
(comment.rating == 1 ? '' : 's');
|
|
||||||
// Make a class (for displaying not yet moderated comments differently)
|
|
||||||
comment.css_class = comment.displayed ? '' : ' moderate';
|
|
||||||
// Create a div for this comment.
|
|
||||||
var context = $.extend({}, opts, comment);
|
|
||||||
var div = $(renderTemplate(commentTemplate, context));
|
|
||||||
|
|
||||||
// If the user has voted on this comment, highlight the correct arrow.
|
|
||||||
if (comment.vote) {
|
|
||||||
var direction = (comment.vote == 1) ? 'u' : 'd';
|
|
||||||
div.find('#' + direction + 'v' + comment.id).hide();
|
|
||||||
div.find('#' + direction + 'u' + comment.id).show();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (opts.moderator || comment.text != '[deleted]') {
|
|
||||||
div.find('a.reply').show();
|
|
||||||
if (comment.proposal_diff)
|
|
||||||
div.find('#sp' + comment.id).show();
|
|
||||||
if (opts.moderator && !comment.displayed)
|
|
||||||
div.find('#cm' + comment.id).show();
|
|
||||||
if (opts.moderator || (opts.username == comment.username))
|
|
||||||
div.find('#dc' + comment.id).show();
|
|
||||||
}
|
|
||||||
return div;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A simple template renderer. Placeholders such as <%id%> are replaced
|
|
||||||
* by context['id'] with items being escaped. Placeholders such as <#id#>
|
|
||||||
* are not escaped.
|
|
||||||
*/
|
|
||||||
function renderTemplate(template, context) {
|
|
||||||
var esc = $(document.createElement('div'));
|
|
||||||
|
|
||||||
function handle(ph, escape) {
|
|
||||||
var cur = context;
|
|
||||||
$.each(ph.split('.'), function() {
|
|
||||||
cur = cur[this];
|
|
||||||
});
|
|
||||||
return escape ? esc.text(cur || "").html() : cur;
|
|
||||||
}
|
|
||||||
|
|
||||||
return template.replace(/<([%#])([\w\.]*)\1>/g, function() {
|
|
||||||
return handle(arguments[2], arguments[1] == '%' ? true : false);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Flash an error message briefly. */
|
|
||||||
function showError(message) {
|
|
||||||
$(document.createElement('div')).attr({'class': 'popup-error'})
|
|
||||||
.append($(document.createElement('div'))
|
|
||||||
.attr({'class': 'error-message'}).text(message))
|
|
||||||
.appendTo('body')
|
|
||||||
.fadeIn("slow")
|
|
||||||
.delay(2000)
|
|
||||||
.fadeOut("slow");
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Add a link the user uses to open the comments popup. */
|
|
||||||
$.fn.comment = function() {
|
|
||||||
return this.each(function() {
|
|
||||||
var id = $(this).attr('id').substring(1);
|
|
||||||
var count = COMMENT_METADATA[id];
|
|
||||||
var title = count + ' comment' + (count == 1 ? '' : 's');
|
|
||||||
var image = count > 0 ? opts.commentBrightImage : opts.commentImage;
|
|
||||||
var addcls = count == 0 ? ' nocomment' : '';
|
|
||||||
$(this)
|
|
||||||
.append(
|
|
||||||
$(document.createElement('a')).attr({
|
|
||||||
href: '#',
|
|
||||||
'class': 'sphinx-comment-open' + addcls,
|
|
||||||
id: 'ao' + id
|
|
||||||
})
|
|
||||||
.append($(document.createElement('img')).attr({
|
|
||||||
src: image,
|
|
||||||
alt: 'comment',
|
|
||||||
title: title
|
|
||||||
}))
|
|
||||||
.click(function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
show($(this).attr('id').substring(2));
|
|
||||||
})
|
|
||||||
)
|
|
||||||
.append(
|
|
||||||
$(document.createElement('a')).attr({
|
|
||||||
href: '#',
|
|
||||||
'class': 'sphinx-comment-close hidden',
|
|
||||||
id: 'ah' + id
|
|
||||||
})
|
|
||||||
.append($(document.createElement('img')).attr({
|
|
||||||
src: opts.closeCommentImage,
|
|
||||||
alt: 'close',
|
|
||||||
title: 'close'
|
|
||||||
}))
|
|
||||||
.click(function(event) {
|
|
||||||
event.preventDefault();
|
|
||||||
hide($(this).attr('id').substring(2));
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
var opts = {
|
|
||||||
processVoteURL: '/_process_vote',
|
|
||||||
addCommentURL: '/_add_comment',
|
|
||||||
getCommentsURL: '/_get_comments',
|
|
||||||
acceptCommentURL: '/_accept_comment',
|
|
||||||
deleteCommentURL: '/_delete_comment',
|
|
||||||
commentImage: '/static/_static/comment.png',
|
|
||||||
closeCommentImage: '/static/_static/comment-close.png',
|
|
||||||
loadingImage: '/static/_static/ajax-loader.gif',
|
|
||||||
commentBrightImage: '/static/_static/comment-bright.png',
|
|
||||||
upArrow: '/static/_static/up.png',
|
|
||||||
downArrow: '/static/_static/down.png',
|
|
||||||
upArrowPressed: '/static/_static/up-pressed.png',
|
|
||||||
downArrowPressed: '/static/_static/down-pressed.png',
|
|
||||||
voting: false,
|
|
||||||
moderator: false
|
|
||||||
};
|
|
||||||
|
|
||||||
if (typeof COMMENT_OPTIONS != "undefined") {
|
|
||||||
opts = jQuery.extend(opts, COMMENT_OPTIONS);
|
|
||||||
}
|
|
||||||
|
|
||||||
var popupTemplate = '\
|
|
||||||
<div class="sphinx-comments" id="sc<%id%>">\
|
|
||||||
<p class="sort-options">\
|
|
||||||
Sort by:\
|
|
||||||
<a href="#" class="sort-option byrating">best rated</a>\
|
|
||||||
<a href="#" class="sort-option byascage">newest</a>\
|
|
||||||
<a href="#" class="sort-option byage">oldest</a>\
|
|
||||||
</p>\
|
|
||||||
<div class="comment-header">Comments</div>\
|
|
||||||
<div class="comment-loading" id="cn<%id%>">\
|
|
||||||
loading comments... <img src="<%loadingImage%>" alt="" /></div>\
|
|
||||||
<ul id="cl<%id%>" class="comment-ul"></ul>\
|
|
||||||
<div id="ca<%id%>">\
|
|
||||||
<p class="add-a-comment">Add a comment\
|
|
||||||
(<a href="#" class="comment-markup" id="ab<%id%>">markup</a>):</p>\
|
|
||||||
<div class="comment-markup-box" id="mb<%id%>">\
|
|
||||||
reStructured text markup: <i>*emph*</i>, <b>**strong**</b>, \
|
|
||||||
<code>``code``</code>, \
|
|
||||||
code blocks: <code>::</code> and an indented block after blank line</div>\
|
|
||||||
<form method="post" id="cf<%id%>" class="comment-form" action="">\
|
|
||||||
<textarea name="comment" cols="80"></textarea>\
|
|
||||||
<p class="propose-button">\
|
|
||||||
<a href="#" id="pc<%id%>" class="show-propose-change">\
|
|
||||||
Propose a change ▹\
|
|
||||||
</a>\
|
|
||||||
<a href="#" id="hc<%id%>" class="hide-propose-change">\
|
|
||||||
Propose a change ▿\
|
|
||||||
</a>\
|
|
||||||
</p>\
|
|
||||||
<textarea name="proposal" id="pt<%id%>" cols="80"\
|
|
||||||
spellcheck="false"></textarea>\
|
|
||||||
<input type="submit" value="Add comment" />\
|
|
||||||
<input type="hidden" name="node" value="<%id%>" />\
|
|
||||||
<input type="hidden" name="parent" value="" />\
|
|
||||||
</form>\
|
|
||||||
</div>\
|
|
||||||
</div>';
|
|
||||||
|
|
||||||
var commentTemplate = '\
|
|
||||||
<div id="cd<%id%>" class="sphinx-comment<%css_class%>">\
|
|
||||||
<div class="vote">\
|
|
||||||
<div class="arrow">\
|
|
||||||
<a href="#" id="uv<%id%>" class="vote" title="vote up">\
|
|
||||||
<img src="<%upArrow%>" />\
|
|
||||||
</a>\
|
|
||||||
<a href="#" id="uu<%id%>" class="un vote" title="vote up">\
|
|
||||||
<img src="<%upArrowPressed%>" />\
|
|
||||||
</a>\
|
|
||||||
</div>\
|
|
||||||
<div class="arrow">\
|
|
||||||
<a href="#" id="dv<%id%>" class="vote" title="vote down">\
|
|
||||||
<img src="<%downArrow%>" id="da<%id%>" />\
|
|
||||||
</a>\
|
|
||||||
<a href="#" id="du<%id%>" class="un vote" title="vote down">\
|
|
||||||
<img src="<%downArrowPressed%>" />\
|
|
||||||
</a>\
|
|
||||||
</div>\
|
|
||||||
</div>\
|
|
||||||
<div class="comment-content">\
|
|
||||||
<p class="tagline comment">\
|
|
||||||
<span class="user-id"><%username%></span>\
|
|
||||||
<span class="rating"><%pretty_rating%></span>\
|
|
||||||
<span class="delta"><%time.delta%></span>\
|
|
||||||
</p>\
|
|
||||||
<div class="comment-text comment"><#text#></div>\
|
|
||||||
<p class="comment-opts comment">\
|
|
||||||
<a href="#" class="reply hidden" id="rl<%id%>">reply ▹</a>\
|
|
||||||
<a href="#" class="close-reply" id="cr<%id%>">reply ▿</a>\
|
|
||||||
<a href="#" id="sp<%id%>" class="show-proposal">proposal ▹</a>\
|
|
||||||
<a href="#" id="hp<%id%>" class="hide-proposal">proposal ▿</a>\
|
|
||||||
<a href="#" id="dc<%id%>" class="delete-comment hidden">delete</a>\
|
|
||||||
<span id="cm<%id%>" class="moderation hidden">\
|
|
||||||
<a href="#" id="ac<%id%>" class="accept-comment">accept</a>\
|
|
||||||
</span>\
|
|
||||||
</p>\
|
|
||||||
<pre class="proposal" id="pr<%id%>">\
|
|
||||||
<#proposal_diff#>\
|
|
||||||
</pre>\
|
|
||||||
<ul class="comment-children" id="cl<%id%>"></ul>\
|
|
||||||
</div>\
|
|
||||||
<div class="clearleft"></div>\
|
|
||||||
</div>\
|
|
||||||
</div>';
|
|
||||||
|
|
||||||
var replyTemplate = '\
|
|
||||||
<li>\
|
|
||||||
<div class="reply-div" id="rd<%id%>">\
|
|
||||||
<form id="rf<%id%>">\
|
|
||||||
<textarea name="comment" cols="80"></textarea>\
|
|
||||||
<input type="submit" value="Add reply" />\
|
|
||||||
<input type="button" value="Cancel" />\
|
|
||||||
<input type="hidden" name="parent" value="<%id%>" />\
|
|
||||||
<input type="hidden" name="node" value="" />\
|
|
||||||
</form>\
|
|
||||||
</div>\
|
|
||||||
</li>';
|
|
||||||
|
|
||||||
$(document).ready(function() {
|
|
||||||
init();
|
|
||||||
});
|
|
||||||
})(jQuery);
|
|
||||||
|
|
||||||
$(document).ready(function() {
|
|
||||||
// add comment anchors for all paragraphs that are commentable
|
|
||||||
$('.sphinx-has-comment').comment();
|
|
||||||
|
|
||||||
// highlight search words in search results
|
|
||||||
$("div.context").each(function() {
|
|
||||||
var params = $.getQueryParameters();
|
|
||||||
var terms = (params.q) ? params.q[0].split(/\s+/) : [];
|
|
||||||
var result = $(this);
|
|
||||||
$.each(terms, function() {
|
|
||||||
result.highlightText(this.toLowerCase(), 'highlighted');
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// directly open comment window if requested
|
|
||||||
var anchor = document.location.hash;
|
|
||||||
if (anchor.substring(0, 9) == '#comment-') {
|
|
||||||
$('#ao' + anchor.substring(9)).click();
|
|
||||||
document.location.hash = '#s' + anchor.substring(9);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
# Makefile for Sphinx documentation
|
||||||
|
#
|
||||||
|
|
||||||
|
# You can set these variables from the command line.
|
||||||
|
SPHINXOPTS =
|
||||||
|
SPHINXBUILD = sphinx-build
|
||||||
|
PAPER =
|
||||||
|
|
||||||
|
# Internal variables.
|
||||||
|
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||||
|
PAPEROPT_letter = -D latex_paper_size=letter
|
||||||
|
ALLSPHINXOPTS = -d doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||||
|
|
||||||
|
.PHONY: help clean html web pickle htmlhelp latex changes linkcheck install
|
||||||
|
|
||||||
|
all: html
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "Please use \`make <target>' where <target> is one of"
|
||||||
|
@echo " html to make standalone HTML files"
|
||||||
|
@echo " pickle to make pickle files (usable by e.g. sphinx-web)"
|
||||||
|
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||||
|
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||||
|
@echo " changes to make an overview over all changed/added/deprecated items"
|
||||||
|
@echo " linkcheck to check all external links for integrity"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
-rm -rf html doctrees pickle htmlhelp latex changes linkcheck
|
||||||
|
|
||||||
|
html:
|
||||||
|
mkdir -p html doctrees
|
||||||
|
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) html
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML pages are in html."
|
||||||
|
|
||||||
|
pickle:
|
||||||
|
mkdir -p pickle doctrees
|
||||||
|
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) pickle
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can process the pickle files or run"
|
||||||
|
@echo " sphinx-web pickle"
|
||||||
|
@echo "to start the sphinx-web server."
|
||||||
|
|
||||||
|
web: pickle
|
||||||
|
|
||||||
|
htmlhelp:
|
||||||
|
mkdir -p htmlhelp doctrees
|
||||||
|
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) htmlhelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||||
|
".hhp project file in htmlhelp."
|
||||||
|
|
||||||
|
latex:
|
||||||
|
mkdir -p latex doctrees
|
||||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) latex
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; the LaTeX files are in .build/latex."
|
||||||
|
@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
|
||||||
|
"run these through (pdf)latex."
|
||||||
|
|
||||||
|
changes:
|
||||||
|
mkdir -p changes doctrees
|
||||||
|
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) changes
|
||||||
|
@echo
|
||||||
|
@echo "The overview file is in changes."
|
||||||
|
|
||||||
|
linkcheck:
|
||||||
|
mkdir -p linkcheck doctrees
|
||||||
|
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) linkcheck
|
||||||
|
@echo
|
||||||
|
@echo "Link check complete; look for any errors in the above output " \
|
||||||
|
"or in linkcheck/output.txt."
|
||||||
|
|
||||||
|
gh-pages: html
|
||||||
|
cd gh-pages; git pull --rebase; cd ..; cp -r ./html/* gh-pages
|
||||||
|
|
||||||
|
publish: gh-pages
|
||||||
|
cd gh-pages; git commit -a; git push; cd ..
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
# Publish the documentation
|
||||||
|
|
||||||
|
The documentation is published through the `gh-pages` branch.
|
||||||
|
|
||||||
|
To publish the documentation, you need to clone the `gh-pages` branch of this repository into
|
||||||
|
`doc/gh-pages`. In `doc`, do:
|
||||||
|
|
||||||
|
git clone -b gh-pages git@github.com:larsimmisch/pyalsaaudio.git gh-pages
|
||||||
|
|
||||||
|
(This is a bit of a hack)
|
||||||
|
|
||||||
|
Once that is set up, you can publish new documentation using:
|
||||||
|
|
||||||
|
make publish
|
||||||
|
|
||||||
|
Be careful when new files are generated, however, you will have to add them
|
||||||
|
manually to git.
|
||||||
@@ -0,0 +1,160 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# alsaaudio documentation documentation build configuration file, created by
|
||||||
|
# sphinx-quickstart on Thu Mar 30 23:52:21 2017.
|
||||||
|
#
|
||||||
|
# This file is execfile()d with the current directory set to its
|
||||||
|
# containing dir.
|
||||||
|
#
|
||||||
|
# Note that not all possible configuration values are present in this
|
||||||
|
# autogenerated file.
|
||||||
|
#
|
||||||
|
# All configuration values have a default; values that are commented out
|
||||||
|
# serve to show the default.
|
||||||
|
|
||||||
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
|
#
|
||||||
|
# import os
|
||||||
|
# import sys
|
||||||
|
# sys.path.insert(0, os.path.abspath('.'))
|
||||||
|
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, '..')
|
||||||
|
from setup import pyalsa_version
|
||||||
|
|
||||||
|
|
||||||
|
# -- General configuration ------------------------------------------------
|
||||||
|
|
||||||
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
|
#
|
||||||
|
# needs_sphinx = '1.0'
|
||||||
|
|
||||||
|
# Add any Sphinx extension module names here, as strings. They can be
|
||||||
|
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||||
|
# ones.
|
||||||
|
extensions = []
|
||||||
|
|
||||||
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
templates_path = ['_templates']
|
||||||
|
|
||||||
|
# The suffix(es) of source filenames.
|
||||||
|
# You can specify multiple suffix as a list of string:
|
||||||
|
#
|
||||||
|
# source_suffix = ['.rst', '.md']
|
||||||
|
source_suffix = '.rst'
|
||||||
|
|
||||||
|
# The master toctree document.
|
||||||
|
master_doc = 'index'
|
||||||
|
|
||||||
|
# General information about the project.
|
||||||
|
project = u'alsaaudio documentation'
|
||||||
|
copyright = u'2017, Lars Immisch & Casper Wilstrup'
|
||||||
|
author = u'Lars Immisch & Casper Wilstrup'
|
||||||
|
|
||||||
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
|
# |version| and |release|, also used in various other places throughout the
|
||||||
|
# built documents.
|
||||||
|
#
|
||||||
|
# The short X.Y version.
|
||||||
|
version = pyalsa_version
|
||||||
|
# The full version, including alpha/beta/rc tags.
|
||||||
|
release = version
|
||||||
|
|
||||||
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
|
# for a list of supported languages.
|
||||||
|
#
|
||||||
|
# This is also used if you do content translation via gettext catalogs.
|
||||||
|
# Usually you set "language" from the command line for these cases.
|
||||||
|
language = None
|
||||||
|
|
||||||
|
# List of patterns, relative to source directory, that match files and
|
||||||
|
# directories to ignore when looking for source files.
|
||||||
|
# This patterns also effect to html_static_path and html_extra_path
|
||||||
|
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||||
|
|
||||||
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
|
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||||
|
todo_include_todos = False
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTML output ----------------------------------------------
|
||||||
|
|
||||||
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
|
# a list of builtin themes.
|
||||||
|
#
|
||||||
|
html_theme = 'alabaster'
|
||||||
|
|
||||||
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
|
# further. For a list of options available for each theme, see the
|
||||||
|
# documentation.
|
||||||
|
#
|
||||||
|
# html_theme_options = {}
|
||||||
|
|
||||||
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
|
html_static_path = ['_static']
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTMLHelp output ------------------------------------------
|
||||||
|
|
||||||
|
# Output file base name for HTML help builder.
|
||||||
|
htmlhelp_basename = 'alsaaudiodocumentationdoc'
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for LaTeX output ---------------------------------------------
|
||||||
|
|
||||||
|
latex_elements = {
|
||||||
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
|
#
|
||||||
|
# 'papersize': 'letterpaper',
|
||||||
|
|
||||||
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
|
#
|
||||||
|
# 'pointsize': '10pt',
|
||||||
|
|
||||||
|
# Additional stuff for the LaTeX preamble.
|
||||||
|
#
|
||||||
|
# 'preamble': '',
|
||||||
|
|
||||||
|
# Latex figure (float) alignment
|
||||||
|
#
|
||||||
|
# 'figure_align': 'htbp',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
|
# (source start file, target name, title,
|
||||||
|
# author, documentclass [howto, manual, or own class]).
|
||||||
|
latex_documents = [
|
||||||
|
(master_doc, 'alsaaudiodocumentation.tex', u'alsaaudio documentation Documentation',
|
||||||
|
u'Lars Immisch', 'manual'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for manual page output ---------------------------------------
|
||||||
|
|
||||||
|
# One entry per manual page. List of tuples
|
||||||
|
# (source start file, name, description, authors, manual section).
|
||||||
|
man_pages = [
|
||||||
|
(master_doc, 'alsaaudiodocumentation', u'alsaaudio documentation Documentation',
|
||||||
|
[author], 1)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for Texinfo output -------------------------------------------
|
||||||
|
|
||||||
|
# Grouping the document tree into Texinfo files. List of tuples
|
||||||
|
# (source start file, target name, title, author,
|
||||||
|
# dir menu entry, description, category)
|
||||||
|
texinfo_documents = [
|
||||||
|
(master_doc, 'alsaaudiodocumentation', u'alsaaudio documentation Documentation',
|
||||||
|
author, 'alsaaudiodocumentation', 'One line description of project.',
|
||||||
|
'Miscellaneous'),
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,3 +1,8 @@
|
|||||||
|
.. alsaaudio documentation documentation master file, created by
|
||||||
|
sphinx-quickstart on Thu Mar 30 23:52:21 2017.
|
||||||
|
You can adapt this file completely to your liking, but it should at least
|
||||||
|
contain the root `toctree` directive.
|
||||||
|
|
||||||
alsaaudio documentation
|
alsaaudio documentation
|
||||||
===================================================
|
===================================================
|
||||||
|
|
||||||
@@ -14,12 +19,14 @@ Download
|
|||||||
|
|
||||||
* `Download from pypi <https://pypi.python.org/pypi/pyalsaaudio>`_
|
* `Download from pypi <https://pypi.python.org/pypi/pyalsaaudio>`_
|
||||||
|
|
||||||
|
|
||||||
Github
|
Github
|
||||||
======
|
======
|
||||||
|
|
||||||
* `Repository <https://github.com/larsimmisch/pyalsaaudio/>`_
|
* `Repository <https://github.com/larsimmisch/pyalsaaudio/>`_
|
||||||
* `Bug tracker <https://github.com/larsimmisch/pyalsaaudio/issues>`_
|
* `Bug tracker <https://github.com/larsimmisch/pyalsaaudio/issues>`_
|
||||||
|
|
||||||
|
|
||||||
Indices and tables
|
Indices and tables
|
||||||
==================
|
==================
|
||||||
|
|
||||||
@@ -27,3 +34,5 @@ Indices and tables
|
|||||||
* :ref:`modindex`
|
* :ref:`modindex`
|
||||||
* :ref:`search`
|
* :ref:`search`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -193,10 +193,10 @@ PCM objects have the following methods:
|
|||||||
``PCM_FORMAT_S16_BE`` Signed 16 bit samples for each channel (Big Endian byte order)
|
``PCM_FORMAT_S16_BE`` Signed 16 bit samples for each channel (Big Endian byte order)
|
||||||
``PCM_FORMAT_U16_LE`` Unsigned 16 bit samples for each channel (Little Endian byte order)
|
``PCM_FORMAT_U16_LE`` Unsigned 16 bit samples for each channel (Little Endian byte order)
|
||||||
``PCM_FORMAT_U16_BE`` Unsigned 16 bit samples for each channel (Big Endian byte order)
|
``PCM_FORMAT_U16_BE`` Unsigned 16 bit samples for each channel (Big Endian byte order)
|
||||||
``PCM_FORMAT_S24_LE`` Signed 24 bit samples for each channel (Little Endian byte order)
|
``PCM_FORMAT_S24_LE`` Signed 24 bit samples for each channel (Little Endian byte order in 4 bytes)
|
||||||
``PCM_FORMAT_S24_BE`` Signed 24 bit samples for each channel (Big Endian byte order)}
|
``PCM_FORMAT_S24_BE`` Signed 24 bit samples for each channel (Big Endian byte order in 4 bytes)
|
||||||
``PCM_FORMAT_U24_LE`` Unsigned 24 bit samples for each channel (Little Endian byte order)
|
``PCM_FORMAT_U24_LE`` Unsigned 24 bit samples for each channel (Little Endian byte order in 4 bytes)
|
||||||
``PCM_FORMAT_U24_BE`` Unsigned 24 bit samples for each channel (Big Endian byte order)
|
``PCM_FORMAT_U24_BE`` Unsigned 24 bit samples for each channel (Big Endian byte order in 4 bytes)
|
||||||
``PCM_FORMAT_S32_LE`` Signed 32 bit samples for each channel (Little Endian byte order)
|
``PCM_FORMAT_S32_LE`` Signed 32 bit samples for each channel (Little Endian byte order)
|
||||||
``PCM_FORMAT_S32_BE`` Signed 32 bit samples for each channel (Big Endian byte order)
|
``PCM_FORMAT_S32_BE`` Signed 32 bit samples for each channel (Big Endian byte order)
|
||||||
``PCM_FORMAT_U32_LE`` Unsigned 32 bit samples for each channel (Little Endian byte order)
|
``PCM_FORMAT_U32_LE`` Unsigned 32 bit samples for each channel (Little Endian byte order)
|
||||||
@@ -210,6 +210,10 @@ PCM objects have the following methods:
|
|||||||
``PCM_FORMAT_IMA_ADPCM`` A 4:1 compressed format defined by the Interactive Multimedia Association.
|
``PCM_FORMAT_IMA_ADPCM`` A 4:1 compressed format defined by the Interactive Multimedia Association.
|
||||||
``PCM_FORMAT_MPEG`` MPEG encoded audio?
|
``PCM_FORMAT_MPEG`` MPEG encoded audio?
|
||||||
``PCM_FORMAT_GSM`` 9600 bits/s constant rate encoding for speech
|
``PCM_FORMAT_GSM`` 9600 bits/s constant rate encoding for speech
|
||||||
|
``PCM_FORMAT_S24_3LE`` Signed 24 bit samples for each channel (Little Endian byte order in 3 bytes)
|
||||||
|
``PCM_FORMAT_S24_3BE`` Signed 24 bit samples for each channel (Big Endian byte order in 3 bytes)
|
||||||
|
``PCM_FORMAT_U24_3LE`` Unsigned 24 bit samples for each channel (Little Endian byte order in 3 bytes)
|
||||||
|
``PCM_FORMAT_U24_3BE`` Unsigned 24 bit samples for each channel (Big Endian byte order in 3 bytes)
|
||||||
========================= ===============
|
========================= ===============
|
||||||
|
|
||||||
|
|
||||||
@@ -439,31 +443,35 @@ Mixer objects have the following methods:
|
|||||||
This method will fail if the mixer has no capture switch capabilities.
|
This method will fail if the mixer has no capture switch capabilities.
|
||||||
|
|
||||||
|
|
||||||
.. method:: Mixer.getvolume([direction])
|
.. method:: Mixer.getvolume(direction=PCM_PLAYBACK, unit=Percent)
|
||||||
|
|
||||||
Returns a list with the current volume settings for each channel. The list
|
Returns a list with the current volume settings for each channel. The list
|
||||||
elements are integer percentages.
|
elements are percentages or dB values, depending on *unit*.
|
||||||
|
|
||||||
The optional *direction* argument can be either :const:`PCM_PLAYBACK` or
|
The *direction* argument can be either :const:`PCM_PLAYBACK` or
|
||||||
:const:`PCM_CAPTURE`, which is relevant if the mixer can control both
|
:const:`PCM_CAPTURE`, which is relevant if the mixer can control both
|
||||||
playback and capture volume. The default value is :const:`PCM_PLAYBACK`
|
playback and capture volume. The default value is :const:`PCM_PLAYBACK`
|
||||||
if the mixer has playback channels, otherwise it is :const:`PCM_CAPTURE`.
|
if the mixer has playback channels, otherwise it is :const:`PCM_CAPTURE`.
|
||||||
|
|
||||||
|
|
||||||
.. method:: Mixer.setvolume(volume, [channel], [direction])
|
.. method:: Mixer.setvolume(volume, channel=MIXER_CHANNEL_ALL, direction=PCM_PLAYBACK, unit=Percent)
|
||||||
|
|
||||||
Change the current volume settings for this mixer. The *volume* argument
|
Change the current volume settings for this mixer. The *volume* argument
|
||||||
controls the new volume setting as an integer percentage.
|
controls the new volume setting as either a percentage or a dB value. Both
|
||||||
|
integer and floating point values can be given.
|
||||||
|
|
||||||
If the optional argument *channel* is present, the volume is set
|
The *channel* argument can be used to restrict the channels for which the volume is
|
||||||
only for this channel. This assumes that the mixer can control the
|
set. By default, the volume of all channels is adjusted. This assumes that the mixer
|
||||||
volume for the channels independently.
|
can control the volume for the channels independently.
|
||||||
|
|
||||||
The optional *direction* argument can be either :const:`PCM_PLAYBACK` or
|
The *direction* argument can be either :const:`PCM_PLAYBACK` or
|
||||||
:const:`PCM_CAPTURE`, which is relevant if the mixer can control both
|
:const:`PCM_CAPTURE`, which is relevant if the mixer can control both
|
||||||
playback and capture volume. The default value is :const:`PCM_PLAYBACK`
|
playback and capture volume. The default value is :const:`PCM_PLAYBACK`
|
||||||
if the mixer has playback channels, otherwise it is :const:`PCM_CAPTURE`.
|
if the mixer has playback channels, otherwise it is :const:`PCM_CAPTURE`.
|
||||||
|
|
||||||
|
The *unit* argument determines how the volume value is interpreted, as a prcentage
|
||||||
|
or as a dB value.
|
||||||
|
|
||||||
.. method:: Mixer.setmute(mute, [channel])
|
.. method:: Mixer.setmute(mute, [channel])
|
||||||
|
|
||||||
Sets the mute flag to a new value. The *mute* argument is either 0 for not
|
Sets the mute flag to a new value. The *mute* argument is either 0 for not
|
||||||
@@ -46,7 +46,7 @@ Data rate
|
|||||||
|
|
||||||
At the other end of the scale, 96000 Hz, 6 channel sound with 64
|
At the other end of the scale, 96000 Hz, 6 channel sound with 64
|
||||||
bit (8 bytes) samples has a data rate of 96000 \* 6 \* 8 = 4608
|
bit (8 bytes) samples has a data rate of 96000 \* 6 \* 8 = 4608
|
||||||
kb/s (almost 5 Mb sound data per second)
|
kb/s (almost 5 MB sound data per second)
|
||||||
|
|
||||||
Period
|
Period
|
||||||
When the hardware processes data this is done in chunks of frames. The time
|
When the hardware processes data this is done in chunks of frames. The time
|
||||||
@@ -1,290 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
<html lang="en" data-content_root="./">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Index — alsaaudio documentation 0.11.0 documentation</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=fa44fd50" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css?v=cb25574f" />
|
|
||||||
<script src="_static/documentation_options.js?v=f3b36f1a"></script>
|
|
||||||
<script src="_static/doctools.js?v=9a2dae69"></script>
|
|
||||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
|
||||||
<link rel="index" title="Index" href="#" />
|
|
||||||
<link rel="search" title="Search" href="search.html" />
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
|
|
||||||
|
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" />
|
|
||||||
|
|
||||||
</head><body>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="document">
|
|
||||||
<div class="documentwrapper">
|
|
||||||
<div class="bodywrapper">
|
|
||||||
|
|
||||||
|
|
||||||
<div class="body" role="main">
|
|
||||||
|
|
||||||
|
|
||||||
<h1 id="index">Index</h1>
|
|
||||||
|
|
||||||
<div class="genindex-jumpbox">
|
|
||||||
<a href="#A"><strong>A</strong></a>
|
|
||||||
| <a href="#C"><strong>C</strong></a>
|
|
||||||
| <a href="#D"><strong>D</strong></a>
|
|
||||||
| <a href="#G"><strong>G</strong></a>
|
|
||||||
| <a href="#H"><strong>H</strong></a>
|
|
||||||
| <a href="#I"><strong>I</strong></a>
|
|
||||||
| <a href="#M"><strong>M</strong></a>
|
|
||||||
| <a href="#P"><strong>P</strong></a>
|
|
||||||
| <a href="#R"><strong>R</strong></a>
|
|
||||||
| <a href="#S"><strong>S</strong></a>
|
|
||||||
| <a href="#V"><strong>V</strong></a>
|
|
||||||
| <a href="#W"><strong>W</strong></a>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<h2 id="A">A</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li>
|
|
||||||
alsaaudio
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="libalsaaudio.html#module-alsaaudio">module</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
</ul></td>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.asoundlib_version">asoundlib_version() (in module alsaaudio)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.PCM.avail">avail() (alsaaudio.PCM method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="C">C</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.Mixer.cardname">cardname() (alsaaudio.Mixer method)</a>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.PCM.cardname">(alsaaudio.PCM method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
</ul></td>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.cards">cards() (in module alsaaudio)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.Mixer.close">close() (alsaaudio.Mixer method)</a>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.PCM.close">(alsaaudio.PCM method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="D">D</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.PCM.drain">drain() (alsaaudio.PCM method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.PCM.drop">drop() (alsaaudio.PCM method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.PCM.dumpinfo">dumpinfo() (alsaaudio.PCM method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="G">G</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.PCM.get_tstamp_mode">get_tstamp_mode() (alsaaudio.PCM method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.PCM.get_tstamp_type">get_tstamp_type() (alsaaudio.PCM method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.Mixer.getenum">getenum() (alsaaudio.Mixer method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.Mixer.getmute">getmute() (alsaaudio.Mixer method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.Mixer.getrange">getrange() (alsaaudio.Mixer method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.Mixer.getrec">getrec() (alsaaudio.Mixer method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.Mixer.getvolume">getvolume() (alsaaudio.Mixer method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="H">H</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.Mixer.handleevents">handleevents() (alsaaudio.Mixer method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.PCM.htimestamp">htimestamp() (alsaaudio.PCM method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="I">I</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.PCM.info">info() (alsaaudio.PCM method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="M">M</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.Mixer">Mixer (class in alsaaudio)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.Mixer.mixer">mixer() (alsaaudio.Mixer method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.Mixer.mixerid">mixerid() (alsaaudio.Mixer method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.mixers">mixers() (in module alsaaudio)</a>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
module
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="libalsaaudio.html#module-alsaaudio">alsaaudio</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="P">P</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.PCM.pause">pause() (alsaaudio.PCM method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.PCM.pcmmode">pcmmode() (alsaaudio.PCM method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.pcms">pcms() (in module alsaaudio)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.PCM.pcmtype">pcmtype() (alsaaudio.PCM method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.Mixer.polldescriptors">polldescriptors() (alsaaudio.Mixer method)</a>
|
|
||||||
|
|
||||||
<ul>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.PCM.polldescriptors">(alsaaudio.PCM method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></li>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.PCM.polldescriptors_revents">polldescriptors_revents() (alsaaudio.PCM method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="R">R</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.PCM.read">read() (alsaaudio.PCM method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="S">S</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.PCM.set_tstamp_mode">set_tstamp_mode() (alsaaudio.PCM method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.PCM.set_tstamp_type">set_tstamp_type() (alsaaudio.PCM method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.PCM.setchannels">setchannels() (alsaaudio.PCM method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.Mixer.setenum">setenum() (alsaaudio.Mixer method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.PCM.setformat">setformat() (alsaaudio.PCM method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.Mixer.setmute">setmute() (alsaaudio.Mixer method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.PCM.setperiodsize">setperiodsize() (alsaaudio.PCM method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.PCM.setrate">setrate() (alsaaudio.PCM method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.Mixer.setrec">setrec() (alsaaudio.Mixer method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.Mixer.setvolume">setvolume() (alsaaudio.Mixer method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.PCM.state">state() (alsaaudio.PCM method)</a>
|
|
||||||
</li>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.Mixer.switchcap">switchcap() (alsaaudio.Mixer method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="V">V</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.Mixer.volumecap">volumecap() (alsaaudio.Mixer method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
<h2 id="W">W</h2>
|
|
||||||
<table style="width: 100%" class="indextable genindextable"><tr>
|
|
||||||
<td style="width: 33%; vertical-align: top;"><ul>
|
|
||||||
<li><a href="libalsaaudio.html#alsaaudio.PCM.write">write() (alsaaudio.PCM method)</a>
|
|
||||||
</li>
|
|
||||||
</ul></td>
|
|
||||||
</tr></table>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
|
|
||||||
<div class="sphinxsidebarwrapper"><div class="relations">
|
|
||||||
<h3>Related Topics</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="index.html">Documentation overview</a><ul>
|
|
||||||
</ul></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<search id="searchbox" style="display: none" role="search">
|
|
||||||
<h3 id="searchlabel">Quick search</h3>
|
|
||||||
<div class="searchformwrapper">
|
|
||||||
<form class="search" action="search.html" method="get">
|
|
||||||
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
|
||||||
<input type="submit" value="Go" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</search>
|
|
||||||
<script>document.getElementById('searchbox').style.display = "block"</script>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="clearer"></div>
|
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
©2017, Lars Immisch & Casper Wilstrup.
|
|
||||||
|
|
||||||
|
|
|
||||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 7.3.7</a>
|
|
||||||
& <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,143 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
<html lang="en" data-content_root="./">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
|
|
||||||
<title>alsaaudio documentation — alsaaudio documentation 0.11.0 documentation</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=fa44fd50" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css?v=cb25574f" />
|
|
||||||
<script src="_static/documentation_options.js?v=f3b36f1a"></script>
|
|
||||||
<script src="_static/doctools.js?v=9a2dae69"></script>
|
|
||||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
|
||||||
<script async="async" src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
|
|
||||||
<link rel="index" title="Index" href="genindex.html" />
|
|
||||||
<link rel="search" title="Search" href="search.html" />
|
|
||||||
<link rel="next" title="Introduction" href="pyalsaaudio.html" />
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
|
|
||||||
|
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" />
|
|
||||||
|
|
||||||
</head><body>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="document">
|
|
||||||
<div class="documentwrapper">
|
|
||||||
<div class="bodywrapper">
|
|
||||||
|
|
||||||
|
|
||||||
<div class="body" role="main">
|
|
||||||
|
|
||||||
<section id="alsaaudio-documentation">
|
|
||||||
<h1>alsaaudio documentation<a class="headerlink" href="#alsaaudio-documentation" title="Link to this heading">¶</a></h1>
|
|
||||||
<div class="toctree-wrapper compound">
|
|
||||||
<p class="caption" role="heading"><span class="caption-text">Contents:</span></p>
|
|
||||||
<ul>
|
|
||||||
<li class="toctree-l1"><a class="reference internal" href="pyalsaaudio.html">Introduction</a></li>
|
|
||||||
<li class="toctree-l1"><a class="reference internal" href="pyalsaaudio.html#what-is-alsa">What is ALSA</a><ul>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="pyalsaaudio.html#alsa-and-python">ALSA and Python</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li class="toctree-l1"><a class="reference internal" href="pyalsaaudio.html#installation">Installation</a></li>
|
|
||||||
<li class="toctree-l1"><a class="reference internal" href="pyalsaaudio.html#testing">Testing</a></li>
|
|
||||||
<li class="toctree-l1"><a class="reference internal" href="terminology.html">PCM Terminology and Concepts</a></li>
|
|
||||||
<li class="toctree-l1"><a class="reference internal" href="libalsaaudio.html"><code class="xref py py-mod docutils literal notranslate"><span class="pre">alsaaudio</span></code></a><ul>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="libalsaaudio.html#alsaaudio.pcms"><code class="docutils literal notranslate"><span class="pre">pcms()</span></code></a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="libalsaaudio.html#alsaaudio.cards"><code class="docutils literal notranslate"><span class="pre">cards()</span></code></a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="libalsaaudio.html#alsaaudio.mixers"><code class="docutils literal notranslate"><span class="pre">mixers()</span></code></a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="libalsaaudio.html#alsaaudio.asoundlib_version"><code class="docutils literal notranslate"><span class="pre">asoundlib_version()</span></code></a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="libalsaaudio.html#pcm-objects">PCM Objects</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="libalsaaudio.html#mixer-objects">Mixer Objects</a></li>
|
|
||||||
<li class="toctree-l2"><a class="reference internal" href="libalsaaudio.html#examples">Examples</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section id="download">
|
|
||||||
<h1>Download<a class="headerlink" href="#download" title="Link to this heading">¶</a></h1>
|
|
||||||
<ul class="simple">
|
|
||||||
<li><p><a class="reference external" href="https://pypi.python.org/pypi/pyalsaaudio">Download from pypi</a></p></li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
<section id="github">
|
|
||||||
<h1>Github<a class="headerlink" href="#github" title="Link to this heading">¶</a></h1>
|
|
||||||
<ul class="simple">
|
|
||||||
<li><p><a class="reference external" href="https://github.com/larsimmisch/pyalsaaudio/">Repository</a></p></li>
|
|
||||||
<li><p><a class="reference external" href="https://github.com/larsimmisch/pyalsaaudio/issues">Bug tracker</a></p></li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
<section id="indices-and-tables">
|
|
||||||
<h1>Indices and tables<a class="headerlink" href="#indices-and-tables" title="Link to this heading">¶</a></h1>
|
|
||||||
<ul class="simple">
|
|
||||||
<li><p><a class="reference internal" href="genindex.html"><span class="std std-ref">Index</span></a></p></li>
|
|
||||||
<li><p><a class="reference internal" href="py-modindex.html"><span class="std std-ref">Module Index</span></a></p></li>
|
|
||||||
<li><p><a class="reference internal" href="search.html"><span class="std std-ref">Search Page</span></a></p></li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
|
|
||||||
<div class="sphinxsidebarwrapper">
|
|
||||||
<div>
|
|
||||||
<h3><a href="#">Table of Contents</a></h3>
|
|
||||||
<ul>
|
|
||||||
<li><a class="reference internal" href="#">alsaaudio documentation</a></li>
|
|
||||||
<li><a class="reference internal" href="#download">Download</a></li>
|
|
||||||
<li><a class="reference internal" href="#github">Github</a></li>
|
|
||||||
<li><a class="reference internal" href="#indices-and-tables">Indices and tables</a></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div><div class="relations">
|
|
||||||
<h3>Related Topics</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="#">Documentation overview</a><ul>
|
|
||||||
<li>Next: <a href="pyalsaaudio.html" title="next chapter">Introduction</a></li>
|
|
||||||
</ul></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div role="note" aria-label="source link">
|
|
||||||
<h3>This Page</h3>
|
|
||||||
<ul class="this-page-menu">
|
|
||||||
<li><a href="_sources/index.rst.txt"
|
|
||||||
rel="nofollow">Show Source</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<search id="searchbox" style="display: none" role="search">
|
|
||||||
<h3 id="searchlabel">Quick search</h3>
|
|
||||||
<div class="searchformwrapper">
|
|
||||||
<form class="search" action="search.html" method="get">
|
|
||||||
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
|
||||||
<input type="submit" value="Go" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</search>
|
|
||||||
<script>document.getElementById('searchbox').style.display = "block"</script>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="clearer"></div>
|
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
©2017, Lars Immisch & Casper Wilstrup.
|
|
||||||
|
|
||||||
|
|
|
||||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 7.3.7</a>
|
|
||||||
& <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
|
|
||||||
|
|
||||||
|
|
|
||||||
<a href="_sources/index.rst.txt"
|
|
||||||
rel="nofollow">Page source</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -0,0 +1,97 @@
|
|||||||
|
''' Use this example from an interactive python session, for example:
|
||||||
|
|
||||||
|
>>> from isine import change
|
||||||
|
>>> change(880)
|
||||||
|
'''
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from threading import Thread
|
||||||
|
from multiprocessing import Queue
|
||||||
|
|
||||||
|
if sys.version_info[0] < 3:
|
||||||
|
from Queue import Empty
|
||||||
|
else:
|
||||||
|
from queue import Empty
|
||||||
|
|
||||||
|
from math import pi, sin
|
||||||
|
import struct
|
||||||
|
import alsaaudio
|
||||||
|
|
||||||
|
sampling_rate = 48000
|
||||||
|
|
||||||
|
format = alsaaudio.PCM_FORMAT_S16_LE
|
||||||
|
framesize = 2 # bytes per frame for the values above
|
||||||
|
channels = 2
|
||||||
|
|
||||||
|
def nearest_frequency(frequency):
|
||||||
|
# calculate the nearest frequency where the wave form fits into the buffer
|
||||||
|
# in other words, select f so that sampling_rate/f is an integer
|
||||||
|
return float(sampling_rate)/int(sampling_rate/frequency)
|
||||||
|
|
||||||
|
def generate(frequency, duration = 0.125):
|
||||||
|
# generate a buffer with a sine wave of `frequency`
|
||||||
|
# that is approximately `duration` seconds long
|
||||||
|
|
||||||
|
# the buffersize we approximately want
|
||||||
|
target_size = int(sampling_rate * channels * duration)
|
||||||
|
|
||||||
|
# the length of a full sine wave at the frequency
|
||||||
|
cycle_size = int(sampling_rate / frequency)
|
||||||
|
|
||||||
|
# number of full cycles we can fit into target_size
|
||||||
|
factor = int(target_size / cycle_size)
|
||||||
|
|
||||||
|
size = max(int(cycle_size * factor), 1)
|
||||||
|
|
||||||
|
sine = [ int(32767 * sin(2 * pi * frequency * i / sampling_rate)) \
|
||||||
|
for i in range(size)]
|
||||||
|
|
||||||
|
return struct.pack('%dh' % size, *sine)
|
||||||
|
|
||||||
|
|
||||||
|
class SinePlayer(Thread):
|
||||||
|
|
||||||
|
def __init__(self, frequency = 440.0):
|
||||||
|
Thread.__init__(self)
|
||||||
|
self.setDaemon(True)
|
||||||
|
self.device = alsaaudio.PCM()
|
||||||
|
self.device.setchannels(channels)
|
||||||
|
self.device.setformat(format)
|
||||||
|
self.device.setrate(sampling_rate)
|
||||||
|
self.queue = Queue()
|
||||||
|
self.change(frequency)
|
||||||
|
|
||||||
|
def change(self, frequency):
|
||||||
|
'''This is called outside of the player thread'''
|
||||||
|
# we generate the buffer in the calling thread for less
|
||||||
|
# latency when switching frequencies
|
||||||
|
|
||||||
|
if frequency > sampling_rate / 2:
|
||||||
|
raise ValueError('maximum frequency is %d' % (sampling_rate / 2))
|
||||||
|
|
||||||
|
f = nearest_frequency(frequency)
|
||||||
|
print('nearest frequency: %f' % f)
|
||||||
|
|
||||||
|
buf = generate(f)
|
||||||
|
self.queue.put(buf)
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
buffer = None
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
buffer = self.queue.get(False)
|
||||||
|
self.device.setperiodsize(int(len(buffer) / framesize))
|
||||||
|
self.device.write(buffer)
|
||||||
|
except Empty:
|
||||||
|
if buffer:
|
||||||
|
self.device.write(buffer)
|
||||||
|
|
||||||
|
|
||||||
|
isine = SinePlayer()
|
||||||
|
isine.start()
|
||||||
|
|
||||||
|
def change(f):
|
||||||
|
isine.change(f)
|
||||||
|
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
## mixertest.py
|
||||||
|
##
|
||||||
|
## This is an example of using the ALSA mixer API
|
||||||
|
##
|
||||||
|
## The script will set the volume or mute switch of the specified Mixer
|
||||||
|
## depending on command line options.
|
||||||
|
##
|
||||||
|
## Examples:
|
||||||
|
## python mixertest.py # list available mixers
|
||||||
|
## python mixertest.py Master # show Master mixer settings
|
||||||
|
## python mixertest.py Master 80 # set the master volume to 80%
|
||||||
|
## python mixertest.py Master 1,90 # set channel 1 volume to 90%
|
||||||
|
## python mixertest.py Master [un]mute # [un]mute the master mixer
|
||||||
|
## python mixertest.py Capture [un]rec # [dis/en]able capture
|
||||||
|
## python mixertest.py Master 0,[un]mute # [un]mute channel 0
|
||||||
|
## python mixertest.py Capture 0,[un]rec # [dis/en]able capture on channel 0
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import getopt
|
||||||
|
import alsaaudio
|
||||||
|
|
||||||
|
def list_cards():
|
||||||
|
print("Available sound cards:")
|
||||||
|
for i in alsaaudio.card_indexes():
|
||||||
|
(name, longname) = alsaaudio.card_name(i)
|
||||||
|
print(" %d: %s (%s)" % (i, name, longname))
|
||||||
|
|
||||||
|
def list_mixers(kwargs):
|
||||||
|
print("Available mixer controls:")
|
||||||
|
for m in alsaaudio.mixers(**kwargs):
|
||||||
|
print(" '%s'" % m)
|
||||||
|
|
||||||
|
def show_mixer(name, kwargs):
|
||||||
|
# Demonstrates how mixer settings are queried.
|
||||||
|
try:
|
||||||
|
mixer = alsaaudio.Mixer(name, **kwargs)
|
||||||
|
except alsaaudio.ALSAAudioError:
|
||||||
|
print("No such mixer", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
print("Mixer name: '%s'" % mixer.mixer())
|
||||||
|
print("Capabilities: %s %s" % (' '.join(mixer.volumecap()),
|
||||||
|
' '.join(mixer.switchcap())))
|
||||||
|
volumes = mixer.getvolume()
|
||||||
|
for i, v in enumerate(volumes):
|
||||||
|
print("Channel %i volume: %.02f%%" % (i, v))
|
||||||
|
|
||||||
|
volumes = mixer.getvolume(unit=alsaaudio.dB)
|
||||||
|
for i, v in enumerate(volumes):
|
||||||
|
print("Channel %i volume: %.02fdB" % (i, v))
|
||||||
|
|
||||||
|
try:
|
||||||
|
mutes = mixer.getmute()
|
||||||
|
for i, m in enumerate(mutes):
|
||||||
|
if m:
|
||||||
|
print("Channel %i is muted" % i)
|
||||||
|
except alsaaudio.ALSAAudioError:
|
||||||
|
# May not support muting
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
recs = mixer.getrec()
|
||||||
|
for i, r in enumerate(recs):
|
||||||
|
if r:
|
||||||
|
print("Channel %i is recording" % i)
|
||||||
|
except alsaaudio.ALSAAudioError:
|
||||||
|
# May not support recording
|
||||||
|
pass
|
||||||
|
|
||||||
|
def set_mixer(name, args, kwargs):
|
||||||
|
# Demonstrates how to set mixer settings
|
||||||
|
try:
|
||||||
|
mixer = alsaaudio.Mixer(name, **kwargs)
|
||||||
|
except alsaaudio.ALSAAudioError:
|
||||||
|
print("No such mixer", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if args.find(',') != -1:
|
||||||
|
args_array = args.split(',')
|
||||||
|
channel = int(args_array[0])
|
||||||
|
args = ','.join(args_array[1:])
|
||||||
|
else:
|
||||||
|
channel = alsaaudio.MIXER_CHANNEL_ALL
|
||||||
|
|
||||||
|
if args in ['mute', 'unmute']:
|
||||||
|
# Mute/unmute the mixer
|
||||||
|
if args == 'mute':
|
||||||
|
mixer.setmute(1, channel)
|
||||||
|
else:
|
||||||
|
mixer.setmute(0, channel)
|
||||||
|
|
||||||
|
elif args in ['rec','unrec']:
|
||||||
|
# Enable/disable recording
|
||||||
|
if args == 'rec':
|
||||||
|
mixer.setrec(1, channel)
|
||||||
|
else:
|
||||||
|
mixer.setrec(0, channel)
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Set volume for specified channel. MIXER_CHANNEL_ALL means set
|
||||||
|
# volume for all channels
|
||||||
|
volume = int(args)
|
||||||
|
mixer.setvolume(volume, channel)
|
||||||
|
|
||||||
|
def usage():
|
||||||
|
print('usage: mixertest.py [-c <card>] [ <control>[,<value>]]',
|
||||||
|
file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
kwargs = {}
|
||||||
|
opts, args = getopt.getopt(sys.argv[1:], 'c:d:?h')
|
||||||
|
for o, a in opts:
|
||||||
|
if o == '-c':
|
||||||
|
kwargs = { 'cardindex': int(a) }
|
||||||
|
elif o == '-d':
|
||||||
|
kwargs = { 'device': a }
|
||||||
|
else:
|
||||||
|
usage()
|
||||||
|
|
||||||
|
list_cards()
|
||||||
|
|
||||||
|
if not len(args):
|
||||||
|
list_mixers(kwargs)
|
||||||
|
elif len(args) == 1:
|
||||||
|
show_mixer(args[0], kwargs)
|
||||||
|
else:
|
||||||
|
set_mixer(args[0], args[1], kwargs)
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Contributed by Stein Magnus Jodal
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import resource
|
||||||
|
import time
|
||||||
|
|
||||||
|
import alsaaudio
|
||||||
|
|
||||||
|
seconds = 0
|
||||||
|
max_rss = 0
|
||||||
|
device = alsaaudio.PCM()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
device.write(b'\x00' * 44100)
|
||||||
|
time.sleep(1)
|
||||||
|
seconds += 1
|
||||||
|
if seconds % 10 == 0:
|
||||||
|
prev_rss = max_rss
|
||||||
|
max_rss = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
|
||||||
|
diff_rss = max_rss - prev_rss
|
||||||
|
print('After %ds: max RSS %d kB, increased %d kB' % (
|
||||||
|
seconds, max_rss, diff_rss))
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
## playbacktest.py
|
||||||
|
##
|
||||||
|
## This is an example of a simple sound playback script.
|
||||||
|
##
|
||||||
|
## The script opens an ALSA pcm for sound playback. Set
|
||||||
|
## various attributes of the device. It then reads data
|
||||||
|
## from stdin and writes it to the device.
|
||||||
|
##
|
||||||
|
## To test it out do the following:
|
||||||
|
## python recordtest.py out.raw # talk to the microphone
|
||||||
|
## python playbacktest.py out.raw
|
||||||
|
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import getopt
|
||||||
|
import alsaaudio
|
||||||
|
|
||||||
|
def usage():
|
||||||
|
print('usage: playbacktest.py [-d <device>] <file>', file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
device = 'default'
|
||||||
|
|
||||||
|
opts, args = getopt.getopt(sys.argv[1:], 'd:')
|
||||||
|
for o, a in opts:
|
||||||
|
if o == '-d':
|
||||||
|
device = a
|
||||||
|
|
||||||
|
if not args:
|
||||||
|
usage()
|
||||||
|
|
||||||
|
f = open(args[0], 'rb')
|
||||||
|
|
||||||
|
# Open the device in playback mode.
|
||||||
|
out = alsaaudio.PCM(alsaaudio.PCM_PLAYBACK, device=device)
|
||||||
|
|
||||||
|
# Set attributes: Mono, 44100 Hz, 16 bit little endian frames
|
||||||
|
out.setchannels(1)
|
||||||
|
out.setrate(44100)
|
||||||
|
out.setformat(alsaaudio.PCM_FORMAT_S16_LE)
|
||||||
|
|
||||||
|
# The period size controls the internal number of frames per period.
|
||||||
|
# The significance of this parameter is documented in the ALSA api.
|
||||||
|
out.setperiodsize(160)
|
||||||
|
|
||||||
|
# Read data from stdin
|
||||||
|
data = f.read(320)
|
||||||
|
while data:
|
||||||
|
out.write(data)
|
||||||
|
data = f.read(320)
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# Simple test script that plays (some) wav files
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import wave
|
||||||
|
import getopt
|
||||||
|
import alsaaudio
|
||||||
|
|
||||||
|
def play(device, f):
|
||||||
|
|
||||||
|
print('%d channels, %d sampling rate\n' % (f.getnchannels(),
|
||||||
|
f.getframerate()))
|
||||||
|
# Set attributes
|
||||||
|
device.setchannels(f.getnchannels())
|
||||||
|
device.setrate(f.getframerate())
|
||||||
|
|
||||||
|
# 8bit is unsigned in wav files
|
||||||
|
if f.getsampwidth() == 1:
|
||||||
|
device.setformat(alsaaudio.PCM_FORMAT_U8)
|
||||||
|
# Otherwise we assume signed data, little endian
|
||||||
|
elif f.getsampwidth() == 2:
|
||||||
|
device.setformat(alsaaudio.PCM_FORMAT_S16_LE)
|
||||||
|
elif f.getsampwidth() == 3:
|
||||||
|
device.setformat(alsaaudio.PCM_FORMAT_S24_3LE)
|
||||||
|
elif f.getsampwidth() == 4:
|
||||||
|
device.setformat(alsaaudio.PCM_FORMAT_S32_LE)
|
||||||
|
else:
|
||||||
|
raise ValueError('Unsupported format')
|
||||||
|
|
||||||
|
periodsize = f.getframerate() / 8
|
||||||
|
|
||||||
|
device.setperiodsize(periodsize)
|
||||||
|
|
||||||
|
data = f.readframes(periodsize)
|
||||||
|
while data:
|
||||||
|
# Read data from stdin
|
||||||
|
device.write(data)
|
||||||
|
data = f.readframes(periodsize)
|
||||||
|
|
||||||
|
|
||||||
|
def usage():
|
||||||
|
print('usage: playwav.py [-d <device>] <file>', file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
device = 'default'
|
||||||
|
|
||||||
|
opts, args = getopt.getopt(sys.argv[1:], 'd:')
|
||||||
|
for o, a in opts:
|
||||||
|
if o == '-d':
|
||||||
|
device = a
|
||||||
|
|
||||||
|
if not args:
|
||||||
|
usage()
|
||||||
|
|
||||||
|
f = wave.open(args[0], 'rb')
|
||||||
|
device = alsaaudio.PCM(device=device)
|
||||||
|
|
||||||
|
play(device, f)
|
||||||
|
|
||||||
|
f.close()
|
||||||
@@ -1,96 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
<html lang="en" data-content_root="./">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Python Module Index — alsaaudio documentation 0.11.0 documentation</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=fa44fd50" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css?v=cb25574f" />
|
|
||||||
<script src="_static/documentation_options.js?v=f3b36f1a"></script>
|
|
||||||
<script src="_static/doctools.js?v=9a2dae69"></script>
|
|
||||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
|
||||||
<link rel="index" title="Index" href="genindex.html" />
|
|
||||||
<link rel="search" title="Search" href="search.html" />
|
|
||||||
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
|
|
||||||
|
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" />
|
|
||||||
|
|
||||||
|
|
||||||
<script>
|
|
||||||
DOCUMENTATION_OPTIONS.COLLAPSE_INDEX = true;
|
|
||||||
</script>
|
|
||||||
|
|
||||||
|
|
||||||
</head><body>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="document">
|
|
||||||
<div class="documentwrapper">
|
|
||||||
<div class="bodywrapper">
|
|
||||||
|
|
||||||
|
|
||||||
<div class="body" role="main">
|
|
||||||
|
|
||||||
|
|
||||||
<h1>Python Module Index</h1>
|
|
||||||
|
|
||||||
<div class="modindex-jumpbox">
|
|
||||||
<a href="#cap-a"><strong>a</strong></a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<table class="indextable modindextable">
|
|
||||||
<tr class="pcap"><td></td><td> </td><td></td></tr>
|
|
||||||
<tr class="cap" id="cap-a"><td></td><td>
|
|
||||||
<strong>a</strong></td><td></td></tr>
|
|
||||||
<tr>
|
|
||||||
<td></td>
|
|
||||||
<td>
|
|
||||||
<a href="libalsaaudio.html#module-alsaaudio"><code class="xref">alsaaudio</code></a> <em>(Linux)</em></td><td>
|
|
||||||
<em></em></td></tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
|
|
||||||
<div class="sphinxsidebarwrapper"><div class="relations">
|
|
||||||
<h3>Related Topics</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="index.html">Documentation overview</a><ul>
|
|
||||||
</ul></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<search id="searchbox" style="display: none" role="search">
|
|
||||||
<h3 id="searchlabel">Quick search</h3>
|
|
||||||
<div class="searchformwrapper">
|
|
||||||
<form class="search" action="search.html" method="get">
|
|
||||||
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
|
||||||
<input type="submit" value="Go" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</search>
|
|
||||||
<script>document.getElementById('searchbox').style.display = "block"</script>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="clearer"></div>
|
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
©2017, Lars Immisch & Casper Wilstrup.
|
|
||||||
|
|
||||||
|
|
|
||||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 7.3.7</a>
|
|
||||||
& <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -1,203 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
<html lang="en" data-content_root="./">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
|
|
||||||
<title>Introduction — alsaaudio documentation 0.11.0 documentation</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=fa44fd50" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css?v=cb25574f" />
|
|
||||||
<script src="_static/documentation_options.js?v=f3b36f1a"></script>
|
|
||||||
<script src="_static/doctools.js?v=9a2dae69"></script>
|
|
||||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
|
||||||
<link rel="index" title="Index" href="genindex.html" />
|
|
||||||
<link rel="search" title="Search" href="search.html" />
|
|
||||||
<link rel="next" title="PCM Terminology and Concepts" href="terminology.html" />
|
|
||||||
<link rel="prev" title="alsaaudio documentation" href="index.html" />
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
|
|
||||||
|
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" />
|
|
||||||
|
|
||||||
</head><body>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="document">
|
|
||||||
<div class="documentwrapper">
|
|
||||||
<div class="bodywrapper">
|
|
||||||
|
|
||||||
|
|
||||||
<div class="body" role="main">
|
|
||||||
|
|
||||||
<section id="introduction">
|
|
||||||
<h1>Introduction<a class="headerlink" href="#introduction" title="Link to this heading">¶</a></h1>
|
|
||||||
<dl class="field-list simple">
|
|
||||||
<dt class="field-odd">Author<span class="colon">:</span></dt>
|
|
||||||
<dd class="field-odd"><p>Casper Wilstrup <<a class="reference external" href="mailto:cwi%40aves.dk">cwi<span>@</span>aves<span>.</span>dk</a>></p>
|
|
||||||
</dd>
|
|
||||||
<dt class="field-even">Author<span class="colon">:</span></dt>
|
|
||||||
<dd class="field-even"><p>Lars Immisch <<a class="reference external" href="mailto:lars%40ibp.de">lars<span>@</span>ibp<span>.</span>de</a>></p>
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
<p id="front">This software is licensed under the PSF license - the same one used by the
|
|
||||||
majority of the python distribution. Basically you can use it for anything you
|
|
||||||
wish (even commercial purposes). There is no warranty whatsoever.</p>
|
|
||||||
<aside class="topic">
|
|
||||||
<p class="topic-title">Abstract</p>
|
|
||||||
<p>This package contains wrappers for accessing the ALSA API from Python. It is
|
|
||||||
currently fairly complete for PCM devices and Mixer access. MIDI sequencer
|
|
||||||
support is low on our priority list, but volunteers are welcome.</p>
|
|
||||||
<p>If you find bugs in the wrappers please use the github issue tracker.
|
|
||||||
Please don’t send bug reports regarding ALSA specifically. There are several
|
|
||||||
bugs in this API, and those should be reported to the ALSA team - not me.</p>
|
|
||||||
</aside>
|
|
||||||
</section>
|
|
||||||
<section id="what-is-alsa">
|
|
||||||
<h1>What is ALSA<a class="headerlink" href="#what-is-alsa" title="Link to this heading">¶</a></h1>
|
|
||||||
<p>The Advanced Linux Sound Architecture (ALSA) provides audio and MIDI
|
|
||||||
functionality to the Linux operating system.</p>
|
|
||||||
<p>Logically ALSA consists of these components:</p>
|
|
||||||
<ul class="simple">
|
|
||||||
<li><p>A set of kernel drivers. — These drivers are responsible for handling the
|
|
||||||
physical sound hardware from within the Linux kernel, and have been the
|
|
||||||
standard sound implementation in Linux since kernel version 2.5</p></li>
|
|
||||||
<li><p>A kernel level API for manipulating the ALSA devices.</p></li>
|
|
||||||
<li><p>A user-space C library for simplified access to the sound hardware from
|
|
||||||
userspace applications. This library is called <em>libasound</em> and is required by
|
|
||||||
all ALSA capable applications.</p></li>
|
|
||||||
</ul>
|
|
||||||
<p>More information about ALSA may be found on the project homepage
|
|
||||||
<a class="reference external" href="http://www.alsa-project.org">http://www.alsa-project.org</a></p>
|
|
||||||
<section id="alsa-and-python">
|
|
||||||
<h2>ALSA and Python<a class="headerlink" href="#alsa-and-python" title="Link to this heading">¶</a></h2>
|
|
||||||
<p>The older Linux sound API (OSS) – which is now deprecated – is well supported
|
|
||||||
by the standard Python library, through the ossaudiodev module. No native ALSA
|
|
||||||
support exists in the standard library.</p>
|
|
||||||
<p>There are a few other “ALSA for Python” projects available, including at least
|
|
||||||
two different projects called pyAlsa. Neither of these seem to be under active
|
|
||||||
development at the time - and neither are very feature complete.</p>
|
|
||||||
<p>I wrote PyAlsaAudio to fill this gap. My long term goal is to have the module
|
|
||||||
included in the standard Python library, but that looks currently unlikely.</p>
|
|
||||||
<p>PyAlsaAudio has full support for sound capture, playback of sound, as well as
|
|
||||||
the ALSA Mixer API.</p>
|
|
||||||
<p>MIDI support is not available, and since I don’t own any MIDI hardware, it’s
|
|
||||||
difficult for me to implement it. Volunteers to work on this would be greatly
|
|
||||||
appreciated.</p>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<section id="installation">
|
|
||||||
<h1>Installation<a class="headerlink" href="#installation" title="Link to this heading">¶</a></h1>
|
|
||||||
<p>Note: the wrappers link with the alsasound library (from the alsa-lib package)
|
|
||||||
and need the ALSA headers for compilation. Verify that you have
|
|
||||||
/usr/lib/libasound.so and /usr/include/alsa (or similar paths) before building.</p>
|
|
||||||
<p><em>On Debian (and probably Ubuntu), install libasound2-dev.</em></p>
|
|
||||||
<p>Naturally you also need to use a kernel with proper ALSA support. This is the
|
|
||||||
default in Linux kernel 2.6 and later. If you are using kernel version 2.4 you
|
|
||||||
may need to install the ALSA patches yourself - although most distributions
|
|
||||||
ship with ALSA kernels.</p>
|
|
||||||
<p>To install, execute the following: —</p>
|
|
||||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ python setup.py build
|
|
||||||
</pre></div>
|
|
||||||
</div>
|
|
||||||
<p>And then as root: —</p>
|
|
||||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="c1"># python setup.py install</span>
|
|
||||||
</pre></div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section id="testing">
|
|
||||||
<h1>Testing<a class="headerlink" href="#testing" title="Link to this heading">¶</a></h1>
|
|
||||||
<p>Make sure that <code class="code docutils literal notranslate"><span class="pre">aplay</span></code> plays a file through the soundcard you want, then
|
|
||||||
try:</p>
|
|
||||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ python playwav.py <filename.wav>
|
|
||||||
</pre></div>
|
|
||||||
</div>
|
|
||||||
<p>If <code class="code docutils literal notranslate"><span class="pre">aplay</span></code> needs a device argument, like
|
|
||||||
<code class="code docutils literal notranslate"><span class="pre">aplay</span> <span class="pre">-D</span> <span class="pre">hw:CARD=sndrpihifiberry,DEV=0</span></code>, use:</p>
|
|
||||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ python playwav.py -d hw:CARD=sndrpihifiberry,DEV=0 <filename.wav>
|
|
||||||
</pre></div>
|
|
||||||
</div>
|
|
||||||
<p>To test PCM recordings (on your default soundcard), verify your
|
|
||||||
microphone works, then do:</p>
|
|
||||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ python recordtest.py -d <device> <filename>
|
|
||||||
</pre></div>
|
|
||||||
</div>
|
|
||||||
<p>Speak into the microphone, and interrupt the recording at any time
|
|
||||||
with <code class="docutils literal notranslate"><span class="pre">Ctl-C</span></code>.</p>
|
|
||||||
<p>Play back the recording with:</p>
|
|
||||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span>$ python playbacktest.py -d <device> <filename>
|
|
||||||
</pre></div>
|
|
||||||
</div>
|
|
||||||
<p>There is a minimal test suite in <code class="code docutils literal notranslate"><span class="pre">test.py</span></code>, but it is
|
|
||||||
a bit dependent on the ALSA configuration and may fail without indicating
|
|
||||||
a real problem.</p>
|
|
||||||
<p>If you find bugs/problems, please file a <a class="reference external" href="https://github.com/larsimmisch/pyalsaaudio/issues">bug report</a>.</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
|
|
||||||
<div class="sphinxsidebarwrapper">
|
|
||||||
<div>
|
|
||||||
<h3><a href="index.html">Table of Contents</a></h3>
|
|
||||||
<ul>
|
|
||||||
<li><a class="reference internal" href="#">Introduction</a></li>
|
|
||||||
<li><a class="reference internal" href="#what-is-alsa">What is ALSA</a><ul>
|
|
||||||
<li><a class="reference internal" href="#alsa-and-python">ALSA and Python</a></li>
|
|
||||||
</ul>
|
|
||||||
</li>
|
|
||||||
<li><a class="reference internal" href="#installation">Installation</a></li>
|
|
||||||
<li><a class="reference internal" href="#testing">Testing</a></li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
</div><div class="relations">
|
|
||||||
<h3>Related Topics</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="index.html">Documentation overview</a><ul>
|
|
||||||
<li>Previous: <a href="index.html" title="previous chapter">alsaaudio documentation</a></li>
|
|
||||||
<li>Next: <a href="terminology.html" title="next chapter">PCM Terminology and Concepts</a></li>
|
|
||||||
</ul></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div role="note" aria-label="source link">
|
|
||||||
<h3>This Page</h3>
|
|
||||||
<ul class="this-page-menu">
|
|
||||||
<li><a href="_sources/pyalsaaudio.rst.txt"
|
|
||||||
rel="nofollow">Show Source</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<search id="searchbox" style="display: none" role="search">
|
|
||||||
<h3 id="searchlabel">Quick search</h3>
|
|
||||||
<div class="searchformwrapper">
|
|
||||||
<form class="search" action="search.html" method="get">
|
|
||||||
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
|
||||||
<input type="submit" value="Go" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</search>
|
|
||||||
<script>document.getElementById('searchbox').style.display = "block"</script>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="clearer"></div>
|
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
©2017, Lars Immisch & Casper Wilstrup.
|
|
||||||
|
|
||||||
|
|
|
||||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 7.3.7</a>
|
|
||||||
& <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
|
|
||||||
|
|
||||||
|
|
|
||||||
<a href="_sources/pyalsaaudio.rst.txt"
|
|
||||||
rel="nofollow">Page source</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
## recordtest.py
|
||||||
|
##
|
||||||
|
## This is an example of a simple sound capture script.
|
||||||
|
##
|
||||||
|
## The script opens an ALSA pcm device for sound capture, sets
|
||||||
|
## various attributes of the capture, and reads in a loop,
|
||||||
|
## writing the data to standard out.
|
||||||
|
##
|
||||||
|
## To test it out do the following:
|
||||||
|
## python recordtest.py out.raw # talk to the microphone
|
||||||
|
## aplay -r 8000 -f S16_LE -c 1 out.raw
|
||||||
|
|
||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
import getopt
|
||||||
|
import alsaaudio
|
||||||
|
|
||||||
|
def usage():
|
||||||
|
print('usage: recordtest.py [-d <device>] <file>', file=sys.stderr)
|
||||||
|
sys.exit(2)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
device = 'default'
|
||||||
|
|
||||||
|
opts, args = getopt.getopt(sys.argv[1:], 'd:')
|
||||||
|
for o, a in opts:
|
||||||
|
if o == '-d':
|
||||||
|
device = a
|
||||||
|
|
||||||
|
if not args:
|
||||||
|
usage()
|
||||||
|
|
||||||
|
f = open(args[0], 'wb')
|
||||||
|
|
||||||
|
# Open the device in nonblocking capture mode. The last argument could
|
||||||
|
# just as well have been zero for blocking mode. Then we could have
|
||||||
|
# left out the sleep call in the bottom of the loop
|
||||||
|
inp = alsaaudio.PCM(alsaaudio.PCM_CAPTURE, alsaaudio.PCM_NONBLOCK, device=device)
|
||||||
|
|
||||||
|
# Set attributes: Mono, 44100 Hz, 16 bit little endian samples
|
||||||
|
inp.setchannels(1)
|
||||||
|
inp.setrate(44100)
|
||||||
|
inp.setformat(alsaaudio.PCM_FORMAT_S16_LE)
|
||||||
|
|
||||||
|
# The period size controls the internal number of frames per period.
|
||||||
|
# The significance of this parameter is documented in the ALSA api.
|
||||||
|
# For our purposes, it is suficcient to know that reads from the device
|
||||||
|
# will return this many frames. Each frame being 2 bytes long.
|
||||||
|
# This means that the reads below will return either 320 bytes of data
|
||||||
|
# or 0 bytes of data. The latter is possible because we are in nonblocking
|
||||||
|
# mode.
|
||||||
|
inp.setperiodsize(160)
|
||||||
|
|
||||||
|
loops = 1000000
|
||||||
|
while loops > 0:
|
||||||
|
loops -= 1
|
||||||
|
# Read data from device
|
||||||
|
l, data = inp.read()
|
||||||
|
|
||||||
|
if l:
|
||||||
|
f.write(data)
|
||||||
|
time.sleep(.001)
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
<html lang="en" data-content_root="./">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>Search — alsaaudio documentation 0.11.0 documentation</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=fa44fd50" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css?v=cb25574f" />
|
|
||||||
|
|
||||||
<script src="_static/documentation_options.js?v=f3b36f1a"></script>
|
|
||||||
<script src="_static/doctools.js?v=9a2dae69"></script>
|
|
||||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
|
||||||
<script src="_static/searchtools.js"></script>
|
|
||||||
<script src="_static/language_data.js"></script>
|
|
||||||
<link rel="index" title="Index" href="genindex.html" />
|
|
||||||
<link rel="search" title="Search" href="#" />
|
|
||||||
<script src="searchindex.js" defer="defer"></script>
|
|
||||||
<meta name="robots" content="noindex" />
|
|
||||||
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
|
|
||||||
|
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" />
|
|
||||||
|
|
||||||
|
|
||||||
</head><body>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="document">
|
|
||||||
<div class="documentwrapper">
|
|
||||||
<div class="bodywrapper">
|
|
||||||
|
|
||||||
|
|
||||||
<div class="body" role="main">
|
|
||||||
|
|
||||||
<h1 id="search-documentation">Search</h1>
|
|
||||||
|
|
||||||
<noscript>
|
|
||||||
<div class="admonition warning">
|
|
||||||
<p>
|
|
||||||
Please activate JavaScript to enable the search
|
|
||||||
functionality.
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</noscript>
|
|
||||||
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Searching for multiple words only shows matches that contain
|
|
||||||
all words.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
|
|
||||||
<form action="" method="get">
|
|
||||||
<input type="text" name="q" aria-labelledby="search-documentation" value="" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
|
||||||
<input type="submit" value="search" />
|
|
||||||
<span id="search-progress" style="padding-left: 10px"></span>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
|
|
||||||
<div id="search-results"></div>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
|
|
||||||
<div class="sphinxsidebarwrapper"><div class="relations">
|
|
||||||
<h3>Related Topics</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="index.html">Documentation overview</a><ul>
|
|
||||||
</ul></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="clearer"></div>
|
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
©2017, Lars Immisch & Casper Wilstrup.
|
|
||||||
|
|
||||||
|
|
|
||||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 7.3.7</a>
|
|
||||||
& <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
'''This package contains wrappers for accessing the ALSA API from Python.
|
||||||
|
It is fairly complete for PCM devices and Mixer access.
|
||||||
|
'''
|
||||||
|
|
||||||
|
from setuptools import setup
|
||||||
|
from setuptools.extension import Extension
|
||||||
|
from sys import version
|
||||||
|
|
||||||
|
pyalsa_version = '0.8.4'
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
setup(
|
||||||
|
name = 'pyalsaaudio',
|
||||||
|
version = pyalsa_version,
|
||||||
|
description = 'ALSA bindings',
|
||||||
|
long_description = __doc__,
|
||||||
|
author = 'Casper Wilstrup',
|
||||||
|
author_email='cwi@aves.dk',
|
||||||
|
maintainer = 'Lars Immisch',
|
||||||
|
maintainer_email = 'lars@ibp.de',
|
||||||
|
license='PSF',
|
||||||
|
platforms=['posix'],
|
||||||
|
url='http://larsimmisch.github.io/pyalsaaudio/',
|
||||||
|
classifiers = [
|
||||||
|
'Development Status :: 4 - Beta',
|
||||||
|
'Intended Audience :: Developers',
|
||||||
|
'License :: OSI Approved :: Python Software Foundation License',
|
||||||
|
'Operating System :: POSIX :: Linux',
|
||||||
|
'Programming Language :: Python :: 2',
|
||||||
|
'Programming Language :: Python :: 3',
|
||||||
|
'Topic :: Multimedia :: Sound/Audio',
|
||||||
|
'Topic :: Multimedia :: Sound/Audio :: Mixers',
|
||||||
|
'Topic :: Multimedia :: Sound/Audio :: Players',
|
||||||
|
'Topic :: Multimedia :: Sound/Audio :: Capture/Recording',
|
||||||
|
],
|
||||||
|
ext_modules=[Extension('alsaaudio',['alsaaudio.c'],
|
||||||
|
libraries=['asound'])]
|
||||||
|
)
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
|
|
||||||
<html lang="en" data-content_root="./">
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" /><meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
||||||
|
|
||||||
<title>PCM Terminology and Concepts — alsaaudio documentation 0.11.0 documentation</title>
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/pygments.css?v=fa44fd50" />
|
|
||||||
<link rel="stylesheet" type="text/css" href="_static/alabaster.css?v=cb25574f" />
|
|
||||||
<script src="_static/documentation_options.js?v=f3b36f1a"></script>
|
|
||||||
<script src="_static/doctools.js?v=9a2dae69"></script>
|
|
||||||
<script src="_static/sphinx_highlight.js?v=dc90522c"></script>
|
|
||||||
<link rel="index" title="Index" href="genindex.html" />
|
|
||||||
<link rel="search" title="Search" href="search.html" />
|
|
||||||
<link rel="next" title="alsaaudio" href="libalsaaudio.html" />
|
|
||||||
<link rel="prev" title="Introduction" href="pyalsaaudio.html" />
|
|
||||||
|
|
||||||
<link rel="stylesheet" href="_static/custom.css" type="text/css" />
|
|
||||||
|
|
||||||
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=0.9, maximum-scale=0.9" />
|
|
||||||
|
|
||||||
</head><body>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="document">
|
|
||||||
<div class="documentwrapper">
|
|
||||||
<div class="bodywrapper">
|
|
||||||
|
|
||||||
|
|
||||||
<div class="body" role="main">
|
|
||||||
|
|
||||||
<section id="pcm-terminology-and-concepts">
|
|
||||||
<h1>PCM Terminology and Concepts<a class="headerlink" href="#pcm-terminology-and-concepts" title="Link to this heading">¶</a></h1>
|
|
||||||
<p>In order to use PCM devices it is useful to be familiar with some concepts and
|
|
||||||
terminology.</p>
|
|
||||||
<dl>
|
|
||||||
<dt>Sample</dt><dd><p>PCM audio, whether it is input or output, consists of <em>samples</em>.
|
|
||||||
A single sample represents the amplitude of one channel of sound
|
|
||||||
at a certain point in time. A lot of individual samples are
|
|
||||||
necessary to represent actual sound; for CD audio, 44100 samples
|
|
||||||
are taken every second.</p>
|
|
||||||
<p>Samples can be of many different sizes, ranging from 8 bit to 64
|
|
||||||
bit precision. The specific format of each sample can also vary -
|
|
||||||
they can be big endian byte integers, little endian byte integers, or
|
|
||||||
floating point numbers.</p>
|
|
||||||
<p>Musically, the sample size determines the dynamic range. The
|
|
||||||
dynamic range is the difference between the quietest and the
|
|
||||||
loudest signal that can be reproduced.</p>
|
|
||||||
</dd>
|
|
||||||
<dt>Frame</dt><dd><p>A frame consists of exactly one sample per channel. If there is only one
|
|
||||||
channel (Mono sound) a frame is simply a single sample. If the sound is
|
|
||||||
stereo, each frame consists of two samples, etc.</p>
|
|
||||||
</dd>
|
|
||||||
<dt>Frame size</dt><dd><p>This is the size in bytes of each frame. This can vary a lot: if each sample
|
|
||||||
is 8 bits, and we’re handling mono sound, the frame size is one byte.
|
|
||||||
For six channel audio with 64 bit floating point samples, the frame size
|
|
||||||
is 48 bytes.</p>
|
|
||||||
</dd>
|
|
||||||
<dt>Rate</dt><dd><p>PCM sound consists of a flow of sound frames. The sound rate controls how
|
|
||||||
often the current frame is replaced. For example, a rate of 8000 Hz
|
|
||||||
means that a new frame is played or captured 8000 times per second.</p>
|
|
||||||
</dd>
|
|
||||||
<dt>Data rate</dt><dd><p>This is the number of bytes which must be consumed or provided per
|
|
||||||
second at a certain frame size and rate.</p>
|
|
||||||
<p>8000 Hz mono sound with 8 bit (1 byte) samples has a data rate of
|
|
||||||
8000 * 1 * 1 = 8 kb/s or 64kbit/s. This is typically used for telephony.</p>
|
|
||||||
<p>At the other end of the scale, 96000 Hz, 6 channel sound with 64
|
|
||||||
bit (8 bytes) samples has a data rate of 96000 * 6 * 8 = 4608
|
|
||||||
kb/s (almost 5 MB sound data per second).</p>
|
|
||||||
<p>If the data rate requirement is not met, an overrun (on capture) or
|
|
||||||
underrun (on playback) occurs; the term “xrun” is used to refer to
|
|
||||||
either event.</p>
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
<dl id="term-period">
|
|
||||||
<dt>Period</dt><dd><p>The CPU processes sample data in chunks of frames, so-called periods
|
|
||||||
(also called fragments by some systems). The operating system kernel’s
|
|
||||||
sample buffer must hold at least two periods (at any given time, one
|
|
||||||
is processed by the sound hardware, and one by the CPU).</p>
|
|
||||||
<p>The completion of a <em>period</em> triggers a CPU interrupt, which causes
|
|
||||||
processing and context switching overhead. Therefore, a smaller period
|
|
||||||
size causes higher CPU resource usage at a given data rate.</p>
|
|
||||||
<p>A bigger size of the <em>buffer</em> improves the system’s resilience to xruns.
|
|
||||||
The buffer being split into a bigger number of smaller periods also does
|
|
||||||
that, as it allows it to be drained / topped up sooner.</p>
|
|
||||||
<p>On the other hand, a bigger size of the <em>buffer</em> also increases the
|
|
||||||
playback latency, that is, the time it takes for a frame from being
|
|
||||||
sent out by the application to being actually audible.</p>
|
|
||||||
<p>Similarly, a bigger <em>period</em> size increases the capture latency.</p>
|
|
||||||
<p>The trade-off between latency, xrun resilience, and resource usage
|
|
||||||
must be made depending on the application.</p>
|
|
||||||
</dd>
|
|
||||||
<dt>Period size</dt><dd><p>This is the size of each period in frames. <em>Not bytes, but frames!</em>
|
|
||||||
In <a class="reference internal" href="libalsaaudio.html#module-alsaaudio" title="alsaaudio (Linux)"><code class="xref py py-mod docutils literal notranslate"><span class="pre">alsaaudio</span></code></a> the period size is set directly, and it is
|
|
||||||
therefore important to understand the significance of this
|
|
||||||
number. If the period size is configured to for example 32,
|
|
||||||
each write should contain exactly 32 frames of sound data, and each
|
|
||||||
read will return either 32 frames of data or nothing at all.</p>
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
<dl id="term-sample-size">
|
|
||||||
<dt>Sample size</dt><dd><p>Each sample takes <em>physical_bits</em> of space. <em>nominal_bits</em> tells
|
|
||||||
how many least significant bits are used. This is the bit depth
|
|
||||||
in the format name and sometimes called just <em>sample bits</em> or
|
|
||||||
<em>format width</em>. <em>significant_bits</em> tells how many most significant
|
|
||||||
bits of the <em>nominal_bits</em> are used by the sample. This can be thought
|
|
||||||
of as the <em>sample resolution</em>. This is visualized as follows:</p>
|
|
||||||
<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">MSB</span> <span class="o">|</span><span class="mi">00000000</span> <span class="n">XXXXXXXX</span> <span class="n">XXXXXXXX</span> <span class="mi">00000000</span><span class="o">|</span> <span class="n">LSB</span>
|
|
||||||
<span class="o">|--</span><span class="n">significant</span><span class="o">--|</span>
|
|
||||||
<span class="o">|---------</span><span class="n">nominal</span><span class="o">---------|</span>
|
|
||||||
<span class="o">|-------------</span><span class="n">physical</span><span class="o">--------------|</span>
|
|
||||||
</pre></div>
|
|
||||||
</div>
|
|
||||||
</dd>
|
|
||||||
</dl>
|
|
||||||
<p>Once you understand these concepts, you will be ready to use the PCM API. Read
|
|
||||||
on.</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="sphinxsidebar" role="navigation" aria-label="main navigation">
|
|
||||||
<div class="sphinxsidebarwrapper"><div class="relations">
|
|
||||||
<h3>Related Topics</h3>
|
|
||||||
<ul>
|
|
||||||
<li><a href="index.html">Documentation overview</a><ul>
|
|
||||||
<li>Previous: <a href="pyalsaaudio.html" title="previous chapter">Introduction</a></li>
|
|
||||||
<li>Next: <a href="libalsaaudio.html" title="next chapter"><code class="xref py py-mod docutils literal notranslate"><span class="pre">alsaaudio</span></code></a></li>
|
|
||||||
</ul></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div role="note" aria-label="source link">
|
|
||||||
<h3>This Page</h3>
|
|
||||||
<ul class="this-page-menu">
|
|
||||||
<li><a href="_sources/terminology.rst.txt"
|
|
||||||
rel="nofollow">Show Source</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<search id="searchbox" style="display: none" role="search">
|
|
||||||
<h3 id="searchlabel">Quick search</h3>
|
|
||||||
<div class="searchformwrapper">
|
|
||||||
<form class="search" action="search.html" method="get">
|
|
||||||
<input type="text" name="q" aria-labelledby="searchlabel" autocomplete="off" autocorrect="off" autocapitalize="off" spellcheck="false"/>
|
|
||||||
<input type="submit" value="Go" />
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</search>
|
|
||||||
<script>document.getElementById('searchbox').style.display = "block"</script>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="clearer"></div>
|
|
||||||
</div>
|
|
||||||
<div class="footer">
|
|
||||||
©2017, Lars Immisch & Casper Wilstrup.
|
|
||||||
|
|
||||||
|
|
|
||||||
Powered by <a href="http://sphinx-doc.org/">Sphinx 7.3.7</a>
|
|
||||||
& <a href="https://github.com/bitprophet/alabaster">Alabaster 0.7.12</a>
|
|
||||||
|
|
||||||
|
|
|
||||||
<a href="_sources/terminology.rst.txt"
|
|
||||||
rel="nofollow">Page source</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
# These are internal tests. They shouldn't fail, but they don't cover all
|
||||||
|
# of the ALSA API. Most importantly PCM.read and PCM.write are missing.
|
||||||
|
# These need to be tested by playbacktest.py and recordtest.py
|
||||||
|
|
||||||
|
# In case of a problem, run these tests. If they fail, file a bug report on
|
||||||
|
# http://github.com/larsimmisch/pyalsaaudio/issues
|
||||||
|
|
||||||
|
import unittest
|
||||||
|
import alsaaudio
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
# we can't test read and write well - these are tested otherwise
|
||||||
|
PCMMethods = [('pcmtype', None),
|
||||||
|
('pcmmode', None),
|
||||||
|
('cardname', None),
|
||||||
|
('setchannels', (2,)),
|
||||||
|
('setrate', (44100,)),
|
||||||
|
('setformat', (alsaaudio.PCM_FORMAT_S8,)),
|
||||||
|
('setperiodsize', (320,))]
|
||||||
|
|
||||||
|
# A clever test would look at the Mixer capabilities and selectively run the
|
||||||
|
# omitted tests, but I am too tired for that.
|
||||||
|
|
||||||
|
MixerMethods = [('cardname', None),
|
||||||
|
('mixer', None),
|
||||||
|
('mixerid', None),
|
||||||
|
('switchcap', None),
|
||||||
|
('volumecap', None),
|
||||||
|
('getvolume', None),
|
||||||
|
('getrange', None),
|
||||||
|
('getenum', None),
|
||||||
|
# ('getmute', None),
|
||||||
|
# ('getrec', None),
|
||||||
|
# ('setvolume', (60,)),
|
||||||
|
# ('setmute', (0,))
|
||||||
|
# ('setrec', (0')),
|
||||||
|
]
|
||||||
|
|
||||||
|
class MixerTest(unittest.TestCase):
|
||||||
|
"""Test Mixer objects"""
|
||||||
|
|
||||||
|
def testMixer(self):
|
||||||
|
"""Open the default Mixers and the Mixers on every card"""
|
||||||
|
|
||||||
|
for c in alsaaudio.card_indexes():
|
||||||
|
mixers = alsaaudio.mixers(cardindex=c)
|
||||||
|
|
||||||
|
for m in mixers:
|
||||||
|
mixer = alsaaudio.Mixer(m, cardindex=c)
|
||||||
|
mixer.close()
|
||||||
|
|
||||||
|
def testMixerAll(self):
|
||||||
|
"Run common Mixer methods on an open object"
|
||||||
|
|
||||||
|
mixers = alsaaudio.mixers()
|
||||||
|
mixer = alsaaudio.Mixer(mixers[0])
|
||||||
|
|
||||||
|
for m, a in MixerMethods:
|
||||||
|
f = alsaaudio.Mixer.__dict__[m]
|
||||||
|
if a is None:
|
||||||
|
f(mixer)
|
||||||
|
else:
|
||||||
|
f(mixer, *a)
|
||||||
|
|
||||||
|
mixer.close()
|
||||||
|
|
||||||
|
def testMixerClose(self):
|
||||||
|
"""Run common Mixer methods on a closed object and verify it raises an
|
||||||
|
error"""
|
||||||
|
|
||||||
|
mixers = alsaaudio.mixers()
|
||||||
|
mixer = alsaaudio.Mixer(mixers[0])
|
||||||
|
mixer.close()
|
||||||
|
|
||||||
|
for m, a in MixerMethods:
|
||||||
|
f = alsaaudio.Mixer.__dict__[m]
|
||||||
|
if a is None:
|
||||||
|
self.assertRaises(alsaaudio.ALSAAudioError, f, mixer)
|
||||||
|
else:
|
||||||
|
self.assertRaises(alsaaudio.ALSAAudioError, f, mixer, *a)
|
||||||
|
|
||||||
|
class PCMTest(unittest.TestCase):
|
||||||
|
"""Test PCM objects"""
|
||||||
|
|
||||||
|
def testPCM(self):
|
||||||
|
"Open a PCM object on every card"
|
||||||
|
|
||||||
|
for c in alsaaudio.card_indexes():
|
||||||
|
pcm = alsaaudio.PCM(cardindex=c)
|
||||||
|
pcm.close()
|
||||||
|
|
||||||
|
def testPCMAll(self):
|
||||||
|
"Run all PCM methods on an open object"
|
||||||
|
|
||||||
|
pcm = alsaaudio.PCM()
|
||||||
|
|
||||||
|
for m, a in PCMMethods:
|
||||||
|
f = alsaaudio.PCM.__dict__[m]
|
||||||
|
if a is None:
|
||||||
|
f(pcm)
|
||||||
|
else:
|
||||||
|
f(pcm, *a)
|
||||||
|
|
||||||
|
pcm.close()
|
||||||
|
|
||||||
|
def testPCMClose(self):
|
||||||
|
"Run all PCM methods on a closed object and verify it raises an error"
|
||||||
|
|
||||||
|
pcm = alsaaudio.PCM()
|
||||||
|
pcm.close()
|
||||||
|
|
||||||
|
for m, a in PCMMethods:
|
||||||
|
f = alsaaudio.PCM.__dict__[m]
|
||||||
|
if a is None:
|
||||||
|
self.assertRaises(alsaaudio.ALSAAudioError, f, pcm)
|
||||||
|
else:
|
||||||
|
self.assertRaises(alsaaudio.ALSAAudioError, f, pcm, *a)
|
||||||
|
|
||||||
|
def testPCMDeprecated(self):
|
||||||
|
with warnings.catch_warnings(record=True) as w:
|
||||||
|
# Cause all warnings to always be triggered.
|
||||||
|
warnings.simplefilter("always")
|
||||||
|
|
||||||
|
try:
|
||||||
|
pcm = alsaaudio.PCM(card='default')
|
||||||
|
except alsaaudio.ALSAAudioError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Verify we got a DepreciationWarning
|
||||||
|
assert len(w) == 1
|
||||||
|
assert issubclass(w[-1].category, DeprecationWarning)
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
unittest.main()
|
||||||