sonolus.py 0.1.4__py3-none-any.whl → 0.1.6__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.

Files changed (79) hide show
  1. sonolus/backend/finalize.py +18 -10
  2. sonolus/backend/interpret.py +7 -7
  3. sonolus/backend/ir.py +24 -0
  4. sonolus/backend/optimize/__init__.py +0 -0
  5. sonolus/backend/{allocate.py → optimize/allocate.py} +4 -3
  6. sonolus/backend/{constant_evaluation.py → optimize/constant_evaluation.py} +7 -7
  7. sonolus/backend/{coalesce.py → optimize/copy_coalesce.py} +3 -3
  8. sonolus/backend/optimize/dead_code.py +185 -0
  9. sonolus/backend/{dominance.py → optimize/dominance.py} +2 -17
  10. sonolus/backend/{flow.py → optimize/flow.py} +6 -5
  11. sonolus/backend/{inlining.py → optimize/inlining.py} +4 -17
  12. sonolus/backend/{liveness.py → optimize/liveness.py} +69 -65
  13. sonolus/backend/optimize/optimize.py +44 -0
  14. sonolus/backend/{passes.py → optimize/passes.py} +1 -1
  15. sonolus/backend/optimize/simplify.py +191 -0
  16. sonolus/backend/{ssa.py → optimize/ssa.py} +31 -18
  17. sonolus/backend/place.py +17 -25
  18. sonolus/backend/utils.py +10 -0
  19. sonolus/backend/visitor.py +360 -101
  20. sonolus/build/cli.py +14 -3
  21. sonolus/build/compile.py +8 -8
  22. sonolus/build/engine.py +10 -5
  23. sonolus/build/project.py +30 -1
  24. sonolus/script/archetype.py +429 -138
  25. sonolus/script/array.py +25 -8
  26. sonolus/script/array_like.py +297 -0
  27. sonolus/script/bucket.py +73 -11
  28. sonolus/script/containers.py +234 -51
  29. sonolus/script/debug.py +8 -8
  30. sonolus/script/easing.py +147 -105
  31. sonolus/script/effect.py +60 -0
  32. sonolus/script/engine.py +71 -4
  33. sonolus/script/globals.py +66 -32
  34. sonolus/script/instruction.py +79 -25
  35. sonolus/script/internal/builtin_impls.py +138 -27
  36. sonolus/script/internal/constant.py +139 -0
  37. sonolus/script/internal/context.py +14 -5
  38. sonolus/script/internal/dict_impl.py +65 -0
  39. sonolus/script/internal/generic.py +6 -9
  40. sonolus/script/internal/impl.py +38 -13
  41. sonolus/script/internal/introspection.py +5 -2
  42. sonolus/script/{math.py → internal/math_impls.py} +28 -28
  43. sonolus/script/internal/native.py +3 -3
  44. sonolus/script/internal/random.py +67 -0
  45. sonolus/script/internal/range.py +81 -0
  46. sonolus/script/internal/transient.py +51 -0
  47. sonolus/script/internal/tuple_impl.py +113 -0
  48. sonolus/script/interval.py +234 -16
  49. sonolus/script/iterator.py +120 -167
  50. sonolus/script/level.py +24 -0
  51. sonolus/script/num.py +79 -47
  52. sonolus/script/options.py +78 -12
  53. sonolus/script/particle.py +37 -4
  54. sonolus/script/pointer.py +4 -4
  55. sonolus/script/print.py +22 -1
  56. sonolus/script/project.py +59 -0
  57. sonolus/script/{graphics.py → quad.py} +75 -12
  58. sonolus/script/record.py +44 -13
  59. sonolus/script/runtime.py +50 -1
  60. sonolus/script/sprite.py +198 -115
  61. sonolus/script/text.py +2 -0
  62. sonolus/script/timing.py +72 -0
  63. sonolus/script/transform.py +296 -66
  64. sonolus/script/ui.py +134 -78
  65. sonolus/script/values.py +6 -13
  66. sonolus/script/vec.py +118 -3
  67. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/METADATA +1 -1
  68. sonolus_py-0.1.6.dist-info/RECORD +89 -0
  69. sonolus/backend/dead_code.py +0 -80
  70. sonolus/backend/optimize.py +0 -37
  71. sonolus/backend/simplify.py +0 -47
  72. sonolus/script/comptime.py +0 -160
  73. sonolus/script/random.py +0 -14
  74. sonolus/script/range.py +0 -58
  75. sonolus_py-0.1.4.dist-info/RECORD +0 -84
  76. /sonolus/script/{callbacks.py → internal/callbacks.py} +0 -0
  77. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/WHEEL +0 -0
  78. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/entry_points.txt +0 -0
  79. {sonolus_py-0.1.4.dist-info → sonolus_py-0.1.6.dist-info}/licenses/LICENSE +0 -0
