sonolus.py 0.1.1__py3-none-any.whl → 0.1.3__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/script/globals.py CHANGED
@@ -86,42 +86,47 @@ def create_global(cls: type, blocks: dict[Mode, Block], offset: int | None):
86
86
 
87
87
  @dataclass_transform()
88
88
  def _play_runtime_environment[T](cls: type[T]) -> T:
89
- return create_global(cls, {Mode.Play: PlayBlock.RuntimeEnvironment}, 0)
89
+ return create_global(cls, {Mode.PLAY: PlayBlock.RuntimeEnvironment}, 0)
90
90
 
91
91
 
92
92
  @dataclass_transform()
93
93
  def _watch_runtime_environment[T](cls: type[T]) -> T:
94
- return create_global(cls, {Mode.Watch: WatchBlock.RuntimeEnvironment}, 0)
94
+ return create_global(cls, {Mode.WATCH: WatchBlock.RuntimeEnvironment}, 0)
95
95
 
96
96
 
97
97
  @dataclass_transform()
98
98
  def _tutorial_runtime_environment[T](cls: type[T]) -> T:
99
- return create_global(cls, {Mode.Tutorial: TutorialBlock.RuntimeEnvironment}, 0)
99
+ return create_global(cls, {Mode.TUTORIAL: TutorialBlock.RuntimeEnvironment}, 0)
100
100
 
101
101
 
102
102
  @dataclass_transform()
103
103
  def _preview_runtime_environment[T](cls: type[T]) -> T:
104
- return create_global(cls, {Mode.Preview: PreviewBlock.RuntimeEnvironment}, 0)
104
+ return create_global(cls, {Mode.PREVIEW: PreviewBlock.RuntimeEnvironment}, 0)
105
105
 
106
106
 
107
107
  @dataclass_transform()
108
108
  def _play_runtime_update[T](cls: type[T]) -> T:
109
- return create_global(cls, {Mode.Play: PlayBlock.RuntimeUpdate}, 0)
109
+ return create_global(cls, {Mode.PLAY: PlayBlock.RuntimeUpdate}, 0)
110
110
 
111
111
 
112
112
  @dataclass_transform()
113
113
  def _watch_runtime_update[T](cls: type[T]) -> T:
114
- return create_global(cls, {Mode.Watch: WatchBlock.RuntimeUpdate}, 0)
114
+ return create_global(cls, {Mode.WATCH: WatchBlock.RuntimeUpdate}, 0)
115
+
116
+
117
+ @dataclass_transform()
118
+ def _preview_runtime_canvas[T](cls: type[T]) -> T:
119
+ return create_global(cls, {Mode.PREVIEW: PreviewBlock.RuntimeCanvas}, 0)
115
120
 
116
121
 
117
122
  @dataclass_transform()
118
123
  def _tutorial_runtime_update[T](cls: type[T]) -> T:
119
- return create_global(cls, {Mode.Tutorial: TutorialBlock.RuntimeUpdate}, 0)
124
+ return create_global(cls, {Mode.TUTORIAL: TutorialBlock.RuntimeUpdate}, 0)
120
125
 
121
126
 
122
127
  @dataclass_transform()
123
128
  def _runtime_touch_array[T](cls: type[T]) -> T:
124
- return create_global(cls, {Mode.Play: PlayBlock.RuntimeTouchArray}, 0)
129
+ return create_global(cls, {Mode.PLAY: PlayBlock.RuntimeTouchArray}, 0)
125
130
 
126
131
 
127
132
  @dataclass_transform()
@@ -129,9 +134,10 @@ def _runtime_skin_transform[T](cls: type[T]) -> T:
129
134
  return create_global(
130
135
  cls,
131
136
  {
132
- Mode.Play: PlayBlock.RuntimeSkinTransform,
133
- Mode.Watch: WatchBlock.RuntimeSkinTransform,
134
- Mode.Tutorial: TutorialBlock.RuntimeSkinTransform,
137
+ Mode.PLAY: PlayBlock.RuntimeSkinTransform,
138
+ Mode.WATCH: WatchBlock.RuntimeSkinTransform,
139
+ Mode.PREVIEW: PreviewBlock.RuntimeSkinTransform,
140
+ Mode.TUTORIAL: TutorialBlock.RuntimeSkinTransform,
135
141
  },
136
142
  0,
137
143
  )
