Compare commits

..

9 Commits

Author SHA1 Message Date
Jade (Rose) Rowland 4f046ae883 cf 2025-11-01 02:40:40 -04:00
Jade (Rose) Rowland 2b575d50b9 temporarily disable sample autocomplete 2025-11-01 02:39:43 -04:00
Jade (Rose) Rowland 7b92874909 fix broken array 2025-11-01 01:55:05 -04:00
Jade (Rose) Rowland 10bfe32a2d rm console 2025-11-01 01:48:42 -04:00
Jade (Rose) Rowland a41b07c67a fixed build 2025-11-01 01:44:32 -04:00
Jade (Rose) Rowland 20027d7208 Merge branch 'jade/fix_sample_loading' of ssh://codeberg.org/uzu/strudel into jade/fix_sample_loading 2025-11-01 01:32:35 -04:00
Jade (Rose) Rowland f38792c569 format 2025-11-01 01:32:24 -04:00
Switch Angel AKA Jade Rose 2c2bfd7070 Merge branch 'main' into jade/fix_sample_loading 2025-11-01 06:31:25 +01:00
Jade (Rose) Rowland 484c5e6cdd working 2025-11-01 01:26:25 -04:00
5 changed files with 45 additions and 165 deletions
+14 -10
View File
@@ -1,8 +1,10 @@
import jsdoc from '../../doc.json';
import { autocompletion } from '@codemirror/autocomplete';
import { h } from './html';
import { Scale } from '@tonaljs/tonal';
import { soundMap } from 'superdough';
//TODO: fix tonal scale import
// import { Scale } from '@tonaljs/tonal';
// import { soundMap } from '@strudel/webaudio';
let soundMap = undefined;
import { complex } from '@strudel/tonal';
const escapeHtml = (str) => {
@@ -79,7 +81,9 @@ const hasExcludedTags = (doc) =>
['superdirtOnly', 'noAutocomplete'].some((tag) => doc.tags?.find((t) => t.originalTitle === tag));
export function bankCompletions() {
const soundDict = soundMap.get();
// TODO: FIX IMPORT
const soundDict = soundMap?.get() ?? {};
const banks = new Set();
for (const key of Object.keys(soundDict)) {
const [bank, suffix] = key.split('_');
@@ -90,13 +94,13 @@ export function bankCompletions() {
.map((name) => ({ label: name, type: 'bank' }));
}
// Attempt to get all scale names from Tonal
// Attempt to get all scale names from Tonal TODO: FIX IMPORT
let scaleCompletions = [];
try {
scaleCompletions = (Scale.names ? Scale.names() : []).map((name) => ({ label: name, type: 'scale' }));
} catch (e) {
console.warn('[autocomplete] Could not load scale names from Tonal:', e);
}
// try {
// scaleCompletions = (Scale.names ? Scale.names() : []).map((name) => ({ label: name, type: 'scale' }));
// } catch (e) {
// console.warn('[autocomplete] Could not load scale names from Tonal:', e);
// }
// Valid mode values for voicing
const modeCompletions = [
@@ -268,7 +272,7 @@ function soundHandler(context) {
const inside = text.slice(quoteIdx + 1);
const fragMatch = inside.match(SOUND_FRAGMENT_MATCH_REGEX);
const fragment = fragMatch ? fragMatch[1] : inside;
const soundNames = Object.keys(soundMap.get()).sort();
const soundNames = Object.keys(soundMap?.get() ?? {}).sort();
const filteredSounds = soundNames.filter((name) => name.includes(fragment));
let options = filteredSounds.map((name) => ({ label: name, type: 'sound' }));
const from = soundContext.to - fragment.length;
+1 -7
View File
@@ -14,12 +14,7 @@ npm i @strudel/gamepad --save
import { gamepad } from '@strudel/gamepad';
// Initialize gamepad (optional index parameter, defaults to 0)
const pad = gamepad(0); // Default mapping is XBOX {a: 0, b: 1, x: 2, y: 3}
//const pad = gamepad(0, 'XBOX'); // XBOX button mapping {a: 0, b: 1, x: 2, y: 3}
//const pad = gamepad(0, 'NES'); // Nintendo button mapping {a: 1, b: 0, x: 3, y: 2}
//const pad = gamepad(0, {a: 0, b: 1, x: 2, y: 3}); // Custom mapping
const pad = gamepad(0);
// Use gamepad inputs in patterns
const pattern = sequence([
@@ -91,7 +86,6 @@ $: sound("hadoken").gain(pad.checkSequence(HADOKEN))
## Multiple Gamepads
You can connect multiple gamepads by specifying the gamepad index:
Make sure to press buttons on all connected gamepads before hitting play, so the browser can properly detect them.
```javascript
const pad1 = gamepad(0); // First gamepad
+1 -24
View File
@@ -107,32 +107,9 @@ $: s("free_hadouken -").slow(2)
samples({free_hadouken: 'https://cdn.freesound.org/previews/67/67674_111920-lq.mp3'})
`} />
### Button Sequences
### Button Mappings
The gamepad module supports different button mapping configurations to accommodate various gamepad layouts:
- **XBOX** (Default): Standard Xbox-style mapping where A=0, B=1, X=2, Y=3
- **NES**: Nintendo-style mapping where B=0, A=1, Y=2, X=3
- **Custom**: Define your own button mapping by passing an object with button assignments
You can specify the mapping when initializing the gamepad:
<MiniRepl
client:idle
tune={`
const pad1 = gamepad(0); // Default mapping is XBOX {a: 0, b: 1, x: 2, y: 3}
const pad2 = gamepad(1, 'XBOX'); // XBOX button mapping {a: 0, b: 1, x: 2, y: 3}
const pad3 = gamepad(2, 'NES'); // Nintendo button mapping {a: 1, b: 0, x: 3, y: 2}
const pad4 = gamepad(3, {a: 0, b: 1, x: 2, y: 3}); // Custom mapping
`}
/>
### Multiple Gamepads
## Multiple Gamepads
Strudel supports multiple gamepads. You can specify the gamepad index to connect to different devices.
Make sure to press buttons on all connected gamepads before hitting play, so the browser can properly detect them.
<MiniRepl
client:idle
+28 -119
View File
@@ -1,62 +1,35 @@
// @strudel/gamepad/index.mjs
import { signal } from '@strudel/core';
import { logger } from '@strudel/core';
// Button mapping for Logitech Dual Action (STANDARD GAMEPAD Vendor: 046d Product: c216)
const buttonMapSettings = {
XBOX: {
// XBOX mapping default
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,
},
NES: {
// Nintendo mapping
a: 1,
b: 0,
x: 3,
y: 2,
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,
},
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, mapping) {
constructor(timeWindow = 1000) {
this.sequence = [];
this.timeWindow = timeWindow;
this.lastInputTime = 0;
this.buttonStates = Array(16).fill(0); // Track previous state of each button
this.buttonMap = mapping;
// Button mapping for character inputs
}
@@ -71,8 +44,7 @@ class ButtonSequenceDetector {
}
// Store the button name instead of index
const buttonName =
Object.keys(this.buttonMap).find((key) => this.buttonMap[key] === buttonIndex) || buttonIndex.toString();
const buttonName = Object.keys(buttonMap).find((key) => buttonMap[key] === buttonIndex) || buttonIndex.toString();
this.sequence.push({
input: buttonName,
@@ -115,9 +87,9 @@ class ButtonSequenceDetector {
// Check if either the input matches directly or they refer to the same button in the map
return (
input === target ||
this.buttonMap[input] === this.buttonMap[target] ||
buttonMap[input] === buttonMap[target] ||
// Also check if the numerical index matches
this.buttonMap[input] === parseInt(target)
buttonMap[input] === parseInt(target)
);
})
? 1
@@ -126,10 +98,9 @@ class ButtonSequenceDetector {
}
class GamepadHandler {
constructor(index = 0, mapping) {
constructor(index = 0) {
// Add index parameter
this._gamepads = {};
this._mapping = mapping;
this._activeGamepad = index; // Use provided index
this._axes = [0, 0, 0, 0];
this._buttons = Array(16).fill(0);
@@ -172,66 +143,12 @@ class GamepadHandler {
}
}
// Add utility function to list all connected gamepads
export const listGamepads = () => {
const gamepads = navigator.getGamepads();
const connectedGamepads = Array.from(gamepads)
.filter((gp) => gp !== null)
.map((gp) => ({
index: gp.index,
id: gp.id,
mapping: gp.mapping,
buttons: gp.buttons.length,
axes: gp.axes.length,
connected: gp.connected,
timestamp: gp.timestamp,
}));
// Format the gamepads info into a readable string
const gamepadsInfo = connectedGamepads.map((gp) => `${gp.index}: ${gp.id}`).join('\n');
logger(`[gamepad] available gamepads:\n${gamepadsInfo}`);
return connectedGamepads;
};
// Module-level state store for toggle states
const gamepadStates = new Map();
export const gamepad = (index = 0, mapping = 'XBOX') => {
// list connected gamepads
const connectedGamepads = listGamepads();
// Check if the requested gamepad index exists
const requestedGamepad = connectedGamepads.find((gp) => gp.index === index);
if (!requestedGamepad) {
throw new Error(
`[gamepad] gamepad at index ${index} not found. available gamepads: ${connectedGamepads.map((gp) => gp.index).join(', ')}`,
);
}
// Handle button mapping
let buttonMap = buttonMapSettings.XBOX;
if (typeof mapping === 'string') {
buttonMap = buttonMapSettings[mapping.toUpperCase()];
} else if (typeof mapping === 'object') {
buttonMap = { ...buttonMapSettings.XBOX, ...mapping };
// Check that all mapping values are valid button indices
const maxButtons = requestedGamepad.buttons; // Standard gamepad has 16 buttons
for (const [key, value] of Object.entries(mapping)) {
if (typeof value !== 'number' || value < 0 || value >= maxButtons) {
throw new Error(
`[gamepad] invalid button mapping for '${key}': ${value}. Must be a number between 0 and ${maxButtons - 1}`,
);
}
}
}
if (!buttonMap) {
throw new Error(`[gamepad] button mapping '${mapping}' not found`);
}
const handler = new GamepadHandler(index, buttonMap);
const sequenceDetector = new ButtonSequenceDetector(2000, buttonMap);
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) => {
@@ -301,14 +218,8 @@ export const gamepad = (index = 0, mapping = 'XBOX') => {
return baseSignal.fmap(() => sequenceDetector.checkSequence(sequence));
};
const checkSequence = btnSequence;
const sequence = btnSequence;
const btnSeq = btnSequence;
const btnseq = btnSequence;
const seq = btnSequence;
logger(
`[gamepad] connected to gamepad ${index} (${requestedGamepad.id}) with ${typeof mapping === 'object' ? 'custom' : mapping} mapping`,
);
const btnseq = btnSeq;
// Return an object with all controls
return {
@@ -323,11 +234,9 @@ export const gamepad = (index = 0, mapping = 'XBOX') => {
]),
),
checkSequence,
sequence,
btnSequence,
btnSeq,
btnseq,
seq,
raw: baseSignal,
};
};
+1 -5
View File
@@ -92,11 +92,7 @@ export function registerSamplesFromDB(config = userSamplesDBConfig, onComplete =
async function blobToDataUrl(blob) {
return new Promise((resolve) => {
var reader = new FileReader();
reader.onload = function (event) {
resolve(event.target.result);
};
reader.readAsDataURL(blob);
resolve(URL.createObjectURL(blob));
});
}