adafruit-circuitpython-flac 1.0.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- adafruit_circuitpython_flac-1.0.2.dist-info/METADATA +176 -0
- adafruit_circuitpython_flac-1.0.2.dist-info/RECORD +14 -0
- adafruit_circuitpython_flac-1.0.2.dist-info/WHEEL +5 -0
- adafruit_circuitpython_flac-1.0.2.dist-info/licenses/LICENSE +21 -0
- adafruit_circuitpython_flac-1.0.2.dist-info/top_level.txt +1 -0
- adafruit_flac/__init__.py +1069 -0
- adafruit_flac/flac_kernel.armv6m.mpy +0 -0
- adafruit_flac/flac_kernel.armv6m.mpy.license +3 -0
- adafruit_flac/flac_kernel.armv7emsp.mpy +0 -0
- adafruit_flac/flac_kernel.armv7emsp.mpy.license +3 -0
- adafruit_flac/flac_kernel.rv32imc.mpy +0 -0
- adafruit_flac/flac_kernel.rv32imc.mpy.license +3 -0
- adafruit_flac/flac_kernel.xtensawin.mpy +0 -0
- adafruit_flac/flac_kernel.xtensawin.mpy.license +3 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: adafruit-circuitpython-flac
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: FLAC decoder and player for CircuitPython, with a turbo (viper) kernel
|
|
5
|
+
Author-email: Adafruit Industries <circuitpython@adafruit.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/adafruit/Adafruit_CircuitPython_FLAC
|
|
8
|
+
Keywords: adafruit,blinka,circuitpython,micropython,flac,audio,decoder,lossless,music,player
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
11
|
+
Classifier: Topic :: Software Development :: Embedded Systems
|
|
12
|
+
Classifier: Topic :: System :: Hardware
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Description-Content-Type: text/x-rst
|
|
15
|
+
License-File: LICENSE
|
|
16
|
+
Requires-Dist: Adafruit-Blinka
|
|
17
|
+
Provides-Extra: optional
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
Introduction
|
|
21
|
+
============
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
.. image:: https://readthedocs.org/projects/adafruit-circuitpython-flac/badge/?version=latest
|
|
25
|
+
:target: https://docs.circuitpython.org/projects/flac/en/latest/
|
|
26
|
+
:alt: Documentation Status
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
.. image:: https://raw.githubusercontent.com/adafruit/Adafruit_CircuitPython_Bundle/main/badges/adafruit_discord.svg
|
|
30
|
+
:target: https://adafru.it/discord
|
|
31
|
+
:alt: Discord
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
.. image:: https://github.com/adafruit/Adafruit_CircuitPython_FLAC/workflows/Build%20CI/badge.svg
|
|
35
|
+
:target: https://github.com/adafruit/Adafruit_CircuitPython_FLAC/actions
|
|
36
|
+
:alt: Build Status
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
.. image:: https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json
|
|
40
|
+
:target: https://github.com/astral-sh/ruff
|
|
41
|
+
:alt: Code Style: Ruff
|
|
42
|
+
|
|
43
|
+
Play FLAC files on CircuitPython. ``FLACDecoder`` decodes FLAC (lossless) audio to 16-bit PCM,
|
|
44
|
+
and ``FLACPlayer`` streams it to an ``I2SOut``, ``PWMAudioOut`` or ``AudioOut`` while your code
|
|
45
|
+
keeps running. On an RP2350 it plays 16-bit/44.1 kHz and 24-bit/96 kHz files from a microSD
|
|
46
|
+
card at the stock 150 MHz.
|
|
47
|
+
|
|
48
|
+
The decoder is written from RFC 9639 and decodes bit-exact. Its hot loops run in a turbo
|
|
49
|
+
(``@micropython.viper``) kernel, prebuilt for each chip architecture.
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
Dependencies
|
|
53
|
+
=============
|
|
54
|
+
This library depends on:
|
|
55
|
+
|
|
56
|
+
* `Adafruit CircuitPython <https://github.com/adafruit/circuitpython>`_ 11 or later, on a board
|
|
57
|
+
that can load native ``.mpy`` files (``microcontroller.cpu.architecture`` is not ``None``)
|
|
58
|
+
|
|
59
|
+
The decoding loops are a precompiled kernel, one file per architecture:
|
|
60
|
+
|
|
61
|
+
====================== =============================================
|
|
62
|
+
``flac_kernel`` file Boards
|
|
63
|
+
====================== =============================================
|
|
64
|
+
``armv6m`` RP2040
|
|
65
|
+
``armv7emsp`` SAMD51 (M4), nRF52840, RP2350, STM32F4
|
|
66
|
+
``xtensawin`` ESP32-S2, ESP32-S3
|
|
67
|
+
``rv32imc`` ESP32-C3, C5, C6
|
|
68
|
+
====================== =============================================
|
|
69
|
+
|
|
70
|
+
Without a kernel the decoder falls back to plain Python, which is correct but far slower than
|
|
71
|
+
real time for music. Only the RP2350 has been tested for playback.
|
|
72
|
+
|
|
73
|
+
Please ensure all dependencies are available on the CircuitPython filesystem.
|
|
74
|
+
This is easily achieved by downloading
|
|
75
|
+
`the Adafruit library and driver bundle <https://circuitpython.org/libraries>`_
|
|
76
|
+
or individual libraries can be installed using
|
|
77
|
+
`circup <https://github.com/adafruit/circup>`_.
|
|
78
|
+
|
|
79
|
+
Installing to a Connected CircuitPython Device with Circup
|
|
80
|
+
==========================================================
|
|
81
|
+
|
|
82
|
+
Make sure that you have ``circup`` installed in your Python environment.
|
|
83
|
+
Install it with the following command if necessary:
|
|
84
|
+
|
|
85
|
+
.. code-block:: shell
|
|
86
|
+
|
|
87
|
+
pip3 install circup
|
|
88
|
+
|
|
89
|
+
With ``circup`` installed and your CircuitPython device connected use the
|
|
90
|
+
following command to install:
|
|
91
|
+
|
|
92
|
+
.. code-block:: shell
|
|
93
|
+
|
|
94
|
+
circup install adafruit_flac
|
|
95
|
+
|
|
96
|
+
Or the following command to update an existing version:
|
|
97
|
+
|
|
98
|
+
.. code-block:: shell
|
|
99
|
+
|
|
100
|
+
circup update
|
|
101
|
+
|
|
102
|
+
Usage Example
|
|
103
|
+
=============
|
|
104
|
+
|
|
105
|
+
.. code-block:: python
|
|
106
|
+
|
|
107
|
+
import adafruit_flac
|
|
108
|
+
from adafruit_fruitjam.peripherals import Peripherals
|
|
109
|
+
|
|
110
|
+
decoder = adafruit_flac.FLACDecoder("song.flac")
|
|
111
|
+
fruit_jam = Peripherals(audio_output="headphone", sample_rate=decoder.sample_rate)
|
|
112
|
+
|
|
113
|
+
player = adafruit_flac.FLACPlayer(fruit_jam.audio)
|
|
114
|
+
player.play(decoder)
|
|
115
|
+
while player.playing:
|
|
116
|
+
player.update() # decode the next piece; call this often while playing
|
|
117
|
+
|
|
118
|
+
Speed and memory
|
|
119
|
+
================
|
|
120
|
+
|
|
121
|
+
Measured on an Adafruit Fruit Jam (RP2350B). Load is decode time divided by audio time; under
|
|
122
|
+
100% keeps up.
|
|
123
|
+
|
|
124
|
+
========================= =========== ===========
|
|
125
|
+
Format 150 MHz 300 MHz
|
|
126
|
+
========================= =========== ===========
|
|
127
|
+
16-bit, 44.1 kHz stereo 30% 15%
|
|
128
|
+
24-bit, 96 kHz stereo 89% 46%
|
|
129
|
+
========================= =========== ===========
|
|
130
|
+
|
|
131
|
+
- Keep the decoder and the audio buffer in internal SRAM. On the RP2350, decoding into PSRAM
|
|
132
|
+
nearly doubled the load at 96 kHz. Create them first in ``code.py``, before other large
|
|
133
|
+
allocations, or pass a preallocated ``array("h")`` as ``FLACPlayer(audio, buffer=...)``.
|
|
134
|
+
- ``FLACPlayer`` decodes straight into the half of its buffer that the audio DMA has already
|
|
135
|
+
copied, so it does not work with ``audiomixer``, which reads the buffer in place.
|
|
136
|
+
- At 24/96 the decoder uses about 83 KB and a 64 ms double buffer 48 KB (plus the output's own
|
|
137
|
+
DMA buffers).
|
|
138
|
+
- ``examples/flac_fruitjam_sd_hires.py`` plays 24/96 from the Fruit Jam's microSD card over
|
|
139
|
+
4-bit SDIO.
|
|
140
|
+
|
|
141
|
+
Building the kernel
|
|
142
|
+
===================
|
|
143
|
+
|
|
144
|
+
``src/flac_turbo.py`` is the kernel source, turbo (``@micropython.viper``) functions.
|
|
145
|
+
``tools/gen_turbo_lpc.py`` writes the unrolled predictors into it. ``src/Makefile`` compiles it
|
|
146
|
+
with ``mpy-cross -march`` for each architecture into ``adafruit_flac/flac_kernel.<arch>.mpy``:
|
|
147
|
+
|
|
148
|
+
.. code-block:: shell
|
|
149
|
+
|
|
150
|
+
cd src
|
|
151
|
+
make MPY_CROSS=path/to/circuitpython/mpy-cross/build/mpy-cross
|
|
152
|
+
|
|
153
|
+
Use an ``mpy-cross`` from the CircuitPython version the files will run on.
|
|
154
|
+
|
|
155
|
+
Testing
|
|
156
|
+
=======
|
|
157
|
+
|
|
158
|
+
``tools/make_test_media.py`` builds test clips (it downloads a CC0 recording and needs ``flac``
|
|
159
|
+
and ``ffmpeg``). ``tests/test_decoder.py`` checks the plain Python decoder against libFLAC under
|
|
160
|
+
CPython, ``tests/test_kernel_model.py`` runs the same with a CPython model of the kernel that
|
|
161
|
+
fails on any 32-bit overflow, and ``tools/turbo_check.py`` compares the kernel with the plain
|
|
162
|
+
Python path on a board.
|
|
163
|
+
|
|
164
|
+
Documentation
|
|
165
|
+
=============
|
|
166
|
+
API documentation for this library can be found on `Read the Docs <https://docs.circuitpython.org/projects/flac/en/latest/>`_.
|
|
167
|
+
|
|
168
|
+
For information on building library documentation, please check out
|
|
169
|
+
`this guide <https://learn.adafruit.com/creating-and-sharing-a-circuitpython-library/sharing-our-docs-on-readthedocs#sphinx-5-1>`_.
|
|
170
|
+
|
|
171
|
+
Contributing
|
|
172
|
+
============
|
|
173
|
+
|
|
174
|
+
Contributions are welcome! Please read our `Code of Conduct
|
|
175
|
+
<https://github.com/adafruit/Adafruit_CircuitPython_FLAC/blob/HEAD/CODE_OF_CONDUCT.md>`_
|
|
176
|
+
before contributing to help this project stay welcoming.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
adafruit_circuitpython_flac-1.0.2.dist-info/licenses/LICENSE,sha256=bQxQlJZdQnI0BAF8SlSIVnkOuhemp1xqC5_RPHvHKIo,1102
|
|
2
|
+
adafruit_flac/__init__.py,sha256=rwd3Jw-iNsKxvUcyM1_yCgq39ftKaRzQtMbMFYvzbgs,36930
|
|
3
|
+
adafruit_flac/flac_kernel.armv6m.mpy,sha256=9xEO-h85uOh3uZtbYS5gjlBK0iVjq3rm7wEaJr0nbm0,27192
|
|
4
|
+
adafruit_flac/flac_kernel.armv6m.mpy.license,sha256=x0CNJztITfJuEPiEy7vRw-7YoXKz7ozBkRLjFbm995Q,114
|
|
5
|
+
adafruit_flac/flac_kernel.armv7emsp.mpy,sha256=w6smDCyCNmPOTKqzlSRt8YPg64LLv6CiZOECJr9FJmA,25052
|
|
6
|
+
adafruit_flac/flac_kernel.armv7emsp.mpy.license,sha256=x0CNJztITfJuEPiEy7vRw-7YoXKz7ozBkRLjFbm995Q,114
|
|
7
|
+
adafruit_flac/flac_kernel.rv32imc.mpy,sha256=Ce8mC4kediLlOZgjNevbHphsZCR12Bjrlznl3MvFTBw,28893
|
|
8
|
+
adafruit_flac/flac_kernel.rv32imc.mpy.license,sha256=x0CNJztITfJuEPiEy7vRw-7YoXKz7ozBkRLjFbm995Q,114
|
|
9
|
+
adafruit_flac/flac_kernel.xtensawin.mpy,sha256=2Q0aupipVwsXfJ99-JEGNMtmo--BxRza1zaaR5u-B7A,31616
|
|
10
|
+
adafruit_flac/flac_kernel.xtensawin.mpy.license,sha256=x0CNJztITfJuEPiEy7vRw-7YoXKz7ozBkRLjFbm995Q,114
|
|
11
|
+
adafruit_circuitpython_flac-1.0.2.dist-info/METADATA,sha256=3VO5GC7e1D2uVmFFgtb81nj7evVWz-FpkIfSzT95mHc,6893
|
|
12
|
+
adafruit_circuitpython_flac-1.0.2.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
13
|
+
adafruit_circuitpython_flac-1.0.2.dist-info/top_level.txt,sha256=jCHJ5quA3TzgTSSG7zbfNmG0-agOLMH_GxhHc1_yDRY,14
|
|
14
|
+
adafruit_circuitpython_flac-1.0.2.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Mikey Sklar for Adafruit Industries
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
adafruit_flac
|
|
@@ -0,0 +1,1069 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026 Mikey Sklar for Adafruit Industries
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""
|
|
4
|
+
`adafruit_flac`
|
|
5
|
+
================================================================================
|
|
6
|
+
|
|
7
|
+
FLAC decoding and playback for CircuitPython.
|
|
8
|
+
|
|
9
|
+
`FLACDecoder` reads a FLAC file and returns 16-bit signed PCM. `FLACPlayer` streams it to an
|
|
10
|
+
audio output (``I2SOut``, ``PWMAudioOut`` or ``AudioOut``) through a double-buffered
|
|
11
|
+
``audiocore.RawSample``; call `FLACPlayer.update` often from your main loop.
|
|
12
|
+
|
|
13
|
+
The hot loops (Rice decoding, the predictors, stereo decorrelation and PCM conversion) run in
|
|
14
|
+
a turbo kernel, ``flac_kernel.<arch>.mpy``, prebuilt from ``src/flac_turbo.py`` with
|
|
15
|
+
``mpy-cross -march``. Without a kernel for the board, plain Python loops run instead
|
|
16
|
+
(correct, but several times slower than real time for music).
|
|
17
|
+
|
|
18
|
+
Written from RFC 9639, "Free Lossless Audio Codec (FLAC)" (section numbers in comments refer
|
|
19
|
+
to it). No third-party decoder code is included. Decoding is bit-exact. 20, 24 and 32-bit
|
|
20
|
+
sources are reduced to their top 16 bits.
|
|
21
|
+
|
|
22
|
+
* Author(s): Mikey Sklar
|
|
23
|
+
|
|
24
|
+
Implementation Notes
|
|
25
|
+
--------------------
|
|
26
|
+
|
|
27
|
+
**Hardware:**
|
|
28
|
+
|
|
29
|
+
* Tested on the Adafruit Fruit Jam (RP2350B): 16-bit/44.1 kHz and 24-bit/96 kHz at the
|
|
30
|
+
stock 150 MHz.
|
|
31
|
+
|
|
32
|
+
**Software and Dependencies:**
|
|
33
|
+
|
|
34
|
+
* Adafruit CircuitPython firmware 11 or later, with native ``.mpy`` loading:
|
|
35
|
+
https://circuitpython.org/downloads
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
import struct
|
|
39
|
+
from array import array
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
from time import monotonic_ns as _now
|
|
43
|
+
except ImportError: # unix port
|
|
44
|
+
from time import time_ns as _now
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
from ulab import numpy as _np
|
|
48
|
+
except ImportError:
|
|
49
|
+
try:
|
|
50
|
+
import numpy as _np # desktop testing with CPython
|
|
51
|
+
except ImportError:
|
|
52
|
+
_np = None
|
|
53
|
+
|
|
54
|
+
# Turbo (viper) kernel, prebuilt per chip as flac_kernel.<arch>.mpy from src/flac_turbo.py.
|
|
55
|
+
# Without it (CPython, or a chip with no build) the plain Python loops run.
|
|
56
|
+
try:
|
|
57
|
+
from adafruit_flac import flac_kernel as _kernel
|
|
58
|
+
except (ImportError, ValueError):
|
|
59
|
+
_kernel = None
|
|
60
|
+
|
|
61
|
+
# Largest sum the 32-bit turbo predictor can hold.
|
|
62
|
+
_INT32 = (1 << 31) - 1
|
|
63
|
+
|
|
64
|
+
__version__ = "1.0.2"
|
|
65
|
+
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_FLAC.git"
|
|
66
|
+
|
|
67
|
+
# Largest value a small int holds on a 32-bit MicroPython build. Anything
|
|
68
|
+
# bigger becomes a heap-allocated long, which is several times slower.
|
|
69
|
+
_SMALL = (1 << 30) - 1
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# Leading zero bits in a byte; 8 for zero.
|
|
73
|
+
def _lz_table():
|
|
74
|
+
t = bytearray(256)
|
|
75
|
+
t[0] = 8
|
|
76
|
+
for i in range(1, 256):
|
|
77
|
+
n = 0
|
|
78
|
+
while not (i << n) & 0x80:
|
|
79
|
+
n += 1
|
|
80
|
+
t[i] = n
|
|
81
|
+
return bytes(t)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
_LZ = _lz_table()
|
|
85
|
+
|
|
86
|
+
# Section 9.1.1 / 9.1.2 lookup tables.
|
|
87
|
+
_BLOCK_SIZES = (
|
|
88
|
+
0,
|
|
89
|
+
192,
|
|
90
|
+
576,
|
|
91
|
+
1152,
|
|
92
|
+
2304,
|
|
93
|
+
4608,
|
|
94
|
+
-1,
|
|
95
|
+
-2,
|
|
96
|
+
256,
|
|
97
|
+
512,
|
|
98
|
+
1024,
|
|
99
|
+
2048,
|
|
100
|
+
4096,
|
|
101
|
+
8192,
|
|
102
|
+
16384,
|
|
103
|
+
32768,
|
|
104
|
+
)
|
|
105
|
+
_RATES = (
|
|
106
|
+
0,
|
|
107
|
+
88200,
|
|
108
|
+
176400,
|
|
109
|
+
192000,
|
|
110
|
+
8000,
|
|
111
|
+
16000,
|
|
112
|
+
22050,
|
|
113
|
+
24000,
|
|
114
|
+
32000,
|
|
115
|
+
44100,
|
|
116
|
+
48000,
|
|
117
|
+
96000,
|
|
118
|
+
-1,
|
|
119
|
+
-2,
|
|
120
|
+
-3,
|
|
121
|
+
0,
|
|
122
|
+
)
|
|
123
|
+
_DEPTHS = (0, 8, 12, 0, 16, 20, 24, 32)
|
|
124
|
+
|
|
125
|
+
# Fixed predictor coefficients, section 9.2.5.
|
|
126
|
+
_FIXED = ((), (1,), (2, -1), (3, -3, 1), (4, -6, 4, -1))
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class FLACError(ValueError):
|
|
130
|
+
"""Raised for files that are not FLAC or use unsupported features."""
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
class _Bits:
|
|
134
|
+
"""MSB-first bit reader over a bytearray. Used for headers; the hot
|
|
135
|
+
residual loop reads the same buffer with its own inlined code."""
|
|
136
|
+
|
|
137
|
+
def __init__(self, data, pos=0):
|
|
138
|
+
self.data = data
|
|
139
|
+
self.pos = pos
|
|
140
|
+
|
|
141
|
+
def read(self, n):
|
|
142
|
+
if n == 0:
|
|
143
|
+
return 0
|
|
144
|
+
pos = self.pos
|
|
145
|
+
d = self.data
|
|
146
|
+
i = pos >> 3
|
|
147
|
+
need = (pos & 7) + n
|
|
148
|
+
v = 0
|
|
149
|
+
for j in range((need + 7) >> 3):
|
|
150
|
+
v = (v << 8) | d[i + j]
|
|
151
|
+
self.pos = pos + n
|
|
152
|
+
return (v >> (((need + 7) & ~7) - need)) & ((1 << n) - 1)
|
|
153
|
+
|
|
154
|
+
def signed(self, n):
|
|
155
|
+
v = self.read(n)
|
|
156
|
+
if v >> (n - 1):
|
|
157
|
+
v -= 1 << n
|
|
158
|
+
return v
|
|
159
|
+
|
|
160
|
+
def unary(self):
|
|
161
|
+
# count zero bits before the next 1
|
|
162
|
+
d = self.data
|
|
163
|
+
pos = self.pos
|
|
164
|
+
q = 0
|
|
165
|
+
while not (d[pos >> 3] << (pos & 7)) & 0x80:
|
|
166
|
+
q += 1
|
|
167
|
+
pos += 1
|
|
168
|
+
self.pos = pos + 1
|
|
169
|
+
return q
|
|
170
|
+
|
|
171
|
+
def align(self):
|
|
172
|
+
self.pos = (self.pos + 7) & ~7
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _rice(d, pos, out, o, end, k):
|
|
176
|
+
"""Decode Rice codes with parameter ``k`` into ``out[o:end]`` from bit
|
|
177
|
+
``pos`` of ``d`` (section 9.2.7.2). Returns the new bit position.
|
|
178
|
+
|
|
179
|
+
This is the hottest loop. ``for ... in range`` is MicroPython's fastest
|
|
180
|
+
loop, and the common cases get their own copy so the loop body has no
|
|
181
|
+
extra branches."""
|
|
182
|
+
lz = _LZ
|
|
183
|
+
if k == 0:
|
|
184
|
+
for o in range(o, end): # noqa: PLR1704, reuses the argument for speed
|
|
185
|
+
i = pos >> 3
|
|
186
|
+
b = (d[i] << (pos & 7)) & 0xFF
|
|
187
|
+
if b:
|
|
188
|
+
q = lz[b]
|
|
189
|
+
else:
|
|
190
|
+
q = 8 - (pos & 7)
|
|
191
|
+
i += 1
|
|
192
|
+
while not d[i]:
|
|
193
|
+
q += 8
|
|
194
|
+
i += 1
|
|
195
|
+
q += lz[d[i]]
|
|
196
|
+
pos += q + 1
|
|
197
|
+
out[o] = (q >> 1) ^ -(q & 1)
|
|
198
|
+
return pos
|
|
199
|
+
mask = (1 << k) - 1
|
|
200
|
+
sh = 24 - k
|
|
201
|
+
if k <= 17:
|
|
202
|
+
# the k remainder bits always fit in one 3-byte window
|
|
203
|
+
for o in range(o, end): # noqa: PLR1704, reuses the argument for speed
|
|
204
|
+
i = pos >> 3
|
|
205
|
+
b = (d[i] << (pos & 7)) & 0xFF
|
|
206
|
+
if b:
|
|
207
|
+
q = lz[b]
|
|
208
|
+
else:
|
|
209
|
+
q = 8 - (pos & 7)
|
|
210
|
+
i += 1
|
|
211
|
+
while not d[i]:
|
|
212
|
+
q += 8
|
|
213
|
+
i += 1
|
|
214
|
+
q += lz[d[i]]
|
|
215
|
+
pos += q + 1
|
|
216
|
+
i = pos >> 3
|
|
217
|
+
v = (q << k) | (((d[i] << 16 | d[i + 1] << 8 | d[i + 2]) >> (sh - (pos & 7))) & mask)
|
|
218
|
+
pos += k
|
|
219
|
+
out[o] = (v >> 1) ^ -(v & 1)
|
|
220
|
+
return pos
|
|
221
|
+
for o in range(o, end):
|
|
222
|
+
i = pos >> 3
|
|
223
|
+
b = (d[i] << (pos & 7)) & 0xFF
|
|
224
|
+
if b:
|
|
225
|
+
q = lz[b]
|
|
226
|
+
else:
|
|
227
|
+
q = 8 - (pos & 7)
|
|
228
|
+
i += 1
|
|
229
|
+
while not d[i]:
|
|
230
|
+
q += 8
|
|
231
|
+
i += 1
|
|
232
|
+
q += lz[d[i]]
|
|
233
|
+
pos += q + 1
|
|
234
|
+
i = pos >> 3
|
|
235
|
+
s = pos & 7
|
|
236
|
+
if s <= sh:
|
|
237
|
+
v = ((d[i] << 16 | d[i + 1] << 8 | d[i + 2]) >> (sh - s)) & mask
|
|
238
|
+
else:
|
|
239
|
+
# past 30 bits this is a long int on a 32-bit build; rare
|
|
240
|
+
v = ((d[i] << 24 | d[i + 1] << 16 | d[i + 2] << 8 | d[i + 3]) >> (32 - s - k)) & mask
|
|
241
|
+
pos += k
|
|
242
|
+
v |= q << k
|
|
243
|
+
out[o] = (v >> 1) ^ -(v & 1)
|
|
244
|
+
return pos
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
_LPC_CACHE = {}
|
|
248
|
+
|
|
249
|
+
# Predictor variants, picked per subframe by `FLACDecoder._predict`.
|
|
250
|
+
_PLAIN = 0 # one dot product
|
|
251
|
+
_CSPLIT = 1 # coefficients split into 8-bit halves: two dot products
|
|
252
|
+
_XSPLIT = 2 # samples split into two limbs: two dot products
|
|
253
|
+
_TURBO = 3 # flac_kernel.lpc_restore, when the sum fits in 32 bits
|
|
254
|
+
_TURBO_SPLIT = 4 # flac_kernel.lpc_restore_split, sample limbs for 24-bit
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
# Orders up to this use the ring-unrolled loop; longer ones rotate locals.
|
|
258
|
+
_RING_MAX = 16
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def _lpc_func(order, mode):
|
|
262
|
+
"""Build an unrolled predictor for one order (section 9.2.6).
|
|
263
|
+
|
|
264
|
+
``out`` holds warm-up samples followed by residuals, and is restored in
|
|
265
|
+
place. The previous ``order`` samples live in local variables, which is
|
|
266
|
+
much faster than indexing ``out`` on MicroPython. For orders up to
|
|
267
|
+
`_RING_MAX` the loop body is unrolled ``order`` times and each step
|
|
268
|
+
overwrites the variable holding the oldest sample, so no variables are
|
|
269
|
+
shuffled between samples.
|
|
270
|
+
|
|
271
|
+
On a 32-bit build a product or sum past 30 bits becomes a heap-allocated
|
|
272
|
+
long int. The split modes keep every value small and stay exact:
|
|
273
|
+
|
|
274
|
+
* ``_CSPLIT``: ``c = a * 256 + b`` with ``0 <= b < 256``. For a shift
|
|
275
|
+
``s >= 8``, ``(A * 256 + B) >> s == (A + (B >> 8)) >> (s - 8)``.
|
|
276
|
+
* ``_XSPLIT``: ``x = h * 2 ** b + l`` with ``0 <= l < 2 ** b``. The
|
|
277
|
+
prediction ``(H * 2 ** b + L) >> s`` equals
|
|
278
|
+
``(H + (L >> b)) >> (s - b)`` when ``s >= b`` and
|
|
279
|
+
``(H << (b - s)) + (L >> s)`` when ``s < b``. Both are
|
|
280
|
+
``((H << u) + (L >> v)) >> w``.
|
|
281
|
+
"""
|
|
282
|
+
key = (order, mode)
|
|
283
|
+
f = _LPC_CACHE.get(key)
|
|
284
|
+
if f is not None:
|
|
285
|
+
return f
|
|
286
|
+
n = order
|
|
287
|
+
r = range(n)
|
|
288
|
+
tup = "," if n == 1 else ""
|
|
289
|
+
lines = ["def f(out, end, coefs, shift, b):"]
|
|
290
|
+
if mode == _CSPLIT:
|
|
291
|
+
names_a = ", ".join("a%d" % j for j in r)
|
|
292
|
+
names_b = ", ".join("b%d" % j for j in r)
|
|
293
|
+
lines.append(" %s%s = [c >> 8 for c in coefs]" % (names_a, tup))
|
|
294
|
+
lines.append(" %s%s = [c & 255 for c in coefs]" % (names_b, tup))
|
|
295
|
+
else:
|
|
296
|
+
lines.append(" %s%s = coefs" % (", ".join("c%d" % j for j in r), tup))
|
|
297
|
+
if mode == _CSPLIT:
|
|
298
|
+
lines.append(" s8 = shift - 8")
|
|
299
|
+
elif mode == _XSPLIT:
|
|
300
|
+
lines.append(" u = b - shift if b > shift else 0")
|
|
301
|
+
lines.append(" v = shift if shift < b else b")
|
|
302
|
+
lines.append(" w = shift - b if shift > b else 0")
|
|
303
|
+
lines.append(" lm = (1 << b) - 1")
|
|
304
|
+
|
|
305
|
+
def predict(var):
|
|
306
|
+
# var(j) names the sample j + 1 back
|
|
307
|
+
if mode == _PLAIN:
|
|
308
|
+
p = " + ".join("c%d * %s" % (j, var("x", j)) for j in r)
|
|
309
|
+
return "((%s) >> shift)" % p
|
|
310
|
+
if mode == _CSPLIT:
|
|
311
|
+
a = " + ".join("a%d * %s" % (j, var("x", j)) for j in r)
|
|
312
|
+
b = " + ".join("b%d * %s" % (j, var("x", j)) for j in r)
|
|
313
|
+
return "(((%s) + ((%s) >> 8)) >> s8)" % (a, b)
|
|
314
|
+
h = " + ".join("c%d * %s" % (j, var("h", j)) for j in r)
|
|
315
|
+
l = " + ".join("c%d * %s" % (j, var("l", j)) for j in r)
|
|
316
|
+
return "((((%s) << u) + ((%s) >> v)) >> w)" % (h, l)
|
|
317
|
+
|
|
318
|
+
def store(ind, name):
|
|
319
|
+
# x is the new sample; keep it (or its limbs) in variable slot `name`
|
|
320
|
+
if mode == _XSPLIT:
|
|
321
|
+
return [ind + "h%s = x >> b" % name, ind + "l%s = x & lm" % name]
|
|
322
|
+
return [ind + "x%s = x" % name]
|
|
323
|
+
|
|
324
|
+
if n <= _RING_MAX:
|
|
325
|
+
# slot t holds the sample whose index is t mod n
|
|
326
|
+
for t in r:
|
|
327
|
+
lines.append(" x = out[%d]" % t)
|
|
328
|
+
lines.extend(store(" ", t))
|
|
329
|
+
lines.append(" stop = end - (end - %d) %% %d" % (n, n))
|
|
330
|
+
lines.append(" for i in range(%d, stop, %d):" % (n, n))
|
|
331
|
+
for t in r:
|
|
332
|
+
idx = "i + %d" % t if t else "i"
|
|
333
|
+
lines.append(
|
|
334
|
+
" x = out[%s] + %s"
|
|
335
|
+
% (idx, predict(lambda v, j, t=t: "%s%d" % (v, (t - 1 - j) % n)))
|
|
336
|
+
)
|
|
337
|
+
lines.append(" out[%s] = x" % idx)
|
|
338
|
+
lines.extend(store(" ", t))
|
|
339
|
+
# the last few samples, exactly and slowly
|
|
340
|
+
lines.append(" for i in range(stop, end):")
|
|
341
|
+
lines.append(" s = 0")
|
|
342
|
+
lines.append(" for j in range(%d):" % n)
|
|
343
|
+
lines.append(" s += coefs[j] * out[i - 1 - j]")
|
|
344
|
+
lines.append(" out[i] += s >> shift")
|
|
345
|
+
else:
|
|
346
|
+
# slot j + 1 holds the sample j + 1 back; shuffle after each sample
|
|
347
|
+
for j in r:
|
|
348
|
+
lines.append(" x = out[%d]" % (n - 1 - j))
|
|
349
|
+
lines.extend(store(" ", j + 1))
|
|
350
|
+
lines.append(" for i in range(%d, end):" % n)
|
|
351
|
+
lines.append(" x = out[i] + %s" % predict(lambda v, j: "%s%d" % (v, j + 1)))
|
|
352
|
+
lines.append(" out[i] = x")
|
|
353
|
+
names = ("h", "l") if mode == _XSPLIT else ("x",)
|
|
354
|
+
for j in range(n, 1, -1):
|
|
355
|
+
for v in names:
|
|
356
|
+
lines.append(" %s%d = %s%d" % (v, j, v, j - 1))
|
|
357
|
+
lines.extend(store(" ", 1))
|
|
358
|
+
ns = {}
|
|
359
|
+
exec("\n".join(lines), ns)
|
|
360
|
+
f = ns["f"]
|
|
361
|
+
_LPC_CACHE[key] = f
|
|
362
|
+
return f
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
class FLACDecoder:
|
|
366
|
+
"""Decode a FLAC file to 16-bit signed PCM.
|
|
367
|
+
|
|
368
|
+
:param file: path string or a file opened in ``"rb"`` mode
|
|
369
|
+
:param bool accel: use ``ulab`` for interleaving when it applies
|
|
370
|
+
:param bool turbo: use the prebuilt ``flac_kernel`` for the hot loops when
|
|
371
|
+
this chip has one
|
|
372
|
+
|
|
373
|
+
Playing a FLAC file::
|
|
374
|
+
|
|
375
|
+
import audiobusio
|
|
376
|
+
import board
|
|
377
|
+
import adafruit_flac
|
|
378
|
+
|
|
379
|
+
audio = audiobusio.I2SOut(board.I2S_BCLK, board.I2S_WS, board.I2S_DIN)
|
|
380
|
+
decoder = adafruit_flac.FLACDecoder("song.flac")
|
|
381
|
+
player = adafruit_flac.FLACPlayer(audio)
|
|
382
|
+
player.play(decoder)
|
|
383
|
+
while player.update():
|
|
384
|
+
pass
|
|
385
|
+
"""
|
|
386
|
+
|
|
387
|
+
def __init__(self, file, *, accel=True, turbo=True):
|
|
388
|
+
self._owns = False
|
|
389
|
+
self._file = None
|
|
390
|
+
self._accel = bool(accel and _np is not None)
|
|
391
|
+
self._kernel = _kernel if turbo else None
|
|
392
|
+
self._buf = self._chan = self._pcm = None
|
|
393
|
+
self._coefs = array("i", [0]) * 32 # scratch for flac_kernel.subframe
|
|
394
|
+
self.lpc_modes = array("i", [0]) * 5
|
|
395
|
+
"""Subframes predicted with the plain, coefficient-split,
|
|
396
|
+
sample-split, turbo and turbo-split loops (for benchmarking)."""
|
|
397
|
+
self._open(file)
|
|
398
|
+
|
|
399
|
+
# -- public API --
|
|
400
|
+
|
|
401
|
+
@property
|
|
402
|
+
def file(self):
|
|
403
|
+
"""The open file object. Assign a new file or path to switch songs."""
|
|
404
|
+
return self._file
|
|
405
|
+
|
|
406
|
+
@file.setter
|
|
407
|
+
def file(self, file):
|
|
408
|
+
self._open(file)
|
|
409
|
+
|
|
410
|
+
def open(self, file):
|
|
411
|
+
"""Switch to a different file (same as assigning `file`)."""
|
|
412
|
+
self._open(file)
|
|
413
|
+
|
|
414
|
+
@property
|
|
415
|
+
def sample_rate(self):
|
|
416
|
+
"""Sample rate in Hz."""
|
|
417
|
+
return self._rate
|
|
418
|
+
|
|
419
|
+
@property
|
|
420
|
+
def channel_count(self):
|
|
421
|
+
"""Number of channels (output is limited to 2)."""
|
|
422
|
+
return self._channels
|
|
423
|
+
|
|
424
|
+
@property
|
|
425
|
+
def bits_per_sample(self):
|
|
426
|
+
"""Always 16."""
|
|
427
|
+
return 16
|
|
428
|
+
|
|
429
|
+
@property
|
|
430
|
+
def source_bits_per_sample(self):
|
|
431
|
+
"""Bit depth stored in the file (8 to 32)."""
|
|
432
|
+
return self._bps
|
|
433
|
+
|
|
434
|
+
@property
|
|
435
|
+
def samples(self):
|
|
436
|
+
"""Total frames in the stream (0 if the encoder did not say)."""
|
|
437
|
+
return self._total
|
|
438
|
+
|
|
439
|
+
@property
|
|
440
|
+
def duration(self):
|
|
441
|
+
"""Length in seconds (0 if unknown)."""
|
|
442
|
+
return self._total / self._rate if self._rate else 0
|
|
443
|
+
|
|
444
|
+
@property
|
|
445
|
+
def samples_decoded(self):
|
|
446
|
+
"""Frames decoded since the file was opened or rewound."""
|
|
447
|
+
return self._decoded
|
|
448
|
+
|
|
449
|
+
def rewind(self):
|
|
450
|
+
"""Go back to the first audio frame."""
|
|
451
|
+
self._file.seek(self._audio_start)
|
|
452
|
+
self._len = 0
|
|
453
|
+
self._off = 0
|
|
454
|
+
self._eof = False
|
|
455
|
+
self._decoded = 0
|
|
456
|
+
self._pcm_len = 0
|
|
457
|
+
self._pcm_pos = 0
|
|
458
|
+
|
|
459
|
+
def readinto(self, buf):
|
|
460
|
+
"""Fill ``buf`` (an ``array('h')`` or memoryview of one) with
|
|
461
|
+
interleaved samples. Returns the number of values written; less than
|
|
462
|
+
``len(buf)`` only at the end of the stream."""
|
|
463
|
+
mv = memoryview(buf)
|
|
464
|
+
total = len(mv)
|
|
465
|
+
got = 0
|
|
466
|
+
while got < total:
|
|
467
|
+
n = self.fill(mv[got:])
|
|
468
|
+
if n == 0:
|
|
469
|
+
break
|
|
470
|
+
got += n
|
|
471
|
+
return got
|
|
472
|
+
|
|
473
|
+
def fill(self, mv):
|
|
474
|
+
"""Copy decoded samples into memoryview ``mv``, decoding at most one
|
|
475
|
+
frame. Returns the number of values written, 0 at end of stream."""
|
|
476
|
+
if self._pcm_pos >= self._pcm_len:
|
|
477
|
+
if not self._decode_pcm():
|
|
478
|
+
return 0
|
|
479
|
+
p = self._pcm_pos
|
|
480
|
+
n = self._pcm_len - p
|
|
481
|
+
n = min(n, len(mv))
|
|
482
|
+
mv[:n] = self._pcm_mv[p : p + n]
|
|
483
|
+
self._pcm_pos = p + n
|
|
484
|
+
return n
|
|
485
|
+
|
|
486
|
+
def decode_frame(self):
|
|
487
|
+
"""Decode the next frame and return its channels as a list of
|
|
488
|
+
``array('i')`` at the source bit depth (each may be longer than the
|
|
489
|
+
block; use `block_size`). Returns None at the end of the stream."""
|
|
490
|
+
if not self._decode():
|
|
491
|
+
return None
|
|
492
|
+
return self._chan
|
|
493
|
+
|
|
494
|
+
@property
|
|
495
|
+
def block_size(self):
|
|
496
|
+
"""Frames in the block most recently returned by `decode_frame`."""
|
|
497
|
+
return self._bs
|
|
498
|
+
|
|
499
|
+
def deinit(self):
|
|
500
|
+
"""Release the file (if this decoder opened it) and buffers."""
|
|
501
|
+
if self._owns and self._file is not None:
|
|
502
|
+
self._file.close()
|
|
503
|
+
self._file = None
|
|
504
|
+
self._buf = None
|
|
505
|
+
self._chan = None
|
|
506
|
+
self._pcm = None
|
|
507
|
+
self._pcm_mv = None
|
|
508
|
+
|
|
509
|
+
def __enter__(self):
|
|
510
|
+
return self
|
|
511
|
+
|
|
512
|
+
def __exit__(self, *exc):
|
|
513
|
+
self.deinit()
|
|
514
|
+
|
|
515
|
+
# -- internals --
|
|
516
|
+
|
|
517
|
+
def _open(self, file):
|
|
518
|
+
if self._owns and self._file is not None:
|
|
519
|
+
self._file.close()
|
|
520
|
+
if isinstance(file, str):
|
|
521
|
+
file = open(file, "rb")
|
|
522
|
+
self._owns = True
|
|
523
|
+
else:
|
|
524
|
+
self._owns = False
|
|
525
|
+
self._file = file
|
|
526
|
+
self._read_metadata()
|
|
527
|
+
maxblock = self._maxblock or 4096
|
|
528
|
+
# Worst case frame is a verbatim block plus headers (section 10.1).
|
|
529
|
+
maxframe = self._maxframe or (maxblock * self._channels_coded * (self._bps + 1) // 8 + 64)
|
|
530
|
+
self._chunk = max(maxframe, 4096)
|
|
531
|
+
# Keep the buffers from the previous file when they are big enough: where they
|
|
532
|
+
# landed (internal SRAM or PSRAM) matters several times over for decode speed,
|
|
533
|
+
# and the first allocation is the one most likely to get SRAM.
|
|
534
|
+
# Hottest first: the channel arrays are read and written many times per sample,
|
|
535
|
+
# the PCM frame twice, the input buffer once. If internal RAM runs short, the
|
|
536
|
+
# input buffer is the one that should spill.
|
|
537
|
+
chan = self._chan or []
|
|
538
|
+
if len(chan) < self._channels_coded or len(chan[0]) < maxblock:
|
|
539
|
+
chan = [array("i", [0]) * maxblock for _ in range(self._channels_coded)]
|
|
540
|
+
self._chan = chan
|
|
541
|
+
if self._pcm is None or len(self._pcm) < maxblock * self._channels:
|
|
542
|
+
self._pcm = array("h", [0]) * (maxblock * self._channels)
|
|
543
|
+
self._pcm_mv = memoryview(self._pcm)
|
|
544
|
+
if self._buf is None or len(self._buf) < 2 * self._chunk + 8:
|
|
545
|
+
self._buf = bytearray(2 * self._chunk + 8) # +8: guard bytes for the bit reader
|
|
546
|
+
self._audio_start = file.tell()
|
|
547
|
+
self.rewind()
|
|
548
|
+
|
|
549
|
+
def _read_metadata(self):
|
|
550
|
+
# Section 8: "fLaC" then metadata blocks, the first being STREAMINFO.
|
|
551
|
+
f = self._file
|
|
552
|
+
head = f.read(4)
|
|
553
|
+
if head == b"ID3\x03" or head[:3] == b"ID3":
|
|
554
|
+
# skip an ID3v2 tag some taggers prepend
|
|
555
|
+
rest = f.read(6)
|
|
556
|
+
size = (rest[2] << 21) | (rest[3] << 14) | (rest[4] << 7) | rest[5]
|
|
557
|
+
f.seek(10 + size)
|
|
558
|
+
head = f.read(4)
|
|
559
|
+
if head != b"fLaC":
|
|
560
|
+
raise FLACError("not a FLAC file")
|
|
561
|
+
self._rate = 0
|
|
562
|
+
last = False
|
|
563
|
+
while not last:
|
|
564
|
+
h = f.read(4)
|
|
565
|
+
if len(h) < 4:
|
|
566
|
+
raise FLACError("truncated metadata")
|
|
567
|
+
last = h[0] & 0x80
|
|
568
|
+
kind = h[0] & 0x7F
|
|
569
|
+
size = (h[1] << 16) | (h[2] << 8) | h[3]
|
|
570
|
+
if kind == 0: # STREAMINFO, section 8.2
|
|
571
|
+
si = f.read(size)
|
|
572
|
+
self._minblock, self._maxblock = struct.unpack(">HH", si[0:4])
|
|
573
|
+
self._maxframe = (si[7] << 16) | (si[8] << 8) | si[9]
|
|
574
|
+
b = _Bits(si, 80)
|
|
575
|
+
self._rate = b.read(20)
|
|
576
|
+
self._channels_coded = b.read(3) + 1
|
|
577
|
+
self._bps = b.read(5) + 1
|
|
578
|
+
hi = b.read(4)
|
|
579
|
+
self._total = (hi << 32) | struct.unpack(">I", si[14:18])[0]
|
|
580
|
+
else:
|
|
581
|
+
f.seek(size, 1)
|
|
582
|
+
if not self._rate:
|
|
583
|
+
raise FLACError("no STREAMINFO")
|
|
584
|
+
if self._channels_coded > 2:
|
|
585
|
+
# only the first two channels are played
|
|
586
|
+
self._channels = 2
|
|
587
|
+
else:
|
|
588
|
+
self._channels = self._channels_coded
|
|
589
|
+
|
|
590
|
+
def _refill(self):
|
|
591
|
+
# Keep at least one maximum frame of data after self._off.
|
|
592
|
+
buf = self._buf
|
|
593
|
+
rem = self._len - self._off
|
|
594
|
+
if rem >= self._chunk or self._eof:
|
|
595
|
+
return
|
|
596
|
+
buf[:rem] = buf[self._off : self._len]
|
|
597
|
+
mv = memoryview(buf)
|
|
598
|
+
want = 2 * self._chunk - rem
|
|
599
|
+
got = self._file.readinto(mv[rem : rem + want]) or 0
|
|
600
|
+
if got < want:
|
|
601
|
+
self._eof = True
|
|
602
|
+
self._len = rem + got
|
|
603
|
+
self._off = 0
|
|
604
|
+
# zero guard bytes so the bit reader never sees stale data
|
|
605
|
+
for i in range(self._len, self._len + 8):
|
|
606
|
+
buf[i] = 0
|
|
607
|
+
|
|
608
|
+
def _decode(self):
|
|
609
|
+
self._refill()
|
|
610
|
+
buf = self._buf
|
|
611
|
+
# Section 9.1: find the frame sync code.
|
|
612
|
+
off = self._off
|
|
613
|
+
end = self._len - 1
|
|
614
|
+
while off < end and not (buf[off] == 0xFF and (buf[off + 1] & 0xFE) == 0xF8):
|
|
615
|
+
off += 1
|
|
616
|
+
if off >= end or self._len - off < 6:
|
|
617
|
+
return False
|
|
618
|
+
b = _Bits(buf, off * 8 + 16)
|
|
619
|
+
bs_code = b.read(4)
|
|
620
|
+
rate_code = b.read(4)
|
|
621
|
+
chan_code = b.read(4)
|
|
622
|
+
depth_code = b.read(3)
|
|
623
|
+
b.read(1)
|
|
624
|
+
# coded frame/sample number, UTF-8 style (section 9.1.5)
|
|
625
|
+
first = b.read(8)
|
|
626
|
+
extra = 0
|
|
627
|
+
while first & (0x80 >> extra) and extra < 7:
|
|
628
|
+
extra += 1
|
|
629
|
+
if extra:
|
|
630
|
+
b.read(8 * (extra - 1))
|
|
631
|
+
bs = _BLOCK_SIZES[bs_code]
|
|
632
|
+
if bs == -1:
|
|
633
|
+
bs = b.read(8) + 1
|
|
634
|
+
elif bs == -2:
|
|
635
|
+
bs = b.read(16) + 1
|
|
636
|
+
elif bs == 0:
|
|
637
|
+
raise FLACError("reserved block size")
|
|
638
|
+
rate = _RATES[rate_code]
|
|
639
|
+
if rate == -1:
|
|
640
|
+
b.read(8)
|
|
641
|
+
elif rate < -1:
|
|
642
|
+
b.read(16)
|
|
643
|
+
b.read(8) # CRC-8, not checked
|
|
644
|
+
bps = _DEPTHS[depth_code] or self._bps
|
|
645
|
+
if bs > len(self._chan[0]):
|
|
646
|
+
self._chan = [array("i", [0]) * bs for _ in self._chan]
|
|
647
|
+
self._pcm = array("h", [0]) * (bs * self._channels)
|
|
648
|
+
self._pcm_mv = memoryview(self._pcm)
|
|
649
|
+
chan = self._chan
|
|
650
|
+
if chan_code < 8:
|
|
651
|
+
nch = chan_code + 1
|
|
652
|
+
for c in range(nch):
|
|
653
|
+
self._subframe(b, chan[c], bs, bps)
|
|
654
|
+
elif chan_code <= 10:
|
|
655
|
+
# section 4.2: the side channel carries one extra bit
|
|
656
|
+
self._subframe(b, chan[0], bs, bps + (chan_code == 9))
|
|
657
|
+
self._subframe(b, chan[1], bs, bps + (chan_code != 9))
|
|
658
|
+
self._decorrelate(chan_code, bs, bps)
|
|
659
|
+
else:
|
|
660
|
+
raise FLACError("reserved channel assignment")
|
|
661
|
+
b.align()
|
|
662
|
+
if (b.pos >> 3) + 2 > self._len:
|
|
663
|
+
return False # truncated last frame
|
|
664
|
+
self._off = (b.pos >> 3) + 2 # skip CRC-16
|
|
665
|
+
self._bs = bs
|
|
666
|
+
self._frame_bps = bps
|
|
667
|
+
self._decoded += bs
|
|
668
|
+
return True
|
|
669
|
+
|
|
670
|
+
def _subframe(self, b, out, bs, bps):
|
|
671
|
+
# Section 9.2.
|
|
672
|
+
kernel = self._kernel
|
|
673
|
+
if kernel is not None:
|
|
674
|
+
pos = kernel.subframe(b.data, _LZ, out, b.pos, bs, bps, self._coefs, self.lpc_modes)
|
|
675
|
+
if pos >= 0:
|
|
676
|
+
b.pos = pos
|
|
677
|
+
return
|
|
678
|
+
if pos == -1:
|
|
679
|
+
raise FLACError("bad subframe")
|
|
680
|
+
# -2: past what 32-bit viper math holds; decode this one in Python
|
|
681
|
+
h = b.read(8)
|
|
682
|
+
kind = (h >> 1) & 0x3F
|
|
683
|
+
wasted = 0
|
|
684
|
+
if h & 1:
|
|
685
|
+
wasted = b.unary() + 1
|
|
686
|
+
bps -= wasted
|
|
687
|
+
if kind == 0: # constant
|
|
688
|
+
v = b.signed(bps)
|
|
689
|
+
for i in range(bs):
|
|
690
|
+
out[i] = v
|
|
691
|
+
elif kind == 1: # verbatim
|
|
692
|
+
for i in range(bs):
|
|
693
|
+
out[i] = b.signed(bps)
|
|
694
|
+
elif 8 <= kind <= 12: # fixed
|
|
695
|
+
order = kind - 8
|
|
696
|
+
for i in range(order):
|
|
697
|
+
out[i] = b.signed(bps)
|
|
698
|
+
self._residual(b, out, bs, order)
|
|
699
|
+
if order:
|
|
700
|
+
self._predict(out, bs, _FIXED[order], 0, bps)
|
|
701
|
+
elif kind >= 32: # LPC
|
|
702
|
+
order = kind - 31
|
|
703
|
+
for i in range(order):
|
|
704
|
+
out[i] = b.signed(bps)
|
|
705
|
+
prec = b.read(4) + 1
|
|
706
|
+
if prec == 16:
|
|
707
|
+
raise FLACError("bad LPC precision")
|
|
708
|
+
shift = b.signed(5)
|
|
709
|
+
if shift < 0:
|
|
710
|
+
raise FLACError("negative LPC shift")
|
|
711
|
+
coefs = tuple(b.signed(prec) for _ in range(order))
|
|
712
|
+
self._residual(b, out, bs, order)
|
|
713
|
+
self._predict(out, bs, coefs, shift, bps)
|
|
714
|
+
else:
|
|
715
|
+
raise FLACError("reserved subframe type")
|
|
716
|
+
if wasted:
|
|
717
|
+
for i in range(bs):
|
|
718
|
+
out[i] <<= wasted
|
|
719
|
+
|
|
720
|
+
def _predict(self, out, bs, coefs, shift, bps):
|
|
721
|
+
# Pick the cheapest predictor whose products and sums provably stay
|
|
722
|
+
# in small ints: |x| < 2 ** (bps - 1), so |sum(c * x)| is at most
|
|
723
|
+
# sum(|c|) << (bps - 1).
|
|
724
|
+
order = len(coefs)
|
|
725
|
+
mode = _PLAIN
|
|
726
|
+
b = bps >> 1 # limb split point for _XSPLIT
|
|
727
|
+
ca = 0
|
|
728
|
+
for c in coefs:
|
|
729
|
+
ca += c if c >= 0 else -c
|
|
730
|
+
k = self._kernel
|
|
731
|
+
if k is not None:
|
|
732
|
+
if ca << (bps - 1) <= _INT32:
|
|
733
|
+
self.lpc_modes[_TURBO] += 1
|
|
734
|
+
k.lpc_restore(out, array("i", coefs), order, shift, bs)
|
|
735
|
+
return
|
|
736
|
+
# 24-bit: split samples into two limbs so each dot product fits 32 bits
|
|
737
|
+
if ca << max(b, bps - 1 - b) <= _INT32 and (
|
|
738
|
+
shift >= b or ca << (bps - 1 - shift) <= _INT32
|
|
739
|
+
):
|
|
740
|
+
self.lpc_modes[_TURBO_SPLIT] += 1
|
|
741
|
+
k.lpc_restore_split(out, array("i", coefs), order, shift, bs, b)
|
|
742
|
+
return
|
|
743
|
+
if ca << (bps - 1) > _SMALL:
|
|
744
|
+
hi = 0
|
|
745
|
+
for c in coefs:
|
|
746
|
+
ch = c >> 8
|
|
747
|
+
hi += ch if ch >= 0 else -ch
|
|
748
|
+
if shift >= 8 and (hi << (bps - 1)) <= _SMALL and (255 * order) << (bps - 1) <= _SMALL:
|
|
749
|
+
mode = _CSPLIT
|
|
750
|
+
elif ca << max(b, bps - 1 - b) <= _SMALL:
|
|
751
|
+
mode = _XSPLIT
|
|
752
|
+
# else: nothing keeps it small; plain with long ints is still exact
|
|
753
|
+
self.lpc_modes[mode] += 1
|
|
754
|
+
_lpc_func(order, mode)(out, bs, coefs, shift, b)
|
|
755
|
+
|
|
756
|
+
def _residual(self, b, out, bs, order):
|
|
757
|
+
# Section 9.2.7.
|
|
758
|
+
kernel = self._kernel
|
|
759
|
+
if kernel is not None:
|
|
760
|
+
pos = kernel.residual(b.data, _LZ, out, b.pos, bs, order)
|
|
761
|
+
if pos < 0:
|
|
762
|
+
raise FLACError("reserved residual coding method")
|
|
763
|
+
b.pos = pos
|
|
764
|
+
return
|
|
765
|
+
self._residual_py(b, out, bs, order)
|
|
766
|
+
|
|
767
|
+
def _residual_py(self, b, out, bs, order):
|
|
768
|
+
method = b.read(2)
|
|
769
|
+
if method > 1:
|
|
770
|
+
raise FLACError("reserved residual coding method")
|
|
771
|
+
pbits = 4 + method
|
|
772
|
+
escape = (1 << pbits) - 1
|
|
773
|
+
porder = b.read(4)
|
|
774
|
+
psize = bs >> porder
|
|
775
|
+
o = order
|
|
776
|
+
d = b.data
|
|
777
|
+
kernel = self._kernel
|
|
778
|
+
for p in range(1 << porder):
|
|
779
|
+
end = psize * (p + 1)
|
|
780
|
+
k = b.read(pbits)
|
|
781
|
+
if k == escape:
|
|
782
|
+
n = b.read(5)
|
|
783
|
+
if n:
|
|
784
|
+
for i in range(o, end):
|
|
785
|
+
out[i] = b.signed(n)
|
|
786
|
+
else:
|
|
787
|
+
for i in range(o, end):
|
|
788
|
+
out[i] = 0
|
|
789
|
+
elif kernel is not None:
|
|
790
|
+
b.pos = kernel.rice_decode(d, _LZ, out, b.pos, o, end, k)
|
|
791
|
+
else:
|
|
792
|
+
b.pos = _rice(d, b.pos, out, o, end, k)
|
|
793
|
+
o = end
|
|
794
|
+
|
|
795
|
+
def _decorrelate(self, code, bs, bps):
|
|
796
|
+
a, s = self._chan[0], self._chan[1]
|
|
797
|
+
# mid/side holds up to bps + 1 bits; << 1 stays in 32 bits up to 24-bit
|
|
798
|
+
if self._kernel is not None and bps <= 24:
|
|
799
|
+
self._kernel.decorrelate(a, s, bs, code)
|
|
800
|
+
return
|
|
801
|
+
if code == 8: # left/side: right = left - side
|
|
802
|
+
for i in range(bs):
|
|
803
|
+
s[i] = a[i] - s[i]
|
|
804
|
+
elif code == 9: # side/right: left = side + right
|
|
805
|
+
for i in range(bs):
|
|
806
|
+
a[i] += s[i]
|
|
807
|
+
else: # mid/side
|
|
808
|
+
for i in range(bs):
|
|
809
|
+
side = s[i]
|
|
810
|
+
mid = (a[i] << 1) | (side & 1)
|
|
811
|
+
a[i] = (mid + side) >> 1
|
|
812
|
+
s[i] = (mid - side) >> 1
|
|
813
|
+
|
|
814
|
+
def _decode_pcm(self):
|
|
815
|
+
if not self._decode():
|
|
816
|
+
return False
|
|
817
|
+
bs = self._bs
|
|
818
|
+
bps = self._frame_bps
|
|
819
|
+
nch = self._channels
|
|
820
|
+
pcm = self._pcm
|
|
821
|
+
chan = self._chan
|
|
822
|
+
kernel = self._kernel
|
|
823
|
+
if kernel is not None:
|
|
824
|
+
for c in range(nch):
|
|
825
|
+
kernel.to_pcm16(pcm, chan[c], bs, nch, c, bps - 16)
|
|
826
|
+
elif self._accel and bps in (16, 24, 32):
|
|
827
|
+
# Bytes 0-1 (16-bit), 1-2 (24-bit) or 2-3 (32-bit) of each
|
|
828
|
+
# little-endian int32 are exactly the 16-bit output. Let ulab copy
|
|
829
|
+
# them byte by byte into place; it has no int32 type.
|
|
830
|
+
off = (bps - 16) >> 3
|
|
831
|
+
step = 2 * nch
|
|
832
|
+
dst = _np.frombuffer(self._pcm_mv[: bs * nch], dtype=_np.uint8)
|
|
833
|
+
for c in range(nch):
|
|
834
|
+
src = _np.frombuffer(chan[c], dtype=_np.uint8, count=4 * bs)
|
|
835
|
+
dst[2 * c :: step] = src[off::4]
|
|
836
|
+
dst[2 * c + 1 :: step] = src[off + 1 :: 4]
|
|
837
|
+
elif nch == 2:
|
|
838
|
+
l, r = chan[0], chan[1]
|
|
839
|
+
if bps > 16:
|
|
840
|
+
sh = bps - 16
|
|
841
|
+
j = 0
|
|
842
|
+
for i in range(bs):
|
|
843
|
+
pcm[j] = l[i] >> sh
|
|
844
|
+
pcm[j + 1] = r[i] >> sh
|
|
845
|
+
j += 2
|
|
846
|
+
else:
|
|
847
|
+
sh = 16 - bps
|
|
848
|
+
j = 0
|
|
849
|
+
for i in range(bs):
|
|
850
|
+
pcm[j] = l[i] << sh
|
|
851
|
+
pcm[j + 1] = r[i] << sh
|
|
852
|
+
j += 2
|
|
853
|
+
else:
|
|
854
|
+
m = chan[0]
|
|
855
|
+
if bps > 16:
|
|
856
|
+
sh = bps - 16
|
|
857
|
+
for i in range(bs):
|
|
858
|
+
pcm[i] = m[i] >> sh
|
|
859
|
+
else:
|
|
860
|
+
sh = 16 - bps
|
|
861
|
+
for i in range(bs):
|
|
862
|
+
pcm[i] = m[i] << sh
|
|
863
|
+
self._pcm_len = bs * nch
|
|
864
|
+
self._pcm_pos = 0
|
|
865
|
+
return True
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
def _gain(pcm, n, q):
|
|
869
|
+
# Scale n samples of pcm in place by q / 32768.
|
|
870
|
+
if _kernel is not None:
|
|
871
|
+
_kernel.gain(pcm, n, q)
|
|
872
|
+
return
|
|
873
|
+
for i in range(n):
|
|
874
|
+
pcm[i] = (pcm[i] * q) >> 15
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
class FLACPlayer:
|
|
878
|
+
"""Stream a `FLACDecoder` to an audio output from Python.
|
|
879
|
+
|
|
880
|
+
Decoded PCM goes straight into a double-buffered `audiocore.RawSample` that
|
|
881
|
+
loops on the output, so playback keeps going while your code runs. Call
|
|
882
|
+
`update` often (several times per ``buffer_ms``); each call does at most one
|
|
883
|
+
FLAC frame of decoding.
|
|
884
|
+
|
|
885
|
+
The output must copy each half when it starts playing it, as ``I2SOut``,
|
|
886
|
+
``PWMAudioOut`` and ``AudioOut`` do (into their DMA buffers): a half is
|
|
887
|
+
rewritten as soon as the output has taken it. Not for ``audiomixer``, which
|
|
888
|
+
reads the sample in place.
|
|
889
|
+
|
|
890
|
+
Keep the buffer in internal RAM where there is a choice: on an RP2350 with
|
|
891
|
+
PSRAM, writing decoded audio to PSRAM nearly doubled the decode time at
|
|
892
|
+
96 kHz. Pass ``buffer`` to allocate it early, before other code takes the
|
|
893
|
+
internal RAM.
|
|
894
|
+
|
|
895
|
+
:param audio: an ``audiobusio.I2SOut``, ``audiopwmio.PWMAudioOut`` or
|
|
896
|
+
``audioio.AudioOut``-like object with ``play(sample, loop=True)``,
|
|
897
|
+
``stop()`` and optionally ``pause()``
|
|
898
|
+
:param int buffer_ms: length of each half of the output buffer. The output's DMA
|
|
899
|
+
needs internal RAM for twice the whole buffer; on a Fruit Jam with the display
|
|
900
|
+
running, 120 ms is about the most that fits at 48 kHz stereo
|
|
901
|
+
:param buffer: optional ``array('h')`` to use for both halves (its length
|
|
902
|
+
sets the half size; ``buffer_ms`` is then ignored)
|
|
903
|
+
:param float level: output level from 0.0 (silent) to 1.0 (unchanged), see `level`
|
|
904
|
+
"""
|
|
905
|
+
|
|
906
|
+
def __init__(self, audio, *, buffer_ms=64, buffer=None, level=1.0):
|
|
907
|
+
self._audio = audio
|
|
908
|
+
self.level = level
|
|
909
|
+
self._buffer_ms = buffer_ms
|
|
910
|
+
self._fixed = buffer is not None
|
|
911
|
+
self._buf = buffer
|
|
912
|
+
self._mv = memoryview(buffer) if buffer is not None else None
|
|
913
|
+
self._zero = array("h", [0]) * 256
|
|
914
|
+
self._decoder = None
|
|
915
|
+
self._playing = False
|
|
916
|
+
self._paused = False
|
|
917
|
+
self.underruns = 0
|
|
918
|
+
"""Count of buffer halves that were not ready in time."""
|
|
919
|
+
|
|
920
|
+
@property
|
|
921
|
+
def playing(self):
|
|
922
|
+
"""True while a file is playing (including while paused)."""
|
|
923
|
+
return self._playing
|
|
924
|
+
|
|
925
|
+
@property
|
|
926
|
+
def paused(self):
|
|
927
|
+
"""True while paused."""
|
|
928
|
+
return self._paused
|
|
929
|
+
|
|
930
|
+
@property
|
|
931
|
+
def decoder(self):
|
|
932
|
+
"""The `FLACDecoder` being played."""
|
|
933
|
+
return self._decoder
|
|
934
|
+
|
|
935
|
+
@property
|
|
936
|
+
def level(self):
|
|
937
|
+
"""Output level from 0.0 (silent) to 1.0 (unchanged, the default), like
|
|
938
|
+
``audiomixer.MixerVoice.level``. Samples are scaled as they are decoded, so a
|
|
939
|
+
change is heard after the buffered audio (up to two ``buffer_ms`` halves)."""
|
|
940
|
+
return self._q / 32768
|
|
941
|
+
|
|
942
|
+
@level.setter
|
|
943
|
+
def level(self, value):
|
|
944
|
+
self._q = min(32768, max(0, int(value * 32768 + 0.5)))
|
|
945
|
+
|
|
946
|
+
def play(self, decoder, *, loop=False):
|
|
947
|
+
"""Start playing ``decoder`` from its current position."""
|
|
948
|
+
import audiocore # noqa: PLC0415, so the decoder also imports on desktop Python
|
|
949
|
+
|
|
950
|
+
self.stop()
|
|
951
|
+
self._decoder = decoder
|
|
952
|
+
self._loop = loop
|
|
953
|
+
ch = decoder.channel_count
|
|
954
|
+
if self._fixed:
|
|
955
|
+
half = len(self._buf) // 2 // ch * ch
|
|
956
|
+
else:
|
|
957
|
+
half = max(256, decoder.sample_rate * self._buffer_ms // 1000) * ch
|
|
958
|
+
if self._buf is None or len(self._buf) != 2 * half:
|
|
959
|
+
self._buf = array("h", [0]) * (2 * half)
|
|
960
|
+
self._mv = memoryview(self._buf)
|
|
961
|
+
self._half = half
|
|
962
|
+
self._eof = False
|
|
963
|
+
self._last_real = -1
|
|
964
|
+
# prime both halves before starting the output
|
|
965
|
+
for w in range(2):
|
|
966
|
+
self._w = w
|
|
967
|
+
self._filled = 0
|
|
968
|
+
while self._filled < half:
|
|
969
|
+
self._fill_once()
|
|
970
|
+
if self._eof and self._last_real < 0:
|
|
971
|
+
self._last_real = w
|
|
972
|
+
self._w = 2
|
|
973
|
+
self._filled = 0
|
|
974
|
+
self._half_ns = half // ch * 1_000_000_000 // decoder.sample_rate
|
|
975
|
+
self._sample = audiocore.RawSample(
|
|
976
|
+
self._mv[: 2 * half],
|
|
977
|
+
channel_count=ch,
|
|
978
|
+
sample_rate=decoder.sample_rate,
|
|
979
|
+
single_buffer=False,
|
|
980
|
+
)
|
|
981
|
+
self._audio.play(self._sample, loop=True)
|
|
982
|
+
self._t0 = _now()
|
|
983
|
+
self._playing = True
|
|
984
|
+
self._paused = False
|
|
985
|
+
|
|
986
|
+
def _fill_once(self):
|
|
987
|
+
# Decode up to one frame into the half being written; pad with silence at the end.
|
|
988
|
+
half = self._half
|
|
989
|
+
a = (self._w & 1) * half + self._filled
|
|
990
|
+
room = half - self._filled
|
|
991
|
+
if not self._eof:
|
|
992
|
+
n = self._decoder.fill(self._mv[a : a + room])
|
|
993
|
+
if not n and self._loop:
|
|
994
|
+
self._decoder.rewind()
|
|
995
|
+
n = self._decoder.fill(self._mv[a : a + room])
|
|
996
|
+
if n:
|
|
997
|
+
if self._q < 32768:
|
|
998
|
+
_gain(self._mv[a : a + n], n, self._q)
|
|
999
|
+
self._filled += n
|
|
1000
|
+
return
|
|
1001
|
+
self._eof = True
|
|
1002
|
+
z = self._zero
|
|
1003
|
+
while room > 0:
|
|
1004
|
+
k = room if room < len(z) else len(z)
|
|
1005
|
+
self._mv[a : a + k] = z[:k]
|
|
1006
|
+
a += k
|
|
1007
|
+
room -= k
|
|
1008
|
+
self._filled = half
|
|
1009
|
+
|
|
1010
|
+
def update(self):
|
|
1011
|
+
"""Do a slice of decoding work. Returns True while still playing."""
|
|
1012
|
+
if not self._playing:
|
|
1013
|
+
return False
|
|
1014
|
+
if self._paused:
|
|
1015
|
+
return True
|
|
1016
|
+
elapsed = _now() - self._t0
|
|
1017
|
+
hd = self._half_ns
|
|
1018
|
+
# halves the output has taken so far: two at start, then one per half
|
|
1019
|
+
fetched = 2 + elapsed // hd
|
|
1020
|
+
if self._last_real >= 0 and fetched >= self._last_real + 3:
|
|
1021
|
+
# the half after the last real one (silence) is now playing
|
|
1022
|
+
self.stop()
|
|
1023
|
+
return False
|
|
1024
|
+
w = self._w
|
|
1025
|
+
if w < fetched:
|
|
1026
|
+
# fell behind: half w was taken before it was ready; skip ahead
|
|
1027
|
+
self.underruns += 1
|
|
1028
|
+
w = self._w = fetched + 1
|
|
1029
|
+
self._filled = 0
|
|
1030
|
+
# Half w reuses the slot of half w - 2, taken at (w - 3) * hd. Leave a quarter
|
|
1031
|
+
# half of margin for the output's background task to copy it.
|
|
1032
|
+
if elapsed < (w - 3) * hd + (hd >> 2):
|
|
1033
|
+
return True
|
|
1034
|
+
self._fill_once()
|
|
1035
|
+
if self._filled >= self._half:
|
|
1036
|
+
if self._eof and self._last_real < 0:
|
|
1037
|
+
self._last_real = w
|
|
1038
|
+
self._w = w + 1
|
|
1039
|
+
self._filled = 0
|
|
1040
|
+
return True
|
|
1041
|
+
|
|
1042
|
+
def pause(self):
|
|
1043
|
+
"""Pause output, if the audio object supports it."""
|
|
1044
|
+
if self._playing and not self._paused:
|
|
1045
|
+
self._audio.pause()
|
|
1046
|
+
self._pause_t = _now()
|
|
1047
|
+
self._paused = True
|
|
1048
|
+
|
|
1049
|
+
def resume(self):
|
|
1050
|
+
"""Resume after `pause`."""
|
|
1051
|
+
if self._paused:
|
|
1052
|
+
self._t0 += _now() - self._pause_t
|
|
1053
|
+
self._audio.resume()
|
|
1054
|
+
self._paused = False
|
|
1055
|
+
|
|
1056
|
+
def stop(self):
|
|
1057
|
+
"""Stop playback."""
|
|
1058
|
+
if self._playing:
|
|
1059
|
+
self._audio.stop()
|
|
1060
|
+
self._playing = False
|
|
1061
|
+
self._paused = False
|
|
1062
|
+
|
|
1063
|
+
def deinit(self):
|
|
1064
|
+
"""Stop and release buffers."""
|
|
1065
|
+
self.stop()
|
|
1066
|
+
self._buf = None
|
|
1067
|
+
self._mv = None
|
|
1068
|
+
self._fixed = False
|
|
1069
|
+
self._sample = None
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|