@@ -142,9 +148,9 @@ def _runtime_particle_transform[T](cls: type[T]) -> T:
142
148
  return create_global(
143
149
  cls,
144
150
  {
145
- Mode.Play: PlayBlock.RuntimeParticleTransform,
146
- Mode.Watch: WatchBlock.RuntimeParticleTransform,
147
- Mode.Tutorial: TutorialBlock.RuntimeParticleTransform,
151
+ Mode.PLAY: PlayBlock.RuntimeParticleTransform,
152
+ Mode.WATCH: WatchBlock.RuntimeParticleTransform,
153
+ Mode.TUTORIAL: TutorialBlock.RuntimeParticleTransform,
148
154
  },
149
155
  0,
150
156
  )
@@ -155,55 +161,84 @@ def _runtime_background[T](cls: type[T]) -> T:
155
161
  return create_global(
156
162
  cls,
157
163
  {
158
- Mode.Play: PlayBlock.RuntimeBackground,
159
- Mode.Watch: WatchBlock.RuntimeBackground,
160
- Mode.Tutorial: TutorialBlock.RuntimeBackground,
164
+ Mode.PLAY: PlayBlock.RuntimeBackground,
165
+ Mode.WATCH: WatchBlock.RuntimeBackground,
166
+ Mode.TUTORIAL: TutorialBlock.RuntimeBackground,
161
167
  },
162
168
  0,
163
169
  )
164
170
 
165
171
 
166
172
  @dataclass_transform()
167
- def _runtime_ui[T](cls: type[T]) -> T:
168
- return create_global(
169
- cls,
170
- {
171
- Mode.Play: PlayBlock.RuntimeUI,
172
- Mode.Watch: WatchBlock.RuntimeUI,
173
- Mode.Tutorial: TutorialBlock.RuntimeUI,
174
- Mode.Preview: PreviewBlock.RuntimeUI,
175
- },
176
- 0,
177
- )
173
+ def _play_runtime_ui[T](cls: type[T]) -> T:
174
+ return create_global(cls, {Mode.PLAY: PlayBlock.RuntimeUI}, 0)
178
175
 
179
176
 
180
177
  @dataclass_transform()
181
- def _runtime_ui_configuration[T](cls: type[T]) -> T:
182
- return create_global(
183
- cls,
184
- {
185
- Mode.Play: PlayBlock.RuntimeUIConfiguration,
186
- Mode.Watch: WatchBlock.RuntimeUIConfiguration,
187
- Mode.Tutorial: TutorialBlock.RuntimeUIConfiguration,
188
- Mode.Preview: PreviewBlock.RuntimeUIConfiguration,
189
- },
190
- 0,
191
- )
178
+ def _watch_runtime_ui[T](cls: type[T]) -> T:
179
+ return create_global(cls, {Mode.WATCH: WatchBlock.RuntimeUI}, 0)
180
+
181
+
182
+ @dataclass_transform()
183
+ def _tutorial_runtime_ui[T](cls: type[T]) -> T:
184
+ return create_global(cls, {Mode.TUTORIAL: TutorialBlock.RuntimeUI}, 0)
185
+
186
+
187
+ @dataclass_transform()
188
+ def _preview_runtime_ui[T](cls: type[T]) -> T:
189
+ return create_global(cls, {Mode.PREVIEW: PreviewBlock.RuntimeUI}, 0)
190
+
191
+
192
+ @dataclass_transform()
193
+ def _play_runtime_ui_configuration[T](cls: type[T]) -> T:
194
+ return create_global(cls, {Mode.PLAY: PlayBlock.RuntimeUIConfiguration}, 0)
195
+
196
+
197
+ @dataclass_transform()
198
+ def _watch_runtime_ui_configuration[T](cls: type[T]) -> T:
199
+ return create_global(cls, {Mode.WATCH: WatchBlock.RuntimeUIConfiguration}, 0)
200
+
201
+
202
+ @dataclass_transform()
203
+ def _tutorial_runtime_ui_configuration[T](cls: type[T]) -> T:
204
+ return create_global(cls, {Mode.TUTORIAL: TutorialBlock.RuntimeUIConfiguration}, 0)
205
+
206
+
207
+ @dataclass_transform()
208
+ def _preview_runtime_ui_configuration[T](cls: type[T]) -> T:
209
+ return create_global(cls, {Mode.PREVIEW: PreviewBlock.RuntimeUIConfiguration}, 0)
192
210
 
