import React, { useState, useEffect, useRef, useMemo } from 'react';
import { Play, Pause, Disc, Wind, Target, Layers } from 'lucide-react';
// --- BIOLOGICAL AUDIO ENGINE ---
class LayaEngine {
constructor() {
this.ctx = new (window.AudioContext || window.webkitAudioContext)();
this.masterBus = this.ctx.createGain();
this.masterBus.connect(this.ctx.destination);
this.setupDrone();
}
setupDrone() {
// A constant 432Hz "Om" drone to ground the user
this.droneOsc = this.ctx.createOscillator();
this.droneGain = this.ctx.createGain();
this.droneOsc.frequency.value = 136.1; // Frequency of Om (Sadhaka)
this.droneGain.gain.value = 0.03;
this.droneOsc.connect(this.droneGain);
this.droneGain.connect(this.masterBus);
this.droneOsc.start();
}
trigger(time, type) {
const osc = this.ctx.createOscillator();
const g = this.ctx.createGain();
if (type === 'sam') {
// Deep resonant impact
osc.frequency.setValueAtTime(60, time);
osc.frequency.exponentialRampToValueAtTime(40, time + 0.4);
g.gain.setValueAtTime(0.5, time);
g.gain.exponentialRampToValueAtTime(0.001, time + 0.6);
} else if (type === 'beat') {
// Sharp clear strike
osc.frequency.setValueAtTime(440, time);
g.gain.setValueAtTime(0.2, time);
g.gain.exponentialRampToValueAtTime(0.001, time + 0.1);
} else {
// Subtle subdivision "tick"
osc.frequency.setValueAtTime(1200, time);
g.gain.setValueAtTime(0.04, time);
g.gain.exponentialRampToValueAtTime(0.001, time + 0.02);
}
osc.connect(g);
g.connect(this.masterBus);
osc.start(time);
osc.stop(time + 0.6);
}
}
const TAALS = [
{ name: 'TEENTAAL', beats: 16, accent: [0, 4, 8, 12] },
{ name: 'JHAPTAAL', beats: 10, accent: [0, 2, 5, 7] },
{ name: 'EKTAAL', beats: 12, accent: [0, 2, 4, 6, 8, 10] },
{ name: 'RUPAK', beats: 7, accent: [0, 3, 5] },
];
const App = () => {
const [mode, setMode] = useState('singularity'); // singularity | prana | mandala
const [isPlaying, setIsPlaying] = useState(false);
const [tempo, setTempo] = useState(80);
const [taal, setTaal] = useState(TAALS[0]);
const [divs, setDivs] = useState(4);
const [phase, setPhase] = useState(0); // 0 to 1 cycle progress
const [impact, setImpact] = useState(0); // For flash effects
const audio = useRef(null);
const scheduler = useRef({ nextTime: 0, matra: 0, sub: 0 });
const rafRef = useRef();
const tick = () => {
const lookahead = 0.1;
if (!audio.current) return;
while (scheduler.current.nextTime < audio.current.ctx.currentTime + lookahead) {
const { matra, sub, nextTime } = scheduler.current;
const isSam = matra === 0 && sub === 0;
const isBeat = sub === 0;
audio.current.trigger(nextTime, isSam ? 'sam' : (isBeat ? 'beat' : 'sub'));
const stepSec = (60 / tempo) / divs;
scheduler.current.nextTime += stepSec;
scheduler.current.sub = (sub + 1) % divs;
if (scheduler.current.sub === 0) {
scheduler.current.matra = (matra + 1) % taal.beats;
}
}
rafRef.current = setTimeout(tick, 25);
};
useEffect(() => {
let anim;
const update = () => {
if (audio.current && isPlaying) {
const beatSec = 60 / tempo;
const totalSec = beatSec * taal.beats;
// Accurate phase calculation based on audio clock
const currentMatraTotal = (scheduler.current.matra * divs + scheduler.current.sub);
const elapsedSinceLastScheduled = audio.current.ctx.currentTime - (scheduler.current.nextTime - (60/tempo/divs));
const p = ((currentMatraTotal / (taal.beats * divs)) + (elapsedSinceLastScheduled / totalSec)) % 1;
setPhase(p);
// Impact decay for visuals
if (elapsedSinceLastScheduled < 0.05) setImpact(1);
else setImpact(prev => prev * 0.9);
}
anim = requestAnimationFrame(update);
};
update();
return () => cancelAnimationFrame(anim);
}, [isPlaying, tempo, taal, divs]);
const toggle = () => {
if (!isPlaying) {
if (!audio.current) audio.current = new LayaEngine();
audio.current.ctx.resume();
scheduler.current = { nextTime: audio.current.ctx.currentTime + 0.1, matra: 0, sub: 0 };
tick();
setIsPlaying(true);
} else {
clearTimeout(rafRef.current);
setIsPlaying(false);
setPhase(0);
setImpact(0);
}
};
return (
{/* MODAL BACKGROUNDS */}
{mode === 'singularity' &&
}
{mode === 'prana' &&
}
{mode === 'mandala' &&
}
{/* UI OVERLAY */}
Becoming the Rhythm
{taal.name}
{/* PLAY BUTTON */}
{/* MODE SWITCHER */}
{[
{ id: 'singularity', icon: , label: 'Singularity' },
{ id: 'prana', icon: , label: 'Prana' },
{ id: 'mandala', icon: , label: 'Mandala' }
].map(m => (
))}
{/* REFINED CONTROLS */}
{/* FLASH LAYER */}
);
};
// --- VARIATION 1: THE SINGULARITY ---
// Focus: Gravitational weight of the cycle.
// Visual: A warped ring where space "bunches up" as you approach the Sam.
const SingularityField = ({ phase, beats, impact }) => {
const canvasRef = useRef();
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
let raf;
const draw = () => {
const w = canvas.width = window.innerWidth;
const h = canvas.height = window.innerHeight;
ctx.clearRect(0,0,w,h);
const cx = w/2; const cy = h/2;
const radius = Math.min(w,h) * 0.3;
// Draw warped path
ctx.beginPath();
ctx.strokeStyle = `rgba(201, 169, 98, ${0.1 + impact * 0.4})`;
ctx.lineWidth = 1;
for(let i=0; i<360; i++) {
const rad = i * Math.PI / 180;
// Gravity effect: space bends toward the top (Sam)
const warpedRad = rad + Math.sin(rad) * 0.1;
const r = radius + (isActiveBeat(i, beats, phase) ? 10 * impact : 0);
const x = cx + r * Math.cos(warpedRad - Math.PI/2);
const y = cy + r * Math.sin(warpedRad - Math.PI/2);
if(i===0) ctx.moveTo(x,y); else ctx.lineTo(x,y);
}
ctx.stroke();
// Current Time Node
const currentAngle = (phase * Math.PI * 2) - Math.PI/2;
ctx.fillStyle = '#c9a962';
ctx.beginPath();
ctx.arc(cx + radius * Math.cos(currentAngle), cy + radius * Math.sin(currentAngle), 3 + impact * 5, 0, Math.PI*2);
ctx.fill();
raf = requestAnimationFrame(draw);
};
draw(); return () => cancelAnimationFrame(raf);
}, [phase, impact]);
const isActiveBeat = (angle, beats, phase) => {
const beatAngle = Math.floor(phase * beats) * (360/beats);
return Math.abs(angle - beatAngle) < 5;
};
return ;
};
// --- VARIATION 2: THE PRANA ---
// Focus: Rhythm as breathing. Expansion and contraction.
const PranaField = ({ phase, beats, impact }) => {
const canvasRef = useRef();
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
let raf;
const draw = () => {
const w = canvas.width = window.innerWidth;
const h = canvas.height = window.innerHeight;
ctx.clearRect(0,0,w,h);
const cx = w/2; const cy = h/2;
// The Breath: Cycle expands and contracts
const breathScale = 1 + Math.sin(phase * Math.PI * 2 - Math.PI/2) * 0.2;
const radius = Math.min(w,h) * 0.25 * breathScale;
ctx.strokeStyle = '#e8e4df';
ctx.globalAlpha = 0.1 + impact * 0.5;
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, Math.PI * 2);
ctx.stroke();
// Subdivision "Cells"
for(let i=0; i cancelAnimationFrame(raf);
}, [phase, impact]);
return ;
};
// --- VARIATION 3: THE MANDALA ---
// Focus: Complexity and interference. Feeling the sub-pulse texture.
const MandalaField = ({ phase, beats, divs, impact }) => {
const canvasRef = useRef();
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
let raf;
const draw = () => {
const w = canvas.width = window.innerWidth;
const h = canvas.height = window.innerHeight;
ctx.clearRect(0,0,w,h);
const cx = w/2; const cy = h/2;
const radius = Math.min(w,h) * 0.3;
ctx.lineWidth = 0.5;
ctx.strokeStyle = `rgba(201, 169, 98, ${0.2 + impact * 0.8})`;
// Interference pattern based on divisions
for(let i=0; i < divs * beats; i++) {
const angle = (i / (divs * beats)) * Math.PI * 2 - Math.PI/2;
const isActive = Math.floor(phase * divs * beats) === i;
ctx.beginPath();
const startR = radius * 0.8;
const endR = radius * (isActive ? 1.1 : 1);
ctx.moveTo(cx + startR * Math.cos(angle), cy + startR * Math.sin(angle));
ctx.lineTo(cx + endR * Math.cos(angle), cy + endR * Math.sin(angle));
ctx.globalAlpha = isActive ? 1 : 0.1;
ctx.stroke();
}
raf = requestAnimationFrame(draw);
};
draw(); return () => cancelAnimationFrame(raf);
}, [phase, impact, divs, beats]);
return ;
};
export default App;