Reality123b commited on
Commit
93e3a28
·
verified ·
1 Parent(s): 0608523

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +177 -123
index.html CHANGED
@@ -149,6 +149,16 @@
149
  font-size: 14px;
150
  }
151
 
 
 
 
 
 
 
 
 
 
 
152
  .button-grid {
153
  display: grid;
154
  grid-template-columns: repeat(2, 1fr);
@@ -183,6 +193,25 @@
183
  font-size: 13px;
184
  margin-top: 20px;
185
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
186
  </style>
187
  </head>
188
  <body>
@@ -192,11 +221,14 @@
192
  <p>Active Noise Cancellation & Focus Music</p>
193
  </div>
194
 
195
- <div id="error" class="error" style="display: none;"></div>
196
 
197
  <div class="card">
198
  <div class="card-header">
199
- <div class="card-title">🔊 Noise Cancellation</div>
 
 
 
200
  <button id="ancBtn" class="btn btn-primary">Start ANC</button>
201
  </div>
202
 
@@ -206,13 +238,16 @@
206
  </div>
207
 
208
  <p class="info-text">
209
- Uses phase inversion to cancel ambient noise picked up by your microphone.
210
  </p>
211
  </div>
212
 
213
  <div class="card">
214
  <div class="card-header">
215
- <div class="card-title">🎵 Focus Music</div>
 
 
 
216
  <button id="musicBtn" class="btn btn-primary">Play</button>
217
  </div>
218
 
@@ -222,7 +257,7 @@
222
  <button class="type-btn active" data-type="brown">Brown Noise</button>
223
  <button class="type-btn" data-type="white">White Noise</button>
224
  <button class="type-btn" data-type="pink">Pink Noise</button>
225
- <button class="type-btn" data-type="binaural">Binaural Beats</button>
226
  </div>
227
  </div>
228
 
@@ -233,17 +268,19 @@
233
  </div>
234
 
235
  <div class="tip">
236
- 💡 Use headphones for best results. Combine ANC with focus music for maximum concentration.
237
  </div>
238
  </div>
239
 
240
  <script>
241
  let audioContext = null;
242
  let micStream = null;
243
- let noiseProcessor = null;
244
- let gainNode = null;
245
- let oscillator = null;
246
- let musicGain = null;
 
 
247
 
248
  let ancActive = false;
249
  let musicActive = false;
@@ -255,19 +292,35 @@
255
  const volumeSlider = document.getElementById('volumeSlider');
256
  const ancValue = document.getElementById('ancValue');
257
  const volumeValue = document.getElementById('volumeValue');
258
- const errorDiv = document.getElementById('error');
 
 
259
  const typeButtons = document.querySelectorAll('.type-btn');
260
 
261
- function showError(message) {
262
- errorDiv.textContent = message;
263
- errorDiv.style.display = 'block';
 
264
  setTimeout(() => {
265
- errorDiv.style.display = 'none';
266
  }, 5000);
267
  }
268
 
 
 
 
 
 
 
 
 
 
 
 
269
  async function startANC() {
270
  try {
 
 
271
  const stream = await navigator.mediaDevices.getUserMedia({
272
  audio: {
273
  echoCancellation: false,
@@ -276,162 +329,156 @@
276
  }
277
  });
278
 
279
- audioContext = new (window.AudioContext || window.webkitAudioContext)();
280
  micStream = stream;
281
-
282
- const source = audioContext.createMediaStreamSource(stream);
283
- noiseProcessor = audioContext.createScriptProcessor(4096, 1, 1);
284
- const output = audioContext.createGain();
285
 
286
- gainNode = output;
287
- output.gain.value = ancSlider.value / 100;
288
 
289
- noiseProcessor.onaudioprocess = (e) => {
290
  const input = e.inputBuffer.getChannelData(0);
291
- const outputData = e.outputBuffer.getChannelData(0);
292
  const strength = ancSlider.value / 100;
293
 
294
  for (let i = 0; i < input.length; i++) {
295
- outputData[i] = -input[i] * strength;
296
  }
297
  };
298
 
299
- source.connect(noiseProcessor);
300
- noiseProcessor.connect(output);
301
- output.connect(audioContext.destination);
302
 
303
  ancActive = true;
304
  ancBtn.textContent = 'Stop ANC';
305
  ancBtn.className = 'btn btn-danger';
 
 
 
306
  } catch (err) {
307
- showError('Microphone access denied. Please allow microphone permissions in your browser settings.');
308
  console.error(err);
309
  }
310
  }
311
 
312
  function stopANC() {
313
- if (noiseProcessor) {
314
- noiseProcessor.disconnect();
315
- noiseProcessor = null;
 
 
 
 
 
 
 
 
316
  }
317
  if (micStream) {
318
  micStream.getTracks().forEach(track => track.stop());
319
  micStream = null;
320
  }
321
- if (audioContext && !musicActive) {
322
- audioContext.close();
323
- audioContext = null;
324
- }
325
- gainNode = null;
326
  ancActive = false;
327
  ancBtn.textContent = 'Start ANC';
328
  ancBtn.className = 'btn btn-primary';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
329
  }
330
 
331
  function startMusic() {
332
  try {
333
- if (!audioContext) {
334
- audioContext = new (window.AudioContext || window.webkitAudioContext)();
335
- }
336
-
337
  const vol = volumeSlider.value / 100;
338
 
 
 
 
339
  if (musicType === 'brown' || musicType === 'white' || musicType === 'pink') {
340
- const bufferSize = 4096;
341
- const noiseBuffer = audioContext.createBuffer(1, bufferSize, audioContext.sampleRate);
342
- const output = noiseBuffer.getChannelData(0);
343
-
344
- if (musicType === 'white') {
345
- for (let i = 0; i < bufferSize; i++) {
346
- output[i] = Math.random() * 2 - 1;
347
- }
348
- } else if (musicType === 'brown') {
349
- let lastOut = 0;
350
- for (let i = 0; i < bufferSize; i++) {
351
- const white = Math.random() * 2 - 1;
352
- output[i] = (lastOut + (0.02 * white)) / 1.02;
353
- lastOut = output[i];
354
- output[i] *= 3.5;
355
- }
356
- } else if (musicType === 'pink') {
357
- let b0 = 0, b1 = 0, b2 = 0, b3 = 0, b4 = 0, b5 = 0, b6 = 0;
358
- for (let i = 0; i < bufferSize; i++) {
359
- const white = Math.random() * 2 - 1;
360
- b0 = 0.99886 * b0 + white * 0.0555179;
361
- b1 = 0.99332 * b1 + white * 0.0750759;
362
- b2 = 0.96900 * b2 + white * 0.1538520;
363
- b3 = 0.86650 * b3 + white * 0.3104856;
364
- b4 = 0.55000 * b4 + white * 0.5329522;
365
- b5 = -0.7616 * b5 - white * 0.0168980;
366
- output[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362;
367
- output[i] *= 0.11;
368
- b6 = white * 0.115926;
369
- }
370
- }
371
-
372
- const noiseSource = audioContext.createBufferSource();
373
- noiseSource.buffer = noiseBuffer;
374
- noiseSource.loop = true;
375
-
376
- const gain = audioContext.createGain();
377
- gain.gain.value = vol;
378
- musicGain = gain;
379
-
380
- noiseSource.connect(gain);
381
- gain.connect(audioContext.destination);
382
- noiseSource.start(0);
383
-
384
- oscillator = noiseSource;
385
- } else if (musicType === 'binaural') {
386
- const osc1 = audioContext.createOscillator();
387
- const osc2 = audioContext.createOscillator();
388
- osc1.frequency.value = 200;
389
- osc2.frequency.value = 210;
390
-
391
- const merger = audioContext.createChannelMerger(2);
392
- const leftGain = audioContext.createGain();
393
- const rightGain = audioContext.createGain();
394
- leftGain.gain.value = vol;
395
- rightGain.gain.value = vol;
396
- musicGain = leftGain;
397
-
398
- osc1.connect(leftGain);
399
- osc2.connect(rightGain);
400
- leftGain.connect(merger, 0, 0);
401
- rightGain.connect(merger, 0, 1);
402
- merger.connect(audioContext.destination);
403
-
404
- osc1.start();
405
- osc2.start();
406
- oscillator = [osc1, osc2];
407
  }
408
 
 
 
 
409
  musicActive = true;
410
  musicBtn.textContent = 'Stop';
411
  musicBtn.className = 'btn btn-danger';
 
 
 
412
  } catch (err) {
413
- showError('Failed to start focus music.');
414
  console.error(err);
415
  }
416
  }
417
 
418
  function stopMusic() {
419
- if (oscillator) {
420
- if (Array.isArray(oscillator)) {
421
- oscillator.forEach(osc => osc.stop());
422
- } else {
423
- oscillator.stop();
424
  }
425
- oscillator = null;
 
426
  }
427
- musicGain = null;
428
- if (audioContext && !ancActive) {
429
- audioContext.close();
430
- audioContext = null;
431
  }
 
432
  musicActive = false;
433
  musicBtn.textContent = 'Play';
434
  musicBtn.className = 'btn btn-primary';
 
 
435
  }
436
 
437
  ancBtn.addEventListener('click', () => {
@@ -452,15 +499,15 @@
452
 
453
  ancSlider.addEventListener('input', (e) => {
454
  ancValue.textContent = e.target.value;
455
- if (gainNode) {
456
- gainNode.gain.value = e.target.value / 100;
457
  }
458
  });
459
 
460
  volumeSlider.addEventListener('input', (e) => {
461
  volumeValue.textContent = e.target.value;
462
- if (musicGain) {
463
- musicGain.gain.value = e.target.value / 100;
464
  }
465
  });
466
 
@@ -476,6 +523,13 @@
476
  }
477
  });
478
  });
 
 
 
 
 
 
 
