A viewer for AtmosphereConf 2026 talks with fixed routes you can link to
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add subtle white noise sound effect during TV static

Uses Web Audio API to generate looped white noise at low volume
during channel switches, with a quick fade-out to avoid clicks.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

+57
+57
public/index.html
··· 444 444 let staticCtx = staticCanvas.getContext('2d'); 445 445 let staticAnimFrame = null; 446 446 447 + // --- Static TV audio (white noise via Web Audio API) --- 448 + let staticAudioCtx = null; 449 + let staticNoiseNode = null; 450 + let staticGainNode = null; 451 + 452 + function startStaticAudio() { 453 + try { 454 + if (!staticAudioCtx) { 455 + staticAudioCtx = new (window.AudioContext || window.webkitAudioContext)(); 456 + } 457 + if (staticAudioCtx.state === 'suspended') staticAudioCtx.resume(); 458 + if (staticNoiseNode) return; // already playing 459 + 460 + // Create white noise buffer (1 second, looped) 461 + const bufferSize = staticAudioCtx.sampleRate; 462 + const noiseBuffer = staticAudioCtx.createBuffer(1, bufferSize, staticAudioCtx.sampleRate); 463 + const data = noiseBuffer.getChannelData(0); 464 + for (let i = 0; i < bufferSize; i++) { 465 + data[i] = Math.random() * 2 - 1; 466 + } 467 + 468 + staticNoiseNode = staticAudioCtx.createBufferSource(); 469 + staticNoiseNode.buffer = noiseBuffer; 470 + staticNoiseNode.loop = true; 471 + 472 + staticGainNode = staticAudioCtx.createGain(); 473 + staticGainNode.gain.value = 0.015; // barely there 474 + 475 + staticNoiseNode.connect(staticGainNode); 476 + staticGainNode.connect(staticAudioCtx.destination); 477 + staticNoiseNode.start(); 478 + } catch (e) { 479 + // Audio context not available — fine, just skip 480 + } 481 + } 482 + 483 + function stopStaticAudio() { 484 + if (staticNoiseNode) { 485 + try { 486 + // Quick fade out to avoid click 487 + if (staticGainNode) { 488 + staticGainNode.gain.setTargetAtTime(0, staticAudioCtx.currentTime, 0.02); 489 + } 490 + setTimeout(() => { 491 + try { staticNoiseNode.stop(); } catch (e) {} 492 + staticNoiseNode = null; 493 + staticGainNode = null; 494 + }, 80); 495 + } catch (e) { 496 + staticNoiseNode = null; 497 + staticGainNode = null; 498 + } 499 + } 500 + } 501 + 447 502 async function fetchVods() { 448 503 let all = []; 449 504 let cursor; ··· 548 603 function startStatic() { 549 604 staticOverlay.classList.add('active'); 550 605 if (!staticAnimFrame) drawStatic(); 606 + startStaticAudio(); 551 607 } 552 608 function stopStatic() { 553 609 staticOverlay.classList.remove('active'); 554 610 if (staticAnimFrame) { cancelAnimationFrame(staticAnimFrame); staticAnimFrame = null; } 611 + stopStaticAudio(); 555 612 } 556 613 557 614 function showChannelInfo(ch, title, uri) {