jevstrudel.git / packages / motion / motion.mjs
1// motion.mjs
2
3import { signal } from '../core/signal.mjs';
4
5/**
6 *  The accelerometer's x-axis value ranges from 0 to 1.
7 * @name accelerationX
8 * @return {Pattern}
9 * @synonyms accX
10 * @tags external_io
11 * @example
12 * n(accelerationX.segment(4).range(0,7)).scale("C:minor")
13 *
14 */
15
16/**
17 *  The accelerometer's y-axis value ranges from 0 to 1.
18 * @name accelerationY
19 * @return {Pattern}
20 * @synonyms accY
21 * @tags external_io
22 * @example
23 * n(accelerationY.segment(4).range(0,7)).scale("C:minor")
24 *
25 */
26
27/**
28 *  The accelerometer's z-axis value ranges from 0 to 1.
29 * @name accelerationZ
30 * @return {Pattern}
31 * @synonyms accZ
32 * @tags external_io
33 * @example
34 * n(accelerationZ.segment(4).range(0,7)).scale("C:minor")
35 *
36 */
37
38/**
39 *  The device's gravity x-axis value ranges from 0 to 1.
40 * @name gravityX
41 * @return {Pattern}
42 * @synonyms gravX
43 * @tags external_io
44 * @example
45 * n(gravityX.segment(4).range(0,7)).scale("C:minor")
46 *
47 */
48
49/**
50 *  The device's gravity y-axis value ranges from 0 to 1.
51 * @name gravityY
52 * @return {Pattern}
53 * @synonyms gravY
54 * @tags external_io
55 * @example
56 * n(gravityY.segment(4).range(0,7)).scale("C:minor")
57 *
58 */
59
60/**
61 *  The device's gravity z-axis value ranges from 0 to 1.
62 * @name gravityZ
63 * @return {Pattern}
64 * @synonyms gravZ
65 * @tags external_io
66 * @example
67 * n(gravityZ.segment(4).range(0,7)).scale("C:minor")
68 *
69 */
70
71/**
72 *  The device's rotation around the alpha-axis value ranges from 0 to 1.
73 * @name rotationAlpha
74 * @return {Pattern}
75 * @synonyms rotA, rotZ, rotationZ
76 * @tags external_io
77 * @example
78 * n(rotationAlpha.segment(4).range(0,7)).scale("C:minor")
79 *
80 */
81
82/**
83 *  The device's rotation around the beta-axis value ranges from 0 to 1.
84 * @name rotationBeta
85 * @return {Pattern}
86 * @synonyms rotB, rotX, rotationX
87 * @tags external_io
88 * @example
89 * n(rotationBeta.segment(4).range(0,7)).scale("C:minor")
90 *
91 */
92
93/**
94 *  The device's rotation around the gamma-axis value ranges from 0 to 1.
95 * @name rotationGamma
96 * @return {Pattern}
97 * @synonyms rotG, rotY, rotationY
98 * @tags external_io
99 * @example
100 * n(rotationGamma.segment(4).range(0,7)).scale("C:minor")
101 *
102 */
103
104/**
105 *  The device's orientation alpha value ranges from 0 to 1.
106 * @name orientationAlpha
107 * @return {Pattern}
108 * @synonyms oriA, oriZ, orientationZ
109 * @tags external_io
110 * @example
111 * n(orientationAlpha.segment(4).range(0,7)).scale("C:minor")
112 *
113 */
114
115/**
116 *  The device's orientation beta value ranges from 0 to 1.
117 * @name orientationBeta
118 * @return {Pattern}
119 * @synonyms oriB, oriX, orientationX
120 * @tags external_io
121 * @example
122 * n(orientationBeta.segment(4).range(0,7)).scale("C:minor")
123 *
124 */
125
126/**
127 *  The device's orientation gamma value ranges from 0 to 1.
128 * @name orientationGamma
129 * @return {Pattern}
130 * @synonyms oriG, oriY, orientationY
131 * @tags external_io
132 * @example
133 * n(orientationGamma.segment(4).range(0,7)).scale("C:minor")
134 *
135 */
136
137/**
138 *  The device's absolute orientation alpha value ranges from 0 to 1.
139 * @name absoluteOrientationAlpha
140 * @return {Pattern}
141 * @synonyms absOriA, absOriZ, absoluteOrientationZ
142 * @tags external_io
143 * @example
144 * n(absoluteOrientationAlpha.segment(4).range(0,7)).scale("C:minor")
145 *
146 */
147
148/**
149 *  The device's absolute orientation beta value ranges from 0 to 1.
150 * @name absoluteOrientationBeta
151 * @return {Pattern}
152 * @synonyms absOriB, absOriX, absoluteOrientationX
153 * @tags external_io
154 * @example
155 * n(absoluteOrientationBeta.segment(4).range(0,7)).scale("C:minor")
156 *
157 */
158
159/**
160 *  The device's absolute orientation gamma value ranges from 0 to 1.
161 * @name absoluteOrientationGamma
162 * @return {Pattern}
163 * @synonyms absOriG, absOriY, absoluteOrientationY
164 * @tags external_io
165 * @example
166 * n(absoluteOrientationGamma.segment(4).range(0,7)).scale("C:minor")
167 *
168 */
169
170class DeviceMotionHandler {
171  constructor() {
172    this.GRAVITY = 9.81;
173
174    // Initialize sensor values
175    this._acceleration = {
176      x: 0,
177      y: 0,
178      z: 0,
179    };
180
181    this._gravity = {
182      x: 0,
183      y: 0,
184      z: 0,
185    };
186
187    this._rotation = {
188      alpha: 0,
189      beta: 0,
190      gamma: 0,
191    };
192
193    this._orientation = {
194      alpha: 0,
195      beta: 0,
196      gamma: 0,
197    };
198
199    this._absoluteOrientation = {
200      alpha: 0,
201      beta: 0,
202      gamma: 0,
203    };
204
205    this._permissionStatus = 'unknown';
206  }
207
208  async requestPermissions() {
209    if (typeof DeviceMotionEvent?.requestPermission === 'function') {
210      try {
211        // iOS requires explicit permission
212        const motionPermission = await DeviceMotionEvent.requestPermission();
213        const orientationPermission = await DeviceOrientationEvent.requestPermission();
214
215        this._permissionStatus =
216          motionPermission === 'granted' && orientationPermission === 'granted' ? 'granted' : 'denied';
217        this.setupEventListeners();
218      } catch (error) {
219        console.error('Permission request failed:', error);
220        this._permissionStatus = 'denied';
221      }
222    } else {
223      this._permissionStatus = 'granted';
224      this.setupEventListeners();
225    }
226  }
227
228  setupEventListeners() {
229    if (this._permissionStatus === 'granted') {
230      // Device Motion handler
231      window.addEventListener('devicemotion', this.handleDeviceMotion.bind(this), true);
232      window.addEventListener('deviceorientation', this.handleDeviceOrientation.bind(this), true);
233      window.addEventListener('deviceorientationabsolute', this.handleAbsoluteDeviceOrientation.bind(this), true);
234    }
235  }
236
237  handleDeviceMotion(event) {
238    //console.log(event);
239    if (event.acceleration) {
240      // Normalize acceleration values to 0-1 range
241      this._acceleration.x = (event.acceleration.x + 1) / 2;
242      this._acceleration.y = (event.acceleration.y + 1) / 2;
243      this._acceleration.z = (event.acceleration.z + 1) / 2;
244    }
245
246    if (event.accelerationIncludingGravity) {
247      // Normalize acceleration values to 0-1 range
248      this._gravity.x = (event.accelerationIncludingGravity.x + this.GRAVITY) / (2 * this.GRAVITY);
249      this._gravity.y = (event.accelerationIncludingGravity.y + this.GRAVITY) / (2 * this.GRAVITY);
250      this._gravity.z = (event.accelerationIncludingGravity.z + this.GRAVITY) / (2 * this.GRAVITY);
251    }
252
253    if (event.rotationRate) {
254      // Normalize rotation values to 0-1 range
255      this._rotation.alpha = (event.rotationRate.alpha + 180) / 360;
256      this._rotation.beta = (event.rotationRate.beta + 180) / 360;
257      this._rotation.gamma = (event.rotationRate.gamma + 180) / 360;
258    }
259  }
260
261  handleDeviceOrientation(event) {
262    this._orientation.alpha = event.alpha / 360; //a(0~360)
263    this._orientation.beta = (event.beta + 180) / 360; //b(-180~180)
264    this._orientation.gamma = (event.gamma + 90) / 180; //g(-90~90)
265  }
266
267  handleAbsoluteDeviceOrientation(event) {
268    this._absoluteOrientation.alpha = event.alpha / 360; //a(0~360)
269    this._absoluteOrientation.beta = (event.beta + 180) / 360; //b(-180~180)
270    this._absoluteOrientation.gamma = (event.gamma + 90) / 180; //g(-90~90)
271  }
272
273  // Getter methods for current values
274  getAcceleration() {
275    return this._acceleration;
276  }
277  getGravity() {
278    return this._gravity;
279  }
280  getRotation() {
281    return this._rotation;
282  }
283  getOrientation() {
284    return this._orientation;
285  }
286  getAbsoluteOrientation() {
287    return this._absoluteOrientation;
288  }
289}
290
291// Create singleton instance
292const deviceMotion = new DeviceMotionHandler();
293
294// Export a function to request permission
295export async function enableMotion() {
296  return deviceMotion.requestPermissions();
297}
298
299// Create signals for acceleration
300export const accelerationX = signal(() => deviceMotion.getAcceleration().x);
301export const accelerationY = signal(() => deviceMotion.getAcceleration().y);
302export const accelerationZ = signal(() => deviceMotion.getAcceleration().z);
303
304// Aliases for shorter names
305export const accX = accelerationX;
306export const accY = accelerationY;
307export const accZ = accelerationZ;
308
309// Create signals for gravity
310export const gravityX = signal(() => deviceMotion.getGravity().x);
311export const gravityY = signal(() => deviceMotion.getGravity().y);
312export const gravityZ = signal(() => deviceMotion.getGravity().z);
313
314// Aliases for shorter names
315export const gravX = gravityX;
316export const gravY = gravityY;
317export const gravZ = gravityZ;
318
319// Create signals for orientation
320export const orientationAlpha = signal(() => deviceMotion.getOrientation().alpha);
321export const orientationBeta = signal(() => deviceMotion.getOrientation().beta);
322export const orientationGamma = signal(() => deviceMotion.getOrientation().gamma);
323// Aliases for shorter names
324export const orientationA = orientationAlpha;
325export const orientationB = orientationBeta;
326export const orientationG = orientationGamma;
327
328// Aliases mapping to X,Y,Z coordinates
329export const orientationX = orientationBeta;
330export const orientationY = orientationGamma;
331export const orientationZ = orientationAlpha;
332
333// Short aliases for A,B,G,X,Y,Z
334
335export const oriA = orientationAlpha;
336export const oriB = orientationBeta;
337export const oriG = orientationGamma;
338
339export const oriX = orientationX;
340export const oriY = orientationY;
341export const oriZ = orientationZ;
342
343// Create signals for absolute orientation
344export const absoluteOrientationAlpha = signal(() => deviceMotion.getAbsoluteOrientation().alpha);
345export const absoluteOrientationBeta = signal(() => deviceMotion.getAbsoluteOrientation().beta);
346export const absoluteOrientationGamma = signal(() => deviceMotion.getAbsoluteOrientation().gamma);
347
348// Aliases for shorter names
349export const absOriA = absoluteOrientationAlpha;
350export const absOriB = absoluteOrientationBeta;
351export const absOriG = absoluteOrientationGamma;
352
353// Aliases mapping to X,Y,Z coordinates
354export const absoluteOrientationX = absoluteOrientationBeta;
355export const absoluteOrientationY = absoluteOrientationGamma;
356export const absoluteOrientationZ = absoluteOrientationAlpha;
357
358// Short aliases for X,Y,Z
359export const absOriX = absoluteOrientationX;
360export const absOriY = absoluteOrientationY;
361export const absOriZ = absoluteOrientationZ;
362
363// Create signals for rotation
364export const rotationAlpha = signal(() => deviceMotion.getRotation().alpha);
365export const rotationBeta = signal(() => deviceMotion.getRotation().beta);
366export const rotationGamma = signal(() => deviceMotion.getRotation().gamma);
367export const rotationX = rotationBeta;
368export const rotationY = rotationGamma;
369export const rotationZ = rotationAlpha;
370
371// Aliases for shorter names
372export const rotA = rotationAlpha;
373export const rotB = rotationBeta;
374export const rotG = rotationGamma;
375export const rotX = rotationX;
376export const rotY = rotationY;
377export const rotZ = rotationZ;
378
379// // Bipolar versions (ranging from -1 to 1 instead of 0 to 1)
380// export const accX2 = accX.toBipolar();
381// export const accY2 = accY.toBipolar();
382// export const accZ2 = accZ.toBipolar();
383
384// export const rotA2 = rotA.toBipolar();
385// export const rotB2 = rotB.toBipolar();
386// export const rotG2 = rotG.toBipolar();