pyglet 3.0.dev3__py3-none-any.whl → 3.0.dev4__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.
- pyglet/__init__.py +2 -2
- pyglet/app/cocoa.py +2 -1
- pyglet/customtypes.py +2 -2
- pyglet/event.py +10 -2
- pyglet/experimental/README.md +26 -1
- pyglet/experimental/flaccodec.py +380 -0
- pyglet/experimental/geoshader_sprite.py +2 -6
- pyglet/experimental/net.py +6 -6
- pyglet/experimental/particles.py +38 -0
- pyglet/experimental/win32priority.py +68 -0
- pyglet/font/dwrite/__init__.py +1 -1
- pyglet/font/dwrite/d2d1_lib.py +1 -1
- pyglet/font/fontconfig.py +1 -1
- pyglet/graphics/__init__.py +39 -7
- pyglet/graphics/api/__init__.py +14 -25
- pyglet/graphics/api/gl/buffer.py +108 -25
- pyglet/graphics/api/gl/draw.py +4 -168
- pyglet/graphics/api/gl/global_opengl.py +1 -1
- pyglet/graphics/api/gl/shader.py +127 -157
- pyglet/graphics/api/gl/sprite.py +99 -0
- pyglet/graphics/api/gl/texture.py +28 -11
- pyglet/graphics/api/gl/vertexdomain.py +115 -4
- pyglet/graphics/api/gl2/buffer.py +3 -4
- pyglet/graphics/api/gl2/draw.py +6 -134
- pyglet/graphics/api/gl2/global_opengl.py +1 -1
- pyglet/graphics/api/gl2/shader.py +64 -9
- pyglet/graphics/api/gl2/shapes.py +1 -1
- pyglet/graphics/api/gl2/sprite.py +87 -0
- pyglet/graphics/api/gl2/vertexdomain.py +3 -2
- pyglet/graphics/api/webgl/__init__.py +1 -1
- pyglet/graphics/api/webgl/buffer.py +100 -30
- pyglet/graphics/api/webgl/draw.py +4 -134
- pyglet/graphics/api/webgl/shader.py +126 -152
- pyglet/graphics/api/webgl/sprite.py +99 -0
- pyglet/graphics/api/webgl/texture.py +2 -0
- pyglet/graphics/api/webgl/vertexdomain.py +2 -1
- pyglet/graphics/atlas.py +4 -4
- pyglet/graphics/buffer.py +27 -0
- pyglet/graphics/draw.py +107 -24
- pyglet/graphics/instance.py +155 -2
- pyglet/graphics/shader.py +287 -11
- pyglet/graphics/texture.py +68 -56
- pyglet/graphics/vertexdomain.py +169 -8
- pyglet/image/__init__.py +12 -12
- pyglet/image/animation.py +9 -4
- pyglet/image/base.py +8 -9
- pyglet/image/codecs/wic.py +1 -1
- pyglet/info.py +319 -125
- pyglet/input/win32/directinput.py +10 -3
- pyglet/lib.py +6 -1
- pyglet/libs/darwin/cocoapy/cocoalibs.py +83 -0
- pyglet/libs/darwin/cocoapy/runtime.py +38 -17
- pyglet/math.py +76 -42
- pyglet/media/codecs/base.py +2 -2
- pyglet/media/codecs/wave.py +17 -11
- pyglet/media/drivers/base.py +15 -8
- pyglet/media/drivers/pulse/adaptation.py +2 -2
- pyglet/media/drivers/pulse/interface.py +2 -2
- pyglet/media/player.py +9 -1
- pyglet/media/synthesis.py +3 -3
- pyglet/model/__init__.py +313 -54
- pyglet/model/codecs/__init__.py +2 -2
- pyglet/model/codecs/obj.py +7 -1
- pyglet/resource.py +74 -6
- pyglet/shapes.py +4 -3
- pyglet/sprite.py +292 -11
- pyglet/text/__init__.py +2 -2
- pyglet/text/caret.py +7 -6
- pyglet/window/__init__.py +2 -2
- {pyglet-3.0.dev3.dist-info → pyglet-3.0.dev4.dist-info}/METADATA +1 -1
- {pyglet-3.0.dev3.dist-info → pyglet-3.0.dev4.dist-info}/RECORD +74 -73
- pyglet/experimental/multitexture_sprite.py +0 -541
- /pyglet/{image/codecs/wincodec_lib.py → libs/win32/wincodec.py} +0 -0
- {pyglet-3.0.dev3.dist-info → pyglet-3.0.dev4.dist-info}/WHEEL +0 -0
- {pyglet-3.0.dev3.dist-info → pyglet-3.0.dev4.dist-info}/licenses/LICENSE +0 -0
pyglet/__init__.py
CHANGED
|
@@ -18,7 +18,7 @@ if TYPE_CHECKING:
|
|
|
18
18
|
from typing import Any, Callable, ItemsView, Sized
|
|
19
19
|
|
|
20
20
|
#: The release version
|
|
21
|
-
version = '3.0.
|
|
21
|
+
version = '3.0.dev4'
|
|
22
22
|
__version__ = version
|
|
23
23
|
|
|
24
24
|
MIN_PYTHON_VERSION = 3, 8
|
|
@@ -103,7 +103,7 @@ class Options:
|
|
|
103
103
|
|
|
104
104
|
debug_graphics_batch: bool = False
|
|
105
105
|
"""If ``True``, prints batch information being drawn, including :py:class:`~pyglet.graphics.Group`'s, VertexDomains,
|
|
106
|
-
and :py:class:`~pyglet.graphics.Texture` information. This can be useful to see how many Group's are being
|
|
106
|
+
and :py:class:`~pyglet.graphics.texture.Texture` information. This can be useful to see how many Group's are being
|
|
107
107
|
consolidated."""
|
|
108
108
|
|
|
109
109
|
debug_lib: bool = False
|
pyglet/app/cocoa.py
CHANGED
|
@@ -6,7 +6,7 @@ import time
|
|
|
6
6
|
from pyglet import app
|
|
7
7
|
from pyglet.app.base import PlatformEventLoop, EventLoop
|
|
8
8
|
from pyglet.libs.darwin import cocoapy, AutoReleasePool, ObjCSubclass, PyObjectEncoding, ObjCInstance, send_super, \
|
|
9
|
-
ObjCClass, get_selector
|
|
9
|
+
ObjCClass, get_selector, set_realtime_thread_policy
|
|
10
10
|
|
|
11
11
|
NSApplication = cocoapy.ObjCClass('NSApplication')
|
|
12
12
|
NSMenu = cocoapy.ObjCClass('NSMenu')
|
|
@@ -161,6 +161,7 @@ class CocoaPlatformEventLoop(PlatformEventLoop):
|
|
|
161
161
|
|
|
162
162
|
def __init__(self):
|
|
163
163
|
super().__init__()
|
|
164
|
+
set_realtime_thread_policy()
|
|
164
165
|
self._timer = None
|
|
165
166
|
|
|
166
167
|
with AutoReleasePool():
|
pyglet/customtypes.py
CHANGED
|
@@ -22,8 +22,8 @@ MediaTypes = Literal["audio", "video", "image"]
|
|
|
22
22
|
|
|
23
23
|
Number = Union[int, float]
|
|
24
24
|
|
|
25
|
-
RGBColor = Tuple[
|
|
26
|
-
RGBAColor = Tuple[
|
|
25
|
+
RGBColor = Tuple[int, int, int]
|
|
26
|
+
RGBAColor = Tuple[int, int, int, int]
|
|
27
27
|
|
|
28
28
|
DataTypes = Literal[
|
|
29
29
|
'f', # float
|
pyglet/event.py
CHANGED
|
@@ -356,8 +356,16 @@ class EventDispatcher:
|
|
|
356
356
|
if not handler:
|
|
357
357
|
continue
|
|
358
358
|
if isinstance(handler, WeakMethod):
|
|
359
|
-
|
|
360
|
-
|
|
359
|
+
weak_handler = handler
|
|
360
|
+
handler = weak_handler()
|
|
361
|
+
if handler is None:
|
|
362
|
+
# Weak handlers can expire before their callback is removed from stack.
|
|
363
|
+
# Skip and remove stale handlers
|
|
364
|
+
if frame.get(event_type) is weak_handler:
|
|
365
|
+
del frame[event_type]
|
|
366
|
+
if not frame and frame in self._event_stack:
|
|
367
|
+
self._event_stack.remove(frame)
|
|
368
|
+
continue
|
|
361
369
|
try:
|
|
362
370
|
invoked = True
|
|
363
371
|
if handler(*args):
|
pyglet/experimental/README.md
CHANGED
|
@@ -3,4 +3,29 @@ Experimental modules
|
|
|
3
3
|
|
|
4
4
|
This package contains experimental modules, which are included here for
|
|
5
5
|
wider testing and feedback. Anything contained within may be broken, refactored,
|
|
6
|
-
or removed without notice.
|
|
6
|
+
or removed without notice.
|
|
7
|
+
|
|
8
|
+
A module may belong in `experimental` when it meets one or more of the following criteria:
|
|
9
|
+
|
|
10
|
+
- The API is still being designed.
|
|
11
|
+
- The implementation needs wider testing.
|
|
12
|
+
- The feature depends on external tools, generated assets, or optional workflows.
|
|
13
|
+
- The feature is useful, but too specialized for the core package.
|
|
14
|
+
- The feature introduces complexity that should not be required for normal users.
|
|
15
|
+
- The feature may eventually move into the stable package after review.
|
|
16
|
+
|
|
17
|
+
## Requirements
|
|
18
|
+
|
|
19
|
+
Each experimental module should include documentation explaining:
|
|
20
|
+
|
|
21
|
+
- What the module solves or does.
|
|
22
|
+
- What tools, files, or assets are required.
|
|
23
|
+
- A minimal example, either as a `"__main__"` execution, or docstring.
|
|
24
|
+
|
|
25
|
+
## Graduation to stable API
|
|
26
|
+
|
|
27
|
+
An experimental module may be considered for promotion to the stable package when:
|
|
28
|
+
|
|
29
|
+
- The API has proven useful and reasonably stable.
|
|
30
|
+
- The feature has documentation and examples.
|
|
31
|
+
- Maintainers are willing to support it long-term.
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import struct
|
|
3
|
+
|
|
4
|
+
from concurrent.futures import ProcessPoolExecutor
|
|
5
|
+
|
|
6
|
+
from pyglet.util import DecodeException
|
|
7
|
+
from pyglet.media.codecs.base import StreamingSource, AudioData, AudioFormat, StaticSource
|
|
8
|
+
from pyglet.media.codecs import MediaDecoder
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# This module uses code from Project Nayuki:
|
|
12
|
+
# https://www.nayuki.io/page/simple-flac-implementation
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FLACDecodeException(DecodeException):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
FIXED_PREDICTION_COEFFICIENTS = ((), (1,), (2, -1), (3, -3, 1), (4, -6, 4, -1))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _initializer(filename, numchannels, sample_size):
|
|
23
|
+
"""Function to open a copy of the file locally in each Process."""
|
|
24
|
+
global _file
|
|
25
|
+
global _numchannels
|
|
26
|
+
global _sample_size
|
|
27
|
+
_file = open(filename, 'rb')
|
|
28
|
+
_numchannels = numchannels
|
|
29
|
+
_sample_size = sample_size
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def decode_frame(offset):
|
|
33
|
+
try:
|
|
34
|
+
# Read a ton of header fields, and ignore most of them
|
|
35
|
+
numchannels = _numchannels
|
|
36
|
+
sample_size = _sample_size
|
|
37
|
+
_file.seek(offset)
|
|
38
|
+
inp = BitInputStream(_file)
|
|
39
|
+
|
|
40
|
+
temp = inp.read_byte()
|
|
41
|
+
if temp == -1:
|
|
42
|
+
return False
|
|
43
|
+
sync = temp << 6 | inp.read_uint(6)
|
|
44
|
+
if sync != 0x3FFE:
|
|
45
|
+
raise FLACDecodeException("Sync code expected")
|
|
46
|
+
|
|
47
|
+
inp.read_uint(1)
|
|
48
|
+
inp.read_uint(1)
|
|
49
|
+
blocksizecode = inp.read_uint(4)
|
|
50
|
+
sampleratecode = inp.read_uint(4)
|
|
51
|
+
chanasgn = inp.read_uint(4)
|
|
52
|
+
inp.read_uint(3)
|
|
53
|
+
inp.read_uint(1)
|
|
54
|
+
|
|
55
|
+
temp = inp.read_uint(8)
|
|
56
|
+
while temp >= 0b11000000:
|
|
57
|
+
inp.read_uint(8)
|
|
58
|
+
temp = (temp << 1) & 0xFF
|
|
59
|
+
|
|
60
|
+
if blocksizecode == 1:
|
|
61
|
+
blocksize = 192
|
|
62
|
+
elif 2 <= blocksizecode <= 5:
|
|
63
|
+
blocksize = 576 << blocksizecode - 2
|
|
64
|
+
elif blocksizecode == 6:
|
|
65
|
+
blocksize = inp.read_uint(8) + 1
|
|
66
|
+
elif blocksizecode == 7:
|
|
67
|
+
blocksize = inp.read_uint(16) + 1
|
|
68
|
+
elif 8 <= blocksizecode <= 15:
|
|
69
|
+
blocksize = 256 << (blocksizecode - 8)
|
|
70
|
+
else:
|
|
71
|
+
raise FLACDecodeException("Invalid Blocksize")
|
|
72
|
+
|
|
73
|
+
if sampleratecode == 12:
|
|
74
|
+
inp.read_uint(8)
|
|
75
|
+
elif sampleratecode in (13, 14):
|
|
76
|
+
inp.read_uint(16)
|
|
77
|
+
|
|
78
|
+
crc8 = inp.read_byte()
|
|
79
|
+
|
|
80
|
+
# Decode each channel's subframe, then skip footer
|
|
81
|
+
if 0 <= chanasgn <= 7:
|
|
82
|
+
samples = [decode_subframe(inp, blocksize, sample_size) for _ in range(chanasgn + 1)]
|
|
83
|
+
elif 8 <= chanasgn <= 10:
|
|
84
|
+
temp0 = decode_subframe(inp, blocksize, sample_size + (1 if (chanasgn == 9) else 0))
|
|
85
|
+
temp1 = decode_subframe(inp, blocksize, sample_size + (0 if (chanasgn == 9) else 1))
|
|
86
|
+
if chanasgn == 8:
|
|
87
|
+
for i in range(blocksize):
|
|
88
|
+
temp1[i] = temp0[i] - temp1[i]
|
|
89
|
+
elif chanasgn == 9:
|
|
90
|
+
for i in range(blocksize):
|
|
91
|
+
temp0[i] += temp1[i]
|
|
92
|
+
elif chanasgn == 10:
|
|
93
|
+
for i in range(blocksize):
|
|
94
|
+
side = temp1[i]
|
|
95
|
+
right = temp0[i] - (side >> 1)
|
|
96
|
+
temp1[i] = right
|
|
97
|
+
temp0[i] = right + side
|
|
98
|
+
samples = [temp0, temp1]
|
|
99
|
+
else:
|
|
100
|
+
raise FLACDecodeException("Reserved channel assignment")
|
|
101
|
+
|
|
102
|
+
inp.align_to_byte()
|
|
103
|
+
inp.read_byte()
|
|
104
|
+
inp.read_byte()
|
|
105
|
+
|
|
106
|
+
# Write the decoded samples
|
|
107
|
+
fmt = str(blocksize * numchannels) + 'h'
|
|
108
|
+
if sample_size == 8:
|
|
109
|
+
return struct.pack(fmt, *(samples[i][j] + 128 for j in range(blocksize) for i in range(numchannels)))
|
|
110
|
+
else:
|
|
111
|
+
return struct.pack(fmt, *(samples[i][j] for j in range(blocksize) for i in range(numchannels)))
|
|
112
|
+
except:
|
|
113
|
+
return b""
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def decode_subframe(inp, blocksize, sampledepth):
|
|
117
|
+
inp.read_uint(1)
|
|
118
|
+
subframe_type = inp.read_uint(6)
|
|
119
|
+
shift = inp.read_uint(1)
|
|
120
|
+
if shift == 1:
|
|
121
|
+
while inp.read_uint(1) == 0:
|
|
122
|
+
shift += 1
|
|
123
|
+
sampledepth -= shift
|
|
124
|
+
|
|
125
|
+
if subframe_type == 0: # Constant coding
|
|
126
|
+
result = [inp.read_signed_int(sampledepth)] * blocksize
|
|
127
|
+
elif subframe_type == 1: # Verbatim coding
|
|
128
|
+
result = [inp.read_signed_int(sampledepth) for _ in range(blocksize)]
|
|
129
|
+
elif 8 <= subframe_type <= 12:
|
|
130
|
+
result = decode_fixed_prediction_subframe(inp, subframe_type - 8, blocksize, sampledepth)
|
|
131
|
+
elif 32 <= subframe_type <= 63:
|
|
132
|
+
result = decode_linear_predictive_coding_subframe(inp, subframe_type - 31, blocksize, sampledepth)
|
|
133
|
+
else:
|
|
134
|
+
raise FLACDecodeException("Reserved subframe type")
|
|
135
|
+
return [(v << shift) for v in result]
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def decode_fixed_prediction_subframe(inp, predorder, blocksize, sampledepth):
|
|
139
|
+
result = [inp.read_signed_int(sampledepth) for _ in range(predorder)]
|
|
140
|
+
decode_residuals(inp, blocksize, result)
|
|
141
|
+
restore_linear_prediction(result, FIXED_PREDICTION_COEFFICIENTS[predorder], 0)
|
|
142
|
+
return result
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def decode_linear_predictive_coding_subframe(inp, lpcorder, blocksize, sampledepth):
|
|
146
|
+
result = [inp.read_signed_int(sampledepth) for _ in range(lpcorder)]
|
|
147
|
+
precision = inp.read_uint(4) + 1
|
|
148
|
+
shift = inp.read_signed_int(5)
|
|
149
|
+
coefs = [inp.read_signed_int(precision) for _ in range(lpcorder)]
|
|
150
|
+
decode_residuals(inp, blocksize, result)
|
|
151
|
+
restore_linear_prediction(result, coefs, shift)
|
|
152
|
+
return result
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def decode_residuals(inp, blocksize, result):
|
|
156
|
+
method = inp.read_uint(2)
|
|
157
|
+
if method >= 2:
|
|
158
|
+
raise FLACDecodeException("Reserved residual coding method")
|
|
159
|
+
parambits = [4, 5][method]
|
|
160
|
+
escapeparam = [0xF, 0x1F][method]
|
|
161
|
+
|
|
162
|
+
partitionorder = inp.read_uint(4)
|
|
163
|
+
numpartitions = 1 << partitionorder
|
|
164
|
+
if blocksize % numpartitions != 0:
|
|
165
|
+
raise FLACDecodeException("Block size not divisible by number of Rice partitions")
|
|
166
|
+
|
|
167
|
+
for i in range(numpartitions):
|
|
168
|
+
count = blocksize >> partitionorder
|
|
169
|
+
if i == 0:
|
|
170
|
+
count -= len(result)
|
|
171
|
+
param = inp.read_uint(parambits)
|
|
172
|
+
if param < escapeparam:
|
|
173
|
+
result.extend(inp.read_rice_signed_int(param) for _ in range(count))
|
|
174
|
+
else:
|
|
175
|
+
numbits = inp.read_uint(5)
|
|
176
|
+
result.extend(inp.read_signed_int(numbits) for _ in range(count))
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def restore_linear_prediction(result, coefs, shift):
|
|
180
|
+
for i in range(len(coefs), len(result)):
|
|
181
|
+
result[i] += sum((result[i - 1 - j] * c) for (j, c) in enumerate(coefs)) >> shift
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class BitInputStream:
|
|
185
|
+
__slots__ = 'inp', 'bitbuffer', 'bitbufferlen'
|
|
186
|
+
|
|
187
|
+
def __init__(self, inp):
|
|
188
|
+
self.inp = inp
|
|
189
|
+
self.bitbuffer = 0
|
|
190
|
+
self.bitbufferlen = 0
|
|
191
|
+
|
|
192
|
+
def align_to_byte(self):
|
|
193
|
+
self.bitbufferlen -= self.bitbufferlen % 8
|
|
194
|
+
|
|
195
|
+
def read_byte(self):
|
|
196
|
+
if self.bitbufferlen >= 8:
|
|
197
|
+
return self.read_uint(8)
|
|
198
|
+
else:
|
|
199
|
+
result = self.inp.read(1)
|
|
200
|
+
if len(result) == 0:
|
|
201
|
+
return -1
|
|
202
|
+
return result[0]
|
|
203
|
+
|
|
204
|
+
def read_uint(self, n):
|
|
205
|
+
bitbuffer = self.bitbuffer
|
|
206
|
+
bitbufferlen = self.bitbufferlen
|
|
207
|
+
|
|
208
|
+
while bitbufferlen < n:
|
|
209
|
+
temp = self.inp.read(1)
|
|
210
|
+
if len(temp) == 0:
|
|
211
|
+
raise EOFError()
|
|
212
|
+
temp = temp[0]
|
|
213
|
+
bitbuffer = (bitbuffer << 8) | temp
|
|
214
|
+
bitbufferlen += 8
|
|
215
|
+
bitbufferlen -= n
|
|
216
|
+
result = (bitbuffer >> bitbufferlen) & ((1 << n) - 1)
|
|
217
|
+
bitbuffer &= (1 << bitbufferlen) - 1
|
|
218
|
+
|
|
219
|
+
self.bitbuffer = bitbuffer
|
|
220
|
+
self.bitbufferlen = bitbufferlen
|
|
221
|
+
return result
|
|
222
|
+
|
|
223
|
+
def read_signed_int(self, n):
|
|
224
|
+
temp = self.read_uint(n)
|
|
225
|
+
temp -= (temp >> (n - 1)) << n
|
|
226
|
+
return temp
|
|
227
|
+
|
|
228
|
+
def read_rice_signed_int(self, param):
|
|
229
|
+
read_uint = self.read_uint
|
|
230
|
+
val = 0
|
|
231
|
+
while read_uint(1) == 0:
|
|
232
|
+
val += 1
|
|
233
|
+
val = (val << param) | read_uint(param)
|
|
234
|
+
return (val >> 1) ^ -(val & 1)
|
|
235
|
+
|
|
236
|
+
def close(self):
|
|
237
|
+
self.inp.close()
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def _find_frame_indicies(data):
|
|
241
|
+
magic1 = struct.pack('BB', int('11111111', 2), int('11111000', 2))
|
|
242
|
+
magic2 = struct.pack('BB', int('11111111', 2), int('11111001', 2))
|
|
243
|
+
indices1 = [m.start() for m in re.finditer(magic1, data)]
|
|
244
|
+
indices2 = [m.start() for m in re.finditer(magic2, data)]
|
|
245
|
+
indices = indices1 + indices2
|
|
246
|
+
indices.sort()
|
|
247
|
+
return indices
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
#########################################
|
|
251
|
+
# Source class:
|
|
252
|
+
#########################################
|
|
253
|
+
class FLACSource(StreamingSource):
|
|
254
|
+
def __init__(self, filename, file=None):
|
|
255
|
+
if file is None:
|
|
256
|
+
file = open(filename, 'rb')
|
|
257
|
+
|
|
258
|
+
self.frame_indices = _find_frame_indicies(file.read())
|
|
259
|
+
self.frame_index = 0
|
|
260
|
+
file.seek(0)
|
|
261
|
+
self._file = BitInputStream(file)
|
|
262
|
+
self._filename = filename
|
|
263
|
+
|
|
264
|
+
if file.read(4) != b'fLaC':
|
|
265
|
+
raise FLACDecodeException("Does not appear to be a FLAC file.")
|
|
266
|
+
|
|
267
|
+
samplerate = numchannels = bits_per_sample = numsamples = None
|
|
268
|
+
|
|
269
|
+
last = False
|
|
270
|
+
while not last:
|
|
271
|
+
last = self._file.read_uint(1) != 0
|
|
272
|
+
block_type = self._file.read_uint(7)
|
|
273
|
+
length = self._file.read_uint(24)
|
|
274
|
+
if block_type == 0: # Stream info block
|
|
275
|
+
min_block_size = self._file.read_uint(16) # in samples
|
|
276
|
+
max_block_size = self._file.read_uint(16) # in samples
|
|
277
|
+
min_frame_size = self._file.read_uint(24) # in bytes, 0 == unknown
|
|
278
|
+
max_frame_size = self._file.read_uint(24) # in bytes, 0 == unknown
|
|
279
|
+
samplerate = self._file.read_uint(20)
|
|
280
|
+
numchannels = self._file.read_uint(3) + 1
|
|
281
|
+
bits_per_sample = self._file.read_uint(5) + 1
|
|
282
|
+
numsamples = self._file.read_uint(36)
|
|
283
|
+
self._file.read_uint(128)
|
|
284
|
+
else: # Skip other metadata blocks
|
|
285
|
+
for i in range(length):
|
|
286
|
+
self._file.read_byte()
|
|
287
|
+
|
|
288
|
+
for offset in self.frame_indices:
|
|
289
|
+
if offset < self._file.inp.tell():
|
|
290
|
+
self.frame_indices.remove(offset)
|
|
291
|
+
|
|
292
|
+
if not samplerate:
|
|
293
|
+
raise FLACDecodeException("Stream info metadata block absent")
|
|
294
|
+
if bits_per_sample % 8 != 0:
|
|
295
|
+
raise FLACDecodeException("Sample depth {} not supported".format(bits_per_sample))
|
|
296
|
+
|
|
297
|
+
self._numsamples = numsamples
|
|
298
|
+
self._duration = float(numsamples) / samplerate / 60
|
|
299
|
+
self.audio_format = AudioFormat(numchannels, bits_per_sample, samplerate)
|
|
300
|
+
|
|
301
|
+
self.executor = ProcessPoolExecutor(max_workers=3, initializer=_initializer,
|
|
302
|
+
initargs=(filename, numchannels, bits_per_sample))
|
|
303
|
+
|
|
304
|
+
def __del__(self):
|
|
305
|
+
self._file.close()
|
|
306
|
+
|
|
307
|
+
def get_audio_data(self, num_bytes, compensation_time=0.0):
|
|
308
|
+
|
|
309
|
+
data = b''
|
|
310
|
+
|
|
311
|
+
while not data and (self.frame_index <= len(self.frame_indices)):
|
|
312
|
+
offset = self.frame_indices[self.frame_index]
|
|
313
|
+
future = self.executor.submit(decode_frame, offset)
|
|
314
|
+
data += future.result()
|
|
315
|
+
self.frame_index += 1
|
|
316
|
+
|
|
317
|
+
if not data:
|
|
318
|
+
return None
|
|
319
|
+
|
|
320
|
+
return AudioData(data, len(data), timestamp=0.1, duration=0.5, events=[])
|
|
321
|
+
|
|
322
|
+
def save(self, filename="output.wav"):
|
|
323
|
+
with open(filename, 'wb') as out:
|
|
324
|
+
numsamples = self._numsamples
|
|
325
|
+
numchannels = self.audio_format.channels
|
|
326
|
+
samplerate = self.audio_format.sample_rate
|
|
327
|
+
sample_size = self.audio_format.sample_size
|
|
328
|
+
|
|
329
|
+
# Start writing WAV file headers
|
|
330
|
+
sampledatalen = numsamples * numchannels * (sample_size // 8)
|
|
331
|
+
|
|
332
|
+
out.write(b"RIFF")
|
|
333
|
+
out.write(struct.pack("<I", sampledatalen + 36))
|
|
334
|
+
out.write(b"WAVE")
|
|
335
|
+
out.write(b"fmt ")
|
|
336
|
+
out.write(struct.pack("<IHHIIHH",
|
|
337
|
+
16,
|
|
338
|
+
0x0001,
|
|
339
|
+
numchannels,
|
|
340
|
+
samplerate,
|
|
341
|
+
samplerate * numchannels * (sample_size // 8),
|
|
342
|
+
numchannels * (sample_size // 8),
|
|
343
|
+
sample_size))
|
|
344
|
+
out.write(b"data")
|
|
345
|
+
out.write(struct.pack("<I", sampledatalen))
|
|
346
|
+
|
|
347
|
+
import time
|
|
348
|
+
start = time.time()
|
|
349
|
+
futures = [self.executor.submit(decode_frame, offset) for offset in self.frame_indices]
|
|
350
|
+
print("elapsed future making", time.time() - start)
|
|
351
|
+
|
|
352
|
+
while futures:
|
|
353
|
+
data = futures.pop(0).result()
|
|
354
|
+
out.write(data)
|
|
355
|
+
|
|
356
|
+
def seek(self, timestamp):
|
|
357
|
+
pass
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
#########################################
|
|
361
|
+
# Decoder class:
|
|
362
|
+
#########################################
|
|
363
|
+
class FLACDecoder(MediaDecoder):
|
|
364
|
+
|
|
365
|
+
def get_file_extensions(self):
|
|
366
|
+
return '.flac',
|
|
367
|
+
|
|
368
|
+
def decode(self, file, filename, streaming=True):
|
|
369
|
+
if streaming:
|
|
370
|
+
return FLACSource(filename, file)
|
|
371
|
+
else:
|
|
372
|
+
return StaticSource(FLACSource(filename, file))
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
def get_decoders():
|
|
376
|
+
return [FLACDecoder()]
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
def get_encoders():
|
|
380
|
+
return []
|
|
@@ -245,7 +245,7 @@ class Sprite(event.EventDispatcher):
|
|
|
245
245
|
"""Create a sprite.
|
|
246
246
|
|
|
247
247
|
:Parameters:
|
|
248
|
-
`img` :
|
|
248
|
+
`img` :
|
|
249
249
|
Image or animation to display.
|
|
250
250
|
`x` : int
|
|
251
251
|
X coordinate of the sprite.
|
|
@@ -418,11 +418,7 @@ class Sprite(event.EventDispatcher):
|
|
|
418
418
|
|
|
419
419
|
@property
|
|
420
420
|
def image(self):
|
|
421
|
-
"""Image or animation to display.
|
|
422
|
-
|
|
423
|
-
:type: :py:class:`~pyglet.image.AbstractImage` or
|
|
424
|
-
:py:class:`~pyglet.image.Animation`
|
|
425
|
-
"""
|
|
421
|
+
"""Image or animation to display."""
|
|
426
422
|
if self._animation:
|
|
427
423
|
return self._animation
|
|
428
424
|
return self._texture
|
pyglet/experimental/net.py
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""Experimental networking
|
|
1
|
+
"""Experimental networking.
|
|
2
2
|
|
|
3
3
|
This module contains experiments in making user-friendly Server and Client
|
|
4
4
|
classes that integrate with pyglet's event system. These are very basic,
|
|
@@ -56,7 +56,7 @@ _debug_net = debug_print('debug_net')
|
|
|
56
56
|
|
|
57
57
|
|
|
58
58
|
class Client(_EventDispatcher):
|
|
59
|
-
def __init__(self, address, port):
|
|
59
|
+
def __init__(self, address: str, port: int):
|
|
60
60
|
"""Create a Client connection to a Server."""
|
|
61
61
|
self._socket = _socket.create_connection((address, port))
|
|
62
62
|
self._address = address
|
|
@@ -70,7 +70,7 @@ class Client(_EventDispatcher):
|
|
|
70
70
|
|
|
71
71
|
self._sentinal = object() # poison pill
|
|
72
72
|
|
|
73
|
-
def close(self):
|
|
73
|
+
def close(self) -> None:
|
|
74
74
|
"""Close the connection."""
|
|
75
75
|
self._queue.put(self._sentinal)
|
|
76
76
|
self._socket.shutdown(1)
|
|
@@ -78,15 +78,15 @@ class Client(_EventDispatcher):
|
|
|
78
78
|
self._terminate.set()
|
|
79
79
|
self.dispatch_event('on_disconnect', self)
|
|
80
80
|
|
|
81
|
-
def send(self, message):
|
|
81
|
+
def send(self, message: bytes) -> None:
|
|
82
82
|
"""Queue a message to send.
|
|
83
83
|
|
|
84
84
|
Put a string of bytes into the queue to send.
|
|
85
85
|
raises a `ConnectionError` if the connection
|
|
86
86
|
has been closed or dropped.
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
|
|
88
|
+
Args:
|
|
89
|
+
message:
|
|
90
90
|
A string of bytes to send.
|
|
91
91
|
"""
|
|
92
92
|
if self._terminate.is_set():
|
pyglet/experimental/particles.py
CHANGED
|
@@ -393,3 +393,41 @@ class ParticleManager:
|
|
|
393
393
|
batch=self._batch, group=self._group)
|
|
394
394
|
pyglet.clock.schedule_once(self._delete_callback, self.lifespan, emitter)
|
|
395
395
|
return emitter
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
if __name__ == "__main__":
|
|
399
|
+
window = pyglet.window.Window(960, 540, caption="ParticleManager Demo", resizable=True)
|
|
400
|
+
|
|
401
|
+
batch = graphics.Batch()
|
|
402
|
+
|
|
403
|
+
label = pyglet.text.Label("Click and drag.", x=5, y=5, batch=batch)
|
|
404
|
+
|
|
405
|
+
particle_img = image.SolidColorImagePattern((255, 255, 255, 255)).create_image(8, 8)
|
|
406
|
+
manager = ParticleManager(
|
|
407
|
+
particle_img,
|
|
408
|
+
lifespan=1.0,
|
|
409
|
+
count=12,
|
|
410
|
+
velocity=(100.0, 80.0),
|
|
411
|
+
spread=(0.30, 0.30),
|
|
412
|
+
color_start=(255, 200, 80, 220),
|
|
413
|
+
color_end=(255, 40, 20, 0),
|
|
414
|
+
scale_start=(0.4, 0.4),
|
|
415
|
+
scale_end=(1.2, 1.2),
|
|
416
|
+
rotation=0.0,
|
|
417
|
+
batch=batch,
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
@window.event
|
|
421
|
+
def on_draw():
|
|
422
|
+
window.clear()
|
|
423
|
+
batch.draw()
|
|
424
|
+
|
|
425
|
+
@window.event
|
|
426
|
+
def on_mouse_press(x, y, button, modifiers):
|
|
427
|
+
manager.create_emitter(x, y)
|
|
428
|
+
|
|
429
|
+
@window.event
|
|
430
|
+
def on_mouse_drag(x, y, dx, dy, buttons, modifiers):
|
|
431
|
+
manager.create_emitter(x, y)
|
|
432
|
+
|
|
433
|
+
pyglet.app.run()
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Utilities to patch Pyglet to provide more precise event timing on Windows.
|
|
2
|
+
# HighTimerResolution is generally the most important of the bunch,
|
|
3
|
+
# since most 60+fps games in Windows will not work right without
|
|
4
|
+
# changing the timer resolution for the app
|
|
5
|
+
|
|
6
|
+
import pyglet
|
|
7
|
+
import ctypes
|
|
8
|
+
|
|
9
|
+
try:
|
|
10
|
+
winmm = ctypes.windll.winmm
|
|
11
|
+
k32 = ctypes.windll.kernel32
|
|
12
|
+
except AttributeError:
|
|
13
|
+
raise NotImplementedError("This is not a Win32 compatible platform.")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class HighTimerResolution:
|
|
17
|
+
"""
|
|
18
|
+
A context manager to adjust the main Win32 platform event loop.
|
|
19
|
+
|
|
20
|
+
Events inside the context manager will use the specified resolution of the
|
|
21
|
+
multimedia timer to provide proper timings for 60fps+ framerates.
|
|
22
|
+
On exiting the context manager, the default resolution timer is restored.
|
|
23
|
+
|
|
24
|
+
Most 60fps games can use a resolution of 4, but you may need to go as low
|
|
25
|
+
as 1 to get the most consistent results.
|
|
26
|
+
|
|
27
|
+
Example:
|
|
28
|
+
|
|
29
|
+
with HighTimerResolution():
|
|
30
|
+
pyglet.app.run()
|
|
31
|
+
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
def __init__(self, resolution=1):
|
|
35
|
+
self.resolution = resolution
|
|
36
|
+
|
|
37
|
+
def __enter__(self):
|
|
38
|
+
winmm.timeBeginPeriod(self.resolution)
|
|
39
|
+
|
|
40
|
+
def __exit__(self, *a):
|
|
41
|
+
winmm.timeEndPeriod()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def pin_thread(pin_thread_mask=0x01):
|
|
45
|
+
"""
|
|
46
|
+
Pin the main Pyglet app thread to one or more cores by way of a thread affinity mask.
|
|
47
|
+
The default thread mask is 1.
|
|
48
|
+
"""
|
|
49
|
+
k32.SetThreadAffinityMask(
|
|
50
|
+
pyglet.app.platform_event_loop._event_thread, pin_thread_mask
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def pin_process():
|
|
55
|
+
"""
|
|
56
|
+
Pins the entire Python interpreter process to the first CPU core.
|
|
57
|
+
This may slightly improve performance due to affinity.
|
|
58
|
+
"""
|
|
59
|
+
k32.SetProcessAffinityMask(k32.GetCurrentProcess(), 1)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def raise_priority(
|
|
63
|
+
priority_class=0x080,
|
|
64
|
+
):
|
|
65
|
+
"""
|
|
66
|
+
Raises the process priority to High. Other priorities can be provided.
|
|
67
|
+
"""
|
|
68
|
+
k32.SetPriorityClass(-1, priority_class)
|
pyglet/font/dwrite/__init__.py
CHANGED
|
@@ -108,7 +108,7 @@ from pyglet.font.dwrite.dwrite_lib import (
|
|
|
108
108
|
DWRITE_INFORMATIONAL_STRING_FULL_NAME,
|
|
109
109
|
)
|
|
110
110
|
from pyglet.font.harfbuzz import get_harfbuzz_shaped_glyphs, get_resource_from_dw_font, harfbuzz_available
|
|
111
|
-
from pyglet.
|
|
111
|
+
from pyglet.libs.win32.wincodec import GUID_WICPixelFormat32bppPBGRA, IWICBitmap
|
|
112
112
|
from pyglet.libs.win32 import UINT16, UINT32, UINT64, com
|
|
113
113
|
from pyglet.libs.win32 import _kernel32 as kernel32
|
|
114
114
|
from pyglet.libs.win32.constants import (
|
pyglet/font/dwrite/d2d1_lib.py
CHANGED
|
@@ -28,7 +28,7 @@ from pyglet.font.dwrite.dwrite_lib import (
|
|
|
28
28
|
IDWriteTextFormat,
|
|
29
29
|
IDWriteTextLayout,
|
|
30
30
|
)
|
|
31
|
-
from pyglet.
|
|
31
|
+
from pyglet.libs.win32.wincodec import IWICBitmap, IWICBitmapSource
|
|
32
32
|
from pyglet.libs.win32 import c_void, com
|
|
33
33
|
from pyglet.libs.win32.types import BYTE, UINT32, UINT64
|
|
34
34
|
|
pyglet/font/fontconfig.py
CHANGED
|
@@ -196,7 +196,7 @@ class FontConfig:
|
|
|
196
196
|
return FontConfigSearchPattern(self._fontconfig)
|
|
197
197
|
|
|
198
198
|
def style_from_face(self, font_face: FT_Face):
|
|
199
|
-
pattern = self._fontconfig.FcFreeTypeQueryFace(font_face,
|
|
199
|
+
pattern = self._fontconfig.FcFreeTypeQueryFace(font_face, b'', 0, None)
|
|
200
200
|
result = FontConfigSearchResult(self._fontconfig, pattern)
|
|
201
201
|
return result.weight, result.italic, result.stretch
|
|
202
202
|
|