193
211
 
194
212
  @dataclass_transform()
195
213
  def _tutorial_instruction[T](cls: type[T]) -> T:
196
- return create_global(cls, {Mode.Tutorial: TutorialBlock.TutorialInstruction}, 0)
214
+ return create_global(cls, {Mode.TUTORIAL: TutorialBlock.TutorialInstruction}, 0)
197
215
 
198
216
 
199
217
  @dataclass_transform()
200
218
  def level_memory[T](cls: type[T]) -> T:
201
- return create_global(cls, {Mode.Play: PlayBlock.LevelMemory, Mode.Watch: WatchBlock.LevelMemory}, None)
219
+ return create_global(
220
+ cls,
221
+ {
222
+ Mode.PLAY: PlayBlock.LevelMemory,
223
+ Mode.WATCH: WatchBlock.LevelMemory,
224
+ Mode.TUTORIAL: TutorialBlock.TutorialMemory,
225
+ },
226
+ None,
227
+ )
202
228
 
203
229
 
204
230
  @dataclass_transform()
205
231
  def level_data[T](cls: type[T]) -> T:
206
- return create_global(cls, {Mode.Play: PlayBlock.LevelData, Mode.Watch: WatchBlock.LevelData}, None)
232
+ return create_global(
233
+ cls,
234
+ {
235
+ Mode.PLAY: PlayBlock.LevelData,
236
+ Mode.WATCH: WatchBlock.LevelData,
237
+ Mode.PREVIEW: PreviewBlock.PreviewData,
238
+ Mode.TUTORIAL: TutorialBlock.TutorialData,
239
+ },
240
+ None,
241
+ )
207
242
 
208
243
 
209
244
  # level_option is handled by the options decorator
@@ -212,12 +247,12 @@ def level_data[T](cls: type[T]) -> T:
212
247
 
213
248
  @dataclass_transform()
214
249
  def _level_score[T](cls: type[T]) -> T:
215
- return create_global(cls, {Mode.Play: PlayBlock.LevelScore, Mode.Watch: WatchBlock.LevelScore}, 0)
250
+ return create_global(cls, {Mode.PLAY: PlayBlock.LevelScore, Mode.WATCH: WatchBlock.LevelScore}, 0)
216
251
 
217
252
 
218
253
  @dataclass_transform()
219
254
  def _level_life[T](cls: type[T]) -> T:
220
- return create_global(cls, {Mode.Play: PlayBlock.LevelLife, Mode.Watch: WatchBlock.LevelLife}, 0)
255
+ return create_global(cls, {Mode.PLAY: PlayBlock.LevelLife, Mode.WATCH: WatchBlock.LevelLife}, 0)
221
256
 
222
257
 
223
258
  # engine_rom is handled by the compiler
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
- from typing import Protocol
3
+ from typing import Protocol, Self
4
4
 
5
5
  from sonolus.script.record import Record
6
6
  from sonolus.script.vec import Vec2
@@ -20,21 +20,12 @@ class Rect(Record):
20
20
  l: float # noqa: E741
21
21
 
22
22
  @classmethod
23
- def from_xywh(cls, x: float, y: float, w: float, h: float) -> Rect:
23
+ def from_center(cls, center: Vec2, dimensions: Vec2) -> Rect:
24
24
  return cls(
25
- t=y + h,
26
- r=x + w,
27
- b=y,
28
- l=x,
29
- )
30
-
31
- @classmethod
32
- def from_center(cls, x: float, y: float, w: float, h: float) -> Rect:
33
- return cls(
34
- t=y + h / 2,
35
- r=x + w / 2,
36
- b=y - h / 2,
37
- l=x - w / 2,
25
+ t=center.y + dimensions.y / 2,
26
+ r=center.x + dimensions.x / 2,
27
+ b=center.y - dimensions.y / 2,
28
+ l=center.x - dimensions.x / 2,
38
29
  )
39
30
 
40
31
  @property
@@ -99,19 +90,37 @@ class Rect(Record):
99
90
  br=self.br,
100
91
  )
101
92
 