sonolus/script/easing.py CHANGED
@@ -1,3 +1,4 @@
1
+ # ruff: noqa: E501
1
2
  import math
2
3
 
3
4
  from sonolus.backend.ops import Op
@@ -7,6 +8,7 @@ from sonolus.script.interval import clamp
7
8
 
8
9
  @native_function(Op.EaseInBack)
9
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."""
10
12
  x = clamp(x, 0, 1)
11
13
  c1 = 1.70158
12
14
  c3 = c1 + 1
@@ -15,6 +17,7 @@ def ease_in_back(x: float) -> float:
15
17
 
16
18
  @native_function(Op.EaseOutBack)
17
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."""
18
21
  x = clamp(x, 0, 1)
19
22
  c1 = 1.70158
20
23
  c3 = c1 + 1
@@ -23,6 +26,7 @@ def ease_out_back(x: float) -> float:
23
26
 
24
27
  @native_function(Op.EaseInOutBack)
25
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."""
26
30
  x = clamp(x, 0, 1)
27
31
  c1 = 1.70158
28
32
  c2 = c1 * 1.525
@@ -32,20 +36,35 @@ def ease_in_out_back(x: float) -> float:
32
36
  return ((2 * x - 2) ** 2 * ((c2 + 1) * (2 * x - 2) + c2) + 2) / 2
33
37
 
34
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
+
35
51
  @native_function(Op.EaseInCirc)
36
52
  def ease_in_circ(x: float) -> float:
53
+ """Interpolate between 0 and 1, starting slow and ending very fast."""
37
54
  x = clamp(x, 0, 1)
38
55
  return 1 - math.sqrt(1 - x**2)
39
56
 
40
57
 
41
58
  @native_function(Op.EaseOutCirc)
42
59
  def ease_out_circ(x: float) -> float:
60
+ """Interpolate between 0 and 1, starting very fast and ending slow."""
43
61
  x = clamp(x, 0, 1)
44
62
  return math.sqrt(1 - (x - 1) ** 2)
45
63
 
46
64
 
47
65
  @native_function(Op.EaseInOutCirc)
48
66
  def ease_in_out_circ(x: float) -> float:
67
+ """Interpolate between 0 and 1, starting and ending slow, very fast in the middle."""
49
68
  x = clamp(x, 0, 1)
50
69
  if x < 0.5:
51
70
  return (1 - math.sqrt(1 - (2 * x) ** 2)) / 2
@@ -53,20 +72,33 @@ def ease_in_out_circ(x: float) -> float:
53
72
  return (math.sqrt(1 - (2 * x - 2) ** 2) + 1) / 2
54
73
 
55
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
+
56
85
  @native_function(Op.EaseInCubic)
57
86
  def ease_in_cubic(x: float) -> float:
87
+ """Interpolate between 0 and 1, starting slow and ending fast with cubic easing."""
58
88
  x = clamp(x, 0, 1)
59
89
  return x**3
60
90
 
61
91
 
62
92
  @native_function(Op.EaseOutCubic)
63
93
  def ease_out_cubic(x: float) -> float:
94
+ """Interpolate between 0 and 1, starting fast and ending slow with cubic easing."""
64
95
  x = clamp(x, 0, 1)
65
96
  return 1 - (1 - x) ** 3
66
97
 
67
98
 
68
99
  @native_function(Op.EaseInOutCubic)
69
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."""
70
102
  x = clamp(x, 0, 1)
71
103
  if x < 0.5:
72
104
  return 4 * x**3
@@ -74,8 +106,19 @@ def ease_in_out_cubic(x: float) -> float:
74
106
  return 1 - (-2 * x + 2) ** 3 / 2
75
107
 
76
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
+
77
119
  @native_function(Op.EaseInElastic)
78
120
  def ease_in_elastic(x: float) -> float:
121
+ """Interpolate between 0 and 1 with oscillations, starting slow and ending fast."""
79
122
  x = clamp(x, 0, 1)
80
123
  c4 = (2 * math.pi) / 3
81
124
  if x in {0, 1}:
@@ -86,6 +129,7 @@ def ease_in_elastic(x: float) -> float:
86
129
 
87
130
  @native_function(Op.EaseOutElastic)
88
131
  def ease_out_elastic(x: float) -> float:
132
+ """Interpolate between 0 and 1 with oscillations, starting fast and ending slow."""
89
133
  x = clamp(x, 0, 1)
90
134
  c4 = (2 * math.pi) / 3
91
135
  if x in {0, 1}:
@@ -96,6 +140,7 @@ def ease_out_elastic(x: float) -> float:
96
140
 
97
141
  @native_function(Op.EaseInOutElastic)
98
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."""
99
144
  x = clamp(x, 0, 1)
100
145
  c5 = (2 * math.pi) / 4.5
101
146
  if x in {0, 1}:
@@ -106,20 +151,39 @@ def ease_in_out_elastic(x: float) -> float:
106
151
  return (2 ** (-20 * x + 10) * math.sin((20 * x - 11.125) * c5)) / 2 + 1
107
152
 
108
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
+
109
170
  @native_function(Op.EaseInExpo)
110
171
  def ease_in_expo(x: float) -> float:
172
+ """Interpolate between 0 and 1, starting extremely slow and ending extremely fast."""
111
173
  x = clamp(x, 0, 1)
112
174
  return 0 if x == 0 else 2 ** (10 * x - 10)
113
175
 
114
176
 
115
177
  @native_function(Op.EaseOutExpo)
116
178
  def ease_out_expo(x: float) -> float:
179
+ """Interpolate between 0 and 1, starting extremely fast and ending extremely slow."""
117
180
  x = clamp(x, 0, 1)
118
181
  return 1 if x == 1 else 1 - 2 ** (-10 * x)
119
182
 
120
183
 
121
184
  @native_function(Op.EaseInOutExpo)
122
185
  def ease_in_out_expo(x: float) -> float:
186
+ """Interpolate between 0 and 1, starting and ending extremely slow, fast in the middle."""
123
187
  x = clamp(x, 0, 1)
124
188
  if x in {0, 1}:
125
189
  return x
@@ -129,195 +193,173 @@ def ease_in_out_expo(x: float) -> float:
129
193
  return (2 - 2 ** (-20 * x + 10)) / 2
130
194
 
131
195
 
132
- @native_function(Op.EaseInOutQuad)
133
- def ease_in_out_quad(x: float) -> float:
134
- x = clamp(x, 0, 1)
135
- if x < 0.5:
136
- return 2 * x**2
137
- else:
138
- return 1 - (-2 * x + 2) ** 2 / 2
139
-
140
-
141
- @native_function(Op.EaseInOutQuart)
142
- def ease_in_out_quart(x: float) -> float:
143
- x = clamp(x, 0, 1)
144
- if x < 0.5:
145
- return 8 * x**4
146
- else:
147
- return 1 - (-2 * x + 2) ** 4 / 2
148
-
149
-
150
- @native_function(Op.EaseInOutQuint)
151
- def ease_in_out_quint(x: float) -> float:
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."""
152
199
  x = clamp(x, 0, 1)
153
- if x < 0.5:
154
- return 16 * x**5
200
+ if x in {0, 1}:
201
+ return x
202
+ elif x < 0.5:
203
+ return (1 - 2 ** (-20 * x)) / 2
155
204
  else:
