adafruit-circuitpython-ogg 1.0.1__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_ogg-1.0.1.dist-info/METADATA +189 -0
- adafruit_circuitpython_ogg-1.0.1.dist-info/RECORD +21 -0
- adafruit_circuitpython_ogg-1.0.1.dist-info/WHEEL +5 -0
- adafruit_circuitpython_ogg-1.0.1.dist-info/licenses/LICENSE +21 -0
- adafruit_circuitpython_ogg-1.0.1.dist-info/top_level.txt +1 -0
- adafruit_ogg/__init__.py +1845 -0
- adafruit_ogg/_float.py +312 -0
- adafruit_ogg/ogg_kernel.armv6m.mpy +0 -0
- adafruit_ogg/ogg_kernel.armv6m.mpy.license +3 -0
- adafruit_ogg/ogg_kernel.armv7emsp.mpy +0 -0
- adafruit_ogg/ogg_kernel.armv7emsp.mpy.license +3 -0
- adafruit_ogg/ogg_kernel.rv32imc.mpy +0 -0
- adafruit_ogg/ogg_kernel.rv32imc.mpy.license +3 -0
- adafruit_ogg/ogg_kernel.xtensawin.mpy +0 -0
- adafruit_ogg/ogg_kernel.xtensawin.mpy.license +3 -0
- adafruit_ogg/ogg_native.armv6m.mpy +0 -0
- adafruit_ogg/ogg_native.armv6m.mpy.license +3 -0
- adafruit_ogg/ogg_native.armv7emsp.mpy +0 -0
- adafruit_ogg/ogg_native.armv7emsp.mpy.license +3 -0
- adafruit_ogg/ogg_native.xtensawin.mpy +0 -0
- adafruit_ogg/ogg_native.xtensawin.mpy.license +3 -0
adafruit_ogg/__init__.py
ADDED
|
@@ -0,0 +1,1845 @@
|
|
|
1
|
+
# SPDX-FileCopyrightText: Copyright (c) 2026 Mikey Sklar for Adafruit Industries
|
|
2
|
+
# SPDX-License-Identifier: MIT
|
|
3
|
+
"""
|
|
4
|
+
`adafruit_ogg`
|
|
5
|
+
================================================================================
|
|
6
|
+
|
|
7
|
+
Ogg Vorbis decoding and playback for CircuitPython.
|
|
8
|
+
|
|
9
|
+
`OggDecoder` reads an Ogg Vorbis file and returns 16-bit signed PCM. `OggPlayer` streams it
|
|
10
|
+
to an audio output (``I2SOut``, ``PWMAudioOut`` or ``AudioOut``) through a double-buffered
|
|
11
|
+
``audiocore.RawSample``; call `OggPlayer.update` often from your main loop.
|
|
12
|
+
|
|
13
|
+
The audio decode (codebooks, floor, residue, channel coupling, the inverse MDCT and
|
|
14
|
+
overlap-add) runs in a prebuilt kernel: ``ogg_native.<arch>.mpy``, built in C from
|
|
15
|
+
``src/native/ogg_native.c``, on Arm and ESP32 (Xtensa) chips, and otherwise
|
|
16
|
+
``ogg_kernel.<arch>.mpy``, built from the turbo (viper) code in ``src/ogg_turbo.py`` with
|
|
17
|
+
``mpy-cross -march``. Both are fixed
|
|
18
|
+
point, so the output can differ from a floating point decoder by a few least significant bits.
|
|
19
|
+
Without a kernel for the board, a plain Python floating point decoder runs instead (correct,
|
|
20
|
+
but many times slower than real time).
|
|
21
|
+
|
|
22
|
+
Written from the Xiph.Org "Vorbis I specification" (section numbers in comments refer to
|
|
23
|
+
it). No third-party decoder code is included.
|
|
24
|
+
|
|
25
|
+
* Author(s): Mikey Sklar
|
|
26
|
+
|
|
27
|
+
Implementation Notes
|
|
28
|
+
--------------------
|
|
29
|
+
|
|
30
|
+
**Hardware:**
|
|
31
|
+
|
|
32
|
+
* Tested on the Adafruit Fruit Jam (RP2350B).
|
|
33
|
+
|
|
34
|
+
**Software and Dependencies:**
|
|
35
|
+
|
|
36
|
+
* Adafruit CircuitPython firmware 11 or later, with native ``.mpy`` loading:
|
|
37
|
+
https://circuitpython.org/downloads
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
import gc
|
|
41
|
+
import math
|
|
42
|
+
import struct
|
|
43
|
+
from array import array
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
from time import monotonic_ns as _now
|
|
47
|
+
except ImportError: # unix port
|
|
48
|
+
from time import time_ns as _now
|
|
49
|
+
|
|
50
|
+
# The audio decode kernel, prebuilt per chip: ogg_native.<arch>.mpy (C, src/native) on Arm
|
|
51
|
+
# and Xtensa chips, else ogg_kernel.<arch>.mpy (turbo, src/ogg_turbo.py). Same functions and tables.
|
|
52
|
+
# Without either (CPython, or a chip with no build) the plain Python decoder runs.
|
|
53
|
+
try:
|
|
54
|
+
from adafruit_ogg import ogg_native as _kernel
|
|
55
|
+
except (ImportError, ValueError):
|
|
56
|
+
try:
|
|
57
|
+
from adafruit_ogg import ogg_kernel as _kernel
|
|
58
|
+
except (ImportError, ValueError):
|
|
59
|
+
_kernel = None
|
|
60
|
+
|
|
61
|
+
__version__ = "1.0.1"
|
|
62
|
+
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_OGG.git"
|
|
63
|
+
|
|
64
|
+
# Plain Python decoder: codewords up to this many bits decode with one table lookup.
|
|
65
|
+
FAST_BITS = 10
|
|
66
|
+
# Turbo kernel: codewords up to this many bits decode with one 16-bit table lookup, longer
|
|
67
|
+
# ones by a binary search. 8 bits is 512 bytes of table per codebook.
|
|
68
|
+
_TURBO_FAST = 8
|
|
69
|
+
|
|
70
|
+
_PI = math.pi
|
|
71
|
+
|
|
72
|
+
# Section 10.1, floor1_inverse_dB_table.
|
|
73
|
+
# fmt: off
|
|
74
|
+
_INV_DB = (
|
|
75
|
+
1.0649863e-07, 1.1341951e-07, 1.2079015e-07, 1.2863978e-07,
|
|
76
|
+
1.3699951e-07, 1.4590251e-07, 1.5538408e-07, 1.6548181e-07,
|
|
77
|
+
1.7623575e-07, 1.8768855e-07, 1.9988561e-07, 2.1287530e-07,
|
|
78
|
+
2.2670913e-07, 2.4144197e-07, 2.5713223e-07, 2.7384213e-07,
|
|
79
|
+
2.9163793e-07, 3.1059021e-07, 3.3077411e-07, 3.5226968e-07,
|
|
80
|
+
3.7516214e-07, 3.9954229e-07, 4.2550680e-07, 4.5315863e-07,
|
|
81
|
+
4.8260743e-07, 5.1396998e-07, 5.4737065e-07, 5.8294187e-07,
|
|
82
|
+
6.2082472e-07, 6.6116941e-07, 7.0413592e-07, 7.4989464e-07,
|
|
83
|
+
7.9862701e-07, 8.5052630e-07, 9.0579828e-07, 9.6466216e-07,
|
|
84
|
+
1.0273513e-06, 1.0941144e-06, 1.1652161e-06, 1.2409384e-06,
|
|
85
|
+
1.3215816e-06, 1.4074654e-06, 1.4989305e-06, 1.5963394e-06,
|
|
86
|
+
1.7000785e-06, 1.8105592e-06, 1.9282195e-06, 2.0535261e-06,
|
|
87
|
+
2.1869758e-06, 2.3290978e-06, 2.4804557e-06, 2.6416497e-06,
|
|
88
|
+
2.8133190e-06, 2.9961443e-06, 3.1908506e-06, 3.3982101e-06,
|
|
89
|
+
3.6190449e-06, 3.8542308e-06, 4.1047004e-06, 4.3714470e-06,
|
|
90
|
+
4.6555282e-06, 4.9580707e-06, 5.2802740e-06, 5.6234160e-06,
|
|
91
|
+
5.9888572e-06, 6.3780469e-06, 6.7925283e-06, 7.2339451e-06,
|
|
92
|
+
7.7040476e-06, 8.2047000e-06, 8.7378876e-06, 9.3057248e-06,
|
|
93
|
+
9.9104632e-06, 1.0554501e-05, 1.1240392e-05, 1.1970856e-05,
|
|
94
|
+
1.2748789e-05, 1.3577278e-05, 1.4459606e-05, 1.5399272e-05,
|
|
95
|
+
1.6400004e-05, 1.7465768e-05, 1.8600792e-05, 1.9809576e-05,
|
|
96
|
+
2.1096914e-05, 2.2467911e-05, 2.3928002e-05, 2.5482978e-05,
|
|
97
|
+
2.7139006e-05, 2.8902651e-05, 3.0780908e-05, 3.2781225e-05,
|
|
98
|
+
3.4911534e-05, 3.7180282e-05, 3.9596466e-05, 4.2169667e-05,
|
|
99
|
+
4.4910090e-05, 4.7828601e-05, 5.0936773e-05, 5.4246931e-05,
|
|
100
|
+
5.7772202e-05, 6.1526565e-05, 6.5524908e-05, 6.9783085e-05,
|
|
101
|
+
7.4317983e-05, 7.9147585e-05, 8.4291040e-05, 8.9768747e-05,
|
|
102
|
+
9.5602426e-05, 0.0001018, 0.0001084, 0.0001154,
|
|
103
|
+
0.0001229, 0.0001309, 0.0001394, 0.0001485,
|
|
104
|
+
0.0001582, 0.0001684, 0.0001794, 0.0001910,
|
|
105
|
+
0.0002035, 0.0002167, 0.0002308, 0.0002458,
|
|
106
|
+
0.0002617, 0.0002788, 0.0002969, 0.0003162,
|
|
107
|
+
0.0003367, 0.0003586, 0.0003819, 0.0004067,
|
|
108
|
+
0.0004332, 0.0004613, 0.0004913, 0.0005232,
|
|
109
|
+
0.0005573, 0.0005935, 0.0006320, 0.0006731,
|
|
110
|
+
0.0007169, 0.0007635, 0.0008131, 0.0008659,
|
|
111
|
+
0.0009222, 0.0009821, 0.0010459, 0.0011139,
|
|
112
|
+
0.0011863, 0.0012634, 0.0013455, 0.0014330,
|
|
113
|
+
0.0015261, 0.0016253, 0.0017309, 0.0018434,
|
|
114
|
+
0.0019632, 0.0020908, 0.0022266, 0.0023713,
|
|
115
|
+
0.0025254, 0.0026895, 0.0028643, 0.0030505,
|
|
116
|
+
0.0032487, 0.0034598, 0.0036847, 0.0039241,
|
|
117
|
+
0.0041792, 0.0044507, 0.0047400, 0.0050480,
|
|
118
|
+
0.0053761, 0.0057254, 0.0060975, 0.0064938,
|
|
119
|
+
0.0069158, 0.0073652, 0.0078438, 0.0083536,
|
|
120
|
+
0.0088964, 0.0094746, 0.0100903, 0.0107460,
|
|
121
|
+
0.0114444, 0.0121881, 0.0129801, 0.0138237,
|
|
122
|
+
0.0147220, 0.0156787, 0.0166976, 0.0177827,
|
|
123
|
+
0.0189384, 0.0201691, 0.0214798, 0.0228757,
|
|
124
|
+
0.0243623, 0.0259455, 0.0276316, 0.0294272,
|
|
125
|
+
0.0313396, 0.0333762, 0.0355452, 0.0378551,
|
|
126
|
+
0.0403151, 0.0429351, 0.0457252, 0.0486967,
|
|
127
|
+
0.0518613, 0.0552315, 0.0588208, 0.0626433,
|
|
128
|
+
0.0667142, 0.0710497, 0.0756669, 0.0805842,
|
|
129
|
+
0.0858210, 0.0913981, 0.0973377, 0.1036633,
|
|
130
|
+
0.1103999, 0.1175743, 0.1252149, 0.1333521,
|
|
131
|
+
0.1420181, 0.1512472, 0.1610761, 0.1715438,
|
|
132
|
+
0.1826916, 0.1945640, 0.2072078, 0.2206734,
|
|
133
|
+
0.2350140, 0.2502865, 0.2665515, 0.2838736,
|
|
134
|
+
0.3023213, 0.3219678, 0.3428911, 0.3651741,
|
|
135
|
+
0.3889052, 0.4141784, 0.4410941, 0.4697589,
|
|
136
|
+
0.5002864, 0.5327979, 0.5674221, 0.6042964,
|
|
137
|
+
0.6435669, 0.6853895, 0.7299300, 0.7773650,
|
|
138
|
+
0.8278826, 0.8816830, 0.9389798, 1.0,
|
|
139
|
+
)
|
|
140
|
+
# fmt: on
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class OggError(ValueError):
|
|
144
|
+
"""Raised for files that are not Ogg Vorbis or use unsupported features."""
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _ilog(x):
|
|
148
|
+
# Section 9.2.1: bits needed to hold x (0 for x <= 0).
|
|
149
|
+
n = 0
|
|
150
|
+
while x > 0:
|
|
151
|
+
n += 1
|
|
152
|
+
x >>= 1
|
|
153
|
+
return n
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def _float32(x):
|
|
157
|
+
# Section 9.2.2: Vorbis packed float.
|
|
158
|
+
mant = x & 0x1FFFFF
|
|
159
|
+
exp = (x >> 21) & 0x3FF
|
|
160
|
+
if x & 0x80000000:
|
|
161
|
+
mant = -mant
|
|
162
|
+
return mant * math.pow(2.0, exp - 788)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _lookup1_values(entries, dim):
|
|
166
|
+
# Section 9.2.3: largest r with r ** dim <= entries.
|
|
167
|
+
r = int(math.pow(entries, 1.0 / dim))
|
|
168
|
+
while math.pow(r + 1, dim) <= entries:
|
|
169
|
+
r += 1
|
|
170
|
+
while r > 0 and math.pow(r, dim) > entries:
|
|
171
|
+
r -= 1
|
|
172
|
+
return r
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
class _Bits:
|
|
176
|
+
"""LSB-first bit reader over one packet (section 2.1.4)."""
|
|
177
|
+
|
|
178
|
+
def __init__(self, data):
|
|
179
|
+
self.data = data
|
|
180
|
+
self.pos = 0
|
|
181
|
+
self.end = len(data)
|
|
182
|
+
self.acc = 0
|
|
183
|
+
self.nb = 0
|
|
184
|
+
self.pad = 0 # zero bits appended past the end of the packet
|
|
185
|
+
self.eop = False
|
|
186
|
+
|
|
187
|
+
def read(self, n):
|
|
188
|
+
acc = self.acc
|
|
189
|
+
nb = self.nb
|
|
190
|
+
if nb < n:
|
|
191
|
+
data = self.data
|
|
192
|
+
pos = self.pos
|
|
193
|
+
end = self.end
|
|
194
|
+
while nb < n:
|
|
195
|
+
if pos < end:
|
|
196
|
+
acc |= data[pos] << nb
|
|
197
|
+
pos += 1
|
|
198
|
+
else:
|
|
199
|
+
self.pad += 8
|
|
200
|
+
nb += 8
|
|
201
|
+
self.pos = pos
|
|
202
|
+
nb -= n
|
|
203
|
+
if nb < self.pad:
|
|
204
|
+
self.eop = True
|
|
205
|
+
self.acc = acc >> n
|
|
206
|
+
self.nb = nb
|
|
207
|
+
return acc & ((1 << n) - 1)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class _Codebook:
|
|
211
|
+
"""Section 3: Huffman codebook plus optional VQ lookup."""
|
|
212
|
+
|
|
213
|
+
def __init__(self, br, float_vq):
|
|
214
|
+
if br.read(24) != 0x564342:
|
|
215
|
+
raise OggError("bad codebook sync")
|
|
216
|
+
self.dim = dim = br.read(16)
|
|
217
|
+
self.entries = entries = br.read(24)
|
|
218
|
+
lengths = bytearray(entries)
|
|
219
|
+
if br.read(1): # ordered
|
|
220
|
+
cur = 0
|
|
221
|
+
length = br.read(5) + 1
|
|
222
|
+
while cur < entries:
|
|
223
|
+
num = br.read(_ilog(entries - cur))
|
|
224
|
+
for i in range(cur, cur + num):
|
|
225
|
+
lengths[i] = length
|
|
226
|
+
cur += num
|
|
227
|
+
length += 1
|
|
228
|
+
if cur > entries:
|
|
229
|
+
raise OggError("bad ordered codebook")
|
|
230
|
+
else:
|
|
231
|
+
sparse = br.read(1)
|
|
232
|
+
for i in range(entries):
|
|
233
|
+
if not sparse or br.read(1):
|
|
234
|
+
lengths[i] = br.read(5) + 1
|
|
235
|
+
self.lengths = lengths
|
|
236
|
+
if float_vq:
|
|
237
|
+
self._build(lengths)
|
|
238
|
+
else:
|
|
239
|
+
# the turbo kernel builds its own tables from the lengths (_pack_books)
|
|
240
|
+
self.maxlen = max(lengths) if entries else 0
|
|
241
|
+
|
|
242
|
+
self.vq = None
|
|
243
|
+
self.lookup = lookup = br.read(4)
|
|
244
|
+
if lookup in (1, 2):
|
|
245
|
+
minimum = _float32(br.read(32))
|
|
246
|
+
delta = _float32(br.read(32))
|
|
247
|
+
bits = br.read(4) + 1
|
|
248
|
+
seq = br.read(1)
|
|
249
|
+
if lookup == 1:
|
|
250
|
+
nvals = _lookup1_values(entries, dim)
|
|
251
|
+
else:
|
|
252
|
+
nvals = entries * dim
|
|
253
|
+
mults = [br.read(bits) for _ in range(nvals)]
|
|
254
|
+
self.minimum = minimum
|
|
255
|
+
self.delta = delta
|
|
256
|
+
self.seq = seq
|
|
257
|
+
self.nvals = nvals
|
|
258
|
+
self.mults = mults
|
|
259
|
+
if lookup in (1, 2) and float_vq:
|
|
260
|
+
# flat array: entry e's vector is vq[e * dim : e * dim + dim]
|
|
261
|
+
nvals = self.nvals
|
|
262
|
+
minimum = self.minimum
|
|
263
|
+
delta = self.delta
|
|
264
|
+
seq = self.seq
|
|
265
|
+
mults = self.mults
|
|
266
|
+
vq = array("f", bytearray(4 * entries * dim))
|
|
267
|
+
k = 0
|
|
268
|
+
for e in range(entries):
|
|
269
|
+
last = 0.0
|
|
270
|
+
if lookup == 1:
|
|
271
|
+
div = 1
|
|
272
|
+
for _ in range(dim):
|
|
273
|
+
v = mults[(e // div) % nvals] * delta + minimum + last
|
|
274
|
+
if seq:
|
|
275
|
+
last = v
|
|
276
|
+
vq[k] = v
|
|
277
|
+
k += 1
|
|
278
|
+
div *= nvals
|
|
279
|
+
else:
|
|
280
|
+
for _ in range(dim):
|
|
281
|
+
v = mults[k] * delta + minimum + last
|
|
282
|
+
if seq:
|
|
283
|
+
last = v
|
|
284
|
+
vq[k] = v
|
|
285
|
+
k += 1
|
|
286
|
+
self.vq = vq
|
|
287
|
+
if lookup > 2:
|
|
288
|
+
raise OggError("bad codebook lookup type")
|
|
289
|
+
if br.eop:
|
|
290
|
+
raise OggError("truncated codebook")
|
|
291
|
+
|
|
292
|
+
def _build(self, lengths):
|
|
293
|
+
# Section 3.2.1 codeword assignment, then a table indexed by the
|
|
294
|
+
# next FAST_BITS stream bits. Entries pack (entry << 5) | length.
|
|
295
|
+
used = [i for i in range(len(lengths)) if lengths[i]]
|
|
296
|
+
maxlen = max(lengths) if used else 0
|
|
297
|
+
self.maxlen = maxlen
|
|
298
|
+
tbits = min(maxlen, FAST_BITS)
|
|
299
|
+
self.tbits = tbits
|
|
300
|
+
self.tmask = (1 << tbits) - 1
|
|
301
|
+
self.table = table = [-1] * (1 << tbits)
|
|
302
|
+
self.long = {}
|
|
303
|
+
if not used:
|
|
304
|
+
return
|
|
305
|
+
if len(used) == 1:
|
|
306
|
+
e = used[0]
|
|
307
|
+
for i in range(len(table)):
|
|
308
|
+
table[i] = (e << 5) | min(lengths[e], tbits)
|
|
309
|
+
return
|
|
310
|
+
avail = [0] * 33
|
|
311
|
+
first = used[0]
|
|
312
|
+
for i in range(1, lengths[first] + 1):
|
|
313
|
+
avail[i] = 1 << (32 - i)
|
|
314
|
+
self._add(first, 0, lengths[first])
|
|
315
|
+
for e in used[1:]:
|
|
316
|
+
length = lengths[e]
|
|
317
|
+
z = length
|
|
318
|
+
while z > 0 and not avail[z]:
|
|
319
|
+
z -= 1
|
|
320
|
+
if z == 0:
|
|
321
|
+
raise OggError("overspecified codebook")
|
|
322
|
+
res = avail[z]
|
|
323
|
+
avail[z] = 0
|
|
324
|
+
# MSB-first codeword -> LSB-first stream order
|
|
325
|
+
v = res >> (32 - length)
|
|
326
|
+
code = 0
|
|
327
|
+
for _ in range(length):
|
|
328
|
+
code = (code << 1) | (v & 1)
|
|
329
|
+
v >>= 1
|
|
330
|
+
self._add(e, code, length)
|
|
331
|
+
for y in range(length, z, -1):
|
|
332
|
+
avail[y] = res + (1 << (32 - y))
|
|
333
|
+
|
|
334
|
+
def _add(self, entry, code, length):
|
|
335
|
+
tbits = self.tbits
|
|
336
|
+
if length <= tbits:
|
|
337
|
+
packed = (entry << 5) | length
|
|
338
|
+
table = self.table
|
|
339
|
+
for i in range(code, 1 << tbits, 1 << length):
|
|
340
|
+
table[i] = packed
|
|
341
|
+
else:
|
|
342
|
+
self.long[code | (1 << length)] = entry
|
|
343
|
+
|
|
344
|
+
def decode(self, br):
|
|
345
|
+
"""Read one codeword; returns the entry number or -1 at end of packet."""
|
|
346
|
+
tb = self.tbits
|
|
347
|
+
acc = br.acc
|
|
348
|
+
nb = br.nb
|
|
349
|
+
if nb < tb:
|
|
350
|
+
data = br.data
|
|
351
|
+
pos = br.pos
|
|
352
|
+
end = br.end
|
|
353
|
+
while nb < tb:
|
|
354
|
+
if pos < end:
|
|
355
|
+
acc |= data[pos] << nb
|
|
356
|
+
pos += 1
|
|
357
|
+
else:
|
|
358
|
+
br.pad += 8
|
|
359
|
+
nb += 8
|
|
360
|
+
br.pos = pos
|
|
361
|
+
e = self.table[acc & self.tmask]
|
|
362
|
+
if e < 0:
|
|
363
|
+
br.acc = acc
|
|
364
|
+
br.nb = nb
|
|
365
|
+
return self._decode_long(br)
|
|
366
|
+
n = e & 31
|
|
367
|
+
nb -= n
|
|
368
|
+
br.acc = acc >> n
|
|
369
|
+
br.nb = nb
|
|
370
|
+
if nb < br.pad:
|
|
371
|
+
br.eop = True
|
|
372
|
+
return -1
|
|
373
|
+
return e >> 5
|
|
374
|
+
|
|
375
|
+
def _decode_long(self, br):
|
|
376
|
+
long = self.long
|
|
377
|
+
for n in range(self.tbits + 1, self.maxlen + 1):
|
|
378
|
+
while br.nb < n:
|
|
379
|
+
if br.pos < br.end:
|
|
380
|
+
br.acc |= br.data[br.pos] << br.nb
|
|
381
|
+
br.pos += 1
|
|
382
|
+
else:
|
|
383
|
+
br.pad += 8
|
|
384
|
+
br.nb += 8
|
|
385
|
+
e = long.get((br.acc & ((1 << n) - 1)) | (1 << n))
|
|
386
|
+
if e is not None:
|
|
387
|
+
br.acc >>= n
|
|
388
|
+
br.nb -= n
|
|
389
|
+
if br.nb < br.pad:
|
|
390
|
+
break
|
|
391
|
+
return e
|
|
392
|
+
br.eop = True
|
|
393
|
+
return -1
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
class _Floor1:
|
|
397
|
+
"""Section 7: floor type 1."""
|
|
398
|
+
|
|
399
|
+
def __init__(self, br):
|
|
400
|
+
parts = br.read(5)
|
|
401
|
+
self.part_class = pc = [br.read(4) for _ in range(parts)]
|
|
402
|
+
nclass = max(pc) + 1 if pc else 0
|
|
403
|
+
self.cdim = []
|
|
404
|
+
self.csub = []
|
|
405
|
+
self.cmaster = []
|
|
406
|
+
self.cbooks = []
|
|
407
|
+
for _ in range(nclass):
|
|
408
|
+
self.cdim.append(br.read(3) + 1)
|
|
409
|
+
sub = br.read(2)
|
|
410
|
+
self.csub.append(sub)
|
|
411
|
+
self.cmaster.append(br.read(8) if sub else -1)
|
|
412
|
+
self.cbooks.append([br.read(8) - 1 for _ in range(1 << sub)])
|
|
413
|
+
self.mult = br.read(2) + 1
|
|
414
|
+
rbits = br.read(4)
|
|
415
|
+
xs = [0, 1 << rbits]
|
|
416
|
+
for c in pc:
|
|
417
|
+
for _ in range(self.cdim[c]):
|
|
418
|
+
xs.append(br.read(rbits))
|
|
419
|
+
self.xs = xs
|
|
420
|
+
nx = len(xs)
|
|
421
|
+
if len(set(xs)) != nx:
|
|
422
|
+
raise OggError("duplicate floor1 X")
|
|
423
|
+
self.range = (256, 128, 86, 64)[self.mult - 1]
|
|
424
|
+
self.ybits = _ilog(self.range - 1)
|
|
425
|
+
# Sections 9.2.4 and 9.2.5
|
|
426
|
+
low = [0] * nx
|
|
427
|
+
high = [0] * nx
|
|
428
|
+
for i in range(2, nx):
|
|
429
|
+
lo = -1
|
|
430
|
+
hi = -1
|
|
431
|
+
for j in range(i):
|
|
432
|
+
if xs[j] < xs[i] and (lo < 0 or xs[j] > xs[lo]):
|
|
433
|
+
lo = j
|
|
434
|
+
if xs[j] > xs[i] and (hi < 0 or xs[j] < xs[hi]):
|
|
435
|
+
hi = j
|
|
436
|
+
low[i] = lo
|
|
437
|
+
high[i] = hi
|
|
438
|
+
self.low = low
|
|
439
|
+
self.high = high
|
|
440
|
+
self.order = sorted(range(nx), key=lambda k: xs[k])
|
|
441
|
+
|
|
442
|
+
def decode(self, br, books):
|
|
443
|
+
"""Section 7.2.3. Returns the Y list, or None if the floor is unused."""
|
|
444
|
+
if not br.read(1):
|
|
445
|
+
return None
|
|
446
|
+
ybits = self.ybits
|
|
447
|
+
ys = [br.read(ybits), br.read(ybits)]
|
|
448
|
+
cdim = self.cdim
|
|
449
|
+
csub = self.csub
|
|
450
|
+
cmaster = self.cmaster
|
|
451
|
+
cbooks = self.cbooks
|
|
452
|
+
for c in self.part_class:
|
|
453
|
+
bits = csub[c]
|
|
454
|
+
cval = 0
|
|
455
|
+
if bits:
|
|
456
|
+
cval = books[cmaster[c]].decode(br)
|
|
457
|
+
mask = (1 << bits) - 1
|
|
458
|
+
sub = cbooks[c]
|
|
459
|
+
for _ in range(cdim[c]):
|
|
460
|
+
book = sub[cval & mask]
|
|
461
|
+
cval >>= bits
|
|
462
|
+
ys.append(books[book].decode(br) if book >= 0 else 0)
|
|
463
|
+
if br.eop:
|
|
464
|
+
return None
|
|
465
|
+
return ys
|
|
466
|
+
|
|
467
|
+
def render(self, ys, n2):
|
|
468
|
+
"""Section 7.2.4: returns n2 linear floor values."""
|
|
469
|
+
xs = self.xs
|
|
470
|
+
low = self.low
|
|
471
|
+
high = self.high
|
|
472
|
+
rng = self.range
|
|
473
|
+
nx = len(xs)
|
|
474
|
+
fy = [0] * nx
|
|
475
|
+
step2 = [False] * nx
|
|
476
|
+
fy[0] = ys[0]
|
|
477
|
+
fy[1] = ys[1]
|
|
478
|
+
step2[0] = step2[1] = True
|
|
479
|
+
for i in range(2, nx):
|
|
480
|
+
lo = low[i]
|
|
481
|
+
hi = high[i]
|
|
482
|
+
# render_point, section 9.2.6
|
|
483
|
+
x0 = xs[lo]
|
|
484
|
+
y0 = fy[lo]
|
|
485
|
+
dy = fy[hi] - y0
|
|
486
|
+
adx = xs[hi] - x0
|
|
487
|
+
off = (abs(dy) * (xs[i] - x0)) // adx
|
|
488
|
+
pred = y0 - off if dy < 0 else y0 + off
|
|
489
|
+
val = ys[i]
|
|
490
|
+
hroom = rng - pred
|
|
491
|
+
lroom = pred
|
|
492
|
+
room = (hroom if hroom < lroom else lroom) * 2
|
|
493
|
+
if val:
|
|
494
|
+
step2[lo] = step2[hi] = step2[i] = True
|
|
495
|
+
if val >= room:
|
|
496
|
+
if hroom > lroom:
|
|
497
|
+
v = val - lroom + pred
|
|
498
|
+
else:
|
|
499
|
+
v = pred - val + hroom - 1
|
|
500
|
+
elif val & 1:
|
|
501
|
+
v = pred - ((val + 1) >> 1)
|
|
502
|
+
else:
|
|
503
|
+
v = pred + (val >> 1)
|
|
504
|
+
if v < 0:
|
|
505
|
+
v = 0
|
|
506
|
+
elif v >= rng:
|
|
507
|
+
v = rng - 1
|
|
508
|
+
fy[i] = v
|
|
509
|
+
else:
|
|
510
|
+
fy[i] = pred
|
|
511
|
+
|
|
512
|
+
out = [0.0] * n2
|
|
513
|
+
mult = self.mult
|
|
514
|
+
order = self.order
|
|
515
|
+
lx = 0
|
|
516
|
+
ly = fy[order[0]] * mult
|
|
517
|
+
hx = 0
|
|
518
|
+
hy = ly
|
|
519
|
+
for k in order[1:]:
|
|
520
|
+
if step2[k]:
|
|
521
|
+
hx = xs[k]
|
|
522
|
+
hy = fy[k] * mult
|
|
523
|
+
_render_line(lx, ly, hx, hy, out, n2)
|
|
524
|
+
lx = hx
|
|
525
|
+
ly = hy
|
|
526
|
+
if hx < n2:
|
|
527
|
+
_render_line(hx, hy, n2, hy, out, n2)
|
|
528
|
+
return out
|
|
529
|
+
|
|
530
|
+
|
|
531
|
+
def _render_line(x0, y0, x1, y1, out, n2):
|
|
532
|
+
# Section 9.2.7, writing inverse-dB values directly.
|
|
533
|
+
if x0 >= n2:
|
|
534
|
+
return
|
|
535
|
+
tab = _INV_DB
|
|
536
|
+
dy = y1 - y0
|
|
537
|
+
adx = x1 - x0
|
|
538
|
+
ady = dy if dy >= 0 else -dy
|
|
539
|
+
base = ady // adx
|
|
540
|
+
if dy < 0:
|
|
541
|
+
base = -base
|
|
542
|
+
sy = base - 1
|
|
543
|
+
else:
|
|
544
|
+
sy = base + 1
|
|
545
|
+
ady -= (base if base >= 0 else -base) * adx
|
|
546
|
+
y = y0
|
|
547
|
+
err = 0
|
|
548
|
+
out[x0] = tab[y]
|
|
549
|
+
x1 = min(x1, n2)
|
|
550
|
+
for x in range(x0 + 1, x1):
|
|
551
|
+
err += ady
|
|
552
|
+
if err >= adx:
|
|
553
|
+
err -= adx
|
|
554
|
+
y += sy
|
|
555
|
+
else:
|
|
556
|
+
y += base
|
|
557
|
+
out[x] = tab[y]
|
|
558
|
+
|
|
559
|
+
|
|
560
|
+
class _Residue:
|
|
561
|
+
"""Section 8: residue types 0, 1 and 2."""
|
|
562
|
+
|
|
563
|
+
def __init__(self, br, rtype):
|
|
564
|
+
self.type = rtype
|
|
565
|
+
self.begin = br.read(24)
|
|
566
|
+
self.end = br.read(24)
|
|
567
|
+
self.psize = br.read(24) + 1
|
|
568
|
+
self.nclass = br.read(6) + 1
|
|
569
|
+
self.classbook = br.read(8)
|
|
570
|
+
cascade = []
|
|
571
|
+
for _ in range(self.nclass):
|
|
572
|
+
lo = br.read(3)
|
|
573
|
+
hi = br.read(5) if br.read(1) else 0
|
|
574
|
+
cascade.append(hi * 8 + lo)
|
|
575
|
+
self.books = [[br.read(8) if c & (1 << j) else -1 for j in range(8)] for c in cascade]
|
|
576
|
+
|
|
577
|
+
def decode(self, br, books, vecs, dnd, n2):
|
|
578
|
+
ch = len(vecs)
|
|
579
|
+
if self.type == 2:
|
|
580
|
+
if all(dnd):
|
|
581
|
+
return
|
|
582
|
+
big = [0.0] * (n2 * ch)
|
|
583
|
+
self._decode(br, books, [big], [False], n2 * ch)
|
|
584
|
+
if ch == 1:
|
|
585
|
+
vecs[0][:] = big
|
|
586
|
+
else:
|
|
587
|
+
for c in range(ch):
|
|
588
|
+
vecs[c][:] = big[c::ch]
|
|
589
|
+
else:
|
|
590
|
+
self._decode(br, books, vecs, dnd, n2)
|
|
591
|
+
|
|
592
|
+
def _decode(self, br, books, vecs, dnd, size):
|
|
593
|
+
# Section 8.6.2
|
|
594
|
+
begin = self.begin if self.begin < size else size
|
|
595
|
+
end = self.end if self.end < size else size
|
|
596
|
+
psize = self.psize
|
|
597
|
+
nparts = (end - begin) // psize
|
|
598
|
+
if nparts <= 0:
|
|
599
|
+
return
|
|
600
|
+
cbook = books[self.classbook]
|
|
601
|
+
cpw = cbook.dim
|
|
602
|
+
nclass = self.nclass
|
|
603
|
+
rbooks = self.books
|
|
604
|
+
fmt0 = self.type == 0
|
|
605
|
+
ch = len(vecs)
|
|
606
|
+
classes = [[0] * (nparts + cpw) for _ in range(ch)]
|
|
607
|
+
for p in range(8):
|
|
608
|
+
part = 0
|
|
609
|
+
while part < nparts:
|
|
610
|
+
if p == 0:
|
|
611
|
+
for c in range(ch):
|
|
612
|
+
if dnd[c]:
|
|
613
|
+
continue
|
|
614
|
+
t = cbook.decode(br)
|
|
615
|
+
if t < 0:
|
|
616
|
+
return
|
|
617
|
+
cl = classes[c]
|
|
618
|
+
for i in range(cpw - 1, -1, -1):
|
|
619
|
+
cl[part + i] = t % nclass
|
|
620
|
+
t //= nclass
|
|
621
|
+
for _ in range(cpw):
|
|
622
|
+
if part >= nparts:
|
|
623
|
+
break
|
|
624
|
+
for c in range(ch):
|
|
625
|
+
if dnd[c]:
|
|
626
|
+
continue
|
|
627
|
+
bk = rbooks[classes[c][part]][p]
|
|
628
|
+
if bk < 0:
|
|
629
|
+
continue
|
|
630
|
+
book = books[bk]
|
|
631
|
+
vq = book.vq
|
|
632
|
+
if vq is None:
|
|
633
|
+
raise OggError("residue book has no VQ")
|
|
634
|
+
v = vecs[c]
|
|
635
|
+
off = begin + part * psize
|
|
636
|
+
dim = book.dim
|
|
637
|
+
if fmt0:
|
|
638
|
+
step = psize // dim
|
|
639
|
+
for i in range(step):
|
|
640
|
+
e = book.decode(br)
|
|
641
|
+
if e < 0:
|
|
642
|
+
return
|
|
643
|
+
k = off + i
|
|
644
|
+
e *= dim
|
|
645
|
+
for j in range(e, e + dim):
|
|
646
|
+
v[k] += vq[j]
|
|
647
|
+
k += step
|
|
648
|
+
elif not _part1(br, book, v, off, off + psize):
|
|
649
|
+
return
|
|
650
|
+
part += 1
|
|
651
|
+
|
|
652
|
+
|
|
653
|
+
def _part1(br, book, v, k, stop):
|
|
654
|
+
# Residue format 1 partition (8.6.4) with the codeword lookup inlined:
|
|
655
|
+
# this loop is where most of the decode time goes. False at end of packet.
|
|
656
|
+
table = book.table
|
|
657
|
+
mask = book.tmask
|
|
658
|
+
tb = book.tbits
|
|
659
|
+
vq = book.vq
|
|
660
|
+
dim = book.dim
|
|
661
|
+
data = br.data
|
|
662
|
+
pos = br.pos
|
|
663
|
+
end = br.end
|
|
664
|
+
acc = br.acc
|
|
665
|
+
nb = br.nb
|
|
666
|
+
pad = br.pad
|
|
667
|
+
while k < stop:
|
|
668
|
+
while nb < tb:
|
|
669
|
+
if pos < end:
|
|
670
|
+
acc |= data[pos] << nb
|
|
671
|
+
pos += 1
|
|
672
|
+
else:
|
|
673
|
+
pad += 8
|
|
674
|
+
nb += 8
|
|
675
|
+
e = table[acc & mask]
|
|
676
|
+
if e < 0:
|
|
677
|
+
br.acc = acc
|
|
678
|
+
br.nb = nb
|
|
679
|
+
br.pos = pos
|
|
680
|
+
br.pad = pad
|
|
681
|
+
e = book._decode_long(br)
|
|
682
|
+
if e < 0:
|
|
683
|
+
return False
|
|
684
|
+
acc = br.acc
|
|
685
|
+
nb = br.nb
|
|
686
|
+
pos = br.pos
|
|
687
|
+
else:
|
|
688
|
+
n = e & 31
|
|
689
|
+
acc >>= n
|
|
690
|
+
nb -= n
|
|
691
|
+
if nb < pad:
|
|
692
|
+
br.eop = True
|
|
693
|
+
br.pad = pad
|
|
694
|
+
return False
|
|
695
|
+
e >>= 5
|
|
696
|
+
e *= dim
|
|
697
|
+
if dim == 2:
|
|
698
|
+
v[k] += vq[e]
|
|
699
|
+
v[k + 1] += vq[e + 1]
|
|
700
|
+
k += 2
|
|
701
|
+
else:
|
|
702
|
+
for j in range(e, e + dim):
|
|
703
|
+
v[k] += vq[j]
|
|
704
|
+
k += 1
|
|
705
|
+
br.acc = acc
|
|
706
|
+
br.nb = nb
|
|
707
|
+
br.pos = pos
|
|
708
|
+
br.pad = pad
|
|
709
|
+
return True
|
|
710
|
+
|
|
711
|
+
|
|
712
|
+
class _Mapping:
|
|
713
|
+
"""Section 4.2.4: mapping type 0."""
|
|
714
|
+
|
|
715
|
+
def __init__(self, br, channels):
|
|
716
|
+
self.submaps = br.read(4) + 1 if br.read(1) else 1
|
|
717
|
+
self.coupling = []
|
|
718
|
+
if br.read(1):
|
|
719
|
+
bits = _ilog(channels - 1)
|
|
720
|
+
for _ in range(br.read(8) + 1):
|
|
721
|
+
m = br.read(bits)
|
|
722
|
+
a = br.read(bits)
|
|
723
|
+
if m == a or m >= channels or a >= channels:
|
|
724
|
+
raise OggError("bad channel coupling")
|
|
725
|
+
self.coupling.append((m, a))
|
|
726
|
+
if br.read(2):
|
|
727
|
+
raise OggError("bad mapping")
|
|
728
|
+
if self.submaps > 1:
|
|
729
|
+
self.mux = [br.read(4) for _ in range(channels)]
|
|
730
|
+
else:
|
|
731
|
+
self.mux = [0] * channels
|
|
732
|
+
self.floor = []
|
|
733
|
+
self.residue = []
|
|
734
|
+
for _ in range(self.submaps):
|
|
735
|
+
br.read(8)
|
|
736
|
+
self.floor.append(br.read(8))
|
|
737
|
+
self.residue.append(br.read(8))
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
class _Ogg:
|
|
741
|
+
"""Ogg page reader (RFC 3533) that assembles Vorbis packets in one reusable buffer.
|
|
742
|
+
|
|
743
|
+
`packet` returns the length of the next packet in ``pkt`` (followed by 8 zero bytes for
|
|
744
|
+
the bit reader), or -1 at the end of the stream. Packet data is read from the file
|
|
745
|
+
straight into ``pkt``."""
|
|
746
|
+
|
|
747
|
+
def __init__(self, f):
|
|
748
|
+
self.hdr = bytearray(27)
|
|
749
|
+
self.lacing = bytearray(255)
|
|
750
|
+
self.lacing_mv = memoryview(self.lacing)
|
|
751
|
+
self.pkt = bytearray(4096)
|
|
752
|
+
self.pkt_mv = memoryview(self.pkt)
|
|
753
|
+
self.reset(f)
|
|
754
|
+
|
|
755
|
+
def reset(self, f):
|
|
756
|
+
"""Start reading ``f`` from its beginning."""
|
|
757
|
+
f.seek(0)
|
|
758
|
+
self.f = f
|
|
759
|
+
self.serial = None
|
|
760
|
+
self.nseg = 0
|
|
761
|
+
self.li = 0
|
|
762
|
+
self.flags = 0
|
|
763
|
+
self.granule = -1
|
|
764
|
+
self.pkt_granule = -1
|
|
765
|
+
self.pkt_eos = False
|
|
766
|
+
|
|
767
|
+
def _page(self):
|
|
768
|
+
# Read the next page header of our stream; its body is left in the file.
|
|
769
|
+
f = self.f
|
|
770
|
+
hdr = self.hdr
|
|
771
|
+
lac = self.lacing
|
|
772
|
+
while True:
|
|
773
|
+
if f.readinto(hdr) != 27:
|
|
774
|
+
return False
|
|
775
|
+
if hdr[0] != 0x4F or hdr[1] != 0x67 or hdr[2] != 0x67 or hdr[3] != 0x53:
|
|
776
|
+
raise OggError("lost Ogg sync")
|
|
777
|
+
flags = hdr[5]
|
|
778
|
+
serial = struct.unpack_from("<I", hdr, 14)[0]
|
|
779
|
+
nseg = hdr[26]
|
|
780
|
+
if nseg and f.readinto(self.lacing_mv[:nseg]) != nseg:
|
|
781
|
+
return False
|
|
782
|
+
if self.serial is None and flags & 2:
|
|
783
|
+
head = f.read(7)
|
|
784
|
+
f.seek(-len(head), 1)
|
|
785
|
+
if head == b"\x01vorbis":
|
|
786
|
+
self.serial = serial
|
|
787
|
+
if serial != self.serial:
|
|
788
|
+
size = 0
|
|
789
|
+
for i in range(nseg):
|
|
790
|
+
size += lac[i]
|
|
791
|
+
f.seek(size, 1)
|
|
792
|
+
continue
|
|
793
|
+
self.nseg = nseg
|
|
794
|
+
self.li = 0
|
|
795
|
+
self.flags = flags
|
|
796
|
+
self.granule = struct.unpack_from("<q", hdr, 6)[0]
|
|
797
|
+
return True
|
|
798
|
+
|
|
799
|
+
def packet(self):
|
|
800
|
+
"""Assemble the next packet into ``pkt``; returns its length or -1."""
|
|
801
|
+
n = 0
|
|
802
|
+
started = False
|
|
803
|
+
f = self.f
|
|
804
|
+
while True:
|
|
805
|
+
if self.li >= self.nseg:
|
|
806
|
+
if self.flags & 4 or not self._page():
|
|
807
|
+
return -1
|
|
808
|
+
if not started and self.flags & 1:
|
|
809
|
+
# continuation of a packet we never saw the start of: skip it
|
|
810
|
+
lac = self.lacing
|
|
811
|
+
skip = 0
|
|
812
|
+
while self.li < self.nseg:
|
|
813
|
+
s = lac[self.li]
|
|
814
|
+
self.li += 1
|
|
815
|
+
skip += s
|
|
816
|
+
if s < 255:
|
|
817
|
+
break
|
|
818
|
+
f.seek(skip, 1)
|
|
819
|
+
continue
|
|
820
|
+
lac = self.lacing
|
|
821
|
+
li = self.li
|
|
822
|
+
nseg = self.nseg
|
|
823
|
+
size = 0
|
|
824
|
+
done = False
|
|
825
|
+
while li < nseg:
|
|
826
|
+
s = lac[li]
|
|
827
|
+
li += 1
|
|
828
|
+
size += s
|
|
829
|
+
if s < 255:
|
|
830
|
+
done = True
|
|
831
|
+
break
|
|
832
|
+
self.li = li
|
|
833
|
+
if n + size + 8 > len(self.pkt):
|
|
834
|
+
grown = bytearray(2 * (n + size + 8))
|
|
835
|
+
grown[:n] = self.pkt[:n]
|
|
836
|
+
self.pkt = grown
|
|
837
|
+
self.pkt_mv = memoryview(grown)
|
|
838
|
+
if size and f.readinto(self.pkt_mv[n : n + size]) != size:
|
|
839
|
+
return -1
|
|
840
|
+
n += size
|
|
841
|
+
started = True
|
|
842
|
+
if done:
|
|
843
|
+
last = li == nseg
|
|
844
|
+
self.pkt_granule = self.granule if last else -1
|
|
845
|
+
self.pkt_eos = bool(last and self.flags & 4)
|
|
846
|
+
pkt = self.pkt
|
|
847
|
+
for i in range(n, n + 8):
|
|
848
|
+
pkt[i] = 0
|
|
849
|
+
return n
|
|
850
|
+
|
|
851
|
+
|
|
852
|
+
def _bits(d, pos, n):
|
|
853
|
+
# n bits at bit position pos, LSB first (only used for the few packet header bits)
|
|
854
|
+
v = 0
|
|
855
|
+
for k in range(n):
|
|
856
|
+
p = pos + k
|
|
857
|
+
v |= ((d[p >> 3] >> (p & 7)) & 1) << k
|
|
858
|
+
return v
|
|
859
|
+
|
|
860
|
+
|
|
861
|
+
def _rev(v, n):
|
|
862
|
+
r = 0
|
|
863
|
+
for _ in range(n):
|
|
864
|
+
r = (r << 1) | (v & 1)
|
|
865
|
+
v >>= 1
|
|
866
|
+
return r
|
|
867
|
+
|
|
868
|
+
|
|
869
|
+
def _as_i32(u):
|
|
870
|
+
return u - (1 << 32) if u >= 1 << 31 else u
|
|
871
|
+
|
|
872
|
+
|
|
873
|
+
def _split30(x):
|
|
874
|
+
# Q30 value split into high and low 15-bit halves for the kernel's 32-bit multiplies
|
|
875
|
+
v = int(round(x * (1 << 30)))
|
|
876
|
+
return v >> 15, v & 0x7FFF
|
|
877
|
+
|
|
878
|
+
|
|
879
|
+
def _slope30(n):
|
|
880
|
+
# rising half of the Vorbis power-sine window (4.3.1), as Q30 split pairs
|
|
881
|
+
hp = _PI / 2
|
|
882
|
+
out = array("i", [0]) * (2 * n)
|
|
883
|
+
for i in range(n):
|
|
884
|
+
h, lo = _split30(math.sin(hp * math.pow(math.sin((i + 0.5) / n * hp), 2)))
|
|
885
|
+
out[2 * i] = h
|
|
886
|
+
out[2 * i + 1] = lo
|
|
887
|
+
return out
|
|
888
|
+
|
|
889
|
+
|
|
890
|
+
def _twiddles(n):
|
|
891
|
+
# tables for kernel.imdct, see its docstring
|
|
892
|
+
h = n >> 2
|
|
893
|
+
m = n >> 1
|
|
894
|
+
tw = array("i", [0]) * (11 * h)
|
|
895
|
+
for j in range(h):
|
|
896
|
+
a = -_PI * (4 * j + 1) / (4 * m)
|
|
897
|
+
tw[4 * j], tw[4 * j + 1] = _split30(math.cos(a))
|
|
898
|
+
tw[4 * j + 2], tw[4 * j + 3] = _split30(math.sin(a))
|
|
899
|
+
a = -_PI * j / m
|
|
900
|
+
t = 4 * h + 4 * j
|
|
901
|
+
tw[t], tw[t + 1] = _split30(math.cos(a))
|
|
902
|
+
tw[t + 2], tw[t + 3] = _split30(math.sin(a))
|
|
903
|
+
for k in range(h >> 1):
|
|
904
|
+
a = 2 * _PI * k / h
|
|
905
|
+
t = 8 * h + 4 * k
|
|
906
|
+
tw[t], tw[t + 1] = _split30(math.cos(a))
|
|
907
|
+
tw[t + 2], tw[t + 3] = _split30(-math.sin(a))
|
|
908
|
+
bits = _ilog(h) - 1
|
|
909
|
+
for j in range(h):
|
|
910
|
+
tw[10 * h + j] = _rev(j, bits)
|
|
911
|
+
return tw
|
|
912
|
+
|
|
913
|
+
|
|
914
|
+
# Twiddle and window tables depend only on the block size: keep them for the next file.
|
|
915
|
+
_CACHE = {}
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
def _cached(make, n):
|
|
919
|
+
key = (make, n)
|
|
920
|
+
t = _CACHE.get(key)
|
|
921
|
+
if t is None:
|
|
922
|
+
t = _CACHE[key] = make(n)
|
|
923
|
+
return t
|
|
924
|
+
|
|
925
|
+
|
|
926
|
+
def _arr(old, name, code, n):
|
|
927
|
+
# The previous file's buffer when it is big enough: it is likely in internal RAM, where
|
|
928
|
+
# a new one, allocated after the program has started, often isn't. New tables get some
|
|
929
|
+
# headroom so the next file is likely to fit.
|
|
930
|
+
a = getattr(old, name, None)
|
|
931
|
+
if a is None or len(a) < n:
|
|
932
|
+
a = array(code, [0]) * (n + (n >> 2) if name in _TABLES else n)
|
|
933
|
+
return a
|
|
934
|
+
|
|
935
|
+
|
|
936
|
+
_TABLES = ("bk", "ft", "lc", "lv", "vq")
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
class _Turbo:
|
|
940
|
+
"""Flat tables and buffers for the kernel, built from the parsed headers. ``old`` is the
|
|
941
|
+
previous file's, whose buffers are reused where they are big enough."""
|
|
942
|
+
|
|
943
|
+
def __init__(self, dec, old=None):
|
|
944
|
+
books = dec._books
|
|
945
|
+
ch = dec._channels
|
|
946
|
+
bs0, bs1 = dec._bs
|
|
947
|
+
n2max = bs1 >> 1
|
|
948
|
+
# Buffers first, while internal RAM is free: they are touched for every sample.
|
|
949
|
+
self.v = _arr(old, "v", "i", ch * n2max) # residue, then spectrum (Q24)
|
|
950
|
+
self.prev = _arr(old, "prev", "i", ch * n2max) # saved second halves (Q24)
|
|
951
|
+
self.re = _arr(old, "re", "i", bs1 >> 2)
|
|
952
|
+
self.im = _arr(old, "im", "i", bs1 >> 2)
|
|
953
|
+
self.pcm = _arr(old, "pcm", "h", ch * n2max)
|
|
954
|
+
yb = getattr(old, "yb", [])
|
|
955
|
+
self.yb = [
|
|
956
|
+
yb[c] if c < len(yb) and len(yb[c]) >= n2max else bytearray(n2max) for c in range(ch)
|
|
957
|
+
]
|
|
958
|
+
self.wk = _arr(old, "wk", "i", 768)
|
|
959
|
+
self.st = _arr(old, "st", "i", 4)
|
|
960
|
+
self.tw = (_cached(_twiddles, bs0), _cached(_twiddles, bs1))
|
|
961
|
+
self.slope = (_cached(_slope30, bs0 >> 1), _cached(_slope30, bs1 >> 1))
|
|
962
|
+
# window placement (4.3.1): (left start, length, right start, length) and slopes
|
|
963
|
+
q0 = bs0 >> 2
|
|
964
|
+
self.win_short = (array("i", (0, bs0 >> 1, bs0 >> 1, bs0 >> 1)), 0, 0)
|
|
965
|
+
self.win_long = {}
|
|
966
|
+
for prev in (0, 1):
|
|
967
|
+
for nxt in (0, 1):
|
|
968
|
+
if prev:
|
|
969
|
+
ls, ll, wl = 0, n2max, 1
|
|
970
|
+
else:
|
|
971
|
+
ls, ll, wl = (bs1 >> 2) - q0, bs0 >> 1, 0
|
|
972
|
+
if nxt:
|
|
973
|
+
rs, rl, wr = n2max, n2max, 1
|
|
974
|
+
else:
|
|
975
|
+
rs, rl, wr = (bs1 * 3 >> 2) - q0, bs0 >> 1, 0
|
|
976
|
+
self.win_long[(prev << 1) | nxt] = (array("i", (ls, ll, rs, rl)), wl, wr)
|
|
977
|
+
self._pack_books(books, old)
|
|
978
|
+
self.floors = [self._pack_floor(f) for f in dec._floors]
|
|
979
|
+
self.residues = [self._pack_residue(r, books) for r in dec._residues]
|
|
980
|
+
maxparts = 0
|
|
981
|
+
for r in dec._residues:
|
|
982
|
+
size = n2max * ch if r.type == 2 else n2max
|
|
983
|
+
end = r.end if r.end < size else size
|
|
984
|
+
begin = r.begin if r.begin < size else size
|
|
985
|
+
parts = (end - begin) // r.psize + books[r.classbook].dim
|
|
986
|
+
maxparts = max(maxparts, parts)
|
|
987
|
+
cls = getattr(old, "cls", None)
|
|
988
|
+
self.cls = (
|
|
989
|
+
cls if cls is not None and len(cls) > ch * maxparts else bytearray(ch * maxparts + 1)
|
|
990
|
+
)
|
|
991
|
+
# floor curve: value for inverse-dB index y is tm[y] / 2**ts[y] (Q24 from the
|
|
992
|
+
# residue scale 2**rq)
|
|
993
|
+
rq = self.rq
|
|
994
|
+
self.tm = array("i", [0]) * 256
|
|
995
|
+
self.ts = array("i", [0]) * 256
|
|
996
|
+
for y in range(256):
|
|
997
|
+
m = _INV_DB[y]
|
|
998
|
+
sh = 0
|
|
999
|
+
while m < 16384:
|
|
1000
|
+
m *= 2
|
|
1001
|
+
sh += 1
|
|
1002
|
+
mi = int(m + 0.5)
|
|
1003
|
+
if mi >= 32768:
|
|
1004
|
+
mi >>= 1
|
|
1005
|
+
sh -= 1
|
|
1006
|
+
self.tm[y] = mi
|
|
1007
|
+
self.ts[y] = sh - 24 + rq
|
|
1008
|
+
|
|
1009
|
+
def _pack_books(self, books, old):
|
|
1010
|
+
# all-integer VQ values (libvorbis files) stay integers, otherwise 1/16 steps
|
|
1011
|
+
rq = 0
|
|
1012
|
+
for b in books:
|
|
1013
|
+
if b.lookup and (b.minimum != int(b.minimum) or b.delta != int(b.delta)):
|
|
1014
|
+
rq = 4
|
|
1015
|
+
self.rq = rq
|
|
1016
|
+
scale = 1 << rq
|
|
1017
|
+
k = _kernel
|
|
1018
|
+
# sizes first, so the tables can be allocated in one go after the header garbage
|
|
1019
|
+
# is collected: the residue decode reads them for every codeword
|
|
1020
|
+
plans = []
|
|
1021
|
+
ft_len = long_len = vq_len = 0
|
|
1022
|
+
for b in books:
|
|
1023
|
+
fb = b.maxlen if b.maxlen < _TURBO_FAST else _TURBO_FAST
|
|
1024
|
+
if b.entries > 4095:
|
|
1025
|
+
fb = 0
|
|
1026
|
+
nlong = k.book_count(b.lengths, b.entries, fb)
|
|
1027
|
+
if b.lookup == 2 or (b.lookup == 1 and b.entries * b.dim <= 4096):
|
|
1028
|
+
lt, nv = 2, b.entries * b.dim
|
|
1029
|
+
elif b.lookup == 1:
|
|
1030
|
+
lt, nv = 1, b.nvals
|
|
1031
|
+
else:
|
|
1032
|
+
lt, nv = 0, 0
|
|
1033
|
+
plans.append((fb, nlong, lt, nv))
|
|
1034
|
+
ft_len += (1 << fb) if fb else 0
|
|
1035
|
+
long_len += nlong
|
|
1036
|
+
vq_len += nv
|
|
1037
|
+
gc.collect()
|
|
1038
|
+
self.bk = bk = _arr(old, "bk", "i", 12 * len(books))
|
|
1039
|
+
self.ft = ft = _arr(old, "ft", "H", ft_len + 1) # book_tables fills its own range
|
|
1040
|
+
self.lc = lc = _arr(old, "lc", "i", long_len + 1)
|
|
1041
|
+
self.lv = lv = _arr(old, "lv", "i", long_len + 1)
|
|
1042
|
+
self.vq = vq = _arr(old, "vq", "i", vq_len + 1)
|
|
1043
|
+
av = array("i", [0]) * 33
|
|
1044
|
+
fo = lo = vo = 0
|
|
1045
|
+
for i, b in enumerate(books):
|
|
1046
|
+
fb, nlong, lt, nv = plans[i]
|
|
1047
|
+
if k.book_tables(b.lengths, b.entries, fb, ft, fo, lc, lv, lo, av) < 0:
|
|
1048
|
+
raise OggError("overspecified codebook")
|
|
1049
|
+
if lt:
|
|
1050
|
+
iv = array("i", [int(round((m * b.delta + b.minimum) * scale)) for m in b.mults])
|
|
1051
|
+
if lt == 2:
|
|
1052
|
+
k.vq_fill(iv, b.nvals, b.entries, b.dim, b.seq, b.lookup == 1, vq, vo)
|
|
1053
|
+
else:
|
|
1054
|
+
vq[vo : vo + nv] = iv
|
|
1055
|
+
h = 12 * i
|
|
1056
|
+
bk[h] = fo
|
|
1057
|
+
bk[h + 1] = fb
|
|
1058
|
+
bk[h + 2] = lo
|
|
1059
|
+
bk[h + 3] = nlong
|
|
1060
|
+
bk[h + 4] = b.dim
|
|
1061
|
+
bk[h + 5] = b.entries
|
|
1062
|
+
bk[h + 6] = lt
|
|
1063
|
+
bk[h + 7] = vo
|
|
1064
|
+
bk[h + 8] = nv
|
|
1065
|
+
bk[h + 9] = b.seq if b.lookup else 0
|
|
1066
|
+
fo += (1 << fb) if fb else 0
|
|
1067
|
+
lo += nlong
|
|
1068
|
+
vo += nv
|
|
1069
|
+
b.lengths = None # no longer needed
|
|
1070
|
+
b.mults = None
|
|
1071
|
+
|
|
1072
|
+
@staticmethod
|
|
1073
|
+
def _pack_floor(f):
|
|
1074
|
+
nx = len(f.xs)
|
|
1075
|
+
parts = len(f.part_class)
|
|
1076
|
+
nclass = len(f.cdim)
|
|
1077
|
+
opc = 16
|
|
1078
|
+
ocl = opc + parts
|
|
1079
|
+
oxs = ocl + 12 * nclass
|
|
1080
|
+
olo = oxs + nx
|
|
1081
|
+
ohi = olo + nx
|
|
1082
|
+
oord = ohi + nx
|
|
1083
|
+
fl = array("i", [0]) * (oord + nx)
|
|
1084
|
+
fl[0] = nx
|
|
1085
|
+
fl[1] = f.mult
|
|
1086
|
+
fl[2] = f.range
|
|
1087
|
+
fl[3] = f.ybits
|
|
1088
|
+
fl[4] = parts
|
|
1089
|
+
fl[5] = nclass
|
|
1090
|
+
fl[6] = opc
|
|
1091
|
+
fl[7] = ocl
|
|
1092
|
+
fl[8] = oxs
|
|
1093
|
+
fl[9] = olo
|
|
1094
|
+
fl[10] = ohi
|
|
1095
|
+
fl[11] = oord
|
|
1096
|
+
for p in range(parts):
|
|
1097
|
+
fl[opc + p] = f.part_class[p]
|
|
1098
|
+
for c in range(nclass):
|
|
1099
|
+
b = ocl + 12 * c
|
|
1100
|
+
fl[b] = f.cdim[c]
|
|
1101
|
+
fl[b + 1] = f.csub[c]
|
|
1102
|
+
fl[b + 2] = f.cmaster[c]
|
|
1103
|
+
for j, bk in enumerate(f.cbooks[c]):
|
|
1104
|
+
fl[b + 4 + j] = bk
|
|
1105
|
+
for i in range(nx):
|
|
1106
|
+
fl[oxs + i] = f.xs[i]
|
|
1107
|
+
fl[olo + i] = f.low[i]
|
|
1108
|
+
fl[ohi + i] = f.high[i]
|
|
1109
|
+
fl[oord + i] = f.order[i]
|
|
1110
|
+
return fl
|
|
1111
|
+
|
|
1112
|
+
@staticmethod
|
|
1113
|
+
def _pack_residue(r, books):
|
|
1114
|
+
if books[r.classbook].dim < 1:
|
|
1115
|
+
raise OggError("bad residue classbook")
|
|
1116
|
+
rs = array("i", [0]) * (6 + 8 * r.nclass)
|
|
1117
|
+
rs[0] = r.type
|
|
1118
|
+
rs[1] = r.begin
|
|
1119
|
+
rs[2] = r.end
|
|
1120
|
+
rs[3] = r.psize
|
|
1121
|
+
rs[4] = r.nclass
|
|
1122
|
+
rs[5] = r.classbook
|
|
1123
|
+
for c in range(r.nclass):
|
|
1124
|
+
for p in range(8):
|
|
1125
|
+
bk = r.books[c][p]
|
|
1126
|
+
if bk >= 0 and not books[bk].lookup:
|
|
1127
|
+
raise OggError("residue book has no VQ")
|
|
1128
|
+
rs[6 + 8 * c + p] = bk
|
|
1129
|
+
return rs
|
|
1130
|
+
|
|
1131
|
+
def decode(self, dec, ogg, nbytes):
|
|
1132
|
+
"""Decode one audio packet (4.3); returns the frames it completes, or -1."""
|
|
1133
|
+
k = _kernel
|
|
1134
|
+
d = ogg.pkt
|
|
1135
|
+
nbits = nbytes * 8
|
|
1136
|
+
if d[0] & 1:
|
|
1137
|
+
return -1
|
|
1138
|
+
mb = dec._mode_bits
|
|
1139
|
+
mi = _bits(d, 1, mb)
|
|
1140
|
+
pos = 1 + mb
|
|
1141
|
+
modes = dec._modes
|
|
1142
|
+
if mi >= len(modes) or pos > nbits:
|
|
1143
|
+
return -1
|
|
1144
|
+
blockflag, mapnum = modes[mi]
|
|
1145
|
+
n = dec._bs[blockflag]
|
|
1146
|
+
n2 = n >> 1
|
|
1147
|
+
if blockflag:
|
|
1148
|
+
win, wl, wr = self.win_long[(_bits(d, pos, 1) << 1) | _bits(d, pos + 1, 1)]
|
|
1149
|
+
pos += 2
|
|
1150
|
+
else:
|
|
1151
|
+
win, wl, wr = self.win_short
|
|
1152
|
+
mp = dec._mappings[mapnum]
|
|
1153
|
+
if mp.submaps != 1:
|
|
1154
|
+
raise OggError("multiple submaps are not supported by the turbo kernel")
|
|
1155
|
+
ch = dec._channels
|
|
1156
|
+
bk = self.bk
|
|
1157
|
+
ft = self.ft
|
|
1158
|
+
lc = self.lc
|
|
1159
|
+
lv = self.lv
|
|
1160
|
+
st = self.st
|
|
1161
|
+
wk = self.wk
|
|
1162
|
+
yb = self.yb
|
|
1163
|
+
floors = self.floors
|
|
1164
|
+
mux = mp.mux
|
|
1165
|
+
used = 0
|
|
1166
|
+
for c in range(ch):
|
|
1167
|
+
pos = k.floor1(
|
|
1168
|
+
d, pos, nbits, floors[mp.floor[mux[c]]], bk, ft, lc, lv, wk, yb[c], n2, st
|
|
1169
|
+
)
|
|
1170
|
+
if st[1]:
|
|
1171
|
+
used |= 1 << c
|
|
1172
|
+
# nonzero vector propagation (4.3.3)
|
|
1173
|
+
decode = used
|
|
1174
|
+
for m, a in mp.coupling:
|
|
1175
|
+
if decode & ((1 << m) | (1 << a)):
|
|
1176
|
+
decode |= (1 << m) | (1 << a)
|
|
1177
|
+
v = self.v
|
|
1178
|
+
pos = k.residue(
|
|
1179
|
+
d,
|
|
1180
|
+
pos,
|
|
1181
|
+
nbits,
|
|
1182
|
+
self.residues[mp.residue[0]],
|
|
1183
|
+
bk,
|
|
1184
|
+
ft,
|
|
1185
|
+
lc,
|
|
1186
|
+
lv,
|
|
1187
|
+
self.vq,
|
|
1188
|
+
v,
|
|
1189
|
+
ch,
|
|
1190
|
+
((1 << ch) - 1) & ~decode,
|
|
1191
|
+
n2,
|
|
1192
|
+
self.cls,
|
|
1193
|
+
st,
|
|
1194
|
+
)
|
|
1195
|
+
for m, a in reversed(mp.coupling):
|
|
1196
|
+
k.couple(v, m * n2, a * n2, n2)
|
|
1197
|
+
pn = dec._prev_n
|
|
1198
|
+
if pn:
|
|
1199
|
+
off = (pn >> 2) - (n >> 2)
|
|
1200
|
+
length = (pn >> 2) + (n >> 2)
|
|
1201
|
+
else:
|
|
1202
|
+
off = 0
|
|
1203
|
+
length = 0
|
|
1204
|
+
dec._prev_n = n
|
|
1205
|
+
tw = self.tw[blockflag]
|
|
1206
|
+
slope = self.slope
|
|
1207
|
+
n2max = dec._bs[1] >> 1
|
|
1208
|
+
pcm = self.pcm
|
|
1209
|
+
prev = self.prev
|
|
1210
|
+
pn2 = pn >> 1
|
|
1211
|
+
for c in range(ch):
|
|
1212
|
+
hasy = (used >> c) & 1
|
|
1213
|
+
if hasy:
|
|
1214
|
+
k.floor_apply(v, c * n2, yb[c], 0, self.tm, self.ts, n2)
|
|
1215
|
+
k.imdct(v, c * n2, tw, n, self.re, self.im)
|
|
1216
|
+
k.overlap(
|
|
1217
|
+
prev,
|
|
1218
|
+
c * n2max,
|
|
1219
|
+
v,
|
|
1220
|
+
c * n2,
|
|
1221
|
+
hasy,
|
|
1222
|
+
pcm,
|
|
1223
|
+
ch,
|
|
1224
|
+
c,
|
|
1225
|
+
length,
|
|
1226
|
+
off,
|
|
1227
|
+
n,
|
|
1228
|
+
pn2,
|
|
1229
|
+
win,
|
|
1230
|
+
slope[wl],
|
|
1231
|
+
slope[wr],
|
|
1232
|
+
)
|
|
1233
|
+
return length
|
|
1234
|
+
|
|
1235
|
+
|
|
1236
|
+
class OggDecoder:
|
|
1237
|
+
"""Decode an Ogg Vorbis file to 16-bit signed PCM.
|
|
1238
|
+
|
|
1239
|
+
:param file: path string or a file opened in ``"rb"`` mode
|
|
1240
|
+
:param bool turbo: use the prebuilt kernel when one loads (default)
|
|
1241
|
+
|
|
1242
|
+
Playing an Ogg file::
|
|
1243
|
+
|
|
1244
|
+
import audiobusio
|
|
1245
|
+
import board
|
|
1246
|
+
import adafruit_ogg
|
|
1247
|
+
|
|
1248
|
+
audio = audiobusio.I2SOut(board.I2S_BCLK, board.I2S_WS, board.I2S_DIN)
|
|
1249
|
+
decoder = adafruit_ogg.OggDecoder("song.ogg")
|
|
1250
|
+
player = adafruit_ogg.OggPlayer(audio)
|
|
1251
|
+
player.play(decoder)
|
|
1252
|
+
while player.playing:
|
|
1253
|
+
player.update()
|
|
1254
|
+
"""
|
|
1255
|
+
|
|
1256
|
+
def __init__(self, file, *, turbo=True):
|
|
1257
|
+
self._owns = False
|
|
1258
|
+
self._file = None
|
|
1259
|
+
self._ogg = None
|
|
1260
|
+
self._turbo = None
|
|
1261
|
+
self._kernel = _kernel if turbo else None
|
|
1262
|
+
self._open(file)
|
|
1263
|
+
|
|
1264
|
+
# -- public API --
|
|
1265
|
+
|
|
1266
|
+
@property
|
|
1267
|
+
def file(self):
|
|
1268
|
+
"""The open file object. Assign a new file or path to switch songs."""
|
|
1269
|
+
return self._file
|
|
1270
|
+
|
|
1271
|
+
@file.setter
|
|
1272
|
+
def file(self, file):
|
|
1273
|
+
self._open(file)
|
|
1274
|
+
|
|
1275
|
+
def open(self, file):
|
|
1276
|
+
"""Switch to a different file (same as assigning `file`)."""
|
|
1277
|
+
self._open(file)
|
|
1278
|
+
|
|
1279
|
+
@property
|
|
1280
|
+
def sample_rate(self):
|
|
1281
|
+
"""Sample rate in Hz."""
|
|
1282
|
+
return self._rate
|
|
1283
|
+
|
|
1284
|
+
@property
|
|
1285
|
+
def channel_count(self):
|
|
1286
|
+
"""1 for mono, 2 for stereo."""
|
|
1287
|
+
return self._channels
|
|
1288
|
+
|
|
1289
|
+
@property
|
|
1290
|
+
def bits_per_sample(self):
|
|
1291
|
+
"""Always 16."""
|
|
1292
|
+
return 16
|
|
1293
|
+
|
|
1294
|
+
@property
|
|
1295
|
+
def samples_decoded(self):
|
|
1296
|
+
"""Frames decoded since the file was opened or rewound."""
|
|
1297
|
+
return self._decoded
|
|
1298
|
+
|
|
1299
|
+
@property
|
|
1300
|
+
def comments(self):
|
|
1301
|
+
"""Vorbis comment tags as a dict of upper-case name to value."""
|
|
1302
|
+
return self._comments
|
|
1303
|
+
|
|
1304
|
+
@property
|
|
1305
|
+
def vendor(self):
|
|
1306
|
+
"""Encoder vendor string."""
|
|
1307
|
+
return self._vendor
|
|
1308
|
+
|
|
1309
|
+
@property
|
|
1310
|
+
def nominal_bitrate(self):
|
|
1311
|
+
"""Encoder's nominal bitrate in bits per second (0 if unknown)."""
|
|
1312
|
+
return self._bitrate
|
|
1313
|
+
|
|
1314
|
+
@property
|
|
1315
|
+
def duration(self):
|
|
1316
|
+
"""Length in seconds, read from the last Ogg page (None if unknown)."""
|
|
1317
|
+
if self._duration is None:
|
|
1318
|
+
self._duration = self._scan_duration()
|
|
1319
|
+
return self._duration
|
|
1320
|
+
|
|
1321
|
+
@property
|
|
1322
|
+
def turbo(self):
|
|
1323
|
+
"""True when a prebuilt kernel (C or turbo) decodes this file."""
|
|
1324
|
+
return self._turbo is not None
|
|
1325
|
+
|
|
1326
|
+
@property
|
|
1327
|
+
def rms_level(self):
|
|
1328
|
+
"""RMS level of the most recently decoded packet, 0.0 to 1.0."""
|
|
1329
|
+
pcm = self._pcm
|
|
1330
|
+
if pcm is None or self._pcm_len == 0:
|
|
1331
|
+
return 0.0
|
|
1332
|
+
total = 0
|
|
1333
|
+
count = self._pcm_len * self._channels
|
|
1334
|
+
# every 8th value is plenty for a level meter and keeps this cheap
|
|
1335
|
+
for i in range(0, count, 8):
|
|
1336
|
+
s = int(pcm[i])
|
|
1337
|
+
total += s * s
|
|
1338
|
+
return math.sqrt(total / ((count + 7) // 8)) / 32768
|
|
1339
|
+
|
|
1340
|
+
def rewind(self):
|
|
1341
|
+
"""Go back to the start of the audio."""
|
|
1342
|
+
self._start()
|
|
1343
|
+
|
|
1344
|
+
def readinto(self, buf):
|
|
1345
|
+
"""Fill ``buf`` (an ``array('h')`` or memoryview of one) with interleaved
|
|
1346
|
+
samples. Returns the number of values written; less than ``len(buf)``
|
|
1347
|
+
only at the end of the stream."""
|
|
1348
|
+
mv = memoryview(buf)
|
|
1349
|
+
total = len(mv)
|
|
1350
|
+
got = 0
|
|
1351
|
+
while got < total:
|
|
1352
|
+
n = self.fill(mv[got:])
|
|
1353
|
+
if n == 0:
|
|
1354
|
+
break
|
|
1355
|
+
got += n
|
|
1356
|
+
return got
|
|
1357
|
+
|
|
1358
|
+
def fill(self, mv):
|
|
1359
|
+
"""Copy decoded samples into memoryview ``mv``, decoding at most one
|
|
1360
|
+
packet. Returns the number of values written, 0 at end of stream."""
|
|
1361
|
+
while self._pcm_pos >= self._pcm_len * self._channels:
|
|
1362
|
+
if not self._decode_next():
|
|
1363
|
+
return 0
|
|
1364
|
+
avail = self._pcm_len * self._channels - self._pcm_pos
|
|
1365
|
+
n = len(mv)
|
|
1366
|
+
n = min(n, avail)
|
|
1367
|
+
p = self._pcm_pos
|
|
1368
|
+
mv[:n] = self._pcm_mv[p : p + n]
|
|
1369
|
+
self._pcm_pos = p + n
|
|
1370
|
+
return n
|
|
1371
|
+
|
|
1372
|
+
def deinit(self):
|
|
1373
|
+
"""Release the file (if this decoder opened it) and buffers."""
|
|
1374
|
+
if self._owns and self._file is not None:
|
|
1375
|
+
self._file.close()
|
|
1376
|
+
self._file = None
|
|
1377
|
+
self._books = None
|
|
1378
|
+
self._turbo = None
|
|
1379
|
+
self._ogg = None
|
|
1380
|
+
self._prev = None
|
|
1381
|
+
self._pcm = None
|
|
1382
|
+
self._pcm_mv = None
|
|
1383
|
+
|
|
1384
|
+
def __enter__(self):
|
|
1385
|
+
return self
|
|
1386
|
+
|
|
1387
|
+
def __exit__(self, *exc):
|
|
1388
|
+
self.deinit()
|
|
1389
|
+
|
|
1390
|
+
# -- internals --
|
|
1391
|
+
|
|
1392
|
+
def _open(self, file):
|
|
1393
|
+
if self._owns and self._file is not None:
|
|
1394
|
+
self._file.close()
|
|
1395
|
+
if isinstance(file, str):
|
|
1396
|
+
file = open(file, "rb")
|
|
1397
|
+
self._owns = True
|
|
1398
|
+
else:
|
|
1399
|
+
self._owns = False
|
|
1400
|
+
self._file = file
|
|
1401
|
+
self._duration = None
|
|
1402
|
+
self._old = self._turbo # its buffers are reused for this file
|
|
1403
|
+
self._turbo = None
|
|
1404
|
+
self._books = None
|
|
1405
|
+
self._pcm = None
|
|
1406
|
+
self._pcm_mv = None
|
|
1407
|
+
gc.collect()
|
|
1408
|
+
# the packet buffer first, while internal RAM is free: the decode reads it bit by bit
|
|
1409
|
+
if self._ogg is None:
|
|
1410
|
+
self._ogg = _Ogg(file)
|
|
1411
|
+
else:
|
|
1412
|
+
self._ogg.reset(file)
|
|
1413
|
+
self._read_headers(self._ogg)
|
|
1414
|
+
self._old = None
|
|
1415
|
+
self._start()
|
|
1416
|
+
|
|
1417
|
+
def _start(self):
|
|
1418
|
+
ogg = self._ogg
|
|
1419
|
+
ogg.reset(self._file)
|
|
1420
|
+
for _ in range(3):
|
|
1421
|
+
if ogg.packet() < 0:
|
|
1422
|
+
raise OggError("missing Vorbis headers")
|
|
1423
|
+
self._prev = None
|
|
1424
|
+
self._prev_n = 0
|
|
1425
|
+
self._decoded = 0
|
|
1426
|
+
self._pcm = None
|
|
1427
|
+
self._pcm_mv = None
|
|
1428
|
+
self._pcm_len = 0
|
|
1429
|
+
self._pcm_pos = 0
|
|
1430
|
+
self._done = False
|
|
1431
|
+
if self._turbo is not None:
|
|
1432
|
+
self._pcm = self._turbo.pcm
|
|
1433
|
+
self._pcm_mv = memoryview(self._pcm)
|
|
1434
|
+
t = self._turbo.prev
|
|
1435
|
+
for i in range(len(t)):
|
|
1436
|
+
t[i] = 0
|
|
1437
|
+
|
|
1438
|
+
def _read_headers(self, ogg):
|
|
1439
|
+
n = ogg.packet()
|
|
1440
|
+
p = bytes(ogg.pkt[: max(n, 0)])
|
|
1441
|
+
if n < 0 or p[:7] != b"\x01vorbis":
|
|
1442
|
+
raise OggError("not an Ogg Vorbis file")
|
|
1443
|
+
version, channels, rate, bmax, bnom, bmin, bs = struct.unpack("<IBIiiiB", p[7:29])
|
|
1444
|
+
if version != 0 or channels == 0 or rate == 0:
|
|
1445
|
+
raise OggError("bad Vorbis identification header")
|
|
1446
|
+
bs0 = 1 << (bs & 15)
|
|
1447
|
+
bs1 = 1 << (bs >> 4)
|
|
1448
|
+
if bs0 < 64 or bs1 > 8192 or bs0 > bs1:
|
|
1449
|
+
raise OggError("bad Vorbis block sizes")
|
|
1450
|
+
self._channels = channels
|
|
1451
|
+
self._rate = rate
|
|
1452
|
+
self._bitrate = bnom if bnom > 0 else 0
|
|
1453
|
+
self._bs = (bs0, bs1)
|
|
1454
|
+
|
|
1455
|
+
n = ogg.packet()
|
|
1456
|
+
p = bytes(ogg.pkt[: max(n, 0)])
|
|
1457
|
+
if n < 0 or p[:7] != b"\x03vorbis":
|
|
1458
|
+
raise OggError("missing Vorbis comment header")
|
|
1459
|
+
self._comments = {}
|
|
1460
|
+
self._vendor = ""
|
|
1461
|
+
try:
|
|
1462
|
+
pos = 7
|
|
1463
|
+
n = struct.unpack("<I", p[pos : pos + 4])[0]
|
|
1464
|
+
self._vendor = str(p[pos + 4 : pos + 4 + n], "utf-8")
|
|
1465
|
+
pos += 4 + n
|
|
1466
|
+
count = struct.unpack("<I", p[pos : pos + 4])[0]
|
|
1467
|
+
pos += 4
|
|
1468
|
+
for _ in range(count):
|
|
1469
|
+
n = struct.unpack("<I", p[pos : pos + 4])[0]
|
|
1470
|
+
tag = str(p[pos + 4 : pos + 4 + n], "utf-8")
|
|
1471
|
+
pos += 4 + n
|
|
1472
|
+
k = tag.find("=")
|
|
1473
|
+
if k > 0:
|
|
1474
|
+
self._comments[tag[:k].upper()] = tag[k + 1 :]
|
|
1475
|
+
except Exception: # noqa: BLE001, tags are informational; a bad block is not fatal
|
|
1476
|
+
pass
|
|
1477
|
+
|
|
1478
|
+
n = ogg.packet()
|
|
1479
|
+
p = bytes(ogg.pkt[: max(n, 0)])
|
|
1480
|
+
if n < 0 or p[:7] != b"\x05vorbis":
|
|
1481
|
+
raise OggError("missing Vorbis setup header")
|
|
1482
|
+
float_vq = self._kernel is None
|
|
1483
|
+
br = _Bits(p)
|
|
1484
|
+
for _ in range(7): # skip the packet type and "vorbis"
|
|
1485
|
+
br.read(8)
|
|
1486
|
+
self._books = [_Codebook(br, float_vq) for _ in range(br.read(8) + 1)]
|
|
1487
|
+
for _ in range(br.read(6) + 1):
|
|
1488
|
+
if br.read(16):
|
|
1489
|
+
raise OggError("bad time domain")
|
|
1490
|
+
self._floors = []
|
|
1491
|
+
for _ in range(br.read(6) + 1):
|
|
1492
|
+
ftype = br.read(16)
|
|
1493
|
+
if ftype != 1:
|
|
1494
|
+
raise OggError("floor type 0 is not supported")
|
|
1495
|
+
self._floors.append(_Floor1(br))
|
|
1496
|
+
self._residues = []
|
|
1497
|
+
for _ in range(br.read(6) + 1):
|
|
1498
|
+
rtype = br.read(16)
|
|
1499
|
+
if rtype > 2:
|
|
1500
|
+
raise OggError("bad residue type")
|
|
1501
|
+
self._residues.append(_Residue(br, rtype))
|
|
1502
|
+
self._mappings = []
|
|
1503
|
+
for _ in range(br.read(6) + 1):
|
|
1504
|
+
if br.read(16):
|
|
1505
|
+
raise OggError("bad mapping type")
|
|
1506
|
+
self._mappings.append(_Mapping(br, channels))
|
|
1507
|
+
self._modes = []
|
|
1508
|
+
for _ in range(br.read(6) + 1):
|
|
1509
|
+
blockflag = br.read(1)
|
|
1510
|
+
if br.read(16) or br.read(16):
|
|
1511
|
+
raise OggError("bad mode")
|
|
1512
|
+
self._modes.append((blockflag, br.read(8)))
|
|
1513
|
+
if not br.read(1) or br.eop:
|
|
1514
|
+
raise OggError("bad setup header framing")
|
|
1515
|
+
self._mode_bits = _ilog(len(self._modes) - 1)
|
|
1516
|
+
if self._kernel is not None and all(m.submaps == 1 for m in self._mappings):
|
|
1517
|
+
self._turbo = _Turbo(self, self._old)
|
|
1518
|
+
self._books = None
|
|
1519
|
+
else:
|
|
1520
|
+
if self._kernel is not None:
|
|
1521
|
+
# multiple submaps: rebuild the codebooks for the Python decoder
|
|
1522
|
+
self._kernel = None
|
|
1523
|
+
return self._read_headers(_reopen(ogg))
|
|
1524
|
+
from adafruit_ogg import _float # noqa: PLC0415, only without the kernel
|
|
1525
|
+
|
|
1526
|
+
self._float = _float
|
|
1527
|
+
self._accel = _float._np is not None
|
|
1528
|
+
self._xf = (
|
|
1529
|
+
_float._Transform(bs0, bs0, self._accel),
|
|
1530
|
+
_float._Transform(bs1, bs0, self._accel),
|
|
1531
|
+
)
|
|
1532
|
+
return None
|
|
1533
|
+
|
|
1534
|
+
def _scan_duration(self):
|
|
1535
|
+
f = self._file
|
|
1536
|
+
here = f.tell()
|
|
1537
|
+
try:
|
|
1538
|
+
size = f.seek(0, 2)
|
|
1539
|
+
if size is None:
|
|
1540
|
+
size = f.tell()
|
|
1541
|
+
start = size - 65536 if size > 65536 else 0
|
|
1542
|
+
f.seek(start)
|
|
1543
|
+
tail = f.read(size - start)
|
|
1544
|
+
k = tail.rfind(b"OggS") if hasattr(tail, "rfind") else -1
|
|
1545
|
+
while k >= 0:
|
|
1546
|
+
if k + 14 <= len(tail):
|
|
1547
|
+
g = struct.unpack("<q", tail[k + 6 : k + 14])[0]
|
|
1548
|
+
if g >= 0:
|
|
1549
|
+
return g / self._rate
|
|
1550
|
+
k = tail.rfind(b"OggS", 0, k)
|
|
1551
|
+
return None
|
|
1552
|
+
finally:
|
|
1553
|
+
f.seek(here)
|
|
1554
|
+
|
|
1555
|
+
def _decode_next(self):
|
|
1556
|
+
# Decode packets until one yields audio. False at end of stream.
|
|
1557
|
+
if self._done:
|
|
1558
|
+
return False
|
|
1559
|
+
ogg = self._ogg
|
|
1560
|
+
turbo = self._turbo
|
|
1561
|
+
while True:
|
|
1562
|
+
nbytes = ogg.packet()
|
|
1563
|
+
if nbytes < 0:
|
|
1564
|
+
self._done = True
|
|
1565
|
+
return False
|
|
1566
|
+
if turbo is not None:
|
|
1567
|
+
n = turbo.decode(self, ogg, nbytes)
|
|
1568
|
+
pcm = turbo.pcm
|
|
1569
|
+
else:
|
|
1570
|
+
frames = self._decode_packet(bytes(ogg.pkt[:nbytes]))
|
|
1571
|
+
if frames is None:
|
|
1572
|
+
n = -1
|
|
1573
|
+
else:
|
|
1574
|
+
pcm, n = frames
|
|
1575
|
+
if n < 0:
|
|
1576
|
+
continue
|
|
1577
|
+
if ogg.pkt_eos and ogg.pkt_granule >= 0:
|
|
1578
|
+
# Ogg end trimming: the last granule says how many frames exist
|
|
1579
|
+
keep = ogg.pkt_granule - self._decoded
|
|
1580
|
+
if 0 <= keep < n:
|
|
1581
|
+
n = keep
|
|
1582
|
+
if n <= 0:
|
|
1583
|
+
if ogg.pkt_eos:
|
|
1584
|
+
self._done = True
|
|
1585
|
+
return False
|
|
1586
|
+
continue
|
|
1587
|
+
if pcm is not self._pcm:
|
|
1588
|
+
self._pcm = pcm
|
|
1589
|
+
self._pcm_mv = memoryview(pcm)
|
|
1590
|
+
self._pcm_len = n
|
|
1591
|
+
self._pcm_pos = 0
|
|
1592
|
+
self._decoded += n
|
|
1593
|
+
return True
|
|
1594
|
+
|
|
1595
|
+
def _decode_packet(self, pkt):
|
|
1596
|
+
"""Plain Python decode (4.3). Returns (interleaved int16 pcm, frames) or None."""
|
|
1597
|
+
br = _Bits(pkt)
|
|
1598
|
+
if br.read(1):
|
|
1599
|
+
return None
|
|
1600
|
+
mi = br.read(self._mode_bits)
|
|
1601
|
+
if mi >= len(self._modes) or br.eop:
|
|
1602
|
+
return None
|
|
1603
|
+
mode = self._modes[mi]
|
|
1604
|
+
blockflag, mapnum = mode
|
|
1605
|
+
n = self._bs[blockflag]
|
|
1606
|
+
n2 = n >> 1
|
|
1607
|
+
if blockflag:
|
|
1608
|
+
prev = br.read(1)
|
|
1609
|
+
nxt = br.read(1)
|
|
1610
|
+
else:
|
|
1611
|
+
prev = nxt = 0
|
|
1612
|
+
mp = self._mappings[mapnum]
|
|
1613
|
+
books = self._books
|
|
1614
|
+
ch = self._channels
|
|
1615
|
+
|
|
1616
|
+
# floors (4.3.2)
|
|
1617
|
+
floors = [None] * ch
|
|
1618
|
+
for c in range(ch):
|
|
1619
|
+
fl = self._floors[mp.floor[mp.mux[c]]]
|
|
1620
|
+
ys = fl.decode(br, books)
|
|
1621
|
+
if ys is not None:
|
|
1622
|
+
floors[c] = fl.render(ys, n2)
|
|
1623
|
+
# nonzero vector propagation (4.3.3)
|
|
1624
|
+
nores = [f is None for f in floors]
|
|
1625
|
+
for m, a in mp.coupling:
|
|
1626
|
+
if not nores[m] or not nores[a]:
|
|
1627
|
+
nores[m] = nores[a] = False
|
|
1628
|
+
# residues (4.3.4)
|
|
1629
|
+
spec = [None] * ch
|
|
1630
|
+
for s in range(mp.submaps):
|
|
1631
|
+
chans = [c for c in range(ch) if mp.mux[c] == s]
|
|
1632
|
+
vecs = [[0.0] * n2 for _ in chans]
|
|
1633
|
+
self._residues[mp.residue[s]].decode(br, books, vecs, [nores[c] for c in chans], n2)
|
|
1634
|
+
for i, c in enumerate(chans):
|
|
1635
|
+
spec[c] = vecs[i]
|
|
1636
|
+
|
|
1637
|
+
xf = self._xf[blockflag]
|
|
1638
|
+
win = xf.window(blockflag, prev, nxt)
|
|
1639
|
+
if self._accel:
|
|
1640
|
+
return self._float.synth_np(self, spec, floors, mp, xf, win, n)
|
|
1641
|
+
return self._float.synth_py(self, spec, floors, mp, xf, win, n)
|
|
1642
|
+
|
|
1643
|
+
|
|
1644
|
+
def _reopen(ogg):
|
|
1645
|
+
ogg.reset(ogg.f)
|
|
1646
|
+
return ogg
|
|
1647
|
+
|
|
1648
|
+
|
|
1649
|
+
def _gain(pcm, n, q):
|
|
1650
|
+
# Scale n samples of pcm in place by q / 32768.
|
|
1651
|
+
if _kernel is not None:
|
|
1652
|
+
_kernel.gain(pcm, n, q)
|
|
1653
|
+
return
|
|
1654
|
+
for i in range(n):
|
|
1655
|
+
pcm[i] = (pcm[i] * q) >> 15
|
|
1656
|
+
|
|
1657
|
+
|
|
1658
|
+
class OggPlayer:
|
|
1659
|
+
"""Stream an `OggDecoder` to an audio output from Python.
|
|
1660
|
+
|
|
1661
|
+
Decoded PCM goes straight into a double-buffered `audiocore.RawSample` that loops on the
|
|
1662
|
+
output, so playback keeps going while your code runs. Call `update` often (several times
|
|
1663
|
+
per ``buffer_ms``); each call decodes at most one Vorbis packet.
|
|
1664
|
+
|
|
1665
|
+
The output must copy each half when it starts playing it, as ``I2SOut``,
|
|
1666
|
+
``PWMAudioOut`` and ``AudioOut`` do (into their DMA buffers): a half is rewritten as soon
|
|
1667
|
+
as the output has taken it. Not for ``audiomixer``, which reads the sample in place.
|
|
1668
|
+
|
|
1669
|
+
Keep the buffer in internal RAM where there is a choice; pass ``buffer`` to allocate it
|
|
1670
|
+
early, before other code takes the internal RAM.
|
|
1671
|
+
|
|
1672
|
+
:param audio: an ``audiobusio.I2SOut``, ``audiopwmio.PWMAudioOut`` or
|
|
1673
|
+
``audioio.AudioOut``-like object with ``play(sample, loop=True)``, ``stop()`` and
|
|
1674
|
+
optionally ``pause()``
|
|
1675
|
+
:param int buffer_ms: length of each half of the output buffer. The output's DMA needs
|
|
1676
|
+
internal RAM for twice the whole buffer.
|
|
1677
|
+
:param buffer: optional ``array('h')`` to use for both halves (its length sets the half
|
|
1678
|
+
size; ``buffer_ms`` is then ignored)
|
|
1679
|
+
:param float level: output level from 0.0 (silent) to 1.0 (unchanged), see `level`
|
|
1680
|
+
"""
|
|
1681
|
+
|
|
1682
|
+
def __init__(self, audio, *, buffer_ms=64, buffer=None, level=1.0):
|
|
1683
|
+
self._audio = audio
|
|
1684
|
+
self.level = level
|
|
1685
|
+
self._buffer_ms = buffer_ms
|
|
1686
|
+
self._fixed = buffer is not None
|
|
1687
|
+
self._buf = buffer
|
|
1688
|
+
self._mv = memoryview(buffer) if buffer is not None else None
|
|
1689
|
+
self._zero = array("h", [0]) * 256
|
|
1690
|
+
self._decoder = None
|
|
1691
|
+
self._playing = False
|
|
1692
|
+
self._paused = False
|
|
1693
|
+
self.underruns = 0
|
|
1694
|
+
"""Count of buffer halves that were not ready in time."""
|
|
1695
|
+
|
|
1696
|
+
@property
|
|
1697
|
+
def playing(self):
|
|
1698
|
+
"""True while a file is playing (including while paused)."""
|
|
1699
|
+
return self._playing
|
|
1700
|
+
|
|
1701
|
+
@property
|
|
1702
|
+
def paused(self):
|
|
1703
|
+
"""True while paused."""
|
|
1704
|
+
return self._paused
|
|
1705
|
+
|
|
1706
|
+
@property
|
|
1707
|
+
def decoder(self):
|
|
1708
|
+
"""The `OggDecoder` being played."""
|
|
1709
|
+
return self._decoder
|
|
1710
|
+
|
|
1711
|
+
@property
|
|
1712
|
+
def level(self):
|
|
1713
|
+
"""Output level from 0.0 (silent) to 1.0 (unchanged, the default), like
|
|
1714
|
+
``audiomixer.MixerVoice.level``. Samples are scaled as they are decoded, so a
|
|
1715
|
+
change is heard after the buffered audio (up to two ``buffer_ms`` halves)."""
|
|
1716
|
+
return self._q / 32768
|
|
1717
|
+
|
|
1718
|
+
@level.setter
|
|
1719
|
+
def level(self, value):
|
|
1720
|
+
self._q = min(32768, max(0, int(value * 32768 + 0.5)))
|
|
1721
|
+
|
|
1722
|
+
def play(self, decoder, *, loop=False):
|
|
1723
|
+
"""Start playing ``decoder`` from its current position."""
|
|
1724
|
+
import audiocore # noqa: PLC0415, so the decoder also imports on desktop Python
|
|
1725
|
+
|
|
1726
|
+
self.stop()
|
|
1727
|
+
self._decoder = decoder
|
|
1728
|
+
self._loop = loop
|
|
1729
|
+
ch = decoder.channel_count
|
|
1730
|
+
if self._fixed:
|
|
1731
|
+
half = len(self._buf) // 2 // ch * ch
|
|
1732
|
+
else:
|
|
1733
|
+
half = max(256, decoder.sample_rate * self._buffer_ms // 1000) * ch
|
|
1734
|
+
if self._buf is None or len(self._buf) != 2 * half:
|
|
1735
|
+
self._buf = array("h", [0]) * (2 * half)
|
|
1736
|
+
self._mv = memoryview(self._buf)
|
|
1737
|
+
self._half = half
|
|
1738
|
+
self._eof = False
|
|
1739
|
+
self._last_real = -1
|
|
1740
|
+
# prime both halves before starting the output
|
|
1741
|
+
for w in range(2):
|
|
1742
|
+
self._w = w
|
|
1743
|
+
self._filled = 0
|
|
1744
|
+
while self._filled < half:
|
|
1745
|
+
self._fill_once()
|
|
1746
|
+
if self._eof and self._last_real < 0:
|
|
1747
|
+
self._last_real = w
|
|
1748
|
+
self._w = 2
|
|
1749
|
+
self._filled = 0
|
|
1750
|
+
self._half_ns = half // ch * 1_000_000_000 // decoder.sample_rate
|
|
1751
|
+
self._sample = audiocore.RawSample(
|
|
1752
|
+
self._mv[: 2 * half],
|
|
1753
|
+
channel_count=ch,
|
|
1754
|
+
sample_rate=decoder.sample_rate,
|
|
1755
|
+
single_buffer=False,
|
|
1756
|
+
)
|
|
1757
|
+
self._audio.play(self._sample, loop=True)
|
|
1758
|
+
self._t0 = _now()
|
|
1759
|
+
self._playing = True
|
|
1760
|
+
self._paused = False
|
|
1761
|
+
|
|
1762
|
+
def _fill_once(self):
|
|
1763
|
+
# Decode up to one packet into the half being written; pad with silence at the end.
|
|
1764
|
+
half = self._half
|
|
1765
|
+
a = (self._w & 1) * half + self._filled
|
|
1766
|
+
room = half - self._filled
|
|
1767
|
+
if not self._eof:
|
|
1768
|
+
n = self._decoder.fill(self._mv[a : a + room])
|
|
1769
|
+
if not n and self._loop:
|
|
1770
|
+
self._decoder.rewind()
|
|
1771
|
+
n = self._decoder.fill(self._mv[a : a + room])
|
|
1772
|
+
if n:
|
|
1773
|
+
if self._q < 32768:
|
|
1774
|
+
_gain(self._mv[a : a + n], n, self._q)
|
|
1775
|
+
self._filled += n
|
|
1776
|
+
return
|
|
1777
|
+
self._eof = True
|
|
1778
|
+
z = self._zero
|
|
1779
|
+
while room > 0:
|
|
1780
|
+
k = room if room < len(z) else len(z)
|
|
1781
|
+
self._mv[a : a + k] = z[:k]
|
|
1782
|
+
a += k
|
|
1783
|
+
room -= k
|
|
1784
|
+
self._filled = half
|
|
1785
|
+
|
|
1786
|
+
def update(self):
|
|
1787
|
+
"""Do a slice of decoding work. Returns True while still playing."""
|
|
1788
|
+
if not self._playing:
|
|
1789
|
+
return False
|
|
1790
|
+
if self._paused:
|
|
1791
|
+
return True
|
|
1792
|
+
elapsed = _now() - self._t0
|
|
1793
|
+
hd = self._half_ns
|
|
1794
|
+
# halves the output has taken so far: two at start, then one per half
|
|
1795
|
+
fetched = 2 + elapsed // hd
|
|
1796
|
+
if self._last_real >= 0 and fetched >= self._last_real + 3:
|
|
1797
|
+
# the half after the last real one (silence) is now playing
|
|
1798
|
+
self.stop()
|
|
1799
|
+
return False
|
|
1800
|
+
w = self._w
|
|
1801
|
+
if w < fetched:
|
|
1802
|
+
# fell behind: half w was taken before it was ready; skip ahead
|
|
1803
|
+
self.underruns += 1
|
|
1804
|
+
w = self._w = fetched + 1
|
|
1805
|
+
self._filled = 0
|
|
1806
|
+
# Half w reuses the slot of half w - 2, taken at (w - 3) * hd. Leave a quarter
|
|
1807
|
+
# half of margin for the output's background task to copy it.
|
|
1808
|
+
if elapsed < (w - 3) * hd + (hd >> 2):
|
|
1809
|
+
return True
|
|
1810
|
+
self._fill_once()
|
|
1811
|
+
if self._filled >= self._half:
|
|
1812
|
+
if self._eof and self._last_real < 0:
|
|
1813
|
+
self._last_real = w
|
|
1814
|
+
self._w = w + 1
|
|
1815
|
+
self._filled = 0
|
|
1816
|
+
return True
|
|
1817
|
+
|
|
1818
|
+
def pause(self):
|
|
1819
|
+
"""Pause output, if the audio object supports it."""
|
|
1820
|
+
if self._playing and not self._paused:
|
|
1821
|
+
self._audio.pause()
|
|
1822
|
+
self._pause_t = _now()
|
|
1823
|
+
self._paused = True
|
|
1824
|
+
|
|
1825
|
+
def resume(self):
|
|
1826
|
+
"""Resume after `pause`."""
|
|
1827
|
+
if self._paused:
|
|
1828
|
+
self._t0 += _now() - self._pause_t
|
|
1829
|
+
self._audio.resume()
|
|
1830
|
+
self._paused = False
|
|
1831
|
+
|
|
1832
|
+
def stop(self):
|
|
1833
|
+
"""Stop playback."""
|
|
1834
|
+
if self._playing:
|
|
1835
|
+
self._audio.stop()
|
|
1836
|
+
self._playing = False
|
|
1837
|
+
self._paused = False
|
|
1838
|
+
|
|
1839
|
+
def deinit(self):
|
|
1840
|
+
"""Stop and release buffers."""
|
|
1841
|
+
self.stop()
|
|
1842
|
+
self._buf = None
|
|
1843
|
+
self._mv = None
|
|
1844
|
+
self._fixed = False
|
|
1845
|
+
self._sample = None
|