102
- def scale(self, x: float, y: float | None, /):
103
- if y is None:
104
- y = x
105
- self.l *= x
106
- self.r *= x
107
- self.t *= y
108
- self.b *= y
109
-
110
- def translate(self, x: float, y: float, /):
111
- self.l += x
112
- self.r += x
113
- self.t += y
114
- self.b += y
93
+ def scale(self, factor: Vec2, /) -> Self:
94
+ return Rect(
95
+ t=self.t * factor.y,
96
+ r=self.r * factor.x,
97
+ b=self.b * factor.y,
98
+ l=self.l * factor.x,
99
+ )
100
+
101
+ def expand(self, expansion: Vec2, /) -> Self:
102
+ return Rect(
103
+ t=self.t + expansion.y,
104
+ r=self.r + expansion.x,
105
+ b=self.b - expansion.y,
106
+ l=self.l - expansion.x,
107
+ )
108
+
109
+ def shrink(self, shrinkage: Vec2, /) -> Self:
110
+ return Rect(
111
+ t=self.t - shrinkage.y,
112
+ r=self.r - shrinkage.x,
113
+ b=self.b + shrinkage.y,
114
+ l=self.l + shrinkage.x,
115
+ )
116
+
117
+ def translate(self, translation: Vec2, /) -> Self:
118
+ return Rect(
119
+ t=self.t + translation.y,
120
+ r=self.r + translation.x,
121
+ b=self.b + translation.y,
122
+ l=self.l + translation.x,
123
+ )
115
124
 
116
125
 
117
126
  class QuadLike(Protocol):
