sonolus.py 0.1.3__py3-none-any.whl → 0.1.5__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.
Potentially problematic release.
This version of sonolus.py might be problematic. Click here for more details.
- sonolus/backend/blocks.py +756 -756
- sonolus/backend/excepthook.py +37 -37
- sonolus/backend/finalize.py +77 -69
- sonolus/backend/interpret.py +7 -7
- sonolus/backend/ir.py +29 -3
- sonolus/backend/mode.py +24 -24
- sonolus/backend/node.py +40 -40
- sonolus/backend/ops.py +197 -197
- sonolus/backend/optimize/__init__.py +0 -0
- sonolus/backend/optimize/allocate.py +126 -0
- sonolus/backend/optimize/constant_evaluation.py +374 -0
- sonolus/backend/optimize/copy_coalesce.py +85 -0
- sonolus/backend/optimize/dead_code.py +185 -0
- sonolus/backend/optimize/dominance.py +96 -0
- sonolus/backend/{flow.py → optimize/flow.py} +122 -92
- sonolus/backend/optimize/inlining.py +137 -0
- sonolus/backend/optimize/liveness.py +177 -0
- sonolus/backend/optimize/optimize.py +44 -0
- sonolus/backend/optimize/passes.py +52 -0
- sonolus/backend/optimize/simplify.py +191 -0
- sonolus/backend/optimize/ssa.py +200 -0
- sonolus/backend/place.py +17 -25
- sonolus/backend/utils.py +58 -48
- sonolus/backend/visitor.py +1151 -882
- sonolus/build/cli.py +7 -1
- sonolus/build/compile.py +88 -90
- sonolus/build/engine.py +10 -5
- sonolus/build/level.py +24 -23
- sonolus/build/node.py +43 -43
- sonolus/script/archetype.py +438 -139
- sonolus/script/array.py +27 -10
- sonolus/script/array_like.py +297 -0
- sonolus/script/bucket.py +253 -191
- sonolus/script/containers.py +257 -51
- sonolus/script/debug.py +26 -10
- sonolus/script/easing.py +365 -0
- sonolus/script/effect.py +191 -131
- sonolus/script/engine.py +71 -4
- sonolus/script/globals.py +303 -269
- sonolus/script/instruction.py +205 -151
- sonolus/script/internal/__init__.py +5 -5
- sonolus/script/internal/builtin_impls.py +255 -144
- sonolus/script/{callbacks.py → internal/callbacks.py} +127 -127
- sonolus/script/internal/constant.py +139 -0
- sonolus/script/internal/context.py +26 -9
- sonolus/script/internal/descriptor.py +17 -17
- sonolus/script/internal/dict_impl.py +65 -0
- sonolus/script/internal/generic.py +6 -9
- sonolus/script/internal/impl.py +38 -13
- sonolus/script/internal/introspection.py +17 -14
- sonolus/script/internal/math_impls.py +121 -0
- sonolus/script/internal/native.py +40 -38
- sonolus/script/internal/random.py +67 -0
- sonolus/script/internal/range.py +81 -0
- sonolus/script/internal/transient.py +51 -0
- sonolus/script/internal/tuple_impl.py +113 -0
- sonolus/script/internal/value.py +3 -3
- sonolus/script/interval.py +338 -112
- sonolus/script/iterator.py +167 -214
- sonolus/script/level.py +24 -0
- sonolus/script/num.py +80 -48
- sonolus/script/options.py +257 -191
- sonolus/script/particle.py +190 -157
- sonolus/script/pointer.py +30 -30
- sonolus/script/print.py +102 -81
- sonolus/script/project.py +8 -0
- sonolus/script/quad.py +263 -0
- sonolus/script/record.py +47 -16
- sonolus/script/runtime.py +52 -1
- sonolus/script/sprite.py +418 -333
- sonolus/script/text.py +409 -407
- sonolus/script/timing.py +114 -42
- sonolus/script/transform.py +332 -48
- sonolus/script/ui.py +216 -160
- sonolus/script/values.py +6 -13
- sonolus/script/vec.py +196 -78
- {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/METADATA +1 -1
- sonolus_py-0.1.5.dist-info/RECORD +89 -0
- {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/WHEEL +1 -1
- {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/licenses/LICENSE +21 -21
- sonolus/backend/allocate.py +0 -51
- sonolus/backend/optimize.py +0 -9
- sonolus/backend/passes.py +0 -6
- sonolus/backend/simplify.py +0 -30
- sonolus/script/comptime.py +0 -160
- sonolus/script/graphics.py +0 -150
- sonolus/script/math.py +0 -92
- sonolus/script/range.py +0 -58
- sonolus_py-0.1.3.dist-info/RECORD +0 -75
- {sonolus_py-0.1.3.dist-info → sonolus_py-0.1.5.dist-info}/entry_points.txt +0 -0
sonolus/script/easing.py
ADDED
|
@@ -0,0 +1,365 @@
|
|
|
1
|
+
# ruff: noqa: E501
|
|
2
|
+
import math
|
|
3
|
+
|
|
4
|
+
from sonolus.backend.ops import Op
|
|
5
|
+
from sonolus.script.internal.native import native_function
|
|
6
|
+
from sonolus.script.interval import clamp
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@native_function(Op.EaseInBack)
|
|
10
|
+
def ease_in_back(x: float) -> float:
|
|
11
|
+
"""Interpolate between 0 and 1, starting slow and ending fast, overshooting below 0 at the start."""
|
|
12
|
+
x = clamp(x, 0, 1)
|
|
13
|
+
c1 = 1.70158
|
|
14
|
+
c3 = c1 + 1
|
|
15
|
+
return c3 * x**3 - c1 * x**2
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@native_function(Op.EaseOutBack)
|
|
19
|
+
def ease_out_back(x: float) -> float:
|
|
20
|
+
"""Interpolate between 0 and 1, starting fast and ending slow, overshooting above 1 at the end."""
|
|
21
|
+
x = clamp(x, 0, 1)
|
|
22
|
+
c1 = 1.70158
|
|
23
|
+
c3 = c1 + 1
|
|
24
|
+
return 1 + c3 * (x - 1) ** 3 + c1 * (x - 1) ** 2
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@native_function(Op.EaseInOutBack)
|
|
28
|
+
def ease_in_out_back(x: float) -> float:
|
|
29
|
+
"""Interpolate between 0 and 1, starting and ending slow with overshooting, fast in the middle."""
|
|
30
|
+
x = clamp(x, 0, 1)
|
|
31
|
+
c1 = 1.70158
|
|
32
|
+
c2 = c1 * 1.525
|
|
33
|
+
if x < 0.5:
|
|
34
|
+
return ((2 * x) ** 2 * ((c2 + 1) * 2 * x - c2)) / 2
|
|
35
|
+
else:
|
|
36
|
+
return ((2 * x - 2) ** 2 * ((c2 + 1) * (2 * x - 2) + c2) + 2) / 2
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@native_function(Op.EaseOutInBack)
|
|
40
|
+
def ease_out_in_back(x: float) -> float:
|
|
41
|
+
"""Interpolate between 0 and 1, fast at the start and end, slow in the middle with overshooting."""
|
|
42
|
+
x = clamp(x, 0, 1)
|
|
43
|
+
c1 = 1.70158
|
|
44
|
+
c3 = c1 + 1
|
|
45
|
+
if x < 0.5:
|
|
46
|
+
return (1 + c3 * (2 * x - 1) ** 3 + c1 * (2 * x - 1) ** 2) / 2
|
|
47
|
+
else:
|
|
48
|
+
return (c3 * (2 * x - 1) ** 3 - c1 * (2 * x - 1) ** 2) / 2 + 0.5
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@native_function(Op.EaseInCirc)
|
|
52
|
+
def ease_in_circ(x: float) -> float:
|
|
53
|
+
"""Interpolate between 0 and 1, starting slow and ending very fast."""
|
|
54
|
+
x = clamp(x, 0, 1)
|
|
55
|
+
return 1 - math.sqrt(1 - x**2)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@native_function(Op.EaseOutCirc)
|
|
59
|
+
def ease_out_circ(x: float) -> float:
|
|
60
|
+
"""Interpolate between 0 and 1, starting very fast and ending slow."""
|
|
61
|
+
x = clamp(x, 0, 1)
|
|
62
|
+
return math.sqrt(1 - (x - 1) ** 2)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@native_function(Op.EaseInOutCirc)
|
|
66
|
+
def ease_in_out_circ(x: float) -> float:
|
|
67
|
+
"""Interpolate between 0 and 1, starting and ending slow, very fast in the middle."""
|
|
68
|
+
x = clamp(x, 0, 1)
|
|
69
|
+
if x < 0.5:
|
|
70
|
+
return (1 - math.sqrt(1 - (2 * x) ** 2)) / 2
|
|
71
|
+
else:
|
|
72
|
+
return (math.sqrt(1 - (2 * x - 2) ** 2) + 1) / 2
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
@native_function(Op.EaseOutInCirc)
|
|
76
|
+
def ease_out_in_circ(x: float) -> float:
|
|
77
|
+
"""Interpolate between 0 and 1, very fast at the start and end, slow in the middle."""
|
|
78
|
+
x = clamp(x, 0, 1)
|
|
79
|
+
if x < 0.5:
|
|
80
|
+
return math.sqrt(1 - (2 * x - 1) ** 2) / 2
|
|
81
|
+
else:
|
|
82
|
+
return (1 - math.sqrt(1 - (2 * x - 1) ** 2)) / 2 + 0.5
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@native_function(Op.EaseInCubic)
|
|
86
|
+
def ease_in_cubic(x: float) -> float:
|
|
87
|
+
"""Interpolate between 0 and 1, starting slow and ending fast with cubic easing."""
|
|
88
|
+
x = clamp(x, 0, 1)
|
|
89
|
+
return x**3
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@native_function(Op.EaseOutCubic)
|
|
93
|
+
def ease_out_cubic(x: float) -> float:
|
|
94
|
+
"""Interpolate between 0 and 1, starting fast and ending slow with cubic easing."""
|
|
95
|
+
x = clamp(x, 0, 1)
|
|
96
|
+
return 1 - (1 - x) ** 3
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@native_function(Op.EaseInOutCubic)
|
|
100
|
+
def ease_in_out_cubic(x: float) -> float:
|
|
101
|
+
"""Interpolate between 0 and 1, starting and ending slow with cubic easing, fast in the middle."""
|
|
102
|
+
x = clamp(x, 0, 1)
|
|
103
|
+
if x < 0.5:
|
|
104
|
+
return 4 * x**3
|
|
105
|
+
else:
|
|
106
|
+
return 1 - (-2 * x + 2) ** 3 / 2
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
@native_function(Op.EaseOutInCubic)
|
|
110
|
+
def ease_out_in_cubic(x: float) -> float:
|
|
111
|
+
"""Interpolate between 0 and 1, fast at the start and end, slow in the middle with cubic easing."""
|
|
112
|
+
x = clamp(x, 0, 1)
|
|
113
|
+
if x < 0.5:
|
|
114
|
+
return (1 - (1 - 2 * x) ** 3) / 2
|
|
115
|
+
else:
|
|
116
|
+
return ((2 * x - 1) ** 3) / 2 + 0.5
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
@native_function(Op.EaseInElastic)
|
|
120
|
+
def ease_in_elastic(x: float) -> float:
|
|
121
|
+
"""Interpolate between 0 and 1 with oscillations, starting slow and ending fast."""
|
|
122
|
+
x = clamp(x, 0, 1)
|
|
123
|
+
c4 = (2 * math.pi) / 3
|
|
124
|
+
if x in {0, 1}:
|
|
125
|
+
return x
|
|
126
|
+
else:
|
|
127
|
+
return -(2 ** (10 * x - 10)) * math.sin((x * 10 - 10.75) * c4)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@native_function(Op.EaseOutElastic)
|
|
131
|
+
def ease_out_elastic(x: float) -> float:
|
|
132
|
+
"""Interpolate between 0 and 1 with oscillations, starting fast and ending slow."""
|
|
133
|
+
x = clamp(x, 0, 1)
|
|
134
|
+
c4 = (2 * math.pi) / 3
|
|
135
|
+
if x in {0, 1}:
|
|
136
|
+
return x
|
|
137
|
+
else:
|
|
138
|
+
return 2 ** (-10 * x) * math.sin((x * 10 - 0.75) * c4) + 1
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@native_function(Op.EaseInOutElastic)
|
|
142
|
+
def ease_in_out_elastic(x: float) -> float:
|
|
143
|
+
"""Interpolate between 0 and 1 with oscillations, slow at the start and end, fast in the middle."""
|
|
144
|
+
x = clamp(x, 0, 1)
|
|
145
|
+
c5 = (2 * math.pi) / 4.5
|
|
146
|
+
if x in {0, 1}:
|
|
147
|
+
return x
|
|
148
|
+
elif x < 0.5:
|
|
149
|
+
return -(2 ** (20 * x - 10) * math.sin((20 * x - 11.125) * c5)) / 2
|
|
150
|
+
else:
|
|
151
|
+
return (2 ** (-20 * x + 10) * math.sin((20 * x - 11.125) * c5)) / 2 + 1
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@native_function(Op.EaseOutInElastic)
|
|
155
|
+
def ease_out_in_elastic(x: float) -> float:
|
|
156
|
+
"""Interpolate between 0 and 1 with oscillations, fast at the start and end, slow in the middle."""
|
|
157
|
+
x = clamp(x, 0, 1)
|
|
158
|
+
c4 = (2 * math.pi) / 3
|
|
159
|
+
if x < 0.5:
|
|
160
|
+
if x == 0:
|
|
161
|
+
return 0
|
|
162
|
+
else:
|
|
163
|
+
return (2 ** (-20 * x + 10) * math.sin((20 * x - 0.75) * c4)) / 2 + 0.5
|
|
164
|
+
elif x == 1:
|
|
165
|
+
return 1
|
|
166
|
+
else:
|
|
167
|
+
return (-(2 ** (10 * (2 * x - 1) - 10)) * math.sin((20 * x - 10.75) * c4)) / 2 + 0.5
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
@native_function(Op.EaseInExpo)
|
|
171
|
+
def ease_in_expo(x: float) -> float:
|
|
172
|
+
"""Interpolate between 0 and 1, starting extremely slow and ending extremely fast."""
|
|
173
|
+
x = clamp(x, 0, 1)
|
|
174
|
+
return 0 if x == 0 else 2 ** (10 * x - 10)
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@native_function(Op.EaseOutExpo)
|
|
178
|
+
def ease_out_expo(x: float) -> float:
|
|
179
|
+
"""Interpolate between 0 and 1, starting extremely fast and ending extremely slow."""
|
|
180
|
+
x = clamp(x, 0, 1)
|
|
181
|
+
return 1 if x == 1 else 1 - 2 ** (-10 * x)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
@native_function(Op.EaseInOutExpo)
|
|
185
|
+
def ease_in_out_expo(x: float) -> float:
|
|
186
|
+
"""Interpolate between 0 and 1, starting and ending extremely slow, fast in the middle."""
|
|
187
|
+
x = clamp(x, 0, 1)
|
|
188
|
+
if x in {0, 1}:
|
|
189
|
+
return x
|
|
190
|
+
elif x < 0.5:
|
|
191
|
+
return 2 ** (20 * x - 10) / 2
|
|
192
|
+
else:
|
|
193
|
+
return (2 - 2 ** (-20 * x + 10)) / 2
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@native_function(Op.EaseOutInExpo)
|
|
197
|
+
def ease_out_in_expo(x: float) -> float:
|
|
198
|
+
"""Interpolate between 0 and 1, extremely fast at the start and end, extremely slow in the middle."""
|
|
199
|
+
x = clamp(x, 0, 1)
|
|
200
|
+
if x in {0, 1}:
|
|
201
|
+
return x
|
|
202
|
+
elif x < 0.5:
|
|
203
|
+
return (1 - 2 ** (-20 * x)) / 2
|
|
204
|
+
else:
|
|
205
|
+
return (2 ** (20 * x - 20)) / 2 + 0.5
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
@native_function(Op.EaseInQuad)
|
|
209
|
+
def ease_in_quad(x: float) -> float:
|
|
210
|
+
"""Interpolate between 0 and 1, starting slow and ending fast with quadratic easing."""
|
|
211
|
+
x = clamp(x, 0, 1)
|
|
212
|
+
return x**2
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
@native_function(Op.EaseOutQuad)
|
|
216
|
+
def ease_out_quad(x: float) -> float:
|
|
217
|
+
"""Interpolate between 0 and 1, starting fast and ending slow with quadratic easing."""
|
|
218
|
+
x = clamp(x, 0, 1)
|
|
219
|
+
return 1 - (1 - x) ** 2
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
@native_function(Op.EaseInOutQuad)
|
|
223
|
+
def ease_in_out_quad(x: float) -> float:
|
|
224
|
+
"""Interpolate between 0 and 1, starting and ending slow with quadratic easing, fast in the middle."""
|
|
225
|
+
x = clamp(x, 0, 1)
|
|
226
|
+
if x < 0.5:
|
|
227
|
+
return 2 * x**2
|
|
228
|
+
else:
|
|
229
|
+
return 1 - (-2 * x + 2) ** 2 / 2
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
@native_function(Op.EaseOutInQuad)
|
|
233
|
+
def ease_out_in_quad(x: float) -> float:
|
|
234
|
+
"""Interpolate between 0 and 1, fast at the start and end, slow in the middle with quadratic easing."""
|
|
235
|
+
x = clamp(x, 0, 1)
|
|
236
|
+
if x < 0.5:
|
|
237
|
+
return (1 - (1 - 2 * x) ** 2) / 2
|
|
238
|
+
else:
|
|
239
|
+
return ((2 * x - 1) ** 2) / 2 + 0.5
|
|
240
|
+
|
|
241
|
+
|
|
242
|
+
@native_function(Op.EaseInQuart)
|
|
243
|
+
def ease_in_quart(x: float) -> float:
|
|
244
|
+
"""Interpolate between 0 and 1, starting very slow and ending very fast with quartic easing."""
|
|
245
|
+
x = clamp(x, 0, 1)
|
|
246
|
+
return x**4
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
@native_function(Op.EaseOutQuart)
|
|
250
|
+
def ease_out_quart(x: float) -> float:
|
|
251
|
+
"""Interpolate between 0 and 1, starting very fast and ending very slow with quartic easing."""
|
|
252
|
+
x = clamp(x, 0, 1)
|
|
253
|
+
return 1 - (1 - x) ** 4
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
@native_function(Op.EaseInOutQuart)
|
|
257
|
+
def ease_in_out_quart(x: float) -> float:
|
|
258
|
+
"""Interpolate between 0 and 1, starting and ending very slow with quartic easing, very fast in the middle."""
|
|
259
|
+
x = clamp(x, 0, 1)
|
|
260
|
+
if x < 0.5:
|
|
261
|
+
return 8 * x**4
|
|
262
|
+
else:
|
|
263
|
+
return 1 - (-2 * x + 2) ** 4 / 2
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@native_function(Op.EaseOutInQuart)
|
|
267
|
+
def ease_out_in_quart(x: float) -> float:
|
|
268
|
+
"""Interpolate between 0 and 1, very fast at the start and end, very slow in the middle with quartic easing."""
|
|
269
|
+
x = clamp(x, 0, 1)
|
|
270
|
+
if x < 0.5:
|
|
271
|
+
return (1 - (1 - 2 * x) ** 4) / 2
|
|
272
|
+
else:
|
|
273
|
+
return ((2 * x - 1) ** 4) / 2 + 0.5
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
@native_function(Op.EaseInQuint)
|
|
277
|
+
def ease_in_quint(x: float) -> float:
|
|
278
|
+
"""Interpolate between 0 and 1, starting extremely slow and ending extremely fast with quintic easing."""
|
|
279
|
+
x = clamp(x, 0, 1)
|
|
280
|
+
return x**5
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
@native_function(Op.EaseOutQuint)
|
|
284
|
+
def ease_out_quint(x: float) -> float:
|
|
285
|
+
"""Interpolate between 0 and 1, starting extremely fast and ending extremely slow with quintic easing."""
|
|
286
|
+
x = clamp(x, 0, 1)
|
|
287
|
+
return 1 - (1 - x) ** 5
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
@native_function(Op.EaseInOutQuint)
|
|
291
|
+
def ease_in_out_quint(x: float) -> float:
|
|
292
|
+
"""Interpolate between 0 and 1, starting and ending extremely slow with quintic easing, extremely fast in the middle."""
|
|
293
|
+
x = clamp(x, 0, 1)
|
|
294
|
+
if x < 0.5:
|
|
295
|
+
return 16 * x**5
|
|
296
|
+
else:
|
|
297
|
+
return 1 - (-2 * x + 2) ** 5 / 2
|
|
298
|
+
|
|
299
|
+
|
|
300
|
+
@native_function(Op.EaseOutInQuint)
|
|
301
|
+
def ease_out_in_quint(x: float) -> float:
|
|
302
|
+
"""Interpolate between 0 and 1, extremely fast at the start and end, extremely slow in the middle with quintic easing."""
|
|
303
|
+
x = clamp(x, 0, 1)
|
|
304
|
+
if x < 0.5:
|
|
305
|
+
return (1 - (1 - 2 * x) ** 5) / 2
|
|
306
|
+
else:
|
|
307
|
+
return ((2 * x - 1) ** 5) / 2 + 0.5
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
@native_function(Op.EaseInSine)
|
|
311
|
+
def ease_in_sine(x: float) -> float:
|
|
312
|
+
"""Interpolate between 0 and 1, starting slow and ending fast with sine easing."""
|
|
313
|
+
x = clamp(x, 0, 1)
|
|
314
|
+
return 1 - math.cos((x * math.pi) / 2)
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
@native_function(Op.EaseOutSine)
|
|
318
|
+
def ease_out_sine(x: float) -> float:
|
|
319
|
+
"""Interpolate between 0 and 1, starting fast and ending slow with sine easing."""
|
|
320
|
+
x = clamp(x, 0, 1)
|
|
321
|
+
return math.sin((x * math.pi) / 2)
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
@native_function(Op.EaseInOutSine)
|
|
325
|
+
def ease_in_out_sine(x: float) -> float:
|
|
326
|
+
"""Interpolate between 0 and 1, starting and ending slow with sine easing, fast in the middle."""
|
|
327
|
+
x = clamp(x, 0, 1)
|
|
328
|
+
return -(math.cos(math.pi * x) - 1) / 2
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
@native_function(Op.EaseOutInSine)
|
|
332
|
+
def ease_out_in_sine(x: float) -> float:
|
|
333
|
+
"""Interpolate between 0 and 1, fast at the start and end, slow in the middle with sine easing."""
|
|
334
|
+
x = clamp(x, 0, 1)
|
|
335
|
+
if x < 0.5:
|
|
336
|
+
return math.sin(math.pi * x) / 2
|
|
337
|
+
else:
|
|
338
|
+
return (1 - math.cos(math.pi * x)) / 2
|
|
339
|
+
|
|
340
|
+
|
|
341
|
+
def linstep(x: float) -> float:
|
|
342
|
+
"""Linear interpolation between 0 and 1."""
|
|
343
|
+
return clamp(x, 0.0, 1.0)
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def smoothstep(x: float) -> float:
|
|
347
|
+
"""Interpolate between 0 and 1 using smoothstep."""
|
|
348
|
+
x = clamp(x, 0.0, 1.0)
|
|
349
|
+
return x * x * (3 - 2 * x)
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def smootherstep(x: float) -> float:
|
|
353
|
+
"""Interpolate between 0 and 1 using smootherstep."""
|
|
354
|
+
x = clamp(x, 0.0, 1.0)
|
|
355
|
+
return x * x * x * (x * (x * 6 - 15) + 10)
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
def step_start(x: float) -> float:
|
|
359
|
+
"""Step function returning 1.0 if x > 0, otherwise 0.0."""
|
|
360
|
+
return 1.0 if x > 0 else 0.0
|
|
361
|
+
|
|
362
|
+
|
|
363
|
+
def step_end(x: float) -> float:
|
|
364
|
+
"""Step function returning 1.0 if x >= 1, otherwise 0.0."""
|
|
365
|
+
return 1.0 if x >= 1 else 0.0
|
sonolus/script/effect.py
CHANGED
|
@@ -1,131 +1,191 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
from dataclasses import dataclass
|
|
4
|
-
from typing import Annotated, Any, NewType, dataclass_transform, get_origin
|
|
5
|
-
|
|
6
|
-
from sonolus.backend.ops import Op
|
|
7
|
-
from sonolus.script.internal.introspection import get_field_specifiers
|
|
8
|
-
from sonolus.script.internal.native import native_function
|
|
9
|
-
from sonolus.script.record import Record
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class Effect(Record):
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
@
|
|
93
|
-
def
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import Annotated, Any, NewType, dataclass_transform, get_origin
|
|
5
|
+
|
|
6
|
+
from sonolus.backend.ops import Op
|
|
7
|
+
from sonolus.script.internal.introspection import get_field_specifiers
|
|
8
|
+
from sonolus.script.internal.native import native_function
|
|
9
|
+
from sonolus.script.record import Record
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Effect(Record):
|
|
13
|
+
"""Sound effect clip.
|
|
14
|
+
|
|
15
|
+
Usage:
|
|
16
|
+
```python
|
|
17
|
+
Effect(id: int)
|
|
18
|
+
```
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
id: int
|
|
22
|
+
"""Effect ID."""
|
|
23
|
+
|
|
24
|
+
def is_available(self) -> bool:
|
|
25
|
+
"""Return whether the effect clip is available."""
|
|
26
|
+
return _has_effect_clip(self.id)
|
|
27
|
+
|
|
28
|
+
def play(self, distance: float) -> None:
|
|
29
|
+
"""Play the effect clip.
|
|
30
|
+
|
|
31
|
+
If the clip was already played within the specified distance, it will be skipped.
|
|
32
|
+
|
|
33
|
+
Arguments:
|
|
34
|
+
distance: Minimum time in seconds since the last play for the effect to play.
|
|
35
|
+
"""
|
|
36
|
+
_play(self.id, distance)
|
|
37
|
+
|
|
38
|
+
def schedule(self, time: float, distance: float) -> None:
|
|
39
|
+
"""Schedule the effect clip to play at a specific time.
|
|
40
|
+
|
|
41
|
+
This is not suitable for real-time effects such as responses to user input. Use `play` instead.
|
|
42
|
+
|
|
43
|
+
This may be called in preprocess to schedule effects upfront.
|
|
44
|
+
|
|
45
|
+
If the clip would play within the specified distance of another play, it will be skipped.
|
|
46
|
+
|
|
47
|
+
Arguments:
|
|
48
|
+
time: Time in seconds when the effect should play.
|
|
49
|
+
distance: Minimum time in seconds after a previous play for the effect to play.
|
|
50
|
+
"""
|
|
51
|
+
_play_scheduled(self.id, time, distance)
|
|
52
|
+
|
|
53
|
+
def loop(self) -> LoopedEffectHandle:
|
|
54
|
+
"""Play the effect clip in a loop until stopped.
|
|
55
|
+
|
|
56
|
+
Returns:
|
|
57
|
+
A handle to stop the loop.
|
|
58
|
+
"""
|
|
59
|
+
return LoopedEffectHandle(_play_looped(self.id))
|
|
60
|
+
|
|
61
|
+
def schedule_loop(self, start_time: float) -> ScheduledLoopedEffectHandle:
|
|
62
|
+
"""Schedule the effect clip to play in a loop until stopped.
|
|
63
|
+
|
|
64
|
+
This is not suitable for real-time effects such as responses to user input. Use `loop` instead.
|
|
65
|
+
|
|
66
|
+
Returns:
|
|
67
|
+
A handle to stop the loop.
|
|
68
|
+
"""
|
|
69
|
+
return ScheduledLoopedEffectHandle(_play_looped_scheduled(self.id, start_time))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class LoopedEffectHandle(Record):
|
|
73
|
+
"""Handle to stop a looped effect."""
|
|
74
|
+
|
|
75
|
+
id: int
|
|
76
|
+
|
|
77
|
+
def stop(self) -> None:
|
|
78
|
+
"""Stop the looped effect."""
|
|
79
|
+
_stop_looped(self.id)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class ScheduledLoopedEffectHandle(Record):
|
|
83
|
+
"""Handle to stop a scheduled looped effect."""
|
|
84
|
+
|
|
85
|
+
id: int
|
|
86
|
+
|
|
87
|
+
def stop(self, end_time: float) -> None:
|
|
88
|
+
"""Stop the scheduled looped effect."""
|
|
89
|
+
_stop_looped_scheduled(self.id, end_time)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@native_function(Op.HasEffectClip)
|
|
93
|
+
def _has_effect_clip(effect_id: int) -> bool:
|
|
94
|
+
raise NotImplementedError
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@native_function(Op.Play)
|
|
98
|
+
def _play(effect_id: int, distance: float) -> None:
|
|
99
|
+
raise NotImplementedError
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
@native_function(Op.PlayLooped)
|
|
103
|
+
def _play_looped(effect_id: int) -> int:
|
|
104
|
+
raise NotImplementedError
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@native_function(Op.PlayLoopedScheduled)
|
|
108
|
+
def _play_looped_scheduled(effect_id: int, start_time: float) -> int:
|
|
109
|
+
raise NotImplementedError
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
@native_function(Op.PlayScheduled)
|
|
113
|
+
def _play_scheduled(effect_id: int, time: float, distance: float) -> None:
|
|
114
|
+
raise NotImplementedError
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@native_function(Op.StopLooped)
|
|
118
|
+
def _stop_looped(handle: int) -> None:
|
|
119
|
+
raise NotImplementedError
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
@native_function(Op.StopLoopedScheduled)
|
|
123
|
+
def _stop_looped_scheduled(handle: int, end_time: float) -> None:
|
|
124
|
+
raise NotImplementedError
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
@dataclass
|
|
128
|
+
class EffectInfo:
|
|
129
|
+
name: str
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def effect(name: str) -> Any:
|
|
133
|
+
"""Define a sound effect clip with the given name."""
|
|
134
|
+
return EffectInfo(name)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
type Effects = NewType("Effects", Any)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@dataclass_transform()
|
|
141
|
+
def effects[T](cls: type[T]) -> T | Effects:
|
|
142
|
+
"""Decorator to define effect clips.
|
|
143
|
+
|
|
144
|
+
Usage:
|
|
145
|
+
```python
|
|
146
|
+
@effects
|
|
147
|
+
class Effects:
|
|
148
|
+
miss: StandardEffect.MISS
|
|
149
|
+
other: Effect = effect("other")
|
|
150
|
+
```
|
|
151
|
+
"""
|
|
152
|
+
if len(cls.__bases__) != 1:
|
|
153
|
+
raise ValueError("Effects class must not inherit from any class (except object)")
|
|
154
|
+
instance = cls()
|
|
155
|
+
names = []
|
|
156
|
+
for i, (name, annotation) in enumerate(get_field_specifiers(cls).items()):
|
|
157
|
+
if get_origin(annotation) is not Annotated:
|
|
158
|
+
raise TypeError(f"Invalid annotation for effects: {annotation}")
|
|
159
|
+
annotation_type = annotation.__args__[0]
|
|
160
|
+
annotation_values = annotation.__metadata__
|
|
161
|
+
if annotation_type is not Effect:
|
|
162
|
+
raise TypeError(f"Invalid annotation for effects: {annotation}, expected annotation of type Effect")
|
|
163
|
+
if len(annotation_values) != 1 or not isinstance(annotation_values[0], EffectInfo):
|
|
164
|
+
raise TypeError(f"Invalid annotation for effects: {annotation}, expected a single string annotation value")
|
|
165
|
+
effect_name = annotation_values[0].name
|
|
166
|
+
names.append(effect_name)
|
|
167
|
+
setattr(instance, name, Effect(i))
|
|
168
|
+
instance._effects_ = names
|
|
169
|
+
instance._is_comptime_value_ = True
|
|
170
|
+
return instance
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class StandardEffect:
|
|
174
|
+
"""Standard sound effect clips."""
|
|
175
|
+
|
|
176
|
+
MISS = Annotated[Effect, effect("#MISS")]
|
|
177
|
+
PERFECT = Annotated[Effect, effect("#PERFECT")]
|
|
178
|
+
GREAT = Annotated[Effect, effect("#GREAT")]
|
|
179
|
+
GOOD = Annotated[Effect, effect("#GOOD")]
|
|
180
|
+
HOLD = Annotated[Effect, effect("#HOLD")]
|
|
181
|
+
MISS_ALTERNATIVE = Annotated[Effect, effect("#MISS_ALTERNATIVE")]
|
|
182
|
+
PERFECT_ALTERNATIVE = Annotated[Effect, effect("#PERFECT_ALTERNATIVE")]
|
|
183
|
+
GREAT_ALTERNATIVE = Annotated[Effect, effect("#GREAT_ALTERNATIVE")]
|
|
184
|
+
GOOD_ALTERNATIVE = Annotated[Effect, effect("#GOOD_ALTERNATIVE")]
|
|
185
|
+
HOLD_ALTERNATIVE = Annotated[Effect, effect("#HOLD_ALTERNATIVE")]
|
|
186
|
+
STAGE = Annotated[Effect, effect("#STAGE")]
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
@effects
|
|
190
|
+
class EmptyEffects:
|
|
191
|
+
pass
|