From d741b4d17a2e2150ee4aa8c713d99baaa4290409 Mon Sep 17 00:00:00 2001 From: nkymut Date: Wed, 27 Nov 2024 01:03:09 +0800 Subject: [PATCH] Add GamePad Module --- packages/gamepad/README.md | 93 ++++++ packages/gamepad/gamepad.mjs | 237 ++++++++++++++++ packages/gamepad/index.mjs | 3 + packages/gamepad/package.json | 38 +++ packages/gamepad/vite.config.js | 19 ++ test/__snapshots__/examples.test.mjs.snap | 329 ++++++++++++++++++++++ test/runtime.mjs | 4 + website/package.json | 3 +- website/src/repl/util.mjs | 1 + 9 files changed, 726 insertions(+), 1 deletion(-) create mode 100644 packages/gamepad/README.md create mode 100644 packages/gamepad/gamepad.mjs create mode 100644 packages/gamepad/index.mjs create mode 100644 packages/gamepad/package.json create mode 100644 packages/gamepad/vite.config.js diff --git a/packages/gamepad/README.md b/packages/gamepad/README.md new file mode 100644 index 00000000..18fd0fe5 --- /dev/null +++ b/packages/gamepad/README.md @@ -0,0 +1,93 @@ +# @strudel/gamepad + +This package adds gamepad input functionality to strudel Patterns. + +## Install + +```sh +npm i @strudel/gamepad --save +``` + +## Usage + +```javascript +import { gamepad } from '@strudel/gamepad'; + +// Initialize gamepad (optional index parameter, defaults to 0) +const pad = gamepad(0); + +// Use gamepad inputs in patterns +const pattern = sequence([ + // Button inputs + pad.a, // A button value (0-1) + pad.tglA, // A button toggle (0 or 1) + + // Analog stick inputs + pad.x1, // Left stick X (0-1) + pad.x1_2, // Left stick X (-1 to 1) +]); +``` + +## Available Controls + +### Buttons +- Face Buttons + - `a`, `b`, `x`, `y` (or uppercase `A`, `B`, `X`, `Y`) + - Toggle versions: `tglA`, `tglB`, `tglX`, `tglY` +- Shoulder Buttons + - `lb`, `rb`, `lt`, `rt` (or uppercase `LB`, `RB`, `LT`, `RT`) + - Toggle versions: `tglLB`, `tglRB`, `tglLT`, `tglRT` +- D-Pad + - `up`, `down`, `left`, `right` (or `u`, `d`, `l`, `r` or uppercase) + - Toggle versions: `tglUp`, `tglDown`, `tglLeft`, `tglRight`(or `tglU`, `tglD`, `tglL`, `tglR`) + +### Analog Sticks +- Left Stick + - `x1`, `y1` (0 to 1 range) + - `x1_2`, `y1_2` (-1 to 1 range) +- Right Stick + - `x2`, `y2` (0 to 1 range) + - `x2_2`, `y2_2` (-1 to 1 range) + +## Examples + +```javascript +// Use button values to control amplitude +$: sequence([ + note("bd").gain(pad.X), // X button controls gain + note("[hh oh]").gain(pad.tglY), // Y button toggles gain +]); + +// Use analog stick for continuous control +$: note("c4*4".add(pad.y1_2.range(-24,24))) // Left stick Y controls pitch shift + .pan(pad.x1_2); // Left stick X controls panning + +// Use toggle buttons to switch patterns on/off + +// Define button sequences +const HADOKEN = [ + 'd', // Down + 'r', // Right + 'a', // A +]; + +const KONAMI = 'uuddlrlrba' //Konami Code ↑↑↓↓←→←→BA + +// Add these lines to enable buttons(but why?) +$:pad.D.segment(16).gain(0) +$:pad.R.segment(16).gain(0) +$:pad.A.segment(16).gain(0) + +// Check button sequence (returns 1 when detected, 0 when not within last 1 second) +$: sound("hadoken").gain(pad.checkSequence(HADOKEN)) + +``` + +## Multiple Gamepads + +You can connect multiple gamepads by specifying the gamepad index: + +```javascript +const pad1 = gamepad(0); // First gamepad +const pad2 = gamepad(1); // Second gamepad +``` diff --git a/packages/gamepad/gamepad.mjs b/packages/gamepad/gamepad.mjs new file mode 100644 index 00000000..a6f32894 --- /dev/null +++ b/packages/gamepad/gamepad.mjs @@ -0,0 +1,237 @@ +// @strudel/gamepad/index.mjs + +import { signal } from '@strudel/core'; + +// Button mapping for Logitech Dual Action (STANDARD GAMEPAD Vendor: 046d Product: c216) +export const buttonMap = { + a: 0, + b: 1, + x: 2, + y: 3, + lb: 4, + rb: 5, + lt: 6, + rt: 7, + back: 8, + start: 9, + u: 12, + up: 12, + d: 13, + down: 13, + l: 14, + left: 14, + r: 15, + right: 15, +}; + +class ButtonSequenceDetector { + constructor(timeWindow = 1000) { + this.sequence = []; + this.timeWindow = timeWindow; + this.lastInputTime = 0; + this.buttonStates = Array(16).fill(0); // Track previous state of each button + // Button mapping for character inputs + } + + addInput(buttonIndex, buttonValue) { + const currentTime = Date.now(); + + // Only add input on button press (rising edge) + if (buttonValue === 1 && this.buttonStates[buttonIndex] === 0) { + // Clear sequence if too much time has passed + if (currentTime - this.lastInputTime > this.timeWindow) { + this.sequence = []; + } + + // Store the button name instead of index + const buttonName = Object.keys(buttonMap).find((key) => buttonMap[key] === buttonIndex) || buttonIndex.toString(); + + this.sequence.push({ + input: buttonName, + timestamp: currentTime, + }); + + this.lastInputTime = currentTime; + + //console.log(this.sequence); + // Keep only inputs within the time window + this.sequence = this.sequence.filter((entry) => currentTime - entry.timestamp <= this.timeWindow); + } + + // Update button state + this.buttonStates[buttonIndex] = buttonValue; + } + + checkSequence(targetSequence) { + if (!Array.isArray(targetSequence) && typeof targetSequence !== 'string') { + console.error('ButtonSequenceDetector: targetSequence must be an array or string'); + return 0; + } + + if (this.sequence.length < targetSequence.length) return 0; + + // Convert string input to array if needed + const sequence = + typeof targetSequence === 'string' + ? targetSequence.toLowerCase().split('') + : targetSequence.map((s) => s.toString().toLowerCase()); + + // Get the last n inputs where n is the target sequence length + const lastInputs = this.sequence.slice(-targetSequence.length).map((entry) => entry.input); + + // Compare sequences + return lastInputs.every((input, index) => { + const target = sequence[index]; + // Check if either the input matches directly or they refer to the same button in the map + return ( + input === target || + buttonMap[input] === buttonMap[target] || + // Also check if the numerical index matches + buttonMap[input] === parseInt(target) + ); + }) + ? 1 + : 0; + } +} + +class GamepadHandler { + constructor(index = 0) { + // Add index parameter + this._gamepads = {}; + this._activeGamepad = index; // Use provided index + this._axes = [0, 0, 0, 0]; + this._buttons = Array(16).fill(0); + this.setupEventListeners(); + this.startPolling(); + } + + setupEventListeners() { + window.addEventListener('gamepadconnected', (e) => { + this._gamepads[e.gamepad.index] = e.gamepad; + if (!this._activeGamepad) { + this._activeGamepad = e.gamepad.index; + } + }); + + window.addEventListener('gamepaddisconnected', (e) => { + delete this._gamepads[e.gamepad.index]; + if (this._activeGamepad === e.gamepad.index) { + this._activeGamepad = Object.keys(this._gamepads)[0] || null; + } + }); + } + + startPolling() { + const poll = () => { + if (this._activeGamepad !== null) { + const gamepad = navigator.getGamepads()[this._activeGamepad]; + if (gamepad) { + // Update axes (normalized to 0-1 range) + this._axes = gamepad.axes.map((axis) => (axis + 1) / 2); + // Update buttons + this._buttons = gamepad.buttons.map((button) => button.value); + } + } + requestAnimationFrame(poll); + }; + poll(); + } + + getAxes() { + return this._axes; + } + getButtons() { + return this._buttons; + } +} + +// Store gamepadValues globally +export const gamepadValues = {}; + +// Replace singleton with factory function +export const gamepad = (index = 0) => { + const handler = new GamepadHandler(index); + const sequenceDetector = new ButtonSequenceDetector(2000); + + // Initialize state for this gamepad if it doesn't exist + if (!gamepadValues[index]) { + gamepadValues[index] = Array(16).fill(0); + } + + // Create signals for this specific gamepad instance + const axes = { + x1: signal(() => handler.getAxes()[0]), + y1: signal(() => handler.getAxes()[1]), + x2: signal(() => handler.getAxes()[2]), + y2: signal(() => handler.getAxes()[3]), + }; + + // Add bipolar versions + axes.x1_2 = axes.x1.toBipolar(); + axes.y1_2 = axes.y1.toBipolar(); + axes.x2_2 = axes.x2.toBipolar(); + axes.y2_2 = axes.y2.toBipolar(); + + // Create button signals + const buttons = Array(16) + .fill(null) + .map((_, i) => { + const btn = signal(() => { + const value = handler.getButtons()[i]; + sequenceDetector.addInput(i, value); + return value; + }); + let lastButtonState = 0; + const toggle = signal(() => { + const currentState = handler.getButtons()[i]; + if (currentState === 1 && lastButtonState === 0) { + // Toggle the state + const newValue = gamepadValues[index][i] === 0 ? 1 : 0; + gamepadValues[index][i] = newValue; + // Broadcast the change + window.postMessage({ + type: 'gamepad-toggle', + gamepadIndex: index, + buttonIndex: i, + value: newValue, + }); + } + lastButtonState = currentState; + return gamepadValues[index][i]; + }); + return { value: btn, toggle }; + }); + + const checkSequence = (sequence) => { + return signal(() => sequenceDetector.checkSequence(sequence)); + }; + + // Return an object with all controls + return { + ...axes, + buttons, + ...Object.fromEntries( + Object.entries(buttonMap).flatMap(([key, index]) => [ + [key.toLowerCase(), buttons[index].value], + [key.toUpperCase(), buttons[index].value], + [`tgl${key.toLowerCase()}`, buttons[index].toggle], + [`tgl${key.toUpperCase()}`, buttons[index].toggle], + ]), + ), + checkSequence, + }; +}; + +// Message-based state updates (add this at the bottom of the file) +if (typeof window !== 'undefined') { + window.addEventListener('message', (e) => { + if (e.data.type === 'gamepad-toggle') { + const { gamepadIndex, buttonIndex, value } = e.data; + if (!gamepadValues[gamepadIndex]) { + gamepadValues[gamepadIndex] = Array(16).fill(0); + } + gamepadValues[gamepadIndex][buttonIndex] = value; + } + }); +} diff --git a/packages/gamepad/index.mjs b/packages/gamepad/index.mjs new file mode 100644 index 00000000..c5558fb9 --- /dev/null +++ b/packages/gamepad/index.mjs @@ -0,0 +1,3 @@ +import './gamepad.mjs'; + +export * from './gamepad.mjs'; diff --git a/packages/gamepad/package.json b/packages/gamepad/package.json new file mode 100644 index 00000000..a827aeda --- /dev/null +++ b/packages/gamepad/package.json @@ -0,0 +1,38 @@ +{ + "name": "@strudel/gamepad", + "version": "1.1.0", + "description": "Gamepad Inputs 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": "^5.0.10" + } +} + \ No newline at end of file diff --git a/packages/gamepad/vite.config.js b/packages/gamepad/vite.config.js new file mode 100644 index 00000000..5df3edc1 --- /dev/null +++ b/packages/gamepad/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/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index 3ad6ea01..638628cf 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -565,6 +565,69 @@ exports[`runs examples > example "_euclidRot" example index 20 1`] = ` ] `; +exports[`runs examples > example "absoluteOrientationAlpha" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | note:C3 ]", + "[ 1/4 → 1/2 | note:C3 ]", + "[ 1/2 → 3/4 | note:C3 ]", + "[ 3/4 → 1/1 | note:C3 ]", + "[ 1/1 → 5/4 | note:C3 ]", + "[ 5/4 → 3/2 | note:C3 ]", + "[ 3/2 → 7/4 | note:C3 ]", + "[ 7/4 → 2/1 | note:C3 ]", + "[ 2/1 → 9/4 | note:C3 ]", + "[ 9/4 → 5/2 | note:C3 ]", + "[ 5/2 → 11/4 | note:C3 ]", + "[ 11/4 → 3/1 | note:C3 ]", + "[ 3/1 → 13/4 | note:C3 ]", + "[ 13/4 → 7/2 | note:C3 ]", + "[ 7/2 → 15/4 | note:C3 ]", + "[ 15/4 → 4/1 | note:C3 ]", +] +`; + +exports[`runs examples > example "absoluteOrientationBeta" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | note:C3 ]", + "[ 1/4 → 1/2 | note:C3 ]", + "[ 1/2 → 3/4 | note:C3 ]", + "[ 3/4 → 1/1 | note:C3 ]", + "[ 1/1 → 5/4 | note:C3 ]", + "[ 5/4 → 3/2 | note:C3 ]", + "[ 3/2 → 7/4 | note:C3 ]", + "[ 7/4 → 2/1 | note:C3 ]", + "[ 2/1 → 9/4 | note:C3 ]", + "[ 9/4 → 5/2 | note:C3 ]", + "[ 5/2 → 11/4 | note:C3 ]", + "[ 11/4 → 3/1 | note:C3 ]", + "[ 3/1 → 13/4 | note:C3 ]", + "[ 13/4 → 7/2 | note:C3 ]", + "[ 7/2 → 15/4 | note:C3 ]", + "[ 15/4 → 4/1 | note:C3 ]", +] +`; + +exports[`runs examples > example "absoluteOrientationGamma" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | note:C3 ]", + "[ 1/4 → 1/2 | note:C3 ]", + "[ 1/2 → 3/4 | note:C3 ]", + "[ 3/4 → 1/1 | note:C3 ]", + "[ 1/1 → 5/4 | note:C3 ]", + "[ 5/4 → 3/2 | note:C3 ]", + "[ 3/2 → 7/4 | note:C3 ]", + "[ 7/4 → 2/1 | note:C3 ]", + "[ 2/1 → 9/4 | note:C3 ]", + "[ 9/4 → 5/2 | note:C3 ]", + "[ 5/2 → 11/4 | note:C3 ]", + "[ 11/4 → 3/1 | note:C3 ]", + "[ 3/1 → 13/4 | note:C3 ]", + "[ 13/4 → 7/2 | note:C3 ]", + "[ 7/2 → 15/4 | note:C3 ]", + "[ 15/4 → 4/1 | note:C3 ]", +] +`; + exports[`runs examples > example "accelerate" example index 0 1`] = ` [ "[ 0/1 → 2/1 | s:sax accelerate:0 ]", @@ -572,6 +635,69 @@ exports[`runs examples > example "accelerate" example index 0 1`] = ` ] `; +exports[`runs examples > example "accelerationX" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | note:C3 ]", + "[ 1/4 → 1/2 | note:C3 ]", + "[ 1/2 → 3/4 | note:C3 ]", + "[ 3/4 → 1/1 | note:C3 ]", + "[ 1/1 → 5/4 | note:C3 ]", + "[ 5/4 → 3/2 | note:C3 ]", + "[ 3/2 → 7/4 | note:C3 ]", + "[ 7/4 → 2/1 | note:C3 ]", + "[ 2/1 → 9/4 | note:C3 ]", + "[ 9/4 → 5/2 | note:C3 ]", + "[ 5/2 → 11/4 | note:C3 ]", + "[ 11/4 → 3/1 | note:C3 ]", + "[ 3/1 → 13/4 | note:C3 ]", + "[ 13/4 → 7/2 | note:C3 ]", + "[ 7/2 → 15/4 | note:C3 ]", + "[ 15/4 → 4/1 | note:C3 ]", +] +`; + +exports[`runs examples > example "accelerationY" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | note:C3 ]", + "[ 1/4 → 1/2 | note:C3 ]", + "[ 1/2 → 3/4 | note:C3 ]", + "[ 3/4 → 1/1 | note:C3 ]", + "[ 1/1 → 5/4 | note:C3 ]", + "[ 5/4 → 3/2 | note:C3 ]", + "[ 3/2 → 7/4 | note:C3 ]", + "[ 7/4 → 2/1 | note:C3 ]", + "[ 2/1 → 9/4 | note:C3 ]", + "[ 9/4 → 5/2 | note:C3 ]", + "[ 5/2 → 11/4 | note:C3 ]", + "[ 11/4 → 3/1 | note:C3 ]", + "[ 3/1 → 13/4 | note:C3 ]", + "[ 13/4 → 7/2 | note:C3 ]", + "[ 7/2 → 15/4 | note:C3 ]", + "[ 15/4 → 4/1 | note:C3 ]", +] +`; + +exports[`runs examples > example "accelerationZ" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | note:C3 ]", + "[ 1/4 → 1/2 | note:C3 ]", + "[ 1/2 → 3/4 | note:C3 ]", + "[ 3/4 → 1/1 | note:C3 ]", + "[ 1/1 → 5/4 | note:C3 ]", + "[ 5/4 → 3/2 | note:C3 ]", + "[ 3/2 → 7/4 | note:C3 ]", + "[ 7/4 → 2/1 | note:C3 ]", + "[ 2/1 → 9/4 | note:C3 ]", + "[ 9/4 → 5/2 | note:C3 ]", + "[ 5/2 → 11/4 | note:C3 ]", + "[ 11/4 → 3/1 | note:C3 ]", + "[ 3/1 → 13/4 | note:C3 ]", + "[ 13/4 → 7/2 | note:C3 ]", + "[ 7/2 → 15/4 | note:C3 ]", + "[ 15/4 → 4/1 | note:C3 ]", +] +`; + exports[`runs examples > example "add" example index 0 1`] = ` [ "[ 0/1 → 1/3 | note:C3 ]", @@ -742,6 +868,8 @@ exports[`runs examples > example "almostNever" example index 0 1`] = ` ] `; +exports[`runs examples > example "altitude" example index 0 1`] = `[]`; + exports[`runs examples > example "always" example index 0 1`] = ` [ "[ 0/1 → 1/8 | s:hh speed:0.5 ]", @@ -3337,6 +3465,75 @@ exports[`runs examples > example "gain" example index 0 1`] = ` exports[`runs examples > example "gap" example index 0 1`] = `[]`; +exports[`runs examples > example "geoHeading" example index 0 1`] = `[]`; + +exports[`runs examples > example "geoSpeed" example index 0 1`] = `[]`; + +exports[`runs examples > example "gravityX" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | note:C3 ]", + "[ 1/4 → 1/2 | note:C3 ]", + "[ 1/2 → 3/4 | note:C3 ]", + "[ 3/4 → 1/1 | note:C3 ]", + "[ 1/1 → 5/4 | note:C3 ]", + "[ 5/4 → 3/2 | note:C3 ]", + "[ 3/2 → 7/4 | note:C3 ]", + "[ 7/4 → 2/1 | note:C3 ]", + "[ 2/1 → 9/4 | note:C3 ]", + "[ 9/4 → 5/2 | note:C3 ]", + "[ 5/2 → 11/4 | note:C3 ]", + "[ 11/4 → 3/1 | note:C3 ]", + "[ 3/1 → 13/4 | note:C3 ]", + "[ 13/4 → 7/2 | note:C3 ]", + "[ 7/2 → 15/4 | note:C3 ]", + "[ 15/4 → 4/1 | note:C3 ]", +] +`; + +exports[`runs examples > example "gravityY" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | note:C3 ]", + "[ 1/4 → 1/2 | note:C3 ]", + "[ 1/2 → 3/4 | note:C3 ]", + "[ 3/4 → 1/1 | note:C3 ]", + "[ 1/1 → 5/4 | note:C3 ]", + "[ 5/4 → 3/2 | note:C3 ]", + "[ 3/2 → 7/4 | note:C3 ]", + "[ 7/4 → 2/1 | note:C3 ]", + "[ 2/1 → 9/4 | note:C3 ]", + "[ 9/4 → 5/2 | note:C3 ]", + "[ 5/2 → 11/4 | note:C3 ]", + "[ 11/4 → 3/1 | note:C3 ]", + "[ 3/1 → 13/4 | note:C3 ]", + "[ 13/4 → 7/2 | note:C3 ]", + "[ 7/2 → 15/4 | note:C3 ]", + "[ 15/4 → 4/1 | note:C3 ]", +] +`; + +exports[`runs examples > example "gravityZ" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | note:C3 ]", + "[ 1/4 → 1/2 | note:C3 ]", + "[ 1/2 → 3/4 | note:C3 ]", + "[ 3/4 → 1/1 | note:C3 ]", + "[ 1/1 → 5/4 | note:C3 ]", + "[ 5/4 → 3/2 | note:C3 ]", + "[ 3/2 → 7/4 | note:C3 ]", + "[ 7/4 → 2/1 | note:C3 ]", + "[ 2/1 → 9/4 | note:C3 ]", + "[ 9/4 → 5/2 | note:C3 ]", + "[ 5/2 → 11/4 | note:C3 ]", + "[ 11/4 → 3/1 | note:C3 ]", + "[ 3/1 → 13/4 | note:C3 ]", + "[ 13/4 → 7/2 | note:C3 ]", + "[ 7/2 → 15/4 | note:C3 ]", + "[ 15/4 → 4/1 | note:C3 ]", +] +`; + +exports[`runs examples > example "heading" example index 0 1`] = `[]`; + exports[`runs examples > example "hpattack" example index 0 1`] = ` [ "[ 0/1 → 1/4 | note:c2 s:sawtooth hcutoff:500 hpattack:0.5 hpenv:4 ]", @@ -4099,6 +4296,8 @@ exports[`runs examples > example "late" example index 0 1`] = ` ] `; +exports[`runs examples > example "latitude" example index 0 1`] = `[]`; + exports[`runs examples > example "layer" example index 0 1`] = ` [ "[ 0/1 → 1/8 | note:C3 ]", @@ -4202,6 +4401,8 @@ exports[`runs examples > example "linger" example index 0 1`] = ` ] `; +exports[`runs examples > example "longitude" example index 0 1`] = `[]`; + exports[`runs examples > example "loop" example index 0 1`] = ` [ "[ 0/1 → 1/1 | s:casio loop:1 ]", @@ -4939,6 +5140,69 @@ exports[`runs examples > example "orbit" example index 0 1`] = ` ] `; +exports[`runs examples > example "orientationAlpha" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | note:C3 ]", + "[ 1/4 → 1/2 | note:C3 ]", + "[ 1/2 → 3/4 | note:C3 ]", + "[ 3/4 → 1/1 | note:C3 ]", + "[ 1/1 → 5/4 | note:C3 ]", + "[ 5/4 → 3/2 | note:C3 ]", + "[ 3/2 → 7/4 | note:C3 ]", + "[ 7/4 → 2/1 | note:C3 ]", + "[ 2/1 → 9/4 | note:C3 ]", + "[ 9/4 → 5/2 | note:C3 ]", + "[ 5/2 → 11/4 | note:C3 ]", + "[ 11/4 → 3/1 | note:C3 ]", + "[ 3/1 → 13/4 | note:C3 ]", + "[ 13/4 → 7/2 | note:C3 ]", + "[ 7/2 → 15/4 | note:C3 ]", + "[ 15/4 → 4/1 | note:C3 ]", +] +`; + +exports[`runs examples > example "orientationBeta" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | note:C3 ]", + "[ 1/4 → 1/2 | note:C3 ]", + "[ 1/2 → 3/4 | note:C3 ]", + "[ 3/4 → 1/1 | note:C3 ]", + "[ 1/1 → 5/4 | note:C3 ]", + "[ 5/4 → 3/2 | note:C3 ]", + "[ 3/2 → 7/4 | note:C3 ]", + "[ 7/4 → 2/1 | note:C3 ]", + "[ 2/1 → 9/4 | note:C3 ]", + "[ 9/4 → 5/2 | note:C3 ]", + "[ 5/2 → 11/4 | note:C3 ]", + "[ 11/4 → 3/1 | note:C3 ]", + "[ 3/1 → 13/4 | note:C3 ]", + "[ 13/4 → 7/2 | note:C3 ]", + "[ 7/2 → 15/4 | note:C3 ]", + "[ 15/4 → 4/1 | note:C3 ]", +] +`; + +exports[`runs examples > example "orientationGamma" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | note:C3 ]", + "[ 1/4 → 1/2 | note:C3 ]", + "[ 1/2 → 3/4 | note:C3 ]", + "[ 3/4 → 1/1 | note:C3 ]", + "[ 1/1 → 5/4 | note:C3 ]", + "[ 5/4 → 3/2 | note:C3 ]", + "[ 3/2 → 7/4 | note:C3 ]", + "[ 7/4 → 2/1 | note:C3 ]", + "[ 2/1 → 9/4 | note:C3 ]", + "[ 9/4 → 5/2 | note:C3 ]", + "[ 5/2 → 11/4 | note:C3 ]", + "[ 11/4 → 3/1 | note:C3 ]", + "[ 3/1 → 13/4 | note:C3 ]", + "[ 13/4 → 7/2 | note:C3 ]", + "[ 7/2 → 15/4 | note:C3 ]", + "[ 15/4 → 4/1 | note:C3 ]", +] +`; + exports[`runs examples > example "outside" example index 0 1`] = ` [ "[ 0/1 → 1/1 | note:A3 ]", @@ -6343,6 +6607,69 @@ exports[`runs examples > example "rootNotes" example index 0 1`] = ` ] `; +exports[`runs examples > example "rotationAlpha" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | note:C3 ]", + "[ 1/4 → 1/2 | note:C3 ]", + "[ 1/2 → 3/4 | note:C3 ]", + "[ 3/4 → 1/1 | note:C3 ]", + "[ 1/1 → 5/4 | note:C3 ]", + "[ 5/4 → 3/2 | note:C3 ]", + "[ 3/2 → 7/4 | note:C3 ]", + "[ 7/4 → 2/1 | note:C3 ]", + "[ 2/1 → 9/4 | note:C3 ]", + "[ 9/4 → 5/2 | note:C3 ]", + "[ 5/2 → 11/4 | note:C3 ]", + "[ 11/4 → 3/1 | note:C3 ]", + "[ 3/1 → 13/4 | note:C3 ]", + "[ 13/4 → 7/2 | note:C3 ]", + "[ 7/2 → 15/4 | note:C3 ]", + "[ 15/4 → 4/1 | note:C3 ]", +] +`; + +exports[`runs examples > example "rotationBeta" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | note:C3 ]", + "[ 1/4 → 1/2 | note:C3 ]", + "[ 1/2 → 3/4 | note:C3 ]", + "[ 3/4 → 1/1 | note:C3 ]", + "[ 1/1 → 5/4 | note:C3 ]", + "[ 5/4 → 3/2 | note:C3 ]", + "[ 3/2 → 7/4 | note:C3 ]", + "[ 7/4 → 2/1 | note:C3 ]", + "[ 2/1 → 9/4 | note:C3 ]", + "[ 9/4 → 5/2 | note:C3 ]", + "[ 5/2 → 11/4 | note:C3 ]", + "[ 11/4 → 3/1 | note:C3 ]", + "[ 3/1 → 13/4 | note:C3 ]", + "[ 13/4 → 7/2 | note:C3 ]", + "[ 7/2 → 15/4 | note:C3 ]", + "[ 15/4 → 4/1 | note:C3 ]", +] +`; + +exports[`runs examples > example "rotationGamma" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | note:C3 ]", + "[ 1/4 → 1/2 | note:C3 ]", + "[ 1/2 → 3/4 | note:C3 ]", + "[ 3/4 → 1/1 | note:C3 ]", + "[ 1/1 → 5/4 | note:C3 ]", + "[ 5/4 → 3/2 | note:C3 ]", + "[ 3/2 → 7/4 | note:C3 ]", + "[ 7/4 → 2/1 | note:C3 ]", + "[ 2/1 → 9/4 | note:C3 ]", + "[ 9/4 → 5/2 | note:C3 ]", + "[ 5/2 → 11/4 | note:C3 ]", + "[ 11/4 → 3/1 | note:C3 ]", + "[ 3/1 → 13/4 | note:C3 ]", + "[ 13/4 → 7/2 | note:C3 ]", + "[ 7/2 → 15/4 | note:C3 ]", + "[ 15/4 → 4/1 | note:C3 ]", +] +`; + exports[`runs examples > example "round" example index 0 1`] = ` [ "[ 0/1 → 1/3 | note:D3 ]", @@ -7537,6 +7864,8 @@ exports[`runs examples > example "speed" example index 0 1`] = ` ] `; +exports[`runs examples > example "speed" example index 0 2`] = `[]`; + exports[`runs examples > example "speed" example index 1 1`] = ` [ "[ 0/1 → 1/3 | speed:1 s:piano clip:1 ]", diff --git a/test/runtime.mjs b/test/runtime.mjs index a8f37a34..49b78560 100644 --- a/test/runtime.mjs +++ b/test/runtime.mjs @@ -21,6 +21,9 @@ import '@strudel/xen/xen.mjs'; // import '@strudel/webaudio/webaudio.mjs'; // import '@strudel/serial/serial.mjs'; import '../website/src/repl/piano'; +//import * as motionHelpers from '../packages/motion/index.mjs'; +//import * as geolocationHelpers from '../packages/geolocation/index.mjs'; +import * as gamepadHelpers from '../packages/gamepad/index.mjs'; class MockedNode { chain() { @@ -169,6 +172,7 @@ evalScope( uiHelpersMocked, webaudio, tonalHelpers, + gamepadHelpers, /* toneHelpers, voicingHelpers, diff --git a/website/package.json b/website/package.json index 942fee9c..96e7e714 100644 --- a/website/package.json +++ b/website/package.json @@ -30,17 +30,18 @@ "@strudel/csound": "workspace:*", "@strudel/desktopbridge": "workspace:*", "@strudel/draw": "workspace:*", + "@strudel/gamepad": "workspace:*", "@strudel/hydra": "workspace:*", "@strudel/midi": "workspace:*", "@strudel/mini": "workspace:*", "@strudel/osc": "workspace:*", "@strudel/serial": "workspace:*", "@strudel/soundfonts": "workspace:*", + "@strudel/tidal": "workspace:*", "@strudel/tonal": "workspace:*", "@strudel/transpiler": "workspace:*", "@strudel/webaudio": "workspace:*", "@strudel/xen": "workspace:*", - "@strudel/tidal": "workspace:*", "@supabase/supabase-js": "^2.39.1", "@tailwindcss/forms": "^0.5.7", "@tailwindcss/typography": "^0.5.10", diff --git a/website/src/repl/util.mjs b/website/src/repl/util.mjs index 905e16b0..1b98b101 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/gamepad'), ]; if (isTauri()) { modules = modules.concat([