156
- return 1 - (-2 * x + 2) ** 5 / 2
157
-
158
-
159
- @native_function(Op.EaseInOutSine)
160
- def ease_in_out_sine(x: float) -> float:
161
- x = clamp(x, 0, 1)
162
- return -(math.cos(math.pi * x) - 1) / 2
205
+ return (2 ** (20 * x - 20)) / 2 + 0.5
163
206
 
164
207
 
165
208
  @native_function(Op.EaseInQuad)
166
209
  def ease_in_quad(x: float) -> float:
210
+ """Interpolate between 0 and 1, starting slow and ending fast with quadratic easing."""
167
211
  x = clamp(x, 0, 1)
168
212
  return x**2
169
213
 
170
214
 
171
215
  @native_function(Op.EaseOutQuad)
172
216
  def ease_out_quad(x: float) -> float:
217
+ """Interpolate between 0 and 1, starting fast and ending slow with quadratic easing."""
173
218
  x = clamp(x, 0, 1)
174
219
  return 1 - (1 - x) ** 2
175
220
 
176
221
 
177
- @native_function(Op.EaseInQuart)
178
- def ease_in_quart(x: float) -> float:
179
- x = clamp(x, 0, 1)
180
- return x**4
181
-
182
-
183
- @native_function(Op.EaseOutQuart)
184
- def ease_out_quart(x: float) -> float:
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."""
185
225
  x = clamp(x, 0, 1)
186
- return 1 - (1 - x) ** 4
226
+ if x < 0.5:
227
+ return 2 * x**2
228
+ else:
229
+ return 1 - (-2 * x + 2) ** 2 / 2
187
230
 
188
231
 
189
- @native_function(Op.EaseInQuint)
190
- def ease_in_quint(x: float) -> float:
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."""
191
235
  x = clamp(x, 0, 1)
192
- return x**5
236
+ if x < 0.5:
237
+ return (1 - (1 - 2 * x) ** 2) / 2
238
+ else:
239
+ return ((2 * x - 1) ** 2) / 2 + 0.5
193
240
 
194
241
 
195
- @native_function(Op.EaseOutQuint)
196
- def ease_out_quint(x: float) -> float:
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."""
197
245
  x = clamp(x, 0, 1)
198
- return 1 - (1 - x) ** 5
246
+ return x**4
199
247
 
200
248
 
201
- @native_function(Op.EaseInSine)
202
- def ease_in_sine(x: float) -> float:
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."""
203
252
  x = clamp(x, 0, 1)
204
- return 1 - math.cos((x * math.pi) / 2)
253
+ return 1 - (1 - x) ** 4
205
254
 
206
255
 
207
- @native_function(Op.EaseOutSine)
208
- def ease_out_sine(x: float) -> float:
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."""
209
259
  x = clamp(x, 0, 1)
210
- return math.sin((x * math.pi) / 2)
260
+ if x < 0.5:
261
+ return 8 * x**4
262
+ else:
263
+ return 1 - (-2 * x + 2) ** 4 / 2
211
264
 
212
265
 
213
- @native_function(Op.EaseOutInBack)
214
- def ease_out_in_back(x: float) -> float:
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."""
215
269
  x = clamp(x, 0, 1)
216
- c1 = 1.70158
217
- c3 = c1 + 1
218
270
  if x < 0.5:
219
- return (1 + c3 * (2 * x - 1) ** 3 + c1 * (2 * x - 1) ** 2) / 2
271
+ return (1 - (1 - 2 * x) ** 4) / 2
220
272
  else:
221
- return (c3 * (2 * x - 1) ** 3 - c1 * (2 * x - 1) ** 2) / 2 + 0.5
273
+ return ((2 * x - 1) ** 4) / 2 + 0.5
222
274
 
223
275
 
224
- @native_function(Op.EaseOutInCirc)
225
- def ease_out_in_circ(x: float) -> float:
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."""
226
279
  x = clamp(x, 0, 1)
227
- if x < 0.5:
228
- return (math.sqrt(1 - (2 * x - 1) ** 2)) / 2
229
- else:
230
- return (1 - math.sqrt(1 - (2 * x - 1) ** 2)) / 2 + 0.5
280
+ return x**5
231
281
 
232
282
 
233
- @native_function(Op.EaseOutInCubic)
234
- def ease_out_in_cubic(x: float) -> float:
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."""
235
286
  x = clamp(x, 0, 1)
