From 5c4b6cda71000378412e45acbab599f34fdb3430 Mon Sep 17 00:00:00 2001 From: nkymut Date: Fri, 30 May 2025 09:44:18 +0800 Subject: [PATCH] Add Vibration feedback support to the GamePad --- packages/gamepad/README.md | 26 ++ packages/gamepad/gamepad.mjs | 40 +- packages/gamepad/vibration.mjs | 288 ++++++++++++++ test/__snapshots__/examples.test.mjs.snap | 444 ++++++++++++++++++++++ 4 files changed, 790 insertions(+), 8 deletions(-) create mode 100644 packages/gamepad/vibration.mjs diff --git a/packages/gamepad/README.md b/packages/gamepad/README.md index 05f1378aa..469c39f2f 100644 --- a/packages/gamepad/README.md +++ b/packages/gamepad/README.md @@ -97,3 +97,29 @@ Make sure to press buttons on all connected gamepads before hitting play, so the const pad1 = gamepad(0); // First gamepad const pad2 = gamepad(1); // Second gamepad ``` + +## Vibration + +You can use the `vibrate` control to provide haptic feedback to the gamepad. +The `vibrate` control is a patternable control, so you can use it to control the vibration of the gamepad. + +The vibrate control has several parameters: + +- `vibGamepadIndex` - Index of the gamepad to vibrate (0-3) +- `vibStrong` - Intensity of the strong/left motor (0-1) +- `vibWeak` - Intensity of the weak/right motor (0-1) +- `vibDuration` - Duration of vibration in milliseconds +- `vibEnable` - Enable/disable vibration (0 or 1) + +```javascript +// Vibration with gamepad +// vibrate(gamepadId, { strong: 1, weak: 0.5, duration: 100 }) +// vibrate("gamepadId") +// vibrate(gamepadId) + +$: note("c4*4").vibrate(0, { strong: 1, weak: 0.5, duration: 100 }) // gamepad index 0 +$: note("c4*4").vibrate("0:1:0.5:200") // Array format +$: note("c4*4").vibrate("<0 1 0 1>") // select gamepad index + +$: note("c4*4").vibStrong("<0.1 0.2 0.5 1>").vibWeak("<0.1 0.2 0.5 1>").vibDuration("<10 20 50 100>").vibrate(0) // gamepad index 0 +``` diff --git a/packages/gamepad/gamepad.mjs b/packages/gamepad/gamepad.mjs index 1cc1ec008..8ce86c58e 100644 --- a/packages/gamepad/gamepad.mjs +++ b/packages/gamepad/gamepad.mjs @@ -1,11 +1,11 @@ // @strudel/gamepad/index.mjs -import { signal } from '@strudel/core'; -import { logger } from '@strudel/core'; +import { signal, Pattern, logger, registerControl, register, isPattern } from '@strudel/core'; +import { vibrationSupported, getGamepadVibrationActuator } from './vibration.mjs'; // Button mapping for Logitech Dual Action (STANDARD GAMEPAD Vendor: 046d Product: c216) -const buttonMapSettings = { +export const buttonMapSettings = { XBOX: { // XBOX mapping default a: 0, @@ -18,6 +18,10 @@ const buttonMapSettings = { rt: 7, back: 8, start: 9, + lstick: 10, + rstick: 11, + ls: 10, + rs: 11, u: 12, up: 12, d: 13, @@ -26,6 +30,7 @@ const buttonMapSettings = { left: 14, r: 15, right: 15, + xbox: 16, }, NES: { // Nintendo mapping @@ -35,10 +40,18 @@ const buttonMapSettings = { y: 2, lb: 4, rb: 5, + zl: 6, + zr: 7, lt: 6, rt: 7, back: 8, + minus: 8, start: 9, + plus: 9, + lstick: 10, + rstick: 11, + ls: 10, + rs: 11, u: 12, up: 12, d: 13, @@ -47,6 +60,8 @@ const buttonMapSettings = { left: 14, r: 15, right: 15, + home: 16, + select: 17, }, }; @@ -55,7 +70,7 @@ class ButtonSequenceDetector { this.sequence = []; this.timeWindow = timeWindow; this.lastInputTime = 0; - this.buttonStates = Array(16).fill(0); // Track previous state of each button + this.buttonStates = Array(Object.keys(mapping).length).fill(0); // Track previous state of each button this.buttonMap = mapping; // Button mapping for character inputs } @@ -132,7 +147,7 @@ class GamepadHandler { this._mapping = mapping; this._activeGamepad = index; // Use provided index this._axes = [0, 0, 0, 0]; - this._buttons = Array(16).fill(0); + this._buttons = Array(Object.keys(mapping).length).fill(0); this.setupEventListeners(); } @@ -216,7 +231,7 @@ export const gamepad = (index = 0, mapping = 'XBOX') => { } 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 + const maxButtons = requestedGamepad.buttons; for (const [key, value] of Object.entries(mapping)) { if (typeof value !== 'number' || value < 0 || value >= maxButtons) { throw new Error( @@ -262,7 +277,7 @@ export const gamepad = (index = 0, mapping = 'XBOX') => { axes.y2_2 = axes.y2.toBipolar(); // Create button patterns - const buttons = Array(16) + const buttons = Array(Object.keys(buttonMap).length) .fill(null) .map((_, i) => { // Create unique key for this gamepad+button combination @@ -306,8 +321,16 @@ export const gamepad = (index = 0, mapping = 'XBOX') => { const btnseq = btnSequence; const seq = btnSequence; + // Create vibration pattern + const vibration = (pattern) => { + return signal(() => { + // Return vibration enable/disable pattern + return pattern; + }); + }; + logger( - `[gamepad] connected to gamepad ${index} (${requestedGamepad.id}) with ${typeof mapping === 'object' ? 'custom' : mapping} mapping`, + `[gamepad] connected to gamepad ${index} (${requestedGamepad.id}) with ${typeof mapping === 'object' ? 'custom' : mapping} mapping${vibrationSupported(index) ? ' (vibration supported)' : ''}`, ); // Return an object with all controls @@ -328,6 +351,7 @@ export const gamepad = (index = 0, mapping = 'XBOX') => { btnSeq, btnseq, seq, + vibration, raw: baseSignal, }; }; diff --git a/packages/gamepad/vibration.mjs b/packages/gamepad/vibration.mjs new file mode 100644 index 000000000..aef64fc5e --- /dev/null +++ b/packages/gamepad/vibration.mjs @@ -0,0 +1,288 @@ +// Gamepad Vibration Controls + +import { registerControl, register, logger, isPattern } from '@strudel/core'; +import { Pattern } from '@strudel/core'; + +/** + * Controls the strong motor intensity for gamepad vibration. + * + * @name vibStrong + * @param {number | Pattern} intensity between 0 and 1 + * @synonyms vibS + * @example + * s("bd hh sd hh").vibStrong("1 0 0.5 0").vibrate(0) + */ +export const { vibStrong, vibS } = registerControl('vibStrong', 'vibS'); + +/** + * Controls the weak motor intensity for gamepad vibration. + * + * @name vibWeak + * @param {number | Pattern} intensity between 0 and 1 + * @synonyms vibW + * @example + * s("bd hh sd hh").vibWeak("0.5 0 0.8 0").vibrate(0) + */ +export const { vibWeak, vibW } = registerControl('vibWeak', 'vibW'); + +/** + * Controls the duration of gamepad vibration in milliseconds. + * + * @name vibDuration + * @param {number | Pattern} duration in milliseconds + * @synonyms vibDur + * @example + * s("bd hh sd hh").vibDuration("200 0 100 0").vibrate(0) + */ +export const { vibDuration, vibDur } = registerControl('vibDuration', 'vibDur'); + +/** + * Enables or disables gamepad vibration for individual events. + * + * @name vibEnable + * @param {number | Pattern} enable 1 to enable, 0 to disable + * @synonyms vibE + * @example + * s("bd hh sd hh").vibEnable("1 0 1 0").vibrate(0) + */ +export const { vibEnable, vibEn } = registerControl('vibEnable', 'vibEn'); + +/** + * Sets the gamepad index for vibration events. + * + * @name vibGamepadIndex + * @param {number | Pattern} index gamepad index (0-3) + * @synonyms vibGpId, vibGamepadIndex, vibGamepadId + * @example + * s("bd hh sd hh").vibGamepadIndex("0 1 0 1").vibrate() + */ +export const { vibGamepadIndex, vibGpId, vibGamepadId } = registerControl('vibGamepadIndex', 'vibGpId', 'vibGamepadId'); + +/** + * Sets both strong and weak vibration intensity at once. + * + * @name vibIntensity + * @param {number | Pattern} intensity between 0 and 1, applied to both motors + * @example + * s("bd*4").vibIntensity(sine.slow(2).range(0, 1)).vibrate(0) + */ +export const vibIntensity = register('vibIntensity', (intensity, pat) => { + return pat.vibStrong(intensity).vibWeak(intensity); +}); + +export const vibrationSupported = (index) => { + const gamepad = navigator.getGamepads()[index]; + return gamepad?.vibrationActuator?.type === 'dual-rumble'; +}; + +export const getGamepadVibrationActuator = (index) => { + const gamepad = navigator.getGamepads()[index]; + if (!gamepad) { + throw new Error(`[gamepad] gamepad at index ${index} not found`); + } + if (!gamepad.vibrationActuator) { + throw new Error(`[gamepad] gamepad at index ${index} does not support vibration`); + } + return gamepad.vibrationActuator; +}; + +// Default vibration latency in milliseconds +const DEFAULT_VIBRATION_LATENCY = 10; + +/** + * Parses vibration string format "id:strong:weak:duration" + * @param {string} vibString - The vibration string to parse + * @returns {object} Parsed vibration parameters + */ +function parseVibrationString(vibString) { + if (typeof vibString !== 'string') { + return null; + } + + const parts = vibString.split(':'); + if (parts.length < 1) { + return null; + } + + const [id, strong, weak, duration] = parts; + + return { + vibGamepadIndex: parseInt(id) || 0, + vibStrong: parseFloat(strong) || 0.8, + vibWeak: parseFloat(weak) || 0.4, + vibDuration: parseInt(duration) || 100, + vibEnable: 1, + }; +} + +/** + * Sends vibration commands to a gamepad based on pattern events. + * + * @name vibrate + * @memberof Pattern + * @param {number | string | Pattern} gamepadIndex - Index of the gamepad to vibrate (0-3), can be patterned, or vibration string + * @param {object} options - Default vibration options + * @param {number} options.strong - Default strong motor intensity (0-1) + * @param {number} options.weak - Default weak motor intensity (0-1) + * @param {number} options.duration - Default duration in milliseconds + * @param {number} options.latency - Latency compensation in milliseconds + * @returns {Pattern} + * @example + * // Basic vibration on beat + * s("bd hh sd hh").vibrate(0) + * + * @example + * // Using hap values for vibration control + * s("").vibStrong("<1 0.1 0.5 0>").vibWeak("<0.5 0 0.8 0>").vibrate(0) + * + * @example + * // With default options + * s("bd*4").vibrate(0, { strong: 1, weak: 0.5, duration: 100 }) + * + * @example + * // Using gamepad input to control vibration + * let gp = gamepad(0); + * note("c a f e").gain(gp.a).vibrate(0, { strong: gp.x1, duration: 200 }) + * + * @example + * // Patternable gamepad index - alternate between gamepads + * s("bd hh sd hh").vibrate("<0 1 0 1>") + * + * @example + * // Complex gamepad switching pattern + * s("bd*8").vibrate("0 1 0 [1 0] 1 0 1 0") + * + * @example + * // Using control parameter for gamepad index + * s("bd hh sd hh").vibGamepad("<0 1 0 1>").vibrate() + * + * @example + * // Assignable vibration syntax: "id:strong:weak:duration" + * s("bd hh sd hh").vibrate("0:1:0.5:200 1:0.8:0.3:150") + * + * @example + * // Complex assignable patterns + * s("bd*8").vibrate("0:0.2:0.4:100 0:0.2:1:100 1:1:0.5:200 1:0.5:1:150") + */ + +export const { vibrate } = registerControl(['vibGamepad', 'vibStrong', 'vibWeak', 'vibDuration']); + +Pattern.prototype.vibrate = function (gamepadIndex, options = {}) { + // Default options + const defaultOptions = { + strong: 0.8, + weak: 0.4, + duration: 100, + latency: DEFAULT_VIBRATION_LATENCY, + ...options, + }; + + // If gamepadIndex is a pattern, handle it based on its content + if (isPattern(gamepadIndex)) { + return this.set( + gamepadIndex.fmap((value) => { + // Simple gamepad index pattern + return { vibGamepadIndex: value }; + }), + ).onTrigger((time_deprecate, hap, currentTime, cps, targetTime) => { + return vibrateHandler(hap, currentTime, cps, targetTime, defaultOptions); + }, false); + } + + // If gamepadIndex is provided as static value, use it directly + // If undefined, will be handled in vibrateHandler (using vibGamepad control or default 0) + return this.onTrigger((time_deprecate, hap, currentTime, cps, targetTime) => { + return vibrateHandler(hap, currentTime, cps, targetTime, defaultOptions, gamepadIndex); + }, false); +}; + +// Helper function to handle the actual vibration logic +function vibrateHandler(hap, currentTime, cps, targetTime, defaultOptions, staticGamepadIndex = null) { + try { + // Determine gamepad index - from hap value or static parameter + let gamepadIndex; + if (staticGamepadIndex !== null && staticGamepadIndex !== undefined) { + gamepadIndex = staticGamepadIndex; + } else if (typeof hap.value === 'object' && ('vibGamepadIndex' in hap.value || 'vibGamepad' in hap.value)) { + const vibGamepadValue = hap.value.vibGamepadIndex; + gamepadIndex = Array.isArray(vibGamepadValue) ? vibGamepadValue[0] : vibGamepadValue; + } else { + gamepadIndex = 0; // fallback default + } + + // Validate gamepad index + gamepadIndex = Math.max(0, Math.min(3, Math.floor(Number(gamepadIndex) || 0))); + + // Check if vibration is supported + if (!vibrationSupported(gamepadIndex)) { + logger(`[gamepad] vibration not supported on gamepad ${gamepadIndex}`, 'warning'); + return; + } + + const vibrationActuator = getGamepadVibrationActuator(gamepadIndex); + + // Extract vibration parameters from hap value or use defaults + let vibStrong, vibWeak, vibDuration, vibEnable; + + if (typeof hap.value === 'object') { + const vibGamepadValue = hap.value.vibGamepadIndex; + + // Handle array format: [index, strong, weak, duration] + if (Array.isArray(vibGamepadValue)) { + vibStrong = vibGamepadValue[1] !== undefined ? vibGamepadValue[1] : defaultOptions.strong; + vibWeak = vibGamepadValue[2] !== undefined ? vibGamepadValue[2] : defaultOptions.weak; + vibDuration = vibGamepadValue[3] !== undefined ? vibGamepadValue[3] : defaultOptions.duration; + vibEnable = 1; + } else { + // Regular object destructuring for non-array values + ({ + vibStrong = defaultOptions.strong, + vibWeak = defaultOptions.weak, + vibDuration = defaultOptions.duration, + vibEnable = 1, + } = hap.value); + } + } else { + // If hap.value is a primitive, use it as vibEnable (0 = off, 1 = on) + vibEnable = hap.value; + vibStrong = defaultOptions.strong; + vibWeak = defaultOptions.weak; + vibDuration = defaultOptions.duration; + } + + // Skip vibration if disabled + if (!vibEnable || vibEnable === 0) { + return; + } + + // Validate parameters + vibStrong = Math.max(0, Math.min(1, Number(vibStrong) || 0)); + vibWeak = Math.max(0, Math.min(1, Number(vibWeak) || 0)); + vibDuration = Math.max(0, Number(vibDuration) || defaultOptions.duration); + + // Calculate timing offset + const offset = (targetTime - currentTime + defaultOptions.latency / 1000) * 1000; + + // Schedule vibration + window.setTimeout( + () => { + try { + vibrationActuator + .playEffect('dual-rumble', { + duration: vibDuration, + strongMagnitude: vibStrong, + weakMagnitude: vibWeak, + }) + .catch((err) => { + logger(`[gamepad] vibration failed: ${err.message}`, 'warning'); + }); + } catch (err) { + logger(`[gamepad] vibration error: ${err.message}`, 'warning'); + } + }, + Math.max(0, offset), + ); + } catch (err) { + logger(`[gamepad] vibration setup failed: ${err.message}`, 'warning'); + } +} diff --git a/test/__snapshots__/examples.test.mjs.snap b/test/__snapshots__/examples.test.mjs.snap index e6f2000e8..e6eee857e 100644 --- a/test/__snapshots__/examples.test.mjs.snap +++ b/test/__snapshots__/examples.test.mjs.snap @@ -8677,6 +8677,258 @@ exports[`runs examples > example "vib" example index 1 1`] = ` ] `; +exports[`runs examples > example "vibDuration" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | s:bd vibDuration:200 ]", + "[ 1/4 → 1/2 | s:hh vibDuration:0 ]", + "[ 1/2 → 3/4 | s:sd vibDuration:100 ]", + "[ 3/4 → 1/1 | s:hh vibDuration:0 ]", + "[ 1/1 → 5/4 | s:bd vibDuration:200 ]", + "[ 5/4 → 3/2 | s:hh vibDuration:0 ]", + "[ 3/2 → 7/4 | s:sd vibDuration:100 ]", + "[ 7/4 → 2/1 | s:hh vibDuration:0 ]", + "[ 2/1 → 9/4 | s:bd vibDuration:200 ]", + "[ 9/4 → 5/2 | s:hh vibDuration:0 ]", + "[ 5/2 → 11/4 | s:sd vibDuration:100 ]", + "[ 11/4 → 3/1 | s:hh vibDuration:0 ]", + "[ 3/1 → 13/4 | s:bd vibDuration:200 ]", + "[ 13/4 → 7/2 | s:hh vibDuration:0 ]", + "[ 7/2 → 15/4 | s:sd vibDuration:100 ]", + "[ 15/4 → 4/1 | s:hh vibDuration:0 ]", +] +`; + +exports[`runs examples > example "vibDuration" example index 0 2`] = ` +[ + "[ 0/1 → 1/4 | s:bd vibDuration:200 ]", + "[ 1/4 → 1/2 | s:hh vibDuration:0 ]", + "[ 1/2 → 3/4 | s:sd vibDuration:100 ]", + "[ 3/4 → 1/1 | s:hh vibDuration:0 ]", + "[ 1/1 → 5/4 | s:bd vibDuration:200 ]", + "[ 5/4 → 3/2 | s:hh vibDuration:0 ]", + "[ 3/2 → 7/4 | s:sd vibDuration:100 ]", + "[ 7/4 → 2/1 | s:hh vibDuration:0 ]", + "[ 2/1 → 9/4 | s:bd vibDuration:200 ]", + "[ 9/4 → 5/2 | s:hh vibDuration:0 ]", + "[ 5/2 → 11/4 | s:sd vibDuration:100 ]", + "[ 11/4 → 3/1 | s:hh vibDuration:0 ]", + "[ 3/1 → 13/4 | s:bd vibDuration:200 ]", + "[ 13/4 → 7/2 | s:hh vibDuration:0 ]", + "[ 7/2 → 15/4 | s:sd vibDuration:100 ]", + "[ 15/4 → 4/1 | s:hh vibDuration:0 ]", +] +`; + +exports[`runs examples > example "vibEnable" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | s:bd vibEnable:1 ]", + "[ 1/4 → 1/2 | s:hh vibEnable:0 ]", + "[ 1/2 → 3/4 | s:sd vibEnable:1 ]", + "[ 3/4 → 1/1 | s:hh vibEnable:0 ]", + "[ 1/1 → 5/4 | s:bd vibEnable:1 ]", + "[ 5/4 → 3/2 | s:hh vibEnable:0 ]", + "[ 3/2 → 7/4 | s:sd vibEnable:1 ]", + "[ 7/4 → 2/1 | s:hh vibEnable:0 ]", + "[ 2/1 → 9/4 | s:bd vibEnable:1 ]", + "[ 9/4 → 5/2 | s:hh vibEnable:0 ]", + "[ 5/2 → 11/4 | s:sd vibEnable:1 ]", + "[ 11/4 → 3/1 | s:hh vibEnable:0 ]", + "[ 3/1 → 13/4 | s:bd vibEnable:1 ]", + "[ 13/4 → 7/2 | s:hh vibEnable:0 ]", + "[ 7/2 → 15/4 | s:sd vibEnable:1 ]", + "[ 15/4 → 4/1 | s:hh vibEnable:0 ]", +] +`; + +exports[`runs examples > example "vibEnable" example index 0 2`] = ` +[ + "[ 0/1 → 1/4 | s:bd vibEnable:1 ]", + "[ 1/4 → 1/2 | s:hh vibEnable:0 ]", + "[ 1/2 → 3/4 | s:sd vibEnable:1 ]", + "[ 3/4 → 1/1 | s:hh vibEnable:0 ]", + "[ 1/1 → 5/4 | s:bd vibEnable:1 ]", + "[ 5/4 → 3/2 | s:hh vibEnable:0 ]", + "[ 3/2 → 7/4 | s:sd vibEnable:1 ]", + "[ 7/4 → 2/1 | s:hh vibEnable:0 ]", + "[ 2/1 → 9/4 | s:bd vibEnable:1 ]", + "[ 9/4 → 5/2 | s:hh vibEnable:0 ]", + "[ 5/2 → 11/4 | s:sd vibEnable:1 ]", + "[ 11/4 → 3/1 | s:hh vibEnable:0 ]", + "[ 3/1 → 13/4 | s:bd vibEnable:1 ]", + "[ 13/4 → 7/2 | s:hh vibEnable:0 ]", + "[ 7/2 → 15/4 | s:sd vibEnable:1 ]", + "[ 15/4 → 4/1 | s:hh vibEnable:0 ]", +] +`; + +exports[`runs examples > example "vibGamepad" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | s:bd vibGamepad:0 ]", + "[ 1/4 → 1/2 | s:hh vibGamepad:1 ]", + "[ 1/2 → 3/4 | s:sd vibGamepad:0 ]", + "[ 3/4 → 1/1 | s:hh vibGamepad:1 ]", + "[ 1/1 → 5/4 | s:bd vibGamepad:0 ]", + "[ 5/4 → 3/2 | s:hh vibGamepad:1 ]", + "[ 3/2 → 7/4 | s:sd vibGamepad:0 ]", + "[ 7/4 → 2/1 | s:hh vibGamepad:1 ]", + "[ 2/1 → 9/4 | s:bd vibGamepad:0 ]", + "[ 9/4 → 5/2 | s:hh vibGamepad:1 ]", + "[ 5/2 → 11/4 | s:sd vibGamepad:0 ]", + "[ 11/4 → 3/1 | s:hh vibGamepad:1 ]", + "[ 3/1 → 13/4 | s:bd vibGamepad:0 ]", + "[ 13/4 → 7/2 | s:hh vibGamepad:1 ]", + "[ 7/2 → 15/4 | s:sd vibGamepad:0 ]", + "[ 15/4 → 4/1 | s:hh vibGamepad:1 ]", +] +`; + +exports[`runs examples > example "vibGamepad" example index 0 2`] = ` +[ + "[ 0/1 → 1/4 | s:bd vibGamepad:0 ]", + "[ 1/4 → 1/2 | s:hh vibGamepad:1 ]", + "[ 1/2 → 3/4 | s:sd vibGamepad:0 ]", + "[ 3/4 → 1/1 | s:hh vibGamepad:1 ]", + "[ 1/1 → 5/4 | s:bd vibGamepad:0 ]", + "[ 5/4 → 3/2 | s:hh vibGamepad:1 ]", + "[ 3/2 → 7/4 | s:sd vibGamepad:0 ]", + "[ 7/4 → 2/1 | s:hh vibGamepad:1 ]", + "[ 2/1 → 9/4 | s:bd vibGamepad:0 ]", + "[ 9/4 → 5/2 | s:hh vibGamepad:1 ]", + "[ 5/2 → 11/4 | s:sd vibGamepad:0 ]", + "[ 11/4 → 3/1 | s:hh vibGamepad:1 ]", + "[ 3/1 → 13/4 | s:bd vibGamepad:0 ]", + "[ 13/4 → 7/2 | s:hh vibGamepad:1 ]", + "[ 7/2 → 15/4 | s:sd vibGamepad:0 ]", + "[ 15/4 → 4/1 | s:hh vibGamepad:1 ]", +] +`; + +exports[`runs examples > example "vibIntensity" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 1/4 → 1/2 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 1/2 → 3/4 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 3/4 → 1/1 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 1/1 → 5/4 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 5/4 → 3/2 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 3/2 → 7/4 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 7/4 → 2/1 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 2/1 → 9/4 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 9/4 → 5/2 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 5/2 → 11/4 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 11/4 → 3/1 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 3/1 → 13/4 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 13/4 → 7/2 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 7/2 → 15/4 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 15/4 → 4/1 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", +] +`; + +exports[`runs examples > example "vibIntensity" example index 0 2`] = ` +[ + "[ 0/1 → 1/4 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 1/4 → 1/2 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 1/2 → 3/4 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 3/4 → 1/1 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 1/1 → 5/4 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 5/4 → 3/2 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 3/2 → 7/4 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 7/4 → 2/1 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 2/1 → 9/4 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 9/4 → 5/2 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 5/2 → 11/4 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 11/4 → 3/1 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 3/1 → 13/4 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 13/4 → 7/2 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 7/2 → 15/4 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", + "[ 15/4 → 4/1 | s:bd vibStrong:0.4999999999999999 vibWeak:0.4999999999999999 ]", +] +`; + +exports[`runs examples > example "vibStrong" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | s:bd vibStrong:1 ]", + "[ 1/4 → 1/2 | s:hh vibStrong:0 ]", + "[ 1/2 → 3/4 | s:sd vibStrong:0.5 ]", + "[ 3/4 → 1/1 | s:hh vibStrong:0 ]", + "[ 1/1 → 5/4 | s:bd vibStrong:1 ]", + "[ 5/4 → 3/2 | s:hh vibStrong:0 ]", + "[ 3/2 → 7/4 | s:sd vibStrong:0.5 ]", + "[ 7/4 → 2/1 | s:hh vibStrong:0 ]", + "[ 2/1 → 9/4 | s:bd vibStrong:1 ]", + "[ 9/4 → 5/2 | s:hh vibStrong:0 ]", + "[ 5/2 → 11/4 | s:sd vibStrong:0.5 ]", + "[ 11/4 → 3/1 | s:hh vibStrong:0 ]", + "[ 3/1 → 13/4 | s:bd vibStrong:1 ]", + "[ 13/4 → 7/2 | s:hh vibStrong:0 ]", + "[ 7/2 → 15/4 | s:sd vibStrong:0.5 ]", + "[ 15/4 → 4/1 | s:hh vibStrong:0 ]", +] +`; + +exports[`runs examples > example "vibStrong" example index 0 2`] = ` +[ + "[ 0/1 → 1/4 | s:bd vibStrong:1 ]", + "[ 1/4 → 1/2 | s:hh vibStrong:0 ]", + "[ 1/2 → 3/4 | s:sd vibStrong:0.5 ]", + "[ 3/4 → 1/1 | s:hh vibStrong:0 ]", + "[ 1/1 → 5/4 | s:bd vibStrong:1 ]", + "[ 5/4 → 3/2 | s:hh vibStrong:0 ]", + "[ 3/2 → 7/4 | s:sd vibStrong:0.5 ]", + "[ 7/4 → 2/1 | s:hh vibStrong:0 ]", + "[ 2/1 → 9/4 | s:bd vibStrong:1 ]", + "[ 9/4 → 5/2 | s:hh vibStrong:0 ]", + "[ 5/2 → 11/4 | s:sd vibStrong:0.5 ]", + "[ 11/4 → 3/1 | s:hh vibStrong:0 ]", + "[ 3/1 → 13/4 | s:bd vibStrong:1 ]", + "[ 13/4 → 7/2 | s:hh vibStrong:0 ]", + "[ 7/2 → 15/4 | s:sd vibStrong:0.5 ]", + "[ 15/4 → 4/1 | s:hh vibStrong:0 ]", +] +`; + +exports[`runs examples > example "vibWeak" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | s:bd vibWeak:0.5 ]", + "[ 1/4 → 1/2 | s:hh vibWeak:0 ]", + "[ 1/2 → 3/4 | s:sd vibWeak:0.8 ]", + "[ 3/4 → 1/1 | s:hh vibWeak:0 ]", + "[ 1/1 → 5/4 | s:bd vibWeak:0.5 ]", + "[ 5/4 → 3/2 | s:hh vibWeak:0 ]", + "[ 3/2 → 7/4 | s:sd vibWeak:0.8 ]", + "[ 7/4 → 2/1 | s:hh vibWeak:0 ]", + "[ 2/1 → 9/4 | s:bd vibWeak:0.5 ]", + "[ 9/4 → 5/2 | s:hh vibWeak:0 ]", + "[ 5/2 → 11/4 | s:sd vibWeak:0.8 ]", + "[ 11/4 → 3/1 | s:hh vibWeak:0 ]", + "[ 3/1 → 13/4 | s:bd vibWeak:0.5 ]", + "[ 13/4 → 7/2 | s:hh vibWeak:0 ]", + "[ 7/2 → 15/4 | s:sd vibWeak:0.8 ]", + "[ 15/4 → 4/1 | s:hh vibWeak:0 ]", +] +`; + +exports[`runs examples > example "vibWeak" example index 0 2`] = ` +[ + "[ 0/1 → 1/4 | s:bd vibWeak:0.5 ]", + "[ 1/4 → 1/2 | s:hh vibWeak:0 ]", + "[ 1/2 → 3/4 | s:sd vibWeak:0.8 ]", + "[ 3/4 → 1/1 | s:hh vibWeak:0 ]", + "[ 1/1 → 5/4 | s:bd vibWeak:0.5 ]", + "[ 5/4 → 3/2 | s:hh vibWeak:0 ]", + "[ 3/2 → 7/4 | s:sd vibWeak:0.8 ]", + "[ 7/4 → 2/1 | s:hh vibWeak:0 ]", + "[ 2/1 → 9/4 | s:bd vibWeak:0.5 ]", + "[ 9/4 → 5/2 | s:hh vibWeak:0 ]", + "[ 5/2 → 11/4 | s:sd vibWeak:0.8 ]", + "[ 11/4 → 3/1 | s:hh vibWeak:0 ]", + "[ 3/1 → 13/4 | s:bd vibWeak:0.5 ]", + "[ 13/4 → 7/2 | s:hh vibWeak:0 ]", + "[ 7/2 → 15/4 | s:sd vibWeak:0.8 ]", + "[ 15/4 → 4/1 | s:hh vibWeak:0 ]", +] +`; + exports[`runs examples > example "vibmod" example index 0 1`] = ` [ "[ 0/1 → 1/2 | note:a vib:4 vibmod:0.25 ]", @@ -8703,6 +8955,198 @@ exports[`runs examples > example "vibmod" example index 1 1`] = ` ] `; +exports[`runs examples > example "vibrate" example index 0 1`] = ` +[ + "[ 0/1 → 1/4 | s:bd ]", + "[ 1/4 → 1/2 | s:hh ]", + "[ 1/2 → 3/4 | s:sd ]", + "[ 3/4 → 1/1 | s:hh ]", + "[ 1/1 → 5/4 | s:bd ]", + "[ 5/4 → 3/2 | s:hh ]", + "[ 3/2 → 7/4 | s:sd ]", + "[ 7/4 → 2/1 | s:hh ]", + "[ 2/1 → 9/4 | s:bd ]", + "[ 9/4 → 5/2 | s:hh ]", + "[ 5/2 → 11/4 | s:sd ]", + "[ 11/4 → 3/1 | s:hh ]", + "[ 3/1 → 13/4 | s:bd ]", + "[ 13/4 → 7/2 | s:hh ]", + "[ 7/2 → 15/4 | s:sd ]", + "[ 15/4 → 4/1 | s:hh ]", +] +`; + +exports[`runs examples > example "vibrate" example index 1 1`] = ` +[ + "[ 0/1 → 1/1 | s:bd vibStrong:1 vibWeak:0.5 ]", + "[ 1/1 → 2/1 | s:hh vibStrong:0.1 vibWeak:0 ]", + "[ 2/1 → 3/1 | s:sd vibStrong:0.5 vibWeak:0.8 ]", + "[ 3/1 → 4/1 | s:hh vibStrong:0 vibWeak:0 ]", +] +`; + +exports[`runs examples > example "vibrate" example index 2 1`] = ` +[ + "[ 0/1 → 1/4 | s:bd ]", + "[ 1/4 → 1/2 | s:bd ]", + "[ 1/2 → 3/4 | s:bd ]", + "[ 3/4 → 1/1 | s:bd ]", + "[ 1/1 → 5/4 | s:bd ]", + "[ 5/4 → 3/2 | s:bd ]", + "[ 3/2 → 7/4 | s:bd ]", + "[ 7/4 → 2/1 | s:bd ]", + "[ 2/1 → 9/4 | s:bd ]", + "[ 9/4 → 5/2 | s:bd ]", + "[ 5/2 → 11/4 | s:bd ]", + "[ 11/4 → 3/1 | s:bd ]", + "[ 3/1 → 13/4 | s:bd ]", + "[ 13/4 → 7/2 | s:bd ]", + "[ 7/2 → 15/4 | s:bd ]", + "[ 15/4 → 4/1 | s:bd ]", +] +`; + +exports[`runs examples > example "vibrate" example index 4 1`] = ` +[ + "[ 0/1 → 1/4 | s:bd vibGamepadIndex:0 ]", + "[ 1/4 → 1/2 | s:hh vibGamepadIndex:0 ]", + "[ 1/2 → 3/4 | s:sd vibGamepadIndex:0 ]", + "[ 3/4 → 1/1 | s:hh vibGamepadIndex:0 ]", + "[ 1/1 → 5/4 | s:bd vibGamepadIndex:1 ]", + "[ 5/4 → 3/2 | s:hh vibGamepadIndex:1 ]", + "[ 3/2 → 7/4 | s:sd vibGamepadIndex:1 ]", + "[ 7/4 → 2/1 | s:hh vibGamepadIndex:1 ]", + "[ 2/1 → 9/4 | s:bd vibGamepadIndex:0 ]", + "[ 9/4 → 5/2 | s:hh vibGamepadIndex:0 ]", + "[ 5/2 → 11/4 | s:sd vibGamepadIndex:0 ]", + "[ 11/4 → 3/1 | s:hh vibGamepadIndex:0 ]", + "[ 3/1 → 13/4 | s:bd vibGamepadIndex:1 ]", + "[ 13/4 → 7/2 | s:hh vibGamepadIndex:1 ]", + "[ 7/2 → 15/4 | s:sd vibGamepadIndex:1 ]", + "[ 15/4 → 4/1 | s:hh vibGamepadIndex:1 ]", +] +`; + +exports[`runs examples > example "vibrate" example index 5 1`] = ` +[ + "[ 0/1 → 1/8 | s:bd vibGamepadIndex:0 ]", + "[ 1/8 → 1/4 | s:bd vibGamepadIndex:1 ]", + "[ 1/4 → 3/8 | s:bd vibGamepadIndex:0 ]", + "[ (3/8 → 7/16) ⇝ 1/2 | s:bd vibGamepadIndex:1 ]", + "[ 3/8 ⇜ (7/16 → 1/2) | s:bd vibGamepadIndex:0 ]", + "[ 1/2 → 5/8 | s:bd vibGamepadIndex:1 ]", + "[ 5/8 → 3/4 | s:bd vibGamepadIndex:0 ]", + "[ 3/4 → 7/8 | s:bd vibGamepadIndex:1 ]", + "[ 7/8 → 1/1 | s:bd vibGamepadIndex:0 ]", + "[ 1/1 → 9/8 | s:bd vibGamepadIndex:0 ]", + "[ 9/8 → 5/4 | s:bd vibGamepadIndex:1 ]", + "[ 5/4 → 11/8 | s:bd vibGamepadIndex:0 ]", + "[ (11/8 → 23/16) ⇝ 3/2 | s:bd vibGamepadIndex:1 ]", + "[ 11/8 ⇜ (23/16 → 3/2) | s:bd vibGamepadIndex:0 ]", + "[ 3/2 → 13/8 | s:bd vibGamepadIndex:1 ]", + "[ 13/8 → 7/4 | s:bd vibGamepadIndex:0 ]", + "[ 7/4 → 15/8 | s:bd vibGamepadIndex:1 ]", + "[ 15/8 → 2/1 | s:bd vibGamepadIndex:0 ]", + "[ 2/1 → 17/8 | s:bd vibGamepadIndex:0 ]", + "[ 17/8 → 9/4 | s:bd vibGamepadIndex:1 ]", + "[ 9/4 → 19/8 | s:bd vibGamepadIndex:0 ]", + "[ (19/8 → 39/16) ⇝ 5/2 | s:bd vibGamepadIndex:1 ]", + "[ 19/8 ⇜ (39/16 → 5/2) | s:bd vibGamepadIndex:0 ]", + "[ 5/2 → 21/8 | s:bd vibGamepadIndex:1 ]", + "[ 21/8 → 11/4 | s:bd vibGamepadIndex:0 ]", + "[ 11/4 → 23/8 | s:bd vibGamepadIndex:1 ]", + "[ 23/8 → 3/1 | s:bd vibGamepadIndex:0 ]", + "[ 3/1 → 25/8 | s:bd vibGamepadIndex:0 ]", + "[ 25/8 → 13/4 | s:bd vibGamepadIndex:1 ]", + "[ 13/4 → 27/8 | s:bd vibGamepadIndex:0 ]", + "[ (27/8 → 55/16) ⇝ 7/2 | s:bd vibGamepadIndex:1 ]", + "[ 27/8 ⇜ (55/16 → 7/2) | s:bd vibGamepadIndex:0 ]", + "[ 7/2 → 29/8 | s:bd vibGamepadIndex:1 ]", + "[ 29/8 → 15/4 | s:bd vibGamepadIndex:0 ]", + "[ 15/4 → 31/8 | s:bd vibGamepadIndex:1 ]", + "[ 31/8 → 4/1 | s:bd vibGamepadIndex:0 ]", +] +`; + +exports[`runs examples > example "vibrate" example index 6 1`] = ` +[ + "[ 0/1 → 1/4 | s:bd vibGamepad:0 ]", + "[ 1/4 → 1/2 | s:hh vibGamepad:0 ]", + "[ 1/2 → 3/4 | s:sd vibGamepad:0 ]", + "[ 3/4 → 1/1 | s:hh vibGamepad:0 ]", + "[ 1/1 → 5/4 | s:bd vibGamepad:1 ]", + "[ 5/4 → 3/2 | s:hh vibGamepad:1 ]", + "[ 3/2 → 7/4 | s:sd vibGamepad:1 ]", + "[ 7/4 → 2/1 | s:hh vibGamepad:1 ]", + "[ 2/1 → 9/4 | s:bd vibGamepad:0 ]", + "[ 9/4 → 5/2 | s:hh vibGamepad:0 ]", + "[ 5/2 → 11/4 | s:sd vibGamepad:0 ]", + "[ 11/4 → 3/1 | s:hh vibGamepad:0 ]", + "[ 3/1 → 13/4 | s:bd vibGamepad:1 ]", + "[ 13/4 → 7/2 | s:hh vibGamepad:1 ]", + "[ 7/2 → 15/4 | s:sd vibGamepad:1 ]", + "[ 15/4 → 4/1 | s:hh vibGamepad:1 ]", +] +`; + +exports[`runs examples > example "vibrate" example index 7 1`] = ` +[ + "[ 0/1 → 1/4 | s:bd vibGamepadIndex:[0 1 0.5 200] ]", + "[ 1/4 → 1/2 | s:hh vibGamepadIndex:[0 1 0.5 200] ]", + "[ 1/2 → 3/4 | s:sd vibGamepadIndex:[1 0.8 0.3 150] ]", + "[ 3/4 → 1/1 | s:hh vibGamepadIndex:[1 0.8 0.3 150] ]", + "[ 1/1 → 5/4 | s:bd vibGamepadIndex:[0 1 0.5 200] ]", + "[ 5/4 → 3/2 | s:hh vibGamepadIndex:[0 1 0.5 200] ]", + "[ 3/2 → 7/4 | s:sd vibGamepadIndex:[1 0.8 0.3 150] ]", + "[ 7/4 → 2/1 | s:hh vibGamepadIndex:[1 0.8 0.3 150] ]", + "[ 2/1 → 9/4 | s:bd vibGamepadIndex:[0 1 0.5 200] ]", + "[ 9/4 → 5/2 | s:hh vibGamepadIndex:[0 1 0.5 200] ]", + "[ 5/2 → 11/4 | s:sd vibGamepadIndex:[1 0.8 0.3 150] ]", + "[ 11/4 → 3/1 | s:hh vibGamepadIndex:[1 0.8 0.3 150] ]", + "[ 3/1 → 13/4 | s:bd vibGamepadIndex:[0 1 0.5 200] ]", + "[ 13/4 → 7/2 | s:hh vibGamepadIndex:[0 1 0.5 200] ]", + "[ 7/2 → 15/4 | s:sd vibGamepadIndex:[1 0.8 0.3 150] ]", + "[ 15/4 → 4/1 | s:hh vibGamepadIndex:[1 0.8 0.3 150] ]", +] +`; + +exports[`runs examples > example "vibrate" example index 8 1`] = ` +[ + "[ 0/1 → 1/8 | s:bd vibGamepadIndex:[0 0.2 0.4 100] ]", + "[ 1/8 → 1/4 | s:bd vibGamepadIndex:[0 0.2 0.4 100] ]", + "[ 1/4 → 3/8 | s:bd vibGamepadIndex:[0 0.2 1 100] ]", + "[ 3/8 → 1/2 | s:bd vibGamepadIndex:[0 0.2 1 100] ]", + "[ 1/2 → 5/8 | s:bd vibGamepadIndex:[1 1 0.5 200] ]", + "[ 5/8 → 3/4 | s:bd vibGamepadIndex:[1 1 0.5 200] ]", + "[ 3/4 → 7/8 | s:bd vibGamepadIndex:[1 0.5 1 150] ]", + "[ 7/8 → 1/1 | s:bd vibGamepadIndex:[1 0.5 1 150] ]", + "[ 1/1 → 9/8 | s:bd vibGamepadIndex:[0 0.2 0.4 100] ]", + "[ 9/8 → 5/4 | s:bd vibGamepadIndex:[0 0.2 0.4 100] ]", + "[ 5/4 → 11/8 | s:bd vibGamepadIndex:[0 0.2 1 100] ]", + "[ 11/8 → 3/2 | s:bd vibGamepadIndex:[0 0.2 1 100] ]", + "[ 3/2 → 13/8 | s:bd vibGamepadIndex:[1 1 0.5 200] ]", + "[ 13/8 → 7/4 | s:bd vibGamepadIndex:[1 1 0.5 200] ]", + "[ 7/4 → 15/8 | s:bd vibGamepadIndex:[1 0.5 1 150] ]", + "[ 15/8 → 2/1 | s:bd vibGamepadIndex:[1 0.5 1 150] ]", + "[ 2/1 → 17/8 | s:bd vibGamepadIndex:[0 0.2 0.4 100] ]", + "[ 17/8 → 9/4 | s:bd vibGamepadIndex:[0 0.2 0.4 100] ]", + "[ 9/4 → 19/8 | s:bd vibGamepadIndex:[0 0.2 1 100] ]", + "[ 19/8 → 5/2 | s:bd vibGamepadIndex:[0 0.2 1 100] ]", + "[ 5/2 → 21/8 | s:bd vibGamepadIndex:[1 1 0.5 200] ]", + "[ 21/8 → 11/4 | s:bd vibGamepadIndex:[1 1 0.5 200] ]", + "[ 11/4 → 23/8 | s:bd vibGamepadIndex:[1 0.5 1 150] ]", + "[ 23/8 → 3/1 | s:bd vibGamepadIndex:[1 0.5 1 150] ]", + "[ 3/1 → 25/8 | s:bd vibGamepadIndex:[0 0.2 0.4 100] ]", + "[ 25/8 → 13/4 | s:bd vibGamepadIndex:[0 0.2 0.4 100] ]", + "[ 13/4 → 27/8 | s:bd vibGamepadIndex:[0 0.2 1 100] ]", + "[ 27/8 → 7/2 | s:bd vibGamepadIndex:[0 0.2 1 100] ]", + "[ 7/2 → 29/8 | s:bd vibGamepadIndex:[1 1 0.5 200] ]", + "[ 29/8 → 15/4 | s:bd vibGamepadIndex:[1 1 0.5 200] ]", + "[ 15/4 → 31/8 | s:bd vibGamepadIndex:[1 0.5 1 150] ]", + "[ 31/8 → 4/1 | s:bd vibGamepadIndex:[1 0.5 1 150] ]", +] +`; + exports[`runs examples > example "voicing" example index 0 1`] = ` [ "[ 0/1 → 1/4 | note:52 ]",