@benev/tact 0.1.0-1

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.
Files changed (212) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +299 -0
  3. package/package.json +60 -0
  4. package/s/demo/main.bundle.ts +14 -0
  5. package/s/demo/main.css +57 -0
  6. package/s/index.html.ts +42 -0
  7. package/s/index.ts +16 -0
  8. package/s/nubs/lookpad/styles.ts +21 -0
  9. package/s/nubs/lookpad/utils/listeners.ts +53 -0
  10. package/s/nubs/lookpad/view.ts +32 -0
  11. package/s/nubs/stick/device.ts +30 -0
  12. package/s/nubs/stick/styles.ts +22 -0
  13. package/s/nubs/stick/utils/calculate_new_vector_from_pointer_position.ts +27 -0
  14. package/s/nubs/stick/utils/find_closest_point_on_circle.ts +15 -0
  15. package/s/nubs/stick/utils/make_pointer_listeners.ts +50 -0
  16. package/s/nubs/stick/utils/within_radius.ts +6 -0
  17. package/s/nubs/stick/view.ts +50 -0
  18. package/s/nubs/stick-graphic/styles.ts +38 -0
  19. package/s/nubs/stick-graphic/types/basis.ts +5 -0
  20. package/s/nubs/stick-graphic/utils/calculate_basis.ts +19 -0
  21. package/s/nubs/stick-graphic/utils/stick_vector_to_pixels.ts +13 -0
  22. package/s/nubs/stick-graphic/utils/transform.ts +10 -0
  23. package/s/nubs/stick-graphic/view.ts +43 -0
  24. package/s/nubs/virtual-gamepad/device.ts +25 -0
  25. package/s/nubs/virtual-gamepad/styles.css.ts +133 -0
  26. package/s/nubs/virtual-gamepad/utils/gamepad-inputs.ts +42 -0
  27. package/s/nubs/virtual-gamepad/utils/prevent-default-touch-shenanigans.ts +12 -0
  28. package/s/nubs/virtual-gamepad/utils/touch-tracking.ts +75 -0
  29. package/s/nubs/virtual-gamepad/view.ts +139 -0
  30. package/s/station/devices/gamepad.ts +81 -0
  31. package/s/station/devices/infra/device.ts +7 -0
  32. package/s/station/devices/infra/group.ts +17 -0
  33. package/s/station/devices/infra/sampler.ts +22 -0
  34. package/s/station/devices/keyboard.ts +53 -0
  35. package/s/station/devices/pointer.ts +95 -0
  36. package/s/station/parts/action.ts +26 -0
  37. package/s/station/parts/defaults.ts +28 -0
  38. package/s/station/parts/resolver.ts +73 -0
  39. package/s/station/parts/routines/aggregate_samples_into_map.ts +20 -0
  40. package/s/station/parts/routines/build_updatable_actions_structure.ts +29 -0
  41. package/s/station/parts/routines/lensing_algorithm.ts +74 -0
  42. package/s/station/parts/switchboard-bindings.ts +21 -0
  43. package/s/station/station.test.ts +86 -0
  44. package/s/station/station.ts +47 -0
  45. package/s/station/switchboard.ts +107 -0
  46. package/s/station/testing/testing.ts +47 -0
  47. package/s/station/types.ts +72 -0
  48. package/s/station/utils/is-pressed.ts +5 -0
  49. package/s/station/utils/modprefix.ts +16 -0
  50. package/s/station/utils/tmax.ts +7 -0
  51. package/s/station/utils/tmin.ts +7 -0
  52. package/s/tests.test.ts +8 -0
  53. package/s/utils/evergreen.ts +10 -0
  54. package/s/utils/gamepads.ts +41 -0
  55. package/s/utils/split-axis.ts +7 -0
  56. package/x/demo/main.bundle.d.ts +1 -0
  57. package/x/demo/main.bundle.js +11 -0
  58. package/x/demo/main.bundle.js.map +1 -0
  59. package/x/demo/main.bundle.min.js +139 -0
  60. package/x/demo/main.bundle.min.js.map +7 -0
  61. package/x/demo/main.css +57 -0
  62. package/x/index.d.ts +13 -0
  63. package/x/index.html +97 -0
  64. package/x/index.html.d.ts +2 -0
  65. package/x/index.html.js +37 -0
  66. package/x/index.html.js.map +1 -0
  67. package/x/index.js +14 -0
  68. package/x/index.js.map +1 -0
  69. package/x/nubs/lookpad/styles.d.ts +1 -0
  70. package/x/nubs/lookpad/styles.js +21 -0
  71. package/x/nubs/lookpad/styles.js.map +1 -0
  72. package/x/nubs/lookpad/utils/listeners.d.ts +19 -0
  73. package/x/nubs/lookpad/utils/listeners.js +37 -0
  74. package/x/nubs/lookpad/utils/listeners.js.map +1 -0
  75. package/x/nubs/lookpad/view.d.ts +1 -0
  76. package/x/nubs/lookpad/view.js +24 -0
  77. package/x/nubs/lookpad/view.js.map +1 -0
  78. package/x/nubs/stick/device.d.ts +15 -0
  79. package/x/nubs/stick/device.js +27 -0
  80. package/x/nubs/stick/device.js.map +1 -0
  81. package/x/nubs/stick/styles.d.ts +1 -0
  82. package/x/nubs/stick/styles.js +22 -0
  83. package/x/nubs/stick/styles.js.map +1 -0
  84. package/x/nubs/stick/utils/calculate_new_vector_from_pointer_position.d.ts +3 -0
  85. package/x/nubs/stick/utils/calculate_new_vector_from_pointer_position.js +16 -0
  86. package/x/nubs/stick/utils/calculate_new_vector_from_pointer_position.js.map +1 -0
  87. package/x/nubs/stick/utils/find_closest_point_on_circle.d.ts +2 -0
  88. package/x/nubs/stick/utils/find_closest_point_on_circle.js +6 -0
  89. package/x/nubs/stick/utils/find_closest_point_on_circle.js.map +1 -0
  90. package/x/nubs/stick/utils/make_pointer_listeners.d.ts +16 -0
  91. package/x/nubs/stick/utils/make_pointer_listeners.js +34 -0
  92. package/x/nubs/stick/utils/make_pointer_listeners.js.map +1 -0
  93. package/x/nubs/stick/utils/within_radius.d.ts +2 -0
  94. package/x/nubs/stick/utils/within_radius.js +4 -0
  95. package/x/nubs/stick/utils/within_radius.js.map +1 -0
  96. package/x/nubs/stick/view.d.ts +2 -0
  97. package/x/nubs/stick/view.js +38 -0
  98. package/x/nubs/stick/view.js.map +1 -0
  99. package/x/nubs/stick-graphic/styles.d.ts +1 -0
  100. package/x/nubs/stick-graphic/styles.js +38 -0
  101. package/x/nubs/stick-graphic/styles.js.map +1 -0
  102. package/x/nubs/stick-graphic/types/basis.d.ts +4 -0
  103. package/x/nubs/stick-graphic/types/basis.js +2 -0
  104. package/x/nubs/stick-graphic/types/basis.js.map +1 -0
  105. package/x/nubs/stick-graphic/utils/calculate_basis.d.ts +2 -0
  106. package/x/nubs/stick-graphic/utils/calculate_basis.js +10 -0
  107. package/x/nubs/stick-graphic/utils/calculate_basis.js.map +1 -0
  108. package/x/nubs/stick-graphic/utils/stick_vector_to_pixels.d.ts +2 -0
  109. package/x/nubs/stick-graphic/utils/stick_vector_to_pixels.js +7 -0
  110. package/x/nubs/stick-graphic/utils/stick_vector_to_pixels.js.map +1 -0
  111. package/x/nubs/stick-graphic/utils/transform.d.ts +2 -0
  112. package/x/nubs/stick-graphic/utils/transform.js +7 -0
  113. package/x/nubs/stick-graphic/utils/transform.js.map +1 -0
  114. package/x/nubs/stick-graphic/view.d.ts +3 -0
  115. package/x/nubs/stick-graphic/view.js +30 -0
  116. package/x/nubs/stick-graphic/view.js.map +1 -0
  117. package/x/nubs/virtual-gamepad/device.d.ts +7 -0
  118. package/x/nubs/virtual-gamepad/device.js +20 -0
  119. package/x/nubs/virtual-gamepad/device.js.map +1 -0
  120. package/x/nubs/virtual-gamepad/styles.css.d.ts +2 -0
  121. package/x/nubs/virtual-gamepad/styles.css.js +133 -0
  122. package/x/nubs/virtual-gamepad/styles.css.js.map +1 -0
  123. package/x/nubs/virtual-gamepad/utils/gamepad-inputs.d.ts +29 -0
  124. package/x/nubs/virtual-gamepad/utils/gamepad-inputs.js +31 -0
  125. package/x/nubs/virtual-gamepad/utils/gamepad-inputs.js.map +1 -0
  126. package/x/nubs/virtual-gamepad/utils/prevent-default-touch-shenanigans.d.ts +1 -0
  127. package/x/nubs/virtual-gamepad/utils/prevent-default-touch-shenanigans.js +9 -0
  128. package/x/nubs/virtual-gamepad/utils/prevent-default-touch-shenanigans.js.map +1 -0
  129. package/x/nubs/virtual-gamepad/utils/touch-tracking.d.ts +6 -0
  130. package/x/nubs/virtual-gamepad/utils/touch-tracking.js +55 -0
  131. package/x/nubs/virtual-gamepad/utils/touch-tracking.js.map +1 -0
  132. package/x/nubs/virtual-gamepad/view.d.ts +2 -0
  133. package/x/nubs/virtual-gamepad/view.js +120 -0
  134. package/x/nubs/virtual-gamepad/view.js.map +1 -0
  135. package/x/station/devices/gamepad.d.ts +10 -0
  136. package/x/station/devices/gamepad.js +70 -0
  137. package/x/station/devices/gamepad.js.map +1 -0
  138. package/x/station/devices/infra/device.d.ts +4 -0
  139. package/x/station/devices/infra/device.js +3 -0
  140. package/x/station/devices/infra/device.js.map +1 -0
  141. package/x/station/devices/infra/group.d.ts +7 -0
  142. package/x/station/devices/infra/group.js +13 -0
  143. package/x/station/devices/infra/group.js.map +1 -0
  144. package/x/station/devices/infra/sampler.d.ts +8 -0
  145. package/x/station/devices/infra/sampler.js +17 -0
  146. package/x/station/devices/infra/sampler.js.map +1 -0
  147. package/x/station/devices/keyboard.d.ts +9 -0
  148. package/x/station/devices/keyboard.js +42 -0
  149. package/x/station/devices/keyboard.js.map +1 -0
  150. package/x/station/devices/pointer.d.ts +11 -0
  151. package/x/station/devices/pointer.js +79 -0
  152. package/x/station/devices/pointer.js.map +1 -0
  153. package/x/station/parts/action.d.ts +12 -0
  154. package/x/station/parts/action.js +23 -0
  155. package/x/station/parts/action.js.map +1 -0
  156. package/x/station/parts/defaults.d.ts +5 -0
  157. package/x/station/parts/defaults.js +22 -0
  158. package/x/station/parts/defaults.js.map +1 -0
  159. package/x/station/parts/resolver.d.ts +10 -0
  160. package/x/station/parts/resolver.js +63 -0
  161. package/x/station/parts/resolver.js.map +1 -0
  162. package/x/station/parts/routines/aggregate_samples_into_map.d.ts +3 -0
  163. package/x/station/parts/routines/aggregate_samples_into_map.js +11 -0
  164. package/x/station/parts/routines/aggregate_samples_into_map.js.map +1 -0
  165. package/x/station/parts/routines/build_updatable_actions_structure.d.ts +5 -0
  166. package/x/station/parts/routines/build_updatable_actions_structure.js +18 -0
  167. package/x/station/parts/routines/build_updatable_actions_structure.js.map +1 -0
  168. package/x/station/parts/routines/lensing_algorithm.d.ts +2 -0
  169. package/x/station/parts/routines/lensing_algorithm.js +42 -0
  170. package/x/station/parts/routines/lensing_algorithm.js.map +1 -0
  171. package/x/station/parts/switchboard-bindings.d.ts +2 -0
  172. package/x/station/parts/switchboard-bindings.js +19 -0
  173. package/x/station/parts/switchboard-bindings.js.map +1 -0
  174. package/x/station/station.d.ts +15 -0
  175. package/x/station/station.js +35 -0
  176. package/x/station/station.js.map +1 -0
  177. package/x/station/station.test.d.ts +11 -0
  178. package/x/station/station.test.js +80 -0
  179. package/x/station/station.test.js.map +1 -0
  180. package/x/station/switchboard.d.ts +30 -0
  181. package/x/station/switchboard.js +90 -0
  182. package/x/station/switchboard.js.map +1 -0
  183. package/x/station/testing/testing.d.ts +58 -0
  184. package/x/station/testing/testing.js +39 -0
  185. package/x/station/testing/testing.js.map +1 -0
  186. package/x/station/types.d.ts +56 -0
  187. package/x/station/types.js +5 -0
  188. package/x/station/types.js.map +1 -0
  189. package/x/station/utils/is-pressed.d.ts +1 -0
  190. package/x/station/utils/is-pressed.js +4 -0
  191. package/x/station/utils/is-pressed.js.map +1 -0
  192. package/x/station/utils/modprefix.d.ts +1 -0
  193. package/x/station/utils/modprefix.js +16 -0
  194. package/x/station/utils/modprefix.js.map +1 -0
  195. package/x/station/utils/tmax.d.ts +1 -0
  196. package/x/station/utils/tmax.js +6 -0
  197. package/x/station/utils/tmax.js.map +1 -0
  198. package/x/station/utils/tmin.d.ts +1 -0
  199. package/x/station/utils/tmin.js +6 -0
  200. package/x/station/utils/tmin.js.map +1 -0
  201. package/x/tests.test.d.ts +1 -0
  202. package/x/tests.test.js +6 -0
  203. package/x/tests.test.js.map +1 -0
  204. package/x/utils/evergreen.d.ts +1 -0
  205. package/x/utils/evergreen.js +10 -0
  206. package/x/utils/evergreen.js.map +1 -0
  207. package/x/utils/gamepads.d.ts +14 -0
  208. package/x/utils/gamepads.js +40 -0
  209. package/x/utils/gamepads.js.map +1 -0
  210. package/x/utils/split-axis.d.ts +1 -0
  211. package/x/utils/split-axis.js +6 -0
  212. package/x/utils/split-axis.js.map +1 -0
