function _array_like_to_array(arr, len) { if (len == null || len > arr.length) len = arr.length; for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i]; return arr2; } function _array_with_holes(arr) { if (Array.isArray(arr)) return arr; } function _iterable_to_array_limit(arr, i) { var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"]; if (_i == null) return; var _arr = []; var _n = true; var _d = false; var _s, _e; try { for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){ _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally{ try { if (!_n && _i["return"] != null) _i["return"](); } finally{ if (_d) throw _e; } } return _arr; } function _non_iterable_rest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } function _sliced_to_array(arr, i) { return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest(); } function _unsupported_iterable_to_array(o, minLen) { if (!o) return; if (typeof o === "string") return _array_like_to_array(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(n); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen); } import * as Tone from 'https://esm.sh/tone'; // --- Module State --- var players = null; var isLoaded = false; var sequence = null; var beatIndex = 0; var activeDrums = new Set(); // More varied and syncopated drum patterns var drumPattern = { // Syncopated kick pattern //'kick': [true, false, false, false, true, false, false, false, true, false, false, false, true, false, false, false], 'kick': [ true, false, false, false, false, true, false, false, true, false, false, true, false, true, false, false ], // Snare on the backbeat (beats 2 and 4) 'snare': [ false, false, false, false, true, false, false, false, false, false, false, false, true, false, false, false ], // Open hi-hat feel on the off-beats 'hihat': [ false, true, false, true, false, true, false, true, false, true, false, true, false, true, false, true ], // Clap layered with snare, but with an extra syncopated hit 'clap': [ false, false, false, false, true, false, false, true, false, false, false, false, true, false, false, false ] }; var fingerToDrumMap = { 'index': 'kick', 'middle': 'snare', 'ring': 'hihat', 'pinky': 'clap' }; // --- Exported Functions --- /** * Loads all drum samples and returns a promise that resolves when loading is complete */ export function loadSamples() { return new Promise(function(resolve, reject) { if (isLoaded) { resolve(); return; } players = new Tone.Players({ urls: { kick: 'assets/kick.wav', snare: 'assets/snare.wav', hihat: 'assets/hihat.wav', clap: 'assets/clap.wav' }, onload: function() { isLoaded = true; // Set volumes after loading players.player('kick').volume.value = -6; // Lowered kick volume players.player('snare').volume.value = 0; players.player('hihat').volume.value = -2; // Softer hi-hat players.player('clap').volume.value = 0; console.log("Drum samples loaded successfully."); resolve(); }, onerror: function(error) { console.error("Error loading drum samples:", error); reject(error); } }).toDestination(); }); } /** * Creates and starts the main 16-step drum loop. * Assumes Tone.Transport has been started elsewhere. */ export function startSequence() { if (!isLoaded || sequence) { console.warn("Drums not loaded or sequence already started. Cannot start sequence."); return; } sequence = new Tone.Sequence(function(time, step) { beatIndex = step; // Update for visualization Object.entries(drumPattern).forEach(function(param) { var _param = _sliced_to_array(param, 2), drum = _param[0], pattern = _param[1]; // If the drum is active AND its pattern has a note on this step... if (activeDrums.has(drum) && pattern[step]) { players.player(drum).start(time); } }); }, Array.from({ length: 16 }, function(_, i) { return i; }), "16n").start(0); console.log("Drum sequence started."); } /** * Updates which drums are active based on finger positions. * @param {object} fingerStates - An object with finger names as keys and boolean `isUp` as values. */ export function updateActiveDrums(fingerStates) { activeDrums.clear(); var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined; try { for(var _iterator = Object.entries(fingerStates)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){ var _step_value = _sliced_to_array(_step.value, 2), finger = _step_value[0], isUp = _step_value[1]; if (isUp) { var drum = fingerToDrumMap[finger]; if (drum) { activeDrums.add(drum); } } } } catch (err) { _didIteratorError = true; _iteratorError = err; } finally{ try { if (!_iteratorNormalCompletion && _iterator.return != null) { _iterator.return(); } } finally{ if (_didIteratorError) { throw _iteratorError; } } } } /** * Returns the set of currently active drums. * @returns {Set} A set of active drum names. */ export function getActiveDrums() { return activeDrums; } /** * Returns the mapping of fingers to drums. * @returns {object} The finger-to-drum map. */ export function getFingerToDrumMap() { return fingerToDrumMap; } /** * Returns the current beat index for external use (like visualization). * @returns {number} The current beat index (0-15). */ export function getCurrentBeat() { return beatIndex; } /** * Returns the master drum pattern object. * @returns {object} The drum pattern. */ export function getDrumPattern() { return drumPattern; }