Claude-ActiveANC / index.html
Reality123b's picture
Update index.html
93e3a28 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Software ANC & Focus Music</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
min-height: 100vh;
background: linear-gradient(135deg, #1e293b 0%, #7e22ce 50%, #1e293b 100%);
padding: 20px;
}
.container {
max-width: 600px;
margin: 0 auto;
}
.header {
text-align: center;
margin-bottom: 30px;
color: white;
}
.header h1 {
font-size: 32px;
margin-bottom: 8px;
}
.header p {
color: #e9d5ff;
font-size: 14px;
}
.card {
background: rgba(255, 255, 255, 0.1);
backdrop-filter: blur(10px);
border-radius: 16px;
padding: 24px;
margin-bottom: 20px;
border: 1px solid rgba(255, 255, 255, 0.2);
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 12px;
}
.card-title {
font-size: 24px;
color: white;
font-weight: 600;
}
.btn {
padding: 12px 24px;
border-radius: 12px;
border: none;
font-weight: 600;
font-size: 14px;
cursor: pointer;
transition: all 0.3s;
}
.btn-primary {
background: #a855f7;
color: white;
}
.btn-primary:hover {
background: #9333ea;
}
.btn-danger {
background: #ef4444;
color: white;
}
.btn-danger:hover {
background: #dc2626;
}
.slider-group {
margin-bottom: 20px;
}
.slider-label {
display: block;
color: #e9d5ff;
font-size: 14px;
font-weight: 500;
margin-bottom: 8px;
}
.slider {
width: 100%;
height: 8px;
border-radius: 4px;
background: rgba(216, 180, 254, 0.3);
outline: none;
-webkit-appearance: none;
appearance: none;
}
.slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: #a855f7;
cursor: pointer;
}
.slider::-moz-range-thumb {
width: 20px;
height: 20px;
border-radius: 50%;
background: #a855f7;
cursor: pointer;
border: none;
}
.info-text {
color: #e9d5ff;
font-size: 13px;
line-height: 1.5;
margin-top: 12px;
}
.error {
background: rgba(239, 68, 68, 0.2);
border: 1px solid #ef4444;
color: #fecaca;
padding: 16px;
border-radius: 12px;
margin-bottom: 20px;
font-size: 14px;
}
.success {
background: rgba(34, 197, 94, 0.2);
border: 1px solid #22c55e;
color: #bbf7d0;
padding: 16px;
border-radius: 12px;
margin-bottom: 20px;
font-size: 14px;
}
.button-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
margin-bottom: 20px;
}
.type-btn {
padding: 12px;
border-radius: 8px;
border: none;
font-weight: 500;
cursor: pointer;
transition: all 0.3s;
background: rgba(255, 255, 255, 0.05);
color: #e9d5ff;
text-transform: capitalize;
}
.type-btn:hover {
background: rgba(255, 255, 255, 0.1);
}
.type-btn.active {
background: #a855f7;
color: white;
}
.tip {
text-align: center;
color: #d8b4fe;
font-size: 13px;
margin-top: 20px;
}
.status {
display: inline-block;
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
margin-left: 8px;
}
.status-active {
background: #22c55e;
color: white;
}
.status-inactive {
background: rgba(255, 255, 255, 0.1);
color: #e9d5ff;
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🎧 Software ANC</h1>
<p>Active Noise Cancellation & Focus Music</p>
</div>
<div id="message" style="display: none;"></div>
<div class="card">
<div class="card-header">
<div>
<span class="card-title">🔊 Noise Cancellation</span>
<span id="ancStatus" class="status status-inactive">OFF</span>
</div>
<button id="ancBtn" class="btn btn-primary">Start ANC</button>
</div>
<div class="slider-group">
<label class="slider-label">ANC Strength: <span id="ancValue">70</span>%</label>
<input type="range" id="ancSlider" class="slider" min="0" max="100" value="70">
</div>
<p class="info-text">
⚠️ Software ANC has limitations due to processing delay. Works best combined with focus music to mask background noise.
</p>
</div>
<div class="card">
<div class="card-header">
<div>
<span class="card-title">🎵 Focus Music</span>
<span id="musicStatus" class="status status-inactive">OFF</span>
</div>
<button id="musicBtn" class="btn btn-primary">Play</button>
</div>
<div class="slider-group">
<label class="slider-label">Sound Type</label>
<div class="button-grid">
<button class="type-btn active" data-type="brown">Brown Noise</button>
<button class="type-btn" data-type="white">White Noise</button>
<button class="type-btn" data-type="pink">Pink Noise</button>
<button class="type-btn" data-type="sine">Sine Wave</button>
</div>
</div>
<div class="slider-group">
<label class="slider-label">Volume: <span id="volumeValue">50</span>%</label>
<input type="range" id="volumeSlider" class="slider" min="0" max="100" value="50">
</div>
</div>
<div class="tip">
💡 Use headphones for best results. Focus music works better than ANC for masking constant sounds like fans.
</div>
</div>
<script>
let audioContext = null;
let micStream = null;
let sourceNode = null;
let scriptNode = null;
let ancGainNode = null;
let musicSource = null;
let musicGainNode = null;
let ancActive = false;
let musicActive = false;
let musicType = 'brown';
const ancBtn = document.getElementById('ancBtn');
const musicBtn = document.getElementById('musicBtn');
const ancSlider = document.getElementById('ancSlider');
const volumeSlider = document.getElementById('volumeSlider');
const ancValue = document.getElementById('ancValue');
const volumeValue = document.getElementById('volumeValue');
const messageDiv = document.getElementById('message');
const ancStatus = document.getElementById('ancStatus');
const musicStatus = document.getElementById('musicStatus');
const typeButtons = document.querySelectorAll('.type-btn');
function showMessage(message, isError = false) {
messageDiv.textContent = message;
messageDiv.className = isError ? 'error' : 'success';
messageDiv.style.display = 'block';
setTimeout(() => {
messageDiv.style.display = 'none';
}, 5000);
}
function initAudioContext() {
if (!audioContext || audioContext.state === 'closed') {
audioContext = new (window.AudioContext || window.webkitAudioContext)();
}
// Resume if suspended (important for mobile)
if (audioContext.state === 'suspended') {
audioContext.resume();
}
return audioContext;
}
async function startANC() {
try {
const ctx = initAudioContext();
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: false,
noiseSuppression: false,
autoGainControl: false
}
});
micStream = stream;
sourceNode = ctx.createMediaStreamSource(stream);
scriptNode = ctx.createScriptProcessor(4096, 1, 1);
ancGainNode = ctx.createGain();
ancGainNode.gain.value = ancSlider.value / 100;
scriptNode.onaudioprocess = (e) => {
const input = e.inputBuffer.getChannelData(0);
const output = e.outputBuffer.getChannelData(0);
const strength = ancSlider.value / 100;
for (let i = 0; i < input.length; i++) {
output[i] = -input[i] * strength;
}
};
sourceNode.connect(scriptNode);
scriptNode.connect(ancGainNode);
ancGainNode.connect(ctx.destination);
ancActive = true;
ancBtn.textContent = 'Stop ANC';
ancBtn.className = 'btn btn-danger';
ancStatus.textContent = 'ON';
ancStatus.className = 'status status-active';
showMessage('ANC Started - Microphone active');
} catch (err) {
showMessage('Microphone access denied. Go to browser Settings → Site Settings → Microphone and allow access.', true);
console.error(err);
}
}
function stopANC() {
if (scriptNode) {
scriptNode.disconnect();
scriptNode = null;
}
if (sourceNode) {
sourceNode.disconnect();
sourceNode = null;
}
if (ancGainNode) {
ancGainNode.disconnect();
ancGainNode = null;
}
if (micStream) {
micStream.getTracks().forEach(track => track.stop());
micStream = null;
}
ancActive = false;
ancBtn.textContent = 'Start ANC';
ancBtn.className = 'btn btn-primary';
ancStatus.textContent = 'OFF';
ancStatus.className = 'status status-inactive';
}
function generateNoiseBuffer(ctx, type) {
const bufferSize = ctx.sampleRate * 2; // 2 seconds
const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
const data = buffer.getChannelData(0);
if (type === 'white') {
for (let i = 0; i < bufferSize; i++) {
data[i] = Math.random() * 2 - 1;
}
} else if (type === 'brown') {
let lastOut = 0;
for (let i = 0; i < bufferSize; i++) {
const white = Math.random() * 2 - 1;
data[i] = (lastOut + (0.02 * white)) / 1.02;
lastOut = data[i];
data[i] *= 3.5;
}
} else if (type === 'pink') {
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;
data[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362;
data[i] *= 0.11;
b6 = white * 0.115926;
}
}
return buffer;
}
function startMusic() {
try {
const ctx = initAudioContext();
const vol = volumeSlider.value / 100;
musicGainNode = ctx.createGain();
musicGainNode.gain.value = vol;
if (musicType === 'brown' || musicType === 'white' || musicType === 'pink') {
const buffer = generateNoiseBuffer(ctx, musicType);
musicSource = ctx.createBufferSource();
musicSource.buffer = buffer;
musicSource.loop = true;
musicSource.connect(musicGainNode);
} else if (musicType === 'sine') {
musicSource = ctx.createOscillator();
musicSource.type = 'sine';
musicSource.frequency.value = 220;
musicSource.connect(musicGainNode);
}
musicGainNode.connect(ctx.destination);
musicSource.start(0);
musicActive = true;
musicBtn.textContent = 'Stop';
musicBtn.className = 'btn btn-danger';
musicStatus.textContent = 'ON';
musicStatus.className = 'status status-active';
showMessage('Focus music playing');
} catch (err) {
showMessage('Failed to start music. Try tapping the screen first to enable audio.', true);
console.error(err);
}
}
function stopMusic() {
if (musicSource) {
try {
musicSource.stop();
} catch (e) {
// Already stopped
}
musicSource.disconnect();
musicSource = null;
}
if (musicGainNode) {
musicGainNode.disconnect();
musicGainNode = null;
}
musicActive = false;
musicBtn.textContent = 'Play';
musicBtn.className = 'btn btn-primary';
musicStatus.textContent = 'OFF';
musicStatus.className = 'status status-inactive';
}
ancBtn.addEventListener('click', () => {
if (ancActive) {
stopANC();
} else {
startANC();
}
});
musicBtn.addEventListener('click', () => {
if (musicActive) {
stopMusic();
} else {
startMusic();
}
});
ancSlider.addEventListener('input', (e) => {
ancValue.textContent = e.target.value;
if (ancGainNode) {
ancGainNode.gain.value = e.target.value / 100;
}
});
volumeSlider.addEventListener('input', (e) => {
volumeValue.textContent = e.target.value;
if (musicGainNode) {
musicGainNode.gain.value = e.target.value / 100;
}
});
typeButtons.forEach(btn => {
btn.addEventListener('click', () => {
typeButtons.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
musicType = btn.dataset.type;
if (musicActive) {
stopMusic();
setTimeout(() => startMusic(), 100);
}
});
});
// Enable audio on first user interaction (required for mobile)
document.body.addEventListener('click', () => {
if (audioContext && audioContext.state === 'suspended') {
audioContext.resume();
}
}, { once: true });
</script>
</body>
</html>