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

@@ -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):
@@ -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))
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()
@@ -102,54 +102,54 @@ def particles[T](cls: type[T]) -> T | Particles:
102
102
 
103
103
 
104
104
  class StandardParticle:
105
- NoteCircularTapNeutral = Annotated[Particle, particle("#NOTE_CIRCULAR_TAP_NEUTRAL")]
106
- NoteCircularTapRed = Annotated[Particle, particle("#NOTE_CIRCULAR_TAP_RED")]
107
- NoteCircularTapGreen = Annotated[Particle, particle("#NOTE_CIRCULAR_TAP_GREEN")]
108
- NoteCircularTapBlue = Annotated[Particle, particle("#NOTE_CIRCULAR_TAP_BLUE")]
109
- NoteCircularTapYellow = Annotated[Particle, particle("#NOTE_CIRCULAR_TAP_YELLOW")]
110
- NoteCircularTapPurple = Annotated[Particle, particle("#NOTE_CIRCULAR_TAP_PURPLE")]
111
- NoteCircularTapCyan = Annotated[Particle, particle("#NOTE_CIRCULAR_TAP_CYAN")]
112
- NoteCircularAlternativeNeutral = Annotated[Particle, particle("#NOTE_CIRCULAR_ALTERNATIVE_NEUTRAL")]
113
- NoteCircularAlternativeRed = Annotated[Particle, particle("#NOTE_CIRCULAR_ALTERNATIVE_RED")]
114
- NoteCircularAlternativeGreen = Annotated[Particle, particle("#NOTE_CIRCULAR_ALTERNATIVE_GREEN")]
115
- NoteCircularAlternativeBlue = Annotated[Particle, particle("#NOTE_CIRCULAR_ALTERNATIVE_BLUE")]
116
- NoteCircularAlternativeYellow = Annotated[Particle, particle("#NOTE_CIRCULAR_ALTERNATIVE_YELLOW")]
117
- NoteCircularAlternativePurple = Annotated[Particle, particle("#NOTE_CIRCULAR_ALTERNATIVE_PURPLE")]
118
- NoteCircularAlternativeCyan = Annotated[Particle, particle("#NOTE_CIRCULAR_ALTERNATIVE_CYAN")]
119
- NoteCircularHoldNeutral = Annotated[Particle, particle("#NOTE_CIRCULAR_HOLD_NEUTRAL")]
120
- NoteCircularHoldRed = Annotated[Particle, particle("#NOTE_CIRCULAR_HOLD_RED")]
121
- NoteCircularHoldGreen = Annotated[Particle, particle("#NOTE_CIRCULAR_HOLD_GREEN")]
122
- NoteCircularHoldBlue = Annotated[Particle, particle("#NOTE_CIRCULAR_HOLD_BLUE")]
123
- NoteCircularHoldYellow = Annotated[Particle, particle("#NOTE_CIRCULAR_HOLD_YELLOW")]
124
- NoteCircularHoldPurple = Annotated[Particle, particle("#NOTE_CIRCULAR_HOLD_PURPLE")]
125
- NoteCircularHoldCyan = Annotated[Particle, particle("#NOTE_CIRCULAR_HOLD_CYAN")]
126
- NoteLinearTapNeutral = Annotated[Particle, particle("#NOTE_LINEAR_TAP_NEUTRAL")]
127
- NoteLinearTapRed = Annotated[Particle, particle("#NOTE_LINEAR_TAP_RED")]
128
- NoteLinearTapGreen = Annotated[Particle, particle("#NOTE_LINEAR_TAP_GREEN")]
129
- NoteLinearTapBlue = Annotated[Particle, particle("#NOTE_LINEAR_TAP_BLUE")]
130
- NoteLinearTapYellow = Annotated[Particle, particle("#NOTE_LINEAR_TAP_YELLOW")]
131
- NoteLinearTapPurple = Annotated[Particle, particle("#NOTE_LINEAR_TAP_PURPLE")]
132
- NoteLinearTapCyan = Annotated[Particle, particle("#NOTE_LINEAR_TAP_CYAN")]
133
- NoteLinearAlternativeNeutral = Annotated[Particle, particle("#NOTE_LINEAR_ALTERNATIVE_NEUTRAL")]
134
- NoteLinearAlternativeRed = Annotated[Particle, particle("#NOTE_LINEAR_ALTERNATIVE_RED")]
135
- NoteLinearAlternativeGreen = Annotated[Particle, particle("#NOTE_LINEAR_ALTERNATIVE_GREEN")]
136
- NoteLinearAlternativeBlue = Annotated[Particle, particle("#NOTE_LINEAR_ALTERNATIVE_BLUE")]
137
- NoteLinearAlternativeYellow = Annotated[Particle, particle("#NOTE_LINEAR_ALTERNATIVE_YELLOW")]
138
- NoteLinearAlternativePurple = Annotated[Particle, particle("#NOTE_LINEAR_ALTERNATIVE_PURPLE")]
139
- NoteLinearAlternativeCyan = Annotated[Particle, particle("#NOTE_LINEAR_ALTERNATIVE_CYAN")]
140
- NoteLinearHoldNeutral = Annotated[Particle, particle("#NOTE_LINEAR_HOLD_NEUTRAL")]
141
- NoteLinearHoldRed = Annotated[Particle, particle("#NOTE_LINEAR_HOLD_RED")]
142
- NoteLinearHoldGreen = Annotated[Particle, particle("#NOTE_LINEAR_HOLD_GREEN")]
143
- NoteLinearHoldBlue = Annotated[Particle, particle("#NOTE_LINEAR_HOLD_BLUE")]
144
- NoteLinearHoldYellow = Annotated[Particle, particle("#NOTE_LINEAR_HOLD_YELLOW")]
145
- NoteLinearHoldPurple = Annotated[Particle, particle("#NOTE_LINEAR_HOLD_PURPLE")]
146
- NoteLinearHoldCyan = Annotated[Particle, particle("#NOTE_LINEAR_HOLD_CYAN")]
147
- LaneCircular = Annotated[Particle, particle("#LANE_CIRCULAR")]
148
- LaneLinear = Annotated[Particle, particle("#LANE_LINEAR")]
149
- SlotCircular = Annotated[Particle, particle("#SLOT_CIRCULAR")]
150
- SlotLinear = Annotated[Particle, particle("#SLOT_LINEAR")]
151
- JudgeLineCircular = Annotated[Particle, particle("#JUDGE_LINE_CIRCULAR")]
152
- JudgeLineLinear = Annotated[Particle, particle("#JUDGE_LINE_LINEAR")]
105
+ NOTE_CIRCULAR_TAP_NEUTRAL = Annotated[Particle, particle("#NOTE_CIRCULAR_TAP_NEUTRAL")]
106
+ NOTE_CIRCULAR_TAP_RED = Annotated[Particle, particle("#NOTE_CIRCULAR_TAP_RED")]
107
+ NOTE_CIRCULAR_TAP_GREEN = Annotated[Particle, particle("#NOTE_CIRCULAR_TAP_GREEN")]
108
+ NOTE_CIRCULAR_TAP_BLUE = Annotated[Particle, particle("#NOTE_CIRCULAR_TAP_BLUE")]
109
+ NOTE_CIRCULAR_TAP_YELLOW = Annotated[Particle, particle("#NOTE_CIRCULAR_TAP_YELLOW")]
110
+ NOTE_CIRCULAR_TAP_PURPLE = Annotated[Particle, particle("#NOTE_CIRCULAR_TAP_PURPLE")]
111
+ NOTE_CIRCULAR_TAP_CYAN = Annotated[Particle, particle("#NOTE_CIRCULAR_TAP_CYAN")]
112
+ NOTE_CIRCULAR_ALTERNATIVE_NEUTRAL = Annotated[Particle, particle("#NOTE_CIRCULAR_ALTERNATIVE_NEUTRAL")]
113
+ NOTE_CIRCULAR_ALTERNATIVE_RED = Annotated[Particle, particle("#NOTE_CIRCULAR_ALTERNATIVE_RED")]
114
+ NOTE_CIRCULAR_ALTERNATIVE_GREEN = Annotated[Particle, particle("#NOTE_CIRCULAR_ALTERNATIVE_GREEN")]
115
+ NOTE_CIRCULAR_ALTERNATIVE_BLUE = Annotated[Particle, particle("#NOTE_CIRCULAR_ALTERNATIVE_BLUE")]
116
+ NOTE_CIRCULAR_ALTERNATIVE_YELLOW = Annotated[Particle, particle("#NOTE_CIRCULAR_ALTERNATIVE_YELLOW")]
117
+ NOTE_CIRCULAR_ALTERNATIVE_PURPLE = Annotated[Particle, particle("#NOTE_CIRCULAR_ALTERNATIVE_PURPLE")]
118
+ NOTE_CIRCULAR_ALTERNATIVE_CYAN = Annotated[Particle, particle("#NOTE_CIRCULAR_ALTERNATIVE_CYAN")]
119
+ NOTE_CIRCULAR_HOLD_NEUTRAL = Annotated[Particle, particle("#NOTE_CIRCULAR_HOLD_NEUTRAL")]
120
+ NOTE_CIRCULAR_HOLD_RED = Annotated[Particle, particle("#NOTE_CIRCULAR_HOLD_RED")]
121
+ NOTE_CIRCULAR_HOLD_GREEN = Annotated[Particle, particle("#NOTE_CIRCULAR_HOLD_GREEN")]
122
+ NOTE_CIRCULAR_HOLD_BLUE = Annotated[Particle, particle("#NOTE_CIRCULAR_HOLD_BLUE")]
123
+ NOTE_CIRCULAR_HOLD_YELLOW = Annotated[Particle, particle("#NOTE_CIRCULAR_HOLD_YELLOW")]
124
+ NOTE_CIRCULAR_HOLD_PURPLE = Annotated[Particle, particle("#NOTE_CIRCULAR_HOLD_PURPLE")]
125
+ NOTE_CIRCULAR_HOLD_CYAN = Annotated[Particle, particle("#NOTE_CIRCULAR_HOLD_CYAN")]
126
+ NOTE_LINEAR_TAP_NEUTRAL = Annotated[Particle, particle("#NOTE_LINEAR_TAP_NEUTRAL")]
127
+ NOTE_LINEAR_TAP_RED = Annotated[Particle, particle("#NOTE_LINEAR_TAP_RED")]
128
+ NOTE_LINEAR_TAP_GREEN = Annotated[Particle, particle("#NOTE_LINEAR_TAP_GREEN")]
129
+ NOTE_LINEAR_TAP_BLUE = Annotated[Particle, particle("#NOTE_LINEAR_TAP_BLUE")]
130
+ NOTE_LINEAR_TAP_YELLOW = Annotated[Particle, particle("#NOTE_LINEAR_TAP_YELLOW")]
131
+ NOTE_LINEAR_TAP_PURPLE = Annotated[Particle, particle("#NOTE_LINEAR_TAP_PURPLE")]
132
+ NOTE_LINEAR_TAP_CYAN = Annotated[Particle, particle("#NOTE_LINEAR_TAP_CYAN")]
133
+ NOTE_LINEAR_ALTERNATIVE_NEUTRAL = Annotated[Particle, particle("#NOTE_LINEAR_ALTERNATIVE_NEUTRAL")]
134
+ NOTE_LINEAR_ALTERNATIVE_RED = Annotated[Particle, particle("#NOTE_LINEAR_ALTERNATIVE_RED")]
135
+ NOTE_LINEAR_ALTERNATIVE_GREEN = Annotated[Particle, particle("#NOTE_LINEAR_ALTERNATIVE_GREEN")]
136
+ NOTE_LINEAR_ALTERNATIVE_BLUE = Annotated[Particle, particle("#NOTE_LINEAR_ALTERNATIVE_BLUE")]
137
+ NOTE_LINEAR_ALTERNATIVE_YELLOW = Annotated[Particle, particle("#NOTE_LINEAR_ALTERNATIVE_YELLOW")]
138
+ NOTE_LINEAR_ALTERNATIVE_PURPLE = Annotated[Particle, particle("#NOTE_LINEAR_ALTERNATIVE_PURPLE")]
139
+ NOTE_LINEAR_ALTERNATIVE_CYAN = Annotated[Particle, particle("#NOTE_LINEAR_ALTERNATIVE_CYAN")]
140
+ NOTE_LINEAR_HOLD_NEUTRAL = Annotated[Particle, particle("#NOTE_LINEAR_HOLD_NEUTRAL")]
141
+ NOTE_LINEAR_HOLD_RED = Annotated[Particle, particle("#NOTE_LINEAR_HOLD_RED")]
142
+ NOTE_LINEAR_HOLD_GREEN = Annotated[Particle, particle("#NOTE_LINEAR_HOLD_GREEN")]
143
+ NOTE_LINEAR_HOLD_BLUE = Annotated[Particle, particle("#NOTE_LINEAR_HOLD_BLUE")]
144
+ NOTE_LINEAR_HOLD_YELLOW = Annotated[Particle, particle("#NOTE_LINEAR_HOLD_YELLOW")]
145
+ NOTE_LINEAR_HOLD_PURPLE = Annotated[Particle, particle("#NOTE_LINEAR_HOLD_PURPLE")]
146
+ NOTE_LINEAR_HOLD_CYAN = Annotated[Particle, particle("#NOTE_LINEAR_HOLD_CYAN")]
147
+ LANE_CIRCULAR = Annotated[Particle, particle("#LANE_CIRCULAR")]
148
+ LANE_LINEAR = Annotated[Particle, particle("#LANE_LINEAR")]
149
+ SLOT_CIRCULAR = Annotated[Particle, particle("#SLOT_CIRCULAR")]
150
+ SLOT_LINEAR = Annotated[Particle, particle("#SLOT_LINEAR")]
151
+ JUDGE_LINE_CIRCULAR = Annotated[Particle, particle("#JUDGE_LINE_CIRCULAR")]
152
+ JUDGE_LINE_LINEAR = Annotated[Particle, particle("#JUDGE_LINE_LINEAR")]
153
153
 
