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.
@@ -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