479
  </script>
480
  </body>
481
  </html>
 
149
  font-size: 14px;
150
  }
151
 
152
+ .success {
153
+ background: rgba(34, 197, 94, 0.2);
154
+ border: 1px solid #22c55e;
155
+ color: #bbf7d0;
156
+ padding: 16px;
157
+ border-radius: 12px;
158
+ margin-bottom: 20px;
159
+ font-size: 14px;
160
+ }
161
+
162
  .button-grid {
163
  display: grid;
164
  grid-template-columns: repeat(2, 1fr);
 
193
  font-size: 13px;
194
  margin-top: 20px;
195
  }
196
+
197
+ .status {
198
+ display: inline-block;
199
+ padding: 4px 12px;
200
+ border-radius: 12px;
201
+ font-size: 12px;
202
+ font-weight: 600;
203
+ margin-left: 8px;
204
+ }
205
+
206
+ .status-active {
207
+ background: #22c55e;
208
+ color: white;
209
+ }
210
+
211
+ .status-inactive {
212
+ background: rgba(255, 255, 255, 0.1);
213
+ color: #e9d5ff;
214
+ }
215
  </style>
216
  </head>
217
  <body>
 
221
  <p>Active Noise Cancellation & Focus Music</p>
222
  </div>
223
 
224
+ <div id="message" style="display: none;"></div>
225
 