@@ -0,0 +1,151 @@
1
+ from dataclasses import dataclass
2
+ from typing import Annotated, Any, NewType, dataclass_transform, get_origin
3
+
4
+ from sonolus.backend.ops import Op
5
+ from sonolus.script.internal.introspection import get_field_specifiers
6
+ from sonolus.script.internal.native import native_function
7
+ from sonolus.script.record import Record
8
+ from sonolus.script.runtime import _TutorialInstruction
9
+ from sonolus.script.text import StandardText
10
+ from sonolus.script.vec import Vec2
11
+
12
+
13
+ class InstructionText(Record):
14
+ id: int
15
+
16
+ def show(self):
17
+ show_instruction(self)
18
+
19
+
20
+ class InstructionIcon(Record):
21
+ id: int
22
+
23
+ def paint(self, position: Vec2, size: float, rotation: float, z: float, a: float):
24
+ _paint(self.id, position.x, position.y, size, rotation, z, a)
25
+
26
+
27
+ @dataclass
28
+ class InstructionTextInfo:
29
+ name: str
30
+
31
+
32
+ @dataclass
33
+ class InstructionIconInfo:
34
+ name: str
35
+
36
+
37
+ def instruction(name: str) -> Any:
38
+ return InstructionTextInfo(name=name)
39
+
40
+
41
+ def instruction_icon(name: str) -> Any:
42
+ return InstructionIconInfo(name=name)
43
+
44
+
45
+ type TutorialInstructions = NewType("TutorialInstructions", Any)
46
+ type TutorialInstructionIcons = NewType("TutorialInstructionIcons", Any)
47
+
48
+
49
+ @dataclass_transform()
50
+ def instructions[T](cls: type[T]) -> T | TutorialInstructions:
51
+ if len(cls.__bases__) != 1:
52
+ raise ValueError("Instructions class must not inherit from any class (except object)")
53
+ instance = cls()
54
+ names = []
55
+ for i, (name, annotation) in enumerate(get_field_specifiers(cls).items()):
56
+ if get_origin(annotation) is not Annotated:
57
+ raise TypeError(f"Invalid annotation for instruction: {annotation}")
58
+ annotation_type = annotation.__args__[0]
59
+ annotation_values = annotation.__metadata__
60
+ if annotation_type is not InstructionText:
61
+ raise TypeError(
62
+ f"Invalid annotation for instruction: {annotation}, expected annotation of type InstructionText"
63
+ )
64
+ if len(annotation_values) != 1 or not isinstance(annotation_values[0], InstructionTextInfo):
65
+ raise TypeError(f"Invalid annotation for instruction: {annotation}, expected a single annotation value")
66
+ instruction_name = annotation_values[0].name
67
+ names.append(instruction_name)
68
+ setattr(instance, name, InstructionText(i))
69
+ instance._instructions_ = names
70
+ instance._is_comptime_value_ = True
71
+ return instance
72
+
73
+
74
+ @dataclass_transform()
75
+ def instruction_icons[T](cls: type[T]) -> T | TutorialInstructionIcons:
76
+ if len(cls.__bases__) != 1:
77
+ raise ValueError("Instruction icons class must not inherit from any class (except object)")
78
+ instance = cls()
79
+ names = []
80
+ for i, (name, annotation) in enumerate(get_field_specifiers(cls).items()):
81
+ if get_origin(annotation) is not Annotated:
82
+ raise TypeError(f"Invalid annotation for instruction icon: {annotation}")
83
+ annotation_type = annotation.__args__[0]
84
+ annotation_values = annotation.__metadata__
85
+ if annotation_type is not InstructionIcon:
86
+ raise TypeError(
87
+ f"Invalid annotation for instruction icon: {annotation}, expected annotation of type InstructionIcon"
88
+ )
89
+ if len(annotation_values) != 1 or not isinstance(annotation_values[0], InstructionIconInfo):
90
+ raise TypeError(
91
+ f"Invalid annotation for instruction icon: {annotation}, expected a single annotation value"
92
+ )
93
+ icon_name = annotation_values[0].name
94
+ names.append(icon_name)
95
+ setattr(instance, name, InstructionIcon(i))
96
+ instance._instruction_icons_ = names
97
+ instance._is_comptime_value_ = True
98
+ return instance
99
+
100
+
101
+ class StandardInstruction:
102
+ TAP = Annotated[InstructionText, instruction(StandardText.TAP)]
103
+ TAP_HOLD = Annotated[InstructionText, instruction(StandardText.TAP_HOLD)]
104
+ TAP_RELEASE = Annotated[InstructionText, instruction(StandardText.TAP_RELEASE)]
105
+ TAP_FLICK = Annotated[InstructionText, instruction(StandardText.TAP_FLICK)]
106
+ TAP_SLIDE = Annotated[InstructionText, instruction(StandardText.TAP_SLIDE)]
107
+ HOLD = Annotated[InstructionText, instruction(StandardText.HOLD)]
108
+ HOLD_SLIDE = Annotated[InstructionText, instruction(StandardText.HOLD_SLIDE)]
109
+ HOLD_FOLLOW = Annotated[InstructionText, instruction(StandardText.HOLD_FOLLOW)]
110
+ RELEASE = Annotated[InstructionText, instruction(StandardText.RELEASE)]
111
+ FLICK = Annotated[InstructionText, instruction(StandardText.FLICK)]
112
+ SLIDE = Annotated[InstructionText, instruction(StandardText.SLIDE)]
113
+ SLIDE_FLICK = Annotated[InstructionText, instruction(StandardText.SLIDE_FLICK)]
114
+ AVOID = Annotated[InstructionText, instruction(StandardText.AVOID)]
115
+ JIGGLE = Annotated[InstructionText, instruction(StandardText.JIGGLE)]
116
+
117
+
118
+ class StandardInstructionIcon:
119
+ HAND = Annotated[InstructionIcon, instruction_icon("#HAND")]
120
+ ARROW = Annotated[InstructionIcon, instruction_icon("#ARROW")]
121
+
122
+
123
+ @instructions
124
+ class EmptyInstructions:
125
+ pass
126
+
127
+
128
+ @instruction_icons
129
+ class EmptyInstructionIcons:
130
+ pass
131
+
132
+
133
+ @native_function(Op.Paint)
134
+ def _paint(
135
+ icon_id: int,
136
+ x: float,
137
+ y: float,
138
+ size: float,
139
+ rotation: float,
140
+ z: float,
141
+ a: float,
142
+ ):
143
+ raise NotImplementedError()
144
+
145
+
146
+ def show_instruction(inst: InstructionText, /):
147
+ _TutorialInstruction.text_id = inst.id
148
+
149
+
150
+ def clear_instruction():
151
+ _TutorialInstruction.text_id = -1
@@ -5,7 +5,7 @@ from sonolus.script.internal.context import ctx
5
5
  from sonolus.script.internal.impl import meta_fn, validate_value
6
6
  from sonolus.script.iterator import ArrayLike, Enumerator, SonolusIterator
7
7
  from sonolus.script.math import MATH_BUILTIN_IMPLS