@@ -0,0 +1,86 @@
1
+
2
+ import {Science, test, expect} from "@e280/science"
3
+ import {SamplerDevice} from "./devices/infra/sampler.js"
4
+ import {testConnect, testSetupAlpha, testSetupBravo} from "./testing/testing.js"
5
+
6
+ export default Science.suite({
7
+ "sample to action value": test(async() => {
8
+ const {station, device, time} = testSetupAlpha()
9
+ expect(station.actions.basic.jump.value).is(0)
10
+ expect(station.actions.basic.shoot.value).is(0)
11
+ device.setSample("Space", 1)
12
+ station.poll(time.now)
13
+ expect(station.actions.basic.jump.value).is(1)
14
+ expect(station.actions.basic.shoot.value).is(0)
15
+ device.setSample("Space", 2)
16
+ device.setSample("pointer.button.left", 3)
17
+ station.poll(time.now)
18
+ expect(station.actions.basic.jump.value).is(2)
19
+ expect(station.actions.basic.shoot.value).is(3)
20
+ }),
21
+
22
+ "switchboard": Science.suite({
23
+ "player1 inputs work": test(async() => {
24
+ const {switchboard, time} = testSetupBravo()
25
+ const [s1, s2] = switchboard.stations
26
+ const d1 = testConnect(switchboard, new SamplerDevice())
27
+ expect(s1.actions.basic.jump.value).is(0)
28
+ expect(s2.actions.basic.jump.value).is(0)
29
+ d1.setSample("Space", 1)
30
+ switchboard.poll(time.now)
31
+ expect(s1.actions.basic.jump.value).is(1)
32
+ expect(s2.actions.basic.jump.value).is(0)
33
+ }),
34
+
35
+ "two devices playing on separate stations": test(async() => {
36
+ const {switchboard, time} = testSetupBravo()
37
+ const [s1, s2] = switchboard.stations
38
+ const d1 = testConnect(switchboard, new SamplerDevice())
39
+ const d2 = testConnect(switchboard, new SamplerDevice())
40
+ d1.setSample("Space", 1)
41
+ d2.setSample("Space", 2)
42
+ switchboard.poll(time.now)
43
+ expect(s1.actions.basic.jump.value).is(1)
44
+ expect(s2.actions.basic.jump.value).is(2)
45
+ }),
46
+
47
+ "player can shimmy": test(async() => {
48
+ const {switchboard, time} = testSetupBravo()
49
+ const [s1, s2] = switchboard.stations
50
+ const d1 = testConnect(switchboard, new SamplerDevice())
51
+ switchboard.shimmy(d1, 1)
52
+ d1.setSample("Space", 1)
53
+ switchboard.poll(time.now)
54
+ expect(s1.actions.basic.jump.value).is(0)
55
+ expect(s2.actions.basic.jump.value).is(1)
56
+ }),
57
+
58
+ "two players can share a station": test(async() => {
59
+ const {switchboard, time} = testSetupBravo()
60
+ const [s1, s2] = switchboard.stations
61
+ const d1 = testConnect(switchboard, new SamplerDevice())
62
+ const d2 = testConnect(switchboard, new SamplerDevice())
63
+ switchboard.shimmy(d2, -1)
64
+ expect(switchboard.stationByDevice(d1)).is(switchboard.stationByDevice(d2))
65
+ d1.setSample("Space", 1)
66
+ switchboard.poll(time.now)
67
+ expect(s1.actions.basic.jump.value).is(1)
68
+ expect(s2.actions.basic.jump.value).is(0)
69
+ d2.setSample("Space", 1)
70
+ switchboard.poll(time.now)
71
+ expect(s1.actions.basic.jump.value).is(1)
72
+ expect(s2.actions.basic.jump.value).is(0)
73
+ d1.setSample("Space", 1)
74
+ d2.setSample("Space", 2)
75
+ switchboard.poll(time.now)
76
+ expect(s1.actions.basic.jump.value).is(2)
77
+ expect(s2.actions.basic.jump.value).is(0)
78
+ d1.setSample("Space", 2)
79
+ d2.setSample("Space", 1)
80
+ switchboard.poll(time.now)
81
+ expect(s1.actions.basic.jump.value).is(2)
82
+ expect(s2.actions.basic.jump.value).is(0)
83
+ }),
84
+ }),
85
+ })
86
+
@@ -0,0 +1,47 @@
1
+
2
+ import {SetG} from "@e280/stz"
3
+ import {Resolver} from "./parts/resolver.js"
4
+ import {Bindings, SampleMap} from "./types.js"
5
+ import {Device} from "./devices/infra/device.js"
6
+ import {aggregate_samples_into_map} from "./parts/routines/aggregate_samples_into_map.js"
7
+
8
+ export class Station<B extends Bindings> {
9
+ readonly modes = new SetG<keyof B>()
10
+ readonly devices = new SetG<Device>()
11
+
12
+ #resolver: Resolver<B>
13
+ #samples: SampleMap = new Map()
14
+
15
+ constructor(bindings: B) {
16
+ this.#resolver = new Resolver(bindings, this.modes, this.#samples)
17
+ }
18
+
19
+ get actions() {
20
+ return this.#resolver.actions
21
+ }
22
+
23
+ get bindings() {
24
+ return this.#resolver.bindings
25
+ }
26
+
27
+ set bindings(bindings: B) {
28
+ this.#resolver.bindings = bindings
29
+ }
30
+
31
+ addModes(...modes: (keyof B)[]) {
32
+ this.modes.adds(...modes)
33
+ return this
34
+ }
35
+
36
+ addDevices(...devices: Device[]) {
37
+ this.devices.adds(...devices)
38
+ return this
39
+ }
40
+
41
+ poll(now: number) {
42
+ this.#samples.clear()
43
+ aggregate_samples_into_map(this.devices, this.#samples)
44
+ this.#resolver.poll(now)
45
+ }
46
+ }
47
+
@@ -0,0 +1,107 @@
1
+
2
+ import {SetG} from "@e280/stz"
3
+ import {Scalar} from "@benev/math"
4
+ import {Station} from "./station.js"
5
+ import {Device} from "./devices/infra/device.js"
6
+ import {switchboardMode, SwitchboardBindings} from "./types.js"
7
+ import {switchboardBindings} from "./parts/switchboard-bindings.js"
8
+
9
+ export class Switchboard<B extends SwitchboardBindings> {
10
+ static readonly mode = switchboardMode
11
+ static readonly bindings = switchboardBindings
12
+
13
+ readonly devices = new SetG<Device>()
14
+
15
+ constructor(public readonly stations: Station<B>[]) {
16
+ for (const station of stations) {
17
+ const fn = (delta: 1 | -1) => () => {
18
+ const device = this.deviceByStation(station)
19
+ if (device) this.shimmy(device, delta)
20
+ }
21
+ station.actions.switchboard.shimmyNext.onDown(fn(1))
22
+ station.actions.switchboard.shimmyPrevious.onDown(fn(-1))
23
+ station.modes.add(Switchboard.mode)
24
+ }
25
+ }
26
+
27
+ *[Symbol.iterator]() {
28
+ yield* this.stations.values()
29
+ }
30
+
31
+ *entries() {
32
+ yield* this.stations.entries()
33
+ }
34
+
35
+ /** poll all the stations */
36
+ poll(now: number) {
37
+ for (const station of this.stations)
38
+ station.poll(now)
39
+ }
40
+
41
+ /** check if a station has a known switchboard device assigned */
42
+ isActive(station: Station<B>) {
43
+ return !!this.deviceByStation(station)
44
+ }
45
+
46
+ stationByIndex(index: number) {
47
+ const station = this.stations.at(index)
48
+ if (!station) throw new Error(`station ${index} not found`)
49
+ return station
50
+ }
51
+
52
+ stationByDevice(device: Device) {
53
+ for (const station of this.stations) {
54
+ if (station.devices.has(device))
55
+ return station
56
+ }
57
+ }
58
+
59
+ deviceByStation(station: Station<B>) {
60
+ for (const device of this.devices) {
61
+ if (station.devices.has(device))
62
+ return device
63
+ }
64
+ }
65
+
66
+ /** move a player's device to the next or previous station */
67
+ shimmy(device: Device, indexDelta: 1 | -1) {
68
+ const oldIndex = this.stations.findIndex(station => station.devices.has(device))
69
+ const maxIndex = Math.max(0, this.stations.length - 1)
70
+ const newIndex = Scalar.clamp(oldIndex + indexDelta, 0, maxIndex)
71
+ const station = this.stationByIndex(newIndex)
72
+ this.connect(device, station)
73
+ return station
74
+ }
75
+
76
+ /** connect-or-assign a device to a station */
77
+ connect<D extends Device>(
78
+ device: D,
79
+ station: Station<B> = this.chooseLonelyStation(),
80
+ ) {
81
+ this.#removeDeviceFromAllStations(device)
82
+ this.devices.add(device)
83
+ station.devices.add(device)
84
+ return () => this.disconnect(device)
85
+ }
86
+
87
+ /** unplug a player's device */
88
+ disconnect(device: Device) {
89
+ this.#removeDeviceFromAllStations(device)
90
+ this.devices.delete(device)
91
+ }
92
+
93
+ /** returns an unassigned station (otherwise the last one) */
94
+ chooseLonelyStation() {
95
+ for (const station of this.stations) {
96
+ if (!this.deviceByStation(station))
97
+ return station
98
+ }
99
+ const index = Math.max(0, this.stations.length - 1)
100
+ return this.stationByIndex(index)
101
+ }
102
+
103
+ #removeDeviceFromAllStations(device: Device) {
104
+ this.stations.forEach(station => station.devices.delete(device))
105
+ }
106
+ }
107
+
@@ -0,0 +1,47 @@
1
+
2
+ import {Station} from "../station.js"
3
+ import {asBindings} from "../types.js"
4
+ import {Switchboard} from "../switchboard.js"
5
+ import {Device} from "../devices/infra/device.js"
6
+ import {SamplerDevice} from "../devices/infra/sampler.js"
7
+ import {switchboardBindings} from "../parts/switchboard-bindings.js"
8
+
9
+ export class TestTime {
10
+ frame = 0
11
+
12
+ get now() {
13
+ return (this.frame++) * (1000 / 60)
14
+ }
15
+ }
16
+
17
+ export function testBindings() {
18
+ return asBindings({
19
+ basic: {
20
+ jump: [{lenses: [{code: "Space"}]}],
21
+ shoot: [{lenses: [{code: "pointer.button.left"}]}],
22
+ },
23
+ })
24
+ }
25
+
26
+ export function testConnect<D extends Device>(switchboard: Switchboard<any>, device: D) {
27
+ switchboard.connect(device)
28
+ return device
29
+ }
30
+
31
+ export function testSetupAlpha() {
32
+ const time = new TestTime()
33
+ const device = new SamplerDevice()
34
+ const station = new Station(testBindings())
35
+ .addModes("basic")
36
+ .addDevices(device)
37
+ return {device, station, time}
38
+ }
39
+
40
+ export function testSetupBravo() {
41
+ const time = new TestTime()
42
+ const station = () => new Station(switchboardBindings(testBindings()))
43
+ .addModes(Switchboard.mode, "basic")
44
+ const switchboard = new Switchboard([station(), station(), station(), station()])
45
+ return {switchboard, time}
46
+ }
47
+
@@ -0,0 +1,72 @@
1
+
2
+ import {Action} from "./parts/action.js"
3
+
4
+ export type Actions<B extends Bindings> = {
5
+ [Mode in keyof B]: {
6
+ [K in keyof B[Mode]]: Action
7
+ }
8
+ }
9
+
10
+ export type Sample = [code: string, value: number]
11
+ export type SampleMap = Map<string, number>
12
+
13
+ export type Timing = (
14
+ | DirectTiming
15
+ | TapTiming
16
+ | HoldTiming
17
+ )
18
+
19
+ export type DirectTiming = {style: "direct"}
20
+ export type TapTiming = {style: "tap", holdTime?: number}
21
+ export type HoldTiming = {style: "hold", holdTime?: number}
22
+
23
+ export type LensSettings = {
24
+ scale: number
25
+ invert: boolean
26
+ deadzone: number
27
+ timing: Timing
28
+ }
29
+
30
+ export type Lens = {
31
+ code: string
32
+ settings?: Partial<LensSettings>
33
+ }
34
+
35
+ export type LensState = {
36
+ lastValue: number
37
+ holdStart: number
38
+ }
39
+
40
+ export type Spoon = {
41
+ lenses: Lens[]
42
+ required?: Lens[]
43
+ forbidden?: Lens[]
44
+ }
45
+
46
+ export type Fork = Spoon[]
47
+
48
+ export type Bracket = {
49
+ [action: string]: Spoon[]
50
+ }
51
+
52
+ export type Bindings = {
53
+ [mode: string]: Bracket
54
+ }
55
+
56
+ export type AsBindings<B extends Bindings> = B
57
+
58
+ export function asBindings<B extends Bindings>(bindings: B) {
59
+ return bindings
60
+ }
61
+
62
+ export const switchboardMode = "switchboard" as const
63
+
64
+ export type AsSwitchboardBindings<B extends Bindings> = {
65
+ [switchboardMode]: {
66
+ shimmyNext: Spoon[],
67
+ shimmyPrevious: Spoon[],
68
+ }
69
+ } & B
70
+
71
+ export type SwitchboardBindings = AsSwitchboardBindings<Bindings>
72
+
@@ -0,0 +1,5 @@
1
+
2
+ export function isPressed(value: number) {
3
+ return value > 0
4
+ }
5
+
@@ -0,0 +1,16 @@
1
+
2
+ export function modprefix(event: KeyboardEvent | PointerEvent | WheelEvent, code: string) {
3
+ const modifiers: string[] = []
4
+
5
+ if (event.ctrlKey) modifiers.push("ctrl")
6
+ if (event.altKey) modifiers.push("alt")
7
+ if (event.metaKey) modifiers.push("meta")
8
+ if (event.shiftKey) modifiers.push("shift")
9
+
10
+ const prefix = modifiers.length > 0
11
+ ? [...modifiers].join("-")
12
+ : "x"
13
+
14
+ return `${prefix}-${code}`
15
+ }
16
+
@@ -0,0 +1,7 @@
1
+
2
+ export function tmax(values: number[]) {
3
+ return values.length > 0
4
+ ? Math.max(...values)
5
+ : 0
6
+ }
7
+
@@ -0,0 +1,7 @@
1
+
2
+ export function tmin(values: number[]) {
3
+ return values.length > 0
4
+ ? Math.min(...values)
5
+ : 0
6
+ }
7
+
@@ -0,0 +1,8 @@
1
+
2
+ import {Science} from "@e280/science"
3
+ import station from "./station/station.test.js"
4
+
5
+ await Science.run({
6
+ station,
7
+ })
8
+
@@ -0,0 +1,10 @@
1
+
2
+ export function evergreen<V>(value: V, fn: () => V | null | undefined) {
3
+ let cached = value
4
+ return () => {
5
+ const next = fn()
6
+ if (next !== undefined && next !== null) cached = next
7
+ return cached
8
+ }
9
+ }
10
+
@@ -0,0 +1,41 @@
1
+
2
+ import {Disposable, ev, MapG} from "@e280/stz"
3
+ import {evergreen} from "./evergreen.js"
4
+
5
+ /** stable reference to a gamepad, has a getter to get the latest gamepad snapshot */
6
+ export class Pad {
7
+ constructor(private get: () => Gamepad) {}
8
+
9
+ get gamepad() {
10
+ return this.get()
11
+ }
12
+ }
13
+
14
+ /** track gamepad lifecycles as they connect or disconnect */
15
+ export function gamepads(on: (pad: Pad) => () => void) {
16
+ const pads = new MapG<number, {pad: Pad, dispose: () => void}>()
17
+ return ev(window, {
18
+ gamepadconnected: ({gamepad}: GamepadEvent) => {
19
+ const get = () => navigator.getGamepads().at(gamepad.index)
20
+ const pad = new Pad(evergreen(gamepad, get))
21
+ const dispose = on(pad)
22
+ pads.set(gamepad.index, {pad, dispose})
23
+ },
24
+
25
+ gamepaddisconnected: ({gamepad}: GamepadEvent) => {
26
+ const result = pads.get(gamepad.index)
27
+ if (result) {
28
+ pads.delete(gamepad.index)
29
+ result.dispose()
30
+ }
31
+ },
32
+ })
33
+ }
34
+
35
+ export class Gamepads implements Disposable {
36
+ dispose: () => void
37
+ constructor(public on: (pad: Pad) => () => void) {
38
+ this.dispose = gamepads(on)
39
+ }
40
+ }
41
+
@@ -0,0 +1,7 @@
1
+
2
+ export function splitAxis(n: number) {
3
+ return (n >= 0)
4
+ ? [0, n]
5
+ : [Math.abs(n), 0]
6
+ }
7
+
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,11 @@
1
+ import { $, view } from "@e280/sly";
2
+ import { NubStick } from "../nubs/stick/view.js";
3
+ import { StickDevice } from "../nubs/stick/device.js";
4
+ $.register({
5
+ TactDemo: view.component(use => {
6
+ const stick = use.once(() => new StickDevice());
7
+ return NubStick(stick);
8
+ }),
9
+ });
10
+ console.log("tact");
11
+ //# sourceMappingURL=main.bundle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"main.bundle.js","sourceRoot":"","sources":["../../s/demo/main.bundle.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,CAAC,EAAE,IAAI,EAAC,MAAM,WAAW,CAAA;AACjC,OAAO,EAAC,QAAQ,EAAC,MAAM,uBAAuB,CAAA;AAC9C,OAAO,EAAC,WAAW,EAAC,MAAM,yBAAyB,CAAA;AAEnD,CAAC,CAAC,QAAQ,CAAC;IACV,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE;QAC9B,MAAM,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,IAAI,WAAW,EAAE,CAAC,CAAA;QAC/C,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAA;IACvB,CAAC,CAAC;CACF,CAAC,CAAA;AAEF,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA"}