diff --git a/packages/motion/README.md b/packages/motion/README.md
new file mode 100644
index 000000000..4fc343f93
--- /dev/null
+++ b/packages/motion/README.md
@@ -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`
diff --git a/packages/motion/docs/devicemotion.mdx b/packages/motion/docs/devicemotion.mdx
new file mode 100644
index 000000000..9afccc890
--- /dev/null
+++ b/packages/motion/docs/devicemotion.mdx
@@ -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:
+
+
+
+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:
- Alpha: 0° to 360°
- Beta: -180° to 180°
- Gamma: -90° to 90° |
+| Absolute Orientation | absoluteOrientationAlpha (absOriA, absOriZ), absoluteOrientationBeta (absOriB, absOriX), absoluteOrientationGamma (absOriG, absOriY) | **Not available for iOS**
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:
+
+
+
+## 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!
diff --git a/packages/motion/index.mjs b/packages/motion/index.mjs
new file mode 100644
index 000000000..b315b6173
--- /dev/null
+++ b/packages/motion/index.mjs
@@ -0,0 +1,3 @@
+import './motion.mjs';
+
+export * from './motion.mjs';
diff --git a/packages/motion/motion.mjs b/packages/motion/motion.mjs
new file mode 100644
index 000000000..875704eae
--- /dev/null
+++ b/packages/motion/motion.mjs
@@ -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();
diff --git a/packages/motion/package.json b/packages/motion/package.json
new file mode 100644
index 000000000..cc117a268
--- /dev/null
+++ b/packages/motion/package.json
@@ -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 ",
+ "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"
+ }
+}
+
\ No newline at end of file
diff --git a/packages/motion/vite.config.js b/packages/motion/vite.config.js
new file mode 100644
index 000000000..5df3edc1b
--- /dev/null
+++ b/packages/motion/vite.config.js
@@ -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',
+ },
+});
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index f85e3ab6e..494d04f0c 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -331,6 +331,16 @@ importers:
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)
+ 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:
dependencies:
'@strudel/core':
@@ -639,6 +649,9 @@ importers:
'@strudel/mini':
specifier: workspace:*
version: link:../packages/mini
+ '@strudel/motion':
+ specifier: workspace:*
+ version: link:../packages/motion
'@strudel/mqtt':
specifier: workspace:*
version: link:../packages/mqtt
diff --git a/test/examples.test.mjs b/test/examples.test.mjs
index 44ffdd5a9..997c1cf37 100644
--- a/test/examples.test.mjs
+++ b/test/examples.test.mjs
@@ -2,9 +2,30 @@ import { queryCode } from './runtime.mjs';
import { describe, it } from 'vitest';
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', () => {
const { docs } = doc;
docs.forEach(async (doc) => {
+ if (skippedExamples.includes(doc.name)) {
+ return;
+ }
doc.examples?.forEach((example, i) => {
it(`example "${doc.name}" example index ${i}`, async ({ expect }) => {
const haps = await queryCode(example, 4);
diff --git a/test/runtime.mjs b/test/runtime.mjs
index 18d4a29e8..b0a329a47 100644
--- a/test/runtime.mjs
+++ b/test/runtime.mjs
@@ -74,72 +74,31 @@ const toneHelpersMocked = {
highpass: mockNode,
};
-strudel.Pattern.prototype.osc = function () {
- return this;
-};
-strudel.Pattern.prototype.csound = function () {
- return this;
-};
-strudel.Pattern.prototype.tone = function () {
- return this;
-};
-strudel.Pattern.prototype.webdirt = function () {
- return this;
-};
-
-// draw mock
-strudel.Pattern.prototype.pianoroll = function () {
- return this;
-};
-
-// speak mock
-strudel.Pattern.prototype.speak = 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;
-};
+[
+ 'osc',
+ 'csound',
+ 'tone',
+ 'webdirt',
+ 'pianoroll',
+ 'speak',
+ 'wave',
+ 'filter',
+ 'adsr',
+ 'webaudio',
+ 'soundfont',
+ 'tune',
+ 'midi',
+ '_scope',
+ '_spiral',
+ '_pitchwheel',
+ '_pianoroll',
+ '_spectrum',
+ 'markcss',
+].forEach((mock) => {
+ strudel.Pattern.prototype[mock] = function () {
+ return this;
+ };
+});
const uiHelpersMocked = {
backgroundImage: id,
@@ -193,7 +152,6 @@ evalScope(
loadcsound,
setcps: id,
Clock: {}, // whatever
- // Tone,
},
);
diff --git a/website/package.json b/website/package.json
index 53a66e945..411581f8d 100644
--- a/website/package.json
+++ b/website/package.json
@@ -32,6 +32,7 @@
"@strudel/hydra": "workspace:*",
"@strudel/midi": "workspace:*",
"@strudel/mini": "workspace:*",
+ "@strudel/motion": "workspace:*",
"@strudel/mqtt": "workspace:*",
"@strudel/osc": "workspace:*",
"@strudel/serial": "workspace:*",
diff --git a/website/src/config.ts b/website/src/config.ts
index a404f542a..fd6cc479f 100644
--- a/website/src/config.ts
+++ b/website/src/config.ts
@@ -84,6 +84,7 @@ export const SIDEBAR: Sidebar = {
{ text: 'Music metadata', link: 'learn/metadata' },
{ text: 'CSound', link: 'learn/csound' },
{ text: 'Hydra', link: 'learn/hydra' },
+ { text: 'Device Motion', link: 'learn/devicemotion' },
],
'Pattern Functions': [
{ text: 'Introduction', link: 'functions/intro' },
diff --git a/website/src/pages/learn/devicemotion.mdx b/website/src/pages/learn/devicemotion.mdx
new file mode 100644
index 000000000..cd54a7cf8
--- /dev/null
+++ b/website/src/pages/learn/devicemotion.mdx
@@ -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';
+
+
diff --git a/website/src/repl/util.mjs b/website/src/repl/util.mjs
index b91273f97..f623e468f 100644
--- a/website/src/repl/util.mjs
+++ b/website/src/repl/util.mjs
@@ -81,6 +81,7 @@ export function loadModules() {
import('@strudel/soundfonts'),
import('@strudel/csound'),
import('@strudel/tidal'),
+ import('@strudel/motion'),
import('@strudel/mqtt'),
];
if (isTauri()) {