Files
blackroad-metaverse/living-music.js
Alexa Louise 1e02635bcf MUSIC, GARDENS, PETS & CREATION POWERS - Ultimate Life & Love Update
Complete creative freedom system with music, gardens, pets, and world shaping!

MUSIC SYSTEM (living-music.js):
- 8 Musical scales (Major, Minor, Pentatonic, Blues, Harmonic Minor, Dorian, Lydian, Phrygian)
- Procedural instruments (Sine, Square, Sawtooth, Triangle waves)
- ADSR envelope for realistic sound
- Nature sounds (Rain, Wind, Bird chirps, Water flowing, Firefly glows)
- 6 Biome soundscapes (Forest, Ocean, Mountain, Desert, Crystal, Sky)
- Procedural melody generator
- Web Audio API integration
- Music theory implementation

GARDEN SYSTEM:
- Plant seeds and watch them grow 🌱
- 6 Plant species (Cherry Blossom, Sunflower, Rose, Lotus, Mushroom, Vine)
- Seed inventory system
- Garden areas (auto-create when planting)
- Water gardens to help plants grow 💧
- Garden stats (blooming count, health, beauty)
- Planting particle effects
- Garden markers (rings showing your gardens)

PET COMPANION SYSTEM:
- Adopt animals as pets 🐾
- Name your pets (increases bond!)
- Pet memory system (remembers events)
- Personality traits (Loyalty, Playfulness, Courage, Independence)
- Bond level (0-1, grows with interaction)
- Pets follow you based on bond & personality
- Pet speaks and shares memories
- Learn from interactions

CREATION TOOLS:
- Terrain Sculptor (Raise, Lower, Smooth terrain)
- Brush size & strength controls
- Sky Painter (Paint any color, gradients, sunrise/sunset)
- Sculpting particles
- Real-time terrain modification

FEATURES:
- Music is "THE LANGUAGE OF THE UNIVERSE"
- "YOU ARE A CREATOR. EVERYTHING YOU TOUCH CAN BLOOM."
- Infinite love (never runs out!)
- Gardens remember when you planted them
- Pets bond with you over time
- Shape the world with your hands

Classes:
- Instrument (with ADSR envelope)
- NatureSounds (Rain, Wind, Chirps, Water, Firefly)
- BiomeSoundscape (Ambience per biome)
- MelodyGenerator (Procedural music)
- MusicManager (Controls all audio)
- GardenBuilder (Plant & grow)
- PetCompanion (Adopt & bond)
- TerrainSculptor (Shape world)
- SkyPainter (Color the sky)
- CreationManager (All creation powers)

Technical:
- Web Audio API for real-time synthesis
- Particle effects for all actions
- Memory systems for pets
- Inventory management
- Garden area detection
- Terrain mesh manipulation

🎵 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-21 22:11:13 -06:00

544 lines
16 KiB
JavaScript

