function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } } function _async_to_generator(fn) { return function() { var self = this, args = arguments; return new Promise(function(resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; } function _class_call_check(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for(var i = 0; i < props.length; i++){ var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _create_class(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } function _ts_generator(thisArg, body) { var f, y, t, g, _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }; return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; function verb(n) { return function(v) { return step([ n, v ]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); while(_)try { if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; if (y = 0, t) op = [ op[0] & 2, t.value ]; switch(op[0]){ case 0: case 1: t = op; break; case 4: _.label++; return { value: op[1], done: false }; case 5: _.label++; y = op[1]; op = [ 0 ]; continue; case 7: op = _.ops.pop(); _.trys.pop(); continue; default: if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) { _.label = op[1]; break; } if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } if (t[2]) _.ops.pop(); _.trys.pop(); continue; } op = body.call(thisArg, _); } catch (e) { op = [ 6, e ]; y = 0; } finally{ f = t = 0; } if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; } } import * as Tone from 'https://esm.sh/tone'; // A simple manager for our Tone.js based music generation export var MusicManager = /*#__PURE__*/ function() { "use strict"; function MusicManager() { _class_call_check(this, MusicManager); this.polySynth = null; this.reverb = null; this.stereoDelay = null; // Add a delay effect property this.analyser = null; // For waveform visualization this.isStarted = false; // Use a Map to store the active arpeggio pattern for each hand this.activePatterns = new Map(); // Use a Map to store the current volume (velocity) for each hand's arpeggio this.handVolumes = new Map(); this.synthPresets = [ // Preset 1: Clean Sine Wave (Default) { harmonicity: 4, modulationIndex: 3, oscillator: { type: 'sine' }, envelope: { attack: 0.01, decay: 0.2, sustain: 0.5, release: 1.0 }, modulation: { type: 'sine' }, modulationEnvelope: { attack: 0.1, decay: 0.01, sustain: 1, release: 0.5 } }, // Preset 2: Buzzy Sawtooth { harmonicity: 1, modulationIndex: 8, oscillator: { type: 'sawtooth' }, // More staccato/plucky envelope envelope: { attack: 0.01, decay: 0.15, sustain: 0.05, release: 0.2 }, modulation: { type: 'square' }, modulationEnvelope: { attack: 0.05, decay: 0.2, sustain: 0.4, release: 0.6 } }, // Preset 3: Funk Electric Piano (Rhodes-like) { harmonicity: 2, modulationIndex: 12, oscillator: { type: 'sine' }, envelope: { attack: 0.02, decay: 0.3, sustain: 0.2, release: 0.8 }, modulation: { type: 'sine' }, modulationEnvelope: { attack: 0.05, decay: 0.2, sustain: 0.1, release: 0.8 }, effects: { reverbWet: 0.3, delayWet: 0.1 // A touch of delay } } ]; this.currentSynthIndex = 0; } _create_class(MusicManager, [ { key: "start", value: // Must be called after a user interaction function start() { var _this = this; return _async_to_generator(function() { return _ts_generator(this, function(_state) { switch(_state.label){ case 0: if (_this.isStarted) return [ 2 ]; return [ 4, Tone.start() ]; case 1: _state.sent(); _this.reverb = new Tone.Reverb({ decay: 5, preDelay: 0.0, wet: 0.8 }).toDestination(); // Create a stereo delay and connect it to the reverb _this.stereoDelay = new Tone.FeedbackDelay("8n", 0.5).connect(_this.reverb); _this.stereoDelay.wet.value = 0; // Start with no delay effect // Create an analyser for the waveform visualizer _this.analyser = new Tone.Analyser('waveform', 1024); // Use PolySynth to allow multiple arpeggios (one per hand) to play simultaneously. // The synth now connects to the analyser, then to the delay, which then connects to the reverb. _this.polySynth = new Tone.PolySynth(Tone.FMSynth, _this.synthPresets[_this.currentSynthIndex]); _this.polySynth.connect(_this.analyser); _this.analyser.connect(_this.stereoDelay); // Set a low volume to avoid clipping and create a more ambient feel _this.polySynth.volume.value = 0; _this.isStarted = true; // Set the master tempo to 100 BPM Tone.Transport.bpm.value = 100; // Start the master transport Tone.Transport.start(); console.log("Tone.js AudioContext started and PolySynth is ready."); return [ 2 ]; } }); })(); } }, { // Starts an arpeggio for a specific hand key: "startArpeggio", value: function startArpeggio(handId, rootNote) { var _this = this; if (!this.polySynth || this.activePatterns.has(handId)) return; // Create a richer arpeggio (Major 7th + Octave) // C Minor Pentatonic scale intervals: Root, Minor Third, Perfect Fourth, Perfect Fifth, Minor Seventh var chord = Tone.Frequency(rootNote).harmonize([ 0, 3, 5, 7, 10, 12 ]); var arpeggioNotes = chord.map(function(freq) { return Tone.Frequency(freq).toNote(); }); // Tone.Pattern cycles through an array of values var pattern = new Tone.Pattern(function(time, note) { // Get the latest volume (velocity) for this hand, defaulting to a soft 0.2 if not set. var velocity = _this.handVolumes.get(handId) || 0.2; // The time argument is the precise time the note should be played _this.polySynth.triggerAttackRelease(note, "16n", time, velocity); }, arpeggioNotes, "upDown"); pattern.interval = "16n"; // The time between notes in the pattern (faster) pattern.start(0); // Start the pattern immediately // Store the pattern and its root note so we can update/stop it later this.activePatterns.set(handId, { pattern: pattern, currentRoot: rootNote }); } }, { // Updates the volume (velocity) of an existing arpeggio key: "updateArpeggioVolume", value: function updateArpeggioVolume(handId, velocity) { // Only update if an arpeggio is active for this hand if (this.polySynth && this.activePatterns.has(handId)) { // Clamp the velocity to be safe var clampedVelocity = Math.max(0, Math.min(1, velocity)); this.handVolumes.set(handId, clampedVelocity); // IMPORTANT FIX: Also set the synth's overall volume. // Since we only have one arpeggio at a time, we can map this directly. // Using logarithmic scaling for a more natural volume control. var volumeInDb = Tone.gainToDb(clampedVelocity); this.polySynth.volume.value = volumeInDb; } } }, { // Updates the notes in an existing arpeggio key: "updateArpeggio", value: function updateArpeggio(handId, newRootNote) { var activePattern = this.activePatterns.get(handId); if (!this.polySynth || !activePattern || activePattern.currentRoot === newRootNote) { return; // No need to update if the note hasn't changed } // Create new notes for the pattern var newChord = Tone.Frequency(newRootNote).harmonize([ 0, 3, 5, 7, 10, 12 ]); activePattern.pattern.values = newChord.map(function(freq) { return Tone.Frequency(freq).toNote(); }); activePattern.currentRoot = newRootNote; } }, { // Stops and cleans up an arpeggio for a specific hand key: "stopArpeggio", value: function stopArpeggio(handId) { var activePattern = this.activePatterns.get(handId); if (activePattern) { activePattern.pattern.stop(0); // Stop the pattern activePattern.pattern.dispose(); // Clean up Tone.js objects this.activePatterns.delete(handId); // Remove from our map this.handVolumes.delete(handId); // Clean up the stored volume // If no other hands are playing, silence the synth. if (this.activePatterns.size === 0) { this.polySynth.volume.value = -Infinity; } } } }, { // Cycles to the next synth preset key: "cycleSynth", value: function cycleSynth() { var _this = this; var _newPreset_effects, _newPreset_effects1; if (!this.polySynth) return; // Stop all currently playing notes/arpeggios before swapping this.activePatterns.forEach(function(value, key) { _this.stopArpeggio(key); }); // Dispose the old synth to free up resources this.polySynth.dispose(); // Cycle to the next preset this.currentSynthIndex = (this.currentSynthIndex + 1) % this.synthPresets.length; var newPreset = this.synthPresets[this.currentSynthIndex]; // Create the new synth but don't connect it yet this.polySynth = new Tone.PolySynth(Tone.FMSynth, newPreset); // Re-establish the audio chain: synth -> analyser -> delay this.polySynth.connect(this.analyser); this.polySynth.volume.value = 0; // Reset volume var _newPreset_effects_reverbWet; // Adjust global effects based on the new preset's settings // Use optional chaining `?.` to safely access `effects` property this.reverb.wet.value = (_newPreset_effects_reverbWet = (_newPreset_effects = newPreset.effects) === null || _newPreset_effects === void 0 ? void 0 : _newPreset_effects.reverbWet) !== null && _newPreset_effects_reverbWet !== void 0 ? _newPreset_effects_reverbWet : 0.8; // Default to 0.8 if not specified var _newPreset_effects_delayWet; this.stereoDelay.wet.value = (_newPreset_effects_delayWet = (_newPreset_effects1 = newPreset.effects) === null || _newPreset_effects1 === void 0 ? void 0 : _newPreset_effects1.delayWet) !== null && _newPreset_effects_delayWet !== void 0 ? _newPreset_effects_delayWet : 0; // Default to 0 if not specified console.log("Switched to synth preset: ".concat(this.currentSynthIndex)); } }, { // Getter for the analyser so the game can use it key: "getAnalyser", value: function getAnalyser() { return this.analyser; } } ]); return MusicManager; }();