mirror of
https://github.com/eliasstepanik/strudel.git
synced 2026-01-11 05:38:35 +00:00
commit
9c9ce50b03
93
packages/gamepad/README.md
Normal file
93
packages/gamepad/README.md
Normal file
@ -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([
|
||||
s("bd").gain(pad.X), // X button controls gain
|
||||
s("[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
|
||||
```
|
||||
117
packages/gamepad/docs/gamepad.mdx
Normal file
117
packages/gamepad/docs/gamepad.mdx
Normal file
@ -0,0 +1,117 @@
|
||||
import { MiniRepl } from '../../../website/src/docs/MiniRepl';
|
||||
|
||||
# Gamepad
|
||||
|
||||
The Gamepad module allows you to integrate gamepad input functionality into your musical patterns. This can be particularly useful for live performances or interactive installations where you want to manipulate sounds using a game controller.
|
||||
|
||||
## Getting Started
|
||||
|
||||
Initialize a gamepad by calling the gamepad() function with an optional index parameter.
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
tune={`// Initialize gamepad (optional index parameter, defaults to 0)
|
||||
const gp = gamepad(0)
|
||||
note("c a f e").mask(gp.a)`}
|
||||
/>
|
||||
|
||||
## Available Controls
|
||||
|
||||
The gamepad module provides access to buttons and analog sticks as normalized signals (0-1) that can modulate your patterns.
|
||||
|
||||
### Buttons
|
||||
|
||||
| Type | Controls |
|
||||
| ---------------- | ---------------------------------------------------------------------------------------------- |
|
||||
| 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
|
||||
|
||||
| Stick | Controls |
|
||||
| ----------- | ------------------------------ |
|
||||
| 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) |
|
||||
|
||||
### Button Sequence
|
||||
|
||||
| Stick | Controls |
|
||||
| --------------- | --------------------------------------- |
|
||||
| Button Sequence | `btnSequence()`, `btnSeq()`, `btnseq()` |
|
||||
|
||||
## Using Gamepad Inputs
|
||||
|
||||
Once initialized, you can use various gamepad inputs in your patterns. Here are some examples:
|
||||
|
||||
### Button Inputs
|
||||
|
||||
You can use button inputs to control different aspects of your music, such as gain or triggering events.
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
tune={`const gp = gamepad(0)
|
||||
// Use button values to control amplitude
|
||||
$: stack(
|
||||
s("[[hh hh] oh hh oh]/2").mask(gp.tglX).bank("RolandTR909"), // X btn for HH
|
||||
s("cr*1").mask(gp.Y).bank("RolandTR909"), // LB btn for CR
|
||||
s("bd").mask(gp.tglA).bank("RolandTR909"), // A btn for BD
|
||||
s("[ht - - mt - - lt - ]/2").mask(gp.tglB).bank("RolandTR909"), // B btn for Toms
|
||||
s("sd*4").mask(gp.RB).bank("RolandTR909"), // RB btn for SD
|
||||
).cpm(120)
|
||||
`}
|
||||
/>
|
||||
|
||||
### Analog Stick Inputs
|
||||
|
||||
Analog sticks can be used for continuous control, such as pitch shifting or panning.
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
tune={`const gp = gamepad(0)
|
||||
// Use analog stick for continuous control
|
||||
$: note("c4 d3 a3 e3").sound("sawtooth")
|
||||
.lpf(gp.x1.range(100,4000))
|
||||
.lpq(gp.y1.range(5,30))
|
||||
.decay(gp.y2.range(0.1,2))
|
||||
.lpenv(gp.x2.range(-5,5))
|
||||
.cpm(120)
|
||||
`}
|
||||
/>
|
||||
|
||||
### Button Sequences
|
||||
|
||||
You can define button sequences to trigger specific actions, like playing a sound when a sequence is detected.
|
||||
|
||||
<MiniRepl client:idle tune={`const gp = gamepad(0)
|
||||
// Define button sequences
|
||||
const HADOUKEN = [
|
||||
'd', // Down
|
||||
'r', // Right
|
||||
'a', // A
|
||||
]
|
||||
const KONAMI = 'uuddlrlrba' //Konami Code ↑↑↓↓←→←→BA
|
||||
|
||||
// Check butto-n sequence (returns 1 while detected, 0 when not within last 1 second)
|
||||
$: s("free_hadouken -").slow(2)
|
||||
.mask(gp.btnSequence(HADOUKEN)).room(1).cpm(120)
|
||||
|
||||
// hadouken.wav by Syna-Max
|
||||
//https://freesound.org/people/Syna-Max/sounds/67674/
|
||||
samples({free_hadouken: 'https://cdn.freesound.org/previews/67/67674_111920-lq.mp3'})
|
||||
`} />
|
||||
|
||||
## Multiple Gamepads
|
||||
|
||||
Strudel supports multiple gamepads. You can specify the gamepad index to connect to different devices.
|
||||
|
||||
<MiniRepl
|
||||
client:idle
|
||||
tune={`const pad1 = gamepad(0); // First gamepad
|
||||
const pad2 = gamepad(1); // Second gamepad`}
|
||||
/>
|
||||
246
packages/gamepad/gamepad.mjs
Normal file
246
packages/gamepad/gamepad.mjs
Normal file
@ -0,0 +1,246 @@
|
||||
// @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());
|
||||
|
||||
//console.log(this.sequence);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getAxes() {
|
||||
return this._axes;
|
||||
}
|
||||
getButtons() {
|
||||
return this._buttons;
|
||||
}
|
||||
}
|
||||
|
||||
// Module-level state store for toggle states
|
||||
const gamepadStates = new Map();
|
||||
|
||||
export const gamepad = (index = 0) => {
|
||||
const handler = new GamepadHandler(index);
|
||||
const sequenceDetector = new ButtonSequenceDetector(2000);
|
||||
|
||||
// Base signal that polls gamepad state and handles sequence detection
|
||||
const baseSignal = signal((t) => {
|
||||
handler.poll();
|
||||
const axes = handler.getAxes();
|
||||
const buttons = handler.getButtons();
|
||||
|
||||
// Add all button inputs to sequence detector
|
||||
buttons.forEach((value, i) => {
|
||||
sequenceDetector.addInput(i, value);
|
||||
});
|
||||
|
||||
return { axes, buttons, t };
|
||||
});
|
||||
|
||||
// Create axes patterns
|
||||
const axes = {
|
||||
x1: baseSignal.fmap((state) => state.axes[0]),
|
||||
y1: baseSignal.fmap((state) => state.axes[1]),
|
||||
x2: baseSignal.fmap((state) => state.axes[2]),
|
||||
y2: baseSignal.fmap((state) => state.axes[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 patterns
|
||||
const buttons = Array(16)
|
||||
.fill(null)
|
||||
.map((_, i) => {
|
||||
// Create unique key for this gamepad+button combination
|
||||
const stateKey = `gamepad${index}_btn${i}`;
|
||||
|
||||
// Initialize toggle state if it doesn't exist
|
||||
if (!gamepadStates.has(stateKey)) {
|
||||
gamepadStates.set(stateKey, {
|
||||
lastButtonState: 0,
|
||||
toggleState: 0,
|
||||
});
|
||||
}
|
||||
|
||||
// Direct button value pattern (no longer needs to call addInput)
|
||||
const btn = baseSignal.fmap((state) => state.buttons[i]);
|
||||
|
||||
// Button toggle pattern with persistent state
|
||||
const toggle = baseSignal.fmap((state) => {
|
||||
const currentState = state.buttons[i];
|
||||
const buttonState = gamepadStates.get(stateKey);
|
||||
|
||||
if (currentState === 1 && buttonState.lastButtonState === 0) {
|
||||
// Toggle the state on rising edge
|
||||
buttonState.toggleState = buttonState.toggleState === 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
buttonState.lastButtonState = currentState;
|
||||
return buttonState.toggleState;
|
||||
});
|
||||
|
||||
return { value: btn, toggle };
|
||||
});
|
||||
|
||||
// Create sequence checker pattern
|
||||
const btnSequence = (sequence) => {
|
||||
return baseSignal.fmap(() => sequenceDetector.checkSequence(sequence));
|
||||
};
|
||||
const checkSequence = btnSequence;
|
||||
const btnSeq = btnSequence;
|
||||
const btnseq = btnSeq;
|
||||
|
||||
// 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,
|
||||
btnSequence,
|
||||
btnSeq,
|
||||
btnseq,
|
||||
raw: baseSignal,
|
||||
};
|
||||
};
|
||||
|
||||
// Optional: Export for debugging or state management
|
||||
export const getGamepadStates = () => Object.fromEntries(gamepadStates);
|
||||
export const clearGamepadStates = () => gamepadStates.clear();
|
||||
3
packages/gamepad/index.mjs
Normal file
3
packages/gamepad/index.mjs
Normal file
@ -0,0 +1,3 @@
|
||||
import './gamepad.mjs';
|
||||
|
||||
export * from './gamepad.mjs';
|
||||
38
packages/gamepad/package.json
Normal file
38
packages/gamepad/package.json
Normal file
@ -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 <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/gamepad/vite.config.js
Normal file
19
packages/gamepad/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
@ -270,6 +270,16 @@ importers:
|
||||
|
||||
packages/embed: {}
|
||||
|
||||
packages/gamepad:
|
||||
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/hs2js:
|
||||
dependencies:
|
||||
web-tree-sitter:
|
||||
@ -643,6 +653,9 @@ importers:
|
||||
'@strudel/draw':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/draw
|
||||
'@strudel/gamepad':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/gamepad
|
||||
'@strudel/hydra':
|
||||
specifier: workspace:*
|
||||
version: link:../packages/hydra
|
||||
|
||||
@ -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() {
|
||||
@ -131,6 +134,7 @@ evalScope(
|
||||
uiHelpersMocked,
|
||||
webaudio,
|
||||
tonalHelpers,
|
||||
gamepadHelpers,
|
||||
/*
|
||||
toneHelpers,
|
||||
voicingHelpers,
|
||||
|
||||
@ -29,6 +29,7 @@
|
||||
"@strudel/csound": "workspace:*",
|
||||
"@strudel/desktopbridge": "workspace:*",
|
||||
"@strudel/draw": "workspace:*",
|
||||
"@strudel/gamepad": "workspace:*",
|
||||
"@strudel/hydra": "workspace:*",
|
||||
"@strudel/midi": "workspace:*",
|
||||
"@strudel/mini": "workspace:*",
|
||||
|
||||
@ -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: 'Input Devices', link: 'learn/input-devices' },
|
||||
{ text: 'Device Motion', link: 'learn/devicemotion' },
|
||||
],
|
||||
'Pattern Functions': [
|
||||
|
||||
@ -5,6 +5,6 @@ layout: ../../layouts/MainLayout.astro
|
||||
|
||||
import { MiniRepl } from '../../docs/MiniRepl';
|
||||
import { JsDoc } from '../../docs/JsDoc';
|
||||
import DeviceMotion from '../../../../packages/motion/docs/devicemotion.mdx';
|
||||
import DeviceMotion from '@strudel/motion/docs/devicemotion.mdx';
|
||||
|
||||
<DeviceMotion />
|
||||
|
||||
15
website/src/pages/learn/input-devices.mdx
Normal file
15
website/src/pages/learn/input-devices.mdx
Normal file
@ -0,0 +1,15 @@
|
||||
---
|
||||
title: Input Devices
|
||||
layout: ../../layouts/MainLayout.astro
|
||||
---
|
||||
|
||||
import { MiniRepl } from '../../docs/MiniRepl';
|
||||
import { JsDoc } from '../../docs/JsDoc';
|
||||
|
||||
import Gamepad from '@strudel/gamepad/docs/gamepad.mdx';
|
||||
|
||||
# Input Devices
|
||||
|
||||
Strudel supports various input devices like Gamepads and MIDI controllers to manipulate patterns in real-time.
|
||||
|
||||
<Gamepad />
|
||||
@ -81,6 +81,7 @@ export function loadModules() {
|
||||
import('@strudel/soundfonts'),
|
||||
import('@strudel/csound'),
|
||||
import('@strudel/tidal'),
|
||||
import('@strudel/gamepad'),
|
||||
import('@strudel/motion'),
|
||||
import('@strudel/mqtt'),
|
||||
];
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user