236
- if x < 0.5:
237
- return (1 - (1 - 2 * x) ** 3) / 2
238
- else:
239
- return ((2 * x - 1) ** 3) / 2 + 0.5
287
+ return 1 - (1 - x) ** 5
240
288
 
241
289
 
242
- @native_function(Op.EaseOutInElastic)
243
- def ease_out_in_elastic(x: float) -> float:
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."""
244
293
  x = clamp(x, 0, 1)
245
- c4 = (2 * math.pi) / 3
246
294
  if x < 0.5:
247
- if x == 0:
248
- return 0
249
- else:
250
- return (2 ** (-20 * x + 10) * math.sin((20 * x - 0.75) * c4)) / 2 + 0.5
251
- elif x == 1:
252
- return 1
295
+ return 16 * x**5
253
296
  else:
254
- return (-(2 ** (10 * (2 * x - 1) - 10)) * math.sin((20 * x - 10.75) * c4)) / 2 + 0.5
297
+ return 1 - (-2 * x + 2) ** 5 / 2
255
298
 
256
299
 
257
- @native_function(Op.EaseOutInExpo)
258
- def ease_out_in_expo(x: float) -> float:
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."""
259
303
  x = clamp(x, 0, 1)
260
- if x in {0, 1}:
261
- return x
262
- elif x < 0.5:
263
- return (1 - 2 ** (-20 * x)) / 2
304
+ if x < 0.5:
305
+ return (1 - (1 - 2 * x) ** 5) / 2
264
306
  else:
265
- return (2 ** (20 * x - 20)) / 2 + 0.5
307
+ return ((2 * x - 1) ** 5) / 2 + 0.5
266
308
 
267
309
 
268
- @native_function(Op.EaseOutInQuad)
269
- def ease_out_in_quad(x: float) -> float:
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."""
270
313
  x = clamp(x, 0, 1)
271
- if x < 0.5:
272
- return (1 - (1 - 2 * x) ** 2) / 2
273
- else:
274
- return ((2 * x - 1) ** 2) / 2 + 0.5
314
+ return 1 - math.cos((x * math.pi) / 2)
275
315
 
276
316
 
277
- @native_function(Op.EaseOutInQuart)
278
- def ease_out_in_quart(x: float) -> float:
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."""
279
320
  x = clamp(x, 0, 1)
280
- if x < 0.5:
281
- return (1 - (1 - 2 * x) ** 4) / 2
282
- else:
283
- return ((2 * x - 1) ** 4) / 2 + 0.5
321
+ return math.sin((x * math.pi) / 2)
284
322
 
285
323
 
286
- @native_function(Op.EaseOutInQuint)
287
- def ease_out_in_quint(x: float) -> float:
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."""
288
327
  x = clamp(x, 0, 1)
289
- if x < 0.5:
290
- return (1 - (1 - 2 * x) ** 5) / 2
291
- else:
292
- return ((2 * x - 1) ** 5) / 2 + 0.5
328
+ return -(math.cos(math.pi * x) - 1) / 2
293
329
 
294
330
 
295
331
  @native_function(Op.EaseOutInSine)
296
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."""
297
334
  x = clamp(x, 0, 1)
298
335
  if x < 0.5:
299
- return (math.sin(math.pi * x)) / 2
336
+ return math.sin(math.pi * x) / 2
300
337
  else:
301
338
  return (1 - math.cos(math.pi * x)) / 2
302
339
 
303
340
 
304
341
  def linstep(x: float) -> float:
342
+ """Linear interpolation between 0 and 1."""
305
343
  return clamp(x, 0.0, 1.0)
306
344
 
307
345
 
308
346
  def smoothstep(x: float) -> float:
347
+ """Interpolate between 0 and 1 using smoothstep."""
309
348
  x = clamp(x, 0.0, 1.0)
310
349
  return x * x * (3 - 2 * x)
311
350
 
312
351
 
313
352
  def smootherstep(x: float) -> float:
353
+ """Interpolate between 0 and 1 using smootherstep."""
314
354
  x = clamp(x, 0.0, 1.0)
