anycoder-c0ef4ef5 / index.html
DiamanteAmarelo's picture
Upload folder using huggingface_hub
eca1aee verified
Raw
History Blame Contribute Delete
42.2 kB
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<meta name="theme-color" content="#0a0a0a">
<meta name="description" content="Everywhere at the End of Time - Simulador de Degradação Auditiva">
<title>Everywhere - Simulador de Degradação</title>
<link rel="manifest" href="data:application/json;base64,ewogICJuYW1lIjogIkV2ZXJ5d2hlcmUgRGVncmFkYWNhbyIsCiAgInNob3J0X25hbWUiOiAiRXZlcnl3aGVyZSIsCiAgInN0YXJ0X3VybCI6ICIuLyIsCiAgImRpc3BsYXkiOiAic3RhbmRhbG9uZSIsCiAgImJhY2tncm91bmRfY29sb3IiOiAiIzBhMGEwYSIsCiAgInRoZW1lX2NvbG9yIjogIiMwYTBhMGEiLAogICJpY29ucyI6IFsKICAgIHsKICAgICAgInNyYyI6ICIuLyIsCiAgICAgICJzaXplcyIjogWyIxMjgiLCA2NCIsIDMyLCAxNl0sCiAgICAgICJ0eXBlIjogImltYWdlL3BuZyIKICAgIH0KICBdCn0=">
<style>
:root {
--bg: #0a0a0a;
--surface: #111111;
--surface-elevated: #161616;
--border: #1a1a1a;
--border-hover: #2a2a2a;
--text: #e0e0e0;
--text-dim: #666666;
--text-dimmer: #444444;
--accent: #999999;
--accent-dim: #555555;
--stage-0: #888888;
--stage-1: #777777;
--stage-2: #666666;
--stage-3: #555555;
--stage-4: #444444;
--stage-5: #333333;
--stage-6: #222222;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
scroll-behavior: smooth;
}
body {
font-family: 'SF Mono', 'Fira Code', 'Courier New', monospace;
background: var(--bg);
color: var(--text);
min-height: 100vh;
display: flex;
flex-direction: column;
overflow-x: hidden;
line-height: 1.5;
}
/* Animated background */
body::before {
content: '';
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background:
radial-gradient(ellipse at 20% 20%, rgba(30, 30, 30, 0.3) 0%, transparent 50%),
radial-gradient(ellipse at 80% 80%, rgba(20, 20, 20, 0.3) 0%, transparent 50%);
pointer-events: none;
z-index: -1;
}
header {
padding: 1.5rem 1rem;
text-align: center;
border-bottom: 1px solid var(--border);
background: linear-gradient(180deg, rgba(17, 17, 17, 0.9) 0%, transparent 100%);
}
header h1 {
font-size: 0.85rem;
font-weight: 400;
letter-spacing: 0.4em;
text-transform: uppercase;
color: var(--text-dim);
margin-bottom: 0.5rem;
}
header a {
color: var(--text-dimmer);
text-decoration: none;
font-size: 0.65rem;
letter-spacing: 0.15em;
transition: color 0.3s ease;
display: inline-block;
padding: 0.25rem 0.5rem;
border: 1px solid transparent;
}
header a:hover {
color: var(--accent);
border-color: var(--border);
background: var(--surface);
}
.stage-display {
padding: 2rem 1rem;
text-align: center;
min-height: 140px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: relative;
}
.stage-display::after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
width: 60%;
height: 1px;
background: linear-gradient(90deg, transparent, var(--border), transparent);
}
.stage-number {
font-size: 3rem;
font-weight: 200;
color: var(--text-dim);
opacity: 0.3;
position: absolute;
top: 1rem;
left: 50%;
transform: translateX(-50%);
letter-spacing: 0.2em;
}
.stage-name {
font-size: 1rem;
font-weight: 400;
letter-spacing: 0.25em;
text-transform: uppercase;
margin-bottom: 0.75rem;
transition: all 0.5s ease;
}
.stage-desc {
font-size: 0.7rem;
color: var(--text-dim);
max-width: 320px;
line-height: 1.7;
font-style: italic;
}
main {
flex: 1;
display: flex;
flex-direction: column;
padding: 1rem;
gap: 1.5rem;
max-width: 600px;
margin: 0 auto;
width: 100%;
}
/* Visualizer */
.visualizer-container {
width: 100%;
height: 120px;
background: var(--surface);
border: 1px solid var(--border);
position: relative;
overflow: hidden;
border-radius: 2px;
}
.visualizer-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 1px;
background: linear-gradient(90deg, transparent, var(--accent-dim), transparent);
}
#visualizer {
width: 100%;
height: 100%;
display: block;
}
/* Controls */
.controls {
display: flex;
flex-direction: column;
gap: 1rem;
}
.file-input-wrapper {
position: relative;
width: 100%;
}
.file-input-wrapper input[type="file"] {
position: absolute;
left: 0;
top: 0;
opacity: 0;
width: 100%;
height: 100%;
cursor: pointer;
z-index: 2;
}
.btn {
background: var(--surface);
border: 1px solid var(--border);
color: var(--text);
padding: 0.9rem 1.2rem;
font-family: inherit;
font-size: 0.7rem;
letter-spacing: 0.2em;
text-transform: uppercase;
cursor: pointer;
transition: all 0.25s ease;
width: 100%;
text-align: center;
position: relative;
overflow: hidden;
}
.btn::before {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.05), transparent);
transition: left 0.5s ease;
}
.btn:hover::before {
left: 100%;
}
.btn:hover:not(:disabled) {
border-color: var(--accent-dim);
background: var(--surface-elevated);
transform: translateY(-1px);
}
.btn:active:not(:disabled) {
transform: translateY(0);
}
.btn:disabled {
opacity: 0.25;
cursor: not-allowed;
}
.btn.active {
background: var(--accent-dim);
border-color: var(--accent);
color: #fff;
}
/* Playback controls */
.playback-controls {
display: flex;
gap: 0.5rem;
}
.playback-controls .btn {
flex: 1;
}
/* Progress */
.progress-section {
width: 100%;
}
.progress-bar {
width: 100%;
height: 3px;
background: var(--border);
cursor: pointer;
position: relative;
border-radius: 1px;
overflow: hidden;
}
.progress-bar::after {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: transparent;
transition: background 0.3s;
}
.progress-bar:hover::after {
background: rgba(255,255,255,0.02);
}
.progress-fill {
height: 100%;
background: var(--accent);
width: 0%;
transition: width 0.1s linear;
position: relative;
}
.progress-fill::after {
content: '';
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
width: 8px;
height: 8px;
background: var(--accent);
border-radius: 50%;
opacity: 0;
transition: opacity 0.2s;
}
.progress-bar:hover .progress-fill::after {
opacity: 1;
}
.time-display {
display: flex;
justify-content: space-between;
font-size: 0.6rem;
color: var(--text-dimmer);
margin-top: 0.6rem;
font-variant-numeric: tabular-nums;
letter-spacing: 0.05em;
}
/* Chaos slider */
.chaos-control {
display: flex;
align-items: center;
gap: 1rem;
padding: 0.75rem 1rem;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 2px;
}
.chaos-control label {
font-size: 0.65rem;
color: var(--text-dim);
white-space: nowrap;
letter-spacing: 0.1em;
text-transform: uppercase;
}
.chaos-control input[type="range"] {
flex: 1;
-webkit-appearance: none;
appearance: none;
background: var(--border);
height: 2px;
outline: none;
border-radius: 1px;
}
.chaos-control input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 14px;
height: 14px;
background: var(--accent);
cursor: pointer;
border-radius: 50%;
transition: transform 0.2s, background 0.2s;
}
.chaos-control input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.2);
background: var(--text);
}
.chaos-value {
font-size: 0.6rem;
color: var(--text-dimmer);
min-width: 2.5em;
text-align: right;
font-variant-numeric: tabular-nums;
}
/* Stage buttons */
.stages-section {
margin-top: 0.5rem;
}
.stages-label {
font-size: 0.6rem;
color: var(--text-dimmer);
letter-spacing: 0.15em;
text-transform: uppercase;
text-align: center;
margin-bottom: 0.75rem;
}
.stages-container {
display: grid;
grid-template-columns: repeat(7, 1fr);
gap: 0.3rem;
}
.stage-btn {
padding: 0.8rem 0.25rem;
font-size: 0.7rem;
background: var(--surface);
border: 1px solid var(--border);
color: var(--text-dim);
cursor: pointer;
transition: all 0.3s ease;
text-align: center;
font-family: inherit;
position: relative;
}
.stage-btn:hover:not(.active) {
border-color: var(--accent-dim);
background: var(--surface-elevated);
}
.stage-btn.active {
background: var(--accent-dim);
border-color: var(--accent);
color: var(--text);
}
.stage-btn[data-stage="0"].active { border-left: 2px solid var(--stage-0); }
.stage-btn[data-stage="6"].active { border-right: 2px solid var(--stage-6); }
.stage-btn::after {
content: attr(data-desc);
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
background: var(--surface-elevated);
border: 1px solid var(--border);
padding: 0.4rem 0.6rem;
font-size: 0.55rem;
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s, transform 0.2s;
z-index: 10;
margin-bottom: 0.5rem;
}
.stage-btn:hover::after {
opacity: 1;
}
/* Status */
.status {
text-align: center;
font-size: 0.6rem;
color: var(--text-dimmer);
padding: 0.75rem;
letter-spacing: 0.05em;
border-top: 1px solid var(--border);
background: var(--surface);
}
.status.error {
color: #996666;
background: rgba(153, 102, 102, 0.1);
}
.status.success {
color: #669966;
background: rgba(102, 153, 102, 0.1);
}
.status.processing {
color: var(--accent);
}
/* Offline badge */
.offline-badge {
position: fixed;
bottom: 1.5rem;
right: 1.5rem;
background: var(--surface);
border: 1px solid var(--border);
padding: 0.5rem 0.75rem;
font-size: 0.55rem;
color: var(--text-dimmer);
opacity: 0;
transition: opacity 0.4s ease;
letter-spacing: 0.1em;
}
.offline-badge.visible {
opacity: 1;
}
/* Tips */
.tips {
font-size: 0.55rem;
color: var(--text-dimmer);
text-align: center;
padding: 0.5rem;
font-style: italic;
}
/* Responsive */
@media (max-width: 480px) {
.stages-container {
grid-template-columns: repeat(4, 1fr);
}
.stages-container .stage-btn:nth-child(5),
.stages-container .stage-btn:nth-child(6),
.stages-container .stage-btn:nth-child(7) {
grid-column: span 1;
}
.stage-display {
padding: 1.5rem 1rem;
min-height: 120px;
}
.stage-number {
font-size: 2.5rem;
}
.stage-name {
font-size: 0.9rem;
}
.stage-desc {
font-size: 0.65rem;
}
.visualizer-container {
height: 100px;
}
}
/* Animations */
@keyframes pulse {
0%, 100% { opacity: 0.3; }
50% { opacity: 0.5; }
}
.status.processing::before {
content: '● ';
animation: pulse 1.5s infinite;
}
</style>
</head>
<body>
<header>
<h1>Everywhere</h1>
<a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">Built with anycoder</a>
</header>
<div class="stage-display">
<span class="stage-number" id="stageNumber">0</span>
<h2 class="stage-name" id="stageName">Estágio 0</h2>
<p class="stage-desc" id="stageDesc">Áudio puro e inalterado</p>
</div>
<main>
<div class="visualizer-container">
<canvas id="visualizer"></canvas>
</div>
<div class="controls">
<div class="file-input-wrapper">
<button class="btn" id="loadBtn">
<span>Carregar Áudio</span>
</button>
<input type="file" id="fileInput" accept=".flac,.mp3,.wav,.ogg,.m4a,.aac,.wma,.aiff">
</div>
<div class="playback-controls">
<button class="btn" id="playBtn" disabled>▶ Reproduzir</button>
<button class="btn" id="pauseBtn" disabled>⏸ Pausar</button>
<button class="btn" id="restartBtn" disabled>↺ Reiniciar</button>
</div>
<div class="progress-section">
<div class="progress-bar" id="progressBar">
<div class="progress-fill" id="progressFill"></div>
</div>
<div class="time-display">
<span id="currentTime">0:00</span>
<span id="duration">0:00</span>
</div>
</div>
<div class="chaos-control">
<label>Caos</label>
<input type="range" id="chaosSlider" min="0" max="100" value="0">
<span class="chaos-value" id="chaosValue">0%</span>
</div>
<div class="stages-section">
<div class="stages-label">Selecione o estágio de degradação</div>
<div class="stages-container">
<button class="stage-btn active" data-stage="0" data-desc="Áudio puro">0</button>
<button class="stage-btn" data-stage="1" data-desc="Nostalgia doce">1</button>
<button class="stage-btn" data-stage="2" data-desc="Fragmentação">2</button>
<button class="stage-btn" data-stage="3" data-desc="Última lucidez">3</button>
<button class="stage-btn" data-stage="4" data-desc="Colapso">4</button>
<button class="stage-btn" data-stage="5" data-desc="Agonia">5</button>
<button class="stage-btn" data-stage="6" data-desc="Dissolução">6</button>
</div>
</div>
<div class="tips">
Dica: Use fones de ouvido para melhor experiência
</div>
</div>
<div class="status" id="status">Aguardando arquivo de áudio...</div>
</main>
<div class="offline-badge" id="offlineBadge">Modo Offline</div>
<script>
// ==================== SERVICE WORKER REGISTRATION ====================
if ('serviceWorker' in navigator) {
const swBlob = new Blob([`${swCode}`], { type: 'application/javascript' });
const swUrl = URL.createObjectURL(swBlob);
navigator.serviceWorker.register(swUrl)
.then(reg => console.log('SW registered'))
.catch(err => console.log('SW registration failed:', err));
}
// ==================== SERVICE WORKER CODE ====================
const swCode = `
const CACHE_NAME = 'everywhere-v2';
const urlsToCache = ['./'];
self.addEventListener('install', event => {
event.waitUntil(
caches.open(CACHE_NAME)
.then(cache => cache.addAll(urlsToCache))
);
self.skipWaiting();
});
self.addEventListener('activate', event => {
event.waitUntil(
caches.keys().then(cacheNames => {
return Promise.all(
cacheNames.map(cacheName => {
if (cacheName !== CACHE_NAME) {
return caches.delete(cacheName);
}
})
);
})
);
self.clients.claim();
});
self.addEventListener('fetch', event => {
event.respondWith(
caches.match(event.request)
.then(response => {
if (response) return response;
return fetch(event.request).then(response => {
if (!response || response.status !== 200 || response.type !== 'basic') {
return response;
}
const responseToCache = response.clone();
caches.open(CACHE_NAME)
.then(cache => cache.put(event.request, responseToCache));
return response;
});
})
);
});
`;
// ==================== INDEXEDDB ====================
const DB_NAME = 'EverywhereAudioDB';
const DB_VERSION = 2;
const STORE_NAME = 'audioFiles';
let db = null;
function openDatabase() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(DB_NAME, DB_VERSION);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
db = request.result;
resolve(db);
};
request.onupgradeneeded = (event) => {
const database = event.target.result;
if (!database.objectStoreNames.contains(STORE_NAME)) {
database.createObjectStore(STORE_NAME, { keyPath: 'id' });
}
};
});
}
async function saveAudioToDB(id, arrayBuffer, fileName) {
return new Promise((resolve, reject) => {
const transaction = db.transaction([STORE_NAME], 'readwrite');
const store = transaction.objectStore(STORE_NAME);
const request = store.put({ id, data: arrayBuffer, name: fileName, timestamp: Date.now() });
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
async function getAudioFromDB(id) {
return new Promise((resolve, reject) => {
const transaction = db.transaction([STORE_NAME], 'readonly');
const store = transaction.objectStore(STORE_NAME);
const request = store.get(id);
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async function clearAudioFromDB(id) {
return new Promise((resolve, reject) => {
const transaction = db.transaction([STORE_NAME], 'readwrite');
const store = transaction.objectStore(STORE_NAME);
const request = store.delete(id);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
// ==================== AUDIO ENGINE ====================
let audioContext = null;
let audioBuffer = null;
let sourceNode = null;
let gainNode = null;
let filterNode = null;
let convolverNode = null;
let delayNode = null;
let feedbackGain = null;
let waveShaperNode = null;
let analyserNode = null;
let masterGain = null;
// Auxiliary source pool for Stage 4+
let auxiliarySources = [];
let auxiliaryPool = [];
const POOL_SIZE = 4;
// LFO oscillators array
let lfos = [];
// Noise and drone for later stages
let noiseSource = null;
let noiseGain = null;
let droneOscillator = null;
let droneGain = null;
let ghostNotesInterval = null;
// State
let currentStage = 0;
let isPlaying = false;
let startTime = 0;
let pauseTime = 0;
let chaosLevel = 0;
let lastStageTime = 0;
let stage6EndScheduled = false;
let animationFrameId = null;
// Stage descriptions
const stageData = [
{ name: 'Estágio 0', desc: 'Áudio puro e inalterado', number: '0' },
{ name: 'Estágio 1', desc: 'Uma nostalgia doce e borrada que, sob uma superfície calma e repetitiva, esconde os primeiros sinais sutis da demência.', number: '1' },
{ name: 'Estágio 2', desc: 'Uma melancólica recusa em aceitar o esquecimento, onde memórias claras começam a se fragmentar sob estática crescente.', number: '2' },
{ name: 'Estágio 3', desc: 'A última tentativa desesperada da memória em manter a lucidez antes de sucumbir totalmente ao caos e à desorientação.', number: '3' },
{ name: 'Estágio 4', desc: 'A ruptura final da consciência, transformando memórias em ruídos caóticos e desoladores.', number: '4' },
{ name: 'Estágio 5', desc: 'O estágio 5 é a agonia da mente em ruínas, onde fragmentos musicais distorcidos dão lugar a um vazio de puro horror e desorientação.', number: '5' },
{ name: 'Estágio 6', desc: 'A dissolução completa do self em silêncio e caos ambientais, marcando o fim definitivo da memória.', number: '6' }
];
function initAudioContext() {
if (!audioContext) {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
return audioContext;
}
// Create impulse response for reverb
function createImpulseResponse(duration, decay, channels = 2) {
const sampleRate = audioContext.sampleRate;
const length = Math.floor(sampleRate * duration);
const impulse = audioContext.createBuffer(channels, length, sampleRate);
for (let channel = 0; channel < channels; channel++) {
const channelData = impulse.getChannelData(channel);
for (let i = 0; i < length; i++) {
channelData[i] = (Math.random() * 2 - 1) * Math.pow(1 - i / length, decay);
}
}
return impulse;
}
// Create waveshaper curve for distortion
function makeDistortionCurve(amount) {
const samples = 44100;
const curve = new Float32Array(samples);
const deg = Math.PI / 180;
for (let i = 0; i < samples; i++) {
const x = (i * 2) / samples - 1;
curve[i] = ((3 + amount) * x * 20 * deg) / (Math.PI + amount * Math.abs(x));
}
return curve;
}
// Create pink noise
function createPinkNoise() {
const bufferSize = 2 * audioContext.sampleRate;
const noiseBuffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);
const output = noiseBuffer.getChannelData(0);
let b0 = 0, b1 = 0, b2 = 0, b3 = 0, b4 = 0, b5 = 0, b6 = 0;
for (let i = 0; i < bufferSize; i++) {
const white = Math.random() * 2 - 1;
b0 = 0.99886 * b0 + white * 0.0555179;
b1 = 0.99332 * b1 + white * 0.0750759;
b2 = 0.96900 * b2 + white * 0.1538520;
b3 = 0.86650 * b3 + white * 0.3104856;
b4 = 0.55000 * b4 + white * 0.5329522;
b5 = -0.7616 * b5 - white * 0.0168980;
output[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362;
output[i] *= 0.11;
b6 = white * 0.115926;
}
return noiseBuffer;
}
// Create brown noise
function createBrownNoise() {
const bufferSize = 2 * audioContext.sampleRate;
const noiseBuffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);
const output = noiseBuffer.getChannelData(0);
let lastOut = 0;
for (let i = 0; i < bufferSize; i++) {
const white = Math.random() * 2 - 1;
output[i] = (lastOut + (0.02 * white)) / 1.02;
lastOut = output[i];
output[i] *= 3.5;
}
return noiseBuffer;
}
function createAudioGraph() {
const ctx = audioContext;
// Main source
sourceNode = ctx.createBufferSource();
sourceNode.buffer = audioBuffer;
sourceNode.loop = true;
// Main processing chain
gainNode = ctx.createGain();
filterNode = ctx.createBiquadFilter();
filterNode.type = 'lowpass';
filterNode.frequency.value = 20000;
filterNode.Q.value = 0.7;
convolverNode = ctx.createConvolver();
convolverNode.buffer = createImpulseResponse(2, 2);
delayNode = ctx.createDelay(5);
delayNode.delayTime.value = 0.3;
feedbackGain = ctx.createGain();
feedbackGain.gain.value = 0.3;
waveShaperNode = ctx.createWaveShaper();
waveShaperNode.curve = makeDistortionCurve(0);
waveShaperNode.oversample = '4x';
// Analyser
analyserNode = ctx.createAnalyser();
analyserNode.fftSize = 2048;
analyserNode.smoothingTimeConstant = 0.8;
// Master gain
masterGain = ctx.createGain();
masterGain.gain.value = 1;
// Connect main chain
sourceNode.connect(gainNode);
gainNode.connect(filterNode);
filterNode.connect(waveShaperNode);
waveShaperNode.connect(convolverNode);
convolverNode.connect(delayNode);
delayNode.connect(feedbackGain);
feedbackGain.connect(delayNode);
convolverNode.connect(analyserNode);
delayNode.connect(analyserNode);
analyserNode.connect(masterGain);
masterGain.connect(ctx.destination);
// Initialize auxiliary source pool
for (let i = 0; i < POOL_SIZE; i++) {
auxiliaryPool.push({
source: null,
gain: null,
filter: null,
inUse: false
});
}
// Setup noise for stages 5-6
const noiseBuffer = createPinkNoise();
noiseSource = ctx.createBufferSource();
noiseSource.buffer = noiseBuffer;
noiseSource.loop = true;
noiseGain = ctx.createGain();
noiseGain.gain.value = 0;
const noiseFilter = ctx.createBiquadFilter();
noiseFilter.type = 'lowpass';
noiseFilter.frequency.value = 1000;
noiseSource.connect(noiseFilter);
noiseFilter.connect(noiseGain);
noiseGain.connect(masterGain);
noiseSource.start();
// Setup drone for stage 6
droneOscillator = ctx.createOscillator();
droneOscillator.type = 'sine';
droneOscillator.frequency.value = 55;
droneGain = ctx.createGain();
droneGain.gain.value = 0;
const droneFilter = ctx.createBiquadFilter();
droneFilter.type = 'lowpass';
droneFilter.frequency.value = 200;
droneOscillator.connect(droneFilter);
droneFilter.connect(droneGain);
droneGain.connect(masterGain);
droneOscillator.start();
}
// LFO management
function createLFO(targetParam, frequency, min, max, type = 'sine') {
const lfo = audioContext.createOscillator();
const lfoGain = audioContext.createGain();
lfo.type = type;
lfo.frequency.value = frequency;
const range = max - min;
lfoGain.gain.value = range / 2;
// Center value
const centerValue = (min + max) / 2;
lfo.connect(lfoGain);
lfoGain.connect(targetParam);
// Set initial value
targetParam.setValueAtTime(centerValue, audioContext.currentTime);
lfo.start();
return { lfo, lfoGain, targetParam, min, max, centerValue };
}
function clearLFOs() {
lfos.forEach(lfoObj => {
try {
if (lfoObj.lfo) lfoObj.lfo.stop();
} catch(e) {}
});
lfos = [];
}
function updateLFOsForStage(stage, chaos) {
clearLFOs();
if (!audioBuffer || !gainNode) return;
const chaosFactor = chaos / 100;
const ctx = audioContext;
const now = ctx.currentTime;
switch(stage) {
case 0:
// Pure audio - minimal processing
gainNode.gain.setTargetAtTime(1, now, 0.5);
filterNode.frequency.setTargetAtTime(20000, now, 0.5);
filterNode.Q.setTargetAtTime(0.7, now, 0.5);
waveShaperNode.curve = makeDistortionCurve(0);
convolverNode.buffer = createImpulseResponse(1, 4);
delayNode.delayTime.setTargetAtTime(0.3, now, 0.5);
feedbackGain.gain.setTargetAtTime(0.2, now, 0.5);
masterGain.gain.setTargetAtTime(1, now, 0.5);
noiseGain.gain.setTargetAtTime(0, now, 0.5);
droneGain.gain.setTargetAtTime(0, now, 0.5);
if (sourceNode && sourceNode.playbackRate) {
sourceNode.playbackRate.setTargetAtTime(1, now, 0.5);
}
break;
case 1:
// Stage 1: Subtle warmth, slight blur
gainNode.gain.setTargetAtTime(0.95, now, 1);
filterNode.frequency.setTargetAtTime(8000 + chaosFactor * 2000, now, 2);
filterNode.Q.setTargetAtTime(1, now, 1);
convolverNode.buffer = createImpulseResponse(1.5 + chaosFactor, 3 - chaosFactor);
delayNode.delayTime.setTargetAtTime(0.35, now, 1);
feedbackGain.gain.setTargetAtTime(0.25 + chaosFactor * 0.1, now, 1);
waveShaperNode.curve = makeDistortionCurve(chaosFactor * 5);
// Subtle LFO for warmth
lfos.push(createLFO(gainNode.gain, 0.08, 0.92, 0.98, 'sine'));
break;
case 2:
// Stage 2: Fragmentation begins
gainNode.gain.setTargetAtTime(0.88 - chaosFactor * 0.05, now, 0.8);
filterNode.frequency.setTargetAtTime(5000 - chaosFactor * 1500, now, 0.8);
filterNode.Q.setTargetAtTime(2 + chaosFactor * 2, now, 0.8);
delayNode.delayTime.setTargetAtTime(0.4 + chaosFactor * 0.15, now, 0.8);
feedbackGain.gain.setTargetAtTime(0.35 + chaosFactor * 0.15, now, 0.8);
waveShaperNode.curve = makeDistortionCurve(5 + chaosFactor * 15);
convolverNode.buffer = createImpulseResponse(2 + chaosFactor, 2);
// LFOs for instability
lfos.push(createLFO(gainNode.gain, 0.12, 0.82, 0.92, 'sine'));
lfos.push(createLFO(filterNode.frequency, 0.06, 3500, 6000, 'triangle'));
break;
case 3:
// Stage 3: Desperate lucidity
gainNode.gain.setTargetAtTime(0.8 - chaosFactor * 0.1, now, 0.5);
filterNode.frequency.setTargetAtTime(3000 - chaosFactor * 1000, now, 0.5);
filterNode.Q.setTargetAtTime(4 + chaosFactor * 3, now, 0.5);
waveShaperNode.curve = makeDistortionCurve(20 + chaosFactor * 30);
convolverNode.buffer = createImpulseResponse(2.5, 1.5);
delayNode.delayTime.setTargetAtTime(0.45 + chaosFactor * 0.15, now, 0.5);
feedbackGain.gain.setTargetAtTime(0.45 + chaosFactor * 0.15, now, 0.5);
// More aggressive LFOs
lfos.push(createLFO(gainNode.gain, 0.2, 0.65, 0.85, 'sine'));
lfos.push(createLFO(filterNode.frequency, 0.15, 2000, 4000, 'sawtooth'));
break;
case 4:
// Stage 4: Collapse of linearity
gainNode.gain.setTargetAtTime(0.7 - chaosFactor * 0.25, now, 0.3);
filterNode.frequency.setTargetAtTime(1800 - chaosFactor * 800, now, 0.3);
filterNode.Q.setTargetAtTime(6 + chaosFactor * 4, now, 0.3);
waveShaperNode.curve = makeDistortionCurve(40 + chaosFactor * 40);
delayNode.delayTime.setTargetAtTime(0.5 + chaosFactor * 0.25, now, 0.3);
feedbackGain.gain.setTargetAtTime(0.5 + chaosFactor * 0.25, now, 0.3);
convolverNode.buffer = createImpulseResponse(3, 1);
// Activate auxiliary sources
activateAuxiliarySources(1 + Math.floor(chaosFactor * 2), chaosFactor);
lfos.push(createLFO(gainNode.gain, 0.3, 0.45, 0.75, 'sine'));
lfos.push(createLFO(filterNode.frequency, 0.2, 800, 2200, 'triangle'));
break;
case 5:
// Stage 5: Agony, horror, disorientation
gainNode.gain.setTargetAtTime(0.5 - chaosFactor * 0.15, now, 0.2);
filterNode.frequency.setTargetAtTime(900 - chaosFactor * 300, now, 0.2);
filterNode.Q.setTargetAtTime(8 + chaosFactor * 2, now, 0.2);
waveShaperNode.curve = makeDistortionCurve(80 + chaosFactor * 70);
delayNode.delayTime.setTargetAtTime(0.55 + chaosFactor * 0.35, now, 0.2);
feedbackGain.gain.setTargetAtTime(0.65, now, 0.2);
convolverNode.buffer = createImpulseResponse(3.5, 0.8);
// More auxiliary sources
activateAuxiliarySources(2 + Math.floor(chaosFactor), chaosFactor);
// Increase noise
noiseGain.gain.setTargetAtTime(0.04 + chaosFactor * 0.08, now, 0.5);
lfos.push(createLFO(gainNode.gain, 0.5, 0.3, 0.55, 'sawtooth'));
lfos.push(createLFO(filterNode.frequency, 0.3, 400, 1100, 'square'));
break;
case 6:
// Stage 6: Dissolution
gainNode.gain.setTargetAtTime(0.3 - chaosFactor * 0.15, now, 1);
filterNode.frequency.setTargetAtTime(100, now, 2);
filterNode.Q.setTargetAtTime(10, now, 2);
waveShaperNode.curve = makeDistortionCurve(150);
masterGain.gain.setTargetAtTime(0.6 - chaosFactor * 0.2, now, 2);
// Drone
droneGain.gain.setTargetAtTime(0.12 + chaosFactor * 0.08, now, 3);
droneOscillator.frequency.setTargetAtTime(55 * (1 - chaosFactor * 0.15), now, 1);
// Brown noise floor
noiseGain.gain.setTargetAtTime(0.12, now, 2);
// Extremely slow playback
if (sourceNode && sourceNode.playbackRate) {
sourceNode.playbackRate.setTargetAtTime(0.08 + Math.random() * 0.08, now, 2);
}
// Ghost notes
clearInterval(ghostNotesInterval);
ghostNotesInterval = setInterval(() => {
if (Math.random() < 0.08) {
triggerGhostNote(chaosFactor);
}
}, 2500);
// Reset auxiliary
deactivateAuxiliarySources();
// Schedule final fade
scheduleStage6End();
break;
}
}
function activateAuxiliarySources(count, chaos) {
for (let i = 0; i < count && i < auxiliaryPool.length; i++) {
if (!auxiliaryPool[i].inUse && audioBuffer && audioContext) {
const auxSource = audioContext.createBufferSource();
auxSource.buffer = audioBuffer;
const startOffset = Math.random() * audioBuffer.duration;
auxSource.start(0, startOffset);
const pitchMod = 0.5 + Math.random() * 0.8;
auxSource.playbackRate.value = pitchMod;
const auxGain = audioContext.createGain();
auxGain.gain.value = 0.25 + Math.random() * 0.2;
const auxFilter = audioContext.createBiquadFilter();
auxFilter.type = 'lowpass';
auxFilter.frequency.value = 1500 + Math.random() * 2500;
auxFilter.Q.value = 2 + Math.random() * 3;
auxSource.connect(auxGain);
aux