226
  <div class="card">
227
  <div class="card-header">
228
+ <div>
229
+ <span class="card-title">🔊 Noise Cancellation</span>
230
+ <span id="ancStatus" class="status status-inactive">OFF</span>
231
+ </div>
232
  <button id="ancBtn" class="btn btn-primary">Start ANC</button>
233
  </div>
234
 
 
238
  </div>
239
 
240
  <p class="info-text">
241
+ ⚠️ Software ANC has limitations due to processing delay. Works best combined with focus music to mask background noise.
242
  </p>
243
  </div>
244
 
245
  <div class="card">
246
  <div class="card-header">
247
+ <div>
248
+ <span class="card-title">🎵 Focus Music</span>
249
+ <span id="musicStatus" class="status status-inactive">OFF</span>
250
+ </div>
251
  <button id="musicBtn" class="btn btn-primary">Play</button>
252
  </div>
253
 
 
257
  <button class="type-btn active" data-type="brown">Brown Noise</button>
258
  <button class="type-btn" data-type="white">White Noise</button>
259
  <button class="type-btn" data-type="pink">Pink Noise</button>
260
+ <button class="type-btn" data-type="sine">Sine Wave</button>
261
  </div>
262
  </div>
263
 
 
268
  </div>
269
 
270
  <div class="tip">
271
+ 💡 Use headphones for best results. Focus music works better than ANC for masking constant sounds like fans.
272
  </div>
273
  </div>
274
 
275
  <script>
276
  let audioContext = null;
277
  let micStream = null;
278
+ let sourceNode = null;
279
+ let scriptNode = null;
280
+ let ancGainNode = null;
281
+
282
+ let musicSource = null;
283
+ let musicGainNode = null;
284
 