8
- from sonolus.script.num import Num
8
+ from sonolus.script.num import is_num
9
9
  from sonolus.script.range import Range
10
10
 
11
11
 
@@ -74,7 +74,7 @@ def _max(*args):
74
74
  else:
75
75
  raise TypeError(f"Unsupported type: {type(iterable)} for max")
76
76
  else:
77
- if not all(isinstance(arg, Num) for arg in args):
77
+ if not all(is_num(arg) for arg in args):
78
78
  raise TypeError("Arguments to max must be numbers")
79
79
  if ctx():
80
80
  result = compile_and_call(_max2, args[0], args[1])
@@ -114,7 +114,7 @@ def _min(*args):
114
114
  else:
115
115
  raise TypeError(f"Unsupported type: {type(iterable)} for min")
116
116
  else:
117
- if not all(isinstance(arg, Num) for arg in args):
117
+ if not all(is_num(arg) for arg in args):
118
118
  raise TypeError("Arguments to min must be numbers")
119
119
  if ctx():
120
120
  result = compile_and_call(_min2, args[0], args[1])
@@ -6,14 +6,14 @@ from sonolus.backend.ir import IRInstr, IRPureInstr, IRSet
6
6
  from sonolus.backend.ops import Op
7
7
  from sonolus.script.internal.context import ctx
8
8
  from sonolus.script.internal.impl import meta_fn, validate_value
9
- from sonolus.script.num import Num
9
+ from sonolus.script.num import Num, is_num
10
10
 
11
11
 
12
12
  def native_call(op: Op, *args: Num) -> Num:
13
13
  if not ctx():
14
14
  raise RuntimeError("Unexpected native call")
15
15
  args = tuple(validate_value(arg) for arg in args)
16
- if not all(isinstance(arg, Num) for arg in args):
16
+ if not all(is_num(arg) for arg in args):
17
17
  raise RuntimeError("All arguments must be of type Num")
18
18
  result = ctx().alloc(size=1)
19
19
  ctx().add_statements(IRSet(result, (IRPureInstr if op.pure else IRInstr)(op, [arg.ir() for arg in args])))
@@ -55,6 +55,12 @@ class Interval(Record):
55
55
  def __and__(self, other: Self) -> Self:
56
56
  return Interval(max(self.start, other.start), min(self.end, other.end))
57
57
 
58
+ def shrink(self, value: float | int) -> Self:
59
+ return Interval(self.start + value, self.end - value)
60
+
61
+ def expand(self, value: float | int) -> Self:
62
+ return Interval(self.start - value, self.end + value)
63
+
58
64
  def lerp(self, x: float, /) -> float:
59
65
  return lerp(self.start, self.end, x)
60
66
 
@@ -67,6 +73,9 @@ class Interval(Record):
67
73
  def unlerp_clamped(self, x: float, /) -> float:
68
74
  return unlerp_clamped(self.start, self.end, x)
69
75
 
76
+ def clamp(self, x: float, /) -> float:
77
+ return clamp(x, self.start, self.end)
78
+
70
79
 
71
80
  @native_function(Op.Lerp)
72
81
  def lerp(a, b, x, /):
@@ -96,3 +105,8 @@ def remap(a, b, c, d, x, /):
96
105
  @native_function(Op.RemapClamped)
97
106
  def remap_clamped(a, b, c, d, x, /):
98
107
  return c + (d - c) * max(0, min(1, (x - a) / (b - a)))
108
+
109
+
110
+ @native_function(Op.Clamp)
111
+ def clamp(x, a, b, /):
112
+ return max(a, min(b, x))
@@ -180,6 +180,9 @@ class ArrayReverser[V: ArrayLike](Record, ArrayLike):
180
180
  def __setitem__(self, index: Num, value: V):
181
181
  self.array[self.size() - 1 - index] = value
182
182
 
183
+ def reversed(self) -> ArrayLike[V]:
184
+ return self.array
185
+
183
186
 
184
187
  class Enumerator[V: SonolusIterator](Record, SonolusIterator):
185
188
  i: int
sonolus/script/level.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  from sonolus.build.collection import Asset
4
- from sonolus.script.archetype import PlayArchetype, StandardImport
4
+ from sonolus.script.archetype import PlayArchetype, StandardArchetypeName, StandardImport
5
5
 
6
6
 
7
7
  class Level:
@@ -39,14 +39,14 @@ class LevelData:
39
39
 
40
40
 
41
41
  class BpmChange(PlayArchetype):
42
- name = "#BPM_CHANGE"
42
+ name = StandardArchetypeName.BPM_CHANGE
43
43
 
44
- beat: StandardImport.Beat
45
- bpm: StandardImport.Bpm
44
+ beat: StandardImport.BEAT
45
+ bpm: StandardImport.BPM
46
46
 
47
47
 
48
48
  class TimescaleChange(PlayArchetype):
49
- name = "#TIMESCALE_CHANGE"
49
+ name = StandardArchetypeName.TIMESCALE_CHANGE
50
50
 
51
- beat: StandardImport.Beat
52
- timescale: StandardImport.Timescale
51
+ beat: StandardImport.BEAT
52
+ timescale: StandardImport.TIMESCALE
sonolus/script/num.py CHANGED
@@ -1,7 +1,9 @@
1
1
  # ruff: noqa: N801
2
+ from __future__ import annotations
3
+
2
4
  import operator
3
5
  from collections.abc import Callable, Iterable
4
- from typing import TYPE_CHECKING, Any, Self, final
6
+ from typing import TYPE_CHECKING, Any, Self, TypeIs, final
5
7
 
6
8
  from sonolus.backend.ir import IRConst, IRGet, IRPureInstr, IRSet
7
9
  from sonolus.backend.ops import Op
@@ -12,14 +14,30 @@ from sonolus.script.internal.impl import meta_fn
12
14
  from sonolus.script.internal.value import Value
13
15
 
14
16
 
17
+ class NumMeta(type):
18
+ def __instancecheck__(cls, instance):
19
+ return isinstance(instance, float | int | bool) or is_num(instance)
20
+
21
+
22
+ def is_num(value: Any) -> TypeIs[Num]:
23
+ """Check if a value is a precisely Num instance."""
24
+ return type.__instancecheck__(Num, value) # type: ignore # noqa: PLC2801
25
+
26
+
15
27
  @final
16
- class _Num(Value):
28
+ class _Num(Value, metaclass=NumMeta):
29
+ # This works for ints, floats, and bools
30
+ # Since we don't support complex numbers, real is equal to the original number
31
+ __match_args__ = ("real",)
32
+
17
33
  data: BlockPlace | float | int | bool
18
34
 
19
35
  def __init__(self, data: Place | float | int | bool):
36
+ if isinstance(data, complex):
37
+ raise TypeError("Cannot create a Num from a complex number")
20
38
  if isinstance(data, int):
21
39
  data = float(data)
22
- if isinstance(data, _Num):
40
+ if is_num(data):
23
41
  raise InternalError("Cannot create a Num from a Num")
24
42
  self.data = data
25
43
 
@@ -55,7 +73,7 @@ class _Num(Value):
55
73
  def _accept_(cls, value: Any) -> Self:
56
74
  if not cls._accepts_(value):
57
75
  raise TypeError(f"Cannot accept {value}")
58
- if isinstance(value, Num):
76
+ if is_num(value):
59
77
  return value
60
78
  return cls(value)
61
79
 
@@ -368,6 +386,14 @@ class _Num(Value):
368
386
  def not_(self) -> Self:
369
387
  return self._unary_op(operator.not_, Op.Not)
370
388
 
389
+ @property
390
+ def real(self) -> Self:
391
+ return self
392
+
393
+ @property
394
+ def imag(self) -> Self:
395
+ return 0
396
+
371
397
 
372
398
  if TYPE_CHECKING:
373
399
 
sonolus/script/options.py CHANGED
@@ -139,13 +139,13 @@ class OptionField(SonolusDescriptor):
139
139
  def __get__(self, instance, owner):
140
140
  if ctx():
141
141
  match ctx().global_state.mode:
142
- case Mode.Play:
142
+ case Mode.PLAY:
143
143
  block = ctx().blocks.LevelOption
144
- case Mode.Watch:
144
+ case Mode.WATCH:
145
145
  block = ctx().blocks.LevelOption
146
- case Mode.Preview:
146
+ case Mode.PREVIEW:
147
147
  block = ctx().blocks.PreviewOption
148
- case Mode.Tutorial:
148
+ case Mode.TUTORIAL:
149
149
  block = None
150
150
  case _:
151
151
  assert_unreachable()