154
154
 
155
155
  @particles
sonolus/script/pointer.py CHANGED
@@ -1,7 +1,7 @@
1
1
  from sonolus.backend.place import BlockPlace
2
2
  from sonolus.script.internal.impl import meta_fn, validate_value
3
3
  from sonolus.script.internal.value import Value
4
- from sonolus.script.num import Num
4
+ from sonolus.script.num import Num, is_num
5
5
 
6
6
 
7
7
  @meta_fn
@@ -14,7 +14,7 @@ def deref[T: Value](block: Num, offset: Num, type_: type[T]) -> T:
14
14
  if not isinstance(block, int):
15
15
  raise TypeError("block must be an integer")
16
16
  else:
17
- if not isinstance(block, Num):
17
+ if not is_num(block):
18
18
  raise TypeError("block must be a Num")
19
19
  block = block.index()
20
20
  if offset._is_py_():
@@ -22,7 +22,7 @@ def deref[T: Value](block: Num, offset: Num, type_: type[T]) -> T:
22
22
  if not isinstance(offset, int):
23
23
  raise TypeError("offset must be an integer")
24
24
  else:
25
- if not isinstance(offset, Num):
25
+ if not is_num(offset):
26
26
  raise TypeError("offset must be a Num")
27
27
  offset = offset.index()