285
  let ancActive = false;
286
  let musicActive = false;
 
292
  const volumeSlider = document.getElementById('volumeSlider');
293
  const ancValue = document.getElementById('ancValue');
294
  const volumeValue = document.getElementById('volumeValue');
295
+ const messageDiv = document.getElementById('message');
296
+ const ancStatus = document.getElementById('ancStatus');
297
+ const musicStatus = document.getElementById('musicStatus');
298
  const typeButtons = document.querySelectorAll('.type-btn');
299
 
300
+ function showMessage(message, isError = false) {
301
+ messageDiv.textContent = message;
302
+ messageDiv.className = isError ? 'error' : 'success';
303
+ messageDiv.style.display = 'block';
304
  setTimeout(() => {
305
+ messageDiv.style.display = 'none';
306
  }, 5000);
307
  }
308
 
309
+ function initAudioContext() {
310
+ if (!audioContext || audioContext.state === 'closed') {
311
+ audioContext = new (window.AudioContext || window.webkitAudioContext)();
312
+ }
313
+ // Resume if suspended (important for mobile)
314
+ if (audioContext.state === 'suspended') {
315
+ audioContext.resume();
316
+ }
317
+ return audioContext;
318
+ }
319
+
320
  async function startANC() {
321
  try {
322
+ const ctx = initAudioContext();
323
+
324
  const stream = await navigator.mediaDevices.getUserMedia({
325
  audio: {
326
  echoCancellation: false,
 
329
  }
330
  });
331
 
 
332
  micStream = stream;
333
+ sourceNode = ctx.createMediaStreamSource(stream);
334
+ scriptNode = ctx.createScriptProcessor(4096, 1, 1);
335
+ ancGainNode = ctx.createGain();
 
336
 
337
+ ancGainNode.gain.value = ancSlider.value / 100;
 
338
 
339
+ scriptNode.onaudioprocess = (e) => {
340
  const input = e.inputBuffer.getChannelData(0);
341
+ const output = e.outputBuffer.getChannelData(0);
342
  const strength = ancSlider.value / 100;
343
 
344
  for (let i = 0; i < input.length; i++) {
345
+ output[i] = -input[i] * strength;
346
  }
347
  };
348
 
349
+ sourceNode.connect(scriptNode);
350
+ scriptNode.connect(ancGainNode);
351
+ ancGainNode.connect(ctx.destination);
352
 
353
  ancActive = true;
354
  ancBtn.textContent = 'Stop ANC';
355
  ancBtn.className = 'btn btn-danger';
356
+ ancStatus.textContent = 'ON';
357
+ ancStatus.className = 'status status-active';
358
+ showMessage('ANC Started - Microphone active');
359
  } catch (err) {
360
+ showMessage('Microphone access denied. Go to browser Settings Site Settings → Microphone and allow access.', true);
361
  console.error(err);
362
  }
363
  }
364
 
365
  function stopANC() {
366
+ if (scriptNode) {
367
+ scriptNode.disconnect();
368
+ scriptNode = null;
369
+ }
370
+ if (sourceNode) {
371
+ sourceNode.disconnect();
372
+ sourceNode = null;
373
+ }
374
+ if (ancGainNode) {
375
+ ancGainNode.disconnect();
376
+ ancGainNode = null;
377
  }
378
  if (micStream) {
379
  micStream.getTracks().forEach(track => track.stop());
380
  micStream = null;
381
  }
382
+
 
 
 
 
383
  ancActive = false;
384
  ancBtn.textContent = 'Start ANC';
385
  ancBtn.className = 'btn btn-primary';
386
+ ancStatus.textContent = 'OFF';
387
+ ancStatus.className = 'status status-inactive';
388
+ }
389
+
390
+ function generateNoiseBuffer(ctx, type) {
391
+ const bufferSize = ctx.sampleRate * 2; // 2 seconds
392
+ const buffer = ctx.createBuffer(1, bufferSize, ctx.sampleRate);
393
+ const data = buffer.getChannelData(0);
394
+
395
+ if (type === 'white') {
396
+ for (let i = 0; i < bufferSize; i++) {
397
+ data[i] = Math.random() * 2 - 1;
398
+ }
399
+ } else if (type === 'brown') {
400
+ let lastOut = 0;
401
+ for (let i = 0; i < bufferSize; i++) {
402
+ const white = Math.random() * 2 - 1;
403
+ data[i] = (lastOut + (0.02 * white)) / 1.02;
404
+ lastOut = data[i];
405
+ data[i] *= 3.5;
406
+ }
407
+ } else if (type === 'pink') {
408
+ let b0 = 0, b1 = 0, b2 = 0, b3 = 0, b4 = 0, b5 = 0, b6 = 0;
409
+ for (let i = 0; i < bufferSize; i++) {
410
+ const white = Math.random() * 2 - 1;
411
+ b0 = 0.99886 * b0 + white * 0.0555179;
412
+ b1 = 0.99332 * b1 + white * 0.0750759;
413
+ b2 = 0.96900 * b2 + white * 0.1538520;
414
+ b3 = 0.86650 * b3 + white * 0.3104856;
415
+ b4 = 0.55000 * b4 + white * 0.5329522;
416
+ b5 = -0.7616 * b5 - white * 0.0168980;
417
+ data[i] = b0 + b1 + b2 + b3 + b4 + b5 + b6 + white * 0.5362;
418
+ data[i] *= 0.11;
419
+ b6 = white * 0.115926;
420
+ }
421
+ }
422
+
423
+ return buffer;
424
  }