315
355
  return x * x * x * (x * (x * 6 - 15) + 10)
316
356
 
317
357
 
318
358
  def step_start(x: float) -> float:
319
- return 1.0 if x >= 0 else 0.0
359
+ """Step function returning 1.0 if x > 0, otherwise 0.0."""
360
+ return 1.0 if x > 0 else 0.0
320
361
 
321
362
 
322
363
  def step_end(x: float) -> float:
364
+ """Step function returning 1.0 if x >= 1, otherwise 0.0."""
323
365
  return 1.0 if x >= 1 else 0.0
sonolus/script/effect.py CHANGED
@@ -10,35 +10,82 @@ from sonolus.script.record import Record
10
10
 
11
11
 
12
12
  class Effect(Record):
13
+ """Sound effect clip.
14
+
15
+ Usage:
16
+ ```python
17
+ Effect(id: int)
18
+ ```
19
+ """
20
+
13
21
  id: int
22
+ """Effect ID."""
14
23
 
15
24
  def is_available(self) -> bool:
25
+ """Return whether the effect clip is available."""
16
26
  return _has_effect_clip(self.id)
17
27
 
18
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
+ """
19
36
  _play(self.id, distance)
20
37
 
21
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
+ """
22
51
  _play_scheduled(self.id, time, distance)
23
52
 
24
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
+ """
25
59
  return LoopedEffectHandle(_play_looped(self.id))
26
60
 
27
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
+ """
28
69
  return ScheduledLoopedEffectHandle(_play_looped_scheduled(self.id, start_time))
29
70
 
30
71
 
31
72
  class LoopedEffectHandle(Record):
73
+ """Handle to stop a looped effect."""
74
+
32
75
  id: int
33
76
 
34
77
  def stop(self) -> None:
78
+ """Stop the looped effect."""
35
79
  _stop_looped(self.id)
36
80
 
37
81
 
38
82
  class ScheduledLoopedEffectHandle(Record):
83
+ """Handle to stop a scheduled looped effect."""
84
+
39
85
  id: int
40
86
 
41
87
  def stop(self, end_time: float) -> None:
88
+ """Stop the scheduled looped effect."""
42
89
  _stop_looped_scheduled(self.id, end_time)
43
90
 
44
91
 
@@ -83,6 +130,7 @@ class EffectInfo:
83
130
 
84
131
 
85
132
  def effect(name: str) -> Any:
133
+ """Define a sound effect clip with the given name."""
86
134
  return EffectInfo(name)
87
135
 
88
136
 
@@ -91,6 +139,16 @@ type Effects = NewType("Effects", Any)
91
139
 
92
140
  @dataclass_transform()
