mirror of
https://github.com/eliasstepanik/strudel-docker.git
synced 2026-01-22 19:18:31 +00:00
Merge pull request #1217 from nkymut/devicemotion
Add Device Motion module
This commit is contained in:
commit
f26a2af38e
72
packages/motion/README.md
Normal file
72
packages/motion/README.md
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
# @strudel/motion
|
||||||
|
|
||||||
|
This package adds device motion sensing functionality to strudel Patterns.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm i @strudel/motion --save
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
| Motion | Long Names & Aliases | Description |
|
||||||
|
|----------------------------|-----------------------------------------------------------|------------------------------------------|
|
||||||
|
| Acceleration | accelerationX (accX), accelerationY (accY), accelerationZ (accZ) | X, Y, Z-axis acceleration values |
|
||||||
|
| Gravity | gravityX (gravX), gravityY (gravY), gravityZ (gravZ) | X, Y, Z-axis gravity values |
|
||||||
|
| Rotation | rotationAlpha (rotA, rotZ), rotationBeta (rotB, rotX), rotationGamma (rotG, rotY) | Rotation around alpha, beta, gamma axes and mapped to X, Y, Z |
|
||||||
|
| Orientation | orientationAlpha (oriA, oriZ), orientationBeta (oriB, oriX), orientationGamma (oriG, oriY) | Orientation alpha, beta, gamma values and mapped to X, Y, Z |
|
||||||
|
| Absolute Orientation | absoluteOrientationAlpha (absOriA, absOriZ), absoluteOrientationBeta (absOriB, absOriX), absoluteOrientationGamma (absOriG, absOriY) | Absolute orientation alpha, beta, gamma values and mapped to X, Y, Z |
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
```js
|
||||||
|
enableMotion() //enable DeviceMotion
|
||||||
|
|
||||||
|
setcpm(200/4)
|
||||||
|
|
||||||
|
$_: accX.segment(16).gain().log()
|
||||||
|
|
||||||
|
$:n("0 1 3 1 5 4")
|
||||||
|
.scale("Bb:lydian")
|
||||||
|
.sometimesBy(0.5,sub(note(12)))
|
||||||
|
.lpf(gravityY.range(20,1000))
|
||||||
|
.lpq(gravityZ.range(1,30))
|
||||||
|
.lpenv(gravityX.range(2,2))
|
||||||
|
.gain(oriX.range(0.2,0.8))
|
||||||
|
.room(oriZ.range(0,0.5))
|
||||||
|
.attack(oriY.range(0,0.3))
|
||||||
|
.delay(rotG.range(0,1))
|
||||||
|
.decay(rotA.range(0,1))
|
||||||
|
.attack(rotB.range(0,0.1))
|
||||||
|
.sound("sawtooth")
|
||||||
|
```
|
||||||
|
|
||||||
|
## Setup SSL for Local Development
|
||||||
|
|
||||||
|
`DeviceMotionEvent` only works with HTTPS, so you'll need to enable SSL for local development.
|
||||||
|
Try installing an SSL plugin for Vite.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd website
|
||||||
|
pnpm install -D @vitejs/plugin-basic-ssl
|
||||||
|
```
|
||||||
|
|
||||||
|
add the basicSsl plugin to the defineConfig block in `strudel/website/astro.config.mjs`
|
||||||
|
|
||||||
|
```js
|
||||||
|
vite: {
|
||||||
|
plugins: [basicSsl()],
|
||||||
|
server: {
|
||||||
|
host: '0.0.0.0', // Ensures it binds to all network interfaces
|
||||||
|
// https: {
|
||||||
|
// key: '../../key.pem', //
|
||||||
|
// cert: '../../cert.pem',
|
||||||
|
// },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
```
|
||||||
|
|
||||||
|
generate an SSL certificate to avoid security warnings.
|
||||||
|
|
||||||
|
`openssl req -new -newkey rsa:2048 -days 365 -nodes -x509 -keyout key.pem -out cert.pem`
|
||||||
82
packages/motion/docs/devicemotion.mdx
Normal file
82
packages/motion/docs/devicemotion.mdx
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { MiniRepl } from '../../../website/src/docs/MiniRepl';
|
||||||
|
import { JsDoc } from '../../../website/src/docs/JsDoc';
|
||||||
|
|
||||||
|
# Device Motion
|
||||||
|
|
||||||
|
Devicemotion module allows you to use your mobile device's motion sensors (accelerometer, gyroscope, and orientation sensors) to control musical parameters in real-time. This creates opportunities for expressive, movement-based musical interactions.
|
||||||
|
|
||||||
|
## Basic Setup
|
||||||
|
|
||||||
|
First, you need to enable device motion sensing:
|
||||||
|
|
||||||
|
<MiniRepl client:idle tune={`enableMotion()`} />
|
||||||
|
|
||||||
|
This will prompt the user for permission to access device motion sensors.
|
||||||
|
|
||||||
|
## Available Motion Parameters
|
||||||
|
|
||||||
|
You can access different types of motion data:
|
||||||
|
|
||||||
|
| Motion | Long Names & Aliases | Description |
|
||||||
|
| -------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
|
| Acceleration | accelerationX (accX), accelerationY (accY), accelerationZ (accZ) | Measures linear acceleration of the device, excluding gravity. Raw values are normalized from g-force. |
|
||||||
|
| Gravity | gravityX (gravX), gravityY (gravY), gravityZ (gravZ) | Indicates device's orientation relative to Earth's gravity. Raw values are normalized from ±9.81 m/s². |
|
||||||
|
| Rotation | rotationAlpha (rotA, rotZ), rotationBeta (rotB, rotX), rotationGamma (rotG, rotY) | Measures rotation rate around each axis. Raw values (±180°/s) are normalized. |
|
||||||
|
| Orientation | orientationAlpha (oriA, oriZ), orientationBeta (oriB, oriX), orientationGamma (oriG, oriY) | Relative orientation from its starting device position. Normalized from:<br/>- Alpha: 0° to 360°<br/>- Beta: -180° to 180°<br/>- Gamma: -90° to 90° |
|
||||||
|
| Absolute Orientation | absoluteOrientationAlpha (absOriA, absOriZ), absoluteOrientationBeta (absOriB, absOriX), absoluteOrientationGamma (absOriG, absOriY) | **Not available for iOS** <br/> Earth-referenced orientation using magnetometer. Same normalization as Orientation. |
|
||||||
|
|
||||||
|
Note:
|
||||||
|
|
||||||
|
- All motion values are normalized to a range of 0 to 1.
|
||||||
|
- Not all devices have the same sensors available
|
||||||
|
Check [DeviceMotionEvent API](https://developer.mozilla.org/en-US/docs/Web/API/DeviceMotionEvent) for browser compatibility
|
||||||
|
- Refer to [Oritentation and motion data explained](https://developer.mozilla.org/en-US/docs/Web/API/Device_orientation_events/Orientation_and_motion_data_explained) for more details
|
||||||
|
|
||||||
|
### Orientation vs Absolute Orientation
|
||||||
|
|
||||||
|
The key difference between regular orientation and absolute orientation is:
|
||||||
|
|
||||||
|
- Regular orientation (`oriX/Y/Z`) measures relative changes in device orientation from its starting position
|
||||||
|
- Absolute orientation (`absOriX/Y/Z`) measures orientation relative to Earth's magnetic field and gravity, providing consistent absolute values regardless of starting position
|
||||||
|
|
||||||
|
For example, if you rotate your device 90 degrees clockwise and then back:
|
||||||
|
|
||||||
|
- Regular orientation will show a change during rotation but return to initial values
|
||||||
|
- Absolute orientation will show the actual compass heading throughout
|
||||||
|
|
||||||
|
This makes absolute orientation particularly useful for creating direction-based musical interactions - for example, performers facing north could play one melody while those facing south play another, creating spatially-aware ensemble performances. Regular orientation, on the other hand, is better suited for detecting relative motion and gestures regardless of which direction the performer is facing.
|
||||||
|
|
||||||
|
## Basic Example
|
||||||
|
|
||||||
|
Here's a simple example that uses device motion to control a synthesizer:
|
||||||
|
|
||||||
|
<MiniRepl
|
||||||
|
client:idle
|
||||||
|
tune={`enableMotion()
|
||||||
|
// Create a simple melody
|
||||||
|
$:n("0 1 3 5")
|
||||||
|
.scale("C:major")
|
||||||
|
// Use tilt (gravity) to control filter
|
||||||
|
.lpf(gravityY.range(200, 2000)) // tilt forward/back for filter cutoff
|
||||||
|
// Use rotation to control effects
|
||||||
|
.room(rotZ.range(0, 0.8)) // rotate device for reverb amount
|
||||||
|
.gain(oriX.range(0.2, 0.8)) // tilt left/right for volume
|
||||||
|
.sound("sawtooth")`}
|
||||||
|
/>
|
||||||
|
|
||||||
|
## Tips for Using Motion Controls
|
||||||
|
|
||||||
|
1. Use `.range(min, max)` to map sensor values to musically useful ranges
|
||||||
|
2. Consider using `.segment()` to smooth out rapid changes in sensor values
|
||||||
|
|
||||||
|
## Debugging
|
||||||
|
|
||||||
|
You can use `segment(16).log()` to see the raw values from any motion sensor:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
$_: accX.segment(16).log(); // logs acceleration values to the console
|
||||||
|
```
|
||||||
|
|
||||||
|
This is helpful when calibrating your ranges and understanding how your device responds to different movements.
|
||||||
|
|
||||||
|
Remember that device motion works best on mobile devices and may not be available on all desktop browsers. Always test your motion-controlled pieces on the target device type!
|
||||||
3
packages/motion/index.mjs
Normal file
3
packages/motion/index.mjs
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import './motion.mjs';
|
||||||
|
|
||||||
|
export * from './motion.mjs';
|
||||||
371
packages/motion/motion.mjs
Normal file
371
packages/motion/motion.mjs
Normal file
@ -0,0 +1,371 @@
|
|||||||
|
// motion.mjs
|
||||||
|
|
||||||
|
import { signal } from '../core/signal.mjs';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The accelerometer's x-axis value ranges from 0 to 1.
|
||||||
|
* @name accelerationX
|
||||||
|
* @return {Pattern}
|
||||||
|
* @synonyms accX
|
||||||
|
* @example
|
||||||
|
* n(accelerationX.segment(4).range(0,7)).scale("C:minor")
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The accelerometer's y-axis value ranges from 0 to 1.
|
||||||
|
* @name accelerationY
|
||||||
|
* @return {Pattern}
|
||||||
|
* @synonyms accY
|
||||||
|
* @example
|
||||||
|
* n(accelerationY.segment(4).range(0,7)).scale("C:minor")
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The accelerometer's z-axis value ranges from 0 to 1.
|
||||||
|
* @name accelerationZ
|
||||||
|
* @return {Pattern}
|
||||||
|
* @synonyms accZ
|
||||||
|
* @example
|
||||||
|
* n(accelerationZ.segment(4).range(0,7)).scale("C:minor")
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The device's gravity x-axis value ranges from 0 to 1.
|
||||||
|
* @name gravityX
|
||||||
|
* @return {Pattern}
|
||||||
|
* @synonyms gravX
|
||||||
|
* @example
|
||||||
|
* n(gravityX.segment(4).range(0,7)).scale("C:minor")
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The device's gravity y-axis value ranges from 0 to 1.
|
||||||
|
* @name gravityY
|
||||||
|
* @return {Pattern}
|
||||||
|
* @synonyms gravY
|
||||||
|
* @example
|
||||||
|
* n(gravityY.segment(4).range(0,7)).scale("C:minor")
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The device's gravity z-axis value ranges from 0 to 1.
|
||||||
|
* @name gravityZ
|
||||||
|
* @return {Pattern}
|
||||||
|
* @synonyms gravZ
|
||||||
|
* @example
|
||||||
|
* n(gravityZ.segment(4).range(0,7)).scale("C:minor")
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The device's rotation around the alpha-axis value ranges from 0 to 1.
|
||||||
|
* @name rotationAlpha
|
||||||
|
* @return {Pattern}
|
||||||
|
* @synonyms rotA, rotZ, rotationZ
|
||||||
|
* @example
|
||||||
|
* n(rotationAlpha.segment(4).range(0,7)).scale("C:minor")
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The device's rotation around the beta-axis value ranges from 0 to 1.
|
||||||
|
* @name rotationBeta
|
||||||
|
* @return {Pattern}
|
||||||
|
* @synonyms rotB, rotX, rotationX
|
||||||
|
* @example
|
||||||
|
* n(rotationBeta.segment(4).range(0,7)).scale("C:minor")
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The device's rotation around the gamma-axis value ranges from 0 to 1.
|
||||||
|
* @name rotationGamma
|
||||||
|
* @return {Pattern}
|
||||||
|
* @synonyms rotG, rotY, rotationY
|
||||||
|
* @example
|
||||||
|
* n(rotationGamma.segment(4).range(0,7)).scale("C:minor")
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The device's orientation alpha value ranges from 0 to 1.
|
||||||
|
* @name orientationAlpha
|
||||||
|
* @return {Pattern}
|
||||||
|
* @synonyms oriA, oriZ, orientationZ
|
||||||
|
* @example
|
||||||
|
* n(orientationAlpha.segment(4).range(0,7)).scale("C:minor")
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The device's orientation beta value ranges from 0 to 1.
|
||||||
|
* @name orientationBeta
|
||||||
|
* @return {Pattern}
|
||||||
|
* @synonyms oriB, oriX, orientationX
|
||||||
|
* @example
|
||||||
|
* n(orientationBeta.segment(4).range(0,7)).scale("C:minor")
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The device's orientation gamma value ranges from 0 to 1.
|
||||||
|
* @name orientationGamma
|
||||||
|
* @return {Pattern}
|
||||||
|
* @synonyms oriG, oriY, orientationY
|
||||||
|
* @example
|
||||||
|
* n(orientationGamma.segment(4).range(0,7)).scale("C:minor")
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The device's absolute orientation alpha value ranges from 0 to 1.
|
||||||
|
* @name absoluteOrientationAlpha
|
||||||
|
* @return {Pattern}
|
||||||
|
* @synonyms absOriA, absOriZ, absoluteOrientationZ
|
||||||
|
* @example
|
||||||
|
* n(absoluteOrientationAlpha.segment(4).range(0,7)).scale("C:minor")
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The device's absolute orientation beta value ranges from 0 to 1.
|
||||||
|
* @name absoluteOrientationBeta
|
||||||
|
* @return {Pattern}
|
||||||
|
* @synonyms absOriB, absOriX, absoluteOrientationX
|
||||||
|
* @example
|
||||||
|
* n(absoluteOrientationBeta.segment(4).range(0,7)).scale("C:minor")
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The device's absolute orientation gamma value ranges from 0 to 1.
|
||||||
|
* @name absoluteOrientationGamma
|
||||||
|
* @return {Pattern}
|
||||||
|
* @synonyms absOriG, absOriY, absoluteOrientationY
|
||||||
|
* @example
|
||||||
|
* n(absoluteOrientationGamma.segment(4).range(0,7)).scale("C:minor")
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
class DeviceMotionHandler {
|
||||||
|
constructor() {
|
||||||
|
this.GRAVITY = 9.81;
|
||||||
|
|
||||||
|
// Initialize sensor values
|
||||||
|
this._acceleration = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
this._gravity = {
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
z: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
this._rotation = {
|
||||||
|
alpha: 0,
|
||||||
|
beta: 0,
|
||||||
|
gamma: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
this._orientation = {
|
||||||
|
alpha: 0,
|
||||||
|
beta: 0,
|
||||||
|
gamma: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
this._absoluteOrientation = {
|
||||||
|
alpha: 0,
|
||||||
|
beta: 0,
|
||||||
|
gamma: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
this._permissionStatus = 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
async requestPermissions() {
|
||||||
|
if (typeof DeviceMotionEvent?.requestPermission === 'function') {
|
||||||
|
try {
|
||||||
|
// iOS requires explicit permission
|
||||||
|
const motionPermission = await DeviceMotionEvent.requestPermission();
|
||||||
|
const orientationPermission = await DeviceOrientationEvent.requestPermission();
|
||||||
|
|
||||||
|
this._permissionStatus =
|
||||||
|
motionPermission === 'granted' && orientationPermission === 'granted' ? 'granted' : 'denied';
|
||||||
|
this.setupEventListeners();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Permission request failed:', error);
|
||||||
|
this._permissionStatus = 'denied';
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._permissionStatus = 'granted';
|
||||||
|
this.setupEventListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setupEventListeners() {
|
||||||
|
if (this._permissionStatus === 'granted') {
|
||||||
|
// Device Motion handler
|
||||||
|
window.addEventListener('devicemotion', this.handleDeviceMotion.bind(this), true);
|
||||||
|
window.addEventListener('deviceorientation', this.handleDeviceOrientation.bind(this), true);
|
||||||
|
window.addEventListener('deviceorientationabsolute', this.handleAbsoluteDeviceOrientation.bind(this), true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDeviceMotion(event) {
|
||||||
|
//console.log(event);
|
||||||
|
if (event.acceleration) {
|
||||||
|
// Normalize acceleration values to 0-1 range
|
||||||
|
this._acceleration.x = (event.acceleration.x + 1) / 2;
|
||||||
|
this._acceleration.y = (event.acceleration.y + 1) / 2;
|
||||||
|
this._acceleration.z = (event.acceleration.z + 1) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.accelerationIncludingGravity) {
|
||||||
|
// Normalize acceleration values to 0-1 range
|
||||||
|
this._gravity.x = (event.accelerationIncludingGravity.x + this.GRAVITY) / (2 * this.GRAVITY);
|
||||||
|
this._gravity.y = (event.accelerationIncludingGravity.y + this.GRAVITY) / (2 * this.GRAVITY);
|
||||||
|
this._gravity.z = (event.accelerationIncludingGravity.z + this.GRAVITY) / (2 * this.GRAVITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.rotationRate) {
|
||||||
|
// Normalize rotation values to 0-1 range
|
||||||
|
this._rotation.alpha = (event.rotationRate.alpha + 180) / 360;
|
||||||
|
this._rotation.beta = (event.rotationRate.beta + 180) / 360;
|
||||||
|
this._rotation.gamma = (event.rotationRate.gamma + 180) / 360;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDeviceOrientation(event) {
|
||||||
|
this._orientation.alpha = event.alpha / 360; //a(0~360)
|
||||||
|
this._orientation.beta = (event.beta + 180) / 360; //b(-180~180)
|
||||||
|
this._orientation.gamma = (event.gamma + 90) / 180; //g(-90~90)
|
||||||
|
}
|
||||||
|
|
||||||
|
handleAbsoluteDeviceOrientation(event) {
|
||||||
|
this._absoluteOrientation.alpha = event.alpha / 360; //a(0~360)
|
||||||
|
this._absoluteOrientation.beta = (event.beta + 180) / 360; //b(-180~180)
|
||||||
|
this._absoluteOrientation.gamma = (event.gamma + 90) / 180; //g(-90~90)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Getter methods for current values
|
||||||
|
getAcceleration() {
|
||||||
|
return this._acceleration;
|
||||||
|
}
|
||||||
|
getGravity() {
|
||||||
|
return this._gravity;
|
||||||
|
}
|
||||||
|
getRotation() {
|
||||||
|
return this._rotation;
|
||||||
|
}
|
||||||
|
getOrientation() {
|
||||||
|
return this._orientation;
|
||||||
|
}
|
||||||
|
getAbsoluteOrientation() {
|
||||||
|
return this._absoluteOrientation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create singleton instance
|
||||||
|
const deviceMotion = new DeviceMotionHandler();
|
||||||
|
|
||||||
|
// Export a function to request permission
|
||||||
|
export async function enableMotion() {
|
||||||
|
return deviceMotion.requestPermissions();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create signals for acceleration
|
||||||
|
export const accelerationX = signal(() => deviceMotion.getAcceleration().x);
|
||||||
|
export const accelerationY = signal(() => deviceMotion.getAcceleration().y);
|
||||||
|
export const accelerationZ = signal(() => deviceMotion.getAcceleration().z);
|
||||||
|
|
||||||
|
// Aliases for shorter names
|
||||||
|
export const accX = accelerationX;
|
||||||
|
export const accY = accelerationY;
|
||||||
|
export const accZ = accelerationZ;
|
||||||
|
|
||||||
|
// Create signals for gravity
|
||||||
|
export const gravityX = signal(() => deviceMotion.getGravity().x);
|
||||||
|
export const gravityY = signal(() => deviceMotion.getGravity().y);
|
||||||
|
export const gravityZ = signal(() => deviceMotion.getGravity().z);
|
||||||
|
|
||||||
|
// Aliases for shorter names
|
||||||
|
export const gravX = gravityX;
|
||||||
|
export const gravY = gravityY;
|
||||||
|
export const gravZ = gravityZ;
|
||||||
|
|
||||||
|
// Create signals for orientation
|
||||||
|
export const orientationAlpha = signal(() => deviceMotion.getOrientation().alpha);
|
||||||
|
export const orientationBeta = signal(() => deviceMotion.getOrientation().beta);
|
||||||
|
export const orientationGamma = signal(() => deviceMotion.getOrientation().gamma);
|
||||||
|
// Aliases for shorter names
|
||||||
|
export const orientationA = orientationAlpha;
|
||||||
|
export const orientationB = orientationBeta;
|
||||||
|
export const orientationG = orientationGamma;
|
||||||
|
|
||||||
|
// Aliases mapping to X,Y,Z coordinates
|
||||||
|
export const orientationX = orientationBeta;
|
||||||
|
export const orientationY = orientationGamma;
|
||||||
|
export const orientationZ = orientationAlpha;
|
||||||
|
|
||||||
|
// Short aliases for A,B,G,X,Y,Z
|
||||||
|
|
||||||
|
export const oriA = orientationAlpha;
|
||||||
|
export const oriB = orientationBeta;
|
||||||
|
export const oriG = orientationGamma;
|
||||||
|
|
||||||
|
export const oriX = orientationX;
|
||||||
|
export const oriY = orientationY;
|
||||||
|
export const oriZ = orientationZ;
|
||||||
|
|
||||||
|
// Create signals for absolute orientation
|
||||||
|
export const absoluteOrientationAlpha = signal(() => deviceMotion.getAbsoluteOrientation().alpha);
|
||||||
|
export const absoluteOrientationBeta = signal(() => deviceMotion.getAbsoluteOrientation().beta);
|
||||||
|
export const absoluteOrientationGamma = signal(() => deviceMotion.getAbsoluteOrientation().gamma);
|
||||||
|
|
||||||
|
// Aliases for shorter names
|
||||||
|
export const absOriA = absoluteOrientationAlpha;
|
||||||
|
export const absOriB = absoluteOrientationBeta;
|
||||||
|
export const absOriG = absoluteOrientationGamma;
|
||||||
|
|
||||||
|
// Aliases mapping to X,Y,Z coordinates
|
||||||
|
export const absoluteOrientationX = absoluteOrientationBeta;
|
||||||
|
export const absoluteOrientationY = absoluteOrientationGamma;
|
||||||
|
export const absoluteOrientationZ = absoluteOrientationAlpha;
|
||||||
|
|
||||||
|
// Short aliases for X,Y,Z
|
||||||
|
export const absOriX = absoluteOrientationX;
|
||||||
|
export const absOriY = absoluteOrientationY;
|
||||||
|
export const absOriZ = absoluteOrientationZ;
|
||||||
|
|
||||||
|
// Create signals for rotation
|
||||||
|
export const rotationAlpha = signal(() => deviceMotion.getRotation().alpha);
|
||||||
|
export const rotationBeta = signal(() => deviceMotion.getRotation().beta);
|
||||||
|
export const rotationGamma = signal(() => deviceMotion.getRotation().gamma);
|
||||||
|
export const rotationX = rotationBeta;
|
||||||
|
export const rotationY = rotationGamma;
|
||||||
|
export const rotationZ = rotationAlpha;
|
||||||
|
|
||||||
|
// Aliases for shorter names
|
||||||
|
export const rotA = rotationAlpha;
|
||||||
|
export const rotB = rotationBeta;
|
||||||
|
export const rotG = rotationGamma;
|
||||||
|
export const rotX = rotationX;
|
||||||
|
export const rotY = rotationY;
|
||||||
|
export const rotZ = rotationZ;
|
||||||
|
|
||||||
|
// // Bipolar versions (ranging from -1 to 1 instead of 0 to 1)
|
||||||
|
// export const accX2 = accX.toBipolar();
|
||||||
|
// export const accY2 = accY.toBipolar();
|
||||||
|
// export const accZ2 = accZ.toBipolar();
|
||||||
|
|
||||||
|
// export const rotA2 = rotA.toBipolar();
|
||||||
|
// export const rotB2 = rotB.toBipolar();
|
||||||
|
// export const rotG2 = rotG.toBipolar();
|
||||||
38
packages/motion/package.json
Normal file
38
packages/motion/package.json
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"name": "@strudel/motion",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"description": "DeviceMotion API for strudel",
|
||||||
|
"main": "index.mjs",
|
||||||
|
"type": "module",
|
||||||
|
"publishConfig": {
|
||||||
|
"main": "dist/index.mjs"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "vite build",
|
||||||
|
"prepublishOnly": "npm run build"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/tidalcycles/strudel.git"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"titdalcycles",
|
||||||
|
"strudel",
|
||||||
|
"pattern",
|
||||||
|
"livecoding",
|
||||||
|
"algorave"
|
||||||
|
],
|
||||||
|
"author": "Yuta Nakayama <nkymut@gmail.com>",
|
||||||
|
"license": "AGPL-3.0-or-later",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/tidalcycles/strudel/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/tidalcycles/strudel#readme",
|
||||||
|
"dependencies": {
|
||||||
|
"@strudel/core": "workspace:*"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"vite": "^6.0.11"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
19
packages/motion/vite.config.js
Normal file
19
packages/motion/vite.config.js
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import { defineConfig } from 'vite';
|
||||||
|
import { dependencies } from './package.json';
|
||||||
|
import { resolve } from 'path';
|
||||||
|
|
||||||
|
// https://vitejs.dev/config/
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [],
|
||||||
|
build: {
|
||||||
|
lib: {
|
||||||
|
entry: resolve(__dirname, 'index.mjs'),
|
||||||
|
formats: ['es'],
|
||||||
|
fileName: (ext) => ({ es: 'index.mjs' })[ext],
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
external: [...Object.keys(dependencies)],
|
||||||
|
},
|
||||||
|
target: 'esnext',
|
||||||
|
},
|
||||||
|
});
|
||||||
13
pnpm-lock.yaml
generated
13
pnpm-lock.yaml
generated
@ -331,6 +331,16 @@ importers:
|
|||||||
specifier: ^3.0.4
|
specifier: ^3.0.4
|
||||||
version: 3.0.4(@types/debug@4.1.12)(@types/node@22.10.10)(@vitest/ui@3.0.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0)
|
version: 3.0.4(@types/debug@4.1.12)(@types/node@22.10.10)(@vitest/ui@3.0.4)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0)
|
||||||
|
|
||||||
|
packages/motion:
|
||||||
|
dependencies:
|
||||||
|
'@strudel/core':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../core
|
||||||
|
devDependencies:
|
||||||
|
vite:
|
||||||
|
specifier: ^6.0.11
|
||||||
|
version: 6.0.11(@types/node@22.10.10)(jiti@2.4.2)(lightningcss@1.29.1)(terser@5.37.0)(yaml@2.7.0)
|
||||||
|
|
||||||
packages/mqtt:
|
packages/mqtt:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@strudel/core':
|
'@strudel/core':
|
||||||
@ -639,6 +649,9 @@ importers:
|
|||||||
'@strudel/mini':
|
'@strudel/mini':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../packages/mini
|
version: link:../packages/mini
|
||||||
|
'@strudel/motion':
|
||||||
|
specifier: workspace:*
|
||||||
|
version: link:../packages/motion
|
||||||
'@strudel/mqtt':
|
'@strudel/mqtt':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../packages/mqtt
|
version: link:../packages/mqtt
|
||||||
|
|||||||
@ -2,9 +2,30 @@ import { queryCode } from './runtime.mjs';
|
|||||||
import { describe, it } from 'vitest';
|
import { describe, it } from 'vitest';
|
||||||
import doc from '../doc.json';
|
import doc from '../doc.json';
|
||||||
|
|
||||||
|
const skippedExamples = [
|
||||||
|
'absoluteOrientationGamma',
|
||||||
|
'absoluteOrientationBeta',
|
||||||
|
'absoluteOrientationAlpha',
|
||||||
|
'orientationGamma',
|
||||||
|
'orientationBeta',
|
||||||
|
'orientationAlpha',
|
||||||
|
'rotationGamma',
|
||||||
|
'rotationBeta',
|
||||||
|
'rotationAlpha',
|
||||||
|
'gravityZ',
|
||||||
|
'gravityY',
|
||||||
|
'gravityX',
|
||||||
|
'accelerationZ',
|
||||||
|
'accelerationY',
|
||||||
|
'accelerationX',
|
||||||
|
];
|
||||||
|
|
||||||
describe('runs examples', () => {
|
describe('runs examples', () => {
|
||||||
const { docs } = doc;
|
const { docs } = doc;
|
||||||
docs.forEach(async (doc) => {
|
docs.forEach(async (doc) => {
|
||||||
|
if (skippedExamples.includes(doc.name)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
doc.examples?.forEach((example, i) => {
|
doc.examples?.forEach((example, i) => {
|
||||||
it(`example "${doc.name}" example index ${i}`, async ({ expect }) => {
|
it(`example "${doc.name}" example index ${i}`, async ({ expect }) => {
|
||||||
const haps = await queryCode(example, 4);
|
const haps = await queryCode(example, 4);
|
||||||
|
|||||||
@ -74,72 +74,31 @@ const toneHelpersMocked = {
|
|||||||
highpass: mockNode,
|
highpass: mockNode,
|
||||||
};
|
};
|
||||||
|
|
||||||
strudel.Pattern.prototype.osc = function () {
|
[
|
||||||
return this;
|
'osc',
|
||||||
};
|
'csound',
|
||||||
strudel.Pattern.prototype.csound = function () {
|
'tone',
|
||||||
return this;
|
'webdirt',
|
||||||
};
|
'pianoroll',
|
||||||
strudel.Pattern.prototype.tone = function () {
|
'speak',
|
||||||
return this;
|
'wave',
|
||||||
};
|
'filter',
|
||||||
strudel.Pattern.prototype.webdirt = function () {
|
'adsr',
|
||||||
return this;
|
'webaudio',
|
||||||
};
|
'soundfont',
|
||||||
|
'tune',
|
||||||
// draw mock
|
'midi',
|
||||||
strudel.Pattern.prototype.pianoroll = function () {
|
'_scope',
|
||||||
return this;
|
'_spiral',
|
||||||
};
|
'_pitchwheel',
|
||||||
|
'_pianoroll',
|
||||||
// speak mock
|
'_spectrum',
|
||||||
strudel.Pattern.prototype.speak = function () {
|
'markcss',
|
||||||
return this;
|
].forEach((mock) => {
|
||||||
};
|
strudel.Pattern.prototype[mock] = function () {
|
||||||
|
return this;
|
||||||
// webaudio mock
|
};
|
||||||
strudel.Pattern.prototype.wave = function () {
|
});
|
||||||
return this;
|
|
||||||
};
|
|
||||||
strudel.Pattern.prototype.filter = function () {
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
strudel.Pattern.prototype.adsr = function () {
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
strudel.Pattern.prototype.webaudio = function () {
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
strudel.Pattern.prototype.soundfont = function () {
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
// tune mock
|
|
||||||
strudel.Pattern.prototype.tune = function () {
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
strudel.Pattern.prototype.midi = function () {
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
strudel.Pattern.prototype._scope = function () {
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
strudel.Pattern.prototype._spiral = function () {
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
strudel.Pattern.prototype._pitchwheel = function () {
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
strudel.Pattern.prototype._pianoroll = function () {
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
strudel.Pattern.prototype._spectrum = function () {
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
strudel.Pattern.prototype.markcss = function () {
|
|
||||||
return this;
|
|
||||||
};
|
|
||||||
|
|
||||||
const uiHelpersMocked = {
|
const uiHelpersMocked = {
|
||||||
backgroundImage: id,
|
backgroundImage: id,
|
||||||
@ -193,7 +152,6 @@ evalScope(
|
|||||||
loadcsound,
|
loadcsound,
|
||||||
setcps: id,
|
setcps: id,
|
||||||
Clock: {}, // whatever
|
Clock: {}, // whatever
|
||||||
// Tone,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@ -32,6 +32,7 @@
|
|||||||
"@strudel/hydra": "workspace:*",
|
"@strudel/hydra": "workspace:*",
|
||||||
"@strudel/midi": "workspace:*",
|
"@strudel/midi": "workspace:*",
|
||||||
"@strudel/mini": "workspace:*",
|
"@strudel/mini": "workspace:*",
|
||||||
|
"@strudel/motion": "workspace:*",
|
||||||
"@strudel/mqtt": "workspace:*",
|
"@strudel/mqtt": "workspace:*",
|
||||||
"@strudel/osc": "workspace:*",
|
"@strudel/osc": "workspace:*",
|
||||||
"@strudel/serial": "workspace:*",
|
"@strudel/serial": "workspace:*",
|
||||||
|
|||||||
@ -84,6 +84,7 @@ export const SIDEBAR: Sidebar = {
|
|||||||
{ text: 'Music metadata', link: 'learn/metadata' },
|
{ text: 'Music metadata', link: 'learn/metadata' },
|
||||||
{ text: 'CSound', link: 'learn/csound' },
|
{ text: 'CSound', link: 'learn/csound' },
|
||||||
{ text: 'Hydra', link: 'learn/hydra' },
|
{ text: 'Hydra', link: 'learn/hydra' },
|
||||||
|
{ text: 'Device Motion', link: 'learn/devicemotion' },
|
||||||
],
|
],
|
||||||
'Pattern Functions': [
|
'Pattern Functions': [
|
||||||
{ text: 'Introduction', link: 'functions/intro' },
|
{ text: 'Introduction', link: 'functions/intro' },
|
||||||
|
|||||||
10
website/src/pages/learn/devicemotion.mdx
Normal file
10
website/src/pages/learn/devicemotion.mdx
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
---
|
||||||
|
title: Device Motion
|
||||||
|
layout: ../../layouts/MainLayout.astro
|
||||||
|
---
|
||||||
|
|
||||||
|
import { MiniRepl } from '../../docs/MiniRepl';
|
||||||
|
import { JsDoc } from '../../docs/JsDoc';
|
||||||
|
import DeviceMotion from '../../../../packages/motion/docs/devicemotion.mdx';
|
||||||
|
|
||||||
|
<DeviceMotion />
|
||||||
@ -81,6 +81,7 @@ export function loadModules() {
|
|||||||
import('@strudel/soundfonts'),
|
import('@strudel/soundfonts'),
|
||||||
import('@strudel/csound'),
|
import('@strudel/csound'),
|
||||||
import('@strudel/tidal'),
|
import('@strudel/tidal'),
|
||||||
|
import('@strudel/motion'),
|
||||||
import('@strudel/mqtt'),
|
import('@strudel/mqtt'),
|
||||||
];
|
];
|
||||||
if (isTauri()) {
|
if (isTauri()) {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user