425
 
426
  function startMusic() {
427
  try {
428
+ const ctx = initAudioContext();
 
 
 
429
  const vol = volumeSlider.value / 100;
430
 
431
+ musicGainNode = ctx.createGain();
432
+ musicGainNode.gain.value = vol;
433
+
434
  if (musicType === 'brown' || musicType === 'white' || musicType === 'pink') {
435
+ const buffer = generateNoiseBuffer(ctx, musicType);
436
+ musicSource = ctx.createBufferSource();
437
+ musicSource.buffer = buffer;
438
+ musicSource.loop = true;
439
+ musicSource.connect(musicGainNode);
440
+ } else if (musicType === 'sine') {
441
+ musicSource = ctx.createOscillator();
442
+ musicSource.type = 'sine';
443
+ musicSource.frequency.value = 220;
444
+ musicSource.connect(musicGainNode);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
445
  }
446
 
447
+ musicGainNode.connect(ctx.destination);
448
+ musicSource.start(0);
449
+
450
  musicActive = true;
451
  musicBtn.textContent = 'Stop';
452
  musicBtn.className = 'btn btn-danger';
453
+ musicStatus.textContent = 'ON';
454
+ musicStatus.className = 'status status-active';
455
+ showMessage('Focus music playing');
456
  } catch (err) {
457
+ showMessage('Failed to start music. Try tapping the screen first to enable audio.', true);
458
  console.error(err);
459
  }
460
  }
461
 
462
  function stopMusic() {
463
+ if (musicSource) {
464
+ try {
465
+ musicSource.stop();
466
+ } catch (e) {
467
+ // Already stopped
468
  }
469
+ musicSource.disconnect();
470
+ musicSource = null;
471
  }
472
+ if (musicGainNode) {
473
+ musicGainNode.disconnect();
474
+ musicGainNode = null;
 
475
  }
476
+
477
  musicActive = false;
478
  musicBtn.textContent = 'Play';
479
  musicBtn.className = 'btn btn-primary';
480
+ musicStatus.textContent = 'OFF';
481
+ musicStatus.className = 'status status-inactive';
482
  }
483
 
484
  ancBtn.addEventListener('click', () => {
 
499
 
500
  ancSlider.addEventListener('input', (e) => {
501
  ancValue.textContent = e.target.value;
502
+ if (ancGainNode) {
503
+ ancGainNode.gain.value = e.target.value / 100;
504
  }
505
  });
506
 
507
  volumeSlider.addEventListener('input', (e) => {
508
  volumeValue.textContent = e.target.value;
509
+ if (musicGainNode) {
510
+ musicGainNode.gain.value = e.target.value / 100;
511
  }
512
  });
513
 
 
523
  }
524
  });
525
  });
526
+
527
+ // Enable audio on first user interaction (required for mobile)
528
+ document.body.addEventListener('click', () => {
529
+ if (audioContext && audioContext.state === 'suspended') {
530
+ audioContext.resume();
531
+ }
532
+ }, { once: true });
533
  </script>
534
  </body>
535
  </html>