93
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
+ """
94
152
  if len(cls.__bases__) != 1:
95
153
  raise ValueError("Effects class must not inherit from any class (except object)")
96
154
  instance = cls()
@@ -113,6 +171,8 @@ def effects[T](cls: type[T]) -> T | Effects:
113
171
 
114
172
 
115
173
  class StandardEffect:
174
+ """Standard sound effect clips."""
175
+
116
176
  MISS = Annotated[Effect, effect("#MISS")]
117
177
  PERFECT = Annotated[Effect, effect("#PERFECT")]
118
178
  GREAT = Annotated[Effect, effect("#GREAT")]
sonolus/script/engine.py CHANGED
@@ -4,7 +4,7 @@ from collections.abc import Callable
4
4
  from typing import Any
5
5
 
6
6
  from sonolus.build.collection import Asset
7
- from sonolus.script.archetype import BaseArchetype, PlayArchetype, PreviewArchetype, WatchArchetype
7
+ from sonolus.script.archetype import PlayArchetype, PreviewArchetype, WatchArchetype, _BaseArchetype
8
8
  from sonolus.script.bucket import Buckets, EmptyBuckets
9
9
  from sonolus.script.effect import Effects, EmptyEffects
10
10
  from sonolus.script.instruction import (
@@ -20,6 +20,21 @@ from sonolus.script.ui import UiConfig
20
20
 
21
21
 
22
22
  class Engine:
23
+ """A Sonolus.py engine.
24
+
25
+ Args:
26
+ name: The name of the engine.
27
+ title: The title of the engine.
28
+ subtitle: The subtitle of the engine.
29
+ author: The author of the engine.
30
+ skin: The default skin for the engine.
31
+ background: The default background for the engine.
32
+ effect: The default effect for the engine.
33
+ particle: The default particle for the engine.
34
+ thumbnail: The thumbnail for the engine.
35
+ data: The engine's modes and configurations.
36
+ """
37
+
23
38
  version = 12
24
39
 
25
40
  def __init__(
@@ -53,10 +68,20 @@ def default_callback() -> Any:
53
68
 
54
69
 
55
70
  class PlayMode:
71
+ """A play mode definition.
72
+
73
+ Args:
74
+ archetypes: A list of play archetypes.
75
+ skin: The skin for the play mode.
76
+ effects: The effects for the play mode.
77
+ particles: The particles for the play mode.
78
+ buckets: The buckets for the play mode.
79
+ """
80
+
56
81
  def __init__(
57
82
  self,
58
83
  *,
59
- archetypes: list[type[BaseArchetype]] | None = None,
84
+ archetypes: list[type[_BaseArchetype]] | None = None,
60
85
  skin: Skin = EmptySkin,
61
86
  effects: Effects = EmptyEffects,
62
87
  particles: Particles = EmptyParticles,
@@ -74,10 +99,21 @@ class PlayMode:
74
99
 
75
100
 
76
101
  class WatchMode:
102
+ """A watch mode definition.
103
+
104
+ Args:
105
+ archetypes: A list of watch archetypes.
106
+ skin: The skin for the watch mode.
107
+ effects: The effects for the watch mode.
108
+ particles: The particles for the watch mode.
109
+ buckets: The buckets for the watch mode.
110
+ update_spawn: A callback returning the spawn time used by archetypes.
111
+ """
112
+
77
113
  def __init__(
78
114
  self,
79
115
  *,
80
- archetypes: list[type[BaseArchetype]] | None = None,
116
+ archetypes: list[type[_BaseArchetype]] | None = None,
81
117
  skin: Skin = EmptySkin,
82
118
  effects: Effects = EmptyEffects,
83
119
  particles: Particles = EmptyParticles,
@@ -97,10 +133,17 @@ class WatchMode:
97
133
 
98
134
 
99
135
  class PreviewMode:
136
+ """A preview mode definition.
137
+
138
+ Args:
139
+ archetypes: A list of preview archetypes.
140
+ skin: The skin for the preview mode.
141
+ """
142
+
100
143
  def __init__(
101
144
  self,
102
145
  *,
103
- archetypes: list[type[BaseArchetype]] | None = None,
146
+ archetypes: list[type[_BaseArchetype]] | None = None,
104
147
  skin: Skin = EmptySkin,
105
148
  ) -> None:
106
149
  self.archetypes = archetypes or []
@@ -112,6 +155,19 @@ class PreviewMode:
112
155
 
113
156
 
114
157
  class TutorialMode:
158
+ """A tutorial mode definition.
159
+
160
+ Args:
161
+ skin: The skin for the tutorial mode.
162
+ effects: The effects for the tutorial mode.
163
+ particles: The particles for the tutorial mode.
164
+ instructions: The instructions for the tutorial mode.
165
+ instruction_icons: The instruction icons for the tutorial mode.
166
+ preprocess: A callback to be called before the tutorial starts.
167
+ navigate: A callback to be called when the user navigates.
168
+ update: A callback to be called each frame.
169
+ """
170
+
115
171
  def __init__(
116
172
  self,
117
173
  *,
@@ -135,6 +191,17 @@ class TutorialMode:
135
191
 
136
192
 
137
193
  class EngineData:
194
+ """A Sonolus.py engine's modes and configurations.
195
+
196
+ Args:
197
+ ui: The UI configuration.
198
+ options: The options for the engine.
199
+ play: The play mode configuration.
200
+ watch: The watch mode configuration.
201
+ preview: The preview mode configuration.
202
+ tutorial: The tutorial mode configuration.
203
+ """
204
+
138
205
  def __init__(
139
206
  self,
140
207
  *,