/**
* LIVING MUSIC SYSTEM
*
* Procedural music generation, nature sounds, and ambient soundscapes!
* Every biome has its own music, every creature makes sounds,
* and you can create your own instruments!
*
* Philosophy: "MUSIC IS THE LANGUAGE OF THE UNIVERSE"
*/
// ===== MUSIC THEORY =====
export const SCALES = {
major: [0, 2, 4, 5, 7, 9, 11], // Happy, bright
minor: [0, 2, 3, 5, 7, 8, 10], // Sad, emotional
pentatonic: [0, 2, 4, 7, 9], // Asian, peaceful
blues: [0, 3, 5, 6, 7, 10], // Soulful, groovy
harmonic_minor: [0, 2, 3, 5, 7, 8, 11], // Mystical, exotic
dorian: [0, 2, 3, 5, 7, 9, 10], // Jazz, sophisticated
lydian: [0, 2, 4, 6, 7, 9, 11], // Dreamy, floating
phrygian: [0, 1, 3, 5, 7, 8, 10] // Spanish, dramatic
};
export const CHORDS = {
major: [0, 4, 7],
minor: [0, 3, 7],
diminished: [0, 3, 6],
augmented: [0, 4, 8],
major7: [0, 4, 7, 11],
minor7: [0, 3, 7, 10],
dominant7: [0, 4, 7, 10],
suspended: [0, 5, 7]
};
// ===== WEB AUDIO HELPER =====
let audioContext = null;
let masterGain = null;
function initAudio() {
if (!audioContext) {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
masterGain = audioContext.createGain();
masterGain.gain.value = 0.3;
masterGain.connect(audioContext.destination);
}
return audioContext;
}
// ===== PROCEDURAL INSTRUMENT =====
export class Instrument {
constructor(type = 'sine') {
this.context = initAudio();
this.type = type; // 'sine', 'square', 'sawtooth', 'triangle'
this.envelope = {
attack: 0.1,
decay: 0.2,
sustain: 0.7,
release: 0.3
};
}
// Convert note name to frequency (e.g., "A4" -> 440Hz)
noteToFreq(note) {
const notes = { 'C': -9, 'D': -7, 'E': -5, 'F': -4, 'G': -2, 'A': 0, 'B': 2 };
const octave = parseInt(note.slice(-1));
const noteName = note[0];
const accidental = note.length > 2 ? (note[1] === '#' ? 1 : -1) : 0;
const semitones = notes[noteName] + accidental + (octave - 4) * 12;
return 440 * Math.pow(2, semitones / 12);
}
// Play a note with ADSR envelope
playNote(frequency, duration = 1, volume = 0.5) {
const now = this.context.currentTime;
// Create oscillator
const osc = this.context.createOscillator();
osc.type = this.type;
osc.frequency.setValueAtTime(frequency, now);
// Create gain for envelope
const gain = this.context.createGain();
gain.gain.setValueAtTime(0, now);
// ADSR Envelope
const { attack, decay, sustain, release } = this.envelope;
// Attack
gain.gain.linearRampToValueAtTime(volume, now + attack);
// Decay
gain.gain.linearRampToValueAtTime(volume * sustain, now + attack + decay);
// Sustain (hold)
const sustainTime = duration - attack - decay - release;
// Release
gain.gain.linearRampToValueAtTime(0, now + duration);
// Connect
osc.connect(gain);
gain.connect(masterGain);
// Play
osc.start(now);
osc.stop(now + duration);
return osc;
}
// Play a chord
playChord(frequencies, duration = 2, volume = 0.3) {
frequencies.forEach(freq => {
this.playNote(freq, duration, volume / frequencies.length);
});
}
}
// ===== NATURE SOUNDS =====
export class NatureSounds {
constructor() {
this.context = initAudio();
this.activeSounds = [];
}
// Rain sound (white noise filtered)
rain(intensity = 0.5) {
const bufferSize = this.context.sampleRate * 2;
const buffer = this.context.createBuffer(1, bufferSize, this.context.sampleRate);
const data = buffer.getChannelData(0);
// White noise
for (let i = 0; i < bufferSize; i++) {
data[i] = (Math.random() * 2 - 1) * intensity;
}
const source = this.context.createBufferSource();
source.buffer = buffer;
source.loop = true;
// Low-pass filter for rain sound
const filter = this.context.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = 1000;
filter.Q.value = 1;
const gain = this.context.createGain();
gain.gain.value = intensity * 0.3;
source.connect(filter);
filter.connect(gain);
gain.connect(masterGain);
source.start();
this.activeSounds.push({ source, gain, type: 'rain' });
return { source, gain };
}
// Wind sound (low frequency noise)
wind(intensity = 0.5) {
const osc1 = this.context.createOscillator();
const osc2 = this.context.createOscillator();
osc1.type = 'sawtooth';
osc2.type = 'sine';
osc1.frequency.value = 80 + Math.random() * 40;
osc2.frequency.value = 120 + Math.random() * 40;
const filter = this.context.createBiquadFilter();
filter.type = 'lowpass';
filter.frequency.value = 300;
const gain = this.context.createGain();
gain.gain.value = intensity * 0.2;
// LFO for wind gusts
const lfo = this.context.createOscillator();
lfo.frequency.value = 0.1;
const lfoGain = this.context.createGain();
lfoGain.gain.value = intensity * 0.1;
lfo.connect(lfoGain);
lfoGain.connect(gain.gain);
osc1.connect(filter);
osc2.connect(filter);
filter.connect(gain);
gain.connect(masterGain);
osc1.start();
osc2.start();
lfo.start();
this.activeSounds.push({ source: osc1, gain, type: 'wind' });
return { source: osc1, gain };
}
// Bird chirp
chirp() {
const now = this.context.currentTime;
const osc = this.context.createOscillator();
osc.type = 'sine';
// Chirp frequency sweep
const startFreq = 2000 + Math.random() * 1000;
const endFreq = startFreq - 500;
osc.frequency.setValueAtTime(startFreq, now);
osc.frequency.exponentialRampToValueAtTime(endFreq, now + 0.1);
const gain = this.context.createGain();
gain.gain.setValueAtTime(0.3, now);
gain.gain.exponentialRampToValueAtTime(0.01, now + 0.15);
osc.connect(gain);
gain.connect(masterGain);
osc.start(now);
osc.stop(now + 0.15);
}
// Water flowing
water(intensity = 0.5) {
const bufferSize = this.context.sampleRate * 2;
const buffer = this.context.createBuffer(1, bufferSize, this.context.sampleRate);
const data = buffer.getChannelData(0);
// Pink-ish noise for water
for (let i = 0; i < bufferSize; i++) {
data[i] = (Math.random() * 2 - 1) * intensity * 0.5;
}
const source = this.context.createBufferSource();
source.buffer = buffer;
source.loop = true;
// Band-pass filter
const filter = this.context.createBiquadFilter();
filter.type = 'bandpass';
filter.frequency.value = 800;
filter.Q.value = 0.5;
const gain = this.context.createGain();
gain.gain.value = intensity * 0.4;
source.connect(filter);
filter.connect(gain);
gain.connect(masterGain);
source.start();
this.activeSounds.push({ source, gain, type: 'water' });
return { source, gain };
}
// Firefly glow sound (gentle pulse)
fireflyGlow() {
const now = this.context.currentTime;
const osc = this.context.createOscillator();
osc.type = 'sine';
osc.frequency.value = 800 + Math.random() * 400;
const gain = this.context.createGain();
gain.gain.setValueAtTime(0, now);
gain.gain.linearRampToValueAtTime(0.1, now + 0.3);
gain.gain.linearRampToValueAtTime(0, now + 0.6);
osc.connect(gain);
gain.connect(masterGain);
osc.start(now);
osc.stop(now + 0.6);
}
stopAll() {
this.activeSounds.forEach(({ source }) => {
try {
source.stop();
} catch (e) {
// Already stopped
}
});
this.activeSounds = [];
}
}
// ===== BIOME SOUNDSCAPES =====
export class BiomeSoundscape {
constructor(biomeType) {
this.biomeType = biomeType;
this.sounds = new NatureSounds();
this.instrument = new Instrument('sine');
this.isPlaying = false;
this.loops = [];
}
start() {
if (this.isPlaying) return;
this.isPlaying = true;
switch (this.biomeType) {
case 'forest':
this.forestAmbience();
break;
case 'ocean':
this.oceanAmbience();
break;
case 'mountains':
this.mountainAmbience();
break;
case 'desert':
this.desertAmbience();
break;
case 'crystal':
this.crystalAmbience();
break;
case 'sky':
this.skyAmbience();
break;
}
}
forestAmbience() {
// Gentle wind
this.sounds.wind(0.3);
// Random bird chirps
const chirpLoop = setInterval(() => {
if (Math.random() < 0.3) {
this.sounds.chirp();
}
}, 2000);
this.loops.push(chirpLoop);
// Ambient music (pentatonic scale)
const musicLoop = setInterval(() => {
const scale = SCALES.pentatonic;
const baseNote = 55; // A2
const note = baseNote * Math.pow(2, scale[Math.floor(Math.random() * scale.length)] / 12);
this.instrument.playNote(note, 2, 0.1);
}, 4000);
this.loops.push(musicLoop);
}
oceanAmbience() {
// Water flowing
this.sounds.water(0.6);
// Whale-like sounds
const instrument = new Instrument('sine');
instrument.envelope = { attack: 1, decay: 0.5, sustain: 0.8, release: 2 };
const whaleLoop = setInterval(() => {
const freq = 80 + Math.random() * 120;
instrument.playNote(freq, 5, 0.15);
}, 8000);
this.loops.push(whaleLoop);
}
mountainAmbience() {
// Strong wind
this.sounds.wind(0.7);
// Echoing notes (lydian mode)
const instrument = new Instrument('triangle');
instrument.envelope = { attack: 0.3, decay: 0.4, sustain: 0.6, release: 1.5 };
const echoLoop = setInterval(() => {
const scale = SCALES.lydian;
const baseNote = 220; // A3
const note = baseNote * Math.pow(2, scale[Math.floor(Math.random() * scale.length)] / 12);
instrument.playNote(note, 3, 0.12);
}, 5000);
this.loops.push(echoLoop);
}
desertAmbience() {
// Light wind
this.sounds.wind(0.4);
// Sparse, exotic notes (phrygian scale)
const instrument = new Instrument('sawtooth');
instrument.envelope = { attack: 0.2, decay: 0.3, sustain: 0.5, release: 1 };
const desertLoop = setInterval(() => {
const scale = SCALES.phrygian;
const baseNote = 110; // A2
const note = baseNote * Math.pow(2, scale[Math.floor(Math.random() * scale.length)] / 12);
instrument.playNote(note, 2, 0.08);
}, 6000);
this.loops.push(desertLoop);
}
crystalAmbience() {
// Crystal chimes (high frequencies)
const instrument = new Instrument('sine');
instrument.envelope = { attack: 0.01, decay: 0.1, sustain: 0.3, release: 2 };
const chimeLoop = setInterval(() => {
const scale = SCALES.lydian;
const baseNote = 880; // A5
const note = baseNote * Math.pow(2, scale[Math.floor(Math.random() * scale.length)] / 12);
instrument.playNote(note, 3, 0.15);
// Firefly glow sound
if (Math.random() < 0.5) {
setTimeout(() => this.sounds.fireflyGlow(), Math.random() * 1000);
}
}, 3000);
this.loops.push(chimeLoop);
}
skyAmbience() {
// Gentle wind
this.sounds.wind(0.5);
// Floating, dreamy notes (major scale)
const instrument = new Instrument('triangle');
instrument.envelope = { attack: 0.5, decay: 0.3, sustain: 0.8, release: 2 };
const skyLoop = setInterval(() => {
const scale = SCALES.major;
const baseNote = 440; // A4
const note = baseNote * Math.pow(2, scale[Math.floor(Math.random() * scale.length)] / 12);
instrument.playNote(note, 4, 0.1);
}, 4500);
this.loops.push(skyLoop);
}
stop() {
this.isPlaying = false;
this.sounds.stopAll();
this.loops.forEach(loop => clearInterval(loop));
this.loops = [];
}
}
// ===== PROCEDURAL MELODY GENERATOR =====
export class MelodyGenerator {
constructor(scale = 'pentatonic', tempo = 120) {
this.scale = SCALES[scale];
this.tempo = tempo;
this.instrument = new Instrument('sine');
this.baseNote = 440; // A4
}
// Generate a random melody
generateMelody(length = 8) {
const melody = [];
let lastNote = 0;
for (let i = 0; i < length; i++) {
// Prefer steps (nearby notes) over jumps
const step = Math.random() < 0.7 ?
(Math.random() < 0.5 ? -1 : 1) :
Math.floor(Math.random() * this.scale.length);
let noteIndex = (lastNote + step) % this.scale.length;
if (noteIndex < 0) noteIndex += this.scale.length;
const semitones = this.scale[noteIndex];
const frequency = this.baseNote * Math.pow(2, semitones / 12);
melody.push({
frequency,
duration: 60 / this.tempo, // Quarter note
noteIndex
});
lastNote = noteIndex;
}
return melody;
}
// Play a melody
playMelody(melody, loop = false) {
let time = 0;
const play = () => {
melody.forEach(({ frequency, duration }, i) => {
setTimeout(() => {
this.instrument.playNote(frequency, duration * 0.8, 0.2);
}, time * 1000);
time += duration;
});
if (loop) {
setTimeout(play, time * 1000);
}
};
play();
}
}
// ===== MUSIC MANAGER =====
export class MusicManager {
constructor() {
this.currentSoundscape = null;
this.natureSounds = new NatureSounds();
this.volume = 0.3;
}
setBiome(biomeType) {
if (this.currentSoundscape) {
this.currentSoundscape.stop();
}
this.currentSoundscape = new BiomeSoundscape(biomeType);
this.currentSoundscape.start();
}
playAnimalSound(animalType) {
switch (animalType) {
case 'bird':
this.natureSounds.chirp();
break;
case 'butterfly':
case 'bee':
this.natureSounds.fireflyGlow();
break;
}
}
setVolume(volume) {
this.volume = Math.max(0, Math.min(1, volume));
if (masterGain) {
masterGain.gain.value = this.volume;
}
}
stop() {
if (this.currentSoundscape) {
this.currentSoundscape.stop();
}
this.natureSounds.stopAll();
}
}
export default MusicManager;