28
28
  if not (isinstance(type_, type) and issubclass(type_, Value)):
@@ -0,0 +1,81 @@
1
+ from enum import IntEnum
2
+
3
+ from sonolus.backend.ops import Op
4
+ from sonolus.script.internal.native import native_function
5
+ from sonolus.script.runtime import HorizontalAlign
6
+ from sonolus.script.vec import Vec2
7
+
8
+
9
+ class PrintFormat(IntEnum):
10
+ NUMBER = 0
11
+ PERCENTAGE = 1
12
+ TIME = 10
13
+ SCORE = 11
14
+ BPM = 20
15
+ TIMESCALE = 21
16
+ BEAT_COUNT = 30
17
+ MEASURE_COUNT = 31
18
+ ENTITY_COUNT = 32
19
+
20
+
21
+ class PrintColor(IntEnum):
22
+ THEME = -1
23
+ NEUTRAL = 0
24
+ RED = 1
25
+ GREEN = 2
26
+ BLUE = 3
27
+ YELLOW = 4
28
+ PURPLE = 5
29
+ CYAN = 6
30
+
31
+
32
+ @native_function(Op.Print)
33
+ def _print(
34
+ value: int | float,
35
+ format: PrintFormat, # noqa: A002
36
+ decimal_places: int,
37
+ anchor_x: float,
38
+ anchor_y: float,
39
+ pivot_x: float,
40
+ pivot_y: float,
41
+ width: float,
42
+ height: float,
43
+ rotation: float,
44
+ color: PrintColor,
45
+ alpha: float,
46
+ horizontal_align: HorizontalAlign,
47
+ background: bool,
48
+ ):
49
+ raise NotImplementedError
50
+
51
+
52
+ def print_number(
53
+ value: int | float,
54
+ *,
55
+ fmt: PrintFormat,
56
+ decimal_places: int = 0,
57
+ anchor: Vec2,
58
+ pivot: Vec2,
59
+ dimensions: Vec2,
60
+ rotation: float = 0,
61
+ color: PrintColor = PrintColor.THEME,
62
+ alpha: float = 1,
63
+ horizontal_align: HorizontalAlign = HorizontalAlign.LEFT,
64
+ background: bool = False,
65
+ ):
66
+ _print(
67
+ value,
68
+ fmt,
69
+ decimal_places,
70
+ anchor.x,
71
+ anchor.y,
72
+ pivot.x,
73
+ pivot.y,
74
+ dimensions.x,
75
+ dimensions.y,
76
+ rotation,
77
+ color,
78
+ alpha,
79
+ horizontal_align,
80
+ background,
81
+ )