Spaces:
Running
Running
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; | |
}(); | |